diff options
| author | Michael Biebl <biebl@debian.org> | 2024-05-05 00:07:30 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2024-05-05 00:07:30 +0200 |
| commit | 34bb501be08aa2b313d88e67d6e0a7e0a3f9cfa6 (patch) | |
| tree | 4e6220877828be4c6f261de09ec0cb2d80e32389 /src/c-stdaux | |
| parent | bba2e4b4de668db525cbfdfc35292e5a0b51671a (diff) | |
New upstream version 1.47.90 upstream/1.47.90
Diffstat (limited to 'src/c-stdaux')
| -rw-r--r-- | src/c-stdaux/.editorconfig | 11 | ||||
| -rw-r--r-- | src/c-stdaux/.github/workflows/ci.yml | 37 | ||||
| -rw-r--r-- | src/c-stdaux/.readthedocs.yaml | 20 | ||||
| -rw-r--r-- | src/c-stdaux/AUTHORS | 43 | ||||
| -rw-r--r-- | src/c-stdaux/NEWS.md | 106 | ||||
| -rw-r--r-- | src/c-stdaux/README.md | 54 | ||||
| -rw-r--r-- | src/c-stdaux/meson.build | 110 | ||||
| -rw-r--r-- | src/c-stdaux/meson_options.txt | 7 | ||||
| -rw-r--r-- | src/c-stdaux/src/docs/api.rst | 5 | ||||
| -rw-r--r-- | src/c-stdaux/src/docs/conf.py | 45 | ||||
| -rw-r--r-- | src/c-stdaux/src/docs/index.rst | 14 | ||||
| -rw-r--r-- | src/c-stdaux/src/docs/requirements.txt | 3 | ||||
| -rw-r--r-- | src/c-stdaux/src/libcstdaux.sym | 6 | ||||
| -rw-r--r-- | src/c-stdaux/src/meson.build | 43 | ||||
| -rw-r--r-- | src/c-stdaux/src/test-api.c | 322 | ||||
| -rw-r--r-- | src/c-stdaux/src/test-basic.c | 624 |
16 files changed, 1450 insertions, 0 deletions
diff --git a/src/c-stdaux/.editorconfig b/src/c-stdaux/.editorconfig new file mode 100644 index 00000000..b10bb4f3 --- /dev/null +++ b/src/c-stdaux/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.{c,h}] +indent_style = space +indent_size = 8 diff --git a/src/c-stdaux/.github/workflows/ci.yml b/src/c-stdaux/.github/workflows/ci.yml new file mode 100644 index 00000000..70a673f7 --- /dev/null +++ b/src/c-stdaux/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: Continuous Integration + +on: + push: + pull_request: + schedule: + - cron: '0 0 * * *' + +jobs: + ci-linux: + name: Linux CI + uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1 + with: + cabuild_ref: "v1" + linux: true + m32: true + matrixmode: true + valgrind: true + ci-macos: + name: MacOS CI + uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1 + with: + cabuild_ref: "v1" + linux: false + macos: true + ci-windows: + name: Windows CI + uses: bus1/cabuild/.github/workflows/ci-c-util.yml@v1 + with: + cabuild_ref: "v1" + linux: false + windows: true + ci-docs: + name: Documentation CI + uses: bus1/cabuild/.github/workflows/ci-sphinx.yml@v1 + with: + source: "./src/docs" diff --git a/src/c-stdaux/.readthedocs.yaml b/src/c-stdaux/.readthedocs.yaml new file mode 100644 index 00000000..5bbe6ac1 --- /dev/null +++ b/src/c-stdaux/.readthedocs.yaml @@ -0,0 +1,20 @@ +# Read the Docs configuration file + +version: 2 + +build: + apt_packages: + - "clang" + os: "ubuntu-22.04" + tools: + python: "3" + +formats: "all" + +python: + install: + - requirements: "src/docs/requirements.txt" + system_packages: true + +sphinx: + configuration: "src/docs/conf.py" diff --git a/src/c-stdaux/AUTHORS b/src/c-stdaux/AUTHORS new file mode 100644 index 00000000..0f2a73fd --- /dev/null +++ b/src/c-stdaux/AUTHORS @@ -0,0 +1,43 @@ +LICENSE: + This project is dual-licensed under both the Apache License, Version + 2.0, and the GNU Lesser General Public License, Version 2.1+. + +AUTHORS-ASL: + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +AUTHORS-LGPL: + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; If not, see <http://www.gnu.org/licenses/>. + +COPYRIGHT: (ordered alphabetically) + Copyright (C) 2018-2022 Red Hat, Inc. + +AUTHORS: (ordered alphabetically) + David Rheinsberg <david.rheinsberg@gmail.com> + Evgeny Vereshchagin <evvers@ya.ru> + Jan Engelhardt <jengelh@inai.de> + Lorenzo Arena <lorenzo.arena@powersoft.com> + Michele Dionisio <michele.dionisio@gmail.com> + Thomas Haller <thaller@redhat.com> + Tom Gundersen <teg@jklm.no> + Yuri Chornoivan <yurchor@ukr.net> diff --git a/src/c-stdaux/NEWS.md b/src/c-stdaux/NEWS.md new file mode 100644 index 00000000..8e7b3c71 --- /dev/null +++ b/src/c-stdaux/NEWS.md @@ -0,0 +1,106 @@ +# c-stdaux - Auxiliary macros and functions for the C standard library + +## CHANGES WITH 1.4.0: + + * New compiler-builtin c_assume_aligned() allows hinting alignment + to the compiler and thus improving code generation. For targets + without such builtins, the function will be a no-op. + + * A new set of memory-load operations is added: c_load_*() + This includes support for reading unaligned & aligned memory, + big-endian & little-endian data, and various standard sizes. + The helpers are basically a pointer cast to `uintX_t*` and a + dereference operation, but they guarantee that strict aliasing + rules, as well as alignment requirements are followed. + + Contributions from: David Rheinsberg, Jan Engelhardt, Tom Gundersen + + - Dußlingen, 2023-01-12 + +## CHANGES WITH 1.3.0: + + * Microsoft Windows is now supported as a target platform. + + * The `C_COMPILER_*` and `C_OS_*` pre-processor constants now + allow identifying the used compiler as well as the target OS. + + * The new `_c_always_inline_` annotation allows telling compilers + to inline a function unless technically not possible. + + * Split c-stdaux.h into modules and include them from the root + header for backwards compatibility. Inclusion of the new modules + is guarded by the `C_COMPILER_*` and `C_OS_*` macros to prevent + them from being used on unspported platforms. A direct include + of the respective modules allows overriding that behavior. + + The new modules provide the same functionality as before on the + previously supported linux platforms. With the support of other + platforms, individual modules might not be available, or generic + functions might provide a stub that provides the same runtime + behavior, but possibly with fewer diagnostics. + + * Rework `c_assert()` to avoid context-expressions and instead use + the ternary-operator to check for the assertion. + + * Improve `c_{un,}likely()` to support constant-folding as well as + -Wparantheses diagnostics if supported by the compiler. This adds + `_c_boolean_expr_()` as a helper to achieve this. + + Contributions from: David Rheinsberg, Thomas Haller + + - Dußlingen, 2022-12-15 + +## CHANGES WITH 1.2.0: + + * Add c_memcmp() as a safe wrapper around memcmp(3) that supports + empty arenas as NULL pointers. + + * Add an API documentation renderer based on the sphinx docutils + suite. The documentation is available on readthedocs.org. + + * Drop stdatomic.h from the public includes. This was not used by + any of the dependent projects, but breaks builds on older GCC + compilers. While this is technically an API break, no breakage + has been discovered in our tests, and thus we deemed it reasonable + to proceed without version bump. + + Contributions from: David Rheinsberg, Thomas Haller + + - Dußlingen, 2022-07-22 + +## CHANGES WITH 1.1.0: + + * Add c_memcpy() as a safe wrapper around memcpy(3) that supports + empty arenas as NULL pointers. + + * Support building on MacOS-X. + + * Rework the apidoc comments and properly document the entire API. + + * Export 'version-scripts' configuration variable alongside the + existing 'cflags' variable. It defines whether c-stdaux was built + with GNU-linker version-scripts, or not. Dependent projects can + use this to decide whether to use version-scripts or not. + Additionally, the new 'version-scripts' meson-option allows + specifying whether to use version-scripts, auto-detect whether to + enable it, or disable it. + + * Fix the export of `cflags` to also be exported in pkg-config, not + just meson subprojects. + + * Avoid NULL-pointers in compile-time macros. This silences possible + false-positives from code sanitizers that otherwise trip over the + NULL pointer dereferences. + + Contributions from: David Rheinsberg, Evgeny Vereshchagin + + - Brno, 2022-06-22 + +## CHANGES WITH 1.0.0: + + * Initial release of c-stdaux. + + Contributions from: David Rheinsberg, Lorenzo Arena, Michele Dionisio, + Yuri Chornoivan + + - Dußlingen, 2022-05-12 diff --git a/src/c-stdaux/README.md b/src/c-stdaux/README.md new file mode 100644 index 00000000..985193c5 --- /dev/null +++ b/src/c-stdaux/README.md @@ -0,0 +1,54 @@ +c-stdaux +======== + +Auxiliary macros and functions for the C standard library + +The c-stdaux project contains support macros and auxiliary functions around the +functionality of common C standard libraries. This includes helpers for the +ISO C Standard Library, but also other common specifications like POSIX or +common extended features of widespread compilers like gcc and clang. + +### Project + + * **Website**: <https://c-util.github.io/c-stdaux> + * **Documentation**: <https://c-stdaux.readthedocs.io> + * **Bug Tracker**: <https://github.com/c-util/c-stdaux/issues> + +### Requirements + +The requirements for this project are: + + * `libc` (e.g., `glibc >= 2.16`) + +At build time, the following software is required: + + * `meson >= 0.60` + * `pkg-config >= 0.29` + +### Build + +The meson build system is used for this project. Contact upstream +documentation for detailed help. In most situations, the following +commands are sufficient to build and install from source: + +```sh +mkdir build +cd build +meson setup .. +ninja +meson test +ninja install +``` + +No custom configuration options are available. + +### Repository: + + - **web**: <https://github.com/c-util/c-stdaux> + - **https**: `https://github.com/c-util/c-stdaux.git` + - **ssh**: `git@github.com:c-util/c-stdaux.git` + +### License: + + - **Apache-2.0** OR **LGPL-2.1-or-later** + - See AUTHORS file for details. diff --git a/src/c-stdaux/meson.build b/src/c-stdaux/meson.build new file mode 100644 index 00000000..6af822be --- /dev/null +++ b/src/c-stdaux/meson.build @@ -0,0 +1,110 @@ +# +# Global Project Setup +# + +project( + 'c-stdaux', + 'c', + default_options: [ + 'c_std=c11' + ], + license: 'Apache', + meson_version: '>=0.60.0', + version: '1.4.0', +) +major = meson.project_version().split('.')[0] +project_description = 'Auxiliary macros and functions for the C standard library' + +mod_pkgconfig = import('pkgconfig') + +# +# CFLAGS +# +# We have a set of compiler flags for GCC and CLANG which adjust their warnings +# and behavior to our coding-style. +# +# This variable is exported to dependent projects via meson for them to use as +# well. Since these exports are limited to strings, we need to be careful that +# the individual entries do not contain spaces (see the assertion below). +# + +cflags = meson.get_compiler('c').get_supported_arguments( + # Enable GNU features of our dependencies. See feature_test_macros(7). + '-D_GNU_SOURCE', + + # Prevent CLANG from complaining that alignof-expressions are GNU-only. + '-Wno-gnu-alignof-expression', + # Never complain about *MAYBE* uninit variables. This is very flaky and + # produces bogus results with LTO. + '-Wno-maybe-uninitialized', + # Do not complain about unknown GCC/CLANG warnings. + '-Wno-unknown-warning-option', + # There is no standardized way to mark unused arguments, so never + # complain about them. + '-Wno-unused-parameter', + + # Preprocessor evaluations often lead to warnings about comparisons + # that are always true/false. Make sure they do not break a build but + # keep them on for diagnostics. + '-Wno-error=type-limits', + # As we use designated field-initializers, this warning should never + # trigger, but still does on GCC in combination with some other + # preprocessor checks. Lets just make sure it does not break builds. + '-Wno-error=missing-field-initializers', + + # Warn if we ever use `__DATE__` and similar in our build. We want + # reproducible builds. + '-Wdate-time', + # We strictly follow decl-before-statements, so check it. + '-Wdeclaration-after-statement', + # More strict logical-op sanity checks. + '-Wlogical-op', + # Loudly complain about missing include-directories. + '-Wmissing-include-dirs', + # We want hints about noreturn functions, so warn about them. + '-Wmissing-noreturn', + # Warn if an extern-decl is inside a function. We want imports as + # global attributes, never as local ones. + '-Wnested-externs', + # Warn about redundant declarations. We want declarations in headers + # and want them to be unique. + '-Wredundant-decls', + # Warn about shadowed variables so we do not accidentally override + # variables of parent scopes and thus confuse macros. + '-Wshadow', + # Warn about aliasing violations. Level-3 produces the least false + # positives, but is the slowest. Force it to avoid breaking -Werror + # builds. + '-Wstrict-aliasing=3', + # Suggest 'noreturn' attributes. They are useful, we want them! + '-Wsuggest-attribute=noreturn', + # Warn about undefined identifiers in preprocessor conditionals. + '-Wundef', + # Make sure literal strings are considered 'const'. + '-Wwrite-strings', +) +assert(not ''.join(cflags).contains(' '), 'Malformed compiler flags.') +add_project_arguments(cflags, language: 'c') + +# +# Version Scripts +# + +use_version_scripts = get_option('version-scripts') +if use_version_scripts == 'auto' + use_version_scripts = meson.get_compiler('c').has_link_argument( + '-Wl,--version-script=' + (meson.current_source_dir() / 'src/libcstdaux.sym') + ) ? 'yes' : 'no' +endif + +# +# Subdir Delegation +# + +subdir('src') + +# +# Meson Subproject Configuration +# + +meson.override_dependency('libcstdaux-'+major, libcstdaux_dep, static: true) diff --git a/src/c-stdaux/meson_options.txt b/src/c-stdaux/meson_options.txt new file mode 100644 index 00000000..82fdaf78 --- /dev/null +++ b/src/c-stdaux/meson_options.txt @@ -0,0 +1,7 @@ +option( + 'version-scripts', + choices: ['yes', 'no', 'auto'], + description: 'Enable GNU-version-scripts for linking', + type: 'combo', + value: 'auto', +) diff --git a/src/c-stdaux/src/docs/api.rst b/src/c-stdaux/src/docs/api.rst new file mode 100644 index 00000000..a25e9fad --- /dev/null +++ b/src/c-stdaux/src/docs/api.rst @@ -0,0 +1,5 @@ +API +=== + +.. c:autodoc:: c-stdaux.h c-stdaux-generic.h c-stdaux-gnuc.h c-stdaux-unix.h + :transform: kerneldoc diff --git a/src/c-stdaux/src/docs/conf.py b/src/c-stdaux/src/docs/conf.py new file mode 100644 index 00000000..b036efa5 --- /dev/null +++ b/src/c-stdaux/src/docs/conf.py @@ -0,0 +1,45 @@ +# +# Sphinx Documentation Configuration +# + +import re +import os +import sys + +import capidocs.kerneldoc +import hawkmoth + +# Global Setup + +project = 'c-stdaux' + +author = 'C-Util Community' +copyright = '2022, C-Util Community' + +# Hawkmoth C-Audodoc Setup + +capidocs.kerneldoc.hawkmoth_conf() + +# Extensions + +exclude_patterns = [] + +extensions = [ + 'hawkmoth', +] + +# Hawkmoth Options + +cautodoc_clang = capidocs.kerneldoc.hawkmoth_include_args() +cautodoc_clang += ["-I" + os.path.abspath("..")] +cautodoc_clang += ["-DC_COMPILER_DOCS"] + +cautodoc_root = os.path.abspath('..') + +cautodoc_transformations = { + 'kerneldoc': capidocs.kerneldoc.hawkmoth_converter, +} + +# HTML Options + +html_theme = 'sphinx_rtd_theme' diff --git a/src/c-stdaux/src/docs/index.rst b/src/c-stdaux/src/docs/index.rst new file mode 100644 index 00000000..5ad99374 --- /dev/null +++ b/src/c-stdaux/src/docs/index.rst @@ -0,0 +1,14 @@ +Introduction +============ + +The **c-stdaux** project contains support-macros and auxiliary functions around +the functionality of common C standard libraries. This includes helpers for the +ISO-C Standard Library, but also other common specifications like POSIX or +common extended features of wide-spread compilers like gcc and clang. + +.. toctree:: + :caption: Library Documentation + :hidden: + + self + api diff --git a/src/c-stdaux/src/docs/requirements.txt b/src/c-stdaux/src/docs/requirements.txt new file mode 100644 index 00000000..97872db1 --- /dev/null +++ b/src/c-stdaux/src/docs/requirements.txt @@ -0,0 +1,3 @@ +c-apidocs>=0.0.2 +clang>=6 +hawkmoth>=0.7 diff --git a/src/c-stdaux/src/libcstdaux.sym b/src/c-stdaux/src/libcstdaux.sym new file mode 100644 index 00000000..2073665f --- /dev/null +++ b/src/c-stdaux/src/libcstdaux.sym @@ -0,0 +1,6 @@ +LIBCSTDAUX_1 { +global: + c_internal_dummy; +local: + *; +}; diff --git a/src/c-stdaux/src/meson.build b/src/c-stdaux/src/meson.build new file mode 100644 index 00000000..f0efa90b --- /dev/null +++ b/src/c-stdaux/src/meson.build @@ -0,0 +1,43 @@ +# +# target: libcstdaux.so +# (No .so is built so far, since we are header-only. This might change in the +# future, if we add more complex helpers.) +# + +libcstdaux_vars = { + 'cflags': ' '.join(cflags), + 'version-scripts': use_version_scripts, +} + +libcstdaux_dep = declare_dependency( + include_directories: include_directories('.'), + variables: libcstdaux_vars, + version: meson.project_version(), +) + +if not meson.is_subproject() + install_headers( + 'c-stdaux.h', + 'c-stdaux-generic.h', + 'c-stdaux-gnuc.h', + 'c-stdaux-unix.h', + ) + + mod_pkgconfig.generate( + description: project_description, + filebase: 'libcstdaux-'+major, + name: 'libcstdaux', + unescaped_variables: libcstdaux_vars, + version: meson.project_version(), + ) +endif + +# +# target: test-* +# + +test_api = executable('test-api', ['test-api.c'], dependencies: libcstdaux_dep) +test('API Symbol Visibility', test_api) + +test_basic = executable('test-basic', ['test-basic.c'], dependencies: libcstdaux_dep) +test('Basic API Behavior', test_basic) diff --git a/src/c-stdaux/src/test-api.c b/src/c-stdaux/src/test-api.c new file mode 100644 index 00000000..e52d42c2 --- /dev/null +++ b/src/c-stdaux/src/test-api.c @@ -0,0 +1,322 @@ +/* + * API Visibility Tests + * This verifies the visibility and availability of the exported API. + */ + +#undef NDEBUG +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "c-stdaux.h" + +#if defined(C_MODULE_GENERIC) + +static inline _c_always_inline_ int always_inline_fn(void) { return 0; } +_c_public_ int c_internal_public_fn(void); +_c_public_ int c_internal_public_fn(void) { return 0; } + +static void cleanup_fn(int p) { (void)p; } +static void direct_cleanup_fn(int p) { (void)p; } +C_DEFINE_CLEANUP(int, cleanup_fn); +C_DEFINE_DIRECT_CLEANUP(int, direct_cleanup_fn); + +static void test_api_generic(void) { + /* C_COMPILER_* */ + { +#ifdef __clang__ + c_assert(C_COMPILER_CLANG); +#endif +#ifdef __GNUC__ + c_assert(C_COMPILER_GNUC); +#endif +#ifdef _MSC_VER + c_assert(C_COMPILER_MSVC); +#endif + } + + /* C_OS_* */ + { +#ifdef __linux__ + c_assert(C_OS_LINUX); +#endif +#ifdef __APPLE__ + c_assert(C_OS_MACOS); +#endif +#ifdef _WIN32 + c_assert(C_OS_WINDOWS); +#endif + } + + /* _c_always_inline_ */ + { + c_assert(!always_inline_fn()); + } + + /* _c_boolean_expr_ */ + { + c_assert(_c_boolean_expr_(true)); + } + + /* _c_likely_ */ + { + c_assert(_c_likely_(true)); + } + + /* _c_public_ */ + { + c_assert(!c_internal_public_fn()); + } + + /* _c_unlikely_ */ + { + c_assert(!_c_unlikely_(false)); + } + + /* C_STRINGIFY */ + { + const char v[] = C_STRINGIFY(foobar); + + c_assert(!strcmp(v, "foobar")); + } + + /* C_CONCATENATE */ + { + int C_CONCATENATE(a, b) = 0; + + c_assert(!ab); + } + + /* C_EXPAND */ + { + int x[] = { C_EXPAND((0, 1)) }; + + c_assert(sizeof(x) / sizeof(*x) == 2); + } + + /* C_VAR */ + { + int C_VAR = 0; c_assert(!C_VAR); /* must be on the same line */ + } + + /* c_assume_aligned */ + { + _Alignas(16) uint8_t data[8] = { 0 }; + + c_assert(c_assume_aligned(data, 16, 0)); + } + + /* c_assert */ + { + c_assert(true); + } + + /* c_load */ + { + uint64_t data[128] = { 0 }; + + c_assert(c_load(uint64_t, le, aligned, data, 0) == 0); + } + + /* C_DEFINE_CLEANUP / C_DEFINE_DIRECT_CLEANUP */ + { + int v = 0; + + cleanup_fnp(&v); + direct_cleanup_fnp(&v); + } + + /* test availability of C symbols */ + { + void *fns[] = { + (void *)c_errno, + (void *)c_memset, + (void *)c_memzero, + (void *)c_memcpy, + (void *)c_load_8, + (void *)c_load_16be_unaligned, + (void *)c_load_16be_aligned, + (void *)c_load_16le_unaligned, + (void *)c_load_16le_aligned, + (void *)c_load_32be_unaligned, + (void *)c_load_32be_aligned, + (void *)c_load_32le_unaligned, + (void *)c_load_32le_aligned, + (void *)c_load_64be_unaligned, + (void *)c_load_64be_aligned, + (void *)c_load_64le_unaligned, + (void *)c_load_64le_aligned, + (void *)c_free, + (void *)c_fclose, + (void *)c_freep, + (void *)c_fclosep, + }; + size_t i; + + for (i = 0; i < sizeof(fns) / sizeof(*fns); ++i) + c_assert(!!fns[i]); + } +} + +#else /* C_MODULE_GENERIC */ + +static void test_api_generic(void) { +} + +#endif /* C_MODULE_GENERIC */ + +#if defined(C_MODULE_GNUC) + +static _c_const_ int const_fn(void) { return 0; } +static _c_deprecated_ _c_unused_ int deprecated_fn(void) { return 0; } +_c_hidden_ int c_internal_hidden_fn(void); +_c_hidden_ int c_internal_hidden_fn(void) { return 0; } +static _c_printf_(1, 2) int printf_fn(const _c_unused_ char *f, ...) { return 0; } +static _c_pure_ int pure_fn(void) { return 0; } +static _c_sentinel_ int sentinel_fn(const _c_unused_ char *f, ...) { return 0; } +static _c_unused_ int unused_fn(void) { return 0; } + +static void test_api_gnuc(void) { + /* _c_cleanup_ */ + { + _c_cleanup_(c_freep) void *foo = NULL; + c_assert(!foo); + } + + /* _c_const_ */ + { + c_assert(!const_fn()); + } + + /* _c_deprecated_ */ + { + /* see deprecated_fn() */ + } + + /* _c_hidden_ */ + { + c_assert(!c_internal_hidden_fn()); + } + + /* _c_packed_ */ + { + struct _c_packed_ FooBar { + int member; + } foobar = {}; + + c_assert(!foobar.member); + } + + /* _c_printf_ */ + { + c_assert(!printf_fn("%d", 1)); + } + + /* _c_pure_ */ + { + c_assert(!pure_fn()); + } + + /* _c_sentinel_ */ + { + c_assert(!sentinel_fn("", NULL)); + } + + /* _c_unused_ */ + { + c_assert(!unused_fn()); + } + + /* C_EXPR_ASSERT */ + { + int v = C_EXPR_ASSERT(0, true, ""); + + c_assert(!v); + } + + /* C_CC_MACRO1, C_CC_MACRO2, C_CC_MACRO3 */ + { +#define MACRO_REAL(_x1, _x2, _x3) ((_x1 + _x2 + _x3) * 0) +#define MACRO1(_x1) C_CC_MACRO1(MACRO_REAL, _x1, 0, 0) +#define MACRO2(_x1, _x2) C_CC_MACRO2(MACRO_REAL, _x1, _x2, 0) +#define MACRO3(_x1, _x2, _x3) C_CC_MACRO3(MACRO_REAL, _x1, _x2, _x3) + c_assert(!MACRO1(1)); + c_assert(!MACRO2(1, 1)); + c_assert(!MACRO3(1, 1, 1)); +#undef MACRO3 +#undef MACRO2 +#undef MACRO1 + } + + /* C_ARRAY_SIZE */ + { + int v[] = { 0, 1, 2 }; + c_assert(C_ARRAY_SIZE(v) == 3); + } + + /* C_DECIMAL_MAX */ + { + c_assert(C_DECIMAL_MAX(uint8_t) == 4); + } + + /* c_container_of */ + { + struct FooBarContainer { + int member; + } v = {}; + + c_assert(c_container_of(&v.member, struct FooBarContainer, member) == &v); + } + + /* c_max, c_min, c_less_by, c_clamp, c_div_round_up */ + { + c_assert(c_max(0, 0) == 0); + c_assert(c_min(0, 0) == 0); + c_assert(c_less_by(0, 0) == 0); + c_assert(c_clamp(0, 0, 0) == 0); + c_assert(c_div_round_up(1, 1) == 1); + } + + /* c_align_to */ + { + c_assert(c_align_to(0, 0) == 0); + } +} + +#else /* C_MODULE_GNUC */ + +static void test_api_gnuc(void) { +} + +#endif /* C_MODULE_GNUC */ + +#if defined(C_MODULE_UNIX) + +static void test_api_unix(void) { + /* test availability of C symbols */ + { + void *fns[] = { + (void *)c_close, + (void *)c_closedir, + (void *)c_closep, + (void *)c_closedirp, + }; + size_t i; + + for (i = 0; i < sizeof(fns) / sizeof(*fns); ++i) + c_assert(!!fns[i]); + } +} + +#else /* C_MODULE_UNIX */ + +static void test_api_unix(void) { +} + +#endif /* C_MODULE_UNIX */ + +int main(void) { + test_api_generic(); + test_api_gnuc(); + test_api_unix(); + return 0; +} diff --git a/src/c-stdaux/src/test-basic.c b/src/c-stdaux/src/test-basic.c new file mode 100644 index 00000000..1d16a828 --- /dev/null +++ b/src/c-stdaux/src/test-basic.c @@ -0,0 +1,624 @@ +/* + * Tests for Basic Functionality + * + * This runs same basic verification that each feature does what we expect it + * to do. More elaborate tests and/or stress-tests are not included here. + */ + +#undef NDEBUG +#include <stdlib.h> +#include "c-stdaux.h" + +#if defined(C_MODULE_GENERIC) + +static int check_cassert_unreachable(int switch_val) { + int result; + + /* Check whether this triggers a "-Wsometimes-uninitialized" warning or + * whether the compiler recognizes c_assert(0) as unreachable code. */ + switch (switch_val) { + case 1: result = 1; break; + case 2: result = 2; break; + default: c_assert(0); + } + + return result; +} + +static void test_basic_generic(int non_constant_expr) { + /* + * Verify `_c_boolean_expr_` evaluates expressions to a boolean value + * and correctly works on all platforms. + */ + { + int v = 0; + + c_assert(_c_boolean_expr_(0) == 0); + c_assert(_c_boolean_expr_(1) == 1); + c_assert(_c_boolean_expr_(2) == 1); + c_assert(_c_boolean_expr_(INT_MIN) == 1); + c_assert(_c_boolean_expr_(INT_MAX) == 1); + + /* verify no double-evaluation takes place */ + c_assert(_c_boolean_expr_(v++) == 0); + c_assert(_c_boolean_expr_(v) == 1); + +#if defined(C_COMPILER_GNUC) + c_assert(__builtin_constant_p(_c_boolean_expr_(1))); + c_assert(!__builtin_constant_p(_c_boolean_expr_(non_constant_expr))); +#endif + } + + /* + * Test that _c_likely_() and _c_unlikely_() can deal with constant + * expressions. + */ + { +#if defined(C_COMPILER_GNUC) + c_assert(__builtin_constant_p(_c_likely_(1))); + c_assert(__builtin_constant_p(_c_unlikely_(1))); + c_assert(!__builtin_constant_p(_c_likely_(non_constant_expr))); + c_assert(!__builtin_constant_p(_c_unlikely_(non_constant_expr))); +#endif + } + + /* + * Test stringify/concatenation helpers. Also make sure to test that + * the passed arguments are evaluated first, before they're stringified + * and/or concatenated. + */ + { +#define TEST_TOKEN foobar + c_assert(!strcmp("foobar", C_STRINGIFY(foobar))); + c_assert(!strcmp("foobar", C_STRINGIFY(TEST_TOKEN))); + c_assert(!strcmp("foobar", C_STRINGIFY(C_CONCATENATE(foo, bar)))); + c_assert(!strcmp("foobarfoobar", C_STRINGIFY(C_CONCATENATE(TEST_TOKEN, foobar)))); + c_assert(!strcmp("foobarfoobar", C_STRINGIFY(C_CONCATENATE(foobar, TEST_TOKEN)))); +#undef TEST_TOKEN + } + + /* + * Test tuple expansion. This is used to strip tuple-wrappers in the + * pre-processor. + * We make sure that it works with {0,1,2}-tuples, as well as only + * strips a single layer. + */ + { + /* + * strcmp() might be a macro, so make sure we get a proper C + * expression below. Otherwise, C_EXPAND() cannot be used that + * way (since it would evaluate to a single macro argument). + */ + int (*f) (const char *, const char *) = strcmp; + + c_assert(!f(C_EXPAND(()) "foobar", "foo" "bar")); + c_assert(!f(C_EXPAND(("foobar")), "foo" "bar")); + c_assert(!f(C_EXPAND(("foobar", "foo" "bar")))); + c_assert(!f C_EXPAND((("foobar", "foo" "bar")))); + } + + /* + * Test C_VAR() macro. It's sole purpose is to create a valid C + * identifier given a single argument (which itself must be a valid + * identifier). + * Just test that we can declare variables with it and use it in + * expressions. + */ + { + { + int C_VAR(sub, UNIQUE) = 5; + /* make sure the variable name does not clash */ + int sub = 12, subUNIQUE = 12, UNIQUEsub = 12; + + c_assert(7 + C_VAR(sub, UNIQUE) == sub); + c_assert(sub == subUNIQUE); + c_assert(sub == UNIQUEsub); + } + { + /* verify C_VAR() with single argument works line-based */ + int C_VAR(sub); C_VAR(sub) = 5; c_assert(C_VAR(sub) == 5); + } + { + /* verify C_VAR() with no argument works line-based */ + int C_VAR(); C_VAR() = 5; c_assert(C_VAR() == 5); + } +#if defined(C_MODULE_GNUC) + { + /* + * Make sure both produce different names, even though they're + * exactly the same expression. + */ + _c_unused_ int C_VAR(sub, __COUNTER__), C_VAR(sub, __COUNTER__); + } +#endif + } + +#if defined(C_MODULE_GNUC) + /* + * Verify that c_free*() works as expected. Since we want to support + * running under valgrind, there is no easy way to verify the + * correctness of free(). Hence, we simply rely on valgrind to catch + * the leaks. + */ + { + int i; + + for (i = 0; i < 16; ++i) { + _c_cleanup_(c_freep) void *foo; + _c_cleanup_(c_freep) int **bar; /* supports any type */ + size_t sz = 128 * 1024; + + foo = malloc(sz); + c_assert(foo); + + bar = malloc(sz); + c_assert(bar); + bar = c_free(bar); + c_assert(!bar); + } + + c_assert(c_free(NULL) == NULL); + } +#endif + +#if defined(C_MODULE_UNIX) + /* + * Test c_fclose() and c_fclosep(). This uses the same logic as the + * tests for c_close() (i.e., sparse FD allocation). + */ + { + int r, i, fd, tmp[2]; + FILE *f; + + r = pipe(tmp); + c_assert(r >= 0); + fd = tmp[0]; + c_close(tmp[1]); + + f = fdopen(fd, "r"); + c_assert(f); + + /* verify c_fclose() returns NULL */ + f = c_fclose(f); + c_assert(!f); + + /* verify c_fclose() deals fine with NULL */ + c_assert(!c_fclose(NULL)); + + /* make sure c_flosep() deals fine with NULL */ + { + _c_cleanup_(c_fclosep) _c_unused_ FILE *t = (void *)0xdeadbeef; + t = NULL; + } + + /* + * Make sure the c_fclose() earlier worked, by allocating the + * FD again and relying on the same FD number to be reused. Do + * this twice, to verify that the c_fclosep() in the cleanup + * path works as well. + */ + for (i = 0; i < 2; ++i) { + _c_cleanup_(c_fclosep) _c_unused_ FILE *t = NULL; + int tfd; + + r = pipe(tmp); + c_assert(r >= 0); + tfd = tmp[0]; + c_close(tmp[1]); + + c_assert(tfd == fd); /* the same as before */ + t = fdopen(tfd, "r"); + c_assert(t); + } + } +#endif + + /* + * Test c_assert(). Make sure side-effects are always evaluated, and + * variables are marked as used regardless of NDEBUG. + */ + { + int v1 = 0, v2 = 0; + +#define NDEBUG 1 + c_assert(!v1); + if (v1) + abort(); + c_assert(++v1); + if (v1 != 1) + abort(); +#undef NDEBUG + c_assert(!v2); + if (v2) + abort(); + c_assert(++v2); + if (v2 != 1) + abort(); + + /* + * Use the `check_cassert_unreachable()` helper to verify the + * compiler does not complain about unreachable code when + * `c_assert(0)` is used. + */ + c_assert(check_cassert_unreachable(1) == 1); + c_assert(check_cassert_unreachable(2) == 2); + } + + /* + * Test c_errno(). Simply verify that the correct value is returned. It + * must always be >0 and equivalent to `errno' if set. + */ + { + c_assert(c_errno() > 0); + + strtol("0xfffffffffffffffffffffffffffffffff", NULL, 0); + c_assert(errno == ERANGE); + c_assert(c_errno() == errno); + + errno = 0; + c_assert(c_errno() != errno); + } + + /* + * Test c_memset(). Simply verify its most basic behavior, as well as + * calling it on empty regions. + */ + { + uint64_t v = (uint64_t)-1; + size_t n; + void *p; + + /* try filling with 0 and 0xff */ + c_assert(v == (uint64_t)-1); + c_memset(&v, 0, sizeof(v)); + c_assert(v == (uint64_t)0); + c_memset(&v, 0xff, sizeof(v)); + c_assert(v == (uint64_t)-1); + + /* + * Try tricking the optimizer into thinking @p cannot be NULL, + * as normal `memset(3)` would allow. + */ + p = NULL; + n = 0; + c_memset(p, 0, n); + if (p) + abort(); + c_assert(p == NULL); + } + + /* + * Test c_memzero(). Simply verify it can clear a trivial area to 0. + */ + { + uint64_t v = (uint64_t)-1; + + c_assert(v == (uint64_t)-1); + c_memzero(&v, sizeof(v)); + c_assert(v == (uint64_t)0); + } + + /* + * Test c_memcpy() with a simple 8-byte copy. + */ + { + uint64_t v1 = (uint64_t)-1, v2 = (uint64_t)0; + + c_assert(v1 == (uint64_t)-1); + c_memcpy(&v1, &v2, sizeof(v1)); + c_assert(v1 == (uint64_t)0); + + c_memcpy(NULL, NULL, 0); + } + + /* + * Test c_memcmp() with. + */ + { + uint64_t v1 = (uint64_t)-1, v2 = (uint64_t)0; + + c_assert(c_memcmp(NULL, NULL, 0) == 0); + c_assert(c_memcmp(&v1, &v2, 0) == 0); + c_assert(c_memcmp(&v1, &v2, 8) != 0); + } + + /* + * Test c_load*() and its mapping to c_load_*() functions. + */ + { + _Alignas(8) uint8_t data[16] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + 1, 2, 3, 4, + 5, 6, 7, 8, + }; + + c_assert(c_load_8(data, 7) == 0); + c_assert(c_load_8(data, 8) == 1); + c_assert(c_load(uint16_t, be, unaligned, data, 7) == UINT16_C(0x0001)); + c_assert(c_load(uint16_t, be, aligned, data, 8) == UINT16_C(0x0102)); + c_assert(c_load(uint16_t, le, unaligned, data, 7) == UINT16_C(0x0100)); + c_assert(c_load(uint16_t, le, aligned, data, 8) == UINT16_C(0x0201)); + c_assert(c_load(uint32_t, be, unaligned, data, 7) == UINT32_C(0x00010203)); + c_assert(c_load(uint32_t, be, aligned, data, 8) == UINT32_C(0x01020304)); + c_assert(c_load(uint32_t, le, unaligned, data, 7) == UINT32_C(0x03020100)); + c_assert(c_load(uint32_t, le, aligned, data, 8) == UINT32_C(0x04030201)); + c_assert(c_load(uint64_t, be, unaligned, data, 7) == UINT64_C(0x0001020304050607)); + c_assert(c_load(uint64_t, be, aligned, data, 8) == UINT64_C(0x0102030405060708)); + c_assert(c_load(uint64_t, le, unaligned, data, 7) == UINT64_C(0x0706050403020100)); + c_assert(c_load(uint64_t, le, aligned, data, 8) == UINT64_C(0x0807060504030201)); + } +} + +#else /* C_MODULE_GENERIC */ + +static void test_basic_generic(int non_constant_expr) { + (void)non_constant_expr; +} + +#endif /* C_MODULE_GENERIC */ + +#if defined(C_MODULE_GNUC) + +static void test_basic_gnuc(int non_constant_expr) { + /* + * Test the C_EXPR_ASSERT() macro to work in static and non-static + * environments, and evaluate exactly to its passed expression. + */ + { + static int v = C_EXPR_ASSERT(1, true, ""); + + c_assert(v == 1); + } + + /* + * Test array-size helper. This simply computes the number of elements + * of an array, instead of the binary size. + */ + { + int bar[8]; + + static_assert(C_ARRAY_SIZE(bar) == 8, ""); + c_assert(__builtin_constant_p(C_ARRAY_SIZE(bar))); + } + + /* + * Test decimal-representation calculator. Make sure it is + * type-independent and just uses the size of the type to calculate how + * many bytes are needed to print that integer in decimal form. Also + * verify that it is a constant expression. + */ + { + static_assert(C_DECIMAL_MAX(char) == 4, ""); + static_assert(C_DECIMAL_MAX(signed char) == 4, ""); + static_assert(C_DECIMAL_MAX(unsigned char) == 4, ""); + static_assert(C_DECIMAL_MAX(unsigned long) == (sizeof(long) == 8 ? 21 : 11), ""); + static_assert(C_DECIMAL_MAX(unsigned long long) == 21, ""); + static_assert(C_DECIMAL_MAX(int32_t) == 11, ""); + static_assert(C_DECIMAL_MAX(uint32_t) == 11, ""); + static_assert(C_DECIMAL_MAX(uint64_t) == 21, ""); + } + + /* + * Test c_container_of(). We cannot test for type-safety, nor for + * other invalid uses, as they'd require negative compile-testing. + * However, we can test that the macro yields the correct values under + * normal use. + */ + { + struct foobar { + int a; + char b; + } sub = {}; + + c_assert(&sub == c_container_of(&sub.a, struct foobar, a)); + c_assert(&sub == c_container_of(&sub.b, struct foobar, b)); + c_assert(&sub == c_container_of((const char *)&sub.b, struct foobar, b)); + + c_assert(!c_container_of(NULL, struct foobar, b)); + } + + /* + * Test min/max macros. Especially check that macro arguments are never + * evaluated multiple times, and if both arguments are constant, the + * return value is constant as well. + */ + { + int foo; + + foo = 0; + c_assert(c_max(1, 5) == 5); + c_assert(c_max(-1, 5) == 5); + c_assert(c_max(-1, -5) == -1); + c_assert(c_max(foo++, -1) == 0); + c_assert(foo == 1); + c_assert(c_max(foo++, foo++) > 0); + c_assert(foo == 3); + + c_assert(__builtin_constant_p(c_max(1, 5))); + c_assert(!__builtin_constant_p(c_max(1, non_constant_expr))); + + foo = 0; + c_assert(c_min(1, 5) == 1); + c_assert(c_min(-1, 5) == -1); + c_assert(c_min(-1, -5) == -5); + c_assert(c_min(foo++, 1) == 0); + c_assert(foo == 1); + c_assert(c_min(foo++, foo++) > 0); + c_assert(foo == 3); + + c_assert(__builtin_constant_p(c_min(1, 5))); + c_assert(!__builtin_constant_p(c_min(1, non_constant_expr))); + } + + /* + * Test c_less_by(), c_clamp(). Make sure they + * evaluate arguments exactly once, and yield a constant expression, + * if all arguments are constant. + */ + { + int foo; + + foo = 8; + c_assert(c_less_by(1, 5) == 0); + c_assert(c_less_by(5, 1) == 4); + c_assert(c_less_by(foo++, 1) == 7); + c_assert(foo == 9); + c_assert(c_less_by(foo++, foo++) >= 0); + c_assert(foo == 11); + + c_assert(__builtin_constant_p(c_less_by(1, 5))); + c_assert(!__builtin_constant_p(c_less_by(1, non_constant_expr))); + + foo = 8; + c_assert(c_clamp(foo, 1, 5) == 5); + c_assert(c_clamp(foo, 9, 20) == 9); + c_assert(c_clamp(foo++, 1, 5) == 5); + c_assert(foo == 9); + c_assert(c_clamp(foo++, foo++, foo++) >= 0); + c_assert(foo == 12); + + c_assert(__builtin_constant_p(c_clamp(0, 1, 5))); + c_assert(!__builtin_constant_p(c_clamp(1, 0, non_constant_expr))); + } + + /* + * Div Round Up: Normal division, but round up to next integer, instead + * of clipping. Also verify that it does not suffer from the integer + * overflow in the prevalent, alternative implementation: + * [(x + y - 1) / y]. + */ + { + int i, j, foo; + +#define TEST_ALT_DIV(_x, _y) (((_x) + (_y) - 1) / (_y)) + foo = 8; + c_assert(c_div_round_up(0, 5) == 0); + c_assert(c_div_round_up(1, 5) == 1); + c_assert(c_div_round_up(5, 5) == 1); + c_assert(c_div_round_up(6, 5) == 2); + c_assert(c_div_round_up(foo++, 1) == 8); + c_assert(foo == 9); + c_assert(c_div_round_up(foo++, foo++) >= 0); + c_assert(foo == 11); + + c_assert(__builtin_constant_p(c_div_round_up(1, 5))); + c_assert(!__builtin_constant_p(c_div_round_up(1, non_constant_expr))); + + /* alternative calculation is [(x + y - 1) / y], but it may overflow */ + for (i = 0; i <= 0xffff; ++i) { + for (j = 1; j <= 0xff; ++j) + c_assert(c_div_round_up(i, j) == TEST_ALT_DIV(i, j)); + for (j = 0xff00; j <= 0xffff; ++j) + c_assert(c_div_round_up(i, j) == TEST_ALT_DIV(i, j)); + } + + /* make sure it doesn't suffer from high overflow */ + c_assert(UINT32_C(0xfffffffa) % 10 == 0); + c_assert(UINT32_C(0xfffffffa) / 10 == UINT32_C(429496729)); + c_assert(c_div_round_up(UINT32_C(0xfffffffa), 10) == UINT32_C(429496729)); + c_assert(TEST_ALT_DIV(UINT32_C(0xfffffffa), 10) == 0); /* overflow */ + + c_assert(UINT32_C(0xfffffffd) % 10 == 3); + c_assert(UINT32_C(0xfffffffd) / 10 == UINT32_C(429496729)); + c_assert(c_div_round_up(UINT32_C(0xfffffffd), 10) == UINT32_C(429496730)); + c_assert(TEST_ALT_DIV(UINT32_C(0xfffffffd), 10) == 0); +#undef TEST_ALT_DIV + } + + /* + * Align to multiple of: Test the alignment macro. Check that it does + * not suffer from incorrect integer overflows, neither should it + * exceed the boundaries of the input type. + */ + { + c_assert(c_align_to(UINT32_C(0), 1) == 0); + c_assert(c_align_to(UINT32_C(0), 2) == 0); + c_assert(c_align_to(UINT32_C(0), 4) == 0); + c_assert(c_align_to(UINT32_C(0), 8) == 0); + c_assert(c_align_to(UINT32_C(1), 8) == 8); + + c_assert(c_align_to(UINT32_C(0xffffffff), 8) == 0); + c_assert(c_align_to(UINT32_C(0xfffffff1), 8) == 0xfffffff8); + c_assert(c_align_to(UINT32_C(0xfffffff1), 8) == 0xfffffff8); + + c_assert(__builtin_constant_p(c_align_to(16, 8))); + c_assert(!__builtin_constant_p(c_align_to(non_constant_expr, 8))); + c_assert(!__builtin_constant_p(c_align_to(16, non_constant_expr))); + c_assert(!__builtin_constant_p(c_align_to(16, non_constant_expr ? 8 : 16))); + c_assert(__builtin_constant_p(c_align_to(16, 7 + 1))); + c_assert(c_align_to(15, non_constant_expr ? 8 : 16) == 16); + } +} + +#else /* C_MODULE_GNUC */ + +static void test_basic_gnuc(int unused0) { + (void)unused0; +} + +#endif /* C_MODULE_GNUC */ + +#if defined(C_MODULE_UNIX) + +static void test_basic_unix(void) { + /* + * Test c_close*(), rely on sparse FD allocation. Make sure all the + * helpers actually close the fd, and cope fine with negative numbers. + */ + { + int r, i, fd1, fd2, tmp[2]; + + r = pipe(tmp); + c_assert(r >= 0); + fd1 = tmp[0]; + fd2 = tmp[1]; + + /* verify c_close() returns -1 */ + c_assert(c_close(fd1) == -1); + c_assert(c_close(fd2) == -1); + + /* verify c_close() deals fine with negative fds */ + c_assert(c_close(-1) == -1); + c_assert(c_close(-16) == -1); + + /* make sure c_closep() deals fine with negative FDs */ + { + _c_cleanup_(c_closep) _c_unused_ int t = 0; + t = -1; + } + + /* + * Make sure the c_close() earlier worked, by allocating the + * FD again and relying on the same FD number to be reused. Do + * this twice, to verify that the c_closep() in the cleanup + * path works as well. + */ + for (i = 0; i < 2; ++i) { + _c_cleanup_(c_closep) _c_unused_ int t1 = -1, t2 = -1; + + r = pipe(tmp); + c_assert(r >= 0); + t1 = tmp[0]; + t2 = tmp[1]; + + c_assert(t1 == fd1); + c_assert(t2 == fd2); + } + } +} + +#else /* C_MODULE_UNIX */ + +static void test_basic_unix(void) { +} + +#endif /* C_MODULE_UNIX */ + +int main(int argc, char **argv) { + (void)argv; + test_basic_generic(argc); + test_basic_gnuc(argc); + test_basic_unix(); + return 0; +} |