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 001/152] 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 002/152] 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 003/152] 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 004/152] 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 005/152] 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 006/152] 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 007/152] 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 008/152] 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 009/152] 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 010/152] 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 011/152] 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 012/152] 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 013/152] 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 014/152] 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 015/152] 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 016/152] 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 017/152] 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 018/152] 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 019/152] 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 020/152] 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 021/152] 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 022/152] 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 023/152] 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 024/152] 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 025/152] 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 026/152] 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 027/152] 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 028/152] 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 029/152] 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 030/152] 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 031/152] 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 032/152] 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 033/152] 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 034/152] 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 035/152] 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 036/152] 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 037/152] 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 038/152] 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 039/152] 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 040/152] 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 041/152] 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 042/152] 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 043/152] 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 044/152] 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 045/152] 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 046/152] 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 047/152] 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 048/152] 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 049/152] 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 050/152] 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 051/152] 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 052/152] 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 053/152] 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 054/152] 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 055/152] 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 056/152] 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 057/152] 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 058/152] 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 059/152] 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 060/152] 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 061/152] 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 062/152] 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 063/152] 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 064/152] 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 065/152] 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 066/152] 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 067/152] 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 068/152] 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 069/152] 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 070/152] 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 071/152] 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 072/152] 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 073/152] 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 074/152] 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 075/152] 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 076/152] 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 077/152] 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 078/152] 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 079/152] 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 080/152] 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 081/152] 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 082/152] 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 083/152] 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 084/152] 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 085/152] 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 086/152] 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 087/152] 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 088/152] 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 089/152] 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 090/152] 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 091/152] 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 092/152] 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 093/152] 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 094/152] 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 095/152] 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 096/152] 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 097/152] 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 098/152] 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 099/152] 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 100/152] 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() From 6ecc2a737e616ef37747296e2621c073bd22344a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 16 Jun 2026 22:12:37 +0200 Subject: [PATCH 101/152] avoid unnecessary container lookups in `simplecpp::preprocess()` (#661) --- simplecpp.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index fc145849..fe3b29af 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3349,20 +3349,20 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL std::map sizeOfType(rawtokens.sizeOfType); sizeOfType.insert(std::make_pair("char", sizeof(char))); sizeOfType.insert(std::make_pair("short", sizeof(short))); - sizeOfType.insert(std::make_pair("short int", sizeOfType["short"])); + sizeOfType.insert(std::make_pair("short int", sizeof(short))); sizeOfType.insert(std::make_pair("int", sizeof(int))); sizeOfType.insert(std::make_pair("long", sizeof(long))); - sizeOfType.insert(std::make_pair("long int", sizeOfType["long"])); + sizeOfType.insert(std::make_pair("long int", sizeof(long))); sizeOfType.insert(std::make_pair("long long", sizeof(long long))); sizeOfType.insert(std::make_pair("float", sizeof(float))); sizeOfType.insert(std::make_pair("double", sizeof(double))); sizeOfType.insert(std::make_pair("long double", sizeof(long double))); sizeOfType.insert(std::make_pair("char *", sizeof(char *))); sizeOfType.insert(std::make_pair("short *", sizeof(short *))); - sizeOfType.insert(std::make_pair("short int *", sizeOfType["short *"])); + sizeOfType.insert(std::make_pair("short int *", sizeof(short *))); sizeOfType.insert(std::make_pair("int *", sizeof(int *))); sizeOfType.insert(std::make_pair("long *", sizeof(long *))); - sizeOfType.insert(std::make_pair("long int *", sizeOfType["long *"])); + sizeOfType.insert(std::make_pair("long int *", sizeof(long *))); sizeOfType.insert(std::make_pair("long long *", sizeof(long long *))); sizeOfType.insert(std::make_pair("float *", sizeof(float *))); sizeOfType.insert(std::make_pair("double *", sizeof(double *))); From 84d0f2ff819b5c92552874c60ab0c2d00049aaaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Wed, 17 Jun 2026 12:14:03 +0200 Subject: [PATCH 102/152] fixed #578 - removed support for `#file` and `#endfile` (#585) --- simplecpp.cpp | 31 ++++++------------------------- test.cpp | 2 -- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index fe3b29af..dcc534ba 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -658,8 +658,6 @@ static const std::string COMMENT_END("*/"); void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, OutputList *outputList) { - std::stack loc; - unsigned int multiline = 0U; const Token *oldLastToken = nullptr; @@ -705,26 +703,15 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, 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; - } // TODO: add support for "# 3" // #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] == '\"')) + 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) @@ -745,12 +732,6 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, lineDirective(location.fileIndex, std::atol(numtok->str().c_str()), location); } } - // #endfile - else if (llNextToken->str() == "endfile" && !loc.empty()) - { - location = loc.top(); - loc.pop(); - } } continue; diff --git a/test.cpp b/test.cpp index 6b034b43..bebc535d 100644 --- a/test.cpp +++ b/test.cpp @@ -2454,8 +2454,6 @@ static void location11() preprocess(code)); } -// TODO: test #file/#endfile - static void missingHeader1() { const char code[] = "#include \"notexist.h\"\n"; From 5ac1f38ec100b98cd4f462d162d5926d63c41732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sat, 20 Jun 2026 02:12:33 +0200 Subject: [PATCH 103/152] moved part of `FileDataCache` implementation into source file (#651) --- simplecpp.cpp | 95 ++++++++++++++++++++++++++++++++++++++++++++------- simplecpp.h | 57 ++++--------------------------- 2 files changed, 89 insertions(+), 63 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index dcc534ba..32edd21d 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3,18 +3,12 @@ * Copyright (C) 2016-2023 simplecpp team */ +// needs to be specified here otherwise _mingw.h will define it as 0x0601 +// causing FileIdInfo not to be available #if defined(_WIN32) # ifndef _WIN32_WINNT # define _WIN32_WINNT 0x0602 # endif -# ifndef NOMINMAX -# define NOMINMAX -# endif -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include -# undef ERROR #endif #include "simplecpp.h" @@ -51,10 +45,19 @@ #include #include -#ifdef _WIN32 +#if defined(_WIN32) +# ifndef NOMINMAX +# define NOMINMAX +# endif +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# undef ERROR # include #else # include +# include #endif static bool isHex(const std::string &s) @@ -3071,6 +3074,65 @@ static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const return ""; } +namespace { + struct FileID { +#ifdef _WIN32 + struct { + std::uint64_t VolumeSerialNumber; + struct { + std::uint64_t IdentifierHi; + std::uint64_t IdentifierLo; + } FileId; + } fileIdInfo; + + bool operator==(const FileID &that) const noexcept { + return fileIdInfo.VolumeSerialNumber == that.fileIdInfo.VolumeSerialNumber && + fileIdInfo.FileId.IdentifierHi == that.fileIdInfo.FileId.IdentifierHi && + fileIdInfo.FileId.IdentifierLo == that.fileIdInfo.FileId.IdentifierLo; + } +#else + dev_t dev; + ino_t ino; + + bool operator==(const FileID& that) const noexcept { + return dev == that.dev && ino == that.ino; + } +#endif + struct Hasher { + std::size_t operator()(const FileID &id) const { +#ifdef _WIN32 + return static_cast(id.fileIdInfo.FileId.IdentifierHi ^ id.fileIdInfo.FileId.IdentifierLo ^ + id.fileIdInfo.VolumeSerialNumber); +#else + return static_cast(id.dev) ^ static_cast(id.ino); +#endif + } + }; + }; +} + +struct simplecpp::FileDataCache::Impl +{ + void clear() + { + mIdMap.clear(); + } + + using id_map_type = std::unordered_map; + + id_map_type mIdMap; +}; + +simplecpp::FileDataCache::FileDataCache() + : mImpl(new Impl) +{} + +simplecpp::FileDataCache::~FileDataCache() = default; +simplecpp::FileDataCache::FileDataCache(FileDataCache &&) noexcept = default; +simplecpp::FileDataCache &simplecpp::FileDataCache::operator=(simplecpp::FileDataCache &&) noexcept = default; + +static bool getFileId(const std::string &path, FileID &id); + std::pair simplecpp::FileDataCache::tryload(FileDataCache::name_map_type::iterator &name_it, const simplecpp::DUI &dui, std::vector &filenames, simplecpp::OutputList *outputList) { const std::string &path = name_it->first; @@ -3079,8 +3141,8 @@ std::pair simplecpp::FileDataCache::tryload(FileDat if (!getFileId(path, fileId)) return {nullptr, false}; - const auto id_it = mIdMap.find(fileId); - if (id_it != mIdMap.end()) { + const auto id_it = mImpl->mIdMap.find(fileId); + if (id_it != mImpl->mIdMap.end()) { name_it->second = id_it->second; return {id_it->second, false}; } @@ -3091,7 +3153,7 @@ std::pair simplecpp::FileDataCache::tryload(FileDat data->tokens.removeComments(); name_it->second = data; - mIdMap.emplace(fileId, data); + mImpl->mIdMap.emplace(fileId, data); mData.emplace_back(data); return {data, true}; @@ -3143,7 +3205,14 @@ std::pair simplecpp::FileDataCache::get(const std:: return {nullptr, false}; } -bool simplecpp::FileDataCache::getFileId(const std::string &path, FileID &id) +void simplecpp::FileDataCache::clear() +{ + mImpl->clear(); + mNameMap.clear(); + mData.clear(); +} + +static bool getFileId(const std::string &path, FileID &id) { #ifdef _WIN32 HANDLE hFile = CreateFileA(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); diff --git a/simplecpp.h b/simplecpp.h index f29166ff..de20ae2e 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -41,10 +41,6 @@ # define SIMPLECPP_LIB #endif -#ifndef _WIN32 -# include -#endif - #if defined(_MSC_VER) # pragma warning(push) // suppress warnings about "conversion from 'type1' to 'type2', possible loss of data" @@ -452,13 +448,14 @@ namespace simplecpp { class SIMPLECPP_LIB FileDataCache { public: - FileDataCache() = default; + FileDataCache(); + ~FileDataCache(); FileDataCache(const FileDataCache &) = delete; - FileDataCache(FileDataCache &&) = default; + FileDataCache(FileDataCache &&) noexcept; FileDataCache &operator=(const FileDataCache &) = delete; - FileDataCache &operator=(FileDataCache &&) = default; + FileDataCache &operator=(FileDataCache &&) noexcept; /** Get the cached data for a file, or load and then return it if it isn't cached. * returns the file data and true if the file was loaded, false if it was cached. */ @@ -472,11 +469,7 @@ namespace simplecpp { mNameMap.emplace(newdata->filename, newdata); } - void clear() { - mNameMap.clear(); - mIdMap.clear(); - mData.clear(); - } + void clear(); using container_type = std::vector>; using iterator = container_type::iterator; @@ -506,51 +499,15 @@ namespace simplecpp { } private: - struct FileID { -#ifdef _WIN32 - struct { - std::uint64_t VolumeSerialNumber; - struct { - std::uint64_t IdentifierHi; - std::uint64_t IdentifierLo; - } FileId; - } fileIdInfo; - - bool operator==(const FileID &that) const noexcept { - return fileIdInfo.VolumeSerialNumber == that.fileIdInfo.VolumeSerialNumber && - fileIdInfo.FileId.IdentifierHi == that.fileIdInfo.FileId.IdentifierHi && - fileIdInfo.FileId.IdentifierLo == that.fileIdInfo.FileId.IdentifierLo; - } -#else - dev_t dev; - ino_t ino; - - bool operator==(const FileID& that) const noexcept { - return dev == that.dev && ino == that.ino; - } -#endif - struct Hasher { - std::size_t operator()(const FileID &id) const { -#ifdef _WIN32 - return static_cast(id.fileIdInfo.FileId.IdentifierHi ^ id.fileIdInfo.FileId.IdentifierLo ^ - id.fileIdInfo.VolumeSerialNumber); -#else - return static_cast(id.dev) ^ static_cast(id.ino); -#endif - } - }; - }; + struct Impl; + std::unique_ptr mImpl; using name_map_type = std::unordered_map; - using id_map_type = std::unordered_map; - - static bool getFileId(const std::string &path, FileID &id); std::pair tryload(name_map_type::iterator &name_it, const DUI &dui, std::vector &filenames, OutputList *outputList); container_type mData; name_map_type mNameMap; - id_map_type mIdMap; }; /** Converts character literal (including prefix, but not ud-suffix) to long long value. From 7dcdbda56d70f799c34d56a63b49ba58450ece69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Mon, 22 Jun 2026 11:49:56 +0200 Subject: [PATCH 104/152] fixed #504 / refs #507 - cleaned up and improved parsing of `#line` preprocessor directive (#586) --- simplecpp.cpp | 70 ++++++++++++++++++++++++++------------------------- test.cpp | 15 ++++++++++- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 32edd21d..a3ca6916 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -699,42 +699,44 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (oldLastToken != cback()) { oldLastToken = cback(); - const Token * const llTok = isLastLinePreprocessor(); - if (!llTok) + + // #line 3 + // #line 3 "file.c" + // #3 + // #3 "file.c" + const Token * ppTok = isLastLinePreprocessor(); + if (!ppTok) continue; - const Token * const llNextToken = llTok->next; - if (!llTok->next) + + const auto advanceAndSkipComments = [](const Token* tok) { + do { + tok = tok->next; + } while (tok && tok->comment); + return tok; + }; + + // skip # + ppTok = advanceAndSkipComments(ppTok); + if (!ppTok) continue; - if (llNextToken->next) { - // TODO: add support for "# 3" - // #3 "file.c" - // #line 3 "file.c" - 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); - } - } + + if (ppTok->str() == "line") + ppTok = advanceAndSkipComments(ppTok); + + if (!ppTok || !ppTok->number) + continue; + + const unsigned int line = std::atol(ppTok->str().c_str()); + ppTok = advanceAndSkipComments(ppTok); + + unsigned int fileindex; + + if (ppTok && ppTok->str()[0] == '\"') + fileindex = fileIndex(replaceAll(ppTok->str().substr(1U, ppTok->str().size() - 2U),"\\\\","\\")); + else + fileindex = location.fileIndex; + + lineDirective(fileindex, line, location); } continue; diff --git a/test.cpp b/test.cpp index bebc535d..603d596d 100644 --- a/test.cpp +++ b/test.cpp @@ -2413,7 +2413,8 @@ static void location8() "# 3\n" "__LINE__ __FILE__\n"; ASSERT_EQUALS("\n" - "2 \"\"", // TODO: should say 3 + "\n" + "3 \"\"", preprocess(code)); } @@ -2454,6 +2455,17 @@ static void location11() preprocess(code)); } +static void location12() +{ + 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 missingHeader1() { const char code[] = "#include \"notexist.h\"\n"; @@ -4060,6 +4072,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(location9); TEST_CASE(location10); TEST_CASE(location11); + TEST_CASE(location12); TEST_CASE(missingHeader1); TEST_CASE(missingHeader2); From 3b3b30e579388f80c245940b2d2b562860dbfafa Mon Sep 17 00:00:00 2001 From: Paul Fultz II Date: Tue, 23 Jun 2026 11:31:12 -0500 Subject: [PATCH 105/152] Remove dead branch in `simplecpp::Macro::expandHashHash()` (#664) --- simplecpp.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index a3ca6916..4898800f 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2318,9 +2318,6 @@ namespace simplecpp { const Token *nextTok = B->next; if (canBeConcatenatedStringOrChar) { - if (unexpectedA) - throw invalidHashHash::unexpectedToken(tok->location, name(), A); - // 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)) { From 1b43796072fddf5ead6aea5c72d266d678f3cb45 Mon Sep 17 00:00:00 2001 From: glankk Date: Wed, 1 Jul 2026 20:29:47 +0200 Subject: [PATCH 106/152] Add file load callback (fixes #667) (#668) --- simplecpp.cpp | 3 +++ simplecpp.h | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/simplecpp.cpp b/simplecpp.cpp index 4898800f..3e572fc0 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3155,6 +3155,9 @@ std::pair simplecpp::FileDataCache::tryload(FileDat mImpl->mIdMap.emplace(fileId, data); mData.emplace_back(data); + if (mLoadCallback) + mLoadCallback(*data); + return {data, true}; } diff --git a/simplecpp.h b/simplecpp.h index de20ae2e..3d6fc526 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -498,6 +499,12 @@ namespace simplecpp { return mData.cend(); } + using load_callback_type = std::function; + + void set_load_callback(load_callback_type cb) { + mLoadCallback = std::move(cb); + } + private: struct Impl; std::unique_ptr mImpl; @@ -508,6 +515,7 @@ namespace simplecpp { container_type mData; name_map_type mNameMap; + load_callback_type mLoadCallback; }; /** Converts character literal (including prefix, but not ud-suffix) to long long value. From 163fc92dd930b902c4c49e6673d88fadc5713106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Thu, 2 Jul 2026 08:41:36 +0200 Subject: [PATCH 107/152] improved test coverage of `simplecpp::Macro::expandHashHash()` (#665) --- test.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test.cpp b/test.cpp index 603d596d..2250ecf1 100644 --- a/test.cpp +++ b/test.cpp @@ -1875,6 +1875,16 @@ static void hashhash_empty_va_args() ASSERT_EQUALS("\n\n\n( 2 )", preprocess(code)); } +static void hashhash_va_args_unexpected() +{ + const char code[] = + "#define C(...)!##__VA_ARGS__\n" + "C(1)"; + simplecpp::OutputList outputList; + preprocess(code, simplecpp::DUI(), &outputList); + ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'C', Invalid ## usage when expanding 'C': Unexpected token '!'\n", toString(outputList)); +} + static void hashhash_universal_character() { const char code[] = @@ -1884,6 +1894,16 @@ static void hashhash_universal_character() ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '\\u01' and '04' yields universal character '\\u0104'. This is undefined behavior according to C standard chapter 5.1.1.2, paragraph 4.\n", toString(outputList)); } +static void hashhash_universal_character_2() +{ + const char code[] = + "#define A(x,y) x##y\nint A(\\U0104, 0104);"; + simplecpp::OutputList outputList; + preprocess(code, simplecpp::DUI(), &outputList); + ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '\\U0104' and '0104' yields universal character '\\U01040104'. This is undefined behavior according to C standard chapter 5.1.1.2, paragraph 4.\n", toString(outputList)); +} + + static void has_include_1() { const char code[] = "#ifdef __has_include\n" @@ -4019,11 +4039,14 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(hashhash_invalid_missing_args); TEST_CASE(hashhash_null_stmt); TEST_CASE(hashhash_empty_va_args); + TEST_CASE(hashhash_va_args_unexpected); + // C standard, 5.1.1.2, paragraph 4: // If a character sequence that matches the syntax of a universal // character name is produced by token concatenation (6.10.3.3), // the behavior is undefined." TEST_CASE(hashhash_universal_character); + TEST_CASE(hashhash_universal_character_2); // c++17 __has_include TEST_CASE(has_include_1); From fc7dca0bea10e1f37a9ff15ec234941122a3685a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 3 Jul 2026 13:48:46 +0200 Subject: [PATCH 108/152] optimized `std::stack` usage in `simplecpp::TokenList::combineOperators()` (#659) --- simplecpp.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 3e572fc0..df8ad5b8 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1017,8 +1017,7 @@ static bool isAlternativeAndBitandBitor(const simplecpp::Token* tok) void simplecpp::TokenList::combineOperators() { - std::stack executableScope; - executableScope.push(false); + std::stack> executableScope{{false}}; for (Token *tok = front(); tok; tok = tok->next) { if (tok->op == '{') { if (executableScope.top()) { From 162eb964e8728990c442eece2cf26b4871a443ba Mon Sep 17 00:00:00 2001 From: glankk Date: Tue, 7 Jul 2026 10:38:36 +0200 Subject: [PATCH 109/152] Fix #669: Manually included files are not reported missing by preprocess (#670) --- simplecpp.cpp | 13 ++++++++++- test.cpp | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index df8ad5b8..d2c398c4 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3517,8 +3517,19 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL includetokenstack.push(rawtokens.cfront()); 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) + if (filedata == nullptr) { + if (outputList) { + simplecpp::Output err{ + simplecpp::Output::EXPLICIT_INCLUDE_NOT_FOUND, + {}, + "Can not open include file '" + *it + "' that is explicitly included." + }; + outputList->emplace_back(std::move(err)); + } + } + else if (filedata->tokens.cfront() != nullptr) { includetokenstack.push(filedata->tokens.cfront()); + } } std::map> maybeUsedMacros; diff --git a/test.cpp b/test.cpp index 2250ecf1..b13140aa 100644 --- a/test.cpp +++ b/test.cpp @@ -2926,6 +2926,64 @@ static void include9() ASSERT_EQUALS("\n#line 2 \"1.h\"\nx = 1 ;", out.stringify()); } +static void include10() // #669 - -include with load() +{ + const char code_c[] = "X\n"; + const char code_h[] = "#define X 123\n"; + + std::vector files; + + const simplecpp::TokenList rawtokens_c = makeTokenList(code_c, files, "src.c"); + const simplecpp::TokenList rawtokens_h = makeTokenList(code_h, files, "inc.h"); + + ASSERT_EQUALS(2U, files.size()); + ASSERT_EQUALS("src.c", files[0]); + ASSERT_EQUALS("inc.h", files[1]); + + simplecpp::FileDataCache cache; + cache.insert({"src.c", rawtokens_c}); + cache.insert({"inc.h", rawtokens_h}); + + simplecpp::OutputList outputList; + simplecpp::DUI dui; + dui.includes.emplace_back("inc.h"); + dui.includes.emplace_back("missing.h"); + cache = simplecpp::load(rawtokens_c, files, dui, &outputList, std::move(cache)); + + ASSERT_EQUALS(1, outputList.size()); + ASSERT_EQUALS("Can not open include file 'missing.h' that is explicitly included.", outputList.begin()->msg); +} + +static void include11() // #669 - -include with preprocess() +{ + const char code_c[] = "X\n"; + const char code_h[] = "#define X 123\n"; + + std::vector files; + + const simplecpp::TokenList rawtokens_c = makeTokenList(code_c, files, "src.c"); + const simplecpp::TokenList rawtokens_h = makeTokenList(code_h, files, "inc.h"); + + ASSERT_EQUALS(2U, files.size()); + ASSERT_EQUALS("src.c", files[0]); + ASSERT_EQUALS("inc.h", files[1]); + + simplecpp::FileDataCache cache; + cache.insert({"src.c", rawtokens_c}); + cache.insert({"inc.h", rawtokens_h}); + + simplecpp::OutputList outputList; + simplecpp::TokenList out(files); + simplecpp::DUI dui; + dui.includes.emplace_back("inc.h"); + dui.includes.emplace_back("missing.h"); + simplecpp::preprocess(out, rawtokens_c, files, cache, dui, &outputList); + + ASSERT_EQUALS("123", out.stringify()); + ASSERT_EQUALS(1, outputList.size()); + ASSERT_EQUALS("Can not open include file 'missing.h' that is explicitly included.", outputList.begin()->msg); +} + static void readfile_nullbyte() { const char code[] = "ab\0cd"; @@ -4118,6 +4176,8 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(include7); // #include MACRO TEST_CASE(include8); // #include MACRO(X) TEST_CASE(include9); // #include MACRO + TEST_CASE(include10); // -include with load() + TEST_CASE(include11); // -include with preprocess() TEST_CASE(multiline1); TEST_CASE(multiline2); From c44d686e1c4d25a72fb285d4c1d381c0d3cfb2b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 10 Jul 2026 23:23:08 +0200 Subject: [PATCH 110/152] Fix #673 (unique Output::Type when directive is used in macro parameter) (#674) --- main.cpp | 3 +++ simplecpp.cpp | 17 ++++++++++++++++- simplecpp.h | 1 + test.cpp | 5 ++++- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/main.cpp b/main.cpp index 685ada12..06afcfbb 100644 --- a/main.cpp +++ b/main.cpp @@ -272,6 +272,9 @@ int main(int argc, char **argv) case simplecpp::Output::SYNTAX_ERROR: std::cerr << "syntax error: "; break; + case simplecpp::Output::DIRECTIVE_AS_MACRO_PARAMETER: + std::cerr << "directive as macro parameter: "; + break; case simplecpp::Output::PORTABILITY_BACKSLASH: std::cerr << "portability: "; break; diff --git a/simplecpp.cpp b/simplecpp.cpp index d2c398c4..748ea6a9 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1602,7 +1602,7 @@ namespace simplecpp { else if (rawtok->op == ')') --par; else if (rawtok->op == '#' && !sameline(rawtok->previous, rawtok)) - throw Error(rawtok->location, "it is invalid to use a preprocessor directive as macro parameter"); + throw invalidDirectiveAsMacroParameter(rawtok->location); rawtokens2.push_back(new Token(rawtok->str(), rawtok1->location, rawtok->whitespaceahead)); rawtok = rawtok->next; } @@ -1698,6 +1698,11 @@ namespace simplecpp { const std::string what; }; + struct invalidDirectiveAsMacroParameter : public Error { + invalidDirectiveAsMacroParameter(const Location &loc) + : Error(loc, "it is invalid to use a preprocessor directive as macro parameter") {} + }; + /** Struct that is thrown when macro is expanded with wrong number of parameters */ struct wrongNumberOfParameters : public Error { wrongNumberOfParameters(const Location &loc, const std::string ¯oName) : Error(loc, "Wrong number of parameters for macro \'" + macroName + "\'.") {} @@ -3344,6 +3349,16 @@ static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token simplecpp::TokenList value(files); try { tok1 = it->second.expand(value, tok, macros, files); + } catch (const simplecpp::Macro::invalidDirectiveAsMacroParameter &err) { + if (outputList) { + simplecpp::Output out{ + simplecpp::Output::DIRECTIVE_AS_MACRO_PARAMETER, + err.location, + "failed to expand \'" + tok->str() + "\', " + err.what + }; + outputList->emplace_back(std::move(out)); + } + return false; } catch (const simplecpp::Macro::Error &err) { if (outputList) { simplecpp::Output out{ diff --git a/simplecpp.h b/simplecpp.h index 3d6fc526..ee2eb100 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -238,6 +238,7 @@ namespace simplecpp { MISSING_HEADER, INCLUDE_NESTED_TOO_DEEPLY, SYNTAX_ERROR, + DIRECTIVE_AS_MACRO_PARAMETER, PORTABILITY_BACKSLASH, UNHANDLED_CHAR_ERROR, EXPLICIT_INCLUDE_NOT_FOUND, diff --git a/test.cpp b/test.cpp index b13140aa..3a858929 100644 --- a/test.cpp +++ b/test.cpp @@ -197,6 +197,9 @@ static std::string toString(const simplecpp::OutputList &outputList) case simplecpp::Output::Type::SYNTAX_ERROR: ostr << "syntax_error,"; break; + case simplecpp::Output::DIRECTIVE_AS_MACRO_PARAMETER: + ostr << "directive_as_macro_parameter,"; + break; case simplecpp::Output::Type::PORTABILITY_BACKSLASH: ostr << "portability_backslash,"; break; @@ -1338,7 +1341,7 @@ static void define_ifdef() simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); - ASSERT_EQUALS("file0,3,syntax_error,failed to expand 'A', it is invalid to use a preprocessor directive as macro parameter\n", toString(outputList)); + ASSERT_EQUALS("file0,3,directive_as_macro_parameter,failed to expand 'A', it is invalid to use a preprocessor directive as macro parameter\n", toString(outputList)); } From 36e6e1211b76393c6d543578f33f4ab1ea7441b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 12 Jul 2026 15:21:36 +0200 Subject: [PATCH 111/152] improved test coverage of throwing code (#666) --- test.cpp | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/test.cpp b/test.cpp index 3a858929..990a6daf 100644 --- a/test.cpp +++ b/test.cpp @@ -132,11 +132,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, std::list *macroUsage = nullptr, std::list *ifCond = nullptr, const std::string &file = std::string()) +static std::string preprocess(const char code[], std::size_t size, 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; - simplecpp::TokenList tokens = makeTokenList(code,files, file); + simplecpp::TokenList tokens = makeTokenList(code, size, files, file); if (dui.removeComments) tokens.removeComments(); simplecpp::TokenList tokens2(files); @@ -145,6 +145,11 @@ static std::string preprocess(const char code[], const simplecpp::DUI &dui, simp return tokens2.stringify(); } +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()) +{ + return preprocess(code, strlen(code), dui, outputList, macroUsage, ifCond, file); +} + static std::string preprocess(const char code[]) { return preprocess(code, simplecpp::DUI(), nullptr); @@ -165,6 +170,11 @@ static std::string preprocess(const char code[], simplecpp::OutputList *outputLi return preprocess(code, simplecpp::DUI(), outputList); } +static std::string preprocess(const char code[], std::size_t size, simplecpp::OutputList *outputList) +{ + return preprocess(code, size, simplecpp::DUI(), outputList); +} + static std::string preprocess(const char code[], std::list *ifCond) { return preprocess(code, simplecpp::DUI(), nullptr, nullptr, ifCond); @@ -916,6 +926,24 @@ static void define_invalid_2() ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, bad macro syntax\n", toString(outputList)); } +static void define_invalid_3() +{ + const char code[] = "#define R()\n" + "R"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to expand 'R', Wrong number of parameters for macro 'R'.\n", toString(outputList)); +} + +static void define_invalid_4() +{ + const char code[] = "#define X(...)\n" + "X"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to expand 'X', Wrong number of parameters for macro 'X'.\n", toString(outputList)); +} + static void define_define_1() { const char code[] = "#define A(x) (x+1)\n" @@ -1345,6 +1373,30 @@ static void define_ifdef() } +static void if_invalid_1() +{ + const char code[] = "#if'\\u'"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, expected digit\n", toString(outputList)); +} + +static void if_invalid_2() +{ + const char code[] = "#if-0xBBB4444444444444%~B"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, division overflow\n", toString(outputList)); +} + +static void if_invalid_3() +{ + const char code[] = "#if@u'\\udefa'"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, surrogate code points not allowed in universal character names\n", toString(outputList)); +} + static void pragma_backslash() { const char code[] = "#pragma comment (longstring, \\\n" @@ -2036,6 +2088,42 @@ static void has_include_6() ASSERT_EQUALS("\n\nA", preprocess(code, dui)); } +static void define_has_include_invalid_1() +{ + const char code[] = "#define A)__has_include\n" + "#if\u000BA"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, missing __has_include argument\n", toString(outputList)); +} + +static void define_has_include_invalid_2() +{ + const char code[] = "#define f __has_include\n" + "#if#f<"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, missing __has_include argument\n", toString(outputList)); +} + +static void define_has_include_invalid_3() +{ + const char code[] = "#define\u0000X\u0007__has_include(\n" + "#if%X&"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, sizeof(code), &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, invalid __has_include expression\n", toString(outputList)); +} + +static void define_has_include_invalid_4() +{ + const char code[] = "#define\u0000X\u0000__has_include<2\n" + "#if*X"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, sizeof(code), &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, invalid __has_include expression\n", toString(outputList)); +} + static void strict_ansi_1() { const char code[] = "#if __STRICT_ANSI__\n" @@ -4010,6 +4098,8 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(define23); // #40 TEST_CASE(define_invalid_1); TEST_CASE(define_invalid_2); + TEST_CASE(define_invalid_3); + TEST_CASE(define_invalid_4); TEST_CASE(define_define_1); TEST_CASE(define_define_2); TEST_CASE(define_define_3); @@ -4055,6 +4145,10 @@ static void runTests(int argc, char **argv, Input input) // UB: #ifdef as macro parameter TEST_CASE(define_ifdef); + TEST_CASE(if_invalid_1); + TEST_CASE(if_invalid_2); + TEST_CASE(if_invalid_3); + TEST_CASE(dollar); TEST_CASE(error1); @@ -4117,6 +4211,11 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(has_include_5); TEST_CASE(has_include_6); + TEST_CASE(define_has_include_invalid_1); + TEST_CASE(define_has_include_invalid_2); + TEST_CASE(define_has_include_invalid_3); + TEST_CASE(define_has_include_invalid_4); + TEST_CASE(strict_ansi_1); TEST_CASE(strict_ansi_2); TEST_CASE(strict_ansi_3); From c968a8ec5a64f93a50f0b5bb79ab9d87edf34352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 13 Jul 2026 17:08:20 +0200 Subject: [PATCH 112/152] Fix #678 workaround needed for gcc bug (old gcc), defaulted noexcept constructors (#679) --- .github/workflows/gcc-versions.yml | 30 ++++++++++++++++++++++++++++++ simplecpp.cpp | 4 ++-- simplecpp.h | 13 +++++++++++-- 3 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/gcc-versions.yml diff --git a/.github/workflows/gcc-versions.yml b/.github/workflows/gcc-versions.yml new file mode 100644 index 00000000..9c365a24 --- /dev/null +++ b/.github/workflows/gcc-versions.yml @@ -0,0 +1,30 @@ +# the purpose of this github action is to test that simplecpp source code can be compiled using old gcc versions. +# the gcc images we use here are copied from the official gcc images on docker hub + +name: gcc-versions + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + + strategy: + matrix: + #image: ["ghcr.io/cppcheck-opensource/gcc:5.4", "ghcr.io/cppcheck-opensource/gcc:6.5", "ghcr.io/cppcheck-opensource/gcc:7.5", "ghcr.io/cppcheck-opensource/gcc:8.5", "ghcr.io/cppcheck-opensource/gcc:9.5"] + image: ["ghcr.io/cppcheck-opensource/gcc:5.4"] + fail-fast: false + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Build and run tests + run: | + docker run --rm -v ${{ github.workspace }}:/simplecpp -w /simplecpp ${{ matrix.image }} make -j$(nproc) simplecpp testrunner + docker run --rm -v ${{ github.workspace }}:/simplecpp -w /simplecpp ${{ matrix.image }} ./testrunner diff --git a/simplecpp.cpp b/simplecpp.cpp index 748ea6a9..35980cbe 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3131,8 +3131,8 @@ simplecpp::FileDataCache::FileDataCache() {} simplecpp::FileDataCache::~FileDataCache() = default; -simplecpp::FileDataCache::FileDataCache(FileDataCache &&) noexcept = default; -simplecpp::FileDataCache &simplecpp::FileDataCache::operator=(simplecpp::FileDataCache &&) noexcept = default; +simplecpp::FileDataCache::FileDataCache(FileDataCache &&) SIMPLECPP_NOEXCEPT = default; +simplecpp::FileDataCache &simplecpp::FileDataCache::operator=(simplecpp::FileDataCache &&) SIMPLECPP_NOEXCEPT = default; static bool getFileId(const std::string &path, FileID &id); diff --git a/simplecpp.h b/simplecpp.h index ee2eb100..27c538ea 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -60,6 +60,15 @@ # endif #endif +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 9 +// Hack to workaround GCC bug. +// Details: https://trac.cppcheck.net/ticket/14850 +// seen on g++ before 10.x +#define SIMPLECPP_NOEXCEPT +#else +#define SIMPLECPP_NOEXCEPT noexcept +#endif + namespace simplecpp { /** C code standard */ enum cstd_t : std::int8_t { CUnknown=-1, C89, C99, C11, C17, C23, C2Y }; @@ -454,10 +463,10 @@ namespace simplecpp { ~FileDataCache(); FileDataCache(const FileDataCache &) = delete; - FileDataCache(FileDataCache &&) noexcept; + FileDataCache(FileDataCache &&) SIMPLECPP_NOEXCEPT; FileDataCache &operator=(const FileDataCache &) = delete; - FileDataCache &operator=(FileDataCache &&) noexcept; + FileDataCache &operator=(FileDataCache &&) SIMPLECPP_NOEXCEPT; /** Get the cached data for a file, or load and then return it if it isn't cached. * returns the file data and true if the file was loaded, false if it was cached. */ From 178f363c7e9158a95f064a00871b81cec373dd7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 14 Jul 2026 15:52:34 +0200 Subject: [PATCH 113/152] made it possibly to actually override the C++ standard in CMake (#677) --- .clang-tidy | 5 +++++ CMakeLists.txt | 6 +++++- test.cpp | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.clang-tidy b/.clang-tidy index e0b384bf..184cac3d 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -31,10 +31,15 @@ Checks: > -modernize-avoid-c-arrays, -modernize-loop-convert, -modernize-pass-by-value, + -modernize-use-auto, + -modernize-use-designated-initializers, -modernize-use-nodiscard, + -modernize-use-ranges, + -modernize-use-starts-ends-with, -modernize-use-trailing-return-type, -readability-avoid-nested-conditional-operator, -readability-braces-around-statements, + -readability-container-contains, -readability-function-cognitive-complexity, -readability-function-size, -readability-implicit-bool-conversion, diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a90efae..f2c99e89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,13 @@ cmake_minimum_required (VERSION 3.10) project (simplecpp LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard to use") set(CMAKE_CXX_STANDARD_REQUIRED ON) +if(CMAKE_CXX_STANDARD LESS 11) + message(FATAL_ERROR "C++ standard was set to ${CMAKE_CXX_STANDARD} but 11 is required as a minimum") +endif() + include(CheckCXXCompilerFlag) if (WIN32) diff --git a/test.cpp b/test.cpp index 990a6daf..37b7da8d 100644 --- a/test.cpp +++ b/test.cpp @@ -18,6 +18,10 @@ #include #include +#ifdef __cpp_lib_string_view +#include +#endif + #ifndef SIMPLECPP_TEST_SOURCE_DIR #error "SIMPLECPP_TEST_SOURCE_DIR is not defined." #endif From f6947eb7cfda1d240aafbb960cc13678a3ed7d8d Mon Sep 17 00:00:00 2001 From: glankk Date: Mon, 20 Jul 2026 13:42:51 +0200 Subject: [PATCH 114/152] Fix #671: Missing newline at end of file should produce a warning (#672) --- integration_test.py | 24 +++---- main.cpp | 8 +++ simplecpp.cpp | 13 ++++ simplecpp.h | 1 + test.cpp | 148 ++++++++++++++++++++++++++++++++------------ 5 files changed, 142 insertions(+), 52 deletions(-) diff --git a/integration_test.py b/integration_test.py index c000b27b..42dda9b2 100644 --- a/integration_test.py +++ b/integration_test.py @@ -17,7 +17,7 @@ def __test_relative_header_create_header(dir, with_pragma_once=True): #error header_was_already_included #endif const int dummy = 1; - """) + """'\n') return header_file, "error: #error header_was_already_included" def __test_relative_header_create_source(dir, include1, include2, is_include1_sys=False, is_include2_sys=False, inv=False): @@ -31,7 +31,7 @@ def __test_relative_header_create_source(dir, include1, include2, is_include1_sy #undef TEST_H_INCLUDED #include {format_include(include1, is_include1_sys)} #include {format_include(include2, is_include2_sys)} - """) + """'\n') return src_file @pytest.mark.parametrize("with_pragma_once", (False, True)) @@ -201,27 +201,27 @@ def test_same_name_header(record_property, tmpdir): #include #include TEST - """) + """'\n') with open(header_a, "wt") as f: f.write(""" #include "same_name.h" - """) + """'\n') with open(header_b, "wt") as f: f.write(""" #include "same_name.h" - """) + """'\n') with open(same_name_a, "wt") as f: f.write(""" #define TEST E - """) + """'\n') with open(same_name_b, "wt") as f: f.write(""" #define TEST OK - """) + """'\n') args = [ format_include_path_arg(include_a), @@ -279,13 +279,13 @@ def test_pragma_once_matching(record_property, tmpdir): for n in names_to_test: f.write(f""" #include {n} - """); + """'\n'); with open(once_header, "wt") as f: f.write(f""" #pragma once ONCE - """); + """'\n'); args = [ format_include_path_arg(test_dir), @@ -463,7 +463,7 @@ def test_include_header_twice(tmpdir): #ifdef BBB # error BBB is defined #endif - """) + """'\n') test_file = os.path.join(tmpdir, 'test.c') with open(test_file, 'wt') as f: @@ -473,7 +473,7 @@ def test_include_header_twice(tmpdir): # define BBB # include "test.h" - """) + """'\n') args = [test_file] @@ -507,7 +507,7 @@ def test_define(record_property, tmpdir): # #589 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') + f.write(b'\xFF\xFE\x3B\x00\x0A\x00') args = [test_file] diff --git a/main.cpp b/main.cpp index 06afcfbb..abb07228 100644 --- a/main.cpp +++ b/main.cpp @@ -278,6 +278,14 @@ int main(int argc, char **argv) case simplecpp::Output::PORTABILITY_BACKSLASH: std::cerr << "portability: "; break; + case simplecpp::Output::PORTABILITY_NO_EOF_NEWLINE: + if (simplecpp::getCStd(dui.std) == simplecpp::CUnknown) { + // Only UB for c code, suppress for c++ code + // If no standard is specified then prefer to have a false negative + continue; + } + std::cerr << "portability: "; + break; case simplecpp::Output::UNHANDLED_CHAR_ERROR: std::cerr << "unhandled char error: "; break; diff --git a/simplecpp.cpp b/simplecpp.cpp index 35980cbe..5ab6aa30 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -662,6 +662,7 @@ static const std::string COMMENT_END("*/"); void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, OutputList *outputList) { unsigned int multiline = 0U; + bool trailing_nl = true; const Token *oldLastToken = nullptr; @@ -671,6 +672,8 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (!stream.good()) break; + trailing_nl = false; + if (ch >= 0x80) { if (outputList) { simplecpp::Output err{ @@ -693,6 +696,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, } else { location.line += multiline + 1; multiline = 0U; + trailing_nl = true; } if (!multiline) location.col = 1; @@ -961,6 +965,15 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, location.adjust(currentToken); } + if (!trailing_nl && outputList) { + Output err{ + Output::PORTABILITY_NO_EOF_NEWLINE, + location, + "No newline at end of file." + }; + outputList->emplace_back(std::move(err)); + } + combineOperators(); } diff --git a/simplecpp.h b/simplecpp.h index 27c538ea..10f7d2a8 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -249,6 +249,7 @@ namespace simplecpp { SYNTAX_ERROR, DIRECTIVE_AS_MACRO_PARAMETER, PORTABILITY_BACKSLASH, + PORTABILITY_NO_EOF_NEWLINE, UNHANDLED_CHAR_ERROR, EXPLICIT_INCLUDE_NOT_FOUND, FILE_NOT_FOUND, diff --git a/test.cpp b/test.cpp index 37b7da8d..1771526c 100644 --- a/test.cpp +++ b/test.cpp @@ -217,6 +217,9 @@ static std::string toString(const simplecpp::OutputList &outputList) case simplecpp::Output::Type::PORTABILITY_BACKSLASH: ostr << "portability_backslash,"; break; + case simplecpp::Output::Type::PORTABILITY_NO_EOF_NEWLINE: + ostr << "portability_no_eof_newline,"; + break; case simplecpp::Output::Type::UNHANDLED_CHAR_ERROR: ostr << "unhandled_char_error,"; break; @@ -241,15 +244,15 @@ static void backslash() // preprocessed differently simplecpp::OutputList outputList; - readfile("//123 \\\n456", &outputList); + readfile("//123 \\\n456\n", &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("//123 \\ \n456", &outputList); + readfile("//123 \\ \n456\n", &outputList); ASSERT_EQUALS("file0,1,portability_backslash,Combination 'backslash space newline' is not portable.\n", toString(outputList)); outputList.clear(); - readfile("#define A \\\n123", &outputList); + readfile("#define A \\\n123\n", &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("#define A \\ \n123", &outputList); + readfile("#define A \\ \n123\n", &outputList); ASSERT_EQUALS("file0,1,portability_backslash,Combination 'backslash space newline' is not portable.\n", toString(outputList)); } @@ -1465,7 +1468,7 @@ static void error4() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtoken = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtoken = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtoken, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error x\n", toString(outputList)); } @@ -1478,7 +1481,7 @@ static void error5() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error x\n", toString(outputList)); } @@ -1491,7 +1494,7 @@ static void error6() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error \n", toString(outputList)); } @@ -1503,7 +1506,7 @@ static void error7() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error blabla\n", toString(outputList)); } @@ -1515,7 +1518,7 @@ static void error8() simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code),files,"test.c"); + const simplecpp::TokenList rawtokens = makeTokenList(code, sizeof(code)-1,files,"test.c"); simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error blabla\n", toString(outputList)); } @@ -3081,9 +3084,9 @@ static void include11() // #669 - -include with preprocess() static void readfile_nullbyte() { - const char code[] = "ab\0cd"; + const char code[] = "ab\0cd\n"; simplecpp::OutputList outputList; - ASSERT_EQUALS("ab cd", readfile(code,sizeof(code), &outputList)); + ASSERT_EQUALS("ab cd", readfile(code,sizeof(code)-1, &outputList)); ASSERT_EQUALS(true, outputList.empty()); // should warning be written? } @@ -3198,7 +3201,7 @@ static void readfile_string_error() outputList.clear(); // Don't warn for a multiline define - readfile("#define A \"abs\\\n\"", &outputList); + readfile("#define A \"abs\\\n\"\n", &outputList); ASSERT_EQUALS("", toString(outputList)); } @@ -3210,11 +3213,11 @@ static void readfile_cpp14_number() static void readfile_unhandled_chars() { simplecpp::OutputList outputList; - readfile("// 你好世界", &outputList); + readfile("// 你好世界\n", &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("s=\"你好世界\"", &outputList); + readfile("s=\"你好世界\"\n", &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("int 你好世界=0;", &outputList); + readfile("int 你好世界=0;\n", &outputList); ASSERT_EQUALS("file0,1,unhandled_char_error,The code contains unhandled character(s) (character code=228). Neither unicode nor extended ascii is supported.\n", toString(outputList)); } @@ -3234,6 +3237,70 @@ static void readfile_file_not_found() ASSERT_EQUALS("file0,0,file_not_found,File is missing: NotAFile\n", toString(outputList)); } +static void readfile_no_eof_newline() +{ + { + const char code[] = ""; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "\\\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); + } + { + const char code[] = "#define A"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); + } + { + const char code[] = "#define A\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "#define A\\"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); + } + { + const char code[] = "// comment"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); + } + { + const char code[] = "// comment\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "/* comment \n comment */"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("file0,2,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); + } + { + const char code[] = "/* comment \n comment */\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } +} + static void stringify1() { const char code_c[] = "#include \"A.h\"\n" @@ -3377,31 +3444,31 @@ static void unicode() { { const char code[] = "\xFE\xFF\x00\x31\x00\x32"; - ASSERT_EQUALS("12", readfile(code, sizeof(code))); + ASSERT_EQUALS("12", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFF\xFE\x31\x00\x32\x00"; - ASSERT_EQUALS("12", readfile(code, sizeof(code))); + ASSERT_EQUALS("12", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFE\xFF\x00\x2f\x00\x2f\x00\x0a\x00\x31"; - ASSERT_EQUALS("//\n1", readfile(code, sizeof(code))); + ASSERT_EQUALS("//\n1", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFF\xFE\x2f\x00\x2f\x00\x0a\x00\x31\x00"; - ASSERT_EQUALS("//\n1", readfile(code, sizeof(code))); + ASSERT_EQUALS("//\n1", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFE\xFF\x00\x22\x00\x61\x00\x22"; - ASSERT_EQUALS("\"a\"", readfile(code, sizeof(code))); + ASSERT_EQUALS("\"a\"", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFF\xFE\x22\x00\x61\x00\x22\x00"; - ASSERT_EQUALS("\"a\"", readfile(code, sizeof(code))); + ASSERT_EQUALS("\"a\"", readfile(code, sizeof(code)-1)); } { const char code[] = "\xff\xfe\x0d\x00\x0a\x00\x2f\x00\x2f\x00\x31\x00\x0d\x00\x0a\x00"; - ASSERT_EQUALS("\n//1", readfile(code, sizeof(code))); + ASSERT_EQUALS("\n//1", readfile(code, sizeof(code)-1)); } } @@ -3409,35 +3476,35 @@ static void unicode_invalid() { { const char code[] = "\xFF"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFE"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { - const char code[] = "\xFE\xFF\x31"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + const char code[] = "\xFE\xFF\x31\x00"; + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { - const char code[] = "\xFF\xFE\x31"; - ASSERT_EQUALS("1", readfile(code, sizeof(code))); + const char code[] = "\xFF\xFE\x31\x00"; + ASSERT_EQUALS("1", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFE\xFF\x31\x32"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { const char code[] = "\xFF\xFE\x31\x32"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { - const char code[] = "\xFE\xFF\x00\x31\x00\x32\x33"; - ASSERT_EQUALS("", readfile(code, sizeof(code))); + const char code[] = "\xFE\xFF\x00\x31\x00\x32\x33\x00"; + ASSERT_EQUALS("", readfile(code, sizeof(code)-1)); } { - const char code[] = "\xFF\xFE\x31\x00\x32\x00\x33"; - ASSERT_EQUALS("123", readfile(code, sizeof(code))); + const char code[] = "\xFF\xFE\x31\x00\x32\x00\x33\x00"; + ASSERT_EQUALS("123", readfile(code, sizeof(code)-1)); } } @@ -3805,19 +3872,19 @@ static void tokenlist_api() // sized array + size { char input[] = "code"; // NOLINT(misc-const-correctness) - simplecpp::TokenList(input,sizeof(input),filenames,""); + simplecpp::TokenList(input,sizeof(input)-1,filenames,""); } { const char input[] = "code"; - simplecpp::TokenList(input,sizeof(input),filenames,""); + simplecpp::TokenList(input,sizeof(input)-1,filenames,""); } { unsigned char input[] = "code"; // NOLINT(misc-const-correctness) - simplecpp::TokenList(input,sizeof(input),filenames,""); + simplecpp::TokenList(input,sizeof(input)-1,filenames,""); } { const unsigned char input[] = "code"; - simplecpp::TokenList(input,sizeof(input),filenames,""); + simplecpp::TokenList(input,sizeof(input)-1,filenames,""); } #endif // !defined(__cpp_lib_string_view) && !defined(__cpp_lib_span) // pointer via View @@ -3837,11 +3904,11 @@ static void tokenlist_api() // sized array + size via View/std::span { char input[] = "code"; // NOLINT(misc-const-correctness) - simplecpp::TokenList({input,sizeof(input)},filenames,""); + simplecpp::TokenList({input,sizeof(input)-1},filenames,""); } { const char input[] = "code"; - simplecpp::TokenList({input,sizeof(input)},filenames,""); + simplecpp::TokenList({input,sizeof(input)-1},filenames,""); } // sized array { @@ -4305,6 +4372,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(readfile_unhandled_chars); TEST_CASE(readfile_error); TEST_CASE(readfile_file_not_found); + TEST_CASE(readfile_no_eof_newline); TEST_CASE(stringify1); From 06c7bf0ba1da03e29bac3f3d77ec83fab01540ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Thu, 23 Jul 2026 15:14:09 +0200 Subject: [PATCH 115/152] Fix #688: #ifdef and #ifndef directives missing from ifCond (#689) --- simplecpp.cpp | 20 ++++++++++++++++---- test.cpp | 21 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 5ab6aa30..57b55e91 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3740,11 +3740,23 @@ 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()].emplace_back(rawtok->next->location); + const std::string &name = rawtok->next->str(); + conditionIsTrue = (macros.find(name) != macros.end() || (hasInclude && name == HAS_INCLUDE)); + maybeUsedMacros[name].emplace_back(rawtok->next->location); + if (ifCond) { + const std::string E = conditionIsTrue ? "1" : "0"; + const long long result = conditionIsTrue ? 1 : 0; + ifCond->emplace_back(rawtok->location, E, result); + } } else if (rawtok->str() == IFNDEF) { - conditionIsTrue = (macros.find(rawtok->next->str()) == macros.end() && !(hasInclude && rawtok->next->str() == HAS_INCLUDE)); - maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); + const std::string &name = rawtok->next->str(); + conditionIsTrue = (macros.find(name) == macros.end() && !(hasInclude && name == HAS_INCLUDE)); + maybeUsedMacros[name].emplace_back(rawtok->next->location); + if (ifCond) { + const std::string E = conditionIsTrue ? "1" : "0"; + const long long result = conditionIsTrue ? 1 : 0; + ifCond->emplace_back(rawtok->location, E, result); + } } 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) { diff --git a/test.cpp b/test.cpp index 1771526c..76c45ecf 100644 --- a/test.cpp +++ b/test.cpp @@ -4026,6 +4026,27 @@ static void ifCond() ASSERT_EQUALS("0", it->E); ASSERT_EQUALS(0, it->result); } + { + const char code[] = "#ifdef NOTDEFINED\n" + "#endif\n" + "#ifndef NOTDEFINED\n" + "#endif\n"; + std::list ifCond; + ASSERT_EQUALS("", preprocess(code, &ifCond)); + ASSERT_EQUALS(2, 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(3, it->location.line); + ASSERT_EQUALS(2, it->location.col); + ASSERT_EQUALS("1", it->E); + ASSERT_EQUALS(1, it->result); + } } static void macroUsage() From c892d730e059876dd45a39d0bcd5b0a07219547a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 24 Jul 2026 08:30:29 +0200 Subject: [PATCH 116/152] clang-tidy.yml: run clang-tidy with all C++ standards (#683) --- .github/workflows/clang-tidy.yml | 58 +++++++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 333672d7..2819fb9d 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -40,12 +40,62 @@ jobs: run: | clang-tidy-22 --verify-config - - name: Prepare CMake + - name: Prepare CMake (C++11) run: | - cmake -S . -B cmake.output -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=23 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + cmake -S . -B cmake.output.cxx11 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=11 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON env: CXX: clang-22 - - name: Clang-Tidy + - name: Clang-Tidy (C++11) run: | - run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output + run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output.cxx11 + + - name: Prepare CMake (C++14) + run: | + cmake -S . -B cmake.output.cxx14 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=14 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + env: + CXX: clang-22 + + - name: Clang-Tidy (C++14) + run: | + run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output.cxx14 + + - name: Prepare CMake (C++17) + run: | + cmake -S . -B cmake.output.cxx17 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=17 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + env: + CXX: clang-22 + + - name: Clang-Tidy (C++17) + run: | + run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output.cxx17 + + - name: Prepare CMake (C++20) + run: | + cmake -S . -B cmake.output.cxx20 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=20 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + env: + CXX: clang-22 + + - name: Clang-Tidy (C++20) + run: | + run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output.cxx20 + + - name: Prepare CMake (C++23) + run: | + cmake -S . -B cmake.output.cxx23 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=23 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + env: + CXX: clang-22 + + - name: Clang-Tidy (C++23) + run: | + run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output.cxx23 + + - name: Prepare CMake (C++26) + run: | + cmake -S . -B cmake.output.cxx26 -Werror=dev --warn-uninitialized -DCMAKE_CXX_STANDARD=26 -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + env: + CXX: clang-22 + + - name: Clang-Tidy (C++26) + run: | + run-clang-tidy-22 -q -j $(nproc) -enable-check-profile -p=cmake.output.cxx26 From df97c306138dfef106b4d2639050a43204535c60 Mon Sep 17 00:00:00 2001 From: glankk Date: Fri, 24 Jul 2026 14:58:39 +0200 Subject: [PATCH 117/152] Fix #686: Add warning for bad line directives (#687) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Marjamäki --- main.cpp | 9 +- simplecpp.cpp | 104 ++++++++++++-- simplecpp.h | 73 +++++----- test.cpp | 383 ++++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 445 insertions(+), 124 deletions(-) diff --git a/main.cpp b/main.cpp index abb07228..c1621544 100644 --- a/main.cpp +++ b/main.cpp @@ -224,7 +224,7 @@ int main(int argc, char **argv) { simplecpp::TokenList *rawtokens; if (toklist_inf == Fstream) { - rawtokens = new simplecpp::TokenList(f,files,filename,&outputList); + rawtokens = new simplecpp::TokenList(f,files,filename,dui,&outputList); } else if (toklist_inf == Sstream || toklist_inf == CharBuffer) { std::ostringstream oss; @@ -233,14 +233,14 @@ int main(int argc, char **argv) const std::string s = oss.str(); if (toklist_inf == Sstream) { std::istringstream iss(s); - rawtokens = new simplecpp::TokenList(iss,files,filename,&outputList); + rawtokens = new simplecpp::TokenList(iss,files,filename,dui,&outputList); } else { - rawtokens = new simplecpp::TokenList({s.data(),s.size()},files,filename,&outputList); + rawtokens = new simplecpp::TokenList({s.data(),s.size()},files,filename,dui,&outputList); } } else { f.close(); - rawtokens = new simplecpp::TokenList(filename,files,&outputList); + rawtokens = new simplecpp::TokenList(filename,files,dui,&outputList); } rawtokens->removeComments(); simplecpp::FileDataCache filedata; @@ -276,6 +276,7 @@ int main(int argc, char **argv) std::cerr << "directive as macro parameter: "; break; case simplecpp::Output::PORTABILITY_BACKSLASH: + case simplecpp::Output::PORTABILITY_LINE_DIRECTIVE: std::cerr << "portability: "; break; case simplecpp::Output::PORTABILITY_NO_EOF_NEWLINE: diff --git a/simplecpp.cpp b/simplecpp.cpp index 57b55e91..48243b6e 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -475,26 +475,26 @@ namespace { simplecpp::TokenList::TokenList(std::vector &filenames) : frontToken(nullptr), backToken(nullptr), files(filenames) {} -simplecpp::TokenList::TokenList(std::istream &istr, std::vector &filenames, const std::string &filename, OutputList *outputList) +simplecpp::TokenList::TokenList(std::istream &istr, std::vector &filenames, const std::string &filename, const DUI &dui, OutputList *outputList) : frontToken(nullptr), backToken(nullptr), files(filenames) { StdIStream stream(istr); - readfile(stream,filename,outputList); + readfile(stream,filename,dui,outputList); } -simplecpp::TokenList::TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, OutputList *outputList, int /*unused*/) +simplecpp::TokenList::TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename, const DUI &dui, OutputList *outputList, int /*unused*/) : frontToken(nullptr), backToken(nullptr), files(filenames) { StdCharBufStream stream(data, size); - readfile(stream,filename,outputList); + readfile(stream,filename,dui,outputList); } -simplecpp::TokenList::TokenList(const std::string &filename, std::vector &filenames, OutputList *outputList) +simplecpp::TokenList::TokenList(const std::string &filename, std::vector &filenames, const DUI &dui, OutputList *outputList) : frontToken(nullptr), backToken(nullptr), files(filenames) { try { FileStream stream(filename, filenames); - readfile(stream,filename,outputList); + readfile(stream,filename,dui,outputList); } catch (const simplecpp::Output & e) { outputList->emplace_back(e); } @@ -659,13 +659,16 @@ void simplecpp::TokenList::lineDirective(unsigned int fileIndex_, unsigned int l static const std::string COMMENT_END("*/"); -void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, OutputList *outputList) +void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, const DUI &dui, OutputList *outputList) { unsigned int multiline = 0U; bool trailing_nl = true; const Token *oldLastToken = nullptr; + const cstd_t cstd = getCStd(dui.std); + const cppstd_t cppstd = getCppStd(dui.std); + Location location(fileIndex(filename), 1, 1); while (stream.good()) { unsigned char ch = stream.readChar(); @@ -727,10 +730,64 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (ppTok->str() == "line") ppTok = advanceAndSkipComments(ppTok); + if (ppTok && (ppTok->str()[0] == '-' || ppTok->str()[0] == '+')) { + if (outputList) { + simplecpp::Output err{ + simplecpp::Output::SYNTAX_ERROR, + location, + "Invalid character in line directive: '" + ppTok->str() + "'." + }; + outputList->emplace_back(std::move(err)); + } + clear(); + return; + } + if (!ppTok || !ppTok->number) continue; - const unsigned int line = std::atol(ppTok->str().c_str()); + constexpr unsigned long line_limit = std::numeric_limits::max(); + unsigned long line; + try { + line = std::min(line_limit, std::stoul(ppTok->str())); + } catch (...) { + line = line_limit; + } + + unsigned long maxline; + if ((cstd != CUnknown && cstd < C99) || (cppstd != CPPUnknown && cppstd < CPP11)) + maxline = 32767; + else + maxline = 2147483647; + + if (line == 0 || line > maxline) { + if (outputList) { + const bool unknown_std = cstd == CUnknown && cppstd == CPPUnknown; + std::string msg = "Line number out of range: " + ppTok->str() + ". "; + if (line == 0) { + msg += "Line number zero is undefined behavior."; + } else { + msg += "Line numbers above " + std::to_string(maxline) + " are "; + if (unknown_std) + msg += "undefined behavior or conditionally supported"; + else if (cppstd >= CPP26) + msg += "conditionally supported"; + else + msg += "undefined behavior"; + if (cstd != CUnknown) + msg += std::string(" in ") + getCStdName(cstd); + else if (cppstd != CPPUnknown) + msg += std::string(" in ") + getCppStdName(cppstd); + msg += "."; + } + simplecpp::Output err{ + simplecpp::Output::PORTABILITY_LINE_DIRECTIVE, + location, msg + }; + outputList->emplace_back(std::move(err)); + } + } + ppTok = advanceAndSkipComments(ppTok); unsigned int fileindex; @@ -3163,7 +3220,7 @@ std::pair simplecpp::FileDataCache::tryload(FileDat return {id_it->second, false}; } - auto *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(); @@ -3999,6 +4056,20 @@ simplecpp::cstd_t simplecpp::getCStd(const std::string &std) return CUnknown; } +const char *simplecpp::getCStdName(cstd_t std) +{ + switch (std) { + case CUnknown: return "C"; + case C89: return "C89"; + case C99: return "C99"; + case C11: return "C11"; + case C17: return "C17"; + case C23: return "C23"; + case C2Y: return "C2Y"; + } + return ""; +} + std::string simplecpp::getCStdString(cstd_t std) { switch (std) { @@ -4051,6 +4122,21 @@ simplecpp::cppstd_t simplecpp::getCppStd(const std::string &std) return CPPUnknown; } +const char *simplecpp::getCppStdName(cppstd_t std) +{ + switch (std) { + case CPPUnknown: return "C++"; + case CPP03: return "C++03"; + case CPP11: return "C++11"; + case CPP14: return "C++14"; + case CPP17: return "C++17"; + case CPP20: return "C++20"; + case CPP23: return "C++23"; + case CPP26: return "C++26"; + } + return ""; +} + std::string simplecpp::getCppStdString(cppstd_t std) { switch (std) { diff --git a/simplecpp.h b/simplecpp.h index 10f7d2a8..e64a8f7b 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -249,6 +249,7 @@ namespace simplecpp { SYNTAX_ERROR, DIRECTIVE_AS_MACRO_PARAMETER, PORTABILITY_BACKSLASH, + PORTABILITY_LINE_DIRECTIVE, PORTABILITY_NO_EOF_NEWLINE, UNHANDLED_CHAR_ERROR, EXPLICIT_INCLUDE_NOT_FOUND, @@ -262,6 +263,21 @@ namespace simplecpp { using OutputList = std::list; + /** + * Command line preprocessor settings. + * On the command line these are configured by -D, -U, -I, --include, -std + */ + struct SIMPLECPP_LIB DUI { + 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 */ + }; + /** List of tokens. */ class SIMPLECPP_LIB TokenList { public: @@ -269,45 +285,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); + TokenList(std::istream &istr, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr); /** 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) + TokenList(const char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data), size-1, filenames, filename, dui, 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) + TokenList(const unsigned char (&data)[size], std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(data, size-1, filenames, filename, dui, outputList, 0) {} #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) + TokenList(const unsigned char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(data, size, filenames, filename, dui, 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(reinterpret_cast(data), size, filenames, filename, outputList, 0) + TokenList(const char* data, std::size_t size, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data), size, filenames, filename, dui, outputList, 0) {} #endif // SIMPLECPP_TOKENLIST_ALLOW_PTR /** generates a token list from the given buffer */ - 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) + TokenList(View data, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, dui, outputList, 0) {} #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(std::span data, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(reinterpret_cast(data.data()), data.size(), filenames, filename, dui, 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(std::span data, std::vector &filenames, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr) + : TokenList(data.data(), data.size(), filenames, filename, dui, outputList, 0) {} #endif // __cpp_lib_span /** generates a token list from the given filename parameter */ - TokenList(const std::string &filename, std::vector &filenames, OutputList *outputList = nullptr); + TokenList(const std::string &filename, std::vector &filenames, const DUI &dui = {}, OutputList *outputList = nullptr); TokenList(const TokenList &other); TokenList(TokenList &&other); ~TokenList(); @@ -323,7 +339,7 @@ namespace simplecpp { 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 readfile(Stream &stream, const std::string &filename=std::string(), const DUI &dui = {}, OutputList *outputList = nullptr); /** * @throws std::overflow_error thrown on overflow or division by zero * @throws std::runtime_error thrown on invalid expressions @@ -387,7 +403,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, const DUI &dui, OutputList *outputList, int /*unused*/); void combineOperators(); @@ -436,21 +452,6 @@ namespace simplecpp { long long result; // condition result }; - /** - * Command line preprocessor settings. - * On the command line these are configured by -D, -U, -I, --include, -std - */ - struct SIMPLECPP_LIB DUI { - 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 */ - }; - struct SIMPLECPP_LIB FileData { /** The canonical filename associated with this data */ std::string filename; @@ -589,9 +590,15 @@ namespace simplecpp { /** Returns the C version a given standard */ SIMPLECPP_LIB cstd_t getCStd(const std::string &std); + /** Returns the name of a C standard */ + SIMPLECPP_LIB const char *getCStdName(cstd_t std); + /** Returns the C++ version a given standard */ SIMPLECPP_LIB cppstd_t getCppStd(const std::string &std); + /** Returns the name of a C++ standard */ + SIMPLECPP_LIB const char *getCppStdName(cppstd_t std); + /** Returns the __STDC_VERSION__ value for a given standard */ SIMPLECPP_LIB std::string getCStdString(const std::string &std); SIMPLECPP_LIB std::string getCStdString(cstd_t std); diff --git a/test.cpp b/test.cpp index 76c45ecf..6b482c69 100644 --- a/test.cpp +++ b/test.cpp @@ -105,42 +105,48 @@ static void testcase(const std::string &name, void (*f)(), int argc, char * cons #define TEST_CASE(F) (testcase(#F, F, argc, argv)) -static simplecpp::TokenList makeTokenList(const char code[], std::size_t size, std::vector &filenames, const std::string &filename=std::string(), simplecpp::OutputList *outputList=nullptr) +static simplecpp::TokenList makeTokenList(const char code[], std::size_t size, std::vector &filenames, const std::string &filename=std::string(), const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList = nullptr) { switch (USE_INPUT) { case Input::Stringstream: { std::istringstream istr(std::string(code, size)); - return {istr,filenames,filename,outputList}; + return {istr,filenames,filename,dui,outputList}; } case Input::CharBuffer: - return {{code, size}, filenames, filename, outputList}; + return {{code, size}, filenames, filename, dui, 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) +static simplecpp::TokenList makeTokenList(const char code[], std::vector &filenames, const std::string &filename=std::string(), const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList=nullptr) { - return makeTokenList(code, strlen(code), filenames, filename, outputList); + return makeTokenList(code, strlen(code), filenames, filename, dui, outputList); } -static std::string readfile(const char code[], simplecpp::OutputList *outputList=nullptr) +static simplecpp::TokenList makeTokenList(const char code[], const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList=nullptr) { std::vector files; - return makeTokenList(code,files,std::string(),outputList).stringify(); + return makeTokenList(code, strlen(code), files, std::string(), dui, outputList); } -static std::string readfile(const char code[], std::size_t size, simplecpp::OutputList *outputList=nullptr) +static std::string readfile(const char code[], const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList = nullptr) { std::vector files; - return makeTokenList(code,size,files,std::string(),outputList).stringify(); + return makeTokenList(code,files,std::string(),dui,outputList).stringify(); +} + +static std::string readfile(const char code[], std::size_t size, const simplecpp::DUI &dui = {}, simplecpp::OutputList *outputList=nullptr) +{ + std::vector files; + return makeTokenList(code,size,files,std::string(),dui,outputList).stringify(); } static std::string preprocess(const char code[], std::size_t size, 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; - simplecpp::TokenList tokens = makeTokenList(code, size, files, file); + simplecpp::TokenList tokens = makeTokenList(code, size, files, file, dui, outputList); if (dui.removeComments) tokens.removeComments(); simplecpp::TokenList tokens2(files); @@ -217,6 +223,9 @@ static std::string toString(const simplecpp::OutputList &outputList) case simplecpp::Output::Type::PORTABILITY_BACKSLASH: ostr << "portability_backslash,"; break; + case simplecpp::Output::Type::PORTABILITY_LINE_DIRECTIVE: + ostr << "portability_line_directive,"; + break; case simplecpp::Output::Type::PORTABILITY_NO_EOF_NEWLINE: ostr << "portability_no_eof_newline,"; break; @@ -244,15 +253,15 @@ static void backslash() // preprocessed differently simplecpp::OutputList outputList; - readfile("//123 \\\n456\n", &outputList); + readfile("//123 \\\n456\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("//123 \\ \n456\n", &outputList); + readfile("//123 \\ \n456\n", {}, &outputList); ASSERT_EQUALS("file0,1,portability_backslash,Combination 'backslash space newline' is not portable.\n", toString(outputList)); outputList.clear(); - readfile("#define A \\\n123\n", &outputList); + readfile("#define A \\\n123\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("#define A \\ \n123\n", &outputList); + readfile("#define A \\ \n123\n", {}, &outputList); ASSERT_EQUALS("file0,1,portability_backslash,Combination 'backslash space newline' is not portable.\n", toString(outputList)); } @@ -927,7 +936,7 @@ static void define_invalid_1() static void define_invalid_2() { - const char code[] = "#define\nhas#"; + const char code[] = "#define\nhas#\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, bad macro syntax\n", toString(outputList)); @@ -936,7 +945,7 @@ static void define_invalid_2() static void define_invalid_3() { const char code[] = "#define R()\n" - "R"; + "R\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,2,syntax_error,failed to expand 'R', Wrong number of parameters for macro 'R'.\n", toString(outputList)); @@ -945,7 +954,7 @@ static void define_invalid_3() static void define_invalid_4() { const char code[] = "#define X(...)\n" - "X"; + "X\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,2,syntax_error,failed to expand 'X', Wrong number of parameters for macro 'X'.\n", toString(outputList)); @@ -1255,7 +1264,7 @@ static void define_va_opt_3() // non-escaped newline without closing parenthesis const char code1[] = "#define err(...) __VA_OPT__(printf( __VA_ARGS__);\n" ")\n" - "err()"; + "err()\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code1, &outputList)); @@ -1267,7 +1276,7 @@ static void define_va_opt_3() // non-escaped newline without open parenthesis const char code2[] = "#define err(...) __VA_OPT__\n" "(something)\n" - "err()"; + "err()\n"; ASSERT_EQUALS("", preprocess(code2, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing opening parenthesis for __VA_OPT__\n", @@ -1278,7 +1287,7 @@ static void define_va_opt_4() { // missing parenthesis const char code1[] = "#define err(...) __VA_OPT__ something\n" - "err()"; + "err()\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code1, &outputList)); @@ -1289,7 +1298,7 @@ static void define_va_opt_4() // missing open parenthesis const char code2[] = "#define err(...) __VA_OPT__ something)\n" - "err()"; + "err()\n"; ASSERT_EQUALS("", preprocess(code2, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing opening parenthesis for __VA_OPT__\n", @@ -1300,7 +1309,7 @@ static void define_va_opt_5() { // parenthesis not directly proceeding __VA_OPT__ const char code[] = "#define err(...) __VA_OPT__ something (something)\n" - "err()"; + "err()\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); @@ -1312,7 +1321,7 @@ static void define_va_opt_6() { // nested __VA_OPT__ const char code[] = "#define err(...) __VA_OPT__(__VA_OPT__(something))\n" - "err()"; + "err()\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); @@ -1323,7 +1332,7 @@ static void define_va_opt_6() static void define_va_opt_7() { // eof in __VA_OPT__ - const char code1[] = "#define err(...) __VA_OPT__"; + const char code1[] = "#define err(...) __VA_OPT__\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code1, &outputList)); @@ -1332,7 +1341,7 @@ static void define_va_opt_7() outputList.clear(); - const char code2[] = "#define err(...) __VA_OPT__("; + const char code2[] = "#define err(...) __VA_OPT__(\n"; ASSERT_EQUALS("", preprocess(code2, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing closing parenthesis for __VA_OPT__\n", @@ -1340,7 +1349,7 @@ static void define_va_opt_7() outputList.clear(); - const char code3[] = "#define err(...) __VA_OPT__(x"; + const char code3[] = "#define err(...) __VA_OPT__(x\n"; ASSERT_EQUALS("", preprocess(code3, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing closing parenthesis for __VA_OPT__\n", @@ -1350,7 +1359,7 @@ static void define_va_opt_7() static void define_va_opt_8() { const char code[] = "#define f(...) #__VA_OPT__(x)\n" - "const char* v1 = f();"; + "const char* v1 = f();\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("\nconst char * v1 = \"\" ;", preprocess(code, &outputList)); @@ -1382,7 +1391,7 @@ static void define_ifdef() static void if_invalid_1() { - const char code[] = "#if'\\u'"; + const char code[] = "#if'\\u'\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, expected digit\n", toString(outputList)); @@ -1390,7 +1399,7 @@ static void if_invalid_1() static void if_invalid_2() { - const char code[] = "#if-0xBBB4444444444444%~B"; + const char code[] = "#if-0xBBB4444444444444%~B\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, division overflow\n", toString(outputList)); @@ -1398,7 +1407,7 @@ static void if_invalid_2() static void if_invalid_3() { - const char code[] = "#if@u'\\udefa'"; + const char code[] = "#if@u'\\udefa'\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, surrogate code points not allowed in universal character names\n", toString(outputList)); @@ -1456,7 +1465,7 @@ static void error3() " bla bla.\"\n"; std::vector files; simplecpp::OutputList outputList; - const simplecpp::TokenList rawtokens = makeTokenList(code, files, "test.c", &outputList); + const simplecpp::TokenList rawtokens = makeTokenList(code, files, "test.c", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } @@ -1699,19 +1708,19 @@ static void hashhash9() simplecpp::OutputList outputList; code = "#define A +##x\n" - "A"; + "A\n"; outputList.clear(); ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '+' and 'x' yields an invalid token.\n", toString(outputList)); code = "#define A 2##=\n" - "A"; + "A\n"; outputList.clear(); ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '2' and '=' yields an invalid token.\n", toString(outputList)); code = "#define A <<##x\n" - "A"; + "A\n"; outputList.clear(); ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '<<' and 'x' yields an invalid token.\n", toString(outputList)); @@ -1880,7 +1889,7 @@ static void hashhash_int_literal() static void hashhash_invalid_1() { - const char code[] = "#define f(a) (##x)\nf(1)"; + const char code[] = "#define f(a) (##x)\nf(1)\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'f', Invalid ## usage when expanding 'f': Unexpected token '('\n", toString(outputList)); @@ -1888,7 +1897,7 @@ static void hashhash_invalid_1() static void hashhash_invalid_2() { - const char code[] = "#define f(a) (x##)\nf(1)"; + const char code[] = "#define f(a) (x##)\nf(1)\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'f', Invalid ## usage when expanding 'f': Unexpected token ')'\n", toString(outputList)); @@ -1897,7 +1906,7 @@ static void hashhash_invalid_2() static void hashhash_invalid_string_number() { const char code[] = - "#define BAD(x) x##12345\nBAD(\"ABC\")"; + "#define BAD(x) x##12345\nBAD(\"ABC\")\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); @@ -1907,7 +1916,7 @@ static void hashhash_invalid_string_number() static void hashhash_invalid_missing_args() { const char code[] = - "#define BAD(x) ##x\nBAD()"; + "#define BAD(x) ##x\nBAD()\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); @@ -1941,7 +1950,7 @@ static void hashhash_va_args_unexpected() { const char code[] = "#define C(...)!##__VA_ARGS__\n" - "C(1)"; + "C(1)\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'C', Invalid ## usage when expanding 'C': Unexpected token '!'\n", toString(outputList)); @@ -1950,7 +1959,7 @@ static void hashhash_va_args_unexpected() static void hashhash_universal_character() { const char code[] = - "#define A(x,y) x##y\nint A(\\u01,04);"; + "#define A(x,y) x##y\nint A(\\u01,04);\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '\\u01' and '04' yields universal character '\\u0104'. This is undefined behavior according to C standard chapter 5.1.1.2, paragraph 4.\n", toString(outputList)); @@ -1959,7 +1968,7 @@ static void hashhash_universal_character() static void hashhash_universal_character_2() { const char code[] = - "#define A(x,y) x##y\nint A(\\U0104, 0104);"; + "#define A(x,y) x##y\nint A(\\U0104, 0104);\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '\\U0104' and '0104' yields universal character '\\U01040104'. This is undefined behavior according to C standard chapter 5.1.1.2, paragraph 4.\n", toString(outputList)); @@ -2098,7 +2107,7 @@ static void has_include_6() static void define_has_include_invalid_1() { const char code[] = "#define A)__has_include\n" - "#if\u000BA"; + "#if\u000BA\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, missing __has_include argument\n", toString(outputList)); @@ -2107,7 +2116,7 @@ static void define_has_include_invalid_1() static void define_has_include_invalid_2() { const char code[] = "#define f __has_include\n" - "#if#f<"; + "#if#f<\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, missing __has_include argument\n", toString(outputList)); @@ -2116,18 +2125,18 @@ static void define_has_include_invalid_2() static void define_has_include_invalid_3() { const char code[] = "#define\u0000X\u0007__has_include(\n" - "#if%X&"; + "#if%X&\n"; simplecpp::OutputList outputList; - ASSERT_EQUALS("", preprocess(code, sizeof(code), &outputList)); + ASSERT_EQUALS("", preprocess(code, sizeof(code)-1, &outputList)); ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, invalid __has_include expression\n", toString(outputList)); } static void define_has_include_invalid_4() { const char code[] = "#define\u0000X\u0000__has_include<2\n" - "#if*X"; + "#if*X\n"; simplecpp::OutputList outputList; - ASSERT_EQUALS("", preprocess(code, sizeof(code), &outputList)); + ASSERT_EQUALS("", preprocess(code, sizeof(code)-1, &outputList)); ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #if condition, invalid __has_include expression\n", toString(outputList)); } @@ -2276,7 +2285,7 @@ static void ifDefinedNestedNoPar() static void ifDefinedInvalid1() // #50 - invalid unterminated defined { - const char code[] = "#if defined(A"; + const char code[] = "#if defined(A\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition\n", toString(outputList)); @@ -2284,7 +2293,7 @@ static void ifDefinedInvalid1() // #50 - invalid unterminated defined static void ifDefinedInvalid2() { - const char code[] = "#if defined"; + const char code[] = "#if defined\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition\n", toString(outputList)); @@ -2584,6 +2593,7 @@ static void location12() preprocess(code)); } + static void missingHeader1() { const char code[] = "#include \"notexist.h\"\n"; @@ -2847,6 +2857,221 @@ static void nullDirective3() ASSERT_EQUALS("\n\n\n\nx = 1 ;", preprocess(code)); } +static void lineDirective() +{ + for (const std::string std : {"c89", "c90", "c++03"}) { + std::string std_name; + if (simplecpp::getCStd(std) != simplecpp::CUnknown) + std_name = simplecpp::getCStdName(simplecpp::getCStd(std)); + else + std_name = simplecpp::getCppStdName(simplecpp::getCppStd(std)); + + simplecpp::DUI dui; + dui.std = std; + + { + const char code[] = + "#line -1\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,syntax_error,Invalid character in line directive: '-'.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 0\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 0. Line number zero is undefined behavior.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 32767\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 32768\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 32768. Line numbers above 32767 are undefined behavior in " + std_name + ".\n", + toString(outputList)); + } + + { + const char code[] = + "#line 18446744073709551617\n" // 2^64 + 1 + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 18446744073709551617. Line numbers above 32767 are undefined behavior in " + std_name + ".\n", + toString(outputList)); + } + } + + for (const std::string std : {"c99", "c11", "c17", "c23", "c2y", "c++11", "c++14", "c++17", "c++20", "c++23"}) { + std::string std_name; + if (simplecpp::getCStd(std) != simplecpp::CUnknown) + std_name = simplecpp::getCStdName(simplecpp::getCStd(std)); + else + std_name = simplecpp::getCppStdName(simplecpp::getCppStd(std)); + + simplecpp::DUI dui; + dui.std = std; + + { + const char code[] = + "#line -1\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,syntax_error,Invalid character in line directive: '-'.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 0\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 0. Line number zero is undefined behavior.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 32767\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 32768\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 2147483647\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 2147483648\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 2147483648. Line numbers above 2147483647 are undefined behavior in " + std_name + ".\n", + toString(outputList)); + } + + { + const char code[] = + "#line 18446744073709551617\n" // 2^64 + 1 + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 18446744073709551617. Line numbers above 2147483647 are undefined behavior in " + std_name + ".\n", + toString(outputList)); + } + } + + { + const std::string std_name = "C++26"; + + simplecpp::DUI dui; + dui.std = "c++26"; + + { + const char code[] = + "#line -1\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,syntax_error,Invalid character in line directive: '-'.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 0\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 0. Line number zero is undefined behavior.\n", + toString(outputList)); + } + + { + const char code[] = + "#line 32767\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 32768\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 2147483647\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + + { + const char code[] = + "#line 2147483648\n" + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 2147483648. Line numbers above 2147483647 are conditionally supported in " + std_name + ".\n", + toString(outputList)); + } + + { + const char code[] = + "#line 18446744073709551617\n" // 2^64 + 1 + ";\n"; + simplecpp::OutputList outputList; + makeTokenList(code, dui, &outputList); + ASSERT_EQUALS("file0,2,portability_line_directive,Line number out of range: 18446744073709551617. Line numbers above 2147483647 are conditionally supported in " + std_name + ".\n", + toString(outputList)); + } + } +} + static void include1() { const char code[] = "#include \"A.h\"\n"; @@ -3086,7 +3311,7 @@ static void readfile_nullbyte() { const char code[] = "ab\0cd\n"; simplecpp::OutputList outputList; - ASSERT_EQUALS("ab cd", readfile(code,sizeof(code)-1, &outputList)); + ASSERT_EQUALS("ab cd", readfile(code,sizeof(code)-1,{},&outputList)); ASSERT_EQUALS(true, outputList.empty()); // should warning be written? } @@ -3114,11 +3339,11 @@ static void readfile_char_error() { simplecpp::OutputList outputList; - readfile("A = L's", &outputList); + readfile("A = L's", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\'). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); outputList.clear(); - readfile("A = 's\n'", &outputList); + readfile("A = 's\n'", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\'). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); } @@ -3172,36 +3397,36 @@ static void readfile_string_error() { simplecpp::OutputList outputList; - readfile("A = \"abs", &outputList); + readfile("A = \"abs", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\"). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); outputList.clear(); - readfile("A = u8\"abs\n\"", &outputList); + readfile("A = u8\"abs\n\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\"). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); outputList.clear(); - readfile("A = R\"as\n(abc)as\"", &outputList); + readfile("A = R\"as\n(abc)as\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,Invalid newline in raw string delimiter.\n", toString(outputList)); outputList.clear(); - readfile("A = u8R\"as\n(abc)as\"", &outputList); + readfile("A = u8R\"as\n(abc)as\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,Invalid newline in raw string delimiter.\n", toString(outputList)); outputList.clear(); - readfile("A = R\"as(abc)a\"", &outputList); + readfile("A = R\"as(abc)a\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,Raw string missing terminating delimiter.\n", toString(outputList)); outputList.clear(); - readfile("A = LR\"as(abc)a\"", &outputList); + readfile("A = LR\"as(abc)a\"", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,Raw string missing terminating delimiter.\n", toString(outputList)); outputList.clear(); - readfile("#define A \"abs", &outputList); + readfile("#define A \"abs", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\"). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); outputList.clear(); // Don't warn for a multiline define - readfile("#define A \"abs\\\n\"\n", &outputList); + readfile("#define A \"abs\\\n\"\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } @@ -3213,11 +3438,11 @@ static void readfile_cpp14_number() static void readfile_unhandled_chars() { simplecpp::OutputList outputList; - readfile("// 你好世界\n", &outputList); + readfile("// 你好世界\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("s=\"你好世界\"\n", &outputList); + readfile("s=\"你好世界\"\n", {}, &outputList); ASSERT_EQUALS("", toString(outputList)); - readfile("int 你好世界=0;\n", &outputList); + readfile("int 你好世界=0;\n", {}, &outputList); ASSERT_EQUALS("file0,1,unhandled_char_error,The code contains unhandled character(s) (character code=228). Neither unicode nor extended ascii is supported.\n", toString(outputList)); } @@ -3233,7 +3458,7 @@ static void readfile_file_not_found() { simplecpp::OutputList outputList; std::vector files; - (void)simplecpp::TokenList("NotAFile", files, &outputList); + (void)simplecpp::TokenList("NotAFile", files, {}, &outputList); ASSERT_EQUALS("file0,0,file_not_found,File is missing: NotAFile\n", toString(outputList)); } @@ -3242,61 +3467,61 @@ static void readfile_no_eof_newline() { const char code[] = ""; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } { const char code[] = "\n"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } { const char code[] = "\\\n"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "#define A"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "#define A\n"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } { const char code[] = "#define A\\"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "// comment"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "// comment\n"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } { const char code[] = "/* comment \n comment */"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("file0,2,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); } { const char code[] = "/* comment \n comment */\n"; simplecpp::OutputList outputList; - readfile(code, sizeof(code)-1, &outputList); + readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } } @@ -3510,7 +3735,7 @@ static void unicode_invalid() static void warning() { - const char code[] = "#warning MSG\n1"; + const char code[] = "#warning MSG\n1\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("\n1", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,#warning,#warning MSG\n", toString(outputList)); @@ -3606,17 +3831,17 @@ static void preprocessSizeOf() { simplecpp::OutputList outputList; - ASSERT_EQUALS("", preprocess("#if 3 > sizeof", &outputList)); + ASSERT_EQUALS("", preprocess("#if 3 > sizeof\n", &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, missing sizeof argument\n", toString(outputList)); outputList.clear(); - ASSERT_EQUALS("", preprocess("#if 3 > sizeof A", &outputList)); + ASSERT_EQUALS("", preprocess("#if 3 > sizeof A\n", &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, missing sizeof argument\n", toString(outputList)); outputList.clear(); - ASSERT_EQUALS("", preprocess("#if 3 > sizeof(int", &outputList)); + ASSERT_EQUALS("", preprocess("#if 3 > sizeof(int\n", &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, invalid sizeof expression\n", toString(outputList)); } @@ -4361,6 +4586,8 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(nullDirective2); TEST_CASE(nullDirective3); + TEST_CASE(lineDirective); + TEST_CASE(include1); TEST_CASE(include2); TEST_CASE(include3); From 1537e9e98a726865ba2d35e46bdfbc073506a10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Fri, 24 Jul 2026 15:21:18 +0200 Subject: [PATCH 118/152] Fix #680: Add option to keep comments in preprocessed code (#681) --- main.cpp | 3 ++- simplecpp.cpp | 10 +++++----- test.cpp | 37 +++++++++++++++++++++++++++++++++---- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/main.cpp b/main.cpp index c1621544..fc11f72a 100644 --- a/main.cpp +++ b/main.cpp @@ -242,7 +242,8 @@ int main(int argc, char **argv) f.close(); rawtokens = new simplecpp::TokenList(filename,files,dui,&outputList); } - rawtokens->removeComments(); + if (dui.removeComments) + rawtokens->removeComments(); simplecpp::FileDataCache filedata; simplecpp::preprocess(outputTokens, *rawtokens, files, filedata, dui, &outputList); simplecpp::cleanup(filedata); diff --git a/simplecpp.cpp b/simplecpp.cpp index 48243b6e..26b1af0d 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3411,7 +3411,7 @@ 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::DUI &dui) { const simplecpp::Token * const tok = tok1; const simplecpp::MacroMap::const_iterator it = tok->name ? macros.find(tok->str()) : macros.end(); @@ -3442,7 +3442,7 @@ static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token } output.takeTokens(value); } else { - if (!tok->comment) + if (!tok->comment || !dui.removeComments) output.push_back(new simplecpp::Token(*tok)); tok1 = tok->next; } @@ -3717,7 +3717,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, dui)) { output.clear(); return; } @@ -3899,7 +3899,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); const Token *tmp = tok; - if (!preprocessToken(expr, tmp, macros, files, outputList)) { + if (!preprocessToken(expr, tmp, macros, files, outputList, dui)) { output.clear(); return; } @@ -3997,7 +3997,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, dui)) { output.clear(); return; } diff --git a/test.cpp b/test.cpp index 6b482c69..9b5fa3cb 100644 --- a/test.cpp +++ b/test.cpp @@ -533,12 +533,15 @@ static void combineOperators_ellipsis() static void comment() { + simplecpp::DUI dui; + dui.removeComments = true; + ASSERT_EQUALS("// abc", readfile("// abc")); - ASSERT_EQUALS("", preprocess("// abc")); + ASSERT_EQUALS("", preprocess("// abc", dui)); ASSERT_EQUALS("/*\n\n*/abc", readfile("/*\n\n*/abc")); - ASSERT_EQUALS("\n\nabc", preprocess("/*\n\n*/abc")); + ASSERT_EQUALS("\n\nabc", preprocess("/*\n\n*/abc", dui)); ASSERT_EQUALS("* p = a / * b / * c ;", readfile("*p=a/ *b/ *c;")); - ASSERT_EQUALS("* p = a / * b / * c ;", preprocess("*p=a/ *b/ *c;")); + ASSERT_EQUALS("* p = a / * b / * c ;", preprocess("*p=a/ *b/ *c;", dui)); } static void comment_multiline() @@ -578,6 +581,27 @@ static void comment_multiline() ASSERT_EQUALS("// abc\ndef", readfile("// abc\\\ndef")); } +static void keep_comments() +{ + simplecpp::DUI dui; + dui.removeComments = false; + + { + const char code[] = "/* comment */\n"; + ASSERT_EQUALS("/* comment */", preprocess(code,dui)); + } + + { + const char code[] = "// comment\n"; + ASSERT_EQUALS("// comment", preprocess(code,dui)); + } + + { + const char code[] = "#define MACRO /* comment */\nMACRO\n"; + ASSERT_EQUALS("\n/* comment */", preprocess(code,dui)); + } +} + static void constFold() { @@ -2584,13 +2608,16 @@ static void location11() static void location12() { + simplecpp::DUI dui; + dui.removeComments = true; + const char code[] = "/**//**/#/**//**/line/**//**/3/**//**/\"file.c\"/**/\n" "__LINE__ __FILE__\n"; ASSERT_EQUALS("\n" "#line 3 \"file.c\"\n" "3 \"file.c\"", - preprocess(code)); + preprocess(code, dui)); } @@ -3244,6 +3271,7 @@ static void include9() simplecpp::TokenList out(files); simplecpp::DUI dui; dui.includePaths.emplace_back("."); + dui.removeComments = true; simplecpp::preprocess(out, rawtokens_c, files, cache, dui); ASSERT_EQUALS("\n#line 2 \"1.h\"\nx = 1 ;", out.stringify()); @@ -4383,6 +4411,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(comment); TEST_CASE(comment_multiline); + TEST_CASE(keep_comments); TEST_CASE(constFold); From 5d81c8a03ee1d068c8846aee5c0cc7b22c08d4db Mon Sep 17 00:00:00 2001 From: glankk Date: Fri, 24 Jul 2026 20:18:58 +0200 Subject: [PATCH 119/152] Improve no_eof_newline warning (#692) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Marjamäki --- main.cpp | 7 ------- simplecpp.cpp | 4 ++-- test.cpp | 18 +++++++++++++----- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/main.cpp b/main.cpp index fc11f72a..92ca27dc 100644 --- a/main.cpp +++ b/main.cpp @@ -278,14 +278,7 @@ int main(int argc, char **argv) break; case simplecpp::Output::PORTABILITY_BACKSLASH: case simplecpp::Output::PORTABILITY_LINE_DIRECTIVE: - std::cerr << "portability: "; - break; case simplecpp::Output::PORTABILITY_NO_EOF_NEWLINE: - if (simplecpp::getCStd(dui.std) == simplecpp::CUnknown) { - // Only UB for c code, suppress for c++ code - // If no standard is specified then prefer to have a false negative - continue; - } std::cerr << "portability: "; break; case simplecpp::Output::UNHANDLED_CHAR_ERROR: diff --git a/simplecpp.cpp b/simplecpp.cpp index 26b1af0d..e97b25f8 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1022,11 +1022,11 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, location.adjust(currentToken); } - if (!trailing_nl && outputList) { + if ((cstd != CUnknown || cppstd == CPPUnknown) && !trailing_nl && outputList) { Output err{ Output::PORTABILITY_NO_EOF_NEWLINE, location, - "No newline at end of file." + "No newline at end of file is undefined behavior in C." }; outputList->emplace_back(std::move(err)); } diff --git a/test.cpp b/test.cpp index 9b5fa3cb..5800a607 100644 --- a/test.cpp +++ b/test.cpp @@ -3508,13 +3508,21 @@ static void readfile_no_eof_newline() const char code[] = "\\\n"; simplecpp::OutputList outputList; readfile(code, sizeof(code)-1, {}, &outputList); - ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file is undefined behavior in C.\n", toString(outputList)); + } + { + const char code[] = "\\\n"; + simplecpp::DUI dui; + dui.std = "c++03"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, dui, &outputList); + ASSERT_EQUALS("", toString(outputList)); } { const char code[] = "#define A"; simplecpp::OutputList outputList; readfile(code, sizeof(code)-1, {}, &outputList); - ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file is undefined behavior in C.\n", toString(outputList)); } { const char code[] = "#define A\n"; @@ -3526,13 +3534,13 @@ static void readfile_no_eof_newline() const char code[] = "#define A\\"; simplecpp::OutputList outputList; readfile(code, sizeof(code)-1, {}, &outputList); - ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file is undefined behavior in C.\n", toString(outputList)); } { const char code[] = "// comment"; simplecpp::OutputList outputList; readfile(code, sizeof(code)-1, {}, &outputList); - ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file is undefined behavior in C.\n", toString(outputList)); } { const char code[] = "// comment\n"; @@ -3544,7 +3552,7 @@ static void readfile_no_eof_newline() const char code[] = "/* comment \n comment */"; simplecpp::OutputList outputList; readfile(code, sizeof(code)-1, {}, &outputList); - ASSERT_EQUALS("file0,2,portability_no_eof_newline,No newline at end of file.\n", toString(outputList)); + ASSERT_EQUALS("file0,2,portability_no_eof_newline,No newline at end of file is undefined behavior in C.\n", toString(outputList)); } { const char code[] = "/* comment \n comment */\n"; From f420e611de146e8aca493a5fb1823e98f2364194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Mon, 3 Aug 2026 14:48:24 +0200 Subject: [PATCH 120/152] Fix #688: Support `#elifdef` and `#elifndef` (#691) --- simplecpp.cpp | 18 ++++++++----- test.cpp | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index e97b25f8..93853617 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -96,6 +96,8 @@ static const simplecpp::TokenString IFNDEF("ifndef"); static const simplecpp::TokenString DEFINED("defined"); static const simplecpp::TokenString ELSE("else"); static const simplecpp::TokenString ELIF("elif"); +static const simplecpp::TokenString ELIFDEF("elifdef"); +static const simplecpp::TokenString ELIFNDEF("elifndef"); static const simplecpp::TokenString ENDIF("endif"); static const simplecpp::TokenString PRAGMA("pragma"); @@ -3637,7 +3639,9 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL continue; } - if (ifstates.size() <= 1U && (rawtok->str() == ELIF || rawtok->str() == ELSE || rawtok->str() == ENDIF)) { + if (ifstates.size() <= 1U && (rawtok->str() == ELIF || rawtok->str() == ELIFDEF || + rawtok->str() == ELIFNDEF || rawtok->str() == ELSE || + rawtok->str() == ENDIF)) { if (outputList) { simplecpp::Output err{ Output::SYNTAX_ERROR, @@ -3778,7 +3782,8 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL rawtok = filedata->tokens.cfront(); continue; } - } else if (rawtok->str() == IF || rawtok->str() == IFDEF || rawtok->str() == IFNDEF || rawtok->str() == ELIF) { + } else if (rawtok->str() == IF || rawtok->str() == IFDEF || rawtok->str() == IFNDEF || rawtok->str() == ELIF || + rawtok->str() == ELIFDEF || rawtok->str() == ELIFNDEF) { if (!sameline(rawtok,rawtok->next)) { if (outputList) { simplecpp::Output out{ @@ -3793,10 +3798,11 @@ 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 && + rawtok->str() != ELIFDEF && rawtok->str() != ELIFNDEF)) { conditionIsTrue = false; } - else if (rawtok->str() == IFDEF) { + else if (rawtok->str() == IFDEF || rawtok->str() == ELIFDEF) { const std::string &name = rawtok->next->str(); conditionIsTrue = (macros.find(name) != macros.end() || (hasInclude && name == HAS_INCLUDE)); maybeUsedMacros[name].emplace_back(rawtok->next->location); @@ -3805,7 +3811,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const long long result = conditionIsTrue ? 1 : 0; ifCond->emplace_back(rawtok->location, E, result); } - } else if (rawtok->str() == IFNDEF) { + } else if (rawtok->str() == IFNDEF || rawtok->str() == ELIFNDEF) { const std::string &name = rawtok->next->str(); conditionIsTrue = (macros.find(name) == macros.end() && !(hasInclude && name == HAS_INCLUDE)); maybeUsedMacros[name].emplace_back(rawtok->next->location); @@ -3936,7 +3942,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } } - if (rawtok->str() != ELIF) { + if (rawtok->str() != ELIF && rawtok->str() != ELIFDEF && rawtok->str() != ELIFNDEF) { // push a new ifstate.. if (ifstates.top() != True) ifstates.push(AlwaysFalse); diff --git a/test.cpp b/test.cpp index 5800a607..d059b237 100644 --- a/test.cpp +++ b/test.cpp @@ -2403,6 +2403,76 @@ static void elif() ASSERT_EQUALS("\n\n\n\n\n3", preprocess(code3)); } +static void elifdef() +{ + { + const char code[] = "#if 1\n" + "1\n" + "#elifdef X\n" + "2\n" + "#else\n" + "3\n" + "#endif"; + ASSERT_EQUALS("\n1", preprocess(code)); + } + { + const char code[] = "#define X\n" + "#if 0\n" + "1\n" + "#elifdef X\n" + "2\n" + "#else\n" + "3\n" + "#endif"; + ASSERT_EQUALS("\n\n\n\n2", preprocess(code)); + } + { + const char code[] = "#if 0\n" + "1\n" + "#elifdef X\n" + "2\n" + "#else\n" + "3\n" + "#endif"; + ASSERT_EQUALS("\n\n\n\n\n3", preprocess(code)); + } +} + +static void elifndef() +{ + { + const char code[] = "#if 1\n" + "1\n" + "#elifndef X\n" + "2\n" + "#else\n" + "3\n" + "#endif"; + ASSERT_EQUALS("\n1", preprocess(code)); + } + { + const char code[] = "#if 0\n" + "1\n" + "#elifndef X\n" + "2\n" + "#else\n" + "3\n" + "#endif"; + ASSERT_EQUALS("\n\n\n2", preprocess(code)); + } + { + const char code[] = "#define X\n" + "#if 0\n" + "1\n" + "#elifndef X\n" + "2\n" + "#else\n" + "3\n" + "#endif"; + ASSERT_EQUALS("\n\n\n\n\n\n3", preprocess(code)); + } +} + static void ifif() { // source code from LLVM @@ -4591,6 +4661,8 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(ifLogical); TEST_CASE(ifSizeof); TEST_CASE(elif); + TEST_CASE(elifdef); + TEST_CASE(elifndef); TEST_CASE(ifif); TEST_CASE(ifoverflow); TEST_CASE(ifdiv0); From 74a5a63fe1f3a7263bf9e2c6a70d59b00787726d Mon Sep 17 00:00:00 2001 From: Paul Fultz II Date: Tue, 4 Aug 2026 08:02:06 -0500 Subject: [PATCH 121/152] Fix rescanning when a macro expansion result forms a new function-like macro call (#682) --- simplecpp.cpp | 56 +++++++++++++++++++++++++-------------------------- test.cpp | 43 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 29 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 93853617..4b24e311 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1707,7 +1707,7 @@ namespace simplecpp { if (output2.cfront() != output2.cback() && macro2tok->str() == this->name()) break; const MacroMap::const_iterator macro = macros.find(macro2tok->str()); - if (macro == macros.end() || !macro->second.functionLike()) + if (macro == macros.end() || !macro->second.functionLike() || macro2tok->isExpandedFrom(¯o->second)) break; TokenList rawtokens2(inputFiles); const Location loc(macro2tok->location); @@ -2161,39 +2161,37 @@ 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 { - if (!temp.cback() || !temp.cback()->name || !tok->next || tok->next->op != '(') { - output.takeTokens(temp); - return tok->next; - } - - if (!sameline(tok, tok->next)) { - output.takeTokens(temp); - return tok->next; - } - + /** Returns the macro to expand when the last token of @p temp is the name of a + * function-like macro and the tokens after @p tok supply its arguments; nullptr otherwise */ + static const Macro *rescanMacro(const TokenList &temp, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros) { + if (!temp.cback() || !temp.cback()->name || !sameline(tok, tok->next) || tok->next->op != '(') + return nullptr; const MacroMap::const_iterator it = macros.find(temp.cback()->str()); - if (it == macros.end() || expandedmacros.find(temp.cback()->str()) != expandedmacros.end()) { - output.takeTokens(temp); - return tok->next; - } + if (it == macros.end() || expandedmacros.find(temp.cback()->str()) != expandedmacros.end()) + return nullptr; + if (!it->second.functionLike() || temp.cback()->isExpandedFrom(&it->second)) + return nullptr; + return &it->second; + } - const Macro &calledMacro = it->second; - if (!calledMacro.functionLike()) { + 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 { + // Expand while the expansion result ends with the name of a function-like + // macro whose arguments are supplied by the tokens that follow it. Each round + // consumes that macro call from the token stream, so tok always advances. + while (const Macro * const calledMacro = rescanMacro(temp, tok, macros, expandedmacros)) { + 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); + if (!tok2) + break; output.takeTokens(temp); - return tok->next; + output.deleteToken(output.back()); + calledMacro->expand(temp, loc, temp2.cfront(), macros, expandedmacros); + tok = tok2; } - - 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); - if (!tok2) - return tok->next; output.takeTokens(temp); - output.deleteToken(output.back()); - calledMacro.expand(output, loc, temp2.cfront(), macros, expandedmacros); - return tok2->next; + return tok->next; } const Token *expandToken(TokenList &output, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens) const { diff --git a/test.cpp b/test.cpp index d059b237..247f63e1 100644 --- a/test.cpp +++ b/test.cpp @@ -949,6 +949,46 @@ static void define23() // #40 "unsigned A , B ;", preprocess(code)); } +static void define24() +{ + // an expansion result that is a function-like macro name must be rescanned + // repeatedly against the tokens that follow it + const char code[] = "#define a(b, c) c\n" + "#define d() a\n" + "#define g(e) h(e, ) h(e, )\n" + "#define h(e, b) d()(, e)()\n" + "#define i()\n" + "g(i)\n"; + ASSERT_EQUALS("", preprocess(code)); +} + +static void define25() +{ + // a macro name that came from expanding that same macro must not be + // re-expanded when rescanned with the tokens that follow it + const char code[] = "#define f() f\n" + "#define wrap(x) x()\n" + "wrap(f())\n"; + ASSERT_EQUALS("\n" + "\n" + "f ( )", preprocess(code)); +} + +static void define26() +{ + // a macro name that came from expanding that same macro must not be + // re-expanded with arguments taken from the raw token stream + const char code[] = "#define f() f\n" + "f()()\n"; + ASSERT_EQUALS("\n" + "f ( )", preprocess(code)); + + const char code2[] = "#define f() f\n" + "f()()()\n"; + ASSERT_EQUALS("\n" + "f ( ) ( )", preprocess(code2)); +} + static void define_invalid_1() { @@ -4520,6 +4560,9 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(define21); // #66 TEST_CASE(define22); // #40 TEST_CASE(define23); // #40 + TEST_CASE(define24); + TEST_CASE(define25); + TEST_CASE(define26); TEST_CASE(define_invalid_1); TEST_CASE(define_invalid_2); TEST_CASE(define_invalid_3); From 93561ef9c4ea59b55c78b9acfbbccb79071efca3 Mon Sep 17 00:00:00 2001 From: glankk Date: Wed, 19 Aug 2026 18:04:28 +0200 Subject: [PATCH 122/152] Fix cppcheck selfcheck issue (#696) --- simplecpp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 4b24e311..8939ff2e 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1771,7 +1771,7 @@ namespace simplecpp { }; struct invalidDirectiveAsMacroParameter : public Error { - invalidDirectiveAsMacroParameter(const Location &loc) + explicit invalidDirectiveAsMacroParameter(const Location &loc) : Error(loc, "it is invalid to use a preprocessor directive as macro parameter") {} }; From 439ac67df072d3108a10a51f94bad7aa5704991b Mon Sep 17 00:00:00 2001 From: glankk Date: Wed, 19 Aug 2026 18:32:21 +0200 Subject: [PATCH 123/152] Add newline to macro def string before tokenizing (#697) --- simplecpp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 8939ff2e..77f635e5 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1595,7 +1595,7 @@ namespace simplecpp { * @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); + const std::string def(name + ' ' + value + '\n'); StdCharBufStream stream(reinterpret_cast(def.data()), def.size()); tokenListDefine.readfile(stream); if (!parseDefine(tokenListDefine.cfront())) From 1392ebd37ccf15283f96fa97be214656f56d06cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Thu, 20 Aug 2026 10:57:08 +0200 Subject: [PATCH 124/152] Fix #700: `macro` not set for tokens expanded from `__LINE__` inside function-like macros (#701) --- simplecpp.cpp | 3 +++ test.cpp | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/simplecpp.cpp b/simplecpp.cpp index 77f635e5..81d09867 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1994,14 +1994,17 @@ namespace simplecpp { if (nameTokInst->str() == "__FILE__") { output.push_back(new Token('\"'+output.file(loc)+'\"', loc)); + output.back()->macro = "__FILE__"; return nameTokInst->next; } if (nameTokInst->str() == "__LINE__") { output.push_back(new Token(toString(loc.line), loc)); + output.back()->macro = "__LINE__"; return nameTokInst->next; } if (nameTokInst->str() == "__COUNTER__") { output.push_back(new Token(toString(usageList.size()-1U), loc)); + output.back()->macro = "__COUNTER__"; return nameTokInst->next; } diff --git a/test.cpp b/test.cpp index 247f63e1..2b0e8ee0 100644 --- a/test.cpp +++ b/test.cpp @@ -3782,6 +3782,31 @@ static void tokenMacro5() ASSERT_EQUALS("SET_BPF_JUMP", tok->macro); } +static void tokenMacro6() +{ + const char code[] = "#define FILE() __FILE__\n" + "#define LINE() __LINE__\n" + "#define COUNTER() __COUNTER__\n" + "FILE()\n" + "LINE()\n" + "COUNTER()\n"; + std::vector files; + simplecpp::FileDataCache cache; + simplecpp::TokenList tokenList(files); + const simplecpp::TokenList rawtokens = makeTokenList(code,files); + simplecpp::preprocess(tokenList, rawtokens, files, cache, simplecpp::DUI()); + const simplecpp::Token *tok; + + tok = tokenList.cfront(); + ASSERT_EQUALS("__FILE__", tok->macro); + + tok = tok->next; + ASSERT_EQUALS("__LINE__", tok->macro); + + tok = tok->next; + ASSERT_EQUALS("__COUNTER__", tok->macro); +} + static void undef() { const char code[] = "#define A\n" @@ -4781,6 +4806,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(tokenMacro3); TEST_CASE(tokenMacro4); TEST_CASE(tokenMacro5); + TEST_CASE(tokenMacro6); TEST_CASE(undef); From 2499b51390e6ea74b8fbad91154f5529134de31a Mon Sep 17 00:00:00 2001 From: glankk Date: Tue, 25 Aug 2026 18:27:55 +0200 Subject: [PATCH 125/152] Call load_callback for all includes, even cached (#703) --- simplecpp.cpp | 15 +++++++++++---- simplecpp.h | 3 ++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 81d09867..8237ac75 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3232,13 +3232,10 @@ std::pair simplecpp::FileDataCache::tryload(FileDat mImpl->mIdMap.emplace(fileId, data); mData.emplace_back(data); - if (mLoadCallback) - mLoadCallback(*data); - return {data, true}; } -std::pair simplecpp::FileDataCache::get(const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader, std::vector &filenames, simplecpp::OutputList *outputList) +std::pair simplecpp::FileDataCache::get_private(const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader, std::vector &filenames, simplecpp::OutputList *outputList) { if (isAbsolutePath(header)) { auto ins = mNameMap.emplace(simplecpp::simplifyPath(header), nullptr); @@ -3284,6 +3281,16 @@ std::pair simplecpp::FileDataCache::get(const std:: return {nullptr, false}; } +std::pair simplecpp::FileDataCache::get(const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader, std::vector &filenames, simplecpp::OutputList *outputList) +{ + auto ret = get_private(sourcefile, header, dui, systemheader, filenames, outputList); + + if (mLoadCallback && ret.first) + mLoadCallback(*ret.first, ret.second); + + return ret; +} + void simplecpp::FileDataCache::clear() { mImpl->clear(); diff --git a/simplecpp.h b/simplecpp.h index e64a8f7b..f4dca183 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -511,7 +511,7 @@ namespace simplecpp { return mData.cend(); } - using load_callback_type = std::function; + using load_callback_type = std::function; void set_load_callback(load_callback_type cb) { mLoadCallback = std::move(cb); @@ -524,6 +524,7 @@ namespace simplecpp { using name_map_type = std::unordered_map; std::pair tryload(name_map_type::iterator &name_it, const DUI &dui, std::vector &filenames, OutputList *outputList); + std::pair get_private(const std::string &sourcefile, const std::string &header, const DUI &dui, bool systemheader, std::vector &filenames, OutputList *outputList); container_type mData; name_map_type mNameMap; From b892cf767f0858347f8ed1da9e912a26e59ccb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 6 Oct 2026 21:40:10 +0200 Subject: [PATCH 126/152] CI: Add claude-review action (#705) --- .github/workflows/CI-windows.yml | 2 +- .github/workflows/claude-review.yml | 40 +++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/claude-review.yml diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 0d1e62e2..e5c4e4b4 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -26,7 +26,7 @@ jobs: - os: windows-2025 sln: slnx - os: windows-11-arm - sln: sln + sln: slnx fail-fast: false runs-on: ${{ matrix.os }} diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml new file mode 100644 index 00000000..968e7e71 --- /dev/null +++ b/.github/workflows/claude-review.yml @@ -0,0 +1,40 @@ +# Automated code review of pull requests using Claude +name: claude-review + +on: + pull_request: + types: [opened, synchronize, ready_for_review, reopened] + +jobs: + review: + if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository + + runs-on: ubuntu-24.04 + + permissions: + contents: read + pull-requests: write + id-token: write + + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 1 + + - name: Claude review + uses: anthropics/claude-code-action@v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + prompt: | + REPO: ${{ github.repository }} + PR NUMBER: ${{ github.event.pull_request.number }} + + Review this pull request. Focus on correctness bugs, potential + false positives/false negatives in checkers, performance problems + and missing tests. Be concise and only report real issues. + + Use `gh pr diff` to see the changes. Post specific issues as inline + comments with `mcp__github_inline_comment__create_inline_comment` + and post a short overall summary with `gh pr comment`. + claude_args: | + --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)" From 6d40e65e5ec34d2db19e0225e9fa573ea34af3d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 26 Oct 2026 10:33:12 +0100 Subject: [PATCH 127/152] CI: remove claude-review action --- .github/workflows/claude-review.yml | 40 ----------------------------- 1 file changed, 40 deletions(-) delete mode 100644 .github/workflows/claude-review.yml diff --git a/.github/workflows/claude-review.yml b/.github/workflows/claude-review.yml deleted file mode 100644 index 968e7e71..00000000 --- a/.github/workflows/claude-review.yml +++ /dev/null @@ -1,40 +0,0 @@ -# Automated code review of pull requests using Claude -name: claude-review - -on: - pull_request: - types: [opened, synchronize, ready_for_review, reopened] - -jobs: - review: - if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository - - runs-on: ubuntu-24.04 - - permissions: - contents: read - pull-requests: write - id-token: write - - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 1 - - - name: Claude review - uses: anthropics/claude-code-action@v1 - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - prompt: | - REPO: ${{ github.repository }} - PR NUMBER: ${{ github.event.pull_request.number }} - - Review this pull request. Focus on correctness bugs, potential - false positives/false negatives in checkers, performance problems - and missing tests. Be concise and only report real issues. - - Use `gh pr diff` to see the changes. Post specific issues as inline - comments with `mcp__github_inline_comment__create_inline_comment` - and post a short overall summary with `gh pr comment`. - claude_args: | - --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)" From 2567ec4e7bd0f51b3a01cf6f90165b8e9a75f944 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 26 Oct 2026 14:48:02 +0100 Subject: [PATCH 128/152] CI: fix CI-mingw failures (#707) --- .github/workflows/CI-mingw.yml | 8 ++------ CMakeLists.txt | 5 +++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/CI-mingw.yml b/.github/workflows/CI-mingw.yml index 390de83d..99991737 100644 --- a/.github/workflows/CI-mingw.yml +++ b/.github/workflows/CI-mingw.yml @@ -16,12 +16,11 @@ jobs: matrix: compiler: [g++, clang++] # TODO: add MSYS after #556 is fixed - msystem: [MINGW32, MINGW64, CLANG64] + # MINGW32 was removed since MSYS2 is phasing out 32-bit environments + msystem: [MINGW64, CLANG64] include: #- msystem: MSYS # pkg-prefix: '' - - msystem: MINGW32 - pkg-prefix: 'mingw-w64-i686-' - msystem: MINGW64 pkg-prefix: 'mingw-w64-x86_64-' - msystem: CLANG64 @@ -33,9 +32,6 @@ 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 diff --git a/CMakeLists.txt b/CMakeLists.txt index f2c99e89..220c6c3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,11 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options_safe(-Wno-thread-safety-negative) add_compile_options_safe(-Wno-thread-safety-beta) + # TODO: add [[clang::lifetimebound]] annotations? + add_compile_options_safe(-Wno-lifetime-safety-intra-tu-suggestions) + add_compile_options_safe(-Wno-lifetime-safety-intra-tu-constructor-suggestions) + add_compile_options_safe(-Wno-lifetime-safety-cross-tu-constructor-suggestions) + # TODO: fix these? add_compile_options(-Wno-padded) add_compile_options(-Wno-sign-conversion) From 370f18decdfa9ff75f2fda85ec3cf9072b82a77b Mon Sep 17 00:00:00 2001 From: Paul Fultz II Date: Tue, 3 Nov 2026 01:27:32 -0600 Subject: [PATCH 129/152] Expand function macros that have arguments at the end (#695) --- run-tests.py | 1 - simplecpp.cpp | 54 +++++++++++++++++++++++++++++++++++++++++---------- test.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 11 deletions(-) diff --git a/run-tests.py b/run-tests.py index 20f59a49..71d59b15 100644 --- a/run-tests.py +++ b/run-tests.py @@ -80,7 +80,6 @@ def cleanup(out: str) -> str: 'macro_rescan_varargs.c', # todo, high priority - 'c99-6_10_3_4_p5.c', 'c99-6_10_3_4_p6.c', 'expr_usual_conversions.c', # condition is true: 4U - 30 >= 0 'stdint.c', diff --git a/simplecpp.cpp b/simplecpp.cpp index 8237ac75..438288e5 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2177,26 +2177,37 @@ namespace simplecpp { return &it->second; } - 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 { - // Expand while the expansion result ends with the name of a function-like - // macro whose arguments are supplied by the tokens that follow it. Each round - // consumes that macro call from the token stream, so tok always advances. + /** Expand while the expansion result in @p temp ends with the name of a + * function-like macro whose arguments are supplied by the tokens after @p tok. + * Each round consumes that macro call from the token stream. The @p gatherCall + * callback copies the "( ... )" tokens that follow @p lpar into its output list + * and returns the matching ')', or nullptr when no complete call is available. + * Returns the first token that was not consumed (tok->next when no call was + * expanded). */ + template + const Token *expandTrailingCalls(TokenList &output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, GatherCall gatherCall) const { while (const Macro * const calledMacro = rescanMacro(temp, tok, macros, expandedmacros)) { TokenList temp2(files); - temp2.push_back(new Token(temp.cback()->str(), tok->location)); + temp2.push_back(new Token(temp.cback()->str(), tok->next->location)); - const Token * const tok2 = appendTokens(temp2, loc, tok->next, macros, expandedmacros, parametertokens); - if (!tok2) + const Token * const closingPar = gatherCall(temp2, tok->next); + if (!closingPar) break; output.takeTokens(temp); output.deleteToken(output.back()); calledMacro->expand(temp, loc, temp2.cfront(), macros, expandedmacros); - tok = tok2; + tok = closingPar; } output.takeTokens(temp); return tok->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 { + return expandTrailingCalls(output, temp, loc, tok, macros, expandedmacros, [&](TokenList &temp2, const Token *lpar) { + return appendTokens(temp2, loc, lpar, macros, expandedmacros, parametertokens); + }); + } + 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) { @@ -2313,12 +2324,35 @@ namespace simplecpp { return false; if (variadic && argnr + 1U >= parametertokens.size()) // empty variadic parameter return true; - for (const Token *partok = parametertokens[argnr]->next; partok != parametertokens[argnr + 1U];) { + const Token * const argEnd = parametertokens[argnr + 1U]; + for (const Token *partok = parametertokens[argnr]->next; partok != argEnd;) { const MacroMap::const_iterator it = macros.find(partok->str()); 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, std::move(expandedmacros2)); + TokenList temp(files); + partok = it->second.expand(temp, loc, partok, macros, std::move(expandedmacros2)); + if (partok->op == '(' && temp.cback() && temp.cback()->name) { + // the expansion result may end with the name of a function-like + // macro whose arguments are supplied by the remaining argument tokens + std::set expandedmacros3(expandedmacros); + expandedmacros3.erase(name()); + partok = expandTrailingCalls(output, temp, loc, partok->previous, macros, expandedmacros3, [&](TokenList &temp2, const Token *lpar) -> const Token * { + unsigned int par = 0; + for (const Token *tok2 = lpar; tok2 != argEnd; tok2 = tok2->next) { + temp2.push_back(new Token(*tok2)); + if (tok2->op == '(') { + ++par; + } else if (tok2->op == ')') { + if (--par == 0U) + return tok2; + } + } + return nullptr; + }); + } else { + output.takeTokens(temp); + } } else { output.push_back(newMacroToken(partok->str(), loc, isReplaced(expandedmacros), partok)); output.back()->macro = partok->macro; diff --git a/test.cpp b/test.cpp index 2b0e8ee0..4ef9bdd2 100644 --- a/test.cpp +++ b/test.cpp @@ -989,6 +989,55 @@ static void define26() "f ( ) ( )", preprocess(code2)); } +static void define27() +{ + // an expansion result inside a macro argument that is a function-like macro + // name must be rescanned against the remaining argument tokens + const char code1[] = "#define f(x) [x]\n" + "#define ID(x) x\n" + "#define G() f\n" + "ID(G()(1))\n"; + ASSERT_EQUALS("\n" + "\n" + "\n" + "[ 1 ]", preprocess(code1)); + + // the rescanned call's arguments are themselves rescanned + const char code2[] = "#define f(x) [x]\n" + "#define ID(x) x\n" + "#define G() f\n" + "ID(G()(G()(2)))\n"; + ASSERT_EQUALS("\n" + "\n" + "\n" + "[ [ 2 ] ]", preprocess(code2)); + + // each rescan round must use the right set of already-expanded macros: + // A(1)->B, B(2)->A, A(3)->B, B(4)->A + const char code3[] = "#define ID(x) x\n" + "#define A(x) B\n" + "#define B(x) A\n" + "ID(A(1)(2)(3)(4))\n"; + ASSERT_EQUALS("\n" + "\n" + "\n" + "A", preprocess(code3)); + + // creduce'd fuzzer case: the missed rescan of o1(k) deferred the call to a + // context where j was wrongly blocked from expanding, so b pasted ')' and + // '1' into an invalid token that broke the parentheses of the m call + const char code4[] = "#define o1(a) a()\n" + "#define b(c, d) c##d\n" + "#define l(c, d) b(c, 1)\n" + "#define m(e, f)\n" + "#define g(h, i) m(h, )\n" + "#define j(h, i) g(h, )\n" + "#define k() l(j(, ), )\n" + "#define n(a) j(l(o, )(a), )\n" + "n(k)\n"; + ASSERT_EQUALS("", preprocess(code4)); +} + static void define_invalid_1() { @@ -4588,6 +4637,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(define24); TEST_CASE(define25); TEST_CASE(define26); + TEST_CASE(define27); TEST_CASE(define_invalid_1); TEST_CASE(define_invalid_2); TEST_CASE(define_invalid_3); From 54b71fe11601111762da8fba80d214c08e13ca1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 4 Nov 2026 17:24:00 +0100 Subject: [PATCH 130/152] Fix #709 Do not evaluate #elif after a taken group (#710) --- simplecpp.cpp | 7 +++++-- test.cpp | 10 ++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 438288e5..9fa548a3 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3839,9 +3839,12 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL return; } + const bool isElif = (rawtok->str() == ELIF || rawtok->str() == ELIFDEF || rawtok->str() == ELIFNDEF); + bool conditionIsTrue; - if (ifstates.top() == AlwaysFalse || (ifstates.top() == ElseIsTrue && rawtok->str() != ELIF && - rawtok->str() != ELIFDEF && rawtok->str() != ELIFNDEF)) { + // An #if is only evaluated in an active group. An #elif is only evaluated + // if no previous group was taken; otherwise it is skipped (C 6.10.1p6). + if (isElif ? (ifstates.top() != ElseIsTrue) : (ifstates.top() != True)) { conditionIsTrue = false; } else if (rawtok->str() == IFDEF || rawtok->str() == ELIFDEF) { diff --git a/test.cpp b/test.cpp index 4ef9bdd2..3574563a 100644 --- a/test.cpp +++ b/test.cpp @@ -2490,6 +2490,16 @@ static void elif() "3\n" "#endif"; ASSERT_EQUALS("\n\n\n\n\n3", preprocess(code3)); + + // #elif after a taken group must not be evaluated + const char code4[] = "#if 1\n" + "1\n" + "#elif 1/0\n" + "2\n" + "#elif garbage(\n" + "3\n" + "#endif"; + ASSERT_EQUALS("\n1", preprocess(code4)); } static void elifdef() From d5e6da62a60870c9694b5af59fb9ce76f31c9a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 5 Nov 2026 09:52:53 +0100 Subject: [PATCH 131/152] Fix #711 (#if true evaluates to false in C++ and C23) (#712) --- simplecpp.cpp | 12 ++++++++++-- test.cpp | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 9fa548a3..1d6298ce 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2824,10 +2824,18 @@ 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) +static void simplifyName(simplecpp::TokenList &expr, const simplecpp::DUI &dui) { + // In C++ and C23 the keywords true/false are 1/0 in #if expressions + const simplecpp::cstd_t cstd = simplecpp::getCStd(dui.std); + const bool hasTrueFalse = (cstd == simplecpp::CUnknown || cstd >= simplecpp::C23); + for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) { if (tok->name) { + if (hasTrueFalse && (tok->str() == "true" || tok->str() == "false")) { + tok->setstr(tok->str() == "true" ? "1" : "0"); + continue; + } static const std::set altop{"and","or","bitand","bitor","compl","not","not_eq","xor"}; if (altop.find(tok->str()) != altop.end()) { bool alt; @@ -3101,7 +3109,7 @@ static long long evaluate(simplecpp::TokenList &expr, const simplecpp::DUI &dui, simplifyComments(expr); simplifySizeof(expr, sizeOfType); simplifyHasInclude(expr, dui); - simplifyName(expr); + simplifyName(expr, dui); simplifyNumbers(expr); expr.constFold(); // TODO: handle invalid expressions diff --git a/test.cpp b/test.cpp index 3574563a..940a3207 100644 --- a/test.cpp +++ b/test.cpp @@ -2609,6 +2609,24 @@ static void ifdiv0() ASSERT_EQUALS("", preprocess(code)); } +static void ifTrueFalse() +{ + const char code[] = "#if true && !false\n" + "1\n" + "#else\n" + "2\n" + "#endif\n"; + simplecpp::DUI dui; + ASSERT_EQUALS("\n1", preprocess(code, dui, nullptr)); + dui.std = "c++11"; + ASSERT_EQUALS("\n1", preprocess(code, dui, nullptr)); + dui.std = "c23"; + ASSERT_EQUALS("\n1", preprocess(code, dui, nullptr)); + // before C23, true and false are ordinary identifiers that evaluate to 0 + dui.std = "c17"; + ASSERT_EQUALS("\n\n\n2", preprocess(code, dui, nullptr)); +} + static void ifalt() // using "and", "or", etc { const char *code; @@ -4794,6 +4812,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(ifif); TEST_CASE(ifoverflow); TEST_CASE(ifdiv0); + TEST_CASE(ifTrueFalse); TEST_CASE(ifalt); // using "and", "or", etc TEST_CASE(ifexpr); TEST_CASE(ifUndefFuncStyleMacro); From 3ba44c28c5d7631a9d5ae15ffccc028d3c6f3471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 6 Nov 2026 12:32:59 +0100 Subject: [PATCH 132/152] Remove macos-14 --- .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 cdda088b..0f459601 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-22.04-arm, ubuntu-24.04, ubuntu-24.04-arm, macos-14, macos-15, macos-15-intel, macos-26, macos-26-intel] + os: [ubuntu-22.04, ubuntu-22.04-arm, ubuntu-24.04, ubuntu-24.04-arm, macos-15, macos-15-intel, macos-26, macos-26-intel] compiler: [clang++] include: - os: ubuntu-22.04 @@ -93,7 +93,7 @@ jobs: - name: make (c++23) run: | make clean - # ubuntu-22.04 and macos-14 do not support c++23 yet + # ubuntu-22.04 do not support c++23 yet make -j$(nproc) CXXOPTS="-Werror -std=c++2b" - name: Run CMake From 4969f5c52b26c2a0fb5d66a18fdf98bcacc5050e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 6 Nov 2026 13:06:31 +0100 Subject: [PATCH 133/152] Fix #714 (# operator escapes backslashes outside string literals) (#715) --- main.cpp | 1 + run-tests.py | 1 - simplecpp.cpp | 118 ++++++++++++++++++++++++++++++++------------------ simplecpp.h | 1 + test.cpp | 82 +++++++++++++++++++++++++++++++++++ 5 files changed, 159 insertions(+), 44 deletions(-) diff --git a/main.cpp b/main.cpp index 92ca27dc..ec0fc5c7 100644 --- a/main.cpp +++ b/main.cpp @@ -279,6 +279,7 @@ int main(int argc, char **argv) case simplecpp::Output::PORTABILITY_BACKSLASH: case simplecpp::Output::PORTABILITY_LINE_DIRECTIVE: case simplecpp::Output::PORTABILITY_NO_EOF_NEWLINE: + case simplecpp::Output::PORTABILITY_STRINGIFY: std::cerr << "portability: "; break; case simplecpp::Output::UNHANDLED_CHAR_ERROR: diff --git a/run-tests.py b/run-tests.py index 71d59b15..e3b0eeae 100644 --- a/run-tests.py +++ b/run-tests.py @@ -80,7 +80,6 @@ def cleanup(out: str) -> str: 'macro_rescan_varargs.c', # todo, high priority - 'c99-6_10_3_4_p6.c', 'expr_usual_conversions.c', # condition is true: 4U - 30 >= 0 'stdint.c', diff --git a/simplecpp.cpp b/simplecpp.cpp index 1d6298ce..3bae3d56 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -642,6 +642,23 @@ static bool isStringLiteralPrefix(const std::string &str) str == "R" || str == "uR" || str == "UR" || str == "LR" || str == "u8R"; } +/** Spelling of a token in the result of the # operator: a \ is inserted + * before each " and \ of a string literal or character constant (C 6.10.3.2p2) */ +static std::string stringifyToken(const std::string &str) +{ + const std::string::size_type quote = str.find_first_of("\"\'"); + if (quote == std::string::npos || str.back() != str[quote] || + (quote > 0 && !isStringLiteralPrefix(str.substr(0, quote)))) + return str; + std::string ret; + for (const char c : str) { + if (c == '\\' || c == '\"') + ret += '\\'; + ret += c; + } + return ret; +} + void simplecpp::TokenList::lineDirective(unsigned int fileIndex_, unsigned int line, Location &location) { if (fileIndex_ != location.fileIndex || line >= location.line) { @@ -1642,6 +1659,7 @@ namespace simplecpp { * @param rawtok macro token * @param macros list of macros * @param inputFiles the input files + * @param outputList destination for warnings, may be nullptr * @return token after macro * @throws Error thrown on missing or invalid preprocessor directives * @throws wrongNumberOfParameters thrown on invalid number of parameters @@ -1650,7 +1668,8 @@ namespace simplecpp { const Token * expand(TokenList & output, const Token * rawtok, const MacroMap ¯os, - std::vector &inputFiles) const { + std::vector &inputFiles, + OutputList *outputList) const { std::set expandedmacros; #ifdef SIMPLECPP_DEBUG_MACRO_EXPANSION @@ -1678,10 +1697,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, outputList)) rawtok = rawtok1->next; } else { - rawtok = expand(output2, rawtok->location, rawtok, macros, expandedmacros); + rawtok = expand(output2, rawtok->location, rawtok, macros, expandedmacros, outputList); } while (output2.cback() && rawtok) { unsigned int par = 0; @@ -1732,7 +1751,7 @@ 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, outputList) != nullptr) break; rawtok = rawtok2->next; } @@ -1949,7 +1968,8 @@ namespace simplecpp { const Token * const lpar, const MacroMap ¯os, const std::set &expandedmacros, - const std::vector ¶metertokens) const { + const std::vector ¶metertokens, + OutputList *outputList) const { if (!lpar || lpar->op != '(') return nullptr; unsigned int par = 0; @@ -1957,11 +1977,11 @@ namespace simplecpp { while (sameline(lpar, tok)) { if (tok->op == '#' && sameline(tok,tok->next) && tok->next->op == '#' && sameline(tok,tok->next->next)) { // A##B => AB - tok = expandHashHash(tokens, rawloc, tok, macros, expandedmacros, parametertokens, false); + tok = expandHashHash(tokens, rawloc, tok, macros, expandedmacros, parametertokens, outputList, false); } else if (tok->op == '#' && sameline(tok, tok->next) && tok->next->op != '#') { - tok = expandHash(tokens, rawloc, tok, expandedmacros, parametertokens); + tok = expandHash(tokens, rawloc, tok, expandedmacros, parametertokens, outputList); } else { - if (!expandArg(tokens, tok, rawloc, macros, expandedmacros, parametertokens)) { + if (!expandArg(tokens, tok, rawloc, macros, expandedmacros, parametertokens, outputList)) { tokens.push_back(new Token(*tok)); if (tok->macro.empty() && (par > 0 || tok->str() != "(")) tokens.back()->macro = name(); @@ -1983,7 +2003,7 @@ namespace simplecpp { return sameline(lpar,tok) ? tok : nullptr; } - const Token * expand(TokenList & 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, OutputList *outputList) const { expandedmacros.insert(nameTokInst->str()); #ifdef SIMPLECPP_DEBUG_MACRO_EXPANSION @@ -2093,7 +2113,7 @@ namespace simplecpp { 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); - tok = expandToken(output, loc, tok->next->next->next, macros, expandedmacros, parametertokens2); + tok = expandToken(output, loc, tok->next->next->next, macros, expandedmacros, parametertokens2, outputList); if (output.back() == comma) output.deleteToken(comma); continue; @@ -2108,7 +2128,7 @@ namespace simplecpp { output.push_back(newMacroToken(tok2->str(), loc, isReplaced(expandedmacros), tok2)); tok = tok->next; } else { - tok = expandToken(output, loc, tok, macros, expandedmacros, parametertokens2); + tok = expandToken(output, loc, tok, macros, expandedmacros, parametertokens2, outputList); } continue; } @@ -2145,10 +2165,10 @@ namespace simplecpp { } if (tok->op == '#') { // A##B => AB - tok = expandHashHash(output, loc, tok->previous, macros, expandedmacros, parametertokens2); + tok = expandHashHash(output, loc, tok->previous, macros, expandedmacros, parametertokens2, outputList); } else { // #123 => "123" - tok = expandHash(output, loc, tok->previous, expandedmacros, parametertokens2); + tok = expandHash(output, loc, tok->previous, expandedmacros, parametertokens2, outputList); } } @@ -2185,7 +2205,7 @@ namespace simplecpp { * Returns the first token that was not consumed (tok->next when no call was * expanded). */ template - const Token *expandTrailingCalls(TokenList &output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, GatherCall gatherCall) const { + const Token *expandTrailingCalls(TokenList &output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, OutputList *outputList, GatherCall gatherCall) const { while (const Macro * const calledMacro = rescanMacro(temp, tok, macros, expandedmacros)) { TokenList temp2(files); temp2.push_back(new Token(temp.cback()->str(), tok->next->location)); @@ -2195,20 +2215,20 @@ namespace simplecpp { break; output.takeTokens(temp); output.deleteToken(output.back()); - calledMacro->expand(temp, loc, temp2.cfront(), macros, expandedmacros); + calledMacro->expand(temp, loc, temp2.cfront(), macros, expandedmacros, outputList); tok = closingPar; } output.takeTokens(temp); return tok->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 { - return expandTrailingCalls(output, temp, loc, tok, macros, expandedmacros, [&](TokenList &temp2, const Token *lpar) { - return appendTokens(temp2, loc, lpar, macros, expandedmacros, parametertokens); + const Token *recursiveExpandToken(TokenList &output, TokenList &temp, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens, OutputList *outputList) const { + return expandTrailingCalls(output, temp, loc, tok, macros, expandedmacros, outputList, [&](TokenList &temp2, const Token *lpar) { + return appendTokens(temp2, loc, lpar, macros, expandedmacros, parametertokens, outputList); }); } - 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, OutputList *outputList) const { // Not name.. if (!tok->name) { output.push_back(newMacroToken(tok->str(), loc, true, tok)); @@ -2218,11 +2238,11 @@ namespace simplecpp { // Macro parameter.. { TokenList temp(files); - if (expandArg(temp, tok, loc, macros, expandedmacros, parametertokens)) { + if (expandArg(temp, tok, loc, macros, expandedmacros, parametertokens, outputList)) { if (tok->str() == "__VA_ARGS__" && temp.empty() && output.cback() && output.cback()->str() == "," && tok->nextSkipComments() && tok->nextSkipComments()->str() == ")") output.deleteToken(output.back()); - return recursiveExpandToken(output, temp, loc, tok, macros, expandedmacros, parametertokens); + return recursiveExpandToken(output, temp, loc, tok, macros, expandedmacros, parametertokens, outputList); } } @@ -2235,8 +2255,8 @@ namespace simplecpp { const Macro &calledMacro = it->second; if (!calledMacro.functionLike()) { TokenList temp(files); - calledMacro.expand(temp, loc, tok, macros, expandedmacros); - return recursiveExpandToken(output, temp, loc, tok, macros, expandedmacros2, parametertokens); + calledMacro.expand(temp, loc, tok, macros, expandedmacros, outputList); + return recursiveExpandToken(output, temp, loc, tok, macros, expandedmacros2, parametertokens, outputList); } if (!sameline(tok, tok->next)) { output.push_back(newMacroToken(tok->str(), loc, true, tok)); @@ -2246,9 +2266,9 @@ namespace simplecpp { tokens.push_back(new Token(*tok)); const Token * tok2 = nullptr; if (tok->next->op == '(') { - tok2 = appendTokens(tokens, loc, tok->next, macros, expandedmacros, parametertokens); + tok2 = appendTokens(tokens, loc, tok->next, macros, expandedmacros, parametertokens, outputList); } - else if (expandArg(tokens, tok->next, loc, macros, expandedmacros, parametertokens)) { + else if (expandArg(tokens, tok->next, loc, macros, expandedmacros, parametertokens, outputList)) { tokens.front()->location = loc; if (tokens.cfront()->next && tokens.cfront()->next->op == '(') tok2 = tok->next; @@ -2258,8 +2278,8 @@ namespace simplecpp { return tok->next; } TokenList temp(files); - calledMacro.expand(temp, loc, tokens.cfront(), macros, expandedmacros); - return recursiveExpandToken(output, temp, loc, tok2, macros, expandedmacros, parametertokens); + calledMacro.expand(temp, loc, tokens.cfront(), macros, expandedmacros, outputList); + return recursiveExpandToken(output, temp, loc, tok2, macros, expandedmacros, parametertokens, outputList); } if (tok->str() == DEFINED) { @@ -2316,7 +2336,7 @@ namespace simplecpp { 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, OutputList *outputList) const { if (!tok->name) return false; const unsigned int argnr = getArgNum(tok->str()); @@ -2331,13 +2351,13 @@ namespace simplecpp { std::set expandedmacros2(expandedmacros); // temporary amnesia to allow reexpansion of currently expanding macros during argument evaluation expandedmacros2.erase(name()); TokenList temp(files); - partok = it->second.expand(temp, loc, partok, macros, std::move(expandedmacros2)); + partok = it->second.expand(temp, loc, partok, macros, std::move(expandedmacros2), outputList); if (partok->op == '(' && temp.cback() && temp.cback()->name) { // the expansion result may end with the name of a function-like // macro whose arguments are supplied by the remaining argument tokens std::set expandedmacros3(expandedmacros); expandedmacros3.erase(name()); - partok = expandTrailingCalls(output, temp, loc, partok->previous, macros, expandedmacros3, [&](TokenList &temp2, const Token *lpar) -> const Token * { + partok = expandTrailingCalls(output, temp, loc, partok->previous, macros, expandedmacros3, outputList, [&](TokenList &temp2, const Token *lpar) -> const Token * { unsigned int par = 0; for (const Token *tok2 = lpar; tok2 != argEnd; tok2 = tok2->next) { temp2.push_back(new Token(*tok2)); @@ -2373,20 +2393,32 @@ 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, OutputList *outputList) const { TokenList tokenListHash(files); const MacroMap macros2; // temporarily bypass macro expansion - tok = expandToken(tokenListHash, loc, tok->next, macros2, expandedmacros, parametertokens); - std::ostringstream ostr; - ostr << '\"'; + tok = expandToken(tokenListHash, loc, tok->next, macros2, expandedmacros, parametertokens, outputList); + std::string str; for (const Token *hashtok = tokenListHash.cfront(), *next; hashtok; hashtok = next) { next = hashtok->next; - ostr << hashtok->str(); + str += stringifyToken(hashtok->str()); if (next && hashtok->whitespaceahead) - ostr << ' '; + str += ' '; + } + // An odd number of trailing backslashes would escape the closing quote and + // yield an unterminated string literal (undefined behavior, C 6.10.3.2p2). + // Drop the final backslash, as gcc and clang do. + std::size_t trailing = 0; + while (trailing < str.size() && str[str.size() - 1U - trailing] == '\\') + ++trailing; + if (trailing % 2U == 1U) { + str.pop_back(); + if (outputList) { + outputList->emplace_back(Output::PORTABILITY_STRINGIFY, loc, + "Invalid string literal when expanding \'" + name() + "\', ignoring final \'\\\'. " + "Undefined behavior according to C standard chapter 6.10.3.2, paragraph 2."); + } } - ostr << '\"'; - output.push_back(newMacroToken(escapeString(ostr.str()), loc, isReplaced(expandedmacros))); + output.push_back(newMacroToken('\"' + str + '\"', loc, isReplaced(expandedmacros))); return tok; } @@ -2402,7 +2434,7 @@ 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 { + const Token *expandHashHash(TokenList &output, const Location &loc, const Token *tok, const MacroMap ¯os, const std::set &expandedmacros, const std::vector ¶metertokens, OutputList *outputList, bool expandResult=true) const { Token *A = output.back(); if (!A) throw invalidHashHash(tok->location, name(), "Missing first argument"); @@ -2483,7 +2515,7 @@ namespace simplecpp { } 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); + nextTok = expandHashHash(output2, loc, nextTok, macros, expandedmacros, parametertokens, outputList); output.deleteToken(A); output.takeTokens(output2); } else { @@ -2494,13 +2526,13 @@ namespace simplecpp { 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, outputList); if (tok2) nextTok = tok2->next; } } if (expandResult) - expandToken(output, loc, tokens.cfront(), macros, expandedmacros, parametertokens); + expandToken(output, loc, tokens.cfront(), macros, expandedmacros, parametertokens, outputList); else output.takeTokens(tokens); for (Token *b = tokensB.front(); b; b = b->next) @@ -3470,7 +3502,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, outputList); } catch (const simplecpp::Macro::invalidDirectiveAsMacroParameter &err) { if (outputList) { simplecpp::Output out{ diff --git a/simplecpp.h b/simplecpp.h index f4dca183..86573fa6 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -251,6 +251,7 @@ namespace simplecpp { PORTABILITY_BACKSLASH, PORTABILITY_LINE_DIRECTIVE, PORTABILITY_NO_EOF_NEWLINE, + PORTABILITY_STRINGIFY, UNHANDLED_CHAR_ERROR, EXPLICIT_INCLUDE_NOT_FOUND, FILE_NOT_FOUND, diff --git a/test.cpp b/test.cpp index 940a3207..0f07aadb 100644 --- a/test.cpp +++ b/test.cpp @@ -229,6 +229,9 @@ static std::string toString(const simplecpp::OutputList &outputList) case simplecpp::Output::Type::PORTABILITY_NO_EOF_NEWLINE: ostr << "portability_no_eof_newline,"; break; + case simplecpp::Output::Type::PORTABILITY_STRINGIFY: + ostr << "portability_stringify,"; + break; case simplecpp::Output::Type::UNHANDLED_CHAR_ERROR: ostr << "unhandled_char_error,"; break; @@ -1709,6 +1712,83 @@ static void hash() "STR(X(Y,Y))")); } +static void hashEscape() +{ + // a \ is only inserted before " and \ inside string literals and character constants + const char code[] = "#define str(x) #x\n" + "str(\\n)\n" + "str(: @\\n)\n" + "str(\"\\n\")\n" + "str('\\4')\n" + "str('\"')\n" + "str(L\"a\\\"b\")"; + ASSERT_EQUALS("\n" + "\"\\n\"\n" + "\": @\\n\"\n" + "\"\\\"\\\\n\\\"\"\n" + "\"'\\\\4'\"\n" + "\"'\\\"'\"\n" + "\"L\\\"a\\\\\\\"b\\\"\"", preprocess(code)); +} + +static void hashEscapeTrailingBackslash() +{ + // an odd number of trailing backslashes would escape the closing quote, + // the final backslash is dropped (like gcc and clang) and a warning is reported + const char msg[] = "Invalid string literal when expanding 'str', ignoring final '\\'. " + "Undefined behavior according to C standard chapter 6.10.3.2, paragraph 2.\n"; + { + const char code[] = "int x;\n" + "#define str(x) #x\n" + "str(\\)\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("int x ;\n\n\"\"", preprocess(code, &outputList)); + ASSERT_EQUALS(std::string("file0,3,portability_stringify,") + msg, toString(outputList)); + } + { + const char code[] = "#define str(x) #x\n" + "str(a\\)\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("\n\"a\"", preprocess(code, &outputList)); + ASSERT_EQUALS(std::string("file0,2,portability_stringify,") + msg, toString(outputList)); + } + { + // three backslashes + const char code[] = "#define str(x) #x\n" + "str(\\\\\\)\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("\n\"\\\\\"", preprocess(code, &outputList)); + ASSERT_EQUALS(std::string("file0,2,portability_stringify,") + msg, toString(outputList)); + } + { + // # in a nested macro + const char code[] = "#define str(x) #x\n" + "#define T(x) str(x)\n" + "T(\\)\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("\n\n\"\"", preprocess(code, &outputList)); + ASSERT_EQUALS(std::string("file0,3,portability_stringify,") + msg, toString(outputList)); + } + { + // in a #if expression + const char code[] = "#define str(x) #x\n" + "#if 1 || str(\\)\n" + "A\n" + "#endif\n"; + simplecpp::OutputList outputList; + preprocess(code, &outputList); + ASSERT_EQUALS(std::string("file0,2,portability_stringify,") + msg, toString(outputList)); + } + { + // even number of backslashes is fine + const char code[] = "#define str(x) #x\n" + "str(\\\\)\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("\n\"\\\\\"", preprocess(code, &outputList)); + ASSERT_EQUALS("", toString(outputList)); + } +} + static void hashhash1() // #4703 { const char code[] = "#define MACRO( A, B, C ) class A##B##C##Creator {};\n" @@ -4734,6 +4814,8 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(garbage_endif); TEST_CASE(hash); + TEST_CASE(hashEscape); + TEST_CASE(hashEscapeTrailingBackslash); TEST_CASE(hashhash1); TEST_CASE(hashhash2); TEST_CASE(hashhash3); From 9fedb7d39018326f99c8d578d9835a187c2c2d50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 6 Nov 2026 13:32:00 +0100 Subject: [PATCH 134/152] Fix #716 (#if: && and || are evaluated with the same precedence) (#717) --- simplecpp.cpp | 48 +++++++++++++++++++++++++++--------------------- test.cpp | 22 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 3bae3d56..6fb28762 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1422,30 +1422,36 @@ void simplecpp::TokenList::constFoldBitwise(Token *tok) static const std::string OR("or"); void simplecpp::TokenList::constFoldLogicalOp(Token *tok) { - for (; tok && tok->op != ')'; tok = tok->next) { - if (tok->name) { - if (isAlternativeBinaryOp(tok,AND)) - tok->setstr("&&"); - else if (isAlternativeBinaryOp(tok,OR)) - tok->setstr("||"); + for (Token *t = tok; t && t->op != ')'; t = t->next) { + if (t->name) { + if (isAlternativeBinaryOp(t,AND)) + t->setstr("&&"); + else if (isAlternativeBinaryOp(t,OR)) + t->setstr("||"); } - if (tok->str() != "&&" && tok->str() != "||") - continue; - if (!tok->previous || !tok->previous->number) - continue; - if (!tok->next || !tok->next->number) - continue; + } - int result; - if (tok->str() == "||") - result = (stringToLL(tok->previous->str()) || stringToLL(tok->next->str())); - else /*if (tok->str() == "&&")*/ - result = (stringToLL(tok->previous->str()) && stringToLL(tok->next->str())); + // && has higher precedence than || + for (const char * const op : {"&&", "||"}) { + for (Token *t = tok; t && t->op != ')'; t = t->next) { + if (t->str() != op) + continue; + if (!t->previous || !t->previous->number) + continue; + if (!t->next || !t->next->number) + continue; - tok = tok->previous; - tok->setstr(toString(result)); - deleteToken(tok->next); - deleteToken(tok->next); + int result; + if (t->str() == "||") + result = (stringToLL(t->previous->str()) || stringToLL(t->next->str())); + else /*if (t->str() == "&&")*/ + result = (stringToLL(t->previous->str()) && stringToLL(t->next->str())); + + t = t->previous; + t->setstr(toString(result)); + deleteToken(t->next); + deleteToken(t->next); + } } } diff --git a/test.cpp b/test.cpp index 0f07aadb..00ebbf1e 100644 --- a/test.cpp +++ b/test.cpp @@ -2532,6 +2532,27 @@ static void ifLogical() ASSERT_EQUALS("\nX", preprocess(code, dui)); } +static void ifLogicalPrecedence() +{ + // && has higher precedence than || + const char code1[] = "#if 1 || 1 && 0\n" + "X\n" + "#endif"; + ASSERT_EQUALS("\nX", preprocess(code1)); + const char code2[] = "#if 1 or 1 and 0\n" + "X\n" + "#endif"; + ASSERT_EQUALS("\nX", preprocess(code2)); + const char code3[] = "#if 0 && 1 || 1\n" + "X\n" + "#endif"; + ASSERT_EQUALS("\nX", preprocess(code3)); + const char code4[] = "#if 0 || 1 && 0\n" + "X\n" + "#endif"; + ASSERT_EQUALS("", preprocess(code4)); +} + static void ifSizeof() { const char code[] = "#if sizeof(unsigned short)==2\n" @@ -4887,6 +4908,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(ifDefinedHashHash); TEST_CASE(ifDefinedHashHash2); TEST_CASE(ifLogical); + TEST_CASE(ifLogicalPrecedence); TEST_CASE(ifSizeof); TEST_CASE(elif); TEST_CASE(elifdef); From 768e6c314d143c159faa9566980524052bd6ca1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 6 Nov 2026 14:13:31 +0100 Subject: [PATCH 135/152] Fix #718 (# operator drops the space that a comment stands for) (#719) --- simplecpp.cpp | 15 +++++++++++++-- test.cpp | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 6fb28762..d034e969 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1490,8 +1490,12 @@ void simplecpp::TokenList::removeComments() while (tok) { Token * const tok1 = tok; tok = tok->next; - if (tok1->comment) + if (tok1->comment) { + // a comment is replaced by one space character + if (tok1->previous) + tok1->previous->whitespaceahead = true; deleteToken(tok1); + } } } @@ -2187,7 +2191,14 @@ namespace simplecpp { if (!parametertokens1.empty()) parametertokens1.swap(parametertokens2); - return functionLike() ? parametertokens2.back()->next : nameTokInst->next; + const Token * const callEnd = functionLike() ? parametertokens2.back() : nameTokInst; + + // Whitespace at the end of the replacement list does not count. + // The expansion is followed by the whitespace after the macro call. + if (output.back() && output.back() != output_end_1) + output.back()->whitespaceahead = callEnd->whitespaceahead; + + return callEnd->next; } /** Returns the macro to expand when the last token of @p temp is the name of a diff --git a/test.cpp b/test.cpp index 00ebbf1e..f87009f8 100644 --- a/test.cpp +++ b/test.cpp @@ -1731,6 +1731,55 @@ static void hashEscape() "\"L\\\"a\\\\\\\"b\\\"\"", preprocess(code)); } +static void hashComment() +{ + // a comment is replaced by one space character + simplecpp::DUI dui; + dui.removeComments = true; + const char code[] = "#define str(x) #x\n" + "#define xstr(x) str(x)\n" + "str(a/**/b)\n" + "str(a/**/ /**/b)\n" + "str(a/**/)\n" + "xstr(a/**/b)"; + ASSERT_EQUALS("\n\n" + "\"a b\"\n" + "\"a b\"\n" + "\"a\"\n" + "\"a b\"", preprocess(code, dui)); + + // comments are kept in the string when they are not removed, like gcc -C + ASSERT_EQUALS("\n\n" + "\"a/**/b\"\n" + "\"a/**/ /**/b\"\n" + "\"a/**/\"\n" + "\"a/**/b\"", preprocess(code)); +} + +static void hashMacroTrailingWhitespace() +{ + // whitespace at the end of a replacement list does not count + simplecpp::DUI dui; + dui.removeComments = true; + const char code[] = "#define str(x) #x\n" + "#define xstr(x) str(x)\n" + "#define MAJOR 1/*major*/\n" + "#define MINOR 2 \n" + "#define F(x) x\n" + "#define G F(a)\n" + "xstr(MAJOR.MINOR)\n" + "xstr(F(a)b)\n" + "xstr(F(a) b)\n" + "xstr(G.b)\n" + "xstr(G b)"; + ASSERT_EQUALS("\n\n\n\n\n\n" + "\"1.2\"\n" + "\"ab\"\n" + "\"a b\"\n" + "\"a.b\"\n" + "\"a b\"", preprocess(code, dui)); +} + static void hashEscapeTrailingBackslash() { // an odd number of trailing backslashes would escape the closing quote, @@ -4836,6 +4885,8 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(hash); TEST_CASE(hashEscape); + TEST_CASE(hashComment); + TEST_CASE(hashMacroTrailingWhitespace); TEST_CASE(hashEscapeTrailingBackslash); TEST_CASE(hashhash1); TEST_CASE(hashhash2); From ab23e4a101b909944f42d915544b45503129298d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 6 Nov 2026 17:30:13 +0100 Subject: [PATCH 136/152] Fix #720 (#if: wrong macro usage for macros defined before the #if) (#721) --- simplecpp.cpp | 13 ++++++++----- test.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index d034e969..bdec34a6 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3935,14 +3935,15 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const bool par = (tok && tok->op == '('); if (par) tok = tok->next; - maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); if (tok) { - if (macros.find(tok->str()) != macros.end()) + if (macros.find(tok->str()) != macros.end()) { + maybeUsedMacros[tok->str()].emplace_back(tok->location); expr.push_back(new Token("1", tok->location)); - else if (hasInclude && tok->str() == HAS_INCLUDE) + } else if (hasInclude && tok->str() == HAS_INCLUDE) { expr.push_back(new Token("1", tok->location)); - else + } else { expr.push_back(new Token("0", tok->location)); + } } if (par) tok = tok ? tok->next : nullptr; @@ -4004,7 +4005,9 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL continue; } - maybeUsedMacros[rawtok->next->str()].emplace_back(rawtok->next->location); + // usage of defined macros is recorded when they are expanded + if (macros.find(tok->str()) == macros.end()) + maybeUsedMacros[tok->str()].emplace_back(tok->location); const Token *tmp = tok; if (!preprocessToken(expr, tmp, macros, files, outputList, dui)) { diff --git a/test.cpp b/test.cpp index f87009f8..2561e3bf 100644 --- a/test.cpp +++ b/test.cpp @@ -4697,6 +4697,30 @@ static void macroUsage() ASSERT_EQUALS(2, it->useLocation.line); ASSERT_EQUALS(8, it->useLocation.col); } + { + const char code[] = "#define X 1\n" + "#if defined(X)\n" + "#endif\n"; + std::list macroUsage; + ASSERT_EQUALS("", preprocess(code, ¯oUsage)); + ASSERT_EQUALS(1, macroUsage.size()); + auto it = macroUsage.cbegin(); + ASSERT_EQUALS("X", it->macroName); + ASSERT_EQUALS(2, it->useLocation.line); + ASSERT_EQUALS(13, it->useLocation.col); + } + { + const char code[] = "#define X 1\n" + "#if X\n" + "#endif\n"; + std::list macroUsage; + ASSERT_EQUALS("", preprocess(code, ¯oUsage)); + ASSERT_EQUALS(1, macroUsage.size()); + auto it = macroUsage.cbegin(); + ASSERT_EQUALS("X", it->macroName); + ASSERT_EQUALS(2, it->useLocation.line); + ASSERT_EQUALS(5, it->useLocation.col); + } } static void isAbsolutePath() { From ca3426a9e14462e9da1fe9ff2116a36f930e5639 Mon Sep 17 00:00:00 2001 From: Abdelrahman Salah <59089096+Abusalah0@users.noreply.github.com> Date: Fri, 6 Nov 2026 22:22:24 +0300 Subject: [PATCH 137/152] Fix #612: reject floating literals in preprocessor conditions (#706) --- simplecpp.cpp | 15 ++++++++++++++ test.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/simplecpp.cpp b/simplecpp.cpp index bdec34a6..d47e58dd 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3123,12 +3123,27 @@ long long simplecpp::characterLiteralToLL(const std::string& str) return multivalue; } +static bool isFloatingPointLiteral(const std::string &str) +{ + const bool startsWithDotDigit = str.size() > 1U && str[0] == '.' && + std::isdigit(static_cast(str[1])); + if (!simplecpp::Token::isNumberLike(str) && !startsWithDotDigit) + return false; + + if (str.find('.') != std::string::npos) + return true; + + return str.find_first_of(isHex(str) ? "pP" : "eE") != std::string::npos; +} + /** * @throws std::runtime_error thrown on invalid literal */ static void simplifyNumbers(simplecpp::TokenList &expr) { for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) { + if (isFloatingPointLiteral(tok->str())) + throw std::runtime_error("floating point literal in preprocessor expression"); if (tok->str().size() == 1U) continue; if (tok->str().compare(0,2,"0x") == 0) diff --git a/test.cpp b/test.cpp index 2561e3bf..dad41680 100644 --- a/test.cpp +++ b/test.cpp @@ -2471,6 +2471,60 @@ static void ifA() ASSERT_EQUALS("\nX", preprocess(code, dui)); } +static void ifFloatingPointLiteral() // #612 +{ + simplecpp::OutputList outputList; + simplecpp::DUI dui; + dui.defines.emplace_back("A=1"); + + const char issue[] = "#if A == 1.0\n" + "int i;\n" + "#endif\n"; + ASSERT_EQUALS("", preprocess(issue, dui, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, floating point literal in preprocessor expression\n", toString(outputList)); + + outputList.clear(); + + const char otherSpelling[] = "#if 0\n" + "#elif .5\n" + "int i;\n" + "#endif\n"; + ASSERT_EQUALS("", preprocess(otherSpelling, &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to evaluate #elif condition, floating point literal in preprocessor expression\n", toString(outputList)); + + outputList.clear(); + + const char exponent[] = "#if 1e3\n" + "int i;\n" + "#endif\n"; + ASSERT_EQUALS("", preprocess(exponent, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, floating point literal in preprocessor expression\n", toString(outputList)); + + outputList.clear(); + + const char suffix[] = "#if 1.f\n" + "int i;\n" + "#endif\n"; + ASSERT_EQUALS("", preprocess(suffix, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, floating point literal in preprocessor expression\n", toString(outputList)); + + outputList.clear(); + + const char hexadecimal[] = "#if 0x1p3\n" + "int i;\n" + "#endif\n"; + ASSERT_EQUALS("", preprocess(hexadecimal, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, floating point literal in preprocessor expression\n", toString(outputList)); + + outputList.clear(); + + const char integer[] = "#if A == 1\n" + "int i;\n" + "#endif\n"; + ASSERT_EQUALS("\nint i ;", preprocess(integer, dui, &outputList)); + ASSERT_EQUALS("", toString(outputList)); +} + static void ifCharLiteral() { const char code[] = "#if ('A'==0x41)\n" @@ -4973,6 +5027,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(ifdef2); TEST_CASE(ifndef); TEST_CASE(ifA); + TEST_CASE(ifFloatingPointLiteral); // #612 TEST_CASE(ifCharLiteral); TEST_CASE(ifDefined); TEST_CASE(ifDefinedNoPar); From b833a752e115b27cfc5aaeb8721d72fa023de5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 6 Nov 2026 20:25:39 +0100 Subject: [PATCH 138/152] Fix #724 (##: valid token pastes are rejected) (#725) --- integration_test.py | 2 +- simplecpp.cpp | 44 +++++++++++++++++++++++++++++++------------- test.cpp | 28 ++++++++++++++++++++++++++-- 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/integration_test.py b/integration_test.py index 42dda9b2..b24f6352 100644 --- a/integration_test.py +++ b/integration_test.py @@ -501,7 +501,7 @@ def test_define(record_property, tmpdir): # #589 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 stderr == "test.cpp:1: syntax error: failed to expand 'TEST_P', Invalid ## usage when expanding 'TEST_P': Combining ')' and '_' yields an invalid token.\n" assert stdout == '\n' def test_utf16_bom(tmpdir): diff --git a/simplecpp.cpp b/simplecpp.cpp index d47e58dd..2f26ddcd 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -160,6 +160,25 @@ static bool sameline(const simplecpp::Token *tok1, const simplecpp::Token *tok2) return tok1 && tok2 && tok1->location.sameline(tok2->location); } +/** is s a punctuator (C11 6.4.6, C++ [lex.operators]) */ +static bool isPunctuator(const std::string &s) +{ + static const std::set punctuators{ + "[", "]", "(", ")", "{", "}", ".", "->", "++", "--", "&", "*", "+", "-", "~", "!", + "/", "%", "<<", ">>", "<", ">", "<=", ">=", "==", "!=", "^", "|", "&&", "||", + "?", ":", ";", "...", "=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|=", + ",", "#", "##", "<:", ":>", "<%", "%>", "%:", "%:%:", "::", ".*", "->*", "<=>" + }; + return punctuators.find(s) != punctuators.end(); +} + +/** is s a valid result when a punctuator is pasted with ## */ +static bool isValidPunctuatorPaste(const std::string &s) +{ + // a pp-number can start with '.' + return isPunctuator(s) || (s.size() >= 2 && s[0] == '.' && std::isdigit(static_cast(s[1]))); +} + static bool isAlternativeBinaryOp(const simplecpp::Token *tok, const std::string &alt) { return (tok->name && @@ -2458,17 +2477,17 @@ namespace simplecpp { if (!sameline(tok, tok->next) || !sameline(tok, tok->next->next)) 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 unexpectedA = (!A->name && !A->number && !A->str().empty() && !canBeConcatenatedWithEqual && !canBeConcatenatedStringOrChar); + // A is a punctuator; the result is checked when B has been expanded + const bool punctuatorA = (!A->name && !A->number && !A->str().empty() && !canBeConcatenatedStringOrChar); const Token * const B = tok->next->next; - if (!B->name && !B->number && B->op && !B->isOneOf("#=")) - throw invalidHashHash::unexpectedToken(tok->location, name(), B); - - if ((canBeConcatenatedWithEqual && B->op != '=') || - (!canBeConcatenatedWithEqual && B->op == '=')) - throw invalidHashHash::cannotCombine(tok->location, name(), A, B); + if (!punctuatorA) { + if (!B->name && !B->number && B->op && !B->isOneOf("#=")) + throw invalidHashHash::unexpectedToken(tok->location, name(), B); + if (B->op == '=') + throw invalidHashHash::cannotCombine(tok->location, name(), A, B); + } // Superficial check; more in-depth would in theory be possible _after_ expandArg if (canBeConcatenatedStringOrChar && (B->number || !B->name)) @@ -2500,16 +2519,15 @@ namespace simplecpp { else if (varargs && A->op == ',') { strAB = ","; } - else if (varargs && unexpectedA) { - throw invalidHashHash::unexpectedToken(tok->location, name(), A); - } else { + if (punctuatorA && !isValidPunctuatorPaste(A->str() + tokensB.cfront()->str())) + throw invalidHashHash::cannotCombine(tok->location, name(), A, tokensB.cfront()); strAB = A->str() + tokensB.cfront()->str(); tokensB.deleteToken(tokensB.front()); } } else { - if (unexpectedA) - throw invalidHashHash::unexpectedToken(tok->location, name(), A); + if (punctuatorA && !isValidPunctuatorPaste(A->str() + B->str())) + throw invalidHashHash::cannotCombine(tok->location, name(), A, B); strAB = A->str() + B->str(); } diff --git a/test.cpp b/test.cpp index dad41680..fdfc9ead 100644 --- a/test.cpp +++ b/test.cpp @@ -2134,7 +2134,7 @@ static void hashhash_invalid_1() const char code[] = "#define f(a) (##x)\nf(1)\n"; simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); - ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'f', Invalid ## usage when expanding 'f': Unexpected token '('\n", toString(outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'f', Invalid ## usage when expanding 'f': Combining '(' and 'x' yields an invalid token.\n", toString(outputList)); } static void hashhash_invalid_2() @@ -2188,6 +2188,28 @@ static void hashhash_empty_va_args() ASSERT_EQUALS("\n\n\n( 2 )", preprocess(code)); } +static void hashhash_punctuator() // #724 +{ + const char code[] = + "#define cat(a,b) a##b\n" + "#define MINUS_GT -##>\n" + "#define VA(...) <##__VA_ARGS__\n" + "cat(+,=) cat(<<,=) cat(-,>) cat(&,&) cat(|,|) cat(+,+) cat(<,=) cat(.,5) MINUS_GT VA(<) VA()\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("\n\n\n+= <<= -> && || ++ <= .5 -> << <", preprocess(code, &outputList)); + ASSERT_EQUALS("", toString(outputList)); +} + +static void hashhash_punctuator_invalid() +{ + const char code[] = + "#define cat(a,b) a##b\n" + "cat(+,x)\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'cat', Invalid ## usage when expanding 'cat': Combining '+' and 'x' yields an invalid token.\n", toString(outputList)); +} + static void hashhash_va_args_unexpected() { const char code[] = @@ -2195,7 +2217,7 @@ static void hashhash_va_args_unexpected() "C(1)\n"; simplecpp::OutputList outputList; preprocess(code, simplecpp::DUI(), &outputList); - ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'C', Invalid ## usage when expanding 'C': Unexpected token '!'\n", toString(outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'C', Invalid ## usage when expanding 'C': Combining '!' and '1' yields an invalid token.\n", toString(outputList)); } static void hashhash_universal_character() @@ -4996,6 +5018,8 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(hashhash_invalid_missing_args); TEST_CASE(hashhash_null_stmt); TEST_CASE(hashhash_empty_va_args); + TEST_CASE(hashhash_punctuator); + TEST_CASE(hashhash_punctuator_invalid); TEST_CASE(hashhash_va_args_unexpected); // C standard, 5.1.1.2, paragraph 4: From 45de9afd1b080755e3b10b0db48ab54e13c114d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 7 Nov 2026 11:49:57 +0100 Subject: [PATCH 139/152] Fix #722 (#if: &&, || and ?: do not short-circuit) (#723) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Marjamäki --- simplecpp.cpp | 191 +++++++++++++++++++++++++++++++++++--------------- test.cpp | 53 ++++++++++++++ 2 files changed, 188 insertions(+), 56 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 2f26ddcd..91af52ea 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -179,14 +179,31 @@ static bool isValidPunctuatorPaste(const std::string &s) return isPunctuator(s) || (s.size() >= 2 && s[0] == '.' && std::isdigit(static_cast(s[1]))); } +/* + * When an operation can't be evaluated (division by zero), its result is an + * error value. The error is only reported if the error value is used, so an + * operand that is not evaluated, as in "0 && (1/0)", does not cause an error. + */ +static const std::string ERROR_VALUE("#error:"); + +static bool isErrorValue(const simplecpp::Token *tok) +{ + return tok && tok->str().compare(0, ERROR_VALUE.size(), ERROR_VALUE) == 0; +} + +static bool isValue(const simplecpp::Token *tok) +{ + return tok && (tok->number || isErrorValue(tok)); +} + static bool isAlternativeBinaryOp(const simplecpp::Token *tok, const std::string &alt) { return (tok->name && tok->str() == alt && tok->previous && tok->next && - (tok->previous->number || tok->previous->name || tok->previous->op == ')') && - (tok->next->number || tok->next->name || tok->next->op == '(')); + (isValue(tok->previous) || tok->previous->name || tok->previous->op == ')') && + (isValue(tok->next) || tok->next->name || tok->next->op == '(')); } static bool isAlternativeUnaryOp(const simplecpp::Token *tok, const std::string &alt) @@ -1072,6 +1089,23 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, combineOperators(); } +/** + * If one operand of the binary operator tok is an error value then + * "lhs op rhs" is replaced by that error value. + * @return the error value token, or nullptr + */ +static simplecpp::Token *foldErrorValue(simplecpp::TokenList &tokens, simplecpp::Token *tok) +{ + simplecpp::Token * const lhs = tok->previous; + if (!isErrorValue(lhs) && !isErrorValue(tok->next)) + return nullptr; + if (!isErrorValue(lhs)) + lhs->setstr(tok->next->str()); + tokens.deleteToken(tok->next); + tokens.deleteToken(tok); + return lhs; +} + void simplecpp::TokenList::constFold() { while (cfront()) { @@ -1105,6 +1139,11 @@ void simplecpp::TokenList::constFold() deleteToken(tok->previous); deleteToken(tok->next); } + + for (const Token *tok = cfront(); tok; tok = tok->next) { + if (isErrorValue(tok)) + throw std::overflow_error(tok->str().substr(ERROR_VALUE.size())); + } } static bool isFloatSuffix(const simplecpp::Token *tok) @@ -1262,17 +1301,27 @@ void simplecpp::TokenList::constFoldUnaryNotPosNeg(simplecpp::Token *tok) else if (isAlternativeUnaryOp(tok, COMPL)) tok->op = '~'; - if (tok->op == '!' && tok->next && tok->next->number) { + if (tok->isOneOf("!~") && isErrorValue(tok->next)) { + tok->setstr(tok->next->str()); + deleteToken(tok->next); + } else if (tok->op == '!' && tok->next && tok->next->number) { tok->setstr(tok->next->str() == "0" ? "1" : "0"); deleteToken(tok->next); } else if (tok->op == '~' && tok->next && tok->next->number) { tok->setstr(toString(~stringToLL(tok->next->str()))); deleteToken(tok->next); } else { - if (tok->previous && (tok->previous->number || tok->previous->name)) + if (tok->previous && (isValue(tok->previous) || tok->previous->name)) continue; - if (!tok->next || !tok->next->number) + if (!isValue(tok->next)) continue; + if (isErrorValue(tok->next)) { + if (tok->isOneOf("+-")) { + tok->setstr(tok->next->str()); + deleteToken(tok->next); + } + continue; + } switch (tok->op) { case '+': tok->setstr(tok->next->str()); @@ -1290,32 +1339,33 @@ void simplecpp::TokenList::constFoldUnaryNotPosNeg(simplecpp::Token *tok) void simplecpp::TokenList::constFoldMulDivRem(Token *tok) { for (; tok && tok->op != ')'; tok = tok->next) { - if (!tok->previous || !tok->previous->number) + if (!isValue(tok->previous) || !isValue(tok->next)) + continue; + if (!tok->isOneOf("*/%")) continue; - if (!tok->next || !tok->next->number) + if (Token * const errorValue = foldErrorValue(*this, tok)) { + tok = errorValue; continue; + } - long long result; + std::string result; if (tok->op == '*') { - result = (stringToLL(tok->previous->str()) * stringToLL(tok->next->str())); - } - else if (tok->op == '/' || tok->op == '%') { + result = toString(stringToLL(tok->previous->str()) * stringToLL(tok->next->str())); + } else { const long long rhs = stringToLL(tok->next->str()); - if (rhs == 0) - throw std::overflow_error("division/modulo by zero"); const long long lhs = stringToLL(tok->previous->str()); - if (rhs == -1 && lhs == std::numeric_limits::min()) - throw std::overflow_error("division overflow"); - if (tok->op == '/') - result = (lhs / rhs); + if (rhs == 0) + result = ERROR_VALUE + "division/modulo by zero"; + else if (rhs == -1 && lhs == std::numeric_limits::min()) + result = ERROR_VALUE + "division overflow"; + else if (tok->op == '/') + result = toString(lhs / rhs); else - result = (lhs % rhs); - } else { - continue; + result = toString(lhs % rhs); } tok = tok->previous; - tok->setstr(toString(result)); + tok->setstr(result); deleteToken(tok->next); deleteToken(tok->next); } @@ -1324,10 +1374,14 @@ void simplecpp::TokenList::constFoldMulDivRem(Token *tok) void simplecpp::TokenList::constFoldAddSub(Token *tok) { for (; tok && tok->op != ')'; tok = tok->next) { - if (!tok->previous || !tok->previous->number) + if (!isValue(tok->previous) || !isValue(tok->next)) + continue; + if (!tok->isOneOf("+-")) continue; - if (!tok->next || !tok->next->number) + if (Token * const errorValue = foldErrorValue(*this, tok)) { + tok = errorValue; continue; + } long long result; if (tok->op == '+') @@ -1347,10 +1401,14 @@ void simplecpp::TokenList::constFoldAddSub(Token *tok) void simplecpp::TokenList::constFoldShift(Token *tok) { for (; tok && tok->op != ')'; tok = tok->next) { - if (!tok->previous || !tok->previous->number) + if (!isValue(tok->previous) || !isValue(tok->next)) + continue; + if (tok->str() != "<<" && tok->str() != ">>") continue; - if (!tok->next || !tok->next->number) + if (Token * const errorValue = foldErrorValue(*this, tok)) { + tok = errorValue; continue; + } long long result; if (tok->str() == "<<") @@ -1374,12 +1432,14 @@ void simplecpp::TokenList::constFoldComparison(Token *tok) if (isAlternativeBinaryOp(tok,NOTEQ)) tok->setstr("!="); - if (!tok->startsWithOneOf("<>=!")) + if (!tok->isOneOf("<>") && tok->str() != "<=" && tok->str() != ">=" && tok->str() != "==" && tok->str() != "!=") continue; - if (!tok->previous || !tok->previous->number) + if (!isValue(tok->previous) || !isValue(tok->next)) continue; - if (!tok->next || !tok->next->number) + if (Token * const errorValue = foldErrorValue(*this, tok)) { + tok = errorValue; continue; + } int result; if (tok->str() == "==") @@ -1419,10 +1479,12 @@ void simplecpp::TokenList::constFoldBitwise(Token *tok) for (tok = tok1; tok && tok->op != ')'; tok = tok->next) { if (tok->op != *op && !isAlternativeBinaryOp(tok, *alternativeOp)) continue; - if (!tok->previous || !tok->previous->number) + if (!isValue(tok->previous) || !isValue(tok->next)) continue; - if (!tok->next || !tok->next->number) + if (Token * const errorValue = foldErrorValue(*this, tok)) { + tok = errorValue; continue; + } long long result; if (*op == '&') result = (stringToLL(tok->previous->str()) & stringToLL(tok->next->str())); @@ -1455,19 +1517,24 @@ void simplecpp::TokenList::constFoldLogicalOp(Token *tok) for (Token *t = tok; t && t->op != ')'; t = t->next) { if (t->str() != op) continue; - if (!t->previous || !t->previous->number) - continue; - if (!t->next || !t->next->number) + if (!isValue(t->previous) || !isValue(t->next)) continue; - int result; - if (t->str() == "||") - result = (stringToLL(t->previous->str()) || stringToLL(t->next->str())); - else /*if (t->str() == "&&")*/ - result = (stringToLL(t->previous->str()) && stringToLL(t->next->str())); + // the right operand is not evaluated if the left operand decides the result + std::string result; + if (isErrorValue(t->previous)) + result = t->previous->str(); + else if (t->str() == "||" && stringToLL(t->previous->str()) != 0) + result = "1"; + else if (t->str() == "&&" && stringToLL(t->previous->str()) == 0) + result = "0"; + else if (isErrorValue(t->next)) + result = t->next->str(); + else + result = (stringToLL(t->next->str()) != 0) ? "1" : "0"; t = t->previous; - t->setstr(toString(result)); + t->setstr(result); deleteToken(t->next); deleteToken(t->next); } @@ -1476,30 +1543,42 @@ 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() != "?") - continue; + // ?: is right-associative, "a ? b : c ? d : e" means "a ? b : (c ? d : e)", + // so the last ? is folded first + std::vector questionTokens; + for (const Token *t = tok1; t && t->op != ')'; t = t->next) { + if (t->str() == "?") + questionTokens.push_back(t); + } + while (!questionTokens.empty()) { + const Token * const tok = questionTokens.back(); + questionTokens.pop_back(); if (!tok->previous || !tok->next || !tok->next->next) throw std::runtime_error("invalid expression"); - if (!tok->previous->number) - continue; - if (tok->next->next->op != ':') - continue; Token * const condTok = tok->previous; Token * const trueTok = tok->next; Token * const falseTok = trueTok->next->next; if (!falseTok) throw std::runtime_error("invalid expression"); - if (condTok == tok1) - tok1 = (condTok->str() != "0" ? trueTok : falseTok); - deleteToken(condTok->next); // ? - deleteToken(trueTok->next); // : - deleteToken(condTok->str() == "0" ? trueTok : falseTok); - deleteToken(condTok); - gotoTok1 = true; + if (trueTok->next->op != ':') + return; + if (!isValue(condTok) || !isValue(trueTok) || !isValue(falseTok)) + return; + if (isErrorValue(condTok)) { + // "error ? x : y" => error + deleteToken(condTok->next); // ? + deleteToken(trueTok->next); // : + deleteToken(trueTok); + deleteToken(falseTok); + } else { + Token * const result = (condTok->str() != "0" ? trueTok : falseTok); + if (condTok == tok1) + tok1 = result; + deleteToken(condTok->next); // ? + deleteToken(trueTok->next); // : + deleteToken(result == falseTok ? trueTok : falseTok); // the operand that is not evaluated + deleteToken(condTok); + } } } diff --git a/test.cpp b/test.cpp index fdfc9ead..bd0da227 100644 --- a/test.cpp +++ b/test.cpp @@ -620,6 +620,22 @@ static void constFold() ASSERT_EQUALS("1", testConstFold("010==8")); ASSERT_EQUALS("exception", testConstFold("!1 ? 2 :")); ASSERT_EQUALS("exception", testConstFold("?2:3")); + ASSERT_EQUALS("0", testConstFold("0 && 1/0")); + ASSERT_EQUALS("1", testConstFold("1 || 1/0")); + ASSERT_EQUALS("2", testConstFold("1 ? 2 : 1/0")); + ASSERT_EQUALS("exception", testConstFold("1 && 1/0")); + ASSERT_EQUALS("exception", testConstFold("1/0 && 0")); + ASSERT_EQUALS("exception", testConstFold("(1/0) ? 1 : 2")); + ASSERT_EQUALS("exception", testConstFold("-(1/0) + 1")); + // ?: is right-associative + ASSERT_EQUALS("1", testConstFold("1 ? 1 : 0 ? 2 : 3")); + ASSERT_EQUALS("3", testConstFold("0 ? 1 : 0 ? 2 : 3")); + ASSERT_EQUALS("2", testConstFold("1 ? 2 : 3 ? 4 : 5 ? 6 : 7")); + ASSERT_EQUALS("7", testConstFold("0 ? 2 : 0 ? 4 : 0 ? 6 : 7")); + ASSERT_EQUALS("6", testConstFold("1 ? 0 ? 5 : 6 : 7")); + ASSERT_EQUALS("2", testConstFold("(1 ? 1 : 0) ? 2 : 3")); + ASSERT_EQUALS("1", testConstFold("1 ? 1 : (1/0) ? 2 : 3")); + ASSERT_EQUALS("exception", testConstFold("0 ? 1 : (1/0) ? 2 : 3")); } #ifdef __CYGWIN__ @@ -2835,6 +2851,42 @@ static void ifdiv0() ASSERT_EQUALS("", preprocess(code)); } +static void ifShortCircuit() +{ + // operands that are not evaluated must not cause errors + const char * const exprs[] = { + "0 && (1/0)", + "!(1 || (1/0))", + "0 ? 1/0 : 0", + "1 ? 0 : 1/0", + "0 && 1 % 0 && 1", + "(1 || 1/0) && !(0 && 1/0) && 0", + "0 && (2 + (3 * (1/0)))", + "0 && -(1/0)", + "0 and (1/0)", + "!(1 or 1/0)", + "defined(X) and X/0", + "0 && 1 bitor (1/0)", + "!(1 || (1/0) bitand 1)", + "0 and 1 xor (1/0)" + }; + for (const char * const expr : exprs) { + const std::string code = std::string("#if ") + expr + "\n" + "1\n" + "#else\n" + "2\n" + "#endif\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("\n\n\n2", preprocess(code.c_str(), &outputList)); + ASSERT_EQUALS("", toString(outputList)); + } + + // evaluated operands are still checked + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess("#if 1 && (1/0)\n#endif\n", &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, division/modulo by zero\n", toString(outputList)); +} + static void ifTrueFalse() { const char code[] = "#if true && !false\n" @@ -5070,6 +5122,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(ifif); TEST_CASE(ifoverflow); TEST_CASE(ifdiv0); + TEST_CASE(ifShortCircuit); TEST_CASE(ifTrueFalse); TEST_CASE(ifalt); // using "and", "or", etc TEST_CASE(ifexpr); From 9f5687ebee3ef1e779074c0de834f310d0c66e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 7 Nov 2026 12:14:55 +0100 Subject: [PATCH 140/152] Fix #726 (#if: ?: treats a zero condition as true unless it is written exactly as 0) (#728) --- simplecpp.cpp | 4 ++-- test.cpp | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 91af52ea..60f879cc 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1305,7 +1305,7 @@ void simplecpp::TokenList::constFoldUnaryNotPosNeg(simplecpp::Token *tok) tok->setstr(tok->next->str()); deleteToken(tok->next); } else if (tok->op == '!' && tok->next && tok->next->number) { - tok->setstr(tok->next->str() == "0" ? "1" : "0"); + tok->setstr(stringToLL(tok->next->str()) == 0 ? "1" : "0"); deleteToken(tok->next); } else if (tok->op == '~' && tok->next && tok->next->number) { tok->setstr(toString(~stringToLL(tok->next->str()))); @@ -1571,7 +1571,7 @@ void simplecpp::TokenList::constFoldQuestionOp(Token *&tok1) deleteToken(trueTok); deleteToken(falseTok); } else { - Token * const result = (condTok->str() != "0" ? trueTok : falseTok); + Token * const result = (stringToLL(condTok->str()) != 0 ? trueTok : falseTok); if (condTok == tok1) tok1 = result; deleteToken(condTok->next); // ? diff --git a/test.cpp b/test.cpp index bd0da227..1296c790 100644 --- a/test.cpp +++ b/test.cpp @@ -2887,6 +2887,32 @@ static void ifShortCircuit() ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, division/modulo by zero\n", toString(outputList)); } +static void ifZeroCondition() +{ + // a zero value that is not written as 0 is false + const char * const exprs[] = { + "00 ? 1 : 0", + "-0 ? 1 : 0", + "-X ? 1 : 0", + "0L ? 1 : 0", + "!00 ? 0 : 1", + "!0L ? 0 : 1" + }; + for (const char * const expr : exprs) { + const std::string code = std::string("#if ") + expr + "\n" + "1\n" + "#else\n" + "2\n" + "#endif\n"; + ASSERT_EQUALS("\n\n\n2", preprocess(code.c_str())); + } + + // the selected operand is evaluated + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess("#if 00 ? 1 : 1/0\n#endif\n", &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, division/modulo by zero\n", toString(outputList)); +} + static void ifTrueFalse() { const char code[] = "#if true && !false\n" @@ -5123,6 +5149,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(ifoverflow); TEST_CASE(ifdiv0); TEST_CASE(ifShortCircuit); + TEST_CASE(ifZeroCondition); TEST_CASE(ifTrueFalse); TEST_CASE(ifalt); // using "and", "or", etc TEST_CASE(ifexpr); From a62b8ee458c5d647db00650b490b5afb8f5f93a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 7 Nov 2026 13:30:10 +0100 Subject: [PATCH 141/152] Fix #727 (#if: two unary operators in a row silently evaluate to false) (#730) --- simplecpp.cpp | 16 +++++++++++++--- test.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 60f879cc..c9d762fa 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1291,9 +1291,16 @@ void simplecpp::TokenList::combineOperators() static const std::string COMPL("compl"); static const std::string NOT("not"); -void simplecpp::TokenList::constFoldUnaryNotPosNeg(simplecpp::Token *tok) +void simplecpp::TokenList::constFoldUnaryNotPosNeg(simplecpp::Token *tok1) { - for (; tok && tok->op != ')'; tok = tok->next) { + if (!tok1 || tok1->op == ')') + return; + Token *last = tok1; + while (last->next && last->next->op != ')') + last = last->next; + + // unary operators are right-associative, "!-0" means "!(-0)", so they are folded from right to left + for (Token *tok = last; tok; tok = (tok == tok1) ? nullptr : tok->previous) { // "not" might be ! if (isAlternativeUnaryOp(tok, NOT)) tok->op = '!'; @@ -1328,7 +1335,10 @@ void simplecpp::TokenList::constFoldUnaryNotPosNeg(simplecpp::Token *tok) deleteToken(tok->next); break; case '-': - tok->setstr(tok->op + tok->next->str()); + if (tok->next->str()[0] == '-') + tok->setstr(tok->next->str().substr(1)); + else + tok->setstr(tok->op + tok->next->str()); deleteToken(tok->next); break; } diff --git a/test.cpp b/test.cpp index 1296c790..00a4a136 100644 --- a/test.cpp +++ b/test.cpp @@ -2913,6 +2913,35 @@ static void ifZeroCondition() ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, division/modulo by zero\n", toString(outputList)); } +static void ifUnaryOperators() +{ + // unary operators in a row + const char * const exprs[] = { + "!!1", + "!-0", + "!+0", + "!~-1", + "- -1", + "-!0", + "-~0", + "~!0", + "+-1 == -1", + "1 && !-0", + "!-X", + "-(-1) == 1", + "- - -1 == -1", + "1 - -1 == 2" + }; + for (const char * const expr : exprs) { + const std::string code = std::string("#if ") + expr + "\n" + "1\n" + "#else\n" + "2\n" + "#endif\n"; + ASSERT_EQUALS("\n1", preprocess(code.c_str())); + } +} + static void ifTrueFalse() { const char code[] = "#if true && !false\n" @@ -5150,6 +5179,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(ifdiv0); TEST_CASE(ifShortCircuit); TEST_CASE(ifZeroCondition); + TEST_CASE(ifUnaryOperators); TEST_CASE(ifTrueFalse); TEST_CASE(ifalt); // using "and", "or", etc TEST_CASE(ifexpr); From 70d76461d69b09eeb896178c7b81dcb4e2714807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 7 Nov 2026 14:41:39 +0100 Subject: [PATCH 142/152] Fix #731 (#if: an operator with a missing operand silently evaluates to false) (#732) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Marjamäki Co-authored-by: Claude Opus 5.5 --- simplecpp.cpp | 5 +++-- test.cpp | 46 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index c9d762fa..48a7ce9e 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3283,8 +3283,9 @@ static long long evaluate(simplecpp::TokenList &expr, const simplecpp::DUI &dui, simplifyName(expr, dui); simplifyNumbers(expr); expr.constFold(); - // TODO: handle invalid expressions - return expr.cfront() && expr.cfront() == expr.cback() && expr.cfront()->number ? stringToLL(expr.cfront()->str()) : 0LL; + if (!expr.cfront() || expr.cfront() != expr.cback() || !expr.cfront()->number) + throw std::runtime_error("invalid expression"); + return stringToLL(expr.cfront()->str()); } static const simplecpp::Token *gotoNextLine(const simplecpp::Token *tok) diff --git a/test.cpp b/test.cpp index 00a4a136..866af490 100644 --- a/test.cpp +++ b/test.cpp @@ -1842,7 +1842,8 @@ static void hashEscapeTrailingBackslash() "#endif\n"; simplecpp::OutputList outputList; preprocess(code, &outputList); - ASSERT_EQUALS(std::string("file0,2,portability_stringify,") + msg, toString(outputList)); + ASSERT_EQUALS(std::string("file0,2,portability_stringify,") + msg + + "file0,2,syntax_error,failed to evaluate #if condition, invalid expression\n", toString(outputList)); } { // even number of backslashes is fine @@ -2942,6 +2943,48 @@ static void ifUnaryOperators() } } +static void ifInvalidExpression() +{ + const char * const exprs[] = { + // missing operand + "1 +", + "(1 +)", + "(1 +) + 1", + "* 1", + "(* 1)", + "1 + * 2", + "1 <<", + "(1 ==)", + "1 &", + "(1 &&)", + "|| 1", + "!", + "(!)", + "-", + "~", + "(? 1 : 2)", + "(1 ? 2 :)", + "(1 ? 2 3)", + "(1 ? : 2)", + // missing operator + "1 2", + "(1 2)", + // empty parentheses + "()", + "1 + ()", + // syntax errors are reported also in operands that are not evaluated + "0 && (1 +)" + }; + for (const char * const expr : exprs) { + const std::string code = std::string("#if ") + expr + "\n" + "1\n" + "#endif\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code.c_str(), &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, invalid expression\n", toString(outputList)); + } +} + static void ifTrueFalse() { const char code[] = "#if true && !false\n" @@ -5180,6 +5223,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(ifShortCircuit); TEST_CASE(ifZeroCondition); TEST_CASE(ifUnaryOperators); + TEST_CASE(ifInvalidExpression); TEST_CASE(ifTrueFalse); TEST_CASE(ifalt); // using "and", "or", etc TEST_CASE(ifexpr); From cab27585e5cde44cb038b0d639cddab5461765c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 7 Nov 2026 15:44:05 +0100 Subject: [PATCH 143/152] Fix #729 (#if: not and compl are only recognized at the start of an expression or after () (#733) Co-authored-by: Claude Opus 5.5 --- simplecpp.cpp | 23 +++++++++++++++++------ test.cpp | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 7 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 48a7ce9e..219a3079 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -206,11 +206,23 @@ static bool isAlternativeBinaryOp(const simplecpp::Token *tok, const std::string (isValue(tok->next) || tok->next->name || tok->next->op == '(')); } +static bool isAlternativeOpName(const simplecpp::Token *tok) +{ + static const std::set altop{"and","or","bitand","bitor","compl","not","not_eq","xor"}; + return tok->name && altop.find(tok->str()) != altop.end(); +} + static bool isAlternativeUnaryOp(const simplecpp::Token *tok, const std::string &alt) { - return ((tok->name && tok->str() == alt) && - (!tok->previous || tok->previous->op == '(') && - (tok->next && (tok->next->name || tok->next->number))); + if (!tok->name || tok->str() != alt) + return false; + // a unary operator comes first or after another operator, but not after an operand + const simplecpp::Token * const prev = tok->previous; + if (prev && (isValue(prev) || prev->op == ')' || (prev->name && !isAlternativeOpName(prev)))) + return false; + // it is followed by the start of an operand + const simplecpp::Token * const next = tok->next; + return next && (next->name || isValue(next) || next->isOneOf("(!~+-")); } static std::string replaceAll(std::string s, const std::string& from, const std::string& to) @@ -1318,7 +1330,7 @@ void simplecpp::TokenList::constFoldUnaryNotPosNeg(simplecpp::Token *tok1) tok->setstr(toString(~stringToLL(tok->next->str()))); deleteToken(tok->next); } else { - if (tok->previous && (isValue(tok->previous) || tok->previous->name)) + if (tok->previous && (isValue(tok->previous) || (tok->previous->name && !isAlternativeOpName(tok->previous)))) continue; if (!isValue(tok->next)) continue; @@ -2992,8 +3004,7 @@ static void simplifyName(simplecpp::TokenList &expr, const simplecpp::DUI &dui) tok->setstr(tok->str() == "true" ? "1" : "0"); continue; } - static const std::set altop{"and","or","bitand","bitor","compl","not","not_eq","xor"}; - if (altop.find(tok->str()) != altop.end()) { + if (isAlternativeOpName(tok)) { bool alt; if (tok->str() == "not" || tok->str() == "compl") { alt = isAlternativeUnaryOp(tok,tok->str()); diff --git a/test.cpp b/test.cpp index 866af490..9b30794b 100644 --- a/test.cpp +++ b/test.cpp @@ -2869,7 +2869,10 @@ static void ifShortCircuit() "defined(X) and X/0", "0 && 1 bitor (1/0)", "!(1 || (1/0) bitand 1)", - "0 and 1 xor (1/0)" + "0 and 1 xor (1/0)", + "0 && not (1/0)", + "0 && compl (1/0)", + "!(1 || not not (1/0))" }; for (const char * const expr : exprs) { const std::string code = std::string("#if ") + expr + "\n" @@ -2886,6 +2889,9 @@ static void ifShortCircuit() simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess("#if 1 && (1/0)\n#endif\n", &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, division/modulo by zero\n", toString(outputList)); + outputList.clear(); + ASSERT_EQUALS("", preprocess("#if not (1/0)\n#endif\n", &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, division/modulo by zero\n", toString(outputList)); } static void ifZeroCondition() @@ -3022,6 +3028,45 @@ static void ifalt() // using "and", "or", etc ASSERT_EQUALS("\n1", preprocess(code)); } +static void ifAltUnaryOperators() // "not" and "compl" anywhere a unary operator is allowed +{ + const char * const exprs[] = { + // at the start of the expression + "not 0", + "(not 0)", + "not 0 && 1", + "compl 0", + // after a binary operator, "?" or ":" + "1 && not 0", + "1 and not 0", + "0 || not 0", + "1 == not 0", + "0 ? 0 : not 0", + "1 ? not 0 : 0", + // next to another unary operator + "not not 1", + "not -0", + "not ~-1", + "not !0 == 0", + "compl compl 1 == 1", + "compl -1 == 0", + "!(compl -1 + 1 == 0)", + "-compl 0 == 1", + // followed by "(" + "not (0)", + "not(0)", + "compl (0) == -1" + }; + for (const char * const expr : exprs) { + const std::string code = std::string("#if ") + expr + "\n" + "1\n" + "#else\n" + "2\n" + "#endif\n"; + ASSERT_EQUALS("\n1", preprocess(code.c_str())); + } +} + static void ifexpr() { const char code[] = "#define MACRO() (1)\n" @@ -5226,6 +5271,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(ifInvalidExpression); TEST_CASE(ifTrueFalse); TEST_CASE(ifalt); // using "and", "or", etc + TEST_CASE(ifAltUnaryOperators); TEST_CASE(ifexpr); TEST_CASE(ifUndefFuncStyleMacro); From a837f612aea907719487013e077ccd1806b4fe86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 7 Nov 2026 18:31:27 +0100 Subject: [PATCH 144/152] Fix #735 (Refactoring: split TokenList::readfile() into smaller functions) (#736) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Marjamäki Co-authored-by: Claude Opus 5.5 --- simplecpp.cpp | 216 ++++++++++++++++++++++++++++++-------------------- simplecpp.h | 21 +++++ 2 files changed, 152 insertions(+), 85 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 219a3079..9f23b04a 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -726,6 +726,133 @@ void simplecpp::TokenList::lineDirective(unsigned int fileIndex_, unsigned int l static const std::string COMMENT_END("*/"); +/** + * Read a name or a number. + * In a number, a ' digit separator is removed, so 1'000 gives "1000". + * The stream is left after the last character of the token. + * @param stream the stream to read from + * @param ch the first character, already read from the stream + * @return the name or number + */ +static simplecpp::TokenString readNameOrNumber(simplecpp::TokenList::Stream &stream, unsigned char ch) +{ + simplecpp::TokenString currentToken; + const bool num = !!std::isdigit(ch); + while (stream.good() && isNameChar(ch)) { + currentToken += ch; + ch = stream.readChar(); + if (num && ch=='\'' && isNameChar(stream.peekChar())) + ch = stream.readChar(); + } + stream.ungetChar(); + return currentToken; +} + +/** + * Read a // comment, up to but not including the newline. + * A backslash followed by a newline continues the comment on the next line. + * The backslash, and any spaces or tabs after it, are then replaced by a + * newline in the comment text. + * @param stream the stream to read from + * @param ch the first '/', already read from the stream + * @param location the location of the comment, used for warnings + * @param multiline incremented for each continued line + * @param outputList if not null, a portability warning is added when there is + * whitespace between the backslash and the newline + * @return the comment text, including the leading // + */ +static simplecpp::TokenString readLineComment(simplecpp::TokenList::Stream &stream, unsigned char ch, const simplecpp::Location &location, unsigned int &multiline, simplecpp::OutputList *outputList) +{ + simplecpp::TokenString currentToken; + while (stream.good() && ch != '\n') { + currentToken += ch; + ch = stream.readChar(); + if (ch == '\\') { + simplecpp::TokenString tmp; + char tmp_ch = ch; + while ((stream.good()) && (tmp_ch == '\\' || tmp_ch == ' ' || tmp_ch == '\t')) { + tmp += tmp_ch; + tmp_ch = stream.readChar(); + } + if (!stream.good()) { + break; + } + + if (tmp_ch != '\n') { + currentToken += tmp; + } else { + const simplecpp::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, location); + ++multiline; + tmp_ch = stream.readChar(); + currentToken += '\n'; + } + ch = tmp_ch; + } + } + if (ch == '\n') { + stream.ungetChar(); + } + return currentToken; +} + +simplecpp::TokenString simplecpp::TokenList::readBlockComment(Stream &stream, unsigned int &multiline) +{ + TokenString currentToken = "/*"; + (void)stream.readChar(); + unsigned char ch = stream.readChar(); + while (stream.good()) { + currentToken += ch; + if (currentToken.size() >= 4U && endsWith(currentToken, COMMENT_END)) + break; + ch = stream.readChar(); + } + // multiline.. + + std::string::size_type pos = 0; + while ((pos = currentToken.find("\\\n",pos)) != std::string::npos) { + currentToken.erase(pos,2); + ++multiline; + } + if (multiline || isLastLinePreprocessor()) { + pos = 0; + while ((pos = currentToken.find('\n',pos)) != std::string::npos) { + currentToken.erase(pos,1); + ++multiline; + } + } + return currentToken; +} + +void simplecpp::TokenList::readErrorText(Stream &stream, unsigned char ch, Location &location) +{ + TokenString currentToken; + char prev = ' '; + while (stream.good() && (prev == '\\' || (ch != '\r' && ch != '\n'))) { + currentToken += ch; + prev = ch; + ch = stream.readChar(); + } + stream.ungetChar(); + 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; +} + void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, const DUI &dui, OutputList *outputList) { unsigned int multiline = 0U; @@ -880,105 +1007,24 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if (cback() && cback()->location.line == location.line && cback()->previous && cback()->previous->op == '#') { 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; - prev = ch; - ch = stream.readChar(); - } - stream.ungetChar(); - 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; + readErrorText(stream, ch, location); continue; } } // number or name if (isNameChar(ch)) { - const bool num = !!std::isdigit(ch); - while (stream.good() && isNameChar(ch)) { - currentToken += ch; - ch = stream.readChar(); - if (num && ch=='\'' && isNameChar(stream.peekChar())) - ch = stream.readChar(); - } - - stream.ungetChar(); + currentToken = readNameOrNumber(stream, ch); } // comment else if (ch == '/' && stream.peekChar() == '/') { - while (stream.good() && ch != '\n') { - currentToken += ch; - ch = stream.readChar(); - if (ch == '\\') { - TokenString tmp; - char tmp_ch = ch; - while ((stream.good()) && (tmp_ch == '\\' || tmp_ch == ' ' || tmp_ch == '\t')) { - tmp += tmp_ch; - tmp_ch = stream.readChar(); - } - if (!stream.good()) { - break; - } - - if (tmp_ch != '\n') { - currentToken += tmp; - } else { - 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, location); - ++multiline; - tmp_ch = stream.readChar(); - currentToken += '\n'; - } - ch = tmp_ch; - } - } - if (ch == '\n') { - stream.ungetChar(); - } + currentToken = readLineComment(stream, ch, location, multiline, outputList); } // comment else if (ch == '/' && stream.peekChar() == '*') { - currentToken = "/*"; - (void)stream.readChar(); - ch = stream.readChar(); - while (stream.good()) { - currentToken += ch; - if (currentToken.size() >= 4U && endsWith(currentToken, COMMENT_END)) - break; - ch = stream.readChar(); - } - // multiline.. - - std::string::size_type pos = 0; - while ((pos = currentToken.find("\\\n",pos)) != std::string::npos) { - currentToken.erase(pos,2); - ++multiline; - } - if (multiline || isLastLinePreprocessor()) { - pos = 0; - while ((pos = currentToken.find('\n',pos)) != std::string::npos) { - currentToken.erase(pos,1); - ++multiline; - } - } + currentToken = readBlockComment(stream, multiline); } // string / char literal diff --git a/simplecpp.h b/simplecpp.h index 86573fa6..2b560d27 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -429,6 +429,27 @@ namespace simplecpp { const Token* lastLineTok(int maxsize=1000) const; const Token* isLastLinePreprocessor(int maxsize=1000) const; + /** + * Read a block comment, up to and including the closing star-slash. + * Each backslash-newline is removed from the text. If the comment is + * on a continued line or in a preprocessor directive, its newlines are + * removed too, so that the directive keeps going after the comment. + * @param stream the stream to read from. The first '/' is already + * read, and the next character is '*'. + * @param multiline incremented for each newline that is removed + * @return the comment text + */ + TokenString readBlockComment(Stream &stream, unsigned int &multiline); + /** + * Read the text of a #error or #warning directive, up to but not + * including the newline, and add it as one token. A backslash-newline + * continues the text on the next line and is removed from it. + * @param stream the stream to read from + * @param ch the first character of the text, already read from the stream + * @param location the location of the text. Updated to point after it. + */ + void readErrorText(Stream &stream, unsigned char ch, Location &location); + unsigned int fileIndex(const std::string &filename); Token *frontToken; From 178e71dbd3e16de3a721f5ed270d461df559a6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 7 Nov 2026 19:18:23 +0100 Subject: [PATCH 145/152] Fix #737 (Refactoring: separate the cppcheck-specific &= handling from TokenList::combineOperators()) (#738) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Marjamäki Co-authored-by: Claude Opus 5.5 --- simplecpp.cpp | 124 +++++++++++++++++++++++++++++++------------------- 1 file changed, 76 insertions(+), 48 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 9f23b04a..5ddd8d99 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1220,26 +1220,83 @@ static bool isAlternativeAndBitandBitor(const simplecpp::Token* tok) return isAlternativeBinaryOp(tok, AND) || isAlternativeBinaryOp(tok, BITAND) || isAlternativeBinaryOp(tok, BITOR); } +/** + * Keep track of whether the code is in a function body. This is not part of + * preprocessing, it is used by the cppcheck-specific handling of &= in + * combineOperators(). + * @param executableScope one entry for each open {, true if it is a function + * body or a block in a function body + * @param tok the current token + * @return true if tok is { or } + */ +static bool updateExecutableScope(std::stack> &executableScope, const simplecpp::Token *tok) +{ + if (tok->op == '{') { + if (executableScope.top()) { + executableScope.push(true); + return true; + } + const simplecpp::Token *prev = tok->previous; + while (prev && prev->isOneOf(";{}(")) + prev = prev->previous; + executableScope.push(prev && prev->op == ')'); + return true; + } + if (tok->op == '}') { + if (executableScope.size() > 1) + executableScope.pop(); + return true; + } + return false; +} + +/** + * Is tok the & of an unnamed reference parameter with a default value in a + * function declaration, as in "void f(x&=2)"? Then "&" and "=" should not be + * combined into "&=". This is a cppcheck-specific heuristic, compilers + * tokenize "&=" as one operator. + * @param tok a & token that is directly followed by = + */ +static bool isAnonymousReferenceParameter(const simplecpp::Token *tok) +{ + int indentlevel = 0; + const simplecpp::Token *start = tok; + while (indentlevel >= 0 && start) { + if (start->op == ')') + ++indentlevel; + else if (start->op == '(') + --indentlevel; + else if (start->isOneOf(";{}")) + break; + start = start->previous; + } + if (indentlevel == -1 && start) { + const simplecpp::Token * const ftok = start; + bool isFuncDecl = ftok->name; + while (isFuncDecl) { + if (!start->name && start->str() != "::" && start->op != '*' && start->op != '&') + isFuncDecl = false; + if (!start->previous) + break; + if (start->previous->isOneOf(";{}:")) + break; + start = start->previous; + } + isFuncDecl &= start != ftok && start->name; + if (isFuncDecl) { + // TODO: we could loop through the parameters here and check if they are correct. + return true; + } + } + return false; +} + void simplecpp::TokenList::combineOperators() { std::stack> executableScope{{false}}; for (Token *tok = front(); tok; tok = tok->next) { - if (tok->op == '{') { - if (executableScope.top()) { - executableScope.push(true); - continue; - } - const Token *prev = tok->previous; - while (prev && prev->isOneOf(";{}(")) - prev = prev->previous; - executableScope.push(prev && prev->op == ')'); + if (updateExecutableScope(executableScope, tok)) continue; - } - if (tok->op == '}') { - if (executableScope.size() > 1) - executableScope.pop(); - continue; - } if (tok->op == '.') { // ellipsis ... @@ -1283,39 +1340,10 @@ void simplecpp::TokenList::combineOperators() continue; if (tok->next->op == '=' && tok->isOneOf("=!<>+-*/%&|^")) { - if (tok->op == '&' && !executableScope.top()) { - // don't combine &= if it is a anonymous reference parameter with default value: - // void f(x&=2) - int indentlevel = 0; - const Token *start = tok; - while (indentlevel >= 0 && start) { - if (start->op == ')') - ++indentlevel; - else if (start->op == '(') - --indentlevel; - else if (start->isOneOf(";{}")) - break; - start = start->previous; - } - if (indentlevel == -1 && start) { - const Token * const ftok = start; - bool isFuncDecl = ftok->name; - while (isFuncDecl) { - if (!start->name && start->str() != "::" && start->op != '*' && start->op != '&') - isFuncDecl = false; - if (!start->previous) - break; - if (start->previous->isOneOf(";{}:")) - break; - start = start->previous; - } - isFuncDecl &= start != ftok && start->name; - if (isFuncDecl) { - // TODO: we could loop through the parameters here and check if they are correct. - continue; - } - } - } + // don't combine &= if it is a anonymous reference parameter with default value: + // void f(x&=2) + if (tok->op == '&' && !executableScope.top() && isAnonymousReferenceParameter(tok)) + continue; tok->setstr(tok->str() + "="); deleteToken(tok->next); } else if ((tok->op == '|' || tok->op == '&') && tok->op == tok->next->op) { From bccf566c40c0117db647863d1b0a137ccb163aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 7 Nov 2026 22:42:17 +0100 Subject: [PATCH 146/152] Fix #739 (Add a lookahead buffer to TokenList::Stream) (#740) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Marjamäki Co-authored-by: Claude Opus 5.5 --- simplecpp.cpp | 142 +++++++++++++++++++++++++++++--------------------- test.cpp | 101 +++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 58 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 5ddd8d99..a6adddad 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -295,68 +295,61 @@ class simplecpp::TokenList::Stream { public: virtual ~Stream() = default; - virtual int get() = 0; - virtual int peek() = 0; - virtual void unget() = 0; - virtual bool good() = 0; - + /** + * Read the next character. CR and CR LF are returned as LF. + * At the end of the input, EOF converted to unsigned char is returned + * and good() becomes false. + */ unsigned char readChar() { - 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 auto ch2 = static_cast(get()); - const int ch16 = makeUtf16Char(ch, ch2); - ch = static_cast(((ch16 >= 0x80) ? 0xff : ch16)); + int ch = getUnit(); + if (ch == EOF) { + atEnd = true; + lastChar = EOF; + return static_cast(EOF); } // Handling of newlines.. if (ch == '\r') { ch = '\n'; - - int ch2 = get(); - if (isUtf16) { - const int c2 = get(); - ch2 = makeUtf16Char(ch2, c2); - } - - if (ch2 != '\n') - ungetChar(); + const int ch2 = getUnit(); + if (ch2 != '\n' && ch2 != EOF) + buffer.push_back(static_cast(ch2)); } - return ch; + lastChar = ch; + return static_cast(ch); } + /** The next character, without reading it. EOF at the end of the input. */ int peekChar() { - int ch = peek(); + const int ch = getUnit(); 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 - if (isUtf16) { - (void)get(); - const auto ch2 = static_cast(peek()); - unget(); - const int ch16 = makeUtf16Char(ch, ch2); - ch = (ch16 >= 0x80) ? 0xff : ch16; - } - - // Handling of newlines.. - if (ch == '\r') - ch = '\n'; - - return ch; + return EOF; + buffer.push_back(static_cast(ch)); + return (ch == '\r') ? '\n' : ch; } + /** Put back the character that was last read with readChar() */ void ungetChar() { - unget(); - if (isUtf16) - unget(); + if (lastChar != EOF) + buffer.push_back(static_cast(lastChar)); + lastChar = EOF; + } + + /** + * false when readChar() has reached the end of the input. + * Unlike std::istream::good(), peekChar() at the end of the input does not make it false. + */ + bool good() const { + return !atEnd; } protected: + /** Raw input, used only by the Stream class. Call readChar(), peekChar() and ungetChar() instead. */ + virtual int get() = 0; + virtual int peek() = 0; + virtual void unget() = 0; + void init() { // initialize since we use peek() in getAndSkipBOM() isUtf16 = false; @@ -365,6 +358,38 @@ class simplecpp::TokenList::Stream { } private: + /** + * Read the next character from the input, or take it from the buffer. + * Newlines are not converted. + */ + int getUnit() { + if (!buffer.empty()) { + const auto ch = static_cast(buffer.back()); + buffer.pop_back(); + return ch; + } + if (inputEnd) + return EOF; + const int ch = get(); + if (ch == EOF) { + inputEnd = true; + return EOF; + } + if (!isUtf16) + return ch; + + // For UTF-16 encoded files the BOM is 0xfeff/0xfffe. If the + // character is non-ASCII character then replace it with 0xff + const int ch2 = get(); + if (ch2 == EOF) { + // a dangling last byte is dropped + inputEnd = true; + return EOF; + } + const int ch16 = makeUtf16Char(static_cast(ch), static_cast(ch2)); + return (ch16 >= 0x80) ? 0xff : ch16; + } + inline int makeUtf16Char(const unsigned char ch, const unsigned char ch2) const { return (bom == 0xfeff) ? (ch<<8 | ch2) : (ch2<<8 | ch); } @@ -400,6 +425,15 @@ class simplecpp::TokenList::Stream { } unsigned short bom; + + /** characters that are read but not used yet. The next character is at the back. */ + std::string buffer; + /** the last character returned by readChar(), EOF if ungetChar() has nothing to put back */ + int lastChar{EOF}; + /** readChar() has returned EOF */ + bool atEnd{false}; + /** get() has returned EOF */ + bool inputEnd{false}; protected: bool isUtf16; }; @@ -414,6 +448,7 @@ namespace { init(); } + protected: int get() override { return istr.get(); } @@ -423,9 +458,6 @@ namespace { void unget() override { istr.unget(); } - bool good() override { - return istr.good(); - } private: std::istream &istr; @@ -441,28 +473,25 @@ namespace { init(); } + protected: int get() override { if (pos >= size) - return lastStatus = EOF; + return EOF; return str[pos++]; } int peek() override { if (pos >= size) - return lastStatus = EOF; + return 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{}; }; class FileStream : public simplecpp::TokenList::Stream { @@ -489,8 +518,9 @@ namespace { file = nullptr; } + protected: int get() override { - lastStatus = lastCh = fgetc(file); + lastCh = fgetc(file); return lastCh; } int peek() override { @@ -502,9 +532,6 @@ namespace { void unget() override { unget_internal(lastCh); } - bool good() override { - return lastStatus != EOF; - } private: void unget_internal(int ch) { @@ -519,7 +546,6 @@ namespace { FILE *file; int lastCh{}; - int lastStatus{}; }; } diff --git a/test.cpp b/test.cpp index 9b30794b..a7db4c3c 100644 --- a/test.cpp +++ b/test.cpp @@ -9,7 +9,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -142,6 +144,20 @@ static std::string readfile(const char code[], std::size_t size, const simplecpp return makeTokenList(code,size,files,std::string(),dui,outputList).stringify(); } +/** Write the code to a temporary file and read it with TokenList(filename), so FileStream is used */ +static std::string readfileFromFile(const char code[], std::size_t size, simplecpp::OutputList *outputList = nullptr) +{ + const std::string filename = "testrunner_filestream.tmp"; + { + std::ofstream f(filename, std::ios::binary); + f.write(code, static_cast(size)); + } + std::vector files; + std::string ret = simplecpp::TokenList(filename, files, {}, outputList).stringify(); + std::remove(filename.c_str()); + return ret; +} + static std::string preprocess(const char code[], std::size_t size, const simplecpp::DUI &dui, simplecpp::OutputList *outputList, std::list *macroUsage = nullptr, std::list *ifCond = nullptr, const std::string &file = std::string()) { std::vector files; @@ -3981,6 +3997,11 @@ static void readfile_char_error() readfile("A = 's\n'", {}, &outputList); ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\'). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); + outputList.clear(); + + // the ' at the end of the file is not dropped after the peek for a digit separator + readfile("A = 1'", {}, &outputList); + ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\'). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); } static void readfile_string() @@ -4168,6 +4189,72 @@ static void readfile_no_eof_newline() readfile(code, sizeof(code)-1, {}, &outputList); ASSERT_EQUALS("", toString(outputList)); } + { + // CR is a newline + const char code[] = "x\r"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, {}, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "x\r\n"; + simplecpp::OutputList outputList; + readfile(code, sizeof(code)-1, {}, &outputList); + ASSERT_EQUALS("", toString(outputList)); + } +} + +static void readfile_cr_newlines() +{ + // CR, CR LF and LF are all newlines + ASSERT_EQUALS("a\nb\nc", readfile("a\rb\rc\r")); + ASSERT_EQUALS("a\nb\nc", readfile("a\r\nb\r\nc\r\n")); + ASSERT_EQUALS("a\nb\nc\n\n\nd", readfile("a\rb\r\nc\n\r\rd\n")); + ASSERT_EQUALS("# define A 1\nA", readfile("#define A 1\rA\r")); +} + +static void readfile_filestream() +{ + // the edge cases from the other readfile tests, read from a file + { + const char code[] = "a\rb\rc\r"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("a\nb\nc", readfileFromFile(code, sizeof(code)-1, &outputList)); + ASSERT_EQUALS("", toString(outputList)); + } + { + const char code[] = "a\r\nb\r\nc\r\n"; + ASSERT_EQUALS("a\nb\nc", readfileFromFile(code, sizeof(code)-1)); + } + { + const char code[] = "a\rb\r\nc\n\r\rd\n"; + ASSERT_EQUALS("a\nb\nc\n\n\nd", readfileFromFile(code, sizeof(code)-1)); + } + { + const char code[] = "1'"; + simplecpp::OutputList outputList; + (void)readfileFromFile(code, sizeof(code)-1, &outputList); + ASSERT_EQUALS("file0,1,syntax_error,No pair for character (\'). Can't process file. File is either invalid or unicode, which is currently not supported.\n", toString(outputList)); + } + { + // UTF-16LE with CR newlines + const char code[] = "\xff\xfe\x78\x00\x0d\x00\x79\x00\x0d\x00"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("x\ny", readfileFromFile(code, sizeof(code)-1, &outputList)); + ASSERT_EQUALS("", toString(outputList)); + } + { + // UTF-16BE with CR newlines + const char code[] = "\xfe\xff\x00\x78\x00\x0d\x00\x79\x00\x0d"; + ASSERT_EQUALS("x\ny", readfileFromFile(code, sizeof(code)-1)); + } + { + // UTF-16 with an odd number of bytes, the dangling last byte is dropped + const char code[] = "\xff\xfe\x31\x00\x0a"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("1", readfileFromFile(code, sizeof(code)-1, &outputList)); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file is undefined behavior in C.\n", toString(outputList)); + } } static void stringify1() @@ -4364,6 +4451,18 @@ static void unicode() const char code[] = "\xff\xfe\x0d\x00\x0a\x00\x2f\x00\x2f\x00\x31\x00\x0d\x00\x0a\x00"; ASSERT_EQUALS("\n//1", readfile(code, sizeof(code)-1)); } + { + // CR newlines + const char code[] = "\xff\xfe\x78\x00\x0d\x00\x79\x00\x0d\x00"; + ASSERT_EQUALS("x\ny", readfile(code, sizeof(code)-1)); + } + { + // odd number of bytes, the dangling last byte is dropped + const char code[] = "\xff\xfe\x31\x00\x0a"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("1", readfile(code, sizeof(code)-1, {}, &outputList)); + ASSERT_EQUALS("file0,1,portability_no_eof_newline,No newline at end of file is undefined behavior in C.\n", toString(outputList)); + } } static void unicode_invalid() @@ -5335,6 +5434,8 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(readfile_error); TEST_CASE(readfile_file_not_found); TEST_CASE(readfile_no_eof_newline); + TEST_CASE(readfile_cr_newlines); + TEST_CASE(readfile_filestream); TEST_CASE(stringify1); From c137175e828c44ab9452fd0245ab41ac04847041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 9 Nov 2026 08:50:00 +0100 Subject: [PATCH 147/152] Fix #744 (Add regression tests for macro expansion) (#745) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Marjamäki Co-authored-by: Claude Opus 5.5 --- test.cpp | 476 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 410 insertions(+), 66 deletions(-) diff --git a/test.cpp b/test.cpp index a7db4c3c..108a6a58 100644 --- a/test.cpp +++ b/test.cpp @@ -5,6 +5,7 @@ #include "simplecpp.h" +#include #include #include #include @@ -45,6 +46,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 TODO_ASSERT_EQUALS(wanted, current, actual) (todoAssertEquals((wanted), (current), (actual), __LINE__)) static std::string pprint(const std::string &in) { @@ -84,6 +86,23 @@ static int assertEquals(const long long &expected, const long long &actual, int return assertEquals(std::to_string(expected), std::to_string(actual), line); } +/** + * For a known bug: actual must be the current (wrong) value. When the bug is + * fixed and actual is the wanted value, the assertion fails so that it is + * changed to ASSERT_EQUALS. + */ +static void todoAssertEquals(const std::string &wanted, const std::string ¤t, const std::string &actual, int line) +{ + if (wanted == actual) { + numberOfFailedAssertions++; + std::cerr << "------ TODO assertion fixed (" << inputString(USE_INPUT) << ")---------" << std::endl; + std::cerr << "line test.cpp:" << line << std::endl; + std::cerr << "change TODO_ASSERT_EQUALS to ASSERT_EQUALS" << std::endl; + return; + } + assertEquals(current, actual, line); +} + static void assertThrowFailed(int line) { numberOfFailedAssertions++; @@ -267,6 +286,48 @@ static std::string toString(const simplecpp::OutputList &outputList) return ostr.str(); } +/** + * Preprocess the code. Each output token is written as str(line:col), or as + * str + '@' + macro name + (line:col) when it comes from a macro expansion. + */ +static std::string expandedTokens(const char code[], const simplecpp::DUI &dui = simplecpp::DUI(), std::list *macroUsage = nullptr) +{ + std::vector files; + simplecpp::FileDataCache cache; + simplecpp::TokenList tokens(files); + const simplecpp::TokenList rawtokens = makeTokenList(code, files); + simplecpp::preprocess(tokens, rawtokens, files, cache, dui, nullptr, macroUsage); + std::string ret; + for (const simplecpp::Token *tok = tokens.cfront(); tok; tok = tok->next) { + if (!ret.empty()) + ret += ' '; + ret += tok->str(); + if (!tok->macro.empty()) + ret += '@' + tok->macro; + ret += '(' + std::to_string(tok->location.line) + ':' + std::to_string(tok->location.col) + ')'; + } + return ret; +} + +/** Each macro usage as "name line:col use line:col", with " unknown" if the value is not known. + * The usages are sorted, the order of the macros is not specified. */ +static std::string toString(const std::list ¯oUsage) +{ + std::vector lines; + for (const simplecpp::MacroUsage &mu : macroUsage) { + std::string line = mu.macroName + ' ' + std::to_string(mu.macroLocation.line) + ':' + std::to_string(mu.macroLocation.col) + + " use " + std::to_string(mu.useLocation.line) + ':' + std::to_string(mu.useLocation.col); + if (!mu.macroValueKnown) + line += " unknown"; + lines.push_back(line + '\n'); + } + std::sort(lines.begin(), lines.end()); + std::string ret; + for (const std::string &line : lines) + ret += line; + return ret; +} + static void backslash() { // preprocessed differently @@ -1095,8 +1156,9 @@ static void define_invalid_3() const char code[] = "#define R()\n" "R\n"; simplecpp::OutputList outputList; - ASSERT_EQUALS("", preprocess(code, &outputList)); - ASSERT_EQUALS("file0,2,syntax_error,failed to expand 'R', Wrong number of parameters for macro 'R'.\n", toString(outputList)); + // TODO: a function-like macro name at the end of the file is not a macro call, gcc outputs R + TODO_ASSERT_EQUALS("\nR", "", preprocess(code, &outputList)); + TODO_ASSERT_EQUALS("", "file0,2,syntax_error,failed to expand 'R', Wrong number of parameters for macro 'R'.\n", toString(outputList)); } static void define_invalid_4() @@ -1104,8 +1166,9 @@ static void define_invalid_4() const char code[] = "#define X(...)\n" "X\n"; simplecpp::OutputList outputList; - ASSERT_EQUALS("", preprocess(code, &outputList)); - ASSERT_EQUALS("file0,2,syntax_error,failed to expand 'X', Wrong number of parameters for macro 'X'.\n", toString(outputList)); + // TODO: a function-like macro name at the end of the file is not a macro call, gcc outputs X + TODO_ASSERT_EQUALS("\nX", "", preprocess(code, &outputList)); + TODO_ASSERT_EQUALS("", "file0,2,syntax_error,failed to expand 'X', Wrong number of parameters for macro 'X'.\n", toString(outputList)); } static void define_define_1() @@ -2243,6 +2306,231 @@ static void hashhash_punctuator_invalid() ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'cat', Invalid ## usage when expanding 'cat': Combining '+' and 'x' yields an invalid token.\n", toString(outputList)); } +static void macro_c11_example3() // C11 6.10.3.5 EXAMPLE 3 +{ + const char code[] = + "#define x 3\n" + "#define f(a) f(x * (a))\n" + "#undef x\n" + "#define x 2\n" + "#define g f\n" + "#define z z[0]\n" + "#define h g(~\n" + "#define m(a) a(w)\n" + "#define w 0,1\n" + "#define t(a) a\n" + "#define p() int\n" + "#define q(x) x\n" + "#define r(x,y) x ## y\n" + "#define str(x) # x\n" + "f(y+1) + f(f(z)) % t(t(g)(0) + t)(1);\n" + "g(x+(3,4)-w) | h 5) & m\n" + "(f)^m(m);\n" + "p() i[q()] = { q(1), r(2,3), r(4,), r(,5), r(,) };\n" + "char c[2][6] = { str(hello), str() };\n"; + // TODO: r(,) leaves an empty token + TODO_ASSERT_EQUALS("\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "f ( 2 * ( y + 1 ) ) + f ( 2 * ( f ( 2 * ( z [ 0 ] ) ) ) ) % f ( 2 * ( 0 ) ) + t ( 1 ) ;\n" + "f ( 2 * ( 2 + ( 3 , 4 ) - 0 , 1 ) ) | f ( 2 * ( ~ 5 ) ) & f ( 2 * ( 0 , 1 ) )\n" + "^ m ( 0 , 1 ) ;\n" + "int i [ ] = { 1 , 23 , 4 , 5 , } ;\n" + "char c [ 2 ] [ 6 ] = { \"hello\" , \"\" } ;", + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "f ( 2 * ( y + 1 ) ) + f ( 2 * ( f ( 2 * ( z [ 0 ] ) ) ) ) % f ( 2 * ( 0 ) ) + t ( 1 ) ;\n" + "f ( 2 * ( 2 + ( 3 , 4 ) - 0 , 1 ) ) | f ( 2 * ( ~ 5 ) ) & f ( 2 * ( 0 , 1 ) )\n" + "^ m ( 0 , 1 ) ;\n" + "int i [ ] = { 1 , 23 , 4 , 5 , } ;\n" + "char c [ 2 ] [ 6 ] = { \"hello\" , \"\" } ;", + preprocess(code)); +} + +static void macro_c11_example4() // C11 6.10.3.5 EXAMPLE 4 +{ + const char code[] = + "#define str(s) # s\n" + "#define xstr(s) str(s)\n" + "#define debug(s, t) printf(\"x\" # s \"= %d, x\" # t \"= %s\", \\\n" + " x ## s, x ## t)\n" + "#define INCFILE(n) vers ## n\n" + "#define glue(a, b) a ## b\n" + "#define xglue(a, b) glue(a, b)\n" + "#define HIGHLOW \"hello\"\n" + "#define LOW LOW \", world\"\n" + "debug(1, 2);\n" + "fputs(str(strncmp(\"abc\\0d\", \"abc\", '\\4') // this goes away\n" + " == 0) str(: @\\n), s);\n" + "xstr(INCFILE(2).h)\n" + "glue(HIGH, LOW);\n" + "xglue(HIGH, LOW)\n"; + // the comments are removed before preprocessing, like cppcheck and the simplecpp program do + simplecpp::DUI dui; + dui.removeComments = true; + // TODO: the space before == is lost after the comment and newline in the # argument + TODO_ASSERT_EQUALS("\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "printf ( \"x\" \"1\" \"= %d, x\" \"2\" \"= %s\" , x1 , x2 ) ;\n" + "fputs ( \"strncmp(\\\"abc\\\\0d\\\", \\\"abc\\\", '\\\\4') == 0\"\n" + "\": @\\n\" , s ) ;\n" + "\"vers2.h\"\n" + "\"hello\" ;\n" + "\"hello\" \", world\"", + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "printf ( \"x\" \"1\" \"= %d, x\" \"2\" \"= %s\" , x1 , x2 ) ;\n" + "fputs ( \"strncmp(\\\"abc\\\\0d\\\", \\\"abc\\\", '\\\\4') ==0\"\n" + "\": @\\n\" , s ) ;\n" + "\"vers2.h\"\n" + "\"hello\" ;\n" + "\"hello\" \", world\"", + preprocess(code, dui)); +} + +static void macro_c11_example5() // C11 6.10.3.5 EXAMPLE 5 +{ + const char code[] = + "#define t(x,y,z) x ## y ## z\n" + "int j[] = { t(1,2,3), t(,4,5), t(6,,7), t(8,9,),\n" + " t(10,,), t(,11,), t(,,12), t(,,) };\n"; + // TODO: t(,,) leaves an empty token + TODO_ASSERT_EQUALS("\n" + "int j [ ] = { 123 , 45 , 67 , 89 ,\n" + "10 , 11 , 12 , } ;", + "\n" + "int j [ ] = { 123 , 45 , 67 , 89 ,\n" + "10 , 11 , 12 , } ;", + preprocess(code)); +} + +static void macro_c11_example7() // C11 6.10.3.5 EXAMPLE 7 +{ + const char code[] = + "#define debug(...) fprintf(stderr, __VA_ARGS__)\n" + "#define showlist(...) puts(#__VA_ARGS__)\n" + "#define report(test, ...) ((test)?puts(#test):\\\n" + " printf(__VA_ARGS__))\n" + "debug(\"Flag\");\n" + "debug(\"X = %d\\n\", x);\n" + "showlist(The first, second, and third items.);\n" + "report(x>y, \"x is %d but y is %d\", x, y);\n"; + ASSERT_EQUALS("\n" + "\n" + "\n" + "\n" + "fprintf ( stderr , \"Flag\" ) ;\n" + "fprintf ( stderr , \"X = %d\\n\" , x ) ;\n" + "puts ( \"The first, second, and third items.\" ) ;\n" + "( ( x > y ) ? puts ( \"x>y\" ) : printf ( \"x is %d but y is %d\" , x , y ) ) ;", + preprocess(code)); +} + +static void macro_c23_va_opt_examples() // C23 6.10.5.1 EXAMPLE +{ + const char code[] = + "#define F(...) f(0 __VA_OPT__(,) __VA_ARGS__)\n" + "#define G(X, ...) f(0, X __VA_OPT__(,) __VA_ARGS__)\n" + "#define SDEF(sname, ...) S sname __VA_OPT__(= { __VA_ARGS__ })\n" + "#define EMP\n" + "F(a,b,c)\n" + "F()\n" + "F(EMP)\n" + "G(a,b,c)\n" + "G(a,)\n" + "G(a)\n" + "SDEF(foo);\n" + "SDEF(bar, 1, 2);\n"; + ASSERT_EQUALS("\n" + "\n" + "\n" + "\n" + "f ( 0 , a , b , c )\n" + "f ( 0 )\n" + "f ( 0 )\n" + "f ( 0 , a , b , c )\n" + "f ( 0 , a )\n" + "f ( 0 , a )\n" + "S foo ;\n" + "S bar = { 1 , 2 } ;", + preprocess(code)); +} + +static void hashhash_empty_placemarker() +{ + const char code[] = + "#define r(x,y) x ## y\n" + "{ r(,) }\n"; + std::vector files; + simplecpp::FileDataCache cache; + simplecpp::TokenList tokens(files); + const simplecpp::TokenList rawtokens = makeTokenList(code, files); + simplecpp::preprocess(tokens, rawtokens, files, cache, simplecpp::DUI()); + std::string str; + for (const simplecpp::Token *tok = tokens.cfront(); tok; tok = tok->next) + str += "[" + tok->str() + "]"; + // TODO: pasting two empty arguments gives an empty token + TODO_ASSERT_EQUALS("[{][}]", "[{][][}]", str); +} + +static void hashhash_invalid_dotdot() +{ + const char code[] = + "#define cat(a,b) a##b\n" + "cat(.,.)\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'cat', Invalid ## usage when expanding 'cat': Combining '.' and '.' yields an invalid token.\n", toString(outputList)); +} + +static void hashhash_invalid_name_string() +{ + const char code[] = + "#define cat(a,b) a##b\n" + "cat(a,\"s\")\n"; + simplecpp::OutputList outputList; + // TODO: a"s" is not a valid token, gcc reports an error + TODO_ASSERT_EQUALS("", "\na\"s\"", preprocess(code, &outputList)); + TODO_ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'cat', Invalid ## usage when expanding 'cat': Combining 'a' and '\"s\"' yields an invalid token.\n", "", toString(outputList)); +} + static void hashhash_va_args_unexpected() { const char code[] = @@ -4284,36 +4572,19 @@ static void stringify1() ASSERT_EQUALS("\n#line 1 \"A.h\"\n1\n2\n#line 1 \"A.h\"\n1\n2", out.stringify()); } +// Token::macro, the locations of expanded tokens and the macro usage are used by cppcheck static void tokenMacro1() { const char code[] = "#define A 123\n" "A"; - std::vector files; - simplecpp::FileDataCache cache; - simplecpp::TokenList tokenList(files); - const simplecpp::TokenList rawtokens = makeTokenList(code,files); - simplecpp::preprocess(tokenList, rawtokens, files, cache, simplecpp::DUI()); - ASSERT_EQUALS("A", tokenList.cback()->macro); + ASSERT_EQUALS("123@A(2:1)", expandedTokens(code)); } static void tokenMacro2() { const char code[] = "#define ADD(X,Y) X+Y\n" "ADD(1,2)"; - std::vector files; - simplecpp::FileDataCache cache; - simplecpp::TokenList tokenList(files); - const simplecpp::TokenList rawtokens = makeTokenList(code,files); - simplecpp::preprocess(tokenList, rawtokens, files, cache, simplecpp::DUI()); - const simplecpp::Token *tok = tokenList.cfront(); - ASSERT_EQUALS("1", tok->str()); - ASSERT_EQUALS("", tok->macro); - tok = tok->next; - ASSERT_EQUALS("+", tok->str()); - ASSERT_EQUALS("ADD", tok->macro); - tok = tok->next; - ASSERT_EQUALS("2", tok->str()); - ASSERT_EQUALS("", tok->macro); + ASSERT_EQUALS("1(2:1) +@ADD(2:1) 2(2:1)", expandedTokens(code)); } static void tokenMacro3() @@ -4321,20 +4592,9 @@ static void tokenMacro3() const char code[] = "#define ADD(X,Y) X+Y\n" "#define FRED 1\n" "ADD(FRED,2)"; - std::vector files; - simplecpp::FileDataCache cache; - simplecpp::TokenList tokenList(files); - const simplecpp::TokenList rawtokens = makeTokenList(code,files); - simplecpp::preprocess(tokenList, rawtokens, files, cache, simplecpp::DUI()); - const simplecpp::Token *tok = tokenList.cfront(); - ASSERT_EQUALS("1", tok->str()); - ASSERT_EQUALS("FRED", tok->macro); - tok = tok->next; - ASSERT_EQUALS("+", tok->str()); - ASSERT_EQUALS("ADD", tok->macro); - tok = tok->next; - ASSERT_EQUALS("2", tok->str()); - ASSERT_EQUALS("", tok->macro); + std::list macroUsage; + ASSERT_EQUALS("1@FRED(3:1) +@ADD(3:1) 2(3:1)", expandedTokens(code, simplecpp::DUI(), ¯oUsage)); + ASSERT_EQUALS("ADD 1:9 use 3:1\nFRED 2:9 use 3:1\n", toString(macroUsage)); } static void tokenMacro4() @@ -4342,14 +4602,9 @@ static void tokenMacro4() const char code[] = "#define A B\n" "#define B 1\n" "A"; - std::vector files; - simplecpp::FileDataCache cache; - simplecpp::TokenList tokenList(files); - const simplecpp::TokenList rawtokens = makeTokenList(code,files); - simplecpp::preprocess(tokenList, rawtokens, files, cache, simplecpp::DUI()); - const simplecpp::Token * const tok = tokenList.cfront(); - ASSERT_EQUALS("1", tok->str()); - ASSERT_EQUALS("A", tok->macro); + std::list macroUsage; + ASSERT_EQUALS("1@A(3:1)", expandedTokens(code, simplecpp::DUI(), ¯oUsage)); + ASSERT_EQUALS("A 1:9 use 3:1\nB 2:9 use 3:1\n", toString(macroUsage)); } static void tokenMacro5() @@ -4357,14 +4612,7 @@ static void tokenMacro5() const char code[] = "#define SET_BPF(code) (code)\n" "#define SET_BPF_JUMP(code) SET_BPF(D | code)\n" "SET_BPF_JUMP(A | B | C);"; - std::vector files; - simplecpp::FileDataCache cache; - simplecpp::TokenList tokenList(files); - const simplecpp::TokenList rawtokens = makeTokenList(code,files); - simplecpp::preprocess(tokenList, rawtokens, files, cache, simplecpp::DUI()); - const simplecpp::Token * const tok = tokenList.cfront()->next; - ASSERT_EQUALS("D", tok->str()); - ASSERT_EQUALS("SET_BPF_JUMP", tok->macro); + ASSERT_EQUALS("(@SET_BPF(3:1) D@SET_BPF_JUMP(3:1) |@SET_BPF_JUMP(3:1) A(3:1) |(3:1) B(3:1) |(3:1) C(3:1) )@SET_BPF(3:1) ;(3:24)", expandedTokens(code)); } static void tokenMacro6() @@ -4375,21 +4623,100 @@ static void tokenMacro6() "FILE()\n" "LINE()\n" "COUNTER()\n"; - std::vector files; - simplecpp::FileDataCache cache; - simplecpp::TokenList tokenList(files); - const simplecpp::TokenList rawtokens = makeTokenList(code,files); - simplecpp::preprocess(tokenList, rawtokens, files, cache, simplecpp::DUI()); - const simplecpp::Token *tok; + ASSERT_EQUALS("\"\"@__FILE__(4:1) 5@__LINE__(5:1) 0@__COUNTER__(6:1)", expandedTokens(code)); +} - tok = tokenList.cfront(); - ASSERT_EQUALS("__FILE__", tok->macro); +static void tokenMacro7() +{ + const char code[] = "#define A 1+2\n" + "x A y\n"; + std::list macroUsage; + ASSERT_EQUALS("x(2:1) 1@A(2:3) +@A(2:3) 2@A(2:3) y(2:5)", expandedTokens(code, simplecpp::DUI(), ¯oUsage)); + ASSERT_EQUALS("A 1:9 use 2:3\n", toString(macroUsage)); +} - tok = tok->next; - ASSERT_EQUALS("__LINE__", tok->macro); +static void tokenMacro8() +{ + // the call g(2) is completed with tokens after the expansion of f. + // The tokens get the location of the ( and the macro g + const char code[] = "#define f g\n" + "#define g(x) x+1\n" + "f(2)\n"; + std::list macroUsage; + ASSERT_EQUALS("2(3:2) +@g(3:2) 1@g(3:2)", expandedTokens(code, simplecpp::DUI(), ¯oUsage)); + ASSERT_EQUALS("f 1:9 use 3:1\ng 2:9 use 3:2\n", toString(macroUsage)); +} - tok = tok->next; - ASSERT_EQUALS("__COUNTER__", tok->macro); +static void tokenMacro9() +{ + // the arguments of g( come after the expansion of f + const char code[] = "#define g(x) x+1\n" + "#define f g(\n" + "f 2)\n"; + std::list macroUsage; + ASSERT_EQUALS("2(3:3) +@g(3:3) 1@g(3:3)", expandedTokens(code, simplecpp::DUI(), ¯oUsage)); + ASSERT_EQUALS("f 2:9 use 3:1\ng 1:9 use 3:3\n", toString(macroUsage)); +} + +static void tokenMacro10() +{ + // the results of # and ## have no macro name + const char code[] = "#define str(x) #x\n" + "#define cat(a,b) a##b\n" + "str(a b) cat(x,y)\n"; + std::list macroUsage; + ASSERT_EQUALS("\"a b\"(3:1) xy(3:10)", expandedTokens(code, simplecpp::DUI(), ¯oUsage)); + ASSERT_EQUALS("cat 2:9 use 3:10\nstr 1:9 use 3:1\n", toString(macroUsage)); +} + +static void tokenMacro11() +{ + const char code[] = "#define F(...) f(__VA_ARGS__)\n" + "F(1,2)\n"; + std::list macroUsage; + ASSERT_EQUALS("f@F(2:1) (@F(2:1) 1(2:1) ,(2:1) 2(2:1) )@F(2:1)", expandedTokens(code, simplecpp::DUI(), ¯oUsage)); + ASSERT_EQUALS("F 1:9 use 2:1\n", toString(macroUsage)); +} + +static void tokenMacro12() +{ + const char code[] = "#define f f\n" + "f\n"; + std::list macroUsage; + ASSERT_EQUALS("f@f(2:1)", expandedTokens(code, simplecpp::DUI(), ¯oUsage)); + ASSERT_EQUALS("f 1:9 use 2:1\n", toString(macroUsage)); +} + +static void tokenMacro13() +{ + // all tokens of the expansion get the location of the macro name + const char code[] = "#define ADD(X,Y) X+Y\n" + "ADD(1,\n" + " 2)\n" + "z\n"; + std::list macroUsage; + ASSERT_EQUALS("1(2:1) +@ADD(2:1) 2(2:1) z(4:1)", expandedTokens(code, simplecpp::DUI(), ¯oUsage)); + ASSERT_EQUALS("ADD 1:9 use 2:1\n", toString(macroUsage)); +} + +static void tokenMacro14() +{ + // A is used in the argument and in the body, so it is listed twice + const char code[] = "#define A 1\n" + "#define B(x) x A\n" + "B(A)\n"; + std::list macroUsage; + ASSERT_EQUALS("1@A(3:1) 1@A(3:1)", expandedTokens(code, simplecpp::DUI(), ¯oUsage)); + ASSERT_EQUALS("A 1:9 use 3:1\nA 1:9 use 3:1\nB 2:9 use 3:1\n", toString(macroUsage)); +} + +static void tokenMacro15() +{ + simplecpp::DUI dui; + dui.defines.emplace_back("D=1+2"); + std::list macroUsage; + ASSERT_EQUALS("x(1:1) 1@D(1:3) +@D(1:3) 2@D(1:3)", expandedTokens("x D\n", dui, ¯oUsage)); + ASSERT_EQUALS("D 1:1 use 1:3 unknown\n", toString(macroUsage)); } static void undef() @@ -5191,6 +5518,11 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(define6); TEST_CASE(define7); TEST_CASE(define8); + TEST_CASE(macro_c11_example3); + TEST_CASE(macro_c11_example4); + TEST_CASE(macro_c11_example5); + TEST_CASE(macro_c11_example7); + TEST_CASE(macro_c23_va_opt_examples); TEST_CASE(define9); TEST_CASE(define10); TEST_CASE(define11); @@ -5314,6 +5646,9 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(hashhash_empty_va_args); TEST_CASE(hashhash_punctuator); TEST_CASE(hashhash_punctuator_invalid); + TEST_CASE(hashhash_empty_placemarker); + TEST_CASE(hashhash_invalid_dotdot); + TEST_CASE(hashhash_invalid_name_string); TEST_CASE(hashhash_va_args_unexpected); // C standard, 5.1.1.2, paragraph 4: @@ -5445,6 +5780,15 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(tokenMacro4); TEST_CASE(tokenMacro5); TEST_CASE(tokenMacro6); + TEST_CASE(tokenMacro7); + TEST_CASE(tokenMacro8); + TEST_CASE(tokenMacro9); + TEST_CASE(tokenMacro10); + TEST_CASE(tokenMacro11); + TEST_CASE(tokenMacro12); + TEST_CASE(tokenMacro13); + TEST_CASE(tokenMacro14); + TEST_CASE(tokenMacro15); TEST_CASE(undef); From 567b72bc8e6b0cd0e7d3330b2e791c0e84b48789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 9 Nov 2026 09:28:06 +0100 Subject: [PATCH 148/152] Fix #746 (Split expandHashHash and expandHash into check and build helpers) (#747) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Marjamäki Co-authored-by: Claude Sonnet 5.5 --- simplecpp.cpp | 139 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 94 insertions(+), 45 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index a6adddad..32da1728 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -733,6 +733,41 @@ static std::string stringifyToken(const std::string &str) return ret; } +/** + * The result of the # operator: the tokens as a string literal (C 6.10.3.2). + * The tokens are separated by a space where there was whitespace between them. + * @param tokens the argument of #, not macro expanded + * @param loc location for the warning + * @param macroName the macro that is expanded, for the warning + * @param outputList if not null, a warning is added when a final backslash is dropped + * @return the string literal, including the quotes + */ +static std::string stringify(const simplecpp::TokenList &tokens, const simplecpp::Location &loc, const std::string ¯oName, simplecpp::OutputList *outputList) +{ + std::string str; + for (const simplecpp::Token *tok = tokens.cfront(), *next; tok; tok = next) { + next = tok->next; + str += stringifyToken(tok->str()); + if (next && tok->whitespaceahead) + str += ' '; + } + // An odd number of trailing backslashes would escape the closing quote and + // yield an unterminated string literal (undefined behavior, C 6.10.3.2p2). + // Drop the final backslash, as gcc and clang do. + std::size_t trailing = 0; + while (trailing < str.size() && str[str.size() - 1U - trailing] == '\\') + ++trailing; + if (trailing % 2U == 1U) { + str.pop_back(); + if (outputList) { + outputList->emplace_back(simplecpp::Output::PORTABILITY_STRINGIFY, loc, + "Invalid string literal when expanding \'" + macroName + "\', ignoring final \'\\\'. " + "Undefined behavior according to C standard chapter 6.10.3.2, paragraph 2."); + } + } + return '\"' + str + '\"'; +} + void simplecpp::TokenList::lineDirective(unsigned int fileIndex_, unsigned int line, Location &location) { if (fileIndex_ != location.fileIndex || line >= location.line) { @@ -2634,28 +2669,7 @@ namespace simplecpp { TokenList tokenListHash(files); const MacroMap macros2; // temporarily bypass macro expansion tok = expandToken(tokenListHash, loc, tok->next, macros2, expandedmacros, parametertokens, outputList); - std::string str; - for (const Token *hashtok = tokenListHash.cfront(), *next; hashtok; hashtok = next) { - next = hashtok->next; - str += stringifyToken(hashtok->str()); - if (next && hashtok->whitespaceahead) - str += ' '; - } - // An odd number of trailing backslashes would escape the closing quote and - // yield an unterminated string literal (undefined behavior, C 6.10.3.2p2). - // Drop the final backslash, as gcc and clang do. - std::size_t trailing = 0; - while (trailing < str.size() && str[str.size() - 1U - trailing] == '\\') - ++trailing; - if (trailing % 2U == 1U) { - str.pop_back(); - if (outputList) { - outputList->emplace_back(Output::PORTABILITY_STRINGIFY, loc, - "Invalid string literal when expanding \'" + name() + "\', ignoring final \'\\\'. " - "Undefined behavior according to C standard chapter 6.10.3.2, paragraph 2."); - } - } - output.push_back(newMacroToken('\"' + str + '\"', loc, isReplaced(expandedmacros))); + output.push_back(newMacroToken(stringify(tokenListHash, loc, name(), outputList), loc, isReplaced(expandedmacros))); return tok; } @@ -2679,20 +2693,9 @@ namespace simplecpp { throw invalidHashHash::unexpectedNewline(tok->location, name()); const bool canBeConcatenatedStringOrChar = isStringLiteral(A->str()) || isCharLiteral(A->str()); - // A is a punctuator; the result is checked when B has been expanded - const bool punctuatorA = (!A->name && !A->number && !A->str().empty() && !canBeConcatenatedStringOrChar); const Token * const B = tok->next->next; - if (!punctuatorA) { - if (!B->name && !B->number && B->op && !B->isOneOf("#=")) - throw invalidHashHash::unexpectedToken(tok->location, name(), B); - if (B->op == '=') - throw invalidHashHash::cannotCombine(tok->location, name(), A, B); - } - - // Superficial check; more in-depth would in theory be possible _after_ expandArg - if (canBeConcatenatedStringOrChar && (B->number || !B->name)) - throw invalidHashHash::cannotCombine(tok->location, name(), A, B); + checkHashHashOperands(tok->location, name(), A, B); TokenList tokensB(files); const Token *nextTok = B->next; @@ -2721,24 +2724,16 @@ namespace simplecpp { strAB = ","; } else { - if (punctuatorA && !isValidPunctuatorPaste(A->str() + tokensB.cfront()->str())) - throw invalidHashHash::cannotCombine(tok->location, name(), A, tokensB.cfront()); + checkPunctuatorPaste(tok->location, name(), A, tokensB.cfront()); strAB = A->str() + tokensB.cfront()->str(); tokensB.deleteToken(tokensB.front()); } } else { - if (punctuatorA && !isValidPunctuatorPaste(A->str() + B->str())) - throw invalidHashHash::cannotCombine(tok->location, name(), A, B); + checkPunctuatorPaste(tok->location, name(), A, B); strAB = A->str() + B->str(); } - // producing universal character is undefined behavior - if (A->previous && A->previous->str() == "\\") { - if (strAB[0] == 'u' && strAB.size() == 5) - throw invalidHashHash::universalCharacterUB(tok->location, name(), A, strAB); - if (strAB[0] == 'U' && strAB.size() == 9) - throw invalidHashHash::universalCharacterUB(tok->location, name(), A, strAB); - } + checkUniversalCharacterPaste(tok->location, name(), A, strAB); if (varargs && tokensB.empty() && tok->previous->str() == ",") { output.deleteToken(A); @@ -2780,6 +2775,60 @@ namespace simplecpp { return nextTok; } + /** A is a punctuator, not a name, number or literal. Pasting it is checked after B is expanded. */ + static bool isPunctuatorOperand(const Token *A) { + return !A->name && !A->number && !A->str().empty() && !isStringLiteral(A->str()) && !isCharLiteral(A->str()); + } + + /** + * Check the operands of A ## B before B is expanded. + * @param loc the location of the ## + * @param macroName the macro that is expanded + * @param A the token before ##, already expanded + * @param B the token after ## in the macro definition + * @throws invalidHashHash if A and B can not be pasted + */ + static void checkHashHashOperands(const Location &loc, const std::string ¯oName, const Token *A, const Token *B) { + if (!isPunctuatorOperand(A)) { + if (!B->name && !B->number && B->op && !B->isOneOf("#=")) + throw invalidHashHash::unexpectedToken(loc, macroName, B); + if (B->op == '=') + throw invalidHashHash::cannotCombine(loc, macroName, A, B); + } + + // Superficial check; more in-depth would in theory be possible _after_ expandArg + if ((isStringLiteral(A->str()) || isCharLiteral(A->str())) && (B->number || !B->name)) + throw invalidHashHash::cannotCombine(loc, macroName, A, B); + } + + /** + * When A is a punctuator, check that pasting A and B gives a valid token. + * @param loc the location of the ## + * @param macroName the macro that is expanded + * @param A the token before ## + * @param B the first token after ##, after expansion + * @throws invalidHashHash if the result is not a valid token + */ + static void checkPunctuatorPaste(const Location &loc, const std::string ¯oName, const Token *A, const Token *B) { + if (isPunctuatorOperand(A) && !isValidPunctuatorPaste(A->str() + B->str())) + throw invalidHashHash::cannotCombine(loc, macroName, A, B); + } + + /** + * Producing a universal character name with ## is undefined behavior. + * @param loc the location of the ## + * @param macroName the macro that is expanded + * @param A the token before ## + * @param strAB the pasted text + * @throws invalidHashHash if A follows a backslash and strAB is a universal character name + */ + static void checkUniversalCharacterPaste(const Location &loc, const std::string ¯oName, const Token *A, const std::string &strAB) { + if (!A->previous || A->previous->str() != "\\") + return; + if ((strAB[0] == 'u' && strAB.size() == 5) || (strAB[0] == 'U' && strAB.size() == 9)) + throw invalidHashHash::universalCharacterUB(loc, macroName, A, strAB); + } + static bool isReplaced(const std::set &expandedmacros) { // return true if size > 1 auto it = expandedmacros.cbegin(); From 22522dde85eeba9a1adf5bfc67d9bf46c677f141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 9 Nov 2026 10:36:45 +0100 Subject: [PATCH 149/152] Fix #748 (Move the argument count check into Macro::getMacroParameters()) (#749) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Marjamäki Co-authored-by: Claude Sonnet 5.5 --- simplecpp.cpp | 51 ++++++++++++++++++++++++++++++++------------------- test.cpp | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 32da1728..3f1d965d 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2204,10 +2204,35 @@ namespace simplecpp { return ~0U; } + /** + * Find the arguments of a call of this macro. + * @param nameTokInst the macro name in the call + * @param calledInDefine the call is in a #define, so the arguments must be on the same line + * @return the tokens that delimit the arguments: the '(', each ',' between two arguments and the ')'. + * Empty when the macro is not function-like. + * @throws wrongNumberOfParameters if a function-like macro has the wrong number of arguments. + * This includes the case where the name is not followed by '(' (or is the last token), + * so the caller must check for that first if it is not an error. + */ std::vector getMacroParameters(const Token *nameTokInst, bool calledInDefine) const { - if (!nameTokInst->next || nameTokInst->next->op != '(' || !functionLike()) + if (!functionLike()) return {}; + std::vector parametertokens; + if (nameTokInst->next && nameTokInst->next->op == '(') + parametertokens = findArgumentDelimiters(nameTokInst, calledInDefine); + + if (variadic) { + if (parametertokens.size() < args.size()) + throw wrongNumberOfParameters(nameTokInst->location, name()); + } else { + if (parametertokens.size() != args.size() + (args.empty() ? 2U : 1U)) + throw wrongNumberOfParameters(nameTokInst->location, name()); + } + return parametertokens; + } + /** The '(', each ',' between two arguments and the ')' of the call. nameTokInst is followed by '('. */ + std::vector findArgumentDelimiters(const Token *nameTokInst, bool calledInDefine) const { std::vector parametertokens; parametertokens.emplace_back(nameTokInst->next); unsigned int par = 0U; @@ -2296,26 +2321,14 @@ namespace simplecpp { const bool calledInDefine = (loc.fileIndex != nameTokInst->location.fileIndex || loc.line < nameTokInst->location.line); - std::vector parametertokens1(getMacroParameters(nameTokInst, calledInDefine)); - - if (functionLike()) { - // No arguments => not macro expansion - if (nameTokInst->next && nameTokInst->next->op != '(') { - output.push_back(new Token(nameTokInst->str(), loc)); - return nameTokInst->next; - } - - // Parse macro-call - if (variadic) { - if (parametertokens1.size() < args.size()) { - throw wrongNumberOfParameters(nameTokInst->location, name()); - } - } else { - if (parametertokens1.size() != args.size() + (args.empty() ? 2U : 1U)) - throw wrongNumberOfParameters(nameTokInst->location, name()); - } + // No arguments => not macro expansion + if (functionLike() && nameTokInst->next && nameTokInst->next->op != '(') { + output.push_back(new Token(nameTokInst->str(), loc)); + return nameTokInst->next; } + std::vector parametertokens1(getMacroParameters(nameTokInst, calledInDefine)); + // If macro call uses __COUNTER__ then expand that first TokenList tokensparams(files); std::vector parametertokens2; diff --git a/test.cpp b/test.cpp index 108a6a58..6bfb3baf 100644 --- a/test.cpp +++ b/test.cpp @@ -1161,6 +1161,42 @@ static void define_invalid_3() TODO_ASSERT_EQUALS("", "file0,2,syntax_error,failed to expand 'R', Wrong number of parameters for macro 'R'.\n", toString(outputList)); } +static void define_wrong_number_of_arguments() +{ + { + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess("#define f(a,b) a+b\nf(1)\n", &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to expand 'f', Wrong number of parameters for macro 'f'.\n", toString(outputList)); + } + { + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess("#define f(a,b) a+b\nf(1,2,3)\n", &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to expand 'f', Wrong number of parameters for macro 'f'.\n", toString(outputList)); + } + { + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess("#define f(a,b,...) a+b\nf(1)\n", &outputList)); + ASSERT_EQUALS("file0,2,syntax_error,failed to expand 'f', Wrong number of parameters for macro 'f'.\n", toString(outputList)); + } + { + simplecpp::OutputList outputList; + ASSERT_EQUALS("\n1", preprocess("#define f(a,...) a __VA_ARGS__\nf(1)\n", &outputList)); + ASSERT_EQUALS("", toString(outputList)); + } + { + // a function-like macro name that is not followed by ( is not expanded + simplecpp::OutputList outputList; + ASSERT_EQUALS("\nf ;", preprocess("#define f(a) a\nf;\n", &outputList)); + ASSERT_EQUALS("", toString(outputList)); + } + { + // TODO: f takes no arguments, gcc reports an error + simplecpp::OutputList outputList; + TODO_ASSERT_EQUALS("", "\n1", preprocess("#define f() 1\nf(1)\n", &outputList)); + TODO_ASSERT_EQUALS("file0,2,syntax_error,failed to expand 'f', Wrong number of parameters for macro 'f'.\n", "", toString(outputList)); + } +} + static void define_invalid_4() { const char code[] = "#define X(...)\n" @@ -5546,6 +5582,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(define_invalid_2); TEST_CASE(define_invalid_3); TEST_CASE(define_invalid_4); + TEST_CASE(define_wrong_number_of_arguments); TEST_CASE(define_define_1); TEST_CASE(define_define_2); TEST_CASE(define_define_3); From faabcfe856787ff00ed6033adbca8898d311326d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 9 Nov 2026 10:58:17 +0100 Subject: [PATCH 150/152] Fix #750 (Extract expandBuiltin, expandDefined and copyMacroCall) (#751) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Marjamäki Co-authored-by: Claude Sonnet 5.5 --- simplecpp.cpp | 194 +++++++++++++++++++++++++++++++++++--------------- test.cpp | 11 +++ 2 files changed, 146 insertions(+), 59 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 3f1d965d..12fcaae4 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1918,6 +1918,34 @@ namespace simplecpp { return valueDefinedInCode_; } + /** + * Copy a macro call "NAME( ... )" to a tokenlist with no line breaks: + * all tokens get the location of the name. + * @param dest destination tokenlist + * @param nameTok the macro name, followed by '(' + * @return the token after the ')', or nullptr if the call is not terminated + * @throws invalidDirectiveAsMacroParameter if there is a preprocessor directive in the arguments + */ + static const Token *copyMacroCall(TokenList &dest, const Token *nameTok) { + const Location &loc = nameTok->location; + dest.push_back(new Token(nameTok->str(), loc, nameTok->whitespaceahead)); + const Token *tok = nameTok->next; + dest.push_back(new Token(tok->str(), loc, tok->whitespaceahead)); + tok = tok->next; + int par = 1; + while (tok && par > 0) { + if (tok->op == '(') + ++par; + else if (tok->op == ')') + --par; + else if (tok->op == '#' && !sameline(tok->previous, tok)) + throw invalidDirectiveAsMacroParameter(tok->location); + dest.push_back(new Token(tok->str(), loc, tok->whitespaceahead)); + tok = tok->next; + } + return tok; + } + /** * Expand macro. This will recursively expand inner macros. * @param output destination tokenlist @@ -1944,24 +1972,9 @@ namespace simplecpp { TokenList output2(inputFiles); if (functionLike() && rawtok->next && rawtok->next->op == '(') { - // Copy macro call to a new tokenlist with no linebreaks const Token * const rawtok1 = rawtok; TokenList rawtokens2(inputFiles); - rawtokens2.push_back(new Token(rawtok->str(), rawtok1->location, rawtok->whitespaceahead)); - rawtok = rawtok->next; - rawtokens2.push_back(new Token(rawtok->str(), rawtok1->location, rawtok->whitespaceahead)); - rawtok = rawtok->next; - int par = 1; - while (rawtok && par > 0) { - if (rawtok->op == '(') - ++par; - else if (rawtok->op == ')') - --par; - else if (rawtok->op == '#' && !sameline(rawtok->previous, rawtok)) - throw invalidDirectiveAsMacroParameter(rawtok->location); - rawtokens2.push_back(new Token(rawtok->str(), rawtok1->location, rawtok->whitespaceahead)); - rawtok = rawtok->next; - } + rawtok = copyMacroCall(rawtokens2, rawtok); if (expand(output2, rawtok1->location, rawtokens2.cfront(), macros, expandedmacros, outputList)) rawtok = rawtok1->next; } else { @@ -2293,6 +2306,29 @@ namespace simplecpp { return sameline(lpar,tok) ? tok : nullptr; } + /** + * Expand the built-in macros __FILE__, __LINE__ and __COUNTER__. + * The usage must already be added to usageList, __COUNTER__ is the number of earlier usages. + * @param output destination tokenlist + * @param loc location of the usage + * @param nameTokInst the macro name + * @return true if the macro is built-in and its value is added to output + */ + bool expandBuiltin(TokenList &output, const Location &loc, const Token *nameTokInst) const { + std::string value; + if (nameTokInst->str() == "__FILE__") + value = '\"' + output.file(loc) + '\"'; + else if (nameTokInst->str() == "__LINE__") + value = toString(loc.line); + else if (nameTokInst->str() == "__COUNTER__") + value = toString(usageList.size() - 1U); + else + return false; + output.push_back(new Token(value, loc)); + output.back()->macro = nameTokInst->str(); + return true; + } + const Token * expand(TokenList & output, const Location &loc, const Token * const nameTokInst, const MacroMap ¯os, std::set expandedmacros, OutputList *outputList) const { expandedmacros.insert(nameTokInst->str()); @@ -2302,21 +2338,8 @@ namespace simplecpp { usageList.emplace_back(loc); - if (nameTokInst->str() == "__FILE__") { - output.push_back(new Token('\"'+output.file(loc)+'\"', loc)); - output.back()->macro = "__FILE__"; - return nameTokInst->next; - } - if (nameTokInst->str() == "__LINE__") { - output.push_back(new Token(toString(loc.line), loc)); - output.back()->macro = "__LINE__"; + if (expandBuiltin(output, loc, nameTokInst)) return nameTokInst->next; - } - if (nameTokInst->str() == "__COUNTER__") { - output.push_back(new Token(toString(usageList.size()-1U), loc)); - output.back()->macro = "__COUNTER__"; - return nameTokInst->next; - } const bool calledInDefine = (loc.fileIndex != nameTokInst->location.fileIndex || loc.line < nameTokInst->location.line); @@ -2567,35 +2590,9 @@ namespace simplecpp { return recursiveExpandToken(output, temp, loc, tok2, macros, expandedmacros, parametertokens, outputList); } - if (tok->str() == DEFINED) { - const Token * const tok2 = tok->next; - const Token * const tok3 = tok2 ? tok2->next : nullptr; - const Token * const tok4 = tok3 ? tok3->next : nullptr; - const Token *defToken = nullptr; - const Token *lastToken = nullptr; - if (sameline(tok, tok4) && tok2->op == '(' && tok3->name && tok4->op == ')') { - defToken = tok3; - lastToken = tok4; - } else if (sameline(tok,tok2) && tok2->name) { - defToken = lastToken = tok2; - } - if (defToken) { - 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)) - macroName = temp.cback()->str(); - 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)); - return lastToken->next; - } - } + const Token *next = nullptr; + if (tok->str() == DEFINED && expandDefined(output, loc, tok, macros, parametertokens, next)) + return next; output.push_back(newMacroToken(tok->str(), loc, true, tok)); if (it != macros.end()) @@ -2603,6 +2600,85 @@ namespace simplecpp { return tok->next; } + /** + * Find the operand of "defined" in a replacement list: "defined X", "defined(X)" or "defined X ## Y". + * @param tok the "defined" token + * @param first output: X + * @param second output: Y for "X ## Y", otherwise nullptr + * @param last output: the last token of the operand + * @return false if there is no operand + */ + static bool findDefinedOperand(const Token *tok, const Token *&first, const Token *&second, const Token *&last) { + const Token * const tok2 = tok->next; + const Token * const tok3 = tok2 ? tok2->next : nullptr; + const Token * const tok4 = tok3 ? tok3->next : nullptr; + first = last = second = nullptr; + if (sameline(tok, tok4) && tok2->op == '(' && tok3->name && tok4->op == ')') { + first = tok3; + last = tok4; + } else if (sameline(tok,tok2) && tok2->name) { + first = last = tok2; + } + if (!first) + return false; + if (first->next && first->next->op == '#' && first->next->next && first->next->next->op == '#' && first->next->next->next && first->next->next->next->name && sameline(first,first->next->next->next)) { + second = first->next->next->next; + last = second; + } + return true; + } + + /** + * The name of the macro that "defined" checks. + * @param first X in "defined X" + * @param second Y in "defined X ## Y", or nullptr + * @param argText for "X ## Y": gives the text of a parameter, and returns false if the token is not a parameter + */ + template + static std::string definedMacroName(const Token *first, const Token *second, ArgText argText) { + std::string macroName = first->str(); + if (second) { + std::string text; + if (argText(first, text)) + macroName = text; + if (argText(second, text)) + macroName += text; + else + macroName += second->str(); + } + return macroName; + } + + /** + * Expand "defined X" or "defined(X)" in the replacement list to 1 or 0. + * The operand can be a parameter, or two names pasted with ##. + * @param output destination tokenlist + * @param loc location for the result + * @param tok the "defined" token + * @param macros all macros + * @param parametertokens arguments of this macro call + * @param next output: the token after the operand + * @return false if there is no operand, then nothing is added to output + */ + bool expandDefined(TokenList &output, const Location &loc, const Token *tok, const MacroMap ¯os, const std::vector ¶metertokens, const Token *&next) const { + const Token *first; + const Token *second; + const Token *last; + if (!findDefinedOperand(tok, first, second, last)) + return false; + TokenList temp(files); + const std::string macroName = definedMacroName(first, second, [&](const Token *argtok, std::string &text) { + if (!expandArg(temp, argtok, parametertokens)) + return false; + text = temp.cback() ? temp.cback()->str() : ""; + return true; + }); + const bool def = (macros.find(macroName) != macros.end()); + output.push_back(newMacroToken(def ? "1" : "0", loc, true)); + next = last->next; + return true; + } + bool expandArg(TokenList &output, const Token *tok, const std::vector ¶metertokens) const { if (!tok->name) return false; diff --git a/test.cpp b/test.cpp index 6bfb3baf..5bb980e1 100644 --- a/test.cpp +++ b/test.cpp @@ -2999,6 +2999,16 @@ static void ifDefinedHashHash2() ASSERT_EQUALS("\n0", preprocess(code, &outputList)); } +static void definedAtEndOfFile() +{ + // "defined" from a macro expansion is undefined behavior; gcc outputs "defined" here. + // The operand is the last token of the file. + const char code[] = "#define X\n" + "#define D defined X\n" + "D"; + ASSERT_EQUALS("\n\n1", preprocess(code)); +} + static void ifLogical() { const char code[] = "#if defined(A) || defined(B)\n" @@ -5727,6 +5737,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(ifDefinedInvalid2); TEST_CASE(ifDefinedHashHash); TEST_CASE(ifDefinedHashHash2); + TEST_CASE(definedAtEndOfFile); TEST_CASE(ifLogical); TEST_CASE(ifLogicalPrecedence); TEST_CASE(ifSizeof); From 99e5a99cd666782d7b3b6d96ac5938b37018099c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Mon, 9 Nov 2026 11:07:32 +0100 Subject: [PATCH 151/152] Add TokenList::setLocation() for setting the location of all tokens (#753) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Marjamäki Co-authored-by: Claude Sonnet 5.5 --- simplecpp.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 12fcaae4..27108a4a 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1850,6 +1850,13 @@ namespace simplecpp { class Macro; using MacroMap = std::unordered_map; + /** Set the location of all tokens */ + static void setLocation(TokenList &tokens, const Location &loc) + { + for (Token *tok = tokens.front(); tok; tok = tok->next) + tok->location = loc; + } + class Macro { public: explicit Macro(std::vector &f) : nameTokDef(nullptr), valueToken(nullptr), endToken(nullptr), files(f), tokenListDefine(f), variadic(false), variadicOpt(false), valueDefinedInCode_(false) {} @@ -2301,8 +2308,7 @@ namespace simplecpp { tok = tok->next; } } - for (Token *tok2 = tokens.front(); tok2; tok2 = tok2->next) - tok2->location = lpar->location; + setLocation(tokens, lpar->location); return sameline(lpar,tok) ? tok : nullptr; } @@ -2793,8 +2799,7 @@ 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)) { - for (Token *b = tokensB.front(); b; b = b->next) - b->location = loc; + setLocation(tokensB, loc); } else { tokensB.push_back(new Token(*B)); tokensB.back()->location = loc; @@ -2829,8 +2834,7 @@ namespace simplecpp { } else if (strAB != "," && macros.find(strAB) == macros.end()) { A->setstr(strAB); - for (Token *b = tokensB.front(); b; b = b->next) - b->location = loc; + setLocation(tokensB, loc); output.takeTokens(tokensB); } else if (sameline(B, nextTok) && sameline(B, nextTok->next) && nextTok->op == '#' && nextTok->next->op == '#') { TokenList output2(files); @@ -2855,8 +2859,7 @@ namespace simplecpp { expandToken(output, loc, tokens.cfront(), macros, expandedmacros, parametertokens, outputList); else output.takeTokens(tokens); - for (Token *b = tokensB.front(); b; b = b->next) - b->location = loc; + setLocation(tokensB, loc); output.takeTokens(tokensB); } } From fb8808bb13c20f1798df3d000d7bc343f3ecb01b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 10 Nov 2026 09:08:50 +0100 Subject: [PATCH 152/152] hash: keep the space after an operator of two characters (#755) Co-authored-by: Claude Sonnet 5.5 --- simplecpp.cpp | 13 ++++++++++++ test.cpp | 57 ++++++++++++++++++++++----------------------------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 27108a4a..2223d431 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1364,7 +1364,9 @@ void simplecpp::TokenList::combineOperators() if (tok->next && tok->next->op == '.' && tok->next->location.col == (tok->location.col + 1) && tok->next->next && tok->next->next->op == '.' && tok->next->next->location.col == (tok->location.col + 2)) { tok->setstr("..."); + tok->whitespaceahead = tok->next->whitespaceahead; deleteToken(tok->next); + tok->whitespaceahead = tok->next->whitespaceahead; deleteToken(tok->next); continue; } @@ -1374,11 +1376,13 @@ void simplecpp::TokenList::combineOperators() deleteToken(tok->previous); if (sameline(tok, tok->next) && (isFloatSuffix(tok->next) || (tok->next && tok->next->startsWithOneOf("AaBbCcDdEeFfPp") && !isAlternativeAndBitandBitor(tok->next)))) { tok->setstr(tok->str() + tok->next->str()); + tok->whitespaceahead = tok->next->whitespaceahead; deleteToken(tok->next); } } if (tok->next && tok->next->number) { tok->setstr(tok->str() + tok->next->str()); + tok->whitespaceahead = tok->next->whitespaceahead; deleteToken(tok->next); } } @@ -1389,7 +1393,9 @@ void simplecpp::TokenList::combineOperators() (isHex(tok->str()) && (lastChar == 'P' || lastChar == 'p'))) && tok->next && tok->next->isOneOf("+-") && tok->next->next && tok->next->next->number) { tok->setstr(tok->str() + tok->next->op + tok->next->next->str()); + tok->whitespaceahead = tok->next->whitespaceahead; deleteToken(tok->next); + tok->whitespaceahead = tok->next->whitespaceahead; deleteToken(tok->next); } @@ -1406,21 +1412,27 @@ void simplecpp::TokenList::combineOperators() if (tok->op == '&' && !executableScope.top() && isAnonymousReferenceParameter(tok)) continue; tok->setstr(tok->str() + "="); + tok->whitespaceahead = tok->next->whitespaceahead; deleteToken(tok->next); } else if ((tok->op == '|' || tok->op == '&') && tok->op == tok->next->op) { tok->setstr(tok->str() + tok->next->str()); + tok->whitespaceahead = tok->next->whitespaceahead; deleteToken(tok->next); } else if (tok->op == ':' && tok->next->op == ':') { tok->setstr(tok->str() + tok->next->str()); + tok->whitespaceahead = tok->next->whitespaceahead; deleteToken(tok->next); } else if (tok->op == '-' && tok->next->op == '>') { tok->setstr(tok->str() + tok->next->str()); + tok->whitespaceahead = tok->next->whitespaceahead; deleteToken(tok->next); } else if ((tok->op == '<' || tok->op == '>') && tok->op == tok->next->op) { tok->setstr(tok->str() + tok->next->str()); + tok->whitespaceahead = tok->next->whitespaceahead; deleteToken(tok->next); if (tok->next && tok->next->op == '=' && tok->next->next && tok->next->next->op != '=') { tok->setstr(tok->str() + tok->next->str()); + tok->whitespaceahead = tok->next->whitespaceahead; deleteToken(tok->next); } } else if ((tok->op == '+' || tok->op == '-') && tok->op == tok->next->op) { @@ -1431,6 +1443,7 @@ void simplecpp::TokenList::combineOperators() if (tok->next->next && tok->next->next->number) continue; tok->setstr(tok->str() + tok->next->str()); + tok->whitespaceahead = tok->next->whitespaceahead; deleteToken(tok->next); } } diff --git a/test.cpp b/test.cpp index 5bb980e1..3f996167 100644 --- a/test.cpp +++ b/test.cpp @@ -1911,6 +1911,14 @@ static void hashMacroTrailingWhitespace() "\"a b\"", preprocess(code, dui)); } +static void hashCombinedOperator() +{ + // the whitespace after an operator of two characters is kept + const char code[] = "#define str(x) #x\n" + "str(a == 0) str(a -> b) str(x... y)\n"; + ASSERT_EQUALS("\n\"a == 0\" \"a -> b\" \"x... y\"", preprocess(code)); +} + static void hashEscapeTrailingBackslash() { // an odd number of trailing backslashes would escape the closing quote, @@ -2427,38 +2435,22 @@ static void macro_c11_example4() // C11 6.10.3.5 EXAMPLE 4 // the comments are removed before preprocessing, like cppcheck and the simplecpp program do simplecpp::DUI dui; dui.removeComments = true; - // TODO: the space before == is lost after the comment and newline in the # argument - TODO_ASSERT_EQUALS("\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "printf ( \"x\" \"1\" \"= %d, x\" \"2\" \"= %s\" , x1 , x2 ) ;\n" - "fputs ( \"strncmp(\\\"abc\\\\0d\\\", \\\"abc\\\", '\\\\4') == 0\"\n" - "\": @\\n\" , s ) ;\n" - "\"vers2.h\"\n" - "\"hello\" ;\n" - "\"hello\" \", world\"", - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "\n" - "printf ( \"x\" \"1\" \"= %d, x\" \"2\" \"= %s\" , x1 , x2 ) ;\n" - "fputs ( \"strncmp(\\\"abc\\\\0d\\\", \\\"abc\\\", '\\\\4') ==0\"\n" - "\": @\\n\" , s ) ;\n" - "\"vers2.h\"\n" - "\"hello\" ;\n" - "\"hello\" \", world\"", - preprocess(code, dui)); + ASSERT_EQUALS("\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "printf ( \"x\" \"1\" \"= %d, x\" \"2\" \"= %s\" , x1 , x2 ) ;\n" + "fputs ( \"strncmp(\\\"abc\\\\0d\\\", \\\"abc\\\", '\\\\4') == 0\"\n" + "\": @\\n\" , s ) ;\n" + "\"vers2.h\"\n" + "\"hello\" ;\n" + "\"hello\" \", world\"", + preprocess(code, dui)); } static void macro_c11_example5() // C11 6.10.3.5 EXAMPLE 5 @@ -5660,6 +5652,7 @@ static void runTests(int argc, char **argv, Input input) TEST_CASE(hashEscape); TEST_CASE(hashComment); TEST_CASE(hashMacroTrailingWhitespace); + TEST_CASE(hashCombinedOperator); TEST_CASE(hashEscapeTrailingBackslash); TEST_CASE(hashhash1); TEST_CASE(hashhash2);