Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#pragma once usage to prevent unwanted inclusions from happening even once #363

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 61 additions & 2 deletions simplecpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel this Mask class is not very elegant imho.

  • I don't feel it should inherit from std::string, it's not used as a std::string.
  • I dont see the point to read the i flag from the string in the constructor like that instead of passing a parameter. Will there be unintentional case matching if the string contains a random "i" somewhere or not.
  • the fnmatch shouldn't be a template function
  • I don't see why match a const char* instead of a std::string.. seems redundant to call .c_str()

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<std::tolower> : fnmatch<identity>)(ustr(c_str()), ustr(s)) == 0;
}
private:
typedef const unsigned char *ustr;
static int identity(int x) { return x; }
template<int(*normalize)(int)>
static int fnmatch(ustr m, ustr s) {
if (*m == '*') for (++m; *s; ++s) if (!fnmatch<normalize>(m, s)) return 0;
return (!*s || !(normalize(*s) == normalize(*m) || *m == '?')) ? *m | *s : fnmatch<normalize>(++m, ++s);
}
bool i; // whether to ignore character case
};

void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenList &rawtokens, std::vector<std::string> &files, std::map<std::string, simplecpp::TokenList *> &filedata, const simplecpp::DUI &dui, simplecpp::OutputList *outputList, std::list<simplecpp::MacroUsage> *macroUsage, std::list<simplecpp::IfCond> *ifCond)
{
#ifdef SIMPLECPP_WINDOWS
Expand Down Expand Up @@ -3382,6 +3416,15 @@ void simplecpp::preprocess(simplecpp::TokenList &output, const simplecpp::TokenL

std::set<std::string> 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<Mask> pragmaOddOnce;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well it could be std::set<std::string> pragmaOnceMasks; .. instead of creating a Mask class what would be the downside to just provide a regular function like:

bool match(const std::string& mask, const std::string& value, bool i);


includetokenstack.push(rawtokens.cfront());
for (std::list<std::string>::const_iterator it = dui.includes.begin(); it != dui.includes.end(); ++it) {
const std::map<std::string, TokenList*>::const_iterator f = filedata.find(*it);
Expand Down Expand Up @@ -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<Mask>::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..
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions simplecpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ namespace simplecpp {

typedef std::string TokenString;
class Macro;
class Mask;

/**
* Location in source code
Expand Down
36 changes: 32 additions & 4 deletions test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#define STRINGIZE_(x) #x
#define STRINGIZE(x) STRINGIZE_(x)
#define CODE(x) #x "\n"

static int numberOfFailedAssertions = 0;

Expand Down Expand Up @@ -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"
Expand All @@ -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));
Expand Down Expand Up @@ -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));
}

Expand All @@ -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));
}

Expand All @@ -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));
}

Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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);

Expand Down