diff --git a/simplecpp.cpp b/simplecpp.cpp index 2dae2f2..14018b5 100755 --- a/simplecpp.cpp +++ b/simplecpp.cpp @@ -2177,7 +2177,7 @@ namespace simplecpp { if (!sameline(tok, tok->next) || !sameline(tok, tok->next->next)) throw invalidHashHash::unexpectedNewline(tok->location, name()); - const bool canBeConcatenatedWithEqual = A->isOneOf("+-*/%&|^") || A->str() == "<<" || A->str() == ">>"; + const bool 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); @@ -3292,6 +3292,40 @@ static std::string getTimeDefine(const struct tm *timep) return std::string("\"").append(buf).append("\""); } +// Reuse some code from https://compressionratings.com/d_archiver_template.html +// SPDX-License-Identifier: CC0-1.0 +class simplecpp::Mask : public std::string { +public: + explicit Mask(const std::string &s) : std::string(s), i(false) { + if (size_t len = length()) { + while (at(--len) != at(0)) { + switch (at(len)) { + case 'i': + i = true; + break; + default: + break; + } + } + resize(len); + if (len) + erase(0, 1); + } + } + bool match(const char *s) const { + return (i ? fnmatch : fnmatch)(ustr(c_str()), ustr(s)) == 0; + } +private: + typedef const unsigned char *ustr; + static int identity(int x) { return x; } + template + static int fnmatch(ustr m, ustr s) { + if (*m == '*') for (++m; *s; ++s) if (!fnmatch(m, s)) return 0; + return (!*s || !(normalize(*s) == normalize(*m) || *m == '?')) ? *m | *s : fnmatch(++m, ++s); + } + bool i; // whether to ignore character case +}; + 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) { #ifdef SIMPLECPP_WINDOWS @@ -3382,6 +3416,15 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL std::set pragmaOnce; + // Support an odd #pragma once usage to prevent unwanted inclusions from happening even once + // Example: + // #ifdef CPPCHECK + // # pragma once "boost/*" + // # pragma once "google/protobuf/*" + // # pragma once "*.pb.h" + // #endif + std::set pragmaOddOnce; + 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); @@ -3508,6 +3551,13 @@ 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))); + bool ignore = false; + for (std::set::iterator it = pragmaOddOnce.begin(); it != pragmaOddOnce.end() && !ignore; ++it) + ignore = it->match(header.c_str()); + if (ignore) { + rawtok = gotoNextLine(rawtok); + continue; + } std::string header2 = getFileName(filedata, rawtok->location.file(), header, dui, systemheader); if (header2.empty()) { // try to load file.. @@ -3708,7 +3758,16 @@ 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()); + std::string mask; + for (const Token *inctok = rawtok->next; sameline(rawtok, inctok = inctok->next); ) { + if (!inctok->comment) + mask += inctok->str(); + } + if (!mask.empty()) { + pragmaOddOnce.insert(Mask(mask)); + } else { + pragmaOnce.insert(rawtok->location.file()); + } } rawtok = gotoNextLine(rawtok); continue; diff --git a/simplecpp.h b/simplecpp.h index f5c6959..7e60897 100755 --- a/simplecpp.h +++ b/simplecpp.h @@ -47,6 +47,7 @@ namespace simplecpp { typedef std::string TokenString; class Macro; + class Mask; /** * Location in source code diff --git a/test.cpp b/test.cpp index 1f979dd..2c01ebc 100644 --- a/test.cpp +++ b/test.cpp @@ -18,6 +18,7 @@ #define STRINGIZE_(x) #x #define STRINGIZE(x) STRINGIZE_(x) +#define CODE(x) #x "\n" static int numberOfFailedAssertions = 0; @@ -1155,6 +1156,10 @@ static void hashhash9() "ADD_OPERATOR(&);\n" "ADD_OPERATOR(|);\n" "ADD_OPERATOR(^);\n" + "ADD_OPERATOR(=);\n" + "ADD_OPERATOR(!);\n" + "ADD_OPERATOR(<);\n" + "ADD_OPERATOR(>);\n" "ADD_OPERATOR(<<);\n" "ADD_OPERATOR(>>);\n"; const char expected[] = "\n" @@ -1166,6 +1171,10 @@ static void hashhash9() "void operator &= ( void ) { x = x & 1 ; } ;\n" "void operator |= ( void ) { x = x | 1 ; } ;\n" "void operator ^= ( void ) { x = x ^ 1 ; } ;\n" + "void operator == ( void ) { x = x = 1 ; } ;\n" + "void operator != ( void ) { x = x ! 1 ; } ;\n" + "void operator <= ( void ) { x = x < 1 ; } ;\n" + "void operator >= ( void ) { x = x > 1 ; } ;\n" "void operator <<= ( void ) { x = x << 1 ; } ;\n" "void operator >>= ( void ) { x = x >> 1 ; } ;"; ASSERT_EQUALS(expected, preprocess(code)); @@ -1374,7 +1383,7 @@ static void hashhash_invalid_string_number() "#define BAD(x) x##12345\nBAD(\"ABC\")"; simplecpp::OutputList outputList; - preprocess(code, simplecpp::DUI(), &outputList); + preprocess(code, &outputList); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'BAD', Invalid ## usage when expanding 'BAD': Combining '\"ABC\"' and '12345' yields an invalid token.\n", toString(outputList)); } @@ -1384,7 +1393,7 @@ static void hashhash_invalid_missing_args() "#define BAD(x) ##x\nBAD()"; simplecpp::OutputList outputList; - preprocess(code, simplecpp::DUI(), &outputList); + preprocess(code, &outputList); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'BAD', Invalid ## usage when expanding 'BAD': Missing first argument\n", toString(outputList)); } @@ -1405,7 +1414,7 @@ static void hashhash_universal_character() const char code[] = "#define A(x,y) x##y\nint A(\\u01,04);"; simplecpp::OutputList outputList; - preprocess(code, simplecpp::DUI(), &outputList); + preprocess(code, &outputList); ASSERT_EQUALS("file0,1,syntax_error,failed to expand 'A', Invalid ## usage when expanding 'A': Combining '\\u01' and '04' yields universal character '\\u0104'. This is undefined behavior according to C standard chapter 5.1.1.2, paragraph 4.\n", toString(outputList)); } @@ -1829,6 +1838,24 @@ static void missingHeader3() ASSERT_EQUALS("", toString(outputList)); } +static void missingHeader4() +{ + const char code[] = CODE(#pragma once "boost/*") + CODE(#pragma once "google/protobuf/*") + CODE(#pragma once "*.pb.h") + CODE(#pragma once "inc/*" i) + CODE(#include "boost/config/workaround.hpp") + CODE(#include "google/protobuf/stubs/port.h") + CODE(#include "proto/message.pb.h") + CODE(#include "inc/lowercase.h") + CODE(#include "Inc/MixedCase.h") + CODE(#include "INC/UPPERCASE.H"); + // none of the given files are included + simplecpp::OutputList outputList; + ASSERT_EQUALS("", preprocess(code, &outputList)); + ASSERT_EQUALS("", toString(outputList)); +} + static void nestedInclude() { const char code[] = "#include \"test.h\"\n"; @@ -2896,7 +2923,7 @@ static void fuzz_crash() { const char code[] = "#define n __VA_OPT__(u\n" "n\n"; - (void)preprocess(code, simplecpp::DUI()); // do not crash + (void)preprocess(code); // do not crash } } @@ -3057,6 +3084,7 @@ int main(int argc, char **argv) TEST_CASE(missingHeader1); TEST_CASE(missingHeader2); TEST_CASE(missingHeader3); + TEST_CASE(missingHeader4); TEST_CASE(nestedInclude); TEST_CASE(systemInclude);