From 78c34de6790607c17ce1841470f22f49902df01c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 10 Dec 2024 14:23:26 +0000 Subject: [PATCH 001/119] Fix #384 (fails to expand inner macro with a parameter that contains a comma) (#392) --- simplecpp.cpp | 10 +++++++--- test.cpp | 13 +++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 67e59abb..798fdeba 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1781,7 +1781,7 @@ 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); + tok = expandHashHash(tokens, rawloc, tok, macros, expandedmacros, parametertokens, false); } else if (tok->op == '#' && sameline(tok, tok->next) && tok->next->op != '#') { tok = expandHash(tokens, rawloc, tok, expandedmacros, parametertokens); } else { @@ -2168,9 +2168,10 @@ namespace simplecpp { * @param macros all macros * @param expandedmacros set with expanded macros, with this macro * @param parametertokens parameters given when expanding this macro + * @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) const { + 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"); @@ -2260,7 +2261,10 @@ namespace simplecpp { nextTok = tok2->next; } } - expandToken(output, loc, tokens.cfront(), macros, expandedmacros, parametertokens); + if (expandResult) + expandToken(output, loc, tokens.cfront(), macros, expandedmacros, parametertokens); + else + output->takeTokens(tokens); for (Token *b = tokensB.front(); b; b = b->next) b->location = loc; output->takeTokens(tokensB); diff --git a/test.cpp b/test.cpp index 8ae2fa25..3e8c11a2 100644 --- a/test.cpp +++ b/test.cpp @@ -809,6 +809,18 @@ static void define_define_19() // #292 ASSERT_EQUALS("\n\n\n1 , 2 , 3", preprocess(code)); } +static void define_define_20() // #384 arg contains comma +{ + const char code[] = "#define Z_IS_ENABLED1(config_macro) Z_IS_ENABLED2(_XXXX##config_macro)\n" + "#define _XXXX1 _YYYY,\n" + "#define Z_IS_ENABLED2(one_or_two_args) Z_IS_ENABLED3(one_or_two_args 1, 0)\n" + "#define Z_IS_ENABLED3(ignore_this, val, ...) val\n" + "#define IS_ENABLED(config_macro) Z_IS_ENABLED1(config_macro)\n" + "#define FEATURE 1\n" + "a = IS_ENABLED(FEATURE)\n"; + ASSERT_EQUALS("\n\n\n\n\n\na = 1", preprocess(code)); +} + static void define_va_args_1() { const char code[] = "#define A(fmt...) dostuff(fmt)\n" @@ -2975,6 +2987,7 @@ int main(int argc, char **argv) TEST_CASE(define_define_17); TEST_CASE(define_define_18); TEST_CASE(define_define_19); + TEST_CASE(define_define_20); // 384 arg contains comma TEST_CASE(define_va_args_1); TEST_CASE(define_va_args_2); TEST_CASE(define_va_args_3); From bacd5bd1ff0ce8863c9382b5ef91fc1f21ce240a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 11 Dec 2024 16:05:53 +0100 Subject: [PATCH 002/119] runastyle --- main.cpp | 15 ++---- simplecpp.cpp | 138 ++++++++++++++++++++++---------------------------- test.cpp | 38 +++++++------- 3 files changed, 85 insertions(+), 106 deletions(-) diff --git a/main.cpp b/main.cpp index 3f02773f..f05cf793 100644 --- a/main.cpp +++ b/main.cpp @@ -29,22 +29,19 @@ int main(int argc, char **argv) bool found = false; const char c = arg[1]; switch (c) { - case 'D': // define symbol - { + case 'D': { // define symbol const char * const value = arg[2] ? (argv[i] + 2) : argv[++i]; dui.defines.push_back(value); found = true; break; } - case 'U': // undefine symbol - { + case 'U': { // undefine symbol const char * const value = arg[2] ? (argv[i] + 2) : argv[++i]; dui.undefined.insert(value); found = true; break; } - case 'I': // include path - { + case 'I': { // include path const char * const value = arg[2] ? (argv[i] + 2) : argv[++i]; dui.includePaths.push_back(value); found = true; @@ -54,8 +51,7 @@ int main(int argc, char **argv) if (std::strncmp(arg, "-include=",9)==0) { dui.includes.push_back(arg+9); found = true; - } - else if (std::strncmp(arg, "-is",3)==0) { + } else if (std::strncmp(arg, "-is",3)==0) { use_istream = true; found = true; } @@ -122,8 +118,7 @@ int main(int argc, char **argv) std::exit(1); } rawtokens = new simplecpp::TokenList(f, files,filename,&outputList); - } - else { + } else { rawtokens = new simplecpp::TokenList(filename,files,&outputList); } rawtokens->removeComments(); diff --git a/simplecpp.cpp b/simplecpp.cpp index 798fdeba..0b791945 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -242,8 +242,7 @@ class simplecpp::TokenList::Stream { virtual void unget() = 0; virtual bool good() = 0; - unsigned char readChar() - { + unsigned char readChar() { unsigned char ch = static_cast(get()); // For UTF-16 encoded files the BOM is 0xfeff/0xfffe. If the @@ -271,8 +270,7 @@ class simplecpp::TokenList::Stream { return ch; } - unsigned char peekChar() - { + unsigned char peekChar() { unsigned char ch = static_cast(peek()); // For UTF-16 encoded files the BOM is 0xfeff/0xfffe. If the @@ -292,8 +290,7 @@ class simplecpp::TokenList::Stream { return ch; } - void ungetChar() - { + void ungetChar() { unget(); if (isUtf16) unget(); @@ -308,13 +305,11 @@ class simplecpp::TokenList::Stream { } private: - inline int makeUtf16Char(const unsigned char ch, const unsigned char ch2) const - { + inline int makeUtf16Char(const unsigned char ch, const unsigned char ch2) const { return (bom == 0xfeff) ? (ch<<8 | ch2) : (ch2<<8 | ch); } - unsigned short getAndSkipBOM() - { + unsigned short getAndSkipBOM() { const int ch1 = peek(); // The UTF-16 BOM is 0xfffe or 0xfeff. @@ -353,8 +348,7 @@ 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) - { + : istr(istr) { assert(istr.good()); init(); } @@ -383,8 +377,7 @@ class StdCharBufStream : public simplecpp::TokenList::Stream { : str(str) , size(size) , pos(0) - , lastStatus(0) - { + , lastStatus(0) { init(); } @@ -418,8 +411,7 @@ class FileStream : public simplecpp::TokenList::Stream { EXPLICIT FileStream(const std::string &filename, std::vector &files) : file(fopen(filename.c_str(), "rb")) , lastCh(0) - , lastStatus(0) - { + , lastStatus(0) { if (!file) { files.push_back(filename); throw simplecpp::Output(files, simplecpp::Output::FILE_NOT_FOUND, "File is missing: " + filename); @@ -455,8 +447,7 @@ class FileStream : public simplecpp::TokenList::Stream { // TODO: use ungetc() as well // UTF-16 has subsequent unget() calls fseek(file, -1, SEEK_CUR); - } - else + } else ungetc(ch, file); } @@ -485,22 +476,19 @@ simplecpp::TokenList::TokenList(const unsigned char* data, std::size_t size, std } 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) + : 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) + : frontToken(nullptr), backToken(nullptr), files(filenames) { - try - { + try { FileStream stream(filename, filenames); readfile(stream,filename,outputList); - } - catch(const simplecpp::Output & e) // TODO handle extra type of errors - { + } catch (const simplecpp::Output & e) { // TODO handle extra type of errors outputList->push_back(e); } } @@ -890,7 +878,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, else back()->setstr(prefix + s); - if (newlines > 0 ) { + if (newlines > 0) { const Token * const llTok = lastLineTok(); if (llTok && llTok->op == '#' && llTok->next && (llTok->next->str() == "define" || llTok->next->str() == "pragma") && llTok->next->next) { multiline += newlines; @@ -2009,14 +1997,14 @@ namespace simplecpp { int paren = 1; while (sameline(tok, tok->next)) { if (tok->next->str() == "(") - ++paren; + ++paren; else if (tok->next->str() == ")") - --paren; + --paren; if (paren == 0) - return tok->next->next; + return tok->next->next; tok = tok->next; if (parametertokens.size() > args.size() && parametertokens.front()->next->str() != ")") - tok = expandToken(output, loc, tok, macros, expandedmacros, parametertokens)->previous; + tok = expandToken(output, loc, tok, macros, expandedmacros, parametertokens)->previous; } } throw Error(tok->location, "Missing parenthesis for __VA_OPT__(content)"); @@ -2708,8 +2696,7 @@ static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI header += headerToken->str(); // cppcheck-suppress selfAssignment - platform-dependent implementation header = realFilename(header); - } - else { + } else { header = realFilename(tok1->str().substr(1U, tok1->str().size() - 2U)); } std::ifstream f; @@ -3625,8 +3612,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL header = realFilename(header); if (tok && tok->op == '>') closingAngularBracket = true; - } - else { + } else { header = realFilename(tok->str().substr(1U, tok->str().size() - 2U)); closingAngularBracket = true; } @@ -3799,25 +3785,24 @@ simplecpp::cstd_t simplecpp::getCStd(const std::string &std) std::string simplecpp::getCStdString(cstd_t std) { - switch (std) - { - case C89: - // __STDC_VERSION__ is not set for C90 although the macro was added in the 1994 amendments - return ""; - case C99: - return "199901L"; - case C11: - return "201112L"; - case C17: - return "201710L"; - case C23: - // supported by GCC 9+ and Clang 9+ - // Clang 9, 10, 11, 12, 13 return "201710L" - // 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 CUnknown: - return ""; + switch (std) { + case C89: + // __STDC_VERSION__ is not set for C90 although the macro was added in the 1994 amendments + return ""; + case C99: + return "199901L"; + case C11: + return "201112L"; + case C17: + return "201710L"; + case C23: + // supported by GCC 9+ and Clang 9+ + // Clang 9, 10, 11, 12, 13 return "201710L" + // 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 CUnknown: + return ""; } return ""; } @@ -3848,30 +3833,29 @@ simplecpp::cppstd_t simplecpp::getCppStd(const std::string &std) std::string simplecpp::getCppStdString(cppstd_t std) { - switch (std) - { - case CPP03: - return "199711L"; - case CPP11: - return "201103L"; - case CPP14: - return "201402L"; - case CPP17: - return "201703L"; - case CPP20: - // GCC 10 returns "201703L" - correct in 11+ - return "202002L"; - case CPP23: - // supported by GCC 11+ and Clang 12+ - // GCC 11, 12, 13 return "202100L" - // Clang 12, 13, 14, 15, 16 do not support "c++23" and "gnu++23" and return "202101L" - // Clang 17, 18 return "202302L" - return "202302L"; - case CPP26: - // supported by Clang 17+ - return "202400L"; - case CPPUnknown: - return ""; + switch (std) { + case CPP03: + return "199711L"; + case CPP11: + return "201103L"; + case CPP14: + return "201402L"; + case CPP17: + return "201703L"; + case CPP20: + // GCC 10 returns "201703L" - correct in 11+ + return "202002L"; + case CPP23: + // supported by GCC 11+ and Clang 12+ + // GCC 11, 12, 13 return "202100L" + // Clang 12, 13, 14, 15, 16 do not support "c++23" and "gnu++23" and return "202101L" + // Clang 17, 18 return "202302L" + return "202302L"; + case CPP26: + // supported by Clang 17+ + return "202400L"; + case CPPUnknown: + return ""; } return ""; } diff --git a/test.cpp b/test.cpp index 3e8c11a2..783e0175 100644 --- a/test.cpp +++ b/test.cpp @@ -731,7 +731,7 @@ static void define_define_11a() "#define TEST_MACRO CONCAT(A, B, C)\n" "TEST_MACRO\n"; ASSERT_EQUALS("\n\n\n\n\n0x1", preprocess(code)); - + const char code2[] = "#define ADDER_S(a, b) a + b\n" // #374 "#define ADDER(x) ADDER_S(x)\n" "#define ARGUMENTS 1, 2\n" @@ -849,18 +849,18 @@ static void define_va_args_4() // cppcheck trac #9754 ASSERT_EQUALS("\nprintf ( 1 , 2 )", preprocess(code)); } -static void define_va_opt_1() +static void define_va_opt_1() { const char code[] = "#define p1(fmt, args...) printf(fmt __VA_OPT__(,) args)\n" "p1(\"hello\");\n" "p1(\"%s\", \"hello\");\n"; ASSERT_EQUALS("\nprintf ( \"hello\" ) ;\n" - "printf ( \"%s\" , \"hello\" ) ;", - preprocess(code)); + "printf ( \"%s\" , \"hello\" ) ;", + preprocess(code)); } -static void define_va_opt_2() +static void define_va_opt_2() { const char code[] = "#define err(...)\\\n" "__VA_OPT__(\\\n" @@ -883,7 +883,7 @@ static void define_va_opt_3() simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code1, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'err', Missing parenthesis for __VA_OPT__(content)\n", - toString(outputList)); + toString(outputList)); outputList.clear(); @@ -894,10 +894,10 @@ static void define_va_opt_3() ASSERT_EQUALS("", preprocess(code2, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'err', Missing parenthesis for __VA_OPT__(content)\n", - toString(outputList)); + toString(outputList)); } -static void define_va_opt_4() +static void define_va_opt_4() { // missing parenthesis const char code1[] = "#define err(...) __VA_OPT__ something\n" @@ -906,7 +906,7 @@ static void define_va_opt_4() simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code1, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'err', Missing parenthesis for __VA_OPT__(content)\n", - toString(outputList)); + toString(outputList)); outputList.clear(); @@ -916,7 +916,7 @@ static void define_va_opt_4() ASSERT_EQUALS("", preprocess(code2, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'err', Missing parenthesis for __VA_OPT__(content)\n", - toString(outputList)); + toString(outputList)); } static void define_va_opt_5() @@ -928,7 +928,7 @@ static void define_va_opt_5() simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'err', Missing parenthesis for __VA_OPT__(content)\n", - toString(outputList)); + toString(outputList)); } static void define_ifdef() @@ -948,14 +948,14 @@ static void define_ifdef() static void pragma_backslash() { const char code[] = "#pragma comment (longstring, \\\n" - "\"HEADER\\\n" - "This is a very long string that is\\\n" - "a multi-line string.\\\n" - "How much more do I have to say?\\\n" - "Well, be prepared, because the\\\n" - "story is just beginning. This is a test\\\n" - "string for demonstration purposes. \")\n"; - + "\"HEADER\\\n" + "This is a very long string that is\\\n" + "a multi-line string.\\\n" + "How much more do I have to say?\\\n" + "Well, be prepared, because the\\\n" + "story is just beginning. This is a test\\\n" + "string for demonstration purposes. \")\n"; + simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); } From 0b6feabdd97f2556b17a83c92a5b022b57617502 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 11 Dec 2024 16:04:42 +0100 Subject: [PATCH 003/119] Fix #395 (hash hash with an empty __VA_ARGS__ in a macro) --- simplecpp.cpp | 31 +++++++++++++++++++++++++++---- test.cpp | 12 ++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 0b791945..476f42ff 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -110,6 +110,15 @@ template static std::string toString(T t) return ostr.str(); } +#ifdef SIMPLECPP_DEBUG_MACRO_EXPANSION +static std::string locstring(const simplecpp::Location &loc) +{ + std::ostringstream ostr; + ostr << '[' << loc.file() << ':' << loc.line << ':' << loc.col << ']'; + return ostr.str(); +} +#endif + static long long stringToLL(const std::string &s) { long long ret; @@ -1528,6 +1537,10 @@ namespace simplecpp { std::vector &inputFiles) const { std::set expandedmacros; +#ifdef SIMPLECPP_DEBUG_MACRO_EXPANSION + std::cout << "expand " << name() << " " << locstring(rawtok->location) << std::endl; +#endif + TokenList output2(inputFiles); if (functionLike() && rawtok->next && rawtok->next->op == '(') { @@ -1797,6 +1810,10 @@ namespace simplecpp { const Token * expand(TokenList * const output, const Location &loc, const Token * const nameTokInst, const MacroMap ¯os, std::set expandedmacros) const { expandedmacros.insert(nameTokInst->str()); +#ifdef SIMPLECPP_DEBUG_MACRO_EXPANSION + std::cout << " expand " << name() << " " << locstring(defineLocation()) << std::endl; +#endif + usageList.push_back(loc); if (nameTokInst->str() == "__FILE__") { @@ -2168,8 +2185,7 @@ namespace simplecpp { const bool canBeConcatenatedWithEqual = A->isOneOf("+-*/%&|^") || A->str() == "<<" || A->str() == ">>"; const bool canBeConcatenatedStringOrChar = isStringLiteral_(A->str()) || isCharLiteral_(A->str()); - if (!A->name && !A->number && A->op != ',' && !A->str().empty() && !canBeConcatenatedWithEqual && !canBeConcatenatedStringOrChar) - throw invalidHashHash::unexpectedToken(tok->location, name(), A); + const bool unexpectedA = (!A->name && !A->number && !A->str().empty() && !canBeConcatenatedWithEqual && !canBeConcatenatedStringOrChar); Token * const B = tok->next->next; if (!B->name && !B->number && B->op && !B->isOneOf("#=")) @@ -2187,6 +2203,9 @@ 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)) { @@ -2205,13 +2224,17 @@ namespace simplecpp { if (expandArg(&tokensB, B, parametertokens)) { if (tokensB.empty()) strAB = A->str(); - else if (varargs && A->op == ',') { + else if (varargs && A->op == ',') strAB = ","; - } else { + else if (varargs && unexpectedA) + throw invalidHashHash::unexpectedToken(tok->location, name(), A); + else { strAB = A->str() + tokensB.cfront()->str(); tokensB.deleteToken(tokensB.front()); } } else { + if (unexpectedA) + throw invalidHashHash::unexpectedToken(tok->location, name(), A); strAB = A->str() + B->str(); } diff --git a/test.cpp b/test.cpp index 783e0175..0f7ebe77 100644 --- a/test.cpp +++ b/test.cpp @@ -1421,6 +1421,17 @@ static void hashhash_null_stmt() ASSERT_EQUALS("\n\n\n\n1 ;", preprocess(code, &outputList)); } +static void hashhash_empty_va_args() +{ + // #395 hash hash with an empty __VA_ARGS__ in a macro + const char code[] = + "#define CAT(a, ...) a##__VA_ARGS__\n" + "#define X(a, ...) CAT(a)\n" + "#define LEVEL_2 (2)\n" + "X(LEVEL_2)\n"; + ASSERT_EQUALS("\n\n\n( 2 )", preprocess(code)); +} + static void hashhash_universal_character() { const char code[] = @@ -3044,6 +3055,7 @@ int main(int argc, char **argv) TEST_CASE(hashhash_invalid_string_number); TEST_CASE(hashhash_invalid_missing_args); TEST_CASE(hashhash_null_stmt); + TEST_CASE(hashhash_empty_va_args); // 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), From 4bb6eba874efd4258a516ab2233f58a20d0de40c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 13 Dec 2024 10:26:40 +0000 Subject: [PATCH 004/119] Fix #397 (Debracket macro not expanded) (#398) --- simplecpp.cpp | 10 ++++++++-- test.cpp | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 476f42ff..050a5d08 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2046,13 +2046,19 @@ namespace simplecpp { calledMacro.expand(&temp, loc, tok, macros, expandedmacros); return recursiveExpandToken(output, temp, loc, tok, macros, expandedmacros2, parametertokens); } - if (!sameline(tok, tok->next) || tok->next->op != '(') { + if (!sameline(tok, tok->next)) { output->push_back(newMacroToken(tok->str(), loc, true, tok)); return tok->next; } TokenList tokens(files); tokens.push_back(new Token(*tok)); - const Token * const tok2 = appendTokens(&tokens, loc, tok->next, macros, expandedmacros, parametertokens); + 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)) { + if (tokens.cfront()->next && tokens.cfront()->next->op == '(') + tok2 = tok->next; + } if (!tok2) { output->push_back(newMacroToken(tok->str(), loc, true, tok)); return tok->next; diff --git a/test.cpp b/test.cpp index 0f7ebe77..bf83763e 100644 --- a/test.cpp +++ b/test.cpp @@ -821,6 +821,20 @@ static void define_define_20() // #384 arg contains comma ASSERT_EQUALS("\n\n\n\n\n\na = 1", preprocess(code)); } +static void define_define_21() // #397 DEBRACKET macro +{ + const char code1[] = "#define A(val) B val\n" + "#define B(val) val\n" + "A((2))\n"; + ASSERT_EQUALS("\n\n2", preprocess(code1)); + + const char code2[] = "#define x (2)\n" + "#define A B x\n" + "#define B(val) val\n" + "A\n"; + ASSERT_EQUALS("\n\n\nB ( 2 )", preprocess(code2)); +} + static void define_va_args_1() { const char code[] = "#define A(fmt...) dostuff(fmt)\n" @@ -2999,6 +3013,7 @@ int main(int argc, char **argv) TEST_CASE(define_define_18); TEST_CASE(define_define_19); TEST_CASE(define_define_20); // 384 arg contains comma + TEST_CASE(define_define_21); TEST_CASE(define_va_args_1); TEST_CASE(define_va_args_2); TEST_CASE(define_va_args_3); From 6a6865d3971ea5cc17db5244b9a5caf9fd6c39ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 13 Dec 2024 17:12:12 +0000 Subject: [PATCH 005/119] Fixup #397 (Debracket macro not expanded) (#399) --- simplecpp.cpp | 1 + test.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/simplecpp.cpp b/simplecpp.cpp index 050a5d08..604bdd19 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2056,6 +2056,7 @@ namespace simplecpp { 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 == '(') tok2 = tok->next; } diff --git a/test.cpp b/test.cpp index bf83763e..97686aec 100644 --- a/test.cpp +++ b/test.cpp @@ -833,6 +833,12 @@ static void define_define_21() // #397 DEBRACKET macro "#define B(val) val\n" "A\n"; ASSERT_EQUALS("\n\n\nB ( 2 )", preprocess(code2)); + + const char code3[] = "#define __GET_ARG2_DEBRACKET(ignore_this, val, ...) __DEBRACKET val\n" + "#define __DEBRACKET(...) __VA_ARGS__\n" + "#5 \"a.c\"\n" + "__GET_ARG2_DEBRACKET(432 (33), (B))\n"; + ASSERT_EQUALS("\n#line 5 \"a.c\"\nB", preprocess(code3)); } static void define_va_args_1() From a853253fc1958000ecad8543d5fd15b6a6b34959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 14 Dec 2024 12:52:44 +0000 Subject: [PATCH 006/119] Fix #400 (inner macro not expanded after hash hash) (#401) --- simplecpp.cpp | 2 +- test.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 604bdd19..3e9dda6c 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2066,7 +2066,7 @@ namespace simplecpp { } TokenList temp(files); calledMacro.expand(&temp, loc, tokens.cfront(), macros, expandedmacros); - return recursiveExpandToken(output, temp, loc, tok2, macros, expandedmacros2, parametertokens); + return recursiveExpandToken(output, temp, loc, tok2, macros, expandedmacros, parametertokens); } if (tok->str() == DEFINED) { diff --git a/test.cpp b/test.cpp index 97686aec..3ff00b33 100644 --- a/test.cpp +++ b/test.cpp @@ -841,6 +841,15 @@ static void define_define_21() // #397 DEBRACKET macro ASSERT_EQUALS("\n#line 5 \"a.c\"\nB", preprocess(code3)); } +static void define_define_22() // #400 inner macro not expanded after hash hash +{ + const char code[] = "#define FOO(a) CAT(DO, STUFF)(1,2)\n" + "#define DOSTUFF(a, b) CAT(3, 4)\n" + "#define CAT(a, b) a##b\n" + "FOO(1)\n"; + ASSERT_EQUALS("\n\n\n34", preprocess(code)); +} + static void define_va_args_1() { const char code[] = "#define A(fmt...) dostuff(fmt)\n" @@ -3020,6 +3029,7 @@ int main(int argc, char **argv) TEST_CASE(define_define_19); TEST_CASE(define_define_20); // 384 arg contains comma TEST_CASE(define_define_21); + TEST_CASE(define_define_22); // #400 TEST_CASE(define_va_args_1); TEST_CASE(define_va_args_2); TEST_CASE(define_va_args_3); From c4b6e37a55128e0cdc7ba1145bee62bb1d880f65 Mon Sep 17 00:00:00 2001 From: Tal500 Date: Wed, 1 Jan 2025 23:47:16 +0200 Subject: [PATCH 007/119] fix: use both absolute and relative header paths in header matching (#362) Co-authored-by: Tal Hadad --- .github/workflows/CI-unixish.yml | 17 ++++- .github/workflows/CI-windows.yml | 18 ++++- .gitignore | 3 + integration_test.py | 94 ++++++++++++++++++++++++ simplecpp.cpp | 121 +++++++++++++++++++++++++------ testutils.py | 57 +++++++++++++++ 6 files changed, 286 insertions(+), 24 deletions(-) create mode 100644 integration_test.py create mode 100644 testutils.py diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index 050e1d74..9a0e8d8e 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -30,7 +30,18 @@ jobs: run: | sudo apt-get update sudo apt-get install libc++-18-dev - + + - name: Install missing software on macos + if: contains(matrix.os, 'macos') + run: | + brew install python3 + + - name: Install missing Python packages + run: | + python3 -m pip config set global.break-system-packages true + python3 -m pip install pip --upgrade + python3 -m pip install pytest + - name: make simplecpp run: make -j$(nproc) @@ -41,6 +52,10 @@ jobs: run: | make -j$(nproc) selfcheck + - name: integration test + run: | + python3 -m pytest integration_test.py + - name: Run CMake run: | cmake -S . -B cmake.output diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 1f78876d..50d5a84e 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -26,7 +26,18 @@ jobs: - name: Setup msbuild.exe uses: microsoft/setup-msbuild@v2 - + + - name: Set up Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: '3.13' + check-latest: true + + - name: Install missing Python packages + run: | + python -m pip install pip --upgrade || exit /b !errorlevel! + python -m pip install pytest || exit /b !errorlevel! + - name: Run cmake if: matrix.os == 'windows-2019' run: | @@ -48,4 +59,9 @@ jobs: - name: Selfcheck run: | .\${{ matrix.config }}\simplecpp.exe simplecpp.cpp -e || exit /b !errorlevel! + + - name: integration test + run: | + set SIMPLECPP_EXE_PATH=.\${{ matrix.config }}\simplecpp.exe + python -m pytest integration_test.py || exit /b !errorlevel! diff --git a/.gitignore b/.gitignore index 183545f1..113cf360 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ testrunner # CLion /.idea /cmake-build-* + +# python +__pycache__/ diff --git a/integration_test.py b/integration_test.py new file mode 100644 index 00000000..0b2b0b38 --- /dev/null +++ b/integration_test.py @@ -0,0 +1,94 @@ +## test with python -m pytest integration_test.py + +import os +import pytest +from testutils import simplecpp, format_include_path_arg, format_include + +def __test_relative_header_create_header(dir, with_pragma_once=True): + header_file = os.path.join(dir, 'test.h') + with open(header_file, 'wt') as f: + f.write(f""" + {"#pragma once" if with_pragma_once else ""} + #ifndef TEST_H_INCLUDED + #define TEST_H_INCLUDED + #else + #error header_was_already_included + #endif + """) + 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): + if inv: + return __test_relative_header_create_source(dir, include1=include2, include2=include1, is_include1_sys=is_include2_sys, is_include2_sys=is_include1_sys) + ## otherwise + + src_file = os.path.join(dir, 'test.c') + with open(src_file, 'wt') as f: + f.write(f""" + #undef TEST_H_INCLUDED + #include {format_include(include1, is_include1_sys)} + #include {format_include(include2, is_include2_sys)} + """) + return src_file + +@pytest.mark.parametrize("with_pragma_once", (False, True)) +@pytest.mark.parametrize("is_sys", (False, True)) +def test_relative_header_1(tmpdir, with_pragma_once, is_sys): + _, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once) + + test_file = __test_relative_header_create_source(tmpdir, "test.h", "test.h", is_include1_sys=is_sys, is_include2_sys=is_sys) + + args = ([format_include_path_arg(tmpdir)] if is_sys else []) + [test_file] + + _, _, stderr = simplecpp(args, cwd=tmpdir) + + if with_pragma_once: + assert stderr == '' + else: + assert double_include_error in stderr + +@pytest.mark.parametrize("inv", (False, True)) +def test_relative_header_2(tmpdir, inv): + header_file, _ = __test_relative_header_create_header(tmpdir) + + test_file = __test_relative_header_create_source(tmpdir, "test.h", header_file, inv=inv) + + args = [test_file] + + _, _, stderr = simplecpp(args, cwd=tmpdir) + assert stderr == '' + +@pytest.mark.parametrize("is_sys", (False, True)) +@pytest.mark.parametrize("inv", (False, True)) +def test_relative_header_3(tmpdir, is_sys, inv): + test_subdir = os.path.join(tmpdir, "test_subdir") + os.mkdir(test_subdir) + header_file, _ = __test_relative_header_create_header(test_subdir) + + test_file = __test_relative_header_create_source(tmpdir, "test_subdir/test.h", header_file, is_include1_sys=is_sys, inv=inv) + + args = [test_file] + + _, _, stderr = simplecpp(args, cwd=tmpdir) + + if is_sys: + assert "missing header: Header not found" in stderr + else: + assert stderr == '' + +@pytest.mark.parametrize("use_short_path", (False, True)) +@pytest.mark.parametrize("is_sys", (False, True)) +@pytest.mark.parametrize("inv", (False, True)) +def test_relative_header_4(tmpdir, use_short_path, is_sys, inv): + test_subdir = os.path.join(tmpdir, "test_subdir") + os.mkdir(test_subdir) + header_file, _ = __test_relative_header_create_header(test_subdir) + if use_short_path: + header_file = "test_subdir/test.h" + + test_file = __test_relative_header_create_source(tmpdir, header_file, "test.h", is_include2_sys=is_sys, inv=inv) + + args = [format_include_path_arg(test_subdir), test_file] + + _, _, stderr = simplecpp(args, cwd=tmpdir) + assert stderr == '' diff --git a/simplecpp.cpp b/simplecpp.cpp index 3e9dda6c..20ae2528 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -43,6 +43,8 @@ #ifdef SIMPLECPP_WINDOWS #include #undef ERROR +#else +#include #endif #if __cplusplus >= 201103L @@ -147,6 +149,11 @@ static unsigned long long stringToULL(const std::string &s) return ret; } +static bool startsWith(const std::string &s, const std::string &p) +{ + return (s.size() >= p.size()) && std::equal(p.begin(), p.end(), s.begin()); +} + static bool endsWith(const std::string &s, const std::string &e) { return (s.size() >= e.size()) && std::equal(e.rbegin(), e.rend(), s.rbegin()); @@ -2680,6 +2687,46 @@ static bool isCpp17OrLater(const simplecpp::DUI &dui) return !std_ver.empty() && (std_ver >= "201703L"); } + +static std::string currentDirectoryOSCalc() { +#ifdef SIMPLECPP_WINDOWS + TCHAR NPath[MAX_PATH]; + GetCurrentDirectory(MAX_PATH, NPath); + return NPath; +#else + const std::size_t size = 1024; + char the_path[size]; + getcwd(the_path, size); + return the_path; +#endif +} + +static const std::string& currentDirectory() { + static const std::string curdir = simplecpp::simplifyPath(currentDirectoryOSCalc()); + return curdir; +} + +static std::string toAbsolutePath(const std::string& path) { + if (path.empty()) { + return path;// preserve error file path that is indicated by an empty string + } + if (!isAbsolutePath(path)) { + return currentDirectory() + "/" + path; + } + // otherwise + return path; +} + +static std::pair extractRelativePathFromAbsolute(const std::string& absolutepath) { + static const std::string prefix = currentDirectory() + "/"; + if (startsWith(absolutepath, prefix)) { + const std::size_t size = prefix.size(); + return std::make_pair(absolutepath.substr(size, absolutepath.size() - size), true); + } + // otherwise + return std::make_pair("", false); +} + static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader); static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI &dui) { @@ -3098,9 +3145,12 @@ static std::string openHeader(std::ifstream &f, const std::string &path) static std::string getRelativeFileName(const std::string &sourcefile, const std::string &header) { + std::string path; if (sourcefile.find_first_of("\\/") != std::string::npos) - return simplecpp::simplifyPath(sourcefile.substr(0, sourcefile.find_last_of("\\/") + 1U) + header); - return simplecpp::simplifyPath(header); + path = sourcefile.substr(0, sourcefile.find_last_of("\\/") + 1U) + header; + else + path = header; + return simplecpp::simplifyPath(path); } static std::string openHeaderRelative(std::ifstream &f, const std::string &sourcefile, const std::string &header) @@ -3110,7 +3160,7 @@ static std::string openHeaderRelative(std::ifstream &f, const std::string &sourc static std::string getIncludePathFileName(const std::string &includePath, const std::string &header) { - std::string path = includePath; + std::string path = toAbsolutePath(includePath); if (!path.empty() && path[path.size()-1U]!='/' && path[path.size()-1U]!='\\') path += '/'; return path + header; @@ -3119,9 +3169,9 @@ static std::string getIncludePathFileName(const std::string &includePath, const static std::string openHeaderIncludePath(std::ifstream &f, const simplecpp::DUI &dui, const std::string &header) { for (std::list::const_iterator it = dui.includePaths.begin(); it != dui.includePaths.end(); ++it) { - std::string simplePath = openHeader(f, getIncludePathFileName(*it, header)); - if (!simplePath.empty()) - return simplePath; + std::string path = openHeader(f, getIncludePathFileName(*it, header)); + if (!path.empty()) + return path; } return ""; } @@ -3131,49 +3181,76 @@ static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const if (isAbsolutePath(header)) return openHeader(f, header); - std::string ret; - if (systemheader) { - ret = openHeaderIncludePath(f, dui, header); - return ret; + // always return absolute path for systemheaders + return toAbsolutePath(openHeaderIncludePath(f, dui, header)); } + std::string ret; + ret = openHeaderRelative(f, sourcefile, header); if (ret.empty()) - return openHeaderIncludePath(f, dui, header); + return toAbsolutePath(openHeaderIncludePath(f, dui, header));// in a similar way to system headers return ret; } -static std::string getFileName(const std::map &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader) +static std::string findPathInMapBothRelativeAndAbsolute(const std::map &filedata, const std::string& path) { + // here there are two possibilities - either we match this from absolute path or from a relative one + if (filedata.find(path) != filedata.end()) {// try first to respect the exact match + return path; + } + // otherwise - try to use the normalize to the correct representation + if (isAbsolutePath(path)) { + const std::pair relativeExtractedResult = extractRelativePathFromAbsolute(path); + if (relativeExtractedResult.second) { + const std::string relativePath = relativeExtractedResult.first; + if (filedata.find(relativePath) != filedata.end()) { + return relativePath; + } + } + } else { + const std::string absolutePath = toAbsolutePath(path); + if (filedata.find(absolutePath) != filedata.end()) + return absolutePath; + } + // otherwise + return ""; +} + +static std::string getFileIdPath(const std::map &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader) { if (filedata.empty()) { return ""; } if (isAbsolutePath(header)) { - return (filedata.find(header) != filedata.end()) ? simplecpp::simplifyPath(header) : ""; + const std::string simplifiedHeaderPath = simplecpp::simplifyPath(header); + return (filedata.find(simplifiedHeaderPath) != filedata.end()) ? simplifiedHeaderPath : ""; } if (!systemheader) { - const std::string relativeFilename = getRelativeFileName(sourcefile, header); - if (filedata.find(relativeFilename) != filedata.end()) - return relativeFilename; + const std::string relativeOrAbsoluteFilename = getRelativeFileName(sourcefile, header);// unknown if absolute or relative, but always simplified + const std::string match = findPathInMapBothRelativeAndAbsolute(filedata, relativeOrAbsoluteFilename); + if (!match.empty()) { + return match; + } } for (std::list::const_iterator it = dui.includePaths.begin(); it != dui.includePaths.end(); ++it) { - std::string s = simplecpp::simplifyPath(getIncludePathFileName(*it, header)); - if (filedata.find(s) != filedata.end()) - return s; + const std::string match = findPathInMapBothRelativeAndAbsolute(filedata, simplecpp::simplifyPath(getIncludePathFileName(*it, header))); + if (!match.empty()) { + return match; + } } if (systemheader && filedata.find(header) != filedata.end()) - return header; + return header;// system header that its file wasn't found in the included paths but alreasy in the filedata - return this as is return ""; } static bool hasFile(const std::map &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader) { - return !getFileName(filedata, sourcefile, header, dui, systemheader).empty(); + return !getFileIdPath(filedata, sourcefile, header, dui, systemheader).empty(); } std::map simplecpp::load(const simplecpp::TokenList &rawtokens, std::vector &filenames, const simplecpp::DUI &dui, simplecpp::OutputList *outputList) @@ -3529,7 +3606,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const bool systemheader = (inctok->str()[0] == '<'); const std::string header(realFilename(inctok->str().substr(1U, inctok->str().size() - 2U))); - std::string header2 = getFileName(filedata, rawtok->location.file(), header, dui, systemheader); + std::string header2 = getFileIdPath(filedata, rawtok->location.file(), header, dui, systemheader); if (header2.empty()) { // try to load file.. std::ifstream f; diff --git a/testutils.py b/testutils.py new file mode 100644 index 00000000..55a2686d --- /dev/null +++ b/testutils.py @@ -0,0 +1,57 @@ +import os +import subprocess +import json + +def __run_subprocess(args, env=None, cwd=None, timeout=None): + p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd) + + try: + stdout, stderr = p.communicate(timeout=timeout) + return_code = p.returncode + p = None + except subprocess.TimeoutExpired: + import psutil + # terminate all the child processes + child_procs = psutil.Process(p.pid).children(recursive=True) + if len(child_procs) > 0: + for child in child_procs: + child.terminate() + try: + # call with timeout since it might be stuck + p.communicate(timeout=5) + p = None + except subprocess.TimeoutExpired: + pass + raise + finally: + if p: + # sending the signal to the process groups causes the parent Python process to terminate as well + #os.killpg(os.getpgid(p.pid), signal.SIGTERM) # Send the signal to all the process groups + p.terminate() + stdout, stderr = p.communicate() + p = None + + stdout = stdout.decode(encoding='utf-8', errors='ignore').replace('\r\n', '\n') + stderr = stderr.decode(encoding='utf-8', errors='ignore').replace('\r\n', '\n') + + return return_code, stdout, stderr + +def simplecpp(args = [], cwd = None): + dir_path = os.path.dirname(os.path.realpath(__file__)) + if 'SIMPLECPP_EXE_PATH' in os.environ: + simplecpp_path = os.environ['SIMPLECPP_EXE_PATH'] + else: + simplecpp_path = os.path.join(dir_path, "simplecpp") + return __run_subprocess([simplecpp_path] + args, cwd = cwd) + +def quoted_string(s): + return json.dumps(str(s)) + +def format_include_path_arg(include_path): + return f"-I{str(include_path)}" + +def format_include(include, is_sys_header=False): + if is_sys_header: + return f"<{quoted_string(include)[1:-1]}>" + else: + return quoted_string(include) From aa3c4b6af92cbac5abf3c997f2e044db835e6cc6 Mon Sep 17 00:00:00 2001 From: olabetskyi <153490942+olabetskyi@users.noreply.github.com> Date: Mon, 10 Feb 2025 13:29:03 +0200 Subject: [PATCH 008/119] Fix #404: simplecpp::TokenList::constFold does not fold '( 0 ) && 10 < X' properly (#405) --- simplecpp.cpp | 98 +++++++++++++++++++++++++++++++++------------------ simplecpp.h | 2 ++ test.cpp | 26 ++++++++++++++ 3 files changed, 92 insertions(+), 34 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 20ae2528..576b0da7 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -954,7 +954,7 @@ void simplecpp::TokenList::constFold() constFoldQuestionOp(&tok); // If there is no '(' we are done with the constant folding - if (tok->op != '(') + if (!tok || tok->op != '(') break; if (!tok->next || !tok->next->next || tok->next->next->op != ')') @@ -1164,10 +1164,7 @@ void simplecpp::TokenList::constFoldMulDivRem(Token *tok) } else continue; - tok = tok->previous; - tok->setstr(toString(result)); - deleteToken(tok->next); - deleteToken(tok->next); + simpleSquash(tok, toString(result)); } } @@ -1187,10 +1184,7 @@ void simplecpp::TokenList::constFoldAddSub(Token *tok) else continue; - tok = tok->previous; - tok->setstr(toString(result)); - deleteToken(tok->next); - deleteToken(tok->next); + simpleSquash(tok, toString(result)); } } @@ -1210,10 +1204,7 @@ void simplecpp::TokenList::constFoldShift(Token *tok) else continue; - tok = tok->previous; - tok->setstr(toString(result)); - deleteToken(tok->next); - deleteToken(tok->next); + simpleSquash(tok, toString(result)); } } @@ -1247,10 +1238,7 @@ void simplecpp::TokenList::constFoldComparison(Token *tok) else continue; - tok = tok->previous; - tok->setstr(toString(result)); - deleteToken(tok->next); - deleteToken(tok->next); + simpleSquash(tok, toString(result)); } } @@ -1282,12 +1270,51 @@ void simplecpp::TokenList::constFoldBitwise(Token *tok) result = (stringToLL(tok->previous->str()) ^ stringToLL(tok->next->str())); else /*if (*op == '|')*/ result = (stringToLL(tok->previous->str()) | stringToLL(tok->next->str())); - tok = tok->previous; - tok->setstr(toString(result)); - deleteToken(tok->next); - deleteToken(tok->next); + simpleSquash(tok, toString(result)); + } + } +} + +void simplecpp::TokenList::simpleSquash(Token *&tok, const std::string & result) +{ + tok = tok->previous; + tok->setstr(result); + deleteToken(tok->next); + deleteToken(tok->next); +} + +void simplecpp::TokenList::squashTokens(Token *&tok, const std::set & breakPoints, bool forwardDirection, const std::string & result) +{ + const char * const brackets = forwardDirection ? "()" : ")("; + Token* Token::* const step = forwardDirection ? &Token::next : &Token::previous; + int skip = 0; + const Token * const tok1 = tok->*step; + while (tok1 && tok1->*step) { + if ((tok1->*step)->op == brackets[1]){ + if (skip) { + --skip; + deleteToken(tok1->*step); + } else + break; + } else if ((tok1->*step)->op == brackets[0]) { + ++skip; + deleteToken(tok1->*step); + } else if (skip) { + deleteToken(tok1->*step); + } else if (breakPoints.count((tok1->*step)->str()) != 0) { + break; + } else { + deleteToken(tok1->*step); } } + simpleSquash(tok, result); +} + +static simplecpp::Token * constFoldGetOperand(simplecpp::Token * tok, bool forwardDirection) +{ + simplecpp::Token* simplecpp::Token::* const step = forwardDirection ? &simplecpp::Token::next : &simplecpp::Token::previous; + const char bracket = forwardDirection ? ')' : '('; + return tok->*step && (tok->*step)->number && (!((tok->*step)->*step) || (((tok->*step)->*step)->op == bracket)) ? tok->*step : nullptr; } static const std::string AND("and"); @@ -1303,21 +1330,24 @@ void simplecpp::TokenList::constFoldLogicalOp(Token *tok) } if (tok->str() != "&&" && tok->str() != "||") continue; - if (!tok->previous || !tok->previous->number) - continue; - if (!tok->next || !tok->next->number) + const Token* const lhs = constFoldGetOperand(tok, false); + const Token* const rhs = constFoldGetOperand(tok, true); + if (!lhs) // if lhs is not a single number we don't need to fold 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())); - - tok = tok->previous; - tok->setstr(toString(result)); - deleteToken(tok->next); - deleteToken(tok->next); + std::set breakPoints; + breakPoints.insert(":"); + breakPoints.insert("?"); + if (tok->str() == "||"){ + if (stringToLL(lhs->str()) != 0LL || (rhs && stringToLL(rhs->str()) != 0LL)) + squashTokens(tok, breakPoints, stringToLL(lhs->str()) != 0LL, toString(1)); + } else /*if (tok->str() == "&&")*/ { + breakPoints.insert("||"); + if (stringToLL(lhs->str()) == 0LL || (rhs && stringToLL(rhs->str()) == 0LL)) + squashTokens(tok, breakPoints, stringToLL(lhs->str()) == 0LL, toString(0)); + else if (rhs && stringToLL(lhs->str()) && stringToLL(rhs->str())) + simpleSquash(tok, "1"); + } } } diff --git a/simplecpp.h b/simplecpp.h index f5c69593..0be48306 100755 --- a/simplecpp.h +++ b/simplecpp.h @@ -301,6 +301,8 @@ namespace simplecpp { void constFoldLogicalOp(Token *tok); void constFoldQuestionOp(Token **tok1); + void simpleSquash(Token *&tok, const std::string & result); + void squashTokens(Token *&tok, const std::set & breakPoints, bool forwardDirection, const std::string & result); std::string readUntil(Stream &stream, const Location &location, char start, char end, OutputList *outputList); void lineDirective(unsigned int fileIndex, unsigned int line, Location *location); diff --git a/test.cpp b/test.cpp index 3ff00b33..622c9b90 100644 --- a/test.cpp +++ b/test.cpp @@ -452,6 +452,15 @@ 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 ) && 10 < X")); + ASSERT_EQUALS("0", testConstFold("1+2*(3+4) && 7 - 7")); + ASSERT_EQUALS("1", testConstFold("( 1 ) || 10 < X")); + ASSERT_EQUALS("1", testConstFold("1+2*(3+4) || 8 - 7")); + ASSERT_EQUALS("X && 0", testConstFold("X && 0")); + ASSERT_EQUALS("X >= 0 || 0 < Y", testConstFold("X >= 0 || 0 < Y")); + ASSERT_EQUALS("X && 1 && Z", testConstFold("X && (1 || Y) && Z")); + ASSERT_EQUALS("0 || Y", testConstFold("0 && X || Y")); + ASSERT_EQUALS("X > 0 && Y", testConstFold("X > 0 && Y")); } #ifdef __CYGWIN__ @@ -1598,6 +1607,22 @@ static void ifA() ASSERT_EQUALS("\nX", preprocess(code, dui)); } +static void ifXorY() +{ + const char code[] = "#if Z > 0 || 0 < Y\n" + "X\n" + "#endif"; + ASSERT_EQUALS("", preprocess(code)); + + simplecpp::DUI dui; + dui.defines.push_back("Z=1"); + ASSERT_EQUALS("\nX", preprocess(code, dui)); + + dui.defines.clear(); + dui.defines.push_back("Y=15"); + ASSERT_EQUALS("\nX", preprocess(code, dui)); +} + static void ifCharLiteral() { const char code[] = "#if ('A'==0x41)\n" @@ -3104,6 +3129,7 @@ int main(int argc, char **argv) TEST_CASE(ifdef2); TEST_CASE(ifndef); TEST_CASE(ifA); + TEST_CASE(ifXorY); TEST_CASE(ifCharLiteral); TEST_CASE(ifDefined); TEST_CASE(ifDefinedNoPar); From cc5738cbbb7ac0a8d121ef5a6c67af08bbb9b936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Mon, 10 Feb 2025 12:45:11 +0100 Subject: [PATCH 009/119] bumped minimum CMake version to 3.10 (#408) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 88c46b9e..c3fcf4ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 3.5) +cmake_minimum_required (VERSION 3.10) project (simplecpp LANGUAGES CXX) option(DISABLE_CPP03_SYNTAX_CHECK "Disable the C++03 syntax check." OFF) From 48a958fe25f0f0f39520b96858b5d6fa6b2c705c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 12 Feb 2025 09:59:54 +0000 Subject: [PATCH 010/119] Fix #403 (Stack overflow in Macro::expand()) (#411) --- simplecpp.cpp | 3 ++- test.cpp | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 576b0da7..d8880355 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2169,7 +2169,8 @@ namespace simplecpp { for (const Token *partok = parametertokens[argnr]->next; partok != parametertokens[argnr + 1U];) { 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())) { - const std::set expandedmacros2; // temporary amnesia to allow reexpansion of currently expanding macros during argument evaluation + 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); } else { output->push_back(newMacroToken(partok->str(), loc, isReplaced(expandedmacros), partok)); diff --git a/test.cpp b/test.cpp index 622c9b90..3e245516 100644 --- a/test.cpp +++ b/test.cpp @@ -859,6 +859,16 @@ static void define_define_22() // #400 inner macro not expanded after hash hash ASSERT_EQUALS("\n\n\n34", preprocess(code)); } +static void define_define_23() // #403 crash (infinite recursion) +{ + const char code[] = "#define C_(x, y) x ## y\n" + "#define C(x, y) C_(x, y)\n" + "#define X(func) C(Y, C(func, Z))\n" + "#define die X(die)\n" + "die(void);\n"; + ASSERT_EQUALS("\n\n\n\nYdieZ ( void ) ;", preprocess(code)); +} + static void define_va_args_1() { const char code[] = "#define A(fmt...) dostuff(fmt)\n" @@ -3055,6 +3065,7 @@ int main(int argc, char **argv) TEST_CASE(define_define_20); // 384 arg contains comma TEST_CASE(define_define_21); TEST_CASE(define_define_22); // #400 + TEST_CASE(define_define_23); // #403 - crash, infinite recursion TEST_CASE(define_va_args_1); TEST_CASE(define_va_args_2); TEST_CASE(define_va_args_3); From 9b0c842f683edef45f758241011f50da93fe02ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 12 Feb 2025 12:49:07 +0000 Subject: [PATCH 011/119] Fix #409 (fuzzing crash in simplecpp::Macro::expandToken()) (#412) --- simplecpp.cpp | 2 +- test.cpp | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index d8880355..b8dd063d 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2125,7 +2125,7 @@ namespace simplecpp { if (expandArg(&temp, defToken, parametertokens)) macroName = temp.cback()->str(); if (expandArg(&temp, defToken->next->next->next, parametertokens)) - macroName += temp.cback()->str(); + macroName += temp.cback() ? temp.cback()->str() : ""; else macroName += defToken->next->next->next->str(); lastToken = defToken->next->next->next; diff --git a/test.cpp b/test.cpp index 3e245516..82b9e1c7 100644 --- a/test.cpp +++ b/test.cpp @@ -1717,6 +1717,17 @@ static void ifDefinedHashHash() ASSERT_EQUALS("file0,4,#error,#error FOO is enabled\n", toString(outputList)); } +static void ifDefinedHashHash2() +{ + // #409 + // do not crash when expanding P() (as ## rhs is "null") + // note: gcc outputs "defined E" + const char code[] = "#define P(p)defined E##p\n" + "P()\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("\n0", preprocess(code, &outputList)); +} + static void ifLogical() { const char code[] = "#if defined(A) || defined(B)\n" @@ -3149,6 +3160,7 @@ int main(int argc, char **argv) TEST_CASE(ifDefinedInvalid1); TEST_CASE(ifDefinedInvalid2); TEST_CASE(ifDefinedHashHash); + TEST_CASE(ifDefinedHashHash2); TEST_CASE(ifLogical); TEST_CASE(ifSizeof); TEST_CASE(elif); From 9ce981ccb54928f148a48ad150da9e1b7520b748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 12 Feb 2025 14:37:25 +0000 Subject: [PATCH 012/119] Revert "fix: use both absolute and relative header paths in header matching (#362)" (#415) --- .github/workflows/CI-unixish.yml | 17 +---- .github/workflows/CI-windows.yml | 18 +---- .gitignore | 3 - integration_test.py | 94 ------------------------ simplecpp.cpp | 121 ++++++------------------------- testutils.py | 57 --------------- 6 files changed, 24 insertions(+), 286 deletions(-) delete mode 100644 integration_test.py delete mode 100644 testutils.py diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index 9a0e8d8e..050e1d74 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -30,18 +30,7 @@ jobs: run: | sudo apt-get update sudo apt-get install libc++-18-dev - - - name: Install missing software on macos - if: contains(matrix.os, 'macos') - run: | - brew install python3 - - - name: Install missing Python packages - run: | - python3 -m pip config set global.break-system-packages true - python3 -m pip install pip --upgrade - python3 -m pip install pytest - + - name: make simplecpp run: make -j$(nproc) @@ -52,10 +41,6 @@ jobs: run: | make -j$(nproc) selfcheck - - name: integration test - run: | - python3 -m pytest integration_test.py - - name: Run CMake run: | cmake -S . -B cmake.output diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 50d5a84e..1f78876d 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -26,18 +26,7 @@ jobs: - name: Setup msbuild.exe uses: microsoft/setup-msbuild@v2 - - - name: Set up Python 3.13 - uses: actions/setup-python@v5 - with: - python-version: '3.13' - check-latest: true - - - name: Install missing Python packages - run: | - python -m pip install pip --upgrade || exit /b !errorlevel! - python -m pip install pytest || exit /b !errorlevel! - + - name: Run cmake if: matrix.os == 'windows-2019' run: | @@ -59,9 +48,4 @@ jobs: - name: Selfcheck run: | .\${{ matrix.config }}\simplecpp.exe simplecpp.cpp -e || exit /b !errorlevel! - - - name: integration test - run: | - set SIMPLECPP_EXE_PATH=.\${{ matrix.config }}\simplecpp.exe - python -m pytest integration_test.py || exit /b !errorlevel! diff --git a/.gitignore b/.gitignore index 113cf360..183545f1 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,3 @@ testrunner # CLion /.idea /cmake-build-* - -# python -__pycache__/ diff --git a/integration_test.py b/integration_test.py deleted file mode 100644 index 0b2b0b38..00000000 --- a/integration_test.py +++ /dev/null @@ -1,94 +0,0 @@ -## test with python -m pytest integration_test.py - -import os -import pytest -from testutils import simplecpp, format_include_path_arg, format_include - -def __test_relative_header_create_header(dir, with_pragma_once=True): - header_file = os.path.join(dir, 'test.h') - with open(header_file, 'wt') as f: - f.write(f""" - {"#pragma once" if with_pragma_once else ""} - #ifndef TEST_H_INCLUDED - #define TEST_H_INCLUDED - #else - #error header_was_already_included - #endif - """) - 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): - if inv: - return __test_relative_header_create_source(dir, include1=include2, include2=include1, is_include1_sys=is_include2_sys, is_include2_sys=is_include1_sys) - ## otherwise - - src_file = os.path.join(dir, 'test.c') - with open(src_file, 'wt') as f: - f.write(f""" - #undef TEST_H_INCLUDED - #include {format_include(include1, is_include1_sys)} - #include {format_include(include2, is_include2_sys)} - """) - return src_file - -@pytest.mark.parametrize("with_pragma_once", (False, True)) -@pytest.mark.parametrize("is_sys", (False, True)) -def test_relative_header_1(tmpdir, with_pragma_once, is_sys): - _, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once) - - test_file = __test_relative_header_create_source(tmpdir, "test.h", "test.h", is_include1_sys=is_sys, is_include2_sys=is_sys) - - args = ([format_include_path_arg(tmpdir)] if is_sys else []) + [test_file] - - _, _, stderr = simplecpp(args, cwd=tmpdir) - - if with_pragma_once: - assert stderr == '' - else: - assert double_include_error in stderr - -@pytest.mark.parametrize("inv", (False, True)) -def test_relative_header_2(tmpdir, inv): - header_file, _ = __test_relative_header_create_header(tmpdir) - - test_file = __test_relative_header_create_source(tmpdir, "test.h", header_file, inv=inv) - - args = [test_file] - - _, _, stderr = simplecpp(args, cwd=tmpdir) - assert stderr == '' - -@pytest.mark.parametrize("is_sys", (False, True)) -@pytest.mark.parametrize("inv", (False, True)) -def test_relative_header_3(tmpdir, is_sys, inv): - test_subdir = os.path.join(tmpdir, "test_subdir") - os.mkdir(test_subdir) - header_file, _ = __test_relative_header_create_header(test_subdir) - - test_file = __test_relative_header_create_source(tmpdir, "test_subdir/test.h", header_file, is_include1_sys=is_sys, inv=inv) - - args = [test_file] - - _, _, stderr = simplecpp(args, cwd=tmpdir) - - if is_sys: - assert "missing header: Header not found" in stderr - else: - assert stderr == '' - -@pytest.mark.parametrize("use_short_path", (False, True)) -@pytest.mark.parametrize("is_sys", (False, True)) -@pytest.mark.parametrize("inv", (False, True)) -def test_relative_header_4(tmpdir, use_short_path, is_sys, inv): - test_subdir = os.path.join(tmpdir, "test_subdir") - os.mkdir(test_subdir) - header_file, _ = __test_relative_header_create_header(test_subdir) - if use_short_path: - header_file = "test_subdir/test.h" - - test_file = __test_relative_header_create_source(tmpdir, header_file, "test.h", is_include2_sys=is_sys, inv=inv) - - args = [format_include_path_arg(test_subdir), test_file] - - _, _, stderr = simplecpp(args, cwd=tmpdir) - assert stderr == '' diff --git a/simplecpp.cpp b/simplecpp.cpp index b8dd063d..1ae47f5e 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -43,8 +43,6 @@ #ifdef SIMPLECPP_WINDOWS #include #undef ERROR -#else -#include #endif #if __cplusplus >= 201103L @@ -149,11 +147,6 @@ static unsigned long long stringToULL(const std::string &s) return ret; } -static bool startsWith(const std::string &s, const std::string &p) -{ - return (s.size() >= p.size()) && std::equal(p.begin(), p.end(), s.begin()); -} - static bool endsWith(const std::string &s, const std::string &e) { return (s.size() >= e.size()) && std::equal(e.rbegin(), e.rend(), s.rbegin()); @@ -2718,46 +2711,6 @@ static bool isCpp17OrLater(const simplecpp::DUI &dui) return !std_ver.empty() && (std_ver >= "201703L"); } - -static std::string currentDirectoryOSCalc() { -#ifdef SIMPLECPP_WINDOWS - TCHAR NPath[MAX_PATH]; - GetCurrentDirectory(MAX_PATH, NPath); - return NPath; -#else - const std::size_t size = 1024; - char the_path[size]; - getcwd(the_path, size); - return the_path; -#endif -} - -static const std::string& currentDirectory() { - static const std::string curdir = simplecpp::simplifyPath(currentDirectoryOSCalc()); - return curdir; -} - -static std::string toAbsolutePath(const std::string& path) { - if (path.empty()) { - return path;// preserve error file path that is indicated by an empty string - } - if (!isAbsolutePath(path)) { - return currentDirectory() + "/" + path; - } - // otherwise - return path; -} - -static std::pair extractRelativePathFromAbsolute(const std::string& absolutepath) { - static const std::string prefix = currentDirectory() + "/"; - if (startsWith(absolutepath, prefix)) { - const std::size_t size = prefix.size(); - return std::make_pair(absolutepath.substr(size, absolutepath.size() - size), true); - } - // otherwise - return std::make_pair("", false); -} - static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader); static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI &dui) { @@ -3176,12 +3129,9 @@ static std::string openHeader(std::ifstream &f, const std::string &path) static std::string getRelativeFileName(const std::string &sourcefile, const std::string &header) { - std::string path; if (sourcefile.find_first_of("\\/") != std::string::npos) - path = sourcefile.substr(0, sourcefile.find_last_of("\\/") + 1U) + header; - else - path = header; - return simplecpp::simplifyPath(path); + return simplecpp::simplifyPath(sourcefile.substr(0, sourcefile.find_last_of("\\/") + 1U) + header); + return simplecpp::simplifyPath(header); } static std::string openHeaderRelative(std::ifstream &f, const std::string &sourcefile, const std::string &header) @@ -3191,7 +3141,7 @@ static std::string openHeaderRelative(std::ifstream &f, const std::string &sourc static std::string getIncludePathFileName(const std::string &includePath, const std::string &header) { - std::string path = toAbsolutePath(includePath); + std::string path = includePath; if (!path.empty() && path[path.size()-1U]!='/' && path[path.size()-1U]!='\\') path += '/'; return path + header; @@ -3200,9 +3150,9 @@ static std::string getIncludePathFileName(const std::string &includePath, const static std::string openHeaderIncludePath(std::ifstream &f, const simplecpp::DUI &dui, const std::string &header) { for (std::list::const_iterator it = dui.includePaths.begin(); it != dui.includePaths.end(); ++it) { - std::string path = openHeader(f, getIncludePathFileName(*it, header)); - if (!path.empty()) - return path; + std::string simplePath = openHeader(f, getIncludePathFileName(*it, header)); + if (!simplePath.empty()) + return simplePath; } return ""; } @@ -3212,76 +3162,49 @@ static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const if (isAbsolutePath(header)) return openHeader(f, header); + std::string ret; + if (systemheader) { - // always return absolute path for systemheaders - return toAbsolutePath(openHeaderIncludePath(f, dui, header)); + ret = openHeaderIncludePath(f, dui, header); + return ret; } - std::string ret; - ret = openHeaderRelative(f, sourcefile, header); if (ret.empty()) - return toAbsolutePath(openHeaderIncludePath(f, dui, header));// in a similar way to system headers + return openHeaderIncludePath(f, dui, header); return ret; } -static std::string findPathInMapBothRelativeAndAbsolute(const std::map &filedata, const std::string& path) { - // here there are two possibilities - either we match this from absolute path or from a relative one - if (filedata.find(path) != filedata.end()) {// try first to respect the exact match - return path; - } - // otherwise - try to use the normalize to the correct representation - if (isAbsolutePath(path)) { - const std::pair relativeExtractedResult = extractRelativePathFromAbsolute(path); - if (relativeExtractedResult.second) { - const std::string relativePath = relativeExtractedResult.first; - if (filedata.find(relativePath) != filedata.end()) { - return relativePath; - } - } - } else { - const std::string absolutePath = toAbsolutePath(path); - if (filedata.find(absolutePath) != filedata.end()) - return absolutePath; - } - // otherwise - return ""; -} - -static std::string getFileIdPath(const std::map &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader) +static std::string getFileName(const std::map &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader) { if (filedata.empty()) { return ""; } if (isAbsolutePath(header)) { - const std::string simplifiedHeaderPath = simplecpp::simplifyPath(header); - return (filedata.find(simplifiedHeaderPath) != filedata.end()) ? simplifiedHeaderPath : ""; + return (filedata.find(header) != filedata.end()) ? simplecpp::simplifyPath(header) : ""; } if (!systemheader) { - const std::string relativeOrAbsoluteFilename = getRelativeFileName(sourcefile, header);// unknown if absolute or relative, but always simplified - const std::string match = findPathInMapBothRelativeAndAbsolute(filedata, relativeOrAbsoluteFilename); - if (!match.empty()) { - return match; - } + const std::string relativeFilename = getRelativeFileName(sourcefile, header); + if (filedata.find(relativeFilename) != filedata.end()) + return relativeFilename; } for (std::list::const_iterator it = dui.includePaths.begin(); it != dui.includePaths.end(); ++it) { - const std::string match = findPathInMapBothRelativeAndAbsolute(filedata, simplecpp::simplifyPath(getIncludePathFileName(*it, header))); - if (!match.empty()) { - return match; - } + std::string s = simplecpp::simplifyPath(getIncludePathFileName(*it, header)); + if (filedata.find(s) != filedata.end()) + return s; } if (systemheader && filedata.find(header) != filedata.end()) - return header;// system header that its file wasn't found in the included paths but alreasy in the filedata - return this as is + return header; return ""; } static bool hasFile(const std::map &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader) { - return !getFileIdPath(filedata, sourcefile, header, dui, systemheader).empty(); + return !getFileName(filedata, sourcefile, header, dui, systemheader).empty(); } std::map simplecpp::load(const simplecpp::TokenList &rawtokens, std::vector &filenames, const simplecpp::DUI &dui, simplecpp::OutputList *outputList) @@ -3637,7 +3560,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const bool systemheader = (inctok->str()[0] == '<'); const std::string header(realFilename(inctok->str().substr(1U, inctok->str().size() - 2U))); - std::string header2 = getFileIdPath(filedata, rawtok->location.file(), header, dui, systemheader); + std::string header2 = getFileName(filedata, rawtok->location.file(), header, dui, systemheader); if (header2.empty()) { // try to load file.. std::ifstream f; diff --git a/testutils.py b/testutils.py deleted file mode 100644 index 55a2686d..00000000 --- a/testutils.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -import subprocess -import json - -def __run_subprocess(args, env=None, cwd=None, timeout=None): - p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd) - - try: - stdout, stderr = p.communicate(timeout=timeout) - return_code = p.returncode - p = None - except subprocess.TimeoutExpired: - import psutil - # terminate all the child processes - child_procs = psutil.Process(p.pid).children(recursive=True) - if len(child_procs) > 0: - for child in child_procs: - child.terminate() - try: - # call with timeout since it might be stuck - p.communicate(timeout=5) - p = None - except subprocess.TimeoutExpired: - pass - raise - finally: - if p: - # sending the signal to the process groups causes the parent Python process to terminate as well - #os.killpg(os.getpgid(p.pid), signal.SIGTERM) # Send the signal to all the process groups - p.terminate() - stdout, stderr = p.communicate() - p = None - - stdout = stdout.decode(encoding='utf-8', errors='ignore').replace('\r\n', '\n') - stderr = stderr.decode(encoding='utf-8', errors='ignore').replace('\r\n', '\n') - - return return_code, stdout, stderr - -def simplecpp(args = [], cwd = None): - dir_path = os.path.dirname(os.path.realpath(__file__)) - if 'SIMPLECPP_EXE_PATH' in os.environ: - simplecpp_path = os.environ['SIMPLECPP_EXE_PATH'] - else: - simplecpp_path = os.path.join(dir_path, "simplecpp") - return __run_subprocess([simplecpp_path] + args, cwd = cwd) - -def quoted_string(s): - return json.dumps(str(s)) - -def format_include_path_arg(include_path): - return f"-I{str(include_path)}" - -def format_include(include, is_sys_header=False): - if is_sys_header: - return f"<{quoted_string(include)[1:-1]}>" - else: - return quoted_string(include) From 09a816319ca6ccc89f45b4e883db07276d19c295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Wed, 12 Feb 2025 19:54:55 +0100 Subject: [PATCH 013/119] Revert "Fix #404: simplecpp::TokenList::constFold does not fold '( 0 ) && 10 < X' properly (#405)" (#416) --- simplecpp.cpp | 98 ++++++++++++++++++--------------------------------- simplecpp.h | 2 -- test.cpp | 26 -------------- 3 files changed, 34 insertions(+), 92 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 1ae47f5e..2316c42b 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -947,7 +947,7 @@ void simplecpp::TokenList::constFold() constFoldQuestionOp(&tok); // If there is no '(' we are done with the constant folding - if (!tok || tok->op != '(') + if (tok->op != '(') break; if (!tok->next || !tok->next->next || tok->next->next->op != ')') @@ -1157,7 +1157,10 @@ void simplecpp::TokenList::constFoldMulDivRem(Token *tok) } else continue; - simpleSquash(tok, toString(result)); + tok = tok->previous; + tok->setstr(toString(result)); + deleteToken(tok->next); + deleteToken(tok->next); } } @@ -1177,7 +1180,10 @@ void simplecpp::TokenList::constFoldAddSub(Token *tok) else continue; - simpleSquash(tok, toString(result)); + tok = tok->previous; + tok->setstr(toString(result)); + deleteToken(tok->next); + deleteToken(tok->next); } } @@ -1197,7 +1203,10 @@ void simplecpp::TokenList::constFoldShift(Token *tok) else continue; - simpleSquash(tok, toString(result)); + tok = tok->previous; + tok->setstr(toString(result)); + deleteToken(tok->next); + deleteToken(tok->next); } } @@ -1231,7 +1240,10 @@ void simplecpp::TokenList::constFoldComparison(Token *tok) else continue; - simpleSquash(tok, toString(result)); + tok = tok->previous; + tok->setstr(toString(result)); + deleteToken(tok->next); + deleteToken(tok->next); } } @@ -1263,51 +1275,12 @@ void simplecpp::TokenList::constFoldBitwise(Token *tok) result = (stringToLL(tok->previous->str()) ^ stringToLL(tok->next->str())); else /*if (*op == '|')*/ result = (stringToLL(tok->previous->str()) | stringToLL(tok->next->str())); - simpleSquash(tok, toString(result)); - } - } -} - -void simplecpp::TokenList::simpleSquash(Token *&tok, const std::string & result) -{ - tok = tok->previous; - tok->setstr(result); - deleteToken(tok->next); - deleteToken(tok->next); -} - -void simplecpp::TokenList::squashTokens(Token *&tok, const std::set & breakPoints, bool forwardDirection, const std::string & result) -{ - const char * const brackets = forwardDirection ? "()" : ")("; - Token* Token::* const step = forwardDirection ? &Token::next : &Token::previous; - int skip = 0; - const Token * const tok1 = tok->*step; - while (tok1 && tok1->*step) { - if ((tok1->*step)->op == brackets[1]){ - if (skip) { - --skip; - deleteToken(tok1->*step); - } else - break; - } else if ((tok1->*step)->op == brackets[0]) { - ++skip; - deleteToken(tok1->*step); - } else if (skip) { - deleteToken(tok1->*step); - } else if (breakPoints.count((tok1->*step)->str()) != 0) { - break; - } else { - deleteToken(tok1->*step); + tok = tok->previous; + tok->setstr(toString(result)); + deleteToken(tok->next); + deleteToken(tok->next); } } - simpleSquash(tok, result); -} - -static simplecpp::Token * constFoldGetOperand(simplecpp::Token * tok, bool forwardDirection) -{ - simplecpp::Token* simplecpp::Token::* const step = forwardDirection ? &simplecpp::Token::next : &simplecpp::Token::previous; - const char bracket = forwardDirection ? ')' : '('; - return tok->*step && (tok->*step)->number && (!((tok->*step)->*step) || (((tok->*step)->*step)->op == bracket)) ? tok->*step : nullptr; } static const std::string AND("and"); @@ -1323,24 +1296,21 @@ void simplecpp::TokenList::constFoldLogicalOp(Token *tok) } if (tok->str() != "&&" && tok->str() != "||") continue; - const Token* const lhs = constFoldGetOperand(tok, false); - const Token* const rhs = constFoldGetOperand(tok, true); - if (!lhs) // if lhs is not a single number we don't need to fold + if (!tok->previous || !tok->previous->number) + continue; + if (!tok->next || !tok->next->number) continue; - std::set breakPoints; - breakPoints.insert(":"); - breakPoints.insert("?"); - if (tok->str() == "||"){ - if (stringToLL(lhs->str()) != 0LL || (rhs && stringToLL(rhs->str()) != 0LL)) - squashTokens(tok, breakPoints, stringToLL(lhs->str()) != 0LL, toString(1)); - } else /*if (tok->str() == "&&")*/ { - breakPoints.insert("||"); - if (stringToLL(lhs->str()) == 0LL || (rhs && stringToLL(rhs->str()) == 0LL)) - squashTokens(tok, breakPoints, stringToLL(lhs->str()) == 0LL, toString(0)); - else if (rhs && stringToLL(lhs->str()) && stringToLL(rhs->str())) - simpleSquash(tok, "1"); - } + 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())); + + tok = tok->previous; + tok->setstr(toString(result)); + deleteToken(tok->next); + deleteToken(tok->next); } } diff --git a/simplecpp.h b/simplecpp.h index 0be48306..f5c69593 100755 --- a/simplecpp.h +++ b/simplecpp.h @@ -301,8 +301,6 @@ namespace simplecpp { void constFoldLogicalOp(Token *tok); void constFoldQuestionOp(Token **tok1); - void simpleSquash(Token *&tok, const std::string & result); - void squashTokens(Token *&tok, const std::set & breakPoints, bool forwardDirection, const std::string & result); std::string readUntil(Stream &stream, const Location &location, char start, char end, OutputList *outputList); void lineDirective(unsigned int fileIndex, unsigned int line, Location *location); diff --git a/test.cpp b/test.cpp index 82b9e1c7..187b7ec6 100644 --- a/test.cpp +++ b/test.cpp @@ -452,15 +452,6 @@ 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 ) && 10 < X")); - ASSERT_EQUALS("0", testConstFold("1+2*(3+4) && 7 - 7")); - ASSERT_EQUALS("1", testConstFold("( 1 ) || 10 < X")); - ASSERT_EQUALS("1", testConstFold("1+2*(3+4) || 8 - 7")); - ASSERT_EQUALS("X && 0", testConstFold("X && 0")); - ASSERT_EQUALS("X >= 0 || 0 < Y", testConstFold("X >= 0 || 0 < Y")); - ASSERT_EQUALS("X && 1 && Z", testConstFold("X && (1 || Y) && Z")); - ASSERT_EQUALS("0 || Y", testConstFold("0 && X || Y")); - ASSERT_EQUALS("X > 0 && Y", testConstFold("X > 0 && Y")); } #ifdef __CYGWIN__ @@ -1617,22 +1608,6 @@ static void ifA() ASSERT_EQUALS("\nX", preprocess(code, dui)); } -static void ifXorY() -{ - const char code[] = "#if Z > 0 || 0 < Y\n" - "X\n" - "#endif"; - ASSERT_EQUALS("", preprocess(code)); - - simplecpp::DUI dui; - dui.defines.push_back("Z=1"); - ASSERT_EQUALS("\nX", preprocess(code, dui)); - - dui.defines.clear(); - dui.defines.push_back("Y=15"); - ASSERT_EQUALS("\nX", preprocess(code, dui)); -} - static void ifCharLiteral() { const char code[] = "#if ('A'==0x41)\n" @@ -3151,7 +3126,6 @@ int main(int argc, char **argv) TEST_CASE(ifdef2); TEST_CASE(ifndef); TEST_CASE(ifA); - TEST_CASE(ifXorY); TEST_CASE(ifCharLiteral); TEST_CASE(ifDefined); TEST_CASE(ifDefinedNoPar); From 78f0f7cef4d84ffea31af2b810f6778b24f4ea91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 18 Mar 2025 14:51:24 +0100 Subject: [PATCH 014/119] clang-tidy.yml: updated to Clang 20 (#388) --- .github/workflows/clang-tidy.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 22602075..9a4f43c9 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -21,19 +21,19 @@ jobs: run: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh - sudo ./llvm.sh 19 - sudo apt-get install clang-tidy-19 + sudo ./llvm.sh 20 + sudo apt-get install clang-tidy-20 - name: Verify clang-tidy configuration run: | - clang-tidy-19 --verify-config + clang-tidy-20 --verify-config - name: Prepare CMake run: | cmake -S . -B cmake.output -G "Unix Makefiles" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DDISABLE_CPP03_SYNTAX_CHECK=ON env: - CXX: clang-19 + CXX: clang-20 - name: Clang-Tidy run: | - run-clang-tidy-19 -q -j $(nproc) -p=cmake.output + run-clang-tidy-20 -q -j $(nproc) -p=cmake.output From 0c8867436fca46820b00cd36cfceebf6f48b56ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sun, 13 Apr 2025 14:50:09 +0200 Subject: [PATCH 015/119] CI-unixish.yml: removed `ubuntu-20.04` (#425) --- .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 050e1d74..46f3dc02 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: compiler: [clang++, g++] - os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04, macos-13, macos-14] + os: [ubuntu-22.04, ubuntu-24.04, macos-13, macos-14] fail-fast: false runs-on: ${{ matrix.os }} From 6adb70f125439a97af6d0136e78de482c3799f26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 6 May 2025 10:49:50 +0200 Subject: [PATCH 016/119] CI-windows.yml: removed `windows-2019` and added `windows-2025` (#426) --- .github/workflows/CI-windows.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 1f78876d..37ebf4fc 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -15,7 +15,7 @@ jobs: build: strategy: matrix: - os: [windows-2019, windows-2022] + os: [windows-2022, windows-2025] config: [Release, Debug] fail-fast: false @@ -27,13 +27,7 @@ jobs: - name: Setup msbuild.exe uses: microsoft/setup-msbuild@v2 - - name: Run cmake - if: matrix.os == 'windows-2019' - run: | - cmake -G "Visual Studio 16 2019" -A x64 . || exit /b !errorlevel! - - - name: Run cmake - if: matrix.os == 'windows-2022' + - name: Run CMake run: | cmake -G "Visual Studio 17 2022" -A x64 . || exit /b !errorlevel! From 62afdd0b754dbbeba1d2c34cae172786da1e201b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 6 May 2025 17:17:14 +0200 Subject: [PATCH 017/119] CI-unixish.yml: added `macos-15` (#407) --- .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 46f3dc02..7e962a47 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: compiler: [clang++, g++] - os: [ubuntu-22.04, ubuntu-24.04, macos-13, macos-14] + os: [ubuntu-22.04, ubuntu-24.04, macos-13, macos-14, macos-15] fail-fast: false runs-on: ${{ matrix.os }} From 07757314899f0a59d4bdb480015f21789a1db9be Mon Sep 17 00:00:00 2001 From: Tal500 Date: Fri, 9 May 2025 16:40:01 +0300 Subject: [PATCH 018/119] Fix relative paths, again (#418) Co-authored-by: Tal Hadad --- .github/workflows/CI-unixish.yml | 16 +++- .github/workflows/CI-windows.yml | 18 ++++- .gitignore | 3 + integration_test.py | 94 ++++++++++++++++++++++ simplecpp.cpp | 134 ++++++++++++++++++++++++------- testutils.py | 57 +++++++++++++ 6 files changed, 292 insertions(+), 30 deletions(-) create mode 100644 integration_test.py create mode 100644 testutils.py diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index 7e962a47..c84fc052 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -30,7 +30,17 @@ jobs: run: | sudo apt-get update sudo apt-get install libc++-18-dev - + + - name: Install missing software on macos + if: contains(matrix.os, 'macos') + run: | + brew install python3 + + - name: Install missing Python packages + run: | + python3 -m pip config set global.break-system-packages true + python3 -m pip install pytest + - name: make simplecpp run: make -j$(nproc) @@ -41,6 +51,10 @@ jobs: run: | make -j$(nproc) selfcheck + - name: integration test + run: | + python3 -m pytest integration_test.py + - name: Run CMake run: | cmake -S . -B cmake.output diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 37ebf4fc..3e017182 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -26,7 +26,18 @@ jobs: - name: Setup msbuild.exe uses: microsoft/setup-msbuild@v2 - + + - name: Set up Python 3.13 + uses: actions/setup-python@v5 + with: + python-version: '3.13' + check-latest: true + + - name: Install missing Python packages + run: | + python -m pip install pip --upgrade || exit /b !errorlevel! + python -m pip install pytest || exit /b !errorlevel! + - name: Run CMake run: | cmake -G "Visual Studio 17 2022" -A x64 . || exit /b !errorlevel! @@ -42,4 +53,9 @@ jobs: - name: Selfcheck run: | .\${{ matrix.config }}\simplecpp.exe simplecpp.cpp -e || exit /b !errorlevel! + + - name: integration test + run: | + set SIMPLECPP_EXE_PATH=.\${{ matrix.config }}\simplecpp.exe + python -m pytest integration_test.py || exit /b !errorlevel! diff --git a/.gitignore b/.gitignore index 183545f1..113cf360 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ testrunner # CLion /.idea /cmake-build-* + +# python +__pycache__/ diff --git a/integration_test.py b/integration_test.py new file mode 100644 index 00000000..0b2b0b38 --- /dev/null +++ b/integration_test.py @@ -0,0 +1,94 @@ +## test with python -m pytest integration_test.py + +import os +import pytest +from testutils import simplecpp, format_include_path_arg, format_include + +def __test_relative_header_create_header(dir, with_pragma_once=True): + header_file = os.path.join(dir, 'test.h') + with open(header_file, 'wt') as f: + f.write(f""" + {"#pragma once" if with_pragma_once else ""} + #ifndef TEST_H_INCLUDED + #define TEST_H_INCLUDED + #else + #error header_was_already_included + #endif + """) + 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): + if inv: + return __test_relative_header_create_source(dir, include1=include2, include2=include1, is_include1_sys=is_include2_sys, is_include2_sys=is_include1_sys) + ## otherwise + + src_file = os.path.join(dir, 'test.c') + with open(src_file, 'wt') as f: + f.write(f""" + #undef TEST_H_INCLUDED + #include {format_include(include1, is_include1_sys)} + #include {format_include(include2, is_include2_sys)} + """) + return src_file + +@pytest.mark.parametrize("with_pragma_once", (False, True)) +@pytest.mark.parametrize("is_sys", (False, True)) +def test_relative_header_1(tmpdir, with_pragma_once, is_sys): + _, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once) + + test_file = __test_relative_header_create_source(tmpdir, "test.h", "test.h", is_include1_sys=is_sys, is_include2_sys=is_sys) + + args = ([format_include_path_arg(tmpdir)] if is_sys else []) + [test_file] + + _, _, stderr = simplecpp(args, cwd=tmpdir) + + if with_pragma_once: + assert stderr == '' + else: + assert double_include_error in stderr + +@pytest.mark.parametrize("inv", (False, True)) +def test_relative_header_2(tmpdir, inv): + header_file, _ = __test_relative_header_create_header(tmpdir) + + test_file = __test_relative_header_create_source(tmpdir, "test.h", header_file, inv=inv) + + args = [test_file] + + _, _, stderr = simplecpp(args, cwd=tmpdir) + assert stderr == '' + +@pytest.mark.parametrize("is_sys", (False, True)) +@pytest.mark.parametrize("inv", (False, True)) +def test_relative_header_3(tmpdir, is_sys, inv): + test_subdir = os.path.join(tmpdir, "test_subdir") + os.mkdir(test_subdir) + header_file, _ = __test_relative_header_create_header(test_subdir) + + test_file = __test_relative_header_create_source(tmpdir, "test_subdir/test.h", header_file, is_include1_sys=is_sys, inv=inv) + + args = [test_file] + + _, _, stderr = simplecpp(args, cwd=tmpdir) + + if is_sys: + assert "missing header: Header not found" in stderr + else: + assert stderr == '' + +@pytest.mark.parametrize("use_short_path", (False, True)) +@pytest.mark.parametrize("is_sys", (False, True)) +@pytest.mark.parametrize("inv", (False, True)) +def test_relative_header_4(tmpdir, use_short_path, is_sys, inv): + test_subdir = os.path.join(tmpdir, "test_subdir") + os.mkdir(test_subdir) + header_file, _ = __test_relative_header_create_header(test_subdir) + if use_short_path: + header_file = "test_subdir/test.h" + + test_file = __test_relative_header_create_source(tmpdir, header_file, "test.h", is_include2_sys=is_sys, inv=inv) + + args = [format_include_path_arg(test_subdir), test_file] + + _, _, stderr = simplecpp(args, cwd=tmpdir) + assert stderr == '' diff --git a/simplecpp.cpp b/simplecpp.cpp index 2316c42b..25c4124a 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -40,6 +40,12 @@ #include #include +#ifdef _WIN32 +#include +#else +#include +#endif + #ifdef SIMPLECPP_WINDOWS #include #undef ERROR @@ -147,6 +153,12 @@ static unsigned long long stringToULL(const std::string &s) return ret; } +// 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 startsWith_(const std::string &s, const std::string &p) +{ + return (s.size() >= p.size()) && std::equal(p.begin(), p.end(), s.begin()); +} + static bool endsWith(const std::string &s, const std::string &e) { return (s.size() >= e.size()) && std::equal(e.rbegin(), e.rend(), s.rbegin()); @@ -2334,17 +2346,12 @@ namespace simplecpp { namespace simplecpp { #ifdef __CYGWIN__ - bool startsWith(const std::string &str, const std::string &s) - { - return (str.size() >= s.size() && str.compare(0, s.size(), s) == 0); - } - 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) { @@ -2681,6 +2688,47 @@ static bool isCpp17OrLater(const simplecpp::DUI &dui) return !std_ver.empty() && (std_ver >= "201703L"); } + +static std::string currentDirectoryOSCalc() { + const std::size_t size = 4096; + char currentPath[size]; + +#ifndef _WIN32 + if (getcwd(currentPath, size) != nullptr) +#else + if (_getcwd(currentPath, size) != nullptr) +#endif + return std::string(currentPath); + + return ""; +} + +static const std::string& currentDirectory() { + static const std::string curdir = simplecpp::simplifyPath(currentDirectoryOSCalc()); + return curdir; +} + +static std::string toAbsolutePath(const std::string& path) { + if (path.empty()) { + return path;// preserve error file path that is indicated by an empty string + } + if (!isAbsolutePath(path)) { + return simplecpp::simplifyPath(currentDirectory() + "/" + path); + } + // otherwise + return simplecpp::simplifyPath(path); +} + +static std::pair extractRelativePathFromAbsolute(const std::string& absolutepath) { + static const std::string prefix = currentDirectory() + "/"; + if (startsWith_(absolutepath, prefix)) { + const std::size_t size = prefix.size(); + return std::make_pair(absolutepath.substr(size, absolutepath.size() - size), true); + } + // otherwise + return std::make_pair("", false); +} + static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader); static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI &dui) { @@ -3099,9 +3147,12 @@ static std::string openHeader(std::ifstream &f, const std::string &path) static std::string getRelativeFileName(const std::string &sourcefile, const std::string &header) { + std::string path; if (sourcefile.find_first_of("\\/") != std::string::npos) - return simplecpp::simplifyPath(sourcefile.substr(0, sourcefile.find_last_of("\\/") + 1U) + header); - return simplecpp::simplifyPath(header); + path = sourcefile.substr(0, sourcefile.find_last_of("\\/") + 1U) + header; + else + path = header; + return simplecpp::simplifyPath(path); } static std::string openHeaderRelative(std::ifstream &f, const std::string &sourcefile, const std::string &header) @@ -3111,7 +3162,7 @@ static std::string openHeaderRelative(std::ifstream &f, const std::string &sourc static std::string getIncludePathFileName(const std::string &includePath, const std::string &header) { - std::string path = includePath; + std::string path = toAbsolutePath(includePath); if (!path.empty() && path[path.size()-1U]!='/' && path[path.size()-1U]!='\\') path += '/'; return path + header; @@ -3120,9 +3171,9 @@ static std::string getIncludePathFileName(const std::string &includePath, const static std::string openHeaderIncludePath(std::ifstream &f, const simplecpp::DUI &dui, const std::string &header) { for (std::list::const_iterator it = dui.includePaths.begin(); it != dui.includePaths.end(); ++it) { - std::string simplePath = openHeader(f, getIncludePathFileName(*it, header)); - if (!simplePath.empty()) - return simplePath; + std::string path = openHeader(f, getIncludePathFileName(*it, header)); + if (!path.empty()) + return path; } return ""; } @@ -3132,49 +3183,76 @@ static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const if (isAbsolutePath(header)) return openHeader(f, header); - std::string ret; - if (systemheader) { - ret = openHeaderIncludePath(f, dui, header); - return ret; + // always return absolute path for systemheaders + return toAbsolutePath(openHeaderIncludePath(f, dui, header)); } + std::string ret; + ret = openHeaderRelative(f, sourcefile, header); if (ret.empty()) - return openHeaderIncludePath(f, dui, header); + return toAbsolutePath(openHeaderIncludePath(f, dui, header));// in a similar way to system headers return ret; } -static std::string getFileName(const std::map &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader) +static std::string findPathInMapBothRelativeAndAbsolute(const std::map &filedata, const std::string& path) { + // here there are two possibilities - either we match this from absolute path or from a relative one + if (filedata.find(path) != filedata.end()) {// try first to respect the exact match + return path; + } + // otherwise - try to use the normalize to the correct representation + if (isAbsolutePath(path)) { + const std::pair relativeExtractedResult = extractRelativePathFromAbsolute(path); + if (relativeExtractedResult.second) { + const std::string relativePath = relativeExtractedResult.first; + if (filedata.find(relativePath) != filedata.end()) { + return relativePath; + } + } + } else { + const std::string absolutePath = toAbsolutePath(path); + if (filedata.find(absolutePath) != filedata.end()) + return absolutePath; + } + // otherwise + return ""; +} + +static std::string getFileIdPath(const std::map &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader) { if (filedata.empty()) { return ""; } if (isAbsolutePath(header)) { - return (filedata.find(header) != filedata.end()) ? simplecpp::simplifyPath(header) : ""; + const std::string simplifiedHeaderPath = simplecpp::simplifyPath(header); + return (filedata.find(simplifiedHeaderPath) != filedata.end()) ? simplifiedHeaderPath : ""; } if (!systemheader) { - const std::string relativeFilename = getRelativeFileName(sourcefile, header); - if (filedata.find(relativeFilename) != filedata.end()) - return relativeFilename; + const std::string relativeOrAbsoluteFilename = getRelativeFileName(sourcefile, header);// unknown if absolute or relative, but always simplified + const std::string match = findPathInMapBothRelativeAndAbsolute(filedata, relativeOrAbsoluteFilename); + if (!match.empty()) { + return match; + } } for (std::list::const_iterator it = dui.includePaths.begin(); it != dui.includePaths.end(); ++it) { - std::string s = simplecpp::simplifyPath(getIncludePathFileName(*it, header)); - if (filedata.find(s) != filedata.end()) - return s; + const std::string match = findPathInMapBothRelativeAndAbsolute(filedata, simplecpp::simplifyPath(getIncludePathFileName(*it, header))); + if (!match.empty()) { + return match; + } } if (systemheader && filedata.find(header) != filedata.end()) - return header; + return header;// system header that its file wasn't found in the included paths but alreasy in the filedata - return this as is return ""; } static bool hasFile(const std::map &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader) { - return !getFileName(filedata, sourcefile, header, dui, systemheader).empty(); + return !getFileIdPath(filedata, sourcefile, header, dui, systemheader).empty(); } std::map simplecpp::load(const simplecpp::TokenList &rawtokens, std::vector &filenames, const simplecpp::DUI &dui, simplecpp::OutputList *outputList) @@ -3530,7 +3608,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const bool systemheader = (inctok->str()[0] == '<'); const std::string header(realFilename(inctok->str().substr(1U, inctok->str().size() - 2U))); - std::string header2 = getFileName(filedata, rawtok->location.file(), header, dui, systemheader); + std::string header2 = getFileIdPath(filedata, rawtok->location.file(), header, dui, systemheader); if (header2.empty()) { // try to load file.. std::ifstream f; diff --git a/testutils.py b/testutils.py new file mode 100644 index 00000000..55a2686d --- /dev/null +++ b/testutils.py @@ -0,0 +1,57 @@ +import os +import subprocess +import json + +def __run_subprocess(args, env=None, cwd=None, timeout=None): + p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd) + + try: + stdout, stderr = p.communicate(timeout=timeout) + return_code = p.returncode + p = None + except subprocess.TimeoutExpired: + import psutil + # terminate all the child processes + child_procs = psutil.Process(p.pid).children(recursive=True) + if len(child_procs) > 0: + for child in child_procs: + child.terminate() + try: + # call with timeout since it might be stuck + p.communicate(timeout=5) + p = None + except subprocess.TimeoutExpired: + pass + raise + finally: + if p: + # sending the signal to the process groups causes the parent Python process to terminate as well + #os.killpg(os.getpgid(p.pid), signal.SIGTERM) # Send the signal to all the process groups + p.terminate() + stdout, stderr = p.communicate() + p = None + + stdout = stdout.decode(encoding='utf-8', errors='ignore').replace('\r\n', '\n') + stderr = stderr.decode(encoding='utf-8', errors='ignore').replace('\r\n', '\n') + + return return_code, stdout, stderr + +def simplecpp(args = [], cwd = None): + dir_path = os.path.dirname(os.path.realpath(__file__)) + if 'SIMPLECPP_EXE_PATH' in os.environ: + simplecpp_path = os.environ['SIMPLECPP_EXE_PATH'] + else: + simplecpp_path = os.path.join(dir_path, "simplecpp") + return __run_subprocess([simplecpp_path] + args, cwd = cwd) + +def quoted_string(s): + return json.dumps(str(s)) + +def format_include_path_arg(include_path): + return f"-I{str(include_path)}" + +def format_include(include, is_sys_header=False): + if is_sys_header: + return f"<{quoted_string(include)[1:-1]}>" + else: + return quoted_string(include) From 2bbb496fe2bdc558c05fb51ce3de0b54023f9007 Mon Sep 17 00:00:00 2001 From: Tal500 Date: Fri, 16 May 2025 14:18:04 +0300 Subject: [PATCH 019/119] Preserve relativeness of included paths (w.r.t. current directory) (#428) Co-authored-by: Tal Hadad --- integration_test.py | 24 ++++++++++++++----- simplecpp.cpp | 56 +++++++++++++++++++++++++++------------------ 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/integration_test.py b/integration_test.py index 0b2b0b38..4fe0129b 100644 --- a/integration_test.py +++ b/integration_test.py @@ -1,6 +1,7 @@ ## test with python -m pytest integration_test.py import os +import pathlib import pytest from testutils import simplecpp, format_include_path_arg, format_include @@ -14,6 +15,7 @@ def __test_relative_header_create_header(dir, with_pragma_once=True): #else #error header_was_already_included #endif + const int dummy = 1; """) return header_file, "error: #error header_was_already_included" @@ -48,33 +50,43 @@ def test_relative_header_1(tmpdir, with_pragma_once, is_sys): assert double_include_error in stderr @pytest.mark.parametrize("inv", (False, True)) -def test_relative_header_2(tmpdir, inv): +@pytest.mark.parametrize("source_relative", (False, True)) +def test_relative_header_2(tmpdir, inv, source_relative): header_file, _ = __test_relative_header_create_header(tmpdir) test_file = __test_relative_header_create_source(tmpdir, "test.h", header_file, inv=inv) - args = [test_file] + args = ["test.c" if source_relative else test_file] - _, _, stderr = simplecpp(args, cwd=tmpdir) + _, stdout, stderr = simplecpp(args, cwd=tmpdir) assert stderr == '' + if source_relative and not inv: + assert '#line 8 "test.h"' in stdout + else: + assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout @pytest.mark.parametrize("is_sys", (False, True)) @pytest.mark.parametrize("inv", (False, True)) -def test_relative_header_3(tmpdir, is_sys, inv): +@pytest.mark.parametrize("source_relative", (False, True)) +def test_relative_header_3(tmpdir, is_sys, inv, source_relative): test_subdir = os.path.join(tmpdir, "test_subdir") os.mkdir(test_subdir) header_file, _ = __test_relative_header_create_header(test_subdir) test_file = __test_relative_header_create_source(tmpdir, "test_subdir/test.h", header_file, is_include1_sys=is_sys, inv=inv) - args = [test_file] + args = ["test.c" if source_relative else test_file] - _, _, stderr = simplecpp(args, cwd=tmpdir) + _, stdout, stderr = simplecpp(args, cwd=tmpdir) if is_sys: assert "missing header: Header not found" in stderr else: assert stderr == '' + if source_relative and not inv: + assert '#line 8 "test_subdir/test.h"' in stdout + else: + assert f'#line 8 "{pathlib.PurePath(test_subdir).as_posix()}/test.h"' in stdout @pytest.mark.parametrize("use_short_path", (False, True)) @pytest.mark.parametrize("is_sys", (False, True)) diff --git a/simplecpp.cpp b/simplecpp.cpp index 25c4124a..d1fa91bf 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3145,11 +3145,11 @@ static std::string openHeader(std::ifstream &f, const std::string &path) return ""; } -static std::string getRelativeFileName(const std::string &sourcefile, const std::string &header) +static std::string getRelativeFileName(const std::string &baseFile, const std::string &header) { std::string path; - if (sourcefile.find_first_of("\\/") != std::string::npos) - path = sourcefile.substr(0, sourcefile.find_last_of("\\/") + 1U) + header; + if (baseFile.find_first_of("\\/") != std::string::npos) + path = baseFile.substr(0, baseFile.find_last_of("\\/") + 1U) + header; else path = header; return simplecpp::simplifyPath(path); @@ -3160,12 +3160,22 @@ static std::string openHeaderRelative(std::ifstream &f, const std::string &sourc return openHeader(f, getRelativeFileName(sourcefile, header)); } +// returns the simplified header path: +// * If the header path is absolute, returns it in absolute path +// * Otherwise, returns it in relative path with respect to the current directory static std::string getIncludePathFileName(const std::string &includePath, const std::string &header) { - std::string path = toAbsolutePath(includePath); - if (!path.empty() && path[path.size()-1U]!='/' && path[path.size()-1U]!='\\') - path += '/'; - return path + header; + std::string simplifiedHeader = simplecpp::simplifyPath(header); + + if (isAbsolutePath(simplifiedHeader)) { + return simplifiedHeader; + } + + std::string basePath = toAbsolutePath(includePath); + if (!basePath.empty() && basePath[basePath.size()-1U]!='/' && basePath[basePath.size()-1U]!='\\') + basePath += '/'; + const std::string absolutesimplifiedHeaderPath = basePath + simplifiedHeader; + return extractRelativePathFromAbsolute(absolutesimplifiedHeaderPath).first; } static std::string openHeaderIncludePath(std::ifstream &f, const simplecpp::DUI &dui, const std::string &header) @@ -3183,17 +3193,16 @@ static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const if (isAbsolutePath(header)) return openHeader(f, header); - if (systemheader) { - // always return absolute path for systemheaders - return toAbsolutePath(openHeaderIncludePath(f, dui, header)); + // prefer first to search the header relatively to source file if found, when not a system header + if (!systemheader) { + std::string relativeHeader = openHeaderRelative(f, sourcefile, header); + if (!relativeHeader.empty()) { + return relativeHeader; + } } - std::string ret; - - ret = openHeaderRelative(f, sourcefile, header); - if (ret.empty()) - return toAbsolutePath(openHeaderIncludePath(f, dui, header));// in a similar way to system headers - return ret; + // search the header on the include paths (provided by the flags "-I...") + return openHeaderIncludePath(f, dui, header); } static std::string findPathInMapBothRelativeAndAbsolute(const std::map &filedata, const std::string& path) { @@ -3212,8 +3221,9 @@ static std::string findPathInMapBothRelativeAndAbsolute(const std::map::const_iterator it = dui.includePaths.begin(); it != dui.includePaths.end(); ++it) { - const std::string match = findPathInMapBothRelativeAndAbsolute(filedata, simplecpp::simplifyPath(getIncludePathFileName(*it, header))); + const std::string match = findPathInMapBothRelativeAndAbsolute(filedata, getIncludePathFileName(*it, header)); if (!match.empty()) { return match; } } - if (systemheader && filedata.find(header) != filedata.end()) - return header;// system header that its file wasn't found in the included paths but alreasy in the filedata - return this as is - return ""; } From 76df97f64b2906d01587b379511c2d9836568c87 Mon Sep 17 00:00:00 2001 From: Tal500 Date: Fri, 30 May 2025 17:44:43 +0300 Subject: [PATCH 020/119] fix: parent relative paths, and rework on the whole path extraction mechanics (#429) --- .github/workflows/CI-unixish.yml | 2 +- .github/workflows/CI-windows.yml | 2 +- integration_test.py | 103 ++++++++++++++++++++++++++----- simplecpp.cpp | 98 ++++++++++++++++++++--------- 4 files changed, 159 insertions(+), 46 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index c84fc052..f5a78ea5 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -53,7 +53,7 @@ jobs: - name: integration test run: | - python3 -m pytest integration_test.py + python3 -m pytest integration_test.py -vv - name: Run CMake run: | diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 3e017182..971f3827 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -57,5 +57,5 @@ jobs: - name: integration test run: | set SIMPLECPP_EXE_PATH=.\${{ matrix.config }}\simplecpp.exe - python -m pytest integration_test.py || exit /b !errorlevel! + python -m pytest integration_test.py -vv || exit /b !errorlevel! diff --git a/integration_test.py b/integration_test.py index 4fe0129b..122ce9aa 100644 --- a/integration_test.py +++ b/integration_test.py @@ -35,40 +35,48 @@ def __test_relative_header_create_source(dir, include1, include2, is_include1_sy @pytest.mark.parametrize("with_pragma_once", (False, True)) @pytest.mark.parametrize("is_sys", (False, True)) -def test_relative_header_1(tmpdir, with_pragma_once, is_sys): +def test_relative_header_1(record_property, tmpdir, with_pragma_once, is_sys): _, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once) test_file = __test_relative_header_create_source(tmpdir, "test.h", "test.h", is_include1_sys=is_sys, is_include2_sys=is_sys) args = ([format_include_path_arg(tmpdir)] if is_sys else []) + [test_file] - _, _, stderr = simplecpp(args, cwd=tmpdir) + _, stdout, stderr = simplecpp(args, cwd=tmpdir) + record_property("stdout", stdout) + record_property("stderr", stderr) if with_pragma_once: assert stderr == '' else: assert double_include_error in stderr +@pytest.mark.parametrize("with_pragma_once", (False, True)) @pytest.mark.parametrize("inv", (False, True)) @pytest.mark.parametrize("source_relative", (False, True)) -def test_relative_header_2(tmpdir, inv, source_relative): - header_file, _ = __test_relative_header_create_header(tmpdir) +def test_relative_header_2(record_property, tmpdir, with_pragma_once, inv, source_relative): + header_file, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once) test_file = __test_relative_header_create_source(tmpdir, "test.h", header_file, inv=inv) args = ["test.c" if source_relative else test_file] _, stdout, stderr = simplecpp(args, cwd=tmpdir) - assert stderr == '' - if source_relative and not inv: - assert '#line 8 "test.h"' in stdout + record_property("stdout", stdout) + record_property("stderr", stderr) + if with_pragma_once: + assert stderr == '' + if inv: + assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout + else: + assert '#line 8 "test.h"' in stdout else: - assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout + assert double_include_error in stderr @pytest.mark.parametrize("is_sys", (False, True)) @pytest.mark.parametrize("inv", (False, True)) @pytest.mark.parametrize("source_relative", (False, True)) -def test_relative_header_3(tmpdir, is_sys, inv, source_relative): +def test_relative_header_3(record_property, tmpdir, is_sys, inv, source_relative): test_subdir = os.path.join(tmpdir, "test_subdir") os.mkdir(test_subdir) header_file, _ = __test_relative_header_create_header(test_subdir) @@ -78,20 +86,23 @@ def test_relative_header_3(tmpdir, is_sys, inv, source_relative): args = ["test.c" if source_relative else test_file] _, stdout, stderr = simplecpp(args, cwd=tmpdir) + record_property("stdout", stdout) + record_property("stderr", stderr) if is_sys: assert "missing header: Header not found" in stderr else: assert stderr == '' - if source_relative and not inv: - assert '#line 8 "test_subdir/test.h"' in stdout - else: + if inv: assert f'#line 8 "{pathlib.PurePath(test_subdir).as_posix()}/test.h"' in stdout + else: + assert '#line 8 "test_subdir/test.h"' in stdout @pytest.mark.parametrize("use_short_path", (False, True)) +@pytest.mark.parametrize("relative_include_dir", (False, True)) @pytest.mark.parametrize("is_sys", (False, True)) @pytest.mark.parametrize("inv", (False, True)) -def test_relative_header_4(tmpdir, use_short_path, is_sys, inv): +def test_relative_header_4(record_property, tmpdir, use_short_path, relative_include_dir, is_sys, inv): test_subdir = os.path.join(tmpdir, "test_subdir") os.mkdir(test_subdir) header_file, _ = __test_relative_header_create_header(test_subdir) @@ -100,7 +111,69 @@ def test_relative_header_4(tmpdir, use_short_path, is_sys, inv): test_file = __test_relative_header_create_source(tmpdir, header_file, "test.h", is_include2_sys=is_sys, inv=inv) - args = [format_include_path_arg(test_subdir), test_file] + args = [format_include_path_arg("test_subdir" if relative_include_dir else test_subdir), test_file] - _, _, stderr = simplecpp(args, cwd=tmpdir) + _, stdout, stderr = simplecpp(args, cwd=tmpdir) + record_property("stdout", stdout) + record_property("stderr", stderr) assert stderr == '' + if (use_short_path and not inv) or (relative_include_dir and inv): + assert '#line 8 "test_subdir/test.h"' in stdout + else: + assert f'#line 8 "{pathlib.PurePath(test_subdir).as_posix()}/test.h"' in stdout + +@pytest.mark.parametrize("with_pragma_once", (False, True)) +@pytest.mark.parametrize("relative_include_dir", (False, True)) +@pytest.mark.parametrize("is_sys", (False, True)) +@pytest.mark.parametrize("inv", (False, True)) +def test_relative_header_5(record_property, tmpdir, with_pragma_once, relative_include_dir, is_sys, inv): # test relative paths with .. + ## in this test, the subdir role is the opposite then the previous - it contains the test.c file, while the parent tmpdir contains the header file + header_file, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once) + if is_sys: + header_file_second_path = "test.h" + else: + header_file_second_path = "../test.h" + + test_subdir = os.path.join(tmpdir, "test_subdir") + os.mkdir(test_subdir) + test_file = __test_relative_header_create_source(test_subdir, header_file, header_file_second_path, is_include2_sys=is_sys, inv=inv) + + args = ([format_include_path_arg(".." if relative_include_dir else tmpdir)] if is_sys else []) + ["test.c"] + + _, stdout, stderr = simplecpp(args, cwd=test_subdir) + record_property("stdout", stdout) + record_property("stderr", stderr) + if with_pragma_once: + assert stderr == '' + if (relative_include_dir or not is_sys) and inv: + assert '#line 8 "../test.h"' in stdout + else: + assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout + else: + assert double_include_error in stderr + +@pytest.mark.parametrize("with_pragma_once", (False, True)) +@pytest.mark.parametrize("relative_include_dir", (False, True)) +@pytest.mark.parametrize("is_sys", (False, True)) +@pytest.mark.parametrize("inv", (False, True)) +def test_relative_header_6(record_property, tmpdir, with_pragma_once, relative_include_dir, is_sys, inv): # test relative paths with .. that is resolved only by an include dir + ## in this test, both the header and the source file are at the same dir, but there is a dummy inclusion dir as a subdir + header_file, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once) + + test_subdir = os.path.join(tmpdir, "test_subdir") + os.mkdir(test_subdir) + test_file = __test_relative_header_create_source(tmpdir, header_file, "../test.h", is_include2_sys=is_sys, inv=inv) + + args = [format_include_path_arg("test_subdir" if relative_include_dir else test_subdir), "test.c"] + + _, stdout, stderr = simplecpp(args, cwd=tmpdir) + record_property("stdout", stdout) + record_property("stderr", stderr) + if with_pragma_once: + assert stderr == '' + if relative_include_dir and inv: + assert '#line 8 "test.h"' in stdout + else: + assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout + else: + assert double_include_error in stderr diff --git a/simplecpp.cpp b/simplecpp.cpp index d1fa91bf..9ff66d8b 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2719,14 +2719,42 @@ static std::string toAbsolutePath(const std::string& path) { return simplecpp::simplifyPath(path); } -static std::pair extractRelativePathFromAbsolute(const std::string& absolutepath) { - static const std::string prefix = currentDirectory() + "/"; - if (startsWith_(absolutepath, prefix)) { - const std::size_t size = prefix.size(); - return std::make_pair(absolutepath.substr(size, absolutepath.size() - size), true); +static std::string dirPath(const std::string& path, bool withTrailingSlash=true) { + const std::size_t lastSlash = path.find_last_of("\\/"); + if (lastSlash == std::string::npos) { + return ""; } - // otherwise - return std::make_pair("", false); + return path.substr(0, lastSlash + (withTrailingSlash ? 1U : 0U)); +} + +static std::string omitPathTrailingSlash(const std::string& path) { + if (endsWith(path, "/")) { + return path.substr(0, path.size() - 1U); + } + return path; +} + +static std::string extractRelativePathFromAbsolute(const std::string& absoluteSimplifiedPath, const std::string& prefixSimplifiedAbsoluteDir = currentDirectory()) { + const std::string normalizedAbsolutePath = omitPathTrailingSlash(absoluteSimplifiedPath); + std::string currentPrefix = omitPathTrailingSlash(prefixSimplifiedAbsoluteDir); + std::string leadingParenting; + while (!startsWith_(normalizedAbsolutePath, currentPrefix)) { + leadingParenting = "../" + leadingParenting; + currentPrefix = dirPath(currentPrefix, false); + } + const std::size_t size = currentPrefix.size(); + std::string relativeFromMeetingPath = normalizedAbsolutePath.substr(size, normalizedAbsolutePath.size() - size); + if (currentPrefix.empty() && !(startsWith_(absoluteSimplifiedPath, "/") && startsWith_(prefixSimplifiedAbsoluteDir, "/"))) { + // In the case that there is no common prefix path, + // and at not both of the paths start with `/` (can happen only in Windows paths on distinct partitions), + // return the absolute simplified path as is because no relative path can match. + return absoluteSimplifiedPath; + } + if (startsWith_(relativeFromMeetingPath, "/")) { + // omit the leading slash + relativeFromMeetingPath = relativeFromMeetingPath.substr(1, relativeFromMeetingPath.size()); + } + return leadingParenting + relativeFromMeetingPath; } static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader); @@ -3147,12 +3175,17 @@ static std::string openHeader(std::ifstream &f, const std::string &path) static std::string getRelativeFileName(const std::string &baseFile, const std::string &header) { - std::string path; - if (baseFile.find_first_of("\\/") != std::string::npos) - path = baseFile.substr(0, baseFile.find_last_of("\\/") + 1U) + header; - else - path = header; - return simplecpp::simplifyPath(path); + const std::string baseFileSimplified = simplecpp::simplifyPath(baseFile); + const std::string baseFileAbsolute = isAbsolutePath(baseFileSimplified) ? + baseFileSimplified : + simplecpp::simplifyPath(currentDirectory() + "/" + baseFileSimplified); + + const std::string headerSimplified = simplecpp::simplifyPath(header); + const std::string path = isAbsolutePath(headerSimplified) ? + headerSimplified : + simplecpp::simplifyPath(dirPath(baseFileAbsolute) + headerSimplified); + + return extractRelativePathFromAbsolute(path); } static std::string openHeaderRelative(std::ifstream &f, const std::string &sourcefile, const std::string &header) @@ -3174,8 +3207,9 @@ static std::string getIncludePathFileName(const std::string &includePath, const std::string basePath = toAbsolutePath(includePath); if (!basePath.empty() && basePath[basePath.size()-1U]!='/' && basePath[basePath.size()-1U]!='\\') basePath += '/'; - const std::string absolutesimplifiedHeaderPath = basePath + simplifiedHeader; - return extractRelativePathFromAbsolute(absolutesimplifiedHeaderPath).first; + const std::string absoluteSimplifiedHeaderPath = simplecpp::simplifyPath(basePath + simplifiedHeader); + // preserve absoluteness/relativieness of the including dir + return isAbsolutePath(includePath) ? absoluteSimplifiedHeaderPath : extractRelativePathFromAbsolute(absoluteSimplifiedHeaderPath); } static std::string openHeaderIncludePath(std::ifstream &f, const simplecpp::DUI &dui, const std::string &header) @@ -3210,22 +3244,18 @@ static std::string findPathInMapBothRelativeAndAbsolute(const std::map relativeExtractedResult = extractRelativePathFromAbsolute(path); - if (relativeExtractedResult.second) { - const std::string relativePath = relativeExtractedResult.first; - if (filedata.find(relativePath) != filedata.end()) { - return relativePath; - } - } + alternativePath = extractRelativePathFromAbsolute(simplecpp::simplifyPath(path)); } else { - const std::string absolutePath = toAbsolutePath(path); - if (filedata.find(absolutePath) != filedata.end()) { - return absolutePath; - } + alternativePath = toAbsolutePath(path); + } + + if (filedata.find(alternativePath) != filedata.end()) { + return alternativePath; } - // otherwise return ""; } @@ -3267,6 +3297,16 @@ static bool hasFile(const std::map &filedat return !getFileIdPath(filedata, sourcefile, header, dui, systemheader).empty(); } +static void safeInsertTokenListToMap(std::map &filedata, const std::string &header2, simplecpp::TokenList *tokens, const std::string &header, const std::string &sourcefile, bool systemheader, const char* contextDesc) +{ + const bool inserted = filedata.insert(std::make_pair(header2, tokens)).second; + if (!inserted) { + std::cerr << "error in " << contextDesc << " - attempt to add a tokenized file to the file map, but this file is already in the map! Details:" << + "header: " << header << " header2: " << header2 << " source: " << sourcefile << " systemheader: " << systemheader << std::endl; + std::abort(); + } +} + std::map simplecpp::load(const simplecpp::TokenList &rawtokens, std::vector &filenames, const simplecpp::DUI &dui, simplecpp::OutputList *outputList) { #ifdef SIMPLECPP_WINDOWS @@ -3343,7 +3383,7 @@ std::map simplecpp::load(const simplecpp::To TokenList *tokens = new TokenList(header2, filenames, outputList); if (dui.removeComments) tokens->removeComments(); - ret[header2] = tokens; + safeInsertTokenListToMap(ret, header2, tokens, header, rawtok->location.file(), systemheader, "simplecpp::load"); if (tokens->front()) filelist.push_back(tokens->front()); } @@ -3630,7 +3670,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL TokenList * const tokens = new TokenList(header2, files, outputList); if (dui.removeComments) tokens->removeComments(); - filedata[header2] = tokens; + safeInsertTokenListToMap(filedata, header2, tokens, header, rawtok->location.file(), systemheader, "simplecpp::preprocess"); } } if (header2.empty()) { From 9ba39709e34a8ccaee5c7afa0d87dd478eaba959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Thu, 12 Jun 2025 11:23:30 +0200 Subject: [PATCH 021/119] aligned GCC warnings with Cppcheck (#434) --- CMakeLists.txt | 19 ++++++++++++++++++- test.cpp | 6 +++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c3fcf4ba..672e63bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,24 @@ function(add_compile_options_safe FLAG) endfunction() if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") - add_compile_options(-Wall -Wextra -pedantic -Wcast-qual -Wfloat-equal -Wmissing-declarations -Wmissing-format-attribute -Wredundant-decls -Wshadow -Wundef -Wold-style-cast -Wno-multichar) + 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") add_compile_definitions(_CRT_SECURE_NO_WARNINGS) elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") diff --git a/test.cpp b/test.cpp index 187b7ec6..86e6ddc8 100644 --- a/test.cpp +++ b/test.cpp @@ -279,9 +279,9 @@ static void characterLiteral() #ifdef __GNUC__ // BEGIN Implementation-specific results - ASSERT_EQUALS(static_cast('AB'), simplecpp::characterLiteralToLL("'AB'")); - ASSERT_EQUALS(static_cast('ABC'), simplecpp::characterLiteralToLL("'ABC'")); - ASSERT_EQUALS(static_cast('ABCD'), simplecpp::characterLiteralToLL("'ABCD'")); + ASSERT_EQUALS('AB', simplecpp::characterLiteralToLL("'AB'")); + ASSERT_EQUALS('ABC', simplecpp::characterLiteralToLL("'ABC'")); + ASSERT_EQUALS('ABCD', simplecpp::characterLiteralToLL("'ABCD'")); ASSERT_EQUALS('\134t', simplecpp::characterLiteralToLL("'\\134t'")); // cppcheck ticket #7452 // END Implementation-specific results #endif From 6cc4f53f4c7ff2adebec67ef21795260355478c7 Mon Sep 17 00:00:00 2001 From: glankk Date: Thu, 12 Jun 2025 17:23:14 +0200 Subject: [PATCH 022/119] Add encoding to open() in run-tests.py (#440) --- run-tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run-tests.py b/run-tests.py index 2f28bf0f..5017f4c1 100644 --- a/run-tests.py +++ b/run-tests.py @@ -16,7 +16,7 @@ def cleanup(out): commands = [] for f in sorted(glob.glob(os.path.expanduser('testsuite/clang-preprocessor-tests/*.c*'))): - for line in open(f, 'rt'): + for line in open(f, 'rt', encoding='utf-8'): if line.startswith('// RUN: %clang_cc1 '): cmd = '' for arg in line[19:].split(): From eb18d11f0fc2aa70330dc5592629dc06ed0213f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 13 Jun 2025 22:26:46 +0200 Subject: [PATCH 023/119] main.cpp: added option `-f` to fail on output error (#436) --- main.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/main.cpp b/main.cpp index f05cf793..424ef6fa 100644 --- a/main.cpp +++ b/main.cpp @@ -18,6 +18,7 @@ int main(int argc, char **argv) bool error = false; const char *filename = nullptr; bool use_istream = false; + bool fail_on_error = false; // Settings.. simplecpp::DUI dui; @@ -70,6 +71,10 @@ int main(int argc, char **argv) error_only = true; found = true; break; + case 'f': + fail_on_error = true; + found = true; + break; } if (!found) { std::cout << "error: option '" << arg << "' is unknown." << std::endl; @@ -172,5 +177,8 @@ int main(int argc, char **argv) } } + if (fail_on_error && !outputList.empty()) + return 1; + return 0; } From a46cb125243f4d6e743eceeba37a485ad131d420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 13 Jun 2025 22:33:42 +0200 Subject: [PATCH 024/119] selfcheck.sh: actually fail if we encountered unexpected errors (#437) --- selfcheck.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/selfcheck.sh b/selfcheck.sh index 3518c654..a43ef8c5 100755 --- a/selfcheck.sh +++ b/selfcheck.sh @@ -1,6 +1,11 @@ #!/bin/sh -output=$(./simplecpp simplecpp.cpp -e 2>&1) +output=$(./simplecpp simplecpp.cpp -e -f 2>&1) ec=$? -echo "$output" | grep -v 'Header not found: <' -exit $ec \ No newline at end of file +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 + if [ ! -z "$errors" ]; then + exit $ec + fi +fi \ No newline at end of file From 8e5c5f49ca66c053c9ab5bd699b7517b5b796536 Mon Sep 17 00:00:00 2001 From: glankk Date: Mon, 16 Jun 2025 13:59:17 +0200 Subject: [PATCH 025/119] Fix #381 (When there are header files with the same name, the correct file cannot be found) (#443) --- integration_test.py | 111 ++++++++++++++++++++++++++++++++++++++++++++ simplecpp.cpp | 7 +++ test.cpp | 2 + 3 files changed, 120 insertions(+) diff --git a/integration_test.py b/integration_test.py index 122ce9aa..ccef84eb 100644 --- a/integration_test.py +++ b/integration_test.py @@ -2,6 +2,7 @@ import os import pathlib +import platform import pytest from testutils import simplecpp, format_include_path_arg, format_include @@ -177,3 +178,113 @@ def test_relative_header_6(record_property, tmpdir, with_pragma_once, relative_i assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout else: assert double_include_error in stderr + +def test_same_name_header(record_property, tmpdir): + include_a = os.path.join(tmpdir, "include_a") + include_b = os.path.join(tmpdir, "include_b") + + test_file = os.path.join(tmpdir, "test.c") + header_a = os.path.join(include_a, "header_a.h") + header_b = os.path.join(include_b, "header_b.h") + same_name_a = os.path.join(include_a, "same_name.h") + same_name_b = os.path.join(include_b, "same_name.h") + + os.mkdir(include_a) + os.mkdir(include_b) + + with open(test_file, "wt") as f: + f.write(""" + #include + #include + TEST + """) + + with open(header_a, "wt") as f: + f.write(""" + #include "same_name.h" + """) + + with open(header_b, "wt") as f: + f.write(""" + #include "same_name.h" + """) + + with open(same_name_a, "wt") as f: + f.write(""" + #define TEST E + """) + + with open(same_name_b, "wt") as f: + f.write(""" + #define TEST OK + """) + + args = [ + format_include_path_arg(include_a), + format_include_path_arg(include_b), + test_file + ] + + _, stdout, stderr = simplecpp(args, cwd=tmpdir) + record_property("stdout", stdout) + record_property("stderr", stderr) + + assert "OK" in stdout + assert stderr == "" + +def test_pragma_once_matching(record_property, tmpdir): + if platform.system() == "win32": + names_to_test = [ + '"once.h"', + '"Once.h"', + '', + '', + '"../test_dir/once.h"', + '"../test_dir/Once.h"', + '"../Test_Dir/once.h"', + '"../Test_Dir/Once.h"', + '"test_subdir/../once.h"', + '"test_subdir/../Once.h"', + '"Test_Subdir/../once.h"', + '"Test_Subdir/../Once.h"', + ] + else: + names_to_test = [ + '"once.h"', + '', + '"../test_dir/once.h"', + '"test_subdir/../once.h"', + ] + + test_dir = os.path.join(tmpdir, "test_dir") + test_subdir = os.path.join(test_dir, "test_subdir") + + test_file = os.path.join(test_dir, "test.c") + once_header = os.path.join(test_dir, "once.h") + + os.mkdir(test_dir) + os.mkdir(test_subdir) + + with open(test_file, "wt") as f: + for n in names_to_test: + f.write(f""" + #include {n} + """); + + with open(once_header, "wt") as f: + f.write(f""" + #pragma once + ONCE + """); + + args = [ + format_include_path_arg(test_dir), + test_file + ] + + _, stdout, stderr = simplecpp(args, cwd=tmpdir) + record_property("stdout", stdout) + record_property("stderr", stderr) + + assert stdout.count("ONCE") == 1 + assert stderr == "" diff --git a/simplecpp.cpp b/simplecpp.cpp index 9ff66d8b..e4f98bb6 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3278,6 +3278,13 @@ static std::string getFileIdPath(const std::map Date: Fri, 20 Jun 2025 21:33:36 +0200 Subject: [PATCH 026/119] Fix #446 (change the rules of relativeness preserving to depend on the source file including it for relative path includes) (#445) --- integration_test.py | 28 ++++++++++++++++------------ simplecpp.cpp | 12 ++++++------ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/integration_test.py b/integration_test.py index ccef84eb..27528e16 100644 --- a/integration_test.py +++ b/integration_test.py @@ -67,7 +67,7 @@ def test_relative_header_2(record_property, tmpdir, with_pragma_once, inv, sourc record_property("stderr", stderr) if with_pragma_once: assert stderr == '' - if inv: + if inv or not source_relative: assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout else: assert '#line 8 "test.h"' in stdout @@ -94,16 +94,17 @@ def test_relative_header_3(record_property, tmpdir, is_sys, inv, source_relative assert "missing header: Header not found" in stderr else: assert stderr == '' - if inv: - assert f'#line 8 "{pathlib.PurePath(test_subdir).as_posix()}/test.h"' in stdout - else: + if source_relative and not inv: assert '#line 8 "test_subdir/test.h"' in stdout + else: + assert f'#line 8 "{pathlib.PurePath(test_subdir).as_posix()}/test.h"' in stdout @pytest.mark.parametrize("use_short_path", (False, True)) @pytest.mark.parametrize("relative_include_dir", (False, True)) @pytest.mark.parametrize("is_sys", (False, True)) @pytest.mark.parametrize("inv", (False, True)) -def test_relative_header_4(record_property, tmpdir, use_short_path, relative_include_dir, is_sys, inv): +@pytest.mark.parametrize("source_relative", (False, True)) +def test_relative_header_4(record_property, tmpdir, use_short_path, relative_include_dir, is_sys, inv, source_relative): test_subdir = os.path.join(tmpdir, "test_subdir") os.mkdir(test_subdir) header_file, _ = __test_relative_header_create_header(test_subdir) @@ -112,13 +113,14 @@ def test_relative_header_4(record_property, tmpdir, use_short_path, relative_inc test_file = __test_relative_header_create_source(tmpdir, header_file, "test.h", is_include2_sys=is_sys, inv=inv) - args = [format_include_path_arg("test_subdir" if relative_include_dir else test_subdir), test_file] + args = [format_include_path_arg("test_subdir" if relative_include_dir else test_subdir), "test.c" if source_relative else test_file] _, stdout, stderr = simplecpp(args, cwd=tmpdir) record_property("stdout", stdout) record_property("stderr", stderr) + assert stderr == '' - if (use_short_path and not inv) or (relative_include_dir and inv): + if (source_relative and use_short_path and not inv) or (relative_include_dir and inv): assert '#line 8 "test_subdir/test.h"' in stdout else: assert f'#line 8 "{pathlib.PurePath(test_subdir).as_posix()}/test.h"' in stdout @@ -127,7 +129,8 @@ def test_relative_header_4(record_property, tmpdir, use_short_path, relative_inc @pytest.mark.parametrize("relative_include_dir", (False, True)) @pytest.mark.parametrize("is_sys", (False, True)) @pytest.mark.parametrize("inv", (False, True)) -def test_relative_header_5(record_property, tmpdir, with_pragma_once, relative_include_dir, is_sys, inv): # test relative paths with .. +@pytest.mark.parametrize("source_relative", (False, True)) +def test_relative_header_5(record_property, tmpdir, with_pragma_once, relative_include_dir, is_sys, inv, source_relative): # test relative paths with .. ## in this test, the subdir role is the opposite then the previous - it contains the test.c file, while the parent tmpdir contains the header file header_file, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once) if is_sys: @@ -139,14 +142,14 @@ def test_relative_header_5(record_property, tmpdir, with_pragma_once, relative_i os.mkdir(test_subdir) test_file = __test_relative_header_create_source(test_subdir, header_file, header_file_second_path, is_include2_sys=is_sys, inv=inv) - args = ([format_include_path_arg(".." if relative_include_dir else tmpdir)] if is_sys else []) + ["test.c"] + args = ([format_include_path_arg(".." if relative_include_dir else tmpdir)] if is_sys else []) + ["test.c" if source_relative else test_file] _, stdout, stderr = simplecpp(args, cwd=test_subdir) record_property("stdout", stdout) record_property("stderr", stderr) if with_pragma_once: assert stderr == '' - if (relative_include_dir or not is_sys) and inv: + if (relative_include_dir if is_sys else source_relative) and inv: assert '#line 8 "../test.h"' in stdout else: assert f'#line 8 "{pathlib.PurePath(tmpdir).as_posix()}/test.h"' in stdout @@ -157,7 +160,8 @@ def test_relative_header_5(record_property, tmpdir, with_pragma_once, relative_i @pytest.mark.parametrize("relative_include_dir", (False, True)) @pytest.mark.parametrize("is_sys", (False, True)) @pytest.mark.parametrize("inv", (False, True)) -def test_relative_header_6(record_property, tmpdir, with_pragma_once, relative_include_dir, is_sys, inv): # test relative paths with .. that is resolved only by an include dir +@pytest.mark.parametrize("source_relative", (False, True)) +def test_relative_header_6(record_property, tmpdir, with_pragma_once, relative_include_dir, is_sys, inv, source_relative): # test relative paths with .. that is resolved only by an include dir ## in this test, both the header and the source file are at the same dir, but there is a dummy inclusion dir as a subdir header_file, double_include_error = __test_relative_header_create_header(tmpdir, with_pragma_once=with_pragma_once) @@ -165,7 +169,7 @@ def test_relative_header_6(record_property, tmpdir, with_pragma_once, relative_i os.mkdir(test_subdir) test_file = __test_relative_header_create_source(tmpdir, header_file, "../test.h", is_include2_sys=is_sys, inv=inv) - args = [format_include_path_arg("test_subdir" if relative_include_dir else test_subdir), "test.c"] + args = [format_include_path_arg("test_subdir" if relative_include_dir else test_subdir), "test.c" if source_relative else test_file] _, stdout, stderr = simplecpp(args, cwd=tmpdir) record_property("stdout", stdout) diff --git a/simplecpp.cpp b/simplecpp.cpp index e4f98bb6..599ffdfe 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3173,7 +3173,7 @@ static std::string openHeader(std::ifstream &f, const std::string &path) return ""; } -static std::string getRelativeFileName(const std::string &baseFile, const std::string &header) +static std::string getRelativeFileName(const std::string &baseFile, const std::string &header, bool returnAbsolutePath) { const std::string baseFileSimplified = simplecpp::simplifyPath(baseFile); const std::string baseFileAbsolute = isAbsolutePath(baseFileSimplified) ? @@ -3185,12 +3185,12 @@ static std::string getRelativeFileName(const std::string &baseFile, const std::s headerSimplified : simplecpp::simplifyPath(dirPath(baseFileAbsolute) + headerSimplified); - return extractRelativePathFromAbsolute(path); + return returnAbsolutePath ? toAbsolutePath(path) : extractRelativePathFromAbsolute(path); } static std::string openHeaderRelative(std::ifstream &f, const std::string &sourcefile, const std::string &header) { - return openHeader(f, getRelativeFileName(sourcefile, header)); + return openHeader(f, getRelativeFileName(sourcefile, header, isAbsolutePath(sourcefile))); } // returns the simplified header path: @@ -3273,14 +3273,14 @@ static std::string getFileIdPath(const std::map Date: Tue, 1 Jul 2025 16:32:07 +0200 Subject: [PATCH 027/119] Fix #368 (__VA_OPT__ is not handled good enough) (#451) --- simplecpp.cpp | 111 ++++++++++++++---- test.cpp | 51 +++++++- .../macro_fn_va_opt.c | 13 ++ 3 files changed, 148 insertions(+), 27 deletions(-) create mode 100644 testsuite/clang-preprocessor-tests/macro_fn_va_opt.c diff --git a/simplecpp.cpp b/simplecpp.cpp index 599ffdfe..97657d60 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -1485,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), valueDefinedInCode_(false) {} + 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) {} Macro(const Token *tok, std::vector &f) : nameTokDef(nullptr), files(f), tokenListDefine(f), valueDefinedInCode_(true) { if (sameline(tok->previousSkipComments(), tok)) @@ -1515,6 +1515,11 @@ namespace simplecpp { *this = other; } + ~Macro() { + delete optExpandValue; + delete optNoExpandValue; + } + Macro &operator=(const Macro &other) { if (this != &other) { files = other.files; @@ -1707,6 +1712,9 @@ namespace simplecpp { bool parseDefine(const Token *nametoken) { nameTokDef = nametoken; variadic = false; + variadicOpt = false; + optExpandValue = nullptr; + optNoExpandValue = nullptr; if (!nameTokDef) { valueToken = endToken = nullptr; args.clear(); @@ -1744,8 +1752,54 @@ namespace simplecpp { if (!sameline(valueToken, nameTokDef)) valueToken = nullptr; endToken = valueToken; - while (sameline(endToken, nameTokDef)) + while (sameline(endToken, nameTokDef)) { + if (variadic && endToken->str() == "__VA_OPT__") + variadicOpt = true; endToken = endToken->next; + } + + if (variadicOpt) { + TokenList expandValue(files); + TokenList noExpandValue(files); + for (const Token *tok = valueToken; tok && tok != endToken;) { + if (tok->str() == "__VA_OPT__") { + if (!sameline(tok, tok->next) || tok->next->op != '(') + throw Error(tok->location, "In definition of '" + nameTokDef->str() + "': Missing opening parenthesis for __VA_OPT__"); + tok = tok->next->next; + int par = 1; + while (tok && tok != endToken) { + if (tok->op == '(') + par++; + else if (tok->op == ')') + par--; + else if (tok->str() == "__VA_OPT__") + throw Error(tok->location, "In definition of '" + nameTokDef->str() + "': __VA_OPT__ cannot be nested"); + if (par == 0) { + tok = tok->next; + break; + } + expandValue.push_back(new Token(*tok)); + tok = tok->next; + } + if (par != 0) { + const Token *const lastTok = expandValue.back() ? expandValue.back() : valueToken->next; + throw Error(lastTok->location, "In definition of '" + nameTokDef->str() + "': Missing closing parenthesis for __VA_OPT__"); + } + } else { + expandValue.push_back(new Token(*tok)); + noExpandValue.push_back(new Token(*tok)); + tok = tok->next; + } + } +#if __cplusplus >= 201103L + optExpandValue = new TokenList(std::move(expandValue)); + optNoExpandValue = new TokenList(std::move(noExpandValue)); +#else + optExpandValue = new TokenList(expandValue); + optNoExpandValue = new TokenList(noExpandValue); +#endif + } + return true; } @@ -1900,8 +1954,22 @@ namespace simplecpp { Token * const output_end_1 = output->back(); + const Token *valueToken2; + const Token *endToken2; + + if (variadicOpt) { + if (parametertokens2.size() > args.size() && parametertokens2[args.size() - 1]->next->op != ')') + valueToken2 = optExpandValue->cfront(); + else + valueToken2 = optNoExpandValue->cfront(); + endToken2 = nullptr; + } else { + valueToken2 = valueToken; + endToken2 = endToken; + } + // expand - for (const Token *tok = valueToken; tok != endToken;) { + for (const Token *tok = valueToken2; tok != endToken2;) { if (tok->op != '#') { // A##B => AB if (sameline(tok, tok->next) && tok->next && tok->next->op == '#' && tok->next->next && tok->next->next->op == '#') { @@ -1950,7 +2018,7 @@ namespace simplecpp { } tok = tok->next; - if (tok == endToken) { + if (tok == endToken2) { output->push_back(new Token(*tok->previous)); break; } @@ -2020,24 +2088,6 @@ namespace simplecpp { // Macro parameter.. { TokenList temp(files); - if (tok->str() == "__VA_OPT__") { - if (sameline(tok, tok->next) && tok->next->str() == "(") { - tok = tok->next; - int paren = 1; - while (sameline(tok, tok->next)) { - if (tok->next->str() == "(") - ++paren; - else if (tok->next->str() == ")") - --paren; - if (paren == 0) - return tok->next->next; - tok = tok->next; - if (parametertokens.size() > args.size() && parametertokens.front()->next->str() != ")") - tok = expandToken(output, loc, tok, macros, expandedmacros, parametertokens)->previous; - } - } - throw Error(tok->location, "Missing parenthesis for __VA_OPT__(content)"); - } 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() == ")") @@ -2338,6 +2388,13 @@ namespace simplecpp { /** is macro variadic? */ bool variadic; + /** does the macro expansion have __VA_OPT__? */ + bool variadicOpt; + + /** Expansion value for varadic macros with __VA_OPT__ expanded and discarded respectively */ + const TokenList *optExpandValue; + const TokenList *optNoExpandValue; + /** was the value of this macro actually defined in the code? */ bool valueDefinedInCode_; }; @@ -3621,6 +3678,16 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } output.clear(); return; + } catch (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); + } + output.clear(); + return; } } else if (ifstates.top() == True && rawtok->str() == INCLUDE) { TokenList inc1(files); diff --git a/test.cpp b/test.cpp index cec253b8..caa6137e 100644 --- a/test.cpp +++ b/test.cpp @@ -923,7 +923,7 @@ static void define_va_opt_3() simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code1, &outputList)); - ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'err', Missing parenthesis for __VA_OPT__(content)\n", + ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing closing parenthesis for __VA_OPT__\n", toString(outputList)); outputList.clear(); @@ -934,7 +934,7 @@ static void define_va_opt_3() "err()"; ASSERT_EQUALS("", preprocess(code2, &outputList)); - ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'err', Missing parenthesis for __VA_OPT__(content)\n", + ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing opening parenthesis for __VA_OPT__\n", toString(outputList)); } @@ -946,7 +946,7 @@ static void define_va_opt_4() simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code1, &outputList)); - ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'err', Missing parenthesis for __VA_OPT__(content)\n", + ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing opening parenthesis for __VA_OPT__\n", toString(outputList)); outputList.clear(); @@ -956,7 +956,7 @@ static void define_va_opt_4() "err()"; ASSERT_EQUALS("", preprocess(code2, &outputList)); - ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'err', Missing parenthesis for __VA_OPT__(content)\n", + ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing opening parenthesis for __VA_OPT__\n", toString(outputList)); } @@ -968,7 +968,46 @@ static void define_va_opt_5() simplecpp::OutputList outputList; ASSERT_EQUALS("", preprocess(code, &outputList)); - ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'err', Missing parenthesis for __VA_OPT__(content)\n", + ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing opening parenthesis for __VA_OPT__\n", + toString(outputList)); +} + +static void define_va_opt_6() +{ + // nested __VA_OPT__ + const char code[] = "#define err(...) __VA_OPT__(__VA_OPT__(something))\n" + "err()"; + + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': __VA_OPT__ cannot be nested\n", + toString(outputList)); +} + +static void define_va_opt_7() +{ + // eof in __VA_OPT__ + const char code1[] = "#define err(...) __VA_OPT__"; + + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code1, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,Failed to parse #define, In definition of 'err': Missing opening parenthesis for __VA_OPT__\n", + toString(outputList)); + + outputList.clear(); + + const char code2[] = "#define err(...) __VA_OPT__("; + + 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", + toString(outputList)); + + outputList.clear(); + + const char code3[] = "#define err(...) __VA_OPT__(x"; + + 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", toString(outputList)); } @@ -3063,6 +3102,8 @@ int main(int argc, char **argv) TEST_CASE(define_va_opt_3); TEST_CASE(define_va_opt_4); TEST_CASE(define_va_opt_5); + TEST_CASE(define_va_opt_6); + TEST_CASE(define_va_opt_7); TEST_CASE(pragma_backslash); // multiline pragma directive diff --git a/testsuite/clang-preprocessor-tests/macro_fn_va_opt.c b/testsuite/clang-preprocessor-tests/macro_fn_va_opt.c new file mode 100644 index 00000000..ccb09e95 --- /dev/null +++ b/testsuite/clang-preprocessor-tests/macro_fn_va_opt.c @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -E %s | grep '^ printf( "%%s" , "Hello" );$' + +#define P( x, ...) printf( x __VA_OPT__(,) __VA_ARGS__ ) +#define PF( x, ...) P( x __VA_OPT__(,) __VA_ARGS__ ) + +int main() +{ + PF( "%s", "Hello" ); + PF( "Hello", ); + PF( "Hello" ); + PF( , ); + PF( ); +} From 0ff0149510fcebc8ccdaf56402d400d5e290fca7 Mon Sep 17 00:00:00 2001 From: glankk Date: Thu, 3 Jul 2025 11:33:59 +0200 Subject: [PATCH 028/119] Fix #449 (Update c++ standard to c++11) (#450) --- .github/workflows/clang-tidy.yml | 2 +- CMakeLists.txt | 21 +------ Makefile | 4 +- simplecpp.cpp | 99 +++++--------------------------- simplecpp.h | 12 ---- 5 files changed, 19 insertions(+), 119 deletions(-) diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 9a4f43c9..a2f7b6dc 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -30,7 +30,7 @@ jobs: - name: Prepare CMake run: | - cmake -S . -B cmake.output -G "Unix Makefiles" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DDISABLE_CPP03_SYNTAX_CHECK=ON + cmake -S . -B cmake.output -G "Unix Makefiles" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON env: CXX: clang-20 diff --git a/CMakeLists.txt b/CMakeLists.txt index 672e63bb..6ab0166e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,8 @@ cmake_minimum_required (VERSION 3.10) project (simplecpp LANGUAGES CXX) -option(DISABLE_CPP03_SYNTAX_CHECK "Disable the C++03 syntax check." OFF) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) include(CheckCXXCompilerFlag) @@ -70,25 +71,7 @@ endif() add_library(simplecpp_obj OBJECT simplecpp.cpp) add_executable(simplecpp $ main.cpp) -set_property(TARGET simplecpp PROPERTY CXX_STANDARD 11) - -if (NOT DISABLE_CPP03_SYNTAX_CHECK) - # it is not possible to set a standard older than C++14 with Visual Studio - if (CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - # we need to create a dummy library as -fsyntax-only will not produce any output files causing the build to fail - add_library(simplecpp-03-syntax OBJECT simplecpp.cpp) - target_compile_options(simplecpp-03-syntax PRIVATE -std=c++03) - if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") - target_compile_options(simplecpp-03-syntax PRIVATE -Wno-long-long) - elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - target_compile_options(simplecpp-03-syntax PRIVATE -Wno-c++11-long-long -Wno-c++11-compat) - endif() - add_dependencies(simplecpp simplecpp-03-syntax) - endif() -endif() - add_executable(testrunner $ test.cpp) -set_property(TARGET testrunner PROPERTY CXX_STANDARD 11) enable_testing() add_test(NAME testrunner COMMAND testrunner) diff --git a/Makefile b/Makefile index db1ca257..73977517 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ all: testrunner simplecpp -CXXFLAGS = -Wall -Wextra -pedantic -Wcast-qual -Wfloat-equal -Wmissing-declarations -Wmissing-format-attribute -Wredundant-decls -Wundef -Wno-multichar -Wold-style-cast -std=c++0x -g +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 LDFLAGS = -g %.o: %.cpp simplecpp.h @@ -11,8 +11,6 @@ testrunner: test.o simplecpp.o $(CXX) $(LDFLAGS) simplecpp.o test.o -o testrunner test: testrunner simplecpp - # The -std=c++03 makes sure that simplecpp.cpp is C++03 conformant. We don't require a C++11 compiler - g++ -std=c++03 -fsyntax-only simplecpp.cpp ./testrunner python3 run-tests.py diff --git a/simplecpp.cpp b/simplecpp.cpp index 97657d60..c29040bc 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -31,12 +31,10 @@ #include #include #include -#if __cplusplus >= 201103L #ifdef SIMPLECPP_WINDOWS #include #endif #include -#endif #include #include @@ -51,18 +49,6 @@ #undef ERROR #endif -#if __cplusplus >= 201103L -#define OVERRIDE override -#define EXPLICIT explicit -#else -#define OVERRIDE -#define EXPLICIT -#endif - -#if (__cplusplus < 201103L) && !defined(__APPLE__) -#define nullptr NULL -#endif - static bool isHex(const std::string &s) { return s.size()>2 && (s.compare(0,2,"0x")==0 || s.compare(0,2,"0X")==0); @@ -368,22 +354,22 @@ class simplecpp::TokenList::Stream { class StdIStream : public simplecpp::TokenList::Stream { public: // cppcheck-suppress uninitDerivedMemberVar - we call Stream::init() to initialize the private members - EXPLICIT StdIStream(std::istream &istr) + explicit StdIStream(std::istream &istr) : istr(istr) { assert(istr.good()); init(); } - virtual int get() OVERRIDE { + virtual int get() override { return istr.get(); } - virtual int peek() OVERRIDE { + virtual int peek() override { return istr.peek(); } - virtual void unget() OVERRIDE { + virtual void unget() override { istr.unget(); } - virtual bool good() OVERRIDE { + virtual bool good() override { return istr.good(); } @@ -402,20 +388,20 @@ class StdCharBufStream : public simplecpp::TokenList::Stream { init(); } - virtual int get() OVERRIDE { + virtual int get() override { if (pos >= size) return lastStatus = EOF; return str[pos++]; } - virtual int peek() OVERRIDE { + virtual int peek() override { if (pos >= size) return lastStatus = EOF; return str[pos]; } - virtual void unget() OVERRIDE { + virtual void unget() override { --pos; } - virtual bool good() OVERRIDE { + virtual bool good() override { return lastStatus != EOF; } @@ -429,7 +415,7 @@ class StdCharBufStream : public simplecpp::TokenList::Stream { class FileStream : public simplecpp::TokenList::Stream { public: // cppcheck-suppress uninitDerivedMemberVar - we call Stream::init() to initialize the private members - EXPLICIT FileStream(const std::string &filename, std::vector &files) + explicit FileStream(const std::string &filename, std::vector &files) : file(fopen(filename.c_str(), "rb")) , lastCh(0) , lastStatus(0) { @@ -440,25 +426,25 @@ class FileStream : public simplecpp::TokenList::Stream { init(); } - ~FileStream() OVERRIDE { + ~FileStream() override { fclose(file); file = nullptr; } - virtual int get() OVERRIDE { + virtual int get() override { lastStatus = lastCh = fgetc(file); return lastCh; } - virtual int peek() OVERRIDE{ + virtual int peek() override{ // keep lastCh intact const int ch = fgetc(file); unget_internal(ch); return ch; } - virtual void unget() OVERRIDE { + virtual void unget() override { unget_internal(lastCh); } - virtual bool good() OVERRIDE { + virtual bool good() override { return lastStatus != EOF; } @@ -519,12 +505,10 @@ simplecpp::TokenList::TokenList(const TokenList &other) : frontToken(nullptr), b *this = other; } -#if __cplusplus >= 201103L simplecpp::TokenList::TokenList(TokenList &&other) : frontToken(nullptr), backToken(nullptr), files(other.files) { *this = std::move(other); } -#endif simplecpp::TokenList::~TokenList() { @@ -543,7 +527,6 @@ simplecpp::TokenList &simplecpp::TokenList::operator=(const TokenList &other) return *this; } -#if __cplusplus >= 201103L simplecpp::TokenList &simplecpp::TokenList::operator=(TokenList &&other) { if (this != &other) { @@ -557,7 +540,6 @@ simplecpp::TokenList &simplecpp::TokenList::operator=(TokenList &&other) } return *this; } -#endif void simplecpp::TokenList::clear() { @@ -1477,11 +1459,7 @@ unsigned int simplecpp::TokenList::fileIndex(const std::string &filename) namespace simplecpp { class Macro; -#if __cplusplus >= 201103L using MacroMap = std::unordered_map; -#else - typedef std::map MacroMap; -#endif class Macro { public: @@ -1791,13 +1769,8 @@ namespace simplecpp { tok = tok->next; } } -#if __cplusplus >= 201103L optExpandValue = new TokenList(std::move(expandValue)); optNoExpandValue = new TokenList(std::move(noExpandValue)); -#else - optExpandValue = new TokenList(expandValue); - optNoExpandValue = new TokenList(noExpandValue); -#endif } return true; @@ -2437,47 +2410,9 @@ namespace simplecpp { #ifdef SIMPLECPP_WINDOWS -#if __cplusplus >= 201103L using MyMutex = std::mutex; template using MyLock = std::lock_guard; -#else -class MyMutex { -public: - MyMutex() { - InitializeCriticalSection(&m_criticalSection); - } - - ~MyMutex() { - DeleteCriticalSection(&m_criticalSection); - } - - CRITICAL_SECTION* lock() { - return &m_criticalSection; - } -private: - CRITICAL_SECTION m_criticalSection; -}; - -template -class MyLock { -public: - explicit MyLock(T& m) - : m_mutex(m) { - EnterCriticalSection(m_mutex.lock()); - } - - ~MyLock() { - LeaveCriticalSection(m_mutex.lock()); - } - -private: - MyLock& operator=(const MyLock&); - MyLock(const MyLock&); - - T& m_mutex; -}; -#endif class RealFileNameMap { public: @@ -4099,7 +4034,3 @@ std::string simplecpp::getCppStdString(const std::string &std) { return getCppStdString(getCppStd(std)); } - -#if (__cplusplus < 201103L) && !defined(__APPLE__) -#undef nullptr -#endif diff --git a/simplecpp.h b/simplecpp.h index f5c69593..9fd95808 100755 --- a/simplecpp.h +++ b/simplecpp.h @@ -27,10 +27,6 @@ # define SIMPLECPP_LIB #endif -#if (__cplusplus < 201103L) && !defined(__APPLE__) -#define nullptr NULL -#endif - #if defined(_MSC_VER) # pragma warning(push) // suppress warnings about "conversion from 'type1' to 'type2', possible loss of data" @@ -214,14 +210,10 @@ namespace simplecpp { /** generates a token list from the given filename parameter */ TokenList(const std::string &filename, std::vector &filenames, OutputList *outputList = nullptr); TokenList(const TokenList &other); -#if __cplusplus >= 201103L TokenList(TokenList &&other); -#endif ~TokenList(); TokenList &operator=(const TokenList &other); -#if __cplusplus >= 201103L TokenList &operator=(TokenList &&other); -#endif void clear(); bool empty() const { @@ -395,8 +387,4 @@ namespace simplecpp { # pragma warning(pop) #endif -#if (__cplusplus < 201103L) && !defined(__APPLE__) -#undef nullptr -#endif - #endif From c1f368832dbefa508480bbcdafea87f044645563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Thu, 3 Jul 2025 11:39:43 +0200 Subject: [PATCH 029/119] Fix #454: Accept __has_include for GNU C standards (#456) --- simplecpp.cpp | 9 ++++++--- test.cpp | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index c29040bc..d925773e 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2673,13 +2673,16 @@ static void simplifySizeof(simplecpp::TokenList &expr, const std::map= "201703L"); } +static bool isGnu(const simplecpp::DUI &dui) +{ + return dui.std.rfind("gnu", 0) != std::string::npos; +} static std::string currentDirectoryOSCalc() { const std::size_t size = 4096; @@ -2752,7 +2755,7 @@ static std::string extractRelativePathFromAbsolute(const std::string& absoluteSi static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader); static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI &dui) { - if (!isCpp17OrLater(dui)) + if (!isCpp17OrLater(dui) && !isGnu(dui)) return; for (simplecpp::Token *tok = expr.front(); tok; tok = tok->next) { @@ -3475,7 +3478,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL // use a dummy vector for the macros because as this is not part of the file and would add an empty entry - e.g. /usr/include/poll.h std::vector dummy; - const bool hasInclude = isCpp17OrLater(dui); + const bool hasInclude = isCpp17OrLater(dui) || isGnu(dui); MacroMap macros; for (std::list::const_iterator it = dui.defines.begin(); it != dui.defines.end(); ++it) { const std::string ¯ostr = *it; diff --git a/test.cpp b/test.cpp index caa6137e..45498a08 100644 --- a/test.cpp +++ b/test.cpp @@ -1602,6 +1602,21 @@ static void has_include_5() ASSERT_EQUALS("", preprocess(code)); } +static void has_include_6() +{ + const char code[] = "#if defined( __has_include)\n" + " #if !__has_include()\n" + " A\n" + " #else\n" + " B\n" + " #endif\n" + "#endif"; + simplecpp::DUI dui; + dui.std = "gnu99"; + ASSERT_EQUALS("\n\nA", preprocess(code, dui)); + ASSERT_EQUALS("", preprocess(code)); +} + static void ifdef1() { const char code[] = "#ifdef A\n" @@ -3164,6 +3179,7 @@ int main(int argc, char **argv) TEST_CASE(has_include_3); TEST_CASE(has_include_4); TEST_CASE(has_include_5); + TEST_CASE(has_include_6); TEST_CASE(ifdef1); TEST_CASE(ifdef2); From 4bbd1bf8e320471f2a46908c947251ed8aa18b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Thu, 3 Jul 2025 12:02:25 +0200 Subject: [PATCH 030/119] Fix #452: Undefined function-style macro does not cause an error (#453) --- simplecpp.cpp | 2 ++ test.cpp | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/simplecpp.cpp b/simplecpp.cpp index d925773e..a69dc0c2 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2827,6 +2827,8 @@ static void simplifyName(simplecpp::TokenList &expr) if (alt) continue; } + if (tok->next && tok->next->str() == "(") + throw std::runtime_error("undefined function-like macro invocation: " + tok->str() + "( ... )"); tok->setstr("0"); } } diff --git a/test.cpp b/test.cpp index 45498a08..fb3e4b22 100644 --- a/test.cpp +++ b/test.cpp @@ -1879,6 +1879,15 @@ static void ifexpr() ASSERT_EQUALS("\n\n1", preprocess(code)); } +static void ifUndefFuncStyleMacro() +{ + const char code[] = "#if A()\n" + "#endif\n"; + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("file0,1,syntax_error,failed to evaluate #if condition, undefined function-like macro invocation: A( ... )\n", toString(outputList)); +} + static void location1() { const char *code; @@ -3202,6 +3211,7 @@ int main(int argc, char **argv) TEST_CASE(ifdiv0); TEST_CASE(ifalt); // using "and", "or", etc TEST_CASE(ifexpr); + TEST_CASE(ifUndefFuncStyleMacro); TEST_CASE(location1); TEST_CASE(location2); From f566848788c1445e56b543255e82ac3b0c952ee9 Mon Sep 17 00:00:00 2001 From: glankk Date: Mon, 7 Jul 2025 10:33:11 +0200 Subject: [PATCH 031/119] Add caching of conditional directive chains (#468) --- simplecpp.cpp | 15 ++++++++++++++- simplecpp.h | 5 +++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index a69dc0c2..e9641b04 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3532,6 +3532,7 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL // AlwaysFalse => drop all code in #if and #else enum IfState { True, ElseIsTrue, AlwaysFalse }; std::stack ifstates; + std::stack iftokens; ifstates.push(True); std::stack includetokenstack; @@ -3855,15 +3856,24 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL ifstates.push(AlwaysFalse); 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; + iftokens.top()->nextcond = rawtok; + iftokens.top() = rawtok; } } else if (rawtok->str() == ELSE) { ifstates.top() = (ifstates.top() == ElseIsTrue) ? True : AlwaysFalse; + iftokens.top()->nextcond = rawtok; + iftokens.top() = rawtok; } else if (rawtok->str() == ENDIF) { ifstates.pop(); + iftokens.top()->nextcond = rawtok; + iftokens.pop(); } else if (rawtok->str() == UNDEF) { if (ifstates.top() == True) { const Token *tok = rawtok->next; @@ -3875,7 +3885,10 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } else if (ifstates.top() == True && rawtok->str() == PRAGMA && rawtok->next && rawtok->next->str() == ONCE && sameline(rawtok,rawtok->next)) { pragmaOnce.insert(rawtok->location.file()); } - rawtok = gotoNextLine(rawtok); + if (ifstates.top() != True && rawtok->nextcond) + rawtok = rawtok->nextcond->previous; + else + rawtok = gotoNextLine(rawtok); continue; } diff --git a/simplecpp.h b/simplecpp.h index 9fd95808..579e6e14 100755 --- a/simplecpp.h +++ b/simplecpp.h @@ -96,12 +96,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), string(s) { + whitespaceahead(wsahead), location(loc), previous(nullptr), next(nullptr), nextcond(nullptr), 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), 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() { @@ -137,6 +137,7 @@ namespace simplecpp { Location location; Token *previous; Token *next; + mutable const Token *nextcond; const Token *previousSkipComments() const { const Token *tok = this->previous; From c7e99745d0ee079ee3041b7ff7bff03ced19307a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Mon, 7 Jul 2025 10:45:27 +0200 Subject: [PATCH 032/119] fix #459: Set __STRICT_ANSI__=1 for non-gnu standards (#460) --- simplecpp.cpp | 7 +++++++ test.cpp | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/simplecpp.cpp b/simplecpp.cpp index e9641b04..c0f6e2c0 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3482,11 +3482,14 @@ 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) { const std::string ¯ostr = *it; const std::string::size_type eq = macrostr.find('='); const std::string::size_type par = macrostr.find('('); const std::string macroname = macrostr.substr(0, std::min(eq,par)); + if (macroname == "__STRICT_ANSI__") + strictAnsiDefined = true; if (dui.undefined.find(macroname) != dui.undefined.end()) continue; const std::string lhs(macrostr.substr(0,eq)); @@ -3495,6 +3498,10 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL macros.insert(std::pair(macro.name(), macro)); } + const bool strictAnsiUndefined = dui.undefined.find("__STRICT_ANSI__") != dui.undefined.cend(); + if (!isGnu(dui) && !strictAnsiDefined && !strictAnsiUndefined) + macros.insert(std::pair("__STRICT_ANSI__", Macro("__STRICT_ANSI__", "1", dummy))); + 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))); diff --git a/test.cpp b/test.cpp index fb3e4b22..ba21d71b 100644 --- a/test.cpp +++ b/test.cpp @@ -1617,6 +1617,48 @@ static void has_include_6() ASSERT_EQUALS("", preprocess(code)); } +static void strict_ansi_1() +{ + const char code[] = "#if __STRICT_ANSI__\n" + " A\n" + "#endif"; + simplecpp::DUI dui; + dui.std = "gnu99"; + ASSERT_EQUALS("", preprocess(code, dui)); +} + +static void strict_ansi_2() +{ + const char code[] = "#if __STRICT_ANSI__\n" + " A\n" + "#endif"; + simplecpp::DUI dui; + dui.std = "c99"; + ASSERT_EQUALS("\nA", preprocess(code, dui)); +} + +static void strict_ansi_3() +{ + const char code[] = "#if __STRICT_ANSI__\n" + " A\n" + "#endif"; + simplecpp::DUI dui; + dui.std = "c99"; + dui.undefined.insert("__STRICT_ANSI__"); + ASSERT_EQUALS("", preprocess(code, dui)); +} + +static void strict_ansi_4() +{ + const char code[] = "#if __STRICT_ANSI__\n" + " A\n" + "#endif"; + simplecpp::DUI dui; + dui.std = "gnu99"; + dui.defines.push_back("__STRICT_ANSI__"); + ASSERT_EQUALS("\nA", preprocess(code, dui)); +} + static void ifdef1() { const char code[] = "#ifdef A\n" @@ -3190,6 +3232,11 @@ int main(int argc, char **argv) TEST_CASE(has_include_5); TEST_CASE(has_include_6); + TEST_CASE(strict_ansi_1); + TEST_CASE(strict_ansi_2); + TEST_CASE(strict_ansi_3); + TEST_CASE(strict_ansi_4); + TEST_CASE(ifdef1); TEST_CASE(ifdef2); TEST_CASE(ifndef); From a0430f34e8b9e8a5980158eb9c7e5101b9f19473 Mon Sep 17 00:00:00 2001 From: glankk Date: Mon, 7 Jul 2025 11:22:23 +0200 Subject: [PATCH 033/119] Include and path handling optimization (#447) --- integration_test.py | 20 +- main.cpp | 3 +- simplecpp.cpp | 521 ++++++++++++-------------------------------- simplecpp.h | 131 ++++++++++- test.cpp | 173 ++++++++------- 5 files changed, 372 insertions(+), 476 deletions(-) diff --git a/integration_test.py b/integration_test.py index 27528e16..a59ae338 100644 --- a/integration_test.py +++ b/integration_test.py @@ -237,7 +237,13 @@ def test_same_name_header(record_property, tmpdir): assert stderr == "" def test_pragma_once_matching(record_property, tmpdir): - if platform.system() == "win32": + test_dir = os.path.join(tmpdir, "test_dir") + test_subdir = os.path.join(test_dir, "test_subdir") + + test_file = os.path.join(test_dir, "test.c") + once_header = os.path.join(test_dir, "once.h") + + if platform.system() == "Windows": names_to_test = [ '"once.h"', '"Once.h"', @@ -251,6 +257,10 @@ def test_pragma_once_matching(record_property, tmpdir): '"test_subdir/../Once.h"', '"Test_Subdir/../once.h"', '"Test_Subdir/../Once.h"', + f"\"{test_dir}/once.h\"", + f"\"{test_dir}/Once.h\"", + f"<{test_dir}/once.h>", + f"<{test_dir}/Once.h>", ] else: names_to_test = [ @@ -258,14 +268,10 @@ def test_pragma_once_matching(record_property, tmpdir): '', '"../test_dir/once.h"', '"test_subdir/../once.h"', + f"\"{test_dir}/once.h\"", + f"<{test_dir}/once.h>", ] - test_dir = os.path.join(tmpdir, "test_dir") - test_subdir = os.path.join(test_dir, "test_subdir") - - test_file = os.path.join(test_dir, "test.c") - once_header = os.path.join(test_dir, "once.h") - os.mkdir(test_dir) os.mkdir(test_subdir) diff --git a/main.cpp b/main.cpp index 424ef6fa..a6d14386 100644 --- a/main.cpp +++ b/main.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -128,7 +127,7 @@ int main(int argc, char **argv) } rawtokens->removeComments(); simplecpp::TokenList outputTokens(files); - std::map filedata; + simplecpp::FileDataCache filedata; simplecpp::preprocess(outputTokens, *rawtokens, files, filedata, dui, &outputList); simplecpp::cleanup(filedata); delete rawtokens; diff --git a/simplecpp.cpp b/simplecpp.cpp index c0f6e2c0..128da0bd 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -4,8 +4,10 @@ */ #if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) -#define SIMPLECPP_WINDOWS -#define NOMINMAX +# define _WIN32_WINNT 0x0602 +# define NOMINMAX +# include +# undef ERROR #endif #include "simplecpp.h" @@ -32,21 +34,16 @@ #include #include #ifdef SIMPLECPP_WINDOWS -#include +# include #endif #include #include #include #ifdef _WIN32 -#include +# include #else -#include -#endif - -#ifdef SIMPLECPP_WINDOWS -#include -#undef ERROR +# include #endif static bool isHex(const std::string &s) @@ -139,12 +136,6 @@ static unsigned long long stringToULL(const std::string &s) return ret; } -// 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 startsWith_(const std::string &s, const std::string &p) -{ - return (s.size() >= p.size()) && std::equal(p.begin(), p.end(), s.begin()); -} - static bool endsWith(const std::string &s, const std::string &e) { return (s.size() >= e.size()) && std::equal(e.rbegin(), e.rend(), s.rbegin()); @@ -435,7 +426,7 @@ class FileStream : public simplecpp::TokenList::Stream { lastStatus = lastCh = fgetc(file); return lastCh; } - virtual int peek() override{ + virtual int peek() override { // keep lastCh intact const int ch = fgetc(file); unget_internal(ch); @@ -2409,132 +2400,6 @@ namespace simplecpp { } #ifdef SIMPLECPP_WINDOWS - -using MyMutex = std::mutex; -template -using MyLock = std::lock_guard; - -class RealFileNameMap { -public: - RealFileNameMap() {} - - bool getCacheEntry(const std::string& path, std::string& returnPath) { - MyLock lock(m_mutex); - - const std::map::iterator it = m_fileMap.find(path); - if (it != m_fileMap.end()) { - returnPath = it->second; - return true; - } - return false; - } - - void addToCache(const std::string& path, const std::string& actualPath) { - MyLock lock(m_mutex); - m_fileMap[path] = actualPath; - } - -private: - std::map m_fileMap; - MyMutex m_mutex; -}; - -static RealFileNameMap realFileNameMap; - -static bool realFileName(const std::string &f, std::string &result) -{ - // are there alpha characters in last subpath? - bool alpha = false; - for (std::string::size_type pos = 1; pos <= f.size(); ++pos) { - const unsigned char c = f[f.size() - pos]; - if (c == '/' || c == '\\') - break; - if (std::isalpha(c)) { - alpha = true; - break; - } - } - - // do not convert this path if there are no alpha characters (either pointless or cause wrong results for . and ..) - if (!alpha) - return false; - - // Lookup filename or foldername on file system - if (!realFileNameMap.getCacheEntry(f, result)) { - - WIN32_FIND_DATAA FindFileData; - -#ifdef __CYGWIN__ - const std::string fConverted = simplecpp::convertCygwinToWindowsPath(f); - const HANDLE hFind = FindFirstFileExA(fConverted.c_str(), FindExInfoBasic, &FindFileData, FindExSearchNameMatch, NULL, 0); -#else - HANDLE hFind = FindFirstFileExA(f.c_str(), FindExInfoBasic, &FindFileData, FindExSearchNameMatch, NULL, 0); -#endif - - if (INVALID_HANDLE_VALUE == hFind) - return false; - result = FindFileData.cFileName; - realFileNameMap.addToCache(f, result); - FindClose(hFind); - } - return true; -} - -static RealFileNameMap realFilePathMap; - -/** Change case in given path to match filesystem */ -static std::string realFilename(const std::string &f) -{ - std::string ret; - ret.reserve(f.size()); // this will be the final size - if (realFilePathMap.getCacheEntry(f, ret)) - return ret; - - // Current subpath - std::string subpath; - - for (std::string::size_type pos = 0; pos < f.size(); ++pos) { - const unsigned char c = f[pos]; - - // Separator.. add subpath and separator - if (c == '/' || c == '\\') { - // if subpath is empty just add separator - if (subpath.empty()) { - ret += c; - continue; - } - - const bool isDriveSpecification = - (pos == 2 && subpath.size() == 2 && std::isalpha(subpath[0]) && subpath[1] == ':'); - - // Append real filename (proper case) - std::string f2; - if (!isDriveSpecification && realFileName(f.substr(0, pos), f2)) - ret += f2; - else - ret += subpath; - - subpath.clear(); - - // Append separator - ret += c; - } else { - subpath += c; - } - } - - if (!subpath.empty()) { - std::string f2; - if (realFileName(f,f2)) - ret += f2; - else - ret += subpath; - } - - realFilePathMap.addToCache(f, ret); - return ret; -} - static bool isAbsolutePath(const std::string &path) { if (path.length() >= 3 && path[0] > 0 && std::isalpha(path[0]) && path[1] == ':' && (path[2] == '\\' || path[2] == '/')) @@ -2542,8 +2407,6 @@ static bool isAbsolutePath(const std::string &path) return path.length() > 1U && (path[0] == '/' || path[0] == '\\'); } #else -#define realFilename(f) f - static bool isAbsolutePath(const std::string &path) { return path.length() > 1U && path[0] == '/'; @@ -2621,8 +2484,7 @@ namespace simplecpp { if (unc) path = '/' + path; - // cppcheck-suppress duplicateExpressionTernary - platform-dependent implementation - return strpbrk(path.c_str(), "*?") == nullptr ? realFilename(path) : path; + return path; } } @@ -2684,37 +2546,8 @@ static bool isGnu(const simplecpp::DUI &dui) return dui.std.rfind("gnu", 0) != std::string::npos; } -static std::string currentDirectoryOSCalc() { - const std::size_t size = 4096; - char currentPath[size]; - -#ifndef _WIN32 - if (getcwd(currentPath, size) != nullptr) -#else - if (_getcwd(currentPath, size) != nullptr) -#endif - return std::string(currentPath); - - return ""; -} - -static const std::string& currentDirectory() { - static const std::string curdir = simplecpp::simplifyPath(currentDirectoryOSCalc()); - return curdir; -} - -static std::string toAbsolutePath(const std::string& path) { - if (path.empty()) { - return path;// preserve error file path that is indicated by an empty string - } - if (!isAbsolutePath(path)) { - return simplecpp::simplifyPath(currentDirectory() + "/" + path); - } - // otherwise - return simplecpp::simplifyPath(path); -} - -static std::string dirPath(const std::string& path, bool withTrailingSlash=true) { +static std::string dirPath(const std::string& path, bool withTrailingSlash=true) +{ const std::size_t lastSlash = path.find_last_of("\\/"); if (lastSlash == std::string::npos) { return ""; @@ -2722,36 +2555,6 @@ static std::string dirPath(const std::string& path, bool withTrailingSlash=true) return path.substr(0, lastSlash + (withTrailingSlash ? 1U : 0U)); } -static std::string omitPathTrailingSlash(const std::string& path) { - if (endsWith(path, "/")) { - return path.substr(0, path.size() - 1U); - } - return path; -} - -static std::string extractRelativePathFromAbsolute(const std::string& absoluteSimplifiedPath, const std::string& prefixSimplifiedAbsoluteDir = currentDirectory()) { - const std::string normalizedAbsolutePath = omitPathTrailingSlash(absoluteSimplifiedPath); - std::string currentPrefix = omitPathTrailingSlash(prefixSimplifiedAbsoluteDir); - std::string leadingParenting; - while (!startsWith_(normalizedAbsolutePath, currentPrefix)) { - leadingParenting = "../" + leadingParenting; - currentPrefix = dirPath(currentPrefix, false); - } - const std::size_t size = currentPrefix.size(); - std::string relativeFromMeetingPath = normalizedAbsolutePath.substr(size, normalizedAbsolutePath.size() - size); - if (currentPrefix.empty() && !(startsWith_(absoluteSimplifiedPath, "/") && startsWith_(prefixSimplifiedAbsoluteDir, "/"))) { - // In the case that there is no common prefix path, - // and at not both of the paths start with `/` (can happen only in Windows paths on distinct partitions), - // return the absolute simplified path as is because no relative path can match. - return absoluteSimplifiedPath; - } - if (startsWith_(relativeFromMeetingPath, "/")) { - // omit the leading slash - relativeFromMeetingPath = relativeFromMeetingPath.substr(1, relativeFromMeetingPath.size()); - } - return leadingParenting + relativeFromMeetingPath; -} - static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader); static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI &dui) { @@ -2796,10 +2599,8 @@ static void simplifyHasInclude(simplecpp::TokenList &expr, const simplecpp::DUI for (simplecpp::Token *headerToken = tok1->next; headerToken != tok3; headerToken = headerToken->next) header += headerToken->str(); - // cppcheck-suppress selfAssignment - platform-dependent implementation - header = realFilename(header); } else { - header = realFilename(tok1->str().substr(1U, tok1->str().size() - 2U)); + header = tok1->str().substr(1U, tok1->str().size() - 2U); } std::ifstream f; const std::string header2 = openHeader(f,dui,sourcefile,header,systemheader); @@ -3131,206 +2932,185 @@ class NonExistingFilesCache { NonExistingFilesCache() {} bool contains(const std::string& path) { - MyLock lock(m_mutex); + std::lock_guard lock(m_mutex); return (m_pathSet.find(path) != m_pathSet.end()); } void add(const std::string& path) { - MyLock lock(m_mutex); + std::lock_guard lock(m_mutex); m_pathSet.insert(path); } void clear() { - MyLock lock(m_mutex); + std::lock_guard lock(m_mutex); m_pathSet.clear(); } private: std::set m_pathSet; - MyMutex m_mutex; + std::mutex m_mutex; }; static NonExistingFilesCache nonExistingFilesCache; #endif -static std::string openHeader(std::ifstream &f, const std::string &path) +static std::string openHeaderDirect(std::ifstream &f, const std::string &path) { - std::string simplePath = simplecpp::simplifyPath(path); #ifdef SIMPLECPP_WINDOWS - if (nonExistingFilesCache.contains(simplePath)) + if (nonExistingFilesCache.contains(path)) return ""; // file is known not to exist, skip expensive file open call #endif - f.open(simplePath.c_str()); + f.open(path.c_str()); if (f.is_open()) - return simplePath; + return path; #ifdef SIMPLECPP_WINDOWS - nonExistingFilesCache.add(simplePath); + nonExistingFilesCache.add(path); #endif return ""; } -static std::string getRelativeFileName(const std::string &baseFile, const std::string &header, bool returnAbsolutePath) -{ - const std::string baseFileSimplified = simplecpp::simplifyPath(baseFile); - const std::string baseFileAbsolute = isAbsolutePath(baseFileSimplified) ? - baseFileSimplified : - simplecpp::simplifyPath(currentDirectory() + "/" + baseFileSimplified); - - const std::string headerSimplified = simplecpp::simplifyPath(header); - const std::string path = isAbsolutePath(headerSimplified) ? - headerSimplified : - simplecpp::simplifyPath(dirPath(baseFileAbsolute) + headerSimplified); - - return returnAbsolutePath ? toAbsolutePath(path) : extractRelativePathFromAbsolute(path); -} - -static std::string openHeaderRelative(std::ifstream &f, const std::string &sourcefile, const std::string &header) -{ - return openHeader(f, getRelativeFileName(sourcefile, header, isAbsolutePath(sourcefile))); -} - -// returns the simplified header path: -// * If the header path is absolute, returns it in absolute path -// * Otherwise, returns it in relative path with respect to the current directory -static std::string getIncludePathFileName(const std::string &includePath, const std::string &header) +static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader) { - std::string simplifiedHeader = simplecpp::simplifyPath(header); + if (isAbsolutePath(header)) + return openHeaderDirect(f, simplecpp::simplifyPath(header)); - if (isAbsolutePath(simplifiedHeader)) { - return simplifiedHeader; + // prefer first to search the header relatively to source file if found, when not a system header + if (!systemheader) { + std::string path = openHeaderDirect(f, simplecpp::simplifyPath(dirPath(sourcefile) + header)); + if (!path.empty()) { + return path; + } } - std::string basePath = toAbsolutePath(includePath); - if (!basePath.empty() && basePath[basePath.size()-1U]!='/' && basePath[basePath.size()-1U]!='\\') - basePath += '/'; - const std::string absoluteSimplifiedHeaderPath = simplecpp::simplifyPath(basePath + simplifiedHeader); - // preserve absoluteness/relativieness of the including dir - return isAbsolutePath(includePath) ? absoluteSimplifiedHeaderPath : extractRelativePathFromAbsolute(absoluteSimplifiedHeaderPath); -} - -static std::string openHeaderIncludePath(std::ifstream &f, const simplecpp::DUI &dui, const std::string &header) -{ - for (std::list::const_iterator it = dui.includePaths.begin(); it != dui.includePaths.end(); ++it) { - std::string path = openHeader(f, getIncludePathFileName(*it, header)); + // search the header on the include paths (provided by the flags "-I...") + for (const auto &includePath : dui.includePaths) { + std::string path = openHeaderDirect(f, simplecpp::simplifyPath(includePath + "/" + header)); if (!path.empty()) return path; } return ""; } -static std::string openHeader(std::ifstream &f, const simplecpp::DUI &dui, const std::string &sourcefile, const std::string &header, bool systemheader) +std::pair simplecpp::FileDataCache::tryload(FileDataCache::name_map_type::iterator &name_it, const simplecpp::DUI &dui, std::vector &filenames, simplecpp::OutputList *outputList) { - if (isAbsolutePath(header)) - return openHeader(f, header); + const std::string &path = name_it->first; + FileID fileId; - // prefer first to search the header relatively to source file if found, when not a system header - if (!systemheader) { - std::string relativeHeader = openHeaderRelative(f, sourcefile, header); - if (!relativeHeader.empty()) { - return relativeHeader; - } + if (!getFileId(path, fileId)) + return {nullptr, false}; + + const auto id_it = mIdMap.find(fileId); + if (id_it != mIdMap.end()) { + name_it->second = id_it->second; + return {id_it->second, false}; } - // search the header on the include paths (provided by the flags "-I...") - return openHeaderIncludePath(f, dui, header); -} + std::ifstream f(path); + FileData *const data = new FileData {path, TokenList(f, filenames, path, outputList)}; -static std::string findPathInMapBothRelativeAndAbsolute(const std::map &filedata, const std::string& path) { - // here there are two possibilities - either we match this from absolute path or from a relative one - if (filedata.find(path) != filedata.end()) {// try first to respect the exact match - return path; - } + if (dui.removeComments) + data->tokens.removeComments(); - // otherwise - try to use the normalize to the correct representation - std::string alternativePath; - if (isAbsolutePath(path)) { - alternativePath = extractRelativePathFromAbsolute(simplecpp::simplifyPath(path)); - } else { - alternativePath = toAbsolutePath(path); - } + name_it->second = data; + mIdMap.emplace(fileId, data); + mData.emplace_back(data); - if (filedata.find(alternativePath) != filedata.end()) { - return alternativePath; - } - return ""; + return {data, true}; } -static std::string getFileIdPath(const std::map &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader) +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) { - if (filedata.empty()) { - return ""; - } if (isAbsolutePath(header)) { - const std::string simplifiedHeaderPath = simplecpp::simplifyPath(header); - const std::string match = findPathInMapBothRelativeAndAbsolute(filedata, simplifiedHeaderPath); - if (!match.empty()) { - return match; + auto ins = mNameMap.emplace(simplecpp::simplifyPath(header), nullptr); + + if (ins.second) { + const auto ret = tryload(ins.first, dui, filenames, outputList); + if (ret.first != nullptr) { + return ret; + } + } else { + return {ins.first->second, false}; } + + return {nullptr, false}; } if (!systemheader) { - const std::string relativeFilename = getRelativeFileName(sourcefile, header, true); - const std::string match = findPathInMapBothRelativeAndAbsolute(filedata, relativeFilename); - if (!match.empty()) { - return match; - } - // if the file exists but hasn't been loaded yet then we need to stop searching here or we could get a false match - std::ifstream f; - openHeader(f, relativeFilename); - if (f.is_open()) { - f.close(); - return ""; + auto ins = mNameMap.emplace(simplecpp::simplifyPath(dirPath(sourcefile) + header), nullptr); + + if (ins.second) { + const auto ret = tryload(ins.first, dui, filenames, outputList); + if (ret.first != nullptr) { + return ret; + } + } else if (ins.first->second != nullptr) { + return {ins.first->second, false}; } - } else if (filedata.find(header) != filedata.end()) { - return header;// system header that its file is already in the filedata - return that as is } - for (std::list::const_iterator it = dui.includePaths.begin(); it != dui.includePaths.end(); ++it) { - const std::string match = findPathInMapBothRelativeAndAbsolute(filedata, getIncludePathFileName(*it, header)); - if (!match.empty()) { - return match; + for (const auto &includePath : dui.includePaths) { + auto ins = mNameMap.emplace(simplecpp::simplifyPath(includePath + "/" + header), nullptr); + + if (ins.second) { + const auto ret = tryload(ins.first, dui, filenames, outputList); + if (ret.first != nullptr) { + return ret; + } + } else if (ins.first->second != nullptr) { + return {ins.first->second, false}; } } - return ""; + return {nullptr, false}; } -static bool hasFile(const std::map &filedata, const std::string &sourcefile, const std::string &header, const simplecpp::DUI &dui, bool systemheader) +bool simplecpp::FileDataCache::getFileId(const std::string &path, FileID &id) { - return !getFileIdPath(filedata, sourcefile, header, dui, systemheader).empty(); -} +#ifdef SIMPLECPP_WINDOWS + HANDLE hFile = CreateFileA(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); -static void safeInsertTokenListToMap(std::map &filedata, const std::string &header2, simplecpp::TokenList *tokens, const std::string &header, const std::string &sourcefile, bool systemheader, const char* contextDesc) -{ - const bool inserted = filedata.insert(std::make_pair(header2, tokens)).second; - if (!inserted) { - std::cerr << "error in " << contextDesc << " - attempt to add a tokenized file to the file map, but this file is already in the map! Details:" << - "header: " << header << " header2: " << header2 << " source: " << sourcefile << " systemheader: " << systemheader << std::endl; - std::abort(); - } + if (hFile == INVALID_HANDLE_VALUE) + return false; + + const BOOL ret = GetFileInformationByHandleEx(hFile, FileIdInfo, &id.fileIdInfo, sizeof(id.fileIdInfo)); + + CloseHandle(hFile); + + return ret == TRUE; +#else + struct stat statbuf; + + if (stat(path.c_str(), &statbuf) != 0) + return false; + + id.dev = statbuf.st_dev; + id.ino = statbuf.st_ino; + + return true; +#endif } -std::map simplecpp::load(const simplecpp::TokenList &rawtokens, std::vector &filenames, const simplecpp::DUI &dui, simplecpp::OutputList *outputList) +simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, std::vector &filenames, const simplecpp::DUI &dui, simplecpp::OutputList *outputList) { #ifdef SIMPLECPP_WINDOWS if (dui.clearIncludeCache) nonExistingFilesCache.clear(); #endif - std::map ret; + FileDataCache cache; std::list filelist; // -include files for (std::list::const_iterator it = dui.includes.begin(); it != dui.includes.end(); ++it) { - const std::string &filename = realFilename(*it); + const std::string &filename = *it; - if (ret.find(filename) != ret.end()) - continue; + const auto loadResult = cache.get("", filename, dui, false, filenames, outputList); + const bool loaded = loadResult.second; + FileData *const filedata = loadResult.first; - std::ifstream fin(filename.c_str()); - if (!fin.is_open()) { + if (filedata == nullptr) { if (outputList) { simplecpp::Output err(filenames); err.type = simplecpp::Output::EXPLICIT_INCLUDE_NOT_FOUND; @@ -3340,18 +3120,17 @@ std::map simplecpp::load(const simplecpp::To } continue; } - fin.close(); - TokenList *tokenlist = new TokenList(filename, filenames, outputList); - if (!tokenlist->front()) { - delete tokenlist; + if (!loaded) + continue; + + if (!filedata->tokens.front()) continue; - } if (dui.removeComments) - tokenlist->removeComments(); - ret[filename] = tokenlist; - filelist.push_back(tokenlist->front()); + filedata->tokens.removeComments(); + + filelist.push_back(filedata->tokens.front()); } for (const Token *rawtok = rawtokens.cfront(); rawtok || !filelist.empty(); rawtok = rawtok ? rawtok->next : nullptr) { @@ -3374,25 +3153,20 @@ std::map simplecpp::load(const simplecpp::To continue; const bool systemheader = (htok->str()[0] == '<'); - const std::string header(realFilename(htok->str().substr(1U, htok->str().size() - 2U))); - if (hasFile(ret, sourcefile, header, dui, systemheader)) - continue; + const std::string header(htok->str().substr(1U, htok->str().size() - 2U)); - std::ifstream f; - const std::string header2 = openHeader(f,dui,sourcefile,header,systemheader); - if (!f.is_open()) + FileData *const filedata = cache.get(sourcefile, header, dui, systemheader, filenames, outputList).first; + if (!filedata) continue; - f.close(); - TokenList *tokens = new TokenList(header2, filenames, outputList); if (dui.removeComments) - tokens->removeComments(); - safeInsertTokenListToMap(ret, header2, tokens, header, rawtok->location.file(), systemheader, "simplecpp::load"); - if (tokens->front()) - filelist.push_back(tokens->front()); + filedata->tokens.removeComments(); + + if (filedata->tokens.front()) + filelist.push_back(filedata->tokens.front()); } - return ret; + return cache; } static bool preprocessToken(simplecpp::TokenList &output, const simplecpp::Token **tok1, simplecpp::MacroMap ¯os, std::vector &files, simplecpp::OutputList *outputList) @@ -3448,7 +3222,7 @@ static std::string getTimeDefine(const struct tm *timep) return std::string("\"").append(buf).append("\""); } -void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenList &rawtokens, std::vector &files, std::map &filedata, const simplecpp::DUI &dui, simplecpp::OutputList *outputList, std::list *macroUsage, std::list *ifCond) +void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenList &rawtokens, std::vector &files, simplecpp::FileDataCache &cache, const simplecpp::DUI &dui, simplecpp::OutputList *outputList, std::list *macroUsage, std::list *ifCond) { #ifdef SIMPLECPP_WINDOWS if (dui.clearIncludeCache) @@ -3548,9 +3322,9 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL includetokenstack.push(rawtokens.cfront()); for (std::list::const_iterator it = dui.includes.begin(); it != dui.includes.end(); ++it) { - const std::map::const_iterator f = filedata.find(*it); - if (f != filedata.end()) - includetokenstack.push(f->second->cfront()); + const FileData *const filedata = cache.get("", *it, dui, false, files, outputList).first; + if (filedata != nullptr && filedata->tokens.cfront() != nullptr) + includetokenstack.push(filedata->tokens.cfront()); } std::map > maybeUsedMacros; @@ -3681,21 +3455,9 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL const Token * const inctok = inc2.cfront(); const bool systemheader = (inctok->str()[0] == '<'); - const std::string header(realFilename(inctok->str().substr(1U, inctok->str().size() - 2U))); - std::string header2 = getFileIdPath(filedata, rawtok->location.file(), header, dui, systemheader); - if (header2.empty()) { - // try to load file.. - std::ifstream f; - header2 = openHeader(f, dui, rawtok->location.file(), header, systemheader); - if (f.is_open()) { - f.close(); - TokenList * const tokens = new TokenList(header2, files, outputList); - if (dui.removeComments) - tokens->removeComments(); - safeInsertTokenListToMap(filedata, header2, tokens, header, rawtok->location.file(), systemheader, "simplecpp::preprocess"); - } - } - if (header2.empty()) { + 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; + if (filedata == nullptr) { if (outputList) { simplecpp::Output out(files); out.type = Output::MISSING_HEADER; @@ -3711,10 +3473,9 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL out.msg = "#include nested too deeply"; outputList->push_back(out); } - } else if (pragmaOnce.find(header2) == pragmaOnce.end()) { + } else if (pragmaOnce.find(filedata->filename) == pragmaOnce.end()) { includetokenstack.push(gotoNextLine(rawtok)); - const TokenList * const includetokens = filedata.find(header2)->second; - rawtok = includetokens ? includetokens->cfront() : nullptr; + rawtok = filedata->tokens.cfront(); continue; } } else if (rawtok->str() == IF || rawtok->str() == IFDEF || rawtok->str() == IFNDEF || rawtok->str() == ELIF) { @@ -3791,12 +3552,10 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL if (systemheader) { while ((tok = tok->next) && tok->op != '>') header += tok->str(); - // cppcheck-suppress selfAssignment - platform-dependent implementation - header = realFilename(header); if (tok && tok->op == '>') closingAngularBracket = true; } else { - header = realFilename(tok->str().substr(1U, tok->str().size() - 2U)); + header = tok->str().substr(1U, tok->str().size() - 2U); closingAngularBracket = true; } std::ifstream f; @@ -3956,11 +3715,9 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL } } -void simplecpp::cleanup(std::map &filedata) +void simplecpp::cleanup(FileDataCache &cache) { - for (std::map::iterator it = filedata.begin(); it != filedata.end(); ++it) - delete it->second; - filedata.clear(); + cache.clear(); } simplecpp::cstd_t simplecpp::getCStd(const std::string &std) diff --git a/simplecpp.h b/simplecpp.h index 579e6e14..76487d6c 100755 --- a/simplecpp.h +++ b/simplecpp.h @@ -6,13 +6,19 @@ #ifndef simplecppH #define simplecppH +#if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__) +# define SIMPLECPP_WINDOWS +#endif + #include #include #include #include #include +#include #include #include +#include #include #ifdef _WIN32 @@ -27,6 +33,12 @@ # define SIMPLECPP_LIB #endif +#ifdef SIMPLECPP_WINDOWS +# include +#else +# include +#endif + #if defined(_MSC_VER) # pragma warning(push) // suppress warnings about "conversion from 'type1' to 'type2', possible loss of data" @@ -43,6 +55,7 @@ namespace simplecpp { typedef std::string TokenString; class Macro; + class FileDataCache; /** * Location in source code @@ -342,7 +355,7 @@ namespace simplecpp { SIMPLECPP_LIB long long characterLiteralToLL(const std::string& str); - SIMPLECPP_LIB std::map load(const TokenList &rawtokens, std::vector &filenames, const DUI &dui, OutputList *outputList = nullptr); + SIMPLECPP_LIB FileDataCache load(const TokenList &rawtokens, std::vector &filenames, const DUI &dui, OutputList *outputList = nullptr); /** * Preprocess @@ -350,18 +363,18 @@ namespace simplecpp { * @param output TokenList that receives the preprocessing output * @param rawtokens Raw tokenlist for top sourcefile * @param files internal data of simplecpp - * @param filedata output from simplecpp::load() + * @param cache output from simplecpp::load() * @param dui defines, undefs, and include paths * @param outputList output: list that will receive output messages * @param macroUsage output: macro usage * @param ifCond output: #if/#elif expressions */ - SIMPLECPP_LIB void preprocess(TokenList &output, const TokenList &rawtokens, std::vector &files, std::map &filedata, const DUI &dui, OutputList *outputList = nullptr, std::list *macroUsage = nullptr, std::list *ifCond = nullptr); + SIMPLECPP_LIB void preprocess(TokenList &output, const TokenList &rawtokens, std::vector &files, FileDataCache &cache, const DUI &dui, OutputList *outputList = nullptr, std::list *macroUsage = nullptr, std::list *ifCond = nullptr); /** * Deallocate data */ - SIMPLECPP_LIB void cleanup(std::map &filedata); + SIMPLECPP_LIB void cleanup(FileDataCache &cache); /** Simplify path */ SIMPLECPP_LIB std::string simplifyPath(std::string path); @@ -382,6 +395,116 @@ 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); + + struct SIMPLECPP_LIB FileData { + /** The canonical filename associated with this data */ + std::string filename; + /** The tokens associated with this file */ + TokenList tokens; + }; + + class SIMPLECPP_LIB FileDataCache { + public: + FileDataCache() = default; + + FileDataCache(const FileDataCache &) = delete; + FileDataCache(FileDataCache &&) = default; + + FileDataCache &operator=(const FileDataCache &) = delete; + FileDataCache &operator=(FileDataCache &&) = default; + + /** 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. */ + 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) { + FileData *const newdata = new FileData(std::move(data)); + + mData.emplace_back(newdata); + mNameMap.emplace(newdata->filename, newdata); + } + + void clear() { + mNameMap.clear(); + mIdMap.clear(); + 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; + + size_type size() const { + return mData.size(); + } + iterator begin() { + return mData.begin(); + } + iterator end() { + return mData.end(); + } + const_iterator begin() const { + return mData.begin(); + } + const_iterator end() const { + return mData.end(); + } + const_iterator cbegin() const { + return mData.cbegin(); + } + const_iterator cend() const { + return mData.cend(); + } + + private: + struct FileID { +#ifdef SIMPLECPP_WINDOWS + 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 SIMPLECPP_WINDOWS + 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 + } + }; + }; + + 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; + + }; } #if defined(_MSC_VER) diff --git a/test.cpp b/test.cpp index ba21d71b..e1968569 100644 --- a/test.cpp +++ b/test.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -100,13 +99,12 @@ static std::string readfile(const char code[], std::size_t size, simplecpp::Outp static std::string preprocess(const char code[], const simplecpp::DUI &dui, simplecpp::OutputList *outputList) { std::vector files; - std::map filedata; + simplecpp::FileDataCache cache; simplecpp::TokenList tokens = makeTokenList(code,files); tokens.removeComments(); simplecpp::TokenList tokens2(files); - simplecpp::preprocess(tokens2, tokens, files, filedata, dui, outputList); - for (auto &i : filedata) - delete i.second; + simplecpp::preprocess(tokens2, tokens, files, cache, dui, outputList); + simplecpp::cleanup(cache); return tokens2.stringify(); } @@ -1077,11 +1075,11 @@ static void error4() // "#error x\n1" const char code[] = "\xFE\xFF\x00\x23\x00\x65\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x20\x00\x78\x00\x0a\x00\x31"; std::vector files; - std::map filedata; + simplecpp::FileDataCache cache; simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); const simplecpp::TokenList rawtoken = makeTokenList(code, sizeof(code),files,"test.c"); - simplecpp::preprocess(tokens2, rawtoken, files, filedata, simplecpp::DUI(), &outputList); + simplecpp::preprocess(tokens2, rawtoken, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error x\n", toString(outputList)); } @@ -1090,11 +1088,11 @@ static void error5() // "#error x\n1" const char code[] = "\xFF\xFE\x23\x00\x65\x00\x72\x00\x72\x00\x6f\x00\x72\x00\x20\x00\x78\x00\x0a\x00\x78\x00\x31\x00"; std::vector files; - std::map filedata; + 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, filedata, simplecpp::DUI(), &outputList); + simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI(), &outputList); ASSERT_EQUALS("file0,1,#error,#error x\n", toString(outputList)); } @@ -2001,12 +1999,14 @@ static void missingHeader2() { const char code[] = "#include \"foo.h\"\n"; // this file exists std::vector files; - std::map filedata; - filedata["foo.h"] = nullptr; + simplecpp::FileDataCache cache; + cache.insert({"foo.h", simplecpp::TokenList(files)}); simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); const simplecpp::TokenList rawtokens = makeTokenList(code,files); - simplecpp::preprocess(tokens2, rawtokens, files, filedata, simplecpp::DUI(), &outputList); + simplecpp::DUI dui; + dui.includePaths.push_back("."); + simplecpp::preprocess(tokens2, rawtokens, files, cache, dui, &outputList); ASSERT_EQUALS("", toString(outputList)); } @@ -2030,13 +2030,15 @@ static void nestedInclude() { const char code[] = "#include \"test.h\"\n"; std::vector files; - simplecpp::TokenList rawtokens = makeTokenList(code,files,"test.h"); - std::map filedata; - filedata["test.h"] = &rawtokens; + const simplecpp::TokenList rawtokens = makeTokenList(code,files,"test.h"); + simplecpp::FileDataCache cache; + cache.insert({"test.h", rawtokens}); simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - simplecpp::preprocess(tokens2, rawtokens, files, filedata, simplecpp::DUI(), &outputList); + simplecpp::DUI dui; + dui.includePaths.push_back("."); + simplecpp::preprocess(tokens2, rawtokens, files, cache, dui, &outputList); ASSERT_EQUALS("file0,1,include_nested_too_deeply,#include nested too deeply\n", toString(outputList)); } @@ -2045,14 +2047,16 @@ static void systemInclude() { const char code[] = "#include \n"; std::vector files; - simplecpp::TokenList rawtokens = makeTokenList(code,files,"local/limits.h"); - std::map filedata; - filedata["limits.h"] = nullptr; - filedata["local/limits.h"] = &rawtokens; + const simplecpp::TokenList rawtokens = makeTokenList(code,files,"local/limits.h"); + simplecpp::FileDataCache cache; + cache.insert({"include/limits.h", simplecpp::TokenList(files)}); + cache.insert({"local/limits.h", rawtokens}); simplecpp::OutputList outputList; simplecpp::TokenList tokens2(files); - simplecpp::preprocess(tokens2, rawtokens, files, filedata, simplecpp::DUI(), &outputList); + simplecpp::DUI dui; + dui.includePaths.push_back("include"); + simplecpp::preprocess(tokens2, rawtokens, files, cache, dui, &outputList); ASSERT_EQUALS("", toString(outputList)); } @@ -2074,9 +2078,9 @@ static void multiline2() simplecpp::TokenList rawtokens = makeTokenList(code,files); ASSERT_EQUALS("# define A /**/ 1\n\nA", rawtokens.stringify()); rawtokens.removeComments(); - std::map filedata; + simplecpp::FileDataCache cache; simplecpp::TokenList tokens2(files); - simplecpp::preprocess(tokens2, rawtokens, files, filedata, simplecpp::DUI()); + simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI()); ASSERT_EQUALS("\n\n1", tokens2.stringify()); } @@ -2089,9 +2093,9 @@ static void multiline3() // #28 - macro with multiline comment simplecpp::TokenList rawtokens = makeTokenList(code,files); ASSERT_EQUALS("# define A /* */ 1\n\nA", rawtokens.stringify()); rawtokens.removeComments(); - std::map filedata; + simplecpp::FileDataCache cache; simplecpp::TokenList tokens2(files); - simplecpp::preprocess(tokens2, rawtokens, files, filedata, simplecpp::DUI()); + simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI()); ASSERT_EQUALS("\n\n1", tokens2.stringify()); } @@ -2105,9 +2109,9 @@ static void multiline4() // #28 - macro with multiline comment simplecpp::TokenList rawtokens = makeTokenList(code,files); ASSERT_EQUALS("# define A /* */ 1\n\n\nA", rawtokens.stringify()); rawtokens.removeComments(); - std::map filedata; + simplecpp::FileDataCache cache; simplecpp::TokenList tokens2(files); - simplecpp::preprocess(tokens2, rawtokens, files, filedata, simplecpp::DUI()); + simplecpp::preprocess(tokens2, rawtokens, files, cache, simplecpp::DUI()); ASSERT_EQUALS("\n\n\n1", tokens2.stringify()); } @@ -2221,19 +2225,21 @@ static void include3() // #16 - crash when expanding macro from header std::vector files; - simplecpp::TokenList rawtokens_c = makeTokenList(code_c, files, "A.c"); - simplecpp::TokenList rawtokens_h = makeTokenList(code_h, files, "A.h"); + const simplecpp::TokenList rawtokens_c = makeTokenList(code_c, files, "A.c"); + const simplecpp::TokenList rawtokens_h = makeTokenList(code_h, files, "A.h"); ASSERT_EQUALS(2U, files.size()); ASSERT_EQUALS("A.c", files[0]); ASSERT_EQUALS("A.h", files[1]); - std::map filedata; - filedata["A.c"] = &rawtokens_c; - filedata["A.h"] = &rawtokens_h; + simplecpp::FileDataCache cache; + cache.insert({"A.c", rawtokens_c}); + cache.insert({"A.h", rawtokens_h}); simplecpp::TokenList out(files); - simplecpp::preprocess(out, rawtokens_c, files, filedata, simplecpp::DUI()); + simplecpp::DUI dui; + dui.includePaths.push_back("."); + simplecpp::preprocess(out, rawtokens_c, files, cache, dui); ASSERT_EQUALS("\n1234", out.stringify()); } @@ -2246,21 +2252,22 @@ static void include4() // #27 - -include std::vector files; - simplecpp::TokenList rawtokens_c = makeTokenList(code_c, files, "27.c"); - simplecpp::TokenList rawtokens_h = makeTokenList(code_h, files, "27.h"); + const simplecpp::TokenList rawtokens_c = makeTokenList(code_c, files, "27.c"); + const simplecpp::TokenList rawtokens_h = makeTokenList(code_h, files, "27.h"); ASSERT_EQUALS(2U, files.size()); ASSERT_EQUALS("27.c", files[0]); ASSERT_EQUALS("27.h", files[1]); - std::map filedata; - filedata["27.c"] = &rawtokens_c; - filedata["27.h"] = &rawtokens_h; + simplecpp::FileDataCache cache; + cache.insert({"27.c", rawtokens_c}); + cache.insert({"27.h", rawtokens_h}); simplecpp::TokenList out(files); simplecpp::DUI dui; + dui.includePaths.push_back("."); dui.includes.push_back("27.h"); - simplecpp::preprocess(out, rawtokens_c, files, filedata, dui); + simplecpp::preprocess(out, rawtokens_c, files, cache, dui); ASSERT_EQUALS("123", out.stringify()); } @@ -2272,19 +2279,21 @@ static void include5() // #3 - handle #include MACRO std::vector files; - simplecpp::TokenList rawtokens_c = makeTokenList(code_c, files, "3.c"); - simplecpp::TokenList rawtokens_h = makeTokenList(code_h, files, "3.h"); + const simplecpp::TokenList rawtokens_c = makeTokenList(code_c, files, "3.c"); + const simplecpp::TokenList rawtokens_h = makeTokenList(code_h, files, "3.h"); ASSERT_EQUALS(2U, files.size()); ASSERT_EQUALS("3.c", files[0]); ASSERT_EQUALS("3.h", files[1]); - std::map filedata; - filedata["3.c"] = &rawtokens_c; - filedata["3.h"] = &rawtokens_h; + simplecpp::FileDataCache cache; + cache.insert({"3.c", rawtokens_c}); + cache.insert({"3.h", rawtokens_h}); simplecpp::TokenList out(files); - simplecpp::preprocess(out, rawtokens_c, files, filedata, simplecpp::DUI()); + simplecpp::DUI dui; + dui.includePaths.push_back("."); + simplecpp::preprocess(out, rawtokens_c, files, cache, dui); ASSERT_EQUALS("\n#line 1 \"3.h\"\n123", out.stringify()); } @@ -2295,16 +2304,16 @@ static void include6() // #57 - incomplete macro #include MACRO(,) std::vector files; - simplecpp::TokenList rawtokens = makeTokenList(code, files, "57.c"); + const simplecpp::TokenList rawtokens = makeTokenList(code, files, "57.c"); ASSERT_EQUALS(1U, files.size()); ASSERT_EQUALS("57.c", files[0]); - std::map filedata; - filedata["57.c"] = &rawtokens; + simplecpp::FileDataCache cache; + cache.insert({"57.c", rawtokens}); simplecpp::TokenList out(files); - simplecpp::preprocess(out, rawtokens, files, filedata, simplecpp::DUI()); + simplecpp::preprocess(out, rawtokens, files, cache, simplecpp::DUI()); } @@ -2316,21 +2325,21 @@ static void include7() // #include MACRO std::vector files; - simplecpp::TokenList rawtokens_c = makeTokenList(code_c, files, "3.c"); - simplecpp::TokenList rawtokens_h = makeTokenList(code_h, files, "3.h"); + const simplecpp::TokenList rawtokens_c = makeTokenList(code_c, files, "3.c"); + const simplecpp::TokenList rawtokens_h = makeTokenList(code_h, files, "3.h"); ASSERT_EQUALS(2U, files.size()); ASSERT_EQUALS("3.c", files[0]); ASSERT_EQUALS("3.h", files[1]); - std::map filedata; - filedata["3.c"] = &rawtokens_c; - filedata["3.h"] = &rawtokens_h; + simplecpp::FileDataCache cache; + cache.insert({"3.c", rawtokens_c}); + cache.insert({"3.h", rawtokens_h}); simplecpp::TokenList out(files); simplecpp::DUI dui; dui.includePaths.push_back("."); - simplecpp::preprocess(out, rawtokens_c, files, filedata, dui); + simplecpp::preprocess(out, rawtokens_c, files, cache, dui); ASSERT_EQUALS("\n#line 1 \"3.h\"\n123", out.stringify()); } @@ -2354,21 +2363,21 @@ static void include9() std::vector files; - simplecpp::TokenList rawtokens_c = makeTokenList(code_c, files, "1.c"); - simplecpp::TokenList rawtokens_h = makeTokenList(code_h, files, "1.h"); + const simplecpp::TokenList rawtokens_c = makeTokenList(code_c, files, "1.c"); + const simplecpp::TokenList rawtokens_h = makeTokenList(code_h, files, "1.h"); ASSERT_EQUALS(2U, files.size()); ASSERT_EQUALS("1.c", files[0]); ASSERT_EQUALS("1.h", files[1]); - std::map filedata; - filedata["1.c"] = &rawtokens_c; - filedata["1.h"] = &rawtokens_h; + simplecpp::FileDataCache cache; + cache.insert({"1.c", rawtokens_c}); + cache.insert({"1.h", rawtokens_h}); simplecpp::TokenList out(files); simplecpp::DUI dui; dui.includePaths.push_back("."); - simplecpp::preprocess(out, rawtokens_c, files, filedata, dui); + simplecpp::preprocess(out, rawtokens_c, files, cache, dui); ASSERT_EQUALS("\n#line 2 \"1.h\"\nx = 1 ;", out.stringify()); } @@ -2536,19 +2545,21 @@ static void stringify1() std::vector files; - simplecpp::TokenList rawtokens_c = makeTokenList(code_c, files, "A.c"); - simplecpp::TokenList rawtokens_h = makeTokenList(code_h, files, "A.h"); + const simplecpp::TokenList rawtokens_c = makeTokenList(code_c, files, "A.c"); + const simplecpp::TokenList rawtokens_h = makeTokenList(code_h, files, "A.h"); ASSERT_EQUALS(2U, files.size()); ASSERT_EQUALS("A.c", files[0]); ASSERT_EQUALS("A.h", files[1]); - std::map filedata; - filedata["A.c"] = &rawtokens_c; - filedata["A.h"] = &rawtokens_h; + simplecpp::FileDataCache cache; + cache.insert({"A.c", rawtokens_c}); + cache.insert({"A.h", rawtokens_h}); simplecpp::TokenList out(files); - simplecpp::preprocess(out, rawtokens_c, files, filedata, simplecpp::DUI()); + simplecpp::DUI dui; + dui.includePaths.push_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()); } @@ -2558,10 +2569,10 @@ static void tokenMacro1() const char code[] = "#define A 123\n" "A"; std::vector files; - std::map filedata; + simplecpp::FileDataCache cache; simplecpp::TokenList tokenList(files); const simplecpp::TokenList rawtokens = makeTokenList(code,files); - simplecpp::preprocess(tokenList, rawtokens, files, filedata, simplecpp::DUI()); + simplecpp::preprocess(tokenList, rawtokens, files, cache, simplecpp::DUI()); ASSERT_EQUALS("A", tokenList.cback()->macro); } @@ -2570,10 +2581,10 @@ static void tokenMacro2() const char code[] = "#define ADD(X,Y) X+Y\n" "ADD(1,2)"; std::vector files; - std::map filedata; + simplecpp::FileDataCache cache; simplecpp::TokenList tokenList(files); const simplecpp::TokenList rawtokens = makeTokenList(code,files); - simplecpp::preprocess(tokenList, rawtokens, files, filedata, simplecpp::DUI()); + simplecpp::preprocess(tokenList, rawtokens, files, cache, simplecpp::DUI()); const simplecpp::Token *tok = tokenList.cfront(); ASSERT_EQUALS("1", tok->str()); ASSERT_EQUALS("", tok->macro); @@ -2591,10 +2602,10 @@ static void tokenMacro3() "#define FRED 1\n" "ADD(FRED,2)"; std::vector files; - std::map filedata; + simplecpp::FileDataCache cache; simplecpp::TokenList tokenList(files); const simplecpp::TokenList rawtokens = makeTokenList(code,files); - simplecpp::preprocess(tokenList, rawtokens, files, filedata, simplecpp::DUI()); + simplecpp::preprocess(tokenList, rawtokens, files, cache, simplecpp::DUI()); const simplecpp::Token *tok = tokenList.cfront(); ASSERT_EQUALS("1", tok->str()); ASSERT_EQUALS("FRED", tok->macro); @@ -2612,10 +2623,10 @@ static void tokenMacro4() "#define B 1\n" "A"; std::vector files; - std::map filedata; + simplecpp::FileDataCache cache; simplecpp::TokenList tokenList(files); const simplecpp::TokenList rawtokens = makeTokenList(code,files); - simplecpp::preprocess(tokenList, rawtokens, files, filedata, simplecpp::DUI()); + 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); @@ -2627,10 +2638,10 @@ static void tokenMacro5() "#define SET_BPF_JUMP(code) SET_BPF(D | code)\n" "SET_BPF_JUMP(A | B | C);"; std::vector files; - std::map filedata; + simplecpp::FileDataCache cache; simplecpp::TokenList tokenList(files); const simplecpp::TokenList rawtokens = makeTokenList(code,files); - simplecpp::preprocess(tokenList, rawtokens, files, filedata, simplecpp::DUI()); + 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); @@ -3064,8 +3075,8 @@ static void preprocess_files() ASSERT_EQUALS(1, files.size()); ASSERT_EQUALS("", *files.cbegin()); - std::map filedata; - simplecpp::preprocess(tokens2, tokens, files, filedata, simplecpp::DUI(), nullptr); + simplecpp::FileDataCache cache; + simplecpp::preprocess(tokens2, tokens, files, cache, simplecpp::DUI(), nullptr); ASSERT_EQUALS(1, files.size()); ASSERT_EQUALS("", *files.cbegin()); } @@ -3081,8 +3092,8 @@ static void preprocess_files() ASSERT_EQUALS(1, files.size()); ASSERT_EQUALS("test.cpp", *files.cbegin()); - std::map filedata; - simplecpp::preprocess(tokens2, tokens, files, filedata, simplecpp::DUI(), nullptr); + simplecpp::FileDataCache cache; + simplecpp::preprocess(tokens2, tokens, files, cache, simplecpp::DUI(), nullptr); ASSERT_EQUALS(1, files.size()); ASSERT_EQUALS("test.cpp", *files.cbegin()); } From 6dd82cb039f669f83150aff400721b8432134b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Tue, 8 Jul 2025 16:42:31 +0200 Subject: [PATCH 034/119] fixed #466 - CI-unixish.yml: added missing `UBSAN_OPTIONS` (#467) --- .github/workflows/CI-unixish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index f5a78ea5..ff7c3f65 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -101,6 +101,8 @@ jobs: run: | make clean make -j$(nproc) test selfcheck CXXFLAGS="-O2 -g3 -fsanitize=undefined -fno-sanitize=signed-integer-overflow" LDFLAGS="-fsanitize=undefined -fno-sanitize=signed-integer-overflow" + env: + UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1:report_error_type=1 # TODO: requires instrumented libc++ - name: Run MemorySanitizer From bd068aeaae1414104458b9fd79911dfb8462ebdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Mon, 14 Jul 2025 12:12:20 +0200 Subject: [PATCH 035/119] fixed #464 - added integration test to `test` make target (#465) --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 73977517..4a6ae6b7 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ testrunner: test.o simplecpp.o test: testrunner simplecpp ./testrunner python3 run-tests.py + python3 -m pytest integration_test.py -vv selfcheck: simplecpp ./selfcheck.sh From 5bf471de82684afe7af168739c037fbf6a248d69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Mon, 14 Jul 2025 12:40:45 +0200 Subject: [PATCH 036/119] added test for #346 (#457) --- test.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test.cpp b/test.cpp index e1968569..3c5f5488 100644 --- a/test.cpp +++ b/test.cpp @@ -3106,6 +3106,11 @@ static void fuzz_crash() "n\n"; (void)preprocess(code, simplecpp::DUI()); // do not crash } + { // #346 + const char code[] = "#define foo(intp)f##oo(intp\n" + "foo(f##oo(intp))\n"; + (void)preprocess(code, simplecpp::DUI()); // do not crash + } } int main(int argc, char **argv) From 29abbc6bdb29eb72d37892dab776e48e9998d8bc Mon Sep 17 00:00:00 2001 From: clock999 Date: Tue, 15 Jul 2025 19:50:26 +0800 Subject: [PATCH 037/119] fix #337 - line splicing in comment not handled properly (#431) --- simplecpp.cpp | 33 +++++++++++++++++++++++++-------- test.cpp | 25 ++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 128da0bd..721bbdeb 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -758,17 +758,34 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, // comment else if (ch == '/' && stream.peekChar() == '/') { - while (stream.good() && ch != '\r' && ch != '\n') { + 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 { + 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); + ++multiline; + tmp_ch = stream.readChar(); + } + ch = tmp_ch; + } } - const std::string::size_type pos = currentToken.find_last_not_of(" \t"); - if (pos < currentToken.size() - 1U && currentToken[pos] == '\\') - portabilityBackslash(outputList, files, location); - if (currentToken[currentToken.size() - 1U] == '\\') { - ++multiline; - currentToken.erase(currentToken.size() - 1U); - } else { + if (ch == '\n') { stream.ungetChar(); } } diff --git a/test.cpp b/test.cpp index 3c5f5488..a7578823 100644 --- a/test.cpp +++ b/test.cpp @@ -434,7 +434,30 @@ static void comment_multiline() 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)); + + const char code1[] = "#define ABC {// \\\r\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"; + ASSERT_EQUALS("\n\n2\n1", preprocess(code2)); + + const char code3[] = "void f() {// \\ \n}\n"; + ASSERT_EQUALS("void f ( ) {", preprocess(code3)); + + const char code4[] = "void f() {// \\\\\\\t\t\n}\n"; + ASSERT_EQUALS("void f ( ) {", preprocess(code4)); + + const char code5[] = "void f() {// \\\\\\a\n}\n"; + ASSERT_EQUALS("void f ( ) {\n}", preprocess(code5)); + + const char code6[] = "void f() {// \\\n\n\n}\n"; + ASSERT_EQUALS("void f ( ) {\n\n\n}", preprocess(code6)); } From d0f2b99d656cb6dab6fc99a105a6c59cbcfbb13a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sun, 3 Aug 2025 07:42:06 +0200 Subject: [PATCH 038/119] Fix #471 (preserve line splicing information in '// ..' comments) (#472) --- simplecpp.cpp | 3 ++- test.cpp | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 721bbdeb..5093b4b7 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -775,12 +775,13 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, if(tmp_ch != '\n') { currentToken += tmp; } else { - TokenString check_portability = currentToken + tmp; + 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); ++multiline; tmp_ch = stream.readChar(); + currentToken += '\n'; } ch = tmp_ch; } diff --git a/test.cpp b/test.cpp index a7578823..de9f250b 100644 --- a/test.cpp +++ b/test.cpp @@ -458,6 +458,9 @@ static void comment_multiline() const char code6[] = "void f() {// \\\n\n\n}\n"; ASSERT_EQUALS("void f ( ) {\n\n\n}", preprocess(code6)); + + // #471 ensure there is newline in comment so that line-splicing can be detected by tools + ASSERT_EQUALS("// abc\ndef", readfile("// abc\\\ndef")); } From 435a74cc192e64499ddf96193becf8073c50376c Mon Sep 17 00:00:00 2001 From: glankk Date: Mon, 4 Aug 2025 15:14:18 +0200 Subject: [PATCH 039/119] Fix #391 (`__TIME__` replacement might be empty depending on compiler) (#441) --- simplecpp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 5093b4b7..addac46f 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3236,7 +3236,7 @@ static std::string getDateDefine(const struct tm *timep) static std::string getTimeDefine(const struct tm *timep) { char buf[] = "??:??:??"; - strftime(buf, sizeof(buf), "%T", timep); + strftime(buf, sizeof(buf), "%H:%M:%S", timep); return std::string("\"").append(buf).append("\""); } From 2b4f727da30c87ef2de79cfe81760e3b1b2ca772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Mon, 11 Aug 2025 15:59:06 +0200 Subject: [PATCH 040/119] simplecpp.cpp: fixed Visual Studio C4800 compiler warnings (#481) --- simplecpp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index addac46f..25d0d3c3 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -745,7 +745,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, // number or name if (isNameChar(ch)) { - const bool num = std::isdigit(ch); + const bool num = !!std::isdigit(ch); while (stream.good() && isNameChar(ch)) { currentToken += ch; ch = stream.readChar(); @@ -886,7 +886,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, } if (prefix.empty()) - push_back(new Token(s, location, std::isspace(stream.peekChar()))); // push string without newlines + push_back(new Token(s, location, !!std::isspace(stream.peekChar()))); // push string without newlines else back()->setstr(prefix + s); @@ -916,7 +916,7 @@ void simplecpp::TokenList::readfile(Stream &stream, const std::string &filename, } } - push_back(new Token(currentToken, location, std::isspace(stream.peekChar()))); + push_back(new Token(currentToken, location, !!std::isspace(stream.peekChar()))); if (multiline) location.col += currentToken.size(); From 5783afac7dded04a5e4bb2c9b6b6b593ea2a4c4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Mon, 11 Aug 2025 16:00:16 +0200 Subject: [PATCH 041/119] Makefile: added `CXXOPTS` and `LDOPTS` to extend `CXXFLAGS` and `LDFLAGS` (#480) --- .github/workflows/CI-unixish.yml | 10 +++++----- Makefile | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index ff7c3f65..cb498b5e 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -80,19 +80,19 @@ jobs: if: matrix.os == 'ubuntu-24.04' && matrix.compiler == 'g++' run: | make clean - make -j$(nproc) test selfcheck CXXFLAGS="-g3 -D_GLIBCXX_DEBUG" + make -j$(nproc) test selfcheck CXXOPTS="-g3 -D_GLIBCXX_DEBUG" - name: Run with libc++ hardening mode if: matrix.os == 'ubuntu-24.04' && matrix.compiler == 'clang++' run: | make clean - make -j$(nproc) test selfcheck CXXFLAGS="-stdlib=libc++ -g3 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG" LDFLAGS="-lc++" + make -j$(nproc) test selfcheck CXXOPTS="-stdlib=libc++ -g3 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG" LDOPTS="-lc++" - name: Run AddressSanitizer if: matrix.os == 'ubuntu-24.04' run: | make clean - make -j$(nproc) test selfcheck CXXFLAGS="-O2 -g3 -fsanitize=address" LDFLAGS="-fsanitize=address" + make -j$(nproc) test selfcheck CXXOPTS="-O2 -g3 -fsanitize=address" LDOPTS="-fsanitize=address" env: ASAN_OPTIONS: detect_stack_use_after_return=1 @@ -100,7 +100,7 @@ jobs: if: matrix.os == 'ubuntu-24.04' run: | make clean - make -j$(nproc) test selfcheck CXXFLAGS="-O2 -g3 -fsanitize=undefined -fno-sanitize=signed-integer-overflow" LDFLAGS="-fsanitize=undefined -fno-sanitize=signed-integer-overflow" + make -j$(nproc) test selfcheck CXXOPTS="-O2 -g3 -fsanitize=undefined -fno-sanitize=signed-integer-overflow" LDOPTS="-fsanitize=undefined -fno-sanitize=signed-integer-overflow" env: UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1:report_error_type=1 @@ -109,4 +109,4 @@ jobs: if: false && matrix.os == 'ubuntu-24.04' && matrix.compiler == 'clang++' run: | make clean - make -j$(nproc) test selfcheck CXXFLAGS="-O2 -g3 -stdlib=libc++ -fsanitize=memory" LDFLAGS="-lc++ -fsanitize=memory" + make -j$(nproc) test selfcheck CXXOPTS="-O2 -g3 -stdlib=libc++ -fsanitize=memory" LDOPTS="-lc++ -fsanitize=memory" diff --git a/Makefile b/Makefile index 4a6ae6b7..d899d2cd 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ all: testrunner simplecpp -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 -LDFLAGS = -g +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 $< From 1678b7d229a7bcf833055766afa1496d68e1397c Mon Sep 17 00:00:00 2001 From: glankk Date: Thu, 14 Aug 2025 17:53:41 +0200 Subject: [PATCH 042/119] Remove execute bit from simplecpp.cpp/h (#494) --- simplecpp.cpp | 0 simplecpp.h | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 simplecpp.cpp mode change 100755 => 100644 simplecpp.h diff --git a/simplecpp.cpp b/simplecpp.cpp old mode 100755 new mode 100644 diff --git a/simplecpp.h b/simplecpp.h old mode 100755 new mode 100644 From f790009b5f19a20b6254c03e8d02c8d3e60f1244 Mon Sep 17 00:00:00 2001 From: glankk Date: Thu, 21 Aug 2025 11:12:21 +0200 Subject: [PATCH 043/119] Fix infinite loop with circular includes (#497) --- simplecpp.cpp | 18 ++++++----- simplecpp.h | 87 +++++++++++++++++++++++++-------------------------- test.cpp | 45 ++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 51 deletions(-) diff --git a/simplecpp.cpp b/simplecpp.cpp index 25d0d3c3..fd327549 100644 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -3109,15 +3109,13 @@ bool simplecpp::FileDataCache::getFileId(const std::string &path, FileID &id) #endif } -simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, std::vector &filenames, const simplecpp::DUI &dui, simplecpp::OutputList *outputList) +simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, std::vector &filenames, const simplecpp::DUI &dui, simplecpp::OutputList *outputList, FileDataCache cache) { #ifdef SIMPLECPP_WINDOWS if (dui.clearIncludeCache) nonExistingFilesCache.clear(); #endif - FileDataCache cache; - std::list filelist; // -include files @@ -3173,15 +3171,21 @@ simplecpp::FileDataCache simplecpp::load(const simplecpp::TokenList &rawtokens, const bool systemheader = (htok->str()[0] == '<'); const std::string header(htok->str().substr(1U, htok->str().size() - 2U)); - FileData *const filedata = cache.get(sourcefile, header, dui, systemheader, filenames, outputList).first; - if (!filedata) + const auto loadResult = cache.get(sourcefile, header, dui, systemheader, filenames, outputList); + const bool loaded = loadResult.second; + + if (!loaded) + continue; + + FileData *const filedata = loadResult.first; + + if (!filedata->tokens.front()) continue; if (dui.removeComments) filedata->tokens.removeComments(); - if (filedata->tokens.front()) - filelist.push_back(filedata->tokens.front()); + filelist.push_back(filedata->tokens.front()); } return cache; diff --git a/simplecpp.h b/simplecpp.h index 76487d6c..8268fa8d 100644 --- a/simplecpp.h +++ b/simplecpp.h @@ -353,49 +353,6 @@ namespace simplecpp { bool removeComments; /** remove comment tokens from included files */ }; - 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); - - /** - * Preprocess - * @todo simplify interface - * @param output TokenList that receives the preprocessing output - * @param rawtokens Raw tokenlist for top sourcefile - * @param files internal data of simplecpp - * @param cache output from simplecpp::load() - * @param dui defines, undefs, and include paths - * @param outputList output: list that will receive output messages - * @param macroUsage output: macro usage - * @param ifCond output: #if/#elif expressions - */ - SIMPLECPP_LIB void preprocess(TokenList &output, const TokenList &rawtokens, std::vector &files, FileDataCache &cache, const DUI &dui, OutputList *outputList = nullptr, std::list *macroUsage = nullptr, std::list *ifCond = nullptr); - - /** - * Deallocate data - */ - SIMPLECPP_LIB void cleanup(FileDataCache &cache); - - /** Simplify path */ - SIMPLECPP_LIB std::string simplifyPath(std::string path); - - /** Convert Cygwin path to Windows path */ - SIMPLECPP_LIB std::string convertCygwinToWindowsPath(const std::string &cygwinPath); - - /** Returns the C version a given standard */ - SIMPLECPP_LIB cstd_t getCStd(const std::string &std); - - /** Returns the C++ version a given standard */ - SIMPLECPP_LIB cppstd_t getCppStd(const std::string &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); - - /** 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); - struct SIMPLECPP_LIB FileData { /** The canonical filename associated with this data */ std::string filename; @@ -503,8 +460,50 @@ namespace simplecpp { container_type mData; name_map_type mNameMap; id_map_type mIdMap; - }; + + 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 = {}); + + /** + * Preprocess + * @todo simplify interface + * @param output TokenList that receives the preprocessing output + * @param rawtokens Raw tokenlist for top sourcefile + * @param files internal data of simplecpp + * @param cache output from simplecpp::load() + * @param dui defines, undefs, and include paths + * @param outputList output: list that will receive output messages + * @param macroUsage output: macro usage + * @param ifCond output: #if/#elif expressions + */ + SIMPLECPP_LIB void preprocess(TokenList &output, const TokenList &rawtokens, std::vector &files, FileDataCache &cache, const DUI &dui, OutputList *outputList = nullptr, std::list *macroUsage = nullptr, std::list *ifCond = nullptr); + + /** + * Deallocate data + */ + SIMPLECPP_LIB void cleanup(FileDataCache &cache); + + /** Simplify path */ + SIMPLECPP_LIB std::string simplifyPath(std::string path); + + /** Convert Cygwin path to Windows path */ + SIMPLECPP_LIB std::string convertCygwinToWindowsPath(const std::string &cygwinPath); + + /** Returns the C version a given standard */ + SIMPLECPP_LIB cstd_t getCStd(const std::string &std); + + /** Returns the C++ version a given standard */ + SIMPLECPP_LIB cppstd_t getCppStd(const std::string &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); + + /** 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); } #if defined(_MSC_VER) diff --git a/test.cpp b/test.cpp index de9f250b..ccb653ca 100644 --- a/test.cpp +++ b/test.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #define STRINGIZE_(x) #x @@ -2087,6 +2088,49 @@ static void systemInclude() ASSERT_EQUALS("", toString(outputList)); } +static void circularInclude() +{ + std::vector files; + simplecpp::FileDataCache cache; + + { + const char *const path = "test.h"; + const char code[] = + "#ifndef TEST_H\n" + "#define TEST_H\n" + "#include \"a/a.h\"\n" + "#endif\n" + ; + cache.insert({path, makeTokenList(code, files, path)}); + } + + { + const char *const path = "a/a.h"; + const char code[] = + "#ifndef A_H\n" + "#define A_H\n" + "#include \"../test.h\"\n" + "#endif\n" + ; + cache.insert({path, makeTokenList(code, files, path)}); + } + + simplecpp::OutputList outputList; + simplecpp::TokenList tokens2(files); + { + std::vector filenames; + const simplecpp::DUI dui; + + const char code[] = "#include \"test.h\"\n"; + const simplecpp::TokenList rawtokens = makeTokenList(code, files, "test.cpp"); + + cache = simplecpp::load(rawtokens, filenames, dui, &outputList, std::move(cache)); + simplecpp::preprocess(tokens2, rawtokens, files, cache, dui, &outputList); + } + + ASSERT_EQUALS("", toString(outputList)); +} + static void multiline1() { const char code[] = "#define A \\\n" @@ -3314,6 +3358,7 @@ int main(int argc, char **argv) TEST_CASE(missingHeader4); TEST_CASE(nestedInclude); TEST_CASE(systemInclude); + TEST_CASE(circularInclude); TEST_CASE(nullDirective1); TEST_CASE(nullDirective2); From fead0b280a3b242b15191c3ebaefc42b6159eb16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 22 Aug 2025 15:18:19 +0200 Subject: [PATCH 044/119] CI-unixish.yml: removed duplicated execution of integration test (#503) --- .github/workflows/CI-unixish.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index cb498b5e..c343e279 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -51,10 +51,6 @@ jobs: run: | make -j$(nproc) selfcheck - - name: integration test - run: | - python3 -m pytest integration_test.py -vv - - name: Run CMake run: | cmake -S . -B cmake.output From 7bca11f0ec435df3c9ccefd37c9a21a3c575355b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 22 Aug 2025 18:24:41 +0200 Subject: [PATCH 045/119] CI-unixish.yml: do not run with `g++` on `macos-*` as it is just an alias for `clang++` (#483) --- .github/workflows/CI-unixish.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index c343e279..718414d8 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -7,8 +7,13 @@ jobs: strategy: matrix: - compiler: [clang++, g++] os: [ubuntu-22.04, ubuntu-24.04, macos-13, macos-14, macos-15] + compiler: [clang++] + include: + - os: ubuntu-22.04 + compiler: g++ + - os: ubuntu-24.04 + compiler: g++ fail-fast: false runs-on: ${{ matrix.os }} From 285998182edd0280a9c1b5fe877f074fe441fd16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Fri, 22 Aug 2025 20:51:46 +0200 Subject: [PATCH 046/119] fixed #478 - fail builds in CI on compiler warnings (#479) --- .github/workflows/CI-unixish.yml | 16 ++++++++-------- .github/workflows/CI-windows.yml | 2 +- .github/workflows/clang-tidy.yml | 2 +- CMakeLists.txt | 4 ++++ appveyor.yml | 3 ++- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index 718414d8..c80aaba2 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -47,10 +47,10 @@ jobs: python3 -m pip install pytest - name: make simplecpp - run: make -j$(nproc) + run: make -j$(nproc) CXXOPTS="-Werror" - name: make test - run: make -j$(nproc) test + run: make -j$(nproc) test CXXOPTS="-Werror" - name: selfcheck run: | @@ -58,7 +58,7 @@ jobs: - name: Run CMake run: | - cmake -S . -B cmake.output + cmake -S . -B cmake.output -DCMAKE_COMPILE_WARNING_AS_ERROR=On - name: CMake simplecpp run: | @@ -81,19 +81,19 @@ jobs: if: matrix.os == 'ubuntu-24.04' && matrix.compiler == 'g++' run: | make clean - make -j$(nproc) test selfcheck CXXOPTS="-g3 -D_GLIBCXX_DEBUG" + make -j$(nproc) test selfcheck CXXOPTS="-Werror -g3 -D_GLIBCXX_DEBUG" - name: Run with libc++ hardening mode if: matrix.os == 'ubuntu-24.04' && matrix.compiler == 'clang++' run: | make clean - make -j$(nproc) test selfcheck CXXOPTS="-stdlib=libc++ -g3 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG" LDOPTS="-lc++" + make -j$(nproc) test selfcheck CXXOPTS="-Werror -stdlib=libc++ -g3 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG" LDOPTS="-lc++" - name: Run AddressSanitizer if: matrix.os == 'ubuntu-24.04' run: | make clean - make -j$(nproc) test selfcheck CXXOPTS="-O2 -g3 -fsanitize=address" LDOPTS="-fsanitize=address" + make -j$(nproc) test selfcheck CXXOPTS="-Werror -O2 -g3 -fsanitize=address" LDOPTS="-fsanitize=address" env: ASAN_OPTIONS: detect_stack_use_after_return=1 @@ -101,7 +101,7 @@ jobs: if: matrix.os == 'ubuntu-24.04' run: | make clean - make -j$(nproc) test selfcheck CXXOPTS="-O2 -g3 -fsanitize=undefined -fno-sanitize=signed-integer-overflow" LDOPTS="-fsanitize=undefined -fno-sanitize=signed-integer-overflow" + make -j$(nproc) test selfcheck CXXOPTS="-Werror -O2 -g3 -fsanitize=undefined -fno-sanitize=signed-integer-overflow" LDOPTS="-fsanitize=undefined -fno-sanitize=signed-integer-overflow" env: UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1:report_error_type=1 @@ -110,4 +110,4 @@ jobs: if: false && matrix.os == 'ubuntu-24.04' && matrix.compiler == 'clang++' run: | make clean - make -j$(nproc) test selfcheck CXXOPTS="-O2 -g3 -stdlib=libc++ -fsanitize=memory" LDOPTS="-lc++ -fsanitize=memory" + make -j$(nproc) test selfcheck CXXOPTS="-Werror -O2 -g3 -stdlib=libc++ -fsanitize=memory" LDOPTS="-lc++ -fsanitize=memory" diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index 971f3827..d4c99388 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -40,7 +40,7 @@ jobs: - name: Run CMake run: | - cmake -G "Visual Studio 17 2022" -A x64 . || exit /b !errorlevel! + cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_COMPILE_WARNING_AS_ERROR=On . || exit /b !errorlevel! - name: Build run: | diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index a2f7b6dc..41d2ee6f 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -30,7 +30,7 @@ jobs: - name: Prepare CMake run: | - cmake -S . -B cmake.output -G "Unix Makefiles" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + cmake -S . -B cmake.output -G "Unix Makefiles" -DCMAKE_COMPILE_WARNING_AS_ERROR=On -DCMAKE_EXPORT_COMPILE_COMMANDS=ON env: CXX: clang-20 diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ab0166e..f9e3eb6d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,10 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") add_compile_options_safe(-Wuseless-cast) elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") add_compile_definitions(_CRT_SECURE_NO_WARNINGS) + # TODO: bump warning level + #add_compile_options(/W4) # Warning Level + # TODO: enable warning + add_compile_options(/wd4267) # warning C4267: '...': conversion from 'size_t' to 'unsigned int', possible loss of data elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Weverything) # no need for c++98 compatibility diff --git a/appveyor.yml b/appveyor.yml index ea8dd1df..09aa6cbe 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,9 +10,10 @@ environment: build_script: - ECHO Building %configuration% %platform% with MSVC %VisualStudioVersion% using %PlatformToolset% PlatformToolset - - cmake -G "Visual Studio 14" . + - cmake -DCMAKE_COMPILE_WARNING_AS_ERROR=On -G "Visual Studio 14" . - dir - 'CALL "C:\Program Files (x86)\Microsoft Visual Studio %VisualStudioVersion%\VC\vcvarsall.bat" %vcvarsall_platform%' + - set _CL_=/WX - msbuild "simplecpp.sln" /consoleloggerparameters:Verbosity=minimal /target:Build /property:Configuration="%configuration%";Platform=%platform% /p:PlatformToolset=%PlatformToolset% /maxcpucount /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" test_script: From 538c5c4cd8baf806835c0d51ce2a9814ca883a7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Sat, 23 Aug 2025 10:47:23 +0200 Subject: [PATCH 047/119] fixed #485 - addressed zizmor findings in GitHub Actions (#486) --- .github/workflows/CI-unixish.yml | 5 +++++ .github/workflows/CI-windows.yml | 5 +++++ .github/workflows/clang-tidy.yml | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/.github/workflows/CI-unixish.yml b/.github/workflows/CI-unixish.yml index c80aaba2..60361389 100644 --- a/.github/workflows/CI-unixish.yml +++ b/.github/workflows/CI-unixish.yml @@ -2,6 +2,9 @@ name: CI-unixish on: [push, pull_request] +permissions: + contents: read + jobs: build: @@ -23,6 +26,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Install missing software on ubuntu if: matrix.os == 'ubuntu-24.04' diff --git a/.github/workflows/CI-windows.yml b/.github/workflows/CI-windows.yml index d4c99388..767bba6c 100644 --- a/.github/workflows/CI-windows.yml +++ b/.github/workflows/CI-windows.yml @@ -6,6 +6,9 @@ name: CI-windows on: [push,pull_request] +permissions: + contents: read + defaults: run: shell: cmd @@ -23,6 +26,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Setup msbuild.exe uses: microsoft/setup-msbuild@v2 diff --git a/.github/workflows/clang-tidy.yml b/.github/workflows/clang-tidy.yml index 41d2ee6f..fd71bdfe 100644 --- a/.github/workflows/clang-tidy.yml +++ b/.github/workflows/clang-tidy.yml @@ -4,6 +4,9 @@ name: clang-tidy on: [push, pull_request] +permissions: + contents: read + jobs: build: @@ -11,6 +14,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Install missing software run: | From 5fcb07307bd08226831667a85984bd5a392fc640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20St=C3=B6neberg?= Date: Thu, 28 Aug 2025 17:34:20 +0200 Subject: [PATCH 048/119] 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 049/119] 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 050/119] 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 051/119] 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 052/119] 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 053/119] 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 054/119] 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 055/119] 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 056/119] 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 057/119] 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 058/119] 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 059/119] 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 060/119] 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 061/119] 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 062/119] 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 063/119] 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 064/119] 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 065/119] 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 066/119] 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 067/119] 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 068/119] 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 069/119] 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 070/119] 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 071/119] 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 072/119] 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 073/119] 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 074/119] 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 075/119] 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 076/119] 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 077/119] 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 078/119] 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 079/119] 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 080/119] 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 081/119] 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 082/119] 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 083/119] 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 084/119] 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 085/119] 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 086/119] 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 087/119] 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 088/119] 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 089/119] 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 090/119] 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 091/119] 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 092/119] 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 093/119] 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 094/119] 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 095/119] 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 096/119] 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 097/119] 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 098/119] 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 099/119] 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 100/119] 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 101/119] 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 102/119] 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 103/119] 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 104/119] 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 105/119] 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 106/119] 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 107/119] 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 108/119] 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 109/119] 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 110/119] 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 111/119] 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 112/119] 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 113/119] 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 114/119] 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 115/119] 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 116/119] 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 117/119] 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 118/119] 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 119/119] 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));