diff --git a/.clang-tidy b/.clang-tidy index fafa4ad09..f1fed609e 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -63,6 +63,7 @@ Checks: - modernize-* - -modernize-deprecated-headers + - -modernize-use-ranges - -modernize-use-std-numbers - -modernize-use-std-print diff --git a/.github/workflows/freestanding-arm.yml b/.github/workflows/freestanding-arm.yml index e9ac2deda..ff3bb0d72 100644 --- a/.github/workflows/freestanding-arm.yml +++ b/.github/workflows/freestanding-arm.yml @@ -46,7 +46,7 @@ jobs: -S . -B cmake-build-gcc -G "Ninja Multi-Config" - -D CMAKE_TOOLCHAIN_FILE=cmake/toolchain/gcc-arm-none-eabi.cmake + -D CMAKE_TOOLCHAIN_FILE=cmake/toolchain/arm-none-eabi-gcc.cmake -D CMAKE_CXX_STANDARD=${{ matrix.cxx }} -D CMAKE_COMPILE_WARNING_AS_ERROR=ON -D TETL_BUILD_CONTRACT_CHECKS=OFF @@ -63,7 +63,7 @@ jobs: -S . -B cmake-build-clang -G "Ninja Multi-Config" - -D CMAKE_TOOLCHAIN_FILE=cmake/toolchain/clang-arm-none-eabi.cmake + -D CMAKE_TOOLCHAIN_FILE=cmake/toolchain/arm-none-eabi-clang.cmake -D CMAKE_CXX_STANDARD=${{ matrix.cxx }} -D CMAKE_COMPILE_WARNING_AS_ERROR=ON -D TETL_BUILD_CONTRACT_CHECKS=OFF diff --git a/.github/workflows/freestanding-avr.yml b/.github/workflows/freestanding-avr.yml index 6f050af36..a30c379b6 100644 --- a/.github/workflows/freestanding-avr.yml +++ b/.github/workflows/freestanding-avr.yml @@ -36,7 +36,7 @@ jobs: cmake -S . -B build -G "Ninja Multi-Config" - -D CMAKE_TOOLCHAIN_FILE=cmake/toolchain/atmega2560.cmake + -D CMAKE_TOOLCHAIN_FILE=cmake/toolchain/avr-gcc.cmake -D CMAKE_CXX_STANDARD=${{ matrix.cxx }} -D CMAKE_COMPILE_WARNING_AS_ERROR=ON -D TETL_BUILD_CONTRACT_CHECKS=OFF diff --git a/.github/workflows/freestanding-riscv.yml b/.github/workflows/freestanding-riscv.yml index 67acaa6cd..f5fc88700 100644 --- a/.github/workflows/freestanding-riscv.yml +++ b/.github/workflows/freestanding-riscv.yml @@ -43,7 +43,7 @@ jobs: -S . -B build -G "Ninja Multi-Config" - -D CMAKE_TOOLCHAIN_FILE=cmake/toolchain/gcc-riscv-none-elf.cmake + -D CMAKE_TOOLCHAIN_FILE=cmake/toolchain/riscv-none-elf-gcc.cmake -D CMAKE_CXX_STANDARD=${{ matrix.cxx }} -D CMAKE_COMPILE_WARNING_AS_ERROR=ON -D TETL_BUILD_CONTRACT_CHECKS=OFF diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1b32a4685..ffca3413c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,12 +25,12 @@ repos: - id: trailing-whitespace - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v21.1.1 + rev: v21.1.2 hooks: - id: clang-format types_or: [c++, c] - repo: https://github.com/fsfe/reuse-tool - rev: v5.1.1 + rev: v6.1.0 hooks: - id: reuse diff --git a/CMakePresets.json b/CMakePresets.json index 34af0dd0d..12c571b49 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -30,7 +30,7 @@ { "name": "avr-gcc", "description": "Configure with avr-gcc toolchain", - "toolchainFile": "${sourceDir}/cmake/toolchain/atmega2560.cmake", + "toolchainFile": "${sourceDir}/cmake/toolchain/avr-gcc.cmake", "inherits": ["_root-config"], "cacheVariables": { "TETL_BUILD_CONTRACT_CHECKS": false @@ -39,7 +39,7 @@ { "name": "arm-gcc", "description": "Configure with arm-gcc toolchain", - "toolchainFile": "${sourceDir}/cmake/toolchain/gcc-arm-none-eabi.cmake", + "toolchainFile": "${sourceDir}/cmake/toolchain/arm-none-eabi-gcc.cmake", "inherits": ["_root-config"], "cacheVariables": { "TETL_BUILD_CONTRACT_CHECKS": false @@ -48,7 +48,7 @@ { "name": "arm-clang", "description": "Configure with arm-clang toolchain", - "toolchainFile": "${sourceDir}/cmake/toolchain/clang-arm-none-eabi.cmake", + "toolchainFile": "${sourceDir}/cmake/toolchain/arm-none-eabi-clang.cmake", "inherits": ["_root-config"], "cacheVariables": { "TETL_BUILD_CONTRACT_CHECKS": false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index caead6a59..41200290e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ ctest --test-dir cmake-build-emscripten -C Debug --output-on-failure ```sh # AVR -cmake -S . -B cmake-build-avr-gcc -G "Ninja Multi-Config" -D CMAKE_TOOLCHAIN_FILE="cmake/toolchain/atmega2560.cmake" +cmake -S . -B cmake-build-avr-gcc -G "Ninja Multi-Config" -D CMAKE_TOOLCHAIN_FILE="cmake/toolchain/avr-gcc.cmake" cmake --build cmake-build-avr-gcc --config Debug ``` diff --git a/README.md b/README.md index 98e40f607..cc4aae2e9 100644 --- a/README.md +++ b/README.md @@ -55,12 +55,12 @@ For examples look at the [examples](./examples) subdirectory or the test files i ### Freestanding -| **Platform** | **Status** | **Notes** | -| :----------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------: | -| **ARM** | [![ARM](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-arm.yml/badge.svg)](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-arm.yml) | GCC 15 | -| **AVR** | [![AVR](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-avr.yml/badge.svg)](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-avr.yml) | GCC 15 | -| **MSP430** | [![MSP430](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-msp430.yml/badge.svg)](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-msp430.yml) | GCC 14 | -| **RISCV** | [![RISCV](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-riscv.yml/badge.svg)](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-riscv.yml) | GCC 15 | +| **Platform** | **Status** | **Notes** | +| :----------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------: | +| **ARM** | [![ARM](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-arm.yml/badge.svg)](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-arm.yml) | GCC 15 & Clang 20 | +| **AVR** | [![AVR](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-avr.yml/badge.svg)](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-avr.yml) | GCC 15 | +| **MSP430** | [![MSP430](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-msp430.yml/badge.svg)](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-msp430.yml) | GCC 14 | +| **RISCV** | [![RISCV](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-riscv.yml/badge.svg)](https://github.com/tobanteEmbedded/tetl/actions/workflows/freestanding-riscv.yml) | GCC 15 | ### Analysis diff --git a/cmake/toolchain/arm-atfe.cmake b/cmake/toolchain/arm-atfe.cmake new file mode 100644 index 000000000..367872f53 --- /dev/null +++ b/cmake/toolchain/arm-atfe.cmake @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: BSL-1.0 +# SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +include_guard(GLOBAL) + +set(ATFE_TOOLCHAIN "" CACHE STRING "Path to ARM toolchain for Embedded") + +if(NOT DEFINED ATFE_TOOLCHAIN) + message(FATAL_ERROR "Could not find ATfE. Use CMake variable ATFE_TOOLCHAIN") +else() + if(NOT EXISTS "${ATFE_TOOLCHAIN}/bin/clang") + message(FATAL_ERROR "ATFE_TOOLCHAIN does not point to a valid ATfE installation") + endif() +endif() + +set(CMAKE_SYSTEM_NAME Generic-ELF) +set(CMAKE_SYSTEM_PROCESSOR arm) + +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +set(CMAKE_C_COMPILER "${ATFE_TOOLCHAIN}/bin/clang") +set(CMAKE_CXX_COMPILER "${ATFE_TOOLCHAIN}/bin/clang++") + +set(ARCH_FLAGS "--target=armv6m-none-eabi -march=armv6m -mfpu=none -mfloat-abi=soft -flto") +set(CPP_FLAGS "-fno-exceptions -fno-rtti") + +set(CMAKE_C_FLAGS_DEBUG "${ARCH_FLAGS} -O0 -g3") +set(CMAKE_CXX_FLAGS_DEBUG "${ARCH_FLAGS} ${CPP_FLAGS} -O0 -g3") + +set(CMAKE_C_FLAGS_RELWITHDEBINFO "${ARCH_FLAGS} -O3 -g3 -DNDEBUG") +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${ARCH_FLAGS} ${CPP_FLAGS} -O3 -g3 -DNDEBUG") + +set(CMAKE_C_FLAGS_RELEASE "${ARCH_FLAGS} -O3 -DNDEBUG") +set(CMAKE_CXX_FLAGS_RELEASE "${ARCH_FLAGS} ${CPP_FLAGS} -O3 -DNDEBUG") + +set(CMAKE_C_FLAGS_MINSIZEREL "${ARCH_FLAGS} -Oz -g3 -DNDEBUG") +set(CMAKE_CXX_FLAGS_MINSIZEREL "${ARCH_FLAGS} ${CPP_FLAGS} -Oz -g3 -DNDEBUG") + +add_link_options( + -nostartfiles + -lcrt0-semihost + -lsemihost + -Wl,--gc-sections + -Wl,-T "${ATFE_TOOLCHAIN}/samples/ldscripts/microbit.ld" +) diff --git a/cmake/toolchain/clang-arm-none-eabi.cmake b/cmake/toolchain/arm-none-eabi-clang.cmake similarity index 99% rename from cmake/toolchain/clang-arm-none-eabi.cmake rename to cmake/toolchain/arm-none-eabi-clang.cmake index 227a3882f..2bcf6e32b 100644 --- a/cmake/toolchain/clang-arm-none-eabi.cmake +++ b/cmake/toolchain/arm-none-eabi-clang.cmake @@ -4,7 +4,7 @@ include_guard(GLOBAL) list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_LIST_DIR}") -include(stm32) +include(arm-none-eabi) execute_process( COMMAND arm-none-eabi-gcc ${MCU} -print-sysroot diff --git a/cmake/toolchain/gcc-arm-none-eabi.cmake b/cmake/toolchain/arm-none-eabi-gcc.cmake similarity index 98% rename from cmake/toolchain/gcc-arm-none-eabi.cmake rename to cmake/toolchain/arm-none-eabi-gcc.cmake index 497364912..a5ec798ad 100644 --- a/cmake/toolchain/gcc-arm-none-eabi.cmake +++ b/cmake/toolchain/arm-none-eabi-gcc.cmake @@ -4,7 +4,7 @@ include_guard(GLOBAL) list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_LIST_DIR}") -include(stm32) +include(arm-none-eabi) set(CMAKE_SYSTEM_PROCESSOR arm) set(CMAKE_SYSTEM_NAME Generic-ELF) diff --git a/cmake/toolchain/stm32.cmake b/cmake/toolchain/arm-none-eabi.cmake similarity index 100% rename from cmake/toolchain/stm32.cmake rename to cmake/toolchain/arm-none-eabi.cmake diff --git a/cmake/toolchain/atmega2560.cmake b/cmake/toolchain/avr-gcc.cmake similarity index 100% rename from cmake/toolchain/atmega2560.cmake rename to cmake/toolchain/avr-gcc.cmake diff --git a/cmake/toolchain/gcc-riscv-none-elf.cmake b/cmake/toolchain/riscv-none-elf-gcc.cmake similarity index 100% rename from cmake/toolchain/gcc-riscv-none-elf.cmake rename to cmake/toolchain/riscv-none-elf-gcc.cmake diff --git a/fuzzing/.gitignore b/fuzzing/.gitignore deleted file mode 100644 index 0a8cf6703..000000000 --- a/fuzzing/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# SPDX-License-Identifier: BSL-1.0 -# SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch -/crash-* -/lcov -/build -/bin -/*.info -/*.gcda -/*.gcno -/*.profraw diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt new file mode 100644 index 000000000..ec1019e27 --- /dev/null +++ b/fuzzing/CMakeLists.txt @@ -0,0 +1,108 @@ +# SPDX-License-Identifier: BSL-1.0 +# SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch +cmake_minimum_required(VERSION 3.28) +project(tetl-fuzzing LANGUAGES CXX) + +if(NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) + message(FATAL_ERROR "tetl-fuzzing needs to build with clang & libFuzzer") +endif() + +find_program(CCACHE ccache) +if(CCACHE) + set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE}) + set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) +endif() + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=fuzzer,address,undefined -ftest-coverage -fcoverage-mapping -fprofile-arcs -fprofile-instr-generate") +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +enable_testing() + +function(tetl_add_fuzz_test _target) + set(target_name "${_target}.fuzz") + + add_executable(${target_name} "src/${target_name}.cpp") + target_include_directories(${target_name} PRIVATE "./include" "../include") + target_compile_options(${target_name} PRIVATE + "-Werror" + "-Weverything" + "-Wno-c++98-compat-pedantic" + "-Wno-c++98-compat" + "-Wno-ctad-maybe-unsupported" + "-Wno-exit-time-destructors" + "-Wno-float-equal" + "-Wno-implicit-int-conversion" + "-Wno-missing-prototypes" + "-Wno-nrvo" + "-Wno-padded" + "-Wno-unsafe-buffer-usage" + ) + + add_test(NAME "${target_name}" COMMAND ${target_name} -max_total_time=60) + set_tests_properties("${target_name}" PROPERTIES ENVIRONMENT "LLVM_PROFILE_FILE=raw/%p-%m.profraw") + set_tests_properties("${target_name}" PROPERTIES WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") +endfunction() + +tetl_add_fuzz_test("algorithm.adjacent_find") +tetl_add_fuzz_test("algorithm.binary_search") +tetl_add_fuzz_test("algorithm.bubble_sort") +tetl_add_fuzz_test("algorithm.equal_range") +tetl_add_fuzz_test("algorithm.equal") +tetl_add_fuzz_test("algorithm.exchange_sort") +tetl_add_fuzz_test("algorithm.gnome_sort") +tetl_add_fuzz_test("algorithm.insertion_sort") +tetl_add_fuzz_test("algorithm.is_partitioned") +tetl_add_fuzz_test("algorithm.is_permutation") +tetl_add_fuzz_test("algorithm.lexicographical_compare") +tetl_add_fuzz_test("algorithm.max_element") +tetl_add_fuzz_test("algorithm.merge_sort") +tetl_add_fuzz_test("algorithm.min_element") +tetl_add_fuzz_test("algorithm.minmax_element") +tetl_add_fuzz_test("algorithm.mismatch") +# tetl_add_fuzz_test("algorithm.nth_element") +tetl_add_fuzz_test("algorithm.partition") +tetl_add_fuzz_test("algorithm.quick_sort") +tetl_add_fuzz_test("algorithm.reverse") +tetl_add_fuzz_test("algorithm.search") +tetl_add_fuzz_test("algorithm.set_difference") +tetl_add_fuzz_test("algorithm.set_intersection") +tetl_add_fuzz_test("algorithm.set_symmetric_difference") +tetl_add_fuzz_test("algorithm.set_union") +tetl_add_fuzz_test("algorithm.shift_left") +# tetl_add_fuzz_test("algorithm.shift_right") +tetl_add_fuzz_test("bit.bit_ceil") +tetl_add_fuzz_test("bit.bit_floor") +tetl_add_fuzz_test("bit.bit_width") +tetl_add_fuzz_test("bit.byteswap") +tetl_add_fuzz_test("bit.countl_one") +tetl_add_fuzz_test("bit.countl_zero") +tetl_add_fuzz_test("bit.countr_one") +tetl_add_fuzz_test("bit.countr_zero") +tetl_add_fuzz_test("bit.has_single_bit") +tetl_add_fuzz_test("bit.popcount") +tetl_add_fuzz_test("bitset") +tetl_add_fuzz_test("cctype") +tetl_add_fuzz_test("charconv.from_chars") +tetl_add_fuzz_test("charconv.to_chars") +tetl_add_fuzz_test("chrono.duration") +tetl_add_fuzz_test("chrono.rounding") +tetl_add_fuzz_test("chrono.year_month_day") +tetl_add_fuzz_test("cmath.lerp") +tetl_add_fuzz_test("cstring.strcmp") +tetl_add_fuzz_test("cstring.strlen") +tetl_add_fuzz_test("cstring.strncmp") +tetl_add_fuzz_test("cstring.strstr") +tetl_add_fuzz_test("numeric.midpoint") +tetl_add_fuzz_test("random.uniform_int_distribution") +tetl_add_fuzz_test("random.uniform_real_distribution") +tetl_add_fuzz_test("string_view.contains") +tetl_add_fuzz_test("string_view.ends_with") +tetl_add_fuzz_test("string_view.find_first_not_of") +tetl_add_fuzz_test("string_view.find_first_of") +tetl_add_fuzz_test("string_view.find_last_not_of") +tetl_add_fuzz_test("string_view.find_last_of") +tetl_add_fuzz_test("string_view.find") +tetl_add_fuzz_test("string_view.rfind") +tetl_add_fuzz_test("string_view.starts_with") +tetl_add_fuzz_test("string") diff --git a/fuzzing/Makefile b/fuzzing/Makefile deleted file mode 100644 index 57432e3c8..000000000 --- a/fuzzing/Makefile +++ /dev/null @@ -1,41 +0,0 @@ -# SPDX-License-Identifier: BSL-1.0 -# SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch - -CXX = clang++-21 -CXXFLAGS = -std=c++26 -g3 -O1 -fsanitize=fuzzer,address,undefined -ftest-coverage -fcoverage-mapping -fprofile-arcs -fprofile-instr-generate -LCOV = lcov --gcov-tool ../scripts/llvm-gcov.sh -MAXTIME ?= 20 - -.PHONY: bin -bin: - mkdir -p bin - -.PHONY: build -build: bin - $(CXX) $(CXXFLAGS) -I ../include -o bin/algorithm.fuzz src/algorithm.fuzz.cpp - $(CXX) $(CXXFLAGS) -I ../include -o bin/bitset.fuzz src/bitset.fuzz.cpp - $(CXX) $(CXXFLAGS) -I ../include -o bin/from_chars.fuzz src/from_chars.fuzz.cpp - $(CXX) $(CXXFLAGS) -I ../include -o bin/string_view.fuzz src/string_view.fuzz.cpp - $(CXX) $(CXXFLAGS) -I ../include -o bin/to_chars.fuzz src/to_chars.fuzz.cpp - -.PHONY: fuzz -fuzz: build - $(LCOV) -c -i -d . --base-directory . -o bin/base_cov.info --ignore-errors inconsistent - LLVM_PROFILE_FILE="bin/%p-%m.profraw" ./bin/algorithm.fuzz -max_total_time=$(MAXTIME) - LLVM_PROFILE_FILE="bin/%p-%m.profraw" ./bin/bitset.fuzz -max_total_time=$(MAXTIME) - LLVM_PROFILE_FILE="bin/%p-%m.profraw" ./bin/from_chars.fuzz -max_total_time=$(MAXTIME) - LLVM_PROFILE_FILE="bin/%p-%m.profraw" ./bin/string_view.fuzz -max_total_time=$(MAXTIME) - LLVM_PROFILE_FILE="bin/%p-%m.profraw" ./bin/to_chars.fuzz -max_total_time=$(MAXTIME) - $(LCOV) -c -d . --base-directory . -o bin/fuzz_cov.info --ignore-errors inconsistent - $(LCOV) -a bin/base_cov.info -a bin/fuzz_cov.info -o bin/cov.info --ignore-errors inconsistent - $(LCOV) --remove bin/cov.info "*clang*" -o bin/cov.info --ignore-errors inconsistent - $(LCOV) --remove bin/cov.info "*c++*" -o bin/cov.info --ignore-errors inconsistent - - -.PHONY: report -report: fuzz - genhtml bin/cov.info --output-directory lcov --ignore-errors inconsistent - -.PHONY: clean -clean: - rm -rf bin lcov *.profraw diff --git a/fuzzing/src/fuzzing.hpp b/fuzzing/include/fuzzing.hpp similarity index 82% rename from fuzzing/src/fuzzing.hpp rename to fuzzing/include/fuzzing.hpp index 021c2b556..d91d6455b 100644 --- a/fuzzing/src/fuzzing.hpp +++ b/fuzzing/include/fuzzing.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -18,13 +19,13 @@ #define RUN(func) \ do { \ if (auto rc = func; rc != 0) { \ - throw rc; \ + throw std::runtime_error{std::format("fuzz failure: {}", rc)}; \ } \ } while (false) namespace etl::fuzzing { -auto to_string(std::errc ec) -> std::string +inline auto to_string(std::errc ec) -> std::string { static auto map = std::map{ { std::errc{}, "errc{}"}, @@ -36,7 +37,7 @@ auto to_string(std::errc ec) -> std::string return map.at(ec); } -auto to_std(etl::errc ec) -> std::errc +inline auto to_std(etl::errc ec) -> std::errc { static auto map = std::map{ { etl::errc{}, std::errc{}}, @@ -47,11 +48,11 @@ auto to_std(etl::errc ec) -> std::errc return map.at(ec); } -auto to_std(etl::from_chars_result r) -> std::from_chars_result +inline auto to_std(etl::from_chars_result r) -> std::from_chars_result { return {.ptr = r.ptr, .ec = to_std(r.ec)}; } -auto to_std(etl::to_chars_result r) -> std::to_chars_result +inline auto to_std(etl::to_chars_result r) -> std::to_chars_result { return {.ptr = r.ptr, .ec = to_std(r.ec)}; } diff --git a/fuzzing/include/sorting.hpp b/fuzzing/include/sorting.hpp new file mode 100644 index 000000000..605fe540a --- /dev/null +++ b/fuzzing/include/sorting.hpp @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch +#pragma once + +#include "fuzzing.hpp" + +#include +#include +#include + +#include +#include + +[[nodiscard]] inline auto test_sort(FuzzedDataProvider& p, auto sorter) -> int +{ + auto data = p.ConsumeRemainingBytes(); + auto view = etl::span{data.data(), data.size()}; + + sorter(view.begin(), view.end(), etl::less()); + if (not std::is_sorted(view.begin(), view.end(), etl::less())) { + std::println(stderr, "Data is not sorted via etl::less. Size = {}", view.size()); + return 1; + } + + sorter(view.begin(), view.end(), etl::greater()); + if (not std::is_sorted(view.begin(), view.end(), etl::greater())) { + std::println(stderr, "Data is not sorted via etl::greater. Size = {}", view.size()); + return 1; + } + + return 0; +} + +#define SORT_FUZZ_MAIN(sorter) \ + extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int \ + { \ + auto p = FuzzedDataProvider{data, size}; \ + RUN(test_sort(p, [](auto f, auto l, auto cmp) { sorter(f, l, cmp); })); \ + return 0; \ + } diff --git a/fuzzing/run.sh b/fuzzing/run.sh new file mode 100755 index 000000000..ca79c435a --- /dev/null +++ b/fuzzing/run.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# SPDX-License-Identifier: BSL-1.0 +# SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +set -e + +export CC="clang-21" +export CXX="clang++-21" + +export CXXFLAGS="-march=native" +export CMAKE_BUILD_TYPE="RelWithDebInfo" +export CMAKE_GENERATOR="Ninja" + +BUILD_DIR="cmake-build-fuzzing" +BRANCH_COVERAGE=0 + +rm -rf "$BUILD_DIR" + +cmake -S fuzzing -B "$BUILD_DIR" -D CMAKE_CXX_STANDARD=26 -D CMAKE_CXX_SCAN_FOR_MODULES=OFF +cmake --build "$BUILD_DIR" + +lcov --gcov-tool ./scripts/llvm-gcov.sh -c -i -d "$BUILD_DIR" --base-directory fuzzing -o "$BUILD_DIR/base_cov.info" --ignore-errors inconsistent --rc branch_coverage=$BRANCH_COVERAGE + +ctest --test-dir "$BUILD_DIR" --output-on-failure -j $(nproc) + +lcov --gcov-tool ./scripts/llvm-gcov.sh -c -d "$BUILD_DIR" --base-directory fuzzing -o "$BUILD_DIR/fuzz_cov.info" --ignore-errors inconsistent --rc branch_coverage=$BRANCH_COVERAGE +lcov --gcov-tool ./scripts/llvm-gcov.sh -a "$BUILD_DIR/base_cov.info" -a "$BUILD_DIR/fuzz_cov.info" -o "$BUILD_DIR/cov.info" --ignore-errors inconsistent --rc branch_coverage=$BRANCH_COVERAGE +lcov --gcov-tool ./scripts/llvm-gcov.sh --remove "$BUILD_DIR/cov.info" "*fuzzing*" -o "$BUILD_DIR/cov.info" --ignore-errors inconsistent --rc branch_coverage=$BRANCH_COVERAGE +lcov --gcov-tool ./scripts/llvm-gcov.sh --remove "$BUILD_DIR/cov.info" "*clang*" -o "$BUILD_DIR/cov.info" --ignore-errors inconsistent --rc branch_coverage=$BRANCH_COVERAGE +lcov --gcov-tool ./scripts/llvm-gcov.sh --remove "$BUILD_DIR/cov.info" "*c++*" -o "$BUILD_DIR/cov.info" --ignore-errors inconsistent --rc branch_coverage=$BRANCH_COVERAGE + +genhtml "$BUILD_DIR/cov.info" --output-directory "$BUILD_DIR/html" --ignore-errors inconsistent --rc genhtml_branch_coverage=$BRANCH_COVERAGE diff --git a/fuzzing/src/algorithm.adjacent_find.fuzz.cpp b/fuzzing/src/algorithm.adjacent_find.fuzz.cpp new file mode 100644 index 000000000..60545f4a2 --- /dev/null +++ b/fuzzing/src/algorithm.adjacent_find.fuzz.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include + +#include + +[[nodiscard]] static auto fuzz_adjacent_find(FuzzedDataProvider& p) -> int +{ + auto const bytes = p.ConsumeRemainingBytes(); + auto const view = etl::span{bytes.data(), bytes.size()}; + + auto const e = etl::adjacent_find(view.begin(), view.end()); + auto const s = std::adjacent_find(view.begin(), view.end()); + if (e != s) { + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_adjacent_find(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.binary_search.fuzz.cpp b/fuzzing/src/algorithm.binary_search.fuzz.cpp new file mode 100644 index 000000000..207849414 --- /dev/null +++ b/fuzzing/src/algorithm.binary_search.fuzz.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include + +#include + +[[nodiscard]] static auto fuzz_binary_search(FuzzedDataProvider& p) -> int +{ + auto const needle = p.ConsumeIntegral(); + auto const haystack = p.ConsumeRemainingBytes(); + auto const view = etl::span{haystack.data(), haystack.size()}; + + auto const e = etl::binary_search(view.begin(), view.end(), needle); + auto const s = std::binary_search(view.begin(), view.end(), needle); + if (e != s) { + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_binary_search(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.bubble_sort.fuzz.cpp b/fuzzing/src/algorithm.bubble_sort.fuzz.cpp new file mode 100644 index 000000000..82681c7f7 --- /dev/null +++ b/fuzzing/src/algorithm.bubble_sort.fuzz.cpp @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "sorting.hpp" + +SORT_FUZZ_MAIN(etl::bubble_sort) diff --git a/fuzzing/src/algorithm.equal.fuzz.cpp b/fuzzing/src/algorithm.equal.fuzz.cpp new file mode 100644 index 000000000..afca0f507 --- /dev/null +++ b/fuzzing/src/algorithm.equal.fuzz.cpp @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include + +#include +#include + +template +[[nodiscard]] static auto fuzz_equal(FuzzedDataProvider& p) -> int +{ + auto const a = p.ConsumeRandomLengthString(); + auto const b = p.ConsumeRandomLengthString(); + + auto const av = etl::string_view{a.data(), a.size()}; + auto const bv = etl::string_view{b.data(), b.size()}; + + auto const e = etl::equal(av.begin(), av.end(), bv.begin(), bv.end()); + auto const s = std::equal(av.begin(), av.end(), bv.begin(), bv.end()); + + if (e != s) { + std::println(stderr, "equal: '{}' vs. '{}'", a, b); + std::println(stderr, "etl: {}", e); + std::println(stderr, "std: {}", s); + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_equal(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.equal_range.fuzz.cpp b/fuzzing/src/algorithm.equal_range.fuzz.cpp new file mode 100644 index 000000000..34d5984af --- /dev/null +++ b/fuzzing/src/algorithm.equal_range.fuzz.cpp @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include + +#include + +[[nodiscard]] static auto fuzz_equal_range(FuzzedDataProvider& p) -> int +{ + auto const needle = p.ConsumeIntegral(); + auto const bytes = p.ConsumeRemainingBytes(); + auto const view = etl::span{bytes.data(), bytes.size()}; + + auto const [el, eu] = etl::equal_range(view.begin(), view.end(), needle); + auto const [sl, su] = std::equal_range(view.begin(), view.end(), needle); + if (el != sl or eu != su) { + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_equal_range(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.exchange_sort.fuzz.cpp b/fuzzing/src/algorithm.exchange_sort.fuzz.cpp new file mode 100644 index 000000000..db49861ef --- /dev/null +++ b/fuzzing/src/algorithm.exchange_sort.fuzz.cpp @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "sorting.hpp" + +SORT_FUZZ_MAIN(etl::exchange_sort) diff --git a/fuzzing/src/algorithm.fuzz.cpp b/fuzzing/src/algorithm.fuzz.cpp deleted file mode 100644 index 7e6b9debb..000000000 --- a/fuzzing/src/algorithm.fuzz.cpp +++ /dev/null @@ -1,215 +0,0 @@ -// SPDX-License-Identifier: BSL-1.0 -// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch - -#include "fuzzing.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -template -[[nodiscard]] auto test_sort_integers(FuzzedDataProvider& p) -> int -{ - auto generator = [&p] { return p.ConsumeIntegral(); }; - auto vec = etl::static_vector{}; - etl::generate_n(etl::back_inserter(vec), vec.capacity(), generator); - - auto etlSet = etl::static_set{begin(vec), end(vec)}; - auto stdSet = std::set{begin(vec), end(vec)}; - if (etlSet.size() != stdSet.size()) { - return 1; - } - - etl::sort(begin(vec), end(vec)); - if (not etl::is_sorted(begin(vec), end(vec))) { - return 1; - } - - return 0; -} - -template -[[nodiscard]] auto test_sort_floats(FuzzedDataProvider& p) -> int -{ - auto generator = [&p] { return p.ConsumeFloatingPoint(); }; - auto vec = etl::static_vector{}; - etl::generate_n(etl::back_inserter(vec), vec.capacity(), generator); - - auto etlSet = etl::static_set{begin(vec), end(vec)}; - auto stdSet = std::set{begin(vec), end(vec)}; - if (etlSet.size() != stdSet.size()) { - return 1; - } - - etl::sort(begin(vec), end(vec)); - if (not etl::is_sorted(begin(vec), end(vec))) { - return 1; - } - - return 0; -} - -template -[[nodiscard]] auto test_search_integers(FuzzedDataProvider& p) -> int -{ - auto generator = [&p] { return p.ConsumeIntegral(); }; - auto src = etl::static_vector{}; - etl::generate_n(etl::back_inserter(src), src.capacity(), generator); - - auto objs = etl::static_vector{}; - etl::generate_n(etl::back_inserter(objs), objs.capacity(), generator); - - auto e = etl::search(begin(src), end(src), begin(objs), end(objs)); - auto s = std::search(begin(src), end(src), begin(objs), end(objs)); - if (e != s) { - return 1; - } - - return 0; -} - -template -[[nodiscard]] auto test_mismatch_integers(FuzzedDataProvider& p) -> int -{ - auto generator = [&p] { return p.ConsumeIntegral(); }; - auto src = etl::static_vector{}; - etl::generate_n(etl::back_inserter(src), src.capacity(), generator); - - auto objs = etl::static_vector{}; - etl::generate_n(etl::back_inserter(objs), objs.capacity(), generator); - - auto e = etl::mismatch(begin(src), end(src), begin(objs), end(objs)); - auto s = std::mismatch(begin(src), end(src), begin(objs), end(objs)); - if ((e.first != s.first) or (e.second != s.second)) { - return 1; - } - - return 0; -} - -template -[[nodiscard]] auto test_max_element_integers(FuzzedDataProvider& p) -> int -{ - auto generator = [&p] { return p.ConsumeIntegral(); }; - auto src = etl::static_vector{}; - etl::generate_n(etl::back_inserter(src), src.capacity(), generator); - - auto e = etl::max_element(begin(src), end(src)); - auto s = std::max_element(begin(src), end(src)); - if (e != s) { - return 1; - } - - return 0; -} - -template -[[nodiscard]] auto test_equal_integers(FuzzedDataProvider& p) -> int -{ - auto generator = [&p] { return p.ConsumeIntegral(); }; - auto lhs = etl::static_vector{}; - etl::generate_n(etl::back_inserter(lhs), lhs.capacity(), generator); - - auto rhs = etl::static_vector{}; - etl::generate_n(etl::back_inserter(rhs), rhs.capacity(), generator); - - auto e = etl::equal(begin(lhs), end(lhs), begin(rhs), end(rhs)); - auto s = std::equal(begin(lhs), end(lhs), begin(rhs), end(rhs)); - if (e != s) { - return 1; - } - - return 0; -} - -[[nodiscard]] auto test_string(FuzzedDataProvider& p) -> int -{ - auto const chars = p.ConsumeBytesWithTerminator(127, 0); - - auto etlString = etl::inplace_string<128>{}; - etl::copy(chars.begin(), chars.end(), etl::back_inserter(etlString)); - - auto stdString = std::string{chars.begin(), chars.end()}; - - if (etlString.size() != stdString.size()) { - return 1; - } - if (etl::strlen(chars.data()) != std::strlen(chars.data())) { - return 1; - } - - return 0; -} - -extern "C" auto LLVMFuzzerTestOneInput(etl::uint8_t const* data, etl::size_t size) -> int -{ - if (size == 0) { - return 0; - } - auto p = FuzzedDataProvider{data, size}; - - RUN(test_sort_integers(p)); - RUN(test_sort_integers(p)); - RUN(test_sort_integers(p)); - RUN(test_sort_integers(p)); - - RUN(test_sort_integers(p)); - RUN(test_sort_integers(p)); - RUN(test_sort_integers(p)); - RUN(test_sort_integers(p)); - - RUN(test_sort_floats(p)); - RUN(test_sort_floats(p)); - RUN(test_sort_floats(p)); - - RUN(test_search_integers(p)); - RUN(test_search_integers(p)); - RUN(test_search_integers(p)); - RUN(test_search_integers(p)); - - RUN(test_search_integers(p)); - RUN(test_search_integers(p)); - RUN(test_search_integers(p)); - RUN(test_search_integers(p)); - - RUN(test_mismatch_integers(p)); - RUN(test_mismatch_integers(p)); - RUN(test_mismatch_integers(p)); - RUN(test_mismatch_integers(p)); - - RUN(test_mismatch_integers(p)); - RUN(test_mismatch_integers(p)); - RUN(test_mismatch_integers(p)); - RUN(test_mismatch_integers(p)); - - RUN(test_max_element_integers(p)); - RUN(test_max_element_integers(p)); - RUN(test_max_element_integers(p)); - RUN(test_max_element_integers(p)); - - RUN(test_max_element_integers(p)); - RUN(test_max_element_integers(p)); - RUN(test_max_element_integers(p)); - RUN(test_max_element_integers(p)); - - RUN(test_equal_integers(p)); - RUN(test_equal_integers(p)); - RUN(test_equal_integers(p)); - RUN(test_equal_integers(p)); - - RUN(test_equal_integers(p)); - RUN(test_equal_integers(p)); - RUN(test_equal_integers(p)); - RUN(test_equal_integers(p)); - - RUN(test_string(p)); - - return 0; -} diff --git a/fuzzing/src/algorithm.gnome_sort.fuzz.cpp b/fuzzing/src/algorithm.gnome_sort.fuzz.cpp new file mode 100644 index 000000000..a5eaa160e --- /dev/null +++ b/fuzzing/src/algorithm.gnome_sort.fuzz.cpp @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "sorting.hpp" + +SORT_FUZZ_MAIN(etl::gnome_sort) diff --git a/fuzzing/src/algorithm.insertion_sort.fuzz.cpp b/fuzzing/src/algorithm.insertion_sort.fuzz.cpp new file mode 100644 index 000000000..f484f3274 --- /dev/null +++ b/fuzzing/src/algorithm.insertion_sort.fuzz.cpp @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "sorting.hpp" + +SORT_FUZZ_MAIN(etl::insertion_sort) diff --git a/fuzzing/src/algorithm.is_partitioned.fuzz.cpp b/fuzzing/src/algorithm.is_partitioned.fuzz.cpp new file mode 100644 index 000000000..e4b57c4ed --- /dev/null +++ b/fuzzing/src/algorithm.is_partitioned.fuzz.cpp @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include +#include +#include + +#include +#include + +[[nodiscard]] static auto fuzz_reverse(FuzzedDataProvider& p) -> int +{ + auto const str = p.ConsumeRandomLengthString(); + auto const view = etl::string_view{str.data(), str.size()}; + + auto const e = etl::is_partitioned(view.begin(), view.end(), [](char c) { return c < 'a'; }); + auto const s = std::is_partitioned(view.begin(), view.end(), [](char c) { return c < 'a'; }); + + if (e != s) { + std::println(stderr, "is_partitioned: '{}'", str); + std::println(stderr, "etl: '{}'", e); + std::println(stderr, "std: '{}'", s); + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_reverse(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.is_permutation.fuzz.cpp b/fuzzing/src/algorithm.is_permutation.fuzz.cpp new file mode 100644 index 000000000..a13388891 --- /dev/null +++ b/fuzzing/src/algorithm.is_permutation.fuzz.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include + +#include + +[[nodiscard]] static auto fuzz_is_permutation(FuzzedDataProvider& p) -> int +{ + auto const a = p.ConsumeBytes(64); + auto const b = p.ConsumeBytes(64); + + auto const va = etl::span{a.data(), a.size()}; + auto const vb = etl::span{b.data(), b.size()}; + + auto const e = etl::is_permutation(va.begin(), va.end(), vb.begin(), vb.end()); + auto const s = std::is_permutation(va.begin(), va.end(), vb.begin(), vb.end()); + if (e != s) { + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_is_permutation(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.lexicographical_compare.fuzz.cpp b/fuzzing/src/algorithm.lexicographical_compare.fuzz.cpp new file mode 100644 index 000000000..8561ddad0 --- /dev/null +++ b/fuzzing/src/algorithm.lexicographical_compare.fuzz.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include + +#include + +[[nodiscard]] static auto fuzz_lexicographical_compare(FuzzedDataProvider& p) -> int +{ + auto const a = p.ConsumeRandomLengthString(); + auto const b = p.ConsumeRandomLengthString(); + + auto const va = etl::string_view{a.data(), a.size()}; + auto const vb = etl::string_view{b.data(), b.size()}; + + auto const e = etl::lexicographical_compare(va.begin(), va.end(), vb.begin(), vb.end()); + auto const s = std::lexicographical_compare(va.begin(), va.end(), vb.begin(), vb.end()); + if (e != s) { + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_lexicographical_compare(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.max_element.fuzz.cpp b/fuzzing/src/algorithm.max_element.fuzz.cpp new file mode 100644 index 000000000..5d52b8381 --- /dev/null +++ b/fuzzing/src/algorithm.max_element.fuzz.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include + +#include + +[[nodiscard]] static auto fuzz_max_element(FuzzedDataProvider& p) -> int +{ + auto const bytes = p.ConsumeRemainingBytes(); + auto const view = etl::span{bytes.data(), bytes.size()}; + + auto const e = etl::max_element(view.begin(), view.end()); + auto const s = std::max_element(view.begin(), view.end()); + if (e != s) { + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_max_element(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.merge_sort.fuzz.cpp b/fuzzing/src/algorithm.merge_sort.fuzz.cpp new file mode 100644 index 000000000..6f28524d9 --- /dev/null +++ b/fuzzing/src/algorithm.merge_sort.fuzz.cpp @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "sorting.hpp" + +SORT_FUZZ_MAIN(etl::merge_sort) diff --git a/fuzzing/src/algorithm.min_element.fuzz.cpp b/fuzzing/src/algorithm.min_element.fuzz.cpp new file mode 100644 index 000000000..cb61667b3 --- /dev/null +++ b/fuzzing/src/algorithm.min_element.fuzz.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include + +#include + +[[nodiscard]] static auto fuzz_min_element(FuzzedDataProvider& p) -> int +{ + auto const bytes = p.ConsumeRemainingBytes(); + auto const view = etl::span{bytes.data(), bytes.size()}; + + auto const e = etl::min_element(view.begin(), view.end()); + auto const s = std::min_element(view.begin(), view.end()); + if (e != s) { + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_min_element(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.minmax_element.fuzz.cpp b/fuzzing/src/algorithm.minmax_element.fuzz.cpp new file mode 100644 index 000000000..c5a5002c8 --- /dev/null +++ b/fuzzing/src/algorithm.minmax_element.fuzz.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include + +#include + +[[nodiscard]] static auto fuzz_minmax_element(FuzzedDataProvider& p) -> int +{ + auto const bytes = p.ConsumeRemainingBytes(); + auto const view = etl::span{bytes.data(), bytes.size()}; + + auto const [emin, emax] = etl::minmax_element(view.begin(), view.end()); + auto const [smin, smax] = std::minmax_element(view.begin(), view.end()); + if (emin != smin or emax != smax) { + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_minmax_element(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.mismatch.fuzz.cpp b/fuzzing/src/algorithm.mismatch.fuzz.cpp new file mode 100644 index 000000000..d3e9615bc --- /dev/null +++ b/fuzzing/src/algorithm.mismatch.fuzz.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include +#include + +template +[[nodiscard]] static auto fuzz_mismatch(FuzzedDataProvider& p) -> int +{ + auto const a = p.ConsumeRandomLengthString(); + auto const b = p.ConsumeRandomLengthString(); + + auto const e = etl::mismatch(a.begin(), a.end(), b.begin(), b.end()); + auto const s = std::mismatch(a.begin(), a.end(), b.begin(), b.end()); + if ((e.first != s.first) or (e.second != s.second)) { + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_mismatch(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.nth_element.fuzz.cpp b/fuzzing/src/algorithm.nth_element.fuzz.cpp new file mode 100644 index 000000000..b90245edc --- /dev/null +++ b/fuzzing/src/algorithm.nth_element.fuzz.cpp @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include +#include + +#include +#include + +[[nodiscard]] static auto fuzz_nth_element(FuzzedDataProvider& p) -> int +{ + auto const original = p.ConsumeRandomLengthString(8); + auto const pos = p.ConsumeIntegralInRange(0, original.empty() ? 0 : original.size() - 1); + + if (std::ranges::any_of(original, [](char c) { return std::isspace(c) != 0; })) { + return 0; + } + // std::transform(original.begin(), original.end(), original.begin(), [](char c) { return std::isspace(c) ? '' : c; + // }); + + auto estr = original; + auto const view = etl::span{estr.data(), estr.size()}; + etl::nth_element(view.begin(), view.begin() + static_cast(pos), view.end()); + if (view.empty()) { + return 0; + } + + auto sstr = original; + std::nth_element(sstr.begin(), sstr.begin() + static_cast(pos), sstr.end()); + + if (estr[pos] != sstr[pos]) { + std::println(stderr, "nth_element mismatch at pos {}, etl: {}, std: {}", pos, estr[pos], sstr[pos]); + std::println(stderr, "str: '{}'", original); + std::println(stderr, "etl: '{}'", estr); + std::println(stderr, "std: '{}'", sstr); + return 1; + } + + // for (auto i{view.begin()}; i < nth; ++i) { + // for (auto j{nth}; j < view.end(); ++j) { + // if (*j < *i) { + // std::println(stderr, "nth_element: Not partitioned '{}' pos {}, size {} ", str, pos, + // original.size()); return 1; + // } + // } + // } + + // auto const nthValue = *nth; + // std::sort(view.begin(), view.end()); + // if (nthValue != *nth) { + // std::println( + // stderr, + // "nth_element: nth value incorrect at {} Is: '{}', should: '{}'\n'{}'\n'{}'", + // pos, + // nthValue, + // *nth, + // original, + // str + // ); + // return 1; + // } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_nth_element(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.partition.fuzz.cpp b/fuzzing/src/algorithm.partition.fuzz.cpp new file mode 100644 index 000000000..a6211bfa9 --- /dev/null +++ b/fuzzing/src/algorithm.partition.fuzz.cpp @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include + +#include + +[[nodiscard]] static auto fuzz_partition(FuzzedDataProvider& p) -> int +{ + auto ebytes = p.ConsumeRemainingBytes(); + auto sbytes = ebytes; + + auto const eview = etl::span{ebytes.data(), ebytes.size()}; + auto const sview = etl::span{sbytes.data(), sbytes.size()}; + + auto const predicate = [](unsigned char x) -> bool { return x < 42; }; + auto const e = etl::partition(eview.begin(), eview.end(), predicate); + auto const s = std::partition(sview.begin(), sview.end(), predicate); + if (std::distance(eview.begin(), e) != std::distance(sview.begin(), s)) { + return 1; + } + + if (not etl::is_partitioned(eview.begin(), eview.end(), predicate)) { + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_partition(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.quick_sort.fuzz.cpp b/fuzzing/src/algorithm.quick_sort.fuzz.cpp new file mode 100644 index 000000000..a585b373d --- /dev/null +++ b/fuzzing/src/algorithm.quick_sort.fuzz.cpp @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "sorting.hpp" + +SORT_FUZZ_MAIN(etl::quick_sort) diff --git a/fuzzing/src/algorithm.reverse.fuzz.cpp b/fuzzing/src/algorithm.reverse.fuzz.cpp new file mode 100644 index 000000000..f504f0b94 --- /dev/null +++ b/fuzzing/src/algorithm.reverse.fuzz.cpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include +#include +#include + +#include +#include + +[[nodiscard]] static auto fuzz_reverse(FuzzedDataProvider& p) -> int +{ + auto const str = p.ConsumeRandomLengthString(); + + auto estr = str; + auto sstr = str; + + auto const e = etl::span{estr.data(), estr.size()}; + auto const s = etl::span{sstr.data(), sstr.size()}; + + etl::reverse(e.begin(), e.end()); + std::reverse(s.begin(), s.end()); + + if (estr != sstr) { + std::println(stderr, "reverse: '{}'", str); + std::println(stderr, "std: '{}'", sstr); + std::println(stderr, "etl: '{}'", estr); + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_reverse(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.search.fuzz.cpp b/fuzzing/src/algorithm.search.fuzz.cpp new file mode 100644 index 000000000..9bcfbcaf4 --- /dev/null +++ b/fuzzing/src/algorithm.search.fuzz.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include +#include + +template +[[nodiscard]] static auto fuzz_search(FuzzedDataProvider& p) -> int +{ + auto const haystack = p.ConsumeRandomLengthString(); + auto const needle = p.ConsumeRandomLengthString(); + + auto const h = etl::span{haystack.data(), haystack.size()}; + auto const n = etl::span{needle.data(), needle.size()}; + + auto const s = std::search(h.begin(), h.end(), n.begin(), n.end()); + auto const e = etl::search(h.begin(), h.end(), n.begin(), n.end()); + auto const d = etl::search(h.begin(), h.end(), etl::default_searcher(n.begin(), n.end())); + if (e != s or d != s) { + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_search(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.set_difference.fuzz.cpp b/fuzzing/src/algorithm.set_difference.fuzz.cpp new file mode 100644 index 000000000..0afaa5073 --- /dev/null +++ b/fuzzing/src/algorithm.set_difference.fuzz.cpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include +#include +#include + +#include +#include + +[[nodiscard]] static auto fuzz_set_difference(FuzzedDataProvider& p) -> int +{ + auto const a = p.ConsumeRandomLengthString(); + auto const b = p.ConsumeRandomLengthString(); + auto const aview = etl::span{a.c_str(), a.size()}; + auto const bview = etl::span{b.c_str(), b.size()}; + + auto eout = std::string{}; + auto sout = std::string{}; + + etl::set_difference(aview.begin(), aview.end(), bview.begin(), bview.end(), std::back_inserter(eout)); + std::set_difference(aview.begin(), aview.end(), bview.begin(), bview.end(), std::back_inserter(sout)); + + if (eout != sout) { + std::println(stderr, "set_difference: '{}' and '{}'", a, b); + std::println(stderr, "etl: '{}'", eout); + std::println(stderr, "std: '{}'", sout); + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_set_difference(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.set_intersection.fuzz.cpp b/fuzzing/src/algorithm.set_intersection.fuzz.cpp new file mode 100644 index 000000000..211480e8a --- /dev/null +++ b/fuzzing/src/algorithm.set_intersection.fuzz.cpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include +#include +#include + +#include +#include + +[[nodiscard]] static auto fuzz_set_intersection(FuzzedDataProvider& p) -> int +{ + auto const a = p.ConsumeRandomLengthString(); + auto const b = p.ConsumeRandomLengthString(); + auto const aview = etl::span{a.c_str(), a.size()}; + auto const bview = etl::span{b.c_str(), b.size()}; + + auto eout = std::string{}; + auto sout = std::string{}; + + etl::set_intersection(aview.begin(), aview.end(), bview.begin(), bview.end(), std::back_inserter(eout)); + std::set_intersection(aview.begin(), aview.end(), bview.begin(), bview.end(), std::back_inserter(sout)); + + if (eout != sout) { + std::println(stderr, "set_intersection: '{}' and '{}'", a, b); + std::println(stderr, "etl: '{}'", eout); + std::println(stderr, "std: '{}'", sout); + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_set_intersection(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.set_symmetric_difference.fuzz.cpp b/fuzzing/src/algorithm.set_symmetric_difference.fuzz.cpp new file mode 100644 index 000000000..c520819f7 --- /dev/null +++ b/fuzzing/src/algorithm.set_symmetric_difference.fuzz.cpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include +#include +#include + +#include +#include + +[[nodiscard]] static auto fuzz_set_symmetric_difference(FuzzedDataProvider& p) -> int +{ + auto const a = p.ConsumeRandomLengthString(); + auto const b = p.ConsumeRandomLengthString(); + auto const aview = etl::span{a.c_str(), a.size()}; + auto const bview = etl::span{b.c_str(), b.size()}; + + auto eout = std::string{}; + auto sout = std::string{}; + + etl::set_symmetric_difference(aview.begin(), aview.end(), bview.begin(), bview.end(), std::back_inserter(eout)); + std::set_symmetric_difference(aview.begin(), aview.end(), bview.begin(), bview.end(), std::back_inserter(sout)); + + if (eout != sout) { + std::println(stderr, "set_symmetric_difference: '{}' and '{}'", a, b); + std::println(stderr, "etl: '{}'", eout); + std::println(stderr, "std: '{}'", sout); + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_set_symmetric_difference(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.set_union.fuzz.cpp b/fuzzing/src/algorithm.set_union.fuzz.cpp new file mode 100644 index 000000000..aac8b0a8d --- /dev/null +++ b/fuzzing/src/algorithm.set_union.fuzz.cpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include +#include +#include + +#include +#include + +[[nodiscard]] static auto fuzz_set_union(FuzzedDataProvider& p) -> int +{ + auto const a = p.ConsumeRandomLengthString(); + auto const b = p.ConsumeRandomLengthString(); + auto const aview = etl::span{a.c_str(), a.size()}; + auto const bview = etl::span{b.c_str(), b.size()}; + + auto eout = std::string{}; + auto sout = std::string{}; + + etl::set_union(aview.begin(), aview.end(), bview.begin(), bview.end(), std::back_inserter(eout)); + std::set_union(aview.begin(), aview.end(), bview.begin(), bview.end(), std::back_inserter(sout)); + + if (eout != sout) { + std::println(stderr, "set_union: '{}' and '{}'", a, b); + std::println(stderr, "etl: '{}'", eout); + std::println(stderr, "std: '{}'", sout); + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_set_union(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.shift_left.fuzz.cpp b/fuzzing/src/algorithm.shift_left.fuzz.cpp new file mode 100644 index 000000000..c4babce87 --- /dev/null +++ b/fuzzing/src/algorithm.shift_left.fuzz.cpp @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include +#include +#include + +#include +#include + +[[nodiscard]] static auto fuzz_shift_left(FuzzedDataProvider& p) -> int +{ + auto shift = p.ConsumeIntegralInRange(0, etl::numeric_limits::max()); + auto ebytes = p.ConsumeRemainingBytes(); + auto sbytes = ebytes; + + auto const eview = etl::span{ebytes.data(), ebytes.size()}; + auto const sview = etl::span{sbytes.data(), sbytes.size()}; + + auto const e = etl::shift_left(eview.begin(), eview.end(), shift); + auto const s = std::shift_left(sview.begin(), sview.end(), shift); + + auto const ed = std::distance(eview.begin(), e); + auto const sd = std::distance(sview.begin(), s); + if ((ed != sd) or not std::equal(eview.begin(), e, sview.begin(), s)) { + std::println(stderr, "size: {}, shift: {}, s: {}, e: {}", eview.size(), shift, sd, ed); + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_shift_left(p)); + return 0; +} diff --git a/fuzzing/src/algorithm.shift_right.fuzz.cpp b/fuzzing/src/algorithm.shift_right.fuzz.cpp new file mode 100644 index 000000000..a5dfa0391 --- /dev/null +++ b/fuzzing/src/algorithm.shift_right.fuzz.cpp @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2020 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include +#include +#include + +#include +#include + +[[nodiscard]] static auto fuzz_shift_right(FuzzedDataProvider& p) -> int +{ + auto shift = p.ConsumeIntegralInRange(0, etl::numeric_limits::max()); + auto ebytes = p.ConsumeRemainingBytes(); + auto sbytes = ebytes; + + auto const eview = etl::span{ebytes.data(), ebytes.size()}; + auto const sview = etl::span{sbytes.data(), sbytes.size()}; + + auto const e = etl::shift_right(eview.begin(), eview.end(), shift); + auto const s = std::shift_right(sview.begin(), sview.end(), shift); + + auto const ed = std::distance(eview.begin(), e); + auto const sd = std::distance(sview.begin(), s); + if ((ed != sd) or not std::equal(e, eview.end(), s, sview.end())) { + std::println(stderr, "size: {}, shift: {}, s: {}, e: {}", eview.size(), shift, sd, ed); + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_shift_right(p)); + return 0; +} diff --git a/fuzzing/src/bit.bit_ceil.fuzz.cpp b/fuzzing/src/bit.bit_ceil.fuzz.cpp new file mode 100644 index 000000000..2eb0c3e3d --- /dev/null +++ b/fuzzing/src/bit.bit_ceil.fuzz.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include + +template +[[nodiscard]] static auto fuzz_bit_ceil(FuzzedDataProvider& p) -> int +{ + auto const num = p.ConsumeIntegral(); + auto const s = std::bit_ceil(num); + auto const e = etl::bit_ceil(num); + return (e != s) ? 1 : 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_bit_ceil(p)); + RUN(fuzz_bit_ceil(p)); + RUN(fuzz_bit_ceil(p)); + RUN(fuzz_bit_ceil(p)); + RUN(fuzz_bit_ceil(p)); + return 0; +} diff --git a/fuzzing/src/bit.bit_floor.fuzz.cpp b/fuzzing/src/bit.bit_floor.fuzz.cpp new file mode 100644 index 000000000..3ebfb3a63 --- /dev/null +++ b/fuzzing/src/bit.bit_floor.fuzz.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include + +template +[[nodiscard]] static auto fuzz_bit_floor(FuzzedDataProvider& p) -> int +{ + auto const num = p.ConsumeIntegral(); + auto const s = std::bit_floor(num); + auto const e = etl::bit_floor(num); + return (e != s) ? 1 : 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_bit_floor(p)); + RUN(fuzz_bit_floor(p)); + RUN(fuzz_bit_floor(p)); + RUN(fuzz_bit_floor(p)); + RUN(fuzz_bit_floor(p)); + return 0; +} diff --git a/fuzzing/src/bit.bit_width.fuzz.cpp b/fuzzing/src/bit.bit_width.fuzz.cpp new file mode 100644 index 000000000..d7256521b --- /dev/null +++ b/fuzzing/src/bit.bit_width.fuzz.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include + +template +[[nodiscard]] static auto fuzz_bit_width(FuzzedDataProvider& p) -> int +{ + auto const num = p.ConsumeIntegral(); + auto const s = std::bit_width(num); + auto const e = etl::bit_width(num); + return (e != s) ? 1 : 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_bit_width(p)); + RUN(fuzz_bit_width(p)); + RUN(fuzz_bit_width(p)); + RUN(fuzz_bit_width(p)); + RUN(fuzz_bit_width(p)); + return 0; +} diff --git a/fuzzing/src/bit.byteswap.fuzz.cpp b/fuzzing/src/bit.byteswap.fuzz.cpp new file mode 100644 index 000000000..b098a9cc8 --- /dev/null +++ b/fuzzing/src/bit.byteswap.fuzz.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include + +#include + +template +[[nodiscard]] static auto fuzz_byteswap(FuzzedDataProvider& p) -> int +{ + auto const num = p.ConsumeIntegral(); + auto const s = std::byteswap(num); + auto const e = etl::byteswap(num); + auto const f = etl::detail::byteswap_fallback(num); + return (e != s or f != s) ? 1 : 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_byteswap(p)); + RUN(fuzz_byteswap(p)); + RUN(fuzz_byteswap(p)); + return 0; +} diff --git a/fuzzing/src/bit.countl_one.fuzz.cpp b/fuzzing/src/bit.countl_one.fuzz.cpp new file mode 100644 index 000000000..732d80da5 --- /dev/null +++ b/fuzzing/src/bit.countl_one.fuzz.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include + +template +[[nodiscard]] static auto fuzz_countl_one(FuzzedDataProvider& p) -> int +{ + auto const num = p.ConsumeIntegral(); + auto const s = std::countl_one(num); + auto const e = etl::countl_one(num); + return (e != s) ? 1 : 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_countl_one(p)); + RUN(fuzz_countl_one(p)); + RUN(fuzz_countl_one(p)); + RUN(fuzz_countl_one(p)); + RUN(fuzz_countl_one(p)); + return 0; +} diff --git a/fuzzing/src/bit.countl_zero.fuzz.cpp b/fuzzing/src/bit.countl_zero.fuzz.cpp new file mode 100644 index 000000000..74bda2fc9 --- /dev/null +++ b/fuzzing/src/bit.countl_zero.fuzz.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include + +template +[[nodiscard]] static auto fuzz_countl_zero(FuzzedDataProvider& p) -> int +{ + auto const num = p.ConsumeIntegral(); + auto const s = std::countl_zero(num); + auto const e = etl::countl_zero(num); + return (e != s) ? 1 : 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_countl_zero(p)); + RUN(fuzz_countl_zero(p)); + RUN(fuzz_countl_zero(p)); + RUN(fuzz_countl_zero(p)); + RUN(fuzz_countl_zero(p)); + return 0; +} diff --git a/fuzzing/src/bit.countr_one.fuzz.cpp b/fuzzing/src/bit.countr_one.fuzz.cpp new file mode 100644 index 000000000..a8d40d06f --- /dev/null +++ b/fuzzing/src/bit.countr_one.fuzz.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include + +template +[[nodiscard]] static auto fuzz_countr_one(FuzzedDataProvider& p) -> int +{ + auto const num = p.ConsumeIntegral(); + auto const s = std::countr_one(num); + auto const e = etl::countr_one(num); + return (e != s) ? 1 : 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_countr_one(p)); + RUN(fuzz_countr_one(p)); + RUN(fuzz_countr_one(p)); + RUN(fuzz_countr_one(p)); + RUN(fuzz_countr_one(p)); + return 0; +} diff --git a/fuzzing/src/bit.countr_zero.fuzz.cpp b/fuzzing/src/bit.countr_zero.fuzz.cpp new file mode 100644 index 000000000..ea39de699 --- /dev/null +++ b/fuzzing/src/bit.countr_zero.fuzz.cpp @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include + +template +[[nodiscard]] static auto fuzz_countr_zero(FuzzedDataProvider& p) -> int +{ + auto const num = p.ConsumeIntegral(); + auto const s = std::countr_zero(num); + auto const e = etl::countr_zero(num); + return (e != s) ? 1 : 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_countr_zero(p)); + RUN(fuzz_countr_zero(p)); + RUN(fuzz_countr_zero(p)); + RUN(fuzz_countr_zero(p)); + RUN(fuzz_countr_zero(p)); + return 0; +} diff --git a/fuzzing/src/bit.has_single_bit.fuzz.cpp b/fuzzing/src/bit.has_single_bit.fuzz.cpp new file mode 100644 index 000000000..befa559c2 --- /dev/null +++ b/fuzzing/src/bit.has_single_bit.fuzz.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include + +template +[[nodiscard]] static auto fuzz_has_single_bit(FuzzedDataProvider& p) -> int +{ + auto const num = p.ConsumeIntegral(); + auto const s = std::has_single_bit(num); + auto const e = etl::has_single_bit(num); + + return (e != s) ? 1 : 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_has_single_bit(p)); + RUN(fuzz_has_single_bit(p)); + RUN(fuzz_has_single_bit(p)); + RUN(fuzz_has_single_bit(p)); + RUN(fuzz_has_single_bit(p)); + return 0; +} diff --git a/fuzzing/src/bit.popcount.fuzz.cpp b/fuzzing/src/bit.popcount.fuzz.cpp new file mode 100644 index 000000000..06cd82858 --- /dev/null +++ b/fuzzing/src/bit.popcount.fuzz.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include + +template +[[nodiscard]] static auto fuzz_popcount(FuzzedDataProvider& p) -> int +{ + auto const num = p.ConsumeIntegral(); + auto const s = std::popcount(num); + auto const e = etl::popcount(num); + auto const f = etl::detail::popcount_fallback(num); + + return (e != s or f != s) ? 1 : 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_popcount(p)); + RUN(fuzz_popcount(p)); + RUN(fuzz_popcount(p)); + RUN(fuzz_popcount(p)); + RUN(fuzz_popcount(p)); + return 0; +} diff --git a/fuzzing/src/bitset.fuzz.cpp b/fuzzing/src/bitset.fuzz.cpp index 7d3f36929..a32260033 100644 --- a/fuzzing/src/bitset.fuzz.cpp +++ b/fuzzing/src/bitset.fuzz.cpp @@ -7,10 +7,10 @@ #include #include -#include +#include template -auto fuzz_bitset(FuzzedDataProvider& p) -> int +static auto fuzz_bitset(FuzzedDataProvider& p) -> int { using UInt = etl::conditional_t<(Size > 32), etl::uint64_t, etl::uint32_t>; @@ -29,18 +29,18 @@ auto fuzz_bitset(FuzzedDataProvider& p) -> int auto const slong = sset.to_ullong(); if ((eview != sview) or (elong != slong)) { - std::printf("etl::bitset::to_ullong\n"); - std::printf("val: '%llu'\n", static_cast(val)); - std::printf("estr: '%s'\nsstr: '%s'\n", estr.c_str(), sstr.c_str()); - std::printf("elong: '%llu' slong: '%llu'\n", elong, slong); + std::println(stderr, "etl::bitset::to_ullong"); + std::println(stderr, "val: '{}'", static_cast(val)); + std::println(stderr, "estr: '{}'\nsstr: '{}'", estr.c_str(), sstr); + std::println(stderr, "elong: '{}' slong: '{}'", elong, slong); return 1; } auto const efromstr = etl::bitset{estr.c_str()}; if (eset != efromstr) { - std::printf("etl::bitset(string_view)\n"); - std::printf("estr: '%s'\nefromstr: '%s'\n", estr.c_str(), efromstr.template to_string().c_str()); + std::println(stderr, "etl::bitset(string_view)"); + std::println(stderr, "estr: '{}'\nefromstr: '{}'", estr.c_str(), efromstr.template to_string().c_str()); return 1; } @@ -49,9 +49,6 @@ auto fuzz_bitset(FuzzedDataProvider& p) -> int extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int { - if (size == 0) { - return 0; - } auto p = FuzzedDataProvider{data, size}; RUN(fuzz_bitset<24>(p)); RUN(fuzz_bitset<32>(p)); diff --git a/fuzzing/src/cctype.fuzz.cpp b/fuzzing/src/cctype.fuzz.cpp new file mode 100644 index 000000000..43a5c84d4 --- /dev/null +++ b/fuzzing/src/cctype.fuzz.cpp @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include + +[[nodiscard]] static auto fuzz_isalpha(FuzzedDataProvider& p) -> int +{ + auto const c = p.ConsumeIntegral(); + + { + auto const s = std::isalnum(static_cast(static_cast(c))) != 0; + auto const e = etl::isalnum(static_cast(static_cast(c))) != 0; + if (e != s) { + std::println(stderr, "std::isalnum({:c}) = {}", c, s); + std::println(stderr, "etl::isalnum({:c}) = {}", c, e); + return 1; + } + } + + { + auto const s = std::isalpha(static_cast(static_cast(c))) != 0; + auto const e = etl::isalpha(static_cast(static_cast(c))) != 0; + if (e != s) { + std::println(stderr, "std::isalpha({:c}) = {}", c, s); + std::println(stderr, "etl::isalpha({:c}) = {}", c, e); + return 1; + } + } + + { + auto const s = std::isblank(static_cast(static_cast(c))) != 0; + auto const e = etl::isblank(static_cast(static_cast(c))) != 0; + if (e != s) { + std::println(stderr, "std::isblank({:c}) = {}", c, s); + std::println(stderr, "etl::isblank({:c}) = {}", c, e); + return 1; + } + } + + { + auto const s = std::iscntrl(static_cast(static_cast(c))) != 0; + auto const e = etl::iscntrl(static_cast(static_cast(c))) != 0; + if (e != s) { + std::println(stderr, "std::iscntrl({:c}) = {}", c, s); + std::println(stderr, "etl::iscntrl({:c}) = {}", c, e); + return 1; + } + } + + { + auto const s = std::isdigit(static_cast(static_cast(c))) != 0; + auto const e = etl::isdigit(static_cast(static_cast(c))) != 0; + if (e != s) { + std::println(stderr, "std::isdigit({:c}) = {}", c, s); + std::println(stderr, "etl::isdigit({:c}) = {}", c, e); + return 1; + } + } + + { + auto const s = std::isgraph(static_cast(static_cast(c))) != 0; + auto const e = etl::isgraph(static_cast(static_cast(c))) != 0; + if (e != s) { + std::println(stderr, "std::isgraph({:c}) = {}", c, s); + std::println(stderr, "etl::isgraph({:c}) = {}", c, e); + return 1; + } + } + + { + auto const s = std::islower(static_cast(static_cast(c))) != 0; + auto const e = etl::islower(static_cast(static_cast(c))) != 0; + if (e != s) { + std::println(stderr, "std::islower({:c}) = {}", c, s); + std::println(stderr, "etl::islower({:c}) = {}", c, e); + return 1; + } + } + + { + auto const s = std::isprint(static_cast(static_cast(c))) != 0; + auto const e = etl::isprint(static_cast(static_cast(c))) != 0; + if (e != s) { + std::println(stderr, "std::isprint({:c}) = {}", c, s); + std::println(stderr, "etl::isprint({:c}) = {}", c, e); + return 1; + } + } + + { + auto const s = std::ispunct(static_cast(static_cast(c))) != 0; + auto const e = etl::ispunct(static_cast(static_cast(c))) != 0; + if (e != s) { + std::println(stderr, "std::ispunct({:c}) = {}", c, s); + std::println(stderr, "etl::ispunct({:c}) = {}", c, e); + return 1; + } + } + + { + auto const s = std::isspace(static_cast(static_cast(c))) != 0; + auto const e = etl::isspace(static_cast(static_cast(c))) != 0; + if (e != s) { + std::println(stderr, "std::isspace({:c}) = {}", c, s); + std::println(stderr, "etl::isspace({:c}) = {}", c, e); + return 1; + } + } + + { + auto const s = std::isupper(static_cast(static_cast(c))) != 0; + auto const e = etl::isupper(static_cast(static_cast(c))) != 0; + if (e != s) { + std::println(stderr, "std::isupper({:c}) = {}", c, s); + std::println(stderr, "etl::isupper({:c}) = {}", c, e); + return 1; + } + } + + { + auto const s = std::isxdigit(static_cast(static_cast(c))) != 0; + auto const e = etl::isxdigit(static_cast(static_cast(c))) != 0; + if (e != s) { + std::println(stderr, "std::isxdigit({:c}) = {}", c, s); + std::println(stderr, "etl::isxdigit({:c}) = {}", c, e); + return 1; + } + } + + { + auto const s = std::tolower(static_cast(static_cast(c))); + auto const e = etl::tolower(static_cast(static_cast(c))); + if (e != s) { + std::println(stderr, "std::tolower({:c}) = {:c}", c, s); + std::println(stderr, "etl::tolower({:c}) = {:c}", c, e); + return 1; + } + } + + { + auto const s = std::toupper(static_cast(static_cast(c))); + auto const e = etl::toupper(static_cast(static_cast(c))); + if (e != s) { + std::println(stderr, "std::toupper({:c}) = {:c}", c, s); + std::println(stderr, "etl::toupper({:c}) = {:c}", c, e); + return 1; + } + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_isalpha(p)); + return 0; +} diff --git a/fuzzing/src/from_chars.fuzz.cpp b/fuzzing/src/charconv.from_chars.fuzz.cpp similarity index 88% rename from fuzzing/src/from_chars.fuzz.cpp rename to fuzzing/src/charconv.from_chars.fuzz.cpp index 038a2e54d..a10c8b7fa 100644 --- a/fuzzing/src/from_chars.fuzz.cpp +++ b/fuzzing/src/charconv.from_chars.fuzz.cpp @@ -14,12 +14,12 @@ #include template -[[nodiscard]] auto test_from_chars(FuzzedDataProvider& p) -> int +[[nodiscard]] static auto test_from_chars(FuzzedDataProvider& p) -> int { using namespace etl::fuzzing; auto const base = p.ConsumeIntegralInRange(2, 36); - auto const input = p.ConsumeRandomLengthString(11); + auto const input = p.ConsumeRandomLengthString(); auto [stdVal, stdPtr, stdEc] = [base, &input] { auto val = IntType{}; @@ -34,12 +34,13 @@ template }(); if (etlVal != stdVal) { - std::println("Str: '{}' Base: {} Value mismatch: etl={} - std={}", input, base, etlVal, stdVal); + std::println(stderr, "Str: '{}' Base: {} Value mismatch: etl={} - std={}", input, base, etlVal, stdVal); return 1; } if (etlEc != stdEc) { std::println( + stderr, "Str: '{}' Base: {} Error mismatch: etl={} - std={}", input, base, @@ -51,6 +52,7 @@ template if (etlPtr != stdPtr) { std::println( + stderr, "Str: '{}' Base: {} Value: etl={} - std={} Error: etl={} - std={} Pointer mismatch: etl={} - std={}", input, base, diff --git a/fuzzing/src/to_chars.fuzz.cpp b/fuzzing/src/charconv.to_chars.fuzz.cpp similarity index 83% rename from fuzzing/src/to_chars.fuzz.cpp rename to fuzzing/src/charconv.to_chars.fuzz.cpp index 326785094..4000cccaf 100644 --- a/fuzzing/src/to_chars.fuzz.cpp +++ b/fuzzing/src/charconv.to_chars.fuzz.cpp @@ -14,7 +14,7 @@ #include template -[[nodiscard]] auto fuzz_to_chars(FuzzedDataProvider& p) -> int +[[nodiscard]] static auto fuzz_to_chars(FuzzedDataProvider& p) -> int { using namespace etl::fuzzing; @@ -35,15 +35,16 @@ template auto const etlDist = std::distance(etlBuf.data(), etlPtr); if ((etlDist != stdDist) or (etlEc != stdEc) or ((etlBuf != stdBuf) and stdEc != std::errc::value_too_large)) { - std::println("Func: {}", __PRETTY_FUNCTION__); - std::println("Value: {} Base: {}", value, base); + std::println(stderr, "Func: {}", __PRETTY_FUNCTION__); + std::println(stderr, "Value: {} Base: {}", value, base); std::println( + stderr, "Buffer: etl='{}' - std='{}'", std::string_view{etlBuf.data(), etlBuf.size()}, std::string_view{stdBuf.data(), stdBuf.size()} ); - std::println("Distance: etl={} - std={}", etlDist, stdDist); - std::println("Error: etl='{}' - std='{}'", to_string(etlEc), to_string(stdEc)); + std::println(stderr, "Distance: etl={} - std={}", etlDist, stdDist); + std::println(stderr, "Error: etl='{}' - std='{}'", to_string(etlEc), to_string(stdEc)); return 1; } diff --git a/fuzzing/src/chrono.duration.fuzz.cpp b/fuzzing/src/chrono.duration.fuzz.cpp new file mode 100644 index 000000000..5ceaed568 --- /dev/null +++ b/fuzzing/src/chrono.duration.fuzz.cpp @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include + +[[nodiscard]] static auto fuzz_duration(FuzzedDataProvider& p) -> int +{ + auto const sec = p.ConsumeIntegral(); + auto const milli = p.ConsumeIntegral(); + + auto const esec = etl::chrono::seconds{sec}; + auto const ssec = std::chrono::seconds{sec}; + + auto const emilli = etl::chrono::milliseconds{milli}; + auto const smilli = std::chrono::milliseconds{milli}; + + auto const esectp = etl::chrono::sys_time{esec}; + auto const ssectp = std::chrono::sys_time{ssec}; + + auto const emillitp = etl::chrono::sys_time{emilli}; + auto const smillitp = std::chrono::sys_time{smilli}; + + if ((esectp == emillitp) != (ssectp == smillitp)) { + std::println(stderr, "sec == milli mismatch: {} vs. {}", ssectp, smillitp); + std::println(stderr, "etl: {}", esectp == emillitp); + std::println(stderr, "std: {}", ssectp == smillitp); + return 1; + } + + if ((esectp != emillitp) != (ssectp != smillitp)) { + std::println(stderr, "sec != milli mismatch: {} vs. {}", ssectp, smillitp); + std::println(stderr, "etl: {}", esectp != emillitp); + std::println(stderr, "std: {}", ssectp != smillitp); + return 1; + } + + if ((esectp < emillitp) != (ssectp < smillitp)) { + std::println(stderr, "sec < milli mismatch: {} vs. {}", ssectp, smillitp); + std::println(stderr, "etl: {}", esectp < emillitp); + std::println(stderr, "std: {}", ssectp < smillitp); + return 1; + } + + if ((esectp <= emillitp) != (ssectp <= smillitp)) { + std::println(stderr, "sec <= milli mismatch: {} vs. {}", ssectp, smillitp); + std::println(stderr, "etl: {}", esectp <= emillitp); + std::println(stderr, "std: {}", ssectp <= smillitp); + return 1; + } + + if ((esectp > emillitp) != (ssectp > smillitp)) { + std::println(stderr, "sec > milli mismatch: {} vs. {}", ssectp, smillitp); + std::println(stderr, "etl: {}", esectp > emillitp); + std::println(stderr, "std: {}", ssectp > smillitp); + return 1; + } + + if ((esectp >= emillitp) != (ssectp >= smillitp)) { + std::println(stderr, "sec >= milli mismatch: {} vs. {}", ssectp, smillitp); + std::println(stderr, "etl: {}", esectp >= emillitp); + std::println(stderr, "std: {}", ssectp >= smillitp); + return 1; + } + + if ((esec + emilli).count() != (ssec + smilli).count()) { + std::println(stderr, "sec + milli mismatch: {} vs. {}", ssec, smilli); + std::println(stderr, "etl: {}", (esec + emilli).count()); + std::println(stderr, "std: {}", (ssec + smilli).count()); + return 1; + } + + if ((esec - emilli).count() != (ssec - smilli).count()) { + std::println(stderr, "sec - milli mismatch: {} vs. {}", ssec, smilli); + std::println(stderr, "etl: {}", (esec - emilli).count()); + std::println(stderr, "std: {}", (ssec - smilli).count()); + return 1; + } + + if (smilli.count() == 0) { + return 0; + } + + if ((esec / emilli) != (ssec / smilli)) { + std::println(stderr, "sec / milli m {} vs. {}", ssec, smilli); + std::println(stderr, "etl: {}", esec / emilli); + std::println(stderr, "std: {}", ssec / smilli); + return 1; + } + + if ((esec % emilli).count() != (ssec % smilli).count()) { + std::println(stderr, "sec % milli m.count() {} vs. {}", ssec, smilli); + std::println(stderr, "etl: {}", (esec % emilli).count()); + std::println(stderr, "std: {}", (ssec % smilli).count()); + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_duration(p)); + return 0; +} diff --git a/fuzzing/src/chrono.rounding.fuzz.cpp b/fuzzing/src/chrono.rounding.fuzz.cpp new file mode 100644 index 000000000..e7c310485 --- /dev/null +++ b/fuzzing/src/chrono.rounding.fuzz.cpp @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include + +[[nodiscard]] static auto fuzz_rounding(FuzzedDataProvider& p) -> int +{ + auto const milli = p.ConsumeIntegral(); + + auto const emilli = etl::chrono::sys_time{etl::chrono::milliseconds{milli}}; + auto const smilli = std::chrono::sys_time{std::chrono::milliseconds{milli}}; + + { + auto const emin = etl::chrono::round(emilli); + auto const smin = std::chrono::round(smilli); + + if (emin.time_since_epoch().count() != smin.time_since_epoch().count()) { + std::println(stderr, "round mismatch from {}", smilli); + std::println(stderr, "etl: {}", emin.time_since_epoch().count()); + std::println(stderr, "std: {}", smin.time_since_epoch().count()); + return 1; + } + } + + { + auto const emin = etl::chrono::floor(emilli); + auto const smin = std::chrono::floor(smilli); + + if (emin.time_since_epoch().count() != smin.time_since_epoch().count()) { + std::println(stderr, "floor mismatch from {}", smilli); + std::println(stderr, "etl: {}", emin.time_since_epoch().count()); + std::println(stderr, "std: {}", smin.time_since_epoch().count()); + return 1; + } + } + + { + auto const emin = etl::chrono::ceil(emilli); + auto const smin = std::chrono::ceil(smilli); + + if (emin.time_since_epoch().count() != smin.time_since_epoch().count()) { + std::println(stderr, "ceil mismatch from {}", smilli); + std::println(stderr, "etl: {}", emin.time_since_epoch().count()); + std::println(stderr, "std: {}", smin.time_since_epoch().count()); + return 1; + } + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_rounding(p)); + return 0; +} diff --git a/fuzzing/src/chrono.year_month_day.fuzz.cpp b/fuzzing/src/chrono.year_month_day.fuzz.cpp new file mode 100644 index 000000000..5c993c73d --- /dev/null +++ b/fuzzing/src/chrono.year_month_day.fuzz.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include + +[[nodiscard]] static auto fuzz_year_month_day(FuzzedDataProvider& p) -> int +{ + auto const y = p.ConsumeIntegral(); + auto const m = p.ConsumeIntegralInRange(0U, 255U); + auto const d = p.ConsumeIntegralInRange(0U, 255U); + + auto const eymd = etl::chrono::year{y} / etl::chrono::month{m} / etl::chrono::day{d}; + auto const symd = std::chrono::year{y} / std::chrono::month{m} / std::chrono::day{d}; + + if (eymd.ok() != symd.ok()) { + std::println(stderr, "etl::chrono::year_month_day::ok() = {}", eymd.ok()); + std::println(stderr, "std::chrono::year_month_day::ok() = {}", symd.ok()); + return 1; + } + + if (not eymd.ok()) { + return 0; + } + + auto const esys = static_cast(eymd); + auto const ssys = static_cast(symd); + + auto const ecount = esys.time_since_epoch().count(); + auto const scount = ssys.time_since_epoch().count(); + + if (ecount != scount) { + std::println(stderr, "etl::chrono::year_month_day::operator sys_days() = {}", ecount); + std::println(stderr, "std::chrono::year_month_day::operator sys_days() = {}", scount); + return 1; + } + + // round-trip + if (eymd != etl::chrono::year_month_day{esys}) { + std::println(stderr, "std::chrono::year_month_day(sys_days) = {}", ecount); + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_year_month_day(p)); + return 0; +} diff --git a/fuzzing/src/cmath.lerp.fuzz.cpp b/fuzzing/src/cmath.lerp.fuzz.cpp new file mode 100644 index 000000000..253c9ea0f --- /dev/null +++ b/fuzzing/src/cmath.lerp.fuzz.cpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include + +template +[[nodiscard]] static auto fuzz_lerp(FuzzedDataProvider& p) -> int +{ + auto const a = p.ConsumeFloatingPoint(); + auto const b = p.ConsumeFloatingPoint(); + auto const t = p.ConsumeFloatingPoint(); + + { + auto const s = std::lerp(a, b, Float(1)); + auto const e = etl::lerp(a, b, Float(1)); + + if (std::isfinite(s) != std::isfinite(e)) { + return 1; + } + + if (std::isnan(s) or std::isinf(s)) { + return 0; + } + + if (s != e) { + return 1; + } + } + + { + auto const s = std::lerp(a, b, t); + auto const e = etl::lerp(a, b, t); + + if (std::isfinite(s) != std::isfinite(e)) { + return 1; + } + + if (std::isnan(s) or std::isinf(s)) { + return 0; + } + + if (s != e) { + return 1; + } + } + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + if (size == 0) { + return 0; + } + + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_lerp(p)); + RUN(fuzz_lerp(p)); + return 0; +} diff --git a/fuzzing/src/cstring.strcmp.fuzz.cpp b/fuzzing/src/cstring.strcmp.fuzz.cpp new file mode 100644 index 000000000..65b19c71a --- /dev/null +++ b/fuzzing/src/cstring.strcmp.fuzz.cpp @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include +#include + +[[nodiscard]] static constexpr auto sign(int x) -> int +{ + if (x > 0) { + return 1; + } + if (x < 0) { + return -1; + } + return 0; +} + +[[nodiscard]] static auto fuzz_strcmp(FuzzedDataProvider& p) -> int +{ + auto const lhs = p.ConsumeRandomLengthString(); + auto const rhs = p.ConsumeRandomLengthString(); + + auto const s = std::strcmp(lhs.c_str(), rhs.c_str()); + auto const e = etl::strcmp(lhs.c_str(), rhs.c_str()); + auto const ef = etl::detail::strcmp(lhs.c_str(), rhs.c_str()); + + if ((sign(e) != sign(s)) or (sign(ef) != sign(s))) { + std::println(stderr, "strcmp('{}', '{}')", lhs, rhs); + std::println(stderr, "std = {}", s); + std::println(stderr, "etl = {}", e); + std::println(stderr, "etl::detail = {}", ef); + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_strcmp(p)); + return 0; +} diff --git a/fuzzing/src/cstring.strlen.fuzz.cpp b/fuzzing/src/cstring.strlen.fuzz.cpp new file mode 100644 index 000000000..2809d4ce0 --- /dev/null +++ b/fuzzing/src/cstring.strlen.fuzz.cpp @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include + +[[nodiscard]] static auto fuzz_strlen(FuzzedDataProvider& p) -> int +{ + auto const str = p.ConsumeRandomLengthString(); + + auto const s = std::strlen(str.c_str()); + auto const e = etl::strlen(str.c_str()); + auto const ef = etl::detail::strlen(str.c_str()); + + if ((e != s) or (ef != s)) { + std::println(stderr, "strlen(\"{}\")", str); + std::println(stderr, "std = {}", s); + std::println(stderr, "etl = {}", e); + std::println(stderr, "etl::detail = {}", ef); + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_strlen(p)); + return 0; +} diff --git a/fuzzing/src/cstring.strncmp.fuzz.cpp b/fuzzing/src/cstring.strncmp.fuzz.cpp new file mode 100644 index 000000000..3db321205 --- /dev/null +++ b/fuzzing/src/cstring.strncmp.fuzz.cpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include +#include + +[[nodiscard]] static constexpr auto sign(int x) -> int +{ + if (x > 0) { + return 1; + } + if (x < 0) { + return -1; + } + return 0; +} + +[[nodiscard]] static auto fuzz_strncmp(FuzzedDataProvider& p) -> int +{ + auto const lhs = p.ConsumeRandomLengthString(); + auto const rhs = p.ConsumeRandomLengthString(); + auto const count = std::min(lhs.size(), rhs.size()); + + auto const s = std::strncmp(lhs.c_str(), rhs.c_str(), count); + auto const e = etl::strncmp(lhs.c_str(), rhs.c_str(), count); + auto const ef = etl::detail::strncmp(lhs.c_str(), rhs.c_str(), count); + + if ((sign(e) != sign(s)) or (sign(ef) != sign(s))) { + std::println(stderr, "strncmp('{}', '{}')", lhs, rhs); + std::println(stderr, "std = {}", s); + std::println(stderr, "etl = {}", e); + std::println(stderr, "etl::detail = {}", ef); + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_strncmp(p)); + return 0; +} diff --git a/fuzzing/src/cstring.strstr.fuzz.cpp b/fuzzing/src/cstring.strstr.fuzz.cpp new file mode 100644 index 000000000..4eb37895e --- /dev/null +++ b/fuzzing/src/cstring.strstr.fuzz.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include +#include + +[[nodiscard]] static auto fuzz_strstr(FuzzedDataProvider& p) -> int +{ + auto const haystack = p.ConsumeRandomLengthString(); + auto const needle = p.ConsumeRandomLengthString(); + + auto const s = std::strstr(haystack.c_str(), needle.c_str()); + auto const e = etl::strstr(haystack.c_str(), needle.c_str()); + auto const ef = etl::detail::strstr(haystack.c_str(), needle.c_str()); + + if ((e != s) or (ef != s)) { + std::println(stderr, "strstr('{}', '{}')", haystack, needle); + if (s == nullptr) { + std::println(stderr, "std = {}", s == nullptr); + std::println(stderr, "etl = {}", e == nullptr); + std::println(stderr, "etl::detail = {}", ef == nullptr); + } else { + std::println(stderr, "std = {}", s - haystack.c_str()); + std::println(stderr, "etl = {}", e - haystack.c_str()); + std::println(stderr, "etl::detail = {}", ef - haystack.c_str()); + } + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_strstr(p)); + return 0; +} diff --git a/fuzzing/src/numeric.midpoint.fuzz.cpp b/fuzzing/src/numeric.midpoint.fuzz.cpp new file mode 100644 index 000000000..aa34670bf --- /dev/null +++ b/fuzzing/src/numeric.midpoint.fuzz.cpp @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include + +#include +#include + +template +[[nodiscard]] static auto fuzz_midpoint(FuzzedDataProvider& p) -> int +{ + auto const [a, b] = [&p] { + if constexpr (etl::integral) { + return std::pair{p.ConsumeIntegral(), p.ConsumeIntegral()}; + } else if constexpr (etl::floating_point) { + return std::pair{p.ConsumeFloatingPoint(), p.ConsumeFloatingPoint()}; + } else { + static_assert(false); + } + }(); + + auto const s = std::midpoint(a, b); + auto const e = etl::midpoint(a, b); + return s != e ? 1 : 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + + RUN(fuzz_midpoint(p)); + RUN(fuzz_midpoint(p)); + RUN(fuzz_midpoint(p)); + RUN(fuzz_midpoint(p)); + RUN(fuzz_midpoint(p)); + + RUN(fuzz_midpoint(p)); + RUN(fuzz_midpoint(p)); + RUN(fuzz_midpoint(p)); + RUN(fuzz_midpoint(p)); + RUN(fuzz_midpoint(p)); + + RUN(fuzz_midpoint(p)); + RUN(fuzz_midpoint(p)); + return 0; +} diff --git a/fuzzing/src/random.uniform_int_distribution.fuzz.cpp b/fuzzing/src/random.uniform_int_distribution.fuzz.cpp new file mode 100644 index 000000000..9d54c8350 --- /dev/null +++ b/fuzzing/src/random.uniform_int_distribution.fuzz.cpp @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include +#include + +#include +#include +#include + +template +[[nodiscard]] static auto fuzz_uniform_int_distribution(FuzzedDataProvider& p) -> int +{ + static constexpr auto min = etl::numeric_limits::min(); + static constexpr auto max = etl::numeric_limits::max(); + + auto const distMin = p.ConsumeIntegralInRange(min, max); + auto const distMax = p.ConsumeIntegralInRange(distMin, max); + if (distMin == distMax) { + return 0; + } + + auto const seed = p.ConsumeIntegral(); + if (seed == 0) { + return 0; + } + + auto urng = etl::xoshiro128plusplus{seed}; + auto dist = etl::uniform_int_distribution{distMin, distMax}; + + auto const val = dist(urng); + if (std::cmp_less(val, distMin) or std::cmp_greater(val, distMax)) { + std::println(stderr, "dist = uniform_int_distribution({}, {}) = {}", distMin, distMax, val); + return 1; + } + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + if (size == 0) { + return 0; + } + + auto p = FuzzedDataProvider{data, size}; + + RUN(fuzz_uniform_int_distribution(p)); + // RUN(fuzz_uniform_int_distribution(p)); + // RUN(fuzz_uniform_int_distribution(p)); + // RUN(fuzz_uniform_int_distribution(p)); + + // RUN(fuzz_uniform_int_distribution(p)); + // RUN(fuzz_uniform_int_distribution(p)); + // RUN(fuzz_uniform_int_distribution(p)); + // RUN(fuzz_uniform_int_distribution(p)); + + return 0; +} diff --git a/fuzzing/src/random.uniform_real_distribution.fuzz.cpp b/fuzzing/src/random.uniform_real_distribution.fuzz.cpp new file mode 100644 index 000000000..2ae48c0c9 --- /dev/null +++ b/fuzzing/src/random.uniform_real_distribution.fuzz.cpp @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include + +#include + +template +[[nodiscard]] static auto fuzz_uniform_real_distribution(FuzzedDataProvider& p) -> int +{ + static constexpr auto min = etl::numeric_limits::lowest(); + static constexpr auto max = etl::numeric_limits::max(); + + auto const distMin = p.ConsumeFloatingPointInRange(min, max); + auto const distMax = p.ConsumeFloatingPointInRange(min, max); + if (distMax <= distMin) { + return 0; + } + + auto urng = etl::xoshiro128starstar{p.ConsumeIntegral()}; + auto dist = etl::uniform_real_distribution{distMin, distMax}; + if (auto const val = dist(urng); val < distMin or val > distMax) { + std::println(stderr, "dist_min: {}, dist_max: {}, val: {}", distMin, distMax, val); + return 1; + } + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + if (size == 0) { + return 0; + } + + auto p = FuzzedDataProvider{data, size}; + + RUN(fuzz_uniform_real_distribution(p)); + RUN(fuzz_uniform_real_distribution(p)); + + return 0; +} diff --git a/fuzzing/src/string.fuzz.cpp b/fuzzing/src/string.fuzz.cpp new file mode 100644 index 000000000..09ac91356 --- /dev/null +++ b/fuzzing/src/string.fuzz.cpp @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include +#include +#include +#include + +#include + +[[nodiscard]] static auto test_string(FuzzedDataProvider& p) -> int +{ + auto const chars = p.ConsumeBytesWithTerminator(127, 0); + + auto etlString = etl::inplace_string<128>{}; + etl::copy(chars.begin(), chars.end(), etl::back_inserter(etlString)); + + auto stdString = std::string{chars.begin(), chars.end()}; + + if (etlString.size() != stdString.size()) { + return 1; + } + if (etl::strlen(chars.data()) != std::strlen(chars.data())) { + return 1; + } + + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + if (size == 0) { + return 0; + } + auto p = FuzzedDataProvider{data, size}; + + RUN(test_string(p)); + + return 0; +} diff --git a/fuzzing/src/string_view.contains.fuzz.cpp b/fuzzing/src/string_view.contains.fuzz.cpp new file mode 100644 index 000000000..801d93f78 --- /dev/null +++ b/fuzzing/src/string_view.contains.fuzz.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include + +static auto fuzz_string_view_contains(FuzzedDataProvider& p) -> int +{ + auto const haystack = p.ConsumeRandomLengthString(); + auto const needle = p.ConsumeRandomLengthString(); + + auto const eview = etl::string_view{haystack.data(), haystack.size()}; + auto const sview = std::string_view{haystack.data(), haystack.size()}; + + if (not needle.empty()) { + auto const epos = eview.contains(needle[0]); + auto const spos = sview.contains(needle[0]); + if (epos != spos) { + std::println(stderr, "etl::string_view::contains(char)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + } + + auto const epos = eview.contains(needle.c_str()); + auto const spos = sview.contains(needle.c_str()); + if (epos != spos) { + std::println(stderr, "etl::string_view::contains(char const*)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_string_view_contains(p)); + return 0; +} diff --git a/fuzzing/src/string_view.ends_with.fuzz.cpp b/fuzzing/src/string_view.ends_with.fuzz.cpp new file mode 100644 index 000000000..703c4a60f --- /dev/null +++ b/fuzzing/src/string_view.ends_with.fuzz.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include + +static auto fuzz_string_view_ends_with(FuzzedDataProvider& p) -> int +{ + auto const haystack = p.ConsumeRandomLengthString(); + auto const needle = p.ConsumeRandomLengthString(); + + auto const eview = etl::string_view{haystack.data(), haystack.size()}; + auto const sview = std::string_view{haystack.data(), haystack.size()}; + + if (not needle.empty()) { + auto const epos = eview.ends_with(needle[0]); + auto const spos = sview.ends_with(needle[0]); + if (epos != spos) { + std::println(stderr, "etl::string_view::ends_with(char)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + } + + auto const epos = eview.ends_with(needle.c_str()); + auto const spos = sview.ends_with(needle.c_str()); + if (epos != spos) { + std::println(stderr, "etl::string_view::ends_with(char const*)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_string_view_ends_with(p)); + return 0; +} diff --git a/fuzzing/src/string_view.find.fuzz.cpp b/fuzzing/src/string_view.find.fuzz.cpp new file mode 100644 index 000000000..744de20e9 --- /dev/null +++ b/fuzzing/src/string_view.find.fuzz.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include + +static auto fuzz_string_view_find(FuzzedDataProvider& p) -> int +{ + auto const haystack = p.ConsumeRandomLengthString(); + auto const needle = p.ConsumeRandomLengthString(); + + auto const eview = etl::string_view{haystack.data(), haystack.size()}; + auto const sview = std::string_view{haystack.data(), haystack.size()}; + + if (not needle.empty()) { + auto const epos = eview.find(needle[0]); + auto const spos = sview.find(needle[0]); + if (epos != spos) { + std::println(stderr, "etl::string_view::find(char)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + } + + auto const epos = eview.find(needle.c_str()); + auto const spos = sview.find(needle.c_str()); + if (epos != spos) { + std::println(stderr, "etl::string_view::find(char const*)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_string_view_find(p)); + return 0; +} diff --git a/fuzzing/src/string_view.find_first_not_of.fuzz.cpp b/fuzzing/src/string_view.find_first_not_of.fuzz.cpp new file mode 100644 index 000000000..bf4fcc0e8 --- /dev/null +++ b/fuzzing/src/string_view.find_first_not_of.fuzz.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include + +static auto fuzz_string_view_find_first_not_of(FuzzedDataProvider& p) -> int +{ + auto const haystack = p.ConsumeRandomLengthString(); + auto const needle = p.ConsumeRandomLengthString(); + + auto const eview = etl::string_view{haystack.data(), haystack.size()}; + auto const sview = std::string_view{haystack.data(), haystack.size()}; + + if (not needle.empty()) { + auto const epos = eview.find_first_not_of(needle[0]); + auto const spos = sview.find_first_not_of(needle[0]); + if (epos != spos) { + std::println(stderr, "etl::string_view::find_first_not_of(char)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + } + + auto const epos = eview.find_first_not_of(needle.c_str()); + auto const spos = sview.find_first_not_of(needle.c_str()); + if (epos != spos) { + std::println(stderr, "etl::string_view::find_first_not_of(char const*)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_string_view_find_first_not_of(p)); + return 0; +} diff --git a/fuzzing/src/string_view.find_first_of.fuzz.cpp b/fuzzing/src/string_view.find_first_of.fuzz.cpp new file mode 100644 index 000000000..2becd3de5 --- /dev/null +++ b/fuzzing/src/string_view.find_first_of.fuzz.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include + +static auto fuzz_string_view_find_first_of(FuzzedDataProvider& p) -> int +{ + auto const haystack = p.ConsumeRandomLengthString(); + auto const needle = p.ConsumeRandomLengthString(); + + auto const eview = etl::string_view{haystack.data(), haystack.size()}; + auto const sview = std::string_view{haystack.data(), haystack.size()}; + + if (not needle.empty()) { + auto const epos = eview.find_first_of(needle[0]); + auto const spos = sview.find_first_of(needle[0]); + if (epos != spos) { + std::println(stderr, "etl::string_view::find_first_of(char)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + } + + auto const epos = eview.find_first_of(needle.c_str()); + auto const spos = sview.find_first_of(needle.c_str()); + if (epos != spos) { + std::println(stderr, "etl::string_view::find_first_of(char const*)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_string_view_find_first_of(p)); + return 0; +} diff --git a/fuzzing/src/string_view.find_last_not_of.fuzz.cpp b/fuzzing/src/string_view.find_last_not_of.fuzz.cpp new file mode 100644 index 000000000..8a29cdbd8 --- /dev/null +++ b/fuzzing/src/string_view.find_last_not_of.fuzz.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include + +static auto fuzz_string_view_find_last_not_of(FuzzedDataProvider& p) -> int +{ + auto const haystack = p.ConsumeRandomLengthString(); + auto const needle = p.ConsumeRandomLengthString(); + + auto const eview = etl::string_view{haystack.data(), haystack.size()}; + auto const sview = std::string_view{haystack.data(), haystack.size()}; + + if (not needle.empty()) { + auto const epos = eview.find_last_not_of(needle[0]); + auto const spos = sview.find_last_not_of(needle[0]); + if (epos != spos) { + std::println(stderr, "etl::string_view::find_last_not_of(char)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + } + + auto const epos = eview.find_last_not_of(needle.c_str()); + auto const spos = sview.find_last_not_of(needle.c_str()); + if (epos != spos) { + std::println(stderr, "etl::string_view::find_last_not_of(char const*)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_string_view_find_last_not_of(p)); + return 0; +} diff --git a/fuzzing/src/string_view.find_last_of.fuzz.cpp b/fuzzing/src/string_view.find_last_of.fuzz.cpp new file mode 100644 index 000000000..0b8feb2e4 --- /dev/null +++ b/fuzzing/src/string_view.find_last_of.fuzz.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include + +static auto fuzz_string_view_find_last_of(FuzzedDataProvider& p) -> int +{ + auto const haystack = p.ConsumeRandomLengthString(); + auto const needle = p.ConsumeRandomLengthString(); + + auto const eview = etl::string_view{haystack.data(), haystack.size()}; + auto const sview = std::string_view{haystack.data(), haystack.size()}; + + if (not needle.empty()) { + auto const epos = eview.find_last_of(needle[0]); + auto const spos = sview.find_last_of(needle[0]); + if (epos != spos) { + std::println(stderr, "etl::string_view::find_last_of(char)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + } + + auto const epos = eview.find_last_of(needle.c_str()); + auto const spos = sview.find_last_of(needle.c_str()); + if (epos != spos) { + std::println(stderr, "etl::string_view::find_last_of(char const*)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_string_view_find_last_of(p)); + return 0; +} diff --git a/fuzzing/src/string_view.fuzz.cpp b/fuzzing/src/string_view.fuzz.cpp deleted file mode 100644 index cc01ec068..000000000 --- a/fuzzing/src/string_view.fuzz.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: BSL-1.0 -// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch - -#include "fuzzing.hpp" - -#include - -#include -#include - -// auto fuzz_string_view_compare(FuzzedDataProvider& p) -> int -// { -// auto sign = [](int x) { return x == 0 ? 0 : (x < 0 ? -1 : 1); }; - -// auto const a = p.ConsumeRandomLengthString(64); -// auto const b = p.ConsumeRandomLengthString(64); - -// auto const eview = etl::string_view{a.data(), a.size()}; -// auto const sview = std::string_view{a.data(), a.size()}; - -// auto const ecmp = eview.compare(b.c_str()); -// auto const scmp = sview.compare(b.c_str()); - -// if (sign(ecmp) != sign(scmp)) { -// std::printf("etl::string_view::compare\n"); -// std::printf("this: '%s' str: '%s'\n", a.c_str(), b.c_str()); -// std::printf("len(this): '%zu' len(str): '%zu'\n", a.size(), b.size()); -// std::printf("ecmp: '%d' scmp: '%d'\n", ecmp, scmp); -// return 1; -// } -// return 0; -// } - -auto fuzz_string_view_find(FuzzedDataProvider& p) -> int -{ - auto const haystack = p.ConsumeRandomLengthString(64); - auto const needle = p.ConsumeRandomLengthString(64); - - auto const eview = etl::string_view{haystack.data(), haystack.size()}; - auto const sview = std::string_view{haystack.data(), haystack.size()}; - - auto const epos = eview.find(needle.c_str()); - auto const spos = sview.find(needle.c_str()); - if (epos != spos) { - std::printf("etl::string_view::find\n"); - std::printf("haystack: '%s' needle: '%s'\n", haystack.c_str(), needle.c_str()); - std::printf("epos: '%zu' spos: '%zu'\n", epos, spos); - return 1; - } - return 0; -} - -auto fuzz_string_view_rfind(FuzzedDataProvider& p) -> int -{ - auto const haystack = p.ConsumeRandomLengthString(64); - auto const needle = p.ConsumeRandomLengthString(64); - - auto const eview = etl::string_view{haystack.data(), haystack.size()}; - auto const sview = std::string_view{haystack.data(), haystack.size()}; - - auto const epos = eview.rfind(needle.c_str()); - auto const spos = sview.rfind(needle.c_str()); - if (epos != spos) { - std::printf("etl::string_view::rfind\n"); - std::printf("haystack: '%s' needle: '%s'\n", haystack.c_str(), needle.c_str()); - std::printf("epos: '%zu' spos: '%zu'\n", epos, spos); - return 1; - } - return 0; -} - -extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int -{ - if (size == 0) { - return 0; - } - auto p = FuzzedDataProvider{data, size}; - // RUN(fuzz_string_view_compare(p)); - RUN(fuzz_string_view_find(p)); - RUN(fuzz_string_view_rfind(p)); - return 0; -} diff --git a/fuzzing/src/string_view.rfind.fuzz.cpp b/fuzzing/src/string_view.rfind.fuzz.cpp new file mode 100644 index 000000000..4900e79bc --- /dev/null +++ b/fuzzing/src/string_view.rfind.fuzz.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include + +static auto fuzz_string_view_rfind(FuzzedDataProvider& p) -> int +{ + auto const haystack = p.ConsumeRandomLengthString(); + auto const needle = p.ConsumeRandomLengthString(); + + auto const eview = etl::string_view{haystack.data(), haystack.size()}; + auto const sview = std::string_view{haystack.data(), haystack.size()}; + + if (not needle.empty()) { + auto const epos = eview.rfind(needle[0]); + auto const spos = sview.rfind(needle[0]); + if (epos != spos) { + std::println(stderr, "etl::string_view::rfind(char)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + } + + auto const epos = eview.rfind(needle.c_str()); + auto const spos = sview.rfind(needle.c_str()); + if (epos != spos) { + std::println(stderr, "etl::string_view::rfind(char const*)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_string_view_rfind(p)); + return 0; +} diff --git a/fuzzing/src/string_view.starts_with.fuzz.cpp b/fuzzing/src/string_view.starts_with.fuzz.cpp new file mode 100644 index 000000000..0f2922453 --- /dev/null +++ b/fuzzing/src/string_view.starts_with.fuzz.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#include "fuzzing.hpp" + +#include + +#include +#include + +static auto fuzz_string_view_starts_with(FuzzedDataProvider& p) -> int +{ + auto const haystack = p.ConsumeRandomLengthString(); + auto const needle = p.ConsumeRandomLengthString(); + + auto const eview = etl::string_view{haystack.data(), haystack.size()}; + auto const sview = std::string_view{haystack.data(), haystack.size()}; + + if (not needle.empty()) { + auto const epos = eview.starts_with(needle[0]); + auto const spos = sview.starts_with(needle[0]); + if (epos != spos) { + std::println(stderr, "etl::string_view::starts_with(char)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + } + + auto const epos = eview.starts_with(needle.c_str()); + auto const spos = sview.starts_with(needle.c_str()); + if (epos != spos) { + std::println(stderr, "etl::string_view::starts_with(char const*)"); + std::println(stderr, "haystack: '{}' needle: '{}'", haystack, needle); + std::println(stderr, "epos: '{}' spos: '{}'", epos, spos); + return 1; + } + return 0; +} + +extern "C" auto LLVMFuzzerTestOneInput(std::uint8_t const* data, std::size_t size) -> int +{ + auto p = FuzzedDataProvider{data, size}; + RUN(fuzz_string_view_starts_with(p)); + return 0; +} diff --git a/include/etl/_algorithm/exchange_sort.hpp b/include/etl/_algorithm/exchange_sort.hpp index c555b2498..48434086d 100644 --- a/include/etl/_algorithm/exchange_sort.hpp +++ b/include/etl/_algorithm/exchange_sort.hpp @@ -18,6 +18,10 @@ namespace etl { template constexpr auto exchange_sort(RandomIt first, RandomIt last, Compare comp) -> void { + if (first == last) { + return; + } + for (auto i = first; i < etl::prev(last); ++i) { for (auto j = etl::next(i); j < last; ++j) { if (comp(*j, *i)) { diff --git a/include/etl/_algorithm/gnome_sort.hpp b/include/etl/_algorithm/gnome_sort.hpp index be2a36570..bdbbdb84e 100644 --- a/include/etl/_algorithm/gnome_sort.hpp +++ b/include/etl/_algorithm/gnome_sort.hpp @@ -14,8 +14,8 @@ namespace etl { /// \details https://en.wikipedia.org/wiki/Gnome_sort /// \note Non-standard extension /// \ingroup algorithm -template -constexpr auto gnome_sort(BidirIt first, BidirIt last, Compare comp) -> void +template +constexpr auto gnome_sort(RandomIt first, RandomIt last, Compare comp) -> void { auto i = first; while (i != last) { @@ -32,8 +32,8 @@ constexpr auto gnome_sort(BidirIt first, BidirIt last, Compare comp) -> void /// \details https://en.wikipedia.org/wiki/Gnome_sort /// \note Non-standard extension /// \ingroup algorithm -template -constexpr auto gnome_sort(BidirIt first, BidirIt last) -> void +template +constexpr auto gnome_sort(RandomIt first, RandomIt last) -> void { etl::gnome_sort(first, last, less()); } diff --git a/include/etl/_algorithm/insertion_sort.hpp b/include/etl/_algorithm/insertion_sort.hpp index 0a74ef94f..03d2d83fd 100644 --- a/include/etl/_algorithm/insertion_sort.hpp +++ b/include/etl/_algorithm/insertion_sort.hpp @@ -6,6 +6,7 @@ #include #include +#include namespace etl { @@ -20,13 +21,13 @@ template constexpr auto insertion_sort(RandomIt first, RandomIt last, Compare comp) -> void { for (auto i = first; i != last; ++i) { - auto key = *i; + auto key = etl::move(*i); auto j = i; while (j != first and comp(key, *(j - 1))) { - *j = *(j - 1); + *j = etl::move(*(j - 1)); --j; } - *j = key; + *j = etl::move(key); } } diff --git a/include/etl/_algorithm/merge_sort.hpp b/include/etl/_algorithm/merge_sort.hpp index b051d5df6..49eb546d8 100644 --- a/include/etl/_algorithm/merge_sort.hpp +++ b/include/etl/_algorithm/merge_sort.hpp @@ -15,19 +15,19 @@ namespace etl { /// \brief Sorts the elements in the range `[first, last)` in non-descending order. /// \details https://en.wikipedia.org/wiki/Merge_sort /// \note Non-standard extension -template -constexpr auto merge_sort(BidirIt first, BidirIt last, Compare comp) -> void +template +constexpr auto merge_sort(RandomIt first, RandomIt last, Compare comp) -> void { if (last - first > 1) { - BidirIt mid = first + (last - first) / 2; + RandomIt mid = first + (last - first) / 2; etl::merge_sort(first, mid, comp); etl::merge_sort(mid, last, comp); etl::inplace_merge(first, mid, last, comp); } } -template -constexpr auto merge_sort(BidirIt first, BidirIt last) -> void +template +constexpr auto merge_sort(RandomIt first, RandomIt last) -> void { etl::merge_sort(first, last, etl::less()); } diff --git a/include/etl/_algorithm/nth_element.hpp b/include/etl/_algorithm/nth_element.hpp index 9bf810b05..c59f805f1 100644 --- a/include/etl/_algorithm/nth_element.hpp +++ b/include/etl/_algorithm/nth_element.hpp @@ -4,11 +4,60 @@ #ifndef TETL_ALGORITHM_NTH_ELEMENT_HPP #define TETL_ALGORITHM_NTH_ELEMENT_HPP -#include -#include +#include +#include +#include +#include +#include namespace etl { +namespace detail { + +template +constexpr auto median_of_three(Iter a, Iter b, Iter c, Compare comp) -> Iter +{ + if (comp(*a, *b)) { + if (comp(*b, *c)) { + return b; // a < b < c + } + if (comp(*a, *c)) { + return c; // a < c <= b + } + return a; // c <= a < b + } + + // !(a < b) + if (comp(*a, *c)) { + return a; // b <= a < c + } + if (comp(*b, *c)) { + return c; // b < c <= a + } + return b; // c <= b <= a +} + +template +constexpr auto unguarded_partition(RandomIt first, RandomIt last, RandomIt pivot, Compare comp) -> RandomIt +{ + while (true) { + while (comp(*first, *pivot)) { + ++first; + } + --last; + while (comp(*pivot, *last)) { + --last; + } + if (not(first < last)) { + return first; + } + etl::iter_swap(first, last); + ++first; + } +} + +} // namespace detail + /// \brief nth_element is a partial sorting algorithm that rearranges elements /// in `[first, last)` such that: /// - The element pointed at by nth is changed to whatever element would occur @@ -22,16 +71,27 @@ namespace etl { template constexpr auto nth_element(RandomIt first, RandomIt nth, RandomIt last, Compare comp) -> void { - // TODO: Improve. Currently forwards to regular sort. - etl::ignore_unused(nth); - etl::sort(first, last, comp); + constexpr auto threshold = 3; + + while (last - first > threshold) { + auto const middle = etl::next(first, (last - first) / 2); + auto const pivot = etl::detail::median_of_three(first, middle, etl::prev(last), comp); + auto const cut = etl::detail::unguarded_partition(first, last, pivot, comp); + + if (cut <= nth) { + first = cut; + } else { + last = cut; + } + } + + etl::insertion_sort(first, last, comp); } template constexpr auto nth_element(RandomIt first, RandomIt nth, RandomIt last) -> void { - etl::ignore_unused(nth); - etl::sort(first, last); + etl::nth_element(first, nth, last, etl::less()); } } // namespace etl diff --git a/include/etl/_algorithm/quick_sort.hpp b/include/etl/_algorithm/quick_sort.hpp new file mode 100644 index 000000000..82c872f67 --- /dev/null +++ b/include/etl/_algorithm/quick_sort.hpp @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +#ifndef TETL_ALGORITHM_QUICK_SORT_HPP +#define TETL_ALGORITHM_QUICK_SORT_HPP + +#include +#include +#include +#include + +namespace etl { + +namespace detail { + +template +constexpr auto lomuto_partition(RandomIt first, RandomIt last, Compare comp) -> RandomIt +{ + auto pivot = etl::prev(last); + auto i = first; + + for (auto j = first; j != pivot; ++j) { + if (comp(*j, *pivot)) { + etl::iter_swap(i, j); + ++i; + } + } + + etl::iter_swap(i, pivot); + return i; // final pivot position +} + +} // namespace detail + +/// \ingroup algorithm +/// @{ + +/// \brief Sorts the elements in the range `[first, last)` in non-descending order. +/// \details https://en.wikipedia.org/wiki/Quick_sort +/// \note Non-standard extension +template +constexpr auto quick_sort(RandomIt first, RandomIt last, Compare comp) -> void +{ + if (first == last or etl::next(first) == last) { + return; + } + + auto pi = etl::detail::lomuto_partition(first, last, comp); + etl::quick_sort(first, pi, comp); + etl::quick_sort(etl::next(pi), last, comp); +} + +template +constexpr auto quick_sort(RandomIt first, RandomIt last) -> void +{ + etl::quick_sort(first, last, etl::less()); +} + +/// @} + +} // namespace etl + +#endif // TETL_ALGORITHM_QUICK_SORT_HPP diff --git a/include/etl/_algorithm/shift_right.hpp b/include/etl/_algorithm/shift_right.hpp index 024b0fbe5..17f643eef 100644 --- a/include/etl/_algorithm/shift_right.hpp +++ b/include/etl/_algorithm/shift_right.hpp @@ -33,7 +33,11 @@ constexpr auto shift_right(BidiIt first, BidiIt last, typename etl::iterator_tra { // The standard only checks for n == 0. n < 0 would be undefined behavior. // This implementation does nothing if n < 0. - if (n <= 0 or n >= etl::distance(first, last)) { + if (n <= 0) { + return first; + } + + if (n >= etl::distance(first, last)) { return last; } diff --git a/include/etl/_algorithm/sort.hpp b/include/etl/_algorithm/sort.hpp index 19624cc3b..850be9b3f 100644 --- a/include/etl/_algorithm/sort.hpp +++ b/include/etl/_algorithm/sort.hpp @@ -4,7 +4,7 @@ #ifndef TETL_ALGORITHM_SORT_HPP #define TETL_ALGORITHM_SORT_HPP -#include +#include namespace etl { @@ -18,7 +18,7 @@ namespace etl { template constexpr auto sort(RandomIt first, RandomIt last, Compare comp) -> void { - etl::insertion_sort(first, last, comp); + etl::quick_sort(first, last, comp); } template diff --git a/include/etl/_algorithm/stable_sort.hpp b/include/etl/_algorithm/stable_sort.hpp index 006b9d8ea..15a6a341b 100644 --- a/include/etl/_algorithm/stable_sort.hpp +++ b/include/etl/_algorithm/stable_sort.hpp @@ -4,7 +4,7 @@ #ifndef TETL_ALGORITHM_STABLE_SORT_HPP #define TETL_ALGORITHM_STABLE_SORT_HPP -#include +#include #include namespace etl { @@ -20,7 +20,7 @@ namespace etl { template constexpr auto stable_sort(RandomIt first, RandomIt last, Compare comp) -> void { - etl::insertion_sort(first, last, comp); + etl::merge_sort(first, last, comp); } template diff --git a/include/etl/_bit/bit_ceil.hpp b/include/etl/_bit/bit_ceil.hpp index fd0599f98..ee24eb6b4 100644 --- a/include/etl/_bit/bit_ceil.hpp +++ b/include/etl/_bit/bit_ceil.hpp @@ -29,11 +29,11 @@ template return UInt{1}; } if constexpr (is_same_v) { - return UInt{1U} << bit_width(UInt{x - 1U}); + return UInt{1U} << bit_width(x - UInt{1U}); } else { // for types subject to integral promotion auto o = etl::numeric_limits::digits - etl::numeric_limits::digits; - return UInt{1U << (bit_width(UInt{x - 1U}) + o) >> o}; + return static_cast(1U << (bit_width(static_cast(x - 1U)) + o) >> o); } } diff --git a/include/etl/_cctype/tolower.hpp b/include/etl/_cctype/tolower.hpp index 80f171898..e9738875a 100644 --- a/include/etl/_cctype/tolower.hpp +++ b/include/etl/_cctype/tolower.hpp @@ -26,10 +26,10 @@ namespace etl { /// \ingroup cctype [[nodiscard]] constexpr auto tolower(int ch) noexcept -> int { - if (isupper(ch) != 0) { - return static_cast(ch + 32); + if (etl::isupper(ch) != 0) { + return ch + 32; } - return static_cast(ch); + return ch; } } // namespace etl diff --git a/include/etl/_cctype/toupper.hpp b/include/etl/_cctype/toupper.hpp index 9c2f655db..b17d5e2e8 100644 --- a/include/etl/_cctype/toupper.hpp +++ b/include/etl/_cctype/toupper.hpp @@ -27,9 +27,9 @@ namespace etl { [[nodiscard]] constexpr auto toupper(int ch) noexcept -> int { if (etl::islower(ch) != 0) { - return static_cast(ch - 32); + return ch - 32; } - return static_cast(ch); + return ch; } } // namespace etl diff --git a/include/etl/_chrono/weekday.hpp b/include/etl/_chrono/weekday.hpp index e05cae989..03fab157c 100644 --- a/include/etl/_chrono/weekday.hpp +++ b/include/etl/_chrono/weekday.hpp @@ -95,7 +95,7 @@ struct weekday { } private: - [[nodiscard]] static constexpr auto weekday_from_days(int tp) noexcept -> etl::uint8_t + [[nodiscard]] static constexpr auto weekday_from_days(etl::int_least32_t tp) noexcept -> etl::uint8_t { return static_cast(tp >= -4 ? (tp + 4) % 7 : ((tp + 5) % 7) + 6); } diff --git a/include/etl/_cmath/erf.hpp b/include/etl/_cmath/erf.hpp index 789777368..18161bd53 100644 --- a/include/etl/_cmath/erf.hpp +++ b/include/etl/_cmath/erf.hpp @@ -16,18 +16,20 @@ inline constexpr struct erf { template [[nodiscard]] constexpr auto operator()(Float arg) const noexcept -> Float { +#if not(defined(__AVR__) and defined(__clang__)) if (not is_constant_evaluated()) { -#if __has_builtin(__builtin_erff) + #if __has_builtin(__builtin_erff) if constexpr (etl::same_as) { return __builtin_erff(arg); } -#endif -#if __has_builtin(__builtin_erf) + #endif + #if __has_builtin(__builtin_erf) if constexpr (etl::same_as) { return __builtin_erf(arg); } -#endif + #endif } +#endif return etl::detail::gcem::erf(arg); } } erf; diff --git a/include/etl/_cmath/log10.hpp b/include/etl/_cmath/log10.hpp index f3dfd8a1d..43d746c28 100644 --- a/include/etl/_cmath/log10.hpp +++ b/include/etl/_cmath/log10.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -31,7 +32,7 @@ inline constexpr struct log10 { } #endif } - return etl::detail::gcem::log(arg) / static_cast(GCEM_LOG_10); + return etl::log(arg) / static_cast(GCEM_LOG_10); } } log10; diff --git a/include/etl/_cmath/log2.hpp b/include/etl/_cmath/log2.hpp index a964c0344..0d16aaec0 100644 --- a/include/etl/_cmath/log2.hpp +++ b/include/etl/_cmath/log2.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -19,19 +20,21 @@ inline constexpr struct log2 { template [[nodiscard]] constexpr auto operator()(Float arg) const noexcept -> Float { +#if not(defined(__AVR__) and defined(__clang__)) if (not is_constant_evaluated()) { -#if __has_builtin(__builtin_log2f) + #if __has_builtin(__builtin_log2f) if constexpr (etl::same_as) { return __builtin_log2f(arg); } -#endif -#if __has_builtin(__builtin_log2) + #endif + #if __has_builtin(__builtin_log2) if constexpr (etl::same_as) { return __builtin_log2(arg); } -#endif + #endif } - return etl::detail::gcem::log2(arg); +#endif + return etl::log(arg) / static_cast(GCEM_LOG_2); } } log2; diff --git a/include/etl/_compare/synth_three_way.hpp b/include/etl/_compare/synth_three_way.hpp index a38a77939..fd0a1ba65 100644 --- a/include/etl/_compare/synth_three_way.hpp +++ b/include/etl/_compare/synth_three_way.hpp @@ -10,7 +10,6 @@ namespace etl { -// // clang-format off // inline constexpr auto synth_three_way = // [](T const& t, U const& u) // requires requires { @@ -26,7 +25,6 @@ namespace etl { // return weak_ordering::equivalent; // } // }; -// // clang-format on // template // using synth_three_way_result diff --git a/include/etl/_compare/three_way_comparable.hpp b/include/etl/_compare/three_way_comparable.hpp index d0046adf6..551e191f2 100644 --- a/include/etl/_compare/three_way_comparable.hpp +++ b/include/etl/_compare/three_way_comparable.hpp @@ -17,32 +17,27 @@ namespace detail { template concept compares_as = same_as, Cat>; -// clang-format off -template +template concept partially_ordered_with = requires(remove_reference_t const& t, remove_reference_t const& u) { - { t < u } -> boolean_testable; - { t > u } -> boolean_testable; + { t < u } -> boolean_testable; + { t > u } -> boolean_testable; { t <= u } -> boolean_testable; { t >= u } -> boolean_testable; - { u < t } -> boolean_testable; - { u > t } -> boolean_testable; + { u < t } -> boolean_testable; + { u > t } -> boolean_testable; { u <= t } -> boolean_testable; { u >= t } -> boolean_testable; }; -// clang-format on } // namespace detail -// clang-format off /// \ingroup compare -template -concept three_way_comparable = - weakly_equality_comparable_with && - detail::partially_ordered_with && - requires(remove_reference_t const& a, remove_reference_t const& b) { - { a <=> b } -> detail::compares_as; - }; -// clang-format on +template +concept three_way_comparable = weakly_equality_comparable_with + && detail::partially_ordered_with + && requires(remove_reference_t const& a, remove_reference_t const& b) { + { a <=> b } -> detail::compares_as; + }; } // namespace etl #endif // TETL_COMPARE_THREE_WAY_COMPAREABLE_HPP diff --git a/include/etl/_concepts/assignable_from.hpp b/include/etl/_concepts/assignable_from.hpp index 730dfc6c9..7cd1cb569 100644 --- a/include/etl/_concepts/assignable_from.hpp +++ b/include/etl/_concepts/assignable_from.hpp @@ -12,19 +12,14 @@ namespace etl { -// clang-format off - /// \headerfile etl/concepts.hpp /// \ingroup concepts -template -concept assignable_from = - is_lvalue_reference_v - // and common_reference_with const&, remove_reference_t const&> - and requires(LHS lhs, RHS&& rhs) { - { lhs = etl::forward(rhs) } -> same_as; - }; - -// clang-format on +template +concept assignable_from = is_lvalue_reference_v + // and common_reference_with const&, remove_reference_t const&> + and requires(LHS lhs, RHS&& rhs) { + { lhs = etl::forward(rhs) } -> same_as; + }; } // namespace etl diff --git a/include/etl/_concepts/weakly_equality_comparable_with.hpp b/include/etl/_concepts/weakly_equality_comparable_with.hpp index b5c034b0c..3fea96f30 100644 --- a/include/etl/_concepts/weakly_equality_comparable_with.hpp +++ b/include/etl/_concepts/weakly_equality_comparable_with.hpp @@ -10,12 +10,10 @@ namespace etl { -// clang-format off - /// \note Non-standard extension /// \headerfile etl/concepts.hpp /// \ingroup concepts -template +template concept weakly_equality_comparable_with = requires(remove_reference_t const& t, remove_reference_t const& u) { { t == u } -> boolean_testable; { t != u } -> boolean_testable; @@ -23,8 +21,6 @@ concept weakly_equality_comparable_with = requires(remove_reference_t const& { u != t } -> boolean_testable; }; -// clang-format on - } // namespace etl #endif // TETL_CONCEPTS_WEAKLY_EQUALITY_COMPAREABLE_WITH_HPP diff --git a/include/etl/_cstring/strcmp.hpp b/include/etl/_cstring/strcmp.hpp index 9f1f9cd9d..bfca41c61 100644 --- a/include/etl/_cstring/strcmp.hpp +++ b/include/etl/_cstring/strcmp.hpp @@ -23,7 +23,7 @@ namespace etl { #if __has_builtin(__builtin_strcmp) return __builtin_strcmp(lhs, rhs); #else - return etl::detail::strcmp(lhs, rhs); + return etl::detail::strcmp(lhs, rhs); #endif } diff --git a/include/etl/_cstring/strncmp.hpp b/include/etl/_cstring/strncmp.hpp index 6656e7809..50d646d17 100644 --- a/include/etl/_cstring/strncmp.hpp +++ b/include/etl/_cstring/strncmp.hpp @@ -25,7 +25,7 @@ namespace etl { #if __has_builtin(__builtin_strncmp) return __builtin_strncmp(lhs, rhs, count); #else - return etl::detail::strncmp(lhs, rhs, count); + return etl::detail::strncmp(lhs, rhs, count); #endif } diff --git a/include/etl/_cstring/strstr.hpp b/include/etl/_cstring/strstr.hpp index 7cf5ffdf5..e0df58027 100644 --- a/include/etl/_cstring/strstr.hpp +++ b/include/etl/_cstring/strstr.hpp @@ -17,12 +17,12 @@ namespace etl { /// \ingroup cstring [[nodiscard]] constexpr auto strstr(char* haystack, char* needle) noexcept -> char* { - return etl::detail::strstr_impl(haystack, needle); + return etl::detail::strstr(haystack, needle); } [[nodiscard]] constexpr auto strstr(char const* haystack, char const* needle) noexcept -> char const* { - return etl::detail::strstr_impl(haystack, needle); + return etl::detail::strstr(haystack, needle); } /// @} diff --git a/include/etl/_cwchar/wcsstr.hpp b/include/etl/_cwchar/wcsstr.hpp index 58645da3f..b4dfacfbc 100644 --- a/include/etl/_cwchar/wcsstr.hpp +++ b/include/etl/_cwchar/wcsstr.hpp @@ -15,7 +15,7 @@ namespace etl { /// https://en.cppreference.com/w/cpp/string/wide/wcspbrk [[nodiscard]] constexpr auto wcsstr(wchar_t* haystack, wchar_t* needle) noexcept -> wchar_t* { - return etl::detail::strstr_impl(haystack, needle); + return etl::detail::strstr(haystack, needle); } /// \brief Finds the first occurrence of the wide string needle in the wide @@ -25,7 +25,7 @@ namespace etl { /// https://en.cppreference.com/w/cpp/string/wide/wcspbrk [[nodiscard]] constexpr auto wcsstr(wchar_t const* haystack, wchar_t const* needle) noexcept -> wchar_t const* { - return etl::detail::strstr_impl(haystack, needle); + return etl::detail::strstr(haystack, needle); } } // namespace etl #endif // TETL_CWCHAR_WCSSTR_HPP diff --git a/include/etl/_iterator/incrementable_traits.hpp b/include/etl/_iterator/incrementable_traits.hpp index dd6e12f53..abca6ac8a 100644 --- a/include/etl/_iterator/incrementable_traits.hpp +++ b/include/etl/_iterator/incrementable_traits.hpp @@ -35,7 +35,6 @@ struct incrementable_traits { using difference_type = typename T::difference_type; }; -// clang-format off template requires(not detail::has_difference_type) and requires(T const& a, T const& b) { { a - b } -> etl::integral; @@ -44,8 +43,6 @@ struct incrementable_traits { using difference_type = etl::make_signed_t() - etl::declval())>; }; -// clang-format on - } // namespace etl #endif // TETL_ITERATOR_INCREMENTABLE_TRAITS_HPP diff --git a/include/etl/_iterator/indirectly_regular_unary_invocable.hpp b/include/etl/_iterator/indirectly_regular_unary_invocable.hpp index 3c174e12e..288aaa32a 100644 --- a/include/etl/_iterator/indirectly_regular_unary_invocable.hpp +++ b/include/etl/_iterator/indirectly_regular_unary_invocable.hpp @@ -15,16 +15,16 @@ namespace etl { -// clang-format off -template -concept indirectly_regular_unary_invocable = - etl::indirectly_readable - and etl::copy_constructible - and etl::regular_invocable&> - and etl::regular_invocable> - and etl::regular_invocable> - and etl::common_reference_with&>, etl::invoke_result_t>>; -// clang-format on +template +concept indirectly_regular_unary_invocable = etl::indirectly_readable + and etl::copy_constructible + and etl::regular_invocable&> + and etl::regular_invocable> + and etl::regular_invocable> + and etl::common_reference_with< + etl::invoke_result_t&>, + etl::invoke_result_t> + >; } // namespace etl diff --git a/include/etl/_iterator/indirectly_unary_invocable.hpp b/include/etl/_iterator/indirectly_unary_invocable.hpp index e113cc98d..8374db05a 100644 --- a/include/etl/_iterator/indirectly_unary_invocable.hpp +++ b/include/etl/_iterator/indirectly_unary_invocable.hpp @@ -15,16 +15,16 @@ namespace etl { -// clang-format off template -concept indirectly_unary_invocable = - etl::indirectly_readable - and etl::copy_constructible - and etl::invocable&> - and etl::invocable> - and etl::invocable> - and etl::common_reference_with&>, etl::invoke_result_t>>; -// clang-format on +concept indirectly_unary_invocable = etl::indirectly_readable + and etl::copy_constructible + and etl::invocable&> + and etl::invocable> + and etl::invocable> + and etl::common_reference_with< + etl::invoke_result_t&>, + etl::invoke_result_t> + >; } // namespace etl diff --git a/include/etl/_iterator/legacy_input_iterator.hpp b/include/etl/_iterator/legacy_input_iterator.hpp index 645a82118..06058a08a 100644 --- a/include/etl/_iterator/legacy_input_iterator.hpp +++ b/include/etl/_iterator/legacy_input_iterator.hpp @@ -14,8 +14,6 @@ namespace etl { -// clang-format off - /// \note Non-standard extension /// \headerfile etl/iterator.hpp /// \ingroup iterator @@ -23,14 +21,13 @@ template concept legacy_input_iterator = etl::legacy_iterator and etl::equality_comparable and requires(Iter i) { typename etl::incrementable_traits::difference_type; typename etl::indirectly_readable_traits::value_type; - // typename etl::common_reference_t&&, typename etl::indirectly_readable_traits::value_type&>; + // typename etl::common_reference_t&&, typename + // etl::indirectly_readable_traits::value_type&>; *i++; // typename etl::common_reference_t::value_type&>; requires etl::signed_integral::difference_type>; }; -// clang-format on - } // namespace etl #endif // TETL_ITERATOR_LEGACY_INPUT_ITERATOR_HPP diff --git a/include/etl/_iterator/weakly_incrementable.hpp b/include/etl/_iterator/weakly_incrementable.hpp index 378efb840..570bc289c 100644 --- a/include/etl/_iterator/weakly_incrementable.hpp +++ b/include/etl/_iterator/weakly_incrementable.hpp @@ -11,7 +11,6 @@ namespace etl { -// clang-format off /// \ingroup iterator template concept weakly_incrementable = etl::movable and requires(Iter i) { @@ -20,7 +19,6 @@ concept weakly_incrementable = etl::movable and requires(Iter i) { { ++i } -> etl::same_as; i++; }; -// clang-format on } // namespace etl diff --git a/include/etl/_random/uniform_real_distribution.hpp b/include/etl/_random/uniform_real_distribution.hpp index 372133c13..c43b6fda1 100644 --- a/include/etl/_random/uniform_real_distribution.hpp +++ b/include/etl/_random/uniform_real_distribution.hpp @@ -4,6 +4,7 @@ #ifndef TETL_RANDOM_UNIFORM_REAL_DISTRIBUTION_HPP #define TETL_RANDOM_UNIFORM_REAL_DISTRIBUTION_HPP +#include #include #include @@ -110,10 +111,7 @@ struct uniform_real_distribution { static_assert(minBits <= 64); // x = a + u * (b - a) - auto const a = parm.a(); - auto const b = parm.b(); - auto const u = etl::generate_canonical(g); - return a + u * (b - a); + return etl::lerp(parm.a(), parm.b(), etl::generate_canonical(g)); } friend constexpr auto operator==(uniform_real_distribution const& x, uniform_real_distribution const& y) -> bool diff --git a/include/etl/_string_view/basic_string_view.hpp b/include/etl/_string_view/basic_string_view.hpp index 1f1a94cb2..560bf6f9e 100644 --- a/include/etl/_string_view/basic_string_view.hpp +++ b/include/etl/_string_view/basic_string_view.hpp @@ -616,6 +616,9 @@ struct basic_string_view { /// substring, or npos if no such character is found. [[nodiscard]] constexpr auto find_last_of(basic_string_view v, size_type pos = npos) const noexcept -> size_type { + if (empty()) { + return npos; + } auto offset = etl::clamp(pos, 0, size() - 1); do { // NOLINT(cppcoreguidelines-avoid-do-while) auto const current = unsafe_at(offset); @@ -675,6 +678,10 @@ struct basic_string_view { /// characters in the given string, or npos if no such character is found. [[nodiscard]] constexpr auto find_last_not_of(basic_string_view v, size_type pos = npos) const noexcept -> size_type { + if (empty()) { + return npos; + } + auto offset = etl::clamp(pos, 0, size() - 1); do { // NOLINT(cppcoreguidelines-avoid-do-while) auto equals = [&](auto ch) { return ch == unsafe_at(offset); }; diff --git a/include/etl/_strings/cstr.hpp b/include/etl/_strings/cstr.hpp index 1e7cc3c6f..d37b2b125 100644 --- a/include/etl/_strings/cstr.hpp +++ b/include/etl/_strings/cstr.hpp @@ -61,32 +61,25 @@ template return dest; } -template +template [[nodiscard]] constexpr auto strcmp(CharT const* lhs, CharT const* rhs) -> int { - for (; *lhs != CharT(0); ++lhs, ++rhs) { - if (*lhs != *rhs) { - break; - } + while (*lhs and (*lhs == *rhs)) { + lhs++; + rhs++; } - return static_cast(*lhs) - static_cast(*rhs); + return static_cast(static_cast(*lhs) - static_cast(*rhs)); } -template +template [[nodiscard]] constexpr auto strncmp(CharT const* lhs, CharT const* rhs, SizeT const count) -> int { - CharT u1{}; - CharT u2{}; - - auto localCount = count; - while (localCount-- > 0) { - u1 = static_cast(*lhs++); - u2 = static_cast(*rhs++); - if (u1 != u2) { - return static_cast(u1 - u2); - } - if (u1 == CharT(0)) { - return 0; + for (auto i = SizeT(0); i < count; ++i) { + auto const l = static_cast(*lhs++); + auto const r = static_cast(*rhs++); + auto const diff = static_cast(l - r); + if (l == CharT(0) or diff != 0) { + return diff; } } @@ -170,14 +163,26 @@ template } template -[[nodiscard]] constexpr auto strstr_impl(CharT* haystack, CharT* needle) noexcept -> CharT* +[[nodiscard]] constexpr auto strstr(CharT* haystack, CharT* needle) noexcept -> CharT* { - while (*haystack != CharT(0)) { - if ((*haystack == *needle) and (strcmp(haystack, needle) == 0)) { - return haystack; + if (not *needle) { + return haystack; + } + + for (auto const* h = haystack; *h; h++) { + auto const* hIt = h; + auto const* nIt = needle; + + while (*hIt and *nIt and *hIt == *nIt) { + hIt++; + nIt++; + } + + if (not *nIt) { // reached end of needle, found match + return haystack + (h - haystack); } - haystack++; } + return nullptr; } diff --git a/include/etl/algorithm.hpp b/include/etl/algorithm.hpp index 56082862a..97a37854e 100644 --- a/include/etl/algorithm.hpp +++ b/include/etl/algorithm.hpp @@ -97,5 +97,6 @@ #include #include #include +#include #endif // TETL_ALGORITHM_HPP diff --git a/scripts/build-atfe.sh b/scripts/build-atfe.sh new file mode 100755 index 000000000..f44e8d9f9 --- /dev/null +++ b/scripts/build-atfe.sh @@ -0,0 +1,17 @@ +#!/bin/sh +# SPDX-License-Identifier: BSL-1.0 +# SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +rm -rf cmake-build-atfe +cmake -S . -B cmake-build-atfe \ + -G Ninja -D CMAKE_BUILD_TYPE=Release \ + --toolchain cmake/toolchain/arm-atfe.cmake \ + -D ATFE_TOOLCHAIN=/home/tobante/bin/ATfE-21.1.1-Linux-x86_64 \ + -D CMAKE_CROSSCOMPILING_EMULATOR="qemu-system-arm;-M;microbit;-semihosting;-nographic;-kernel" \ + -D CMAKE_CXX_STANDARD=26 \ + -D CMAKE_COMPILE_WARNING_AS_ERROR=ON \ + -D CMAKE_CXX_SCAN_FOR_MODULES=OFF \ + -D TETL_BUILD_CONTRACT_CHECKS=OFF + +cmake --build cmake-build-atfe --target all +ctest --test-dir cmake-build-atfe --output-on-failure -j 16 diff --git a/scripts/loc_history.py b/scripts/loc_history.py index 10fcabe54..40b1e7550 100644 --- a/scripts/loc_history.py +++ b/scripts/loc_history.py @@ -5,7 +5,9 @@ Requires scc tool in $PATH, then run: -python scripts/for_each_commit.py https://github.com/tobanteEmbedded/tetl --branch linalg --dest some-scratch-dir/tetl --cmd "scc -f json2 --exclude-dir arm_make -o ../out/{date}-{commit}.json ." +mkdir -p some-scratch-dir/out +python scripts/for_each_commit.py https://github.com/tobanteEmbedded/tetl --branch main --dest some-scratch-dir/tetl --cmd "scc -f json2 --exclude-dir arm_make -o ../out/{date}-{commit}.json ." +python scripts/loc_history.py some-scratch-dir/out """ import glob @@ -28,27 +30,40 @@ def main(): with open(json_dir/json_file, 'r') as f: content = json.load(f) - file_sum = 0 + total_lines = 0 + code_lines = 0 + comment_lines = 0 + blank_lines = 0 for lang in content['languageSummary']: - file_sum += lang['Lines'] + total_lines += lang['Lines'] + code_lines += lang['Code'] + comment_lines += lang['Comment'] + blank_lines += lang['Blank'] date_and_commit = json_file.stem.rsplit('-', 1) results.append({ 'date': date_and_commit[0], 'commit': date_and_commit[1], - 'lines': file_sum, + 'total': total_lines, + 'code': code_lines, + 'comment': comment_lines, + 'blank': blank_lines, }) df = pd.DataFrame.from_records(results) df['date'] = pd.to_datetime(df['date'], utc=True) - df.to_csv("loc.csv", encoding='utf-8', sep=';', index=False) + df['delta'] = df['total'].diff() print(df) print(df.dtypes) - plt.plot(df['date'], df['lines']) + plt.plot(df['date'], df['total'], label='Total') + plt.plot(df['date'], df['code'], label='Code') + plt.plot(df['date'], df['comment'], label='Comment') + plt.plot(df['date'], df['blank'], label='Blank') plt.xlabel('Date') plt.ylabel('LOC') plt.grid(which='both') + plt.legend() plt.show() diff --git a/scripts/sorting.cpp b/scripts/sorting.cpp new file mode 100644 index 000000000..0f58b2a8c --- /dev/null +++ b/scripts/sorting.cpp @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: BSL-1.0 +// SPDX-FileCopyrightText: Copyright (C) 2025 Tobias Hienzsch + +// Count compare, copy, move & swap operations in sort algorithms. +// Run from root of git repo: +// clang++ -std=c++23 -O3 -march=native -I include scripts/sorting.cpp -o sorting +// ./sorting + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +struct SortCounters { + inline static uint64_t comps = 0; + inline static uint64_t swaps = 0; // times your swap(T&,T&) was used + inline static uint64_t copies = 0; // copy-ctor or copy-assign + inline static uint64_t moves = 0; // move-ctor or move-assign + + static void reset() + { + comps = swaps = copies = moves = 0; + } +}; + +template +struct Instrumented { + T v; + + Instrumented() = default; + Instrumented(T const& x) + : v(x) + { + } + Instrumented(T&& x) noexcept(etl::is_nothrow_move_constructible_v) + : v(etl::move(x)) + { + } + + Instrumented(Instrumented const& o) + : v(o.v) + { + ++SortCounters::copies; + } + Instrumented(Instrumented&& o) noexcept(etl::is_nothrow_move_constructible_v) + : v(etl::move(o.v)) + { + ++SortCounters::moves; + } + + auto operator=(Instrumented const& o) -> Instrumented& + { + v = o.v; + ++SortCounters::copies; + return *this; + } + auto operator=(Instrumented&& o) noexcept(etl::is_nothrow_move_assignable_v) -> Instrumented& + { + v = etl::move(o.v); + ++SortCounters::moves; + return *this; + } + + friend auto operator<(Instrumented const& lhs, Instrumented const& rhs) -> bool + { + ++SortCounters::comps; + return lhs.v < rhs.v; + } + + friend auto swap(Instrumented& lhs, Instrumented& rhs) noexcept(noexcept(etl::swap(lhs.v, rhs.v))) -> void + { + using etl::swap; + swap(lhs.v, rhs.v); + ++SortCounters::swaps; + } +}; + +// handy lower bound log2(n!) to compare with comparison sorts’ theoretical minimum +inline double log2_factorial(size_t n) +{ + double s = 0.0; + for (size_t i = 2; i <= n; ++i) { + s += std::log2((double)i); + } + return s; +} + +template +void run_case(char const* label, SortFn sorter, size_t n) +{ + std::vector> a; + a.reserve(n); + std::mt19937 rng(123456789); + for (size_t i = 0; i < n; ++i) { + a.emplace_back(int(rng())); + // a.emplace_back(int(i)); + } + + SortCounters::reset(); + auto aview = etl::span>{a.data(), a.size()}; + sorter(aview.begin(), aview.end()); // call your sorter or std::sort + + double const lb = log2_factorial(n); // ~ n log2 n - 1.44 n + double const nlogn = n * std::log2((double)n); // rough model + + std::println( + "{:20}|{:^10}|{:^15}|{:^15.3f}|{:^15.3f}|{:^15.3f}|{:^10}|{:^10}|{:^10}", + label, + n, + SortCounters::comps, + SortCounters::comps / static_cast(n * n), + SortCounters::comps / std::max(1.0, nlogn), + SortCounters::comps / std::max(1.0, lb), + SortCounters::swaps, + SortCounters::copies, + SortCounters::moves + ); +} + +int main() +{ + for (size_t n : {1u << 2, 1u << 4, 1u << 8, 1u << 12, 1u << 14}) { + std::println( + "{:^20}|{:^10}|{:^15}|{:^15}|{:^15}|{:^15}|{:^10}|{:^10}|{:^10}", + "Algorithm", + "Size", + "Comps", + "Comps/(n^2)", + "Comps/(nlogn)", + "Comps/(n!)", + "Swap", + "Copy", + "Move" + ); + + run_case("std::sort", [](auto f, auto l) { std::sort(f, l); }, n); + run_case("etl::sort", [](auto f, auto l) { etl::sort(f, l); }, n); + + run_case("std::stable_sort", [](auto f, auto l) { std::stable_sort(f, l); }, n); + run_case("etl::stable_sort", [](auto f, auto l) { etl::stable_sort(f, l); }, n); + + run_case("etl::merge_sort", [](auto f, auto l) { etl::merge_sort(f, l); }, n); + run_case("etl::quick_sort", [](auto f, auto l) { etl::quick_sort(f, l); }, n); + run_case("etl::insertion_sort", [](auto f, auto l) { etl::insertion_sort(f, l); }, n); + run_case("etl::bubble_sort", [](auto f, auto l) { etl::bubble_sort(f, l); }, n); + run_case("etl::exchange_sort", [](auto f, auto l) { etl::exchange_sort(f, l); }, n); + run_case("etl::gnome_sort", [](auto f, auto l) { etl::gnome_sort(f, l); }, n); + + run_case("std::nth_element", [](auto f, auto l) { std::nth_element(f, etl::midpoint(f, l), l); }, n); + run_case("etl::nth_element", [](auto f, auto l) { etl::nth_element(f, etl::midpoint(f, l), l); }, n); + std::puts(""); + } +} diff --git a/src/inc/algorithm.inc b/src/inc/algorithm.inc index d1f3bcfc1..65280490a 100644 --- a/src/inc/algorithm.inc +++ b/src/inc/algorithm.inc @@ -52,6 +52,7 @@ using etl::partial_sort; using etl::partition; using etl::partition_copy; using etl::partition_point; +using etl::ranges::in_fun_result; using etl::remove; using etl::remove_copy; using etl::remove_copy_if; @@ -78,7 +79,6 @@ using etl::transform; using etl::unique; using etl::unique_copy; using etl::upper_bound; -using etl::ranges::in_fun_result; // Non-standard extensions using etl::bubble_sort; @@ -86,5 +86,6 @@ using etl::exchange_sort; using etl::gnome_sort; using etl::insertion_sort; using etl::merge_sort; +using etl::quick_sort; } // namespace etl diff --git a/tests/chrono/calender.day.t.cpp b/tests/chrono/calender.day.t.cpp index b88657ed5..e38e2e83c 100644 --- a/tests/chrono/calender.day.t.cpp +++ b/tests/chrono/calender.day.t.cpp @@ -191,7 +191,7 @@ namespace chrono = etl::chrono; { // traits CHECK(etl::is_trivially_default_constructible_v); - CHECK(etl::is_nothrow_constructible_v); + CHECK(etl::is_nothrow_constructible_v); { auto const wdi = chrono::weekday_indexed{}; diff --git a/tests/chrono/calender.year.t.cpp b/tests/chrono/calender.year.t.cpp index 66a770e58..aaa9f9d3c 100644 --- a/tests/chrono/calender.year.t.cpp +++ b/tests/chrono/calender.year.t.cpp @@ -20,7 +20,7 @@ namespace chrono = etl::chrono; // traits CHECK(etl::is_trivially_default_constructible_v); - CHECK(etl::is_nothrow_constructible_v); + CHECK(etl::is_nothrow_constructible_v); CHECK(static_cast(chrono::year::min()) == -32767); CHECK(static_cast(chrono::year::max()) == +32767); diff --git a/tests/cmath/cosh.t.cpp b/tests/cmath/cosh.t.cpp index 9bad82055..a995689b5 100644 --- a/tests/cmath/cosh.t.cpp +++ b/tests/cmath/cosh.t.cpp @@ -21,7 +21,6 @@ static constexpr auto test() -> bool CHECK_APPROX(etl::cosh(T(1)), T(1.543080635)); CHECK_APPROX(etl::cosh(T(2)), T(3.762195691)); CHECK_APPROX(etl::cosh(T(4)), T(27.30823284)); - CHECK_APPROX(etl::cosh(T(8)), T(1490.479161)); return true; }