diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml new file mode 100644 index 000000000..242b47567 --- /dev/null +++ b/.github/workflows/fuzzing.yml @@ -0,0 +1,59 @@ +name: Celix Fuzzing + +on: + push: + pull_request: + schedule: + - cron: '0 3 * * *' + +jobs: + fuzz-utils: + runs-on: ubuntu-22.04 + timeout-minutes: 30 + steps: + - name: Checkout source code + uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0 + - name: Set up Python + uses: actions/setup-python@7f4fc3e22c37d6ff65e88745f38bd3157c663f7c #v4.9.1 + with: + python-version: '3.x' + - name: Set Compiler Environment Variables + run: | + echo "CC=clang" >> $GITHUB_ENV + echo "CXX=clang++" >> $GITHUB_ENV + - name: Install Conan + run: pip install conan + - name: Cache Conan + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 #v4.3.0 + with: + path: ~/.conan2/p + key: ${{ runner.os }}-conan-${{ hashFiles('conanfile.py', 'libs/utils/**') }} + restore-keys: | + ${{ runner.os }}-conan- + - name: Setup Conan Profile + run: | + conan profile detect + - name: Conan install + run: conan install . --output-folder=build --build=missing -o "celix/*:build_utils=True" -o "celix/*:enable_fuzzing=True" -o "celix/*:enable_address_sanitizer=True" -o "celix/*:enable_undefined_sanitizer=True" + - name: Conan build + run: conan build . --output-folder=build -o "celix/*:build_utils=True" -o "celix/*:enable_fuzzing=True" -o "celix/*:enable_address_sanitizer=True" -o "celix/*:enable_undefined_sanitizer=True" -o "celix/*:celix_err_buffer_size=5120" + - name: Set fuzzer run time + id: set-runtime + run: | + if [[ "${{ github.event_name }}" == "schedule" ]]; then + echo "FUZZ_TIME=600" >> ${GITHUB_ENV} + else + echo "FUZZ_TIME=30" >> ${GITHUB_ENV} + fi + - name: Run properties fuzzer + run: | + source build/conanrun.sh + ./build/libs/utils/fuzzing/celix_properties_fuzzer -max_total_time=$FUZZ_TIME ./build/libs/utils/fuzzing/properties_corpus + - name: Run version fuzzer + run: | + source build/conanrun.sh + ./build/libs/utils/fuzzing/celix_version_fuzzer -max_total_time=$FUZZ_TIME ./build/libs/utils/fuzzing/version_corpus + - name: Run filter fuzzer + run: | + source build/conanrun.sh + ./build/libs/utils/fuzzing/celix_filter_fuzzer -max_total_time=$FUZZ_TIME ./build/libs/utils/fuzzing/filter_corpus diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 61bfd9eba..eceb860b5 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -56,6 +56,7 @@ jobs: env: CONAN_BUILD_OPTIONS: | -o celix/*:enable_testing=True + -o celix/*:enable_benchmarking=True -o celix/*:enable_address_sanitizer=True -o celix/*:build_all=True -o celix/*:enable_cmake_warning_tests=True @@ -79,7 +80,7 @@ jobs: uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c #v3.3.0 - name: Install dependencies run: | - brew install lcov jansson rapidjson libzip ccache ninja openssl@1.1 + brew install lcov jansson rapidjson libzip ccache ninja openssl@1.1 google-benchmark - name: Prepare ccache timestamp id: ccache_cache_timestamp run: | @@ -95,6 +96,7 @@ jobs: env: BUILD_OPTIONS: | -DENABLE_TESTING=ON + -DENABLE_BENCHMARKING=ON -DENABLE_ADDRESS_SANITIZER=ON -DENABLE_TESTING_ON_CI=ON -DCMAKE_BUILD_TYPE=Release diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index a472b72e8..dc0964f4a 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -77,7 +77,9 @@ jobs: CXX: ${{ matrix.compiler[1] }} CONAN_BUILD_OPTIONS: | -o celix:enable_testing=True + -o celix:enable_benchmarking=True -o celix:enable_address_sanitizer=True + -o celix:enable_undefined_sanitizer=True -o celix:build_all=True -o celix:enable_cmake_warning_tests=True -o celix:enable_testing_on_ci=True @@ -120,6 +122,7 @@ jobs: libzip-dev \ libjansson-dev \ libcurl4-openssl-dev \ + libbenchmark-dev \ default-jdk \ cmake \ libffi-dev \ @@ -145,6 +148,7 @@ jobs: BUILD_OPTIONS: | -DBUILD_EXPERIMENTAL=ON -DENABLE_TESTING=ON + -DENABLE_BENCHMARKING=ON -DRSA_JSON_RPC=ON -DRSA_REMOTE_SERVICE_ADMIN_SHM_V2=ON -DENABLE_TESTING_ON_CI=ON diff --git a/CMakeLists.txt b/CMakeLists.txt index d72ce4d76..09e065ae8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,6 +168,8 @@ endif () option(ENABLE_CMAKE_WARNING_TESTS "Enable cmake warning tests to test warning prints" OFF) option(ENABLE_TESTING_ON_CI "Whether to enable testing on CI. This influence allowed timing errors during unit tests" OFF) option(ENABLE_DEPRECATED_WARNINGS "Enable compiler warnings for usage of deprecated functionality" OFF) +option(ENABLE_FUZZING "Enable fuzz testing, using LibFuzzer" OFF) #Note support for LibFuzzer is built-in for Clang +option(ENABLE_BENCHMARKING "Enable benchmarking, using Google Benchmark" OFF) if (NOT ENABLE_DEPRECATED_WARNINGS) set(CMAKE_C_FLAGS "-Wno-deprecated-declarations ${CMAKE_C_FLAGS}") diff --git a/cmake/celix_project/CelixProject.cmake b/cmake/celix_project/CelixProject.cmake index 0f3132744..8f231a2dd 100644 --- a/cmake/celix_project/CelixProject.cmake +++ b/cmake/celix_project/CelixProject.cmake @@ -25,13 +25,17 @@ mark_as_advanced(CLEAR ENABLE_UNDEFINED_SANITIZER) mark_as_advanced(CLEAR ENABLE_THREAD_SANITIZER) if (ENABLE_ADDRESS_SANITIZER) + set(UBSAN_SAN "") + if (ENABLE_UNDEFINED_SANITIZER) + set(UBSAN_SAN "undefined,") + endif () if("${CMAKE_C_COMPILER_ID}" MATCHES "Clang") set(CMAKE_C_FLAGS "-DCELIX_ASAN_ENABLED ${CMAKE_C_FLAGS}") - set(CMAKE_C_FLAGS "-shared-libasan -fsanitize=address -fno-omit-frame-pointer ${CMAKE_C_FLAGS}") - set(CMAKE_CXX_FLAGS "-shared-libasan -fsanitize=address -fno-omit-frame-pointer ${CMAKE_CXX_FLAGS}") + set(CMAKE_C_FLAGS "-shared-libasan -fsanitize=${UBSAN_SAN}address -fno-omit-frame-pointer ${CMAKE_C_FLAGS}") + set(CMAKE_CXX_FLAGS "-shared-libasan -fsanitize=${UBSAN_SAN}address -fno-omit-frame-pointer ${CMAKE_CXX_FLAGS}") if (APPLE) - set(CMAKE_EXE_LINKER_FLAGS "-fsanitize=address ${CMAKE_EXE_LINKER_FLAGS}") - set(CMAKE_SHARED_LINKER_FLAGS "-fsanitize=address ${CMAKE_SHARED_LINKER_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "-fsanitize=${UBSAN_SAN}address ${CMAKE_EXE_LINKER_FLAGS}") + set(CMAKE_SHARED_LINKER_FLAGS "-fsanitize=${UBSAN_SAN}address ${CMAKE_SHARED_LINKER_FLAGS}") else () # Fix a linux clang deficiency where the ASan runtime library is not found automatically # Find the ASan runtime library path and set RPATH @@ -56,19 +60,15 @@ if (ENABLE_ADDRESS_SANITIZER) endif () elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") set(CMAKE_C_FLAGS "-DCELIX_ASAN_ENABLED ${CMAKE_C_FLAGS}") - set(CMAKE_C_FLAGS "-lasan -fsanitize=address -fno-omit-frame-pointer ${CMAKE_C_FLAGS}") - set(CMAKE_CXX_FLAGS "-lasan -fsanitize=address -fno-omit-frame-pointer ${CMAKE_CXX_FLAGS}") + set(CMAKE_C_FLAGS "-lasan -fsanitize=${UBSAN_SAN}address -fno-omit-frame-pointer ${CMAKE_C_FLAGS}") + set(CMAKE_CXX_FLAGS "-lasan -fsanitize=${UBSAN_SAN}address -fno-omit-frame-pointer ${CMAKE_CXX_FLAGS}") else () message(WARNING "Address sanitizer is not supported for ${CMAKE_C_COMPILER_ID}") endif () -endif() - -if (ENABLE_UNDEFINED_SANITIZER) +elseif (ENABLE_UNDEFINED_SANITIZER) set(CMAKE_C_FLAGS "-fsanitize=undefined ${CMAKE_C_FLAGS}") set(CMAKE_CXX_FLAGS "-fsanitize=undefined ${CMAKE_CXX_FLAGS}") -endif() - -if (ENABLE_THREAD_SANITIZER) +elseif (ENABLE_THREAD_SANITIZER) set(CMAKE_C_FLAGS "-fsanitize=thread ${CMAKE_C_FLAGS}") set(CMAKE_CXX_FLAGS "-fsanitize=thread ${CMAKE_CXX_FLAGS}") endif() diff --git a/cmake/cmake_celix/BundlePackaging.cmake b/cmake/cmake_celix/BundlePackaging.cmake index 4200f390a..bb4869022 100644 --- a/cmake/cmake_celix/BundlePackaging.cmake +++ b/cmake/cmake_celix/BundlePackaging.cmake @@ -287,23 +287,23 @@ function(add_celix_bundle) ######################################################### ###### Packaging the bundle using using jar or zip and a content dir. Configuring dependencies ###### - if (JAR_COMMAND) + if (ZIP_COMMAND) + file(MAKE_DIRECTORY ${BUNDLE_CONTENT_DIR}) #Note needed because working_directory is bundle content dir add_custom_command(OUTPUT ${BUNDLE_FILE} - COMMAND ${CMAKE_COMMAND} -E make_directory ${BUNDLE_CONTENT_DIR} COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BUNDLE_GEN_DIR}/MANIFEST.json ${BUNDLE_CONTENT_DIR}/META-INF/MANIFEST.json - COMMAND ${JAR_COMMAND} ${CELIX_JAR_COMMAND_ARGUMENTS} ${BUNDLE_FILE} -C ${BUNDLE_CONTENT_DIR} . + COMMAND ${ZIP_COMMAND} ${CELIX_ZIP_COMMAND_ARGUMENTS} ${BUNDLE_FILE} * COMMENT "Packaging ${BUNDLE_TARGET_NAME}" DEPENDS ${BUNDLE_TARGET_NAME} "$" ${BUNDLE_GEN_DIR}/MANIFEST.json - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + WORKING_DIRECTORY ${BUNDLE_CONTENT_DIR} ) - elseif (ZIP_COMMAND) - file(MAKE_DIRECTORY ${BUNDLE_CONTENT_DIR}) #Note needed because working_directory is bundle content dir + elseif (JAR_COMMAND) add_custom_command(OUTPUT ${BUNDLE_FILE} + COMMAND ${CMAKE_COMMAND} -E make_directory ${BUNDLE_CONTENT_DIR} COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BUNDLE_GEN_DIR}/MANIFEST.json ${BUNDLE_CONTENT_DIR}/META-INF/MANIFEST.json - COMMAND ${ZIP_COMMAND} ${CELIX_ZIP_COMMAND_ARGUMENTS} ${BUNDLE_FILE} * + COMMAND ${JAR_COMMAND} ${CELIX_JAR_COMMAND_ARGUMENTS} ${BUNDLE_FILE} -C ${BUNDLE_CONTENT_DIR} . COMMENT "Packaging ${BUNDLE_TARGET_NAME}" DEPENDS ${BUNDLE_TARGET_NAME} "$" ${BUNDLE_GEN_DIR}/MANIFEST.json - WORKING_DIRECTORY ${BUNDLE_CONTENT_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) else () message(FATAL_ERROR "A jar or zip command is needed to jar/zip bundles") @@ -934,21 +934,21 @@ function(install_celix_bundle) set(BUNDLE_FILE_INSTALL "${BUNDLE_FILE}.install") get_target_property(BUNDLE_FILE_NAME ${BUNDLE} "BUNDLE_FILE_NAME") get_target_property(BUNDLE_GEN_DIR ${BUNDLE} "BUNDLE_GEN_DIR") - if (JAR_COMMAND) + if (ZIP_COMMAND) install(CODE "execute_process( COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BUNDLE_GEN_DIR}/MANIFEST.json META-INF/MANIFEST.json - COMMAND ${JAR_COMMAND} ${CELIX_JAR_COMMAND_ARGUMENTS} ${BUNDLE_FILE_INSTALL} -C ${BUNDLE_CONTENT_DIR} . - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${ZIP_COMMAND} ${CELIX_ZIP_COMMAND_ARGUMENTS} ${BUNDLE_FILE_INSTALL} . -i * + WORKING_DIRECTORY ${BUNDLE_CONTENT_DIR} )" COMPONENT ${BUNDLE} ) - elseif (ZIP_COMMAND) + elseif (JAR_COMMAND) install(CODE "execute_process( COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BUNDLE_GEN_DIR}/MANIFEST.json META-INF/MANIFEST.json - COMMAND ${ZIP_COMMAND} ${CELIX_ZIP_COMMAND_ARGUMENTS} ${BUNDLE_FILE_INSTALL} . -i * - WORKING_DIRECTORY ${BUNDLE_CONTENT_DIR} + COMMAND ${JAR_COMMAND} ${CELIX_JAR_COMMAND_ARGUMENTS} ${BUNDLE_FILE_INSTALL} -C ${BUNDLE_CONTENT_DIR} . + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} )" COMPONENT ${BUNDLE} ) diff --git a/conanfile.py b/conanfile.py index 09ca5a332..bb1c63907 100644 --- a/conanfile.py +++ b/conanfile.py @@ -47,6 +47,8 @@ class CelixConan(ConanFile): "enable_address_sanitizer": False, "enable_undefined_sanitizer": False, "enable_thread_sanitizer": False, + "enable_fuzzing": False, + "enable_benchmarking": False, "install_find_modules": False, "build_all": False, "build_http_admin": False, @@ -130,6 +132,9 @@ def validate(self): if self.options.build_rsa_discovery_zeroconf and self.settings.os != "Linux": raise ConanInvalidConfiguration("Celix build_rsa_discovery_zeroconf is only supported for Linux") + if self.options.enable_fuzzing and self.settings.compiler != "clang" and self.settings.compiler != "apple-clang": + raise ConanInvalidConfiguration("Celix enable_fuzzing=True requires the 'clang' compiler") + self.validate_config_option_is_positive_number("celix_err_buffer_size") self.validate_config_option_is_positive_number("celix_utils_max_strlen") self.validate_config_option_is_positive_number("celix_properties_optimization_string_buffer_size") @@ -145,12 +150,18 @@ def package_id(self): del self.info.options.enable_testing_on_ci del self.info.options.enable_ccache del self.info.options.enable_deprecated_warnings + del self.info.options.enable_testing + del self.info.options.enable_benchmarking + del self.info.options.enable_fuzzing + del self.info.options.enable_code_coverage def build_requirements(self): if self.options.enable_testing: self.test_requires("gtest/1.10.0") if self.options.enable_ccache: self.build_requires("ccache/4.7.4") + if self.options.enable_benchmarking: + self.test_requires("benchmark/[>=1.6.2]") def configure(self): # copy options to options, fill in defaults if not set @@ -308,6 +319,8 @@ def configure(self): self.options['openssl'].shared = True if self.options.enable_testing: self.options['gtest'].shared = True + if self.options.enable_benchmarking: + self.options['benchmark'].shared = True if (self.options.build_rsa_discovery_common or (self.options.build_rsa_remote_service_admin_dfi and self.options.enable_testing)): self.options['libxml2'].shared = True diff --git a/documents/README.md b/documents/README.md index fefec7971..c633e7d11 100644 --- a/documents/README.md +++ b/documents/README.md @@ -81,7 +81,6 @@ bundles contains binaries depending on the stdlibc++ library. * Building * [Building and Installing Apache Celix](building/README.md) - * [Building and Developing Apache Celix with CLion](building/dev_celix_with_clion.md) * C Patterns * [Apache Celix C Patterns](c_patterns.md) * Utils diff --git a/documents/building/README.md b/documents/building/README.md index c64351003..7d01b8bb7 100644 --- a/documents/building/README.md +++ b/documents/building/README.md @@ -237,3 +237,10 @@ cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ../libs/pushstreams make -j sudo make install ``` + +# Further Reading + +- [Building with CLion](dev_celix_with_clion.md) +- [Building and Running Tests](testing.md) +- [Fuzz Testing](fuzz_testing.md) +- [Building and Running Benchmarks](benchmarks.md) diff --git a/documents/building/benchmarks.md b/documents/building/benchmarks.md new file mode 100644 index 000000000..d1061b3b8 --- /dev/null +++ b/documents/building/benchmarks.md @@ -0,0 +1,74 @@ +--- +title: Benchmarks in Apache Celix +--- + + + + +# Benchmarks in Apache Celix + +This document describes how to build and run benchmarks for Apache Celix. + +## Building Benchmarks + +Benchmarks can be built using the CMake option `ENABLE_BENCHMARKING` +The Apache Celix benchmarks uses Google benchmark library. + +To build benchmarks run: + +```sh +cmake -B build -DENABLE_BENCHMARKING=ON +cmake --build build +``` + +## Benchmarks + +The following benchmark executables are available after building the utils and framework benchmarks: + +**Utils Benchmarks:** +- `build/libs/utils/benchmark/celix_filter_benchmark` +- `build/libs/utils/benchmark/celix_long_hashmap_benchmark` +- `build/libs/utils/benchmark/celix_string_hashmap_benchmark` +- `build/libs/utils/benchmark/celix_utils_benchmark` + +**Framework Benchmarks:** +- `build/libs/framework/benchmark/celix_framework_benchmark` + +Paths may vary depending on your configuration and enabled options. + +## Running Benchmarks + +Benchmark executables are located in the `build` directory, typically under the relevant bundle or library subdirectory. To run a benchmark: + +```sh +./build/libs/utils/benchmarks/celix_utils_benchmark +## Command-Line Options +The benchmark executables accept standard Google Benchmark command-line options. +For example, to run only benchmarks matching a specific pattern and output results in JSON format: + +```bash +./build/libs/utils/benchmark/celix_filter_benchmark --benchmark_filter=complexFilter --benchmark_format=json +``` + +Replace `celix_utils_benchmark` and the filter pattern as needed. To see a list of supported command-line flags, run the benchmark executable with the `--help` option: + +```bash +./build/libs/utils/benchmarks/./celix_filter_benchmark --help +``` + +This will display all available Google Benchmark options. diff --git a/documents/building/fuzz_testing.md b/documents/building/fuzz_testing.md new file mode 100644 index 000000000..50f0abb21 --- /dev/null +++ b/documents/building/fuzz_testing.md @@ -0,0 +1,74 @@ +--- +title: Fuzz testing with libFuzzer +--- + + + +# Fuzz Testing with libFuzzer + +The utilities library contains fuzz targets that can be built with +[LLVM libFuzzer](https://llvm.org/docs/LibFuzzer.html). Fuzzing is +enabled when using the Clang compiler and the `UTILS_LIBFUZZER` CMake +option. + +## Building + +Configure CMake with Clang and enable the libFuzzer option: + +```bash +cmake \ + -G Ninja \ + -S . -B build \ + -DCMAKE_C_COMPILER=clang \ + -DCMAKE_CXX_COMPILER=clang++ \ + -DENABLE_FUZZING=ON +``` + +Build the fuzzer executables: + +```bash +cmake --build build --parallel --target celix_properties_fuzzer celix_version_fuzzer celix_filter_fuzzer +``` + +## Corpus + +The `corpus` directories for the fuzzers contain a few seed inputs, which help guide the initial fuzzing process. +More files can be added to these directories to improve coverage. The fuzzer will automatically use all files in the +specified corpus directory as starting points for mutation and exploration. + +## Running +The resulting fuzzers accept standard libFuzzer command line options. For example, to run each fuzzer for 30 seconds +using the provided seed corpus and print coverage information: + +```bash +./build/libs/utils/celix_filter_fuzzer -max_total_time=30 -print_coverage=1 ./build/libs/utils/filter_corpus +``` + +Replace `celix_filter_fuzzer` and `filter_corpus` with the appropriate fuzzer executable and corpus directory as needed. +To see a list of supported command-line flags, run the fuzzer executable with the `-help=1` option. For example: + +```bash +./build/libs/utils/celix_filter_fuzzer -help=1 +``` + +This will display all available LibFuzzer options. + +## Continuous Fuzzing + +A GitHub Actions workflow runs the fuzzer periodically. The workflow +configuration can be found at `.github/workflows/fuzzing.yml`. diff --git a/documents/building/testing.md b/documents/building/testing.md new file mode 100644 index 000000000..557258ce0 --- /dev/null +++ b/documents/building/testing.md @@ -0,0 +1,65 @@ +--- +title: Testing Apache Celix +--- + + + +# Testing Apache Celix + +This document describes how to build and run tests for Apache Celix. + +## Building Tests + +Celix uses CMake and Google Test for its unit and integration tests. To build the tests, ensure you have all dependencies installed, then run: + +```sh +cmake -B build -DENABLE_TESTING=ON -DCMAKE_BUILD_TYPE=Debug +cmake --build build +``` + +```sh +#conan + +To enable AddressSanitizer (ASAN) when building tests, configure CMake with the `ENABLE_ASAN` option: + +```sh +cmake -B build -DENABLE_TESTING=ON -DENABLE_ADDRESS_SANITIZER=ON -DCMAKE_BUILD_TYPE=Debug +cmake --build build +``` + +This will build Apache Celix and its tests with ASAN enabled, helping to detect memory errors during test execution. + +## Running Tests + +After building, you can run all tests using CTest: + +```sh +ctest --output-on-failure --test-dir build +``` + +Or run a test for a specific subdir, e.g.: + +```sh +ctest --output-on-failure --test-dir build/bundles/shell +``` + +Or run a specific test binary directly from the `build` directory, e.g.: + +```sh +./build/bundles/components_ready_check/tests/components_ready_check_test +``` diff --git a/libs/framework/benchmark/CMakeLists.txt b/libs/framework/benchmark/CMakeLists.txt index ba39fcbb4..875d2f7f3 100644 --- a/libs/framework/benchmark/CMakeLists.txt +++ b/libs/framework/benchmark/CMakeLists.txt @@ -15,14 +15,7 @@ # specific language governing permissions and limitations # under the License. -set(FRAMEWORK_BENCHMARK_DEFAULT "OFF") -find_package(benchmark QUIET) -if (benchmark_FOUND) - set(FRAMEWORK_BENCHMARK_DEFAULT "ON") -endif () - -celix_subproject(FRAMEWORK_BENCHMARK "Option to enable Celix framework benchmark" ${FRAMEWORK_BENCHMARK_DEFAULT}) -if (FRAMEWORK_BENCHMARK AND CELIX_CXX17) +if (ENABLE_BENCHMARKING AND CELIX_CXX17) set(CMAKE_CXX_STANDARD 17) find_package(benchmark REQUIRED) diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index 4b8c08a35..e28317b49 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -126,5 +126,23 @@ if (UTILS) add_subdirectory(gtest) endif () + + if (ENABLE_FUZZING) + add_library(utils_cuf STATIC ${UTILS_SRC}) + target_compile_definitions(utils_cuf PRIVATE CELIX_UTILS_STATIC_DEFINE) + target_include_directories(utils_cuf PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include + ${CMAKE_CURRENT_LIST_DIR}/include_internal + ${CMAKE_BINARY_DIR}/celix/gen/includes/utils + ${CMAKE_BINARY_DIR}/celix/gen/src/utils + include_deprecated + ) + target_link_libraries(utils_cuf PUBLIC ${UTILS_PUBLIC_DEPS} ${UTILS_PRIVATE_DEPS}) + target_compile_options(utils_cuf PRIVATE -fsanitize=fuzzer) + target_link_options(utils_cuf PRIVATE -Wl,--gc-sections) + + add_subdirectory(fuzzing) + endif () + add_subdirectory(benchmark) endif () diff --git a/libs/utils/benchmark/CMakeLists.txt b/libs/utils/benchmark/CMakeLists.txt index 2f891e6cb..4c22fc2df 100644 --- a/libs/utils/benchmark/CMakeLists.txt +++ b/libs/utils/benchmark/CMakeLists.txt @@ -15,14 +15,7 @@ # specific language governing permissions and limitations # under the License. -set(UTILS_BENCHMARK_DEFAULT "OFF") -find_package(benchmark QUIET) -if (benchmark_FOUND) - set(UTILS_BENCHMARK_DEFAULT "ON") -endif () - -celix_subproject(UTILS_BENCHMARK "Option to enable Celix framework benchmark" ${UTILS_BENCHMARK_DEFAULT}) -if (UTILS_BENCHMARK) +if (ENABLE_BENCHMARKING) find_package(benchmark REQUIRED) add_executable(celix_string_hashmap_benchmark diff --git a/libs/utils/fuzzing/CMakeLists.txt b/libs/utils/fuzzing/CMakeLists.txt new file mode 100644 index 000000000..a7e248e35 --- /dev/null +++ b/libs/utils/fuzzing/CMakeLists.txt @@ -0,0 +1,52 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. + +if (ENABLE_ADDRESS_SANITIZER OR ENABLE_UNDEFINED_SANITIZER) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libsan") +endif () + +add_executable(celix_properties_fuzzer src/PropertiesFuzz.cc) +target_link_libraries(celix_properties_fuzzer PRIVATE utils_cuf) +target_compile_options(celix_properties_fuzzer PRIVATE -fsanitize=fuzzer) +target_link_options(celix_properties_fuzzer PRIVATE -fsanitize=fuzzer) + +add_executable(celix_version_fuzzer src/VersionFuzz.cc) +target_link_libraries(celix_version_fuzzer PRIVATE utils_cuf) +target_compile_options(celix_version_fuzzer PRIVATE -fsanitize=fuzzer) +target_link_options(celix_version_fuzzer PRIVATE -fsanitize=fuzzer) + +add_executable(celix_filter_fuzzer src/FilterFuzz.cc) +target_link_libraries(celix_filter_fuzzer PRIVATE utils_cuf) +target_compile_options(celix_filter_fuzzer PRIVATE -fsanitize=fuzzer) +target_link_options(celix_filter_fuzzer PRIVATE -fsanitize=fuzzer) + +add_custom_command(TARGET celix_properties_fuzzer POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/properties_corpus + $/properties_corpus +) + +add_custom_command(TARGET celix_version_fuzzer POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/version_corpus + $/version_corpus +) + +add_custom_command(TARGET celix_filter_fuzzer POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/filter_corpus + $/filter_corpus +) + diff --git a/libs/utils/fuzzing/filter_corpus/complex.txt b/libs/utils/fuzzing/filter_corpus/complex.txt new file mode 100644 index 000000000..1ffe40511 --- /dev/null +++ b/libs/utils/fuzzing/filter_corpus/complex.txt @@ -0,0 +1 @@ +(&(test_attr1=attr1)(|(test_attr2=attr2)(test_attr3=attr3))) diff --git a/libs/utils/fuzzing/filter_corpus/simple.txt b/libs/utils/fuzzing/filter_corpus/simple.txt new file mode 100644 index 000000000..bfa80ac8d --- /dev/null +++ b/libs/utils/fuzzing/filter_corpus/simple.txt @@ -0,0 +1 @@ +(key=value) diff --git a/libs/utils/fuzzing/properties_corpus/complex.json b/libs/utils/fuzzing/properties_corpus/complex.json new file mode 100644 index 000000000..070856559 --- /dev/null +++ b/libs/utils/fuzzing/properties_corpus/complex.json @@ -0,0 +1,2 @@ +{"key1":"value1","key2":"value2","object1":{"key3":"value3","key4":"value4"},"object2":{"key5":"value5"},"object3":{"object4":{"key6":"value6"}},"strArr":["value1","value2"],"intArr":[1,2],"realArr":[1.0,2.0],"boolArr":[true,false],"versionArr":["version<1.2.3.qualifier>","version<4.5.6.qualifier>"]} + diff --git a/libs/utils/fuzzing/properties_corpus/simple.json b/libs/utils/fuzzing/properties_corpus/simple.json new file mode 100644 index 000000000..29f05f6bf --- /dev/null +++ b/libs/utils/fuzzing/properties_corpus/simple.json @@ -0,0 +1 @@ +{"key":"value"} diff --git a/libs/utils/fuzzing/src/FilterFuzz.cc b/libs/utils/fuzzing/src/FilterFuzz.cc new file mode 100644 index 000000000..ddc5021c0 --- /dev/null +++ b/libs/utils/fuzzing/src/FilterFuzz.cc @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include + +int filterParseFuzzOneInput(const uint8_t* data, size_t size) { + char* buffer = static_cast(malloc(size + 1)); + if (buffer == nullptr) { + return 0; + } + memcpy(buffer, data, size); + buffer[size] = '\0'; + + celix_filter_t* filter = celix_filter_create(buffer); + celix_filter_destroy(filter); + celix_err_resetErrors(); + + free(buffer); + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { return filterParseFuzzOneInput(data, size); } diff --git a/libs/utils/fuzzing/src/PropertiesFuzz.cc b/libs/utils/fuzzing/src/PropertiesFuzz.cc new file mode 100644 index 000000000..627c2dd56 --- /dev/null +++ b/libs/utils/fuzzing/src/PropertiesFuzz.cc @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include + +int propertiesParserFuzzOneInput(const uint8_t* data, size_t size) { + int flags = 0; + if (size > 0) { + flags = data[0] & 0x3F; // use 6 bits for decode flags + data += 1; + size -= 1; + } + char* buffer = static_cast(malloc(size + 1)); + if (buffer == nullptr) { + return 0; + } + memcpy(buffer, data, size); + buffer[size] = '\0'; + + celix_properties_t* props = nullptr; + celix_properties_loadFromString(buffer, flags, &props); + celix_properties_destroy(props); + celix_err_resetErrors(); + + free(buffer); + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + return propertiesParserFuzzOneInput(data, size); +} diff --git a/libs/utils/fuzzing/src/VersionFuzz.cc b/libs/utils/fuzzing/src/VersionFuzz.cc new file mode 100644 index 000000000..b1fc7e67f --- /dev/null +++ b/libs/utils/fuzzing/src/VersionFuzz.cc @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include + +int versionParseFuzzOneInput(const uint8_t* data, size_t size) { + char* buffer = static_cast(malloc(size + 1)); + if (buffer == nullptr) { + return 0; + } + memcpy(buffer, data, size); + buffer[size] = '\0'; + + celix_version_t* version = celix_version_createVersionFromString(buffer); + celix_version_destroy(version); + celix_err_resetErrors(); + + free(buffer); + return 0; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { return versionParseFuzzOneInput(data, size); } diff --git a/libs/utils/fuzzing/version_corpus/qualifier.txt b/libs/utils/fuzzing/version_corpus/qualifier.txt new file mode 100644 index 000000000..da3ad8b4e --- /dev/null +++ b/libs/utils/fuzzing/version_corpus/qualifier.txt @@ -0,0 +1 @@ +2.0.0.qualifier diff --git a/libs/utils/fuzzing/version_corpus/simple.txt b/libs/utils/fuzzing/version_corpus/simple.txt new file mode 100644 index 000000000..0495c4a88 --- /dev/null +++ b/libs/utils/fuzzing/version_corpus/simple.txt @@ -0,0 +1 @@ +1.2.3