From 09edbd1824d751dde50ac5d6705efe9e7f4c6377 Mon Sep 17 00:00:00 2001 From: chrchr Date: Thu, 27 Apr 2023 20:36:44 +0200 Subject: [PATCH 1/2] Fix #10720 Hang/Crash with big variadic template --- lib/tokenize.cpp | 47 ++++++++++++++++++++++++++++---------- lib/tokenize.h | 1 + test/testsimplifyusing.cpp | 29 ++++++++++++++++++++++- 3 files changed, 64 insertions(+), 13 deletions(-) diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index ecebb500124..3985d45de6b 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -2760,11 +2760,23 @@ static bool scopesMatch(const std::string &scope1, const std::string &scope2, co return false; } +static unsigned int tokDistance(const Token* tok1, const Token* tok2) { + unsigned int dist = 0; + const Token* tok = tok1; + while (tok != tok2) { + ++dist; + tok = tok->next(); + } + return dist; +}; + bool Tokenizer::simplifyUsing() { if (!isCPP() || mSettings->standards.cpp < Standards::CPP11) return false; + const unsigned int maxReplacementTokens = 1000; // limit the number of tokens we replace + bool substitute = false; ScopeInfo3 scopeInfo; ScopeInfo3 *currentScope = &scopeInfo; @@ -3006,6 +3018,12 @@ bool Tokenizer::simplifyUsing() } else if (!usingMatch(nameToken, scope, &tok1, scope1, currentScope1, nullptr)) continue; + const auto nReplace = tokDistance(start, usingEnd); + if (nReplace > maxReplacementTokens) { + simplifyUsingError(usingStart, usingEnd); + continue; + } + // remove the qualification std::string fullScope = scope; std::string removed; @@ -3187,18 +3205,7 @@ bool Tokenizer::simplifyUsing() } } else { skip = true; - if (mSettings->debugwarnings && mErrorLogger) { - std::string str; - for (Token *tok3 = usingStart; tok3 && tok3 != usingEnd; tok3 = tok3->next()) { - if (!str.empty()) - str += ' '; - str += tok3->str(); - } - str += " ;"; - std::list callstack(1, usingStart); - mErrorLogger->reportErr(ErrorMessage(callstack, &list, Severity::debug, "simplifyUsing", - "Failed to parse \'" + str + "\'. The checking continues anyway.", Certainty::normal)); - } + simplifyUsingError(usingStart, usingEnd); } tok1 = after; } @@ -3233,6 +3240,22 @@ bool Tokenizer::simplifyUsing() return substitute; } +void Tokenizer::simplifyUsingError(const Token* usingStart, const Token* usingEnd) +{ + if (mSettings->debugwarnings && mErrorLogger) { + std::string str; + for (const Token *tok = usingStart; tok && tok != usingEnd; tok = tok->next()) { + if (!str.empty()) + str += ' '; + str += tok->str(); + } + str += " ;"; + std::list callstack(1, usingStart); + mErrorLogger->reportErr(ErrorMessage(callstack, &list, Severity::debug, "simplifyUsing", + "Failed to parse \'" + str + "\'. The checking continues anyway.", Certainty::normal)); + } +} + bool Tokenizer::createTokens(std::istream &code, const std::string& FileName) { diff --git a/lib/tokenize.h b/lib/tokenize.h index de05fdfd4b5..212302a9daf 100644 --- a/lib/tokenize.h +++ b/lib/tokenize.h @@ -280,6 +280,7 @@ class CPPCHECKLIB Tokenizer { /** */ bool simplifyUsing(); + void simplifyUsingError(const Token* usingStart, const Token* usingEnd); /** Simplify useless C++ empty namespaces, like: 'namespace %name% { }'*/ void simplifyEmptyNamespaces(); diff --git a/test/testsimplifyusing.cpp b/test/testsimplifyusing.cpp index 45f578cb093..b55fd43f97e 100644 --- a/test/testsimplifyusing.cpp +++ b/test/testsimplifyusing.cpp @@ -24,6 +24,8 @@ #include "token.h" #include "tokenize.h" +#include + #include // IWYU pragma: keep #include @@ -92,13 +94,14 @@ class TestSimplifyUsing : public TestFixture { TEST_CASE(simplifyUsing10172); TEST_CASE(simplifyUsing10173); TEST_CASE(simplifyUsing10335); + TEST_CASE(simplifyUsing10720); TEST_CASE(scopeInfo1); TEST_CASE(scopeInfo2); } #define tok(...) tok_(__FILE__, __LINE__, __VA_ARGS__) - std::string tok_(const char* file, int line, const char code[], cppcheck::Platform::Type type = cppcheck::Platform::Type::Native, bool debugwarnings = true) { + std::string tok_(const char* file, int line, const char code[], cppcheck::Platform::Type type = cppcheck::Platform::Type::Native, bool debugwarnings = true, bool preprocess = false) { errout.str(""); settings0.certainty.enable(Certainty::inconclusive); @@ -106,6 +109,18 @@ class TestSimplifyUsing : public TestFixture { PLATFORM(settings0.platform, type); Tokenizer tokenizer(&settings0, this); + if (preprocess) { + std::vector files{ "test.cpp" }; + std::istringstream istr(code); + const simplecpp::TokenList tokens1(istr, files, files[0]); + + simplecpp::TokenList tokens2(files); + std::map filedata; + simplecpp::preprocess(tokens2, tokens1, files, filedata, simplecpp::DUI()); + + tokenizer.createTokens(std::move(tokens2)); + } + std::istringstream istr(code); ASSERT_LOC(tokenizer.tokenize(istr, "test.cpp"), file, line); @@ -1346,6 +1361,18 @@ class TestSimplifyUsing : public TestFixture { ASSERT_EQUALS(exp, tok(code)); } + void simplifyUsing10720() { + const char code[] = "template \n" + "struct S {};\n" + "#define STAMP(thiz, prev) using thiz = S;\n" + "STAMP(A, int);\n" + "STAMP(B, A);\n" + "STAMP(C, B);\n"; + const char exp[] = "enum E : unsigned char { E0 } ;"; + tok(code, cppcheck::Platform::Type::Native, /*debugwarnings*/ true, /*preprocess*/ true); + ASSERT_EQUALS(errout.str().compare(0, 64, "[test.cpp:6]: (debug) Failed to parse 'using C = S < S < S < int"), 0); + } + void scopeInfo1() { const char code[] = "struct A {\n" " enum class Mode { UNKNOWN, ENABLED, NONE, };\n" From ba6f3f744d500ef84eae8ae844d108cd2992ca2c Mon Sep 17 00:00:00 2001 From: chrchr-github Date: Thu, 27 Apr 2023 21:44:39 +0200 Subject: [PATCH 2/2] Fix CI --- Makefile | 2 +- test/testsimplifyusing.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 38aa65c215a..d18c7908cb3 100644 --- a/Makefile +++ b/Makefile @@ -805,7 +805,7 @@ test/testsimplifytokens.o: test/testsimplifytokens.cpp lib/check.h lib/color.h l test/testsimplifytypedef.o: test/testsimplifytypedef.cpp externals/simplecpp/simplecpp.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsimplifytypedef.cpp -test/testsimplifyusing.o: test/testsimplifyusing.cpp lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h +test/testsimplifyusing.o: test/testsimplifyusing.cpp externals/simplecpp/simplecpp.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testsimplifyusing.cpp test/testsingleexecutor.o: test/testsingleexecutor.cpp cli/executor.h cli/singleexecutor.h lib/analyzerinfo.h lib/check.h lib/color.h lib/config.h lib/cppcheck.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h test/fixture.h test/helpers.h test/redirect.h diff --git a/test/testsimplifyusing.cpp b/test/testsimplifyusing.cpp index b55fd43f97e..1cc7d1aed4f 100644 --- a/test/testsimplifyusing.cpp +++ b/test/testsimplifyusing.cpp @@ -117,7 +117,7 @@ class TestSimplifyUsing : public TestFixture { simplecpp::TokenList tokens2(files); std::map filedata; simplecpp::preprocess(tokens2, tokens1, files, filedata, simplecpp::DUI()); - + tokenizer.createTokens(std::move(tokens2)); } @@ -1368,7 +1368,6 @@ class TestSimplifyUsing : public TestFixture { "STAMP(A, int);\n" "STAMP(B, A);\n" "STAMP(C, B);\n"; - const char exp[] = "enum E : unsigned char { E0 } ;"; tok(code, cppcheck::Platform::Type::Native, /*debugwarnings*/ true, /*preprocess*/ true); ASSERT_EQUALS(errout.str().compare(0, 64, "[test.cpp:6]: (debug) Failed to parse 'using C = S < S < S < int"), 0); }