From fead0b280a3b242b15191c3ebaefc42b6159eb16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 22 Aug 2025 15:18:19 +0200 Subject: [PATCH 001/104] CI-unixish.yml: removed duplicated execution of integration test (#503) --- .github/workflows/CI-unixish.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index cb498b5e..c343e279 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -51,10 +51,6 @@ jobs: run: | make -j$(nproc) selfcheck - - name: integration test - run: | - python3 -m pytest integration_test.py -vv - - name: Run CMake run: | cmake -S . -B cmake.output From 7bca11f0ec435df3c9ccefd37c9a21a3c575355b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 22 Aug 2025 18:24:41 +0200 Subject: [PATCH 002/104] CI-unixish.yml: do not run with `g++` on `macos-*` as it is just an alias for `clang++` (#483) --- .github/workflows/CI-unixish.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index c343e279..718414d8 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -7,8 +7,13 @@ jobs: strategy: matrix: - compiler: [clang++, g++] os: [ubuntu-22.04, ubuntu-24.04, macos-13, macos-14, macos-15] + compiler: [clang++] + include: + - os: ubuntu-22.04 + compiler: g++ + - os: ubuntu-24.04 + compiler: g++ fail-fast: false runs-on: ${{ matrix.os }} From 285998182edd0280a9c1b5fe877f074fe441fd16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 22 Aug 2025 20:51:46 +0200 Subject: [PATCH 003/104] fixed #478 - fail builds in CI on compiler warnings (#479) --- .github/workflows/CI-unixish.yml | 16 ++++++++-------- .github/workflows/CI-windows.yml | 2 +- .github/workflows/clang-tidy.yml | 2 +- CMakeLists.txt | 4 ++++ appveyor.yml | 3 ++- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index 718414d8..c80aaba2 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -47,10 +47,10 @@ jobs: python3 -m pip install pytest - name: make simplecpp - run: make -j$(nproc) + run: make -j$(nproc) CXXOPTS="-Werror" - name: make test - run: make -j$(nproc) test + run: make -j$(nproc) test CXXOPTS="-Werror" - name: selfcheck run: | @@ -58,7 +58,7 @@ jobs: - name: Run CMake run: | - cmake -S . -B cmake.output + cmake -S . -B cmake.output -DCMAKE_COMPILE_WARNING_AS_ERROR=On - name: CMake simplecpp run: | @@ -81,19 +81,19 @@ jobs: if: matrix.os == 'ubuntu-24.04' && matrix.compiler == 'g++' run: | make clean - make -j$(nproc) test selfcheck CXXOPTS="-g3 -D_GLIBCXX_DEBUG" + make -j$(nproc) test selfcheck CXXOPTS="-Werror -g3 -D_GLIBCXX_DEBUG" - name: Run with libc++ hardening mode if: matrix.os == 'ubuntu-24.04' && matrix.compiler == 'clang++' run: | make clean - make -j$(nproc) test selfcheck CXXOPTS="-stdlib=libc++ -g3 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG" LDOPTS="-lc++" + make -j$(nproc) test selfcheck CXXOPTS="-Werror -stdlib=libc++ -g3 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG" LDOPTS="-lc++" - name: Run AddressSanitizer if: matrix.os == 'ubuntu-24.04' run: | make clean - make -j$(nproc) test selfcheck CXXOPTS="-O2 -g3 -fsanitize=address" LDOPTS="-fsanitize=address" + make -j$(nproc) test selfcheck CXXOPTS="-Werror -O2 -g3 -fsanitize=address" LDOPTS="-fsanitize=address" env: ASAN_OPTIONS: detect_stack_use_after_return=1 @@ -101,7 +101,7 @@ jobs: if: matrix.os == 'ubuntu-24.04' run: | make clean - make -j$(nproc) test selfcheck CXXOPTS="-O2 -g3 -fsanitize=undefined -fno-sanitize=signed-integer-overflow" LDOPTS="-fsanitize=undefined -fno-sanitize=signed-integer-overflow" + make -j$(nproc) test selfcheck CXXOPTS="-Werror -O2 -g3 -fsanitize=undefined -fno-sanitize=signed-integer-overflow" LDOPTS="-fsanitize=undefined -fno-sanitize=signed-integer-overflow" env: UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1:report_error_type=1 @@ -110,4 +110,4 @@ jobs: if: false && matrix.os == 'ubuntu-24.04' && matrix.compiler == 'clang++' run: | make clean - make -j$(nproc) test selfcheck CXXOPTS="-O2 -g3 -stdlib=libc++ -fsanitize=memory" LDOPTS="-lc++ -fsanitize=memory" + make -j$(nproc) test selfcheck CXXOPTS="-Werror -O2 -g3 -stdlib=libc++ -fsanitize=memory" LDOPTS="-lc++ -fsanitize=memory" diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 971f3827..d4c99388 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -40,7 +40,7 @@ jobs: - name: Run CMake run: | - cmake -G "Visual Studio 17 2022" -A x64 . || exit /b !errorlevel! + cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_COMPILE_WARNING_AS_ERROR=On . || exit /b !errorlevel! - name: Build run: | diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index a2f7b6dc..41d2ee6f 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -30,7 +30,7 @@ jobs: - name: Prepare CMake run: | - cmake -S . -B cmake.output -G "Unix Makefiles" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + cmake -S . -B cmake.output -G "Unix Makefiles" -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON env: CXX: clang-20 diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ab0166e..f9e3eb6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,10 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") add_compile_options_safe(-Wuseless-cast) elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") add_compile_definitions(_CRT_SECURE_NO_WARNINGS) + # TODO: bump warning level + #add_compile_options(/W4) # Warning Level + # TODO: enable warning + add_compile_options(/wd4267) # warning C4267: '...': conversion from 'size_t' to 'unsigned int', possible loss of data elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Weverything) # no need for c++98 compatibility diff --git a/appveyor.yml b/appveyor.yml index ea8dd1df..09aa6cbe 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,9 +10,10 @@ environment: build_script: - ECHO Building %configuration% %platform% with MSVC %VisualStudioVersion% using %PlatformToolset% PlatformToolset - - cmake -G "Visual Studio 14" . + - cmake -DCMAKE_COMPILE_WARNING_AS_ERROR=On -G "Visual Studio 14" . - dir - 'CALL "C:\Program Files (x86)\Microsoft Visual Studio %VisualStudioVersion%\VC\vcvarsall.bat" %vcvarsall_platform%' + - set _CL_=/WX - msbuild "simplecpp.sln" /consoleloggerparameters:Verbosity=minimal /target:Build /property:Configuration="%configuration%";Platform=%platform% /p:PlatformToolset=%PlatformToolset% /maxcpucount /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" test_script: From 538c5c4cd8baf806835c0d51ce2a9814ca883a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sat, 23 Aug 2025 10:47:23 +0200 Subject: [PATCH 004/104] fixed #485 - addressed zizmor findings in GitHub Actions (#486) --- .github/workflows/CI-unixish.yml | 5 +++++ .github/workflows/CI-windows.yml | 5 +++++ .github/workflows/clang-tidy.yml | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index c80aaba2..60361389 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -2,6 +2,9 @@ name: CI-unixish on: [push, pull_request] +permissions: + contents: read + jobs: build: @@ -23,6 +26,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Install missing software on ubuntu if: matrix.os == 'ubuntu-24.04' diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index d4c99388..767bba6c 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -6,6 +6,9 @@ name: CI-windows on: [push,pull_request] +permissions: + contents: read + defaults: run: shell: cmd @@ -23,6 +26,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Setup msbuild.exe uses: microsoft/setup-msbuild@v2 diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 41d2ee6f..fd71bdfe 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -4,6 +4,9 @@ name: clang-tidy on: [push, pull_request] +permissions: + contents: read + jobs: build: @@ -11,6 +14,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Install missing software run: | From 5fcb07307bd08226831667a85984bd5a392fc640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Thu, 28 Aug 2025 17:34:20 +0200 Subject: [PATCH 005/104] fixed #476 - fixed MSYS2 compilation with MSYS msystem (#513) --- simplecpp.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index fd327549..74ab66e1 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2385,12 +2385,17 @@ namespace simplecpp { namespace simplecpp { #ifdef __CYGWIN__ + static bool startsWith(const std::string &s, const std::string &p) + { + return (s.size() >= p.size()) && std::equal(p.begin(), p.end(), s.begin()); + } + std::string convertCygwinToWindowsPath(const std::string &cygwinPath) { std::string windowsPath; std::string::size_type pos = 0; - if (cygwinPath.size() >= 11 && startsWith_(cygwinPath, "/cygdrive/")) { + if (cygwinPath.size() >= 11 && startsWith(cygwinPath, "/cygdrive/")) { const unsigned char driveLetter = cygwinPath[10]; if (std::isalpha(driveLetter)) { if (cygwinPath.size() == 11) { From cfd179711f413aa8e0da9c2f437ad4f8938d5f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 28 Aug 2025 19:49:29 +0200 Subject: [PATCH 006/104] Switch to uncrustify (#517) --- .uncrustify.cfg | 170 ++++++++++++++++++++++++++++++++++++++++++++++++ runastyle | 23 ------- runastyle.bat | 26 -------- runformat | 39 +++++++++++ simplecpp.cpp | 18 ++--- simplecpp.h | 9 ++- test.cpp | 24 +++---- 7 files changed, 234 insertions(+), 75 deletions(-) create mode 100644 .uncrustify.cfg delete mode 100755 runastyle delete mode 100644 runastyle.bat create mode 100755 runformat diff --git a/.uncrustify.cfg b/.uncrustify.cfg new file mode 100644 index 00000000..81722ff7 --- /dev/null +++ b/.uncrustify.cfg @@ -0,0 +1,170 @@ +# Uncrustify-0.80.1_f + +# The original size of tabs in the input. +# +# Default: 8 +input_tab_size = 4 # unsigned number + +# The size of tabs in the output (only used if align_with_tabs=true). +# +# Default: 8 +output_tab_size = 4 # unsigned number + +# Add or remove space between 'while' and '('. +sp_while_paren_open = add # ignore/add/remove/force + +# Add or remove space around boolean operators '&&' and '||'. +sp_bool = force # ignore/add/remove/force + +# Add or remove space inside '(' and ')'. +sp_inside_paren = remove # ignore/add/remove/force + +# Add or remove space between nested parentheses, i.e. '((' vs. ') )'. +sp_paren_paren = remove # ignore/add/remove/force + +# Add or remove space between ')' and '{'. +sp_paren_brace = force # ignore/add/remove/force + +# Add or remove space between pointer stars '*'. +sp_between_ptr_star = remove # ignore/add/remove/force + +# Add or remove space before '<'. +sp_before_angle = remove # ignore/add/remove/force + +# Add or remove space inside '<' and '>'. +sp_inside_angle = remove # ignore/add/remove/force + +# Add or remove space after '>'. +sp_after_angle = add # ignore/add/remove/force + +# Add or remove space between '>' and '(' as found in 'new List(foo);'. +sp_angle_paren = remove # ignore/add/remove/force + +# Add or remove space between '>' and a word as in 'List m;' or +# 'template static ...'. +sp_angle_word = add # ignore/add/remove/force + +# Add or remove space between '>' and '>' in '>>' (template stuff). +# +# Default: add +sp_angle_shift = ignore # ignore/add/remove/force + +# (C++11) Permit removal of the space between '>>' in 'foo >'. Note +# that sp_angle_shift cannot remove the space without this option. +sp_permit_cpp11_shift = true # true/false + +# Add or remove space before '(' of control statements ('if', 'for', 'switch', +# 'while', etc.). +sp_before_sparen = force # ignore/add/remove/force + +# Add or remove space inside '(' and ')' of control statements. +sp_inside_sparen = remove # ignore/add/remove/force + +# Add or remove space after ')' of control statements. +sp_after_sparen = force # ignore/add/remove/force + +# Add or remove space between ')' and '{' of of control statements. +sp_sparen_brace = force # ignore/add/remove/force + +# Add or remove space before ';' in non-empty 'for' statements. +sp_before_semi_for = remove # ignore/add/remove/force + +# Add or remove space after the final semicolon of an empty part of a for +# statement, as in 'for ( ; ; )'. +sp_after_semi_for_empty = remove # ignore/add/remove/force + +# Add or remove space before '[]'. +sp_before_squares = remove # ignore/add/remove/force + +# Add or remove space before C++17 structured bindings. +sp_cpp_before_struct_binding = ignore # ignore/add/remove/force + +# Add or remove space inside a non-empty '[' and ']'. +sp_inside_square = remove # ignore/add/remove/force + +# Add or remove space after class ':'. +sp_after_class_colon = force # ignore/add/remove/force + +# Add or remove space before class ':'. +sp_before_class_colon = force # ignore/add/remove/force + +# Add or remove space inside '{}'. +sp_inside_braces_empty = remove # ignore/add/remove/force + +# Add or remove space between 'else' and '{' if on the same line. +sp_else_brace = force # ignore/add/remove/force + +# Add or remove space between '}' and 'else' if on the same line. +sp_brace_else = force # ignore/add/remove/force + +# Add or remove space before the '{' of a 'catch' statement, if the '{' and +# 'catch' are on the same line, as in 'catch (decl) {'. +sp_catch_brace = force # ignore/add/remove/force + +# Add or remove space between '}' and 'catch' if on the same line. +sp_brace_catch = force # ignore/add/remove/force + +# The number of columns to indent per level. Usually 2, 3, 4, or 8. +# +# Default: 8 +indent_columns = 4 # unsigned number + +# How to use tabs when indenting code. +# +# 0: Spaces only +# 1: Indent with tabs to brace level, align with spaces (default) +# 2: Indent and align with tabs, using spaces when not on a tabstop +# +# Default: 1 +indent_with_tabs = 0 # unsigned number + +# Whether to indent the body of a 'namespace'. +indent_namespace = true # true/false + +# Whether the 'class' body is indented. +indent_class = true # true/false + +# How to indent access specifiers that are followed by a +# colon. +# +# >0: Absolute column where 1 is the leftmost column +# <=0: Subtract from brace indent +# +# Default: 1 +indent_access_spec = -4 # number + +# Whether to collapse empty blocks between '{' and '}' except for functions. +# Use nl_collapse_empty_body_functions to specify how empty function braces +# should be formatted. +nl_collapse_empty_body = true # true/false + +# Whether to collapse empty blocks between '{' and '}' for functions only. +# If true, overrides nl_inside_empty_func. +nl_collapse_empty_body_functions = true # true/false + +# Whether to convert all tabs to spaces in comments. If false, tabs in +# comments are left alone, unless used for indenting. +cmt_convert_tab_to_spaces = true # true/false + +# An offset value that controls the indentation of the body of a multiline #define. +# 'body' refers to all the lines of a multiline #define except the first line. +# Requires 'pp_ignore_define_body = false'. +# +# <0: Absolute column: the body indentation starts off at the specified column +# (ex. -3 ==> the body is indented starting from column 3) +# >=0: Relative to the column of the '#' of '#define' +# (ex. 3 ==> the body is indented starting 3 columns at the right of '#') +# +# Default: 8 +pp_multiline_define_body_indent = 4 # number + +# The value might be used twice: +# - at the assignment +# - at the opening brace +# +# To prevent the double use of the indentation value, use this option with the +# value 'true'. +# +# true: indentation will be used only once +# false: indentation will be used every time (default) +indent_cpp_lambda_only_once = true # true/false diff --git a/runastyle b/runastyle deleted file mode 100755 index 64298273..00000000 --- a/runastyle +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash -# The version check in this script is used to avoid commit battles -# between different developers that use different astyle versions as -# different versions might have different output (this has happened in -# the past). - -# If project management wishes to take a newer astyle version into use -# just change this string to match the start of astyle version string. -ASTYLE_VERSION="Artistic Style Version 3.0.1" -ASTYLE="astyle" - -DETECTED_VERSION=`$ASTYLE --version 2>&1` -if [[ "$DETECTED_VERSION" != ${ASTYLE_VERSION}* ]]; then - echo "You should use: ${ASTYLE_VERSION}"; - echo "Detected: ${DETECTED_VERSION}" - exit 1; -fi - -style="--style=kr --indent=spaces=4 --indent-namespaces --lineend=linux --min-conditional-indent=0" -options="--options=none --pad-header --unpad-paren --suffix=none --convert-tabs --attach-inlines --attach-classes --attach-namespaces" - -$ASTYLE $style $options *.cpp -$ASTYLE $style $options *.h diff --git a/runastyle.bat b/runastyle.bat deleted file mode 100644 index b8f11561..00000000 --- a/runastyle.bat +++ /dev/null @@ -1,26 +0,0 @@ -@REM Script to run AStyle on the sources -@REM The version check in this script is used to avoid commit battles -@REM between different developers that use different astyle versions as -@REM different versions might have different output (this has happened in -@REM the past). - -@REM If project management wishes to take a newer astyle version into use -@REM just change this string to match the start of astyle version string. -@SET ASTYLE_VERSION="Artistic Style Version 3.0.1" -@SET ASTYLE="astyle" - -@SET DETECTED_VERSION="" -@FOR /F "tokens=*" %%i IN ('%ASTYLE% --version') DO SET DETECTED_VERSION=%%i -@ECHO %DETECTED_VERSION% | FINDSTR /B /C:%ASTYLE_VERSION% > nul && ( - ECHO "%DETECTED_VERSION%" matches %ASTYLE_VERSION% -) || ( - ECHO You should use: %ASTYLE_VERSION% - ECHO Detected: "%DETECTED_VERSION%" - GOTO EXIT_ERROR -) - -@SET STYLE=--style=kr --indent=spaces=4 --indent-namespaces --lineend=linux --min-conditional-indent=0 -@SET OPTIONS=--pad-header --unpad-paren --suffix=none --convert-tabs --attach-inlines --attach-classes --attach-namespaces - -%ASTYLE% %STYLE% %OPTIONS% *.cpp -%ASTYLE% %STYLE% %OPTIONS% *.h \ No newline at end of file diff --git a/runformat b/runformat new file mode 100755 index 00000000..2f883a76 --- /dev/null +++ b/runformat @@ -0,0 +1,39 @@ +#!/bin/bash +# +# uncrustify-0.72 is used to format simplecpp and cppcheck source code. +# +# 1. Download source code: https://github.com/uncrustify/uncrustify/archive/refs/tags/uncrustify-0.72.0.zip +# It's important that all developers use the exact same version so we don't get a "format battle". +# 2. Building: +# - Linux: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make +# - Windows: mkdir build && cd build && cmake -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=Release .. && nmake +# 3. Ensure that the binary "uncrustify" is found by runformat. Either: +# - you can put uncrustify in your PATH +# - you can create an environment variable UNCRUSTIFY that has the full path of the binary + +UNCRUSTIFY_VERSION="0.72.0" +UNCRUSTIFY="${UNCRUSTIFY-uncrustify}" + +DETECTED_VERSION=$("$UNCRUSTIFY" --version 2>&1 | grep -o -E '[0-9.]+') +if [ "$DETECTED_VERSION" != "${UNCRUSTIFY_VERSION}" ]; then + echo "You should use version: ${UNCRUSTIFY_VERSION}" + echo "Detected version: ${DETECTED_VERSION}" + exit 1 +fi + +# OS variables +[ $(uname -s) = "Darwin" ] && export OSX=1 && export UNIX=1 +[ $(uname -s) = "Linux" ] && export LINUX=1 && export UNIX=1 +uname -s | grep -q "_NT-" && export WINDOWS=1 + +if [ $OSX ] +then + export CPUCOUNT=$(sysctl -n hw.ncpu) +elif [ $LINUX ] +then + export CPUCOUNT=$(nproc) +else + export CPUCOUNT="1" +fi + +$UNCRUSTIFY -c .uncrustify.cfg --no-backup *.cpp *.h diff --git a/simplecpp.cpp b/simplecpp.cpp index 74ab66e1..22a45e7a 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -761,18 +761,18 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, while (stream.good() && ch != '\n') { currentToken += ch; ch = stream.readChar(); - if(ch == '\\') { + if (ch == '\\') { TokenString tmp; char tmp_ch = ch; - while((stream.good()) && (tmp_ch == '\\' || tmp_ch == ' ' || tmp_ch == '\t')) { + while ((stream.good()) && (tmp_ch == '\\' || tmp_ch == ' ' || tmp_ch == '\t')) { tmp += tmp_ch; tmp_ch = stream.readChar(); } - if(!stream.good()) { + if (!stream.good()) { break; } - if(tmp_ch != '\n') { + if (tmp_ch != '\n') { currentToken += tmp; } else { const TokenString check_portability = currentToken + tmp; @@ -1667,7 +1667,7 @@ namespace simplecpp { } invalidHashHash(const Location &loc, const std::string ¯oName, const std::string &message) - : Error(loc, format(macroName, message)) { } + : Error(loc, format(macroName, message)) {} static inline invalidHashHash unexpectedToken(const Location &loc, const std::string ¯oName, const Token *tokenA) { return invalidHashHash(loc, macroName, "Unexpected token '"+ tokenA->str()+"'"); @@ -2672,7 +2672,7 @@ static unsigned long long stringToULLbounded( int base = 0, std::ptrdiff_t minlen = 1, std::size_t maxlen = std::string::npos -) + ) { const std::string sub = s.substr(pos, maxlen); const char * const start = sub.c_str(); @@ -3354,7 +3354,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL includetokenstack.push(filedata->tokens.cfront()); } - std::map > maybeUsedMacros; + std::map> maybeUsedMacros; for (const Token *rawtok = nullptr; rawtok || !includetokenstack.empty();) { if (rawtok == nullptr) { @@ -3751,11 +3751,11 @@ simplecpp::cstd_t simplecpp::getCStd(const std::string &std) { if (std == "c90" || std == "c89" || std == "iso9899:1990" || std == "iso9899:199409" || std == "gnu90" || std == "gnu89") return C89; - if (std == "c99" || std == "c9x" || std == "iso9899:1999" || std == "iso9899:199x" || std == "gnu99"|| std == "gnu9x") + if (std == "c99" || std == "c9x" || std == "iso9899:1999" || std == "iso9899:199x" || std == "gnu99" || std == "gnu9x") return C99; if (std == "c11" || std == "c1x" || std == "iso9899:2011" || std == "gnu11" || std == "gnu1x") return C11; - if (std == "c17" || std == "c18" || std == "iso9899:2017" || std == "iso9899:2018" || std == "gnu17"|| std == "gnu18") + if (std == "c17" || std == "c18" || std == "iso9899:2017" || std == "iso9899:2018" || std == "gnu17" || std == "gnu18") return C17; if (std == "c23" || std == "gnu23" || std == "c2x" || std == "gnu2x") return C23; diff --git a/simplecpp.h b/simplecpp.h index 8268fa8d..05de07dd 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -114,8 +114,7 @@ namespace simplecpp { } Token(const Token &tok) : - macro(tok.macro), op(tok.op), comment(tok.comment), name(tok.name), number(tok.number), whitespaceahead(tok.whitespaceahead), location(tok.location), previous(nullptr), next(nullptr), nextcond(nullptr), string(tok.string), mExpandedFrom(tok.mExpandedFrom) { - } + macro(tok.macro), op(tok.op), comment(tok.comment), name(tok.name), number(tok.number), whitespaceahead(tok.whitespaceahead), location(tok.location), previous(nullptr), next(nullptr), nextcond(nullptr), string(tok.string), mExpandedFrom(tok.mExpandedFrom) {} void flags() { name = (std::isalpha(static_cast(string[0])) || string[0] == '_' || string[0] == '$') @@ -325,9 +324,9 @@ namespace simplecpp { struct SIMPLECPP_LIB MacroUsage { explicit MacroUsage(const std::vector &f, bool macroValueKnown_) : macroLocation(f), useLocation(f), macroValueKnown(macroValueKnown_) {} std::string macroName; - Location macroLocation; - Location useLocation; - bool macroValueKnown; + Location macroLocation; + Location useLocation; + bool macroValueKnown; }; /** Tracking #if/#elif expressions */ diff --git a/test.cpp b/test.cpp index ccb653ca..7b108638 100644 --- a/test.cpp +++ b/test.cpp @@ -22,7 +22,7 @@ static int numberOfFailedAssertions = 0; #define ASSERT_EQUALS(expected, actual) (assertEquals((expected), (actual), __LINE__)) -#define ASSERT_THROW_EQUALS(stmt, e, expected) do { try { stmt; assertThrowFailed(__LINE__); } catch (const e& ex) { assertEquals((expected), (ex.what()), __LINE__); } } while(false) +#define ASSERT_THROW_EQUALS(stmt, e, expected) do { try { stmt; assertThrowFailed(__LINE__); } catch (const e& ex) { assertEquals((expected), (ex.what()), __LINE__); } } while (false) static std::string pprint(const std::string &in) { @@ -250,10 +250,10 @@ static void characterLiteral() ASSERT_EQUALS('\u0012', simplecpp::characterLiteralToLL("'\\u0012'")); ASSERT_EQUALS('\U00000012', simplecpp::characterLiteralToLL("'\\U00000012'")); - ASSERT_EQUALS((static_cast(static_cast('b')) << 8) | static_cast('c'), simplecpp::characterLiteralToLL("'bc'")); + ASSERT_EQUALS((static_cast(static_cast('b')) << 8) | static_cast('c'), simplecpp::characterLiteralToLL("'bc'")); ASSERT_EQUALS((static_cast(static_cast('\x23')) << 8) | static_cast('\x45'), simplecpp::characterLiteralToLL("'\\x23\\x45'")); - ASSERT_EQUALS((static_cast(static_cast('\11')) << 8) | static_cast('\222'), simplecpp::characterLiteralToLL("'\\11\\222'")); - ASSERT_EQUALS((static_cast(static_cast('\a')) << 8) | static_cast('\b'), simplecpp::characterLiteralToLL("'\\a\\b'")); + ASSERT_EQUALS((static_cast(static_cast('\11')) << 8) | static_cast('\222'), simplecpp::characterLiteralToLL("'\\11\\222'")); + ASSERT_EQUALS((static_cast(static_cast('\a')) << 8) | static_cast('\b'), simplecpp::characterLiteralToLL("'\\a\\b'")); if (sizeof(int) <= 4) ASSERT_EQUALS(-1, simplecpp::characterLiteralToLL("'\\xff\\xff\\xff\\xff'")); else @@ -438,14 +438,14 @@ static void comment_multiline() ASSERT_EQUALS("\n\nvoid f ( ) {", preprocess(code)); const char code1[] = "#define ABC {// \\\r\n" - "}\n" - "void f() ABC\n"; + "}\n" + "void f() ABC\n"; ASSERT_EQUALS("\n\nvoid f ( ) {", preprocess(code1)); const char code2[] = "#define A 1// \\\r" - "\r" - "2\r" - "A\r"; + "\r" + "2\r" + "A\r"; ASSERT_EQUALS("\n\n2\n1", preprocess(code2)); const char code3[] = "void f() {// \\ \n}\n"; @@ -1960,7 +1960,7 @@ static void location1() const char *code; code = "# 1 \"main.c\"\n\n\n" - "x"; + "x"; ASSERT_EQUALS("\n#line 3 \"main.c\"\nx", preprocess(code)); } @@ -2290,7 +2290,7 @@ static void include2() static void include3() // #16 - crash when expanding macro from header { const char code_c[] = "#include \"A.h\"\n" - "glue(1,2,3,4)\n" ; + "glue(1,2,3,4)\n"; const char code_h[] = "#define glue(a,b,c,d) a##b##c##d\n"; std::vector files; @@ -2317,7 +2317,7 @@ static void include3() // #16 - crash when expanding macro from header static void include4() // #27 - -include { - const char code_c[] = "X\n" ; + const char code_c[] = "X\n"; const char code_h[] = "#define X 123\n"; std::vector files; From f282eec74b776fe66bebf1de2c1202a5c767ae6c Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Fri, 29 Aug 2025 02:59:18 -0400 Subject: [PATCH 007/104] chore: Improve runformat script and integrate CI checks (#520) --- .github/workflows/format.yml | 62 +++++++++++ .gitignore | 1 + runformat | 201 +++++++++++++++++++++++++++++------ 3 files changed, 232 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/format.yml diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 00000000..4d5657f8 --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,62 @@ +# Syntax reference https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions +# Environment reference https://help.github.com/en/actions/reference/virtual-environments-for-github-hosted-runners +name: format + +on: + push: + branches: + - 'master' + - 'releases/**' + - '1.*' + tags: + - '1.*' + pull_request: + +permissions: + contents: read + +jobs: + format: + + runs-on: ubuntu-22.04 + + defaults: + run: + shell: bash -euo pipefail {0} + + env: + UNCRUSTIFY_INSTALL_DIR: ${{ github.workspace }}/runformat-uncrustify + + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Determine uncrustify version + id: get-uncrustify-version + run: | + version="$(./runformat --expected-uncrustify-version)" + echo "Expected uncrustify version: $version" + echo "version=$version" >> "$GITHUB_OUTPUT" + + - name: Set UNCRUSTIFY_VERSION env variable + run: | + version=$(./runformat --expected-uncrustify-version) + echo "version [$version]" + echo "UNCRUSTIFY_VERSION=${version}" >> "$GITHUB_ENV" + + - name: Cache uncrustify + uses: actions/cache@v4 + id: cache-uncrustify + with: + path: ${{ env.UNCRUSTIFY_INSTALL_DIR }} + key: ${{ runner.os }}-uncrustify-${{ steps.get-uncrustify-version.outputs.version }} + + - name: Install uncrustify + if: steps.cache-uncrustify.outputs.cache-hit != 'true' + run: | + ./runformat --install --install-dir "${UNCRUSTIFY_INSTALL_DIR}" + + - name: Uncrustify check + run: | + ./runformat diff --git a/.gitignore b/.gitignore index 113cf360..34d9c55d 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ *.app simplecpp testrunner +/.runformat-uncrustify # CLion /.idea diff --git a/runformat b/runformat index 2f883a76..0dc1ad52 100755 --- a/runformat +++ b/runformat @@ -1,39 +1,176 @@ #!/bin/bash # -# uncrustify-0.72 is used to format simplecpp and cppcheck source code. +# runformat - format this project's C++ sources with Uncrustify. # -# 1. Download source code: https://github.com/uncrustify/uncrustify/archive/refs/tags/uncrustify-0.72.0.zip -# It's important that all developers use the exact same version so we don't get a "format battle". -# 2. Building: -# - Linux: mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -# - Windows: mkdir build && cd build && cmake -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=Release .. && nmake -# 3. Ensure that the binary "uncrustify" is found by runformat. Either: -# - you can put uncrustify in your PATH -# - you can create an environment variable UNCRUSTIFY that has the full path of the binary - -UNCRUSTIFY_VERSION="0.72.0" -UNCRUSTIFY="${UNCRUSTIFY-uncrustify}" - -DETECTED_VERSION=$("$UNCRUSTIFY" --version 2>&1 | grep -o -E '[0-9.]+') -if [ "$DETECTED_VERSION" != "${UNCRUSTIFY_VERSION}" ]; then - echo "You should use version: ${UNCRUSTIFY_VERSION}" - echo "Detected version: ${DETECTED_VERSION}" - exit 1 +# Usage: +# ./runformat # format using the configured Uncrustify +# ./runformat --install # download, build, and use Uncrustify locally +# ./runformat --install --install-dir /abs/path +# ./runformat --expected-uncrustify-version # print ONLY the expected Uncrustify version +# +# You may also set: +# UNCRUSTIFY=/abs/path/to/uncrustify # use a specific binary +# UNCRUSTIFY_INSTALL_DIR=/abs/path # where `--install` will install +# +# Requirements: +# - All developers must use the *exact* same Uncrustify version to avoid format churn. +# - Either: +# * Have `uncrustify` in PATH, or +# * Set env var UNCRUSTIFY=/absolute/path/to/uncrustify, or +# * Run `./runformat --install` to fetch & build the pinned version locally. +# +# Notes: +# - The local install lives under: ./.runformat-uncrustify/uncrustify--install +# - The config file is expected at: ./.uncrustify.cfg +# + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +UNCRUSTIFY_VERSION="0.80.1" +UNCRUSTIFY_HASH="6bf662e05c4140dd4df5e45d6690cad96b4ef23c293b85813f5c725bbf1894d0" + +UNCRUSTIFY_WORK_DIR="${SCRIPT_DIR}/.runformat-uncrustify" + +# Allow external install dir override (arg or env). If not set, default under work dir. +DEFAULT_INSTALL_DIR="${UNCRUSTIFY_WORK_DIR}/uncrustify-${UNCRUSTIFY_VERSION}-install" +UNCRUSTIFY_INSTALL_DIR="${UNCRUSTIFY_INSTALL_DIR:-$DEFAULT_INSTALL_DIR}" +UNCRUSTIFY_BIN="${UNCRUSTIFY_INSTALL_DIR}/bin/uncrustify" + +# Allow override via env; default to local pinned build path. +UNCRUSTIFY="${UNCRUSTIFY:-$UNCRUSTIFY_BIN}" +UNCRUSTIFY_CONFIG="${SCRIPT_DIR}/.uncrustify.cfg" + +err() { echo -e >&2 "ERROR: $@\n"; } +die() { err $@; exit 1; } + +install_uncrustify() { + local root="uncrustify-${UNCRUSTIFY_VERSION}" + local file="${root}.tar.gz" + local url="https://github.com/uncrustify/uncrustify/releases/download/${root}/${file}" + + mkdir -p "${UNCRUSTIFY_WORK_DIR}" + + echo "Downloading ${file}..." + curl -fsSL -o "${UNCRUSTIFY_WORK_DIR}/${file}" "${url}" + + ( + cd "${UNCRUSTIFY_WORK_DIR}" + + echo "${UNCRUSTIFY_HASH} ${file}" > "${file}.sha256" + sha256sum -c "${file}.sha256" + rm -f "${file}.sha256" + + command -v cmake >/dev/null 2>&1 || die "cmake executable not found." + + echo "Extracting archive..." + rm -rf "${root}" "${root}-build" + mkdir -p "${root}" + tar -xzf "${file}" --strip-components=1 -C "${root}" + + echo "Configuring (prefix: ${UNCRUSTIFY_INSTALL_DIR})..." + cmake \ + -DCMAKE_BUILD_TYPE:STRING=Release \ + -DCMAKE_INSTALL_PREFIX:PATH="${UNCRUSTIFY_INSTALL_DIR}" \ + -S "${root}" -B "${root}-build" + + echo "Building & installing..." + cmake --build "${root}-build" --config Release --target install --parallel + ) + + echo "Installed Uncrustify to: ${UNCRUSTIFY_INSTALL_DIR}" +} + +print_usage_and_exit() { + sed -n '2,25p' "$0" | sed 's/^# \{0,1\}//' + exit 0 +} + +# Print ONLY expected Uncrustify version (no extra text). +print_expected_uncrustify_version_and_exit() { + printf '%s\n' "$UNCRUSTIFY_VERSION" + exit 0 +} + +# -------------------------- +# Argument parsing +# -------------------------- +DO_INSTALL=0 +PRINT_EXPECTED_UNCRUSTIFY_VERSION=0 +# Accept: --install, --install-dir , -h/--help +while [[ $# -gt 0 ]]; do + case "$1" in + -h|--help) + print_usage_and_exit + ;; + --install) + DO_INSTALL=1 + shift + ;; + --install-dir) + [[ $# -ge 2 ]] || die "$1 requires a path argument" + UNCRUSTIFY_INSTALL_DIR="$(readlink -m "$2" 2>/dev/null || realpath -m "$2")" + UNCRUSTIFY_BIN="${UNCRUSTIFY_INSTALL_DIR}/bin/uncrustify" + # Only update UNCRUSTIFY default if user hasn't explicitly set it + if [[ "${UNCRUSTIFY:-}" != "${UNCRUSTIFY_BIN}" ]]; then + UNCRUSTIFY="${UNCRUSTIFY_BIN}" + fi + shift 2 + ;; + --expected-uncrustify-version) + PRINT_EXPECTED_UNCRUSTIFY_VERSION=1 + shift + ;; + *) + # ignore unrecognized positional args for now + shift + ;; + esac +done + +if [[ "$DO_INSTALL" -eq 1 ]]; then + install_uncrustify + # Ensure we use the freshly installed binary for this run + UNCRUSTIFY="$UNCRUSTIFY_BIN" fi -# OS variables -[ $(uname -s) = "Darwin" ] && export OSX=1 && export UNIX=1 -[ $(uname -s) = "Linux" ] && export LINUX=1 && export UNIX=1 -uname -s | grep -q "_NT-" && export WINDOWS=1 - -if [ $OSX ] -then - export CPUCOUNT=$(sysctl -n hw.ncpu) -elif [ $LINUX ] -then - export CPUCOUNT=$(nproc) -else - export CPUCOUNT="1" +# If requested, print ONLY the expected Uncrustify version and exit. +if [[ "$PRINT_EXPECTED_UNCRUSTIFY_VERSION" -eq 1 ]]; then + print_expected_uncrustify_version_and_exit fi -$UNCRUSTIFY -c .uncrustify.cfg --no-backup *.cpp *.h +# -------------------------- +# Validate & run +# -------------------------- + +# Check Uncrustify availability +if ! command -v "$UNCRUSTIFY" >/dev/null 2>&1; then + err "Uncrustify executable not found: $UNCRUSTIFY" + die "Add it to PATH, set UNCRUSTIFY=/path/to/uncrustify, or run: $0 --install [--install-dir DIR]" +fi + +# Version check +DETECTED_VERSION="$("$UNCRUSTIFY" --version 2>&1 | grep -oE '[0-9]+(\.[0-9]+)*' | head -n1 || true)" +echo "Detected Uncrustify: ${DETECTED_VERSION:-unknown}" +if [[ "$DETECTED_VERSION" != "${UNCRUSTIFY_VERSION}" ]]; then + die "Expected Uncrustify ${UNCRUSTIFY_VERSION}. Re-run with --install (and optionally --install-dir) or set UNCRUSTIFY." +fi + +# Config check +[[ -f "$UNCRUSTIFY_CONFIG" ]] || die "Uncrustify config not found at: $UNCRUSTIFY_CONFIG" + +# Run formatter +echo "Running formatter..." +$UNCRUSTIFY -c "$UNCRUSTIFY_CONFIG" -l CPP --no-backup --replace *.cpp *.h + +# Show diff and fail if changes exist +echo "Checking for formatting changes..." +git diff --exit-code || { + echo + echo "Formatting changes were applied. Please review and commit." + exit 1 +} + +echo "Formatting is clean." From 871dc30b45d401780a8d6f38d88db8fe56a5d5f1 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Fri, 29 Aug 2025 03:19:27 -0400 Subject: [PATCH 008/104] chore: Improve attribution by ignoring bulk formatting (#518) --- .git-blame-ignore-revs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..bd256eb3 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,16 @@ +# +# This file lists revisions that should be ignored when considering +# attribution for the actual code written. Code style changes should +# not be considered as modifications with regards to attribution. +# +# To see clean and meaningful blame information. +# $ git blame important.py --ignore-revs-file .git-blame-ignore-revs +# +# To configure git to automatically ignore revisions listed in a file +# on every call to git blame. +# $ git config blame.ignoreRevsFile .git-blame-ignore-revs +# +# Ignore changes introduced when doing global file format changes + +# Switch to uncrustify (#517) +cfd179711f413aa8e0da9c2f437ad4f8938d5f70 From cd2dd49cf1c183da0b8fc0746dba953fcfbb9b18 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Fri, 29 Aug 2025 06:11:50 -0400 Subject: [PATCH 009/104] tests: Fix execution of testrunner for out-of-source builds (#510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Oliver Stöneberg --- .github/workflows/CI-unixish.yml | 3 +++ CMakeLists.txt | 4 ++++ Makefile | 10 ++++++++-- test.cpp | 12 +++++++++++- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index 60361389..f76fdd15 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -73,6 +73,9 @@ jobs: run: | cmake --build cmake.output --target testrunner -- -j $(nproc) ./cmake.output/testrunner + # Re-run tests from within the build directory to validate that + # SIMPLECPP_TEST_SOURCE_DIR is correctly defined and resolved + (cd cmake.output && ./testrunner) - name: Run valgrind if: matrix.os == 'ubuntu-24.04' diff --git a/CMakeLists.txt b/CMakeLists.txt index f9e3eb6d..8799b7a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,10 @@ add_library(simplecpp_obj OBJECT simplecpp.cpp) add_executable(simplecpp $ main.cpp) add_executable(testrunner $ test.cpp) +target_compile_definitions(testrunner + PRIVATE + SIMPLECPP_TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" +) enable_testing() add_test(NAME testrunner COMMAND testrunner) diff --git a/Makefile b/Makefile index d899d2cd..7489ec83 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,17 @@ all: testrunner simplecpp +CPPFLAGS ?= CXXFLAGS = -Wall -Wextra -pedantic -Wcast-qual -Wfloat-equal -Wmissing-declarations -Wmissing-format-attribute -Wredundant-decls -Wundef -Wno-multichar -Wold-style-cast -std=c++11 -g $(CXXOPTS) LDFLAGS = -g $(LDOPTS) -%.o: %.cpp simplecpp.h - $(CXX) $(CXXFLAGS) -c $< +# Define test source dir macro for compilation (preprocessor flags) +TEST_CPPFLAGS = -DSIMPLECPP_TEST_SOURCE_DIR=\"$(CURDIR)\" + +# Only test.o gets the define +test.o: CPPFLAGS += $(TEST_CPPFLAGS) +%.o: %.cpp simplecpp.h + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< testrunner: test.o simplecpp.o $(CXX) $(LDFLAGS) simplecpp.o test.o -o testrunner diff --git a/test.cpp b/test.cpp index 7b108638..c44524a3 100644 --- a/test.cpp +++ b/test.cpp @@ -16,9 +16,14 @@ #include #include +#ifndef SIMPLECPP_TEST_SOURCE_DIR +#error "SIMPLECPP_TEST_SOURCE_DIR is not defined." +#endif + #define STRINGIZE_(x) #x #define STRINGIZE(x) STRINGIZE_(x) +static const std::string testSourceDir = SIMPLECPP_TEST_SOURCE_DIR; static int numberOfFailedAssertions = 0; #define ASSERT_EQUALS(expected, actual) (assertEquals((expected), (actual), __LINE__)) @@ -1556,6 +1561,7 @@ static void has_include_1() " #endif\n" "#endif"; simplecpp::DUI dui; + dui.includePaths.push_back(testSourceDir); dui.std = "c++17"; ASSERT_EQUALS("\n\nA", preprocess(code, dui)); dui.std = "c++14"; @@ -1573,6 +1579,7 @@ static void has_include_2() " #endif\n" "#endif"; simplecpp::DUI dui; + dui.includePaths.push_back(testSourceDir); dui.std = "c++17"; ASSERT_EQUALS("\n\nA", preprocess(code, dui)); ASSERT_EQUALS("", preprocess(code)); @@ -1592,7 +1599,7 @@ static void has_include_3() // Test file not found... ASSERT_EQUALS("\n\n\n\nB", preprocess(code, dui)); // Unless -I is set (preferably, we should differentiate -I and -isystem...) - dui.includePaths.push_back("./testsuite"); + dui.includePaths.push_back(testSourceDir + "/testsuite"); ASSERT_EQUALS("\n\nA", preprocess(code, dui)); ASSERT_EQUALS("", preprocess(code)); } @@ -1608,6 +1615,7 @@ static void has_include_4() "#endif"; simplecpp::DUI dui; dui.std = "c++17"; + dui.includePaths.push_back(testSourceDir); ASSERT_EQUALS("\n\nA", preprocess(code, dui)); ASSERT_EQUALS("", preprocess(code)); } @@ -1623,6 +1631,7 @@ static void has_include_5() "#endif"; simplecpp::DUI dui; dui.std = "c++17"; + dui.includePaths.push_back(testSourceDir); ASSERT_EQUALS("\n\nA", preprocess(code, dui)); ASSERT_EQUALS("", preprocess(code)); } @@ -1638,6 +1647,7 @@ static void has_include_6() "#endif"; simplecpp::DUI dui; dui.std = "gnu99"; + dui.includePaths.push_back(testSourceDir); ASSERT_EQUALS("\n\nA", preprocess(code, dui)); ASSERT_EQUALS("", preprocess(code)); } From 9db6a20f9d35108b1f81a2344c7bebf647151bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 29 Aug 2025 13:45:22 +0200 Subject: [PATCH 010/104] CI-unixish.yml: cleaned up prerequisites for `macos-*` (#519) --- .github/workflows/CI-unixish.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index f76fdd15..c81f023c 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -41,10 +41,11 @@ jobs: sudo apt-get update sudo apt-get install libc++-18-dev + # coreutils contains "nproc" - name: Install missing software on macos if: contains(matrix.os, 'macos') run: | - brew install python3 + brew install coreutils - name: Install missing Python packages run: | From 0689d834e49a50a2fc542f7262d2b660ac6575b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 29 Aug 2025 13:45:36 +0200 Subject: [PATCH 011/104] fixed #500 - added callgrind step to CI (#501) --- .github/workflows/CI-unixish.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index c81f023c..b045bcbd 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -120,3 +120,22 @@ jobs: run: | make clean make -j$(nproc) test selfcheck CXXOPTS="-Werror -O2 -g3 -stdlib=libc++ -fsanitize=memory" LDOPTS="-lc++ -fsanitize=memory" + + - name: Run callgrind + if: matrix.os == 'ubuntu-24.04' + run: | + wget https://github.com/danmar/simplecpp/archive/refs/tags/1.5.1.tar.gz + tar xvf 1.5.1.tar.gz + make clean + make -j$(nproc) CXXOPTS="-O2 -g3" + valgrind --tool=callgrind ./simplecpp -e simplecpp-1.5.1/simplecpp.cpp 2>callgrind.log || (cat callgrind.log && false) + cat callgrind.log + callgrind_annotate --auto=no > callgrind.annotated.log + head -50 callgrind.annotated.log + + - uses: actions/upload-artifact@v4 + if: matrix.os == 'ubuntu-24.04' + with: + name: Callgrind Output - ${{ matrix.compiler }} + path: | + ./callgrind.* From 3911fdd284653ea52087f44574c986110f09ae5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 29 Aug 2025 13:56:58 +0200 Subject: [PATCH 012/104] fixed some `Variable copied when it could be moved` Coverity warnings (#495) --- simplecpp.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 22a45e7a..d576f6e4 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -611,7 +611,7 @@ static void portabilityBackslash(simplecpp::OutputList *outputList, const std::v err.type = simplecpp::Output::PORTABILITY_BACKSLASH; err.location = location; err.msg = "Combination 'backslash space newline' is not portable."; - outputList->push_back(err); + outputList->push_back(std::move(err)); } static bool isStringLiteralPrefix(const std::string &str) @@ -855,7 +855,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, err.type = Output::SYNTAX_ERROR; err.location = location; err.msg = "Raw string missing terminating delimiter."; - outputList->push_back(err); + outputList->push_back(std::move(err)); } return; } @@ -1397,7 +1397,7 @@ std::string simplecpp::TokenList::readUntil(Stream &stream, const Location &loca err.type = Output::SYNTAX_ERROR; err.location = location; err.msg = std::string("No pair for character (") + start + "). Can't process file. File is either invalid or unicode, which is currently not supported."; - outputList->push_back(err); + outputList->push_back(std::move(err)); } return ""; } @@ -2178,7 +2178,7 @@ namespace simplecpp { if (it != macros.end() && !partok->isExpandedFrom(&it->second) && (partok->str() == name() || expandedmacros.find(partok->str()) == expandedmacros.end())) { std::set expandedmacros2(expandedmacros); // temporary amnesia to allow reexpansion of currently expanding macros during argument evaluation expandedmacros2.erase(name()); - partok = it->second.expand(output, loc, partok, macros, expandedmacros2); + partok = it->second.expand(output, loc, partok, macros, std::move(expandedmacros2)); } else { output->push_back(newMacroToken(partok->str(), loc, isReplaced(expandedmacros), partok)); output->back()->macro = partok->macro; @@ -3137,7 +3137,7 @@ simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, err.type = simplecpp::Output::EXPLICIT_INCLUDE_NOT_FOUND; err.location = Location(filenames); err.msg = "Can not open include file '" + filename + "' that is explicitly included."; - outputList->push_back(err); + outputList->push_back(std::move(err)); } continue; } @@ -3210,7 +3210,7 @@ static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token out.type = simplecpp::Output::SYNTAX_ERROR; out.location = err.location; out.msg = "failed to expand \'" + tok->str() + "\', " + err.what; - outputList->push_back(out); + outputList->push_back(std::move(out)); } return false; } @@ -3397,7 +3397,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL err.msg += tok->str(); } err.msg = '#' + rawtok->str() + ' ' + err.msg; - outputList->push_back(err); + outputList->push_back(std::move(err)); } if (rawtok->str() == ERROR) { output.clear(); @@ -3557,7 +3557,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL out.type = Output::SYNTAX_ERROR; out.location = rawtok->location; out.msg = "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition"; - outputList->push_back(out); + outputList->push_back(std::move(out)); } output.clear(); return; @@ -3736,7 +3736,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL mu.macroName = macro.name(); mu.macroLocation = macro.defineLocation(); mu.useLocation = *usageIt; - macroUsage->push_back(mu); + macroUsage->push_back(std::move(mu)); } } } From 4056cd5fcb1d002b3e0ce3b7a3dc80aac720e134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 29 Aug 2025 13:59:14 +0200 Subject: [PATCH 013/104] added `TokenList` constructors with modern buffer wrappers and hide "unsafe" ones - if available (#496) --- .github/workflows/CI-unixish.yml | 10 ++++++ simplecpp.cpp | 9 +---- simplecpp.h | 62 ++++++++++++++++++++++++++++++-- test.cpp | 40 +++++++++++++++++++++ 4 files changed, 111 insertions(+), 10 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index b045bcbd..adb91138 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -62,6 +62,16 @@ jobs: run: | make -j$(nproc) selfcheck + - name: make testrunner (c++17) + run: | + make clean + make -j$(nproc) testrunner CXXOPTS="-std=c++17" + + - name: make testrunner (c++20) + run: | + make clean + make -j$(nproc) testrunner CXXOPTS="-std=c++20" + - name: Run CMake run: | cmake -S . -B cmake.output -DCMAKE_COMPILE_WARNING_AS_ERROR=On diff --git a/simplecpp.cpp b/simplecpp.cpp index d576f6e4..23aa7387 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -466,20 +466,13 @@ simplecpp::TokenList::TokenList(std::istream &istr, std::vector &fi readfile(stream,filename,outputList); } -simplecpp::TokenList::TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, OutputList *outputList) +simplecpp::TokenList::TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, OutputList *outputList, int /*unused*/) : frontToken(nullptr), backToken(nullptr), files(filenames) { StdCharBufStream stream(data, size); readfile(stream,filename,outputList); } -simplecpp::TokenList::TokenList(const char* data, std::size_t size, std::vector &filenames, const std::string &filename, OutputList *outputList) - : frontToken(nullptr), backToken(nullptr), files(filenames) -{ - StdCharBufStream stream(reinterpret_cast(data), size); - readfile(stream,filename,outputList); -} - simplecpp::TokenList::TokenList(const std::string &filename, std::vector &filenames, OutputList *outputList) : frontToken(nullptr), backToken(nullptr), files(filenames) { diff --git a/simplecpp.h b/simplecpp.h index 05de07dd..f035ea95 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -20,6 +20,16 @@ #include #include #include +#if __cplusplus >= 202002L +# include +#endif + +#if defined(__cpp_lib_string_view) && !defined(__cpp_lib_span) +#include +#endif +#ifdef __cpp_lib_span +#include +#endif #ifdef _WIN32 # ifdef SIMPLECPP_EXPORT @@ -46,6 +56,15 @@ # pragma warning(disable : 4244) #endif +// provide legacy (i.e. raw pointer) API for TokenList +// note: std::istream has an overhead compared to raw pointers +#ifndef SIMPLECPP_TOKENLIST_ALLOW_PTR +// still provide the legacy API in case we lack the performant wrappers +# if !defined(__cpp_lib_string_view) && !defined(__cpp_lib_span) +# define SIMPLECPP_TOKENLIST_ALLOW_PTR +# endif +#endif + namespace simplecpp { /** C code standard */ enum cstd_t { CUnknown=-1, C89, C99, C11, C17, C23 }; @@ -216,10 +235,45 @@ namespace simplecpp { explicit TokenList(std::vector &filenames); /** generates a token list from the given std::istream parameter */ TokenList(std::istream &istr, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr); +#ifdef SIMPLECPP_TOKENLIST_ALLOW_PTR + /** generates a token list from the given buffer */ + template + TokenList(const char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data), size-1, filenames, filename, outputList, 0) + {} + /** generates a token list from the given buffer */ + template + TokenList(const unsigned char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) + : TokenList(data, size-1, filenames, filename, outputList, 0) + {} + /** generates a token list from the given buffer */ - TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr); + TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) + : TokenList(data, size, filenames, filename, outputList, 0) + {} /** generates a token list from the given buffer */ - TokenList(const char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr); + TokenList(const char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data), size, filenames, filename, outputList, 0) + {} +#endif +#if defined(__cpp_lib_string_view) && !defined(__cpp_lib_span) + /** generates a token list from the given buffer */ + TokenList(std::string_view data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, outputList, 0) + {} +#endif +#ifdef __cpp_lib_span + /** generates a token list from the given buffer */ + TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, outputList, 0) + {} + + /** generates a token list from the given buffer */ + TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) + : TokenList(data.data(), data.size(), filenames, filename, outputList, 0) + {} +#endif + /** generates a token list from the given filename parameter */ TokenList(const std::string &filename, std::vector &filenames, OutputList *outputList = nullptr); TokenList(const TokenList &other); @@ -295,6 +349,8 @@ namespace simplecpp { } private: + TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, OutputList *outputList, int unused); + void combineOperators(); void constFoldUnaryNotPosNeg(Token *tok); @@ -505,6 +561,8 @@ namespace simplecpp { SIMPLECPP_LIB std::string getCppStdString(cppstd_t std); } +#undef SIMPLECPP_TOKENLIST_ALLOW_PTR + #if defined(_MSC_VER) # pragma warning(pop) #endif diff --git a/test.cpp b/test.cpp index c44524a3..0ecaa3b1 100644 --- a/test.cpp +++ b/test.cpp @@ -3179,6 +3179,44 @@ static void preprocess_files() } } +static void safe_api() +{ + // this test is to make sure the safe APIs are compiling +#if defined(__cpp_lib_string_view) || defined(__cpp_lib_span) + std::vector filenames; +# if defined(__cpp_lib_string_view) + { + const char input[] = "code"; + const std::string_view sv = input; + // std::string_view can be implicitly converted into a std::span + simplecpp::TokenList(sv,filenames,""); + } +# endif +# ifdef __cpp_lib_span + { + char input[] = "code"; + const std::span sp = input; + simplecpp::TokenList(sp,filenames,""); + } + { + const char input[] = "code"; + const std::span sp = input; + simplecpp::TokenList(sp,filenames,""); + } + { + unsigned char input[] = "code"; + const std::span sp = input; + simplecpp::TokenList(sp,filenames,""); + } + { + const unsigned char input[] = "code"; + const std::span sp = input; + simplecpp::TokenList(sp,filenames,""); + } +# endif +#endif +} + static void fuzz_crash() { { @@ -3445,6 +3483,8 @@ int main(int argc, char **argv) TEST_CASE(preprocess_files); + TEST_CASE(safe_api); + TEST_CASE(fuzz_crash); return numberOfFailedAssertions > 0 ? EXIT_FAILURE : EXIT_SUCCESS; From 4db4304739e8634e62ec3a6ba8eae3d732ab4275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 29 Aug 2025 17:11:33 +0200 Subject: [PATCH 014/104] simplecpp.h: fixed formatting (#522) --- simplecpp.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/simplecpp.h b/simplecpp.h index f035ea95..ac367154 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -244,7 +244,7 @@ namespace simplecpp { /** generates a token list from the given buffer */ template TokenList(const unsigned char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(data, size-1, filenames, filename, outputList, 0) + : TokenList(data, size-1, filenames, filename, outputList, 0) {} /** generates a token list from the given buffer */ @@ -265,12 +265,12 @@ namespace simplecpp { #ifdef __cpp_lib_span /** generates a token list from the given buffer */ TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, outputList, 0) + : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, outputList, 0) {} /** generates a token list from the given buffer */ TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) - : TokenList(data.data(), data.size(), filenames, filename, outputList, 0) + : TokenList(data.data(), data.size(), filenames, filename, outputList, 0) {} #endif From 740b2b94a6eb50a1b81822f52d8347c2514cffd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 29 Aug 2025 18:30:13 +0200 Subject: [PATCH 015/104] do not use stream to read file in `FileDataCache::tryload()` (#523) --- simplecpp.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 23aa7387..84e4b54b 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3022,8 +3022,7 @@ std::pair simplecpp::FileDataCache::tryload(FileDat return {id_it->second, false}; } - std::ifstream f(path); - FileData *const data = new FileData {path, TokenList(f, filenames, path, outputList)}; + FileData *const data = new FileData {path, TokenList(path, filenames, outputList)}; if (dui.removeComments) data->tokens.removeComments(); From 37fa4f49b33aca8b4e86f42b43c3d57060e725b5 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Sat, 30 Aug 2025 13:26:00 -0400 Subject: [PATCH 016/104] Improve test runner script with refactoring and prerequisite checks (#509) --- run-tests.py | 64 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/run-tests.py b/run-tests.py index 5017f4c1..8810bf2b 100644 --- a/run-tests.py +++ b/run-tests.py @@ -1,17 +1,30 @@ - import glob import os +import shutil import subprocess import sys -def cleanup(out): - ret = '' - for s in out.decode('utf-8').split('\n'): - if len(s) > 1 and s[0] == '#': + +def cleanup(out: str) -> str: + parts = [] + for line in out.splitlines(): + if len(line) > 1 and line[0] == '#': continue - s = "".join(s.split()) - ret = ret + s - return ret + parts.append("".join(line.split())) + return "".join(parts) + + +# Check for required compilers and exit if any are missing +CLANG_EXE = shutil.which('clang') +if not CLANG_EXE: + sys.exit('Failed to run tests: clang compiler not found') + +GCC_EXE = shutil.which('gcc') +if not GCC_EXE: + sys.exit('Failed to run tests: gcc compiler not found') + +SIMPLECPP_EXE = './simplecpp' + commands = [] @@ -78,6 +91,21 @@ def cleanup(out): 'pr57580.c', ] + +def run(compiler_executable: str, compiler_args: list[str]) -> tuple[int, str, str]: + """Execute a compiler command and capture its exit code, stdout, and stderr.""" + compiler_cmd = [compiler_executable] + compiler_cmd.extend(compiler_args) + + with subprocess.Popen(compiler_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding="utf-8") as process: + stdout, stderr = process.communicate() + exit_code = process.returncode + + output = cleanup(stdout) + error = (stderr or "").strip() + return (exit_code, output, error) + + numberOfSkipped = 0 numberOfFailed = 0 numberOfFixed = 0 @@ -89,26 +117,12 @@ def cleanup(out): numberOfSkipped = numberOfSkipped + 1 continue - clang_cmd = ['clang'] - clang_cmd.extend(cmd.split(' ')) - p = subprocess.Popen(clang_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - comm = p.communicate() - clang_output = cleanup(comm[0]) + _, clang_output, _ = run(CLANG_EXE, cmd.split(' ')) - gcc_cmd = ['gcc'] - gcc_cmd.extend(cmd.split(' ')) - p = subprocess.Popen(gcc_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - comm = p.communicate() - gcc_output = cleanup(comm[0]) + _, gcc_output, _ = run(GCC_EXE, cmd.split(' ')) - simplecpp_cmd = ['./simplecpp'] # -E is not supported and we bail out on unknown options - simplecpp_cmd.extend(cmd.replace('-E ', '', 1).split(' ')) - p = subprocess.Popen(simplecpp_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - comm = p.communicate() - simplecpp_ec = p.returncode - simplecpp_output = cleanup(comm[0]) - simplecpp_err = comm[0].decode('utf-8').strip() + simplecpp_ec, simplecpp_output, simplecpp_err = run(SIMPLECPP_EXE, cmd.replace('-E ', '', 1).split(' ')) if simplecpp_output != clang_output and simplecpp_output != gcc_output: filename = cmd[cmd.rfind('/')+1:] From 04f4dfa2c62432d9bd4e36711fa06ff78395e4ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 7 Sep 2025 23:41:58 +0200 Subject: [PATCH 017/104] main.cpp: added missing help entry for `-f` (#528) --- main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/main.cpp b/main.cpp index a6d14386..2397e847 100644 --- a/main.cpp +++ b/main.cpp @@ -106,6 +106,7 @@ int main(int argc, char **argv) std::cout << " -q Quiet mode (no output)." << std::endl; std::cout << " -is Use std::istream interface." << std::endl; std::cout << " -e Output errors only." << std::endl; + std::cout << " -f Fail when errors were encountered (exitcode 1)." << std::endl; std::exit(0); } From 68e9c3f4884bd6939a90faff766929a519e3eebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 10 Sep 2025 11:41:33 +0200 Subject: [PATCH 018/104] simplecpp.h: made `Token::flags()` private (#526) --- simplecpp.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/simplecpp.h b/simplecpp.h index ac367154..11accc74 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -135,14 +135,6 @@ namespace simplecpp { Token(const Token &tok) : macro(tok.macro), op(tok.op), comment(tok.comment), name(tok.name), number(tok.number), whitespaceahead(tok.whitespaceahead), location(tok.location), previous(nullptr), next(nullptr), nextcond(nullptr), string(tok.string), mExpandedFrom(tok.mExpandedFrom) {} - void flags() { - name = (std::isalpha(static_cast(string[0])) || string[0] == '_' || string[0] == '$') - && (std::memchr(string.c_str(), '\'', string.size()) == nullptr); - comment = string.size() > 1U && string[0] == '/' && (string[1] == '/' || string[1] == '*'); - number = isNumberLike(string); - op = (string.size() == 1U && !name && !comment && !number) ? string[0] : '\0'; - } - const TokenString& str() const { return string; } @@ -197,6 +189,14 @@ namespace simplecpp { void printAll() const; void printOut() const; private: + void flags() { + name = (std::isalpha(static_cast(string[0])) || string[0] == '_' || string[0] == '$') + && (std::memchr(string.c_str(), '\'', string.size()) == nullptr); + comment = string.size() > 1U && string[0] == '/' && (string[1] == '/' || string[1] == '*'); + number = isNumberLike(string); + op = (string.size() == 1U && !name && !comment && !number) ? string[0] : '\0'; + } + TokenString string; std::set mExpandedFrom; From fd60cfe05948253c27c274c9198678b49d00ab32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 10 Sep 2025 11:41:47 +0200 Subject: [PATCH 019/104] avoid impossible macro lookups in `preprocessToken()` (#532) --- simplecpp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 84e4b54b..27b05519 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3191,7 +3191,7 @@ simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token **tok1, simplecpp::MacroMap ¯os, std::vector &files, simplecpp::OutputList *outputList) { const simplecpp::Token * const tok = *tok1; - const simplecpp::MacroMap::const_iterator it = macros.find(tok->str()); + const simplecpp::MacroMap::const_iterator it = tok->name ? macros.find(tok->str()) : macros.end(); if (it != macros.end()) { simplecpp::TokenList value(files); try { From 919a25b0ab1edb0635802a236bdf2ee7e58bbe2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 10 Sep 2025 12:09:49 +0200 Subject: [PATCH 020/104] added support for `c2y` and `gnu2y` as standards (#521) --- simplecpp.cpp | 8 +++++++- simplecpp.h | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 27b05519..5b8fde81 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3751,6 +3751,8 @@ simplecpp::cstd_t simplecpp::getCStd(const std::string &std) return C17; if (std == "c23" || std == "gnu23" || std == "c2x" || std == "gnu2x") return C23; + if (std == "c2y" || std == "gnu2y") + return C2Y; return CUnknown; } @@ -3772,6 +3774,10 @@ std::string simplecpp::getCStdString(cstd_t std) // Clang 14, 15, 16, 17 return "202000L" // Clang 9, 10, 11, 12, 13, 14, 15, 16, 17 do not support "c23" and "gnu23" return "202311L"; + case C2Y: + // supported by GCC 15+ and Clang 19+ + // Clang 19, 20, 21, 22 return "202400L" + return "202500L"; case CUnknown: return ""; } @@ -3823,7 +3829,7 @@ std::string simplecpp::getCppStdString(cppstd_t std) // Clang 17, 18 return "202302L" return "202302L"; case CPP26: - // supported by Clang 17+ + // supported by GCC 14+ and Clang 17+ return "202400L"; case CPPUnknown: return ""; diff --git a/simplecpp.h b/simplecpp.h index 11accc74..43a03730 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -67,7 +67,7 @@ namespace simplecpp { /** C code standard */ - enum cstd_t { CUnknown=-1, C89, C99, C11, C17, C23 }; + enum cstd_t { CUnknown=-1, C89, C99, C11, C17, C23, C2Y }; /** C++ code standard */ enum cppstd_t { CPPUnknown=-1, CPP03, CPP11, CPP14, CPP17, CPP20, CPP23, CPP26 }; From 6c9eaf437a43467d56f10d57496e15acbca3a784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 10 Sep 2025 12:10:01 +0200 Subject: [PATCH 021/104] pass `TokenList` by reference internally (#530) --- simplecpp.cpp | 148 +++++++++++++++++++++++++------------------------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 5b8fde81..21c4bd82 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1528,7 +1528,7 @@ namespace simplecpp { * @return token after macro * @throw Can throw wrongNumberOfParameters or invalidHashHash */ - const Token * expand(TokenList * const output, + const Token * expand(TokenList & output, const Token * rawtok, const MacroMap ¯os, std::vector &inputFiles) const { @@ -1559,10 +1559,10 @@ namespace simplecpp { rawtokens2.push_back(new Token(rawtok->str(), rawtok1->location, rawtok->whitespaceahead)); rawtok = rawtok->next; } - if (expand(&output2, rawtok1->location, rawtokens2.cfront(), macros, expandedmacros)) + if (expand(output2, rawtok1->location, rawtokens2.cfront(), macros, expandedmacros)) rawtok = rawtok1->next; } else { - rawtok = expand(&output2, rawtok->location, rawtok, macros, expandedmacros); + rawtok = expand(output2, rawtok->location, rawtok, macros, expandedmacros); } while (output2.cback() && rawtok) { unsigned int par = 0; @@ -1610,11 +1610,11 @@ namespace simplecpp { } if (!rawtok2 || par != 1U) break; - if (macro->second.expand(&output2, rawtok->location, rawtokens2.cfront(), macros, expandedmacros) != nullptr) + if (macro->second.expand(output2, rawtok->location, rawtokens2.cfront(), macros, expandedmacros) != nullptr) break; rawtok = rawtok2->next; } - output->takeTokens(output2); + output.takeTokens(output2); return rawtok; } @@ -1810,7 +1810,7 @@ namespace simplecpp { return parametertokens; } - const Token *appendTokens(TokenList *tokens, + const Token *appendTokens(TokenList &tokens, const Location &rawloc, const Token * const lpar, const MacroMap ¯os, @@ -1828,9 +1828,9 @@ namespace simplecpp { tok = expandHash(tokens, rawloc, tok, expandedmacros, parametertokens); } else { if (!expandArg(tokens, tok, rawloc, macros, expandedmacros, parametertokens)) { - tokens->push_back(new Token(*tok)); + tokens.push_back(new Token(*tok)); if (tok->macro.empty() && (par > 0 || tok->str() != "(")) - tokens->back()->macro = name(); + tokens.back()->macro = name(); } if (tok->op == '(') @@ -1843,12 +1843,12 @@ namespace simplecpp { tok = tok->next; } } - for (Token *tok2 = tokens->front(); tok2; tok2 = tok2->next) + for (Token *tok2 = tokens.front(); tok2; tok2 = tok2->next) tok2->location = lpar->location; return sameline(lpar,tok) ? tok : nullptr; } - const Token * expand(TokenList * const output, const Location &loc, const Token * const nameTokInst, const MacroMap ¯os, std::set expandedmacros) const { + const Token * expand(TokenList & output, const Location &loc, const Token * const nameTokInst, const MacroMap ¯os, std::set expandedmacros) const { expandedmacros.insert(nameTokInst->str()); #ifdef SIMPLECPP_DEBUG_MACRO_EXPANSION @@ -1858,15 +1858,15 @@ namespace simplecpp { usageList.push_back(loc); if (nameTokInst->str() == "__FILE__") { - output->push_back(new Token('\"'+loc.file()+'\"', loc)); + output.push_back(new Token('\"'+loc.file()+'\"', loc)); return nameTokInst->next; } if (nameTokInst->str() == "__LINE__") { - output->push_back(new Token(toString(loc.line), loc)); + output.push_back(new Token(toString(loc.line), loc)); return nameTokInst->next; } if (nameTokInst->str() == "__COUNTER__") { - output->push_back(new Token(toString(usageList.size()-1U), loc)); + output.push_back(new Token(toString(usageList.size()-1U), loc)); return nameTokInst->next; } @@ -1878,7 +1878,7 @@ namespace simplecpp { if (functionLike()) { // No arguments => not macro expansion if (nameTokInst->next && nameTokInst->next->op != '(') { - output->push_back(new Token(nameTokInst->str(), loc)); + output.push_back(new Token(nameTokInst->str(), loc)); return nameTokInst->next; } @@ -1927,7 +1927,7 @@ namespace simplecpp { } } - Token * const output_end_1 = output->back(); + Token * const output_end_1 = output.back(); const Token *valueToken2; const Token *endToken2; @@ -1952,20 +1952,20 @@ namespace simplecpp { throw invalidHashHash::unexpectedNewline(tok->location, name()); if (variadic && tok->op == ',' && tok->next->next->next->str() == args.back()) { Token *const comma = newMacroToken(tok->str(), loc, isReplaced(expandedmacros), tok); - output->push_back(comma); + output.push_back(comma); tok = expandToken(output, loc, tok->next->next->next, macros, expandedmacros, parametertokens2); - if (output->back() == comma) - output->deleteToken(comma); + if (output.back() == comma) + output.deleteToken(comma); continue; } TokenList new_output(files); - if (!expandArg(&new_output, tok, parametertokens2)) - output->push_back(newMacroToken(tok->str(), loc, isReplaced(expandedmacros), tok)); + if (!expandArg(new_output, tok, parametertokens2)) + output.push_back(newMacroToken(tok->str(), loc, isReplaced(expandedmacros), tok)); else if (new_output.empty()) // placemarker token - output->push_back(newMacroToken("", loc, isReplaced(expandedmacros))); + output.push_back(newMacroToken("", loc, isReplaced(expandedmacros))); else for (const Token *tok2 = new_output.cfront(); tok2; tok2 = tok2->next) - output->push_back(newMacroToken(tok2->str(), loc, isReplaced(expandedmacros), tok2)); + output.push_back(newMacroToken(tok2->str(), loc, isReplaced(expandedmacros), tok2)); tok = tok->next; } else { tok = expandToken(output, loc, tok, macros, expandedmacros, parametertokens2); @@ -1981,20 +1981,20 @@ namespace simplecpp { } if (numberOfHash == 4 && tok->next->location.col + 1 == tok->next->next->location.col) { // # ## # => ## - output->push_back(newMacroToken("##", loc, isReplaced(expandedmacros))); + output.push_back(newMacroToken("##", loc, isReplaced(expandedmacros))); tok = hashToken; continue; } if (numberOfHash >= 2 && tok->location.col + 1 < tok->next->location.col) { - output->push_back(new Token(*tok)); + output.push_back(new Token(*tok)); tok = tok->next; continue; } tok = tok->next; if (tok == endToken2) { - output->push_back(new Token(*tok->previous)); + output.push_back(new Token(*tok->previous)); break; } if (tok->op == '#') { @@ -2007,7 +2007,7 @@ namespace simplecpp { } if (!functionLike()) { - for (Token *tok = output_end_1 ? output_end_1->next : output->front(); tok; tok = tok->next) { + for (Token *tok = output_end_1 ? output_end_1->next : output.front(); tok; tok = tok->next) { tok->macro = nameTokInst->str(); } } @@ -2018,55 +2018,55 @@ namespace simplecpp { return functionLike() ? parametertokens2.back()->next : nameTokInst->next; } - const Token *recursiveExpandToken(TokenList *output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { + const Token *recursiveExpandToken(TokenList &output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { if (!(temp.cback() && temp.cback()->name && tok->next && tok->next->op == '(')) { - output->takeTokens(temp); + output.takeTokens(temp); return tok->next; } if (!sameline(tok, tok->next)) { - output->takeTokens(temp); + output.takeTokens(temp); return tok->next; } const MacroMap::const_iterator it = macros.find(temp.cback()->str()); if (it == macros.end() || expandedmacros.find(temp.cback()->str()) != expandedmacros.end()) { - output->takeTokens(temp); + output.takeTokens(temp); return tok->next; } const Macro &calledMacro = it->second; if (!calledMacro.functionLike()) { - output->takeTokens(temp); + output.takeTokens(temp); return tok->next; } TokenList temp2(files); temp2.push_back(new Token(temp.cback()->str(), tok->location)); - const Token * const tok2 = appendTokens(&temp2, loc, tok->next, macros, expandedmacros, parametertokens); + const Token * const tok2 = appendTokens(temp2, loc, tok->next, macros, expandedmacros, parametertokens); if (!tok2) return tok->next; - output->takeTokens(temp); - output->deleteToken(output->back()); + output.takeTokens(temp); + output.deleteToken(output.back()); calledMacro.expand(output, loc, temp2.cfront(), macros, expandedmacros); return tok2->next; } - const Token *expandToken(TokenList *output, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { + const Token *expandToken(TokenList &output, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { // Not name.. if (!tok->name) { - output->push_back(newMacroToken(tok->str(), loc, true, tok)); + output.push_back(newMacroToken(tok->str(), loc, true, tok)); return tok->next; } // Macro parameter.. { TokenList temp(files); - if (expandArg(&temp, tok, loc, macros, expandedmacros, parametertokens)) { - if (tok->str() == "__VA_ARGS__" && temp.empty() && output->cback() && output->cback()->str() == "," && + if (expandArg(temp, tok, loc, macros, expandedmacros, parametertokens)) { + if (tok->str() == "__VA_ARGS__" && temp.empty() && output.cback() && output.cback()->str() == "," && tok->nextSkipComments() && tok->nextSkipComments()->str() == ")") - output->deleteToken(output->back()); + output.deleteToken(output.back()); return recursiveExpandToken(output, temp, loc, tok, macros, expandedmacros, parametertokens); } } @@ -2080,29 +2080,29 @@ namespace simplecpp { const Macro &calledMacro = it->second; if (!calledMacro.functionLike()) { TokenList temp(files); - calledMacro.expand(&temp, loc, tok, macros, expandedmacros); + calledMacro.expand(temp, loc, tok, macros, expandedmacros); return recursiveExpandToken(output, temp, loc, tok, macros, expandedmacros2, parametertokens); } if (!sameline(tok, tok->next)) { - output->push_back(newMacroToken(tok->str(), loc, true, tok)); + output.push_back(newMacroToken(tok->str(), loc, true, tok)); return tok->next; } TokenList tokens(files); tokens.push_back(new Token(*tok)); const Token * tok2 = nullptr; if (tok->next->op == '(') - tok2 = appendTokens(&tokens, loc, tok->next, macros, expandedmacros, parametertokens); - else if (expandArg(&tokens, tok->next, loc, macros, expandedmacros, parametertokens)) { + tok2 = appendTokens(tokens, loc, tok->next, macros, expandedmacros, parametertokens); + else if (expandArg(tokens, tok->next, loc, macros, expandedmacros, parametertokens)) { tokens.front()->location = loc; if (tokens.cfront()->next && tokens.cfront()->next->op == '(') tok2 = tok->next; } if (!tok2) { - output->push_back(newMacroToken(tok->str(), loc, true, tok)); + output.push_back(newMacroToken(tok->str(), loc, true, tok)); return tok->next; } TokenList temp(files); - calledMacro.expand(&temp, loc, tokens.cfront(), macros, expandedmacros); + calledMacro.expand(temp, loc, tokens.cfront(), macros, expandedmacros); return recursiveExpandToken(output, temp, loc, tok2, macros, expandedmacros, parametertokens); } @@ -2122,25 +2122,25 @@ namespace simplecpp { std::string macroName = defToken->str(); if (defToken->next && defToken->next->op == '#' && defToken->next->next && defToken->next->next->op == '#' && defToken->next->next->next && defToken->next->next->next->name && sameline(defToken,defToken->next->next->next)) { TokenList temp(files); - if (expandArg(&temp, defToken, parametertokens)) + if (expandArg(temp, defToken, parametertokens)) macroName = temp.cback()->str(); - if (expandArg(&temp, defToken->next->next->next, parametertokens)) + if (expandArg(temp, defToken->next->next->next, parametertokens)) macroName += temp.cback() ? temp.cback()->str() : ""; else macroName += defToken->next->next->next->str(); lastToken = defToken->next->next->next; } const bool def = (macros.find(macroName) != macros.end()); - output->push_back(newMacroToken(def ? "1" : "0", loc, true)); + output.push_back(newMacroToken(def ? "1" : "0", loc, true)); return lastToken->next; } } - output->push_back(newMacroToken(tok->str(), loc, true, tok)); + output.push_back(newMacroToken(tok->str(), loc, true, tok)); return tok->next; } - bool expandArg(TokenList *output, const Token *tok, const std::vector ¶metertokens) const { + bool expandArg(TokenList &output, const Token *tok, const std::vector ¶metertokens) const { if (!tok->name) return false; @@ -2153,12 +2153,12 @@ namespace simplecpp { return true; for (const Token *partok = parametertokens[argnr]->next; partok != parametertokens[argnr + 1U]; partok = partok->next) - output->push_back(new Token(*partok)); + output.push_back(new Token(*partok)); return true; } - bool expandArg(TokenList *output, const Token *tok, const Location &loc, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { + bool expandArg(TokenList &output, const Token *tok, const Location &loc, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { if (!tok->name) return false; const unsigned int argnr = getArgNum(tok->str()); @@ -2173,13 +2173,13 @@ namespace simplecpp { expandedmacros2.erase(name()); partok = it->second.expand(output, loc, partok, macros, std::move(expandedmacros2)); } else { - output->push_back(newMacroToken(partok->str(), loc, isReplaced(expandedmacros), partok)); - output->back()->macro = partok->macro; + output.push_back(newMacroToken(partok->str(), loc, isReplaced(expandedmacros), partok)); + output.back()->macro = partok->macro; partok = partok->next; } } - if (tok->whitespaceahead && output->back()) - output->back()->whitespaceahead = true; + if (tok->whitespaceahead && output.back()) + output.back()->whitespaceahead = true; return true; } @@ -2192,10 +2192,10 @@ namespace simplecpp { * @param parametertokens parameters given when expanding this macro * @return token after the X */ - const Token *expandHash(TokenList *output, const Location &loc, const Token *tok, const std::set &expandedmacros, const std::vector ¶metertokens) const { + const Token *expandHash(TokenList &output, const Location &loc, const Token *tok, const std::set &expandedmacros, const std::vector ¶metertokens) const { TokenList tokenListHash(files); const MacroMap macros2; // temporarily bypass macro expansion - tok = expandToken(&tokenListHash, loc, tok->next, macros2, expandedmacros, parametertokens); + tok = expandToken(tokenListHash, loc, tok->next, macros2, expandedmacros, parametertokens); std::ostringstream ostr; ostr << '\"'; for (const Token *hashtok = tokenListHash.cfront(), *next; hashtok; hashtok = next) { @@ -2205,7 +2205,7 @@ namespace simplecpp { ostr << ' '; } ostr << '\"'; - output->push_back(newMacroToken(escapeString(ostr.str()), loc, isReplaced(expandedmacros))); + output.push_back(newMacroToken(escapeString(ostr.str()), loc, isReplaced(expandedmacros))); return tok; } @@ -2221,8 +2221,8 @@ namespace simplecpp { * @param expandResult expand ## result i.e. "AB"? * @return token after B */ - const Token *expandHashHash(TokenList *output, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens, bool expandResult=true) const { - Token *A = output->back(); + const Token *expandHashHash(TokenList &output, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens, bool expandResult=true) const { + Token *A = output.back(); if (!A) throw invalidHashHash(tok->location, name(), "Missing first argument"); if (!sameline(tok, tok->next) || !sameline(tok, tok->next->next)) @@ -2253,20 +2253,20 @@ namespace simplecpp { // It seems clearer to handle this case separately even though the code is similar-ish, but we don't want to merge here. // TODO The question is whether the ## or varargs may still apply, and how to provoke? - if (expandArg(&tokensB, B, parametertokens)) { + if (expandArg(tokensB, B, parametertokens)) { for (Token *b = tokensB.front(); b; b = b->next) b->location = loc; } else { tokensB.push_back(new Token(*B)); tokensB.back()->location = loc; } - output->takeTokens(tokensB); + output.takeTokens(tokensB); } else { std::string strAB; const bool varargs = variadic && !args.empty() && B->str() == args[args.size()-1U]; - if (expandArg(&tokensB, B, parametertokens)) { + if (expandArg(tokensB, B, parametertokens)) { if (tokensB.empty()) strAB = A->str(); else if (varargs && A->op == ',') @@ -2292,27 +2292,27 @@ namespace simplecpp { } if (varargs && tokensB.empty() && tok->previous->str() == ",") - output->deleteToken(A); + output.deleteToken(A); else if (strAB != "," && macros.find(strAB) == macros.end()) { A->setstr(strAB); for (Token *b = tokensB.front(); b; b = b->next) b->location = loc; - output->takeTokens(tokensB); + output.takeTokens(tokensB); } else if (sameline(B, nextTok) && sameline(B, nextTok->next) && nextTok->op == '#' && nextTok->next->op == '#') { TokenList output2(files); output2.push_back(new Token(strAB, tok->location)); - nextTok = expandHashHash(&output2, loc, nextTok, macros, expandedmacros, parametertokens); - output->deleteToken(A); - output->takeTokens(output2); + nextTok = expandHashHash(output2, loc, nextTok, macros, expandedmacros, parametertokens); + output.deleteToken(A); + output.takeTokens(output2); } else { - output->deleteToken(A); + output.deleteToken(A); TokenList tokens(files); tokens.push_back(new Token(strAB, tok->location)); // for function like macros, push the (...) if (tokensB.empty() && sameline(B,B->next) && B->next->op=='(') { const MacroMap::const_iterator it = macros.find(strAB); if (it != macros.end() && expandedmacros.find(strAB) == expandedmacros.end() && it->second.functionLike()) { - const Token * const tok2 = appendTokens(&tokens, loc, B->next, macros, expandedmacros, parametertokens); + const Token * const tok2 = appendTokens(tokens, loc, B->next, macros, expandedmacros, parametertokens); if (tok2) nextTok = tok2->next; } @@ -2320,10 +2320,10 @@ namespace simplecpp { if (expandResult) expandToken(output, loc, tokens.cfront(), macros, expandedmacros, parametertokens); else - output->takeTokens(tokens); + output.takeTokens(tokens); for (Token *b = tokensB.front(); b; b = b->next) b->location = loc; - output->takeTokens(tokensB); + output.takeTokens(tokensB); } } @@ -3195,7 +3195,7 @@ static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token if (it != macros.end()) { simplecpp::TokenList value(files); try { - *tok1 = it->second.expand(&value, tok, macros, files); + *tok1 = it->second.expand(value, tok, macros, files); } catch (simplecpp::Macro::Error &err) { if (outputList) { simplecpp::Output out(files); From ad24c6e5b5d8df9320cac179068c24adb798d9d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 10 Sep 2025 14:41:54 +0200 Subject: [PATCH 022/104] fixes #352 - internally default to latest standard if none is provided (#356) --- simplecpp.cpp | 2 +- test.cpp | 53 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 21c4bd82..b1505cb6 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2554,7 +2554,7 @@ static void simplifySizeof(simplecpp::TokenList &expr, const std::map= "201703L"); + return std_ver.empty() || (std_ver >= "201703L"); } static bool isGnu(const simplecpp::DUI &dui) diff --git a/test.cpp b/test.cpp index 0ecaa3b1..f1fe2d1c 100644 --- a/test.cpp +++ b/test.cpp @@ -1562,11 +1562,13 @@ static void has_include_1() "#endif"; simplecpp::DUI dui; dui.includePaths.push_back(testSourceDir); - dui.std = "c++17"; - ASSERT_EQUALS("\n\nA", preprocess(code, dui)); + ASSERT_EQUALS("\n\nA", preprocess(code, dui)); // we default to latest standard internally dui.std = "c++14"; ASSERT_EQUALS("", preprocess(code, dui)); - ASSERT_EQUALS("", preprocess(code)); + dui.std = "c++17"; + ASSERT_EQUALS("\n\nA", preprocess(code, dui)); + dui.std = "c++20"; + ASSERT_EQUALS("\n\nA", preprocess(code, dui)); } static void has_include_2() @@ -1580,9 +1582,13 @@ static void has_include_2() "#endif"; simplecpp::DUI dui; dui.includePaths.push_back(testSourceDir); + ASSERT_EQUALS("\n\nA", preprocess(code, dui)); // we default to latest standard internally + dui.std = "c++14"; + ASSERT_EQUALS("", preprocess(code, dui)); dui.std = "c++17"; ASSERT_EQUALS("\n\nA", preprocess(code, dui)); - ASSERT_EQUALS("", preprocess(code)); + dui.std = "c++20"; + ASSERT_EQUALS("\n\nA", preprocess(code, dui)); } static void has_include_3() @@ -1595,13 +1601,24 @@ static void has_include_3() " #endif\n" "#endif"; simplecpp::DUI dui; - dui.std = "c++17"; + // Test file not found... + ASSERT_EQUALS("\n\n\n\nB", preprocess(code, dui)); // we default to latest standard internally + dui.std = "c++14"; + ASSERT_EQUALS("", preprocess(code, dui)); + dui.std = "c++17"; ASSERT_EQUALS("\n\n\n\nB", preprocess(code, dui)); + // Unless -I is set (preferably, we should differentiate -I and -isystem...) dui.includePaths.push_back(testSourceDir + "/testsuite"); + dui.std = ""; + ASSERT_EQUALS("\n\nA", preprocess(code, dui)); // we default to latest standard internally + dui.std = "c++14"; + ASSERT_EQUALS("", preprocess(code, dui)); + dui.std = "c++17"; + ASSERT_EQUALS("\n\nA", preprocess(code, dui)); + dui.std = "c++20"; ASSERT_EQUALS("\n\nA", preprocess(code, dui)); - ASSERT_EQUALS("", preprocess(code)); } static void has_include_4() @@ -1614,10 +1631,14 @@ static void has_include_4() " #endif\n" "#endif"; simplecpp::DUI dui; + dui.includePaths.push_back(testSourceDir); // we default to latest standard internally + ASSERT_EQUALS("\n\nA", preprocess(code, dui)); + dui.std = "c++14"; + ASSERT_EQUALS("", preprocess(code, dui)); dui.std = "c++17"; - dui.includePaths.push_back(testSourceDir); ASSERT_EQUALS("\n\nA", preprocess(code, dui)); - ASSERT_EQUALS("", preprocess(code)); + dui.std = "c++20"; + ASSERT_EQUALS("\n\nA", preprocess(code, dui)); } static void has_include_5() @@ -1630,10 +1651,14 @@ static void has_include_5() " #endif\n" "#endif"; simplecpp::DUI dui; - dui.std = "c++17"; + ASSERT_EQUALS("\n\nA", preprocess(code, dui)); // we default to latest standard internally dui.includePaths.push_back(testSourceDir); + dui.std = "c++14"; + ASSERT_EQUALS("", preprocess(code, dui)); + dui.std = "c++17"; + ASSERT_EQUALS("\n\nA", preprocess(code, dui)); + dui.std = "c++20"; ASSERT_EQUALS("\n\nA", preprocess(code, dui)); - ASSERT_EQUALS("", preprocess(code)); } static void has_include_6() @@ -1646,10 +1671,12 @@ static void has_include_6() " #endif\n" "#endif"; simplecpp::DUI dui; - dui.std = "gnu99"; dui.includePaths.push_back(testSourceDir); + ASSERT_EQUALS("\n\nA", preprocess(code, dui)); // we default to latest standard internally + dui.std = "c++99"; + ASSERT_EQUALS("", preprocess(code, dui)); + dui.std = "gnu99"; ASSERT_EQUALS("\n\nA", preprocess(code, dui)); - ASSERT_EQUALS("", preprocess(code)); } static void strict_ansi_1() @@ -2983,6 +3010,7 @@ static void stdcVersionDefine() " __STDC_VERSION__\n" "#endif\n"; simplecpp::DUI dui; + ASSERT_EQUALS("", preprocess(code, dui)); dui.std = "c11"; ASSERT_EQUALS("\n201112L", preprocess(code, dui)); } @@ -2993,6 +3021,7 @@ static void cpluscplusDefine() " __cplusplus\n" "#endif\n"; simplecpp::DUI dui; + ASSERT_EQUALS("", preprocess(code, dui)); dui.std = "c++11"; ASSERT_EQUALS("\n201103L", preprocess(code, dui)); } From 8c80e7c0f441c95f1bb3433bf5bcd181f244314e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 12 Sep 2025 10:54:16 +0200 Subject: [PATCH 023/104] disabled `-Wpoison-system-directories` warnings on macOS for now (#542) --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8799b7a4..0cc62e88 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,6 +70,11 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") # use force DWARF 4 debug format since not all tools might be able to handle DWARF 5 yet - e.g. valgrind on ubuntu 20.04 add_compile_options(-gdwarf-4) endif() + if (APPLE) + # CMake is sometimes chosing the wrong compiler on macos-* runners + # see https://github.com/actions/runner/issues/4034 + add_compile_options(-Wno-poison-system-directories) + endif() endif() add_library(simplecpp_obj OBJECT simplecpp.cpp) From 4fc9ec92161ff9840fd2039986f8933c80be069a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 12 Sep 2025 11:04:09 +0200 Subject: [PATCH 024/104] uninstall `man-db` package on ubuntu to avoid potential stalls in package installations (#543) --- .github/workflows/CI-unixish.yml | 8 ++++++++ .github/workflows/clang-tidy.yml | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index adb91138..74100265 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -29,6 +29,14 @@ jobs: with: persist-credentials: false + # the man-db trigger causes package installations to stall for several minutes at times. so just drop the package. + # see https://github.com/actions/runner/issues/4030 + - name: Remove man-db package on ubuntu + if: matrix.os == 'ubuntu-24.04' + run: | + sudo apt-get update + sudo apt-get remove man-db + - name: Install missing software on ubuntu if: matrix.os == 'ubuntu-24.04' run: | diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index fd71bdfe..9ebb6683 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -17,6 +17,13 @@ jobs: with: persist-credentials: false + # the man-db trigger causes package installations to stall for several minutes at times. so just drop the package. + # see https://github.com/actions/runner/issues/4030 + - name: Remove man-db package + run: | + sudo apt-get update + sudo apt-get remove man-db + - name: Install missing software run: | sudo apt-get update From de1d67bcd6129858261050bed80e0f6e4e487dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 21 Sep 2025 20:08:14 +0200 Subject: [PATCH 025/104] fixed #524 - use `CreateFileA()` for `_WIN32` only (fixes includes not found on MinGW) (#541) Co-authored-by: glank --- simplecpp.cpp | 13 ++++++++++--- simplecpp.h | 10 +++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index b1505cb6..4d64d582 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3,15 +3,22 @@ * Copyright (C) 2016-2023 simplecpp team */ -#if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) -# define _WIN32_WINNT 0x0602 +#if defined(_WIN32) +# ifndef _WIN32_WINNT +# define _WIN32_WINNT 0x0602 +# endif # define NOMINMAX +# define WIN32_LEAN_AND_MEAN # include # undef ERROR #endif #include "simplecpp.h" +#if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) +# define SIMPLECPP_WINDOWS +#endif + #include #include #include @@ -3082,7 +3089,7 @@ std::pair simplecpp::FileDataCache::get(const std:: bool simplecpp::FileDataCache::getFileId(const std::string &path, FileID &id) { -#ifdef SIMPLECPP_WINDOWS +#ifdef _WIN32 HANDLE hFile = CreateFileA(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) diff --git a/simplecpp.h b/simplecpp.h index 43a03730..ef748b3e 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -6,10 +6,6 @@ #ifndef simplecppH #define simplecppH -#if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) -# define SIMPLECPP_WINDOWS -#endif - #include #include #include @@ -43,7 +39,7 @@ # define SIMPLECPP_LIB #endif -#ifdef SIMPLECPP_WINDOWS +#ifdef _WIN32 # include #else # include @@ -471,7 +467,7 @@ namespace simplecpp { private: struct FileID { -#ifdef SIMPLECPP_WINDOWS +#ifdef _WIN32 struct { std::uint64_t VolumeSerialNumber; struct { @@ -495,7 +491,7 @@ namespace simplecpp { #endif struct Hasher { std::size_t operator()(const FileID &id) const { -#ifdef SIMPLECPP_WINDOWS +#ifdef _WIN32 return static_cast(id.fileIdInfo.FileId.IdentifierHi ^ id.fileIdInfo.FileId.IdentifierLo ^ id.fileIdInfo.VolumeSerialNumber); #else From 32cccf5d74b8b0356fcb161e3ada23d7b3ccb43c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 23 Sep 2025 09:22:11 +0200 Subject: [PATCH 026/104] main.cpp: improved handling of empty options (#540) --- main.cpp | 48 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/main.cpp b/main.cpp index 2397e847..2ddf78cd 100644 --- a/main.cpp +++ b/main.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include int main(int argc, char **argv) @@ -30,49 +31,76 @@ int main(int argc, char **argv) const char c = arg[1]; switch (c) { case 'D': { // define symbol + found = true; const char * const value = arg[2] ? (argv[i] + 2) : argv[++i]; + if (!value) { + std::cout << "error: option -D with no value." << std::endl; + error = true; + break; + } dui.defines.push_back(value); - found = true; break; } case 'U': { // undefine symbol + found = true; const char * const value = arg[2] ? (argv[i] + 2) : argv[++i]; + if (!value) { + std::cout << "error: option -U with no value." << std::endl; + error = true; + break; + } dui.undefined.insert(value); - found = true; break; } case 'I': { // include path - const char * const value = arg[2] ? (argv[i] + 2) : argv[++i]; - dui.includePaths.push_back(value); found = true; + const char * const value = arg[2] ? (arg + 2) : argv[++i]; + if (!value) { + std::cout << "error: option -I with no value." << std::endl; + error = true; + break; + } + dui.includePaths.push_back(value); break; } case 'i': if (std::strncmp(arg, "-include=",9)==0) { - dui.includes.push_back(arg+9); found = true; + std::string value = arg + 9; + if (value.empty()) { + std::cout << "error: option -include with no value." << std::endl; + error = true; + break; + } + dui.includes.push_back(std::move(value)); } else if (std::strncmp(arg, "-is",3)==0) { - use_istream = true; found = true; + use_istream = true; } break; case 's': if (std::strncmp(arg, "-std=",5)==0) { - dui.std = arg + 5; found = true; + std::string value = arg + 5; + if (value.empty()) { + std::cout << "error: option -std with no value." << std::endl; + error = true; + break; + } + dui.std = std::move(value); } break; case 'q': - quiet = true; found = true; + quiet = true; break; case 'e': - error_only = true; found = true; + error_only = true; break; case 'f': - fail_on_error = true; found = true; + fail_on_error = true; break; } if (!found) { From 1420eb61d45b6e89d7be898052a455791184913a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 23 Sep 2025 11:06:45 +0200 Subject: [PATCH 027/104] fixed #462 - added CLI option `-l` to print line numbers (#463) --- main.cpp | 8 +++++++- simplecpp.cpp | 15 ++++++++++++--- simplecpp.h | 4 ++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/main.cpp b/main.cpp index 2ddf78cd..cbae6ac8 100644 --- a/main.cpp +++ b/main.cpp @@ -19,6 +19,7 @@ int main(int argc, char **argv) const char *filename = nullptr; bool use_istream = false; bool fail_on_error = false; + bool linenrs = false; // Settings.. simplecpp::DUI dui; @@ -102,6 +103,10 @@ int main(int argc, char **argv) found = true; fail_on_error = true; break; + case 'l': + linenrs = true; + found = true; + break; } if (!found) { std::cout << "error: option '" << arg << "' is unknown." << std::endl; @@ -135,6 +140,7 @@ int main(int argc, char **argv) std::cout << " -is Use std::istream interface." << std::endl; std::cout << " -e Output errors only." << std::endl; std::cout << " -f Fail when errors were encountered (exitcode 1)." << std::endl; + std::cout << " -l Print lines numbers." << std::endl; std::exit(0); } @@ -165,7 +171,7 @@ int main(int argc, char **argv) // Output if (!quiet) { if (!error_only) - std::cout << outputTokens.stringify() << std::endl; + std::cout << outputTokens.stringify(linenrs) << std::endl; for (const simplecpp::Output &output : outputList) { std::cerr << output.location.file() << ':' << output.location.line << ": "; diff --git a/simplecpp.cpp b/simplecpp.cpp index 4d64d582..0621fe06 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -553,24 +553,33 @@ void simplecpp::TokenList::push_back(Token *tok) backToken = tok; } -void simplecpp::TokenList::dump() const +void simplecpp::TokenList::dump(bool linenrs) const { - std::cout << stringify() << std::endl; + std::cout << stringify(linenrs) << std::endl; } -std::string simplecpp::TokenList::stringify() const +std::string simplecpp::TokenList::stringify(bool linenrs) const { std::ostringstream ret; Location loc(files); + bool filechg = true; for (const Token *tok = cfront(); tok; tok = tok->next) { if (tok->location.line < loc.line || tok->location.fileIndex != loc.fileIndex) { ret << "\n#line " << tok->location.line << " \"" << tok->location.file() << "\"\n"; loc = tok->location; + filechg = true; + } + + if (linenrs && filechg) { + ret << loc.line << ": "; + filechg = false; } while (tok->location.line > loc.line) { ret << '\n'; loc.line++; + if (linenrs) + ret << loc.line << ": "; } if (sameline(tok->previous, tok)) diff --git a/simplecpp.h b/simplecpp.h index ef748b3e..a014a6fc 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -284,8 +284,8 @@ namespace simplecpp { } void push_back(Token *tok); - void dump() const; - std::string stringify() const; + void dump(bool linenrs = false) const; + std::string stringify(bool linenrs = false) const; void readfile(Stream &stream, const std::string &filename=std::string(), OutputList *outputList = nullptr); void constFold(); From 67f8e0e2436c99eeabbc5115af12ca40479ba4d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 23 Sep 2025 11:07:34 +0200 Subject: [PATCH 028/104] run-tests.py: improved output when test failed (#545) --- run-tests.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/run-tests.py b/run-tests.py index 8810bf2b..20f59a49 100644 --- a/run-tests.py +++ b/run-tests.py @@ -92,7 +92,7 @@ def cleanup(out: str) -> str: ] -def run(compiler_executable: str, compiler_args: list[str]) -> tuple[int, str, str]: +def run(compiler_executable: str, compiler_args: list[str]) -> tuple[int, str, str, str]: """Execute a compiler command and capture its exit code, stdout, and stderr.""" compiler_cmd = [compiler_executable] compiler_cmd.extend(compiler_args) @@ -103,7 +103,7 @@ def run(compiler_executable: str, compiler_args: list[str]) -> tuple[int, str, s output = cleanup(stdout) error = (stderr or "").strip() - return (exit_code, output, error) + return (exit_code, output, stdout, error) numberOfSkipped = 0 @@ -117,20 +117,32 @@ def run(compiler_executable: str, compiler_args: list[str]) -> tuple[int, str, s numberOfSkipped = numberOfSkipped + 1 continue - _, clang_output, _ = run(CLANG_EXE, cmd.split(' ')) + _, clang_output_c, clang_output, _ = run(CLANG_EXE, cmd.split(' ')) - _, gcc_output, _ = run(GCC_EXE, cmd.split(' ')) + _, gcc_output_c, gcc_output, _ = run(GCC_EXE, cmd.split(' ')) # -E is not supported and we bail out on unknown options - simplecpp_ec, simplecpp_output, simplecpp_err = run(SIMPLECPP_EXE, cmd.replace('-E ', '', 1).split(' ')) + simplecpp_ec, simplecpp_output_c, simplecpp_output, simplecpp_err = run(SIMPLECPP_EXE, cmd.replace('-E ', '', 1).split(' ')) - if simplecpp_output != clang_output and simplecpp_output != gcc_output: + if simplecpp_output_c != clang_output_c and simplecpp_output_c != gcc_output_c: filename = cmd[cmd.rfind('/')+1:] if filename in todo: print('TODO ' + cmd) usedTodos.append(filename) else: print('FAILED ' + cmd) + print('---expected (clang):') + print(clang_output_c) + print('---expected (gcc):') + print(gcc_output_c) + print('---actual:') + print(simplecpp_output_c) + print('---output (clang):') + print(clang_output) + print('---output (gcc):') + print(gcc_output) + print('---output (simplecpp):') + print(simplecpp_output) if simplecpp_ec: print('simplecpp failed - ' + simplecpp_err) numberOfFailed = numberOfFailed + 1 From 3deeb916acdb04be19811289dc13a2dc09da06c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 23 Sep 2025 19:25:00 +0200 Subject: [PATCH 029/104] CI-windows.yml: added `windows-11-arm` (#544) --- .github/workflows/CI-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 767bba6c..7985995f 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -18,7 +18,7 @@ jobs: build: strategy: matrix: - os: [windows-2022, windows-2025] + os: [windows-2022, windows-2025, windows-11-arm] config: [Release, Debug] fail-fast: false From 7f59eec64312aec7456ebdabb39e351280c11ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Thu, 25 Sep 2025 08:21:12 +0200 Subject: [PATCH 030/104] fixed #498 - avoid potential leak in `simplecpp::Macro::parseDefine()` (#527) --- simplecpp.cpp | 6 ++++-- test.cpp | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 0621fe06..799d6c9b 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1709,7 +1709,9 @@ namespace simplecpp { nameTokDef = nametoken; variadic = false; variadicOpt = false; + delete optExpandValue; optExpandValue = nullptr; + delete optNoExpandValue; optNoExpandValue = nullptr; if (!nameTokDef) { valueToken = endToken = nullptr; @@ -2383,8 +2385,8 @@ namespace simplecpp { bool variadicOpt; /** Expansion value for varadic macros with __VA_OPT__ expanded and discarded respectively */ - const TokenList *optExpandValue; - const TokenList *optNoExpandValue; + const TokenList *optExpandValue{}; + const TokenList *optNoExpandValue{}; /** was the value of this macro actually defined in the code? */ bool valueDefinedInCode_; diff --git a/test.cpp b/test.cpp index f1fe2d1c..b9869b5b 100644 --- a/test.cpp +++ b/test.cpp @@ -3246,6 +3246,7 @@ static void safe_api() #endif } +// crashes detected by fuzzer static void fuzz_crash() { { @@ -3260,6 +3261,16 @@ static void fuzz_crash() } } +// memory leaks detected by LSAN/valgrind +static void leak() +{ + { // #498 + const char code[] = "#define e(...)__VA_OPT__()\n" + "#define e\n"; + (void)preprocess(code, simplecpp::DUI()); + } +} + int main(int argc, char **argv) { TEST_CASE(backslash); @@ -3516,5 +3527,7 @@ int main(int argc, char **argv) TEST_CASE(fuzz_crash); + TEST_CASE(leak); + return numberOfFailedAssertions > 0 ? EXIT_FAILURE : EXIT_SUCCESS; } From 52fb0b914fe4bc74df5c34b4093aa78505dba180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Mon, 29 Sep 2025 02:20:48 +0200 Subject: [PATCH 031/104] simplecpp.h: prevent internal define `SIMPLECPP_LIB` from spilling (#525) --- simplecpp.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/simplecpp.h b/simplecpp.h index a014a6fc..4675d1f3 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -563,4 +563,6 @@ namespace simplecpp { # pragma warning(pop) #endif +#undef SIMPLECPP_LIB + #endif From 60e743a208f76c7cd68d3e8501347da9f9b39035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Mon, 29 Sep 2025 02:21:42 +0200 Subject: [PATCH 032/104] bail out on CMake related issues in the CI (#550) --- .github/workflows/CI-unixish.yml | 2 +- .github/workflows/CI-windows.yml | 2 +- .github/workflows/clang-tidy.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index 74100265..07ee9731 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -82,7 +82,7 @@ jobs: - name: Run CMake run: | - cmake -S . -B cmake.output -DCMAKE_COMPILE_WARNING_AS_ERROR=On + cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_COMPILE_WARNING_AS_ERROR=On - name: CMake simplecpp run: | diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 7985995f..ccf208fa 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -45,7 +45,7 @@ jobs: - name: Run CMake run: | - cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_COMPILE_WARNING_AS_ERROR=On . || exit /b !errorlevel! + cmake -G "Visual Studio 17 2022" -A x64 -Werror=dev --warn-uninitialized -DCMAKE_COMPILE_WARNING_AS_ERROR=On . || exit /b !errorlevel! - name: Build run: | diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 9ebb6683..8bc15d1a 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -42,7 +42,7 @@ jobs: - name: Prepare CMake run: | - cmake -S . -B cmake.output -G "Unix Makefiles" -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON env: CXX: clang-20 From d86671f47544882a1cb2860a7e007d9a20437a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Mon, 29 Sep 2025 02:35:31 +0200 Subject: [PATCH 033/104] clang-tidy.yml: updated to Clang 21 (#421) --- .clang-tidy | 1 + .github/workflows/clang-tidy.yml | 10 +++++----- CMakeLists.txt | 17 +++++++++++++---- simplecpp.cpp | 27 +++++++++++++++++---------- simplecpp.h | 1 + 5 files changed, 37 insertions(+), 19 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index c03da523..806a8608 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -49,6 +49,7 @@ Checks: > -readability-magic-numbers, -readability-redundant-inline-specifier, -readability-simplify-boolean-expr, + -readability-use-concise-preprocessor-directives, -readability-uppercase-literal-suffix, -performance-avoid-endl, -performance-enum-size, diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 8bc15d1a..4b52d7bc 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -33,19 +33,19 @@ jobs: run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh - sudo ./llvm.sh 20 - sudo apt-get install clang-tidy-20 + sudo ./llvm.sh 21 + sudo apt-get install clang-tidy-21 - name: Verify clang-tidy configuration run: | - clang-tidy-20 --verify-config + clang-tidy-21 --verify-config - name: Prepare CMake run: | cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON env: - CXX: clang-20 + CXX: clang-21 - name: Clang-Tidy run: | - run-clang-tidy-20 -q -j $(nproc) -p=cmake.output + run-clang-tidy-21 -q -j $(nproc) -p=cmake.output diff --git a/CMakeLists.txt b/CMakeLists.txt index 0cc62e88..efdc0d96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,16 +49,25 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") # no need for c++98 compatibility add_compile_options(-Wno-c++98-compat-pedantic) # these are not really fixable - add_compile_options(-Wno-exit-time-destructors -Wno-global-constructors -Wno-weak-vtables) + add_compile_options(-Wno-exit-time-destructors) + add_compile_options(-Wno-global-constructors) + add_compile_options(-Wno-weak-vtables) add_compile_options_safe(-Wno-unsafe-buffer-usage) + add_compile_options_safe(-Wno-nrvo) # we are not interested in these - add_compile_options(-Wno-multichar -Wno-four-char-constants) + add_compile_options(-Wno-multichar) + add_compile_options(-Wno-four-char-constants) # ignore C++11-specific warning - add_compile_options(-Wno-suggest-override -Wno-suggest-destructor-override) + add_compile_options(-Wno-suggest-override) + add_compile_options(-Wno-suggest-destructor-override) # contradicts -Wcovered-switch-default add_compile_options(-Wno-switch-default) # TODO: fix these? - add_compile_options(-Wno-padded -Wno-sign-conversion -Wno-implicit-int-conversion -Wno-shorten-64-to-32 -Wno-shadow-field-in-constructor) + add_compile_options(-Wno-padded) + add_compile_options(-Wno-sign-conversion) + add_compile_options(-Wno-implicit-int-conversion) + add_compile_options(-Wno-shorten-64-to-32) + add_compile_options(-Wno-shadow-field-in-constructor) if (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 14 OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 14) # TODO: verify this regression still exists in clang-15 diff --git a/simplecpp.cpp b/simplecpp.cpp index 799d6c9b..320836be 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -874,7 +874,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, back()->setstr(currentToken); location.adjust(currentToken); if (currentToken.find_first_of("\r\n") == std::string::npos) - location.col += 2 + 2 * delim.size(); + location.col += 2 + (2 * delim.size()); else location.col += 1 + delim.size(); @@ -1329,6 +1329,7 @@ void simplecpp::TokenList::constFoldLogicalOp(Token *tok) void simplecpp::TokenList::constFoldQuestionOp(Token **tok1) { bool gotoTok1 = false; + // NOLINTNEXTLINE(misc-const-correctness) - technically correct but used to access non-const data for (Token *tok = *tok1; tok && tok->op != ')'; tok = gotoTok1 ? *tok1 : tok->next) { gotoTok1 = false; if (tok->str() != "?") @@ -1508,7 +1509,12 @@ namespace simplecpp { } Macro(const Macro &other) : nameTokDef(nullptr), files(other.files), tokenListDefine(other.files), valueDefinedInCode_(other.valueDefinedInCode_) { - *this = other; + // TODO: remove the try-catch - see #537 + // avoid bugprone-exception-escape clang-tidy warning + try { + *this = other; + } + catch (const Error&) {} // NOLINT(bugprone-empty-catch) } ~Macro() { @@ -1945,6 +1951,7 @@ namespace simplecpp { } } + // NOLINTNEXTLINE(misc-const-correctness) - technically correct but used to access non-const data Token * const output_end_1 = output.back(); const Token *valueToken2; @@ -2250,7 +2257,7 @@ namespace simplecpp { const bool canBeConcatenatedStringOrChar = isStringLiteral_(A->str()) || isCharLiteral_(A->str()); const bool unexpectedA = (!A->name && !A->number && !A->str().empty() && !canBeConcatenatedWithEqual && !canBeConcatenatedStringOrChar); - Token * const B = tok->next->next; + const Token * const B = tok->next->next; if (!B->name && !B->number && B->op && !B->isOneOf("#=")) throw invalidHashHash::unexpectedToken(tok->location, name(), B); @@ -2528,11 +2535,11 @@ static void simplifySizeof(simplecpp::TokenList &expr, const std::mapnext) { if (tok->str() != "sizeof") continue; - simplecpp::Token *tok1 = tok->next; + const simplecpp::Token *tok1 = tok->next; if (!tok1) { throw std::runtime_error("missing sizeof argument"); } - simplecpp::Token *tok2 = tok1->next; + const simplecpp::Token *tok2 = tok1->next; if (!tok2) { throw std::runtime_error("missing sizeof argument"); } @@ -2547,7 +2554,7 @@ static void simplifySizeof(simplecpp::TokenList &expr, const std::mapnext) { + for (const simplecpp::Token *typeToken = tok1; typeToken != tok2; typeToken = typeToken->next) { if ((typeToken->str() == "unsigned" || typeToken->str() == "signed") && typeToken->next->name) continue; if (typeToken->str() == "*" && type.find('*') != std::string::npos) @@ -2598,11 +2605,11 @@ static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) { if (tok->str() != HAS_INCLUDE) continue; - simplecpp::Token *tok1 = tok->next; + const simplecpp::Token *tok1 = tok->next; if (!tok1) { throw std::runtime_error("missing __has_include argument"); } - simplecpp::Token *tok2 = tok1->next; + const simplecpp::Token *tok2 = tok1->next; if (!tok2) { throw std::runtime_error("missing __has_include argument"); } @@ -2620,7 +2627,7 @@ static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI const bool systemheader = (tok1 && tok1->op == '<'); std::string header; if (systemheader) { - simplecpp::Token *tok3 = tok1->next; + const simplecpp::Token *tok3 = tok1->next; if (!tok3) { throw std::runtime_error("missing __has_include closing angular bracket"); } @@ -2631,7 +2638,7 @@ static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI } } - for (simplecpp::Token *headerToken = tok1->next; headerToken != tok3; headerToken = headerToken->next) + for (const simplecpp::Token *headerToken = tok1->next; headerToken != tok3; headerToken = headerToken->next) header += headerToken->str(); } else { header = tok1->str().substr(1U, tok1->str().size() - 2U); diff --git a/simplecpp.h b/simplecpp.h index 4675d1f3..c6c3b515 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -426,6 +426,7 @@ namespace simplecpp { std::pair get(const std::string &sourcefile, const std::string &header, const DUI &dui, bool systemheader, std::vector &filenames, OutputList *outputList); void insert(FileData data) { + // NOLINTNEXTLINE(misc-const-correctness) - FP FileData *const newdata = new FileData(std::move(data)); mData.emplace_back(newdata); From dcc0380ae268f223c193434b6a1df6939305abc1 Mon Sep 17 00:00:00 2001 From: chrchr-github <78114321+chrchr-github@users.noreply.github.com> Date: Mon, 29 Sep 2025 10:39:32 +0200 Subject: [PATCH 034/104] Fix #546 fuzzing crash in simplecpp::preprocess() (#553) --- simplecpp.cpp | 8 +++++--- test.cpp | 6 ++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 320836be..c5760145 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3602,9 +3602,11 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL header = tok->str().substr(1U, tok->str().size() - 2U); closingAngularBracket = true; } - std::ifstream f; - const std::string header2 = openHeader(f,dui,sourcefile,header,systemheader); - expr.push_back(new Token(header2.empty() ? "0" : "1", tok->location)); + if (tok) { + std::ifstream f; + const std::string header2 = openHeader(f,dui,sourcefile,header,systemheader); + expr.push_back(new Token(header2.empty() ? "0" : "1", tok->location)); + } } if (par) tok = tok ? tok->next : nullptr; diff --git a/test.cpp b/test.cpp index b9869b5b..f42b2189 100644 --- a/test.cpp +++ b/test.cpp @@ -3259,6 +3259,12 @@ static void fuzz_crash() "foo(f##oo(intp))\n"; (void)preprocess(code, simplecpp::DUI()); // do not crash } + { // #546 + const char code[] = "#if __has_include<\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); // do not crash + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition\n", toString(outputList)); + } } // memory leaks detected by LSAN/valgrind From dcc4fddb4942dfd309897a8e867c9b61b170f79e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 1 Oct 2025 17:28:29 +0200 Subject: [PATCH 035/104] main.cpp: error out when file/path provided by `-I` or `-include=`, or the input does not exist (#435) --- integration_test.py | 148 ++++++++++++++++++++++++++++++++++++++++++++ main.cpp | 75 +++++++++++++++------- 2 files changed, 202 insertions(+), 21 deletions(-) diff --git a/integration_test.py b/integration_test.py index a59ae338..e5497db3 100644 --- a/integration_test.py +++ b/integration_test.py @@ -298,3 +298,151 @@ def test_pragma_once_matching(record_property, tmpdir): assert stdout.count("ONCE") == 1 assert stderr == "" + + +def test_input_multiple(record_property, tmpdir): + test_file = os.path.join(tmpdir, "test.c") + with open(test_file, 'w'): + pass + + test_file_1 = os.path.join(tmpdir, "test1.c") + with open(test_file_1, 'w'): + pass + + args = [ + 'test.c', + 'test1.c' + ] + + _, stdout, stderr = simplecpp(args, cwd=tmpdir) + record_property("stdout", stdout) + record_property("stderr", stderr) + + assert '' == stderr + assert "error: multiple filenames specified\n" == stdout + + +def test_input_missing(record_property, tmpdir): + args = [ + 'missing.c' + ] + + _, stdout, stderr = simplecpp(args, cwd=tmpdir) + record_property("stdout", stdout) + record_property("stderr", stderr) + + assert '' == stderr + assert "error: could not open file 'missing.c'\n" == stdout + + +def test_input_dir(record_property, tmpdir): + test_dir = os.path.join(tmpdir, "test") + os.mkdir(test_dir) + + args = [ + 'test' + ] + + _, stdout, stderr = simplecpp(args, cwd=tmpdir) + record_property("stdout", stdout) + record_property("stderr", stderr) + + assert '' == stderr + assert "error: could not open file 'test'\n" == stdout + + +def test_incpath_missing(record_property, tmpdir): + test_file = os.path.join(tmpdir, "test.c") + with open(test_file, 'w'): + pass + + test_dir = os.path.join(tmpdir, "test") + os.mkdir(test_dir) + + args = [ + '-Itest', + '-Imissing', + 'test.c' + ] + + _, stdout, stderr = simplecpp(args, cwd=tmpdir) + record_property("stdout", stdout) + record_property("stderr", stderr) + + assert '' == stderr + assert "error: could not find include path 'missing'\n" == stdout + + +def test_incpath_file(record_property, tmpdir): + test_file = os.path.join(tmpdir, "test.c") + with open(test_file, 'w'): + pass + + inc_dir = os.path.join(tmpdir, "inc") + os.mkdir(inc_dir) + + inc_file = os.path.join(tmpdir, "inc.h") + with open(test_file, 'w'): + pass + + args = [ + '-Iinc', + '-Iinc.h', + 'test.c' + ] + + _, stdout, stderr = simplecpp(args, cwd=tmpdir) + record_property("stdout", stdout) + record_property("stderr", stderr) + + assert '' == stderr + assert "error: could not find include path 'inc.h'\n" == stdout + + +def test_incfile_missing(record_property, tmpdir): + test_file = os.path.join(tmpdir, "test.c") + with open(test_file, 'w'): + pass + + inc_file = os.path.join(tmpdir, "inc.h") + with open(inc_file, 'w'): + pass + + args = [ + '-include=inc.h', + '-include=missing.h', + 'test.c' + ] + + _, stdout, stderr = simplecpp(args, cwd=tmpdir) + record_property("stdout", stdout) + record_property("stderr", stderr) + + assert '' == stderr + assert "error: could not open include 'missing.h'\n" == stdout + + +def test_incpath_dir(record_property, tmpdir): + test_file = os.path.join(tmpdir, "test.c") + with open(test_file, 'w'): + pass + + inc_file = os.path.join(tmpdir, "inc.h") + with open(inc_file, 'w'): + pass + + inc_dir = os.path.join(tmpdir, "inc") + os.mkdir(inc_dir) + + args = [ + '-include=inc.h', + '-include=inc', + 'test.c' + ] + + _, stdout, stderr = simplecpp(args, cwd=tmpdir) + record_property("stdout", stdout) + record_property("stderr", stderr) + + assert '' == stderr + assert "error: could not open include 'inc'\n" == stdout diff --git a/main.cpp b/main.cpp index cbae6ac8..0196d6e4 100644 --- a/main.cpp +++ b/main.cpp @@ -9,10 +9,20 @@ #include #include #include +#include #include #include #include +static bool isDir(const std::string& path) +{ + struct stat file_stat; + if (stat(path.c_str(), &file_stat) == -1) + return false; + + return (file_stat.st_mode & S_IFMT) == S_IFDIR; +} + int main(int argc, char **argv) { bool error = false; @@ -23,6 +33,7 @@ int main(int argc, char **argv) // Settings.. simplecpp::DUI dui; + dui.removeComments = true; bool quiet = false; bool error_only = false; for (int i = 1; i < argc; i++) { @@ -114,18 +125,18 @@ int main(int argc, char **argv) } } else if (filename) { std::cout << "error: multiple filenames specified" << std::endl; - std::exit(1); + return 1; } else { filename = arg; } } if (error) - std::exit(1); + return 1; if (quiet && error_only) { std::cout << "error: -e cannot be used in conjunction with -q" << std::endl; - std::exit(1); + return 1; } if (!filename) { @@ -141,32 +152,54 @@ int main(int argc, char **argv) std::cout << " -e Output errors only." << std::endl; std::cout << " -f Fail when errors were encountered (exitcode 1)." << std::endl; std::cout << " -l Print lines numbers." << std::endl; - std::exit(0); + return 0; } - dui.removeComments = true; + // TODO: move this logic into simplecpp + bool inp_missing = false; + + for (const std::string& inc : dui.includes) { + std::ifstream f(inc); + if (!f.is_open() || isDir(inc)) { + inp_missing = true; + std::cout << "error: could not open include '" << inc << "'" << std::endl; + } + } + + for (const std::string& inc : dui.includePaths) { + if (!isDir(inc)) { + inp_missing = true; + std::cout << "error: could not find include path '" << inc << "'" << std::endl; + } + } + + std::ifstream f(filename); + if (!f.is_open() || isDir(filename)) { + inp_missing = true; + std::cout << "error: could not open file '" << filename << "'" << std::endl; + } + + if (inp_missing) + return 1; // Perform preprocessing simplecpp::OutputList outputList; std::vector files; - simplecpp::TokenList *rawtokens; - if (use_istream) { - std::ifstream f(filename); - if (!f.is_open()) { - std::cout << "error: could not open file '" << filename << "'" << std::endl; - std::exit(1); + simplecpp::TokenList outputTokens(files); + { + simplecpp::TokenList *rawtokens; + if (use_istream) { + rawtokens = new simplecpp::TokenList(f, files,filename,&outputList); + } else { + f.close(); + rawtokens = new simplecpp::TokenList(filename,files,&outputList); } - rawtokens = new simplecpp::TokenList(f, files,filename,&outputList); - } else { - rawtokens = new simplecpp::TokenList(filename,files,&outputList); + rawtokens->removeComments(); + simplecpp::FileDataCache filedata; + simplecpp::preprocess(outputTokens, *rawtokens, files, filedata, dui, &outputList); + simplecpp::cleanup(filedata); + delete rawtokens; } - rawtokens->removeComments(); - simplecpp::TokenList outputTokens(files); - simplecpp::FileDataCache filedata; - simplecpp::preprocess(outputTokens, *rawtokens, files, filedata, dui, &outputList); - simplecpp::cleanup(filedata); - delete rawtokens; - rawtokens = nullptr; // Output if (!quiet) { From 009ceca046d6cda5c75bce64a15d396e0312f785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 1 Oct 2025 17:28:43 +0200 Subject: [PATCH 036/104] enabled and fixed `modernize-use-override` clang-tidy warnings (#552) --- .clang-tidy | 1 - simplecpp.cpp | 24 ++++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 806a8608..c38c46f7 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -36,7 +36,6 @@ Checks: > -modernize-use-equals-delete, -modernize-use-default-member-init, -modernize-use-nodiscard, - -modernize-use-override, -modernize-use-trailing-return-type, -modernize-use-using, -readability-avoid-nested-conditional-operator, diff --git a/simplecpp.cpp b/simplecpp.cpp index c5760145..2cf1c6c0 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -358,16 +358,16 @@ class StdIStream : public simplecpp::TokenList::Stream { init(); } - virtual int get() override { + int get() override { return istr.get(); } - virtual int peek() override { + int peek() override { return istr.peek(); } - virtual void unget() override { + void unget() override { istr.unget(); } - virtual bool good() override { + bool good() override { return istr.good(); } @@ -386,20 +386,20 @@ class StdCharBufStream : public simplecpp::TokenList::Stream { init(); } - virtual int get() override { + int get() override { if (pos >= size) return lastStatus = EOF; return str[pos++]; } - virtual int peek() override { + int peek() override { if (pos >= size) return lastStatus = EOF; return str[pos]; } - virtual void unget() override { + void unget() override { --pos; } - virtual bool good() override { + bool good() override { return lastStatus != EOF; } @@ -429,20 +429,20 @@ class FileStream : public simplecpp::TokenList::Stream { file = nullptr; } - virtual int get() override { + int get() override { lastStatus = lastCh = fgetc(file); return lastCh; } - virtual int peek() override { + int peek() override { // keep lastCh intact const int ch = fgetc(file); unget_internal(ch); return ch; } - virtual void unget() override { + void unget() override { unget_internal(lastCh); } - virtual bool good() override { + bool good() override { return lastStatus != EOF; } From 5b736f252fe983c7ac7d122dccdf0aeb3b00ec64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 1 Oct 2025 17:37:28 +0200 Subject: [PATCH 037/104] improved and exposed `isAbsolutePath()` (#538) --- simplecpp.cpp | 29 +++++++++++++++++------------ simplecpp.h | 3 +++ test.cpp | 37 ++++++++++++++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 2cf1c6c0..0029aac0 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2438,21 +2438,26 @@ namespace simplecpp { return windowsPath; } #endif -} + bool isAbsolutePath(const std::string &path) + { #ifdef SIMPLECPP_WINDOWS -static bool isAbsolutePath(const std::string &path) -{ - if (path.length() >= 3 && path[0] > 0 && std::isalpha(path[0]) && path[1] == ':' && (path[2] == '\\' || path[2] == '/')) - return true; - return path.length() > 1U && (path[0] == '/' || path[0] == '\\'); -} + // C:\\path\\file + // C:/path/file + if (path.length() >= 3 && std::isalpha(path[0]) && path[1] == ':' && (path[2] == '\\' || path[2] == '/')) + return true; + + // \\host\path\file + // //host/path/file + if (path.length() >= 2 && (path[0] == '\\' || path[0] == '/') && (path[1] == '\\' || path[1] == '/')) + return true; + + return false; #else -static bool isAbsolutePath(const std::string &path) -{ - return path.length() > 1U && path[0] == '/'; -} + return !path.empty() && path[0] == '/'; #endif + } +} namespace simplecpp { /** @@ -3013,7 +3018,7 @@ static std::string openHeaderDirect(std::ifstream &f, const std::string &path) static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader) { - if (isAbsolutePath(header)) + if (simplecpp::isAbsolutePath(header)) return openHeaderDirect(f, simplecpp::simplifyPath(header)); // prefer first to search the header relatively to source file if found, when not a system header diff --git a/simplecpp.h b/simplecpp.h index c6c3b515..b461de47 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -556,6 +556,9 @@ namespace simplecpp { /** Returns the __cplusplus value for a given standard */ SIMPLECPP_LIB std::string getCppStdString(const std::string &std); SIMPLECPP_LIB std::string getCppStdString(cppstd_t std); + + /** Checks if given path is absolute */ + SIMPLECPP_LIB bool isAbsolutePath(const std::string &path); } #undef SIMPLECPP_TOKENLIST_ALLOW_PTR diff --git a/test.cpp b/test.cpp index f42b2189..d42d6570 100644 --- a/test.cpp +++ b/test.cpp @@ -45,7 +45,7 @@ static int assertEquals(const std::string &expected, const std::string &actual, if (expected != actual) { numberOfFailedAssertions++; std::cerr << "------ assertion failed ---------" << std::endl; - std::cerr << "line " << line << std::endl; + std::cerr << "line test.cpp:" << line << std::endl; std::cerr << "expected:" << pprint(expected) << std::endl; std::cerr << "actual:" << pprint(actual) << std::endl; } @@ -3246,6 +3246,39 @@ static void safe_api() #endif } +static void isAbsolutePath() { +#ifdef _WIN32 + ASSERT_EQUALS(true, simplecpp::isAbsolutePath("C:\\foo\\bar")); + ASSERT_EQUALS(true, simplecpp::isAbsolutePath("C:/foo/bar")); + ASSERT_EQUALS(true, simplecpp::isAbsolutePath("\\\\foo\\bar")); + + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("foo\\bar")); + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("foo/bar")); + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("foo.cpp")); + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("C:foo.cpp")); + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("C:foo\\bar.cpp")); + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("bar.cpp")); + //ASSERT_EQUALS(true, simplecpp::isAbsolutePath("\\")); // TODO + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("0:\\foo\\bar")); + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("0:/foo/bar")); + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("\\foo\\bar")); + //ASSERT_EQUALS(false, simplecpp::isAbsolutePath("\\\\")); // TODO + //ASSERT_EQUALS(false, simplecpp::isAbsolutePath("//")); // TODO + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("/foo/bar")); + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("/")); +#else + ASSERT_EQUALS(true, simplecpp::isAbsolutePath("/foo/bar")); + ASSERT_EQUALS(true, simplecpp::isAbsolutePath("/")); + ASSERT_EQUALS(true, simplecpp::isAbsolutePath("//host/foo/bar")); + + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("foo/bar")); + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("foo.cpp")); + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("C:\\foo\\bar")); + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("C:/foo/bar")); + ASSERT_EQUALS(false, simplecpp::isAbsolutePath("\\\\foo\\bar")); +#endif +} + // crashes detected by fuzzer static void fuzz_crash() { @@ -3531,6 +3564,8 @@ int main(int argc, char **argv) TEST_CASE(safe_api); + TEST_CASE(isAbsolutePath); + TEST_CASE(fuzz_crash); TEST_CASE(leak); From a5fdea987afa4cf7cc30e9abd37b5b93c6bd6eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Thu, 2 Oct 2025 12:11:08 +0200 Subject: [PATCH 038/104] selfcheck.sh: also run with system includes made available (#438) --- .github/workflows/CI-unixish.yml | 4 +- Makefile | 2 +- selfcheck.sh | 106 ++++++++++++++++++++++++++++++- 3 files changed, 106 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index 07ee9731..9778f68a 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -44,10 +44,10 @@ jobs: sudo apt-get install valgrind - name: Install missing software on ubuntu (clang++) - if: matrix.os == 'ubuntu-24.04' && matrix.compiler == 'clang++' + if: contains(matrix.os, 'ubuntu') && matrix.compiler == 'clang++' run: | sudo apt-get update - sudo apt-get install libc++-18-dev + sudo apt-get install libc++-dev # coreutils contains "nproc" - name: Install missing software on macos diff --git a/Makefile b/Makefile index 7489ec83..b6bc26da 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ test: testrunner simplecpp python3 -m pytest integration_test.py -vv selfcheck: simplecpp - ./selfcheck.sh + CXX=$(CXX) ./selfcheck.sh simplecpp: main.o simplecpp.o $(CXX) $(LDFLAGS) main.o simplecpp.o -o simplecpp diff --git a/selfcheck.sh b/selfcheck.sh index a43ef8c5..cc77981d 100755 --- a/selfcheck.sh +++ b/selfcheck.sh @@ -1,11 +1,111 @@ -#!/bin/sh +#!/bin/bash output=$(./simplecpp simplecpp.cpp -e -f 2>&1) ec=$? errors=$(echo "$output" | grep -v 'Header not found: <') if [ $ec -ne 0 ]; then - # only fail if got errors which do not refer to missing system includes + # only fail if we got errors which do not refer to missing system includes if [ ! -z "$errors" ]; then exit $ec fi -fi \ No newline at end of file +fi + +if [ -z "$CXX" ]; then + exit 0 +fi + +cxx_type=$($CXX --version | head -1 | cut -d' ' -f1) +if [ "$cxx_type" = "Ubuntu" ] || [ "$cxx_type" = "Debian" ]; then + cxx_type=$($CXX --version | head -1 | cut -d' ' -f2) +fi + +# TODO: generate defines from compiler +if [ "$cxx_type" = "g++" ]; then + defs= + defs="$defs -D__GNUC__" + defs="$defs -D__STDC__" + defs="$defs -D__x86_64__" + defs="$defs -D__STDC_HOSTED__" + defs="$defs -D__CHAR_BIT__=8" + defs="$defs -D__has_builtin(x)=(1)" + defs="$defs -D__has_cpp_attribute(x)=(1)" + defs="$defs -D__has_attribute(x)=(1)" + + inc= + while read line + do + inc="$inc -I$line" + done <<< "$($CXX -x c++ -v -c -S - 2>&1 < /dev/null | grep -e'^ [/A-Z]' | grep -v /cc1plus)" +elif [ "$cxx_type" = "clang" ]; then + # libstdc++ + defs= + defs="$defs -D__x86_64__" + defs="$defs -D__STDC_HOSTED__" + defs="$defs -D__CHAR_BIT__=8" + defs="$defs -D__has_builtin(x)=(1)" + defs="$defs -D__has_cpp_attribute(x)=(1)" + defs="$defs -D__has_feature(x)=(1)" + defs="$defs -D__has_include_next(x)=(0)" + defs="$defs -D__has_attribute(x)=(0)" + defs="$defs -D__building_module(x)=(0)" + + inc= + while read line + do + inc="$inc -I$line" + done <<< "$($CXX -x c++ -v -c -S - 2>&1 < /dev/null | grep -e'^ [/A-Z]')" + + # TODO: enable + # libc++ + #defs= + #defs="$defs -D__x86_64__" + #defs="$defs -D__linux__" + #defs="$defs -D__SIZEOF_SIZE_T__=8" + #defs="$defs -D__has_include_next(x)=(0)" + #defs="$defs -D__has_builtin(x)=(1)" + #defs="$defs -D__has_feature(x)=(1)" + + #inc= + #while read line + #do + # inc="$inc -I$line" + #done <<< "$($CXX -x c++ -stdlib=libc++ -v -c -S - 2>&1 < /dev/null | grep -e'^ [/A-Z]')" +elif [ "$cxx_type" = "Apple" ]; then + defs= + defs="$defs -D__BYTE_ORDER__" + defs="$defs -D__APPLE__" + defs="$defs -D__GNUC__=15" + defs="$defs -D__x86_64__" + defs="$defs -D__SIZEOF_SIZE_T__=8" + defs="$defs -D__LITTLE_ENDIAN__" + defs="$defs -D__has_feature(x)=(0)" + defs="$defs -D__has_extension(x)=(1)" + defs="$defs -D__has_attribute(x)=(0)" + defs="$defs -D__has_cpp_attribute(x)=(0)" + defs="$defs -D__has_include_next(x)=(0)" + defs="$defs -D__has_builtin(x)=(1)" + defs="$defs -D__is_target_os(x)=(0)" + defs="$defs -D__is_target_arch(x)=(0)" + defs="$defs -D__is_target_vendor(x)=(0)" + defs="$defs -D__is_target_environment(x)=(0)" + defs="$defs -D__is_target_variant_os(x)=(0)" + defs="$defs -D__is_target_variant_environment(x)=(0)" + + inc= + while read line + do + inc="$inc -I$line" + # TODO: pass the framework path as such when possible + done <<< "$($CXX -x c++ -v -c -S - 2>&1 < /dev/null | grep -e'^ [/A-Z]' | sed 's/ (framework directory)//g')" + echo $inc +else + echo "unknown compiler '$cxx_type'" + exit 1 +fi + +# run with -std=gnuc++* so __has_include(...) is available +./simplecpp simplecpp.cpp -e -f -std=gnu++11 $defs $inc +ec=$? +if [ $ec -ne 0 ]; then + exit $ec +fi From 0334a803e4cc2d6fa938c4fa6e58b2d1dde3ef4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Thu, 2 Oct 2025 12:11:21 +0200 Subject: [PATCH 039/104] enabled and fixed `modernize-use-using` clang-tidy warnings (#557) --- .clang-tidy | 1 - simplecpp.h | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index c38c46f7..5bbc4850 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -37,7 +37,6 @@ Checks: > -modernize-use-default-member-init, -modernize-use-nodiscard, -modernize-use-trailing-return-type, - -modernize-use-using, -readability-avoid-nested-conditional-operator, -readability-braces-around-statements, -readability-function-cognitive-complexity, diff --git a/simplecpp.h b/simplecpp.h index b461de47..78609af3 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -68,7 +68,7 @@ namespace simplecpp { /** C++ code standard */ enum cppstd_t { CPPUnknown=-1, CPP03, CPP11, CPP14, CPP17, CPP20, CPP23, CPP26 }; - typedef std::string TokenString; + using TokenString = std::string; class Macro; class FileDataCache; @@ -221,7 +221,7 @@ namespace simplecpp { std::string msg; }; - typedef std::list OutputList; + using OutputList = std::list; /** List of tokens. */ class SIMPLECPP_LIB TokenList { @@ -439,10 +439,10 @@ namespace simplecpp { mData.clear(); } - typedef std::vector> container_type; - typedef container_type::iterator iterator; - typedef container_type::const_iterator const_iterator; - typedef container_type::size_type size_type; + using container_type = std::vector>; + using iterator = container_type::iterator; + using const_iterator = container_type::const_iterator; + using size_type = container_type::size_type; size_type size() const { return mData.size(); From b070df6f4ca9f600779b45b056fab3e3ac2c82c2 Mon Sep 17 00:00:00 2001 From: chrchr-github <78114321+chrchr-github@users.noreply.github.com> Date: Thu, 2 Oct 2025 12:47:34 +0200 Subject: [PATCH 040/104] Fix #470 Crash in simplecpp::Macro::expand() (#555) --- simplecpp.cpp | 8 +++++++- test.cpp | 11 +++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 0029aac0..ee58f430 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2019,7 +2019,13 @@ namespace simplecpp { tok = tok->next; if (tok == endToken2) { - output.push_back(new Token(*tok->previous)); + if (tok) { + output.push_back(new Token(*tok->previous)); + } + else { + output.push_back(new Token(*nameTokInst)); + output.back()->setstr("\"\""); + } break; } if (tok->op == '#') { diff --git a/test.cpp b/test.cpp index d42d6570..d5e5d1f8 100644 --- a/test.cpp +++ b/test.cpp @@ -1041,6 +1041,16 @@ static void define_va_opt_7() toString(outputList)); } +static void define_va_opt_8() +{ + const char code[] = "#define f(...) #__VA_OPT__(x)\n" + "const char* v1 = f();"; + + simplecpp::OutputList outputList; + ASSERT_EQUALS("\nconst char * v1 = \"\" ;", preprocess(code, &outputList)); + ASSERT_EQUALS("", toString(outputList)); +} + static void define_ifdef() { const char code[] = "#define A(X) X\n" @@ -3383,6 +3393,7 @@ int main(int argc, char **argv) TEST_CASE(define_va_opt_5); TEST_CASE(define_va_opt_6); TEST_CASE(define_va_opt_7); + TEST_CASE(define_va_opt_8); TEST_CASE(pragma_backslash); // multiline pragma directive From d1db554870f4f5ef320622801e0f693628aef417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 3 Oct 2025 11:06:04 +0200 Subject: [PATCH 041/104] enabled and fixed `modernize-use-emplace` clang-tidy warnings (#558) --- .clang-tidy | 1 - main.cpp | 6 +++--- simplecpp.cpp | 4 ++-- test.cpp | 38 +++++++++++++++++++------------------- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 5bbc4850..b88a3d36 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -31,7 +31,6 @@ Checks: > -modernize-pass-by-value, -modernize-return-braced-init-list, -modernize-use-auto, - -modernize-use-emplace, -modernize-use-equals-default, -modernize-use-equals-delete, -modernize-use-default-member-init, diff --git a/main.cpp b/main.cpp index 0196d6e4..fe8ea292 100644 --- a/main.cpp +++ b/main.cpp @@ -50,7 +50,7 @@ int main(int argc, char **argv) error = true; break; } - dui.defines.push_back(value); + dui.defines.emplace_back(value); break; } case 'U': { // undefine symbol @@ -72,7 +72,7 @@ int main(int argc, char **argv) error = true; break; } - dui.includePaths.push_back(value); + dui.includePaths.emplace_back(value); break; } case 'i': @@ -84,7 +84,7 @@ int main(int argc, char **argv) error = true; break; } - dui.includes.push_back(std::move(value)); + dui.includes.emplace_back(std::move(value)); } else if (std::strncmp(arg, "-is",3)==0) { found = true; use_istream = true; diff --git a/simplecpp.cpp b/simplecpp.cpp index ee58f430..d37c0aa7 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1734,7 +1734,7 @@ namespace simplecpp { argtok->next && argtok->next->op == ')') { variadic = true; if (!argtok->previous->name) - args.push_back("__VA_ARGS__"); + args.emplace_back("__VA_ARGS__"); argtok = argtok->next; // goto ')' break; } @@ -3653,7 +3653,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL E += (E.empty() ? "" : " ") + tok->str(); const long long result = evaluate(expr, dui, sizeOfType); conditionIsTrue = (result != 0); - ifCond->push_back(IfCond(rawtok->location, E, result)); + ifCond->emplace_back(rawtok->location, E, result); } else { const long long result = evaluate(expr, dui, sizeOfType); conditionIsTrue = (result != 0); diff --git a/test.cpp b/test.cpp index d5e5d1f8..26e3b95b 100644 --- a/test.cpp +++ b/test.cpp @@ -1727,7 +1727,7 @@ static void strict_ansi_4() "#endif"; simplecpp::DUI dui; dui.std = "gnu99"; - dui.defines.push_back("__STRICT_ANSI__"); + dui.defines.emplace_back("__STRICT_ANSI__"); ASSERT_EQUALS("\nA", preprocess(code, dui)); } @@ -1774,7 +1774,7 @@ static void ifA() ASSERT_EQUALS("", preprocess(code)); simplecpp::DUI dui; - dui.defines.push_back("A=1"); + dui.defines.emplace_back("A=1"); ASSERT_EQUALS("\nX", preprocess(code, dui)); } @@ -1793,7 +1793,7 @@ static void ifDefined() "#endif"; simplecpp::DUI dui; ASSERT_EQUALS("", preprocess(code, dui)); - dui.defines.push_back("A=1"); + dui.defines.emplace_back("A=1"); ASSERT_EQUALS("\nX", preprocess(code, dui)); } @@ -1804,7 +1804,7 @@ static void ifDefinedNoPar() "#endif"; simplecpp::DUI dui; ASSERT_EQUALS("", preprocess(code, dui)); - dui.defines.push_back("A=1"); + dui.defines.emplace_back("A=1"); ASSERT_EQUALS("\nX", preprocess(code, dui)); } @@ -1816,7 +1816,7 @@ static void ifDefinedNested() "#endif"; simplecpp::DUI dui; ASSERT_EQUALS("", preprocess(code, dui)); - dui.defines.push_back("FOO=1"); + dui.defines.emplace_back("FOO=1"); ASSERT_EQUALS("\n\nX", preprocess(code, dui)); } @@ -1828,7 +1828,7 @@ static void ifDefinedNestedNoPar() "#endif"; simplecpp::DUI dui; ASSERT_EQUALS("", preprocess(code, dui)); - dui.defines.push_back("FOO=1"); + dui.defines.emplace_back("FOO=1"); ASSERT_EQUALS("\n\nX", preprocess(code, dui)); } @@ -1881,10 +1881,10 @@ static void ifLogical() simplecpp::DUI dui; ASSERT_EQUALS("", preprocess(code, dui)); dui.defines.clear(); - dui.defines.push_back("A=1"); + dui.defines.emplace_back("A=1"); ASSERT_EQUALS("\nX", preprocess(code, dui)); dui.defines.clear(); - dui.defines.push_back("B=1"); + dui.defines.emplace_back("B=1"); ASSERT_EQUALS("\nX", preprocess(code, dui)); } @@ -2079,7 +2079,7 @@ static void missingHeader2() simplecpp::TokenList tokens2(files); const simplecpp::TokenList rawtokens = makeTokenList(code,files); simplecpp::DUI dui; - dui.includePaths.push_back("."); + dui.includePaths.emplace_back("."); simplecpp::preprocess(tokens2, rawtokens, files, cache, dui, &outputList); ASSERT_EQUALS("", toString(outputList)); } @@ -2111,7 +2111,7 @@ static void nestedInclude() simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); simplecpp::DUI dui; - dui.includePaths.push_back("."); + dui.includePaths.emplace_back("."); simplecpp::preprocess(tokens2, rawtokens, files, cache, dui, &outputList); ASSERT_EQUALS("file0,1,include_nested_too_deeply,#include nested too deeply\n", toString(outputList)); @@ -2129,7 +2129,7 @@ static void systemInclude() simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); simplecpp::DUI dui; - dui.includePaths.push_back("include"); + dui.includePaths.emplace_back("include"); simplecpp::preprocess(tokens2, rawtokens, files, cache, dui, &outputList); ASSERT_EQUALS("", toString(outputList)); @@ -2355,7 +2355,7 @@ static void include3() // #16 - crash when expanding macro from header simplecpp::TokenList out(files); simplecpp::DUI dui; - dui.includePaths.push_back("."); + dui.includePaths.emplace_back("."); simplecpp::preprocess(out, rawtokens_c, files, cache, dui); ASSERT_EQUALS("\n1234", out.stringify()); @@ -2382,8 +2382,8 @@ static void include4() // #27 - -include simplecpp::TokenList out(files); simplecpp::DUI dui; - dui.includePaths.push_back("."); - dui.includes.push_back("27.h"); + dui.includePaths.emplace_back("."); + dui.includes.emplace_back("27.h"); simplecpp::preprocess(out, rawtokens_c, files, cache, dui); ASSERT_EQUALS("123", out.stringify()); @@ -2409,7 +2409,7 @@ static void include5() // #3 - handle #include MACRO simplecpp::TokenList out(files); simplecpp::DUI dui; - dui.includePaths.push_back("."); + dui.includePaths.emplace_back("."); simplecpp::preprocess(out, rawtokens_c, files, cache, dui); ASSERT_EQUALS("\n#line 1 \"3.h\"\n123", out.stringify()); @@ -2455,7 +2455,7 @@ static void include7() // #include MACRO simplecpp::TokenList out(files); simplecpp::DUI dui; - dui.includePaths.push_back("."); + dui.includePaths.emplace_back("."); simplecpp::preprocess(out, rawtokens_c, files, cache, dui); ASSERT_EQUALS("\n#line 1 \"3.h\"\n123", out.stringify()); @@ -2493,7 +2493,7 @@ static void include9() simplecpp::TokenList out(files); simplecpp::DUI dui; - dui.includePaths.push_back("."); + dui.includePaths.emplace_back("."); simplecpp::preprocess(out, rawtokens_c, files, cache, dui); ASSERT_EQUALS("\n#line 2 \"1.h\"\nx = 1 ;", out.stringify()); @@ -2675,7 +2675,7 @@ static void stringify1() simplecpp::TokenList out(files); simplecpp::DUI dui; - dui.includePaths.push_back("."); + dui.includePaths.emplace_back("."); simplecpp::preprocess(out, rawtokens_c, files, cache, dui); ASSERT_EQUALS("\n#line 1 \"A.h\"\n1\n2\n#line 1 \"A.h\"\n1\n2", out.stringify()); @@ -2778,7 +2778,7 @@ static void userdef() { const char code[] = "#ifdef A\n123\n#endif\n"; simplecpp::DUI dui; - dui.defines.push_back("A=1"); + dui.defines.emplace_back("A=1"); ASSERT_EQUALS("\n123", preprocess(code, dui)); } From 41389249d6bb2a39d3e06de469a97aef7cdb9961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 7 Oct 2025 17:09:01 +0200 Subject: [PATCH 042/104] enabled and fixed `performance-enum-size` clang-tidy warnings (#559) --- .clang-tidy | 1 - simplecpp.cpp | 3 ++- simplecpp.h | 11 +++++------ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index b88a3d36..725db88e 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -49,7 +49,6 @@ Checks: > -readability-use-concise-preprocessor-directives, -readability-uppercase-literal-suffix, -performance-avoid-endl, - -performance-enum-size, -performance-inefficient-string-concatenation, -performance-no-automatic-move, -performance-noexcept-move-constructor diff --git a/simplecpp.cpp b/simplecpp.cpp index d37c0aa7..ea33fd14 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -24,6 +24,7 @@ #include #include #include // IWYU pragma: keep +#include #include #include #include @@ -3366,7 +3367,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL // True => code in current #if block should be kept // ElseIsTrue => code in current #if block should be dropped. the code in the #else should be kept. // AlwaysFalse => drop all code in #if and #else - enum IfState { True, ElseIsTrue, AlwaysFalse }; + enum IfState : std::uint8_t { True, ElseIsTrue, AlwaysFalse }; std::stack ifstates; std::stack iftokens; ifstates.push(True); diff --git a/simplecpp.h b/simplecpp.h index 78609af3..da859cba 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -7,6 +7,7 @@ #define simplecppH #include +#include #include #include #include @@ -39,9 +40,7 @@ # define SIMPLECPP_LIB #endif -#ifdef _WIN32 -# include -#else +#ifndef _WIN32 # include #endif @@ -63,10 +62,10 @@ namespace simplecpp { /** C code standard */ - enum cstd_t { CUnknown=-1, C89, C99, C11, C17, C23, C2Y }; + enum cstd_t : std::int8_t { CUnknown=-1, C89, C99, C11, C17, C23, C2Y }; /** C++ code standard */ - enum cppstd_t { CPPUnknown=-1, CPP03, CPP11, CPP14, CPP17, CPP20, CPP23, CPP26 }; + enum cppstd_t : std::int8_t { CPPUnknown=-1, CPP03, CPP11, CPP14, CPP17, CPP20, CPP23, CPP26 }; using TokenString = std::string; class Macro; @@ -204,7 +203,7 @@ namespace simplecpp { /** Output from preprocessor */ struct SIMPLECPP_LIB Output { explicit Output(const std::vector &files) : type(ERROR), location(files) {} - enum Type { + enum Type : std::uint8_t { ERROR, /* #error */ WARNING, /* #warning */ MISSING_HEADER, From fba490989318d105ecf3a094943331499a89af74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 7 Oct 2025 17:46:14 +0200 Subject: [PATCH 043/104] fixed #562 - avoid potential redefinition of Windows-specific macros (#563) --- simplecpp.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index ea33fd14..9f7de67d 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -7,8 +7,12 @@ # ifndef _WIN32_WINNT # define _WIN32_WINNT 0x0602 # endif -# define NOMINMAX -# define WIN32_LEAN_AND_MEAN +# ifndef NOMINMAX +# define NOMINMAX +# endif +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif # include # undef ERROR #endif From 31164c2eb28cc117aad8dbc6e24afd0433884d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 7 Oct 2025 19:24:22 +0200 Subject: [PATCH 044/104] selfcheck.sh: added options `VALGRIND_TOOL` and `SIMPLECPP_PATH` / CI-unixish.yml: use `selfcheck.sh` to run valgrind (#560) --- .github/workflows/CI-unixish.yml | 14 +++++++++----- selfcheck.sh | 26 ++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index 9778f68a..b4089ee1 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -100,9 +100,10 @@ jobs: if: matrix.os == 'ubuntu-24.04' run: | make clean - make -j$(nproc) + make -j$(nproc) CXXOPTS="-O1" valgrind --leak-check=full --num-callers=50 --show-reachable=yes --track-origins=yes --gen-suppressions=all --error-exitcode=42 ./testrunner - valgrind --leak-check=full --num-callers=50 --show-reachable=yes --track-origins=yes --gen-suppressions=all --error-exitcode=42 ./simplecpp simplecpp.cpp -e + # TODO: run Python tests with valgrind + VALGRIND_TOOL=memcheck ./selfcheck.sh - name: Run with libstdc++ debug mode if: matrix.os == 'ubuntu-24.04' && matrix.compiler == 'g++' @@ -146,10 +147,13 @@ jobs: tar xvf 1.5.1.tar.gz make clean make -j$(nproc) CXXOPTS="-O2 -g3" - valgrind --tool=callgrind ./simplecpp -e simplecpp-1.5.1/simplecpp.cpp 2>callgrind.log || (cat callgrind.log && false) + VALGRIND_TOOL=callgrind SIMPLECPP_PATH=simplecpp-1.5.1 ./selfcheck.sh >callgrind.log || (cat callgrind.log && false) cat callgrind.log - callgrind_annotate --auto=no > callgrind.annotated.log - head -50 callgrind.annotated.log + for f in callgrind.out.*; + do + callgrind_annotate --auto=no $f > $f.annotated.log + head -50 $f.annotated.log + done - uses: actions/upload-artifact@v4 if: matrix.os == 'ubuntu-24.04' diff --git a/selfcheck.sh b/selfcheck.sh index cc77981d..e708023a 100755 --- a/selfcheck.sh +++ b/selfcheck.sh @@ -1,7 +1,28 @@ #!/bin/bash -output=$(./simplecpp simplecpp.cpp -e -f 2>&1) +if [ -z "$SIMPLECPP_PATH" ]; then + SIMPLECPP_PATH=. +fi + +if [ -n "$VALGRIND_TOOL" ]; then + if [ "$VALGRIND_TOOL" = "memcheck" ]; then + VALGRIND_OPTS="--error-limit=yes --leak-check=full --num-callers=50 --show-reachable=yes --track-origins=yes --gen-suppressions=all --error-exitcode=42" + elif [ "$VALGRIND_TOOL" = "callgrind" ]; then + VALGRIND_OPTS="--tool=callgrind" + else + echo "unsupported valgrind tool '$VALGRIND_TOOL'" + exit 1 + fi + VALGRIND_CMD="valgrind --tool=$VALGRIND_TOOL --log-fd=9 $VALGRIND_OPTS" + VALGRIND_REDIRECT="valgrind_$VALGRIND_TOOL.log" +else + VALGRIND_CMD= + VALGRIND_REDIRECT="/dev/null" +fi + +output=$($VALGRIND_CMD ./simplecpp "$SIMPLECPP_PATH/simplecpp.cpp" -e -f 2>&1 9> "$VALGRIND_REDIRECT") ec=$? +cat "$VALGRIND_REDIRECT" errors=$(echo "$output" | grep -v 'Header not found: <') if [ $ec -ne 0 ]; then # only fail if we got errors which do not refer to missing system includes @@ -104,8 +125,9 @@ else fi # run with -std=gnuc++* so __has_include(...) is available -./simplecpp simplecpp.cpp -e -f -std=gnu++11 $defs $inc +$VALGRIND_CMD ./simplecpp "$SIMPLECPP_PATH/simplecpp.cpp" -e -f -std=gnu++11 $defs $inc 9> "$VALGRIND_REDIRECT" ec=$? +cat "$VALGRIND_REDIRECT" if [ $ec -ne 0 ]; then exit $ec fi From ccde80d6982d39f1f4ae6968828750f175b8912d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 8 Oct 2025 20:28:46 +0200 Subject: [PATCH 045/104] enabled and fixed `modernize-use-equals-default` clang-tidy warnings (#561) --- .clang-tidy | 1 - simplecpp.cpp | 2 +- simplecpp.h | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 725db88e..c93066ab 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -31,7 +31,6 @@ Checks: > -modernize-pass-by-value, -modernize-return-braced-init-list, -modernize-use-auto, - -modernize-use-equals-default, -modernize-use-equals-delete, -modernize-use-default-member-init, -modernize-use-nodiscard, diff --git a/simplecpp.cpp b/simplecpp.cpp index 9f7de67d..a8e197b6 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -245,7 +245,7 @@ void simplecpp::Token::printOut() const // cppcheck-suppress noConstructor - we call init() in the inherited to initialize the private members class simplecpp::TokenList::Stream { public: - virtual ~Stream() {} + virtual ~Stream() = default; virtual int get() = 0; virtual int peek() = 0; diff --git a/simplecpp.h b/simplecpp.h index da859cba..a23e4cb4 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -78,7 +78,7 @@ namespace simplecpp { public: explicit Location(const std::vector &f) : files(f), fileIndex(0), line(1U), col(0U) {} - Location(const Location &loc) : files(loc.files), fileIndex(loc.fileIndex), line(loc.line), col(loc.col) {} + Location(const Location &loc) = default; Location &operator=(const Location &other) { if (this != &other) { From a74ed72db655ea71be0177dd9944fb6d3fc02e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Thu, 9 Oct 2025 18:04:13 +0200 Subject: [PATCH 046/104] enabled and fixed `modernize-use-default-member-init` clang-tidy warnings (#564) --- .clang-tidy | 1 - simplecpp.cpp | 16 +++++++--------- simplecpp.h | 24 ++++++++++++------------ 3 files changed, 19 insertions(+), 22 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index c93066ab..b7c39f2b 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -32,7 +32,6 @@ Checks: > -modernize-return-braced-init-list, -modernize-use-auto, -modernize-use-equals-delete, - -modernize-use-default-member-init, -modernize-use-nodiscard, -modernize-use-trailing-return-type, -readability-avoid-nested-conditional-operator, diff --git a/simplecpp.cpp b/simplecpp.cpp index a8e197b6..d5b51080 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -386,8 +386,7 @@ class StdCharBufStream : public simplecpp::TokenList::Stream { StdCharBufStream(const unsigned char* str, std::size_t size) : str(str) , size(size) - , pos(0) - , lastStatus(0) { + { init(); } @@ -411,8 +410,8 @@ class StdCharBufStream : public simplecpp::TokenList::Stream { private: const unsigned char *str; const std::size_t size; - std::size_t pos; - int lastStatus; + std::size_t pos{}; + int lastStatus{}; }; class FileStream : public simplecpp::TokenList::Stream { @@ -420,8 +419,7 @@ class FileStream : public simplecpp::TokenList::Stream { // cppcheck-suppress uninitDerivedMemberVar - we call Stream::init() to initialize the private members explicit FileStream(const std::string &filename, std::vector &files) : file(fopen(filename.c_str(), "rb")) - , lastCh(0) - , lastStatus(0) { + { if (!file) { files.push_back(filename); throw simplecpp::Output(files, simplecpp::Output::FILE_NOT_FOUND, "File is missing: " + filename); @@ -465,8 +463,8 @@ class FileStream : public simplecpp::TokenList::Stream { FileStream &operator=(const FileStream&); FILE *file; - int lastCh; - int lastStatus; + int lastCh{}; + int lastStatus{}; }; simplecpp::TokenList::TokenList(std::vector &filenames) : frontToken(nullptr), backToken(nullptr), files(filenames) {} @@ -1487,7 +1485,7 @@ namespace simplecpp { class Macro { public: - explicit Macro(std::vector &f) : nameTokDef(nullptr), valueToken(nullptr), endToken(nullptr), files(f), tokenListDefine(f), variadic(false), variadicOpt(false), optExpandValue(nullptr), optNoExpandValue(nullptr), valueDefinedInCode_(false) {} + explicit Macro(std::vector &f) : nameTokDef(nullptr), valueToken(nullptr), endToken(nullptr), files(f), tokenListDefine(f), variadic(false), variadicOpt(false), valueDefinedInCode_(false) {} Macro(const Token *tok, std::vector &f) : nameTokDef(nullptr), files(f), tokenListDefine(f), valueDefinedInCode_(true) { if (sameline(tok->previousSkipComments(), tok)) diff --git a/simplecpp.h b/simplecpp.h index a23e4cb4..cbea068b 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -76,7 +76,7 @@ namespace simplecpp { */ class SIMPLECPP_LIB Location { public: - explicit Location(const std::vector &f) : files(f), fileIndex(0), line(1U), col(0U) {} + explicit Location(const std::vector &f) : files(f) {} Location(const Location &loc) = default; @@ -109,9 +109,9 @@ namespace simplecpp { } const std::vector &files; - unsigned int fileIndex; - unsigned int line; - unsigned int col; + unsigned int fileIndex{}; + unsigned int line{1}; + unsigned int col{}; private: static const std::string emptyFileName; }; @@ -123,12 +123,12 @@ namespace simplecpp { class SIMPLECPP_LIB Token { public: Token(const TokenString &s, const Location &loc, bool wsahead = false) : - whitespaceahead(wsahead), location(loc), previous(nullptr), next(nullptr), nextcond(nullptr), string(s) { + whitespaceahead(wsahead), location(loc), string(s) { flags(); } Token(const Token &tok) : - macro(tok.macro), op(tok.op), comment(tok.comment), name(tok.name), number(tok.number), whitespaceahead(tok.whitespaceahead), location(tok.location), previous(nullptr), next(nullptr), nextcond(nullptr), string(tok.string), mExpandedFrom(tok.mExpandedFrom) {} + macro(tok.macro), op(tok.op), comment(tok.comment), name(tok.name), number(tok.number), whitespaceahead(tok.whitespaceahead), location(tok.location), string(tok.string), mExpandedFrom(tok.mExpandedFrom) {} const TokenString& str() const { return string; @@ -153,9 +153,9 @@ namespace simplecpp { bool number; bool whitespaceahead; Location location; - Token *previous; - Token *next; - mutable const Token *nextcond; + Token *previous{}; + Token *next{}; + mutable const Token *nextcond{}; const Token *previousSkipComments() const { const Token *tok = this->previous; @@ -393,14 +393,14 @@ namespace simplecpp { * On the command line these are configured by -D, -U, -I, --include, -std */ struct SIMPLECPP_LIB DUI { - DUI() : clearIncludeCache(false), removeComments(false) {} + DUI() = default; std::list defines; std::set undefined; std::list includePaths; std::list includes; std::string std; - bool clearIncludeCache; - bool removeComments; /** remove comment tokens from included files */ + bool clearIncludeCache{}; + bool removeComments{}; /** remove comment tokens from included files */ }; struct SIMPLECPP_LIB FileData { From cb9a9a21cc1f9c1c14a5065fe98fcdf42d7b7319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 12 Oct 2025 14:21:28 +0200 Subject: [PATCH 047/104] enabled and fixed `modernize-use-equals-delete` clang-tidy warnings (#565) --- .clang-tidy | 1 - simplecpp.cpp | 6 +++--- simplecpp.h | 5 ++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index b7c39f2b..182f1a55 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -31,7 +31,6 @@ Checks: > -modernize-pass-by-value, -modernize-return-braced-init-list, -modernize-use-auto, - -modernize-use-equals-delete, -modernize-use-nodiscard, -modernize-use-trailing-return-type, -readability-avoid-nested-conditional-operator, diff --git a/simplecpp.cpp b/simplecpp.cpp index d5b51080..7774fd29 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -427,6 +427,9 @@ class FileStream : public simplecpp::TokenList::Stream { init(); } + FileStream(const FileStream&) = delete; + FileStream &operator=(const FileStream&) = delete; + ~FileStream() override { fclose(file); file = nullptr; @@ -459,9 +462,6 @@ class FileStream : public simplecpp::TokenList::Stream { ungetc(ch, file); } - FileStream(const FileStream&); - FileStream &operator=(const FileStream&); - FILE *file; int lastCh{}; int lastStatus{}; diff --git a/simplecpp.h b/simplecpp.h index cbea068b..7e556657 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -130,6 +130,8 @@ namespace simplecpp { Token(const Token &tok) : macro(tok.macro), op(tok.op), comment(tok.comment), name(tok.name), number(tok.number), whitespaceahead(tok.whitespaceahead), location(tok.location), string(tok.string), mExpandedFrom(tok.mExpandedFrom) {} + Token &operator=(const Token &tok) = delete; + const TokenString& str() const { return string; } @@ -195,9 +197,6 @@ namespace simplecpp { TokenString string; std::set mExpandedFrom; - - // Not implemented - prevent assignment - Token &operator=(const Token &tok); }; /** Output from preprocessor */ From 318a37b553b4ccafeb6c84db1086c4f8a0756ad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sat, 18 Oct 2025 16:18:49 +0200 Subject: [PATCH 048/104] optimized handling of some preprocessor directives (#502) --- simplecpp.cpp | 108 ++++++++++++++++++++++++-------------------------- simplecpp.h | 3 +- 2 files changed, 53 insertions(+), 58 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 7774fd29..8f1879ef 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -697,33 +697,55 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (oldLastToken != cback()) { oldLastToken = cback(); - if (!isLastLinePreprocessor()) + const Token * const llTok = isLastLinePreprocessor(); + if (!llTok) continue; - const std::string lastline(lastLine()); - if (lastline == "# file %str%") { - const Token *strtok = cback(); - while (strtok->comment) - strtok = strtok->previous; - loc.push(location); - location.fileIndex = fileIndex(strtok->str().substr(1U, strtok->str().size() - 2U)); - location.line = 1U; - } else if (lastline == "# line %num%") { - const Token *numtok = cback(); - while (numtok->comment) - numtok = numtok->previous; - lineDirective(location.fileIndex, std::atol(numtok->str().c_str()), &location); - } else if (lastline == "# %num% %str%" || lastline == "# line %num% %str%") { - const Token *strtok = cback(); - while (strtok->comment) - strtok = strtok->previous; - const Token *numtok = strtok->previous; - while (numtok->comment) - numtok = numtok->previous; - lineDirective(fileIndex(replaceAll(strtok->str().substr(1U, strtok->str().size() - 2U),"\\\\","\\")), - std::atol(numtok->str().c_str()), &location); + const Token * const llNextToken = llTok->next; + if (!llTok->next) + continue; + if (llNextToken->next) { + // #file "file.c" + if (llNextToken->str() == "file" && + llNextToken->next->str()[0] == '\"') + { + const Token *strtok = cback(); + while (strtok->comment) + strtok = strtok->previous; + loc.push(location); + location.fileIndex = fileIndex(strtok->str().substr(1U, strtok->str().size() - 2U)); + location.line = 1U; + } + // #3 "file.c" + // #line 3 "file.c" + else if ((llNextToken->number && + llNextToken->next->str()[0] == '\"') || + (llNextToken->str() == "line" && + llNextToken->next->number && + llNextToken->next->next && + llNextToken->next->next->str()[0] == '\"')) + { + const Token *strtok = cback(); + while (strtok->comment) + strtok = strtok->previous; + const Token *numtok = strtok->previous; + while (numtok->comment) + numtok = numtok->previous; + lineDirective(fileIndex(replaceAll(strtok->str().substr(1U, strtok->str().size() - 2U),"\\\\","\\")), + std::atol(numtok->str().c_str()), &location); + } + // #line 3 + else if (llNextToken->str() == "line" && + llNextToken->next->number) + { + const Token *numtok = cback(); + while (numtok->comment) + numtok = numtok->previous; + lineDirective(location.fileIndex, std::atol(numtok->str().c_str()), &location); + } } // #endfile - else if (lastline == "# endfile" && !loc.empty()) { + else if (llNextToken->str() == "endfile" && !loc.empty()) + { location = loc.top(); loc.pop(); } @@ -740,8 +762,8 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, TokenString currentToken; if (cback() && cback()->location.line == location.line && cback()->previous && cback()->previous->op == '#') { - const Token* const llTok = lastLineTok(); - if (llTok && llTok->op == '#' && llTok->next && (llTok->next->str() == "error" || llTok->next->str() == "warning")) { + const Token* const ppTok = cback()->previous; + if (ppTok->next && (ppTok->next->str() == "error" || ppTok->next->str() == "warning")) { char prev = ' '; while (stream.good() && (prev == '\\' || (ch != '\r' && ch != '\n'))) { currentToken += ch; @@ -1418,34 +1440,6 @@ std::string simplecpp::TokenList::readUntil(Stream &stream, const Location &loca return ret; } -std::string simplecpp::TokenList::lastLine(int maxsize) const -{ - std::string ret; - int count = 0; - for (const Token *tok = cback(); ; tok = tok->previous) { - if (!sameline(tok, cback())) { - break; - } - if (tok->comment) - continue; - if (++count > maxsize) - return ""; - if (!ret.empty()) - ret += ' '; - // add tokens in reverse for performance reasons - if (tok->str()[0] == '\"') - ret += "%rts%"; // %str% - else if (tok->number) - ret += "%mun%"; // %num% - else { - ret += tok->str(); - std::reverse(ret.end() - tok->str().length(), ret.end()); - } - } - std::reverse(ret.begin(), ret.end()); - return ret; -} - const simplecpp::Token* simplecpp::TokenList::lastLineTok(int maxsize) const { const Token* prevTok = nullptr; @@ -1462,10 +1456,12 @@ const simplecpp::Token* simplecpp::TokenList::lastLineTok(int maxsize) const return prevTok; } -bool simplecpp::TokenList::isLastLinePreprocessor(int maxsize) const +const simplecpp::Token* simplecpp::TokenList::isLastLinePreprocessor(int maxsize) const { const Token * const prevTok = lastLineTok(maxsize); - return prevTok && prevTok->op == '#'; + if (prevTok && prevTok->op == '#') + return prevTok; + return nullptr; } unsigned int simplecpp::TokenList::fileIndex(const std::string &filename) diff --git a/simplecpp.h b/simplecpp.h index 7e556657..e164fa90 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -359,9 +359,8 @@ namespace simplecpp { std::string readUntil(Stream &stream, const Location &location, char start, char end, OutputList *outputList); void lineDirective(unsigned int fileIndex, unsigned int line, Location *location); - std::string lastLine(int maxsize=1000) const; const Token* lastLineTok(int maxsize=1000) const; - bool isLastLinePreprocessor(int maxsize=1000) const; + const Token* isLastLinePreprocessor(int maxsize=1000) const; unsigned int fileIndex(const std::string &filename); From a766d223ca5a9e38cbc4844ef94813535663ca15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sat, 18 Oct 2025 16:19:04 +0200 Subject: [PATCH 049/104] enabled and cleaned up some compiler warning flags (#551) --- CMakeLists.txt | 36 ++++++++++++++++++++---------------- Makefile | 3 ++- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index efdc0d96..f13fb3fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,49 +19,50 @@ function(add_compile_options_safe FLAG) endif() endfunction() -if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") add_compile_options(-pedantic) + add_compile_options(-Wall) add_compile_options(-Wextra) add_compile_options(-Wcast-qual) # Cast for removing type qualifiers add_compile_options(-Wfloat-equal) # Floating values used in equality comparisons add_compile_options(-Wmissing-declarations) # If a global function is defined without a previous declaration add_compile_options(-Wmissing-format-attribute) # - add_compile_options(-Wno-long-long) add_compile_options(-Wpacked) # add_compile_options(-Wredundant-decls) # if anything is declared more than once in the same scope add_compile_options(-Wundef) - add_compile_options(-Wno-missing-braces) - add_compile_options(-Wno-sign-compare) - add_compile_options(-Wno-multichar) add_compile_options(-Woverloaded-virtual) # when a function declaration hides virtual functions from a base class add_compile_options(-Wsuggest-attribute=noreturn) add_compile_options_safe(-Wuseless-cast) -elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + + # we are not interested in these + set_source_files_properties(test.cpp PROPERTIES COMPILE_FLAGS -Wno-multichar) +elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") add_compile_definitions(_CRT_SECURE_NO_WARNINGS) - # TODO: bump warning level - #add_compile_options(/W4) # Warning Level - # TODO: enable warning + + add_compile_options(/W4) # Warning Level + + add_compile_options(/wd4127) # warning C4127: conditional expression is constant + add_compile_options(/wd4244) # warning C4244: 'x': conversion from 'int' to 'char', possible loss of data add_compile_options(/wd4267) # warning C4267: '...': conversion from 'size_t' to 'unsigned int', possible loss of data + add_compile_options(/wd4706) # warning C4706: assignment within conditional expression elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Weverything) + # no need for c++98 compatibility add_compile_options(-Wno-c++98-compat-pedantic) - # these are not really fixable + + # these are not really fixable until newer standards add_compile_options(-Wno-exit-time-destructors) add_compile_options(-Wno-global-constructors) add_compile_options(-Wno-weak-vtables) add_compile_options_safe(-Wno-unsafe-buffer-usage) add_compile_options_safe(-Wno-nrvo) - # we are not interested in these - add_compile_options(-Wno-multichar) - add_compile_options(-Wno-four-char-constants) - # ignore C++11-specific warning - add_compile_options(-Wno-suggest-override) - add_compile_options(-Wno-suggest-destructor-override) + # contradicts -Wcovered-switch-default add_compile_options(-Wno-switch-default) + # TODO: fix these? add_compile_options(-Wno-padded) add_compile_options(-Wno-sign-conversion) @@ -69,6 +70,9 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wno-shorten-64-to-32) add_compile_options(-Wno-shadow-field-in-constructor) + # we are not interested in these + set_source_files_properties(test.cpp PROPERTIES COMPILE_FLAGS "-Wno-multichar -Wno-four-char-constants") + if (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 14 OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 14) # TODO: verify this regression still exists in clang-15 if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") diff --git a/Makefile b/Makefile index b6bc26da..b7b54597 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ all: testrunner simplecpp CPPFLAGS ?= -CXXFLAGS = -Wall -Wextra -pedantic -Wcast-qual -Wfloat-equal -Wmissing-declarations -Wmissing-format-attribute -Wredundant-decls -Wundef -Wno-multichar -Wold-style-cast -std=c++11 -g $(CXXOPTS) +CXXFLAGS = -Wall -Wextra -pedantic -Wcast-qual -Wfloat-equal -Wmissing-declarations -Wmissing-format-attribute -Wpacked -Wredundant-decls -Wundef -Woverloaded-virtual -std=c++11 -g $(CXXOPTS) LDFLAGS = -g $(LDOPTS) # Define test source dir macro for compilation (preprocessor flags) @@ -9,6 +9,7 @@ TEST_CPPFLAGS = -DSIMPLECPP_TEST_SOURCE_DIR=\"$(CURDIR)\" # Only test.o gets the define test.o: CPPFLAGS += $(TEST_CPPFLAGS) +test.o: CXXFLAGS += -Wno-multichar %.o: %.cpp simplecpp.h $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< From 8e85885e62ff43ed51338c1c1e8a4dbb690b96a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Mon, 20 Oct 2025 17:21:01 +0200 Subject: [PATCH 050/104] refs #424 - removed filelist parameter from `simplecpp::Output` constructors / cleanups (#569) --- simplecpp.cpp | 220 +++++++++++++++++++++++++++----------------------- simplecpp.h | 3 +- 2 files changed, 122 insertions(+), 101 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 8f1879ef..d56a6ce8 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -422,7 +422,7 @@ class FileStream : public simplecpp::TokenList::Stream { { if (!file) { files.push_back(filename); - throw simplecpp::Output(files, simplecpp::Output::FILE_NOT_FOUND, "File is missing: " + filename); + throw simplecpp::Output(simplecpp::Output::FILE_NOT_FOUND, simplecpp::Location(files), "File is missing: " + filename); } init(); } @@ -615,14 +615,15 @@ static std::string escapeString(const std::string &str) return ostr.str(); } -static void portabilityBackslash(simplecpp::OutputList *outputList, const std::vector &files, const simplecpp::Location &location) +static void portabilityBackslash(simplecpp::OutputList *outputList, const simplecpp::Location &location) { if (!outputList) return; - simplecpp::Output err(files); - err.type = simplecpp::Output::PORTABILITY_BACKSLASH; - err.location = location; - err.msg = "Combination 'backslash space newline' is not portable."; + simplecpp::Output err = { + simplecpp::Output::PORTABILITY_BACKSLASH, + location, + "Combination 'backslash space newline' is not portable." + }; outputList->push_back(std::move(err)); } @@ -670,13 +671,12 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (ch >= 0x80) { if (outputList) { - simplecpp::Output err(files); - err.type = simplecpp::Output::UNHANDLED_CHAR_ERROR; - err.location = location; - std::ostringstream s; - s << static_cast(ch); - err.msg = "The code contains unhandled character(s) (character code=" + s.str() + "). Neither unicode nor extended ascii is supported."; - outputList->push_back(err); + simplecpp::Output err = { + simplecpp::Output::UNHANDLED_CHAR_ERROR, + location, + "The code contains unhandled character(s) (character code=" + std::to_string(static_cast(ch)) + "). Neither unicode nor extended ascii is supported." + }; + outputList->push_back(std::move(err)); } clear(); return; @@ -685,7 +685,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (ch == '\n') { if (cback() && cback()->op == '\\') { if (location.col > cback()->location.col + 1U) - portabilityBackslash(outputList, files, cback()->location); + portabilityBackslash(outputList, cback()->location); ++multiline; deleteToken(back()); } else { @@ -812,7 +812,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, const TokenString check_portability = currentToken + tmp; const std::string::size_type pos = check_portability.find_last_not_of(" \t"); if (pos < check_portability.size() - 1U && check_portability[pos] == '\\') - portabilityBackslash(outputList, files, location); + portabilityBackslash(outputList, location); ++multiline; tmp_ch = stream.readChar(); currentToken += '\n'; @@ -872,11 +872,12 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, } if (!stream.good() || ch == '\n') { if (outputList) { - Output err(files); - err.type = Output::SYNTAX_ERROR; - err.location = location; - err.msg = "Invalid newline in raw string delimiter."; - outputList->push_back(err); + Output err = { + Output::SYNTAX_ERROR, + location, + "Invalid newline in raw string delimiter." + }; + outputList->push_back(std::move(err)); } return; } @@ -885,10 +886,11 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, currentToken += stream.readChar(); if (!endsWith(currentToken, endOfRawString)) { if (outputList) { - Output err(files); - err.type = Output::SYNTAX_ERROR; - err.location = location; - err.msg = "Raw string missing terminating delimiter."; + Output err = { + Output::SYNTAX_ERROR, + location, + "Raw string missing terminating delimiter." + }; outputList->push_back(std::move(err)); } return; @@ -1428,10 +1430,11 @@ std::string simplecpp::TokenList::readUntil(Stream &stream, const Location &loca if (!stream.good() || ch != end) { clear(); if (outputList) { - Output err(files); - err.type = Output::SYNTAX_ERROR; - err.location = location; - err.msg = std::string("No pair for character (") + start + "). Can't process file. File is either invalid or unicode, which is currently not supported."; + Output err = { + Output::SYNTAX_ERROR, + location, + std::string("No pair for character (") + start + "). Can't process file. File is either invalid or unicode, which is currently not supported." + }; outputList->push_back(std::move(err)); } return ""; @@ -3160,10 +3163,11 @@ simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, if (filedata == nullptr) { if (outputList) { - simplecpp::Output err(filenames); - err.type = simplecpp::Output::EXPLICIT_INCLUDE_NOT_FOUND; - err.location = Location(filenames); - err.msg = "Can not open include file '" + filename + "' that is explicitly included."; + simplecpp::Output err = { + simplecpp::Output::EXPLICIT_INCLUDE_NOT_FOUND, + Location(filenames), + "Can not open include file '" + filename + "' that is explicitly included." + }; outputList->push_back(std::move(err)); } continue; @@ -3231,12 +3235,13 @@ static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token simplecpp::TokenList value(files); try { *tok1 = it->second.expand(value, tok, macros, files); - } catch (simplecpp::Macro::Error &err) { + } catch (const simplecpp::Macro::Error &err) { if (outputList) { - simplecpp::Output out(files); - out.type = simplecpp::Output::SYNTAX_ERROR; - out.location = err.location; - out.msg = "failed to expand \'" + tok->str() + "\', " + err.what; + simplecpp::Output out = { + simplecpp::Output::SYNTAX_ERROR, + err.location, + "failed to expand \'" + tok->str() + "\', " + err.what + }; outputList->push_back(std::move(out)); } return false; @@ -3348,10 +3353,12 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const cppstd_t cpp_std = simplecpp::getCppStd(dui.std); if (cpp_std == CPPUnknown) { if (outputList) { - simplecpp::Output err(files); - err.type = Output::DUI_ERROR; - err.msg = "unknown standard specified: '" + dui.std + "'"; - outputList->push_back(err); + simplecpp::Output err = { + Output::DUI_ERROR, + Location(files), + "unknown standard specified: '" + dui.std + "'" + }; + outputList->push_back(std::move(err)); } output.clear(); return; @@ -3403,11 +3410,12 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL if (ifstates.size() <= 1U && (rawtok->str() == ELIF || rawtok->str() == ELSE || rawtok->str() == ENDIF)) { if (outputList) { - simplecpp::Output err(files); - err.type = Output::SYNTAX_ERROR; - err.location = rawtok->location; - err.msg = "#" + rawtok->str() + " without #if"; - outputList->push_back(err); + simplecpp::Output err = { + Output::SYNTAX_ERROR, + rawtok->location, + "#" + rawtok->str() + " without #if" + }; + outputList->push_back(std::move(err)); } output.clear(); return; @@ -3415,15 +3423,19 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL if (ifstates.top() == True && (rawtok->str() == ERROR || rawtok->str() == WARNING)) { if (outputList) { - simplecpp::Output err(rawtok->location.files); - err.type = rawtok->str() == ERROR ? Output::ERROR : Output::WARNING; - err.location = rawtok->location; + std::string msg; for (const Token *tok = rawtok->next; tok && sameline(rawtok,tok); tok = tok->next) { - if (!err.msg.empty() && isNameChar(tok->str()[0])) - err.msg += ' '; - err.msg += tok->str(); + if (!msg.empty() && isNameChar(tok->str()[0])) + msg += ' '; + msg += tok->str(); } - err.msg = '#' + rawtok->str() + ' ' + err.msg; + msg = '#' + rawtok->str() + ' ' + msg; + simplecpp::Output err = { + rawtok->str() == ERROR ? Output::ERROR : Output::WARNING, + rawtok->location, + std::move(msg) + }; + outputList->push_back(std::move(err)); } if (rawtok->str() == ERROR) { @@ -3446,21 +3458,23 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } } catch (const std::runtime_error &) { if (outputList) { - simplecpp::Output err(files); - err.type = Output::SYNTAX_ERROR; - err.location = rawtok->location; - err.msg = "Failed to parse #define"; - outputList->push_back(err); + simplecpp::Output err = { + Output::SYNTAX_ERROR, + rawtok->location, + "Failed to parse #define" + }; + outputList->push_back(std::move(err)); } output.clear(); return; - } catch (simplecpp::Macro::Error &err) { + } catch (const simplecpp::Macro::Error &err) { if (outputList) { - simplecpp::Output out(files); - out.type = simplecpp::Output::SYNTAX_ERROR; - out.location = err.location; - out.msg = "Failed to parse #define, " + err.what; - outputList->push_back(out); + simplecpp::Output out = { + simplecpp::Output::SYNTAX_ERROR, + err.location, + "Failed to parse #define, " + err.what + }; + outputList->push_back(std::move(out)); } output.clear(); return; @@ -3496,11 +3510,12 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL if (inc2.empty() || inc2.cfront()->str().size() <= 2U) { if (outputList) { - simplecpp::Output err(files); - err.type = Output::SYNTAX_ERROR; - err.location = rawtok->location; - err.msg = "No header in #include"; - outputList->push_back(err); + simplecpp::Output err = { + Output::SYNTAX_ERROR, + rawtok->location, + "No header in #include" + }; + outputList->push_back(std::move(err)); } output.clear(); return; @@ -3513,19 +3528,21 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const FileData *const filedata = cache.get(rawtok->location.file(), header, dui, systemheader, files, outputList).first; if (filedata == nullptr) { if (outputList) { - simplecpp::Output out(files); - out.type = Output::MISSING_HEADER; - out.location = rawtok->location; - out.msg = "Header not found: " + inctok->str(); - outputList->push_back(out); + simplecpp::Output out = { + simplecpp::Output::MISSING_HEADER, + rawtok->location, + "Header not found: " + inctok->str() + }; + outputList->push_back(std::move(out)); } } else if (includetokenstack.size() >= 400) { if (outputList) { - simplecpp::Output out(files); - out.type = Output::INCLUDE_NESTED_TOO_DEEPLY; - out.location = rawtok->location; - out.msg = "#include nested too deeply"; - outputList->push_back(out); + simplecpp::Output out = { + simplecpp::Output::INCLUDE_NESTED_TOO_DEEPLY, + rawtok->location, + "#include nested too deeply" + }; + outputList->push_back(std::move(out)); } } else if (pragmaOnce.find(filedata->filename) == pragmaOnce.end()) { includetokenstack.push(gotoNextLine(rawtok)); @@ -3535,11 +3552,12 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } else if (rawtok->str() == IF || rawtok->str() == IFDEF || rawtok->str() == IFNDEF || rawtok->str() == ELIF) { if (!sameline(rawtok,rawtok->next)) { if (outputList) { - simplecpp::Output out(files); - out.type = Output::SYNTAX_ERROR; - out.location = rawtok->location; - out.msg = "Syntax error in #" + rawtok->str(); - outputList->push_back(out); + simplecpp::Output out = { + simplecpp::Output::SYNTAX_ERROR, + rawtok->location, + "Syntax error in #" + rawtok->str() + }; + outputList->push_back(std::move(out)); } output.clear(); return; @@ -3580,10 +3598,11 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL tok = tok ? tok->next : nullptr; if (!tok || !sameline(rawtok,tok) || (par && tok->op != ')')) { if (outputList) { - Output out(rawtok->location.files); - out.type = Output::SYNTAX_ERROR; - out.location = rawtok->location; - out.msg = "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition"; + Output out = { + Output::SYNTAX_ERROR, + rawtok->location, + "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition" + }; outputList->push_back(std::move(out)); } output.clear(); @@ -3622,11 +3641,12 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL tok = tok ? tok->next : nullptr; if (!tok || !sameline(rawtok,tok) || (par && tok->op != ')') || (!closingAngularBracket)) { if (outputList) { - Output out(rawtok->location.files); - out.type = Output::SYNTAX_ERROR; - out.location = rawtok->location; - out.msg = "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition"; - outputList->push_back(out); + Output out = { + Output::SYNTAX_ERROR, + rawtok->location, + "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition" + }; + outputList->push_back(std::move(out)); } output.clear(); return; @@ -3659,13 +3679,15 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } } catch (const std::exception &e) { if (outputList) { - Output out(rawtok->location.files); - out.type = Output::SYNTAX_ERROR; - out.location = rawtok->location; - out.msg = "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition"; + std::string msg = "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition"; if (e.what() && *e.what()) - out.msg += std::string(", ") + e.what(); - outputList->push_back(out); + msg += std::string(", ") + e.what(); + Output out = { + Output::SYNTAX_ERROR, + rawtok->location, + std::move(msg) + }; + outputList->push_back(std::move(out)); } output.clear(); return; diff --git a/simplecpp.h b/simplecpp.h index e164fa90..b1b25fad 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -201,7 +201,6 @@ namespace simplecpp { /** Output from preprocessor */ struct SIMPLECPP_LIB Output { - explicit Output(const std::vector &files) : type(ERROR), location(files) {} enum Type : std::uint8_t { ERROR, /* #error */ WARNING, /* #warning */ @@ -214,7 +213,7 @@ namespace simplecpp { FILE_NOT_FOUND, DUI_ERROR } type; - explicit Output(const std::vector& files, Type type, const std::string& msg) : type(type), location(files), msg(msg) {} + Output(Type type, const Location& loc, std::string msg) : type(type), location(loc), msg(std::move(msg)) {} Location location; std::string msg; }; From bd154957a525c71ad29362f22aecc67d96682b4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 28 Oct 2025 12:08:34 +0100 Subject: [PATCH 051/104] enabled and fixed `readability-simplify-boolean-expr` clang-tidy warnings (#568) --- .clang-tidy | 1 - simplecpp.cpp | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 182f1a55..8dc64681 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -42,7 +42,6 @@ Checks: > -readability-isolate-declaration, -readability-magic-numbers, -readability-redundant-inline-specifier, - -readability-simplify-boolean-expr, -readability-use-concise-preprocessor-directives, -readability-uppercase-literal-suffix, -performance-avoid-endl, diff --git a/simplecpp.cpp b/simplecpp.cpp index d56a6ce8..1e18d2ef 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -882,7 +882,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, return; } const std::string endOfRawString(')' + delim + currentToken); - while (stream.good() && !(endsWith(currentToken, endOfRawString) && currentToken.size() > 1)) + while (stream.good() && (!endsWith(currentToken, endOfRawString) || currentToken.size() <= 1)) currentToken += stream.readChar(); if (!endsWith(currentToken, endOfRawString)) { if (outputList) { @@ -2052,7 +2052,7 @@ namespace simplecpp { } const Token *recursiveExpandToken(TokenList &output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { - if (!(temp.cback() && temp.cback()->name && tok->next && tok->next->op == '(')) { + if (!temp.cback() || !temp.cback()->name || !tok->next || tok->next->op != '(') { output.takeTokens(temp); return tok->next; } From e78af461426b3cae40b1cc79a4a5e540b057b613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 28 Oct 2025 20:36:01 +0100 Subject: [PATCH 052/104] cleaned up includes based on `include-what-you-use` (#576) --- simplecpp.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simplecpp.h b/simplecpp.h index b1b25fad..ee52ad43 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #if __cplusplus >= 202002L # include @@ -41,7 +42,7 @@ #endif #ifndef _WIN32 -# include +# include #endif #if defined(_MSC_VER) @@ -69,7 +70,6 @@ namespace simplecpp { using TokenString = std::string; class Macro; - class FileDataCache; /** * Location in source code From 4aa40060b86e3103825a00b95a4c383e34a148a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 28 Oct 2025 20:36:15 +0100 Subject: [PATCH 053/104] enabled and fixed `modernize-use-auto` clang-tidy warnings (#574) --- .clang-tidy | 1 - simplecpp.cpp | 24 ++++++++++++------------ simplecpp.h | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 8dc64681..b63cb3d9 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -30,7 +30,6 @@ Checks: > -modernize-loop-convert, -modernize-pass-by-value, -modernize-return-braced-init-list, - -modernize-use-auto, -modernize-use-nodiscard, -modernize-use-trailing-return-type, -readability-avoid-nested-conditional-operator, diff --git a/simplecpp.cpp b/simplecpp.cpp index 1e18d2ef..e6186be2 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -253,12 +253,12 @@ class simplecpp::TokenList::Stream { virtual bool good() = 0; unsigned char readChar() { - unsigned char ch = static_cast(get()); + auto ch = static_cast(get()); // For UTF-16 encoded files the BOM is 0xfeff/0xfffe. If the // character is non-ASCII character then replace it with 0xff if (isUtf16) { - const unsigned char ch2 = static_cast(get()); + const auto ch2 = static_cast(get()); const int ch16 = makeUtf16Char(ch, ch2); ch = static_cast(((ch16 >= 0x80) ? 0xff : ch16)); } @@ -281,13 +281,13 @@ class simplecpp::TokenList::Stream { } unsigned char peekChar() { - unsigned char ch = static_cast(peek()); + auto ch = static_cast(peek()); // For UTF-16 encoded files the BOM is 0xfeff/0xfffe. If the // character is non-ASCII character then replace it with 0xff if (isUtf16) { (void)get(); - const unsigned char ch2 = static_cast(peek()); + const auto ch2 = static_cast(peek()); unget(); const int ch16 = makeUtf16Char(ch, ch2); ch = static_cast(((ch16 >= 0x80) ? 0xff : ch16)); @@ -1705,7 +1705,7 @@ namespace simplecpp { private: /** Create new token where Token::macro is set for replaced tokens */ Token *newMacroToken(const TokenString &str, const Location &loc, bool replaced, const Token *expandedFromToken=nullptr) const { - Token *tok = new Token(str,loc); + auto *tok = new Token(str,loc); if (replaced) tok->macro = nameTokDef->str(); if (expandedFromToken) @@ -2365,11 +2365,11 @@ namespace simplecpp { static bool isReplaced(const std::set &expandedmacros) { // return true if size > 1 - std::set::const_iterator it = expandedmacros.begin(); - if (it == expandedmacros.end()) + auto it = expandedmacros.cbegin(); + if (it == expandedmacros.cend()) return false; ++it; - return (it != expandedmacros.end()); + return (it != expandedmacros.cend()); } /** name token in definition */ @@ -3060,7 +3060,7 @@ std::pair simplecpp::FileDataCache::tryload(FileDat return {id_it->second, false}; } - FileData *const data = new FileData {path, TokenList(path, filenames, outputList)}; + auto *const data = new FileData {path, TokenList(path, filenames, outputList)}; if (dui.removeComments) data->tokens.removeComments(); @@ -3154,7 +3154,7 @@ simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, std::list filelist; // -include files - for (std::list::const_iterator it = dui.includes.begin(); it != dui.includes.end(); ++it) { + for (auto it = dui.includes.cbegin(); it != dui.includes.cend(); ++it) { const std::string &filename = *it; const auto loadResult = cache.get("", filename, dui, false, filenames, outputList); @@ -3316,7 +3316,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const bool hasInclude = isCpp17OrLater(dui) || isGnu(dui); MacroMap macros; bool strictAnsiDefined = false; - for (std::list::const_iterator it = dui.defines.begin(); it != dui.defines.end(); ++it) { + for (auto it = dui.defines.cbegin(); it != dui.defines.cend(); ++it) { const std::string ¯ostr = *it; const std::string::size_type eq = macrostr.find('='); const std::string::size_type par = macrostr.find('('); @@ -3382,7 +3382,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL std::set pragmaOnce; includetokenstack.push(rawtokens.cfront()); - for (std::list::const_iterator it = dui.includes.begin(); it != dui.includes.end(); ++it) { + for (auto it = dui.includes.cbegin(); it != dui.includes.cend(); ++it) { const FileData *const filedata = cache.get("", *it, dui, false, files, outputList).first; if (filedata != nullptr && filedata->tokens.cfront() != nullptr) includetokenstack.push(filedata->tokens.cfront()); diff --git a/simplecpp.h b/simplecpp.h index ee52ad43..22f3c19f 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -423,7 +423,7 @@ namespace simplecpp { void insert(FileData data) { // NOLINTNEXTLINE(misc-const-correctness) - FP - FileData *const newdata = new FileData(std::move(data)); + auto *const newdata = new FileData(std::move(data)); mData.emplace_back(newdata); mNameMap.emplace(newdata->filename, newdata); From a8fcf9bb5eb26dfcd1a830f0ac0502d46cd42604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 28 Oct 2025 20:36:27 +0100 Subject: [PATCH 054/104] moved static array into `simplifyName()` and simplified it (#575) --- simplecpp.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index e6186be2..4e892e03 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2666,12 +2666,11 @@ static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI } } -static const char * const altopData[] = {"and","or","bitand","bitor","compl","not","not_eq","xor"}; -static const std::set altop(&altopData[0], &altopData[8]); static void simplifyName(simplecpp::TokenList &expr) { for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) { if (tok->name) { + static const std::set altop = {"and","or","bitand","bitor","compl","not","not_eq","xor"}; if (altop.find(tok->str()) != altop.end()) { bool alt; if (tok->str() == "not" || tok->str() == "compl") { From 6ddb8c6a37a304069527053972269eb7081bee32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 28 Oct 2025 20:37:05 +0100 Subject: [PATCH 055/104] improved testing of `#line` handling (#505) --- simplecpp.cpp | 1 + test.cpp | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/simplecpp.cpp b/simplecpp.cpp index 4e892e03..7d306fd7 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -715,6 +715,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, location.fileIndex = fileIndex(strtok->str().substr(1U, strtok->str().size() - 2U)); location.line = 1U; } + // TODO: add support for "# 3" // #3 "file.c" // #line 3 "file.c" else if ((llNextToken->number && diff --git a/test.cpp b/test.cpp index 26e3b95b..5f09c17f 100644 --- a/test.cpp +++ b/test.cpp @@ -2061,6 +2061,77 @@ static void location5() "int x ;", preprocess(code)); } +static void location6() +{ + const char code[] = + "#line 3\n" + "__LINE__ __FILE__\n"; + ASSERT_EQUALS("\n" + "\n" + "3 \"\"", + preprocess(code)); +} + +static void location7() +{ + const char code[] = + "#line 3 \"file.c\"\n" + "__LINE__ __FILE__\n"; + ASSERT_EQUALS("\n" + "#line 3 \"file.c\"\n" + "3 \"file.c\"", + preprocess(code)); +} + +static void location8() +{ + const char code[] = + "# 3\n" + "__LINE__ __FILE__\n"; + ASSERT_EQUALS("\n" + "2 \"\"", // TODO: should say 3 + preprocess(code)); +} + +static void location9() +{ + const char code[] = + "# 3 \"file.c\"\n" + "__LINE__ __FILE__\n"; + ASSERT_EQUALS("\n" + "#line 3 \"file.c\"\n" + "3 \"file.c\"", + preprocess(code)); +} + +static void location10() +{ + const char code[] = + "#line 3\n" + "__LINE__ __FILE__\n"; + ASSERT_EQUALS("\n" + "\n" // TODO: should this have the #line marker? + "3 \"\"", + preprocess(code)); +} + +static void location11() +{ + const char code[] = + "#line 3 \"file.c\"\n" + "__LINE__ __FILE__\n" + "#line 33 \"file2.c\"\n" + "__LINE__ __FILE__\n"; + ASSERT_EQUALS("\n" + "#line 3 \"file.c\"\n" + "3 \"file.c\"\n" + "#line 33 \"file2.c\"\n" + "33 \"file2.c\"", + preprocess(code)); +} + +// TODO: test #file/#endfile + static void missingHeader1() { const char code[] = "#include \"notexist.h\"\n"; @@ -3489,6 +3560,12 @@ int main(int argc, char **argv) TEST_CASE(location3); TEST_CASE(location4); TEST_CASE(location5); + TEST_CASE(location6); + TEST_CASE(location7); + TEST_CASE(location8); + TEST_CASE(location9); + TEST_CASE(location10); + TEST_CASE(location11); TEST_CASE(missingHeader1); TEST_CASE(missingHeader2); From d381ec26441476e5b6618b1cdc0bb118395547a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sat, 1 Nov 2025 14:23:29 +0100 Subject: [PATCH 056/104] CI-unixish.yml: added profile-guided optimization (PGO) to callgrind step (#580) --- .github/workflows/CI-unixish.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index b4089ee1..d6800a82 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -43,11 +43,12 @@ jobs: sudo apt-get update sudo apt-get install valgrind + # llvm contains llvm-profdata - name: Install missing software on ubuntu (clang++) if: contains(matrix.os, 'ubuntu') && matrix.compiler == 'clang++' run: | sudo apt-get update - sudo apt-get install libc++-dev + sudo apt-get install libc++-dev llvm # coreutils contains "nproc" - name: Install missing software on macos @@ -145,15 +146,34 @@ jobs: run: | wget https://github.com/danmar/simplecpp/archive/refs/tags/1.5.1.tar.gz tar xvf 1.5.1.tar.gz + rm -f 1.5.1.tar.gz + make clean - make -j$(nproc) CXXOPTS="-O2 -g3" + make -j$(nproc) CXXOPTS="-O2 -g3" simplecpp VALGRIND_TOOL=callgrind SIMPLECPP_PATH=simplecpp-1.5.1 ./selfcheck.sh >callgrind.log || (cat callgrind.log && false) cat callgrind.log + + # PGO - start + make clean + make -j$(nproc) CXXOPTS="-O2 -g3 -fprofile-generate" LDOPTS="-fprofile-generate" simplecpp + SIMPLECPP_PATH=simplecpp-1.5.1 ./selfcheck.sh >/dev/null + + if compgen -G "default_*.profraw" > /dev/null; then + llvm-profdata merge -output=default.profdata default_*.profraw + fi + + make clean + make -j$(nproc) CXXOPTS="-O2 -g3 -fprofile-use" LDOPTS="-fprofile-use" simplecpp + VALGRIND_TOOL=callgrind SIMPLECPP_PATH=simplecpp-1.5.1 ./selfcheck.sh >callgrind_pgo.log || (cat callgrind_pgo.log && false) + cat callgrind_pgo.log + # PGO - end + for f in callgrind.out.*; do callgrind_annotate --auto=no $f > $f.annotated.log head -50 $f.annotated.log done + rm -rf simplecpp-1.5.1 - uses: actions/upload-artifact@v4 if: matrix.os == 'ubuntu-24.04' From 7a81c1ab935a28b38e21d0338654ee256577d3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 5 Nov 2025 11:49:40 +0100 Subject: [PATCH 057/104] Fix #581 (Failure when header is included twice with different macros defined) (#582) --- integration_test.py | 35 +++++++++++++++++++++++++++++++++++ simplecpp.cpp | 11 +++++------ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/integration_test.py b/integration_test.py index e5497db3..068da776 100644 --- a/integration_test.py +++ b/integration_test.py @@ -446,3 +446,38 @@ def test_incpath_dir(record_property, tmpdir): assert '' == stderr assert "error: could not open include 'inc'\n" == stdout + + +def test_include_header_twice(tmpdir): + """ Issue #581 - Failure when header is included twice with different + macros defined""" + + header_file = tmpdir / 'test.h' + with open(header_file, 'wt') as f: + f.write(f""" + #if defined AAA + #elif defined BBB + # undef BBB + #endif + + #ifdef BBB + # error BBB is defined + #endif + """) + + test_file = os.path.join(tmpdir, 'test.c') + with open(test_file, 'wt') as f: + f.write(f""" + # define Y + # include "test.h" + + # define BBB + # include "test.h" + """) + + args = [test_file] + + _, stdout, stderr = simplecpp(args, cwd=tmpdir) + + assert stderr == '' + diff --git a/simplecpp.cpp b/simplecpp.cpp index 7d306fd7..ef891eb1 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3701,12 +3701,11 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL else ifstates.push(conditionIsTrue ? True : ElseIsTrue); iftokens.push(rawtok); - } else if (ifstates.top() == True) { - ifstates.top() = AlwaysFalse; - iftokens.top()->nextcond = rawtok; - iftokens.top() = rawtok; - } else if (ifstates.top() == ElseIsTrue && conditionIsTrue) { - ifstates.top() = True; + } else { + if (ifstates.top() == True) + ifstates.top() = AlwaysFalse; + else if (ifstates.top() == ElseIsTrue && conditionIsTrue) + ifstates.top() = True; iftokens.top()->nextcond = rawtok; iftokens.top() = rawtok; } From 5cd15b3eaf7389347b018131e8f6e2bf45590b7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 7 Nov 2025 15:06:52 +0100 Subject: [PATCH 058/104] test.cpp: do not unconditionally remove comments (#506) --- test.cpp | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/test.cpp b/test.cpp index 5f09c17f..69e08781 100644 --- a/test.cpp +++ b/test.cpp @@ -107,7 +107,8 @@ static std::string preprocess(const char code[], const simplecpp::DUI &dui, simp std::vector files; simplecpp::FileDataCache cache; simplecpp::TokenList tokens = makeTokenList(code,files); - tokens.removeComments(); + if (dui.removeComments) + tokens.removeComments(); simplecpp::TokenList tokens2(files); simplecpp::preprocess(tokens2, tokens, files, cache, dui, outputList); simplecpp::cleanup(cache); @@ -437,33 +438,36 @@ static void comment() static void comment_multiline() { + simplecpp::DUI dui; + dui.removeComments = true; + const char code[] = "#define ABC {// \\\n" "}\n" "void f() ABC\n"; - ASSERT_EQUALS("\n\nvoid f ( ) {", preprocess(code)); + ASSERT_EQUALS("\n\nvoid f ( ) {", preprocess(code, dui)); const char code1[] = "#define ABC {// \\\r\n" "}\n" "void f() ABC\n"; - ASSERT_EQUALS("\n\nvoid f ( ) {", preprocess(code1)); + ASSERT_EQUALS("\n\nvoid f ( ) {", preprocess(code1, dui)); const char code2[] = "#define A 1// \\\r" "\r" "2\r" "A\r"; - ASSERT_EQUALS("\n\n2\n1", preprocess(code2)); + ASSERT_EQUALS("\n\n2\n1", preprocess(code2, dui)); const char code3[] = "void f() {// \\ \n}\n"; - ASSERT_EQUALS("void f ( ) {", preprocess(code3)); + ASSERT_EQUALS("void f ( ) {", preprocess(code3, dui)); const char code4[] = "void f() {// \\\\\\\t\t\n}\n"; - ASSERT_EQUALS("void f ( ) {", preprocess(code4)); + ASSERT_EQUALS("void f ( ) {", preprocess(code4, dui)); const char code5[] = "void f() {// \\\\\\a\n}\n"; - ASSERT_EQUALS("void f ( ) {\n}", preprocess(code5)); + ASSERT_EQUALS("void f ( ) {\n}", preprocess(code5, dui)); const char code6[] = "void f() {// \\\n\n\n}\n"; - ASSERT_EQUALS("void f ( ) {\n\n\n}", preprocess(code6)); + ASSERT_EQUALS("void f ( ) {\n\n\n}", preprocess(code6, dui)); // #471 ensure there is newline in comment so that line-splicing can be detected by tools ASSERT_EQUALS("// abc\ndef", readfile("// abc\\\ndef")); @@ -570,9 +574,12 @@ static void define6() static void define7() { + simplecpp::DUI dui; + dui.removeComments = true; + const char code[] = "#define A(X) X+1\n" "A(1 /*23*/)"; - ASSERT_EQUALS("\n1 + 1", preprocess(code)); + ASSERT_EQUALS("\n1 + 1", preprocess(code, dui)); } static void define8() // 6.10.3.10 @@ -1591,6 +1598,7 @@ static void has_include_2() " #endif\n" "#endif"; simplecpp::DUI dui; + dui.removeComments = true; // TODO: remove this dui.includePaths.push_back(testSourceDir); ASSERT_EQUALS("\n\nA", preprocess(code, dui)); // we default to latest standard internally dui.std = "c++14"; @@ -2167,7 +2175,9 @@ static void missingHeader4() { const char code[] = "#/**/include <>\n"; simplecpp::OutputList outputList; - ASSERT_EQUALS("", preprocess(code, &outputList)); + simplecpp::DUI dui; + dui.removeComments = true; // TODO: remove this + ASSERT_EQUALS("", preprocess(code, dui, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,No header in #include\n", toString(outputList)); } From 7ac711b48903a658f15e3a28b5b23765f0e49f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sat, 29 Nov 2025 17:56:19 +0100 Subject: [PATCH 059/104] fixed #583 - fall back to `GetFileInformationByHandle()` when `GetFileInformationByHandleEx()` fails in `simplecpp::FileDataCache::getFileId()` (#594) Co-authored-by: willenbr24 --- simplecpp.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index ef891eb1..9ce846d9 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3126,7 +3126,21 @@ bool simplecpp::FileDataCache::getFileId(const std::string &path, FileID &id) if (hFile == INVALID_HANDLE_VALUE) return false; - const BOOL ret = GetFileInformationByHandleEx(hFile, FileIdInfo, &id.fileIdInfo, sizeof(id.fileIdInfo)); + BOOL ret = GetFileInformationByHandleEx(hFile, FileIdInfo, &id.fileIdInfo, sizeof(id.fileIdInfo)); + if (!ret) { + const DWORD err = GetLastError(); + if (err == ERROR_INVALID_PARAMETER || // encountered when using a non-NTFS filesystem e.g. exFAT + err == ERROR_NOT_SUPPORTED) // encountered on Windows Server Core (used as a Docker container) + { + BY_HANDLE_FILE_INFORMATION fileInfo; + ret = GetFileInformationByHandle(hFile, &fileInfo); + if (ret) { + id.fileIdInfo.VolumeSerialNumber = static_cast(fileInfo.dwVolumeSerialNumber); + id.fileIdInfo.FileId.IdentifierHi = static_cast(fileInfo.nFileIndexHigh); + id.fileIdInfo.FileId.IdentifierLo = static_cast(fileInfo.nFileIndexLow); + } + } + } CloseHandle(hFile); From 72ecd546caa0f1d0737ea999d7b989eaa5e44aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sat, 29 Nov 2025 18:47:07 +0100 Subject: [PATCH 060/104] avoid some unchecked pointer dereferences (#593) --- simplecpp.cpp | 24 ++++++++++++------------ simplecpp.h | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 9ce846d9..8e10ca54 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -984,7 +984,7 @@ void simplecpp::TokenList::constFold() constFoldComparison(tok); constFoldBitwise(tok); constFoldLogicalOp(tok); - constFoldQuestionOp(&tok); + constFoldQuestionOp(tok); // If there is no '(' we are done with the constant folding if (tok->op != '(') @@ -1354,11 +1354,11 @@ void simplecpp::TokenList::constFoldLogicalOp(Token *tok) } } -void simplecpp::TokenList::constFoldQuestionOp(Token **tok1) +void simplecpp::TokenList::constFoldQuestionOp(Token *&tok1) { bool gotoTok1 = false; // NOLINTNEXTLINE(misc-const-correctness) - technically correct but used to access non-const data - for (Token *tok = *tok1; tok && tok->op != ')'; tok = gotoTok1 ? *tok1 : tok->next) { + for (Token *tok = tok1; tok && tok->op != ')'; tok = gotoTok1 ? tok1 : tok->next) { gotoTok1 = false; if (tok->str() != "?") continue; @@ -1373,8 +1373,8 @@ void simplecpp::TokenList::constFoldQuestionOp(Token **tok1) Token * const falseTok = trueTok->next->next; if (!falseTok) throw std::runtime_error("invalid expression"); - if (condTok == *tok1) - *tok1 = (condTok->str() != "0" ? trueTok : falseTok); + if (condTok == tok1) + tok1 = (condTok->str() != "0" ? trueTok : falseTok); deleteToken(condTok->next); // ? deleteToken(trueTok->next); // : deleteToken(condTok->str() == "0" ? trueTok : falseTok); @@ -3241,14 +3241,14 @@ simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, return cache; } -static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token **tok1, simplecpp::MacroMap ¯os, std::vector &files, simplecpp::OutputList *outputList) +static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token *&tok1, simplecpp::MacroMap ¯os, std::vector &files, simplecpp::OutputList *outputList) { - const simplecpp::Token * const tok = *tok1; + const simplecpp::Token * const tok = tok1; const simplecpp::MacroMap::const_iterator it = tok->name ? macros.find(tok->str()) : macros.end(); if (it != macros.end()) { simplecpp::TokenList value(files); try { - *tok1 = it->second.expand(value, tok, macros, files); + tok1 = it->second.expand(value, tok, macros, files); } catch (const simplecpp::Macro::Error &err) { if (outputList) { simplecpp::Output out = { @@ -3264,7 +3264,7 @@ static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token } else { if (!tok->comment) output.push_back(new simplecpp::Token(*tok)); - *tok1 = tok->next; + tok1 = tok->next; } return true; } @@ -3502,7 +3502,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL TokenList inc2(files); if (!inc1.empty() && inc1.cfront()->name) { const Token *inctok = inc1.cfront(); - if (!preprocessToken(inc2, &inctok, macros, files, outputList)) { + if (!preprocessToken(inc2, inctok, macros, files, outputList)) { output.clear(); return; } @@ -3671,7 +3671,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL maybeUsedMacros[rawtok->next->str()].push_back(rawtok->next->location); const Token *tmp = tok; - if (!preprocessToken(expr, &tmp, macros, files, outputList)) { + if (!preprocessToken(expr, tmp, macros, files, outputList)) { output.clear(); return; } @@ -3769,7 +3769,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const Location loc(rawtok->location); TokenList tokens(files); - if (!preprocessToken(tokens, &rawtok, macros, files, outputList)) { + if (!preprocessToken(tokens, rawtok, macros, files, outputList)) { output.clear(); return; } diff --git a/simplecpp.h b/simplecpp.h index 22f3c19f..15187063 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -353,7 +353,7 @@ namespace simplecpp { void constFoldComparison(Token *tok); void constFoldBitwise(Token *tok); void constFoldLogicalOp(Token *tok); - void constFoldQuestionOp(Token **tok1); + void constFoldQuestionOp(Token *&tok1); std::string readUntil(Stream &stream, const Location &location, char start, char end, OutputList *outputList); void lineDirective(unsigned int fileIndex, unsigned int line, Location *location); From d764cb250ee3310d79d6ac1ff723ce08fdf0648d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 7 Dec 2025 23:24:43 +0100 Subject: [PATCH 061/104] refs #424 / fixed #589 - do not store reference to filelist in `simplecpp::Location` / added `simplecpp::TokenList::file()` / cleanups (#573) --- integration_test.py | 22 +++++++++++++++++++ main.cpp | 2 +- simplecpp.cpp | 53 +++++++++++++++++++++++---------------------- simplecpp.h | 33 ++++++++++------------------ test.cpp | 2 +- 5 files changed, 63 insertions(+), 49 deletions(-) diff --git a/integration_test.py b/integration_test.py index 068da776..3ca2fd02 100644 --- a/integration_test.py +++ b/integration_test.py @@ -481,3 +481,25 @@ def test_include_header_twice(tmpdir): assert stderr == '' + +def test_define(record_property, tmpdir): # #589 + test_file = os.path.join(tmpdir, "test.cpp") + with open(test_file, 'w') as f: + f.write( +"""#define PREFIX_WITH_MACRO(test_name) Macro##test_name + +TEST_P(PREFIX_WITH_MACRO(NamingTest), n) {} +""") + + args = [ + '-DTEST_P(A,B)=void __ ## A ## _ ## B ( )', + 'test.cpp' + ] + + exitcode, stdout, stderr = simplecpp(args, cwd=tmpdir) + record_property("stdout", stdout) + record_property("stderr", stderr) + + assert exitcode == 0 + assert stderr == "test.cpp:1: syntax error: failed to expand 'TEST_P', Invalid ## usage when expanding 'TEST_P': Unexpected token ')'\n" + assert stdout == '\n' \ No newline at end of file diff --git a/main.cpp b/main.cpp index fe8ea292..62ccc022 100644 --- a/main.cpp +++ b/main.cpp @@ -207,7 +207,7 @@ int main(int argc, char **argv) std::cout << outputTokens.stringify(linenrs) << std::endl; for (const simplecpp::Output &output : outputList) { - std::cerr << output.location.file() << ':' << output.location.line << ": "; + std::cerr << outputTokens.file(output.location) << ':' << output.location.line << ": "; switch (output.type) { case simplecpp::Output::ERROR: std::cerr << "#error: "; diff --git a/simplecpp.cpp b/simplecpp.cpp index 8e10ca54..be775a90 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -182,8 +182,6 @@ static std::string replaceAll(std::string s, const std::string& from, const std: return s; } -const std::string simplecpp::Location::emptyFileName; - void simplecpp::Location::adjust(const std::string &str) { if (strpbrk(str.c_str(), "\r\n") == nullptr) { @@ -422,7 +420,7 @@ class FileStream : public simplecpp::TokenList::Stream { { if (!file) { files.push_back(filename); - throw simplecpp::Output(simplecpp::Output::FILE_NOT_FOUND, simplecpp::Location(files), "File is missing: " + filename); + throw simplecpp::Output(simplecpp::Output::FILE_NOT_FOUND, {}, "File is missing: " + filename); } init(); } @@ -564,11 +562,11 @@ void simplecpp::TokenList::dump(bool linenrs) const std::string simplecpp::TokenList::stringify(bool linenrs) const { std::ostringstream ret; - Location loc(files); + Location loc; bool filechg = true; for (const Token *tok = cfront(); tok; tok = tok->next) { if (tok->location.line < loc.line || tok->location.fileIndex != loc.fileIndex) { - ret << "\n#line " << tok->location.line << " \"" << tok->location.file() << "\"\n"; + ret << "\n#line " << tok->location.line << " \"" << file(tok->location) << "\"\n"; loc = tok->location; filechg = true; } @@ -633,16 +631,16 @@ static bool isStringLiteralPrefix(const std::string &str) str == "R" || str == "uR" || str == "UR" || str == "LR" || str == "u8R"; } -void simplecpp::TokenList::lineDirective(unsigned int fileIndex, unsigned int line, Location *location) +void simplecpp::TokenList::lineDirective(unsigned int fileIndex, unsigned int line, Location &location) { - if (fileIndex != location->fileIndex || line >= location->line) { - location->fileIndex = fileIndex; - location->line = line; + if (fileIndex != location.fileIndex || line >= location.line) { + location.fileIndex = fileIndex; + location.line = line; return; } - if (line + 2 >= location->line) { - location->line = line; + if (line + 2 >= location.line) { + location.line = line; while (cback()->op != '#') deleteToken(back()); deleteToken(back()); @@ -660,10 +658,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, const Token *oldLastToken = nullptr; - Location location(files); - location.fileIndex = fileIndex(filename); - location.line = 1U; - location.col = 1U; + Location location(fileIndex(filename), 1, 1); while (stream.good()) { unsigned char ch = stream.readChar(); if (!stream.good()) @@ -732,7 +727,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, while (numtok->comment) numtok = numtok->previous; lineDirective(fileIndex(replaceAll(strtok->str().substr(1U, strtok->str().size() - 2U),"\\\\","\\")), - std::atol(numtok->str().c_str()), &location); + std::atol(numtok->str().c_str()), location); } // #line 3 else if (llNextToken->str() == "line" && @@ -741,7 +736,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, const Token *numtok = cback(); while (numtok->comment) numtok = numtok->previous; - lineDirective(location.fileIndex, std::atol(numtok->str().c_str()), &location); + lineDirective(location.fileIndex, std::atol(numtok->str().c_str()), location); } } // #endfile @@ -1478,6 +1473,12 @@ unsigned int simplecpp::TokenList::fileIndex(const std::string &filename) return files.size() - 1U; } +const std::string& simplecpp::TokenList::file(const Location& loc) const +{ + static const std::string s_emptyFileName; + return loc.fileIndex < files.size() ? files[loc.fileIndex] : s_emptyFileName; +} + namespace simplecpp { class Macro; @@ -1885,7 +1886,7 @@ namespace simplecpp { usageList.push_back(loc); if (nameTokInst->str() == "__FILE__") { - output.push_back(new Token('\"'+loc.file()+'\"', loc)); + output.push_back(new Token('\"'+output.file(loc)+'\"', loc)); return nameTokInst->next; } if (nameTokInst->str() == "__LINE__") { @@ -2637,7 +2638,7 @@ static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI } } - const std::string &sourcefile = tok->location.file(); + const std::string &sourcefile = expr.file(tok->location); const bool systemheader = (tok1 && tok1->op == '<'); std::string header; if (systemheader) { @@ -3179,7 +3180,7 @@ simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, if (outputList) { simplecpp::Output err = { simplecpp::Output::EXPLICIT_INCLUDE_NOT_FOUND, - Location(filenames), + {}, "Can not open include file '" + filename + "' that is explicitly included." }; outputList->push_back(std::move(err)); @@ -3212,7 +3213,7 @@ simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, if (!rawtok || rawtok->str() != INCLUDE) continue; - const std::string &sourcefile = rawtok->location.file(); + const std::string &sourcefile = rawtokens.file(rawtok->location); const Token * const htok = rawtok->nextSkipComments(); if (!sameline(rawtok, htok)) @@ -3369,7 +3370,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL if (outputList) { simplecpp::Output err = { Output::DUI_ERROR, - Location(files), + {}, "unknown standard specified: '" + dui.std + "'" }; outputList->push_back(std::move(err)); @@ -3539,7 +3540,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const bool systemheader = (inctok->str()[0] == '<'); const std::string header(inctok->str().substr(1U, inctok->str().size() - 2U)); - const FileData *const filedata = cache.get(rawtok->location.file(), header, dui, systemheader, files, outputList).first; + const FileData *const filedata = cache.get(rawtokens.file(rawtok->location), header, dui, systemheader, files, outputList).first; if (filedata == nullptr) { if (outputList) { simplecpp::Output out = { @@ -3632,7 +3633,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL tok = tok->next; bool closingAngularBracket = false; if (tok) { - const std::string &sourcefile = rawtok->location.file(); + const std::string &sourcefile = rawtokens.file(rawtok->location); const bool systemheader = (tok && tok->op == '<'); std::string header; @@ -3740,7 +3741,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL macros.erase(tok->str()); } } else if (ifstates.top() == True && rawtok->str() == PRAGMA && rawtok->next && rawtok->next->str() == ONCE && sameline(rawtok,rawtok->next)) { - pragmaOnce.insert(rawtok->location.file()); + pragmaOnce.insert(rawtokens.file(rawtok->location)); } if (ifstates.top() != True && rawtok->nextcond) rawtok = rawtok->nextcond->previous; @@ -3796,7 +3797,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const std::list& temp = maybeUsedMacros[macro.name()]; usage.insert(usage.end(), temp.begin(), temp.end()); for (std::list::const_iterator usageIt = usage.begin(); usageIt != usage.end(); ++usageIt) { - MacroUsage mu(usageIt->files, macro.valueDefinedInCode()); + MacroUsage mu(macro.valueDefinedInCode()); mu.macroName = macro.name(); mu.macroLocation = macro.defineLocation(); mu.useLocation = *usageIt; diff --git a/simplecpp.h b/simplecpp.h index 15187063..42a8ed80 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -74,20 +74,16 @@ namespace simplecpp { /** * Location in source code */ - class SIMPLECPP_LIB Location { - public: - explicit Location(const std::vector &f) : files(f) {} + struct SIMPLECPP_LIB Location { + Location() = default; + Location(unsigned int fileIndex, unsigned int line, unsigned int col) + : fileIndex(fileIndex) + , line(line) + , col(col) + {} Location(const Location &loc) = default; - - Location &operator=(const Location &other) { - if (this != &other) { - fileIndex = other.fileIndex; - line = other.line; - col = other.col; - } - return *this; - } + Location &operator=(const Location &other) = default; /** increment this location by string */ void adjust(const std::string &str); @@ -104,16 +100,9 @@ namespace simplecpp { return fileIndex == other.fileIndex && line == other.line; } - const std::string& file() const { - return fileIndex < files.size() ? files[fileIndex] : emptyFileName; - } - - const std::vector &files; unsigned int fileIndex{}; unsigned int line{1}; unsigned int col{}; - private: - static const std::string emptyFileName; }; /** @@ -341,6 +330,8 @@ namespace simplecpp { return files; } + const std::string& file(const Location& loc) const; + private: TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, OutputList *outputList, int unused); @@ -356,7 +347,7 @@ namespace simplecpp { void constFoldQuestionOp(Token *&tok1); std::string readUntil(Stream &stream, const Location &location, char start, char end, OutputList *outputList); - void lineDirective(unsigned int fileIndex, unsigned int line, Location *location); + void lineDirective(unsigned int fileIndex, unsigned int line, Location &location); const Token* lastLineTok(int maxsize=1000) const; const Token* isLastLinePreprocessor(int maxsize=1000) const; @@ -370,7 +361,7 @@ namespace simplecpp { /** Tracking how macros are used */ struct SIMPLECPP_LIB MacroUsage { - explicit MacroUsage(const std::vector &f, bool macroValueKnown_) : macroLocation(f), useLocation(f), macroValueKnown(macroValueKnown_) {} + explicit MacroUsage(bool macroValueKnown_) : macroValueKnown(macroValueKnown_) {} std::string macroName; Location macroLocation; Location useLocation; diff --git a/test.cpp b/test.cpp index 69e08781..e46e408d 100644 --- a/test.cpp +++ b/test.cpp @@ -3206,7 +3206,7 @@ static void stdValid() static void assertToken(const std::string& s, bool name, bool number, bool comment, char op, int line) { const std::vector f; - const simplecpp::Location l(f); + const simplecpp::Location l; const simplecpp::Token t(s, l); assertEquals(name, t.name, line); assertEquals(number, t.number, line); From f4226e008514685ab49c7d85068ba2b486d434f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 10 Dec 2025 21:39:30 +0100 Subject: [PATCH 062/104] CI-unixish.yml: removed `macos-13` / added `macos-15-intel` and `macos-26` (#602) --- .github/workflows/CI-unixish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index d6800a82..c675d565 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: - os: [ubuntu-22.04, ubuntu-24.04, macos-13, macos-14, macos-15] + os: [ubuntu-22.04, ubuntu-24.04, macos-14, macos-15, macos-15-intel, macos-26] compiler: [clang++] include: - os: ubuntu-22.04 From c7b9f5ab351a40c52624b742455e04d81abd7f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 12 Dec 2025 20:04:52 +0100 Subject: [PATCH 063/104] updated `actions/setup-python` to `v6` (#604) --- .github/workflows/CI-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index ccf208fa..9418d31d 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -33,7 +33,7 @@ jobs: uses: microsoft/setup-msbuild@v2 - name: Set up Python 3.13 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.13' check-latest: true From e177522d05957085339d7bb958552fbc32a0ea8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 12 Dec 2025 20:05:05 +0100 Subject: [PATCH 064/104] updated `actions/checkout` to `v6` (#603) --- .github/workflows/CI-unixish.yml | 2 +- .github/workflows/CI-windows.yml | 2 +- .github/workflows/clang-tidy.yml | 2 +- .github/workflows/format.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index c675d565..4a4cfcdc 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -25,7 +25,7 @@ jobs: CXX: ${{ matrix.compiler }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 9418d31d..06286525 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -25,7 +25,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 4b52d7bc..9109be9e 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 4d5657f8..e9f1361d 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -28,7 +28,7 @@ jobs: UNCRUSTIFY_INSTALL_DIR: ${{ github.workspace }}/runformat-uncrustify steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: persist-credentials: false From a45afce7ca12b28f0451f12b6c67eb653be6eb60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 12 Dec 2025 20:05:19 +0100 Subject: [PATCH 065/104] added `@throws` to documentation / adjusted some catch handlers (#600) --- simplecpp.cpp | 66 +++++++++++++++++++++++++-------------------------- simplecpp.h | 34 ++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 34 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index be775a90..581c9b10 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -414,6 +413,9 @@ class StdCharBufStream : public simplecpp::TokenList::Stream { class FileStream : public simplecpp::TokenList::Stream { public: + /** + * @throws simplecpp::Output thrown if file is not found + */ // cppcheck-suppress uninitDerivedMemberVar - we call Stream::init() to initialize the private members explicit FileStream(const std::string &filename, std::vector &files) : file(fopen(filename.c_str(), "rb")) @@ -487,7 +489,7 @@ simplecpp::TokenList::TokenList(const std::string &filename, std::vectorpush_back(e); } } @@ -1488,6 +1490,9 @@ namespace simplecpp { public: explicit Macro(std::vector &f) : nameTokDef(nullptr), valueToken(nullptr), endToken(nullptr), files(f), tokenListDefine(f), variadic(false), variadicOpt(false), valueDefinedInCode_(false) {} + /** + * @throws std::runtime_error thrown on bad macro syntax + */ Macro(const Token *tok, std::vector &f) : nameTokDef(nullptr), files(f), tokenListDefine(f), valueDefinedInCode_(true) { if (sameline(tok->previousSkipComments(), tok)) throw std::runtime_error("bad macro syntax"); @@ -1504,6 +1509,9 @@ namespace simplecpp { throw std::runtime_error("bad macro syntax"); } + /** + * @throws std::runtime_error thrown on bad macro syntax + */ Macro(const std::string &name, const std::string &value, std::vector &f) : nameTokDef(nullptr), files(f), tokenListDefine(f), valueDefinedInCode_(false) { const std::string def(name + ' ' + value); StdCharBufStream stream(reinterpret_cast(def.data()), def.size()); @@ -1552,7 +1560,9 @@ namespace simplecpp { * @param macros list of macros * @param inputFiles the input files * @return token after macro - * @throw Can throw wrongNumberOfParameters or invalidHashHash + * @throws Error thrown on missing or invalid preprocessor directives + * @throws wrongNumberOfParameters thrown on invalid number of parameters + * @throws invalidHashHash thrown on invalid ## usage */ const Token * expand(TokenList & output, const Token * rawtok, @@ -2544,7 +2554,9 @@ namespace simplecpp { } } -/** Evaluate sizeof(type) */ +/** Evaluate sizeof(type) + * @throws std::runtime_error thrown on missing arguments or invalid expression + */ static void simplifySizeof(simplecpp::TokenList &expr, const std::map &sizeOfType) { for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) { @@ -2612,6 +2624,10 @@ static std::string dirPath(const std::string& path, bool withTrailingSlash=true) } static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader); + +/** Evaluate __has_include(include) + * @throws std::runtime_error thrown on missing arguments or invalid expression + */ static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI &dui) { if (!isCpp17OrLater(dui) && !isGnu(dui)) @@ -2668,6 +2684,9 @@ static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI } } +/** Evaluate name + * @throws std::runtime_error thrown on undefined function-like macro + */ static void simplifyName(simplecpp::TokenList &expr) { for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) { @@ -2696,7 +2715,7 @@ static void simplifyName(simplecpp::TokenList &expr) * unsigned long long value, updating pos to point to the first * unused element of s. * Returns ULLONG_MAX if the result is not representable and - * throws if the above requirements were not possible to satisfy. + * @throws std::runtime_error thrown if the above requirements were not possible to satisfy. */ static unsigned long long stringToULLbounded( const std::string& s, @@ -2716,34 +2735,6 @@ static unsigned long long stringToULLbounded( return value; } -/* Converts character literal (including prefix, but not ud-suffix) - * to long long value. - * - * Assumes ASCII-compatible single-byte encoded str for narrow literals - * and UTF-8 otherwise. - * - * For target assumes - * - execution character set encoding matching str - * - UTF-32 execution wide-character set encoding - * - requirements for __STDC_UTF_16__, __STDC_UTF_32__ and __STDC_ISO_10646__ satisfied - * - char16_t is 16bit wide - * - char32_t is 32bit wide - * - wchar_t is 32bit wide and unsigned - * - matching char signedness to host - * - matching sizeof(int) to host - * - * For host assumes - * - ASCII-compatible execution character set - * - * For host and target assumes - * - CHAR_BIT == 8 - * - two's complement - * - * Implements multi-character narrow literals according to GCC's behavior, - * except multi code unit universal character names are not supported. - * Multi-character wide literals are not supported. - * Limited support of universal character names for non-UTF-8 execution character set encodings. - */ long long simplecpp::characterLiteralToLL(const std::string& str) { // default is wide/utf32 @@ -2937,6 +2928,9 @@ long long simplecpp::characterLiteralToLL(const std::string& str) return multivalue; } +/** + * @throws std::runtime_error thrown on invalid literal + */ static void simplifyNumbers(simplecpp::TokenList &expr) { for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) { @@ -2959,6 +2953,10 @@ static void simplifyComments(simplecpp::TokenList &expr) } } +/** + * @throws std::runtime_error thrown on invalid literals, missing sizeof arguments or invalid expressions, + * missing __has_include() arguments or expressions, undefined function-like macros, invalid number literals + */ static long long evaluate(simplecpp::TokenList &expr, const simplecpp::DUI &dui, const std::map &sizeOfType) { simplifyComments(expr); @@ -3692,7 +3690,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const long long result = evaluate(expr, dui, sizeOfType); conditionIsTrue = (result != 0); } - } catch (const std::exception &e) { + } catch (const std::runtime_error &e) { if (outputList) { std::string msg = "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition"; if (e.what() && *e.what()) diff --git a/simplecpp.h b/simplecpp.h index 42a8ed80..6f7c8c66 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -338,12 +338,18 @@ namespace simplecpp { void combineOperators(); void constFoldUnaryNotPosNeg(Token *tok); + /** + * @throws std::overflow_error thrown on overflow or division by zero + */ void constFoldMulDivRem(Token *tok); void constFoldAddSub(Token *tok); void constFoldShift(Token *tok); void constFoldComparison(Token *tok); void constFoldBitwise(Token *tok); void constFoldLogicalOp(Token *tok); + /** + * @throws std::runtime_error thrown on invalid expressions + */ void constFoldQuestionOp(Token *&tok1); std::string readUntil(Stream &stream, const Location &location, char start, char end, OutputList *outputList); @@ -501,6 +507,34 @@ namespace simplecpp { id_map_type mIdMap; }; + /** Converts character literal (including prefix, but not ud-suffix) to long long value. + * + * Assumes ASCII-compatible single-byte encoded str for narrow literals + * and UTF-8 otherwise. + * + * For target assumes + * - execution character set encoding matching str + * - UTF-32 execution wide-character set encoding + * - requirements for __STDC_UTF_16__, __STDC_UTF_32__ and __STDC_ISO_10646__ satisfied + * - char16_t is 16bit wide + * - char32_t is 32bit wide + * - wchar_t is 32bit wide and unsigned + * - matching char signedness to host + * - matching sizeof(int) to host + * + * For host assumes + * - ASCII-compatible execution character set + * + * For host and target assumes + * - CHAR_BIT == 8 + * - two's complement + * + * Implements multi-character narrow literals according to GCC's behavior, + * except multi code unit universal character names are not supported. + * Multi-character wide literals are not supported. + * Limited support of universal character names for non-UTF-8 execution character set encodings. + * @throws std::runtime_error thrown on invalid literal + */ SIMPLECPP_LIB long long characterLiteralToLL(const std::string& str); SIMPLECPP_LIB FileDataCache load(const TokenList &rawtokens, std::vector &filenames, const DUI &dui, OutputList *outputList = nullptr, FileDataCache cache = {}); From 43f68be586cfb8e20583a25f62047eaa38ee9a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 14 Dec 2025 17:15:27 +0100 Subject: [PATCH 066/104] CI-unixish.yml: also run sanitizers on latest macOS (#607) --- .github/workflows/CI-unixish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index 4a4cfcdc..d7ea894a 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -119,7 +119,7 @@ jobs: make -j$(nproc) test selfcheck CXXOPTS="-Werror -stdlib=libc++ -g3 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG" LDOPTS="-lc++" - name: Run AddressSanitizer - if: matrix.os == 'ubuntu-24.04' + if: matrix.os == 'ubuntu-24.04' || matrix.os == 'macos-26' run: | make clean make -j$(nproc) test selfcheck CXXOPTS="-Werror -O2 -g3 -fsanitize=address" LDOPTS="-fsanitize=address" @@ -127,7 +127,7 @@ jobs: ASAN_OPTIONS: detect_stack_use_after_return=1 - name: Run UndefinedBehaviorSanitizer - if: matrix.os == 'ubuntu-24.04' + if: matrix.os == 'ubuntu-24.04' || matrix.os == 'macos-26' run: | make clean make -j$(nproc) test selfcheck CXXOPTS="-Werror -O2 -g3 -fsanitize=undefined -fno-sanitize=signed-integer-overflow" LDOPTS="-fsanitize=undefined -fno-sanitize=signed-integer-overflow" From 077bed15bd38605d64ed9aee8642e6507e72df46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Mon, 15 Dec 2025 12:17:23 +0100 Subject: [PATCH 067/104] build all code with all available C++ standards in CI (#606) --- .github/workflows/CI-unixish.yml | 19 +++++++++++++++---- .github/workflows/CI-windows.yml | 18 +++++++++++++++++- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index d7ea894a..6fd8a429 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -71,15 +71,26 @@ jobs: run: | make -j$(nproc) selfcheck - - name: make testrunner (c++17) + - name: make (c++14) run: | make clean - make -j$(nproc) testrunner CXXOPTS="-std=c++17" + make -j$(nproc) CXXOPTS="-Werror -std=c++14" - - name: make testrunner (c++20) + - name: make (c++17) run: | make clean - make -j$(nproc) testrunner CXXOPTS="-std=c++20" + make -j$(nproc) CXXOPTS="-Werror -std=c++17" + + - name: make (c++20) + run: | + make clean + make -j$(nproc) CXXOPTS="-Werror -std=c++20" + + - name: make (c++23) + run: | + make clean + # ubuntu-22.04 and macos-14 do not support c++23 yet + make -j$(nproc) CXXOPTS="-Werror -std=c++2b" - name: Run CMake run: | diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 06286525..e78c1f7d 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -63,4 +63,20 @@ jobs: run: | set SIMPLECPP_EXE_PATH=.\${{ matrix.config }}\simplecpp.exe python -m pytest integration_test.py -vv || exit /b !errorlevel! - + + - name: Run CMake (c++17) + run: | + cmake -S . -B build.cxx17 -G "Visual Studio 17 2022" -A x64 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=20 -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! + + - name: Build (c++17) + run: | + msbuild -m build.cxx17\simplecpp.sln /p:Configuration=${{ matrix.config }} /p:Platform=x64 || exit /b !errorlevel! + + - name: Run CMake (c++20) + run: | + cmake -S . -B build.cxx20 -G "Visual Studio 17 2022" -A x64 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=20 -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! + + - name: Build (c++20) + run: | + msbuild -m build.cxx20\simplecpp.sln /p:Configuration=${{ matrix.config }} /p:Platform=x64 || exit /b !errorlevel! + From 00a131e5c988b35765e882cacc8b31e2d988d558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 16 Dec 2025 10:34:48 +0100 Subject: [PATCH 068/104] improved `simplecpp::TokenList` constructors (#599) --- simplecpp.h | 51 +++++++++++++++++++++++++---- test.cpp | 94 ++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 129 insertions(+), 16 deletions(-) diff --git a/simplecpp.h b/simplecpp.h index 6f7c8c66..9a847d14 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -69,6 +69,46 @@ namespace simplecpp { enum cppstd_t : std::int8_t { CPPUnknown=-1, CPP03, CPP11, CPP14, CPP17, CPP20, CPP23, CPP26 }; using TokenString = std::string; + +#if defined(__cpp_lib_string_view) && !defined(__cpp_lib_span) + using View = std::string_view; +#else + struct View + { + // cppcheck-suppress noExplicitConstructor + View(const char* data) + : mData(data) + , mSize(strlen(data)) + {} + + // only provide when std::span is not available so using untyped initilization won't use View +#if !defined(__cpp_lib_span) + View(const char* data, std::size_t size) + : mData(data) + , mSize(size) + {} + + // cppcheck-suppress noExplicitConstructor + View(const std::string& str) + : mData(str.data()) + , mSize(str.size()) + {} +#endif // !defined(__cpp_lib_span) + + const char* data() const { + return mData; + } + + std::size_t size() const { + return mSize; + } + + private: + const char* mData; + std::size_t mSize; + }; +#endif // defined(__cpp_lib_string_view) && !defined(__cpp_lib_span) + class Macro; /** @@ -217,7 +257,6 @@ namespace simplecpp { explicit TokenList(std::vector &filenames); /** generates a token list from the given std::istream parameter */ TokenList(std::istream &istr, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr); -#ifdef SIMPLECPP_TOKENLIST_ALLOW_PTR /** generates a token list from the given buffer */ template TokenList(const char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) @@ -228,7 +267,7 @@ namespace simplecpp { TokenList(const unsigned char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) : TokenList(data, size-1, filenames, filename, outputList, 0) {} - +#ifdef SIMPLECPP_TOKENLIST_ALLOW_PTR /** generates a token list from the given buffer */ TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) : TokenList(data, size, filenames, filename, outputList, 0) @@ -237,13 +276,11 @@ namespace simplecpp { TokenList(const char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) : TokenList(reinterpret_cast(data), size, filenames, filename, outputList, 0) {} -#endif -#if defined(__cpp_lib_string_view) && !defined(__cpp_lib_span) +#endif // SIMPLECPP_TOKENLIST_ALLOW_PTR /** generates a token list from the given buffer */ - TokenList(std::string_view data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) + TokenList(View data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, outputList, 0) {} -#endif #ifdef __cpp_lib_span /** generates a token list from the given buffer */ TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) @@ -254,7 +291,7 @@ namespace simplecpp { TokenList(std::span data, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) : TokenList(data.data(), data.size(), filenames, filename, outputList, 0) {} -#endif +#endif // __cpp_lib_span /** generates a token list from the given filename parameter */ TokenList(const std::string &filename, std::vector &filenames, OutputList *outputList = nullptr); diff --git a/test.cpp b/test.cpp index e46e408d..300245cd 100644 --- a/test.cpp +++ b/test.cpp @@ -3299,20 +3299,97 @@ static void preprocess_files() } } -static void safe_api() +static void tokenlist_api() { - // this test is to make sure the safe APIs are compiling -#if defined(__cpp_lib_string_view) || defined(__cpp_lib_span) std::vector filenames; -# if defined(__cpp_lib_string_view) +# if !defined(__cpp_lib_string_view) && !defined(__cpp_lib_span) + // sized array + size + { + char input[] = "code"; // NOLINT(misc-const-correctness) + simplecpp::TokenList(input,sizeof(input),filenames,""); + } + { + const char input[] = "code"; + simplecpp::TokenList(input,sizeof(input),filenames,""); + } + { + unsigned char input[] = "code"; // NOLINT(misc-const-correctness) + simplecpp::TokenList(input,sizeof(input),filenames,""); + } + { + const unsigned char input[] = "code"; + simplecpp::TokenList(input,sizeof(input),filenames,""); + } +#endif // !defined(__cpp_lib_string_view) && !defined(__cpp_lib_span) + // pointer via View + { + const char * const input = "code"; + simplecpp::TokenList({input},filenames,""); + } + // sized array via View + { + char input[] = "code"; // NOLINT(misc-const-correctness) + simplecpp::TokenList(simplecpp::View{input},filenames,""); + } + { + const char input[] = "code"; + simplecpp::TokenList(simplecpp::View{input},filenames,""); + } + // sized array + size via View/std::span + { + char input[] = "code"; // NOLINT(misc-const-correctness) + simplecpp::TokenList({input,sizeof(input)},filenames,""); + } + { + const char input[] = "code"; + simplecpp::TokenList({input,sizeof(input)},filenames,""); + } + // sized array + { + char input[] = "code"; // NOLINT(misc-const-correctness) + simplecpp::TokenList(input,filenames,""); + } + { + const char input[] = "code"; + simplecpp::TokenList(input,filenames,""); + } + { + unsigned char input[] = "code"; // NOLINT(misc-const-correctness) + simplecpp::TokenList(input,filenames,""); + } + { + const unsigned char input[] = "code"; + simplecpp::TokenList(input,filenames,""); + } + // std::string via View/std::span (implicit) + { + std::string input = "code"; // NOLINT(misc-const-correctness) + simplecpp::TokenList(input,filenames,""); + } + { + const std::string input = "code"; + simplecpp::TokenList(input,filenames,""); + } + // std::string via View/std::span (explicit) + { + std::string input = "code"; // NOLINT(misc-const-correctness) + simplecpp::TokenList({input},filenames,""); + } + { + const std::string input = "code"; + simplecpp::TokenList({input},filenames,""); + } + + // this test is to make sure the safe APIs are compiling +#ifdef __cpp_lib_string_view { const char input[] = "code"; const std::string_view sv = input; // std::string_view can be implicitly converted into a std::span simplecpp::TokenList(sv,filenames,""); } -# endif -# ifdef __cpp_lib_span +#endif // __cpp_lib_string_view +#ifdef __cpp_lib_span { char input[] = "code"; const std::span sp = input; @@ -3333,8 +3410,7 @@ static void safe_api() const std::span sp = input; simplecpp::TokenList(sp,filenames,""); } -# endif -#endif +#endif // __cpp_lib_span } static void isAbsolutePath() { @@ -3660,7 +3736,7 @@ int main(int argc, char **argv) TEST_CASE(preprocess_files); - TEST_CASE(safe_api); + TEST_CASE(tokenlist_api); TEST_CASE(isAbsolutePath); From cb62a62042986f5bd6da281d924980e429d37242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 17 Dec 2025 18:33:59 +0100 Subject: [PATCH 069/104] improved test coverage of `simplecpp::characterLiteralToLL()` (#601) --- test.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test.cpp b/test.cpp index 300245cd..2b2badd6 100644 --- a/test.cpp +++ b/test.cpp @@ -321,6 +321,8 @@ static void characterLiteral() ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("u8'\343\201\202'"), std::runtime_error, "code point too large"); ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("u8'\360\223\200\200'"), std::runtime_error, "code point too large"); + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("'\\U11111111"), std::runtime_error, "code point too large"); + ASSERT_EQUALS('\x89', simplecpp::characterLiteralToLL("'\x89'")); ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("U'\x89'"), std::runtime_error, "assumed UTF-8 encoded source, but sequence is invalid"); @@ -372,6 +374,33 @@ static void characterLiteral() ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("U'\xed\xa0\x80'"), std::runtime_error, "assumed UTF-8 encoded source, but sequence is invalid"); ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("U'\xed\xbf\xbf'"), std::runtime_error, "assumed UTF-8 encoded source, but sequence is invalid"); + + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL(""), std::runtime_error, "expected a character literal"); + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("LU"), std::runtime_error, "expected a character literal"); + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL(";\n"), std::runtime_error, "expected a character literal"); + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("u8U"), std::runtime_error, "expected a character literal"); + + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("'\n\n"), std::runtime_error, "raw single quotes and newlines not allowed in character literals"); + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("''&"), std::runtime_error, "raw single quotes and newlines not allowed in character literals"); + + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("L'\fff"), std::runtime_error, "multiple characters only supported in narrow character literals"); + + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("'\\\n"), std::runtime_error, "unexpected end of character literal"); + + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("'"), std::runtime_error, "missing closing quote in character literal"); + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("u'"), std::runtime_error, "missing closing quote in character literal"); + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("L'"), std::runtime_error, "missing closing quote in character literal"); + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("'a"), std::runtime_error, "missing closing quote in character literal"); + + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("''"), std::runtime_error, "empty character literal"); + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("L''"), std::runtime_error, "empty character literal"); + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("U''"), std::runtime_error, "empty character literal"); + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("u''"), std::runtime_error, "empty character literal"); + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("u8''"), std::runtime_error, "empty character literal"); + + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("'\\555"), std::runtime_error, "numeric escape sequence too large"); + + ASSERT_THROW_EQUALS(simplecpp::characterLiteralToLL("u'Ó"), std::runtime_error, "assumed UTF-8 encoded source, but character literal ends unexpectedly"); } static void combineOperators_floatliteral() From 9eea499a2412eb47b5338bb5071a57bfa9fab861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 17 Dec 2025 21:36:42 +0100 Subject: [PATCH 070/104] CI-windows.yml: fixed standard in C++17 build (#609) --- .github/workflows/CI-windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index e78c1f7d..521bc24c 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -66,7 +66,7 @@ jobs: - name: Run CMake (c++17) run: | - cmake -S . -B build.cxx17 -G "Visual Studio 17 2022" -A x64 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=20 -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! + cmake -S . -B build.cxx17 -G "Visual Studio 17 2022" -A x64 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=17 -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! - name: Build (c++17) run: | From 10cfb949b62819fe37f731255818faf4fd2ddd54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 17 Dec 2025 21:37:49 +0100 Subject: [PATCH 071/104] added MinGW workflow (#475) Co-authored-by: glankk --- .github/workflows/CI-mingw.yml | 138 +++++++++++++++++++++++++++++++++ CMakeLists.txt | 12 ++- selfcheck.sh | 15 +++- simplecpp.cpp | 2 +- 4 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/CI-mingw.yml diff --git a/.github/workflows/CI-mingw.yml b/.github/workflows/CI-mingw.yml new file mode 100644 index 00000000..a3bac5cf --- /dev/null +++ b/.github/workflows/CI-mingw.yml @@ -0,0 +1,138 @@ +name: CI-mingw + +on: [push, pull_request] + +permissions: + contents: read + +defaults: + run: + shell: msys2 {0} + +jobs: + build: + + strategy: + matrix: + compiler: [g++, clang++] + # TODO: add MSYS after #556 is fixed + msystem: [MINGW32, MINGW64, CLANG64] + include: + #- msystem: MSYS + # pkg-prefix: '' + - msystem: MINGW32 + pkg-prefix: 'mingw-w64-i686-' + - msystem: MINGW64 + pkg-prefix: 'mingw-w64-x86_64-' + - msystem: CLANG64 + pkg-prefix: 'mingw-w64-clang-x86_64-' + - compiler: g++ + compiler-pkg: gcc + - compiler: clang++ + compiler-pkg: clang + exclude: + - msystem: CLANG64 + compiler: g++ + fail-fast: false + + runs-on: windows-2025 + + env: + CXX: ${{ matrix.compiler }} + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Set up MSYS2 + uses: msys2/setup-msys2@v2 + with: + release: false # use pre-installed + msystem: ${{ matrix.msystem }} + # TODO: install mingw-w64-x86_64-make and use mingw32.make instead - currently fails with "Windows Subsystem for Linux has no installed distributions." + # TODO: also run tests with non-prefixed Python? + install: >- + make + ${{ matrix.pkg-prefix }}cmake + ${{ matrix.pkg-prefix }}python + ${{ matrix.pkg-prefix }}python-pytest + + - name: install compiler + run: | + pacman -S --noconfirm ${{ matrix.pkg-prefix }}${{ matrix.compiler-pkg }} + ${CXX} -v + + - name: make simplecpp + run: | + make -j$(nproc) CXXOPTS="-Werror" + + # gcc *and* clang are required to run-tests.py + # install it at this point since it has gcc as dependency which might interfere with the build + - name: install compiler (clang) + if: matrix.compiler == 'g++' + run: | + pacman -S --noconfirm clang + + - name: install compiler (gcc) + if: matrix.compiler == 'clang++' + run: | + pacman -S --noconfirm gcc + + - name: make test + run: | + # TODO: run tests with Windows paths + make -j$(nproc) test + + - name: selfcheck + run: | + # TODO: run tests with Windows paths + make -j$(nproc) selfcheck + + - name: make (c++14) + run: | + make clean + make -j$(nproc) CXXOPTS="-Werror -std=c++14" + + - name: make (c++17) + run: | + make clean + make -j$(nproc) CXXOPTS="-Werror -std=c++17" + + - name: make (c++20) + run: | + make clean + make -j$(nproc) CXXOPTS="-Werror -std=c++20" + + - name: make (c++23) + run: | + make clean + make -j$(nproc) CXXOPTS="-Werror -std=c++23" + + - name: Run CMake + run: | + cmake -S . -B cmake.output -DCMAKE_COMPILE_WARNING_AS_ERROR=On + + - name: CMake simplecpp + run: | + cmake --build cmake.output --target simplecpp -- -j $(nproc) + + - name: CMake testrunner + run: | + cmake --build cmake.output --target testrunner -- -j $(nproc) + + - name: Run testrunner + run: | + ./cmake.output/testrunner + + - name: Run with libstdc++ debug mode + if: matrix.compiler == 'g++' + run: | + make clean + make -j$(nproc) test selfcheck CXXOPTS="-Werror -g3 -D_GLIBCXX_DEBUG" + + - name: Run with libc++ hardening mode + if: matrix.compiler == 'clang++' && matrix.msystem == 'CLANG64' + run: | + make clean + make -j$(nproc) test selfcheck CXXOPTS="-Werror -stdlib=libc++ -g3 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG" LDOPTS="-lc++" diff --git a/CMakeLists.txt b/CMakeLists.txt index f13fb3fb..0a90efae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,9 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") add_compile_options(-Woverloaded-virtual) # when a function declaration hides virtual functions from a base class add_compile_options(-Wsuggest-attribute=noreturn) - add_compile_options_safe(-Wuseless-cast) + if (NOT MINGW) + add_compile_options_safe(-Wuseless-cast) + endif() # we are not interested in these set_source_files_properties(test.cpp PROPERTIES COMPILE_FLAGS -Wno-multichar) @@ -62,6 +64,14 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") # contradicts -Wcovered-switch-default add_compile_options(-Wno-switch-default) + if (MINGW) + add_compile_options(-Wno-reserved-macro-identifier) + add_compile_options(-Wno-unused-macros) + endif() + + # these are experimental warnings which might produce false positives + add_compile_options_safe(-Wno-thread-safety-negative) + add_compile_options_safe(-Wno-thread-safety-beta) # TODO: fix these? add_compile_options(-Wno-padded) diff --git a/selfcheck.sh b/selfcheck.sh index e708023a..b2129cc9 100755 --- a/selfcheck.sh +++ b/selfcheck.sh @@ -41,16 +41,20 @@ if [ "$cxx_type" = "Ubuntu" ] || [ "$cxx_type" = "Debian" ]; then fi # TODO: generate defines from compiler -if [ "$cxx_type" = "g++" ]; then +if [ "$cxx_type" = "g++" ] || [ "$cxx_type" = "g++.exe" ]; then defs= defs="$defs -D__GNUC__" defs="$defs -D__STDC__" defs="$defs -D__x86_64__" defs="$defs -D__STDC_HOSTED__" defs="$defs -D__CHAR_BIT__=8" + if [ "${MSYSTEM}" = "MINGW32" ] || [ "${MSYSTEM}" = "MINGW64" ]; then + defs="$defs -D_WIN32" + fi defs="$defs -D__has_builtin(x)=(1)" defs="$defs -D__has_cpp_attribute(x)=(1)" defs="$defs -D__has_attribute(x)=(1)" + defs="$defs -Ddefined(x)=(0)" inc= while read line @@ -63,12 +67,19 @@ elif [ "$cxx_type" = "clang" ]; then defs="$defs -D__x86_64__" defs="$defs -D__STDC_HOSTED__" defs="$defs -D__CHAR_BIT__=8" + defs="$defs -D__BYTE_ORDER__=1234" + defs="$defs -D__SIZEOF_SIZE_T__=8" + if [ "${MSYSTEM}" = "MINGW32" ] || [ "${MSYSTEM}" = "MINGW64" ] || [ "${MSYSTEM}" = "CLANG64" ]; then + defs="$defs -D_WIN32" + fi defs="$defs -D__has_builtin(x)=(1)" defs="$defs -D__has_cpp_attribute(x)=(1)" defs="$defs -D__has_feature(x)=(1)" - defs="$defs -D__has_include_next(x)=(0)" + defs="$defs -D__has_include_next(x)=(1)" defs="$defs -D__has_attribute(x)=(0)" defs="$defs -D__building_module(x)=(0)" + defs="$defs -D__has_extension(x)=(1)" + defs="$defs -Ddefined(x)=(0)" inc= while read line diff --git a/simplecpp.cpp b/simplecpp.cpp index 581c9b10..b1525d6d 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3120,7 +3120,7 @@ std::pair simplecpp::FileDataCache::get(const std:: bool simplecpp::FileDataCache::getFileId(const std::string &path, FileID &id) { #ifdef _WIN32 - HANDLE hFile = CreateFileA(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + HANDLE hFile = CreateFileA(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile == INVALID_HANDLE_VALUE) return false; From d7a259d2f80240c1749d97407a64fdeb6ab9705b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 31 Dec 2025 15:31:58 +0100 Subject: [PATCH 072/104] do not default `simplecpp::Location::line` to `1` (#597) --- simplecpp.cpp | 1 + simplecpp.h | 2 +- test.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index b1525d6d..e2845dd2 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -565,6 +565,7 @@ std::string simplecpp::TokenList::stringify(bool linenrs) const { std::ostringstream ret; Location loc; + loc.line = 1; bool filechg = true; for (const Token *tok = cfront(); tok; tok = tok->next) { if (tok->location.line < loc.line || tok->location.fileIndex != loc.fileIndex) { diff --git a/simplecpp.h b/simplecpp.h index 9a847d14..d131ded4 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -141,7 +141,7 @@ namespace simplecpp { } unsigned int fileIndex{}; - unsigned int line{1}; + unsigned int line{}; unsigned int col{}; }; diff --git a/test.cpp b/test.cpp index 2b2badd6..a17ffbb9 100644 --- a/test.cpp +++ b/test.cpp @@ -2761,7 +2761,7 @@ static void readfile_file_not_found() simplecpp::OutputList outputList; std::vector files; (void)simplecpp::TokenList("NotAFile", files, &outputList); - ASSERT_EQUALS("file0,1,file_not_found,File is missing: NotAFile\n", toString(outputList)); + ASSERT_EQUALS("file0,0,file_not_found,File is missing: NotAFile\n", toString(outputList)); } static void stringify1() From 10f505214f70b03a7e9b34e293b7324e3a996415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 2 Jan 2026 17:42:49 +0100 Subject: [PATCH 073/104] enabled and fixed `modernize-return-braced-init-list` clang-tidy warnings (#610) --- .clang-tidy | 1 - simplecpp.cpp | 10 +++++----- test.cpp | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index b63cb3d9..d16cc35c 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -29,7 +29,6 @@ Checks: > -modernize-avoid-c-arrays, -modernize-loop-convert, -modernize-pass-by-value, - -modernize-return-braced-init-list, -modernize-use-nodiscard, -modernize-use-trailing-return-type, -readability-avoid-nested-conditional-operator, diff --git a/simplecpp.cpp b/simplecpp.cpp index e2845dd2..62dca039 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1700,19 +1700,19 @@ namespace simplecpp { : Error(loc, format(macroName, message)) {} static inline invalidHashHash unexpectedToken(const Location &loc, const std::string ¯oName, const Token *tokenA) { - return invalidHashHash(loc, macroName, "Unexpected token '"+ tokenA->str()+"'"); + return {loc, macroName, "Unexpected token '"+ tokenA->str()+"'"}; } static inline invalidHashHash cannotCombine(const Location &loc, const std::string ¯oName, const Token *tokenA, const Token *tokenB) { - return invalidHashHash(loc, macroName, "Combining '"+ tokenA->str()+ "' and '"+ tokenB->str() + "' yields an invalid token."); + return {loc, macroName, "Combining '"+ tokenA->str()+ "' and '"+ tokenB->str() + "' yields an invalid token."}; } static inline invalidHashHash unexpectedNewline(const Location &loc, const std::string ¯oName) { - return invalidHashHash(loc, macroName, "Unexpected newline"); + return {loc, macroName, "Unexpected newline"}; } static inline invalidHashHash universalCharacterUB(const Location &loc, const std::string ¯oName, const Token* tokenA, const std::string& strAB) { - return invalidHashHash(loc, macroName, "Combining '\\"+ tokenA->str()+ "' and '"+ strAB.substr(tokenA->str().size()) + "' yields universal character '\\" + strAB + "'. This is undefined behavior according to C standard chapter 5.1.1.2, paragraph 4."); + return {loc, macroName, "Combining '\\"+ tokenA->str()+ "' and '"+ strAB.substr(tokenA->str().size()) + "' yields universal character '\\" + strAB + "'. This is undefined behavior according to C standard chapter 5.1.1.2, paragraph 4."}; } }; private: @@ -1829,7 +1829,7 @@ namespace simplecpp { std::vector getMacroParameters(const Token *nameTokInst, bool calledInDefine) const { if (!nameTokInst->next || nameTokInst->next->op != '(' || !functionLike()) - return std::vector(); + return {}; std::vector parametertokens; parametertokens.push_back(nameTokInst->next); diff --git a/test.cpp b/test.cpp index a17ffbb9..78df4a0e 100644 --- a/test.cpp +++ b/test.cpp @@ -82,7 +82,7 @@ static void testcase(const std::string &name, void (*f)(), int argc, char * cons static simplecpp::TokenList makeTokenList(const char code[], std::size_t size, std::vector &filenames, const std::string &filename=std::string(), simplecpp::OutputList *outputList=nullptr) { std::istringstream istr(std::string(code, size)); - return simplecpp::TokenList(istr,filenames,filename,outputList); + return {istr,filenames,filename,outputList}; } static simplecpp::TokenList makeTokenList(const char code[], std::vector &filenames, const std::string &filename=std::string(), simplecpp::OutputList *outputList=nullptr) From 6d45cd7bf7f12878ed99a5dcf5ee410ef6492a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 14 Jan 2026 02:33:57 +0100 Subject: [PATCH 074/104] fixed #616 - report bad macro syntax via `OutputList` (#617) --- simplecpp.cpp | 17 +++++++++++++++-- test.cpp | 14 ++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 62dca039..3a05ec84 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3341,8 +3341,21 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL continue; const std::string lhs(macrostr.substr(0,eq)); const std::string rhs(eq==std::string::npos ? std::string("1") : macrostr.substr(eq+1)); - const Macro macro(lhs, rhs, dummy); - macros.insert(std::pair(macro.name(), macro)); + try { + const Macro macro(lhs, rhs, dummy); + macros.insert(std::pair(macro.name(), macro)); + } catch (const std::runtime_error& e) { + if (outputList) { + simplecpp::Output err = { + Output::DUI_ERROR, + {}, + e.what() + }; + outputList->push_back(std::move(err)); + } + output.clear(); + return; + } } const bool strictAnsiUndefined = dui.undefined.find("__STRICT_ANSI__") != dui.undefined.cend(); diff --git a/test.cpp b/test.cpp index 78df4a0e..9583468f 100644 --- a/test.cpp +++ b/test.cpp @@ -3442,6 +3442,18 @@ static void tokenlist_api() #endif // __cpp_lib_span } +static void bad_macro_syntax() // #616 +{ + simplecpp::DUI dui; + dui.defines.emplace_back("\""); + + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess("", dui, &outputList)); + ASSERT_EQUALS(1, outputList.size()); + ASSERT_EQUALS(simplecpp::Output::Type::DUI_ERROR, outputList.cbegin()->type); + ASSERT_EQUALS("bad macro syntax. macroname=\" value=1", outputList.cbegin()->msg); +} + static void isAbsolutePath() { #ifdef _WIN32 ASSERT_EQUALS(true, simplecpp::isAbsolutePath("C:\\foo\\bar")); @@ -3769,6 +3781,8 @@ int main(int argc, char **argv) TEST_CASE(isAbsolutePath); + TEST_CASE(bad_macro_syntax); + TEST_CASE(fuzz_crash); TEST_CASE(leak); From 15f833511270545440a8567ba44d8b4c9d9e6936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 14 Jan 2026 15:53:18 +0100 Subject: [PATCH 075/104] added more `@throws` to documentation (#618) --- simplecpp.cpp | 1 + simplecpp.h | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/simplecpp.cpp b/simplecpp.cpp index 3a05ec84..94ee2fa7 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2957,6 +2957,7 @@ static void simplifyComments(simplecpp::TokenList &expr) /** * @throws std::runtime_error thrown on invalid literals, missing sizeof arguments or invalid expressions, * missing __has_include() arguments or expressions, undefined function-like macros, invalid number literals + * @throws std::overflow_error thrown on overflow or division by zero */ static long long evaluate(simplecpp::TokenList &expr, const simplecpp::DUI &dui, const std::map &sizeOfType) { diff --git a/simplecpp.h b/simplecpp.h index d131ded4..46a43bc4 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -311,6 +311,10 @@ namespace simplecpp { std::string stringify(bool linenrs = false) const; void readfile(Stream &stream, const std::string &filename=std::string(), OutputList *outputList = nullptr); + /** + * @throws std::overflow_error thrown on overflow or division by zero + * @throws std::runtime_error thrown on invalid expressions + */ void constFold(); void removeComments(); From 10c96815e402299da1d3ab1f753dda4c1b0b12f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Thu, 15 Jan 2026 16:58:22 +0100 Subject: [PATCH 076/104] use `emplace_back` (#619) --- simplecpp.cpp | 74 +++++++++++++++++++++++++-------------------------- test.cpp | 12 ++++----- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 94ee2fa7..1d14a06c 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -421,7 +421,7 @@ class FileStream : public simplecpp::TokenList::Stream { : file(fopen(filename.c_str(), "rb")) { if (!file) { - files.push_back(filename); + files.emplace_back(filename); throw simplecpp::Output(simplecpp::Output::FILE_NOT_FOUND, {}, "File is missing: " + filename); } init(); @@ -490,7 +490,7 @@ simplecpp::TokenList::TokenList(const std::string &filename, std::vectorpush_back(e); + outputList->emplace_back(e); } } @@ -625,7 +625,7 @@ static void portabilityBackslash(simplecpp::OutputList *outputList, const simple location, "Combination 'backslash space newline' is not portable." }; - outputList->push_back(std::move(err)); + outputList->emplace_back(std::move(err)); } static bool isStringLiteralPrefix(const std::string &str) @@ -674,7 +674,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, location, "The code contains unhandled character(s) (character code=" + std::to_string(static_cast(ch)) + "). Neither unicode nor extended ascii is supported." }; - outputList->push_back(std::move(err)); + outputList->emplace_back(std::move(err)); } clear(); return; @@ -876,7 +876,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, location, "Invalid newline in raw string delimiter." }; - outputList->push_back(std::move(err)); + outputList->emplace_back(std::move(err)); } return; } @@ -890,7 +890,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, location, "Raw string missing terminating delimiter." }; - outputList->push_back(std::move(err)); + outputList->emplace_back(std::move(err)); } return; } @@ -1434,7 +1434,7 @@ std::string simplecpp::TokenList::readUntil(Stream &stream, const Location &loca location, std::string("No pair for character (") + start + "). Can't process file. File is either invalid or unicode, which is currently not supported." }; - outputList->push_back(std::move(err)); + outputList->emplace_back(std::move(err)); } return ""; } @@ -1472,7 +1472,7 @@ unsigned int simplecpp::TokenList::fileIndex(const std::string &filename) if (files[i] == filename) return i; } - files.push_back(filename); + files.emplace_back(filename); return files.size() - 1U; } @@ -1754,7 +1754,7 @@ namespace simplecpp { break; } if (argtok->op != ',') - args.push_back(argtok->str()); + args.emplace_back(argtok->str()); argtok = argtok->next; } if (!sameline(nametoken, argtok)) { @@ -1832,19 +1832,19 @@ namespace simplecpp { return {}; std::vector parametertokens; - parametertokens.push_back(nameTokInst->next); + parametertokens.emplace_back(nameTokInst->next); unsigned int par = 0U; for (const Token *tok = nameTokInst->next->next; calledInDefine ? sameline(tok, nameTokInst) : (tok != nullptr); tok = tok->next) { if (tok->op == '(') ++par; else if (tok->op == ')') { if (par == 0U) { - parametertokens.push_back(tok); + parametertokens.emplace_back(tok); break; } --par; } else if (par == 0U && tok->op == ',' && (!variadic || parametertokens.size() < args.size())) - parametertokens.push_back(tok); + parametertokens.emplace_back(tok); } return parametertokens; } @@ -1894,7 +1894,7 @@ namespace simplecpp { std::cout << " expand " << name() << " " << locstring(defineLocation()) << std::endl; #endif - usageList.push_back(loc); + usageList.emplace_back(loc); if (nameTokInst->str() == "__FILE__") { output.push_back(new Token('\"'+output.file(loc)+'\"', loc)); @@ -1954,11 +1954,11 @@ namespace simplecpp { for (const Token *tok = parametertokens1[0]; tok && par < parametertokens1.size(); tok = tok->next) { if (tok->str() == "__COUNTER__") { tokensparams.push_back(new Token(toString(counterMacro.usageList.size()), tok->location)); - counterMacro.usageList.push_back(tok->location); + counterMacro.usageList.emplace_back(tok->location); } else { tokensparams.push_back(new Token(*tok)); if (tok == parametertokens1[par]) { - parametertokens2.push_back(tokensparams.cback()); + parametertokens2.emplace_back(tokensparams.cback()); par++; } } @@ -3183,7 +3183,7 @@ simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, {}, "Can not open include file '" + filename + "' that is explicitly included." }; - outputList->push_back(std::move(err)); + outputList->emplace_back(std::move(err)); } continue; } @@ -3197,7 +3197,7 @@ simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, if (dui.removeComments) filedata->tokens.removeComments(); - filelist.push_back(filedata->tokens.front()); + filelist.emplace_back(filedata->tokens.front()); } for (const Token *rawtok = rawtokens.cfront(); rawtok || !filelist.empty(); rawtok = rawtok ? rawtok->next : nullptr) { @@ -3236,7 +3236,7 @@ simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, if (dui.removeComments) filedata->tokens.removeComments(); - filelist.push_back(filedata->tokens.front()); + filelist.emplace_back(filedata->tokens.front()); } return cache; @@ -3257,7 +3257,7 @@ static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token err.location, "failed to expand \'" + tok->str() + "\', " + err.what }; - outputList->push_back(std::move(out)); + outputList->emplace_back(std::move(out)); } return false; } @@ -3352,7 +3352,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL {}, e.what() }; - outputList->push_back(std::move(err)); + outputList->emplace_back(std::move(err)); } output.clear(); return; @@ -3386,7 +3386,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL {}, "unknown standard specified: '" + dui.std + "'" }; - outputList->push_back(std::move(err)); + outputList->emplace_back(std::move(err)); } output.clear(); return; @@ -3443,7 +3443,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL rawtok->location, "#" + rawtok->str() + " without #if" }; - outputList->push_back(std::move(err)); + outputList->emplace_back(std::move(err)); } output.clear(); return; @@ -3464,7 +3464,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL std::move(msg) }; - outputList->push_back(std::move(err)); + outputList->emplace_back(std::move(err)); } if (rawtok->str() == ERROR) { output.clear(); @@ -3491,7 +3491,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL rawtok->location, "Failed to parse #define" }; - outputList->push_back(std::move(err)); + outputList->emplace_back(std::move(err)); } output.clear(); return; @@ -3502,7 +3502,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL err.location, "Failed to parse #define, " + err.what }; - outputList->push_back(std::move(out)); + outputList->emplace_back(std::move(out)); } output.clear(); return; @@ -3543,7 +3543,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL rawtok->location, "No header in #include" }; - outputList->push_back(std::move(err)); + outputList->emplace_back(std::move(err)); } output.clear(); return; @@ -3561,7 +3561,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL rawtok->location, "Header not found: " + inctok->str() }; - outputList->push_back(std::move(out)); + outputList->emplace_back(std::move(out)); } } else if (includetokenstack.size() >= 400) { if (outputList) { @@ -3570,7 +3570,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL rawtok->location, "#include nested too deeply" }; - outputList->push_back(std::move(out)); + outputList->emplace_back(std::move(out)); } } else if (pragmaOnce.find(filedata->filename) == pragmaOnce.end()) { includetokenstack.push(gotoNextLine(rawtok)); @@ -3585,7 +3585,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL rawtok->location, "Syntax error in #" + rawtok->str() }; - outputList->push_back(std::move(out)); + outputList->emplace_back(std::move(out)); } output.clear(); return; @@ -3596,10 +3596,10 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL conditionIsTrue = false; else if (rawtok->str() == IFDEF) { conditionIsTrue = (macros.find(rawtok->next->str()) != macros.end() || (hasInclude && rawtok->next->str() == HAS_INCLUDE)); - maybeUsedMacros[rawtok->next->str()].push_back(rawtok->next->location); + maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); } else if (rawtok->str() == IFNDEF) { conditionIsTrue = (macros.find(rawtok->next->str()) == macros.end() && !(hasInclude && rawtok->next->str() == HAS_INCLUDE)); - maybeUsedMacros[rawtok->next->str()].push_back(rawtok->next->location); + maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); } else { /*if (rawtok->str() == IF || rawtok->str() == ELIF)*/ TokenList expr(files); for (const Token *tok = rawtok->next; tok && tok->location.sameline(rawtok->location); tok = tok->next) { @@ -3613,7 +3613,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const bool par = (tok && tok->op == '('); if (par) tok = tok->next; - maybeUsedMacros[rawtok->next->str()].push_back(rawtok->next->location); + maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); if (tok) { if (macros.find(tok->str()) != macros.end()) expr.push_back(new Token("1", tok->location)); @@ -3631,7 +3631,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL rawtok->location, "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition" }; - outputList->push_back(std::move(out)); + outputList->emplace_back(std::move(out)); } output.clear(); return; @@ -3674,7 +3674,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL rawtok->location, "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition" }; - outputList->push_back(std::move(out)); + outputList->emplace_back(std::move(out)); } output.clear(); return; @@ -3682,7 +3682,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL continue; } - maybeUsedMacros[rawtok->next->str()].push_back(rawtok->next->location); + maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); const Token *tmp = tok; if (!preprocessToken(expr, tmp, macros, files, outputList)) { @@ -3715,7 +3715,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL rawtok->location, std::move(msg) }; - outputList->push_back(std::move(out)); + outputList->emplace_back(std::move(out)); } output.clear(); return; @@ -3814,7 +3814,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL mu.macroName = macro.name(); mu.macroLocation = macro.defineLocation(); mu.useLocation = *usageIt; - macroUsage->push_back(std::move(mu)); + macroUsage->emplace_back(std::move(mu)); } } } diff --git a/test.cpp b/test.cpp index 9583468f..ef7249e7 100644 --- a/test.cpp +++ b/test.cpp @@ -1607,7 +1607,7 @@ static void has_include_1() " #endif\n" "#endif"; simplecpp::DUI dui; - dui.includePaths.push_back(testSourceDir); + dui.includePaths.emplace_back(testSourceDir); ASSERT_EQUALS("\n\nA", preprocess(code, dui)); // we default to latest standard internally dui.std = "c++14"; ASSERT_EQUALS("", preprocess(code, dui)); @@ -1628,7 +1628,7 @@ static void has_include_2() "#endif"; simplecpp::DUI dui; dui.removeComments = true; // TODO: remove this - dui.includePaths.push_back(testSourceDir); + dui.includePaths.emplace_back(testSourceDir); ASSERT_EQUALS("\n\nA", preprocess(code, dui)); // we default to latest standard internally dui.std = "c++14"; ASSERT_EQUALS("", preprocess(code, dui)); @@ -1657,7 +1657,7 @@ static void has_include_3() ASSERT_EQUALS("\n\n\n\nB", preprocess(code, dui)); // Unless -I is set (preferably, we should differentiate -I and -isystem...) - dui.includePaths.push_back(testSourceDir + "/testsuite"); + dui.includePaths.emplace_back(testSourceDir + "/testsuite"); dui.std = ""; ASSERT_EQUALS("\n\nA", preprocess(code, dui)); // we default to latest standard internally dui.std = "c++14"; @@ -1678,7 +1678,7 @@ static void has_include_4() " #endif\n" "#endif"; simplecpp::DUI dui; - dui.includePaths.push_back(testSourceDir); // we default to latest standard internally + dui.includePaths.emplace_back(testSourceDir); // we default to latest standard internally ASSERT_EQUALS("\n\nA", preprocess(code, dui)); dui.std = "c++14"; ASSERT_EQUALS("", preprocess(code, dui)); @@ -1699,7 +1699,7 @@ static void has_include_5() "#endif"; simplecpp::DUI dui; ASSERT_EQUALS("\n\nA", preprocess(code, dui)); // we default to latest standard internally - dui.includePaths.push_back(testSourceDir); + dui.includePaths.emplace_back(testSourceDir); dui.std = "c++14"; ASSERT_EQUALS("", preprocess(code, dui)); dui.std = "c++17"; @@ -1718,7 +1718,7 @@ static void has_include_6() " #endif\n" "#endif"; simplecpp::DUI dui; - dui.includePaths.push_back(testSourceDir); + dui.includePaths.emplace_back(testSourceDir); ASSERT_EQUALS("\n\nA", preprocess(code, dui)); // we default to latest standard internally dui.std = "c++99"; ASSERT_EQUALS("", preprocess(code, dui)); From 1453e4e738e9f4159165893c881380bba975c29d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Mon, 19 Jan 2026 17:31:29 +0100 Subject: [PATCH 077/104] use initializer lists without assignment (#624) --- simplecpp.cpp | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 1d14a06c..9371eaf6 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -620,7 +620,7 @@ static void portabilityBackslash(simplecpp::OutputList *outputList, const simple { if (!outputList) return; - simplecpp::Output err = { + simplecpp::Output err{ simplecpp::Output::PORTABILITY_BACKSLASH, location, "Combination 'backslash space newline' is not portable." @@ -669,7 +669,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (ch >= 0x80) { if (outputList) { - simplecpp::Output err = { + simplecpp::Output err{ simplecpp::Output::UNHANDLED_CHAR_ERROR, location, "The code contains unhandled character(s) (character code=" + std::to_string(static_cast(ch)) + "). Neither unicode nor extended ascii is supported." @@ -871,7 +871,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, } if (!stream.good() || ch == '\n') { if (outputList) { - Output err = { + Output err{ Output::SYNTAX_ERROR, location, "Invalid newline in raw string delimiter." @@ -885,7 +885,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, currentToken += stream.readChar(); if (!endsWith(currentToken, endOfRawString)) { if (outputList) { - Output err = { + Output err{ Output::SYNTAX_ERROR, location, "Raw string missing terminating delimiter." @@ -1429,7 +1429,7 @@ std::string simplecpp::TokenList::readUntil(Stream &stream, const Location &loca if (!stream.good() || ch != end) { clear(); if (outputList) { - Output err = { + Output err{ Output::SYNTAX_ERROR, location, std::string("No pair for character (") + start + "). Can't process file. File is either invalid or unicode, which is currently not supported." @@ -2692,7 +2692,7 @@ static void simplifyName(simplecpp::TokenList &expr) { for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) { if (tok->name) { - static const std::set altop = {"and","or","bitand","bitor","compl","not","not_eq","xor"}; + static const std::set altop{"and","or","bitand","bitor","compl","not","not_eq","xor"}; if (altop.find(tok->str()) != altop.end()) { bool alt; if (tok->str() == "not" || tok->str() == "compl") { @@ -3178,7 +3178,7 @@ simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, if (filedata == nullptr) { if (outputList) { - simplecpp::Output err = { + simplecpp::Output err{ simplecpp::Output::EXPLICIT_INCLUDE_NOT_FOUND, {}, "Can not open include file '" + filename + "' that is explicitly included." @@ -3252,7 +3252,7 @@ static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token tok1 = it->second.expand(value, tok, macros, files); } catch (const simplecpp::Macro::Error &err) { if (outputList) { - simplecpp::Output out = { + simplecpp::Output out{ simplecpp::Output::SYNTAX_ERROR, err.location, "failed to expand \'" + tok->str() + "\', " + err.what @@ -3347,7 +3347,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL macros.insert(std::pair(macro.name(), macro)); } catch (const std::runtime_error& e) { if (outputList) { - simplecpp::Output err = { + simplecpp::Output err{ Output::DUI_ERROR, {}, e.what() @@ -3366,7 +3366,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL macros.insert(std::make_pair("__FILE__", Macro("__FILE__", "__FILE__", dummy))); macros.insert(std::make_pair("__LINE__", Macro("__LINE__", "__LINE__", dummy))); macros.insert(std::make_pair("__COUNTER__", Macro("__COUNTER__", "__COUNTER__", dummy))); - struct tm ltime = {}; + struct tm ltime {}; getLocaltime(ltime); macros.insert(std::make_pair("__DATE__", Macro("__DATE__", getDateDefine(<ime), dummy))); macros.insert(std::make_pair("__TIME__", Macro("__TIME__", getTimeDefine(<ime), dummy))); @@ -3381,7 +3381,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const cppstd_t cpp_std = simplecpp::getCppStd(dui.std); if (cpp_std == CPPUnknown) { if (outputList) { - simplecpp::Output err = { + simplecpp::Output err{ Output::DUI_ERROR, {}, "unknown standard specified: '" + dui.std + "'" @@ -3438,7 +3438,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL if (ifstates.size() <= 1U && (rawtok->str() == ELIF || rawtok->str() == ELSE || rawtok->str() == ENDIF)) { if (outputList) { - simplecpp::Output err = { + simplecpp::Output err{ Output::SYNTAX_ERROR, rawtok->location, "#" + rawtok->str() + " without #if" @@ -3458,7 +3458,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL msg += tok->str(); } msg = '#' + rawtok->str() + ' ' + msg; - simplecpp::Output err = { + simplecpp::Output err{ rawtok->str() == ERROR ? Output::ERROR : Output::WARNING, rawtok->location, std::move(msg) @@ -3486,7 +3486,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } } catch (const std::runtime_error &) { if (outputList) { - simplecpp::Output err = { + simplecpp::Output err{ Output::SYNTAX_ERROR, rawtok->location, "Failed to parse #define" @@ -3497,7 +3497,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL return; } catch (const simplecpp::Macro::Error &err) { if (outputList) { - simplecpp::Output out = { + simplecpp::Output out{ simplecpp::Output::SYNTAX_ERROR, err.location, "Failed to parse #define, " + err.what @@ -3538,7 +3538,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL if (inc2.empty() || inc2.cfront()->str().size() <= 2U) { if (outputList) { - simplecpp::Output err = { + simplecpp::Output err{ Output::SYNTAX_ERROR, rawtok->location, "No header in #include" @@ -3556,7 +3556,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const FileData *const filedata = cache.get(rawtokens.file(rawtok->location), header, dui, systemheader, files, outputList).first; if (filedata == nullptr) { if (outputList) { - simplecpp::Output out = { + simplecpp::Output out{ simplecpp::Output::MISSING_HEADER, rawtok->location, "Header not found: " + inctok->str() @@ -3565,7 +3565,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } } else if (includetokenstack.size() >= 400) { if (outputList) { - simplecpp::Output out = { + simplecpp::Output out{ simplecpp::Output::INCLUDE_NESTED_TOO_DEEPLY, rawtok->location, "#include nested too deeply" @@ -3580,7 +3580,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } else if (rawtok->str() == IF || rawtok->str() == IFDEF || rawtok->str() == IFNDEF || rawtok->str() == ELIF) { if (!sameline(rawtok,rawtok->next)) { if (outputList) { - simplecpp::Output out = { + simplecpp::Output out{ simplecpp::Output::SYNTAX_ERROR, rawtok->location, "Syntax error in #" + rawtok->str() @@ -3626,7 +3626,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL tok = tok ? tok->next : nullptr; if (!tok || !sameline(rawtok,tok) || (par && tok->op != ')')) { if (outputList) { - Output out = { + Output out{ Output::SYNTAX_ERROR, rawtok->location, "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition" @@ -3669,7 +3669,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL tok = tok ? tok->next : nullptr; if (!tok || !sameline(rawtok,tok) || (par && tok->op != ')') || (!closingAngularBracket)) { if (outputList) { - Output out = { + Output out{ Output::SYNTAX_ERROR, rawtok->location, "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition" @@ -3710,7 +3710,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL std::string msg = "failed to evaluate " + std::string(rawtok->str() == IF ? "#if" : "#elif") + " condition"; if (e.what() && *e.what()) msg += std::string(", ") + e.what(); - Output out = { + Output out{ Output::SYNTAX_ERROR, rawtok->location, std::move(msg) From 814942b380d25437cbf69fd5b4addd1a49268309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Mon, 19 Jan 2026 17:31:43 +0100 Subject: [PATCH 078/104] test.cpp: improved testing of `__FILE__` (#623) --- test.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test.cpp b/test.cpp index ef7249e7..a14f588e 100644 --- a/test.cpp +++ b/test.cpp @@ -102,11 +102,11 @@ static std::string readfile(const char code[], std::size_t size, simplecpp::Outp return makeTokenList(code,size,files,std::string(),outputList).stringify(); } -static std::string preprocess(const char code[], const simplecpp::DUI &dui, simplecpp::OutputList *outputList) +static std::string preprocess(const char code[], const simplecpp::DUI &dui, simplecpp::OutputList *outputList, const std::string &file = std::string()) { std::vector files; simplecpp::FileDataCache cache; - simplecpp::TokenList tokens = makeTokenList(code,files); + simplecpp::TokenList tokens = makeTokenList(code,files, file); if (dui.removeComments) tokens.removeComments(); simplecpp::TokenList tokens2(files); @@ -120,6 +120,11 @@ static std::string preprocess(const char code[]) return preprocess(code, simplecpp::DUI(), nullptr); } +static std::string preprocess(const char code[], const std::string &file) +{ + return preprocess(code, simplecpp::DUI(), nullptr, file); +} + static std::string preprocess(const char code[], const simplecpp::DUI &dui) { return preprocess(code, dui, nullptr); @@ -193,7 +198,7 @@ static void backslash() static void builtin() { - ASSERT_EQUALS("\"\" 1 0", preprocess("__FILE__ __LINE__ __COUNTER__")); + ASSERT_EQUALS("\"test.c\" 1 0", preprocess("__FILE__ __LINE__ __COUNTER__", "test.c")); ASSERT_EQUALS("\n\n3", preprocess("\n\n__LINE__")); ASSERT_EQUALS("\n\n0", preprocess("\n\n__COUNTER__")); ASSERT_EQUALS("\n\n0 1", preprocess("\n\n__COUNTER__ __COUNTER__")); From 4c068867b2519ff03d65334230da690dd8355fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 21 Jan 2026 23:46:34 +0100 Subject: [PATCH 079/104] make it possible to explicitly disable the legacy `TokenList` constructors (#621) --- main.cpp | 1 + simplecpp.h | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/main.cpp b/main.cpp index 62ccc022..d67f94c2 100644 --- a/main.cpp +++ b/main.cpp @@ -3,6 +3,7 @@ * Copyright (C) 2016-2023 simplecpp team */ +#define SIMPLECPP_TOKENLIST_ALLOW_PTR 0 #include "simplecpp.h" #include diff --git a/simplecpp.h b/simplecpp.h index 46a43bc4..54f6a90c 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -57,7 +57,9 @@ #ifndef SIMPLECPP_TOKENLIST_ALLOW_PTR // still provide the legacy API in case we lack the performant wrappers # if !defined(__cpp_lib_string_view) && !defined(__cpp_lib_span) -# define SIMPLECPP_TOKENLIST_ALLOW_PTR +# define SIMPLECPP_TOKENLIST_ALLOW_PTR 1 +# else +# define SIMPLECPP_TOKENLIST_ALLOW_PTR 0 # endif #endif @@ -267,7 +269,7 @@ namespace simplecpp { TokenList(const unsigned char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) : TokenList(data, size-1, filenames, filename, outputList, 0) {} -#ifdef SIMPLECPP_TOKENLIST_ALLOW_PTR +#if SIMPLECPP_TOKENLIST_ALLOW_PTR /** generates a token list from the given buffer */ TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), OutputList *outputList = nullptr) : TokenList(data, size, filenames, filename, outputList, 0) From 863489a2ceb32f41776ea1c88a5e745ca4254d00 Mon Sep 17 00:00:00 2001 From: chrchr-github <78114321+chrchr-github@users.noreply.github.com> Date: Tue, 27 Jan 2026 11:57:53 +0100 Subject: [PATCH 080/104] Fix #615 User-defined literal created from alternative `and` (#620) --- simplecpp.cpp | 13 +++++++++---- test.cpp | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 9371eaf6..6f929fa8 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1005,6 +1005,14 @@ static bool isFloatSuffix(const simplecpp::Token *tok) return c == 'f' || c == 'l'; } +static const std::string AND("and"); +static const std::string BITAND("bitand"); +static const std::string BITOR("bitor"); +static bool isAlternativeAndBitandBitor(const simplecpp::Token* tok) +{ + return isAlternativeBinaryOp(tok, AND) || isAlternativeBinaryOp(tok, BITAND) || isAlternativeBinaryOp(tok, BITOR); +} + void simplecpp::TokenList::combineOperators() { std::stack executableScope; @@ -1040,7 +1048,7 @@ void simplecpp::TokenList::combineOperators() if (tok->previous && tok->previous->number && sameline(tok->previous, tok) && tok->previous->str().find_first_of("._") == std::string::npos) { tok->setstr(tok->previous->str() + '.'); deleteToken(tok->previous); - if (sameline(tok, tok->next) && (isFloatSuffix(tok->next) || (tok->next && tok->next->startsWithOneOf("AaBbCcDdEeFfPp")))) { + if (sameline(tok, tok->next) && (isFloatSuffix(tok->next) || (tok->next && tok->next->startsWithOneOf("AaBbCcDdEeFfPp") && !isAlternativeAndBitandBitor(tok->next)))) { tok->setstr(tok->str() + tok->next->str()); deleteToken(tok->next); } @@ -1285,8 +1293,6 @@ void simplecpp::TokenList::constFoldComparison(Token *tok) } } -static const std::string BITAND("bitand"); -static const std::string BITOR("bitor"); static const std::string XOR("xor"); void simplecpp::TokenList::constFoldBitwise(Token *tok) { @@ -1321,7 +1327,6 @@ void simplecpp::TokenList::constFoldBitwise(Token *tok) } } -static const std::string AND("and"); static const std::string OR("or"); void simplecpp::TokenList::constFoldLogicalOp(Token *tok) { diff --git a/test.cpp b/test.cpp index a14f588e..26a2fb49 100644 --- a/test.cpp +++ b/test.cpp @@ -433,6 +433,7 @@ static void combineOperators_floatliteral() ASSERT_EQUALS("1p + 3", preprocess("1p+3")); ASSERT_EQUALS("1.0_a . b", preprocess("1.0_a.b")); ASSERT_EQUALS("1_a . b", preprocess("1_a.b")); + ASSERT_EQUALS("bool x = d != 0. and b ;", preprocess("bool x = d != 0. and b;")); } static void combineOperators_increment() From d3cc78d0aa27a07d1189c9bc43d2bcac95ae5dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 28 Jan 2026 19:28:02 +0100 Subject: [PATCH 081/104] clang-tidy.yml: updated to Clang 22 (#514) --- .clang-tidy | 4 +- .github/workflows/clang-tidy.yml | 10 +- simplecpp.cpp | 263 +++++++++++++++++-------------- test.cpp | 3 +- 4 files changed, 152 insertions(+), 128 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index d16cc35c..e0b384bf 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -4,6 +4,7 @@ Checks: > -abseil-*, -altera-*, -android-*, + -boost-*, -cert-*, -clang-analyzer-*, -cppcoreguidelines-*, @@ -17,11 +18,12 @@ Checks: > -objc-*, -openmp-*, -zircon-*, - -boost-use-ranges, -bugprone-branch-clone, -bugprone-easily-swappable-parameters, -bugprone-narrowing-conversions, -bugprone-switch-missing-default-case, + -bugprone-throwing-static-initialization, + -bugprone-unchecked-string-to-number-conversion, -concurrency-mt-unsafe, -misc-no-recursion, -misc-non-private-member-variables-in-classes, diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 9109be9e..b2b32b7d 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -33,19 +33,19 @@ jobs: run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh - sudo ./llvm.sh 21 - sudo apt-get install clang-tidy-21 + sudo ./llvm.sh 22 + sudo apt-get install clang-tidy-22 - name: Verify clang-tidy configuration run: | - clang-tidy-21 --verify-config + clang-tidy-22 --verify-config - name: Prepare CMake run: | cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON env: - CXX: clang-21 + CXX: clang-22 - name: Clang-Tidy run: | - run-clang-tidy-21 -q -j $(nproc) -p=cmake.output + run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output diff --git a/simplecpp.cpp b/simplecpp.cpp index 6f929fa8..8263ddb3 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -351,121 +351,124 @@ class simplecpp::TokenList::Stream { bool isUtf16; }; -class StdIStream : public simplecpp::TokenList::Stream { -public: - // cppcheck-suppress uninitDerivedMemberVar - we call Stream::init() to initialize the private members - explicit StdIStream(std::istream &istr) - : istr(istr) { - assert(istr.good()); - init(); - } +namespace { + class StdIStream : public simplecpp::TokenList::Stream { + public: + // cppcheck-suppress uninitDerivedMemberVar - we call Stream::init() to initialize the private members + explicit StdIStream(std::istream &istr) + : istr(istr) { + assert(istr.good()); + init(); + } - int get() override { - return istr.get(); - } - int peek() override { - return istr.peek(); - } - void unget() override { - istr.unget(); - } - bool good() override { - return istr.good(); - } + int get() override { + return istr.get(); + } + int peek() override { + return istr.peek(); + } + void unget() override { + istr.unget(); + } + bool good() override { + return istr.good(); + } -private: - std::istream &istr; -}; + private: + std::istream &istr; + }; -class StdCharBufStream : public simplecpp::TokenList::Stream { -public: - // cppcheck-suppress uninitDerivedMemberVar - we call Stream::init() to initialize the private members - StdCharBufStream(const unsigned char* str, std::size_t size) - : str(str) - , size(size) - { - init(); - } + class StdCharBufStream : public simplecpp::TokenList::Stream { + public: + // cppcheck-suppress uninitDerivedMemberVar - we call Stream::init() to initialize the private members + StdCharBufStream(const unsigned char* str, std::size_t size) + : str(str) + , size(size) + { + init(); + } - int get() override { - if (pos >= size) - return lastStatus = EOF; - return str[pos++]; - } - int peek() override { - if (pos >= size) - return lastStatus = EOF; - return str[pos]; - } - void unget() override { - --pos; - } - bool good() override { - return lastStatus != EOF; - } + int get() override { + if (pos >= size) + return lastStatus = EOF; + return str[pos++]; + } + int peek() override { + if (pos >= size) + return lastStatus = EOF; + return str[pos]; + } + void unget() override { + --pos; + } + bool good() override { + return lastStatus != EOF; + } -private: - const unsigned char *str; - const std::size_t size; - std::size_t pos{}; - int lastStatus{}; -}; + private: + const unsigned char *str; + const std::size_t size; + std::size_t pos{}; + int lastStatus{}; + }; -class FileStream : public simplecpp::TokenList::Stream { -public: - /** - * @throws simplecpp::Output thrown if file is not found - */ - // cppcheck-suppress uninitDerivedMemberVar - we call Stream::init() to initialize the private members - explicit FileStream(const std::string &filename, std::vector &files) - : file(fopen(filename.c_str(), "rb")) - { - if (!file) { - files.emplace_back(filename); - throw simplecpp::Output(simplecpp::Output::FILE_NOT_FOUND, {}, "File is missing: " + filename); + class FileStream : public simplecpp::TokenList::Stream { + public: + /** + * @throws simplecpp::Output thrown if file is not found + */ + // cppcheck-suppress uninitDerivedMemberVar - we call Stream::init() to initialize the private members + explicit FileStream(const std::string &filename, std::vector &files) + : file(fopen(filename.c_str(), "rb")) + { + if (!file) { + files.emplace_back(filename); + throw simplecpp::Output(simplecpp::Output::FILE_NOT_FOUND, {}, "File is missing: " + filename); + } + init(); } - init(); - } - FileStream(const FileStream&) = delete; - FileStream &operator=(const FileStream&) = delete; + FileStream(const FileStream&) = delete; + FileStream &operator=(const FileStream&) = delete; - ~FileStream() override { - fclose(file); - file = nullptr; - } + ~FileStream() override { + fclose(file); + file = nullptr; + } - int get() override { - lastStatus = lastCh = fgetc(file); - return lastCh; - } - int peek() override { - // keep lastCh intact - const int ch = fgetc(file); - unget_internal(ch); - return ch; - } - void unget() override { - unget_internal(lastCh); - } - bool good() override { - return lastStatus != EOF; - } + int get() override { + lastStatus = lastCh = fgetc(file); + return lastCh; + } + int peek() override { + // keep lastCh intact + const int ch = fgetc(file); + unget_internal(ch); + return ch; + } + void unget() override { + unget_internal(lastCh); + } + bool good() override { + return lastStatus != EOF; + } -private: - void unget_internal(int ch) { - if (isUtf16) { - // TODO: use ungetc() as well - // UTF-16 has subsequent unget() calls - fseek(file, -1, SEEK_CUR); - } else - ungetc(ch, file); - } + private: + void unget_internal(int ch) { + if (isUtf16) { + // TODO: use ungetc() as well + // UTF-16 has subsequent unget() calls + fseek(file, -1, SEEK_CUR); + } else { + ungetc(ch, file); + } + } - FILE *file; - int lastCh{}; - int lastStatus{}; -}; + FILE *file; + int lastCh{}; + int lastStatus{}; + }; +} simplecpp::TokenList::TokenList(std::vector &filenames) : frontToken(nullptr), backToken(nullptr), files(filenames) {} @@ -1187,8 +1190,9 @@ void simplecpp::TokenList::constFoldMulDivRem(Token *tok) continue; long long result; - if (tok->op == '*') + if (tok->op == '*') { result = (stringToLL(tok->previous->str()) * stringToLL(tok->next->str())); + } else if (tok->op == '/' || tok->op == '%') { const long long rhs = stringToLL(tok->next->str()); if (rhs == 0) @@ -1200,8 +1204,9 @@ void simplecpp::TokenList::constFoldMulDivRem(Token *tok) result = (lhs / rhs); else result = (lhs % rhs); - } else + } else { continue; + } tok = tok->previous; tok->setstr(toString(result)); @@ -1422,8 +1427,9 @@ std::string simplecpp::TokenList::readUntil(Stream &stream, const Location &loca ret.erase(ret.size()-1U); backslash = (next == '\r'); update_ch = false; - } else if (next == '\\') + } else if (next == '\\') { update_ch = !update_ch; + } ret += next; } while (next == '\\'); if (update_ch) @@ -1544,8 +1550,9 @@ namespace simplecpp { if (this != &other) { files = other.files; valueDefinedInCode_ = other.valueDefinedInCode_; - if (other.tokenListDefine.empty()) + if (other.tokenListDefine.empty()) { parseDefine(other.nameTokDef); + } else { tokenListDefine = other.tokenListDefine; parseDefine(tokenListDefine.cfront()); @@ -1614,15 +1621,17 @@ namespace simplecpp { if (par==0) break; --par; - } else if (macro2tok->op == ')') + } else if (macro2tok->op == ')') { ++par; + } macro2tok = macro2tok->previous; } if (macro2tok) { // macro2tok->op == '(' macro2tok = macro2tok->previous; expandedmacros.insert(name()); - } else if (rawtok->op == '(') + } else if (rawtok->op == '(') { macro2tok = output2.back(); + } if (!macro2tok || !macro2tok->name) break; if (output2.cfront() != output2.cback() && macro2tok->str() == this->name()) @@ -1642,8 +1651,9 @@ namespace simplecpp { const Token *rawtok2 = rawtok; for (; rawtok2; rawtok2 = rawtok2->next) { rawtokens2.push_back(new Token(rawtok2->str(), loc)); - if (rawtok2->op == '(') + if (rawtok2->op == '(') { ++par; + } else if (rawtok2->op == ')') { if (par <= 1U) break; @@ -1840,16 +1850,18 @@ namespace simplecpp { parametertokens.emplace_back(nameTokInst->next); unsigned int par = 0U; for (const Token *tok = nameTokInst->next->next; calledInDefine ? sameline(tok, nameTokInst) : (tok != nullptr); tok = tok->next) { - if (tok->op == '(') + if (tok->op == '(') { ++par; + } else if (tok->op == ')') { if (par == 0U) { parametertokens.emplace_back(tok); break; } --par; - } else if (par == 0U && tok->op == ',' && (!variadic || parametertokens.size() < args.size())) + } else if (par == 0U && tok->op == ',' && (!variadic || parametertokens.size() < args.size())) { parametertokens.emplace_back(tok); + } } return parametertokens; } @@ -1877,8 +1889,9 @@ namespace simplecpp { tokens.back()->macro = name(); } - if (tok->op == '(') + if (tok->op == '(') { ++par; + } else if (tok->op == ')') { --par; if (par == 0U) @@ -1951,8 +1964,9 @@ namespace simplecpp { const MacroMap::const_iterator m = macros.find("__COUNTER__"); - if (!counter || m == macros.end()) + if (!counter || m == macros.end()) { parametertokens2.swap(parametertokens1); + } else { const Macro &counterMacro = m->second; unsigned int par = 0; @@ -2141,8 +2155,9 @@ namespace simplecpp { TokenList tokens(files); tokens.push_back(new Token(*tok)); const Token * tok2 = nullptr; - if (tok->next->op == '(') + if (tok->next->op == '(') { tok2 = appendTokens(tokens, loc, tok->next, macros, expandedmacros, parametertokens); + } else if (expandArg(tokens, tok->next, loc, macros, expandedmacros, parametertokens)) { tokens.front()->location = loc; if (tokens.cfront()->next && tokens.cfront()->next->op == '(') @@ -2318,12 +2333,15 @@ namespace simplecpp { const bool varargs = variadic && !args.empty() && B->str() == args[args.size()-1U]; if (expandArg(tokensB, B, parametertokens)) { - if (tokensB.empty()) + if (tokensB.empty()) { strAB = A->str(); - else if (varargs && A->op == ',') + } + else if (varargs && A->op == ',') { strAB = ","; - else if (varargs && unexpectedA) + } + else if (varargs && unexpectedA) { throw invalidHashHash::unexpectedToken(tok->location, name(), A); + } else { strAB = A->str() + tokensB.cfront()->str(); tokensB.deleteToken(tokensB.front()); @@ -2342,8 +2360,9 @@ namespace simplecpp { throw invalidHashHash::universalCharacterUB(tok->location, name(), A, strAB); } - if (varargs && tokensB.empty() && tok->previous->str() == ",") + if (varargs && tokensB.empty() && tok->previous->str() == ",") { output.deleteToken(A); + } else if (strAB != "," && macros.find(strAB) == macros.end()) { A->setstr(strAB); for (Token *b = tokensB.front(); b; b = b->next) @@ -2761,8 +2780,9 @@ long long simplecpp::characterLiteralToLL(const std::string& str) pos = 3; } else if (str.size() >= 2 && (str[0] == 'L' || str[0] == 'U') && str[1] == '\'') { pos = 2; - } else + } else { throw std::runtime_error("expected a character literal"); + } unsigned long long multivalue = 0; @@ -3597,8 +3617,9 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } bool conditionIsTrue; - if (ifstates.top() == AlwaysFalse || (ifstates.top() == ElseIsTrue && rawtok->str() != ELIF)) + if (ifstates.top() == AlwaysFalse || (ifstates.top() == ElseIsTrue && rawtok->str() != ELIF)) { conditionIsTrue = false; + } else if (rawtok->str() == IFDEF) { conditionIsTrue = (macros.find(rawtok->next->str()) != macros.end() || (hasInclude && rawtok->next->str() == HAS_INCLUDE)); maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); diff --git a/test.cpp b/test.cpp index 26a2fb49..fa94a266 100644 --- a/test.cpp +++ b/test.cpp @@ -67,8 +67,9 @@ static void assertThrowFailed(int line) static void testcase(const std::string &name, void (*f)(), int argc, char * const *argv) { - if (argc == 1) + if (argc == 1) { f(); + } else { for (int i = 1; i < argc; i++) { if (name == argv[i]) From 5e00b6083ae36b0e156b2eb6e8dc2e7fe225716f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 10 Feb 2026 14:39:09 +0100 Subject: [PATCH 082/104] removed workarounds for Visual Studio conflicts with Cppcheck functions with same names (#627) --- simplecpp.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 8263ddb3..7afc17ab 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -67,14 +67,12 @@ static bool isOct(const std::string &s) return s.size()>1 && (s[0]=='0') && (s[1] >= '0') && (s[1] < '8'); } -// TODO: added an undercore since this conflicts with a function of the same name in utils.h from Cppcheck source when building Cppcheck with MSBuild -static bool isStringLiteral_(const std::string &s) +static bool isStringLiteral(const std::string &s) { return s.size() > 1 && (s[0]=='\"') && (*s.rbegin()=='\"'); } -// TODO: added an undercore since this conflicts with a function of the same name in utils.h from Cppcheck source when building Cppcheck with MSBuild -static bool isCharLiteral_(const std::string &s) +static bool isCharLiteral(const std::string &s) { // char literal patterns can include 'a', '\t', '\000', '\xff', 'abcd', and maybe '' // This only checks for the surrounding '' but doesn't parse the content. @@ -2295,7 +2293,7 @@ namespace simplecpp { throw invalidHashHash::unexpectedNewline(tok->location, name()); const bool canBeConcatenatedWithEqual = A->isOneOf("+-*/%&|^") || A->str() == "<<" || A->str() == ">>"; - const bool canBeConcatenatedStringOrChar = isStringLiteral_(A->str()) || isCharLiteral_(A->str()); + const bool canBeConcatenatedStringOrChar = isStringLiteral(A->str()) || isCharLiteral(A->str()); const bool unexpectedA = (!A->name && !A->number && !A->str().empty() && !canBeConcatenatedWithEqual && !canBeConcatenatedStringOrChar); const Token * const B = tok->next->next; From 8a30993ececc91d9540832dd70e943727dd0050e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 10 Mar 2026 10:36:31 +0100 Subject: [PATCH 083/104] reverted to Clang 21 for now (#630) --- .clang-tidy | 2 -- .github/workflows/clang-tidy.yml | 10 +++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index e0b384bf..ba15bb0f 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -22,8 +22,6 @@ Checks: > -bugprone-easily-swappable-parameters, -bugprone-narrowing-conversions, -bugprone-switch-missing-default-case, - -bugprone-throwing-static-initialization, - -bugprone-unchecked-string-to-number-conversion, -concurrency-mt-unsafe, -misc-no-recursion, -misc-non-private-member-variables-in-classes, diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index b2b32b7d..9109be9e 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -33,19 +33,19 @@ jobs: run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh - sudo ./llvm.sh 22 - sudo apt-get install clang-tidy-22 + sudo ./llvm.sh 21 + sudo apt-get install clang-tidy-21 - name: Verify clang-tidy configuration run: | - clang-tidy-22 --verify-config + clang-tidy-21 --verify-config - name: Prepare CMake run: | cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON env: - CXX: clang-22 + CXX: clang-21 - name: Clang-Tidy run: | - run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output + run-clang-tidy-21 -q -j $(nproc) -p=cmake.output From efb55b81fd6addfd23f608cce33f0eef02960752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 15 Mar 2026 18:45:54 +0100 Subject: [PATCH 084/104] clang-tidy.yml: updated to Clang 22 - again (#633) --- .clang-tidy | 2 ++ .github/workflows/clang-tidy.yml | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index ba15bb0f..e0b384bf 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -22,6 +22,8 @@ Checks: > -bugprone-easily-swappable-parameters, -bugprone-narrowing-conversions, -bugprone-switch-missing-default-case, + -bugprone-throwing-static-initialization, + -bugprone-unchecked-string-to-number-conversion, -concurrency-mt-unsafe, -misc-no-recursion, -misc-non-private-member-variables-in-classes, diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 9109be9e..b2b32b7d 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -33,19 +33,19 @@ jobs: run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh - sudo ./llvm.sh 21 - sudo apt-get install clang-tidy-21 + sudo ./llvm.sh 22 + sudo apt-get install clang-tidy-22 - name: Verify clang-tidy configuration run: | - clang-tidy-21 --verify-config + clang-tidy-22 --verify-config - name: Prepare CMake run: | cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON env: - CXX: clang-21 + CXX: clang-22 - name: Clang-Tidy run: | - run-clang-tidy-21 -q -j $(nproc) -p=cmake.output + run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output From ae1f0fbdfc626548398e80676928d5bfd01e0688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 15 Mar 2026 20:06:17 +0100 Subject: [PATCH 085/104] added basic test coverage for `IfCond` and `MacroUsage` (#629) --- test.cpp | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/test.cpp b/test.cpp index fa94a266..1dd59587 100644 --- a/test.cpp +++ b/test.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -103,7 +104,7 @@ static std::string readfile(const char code[], std::size_t size, simplecpp::Outp return makeTokenList(code,size,files,std::string(),outputList).stringify(); } -static std::string preprocess(const char code[], const simplecpp::DUI &dui, simplecpp::OutputList *outputList, const std::string &file = std::string()) +static std::string preprocess(const char code[], const simplecpp::DUI &dui, simplecpp::OutputList *outputList, std::list *macroUsage = nullptr, std::list *ifCond = nullptr, const std::string &file = std::string()) { std::vector files; simplecpp::FileDataCache cache; @@ -111,7 +112,7 @@ static std::string preprocess(const char code[], const simplecpp::DUI &dui, simp if (dui.removeComments) tokens.removeComments(); simplecpp::TokenList tokens2(files); - simplecpp::preprocess(tokens2, tokens, files, cache, dui, outputList); + simplecpp::preprocess(tokens2, tokens, files, cache, dui, outputList, macroUsage, ifCond); simplecpp::cleanup(cache); return tokens2.stringify(); } @@ -123,7 +124,7 @@ static std::string preprocess(const char code[]) static std::string preprocess(const char code[], const std::string &file) { - return preprocess(code, simplecpp::DUI(), nullptr, file); + return preprocess(code, simplecpp::DUI(), nullptr, nullptr, nullptr, file); } static std::string preprocess(const char code[], const simplecpp::DUI &dui) @@ -136,6 +137,16 @@ static std::string preprocess(const char code[], simplecpp::OutputList *outputLi return preprocess(code, simplecpp::DUI(), outputList); } +static std::string preprocess(const char code[], std::list *ifCond) +{ + return preprocess(code, simplecpp::DUI(), nullptr, nullptr, ifCond); +} + +static std::string preprocess(const char code[], std::list *macroUsage) +{ + return preprocess(code, simplecpp::DUI(), nullptr, macroUsage); +} + static std::string toString(const simplecpp::OutputList &outputList) { std::ostringstream ostr; @@ -3461,6 +3472,70 @@ static void bad_macro_syntax() // #616 ASSERT_EQUALS("bad macro syntax. macroname=\" value=1", outputList.cbegin()->msg); } +static void ifCond() +{ + { + const char code[] = "int i;"; + std::list ifCond; + ASSERT_EQUALS("int i ;", preprocess(code, &ifCond)); + ASSERT_EQUALS(0, ifCond.size()); + } + { + const char code[] = "#if 0\n" + "# elif __GNUC__ == 1\n" + "# elif defined(__APPLE__)\n" + "#endif\n"; + std::list ifCond; + ASSERT_EQUALS("", preprocess(code, &ifCond)); + ASSERT_EQUALS(3, ifCond.size()); + auto it = ifCond.cbegin(); + ASSERT_EQUALS(0, it->location.fileIndex); + ASSERT_EQUALS(1, it->location.line); + ASSERT_EQUALS(2, it->location.col); + ASSERT_EQUALS("0", it->E); + ASSERT_EQUALS(0, it->result); + ++it; + ASSERT_EQUALS(0, it->location.fileIndex); + ASSERT_EQUALS(2, it->location.line); + ASSERT_EQUALS(3, it->location.col); + ASSERT_EQUALS("__GNUC__ == 1", it->E); + ASSERT_EQUALS(0, it->result); + ++it; + ASSERT_EQUALS(0, it->location.fileIndex); + ASSERT_EQUALS(3, it->location.line); + ASSERT_EQUALS(4, it->location.col); + ASSERT_EQUALS("0", it->E); + ASSERT_EQUALS(0, it->result); + } +} + +static void macroUsage() +{ + { + const char code[] = "int i;"; + std::list macroUsage; + ASSERT_EQUALS("int i ;", preprocess(code, ¯oUsage)); + ASSERT_EQUALS(0, macroUsage.size()); + } + { + const char code[] = "#define DEF_1\n" + "#ifdef DEF_1\n" + "#endif\n"; + std::list macroUsage; + ASSERT_EQUALS("", preprocess(code, ¯oUsage)); + ASSERT_EQUALS(1, macroUsage.size()); + auto it = macroUsage.cbegin(); + ASSERT_EQUALS("DEF_1", it->macroName); + ASSERT_EQUALS(0, it->macroLocation.fileIndex); + ASSERT_EQUALS(1, it->macroLocation.line); + ASSERT_EQUALS(9, it->macroLocation.col); + ASSERT_EQUALS(true, it->macroValueKnown); + ASSERT_EQUALS(0, it->useLocation.fileIndex); + ASSERT_EQUALS(2, it->useLocation.line); + ASSERT_EQUALS(8, it->useLocation.col); + } +} + static void isAbsolutePath() { #ifdef _WIN32 ASSERT_EQUALS(true, simplecpp::isAbsolutePath("C:\\foo\\bar")); @@ -3790,6 +3865,9 @@ int main(int argc, char **argv) TEST_CASE(bad_macro_syntax); + TEST_CASE(ifCond); + TEST_CASE(macroUsage); + TEST_CASE(fuzz_crash); TEST_CASE(leak); From f437d61a26ccf3a7163456d14a507db6b1de5e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 27 Mar 2026 18:16:06 +0100 Subject: [PATCH 086/104] fixed #632 - avoid unhandled `simplecpp::Macro::Error` in `simplecpp::preprocess` with `-D` (#631) --- simplecpp.cpp | 22 ++++++++++++++++++---- test.cpp | 14 ++++++++++++-- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 7afc17ab..b59f773c 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1739,6 +1739,9 @@ namespace simplecpp { return tok; } + /** + * @throws Error thrown in case of __VA_OPT__ issues + */ bool parseDefine(const Token *nametoken) { nameTokDef = nametoken; variadic = false; @@ -3379,6 +3382,17 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } output.clear(); return; + } catch (const simplecpp::Macro::Error& e) { + if (outputList) { + simplecpp::Output err{ + Output::DUI_ERROR, + {}, + e.what + }; + outputList->emplace_back(std::move(err)); + } + output.clear(); + return; } } @@ -3507,14 +3521,14 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL else it->second = macro; } - } catch (const std::runtime_error &) { + } catch (const std::runtime_error &err) { if (outputList) { - simplecpp::Output err{ + simplecpp::Output out{ Output::SYNTAX_ERROR, rawtok->location, - "Failed to parse #define" + std::string("Failed to parse #define, ") + err.what() }; - outputList->emplace_back(std::move(err)); + outputList->emplace_back(std::move(out)); } output.clear(); return; diff --git a/test.cpp b/test.cpp index 1dd59587..b0bf0b4f 100644 --- a/test.cpp +++ b/test.cpp @@ -698,7 +698,7 @@ static void define_invalid_1() const char code[] = "#define A(\nB\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); - ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define\n", toString(outputList)); + ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, bad macro syntax\n", toString(outputList)); } static void define_invalid_2() @@ -706,7 +706,7 @@ static void define_invalid_2() const char code[] = "#define\nhas#"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); - ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define\n", toString(outputList)); + ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, bad macro syntax\n", toString(outputList)); } static void define_define_1() @@ -1105,6 +1105,15 @@ static void define_va_opt_8() ASSERT_EQUALS("", toString(outputList)); } +static void define_va_opt_9() +{ + simplecpp::DUI dui; + dui.defines.emplace_back("f(...)=__VA_OPT__"); + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess("", dui, &outputList)); + ASSERT_EQUALS("file0,0,dui_error,In definition of 'f': Missing opening parenthesis for __VA_OPT__\n", toString(outputList)); +} + static void define_ifdef() { const char code[] = "#define A(X) X\n" @@ -3674,6 +3683,7 @@ int main(int argc, char **argv) TEST_CASE(define_va_opt_6); TEST_CASE(define_va_opt_7); TEST_CASE(define_va_opt_8); + TEST_CASE(define_va_opt_9); // #632 TEST_CASE(pragma_backslash); // multiline pragma directive From 87c13d6709762fa722beea544002a4e7a44761b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Thu, 9 Apr 2026 15:33:19 +0200 Subject: [PATCH 087/104] clang-tidy.yml: run clang-tidy with C++23 (#635) --- .github/workflows/clang-tidy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index b2b32b7d..333672d7 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -42,7 +42,7 @@ jobs: - name: Prepare CMake run: | - cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=23 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON env: CXX: clang-22 From 6521caaab5024148138c4e0954d5fc966dd11cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Thu, 9 Apr 2026 16:22:13 +0200 Subject: [PATCH 088/104] CI-mingw.yml: exclude `clang++` on `MINGW32` as it is no longer available (#644) --- .github/workflows/CI-mingw.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/CI-mingw.yml b/.github/workflows/CI-mingw.yml index a3bac5cf..390de83d 100644 --- a/.github/workflows/CI-mingw.yml +++ b/.github/workflows/CI-mingw.yml @@ -33,6 +33,9 @@ jobs: exclude: - msystem: CLANG64 compiler: g++ + # the mingw-w64-i686-clang package is no longer available + - msystem: MINGW32 + compiler: clang++ fail-fast: false runs-on: windows-2025 From 7360858b281db99cd5a7e6da6a0558ca6aa8d9bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Thu, 9 Apr 2026 17:45:28 +0200 Subject: [PATCH 089/104] CI-windows.yml: updated Python to 3.14 (#642) --- .github/workflows/CI-windows.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 521bc24c..cfced0d2 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -32,10 +32,10 @@ jobs: - name: Setup msbuild.exe uses: microsoft/setup-msbuild@v2 - - name: Set up Python 3.13 + - name: Set up Python uses: actions/setup-python@v6 with: - python-version: '3.13' + python-version: '3.14' check-latest: true - name: Install missing Python packages From 1d0c78039150157f91972b854f464347a4b9df71 Mon Sep 17 00:00:00 2001 From: chrchr-github <78114321+chrchr-github@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:25:08 +0200 Subject: [PATCH 090/104] Refs #638 Fix stylistic issues uncovered by cppcheck (#643) --- simplecpp.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simplecpp.h b/simplecpp.h index 54f6a90c..49aa600b 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -83,7 +83,7 @@ namespace simplecpp { , mSize(strlen(data)) {} - // only provide when std::span is not available so using untyped initilization won't use View + // only provide when std::span is not available so using untyped initialization won't use View #if !defined(__cpp_lib_span) View(const char* data, std::size_t size) : mData(data) @@ -376,7 +376,7 @@ namespace simplecpp { const std::string& file(const Location& loc) const; private: - TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, OutputList *outputList, int unused); + TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, OutputList *outputList, int /*unused*/); void combineOperators(); From 7e7a525ab1afb4fd7c16f357950ed1d1e213ff19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 10 Apr 2026 14:07:33 +0200 Subject: [PATCH 091/104] CI-unixish.yml: added `ubuntu-22.04-arm`, `ubuntu-24.04-arm` and `macos-26-intel` (#640) --- .github/workflows/CI-unixish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index 6fd8a429..1a4ce649 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: - os: [ubuntu-22.04, ubuntu-24.04, macos-14, macos-15, macos-15-intel, macos-26] + os: [ubuntu-22.04, ubuntu-22.04-arm, ubuntu-24.04, ubuntu-24.04-arm, macos-14, macos-15, macos-15-intel, macos-26, macos-26-intel] compiler: [clang++] include: - os: ubuntu-22.04 From 4044f8fcdf175814668c17ea4a768a751ba1313c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 12 Apr 2026 09:04:34 +0200 Subject: [PATCH 092/104] main.cpp: fixed handling of options without value (#645) --- main.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/main.cpp b/main.cpp index d67f94c2..5c48f830 100644 --- a/main.cpp +++ b/main.cpp @@ -86,7 +86,7 @@ int main(int argc, char **argv) break; } dui.includes.emplace_back(std::move(value)); - } else if (std::strncmp(arg, "-is",3)==0) { + } else if (std::strcmp(arg, "-is")==0) { found = true; use_istream = true; } @@ -104,20 +104,28 @@ int main(int argc, char **argv) } break; case 'q': - found = true; - quiet = true; + if (std::strcmp(arg, "-q")==0) { + found = true; + quiet = true; + } break; case 'e': - found = true; - error_only = true; + if (std::strcmp(arg, "-e")==0) { + found = true; + error_only = true; + } break; case 'f': - found = true; - fail_on_error = true; + if (std::strcmp(arg, "-f")==0) { + found = true; + fail_on_error = true; + } break; case 'l': - linenrs = true; - found = true; + if (std::strcmp(arg, "-l")==0) { + linenrs = true; + found = true; + } break; } if (!found) { From ac5cbe3408ad76c2c845c66ccbc2680e93a5c80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 13 Apr 2026 10:49:46 +0200 Subject: [PATCH 093/104] Fix #590 (Incorrect expansion of functional macros) (#646) --- simplecpp.cpp | 2 ++ simplecpp.h | 3 +++ test.cpp | 11 +++++++++++ 3 files changed, 16 insertions(+) diff --git a/simplecpp.cpp b/simplecpp.cpp index b59f773c..a0e8caa8 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2204,6 +2204,8 @@ namespace simplecpp { } output.push_back(newMacroToken(tok->str(), loc, true, tok)); + if (it != macros.end()) + output.back()->markExpandedFrom(&it->second); return tok->next; } diff --git a/simplecpp.h b/simplecpp.h index 49aa600b..b744e62d 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -213,6 +213,9 @@ namespace simplecpp { bool isExpandedFrom(const Macro* m) const { return mExpandedFrom.find(m) != mExpandedFrom.end(); } + void markExpandedFrom(const Macro* m) { + mExpandedFrom.insert(m); + } void printAll() const; void printOut() const; diff --git a/test.cpp b/test.cpp index b0bf0b4f..bc8cb4d5 100644 --- a/test.cpp +++ b/test.cpp @@ -946,6 +946,16 @@ static void define_define_23() // #403 crash (infinite recursion) ASSERT_EQUALS("\n\n\n\nYdieZ ( void ) ;", preprocess(code)); } +static void define_define_24() // #590 +{ + const char code[] = "#define B A\n" + "#define A x(B)\n" + "#define C(s) s\n" + "#define D(s) C(s)\n" + "D(A)\n"; + ASSERT_EQUALS("\n\n\n\nx ( A )", preprocess(code)); +} + static void define_va_args_1() { const char code[] = "#define A(fmt...) dostuff(fmt)\n" @@ -3671,6 +3681,7 @@ int main(int argc, char **argv) TEST_CASE(define_define_21); TEST_CASE(define_define_22); // #400 TEST_CASE(define_define_23); // #403 - crash, infinite recursion + TEST_CASE(define_define_24); // #590 TEST_CASE(define_va_args_1); TEST_CASE(define_va_args_2); TEST_CASE(define_va_args_3); From ea9d3cbb19207e21e2ce4708b4c7076e11a8fd85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Thu, 16 Apr 2026 13:51:08 +0200 Subject: [PATCH 094/104] Refs #638: Avoid shadowing arguments (#639) --- simplecpp.cpp | 6 +++--- simplecpp.h | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index a0e8caa8..a7ced05a 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -635,10 +635,10 @@ static bool isStringLiteralPrefix(const std::string &str) str == "R" || str == "uR" || str == "UR" || str == "LR" || str == "u8R"; } -void simplecpp::TokenList::lineDirective(unsigned int fileIndex, unsigned int line, Location &location) +void simplecpp::TokenList::lineDirective(unsigned int fileIndex_, unsigned int line, Location &location) { - if (fileIndex != location.fileIndex || line >= location.line) { - location.fileIndex = fileIndex; + if (fileIndex_ != location.fileIndex || line >= location.line) { + location.fileIndex = fileIndex_; location.line = line; return; } diff --git a/simplecpp.h b/simplecpp.h index b744e62d..f29166ff 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -174,9 +174,9 @@ namespace simplecpp { bool isOneOf(const char ops[]) const; bool startsWithOneOf(const char c[]) const; bool endsWithOneOf(const char c[]) const; - static bool isNumberLike(const std::string& str) { - return std::isdigit(static_cast(str[0])) || - (str.size() > 1U && (str[0] == '-' || str[0] == '+') && std::isdigit(static_cast(str[1]))); + static bool isNumberLike(const std::string& s) { + return std::isdigit(static_cast(s[0])) || + (s.size() > 1U && (s[0] == '-' || s[0] == '+') && std::isdigit(static_cast(s[1]))); } TokenString macro; @@ -399,7 +399,7 @@ namespace simplecpp { void constFoldQuestionOp(Token *&tok1); std::string readUntil(Stream &stream, const Location &location, char start, char end, OutputList *outputList); - void lineDirective(unsigned int fileIndex, unsigned int line, Location &location); + void lineDirective(unsigned int fileIndex_, unsigned int line, Location &location); const Token* lastLineTok(int maxsize=1000) const; const Token* isLastLinePreprocessor(int maxsize=1000) const; From 316d4ee8e489ceaf1b9926df24d37b784d3655a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 17 Apr 2026 00:14:30 +0200 Subject: [PATCH 095/104] test.cpp: also run tests with char buffer (#261) --- test.cpp | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/test.cpp b/test.cpp index bc8cb4d5..b654e159 100644 --- a/test.cpp +++ b/test.cpp @@ -6,6 +6,7 @@ #include "simplecpp.h" #include +#include #include #include #include @@ -25,6 +26,15 @@ #define STRINGIZE(x) STRINGIZE_(x) static const std::string testSourceDir = SIMPLECPP_TEST_SOURCE_DIR; + +namespace { + enum class Input : std::uint8_t { + Stringstream, + CharBuffer + }; +} + +static Input USE_INPUT = Input::Stringstream; static int numberOfFailedAssertions = 0; #define ASSERT_EQUALS(expected, actual) (assertEquals((expected), (actual), __LINE__)) @@ -41,11 +51,21 @@ static std::string pprint(const std::string &in) return ret; } +static const char* inputString(Input input) { + switch (input) { + case Input::Stringstream: + return "Stringstream"; + case Input::CharBuffer: + return "CharBuffer"; + } + return ""; // unreachable - needed for GCC and Visual Studio +} + static int assertEquals(const std::string &expected, const std::string &actual, int line) { if (expected != actual) { numberOfFailedAssertions++; - std::cerr << "------ assertion failed ---------" << std::endl; + std::cerr << "------ assertion failed (" << inputString(USE_INPUT) << ")---------" << std::endl; std::cerr << "line test.cpp:" << line << std::endl; std::cerr << "expected:" << pprint(expected) << std::endl; std::cerr << "actual:" << pprint(actual) << std::endl; @@ -83,8 +103,16 @@ static void testcase(const std::string &name, void (*f)(), int argc, char * cons static simplecpp::TokenList makeTokenList(const char code[], std::size_t size, std::vector &filenames, const std::string &filename=std::string(), simplecpp::OutputList *outputList=nullptr) { - std::istringstream istr(std::string(code, size)); - return {istr,filenames,filename,outputList}; + switch (USE_INPUT) { + case Input::Stringstream: { + std::istringstream istr(std::string(code, size)); + return {istr,filenames,filename,outputList}; + } + case Input::CharBuffer: + return {{code, size}, filenames, filename, outputList}; + } + + return simplecpp::TokenList{filenames}; // unreachable - needed for GCC and Visual Studio } static simplecpp::TokenList makeTokenList(const char code[], std::vector &filenames, const std::string &filename=std::string(), simplecpp::OutputList *outputList=nullptr) @@ -3619,8 +3647,10 @@ static void leak() } } -int main(int argc, char **argv) +static void runTests(int argc, char **argv, Input input) { + USE_INPUT = input; + TEST_CASE(backslash); TEST_CASE(builtin); @@ -3892,6 +3922,11 @@ int main(int argc, char **argv) TEST_CASE(fuzz_crash); TEST_CASE(leak); +} +int main(int argc, char **argv) +{ + runTests(argc, argv, Input::Stringstream); + runTests(argc, argv, Input::CharBuffer); return numberOfFailedAssertions > 0 ? EXIT_FAILURE : EXIT_SUCCESS; } From 470196af7c753d3aee222c1534c63e5da869335e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 17 Apr 2026 23:27:32 +0200 Subject: [PATCH 096/104] CI-unixish.yml: added some missing `g++` cases to matrix (#649) --- .github/workflows/CI-unixish.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index 1a4ce649..cdda088b 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -15,8 +15,12 @@ jobs: include: - os: ubuntu-22.04 compiler: g++ + - os: ubuntu-22.04-arm + compiler: g++ - os: ubuntu-24.04 compiler: g++ + - os: ubuntu-24.04-arm + compiler: g++ fail-fast: false runs-on: ${{ matrix.os }} From 24136a07c2ee0ef8a383a18483dbde20c4a8cf16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 26 Apr 2026 03:00:53 +0200 Subject: [PATCH 097/104] added tests for various fixed issues (#648) --- test.cpp | 195 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) diff --git a/test.cpp b/test.cpp index b654e159..cd6a4db5 100644 --- a/test.cpp +++ b/test.cpp @@ -719,6 +719,181 @@ static void define13() "}", preprocess(code)); } +static void define14() // #296 +{ + const char code[] = "#define bar(x) x % 2\n" + "#define foo(x) printf(#x \"\\n\")\n" + "\n" + " foo(bar(3));\n"; + ASSERT_EQUALS("\n" + "\n" + "\n" + "printf ( \"bar(3)\" \"\\n\" ) ;", preprocess(code)); +} + +static void define15() // #231 +{ + const char code[] = "#define CAT(a, b) CAT2(a, b)\n" + "#define CAT2(a, b) a ## b\n" + "#define FOO x\n" + "#define BAR() CAT(F, OO)\n" + "#define BAZ CAT(B, AR)()\n" + "BAZ\n"; + ASSERT_EQUALS("\n" + "\n" + "\n" + "\n" + "\n" + "x", preprocess(code)); +} + +static void define16() // #201 +{ + const char code[] = "#define ALL_COLORS(warm_colors) \\\n" + " X(Blue) \\\n" + " X(Green) \\\n" + " X(Purple) \\\n" + " warm_colors\n" + "\n" + "#define WARM_COLORS \\\n" + " X(Red) \\\n" + " X(Yellow) \\\n" + " X(Orange)\n" + "\n" + "#define COLOR_SET ALL_COLORS(WARM_COLORS)\n" + "\n" + "#define X(color) #color,\n" + "\n" + "COLOR_SET\n"; + ASSERT_EQUALS("\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\"Blue\" , \"Green\" , \"Purple\" , \"Red\" , \"Yellow\" , \"Orange\" ,", preprocess(code)); +} + +static void define17() // #185 +{ + const char code[] = "#define at(x, y) x##y\n" + "#define b(...) \\\n" + "aa(__VA_ARGS__, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , \\\n" + ", , , , , , , , 2)\n" + "#define aa(c, d, a, b, e, f, g, h, ab, ac, i, ad, j, k, l, m, n, o, p, ae, q, \\\n" + "r, s, t, u, v, w, x, y, z, af, ag, ah, ai, aj, ak, al, am, an, ao, \\\n" + "ap) \\\n" + "ap\n" + "#define aq(...) ar(b(__VA_ARGS__), __VA_ARGS__) static_assert(true, \" \")\n" + "#define ar(ap, ...) at(I_, ap)(__VA_ARGS__)\n" + "#define I_2(as, a)\n" + "aq(a, array);\n"; + ASSERT_EQUALS("\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "static_assert ( true , \" \" ) ;", preprocess(code)); +} + +static void define18() // #130 +{ + const char code[] = "#define MAC2STR(x) x[0],x[1],x[2],x[3],x[4],x[5]\n" + "#define FT_DEBUG(fmt, args...) if(pGlobalCtx && pGlobalCtx->debug_level>=2) printf(\"FT-dbg: \"fmt, ##args)\n" + "\n" + "FT_DEBUG(\" %02x:%02x:%02x:%02x:%02x:%02x\\n\", MAC2STR(pCtx->wlan_intf_addr[i]));\n"; + ASSERT_EQUALS("\n" + "\n" + "\n" + "if ( pGlobalCtx && pGlobalCtx -> debug_level >= 2 ) printf ( \"FT-dbg: \" \" %02x:%02x:%02x:%02x:%02x:%02x\\n\" , pCtx -> wlan_intf_addr [ i ] [ 0 ] , pCtx -> wlan_intf_addr [ i ] [ 1 ] , pCtx -> wlan_intf_addr [ i ] [ 2 ] , pCtx -> wlan_intf_addr [ i ] [ 3 ] , pCtx -> wlan_intf_addr [ i ] [ 4 ] , pCtx -> wlan_intf_addr [ i ] [ 5 ] ) ;", preprocess(code)); +} + +static void define19() // #124 +{ + const char code[] = "#define CONCAT(tok) tok##suffix\n" + "\n" + "CONCAT(Test);\n" + "CONCAT(const Test);\n"; + ASSERT_EQUALS("\n" + "\n" + "Testsuffix ;\n" + "const Testsuffix ;", preprocess(code)); +} + +static void define20() // #113 +{ + const char code[] = "#define TARGS4 T1,T2,T3,T4\n" + "#define FOOIMPL(T__CLASS, TARGS) void foo(const T__CLASS& x) { }\n" + "#define FOOIMPL_4(T__CLASS) FOOIMPL(T__CLASS, TARGS4)\n" + "FOOIMPL_4(y)\n"; + ASSERT_EQUALS("\n" + "\n" + "\n" + "void foo ( const y < T1 , T2 , T3 , T4 > & x ) { }", preprocess(code)); +} + +static void define21() // #66 +{ + const char code[] = "#define GETMYID(a) ((a))+1\n" + "#define FIGHT_FOO(c, ...) foo(c, ##__VA_ARGS__)\n" + "FIGHT_FOO(1, GETMYID(a));\n"; + ASSERT_EQUALS("\n" + "\n" + "foo ( 1 , ( ( a ) ) + 1 ) ;", preprocess(code)); +} + +static void define22() // #40 +{ + const char code[] = "#define COUNTER_NAME(NAME, ...) NAME##Count\n" + "#define COMMA ,\n" + "\n" + "#define DECLARE_COUNTERS(LIST) unsigned long LIST(COUNTER_NAME, COMMA);\n" + "\n" + "#define ACTUAL_LIST(FUNCTION, SEPARATOR) \\\n" + "FUNCTION(event1, int, foo) SEPARATOR \\\n" + "FUNCTION(event2, char, bar)\n" + "\n" + "DECLARE_COUNTERS(ACTUAL_LIST)\n"; + ASSERT_EQUALS("\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "unsigned long event1Count , event2Count ;", preprocess(code)); +} + +static void define23() // #40 +{ + const char code[] = "#define COMMA ,\n" + "#define MULTI(SEPARATOR) A SEPARATOR B\n" + "\n" + "#define VARS MULTI(COMMA)\n" + "unsigned VARS;\n"; + ASSERT_EQUALS("\n" + "\n" + "\n" + "\n" + "unsigned A , B ;", preprocess(code)); +} static void define_invalid_1() @@ -1181,6 +1356,15 @@ static void pragma_backslash() ASSERT_EQUALS("", preprocess(code, &outputList)); } +static void pragma_backslash_2() // #217 +{ + const char code[] = "#pragma comment(linker, \"foo \\\n" + "bar\")\n"; + + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); +} + static void dollar() { ASSERT_EQUALS("$ab", readfile("$ab")); @@ -3685,6 +3869,16 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(define11); TEST_CASE(define12); TEST_CASE(define13); + TEST_CASE(define14); // #296 + TEST_CASE(define15); // #231 + TEST_CASE(define16); // #201 + TEST_CASE(define17); // #185 + TEST_CASE(define18); // #130 + TEST_CASE(define19); // #124 + TEST_CASE(define20); // #113 + TEST_CASE(define21); // #66 + TEST_CASE(define22); // #40 + TEST_CASE(define23); // #40 TEST_CASE(define_invalid_1); TEST_CASE(define_invalid_2); TEST_CASE(define_define_1); @@ -3727,6 +3921,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(define_va_opt_9); // #632 TEST_CASE(pragma_backslash); // multiline pragma directive + TEST_CASE(pragma_backslash_2); // #217 // UB: #ifdef as macro parameter TEST_CASE(define_ifdef); From 2f254c2d5df2b08050ce43522236ae52a40640d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 28 Apr 2026 12:46:20 +0200 Subject: [PATCH 098/104] README.md: added note about repository move [skip ci] (#650) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e4bdb2f1..dae242ea 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Simple C/C++ preprocessor +NOTE: This repository was recently moved from https://github.com/danmar/simplecpp to https://github.com/cppcheck-opensource/simplecpp. Please make sure to update all your references to it accordingly. + This is a simple C/C++ preprocessor. The goal is to have good conformance with the C and C++ standards and to handle nonstandard preprocessor extensions in gcc / clang / visual studio preprocessors. Most of the preprocessor testcases in gcc and clang are handled OK by simplecpp. From 24f285138c4cdc32a59e0779685962873410bd02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Thu, 30 Apr 2026 10:32:43 +0200 Subject: [PATCH 099/104] main.cpp: added option `-input=file|fstream|fstream|buffer` to specify the `TokenList` interface / removed `-is` (#647) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Marjamäki --- main.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/main.cpp b/main.cpp index 5c48f830..685ada12 100644 --- a/main.cpp +++ b/main.cpp @@ -6,12 +6,14 @@ #define SIMPLECPP_TOKENLIST_ALLOW_PTR 0 #include "simplecpp.h" +#include #include #include #include #include -#include +#include #include +#include #include #include @@ -28,7 +30,12 @@ int main(int argc, char **argv) { bool error = false; const char *filename = nullptr; - bool use_istream = false; + enum : std::uint8_t { + File, + Fstream, + Sstream, + CharBuffer + } toklist_inf = File; bool fail_on_error = false; bool linenrs = false; @@ -86,9 +93,28 @@ int main(int argc, char **argv) break; } dui.includes.emplace_back(std::move(value)); - } else if (std::strcmp(arg, "-is")==0) { + } + else if (std::strncmp(arg, "-input=",7)==0) { found = true; - use_istream = true; + const std::string input = arg + 7; + if (input.empty()) { + std::cout << "error: option -input with no value." << std::endl; + error = true; + break; + } + if (input == "file") { + toklist_inf = File; + } else if (input == "fstream") { + toklist_inf = Fstream; + } else if (input == "sstream") { + toklist_inf = Sstream; + } else if (input == "buffer") { + toklist_inf = CharBuffer; + } else { + std::cout << "error: unknown input type '" << input << "'." << std::endl; + error = true; + break; + } } break; case 's': @@ -117,8 +143,8 @@ int main(int argc, char **argv) break; case 'f': if (std::strcmp(arg, "-f")==0) { - found = true; fail_on_error = true; + found = true; } break; case 'l': @@ -157,7 +183,7 @@ int main(int argc, char **argv) std::cout << " -UNAME Undefine NAME." << std::endl; std::cout << " -std=STD Specify standard." << std::endl; std::cout << " -q Quiet mode (no output)." << std::endl; - std::cout << " -is Use std::istream interface." << std::endl; + std::cout << " -input=INPUT Specify input type - file (default), fstream, sstream, buffer." << std::endl; std::cout << " -e Output errors only." << std::endl; std::cout << " -f Fail when errors were encountered (exitcode 1)." << std::endl; std::cout << " -l Print lines numbers." << std::endl; @@ -197,8 +223,21 @@ int main(int argc, char **argv) simplecpp::TokenList outputTokens(files); { simplecpp::TokenList *rawtokens; - if (use_istream) { - rawtokens = new simplecpp::TokenList(f, files,filename,&outputList); + if (toklist_inf == Fstream) { + rawtokens = new simplecpp::TokenList(f,files,filename,&outputList); + } + else if (toklist_inf == Sstream || toklist_inf == CharBuffer) { + std::ostringstream oss; + oss << f.rdbuf(); + f.close(); + const std::string s = oss.str(); + if (toklist_inf == Sstream) { + std::istringstream iss(s); + rawtokens = new simplecpp::TokenList(iss,files,filename,&outputList); + } + else { + rawtokens = new simplecpp::TokenList({s.data(),s.size()},files,filename,&outputList); + } } else { f.close(); rawtokens = new simplecpp::TokenList(filename,files,&outputList); From 79db44c86b56685e31bfbee35b2a4120f1774e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 15 May 2026 16:38:37 +0200 Subject: [PATCH 100/104] CI-windows.yml: removed explicit usage of Visual Studio CMake generators (#641) --- .github/workflows/CI-windows.yml | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index cfced0d2..0d1e62e2 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -12,7 +12,7 @@ permissions: defaults: run: shell: cmd - + jobs: build: @@ -20,6 +20,13 @@ jobs: matrix: os: [windows-2022, windows-2025, windows-11-arm] config: [Release, Debug] + include: + - os: windows-2022 + sln: sln + - os: windows-2025 + sln: slnx + - os: windows-11-arm + sln: sln fail-fast: false runs-on: ${{ matrix.os }} @@ -45,12 +52,12 @@ jobs: - name: Run CMake run: | - cmake -G "Visual Studio 17 2022" -A x64 -Werror=dev --warn-uninitialized -DCMAKE_COMPILE_WARNING_AS_ERROR=On . || exit /b !errorlevel! + cmake -A x64 -Werror=dev --warn-uninitialized -DCMAKE_COMPILE_WARNING_AS_ERROR=On . || exit /b !errorlevel! - name: Build run: | - msbuild -m simplecpp.sln /p:Configuration=${{ matrix.config }} /p:Platform=x64 || exit /b !errorlevel! - + msbuild -m simplecpp.${{ matrix.sln }} /p:Configuration=${{ matrix.config }} /p:Platform=x64 || exit /b !errorlevel! + - name: Test run: | .\${{ matrix.config }}\testrunner.exe || exit /b !errorlevel! @@ -66,17 +73,17 @@ jobs: - name: Run CMake (c++17) run: | - cmake -S . -B build.cxx17 -G "Visual Studio 17 2022" -A x64 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=17 -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! + cmake -S . -B build.cxx17 -A x64 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=17 -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! - name: Build (c++17) run: | - msbuild -m build.cxx17\simplecpp.sln /p:Configuration=${{ matrix.config }} /p:Platform=x64 || exit /b !errorlevel! + msbuild -m build.cxx17\simplecpp.${{ matrix.sln }} /p:Configuration=${{ matrix.config }} /p:Platform=x64 || exit /b !errorlevel! - name: Run CMake (c++20) run: | - cmake -S . -B build.cxx20 -G "Visual Studio 17 2022" -A x64 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=20 -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! + cmake -S . -B build.cxx20 -A x64 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=20 -DCMAKE_COMPILE_WARNING_AS_ERROR=On || exit /b !errorlevel! - name: Build (c++20) run: | - msbuild -m build.cxx20\simplecpp.sln /p:Configuration=${{ matrix.config }} /p:Platform=x64 || exit /b !errorlevel! + msbuild -m build.cxx20\simplecpp.${{ matrix.sln }} /p:Configuration=${{ matrix.config }} /p:Platform=x64 || exit /b !errorlevel! From cc67864c507113789f9741b4ed157f5d82c5f408 Mon Sep 17 00:00:00 2001 From: chrchr-github <78114321+chrchr-github@users.noreply.github.com> Date: Wed, 20 May 2026 20:26:14 +0200 Subject: [PATCH 101/104] Fix known condition found by cppcheck (#652) --- simplecpp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index a7ced05a..7f65309d 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3687,7 +3687,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL bool closingAngularBracket = false; if (tok) { const std::string &sourcefile = rawtokens.file(rawtok->location); - const bool systemheader = (tok && tok->op == '<'); + const bool systemheader = tok->op == '<'; std::string header; if (systemheader) { From 4ca0ee6b86036bc8b746ffaed735c9370aa54eef Mon Sep 17 00:00:00 2001 From: chrchr-github <78114321+chrchr-github@users.noreply.github.com> Date: Wed, 27 May 2026 09:52:51 +0200 Subject: [PATCH 102/104] Fix hang on UTF-16 LE BOM file (#636) Co-authored-by: chrchr-github --- integration_test.py | 15 ++++++++++++++- simplecpp.cpp | 10 ++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/integration_test.py b/integration_test.py index 3ca2fd02..c000b27b 100644 --- a/integration_test.py +++ b/integration_test.py @@ -502,4 +502,17 @@ def test_define(record_property, tmpdir): # #589 assert exitcode == 0 assert stderr == "test.cpp:1: syntax error: failed to expand 'TEST_P', Invalid ## usage when expanding 'TEST_P': Unexpected token ')'\n" - assert stdout == '\n' \ No newline at end of file + assert stdout == '\n' + +def test_utf16_bom(tmpdir): + test_file = os.path.join(tmpdir, "test.cpp") + with open(test_file, 'wb') as f: + f.write(b'\xFF\xFE\x3B\x00') + + args = [test_file] + + exitcode, stdout, stderr = simplecpp(args, cwd=tmpdir) + + assert exitcode == 0 + assert stderr == '' + assert stdout == ';\n' diff --git a/simplecpp.cpp b/simplecpp.cpp index 7f65309d..6edbcb81 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -275,8 +275,10 @@ class simplecpp::TokenList::Stream { return ch; } - unsigned char peekChar() { - auto ch = static_cast(peek()); + int peekChar() { + int ch = peek(); + if (ch == EOF) + return ch; // For UTF-16 encoded files the BOM is 0xfeff/0xfffe. If the // character is non-ASCII character then replace it with 0xff @@ -285,7 +287,7 @@ class simplecpp::TokenList::Stream { const auto ch2 = static_cast(peek()); unget(); const int ch16 = makeUtf16Char(ch, ch2); - ch = static_cast(((ch16 >= 0x80) ? 0xff : ch16)); + ch = (ch16 >= 0x80) ? 0xff : ch16; } // Handling of newlines.. @@ -598,7 +600,7 @@ std::string simplecpp::TokenList::stringify(bool linenrs) const return ret.str(); } -static bool isNameChar(unsigned char ch) +static bool isNameChar(int ch) { return std::isalnum(ch) || ch == '_' || ch == '$'; } From 2ce31ac4fb0f89c1db866774c34569af1102ef0e Mon Sep 17 00:00:00 2001 From: Wija <50847546+wjakobsson@users.noreply.github.com> Date: Wed, 27 May 2026 09:53:15 +0200 Subject: [PATCH 103/104] Fixed #591: Newline in #error message (#654) Co-authored-by: William Jakobsson --- simplecpp.cpp | 17 +++++++++++++++-- test.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 6edbcb81..47e67483 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -773,8 +773,21 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, ch = stream.readChar(); } stream.ungetChar(); - push_back(new Token(currentToken, location)); - location.adjust(currentToken); + std::string::size_type pos = 0; + unsigned int spliced = 0; + while ((pos = currentToken.find('\\', pos)) != std::string::npos) { + if (pos + 1 < currentToken.size() && currentToken[pos + 1] == '\n') { + currentToken.erase(pos, 2); + ++spliced; + } else { + ++pos; + } + } + if (!currentToken.empty()) { + push_back(new Token(currentToken, location)); + location.adjust(currentToken); + } + location.line += spliced; continue; } } diff --git a/test.cpp b/test.cpp index cd6a4db5..e45cbe39 100644 --- a/test.cpp +++ b/test.cpp @@ -1423,6 +1423,43 @@ static void error5() ASSERT_EQUALS("file0,1,#error,#error x\n", toString(outputList)); } +static void error6() +{ + // "#error\" + const char code[] = "\xFF\xFE\x23\x00\x65\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x20\x00\x5c\x00\x0a\x00"; + std::vector files; + simplecpp::FileDataCache cache; + simplecpp::OutputList outputList; + simplecpp::TokenList tokens2(files); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); + ASSERT_EQUALS("file0,1,#error,#error \n", toString(outputList)); +} + +static void error7() +{ + const char code[] = "#error bla\\\nbla\n"; + std::vector files; + simplecpp::FileDataCache cache; + simplecpp::OutputList outputList; + simplecpp::TokenList tokens2(files); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); + ASSERT_EQUALS("file0,1,#error,#error blabla\n", toString(outputList)); +} + +static void error8() +{ + const char code[] = "#error bla\\\r\nbla\n"; + std::vector files; + simplecpp::FileDataCache cache; + simplecpp::OutputList outputList; + simplecpp::TokenList tokens2(files); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); + ASSERT_EQUALS("file0,1,#error,#error blabla\n", toString(outputList)); +} + static void garbage() { simplecpp::OutputList outputList; @@ -3933,6 +3970,9 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(error3); TEST_CASE(error4); TEST_CASE(error5); + TEST_CASE(error6); + TEST_CASE(error7); + TEST_CASE(error8); TEST_CASE(garbage); TEST_CASE(garbage_endif); From 3c2a66021ebc15208decad109ad0ab5381c8730c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Tue, 2 Jun 2026 12:54:27 +0200 Subject: [PATCH 104/104] Fix #657: combineOperators(): &= gets split up incorrectly (#658) --- simplecpp.cpp | 2 +- test.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 47e67483..fc145849 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1040,7 +1040,7 @@ void simplecpp::TokenList::combineOperators() continue; } const Token *prev = tok->previous; - while (prev && prev->isOneOf(";{}()")) + while (prev && prev->isOneOf(";{}(")) prev = prev->previous; executableScope.push(prev && prev->op == ')'); continue; diff --git a/test.cpp b/test.cpp index e45cbe39..6b034b43 100644 --- a/test.cpp +++ b/test.cpp @@ -493,6 +493,7 @@ static void combineOperators_andequal() ASSERT_EQUALS("x &= 2 ;", preprocess("x &= 2;")); ASSERT_EQUALS("void f ( x & = 2 ) ;", preprocess("void f(x &= 2);")); ASSERT_EQUALS("f ( x &= 2 ) ;", preprocess("f(x &= 2);")); + ASSERT_EQUALS("f ( ) { return new int ( i &= 1 ) ; }", preprocess("f () { return new int(i &= 1); }")); } static void combineOperators_ellipsis()