From 70745b527acdbb2cb554ef4e115eddd32d1e5f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 5 Dec 2023 14:22:32 +0100 Subject: [PATCH] Fix #12210 (Cppcheck hang in SymbolDatabase::createSymbolDatabaseExprIds) (#5699) --- Makefile | 2 +- lib/symboldatabase.cpp | 189 +++++++++++++++++++++++++---------- lib/token.cpp | 5 +- lib/token.h | 5 +- test/cli/test-other.py | 115 +-------------------- test/cli/test-performance.py | 149 +++++++++++++++++++++++++++ test/testnullpointer.cpp | 2 +- test/testvarid.cpp | 85 ++++++++++++++-- 8 files changed, 375 insertions(+), 177 deletions(-) create mode 100644 test/cli/test-performance.py diff --git a/Makefile b/Makefile index 6624500a1a4..8c698501da1 100644 --- a/Makefile +++ b/Makefile @@ -887,7 +887,7 @@ test/testvaarg.o: test/testvaarg.cpp lib/addoninfo.h lib/check.h lib/checkvaarg. test/testvalueflow.o: test/testvalueflow.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.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/helpers.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testvalueflow.cpp -test/testvarid.o: test/testvarid.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.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/testvarid.o: test/testvarid.cpp lib/addoninfo.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.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/helpers.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ test/testvarid.cpp externals/simplecpp/simplecpp.o: externals/simplecpp/simplecpp.cpp externals/simplecpp/simplecpp.h diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index 960deb886c6..a3278f3f191 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -1571,16 +1571,101 @@ static std::string getIncompleteNameID(const Token* tok) return result + tok->expressionString(); } +namespace { + struct ExprIdKey { + std::string parentOp; + nonneg int operand1; + nonneg int operand2; + bool operator<(const ExprIdKey& k) const { + return std::tie(parentOp, operand1, operand2) < std::tie(k.parentOp, k.operand1, k.operand2); + } + }; + using ExprIdMap = std::map; + void setParentExprId(Token* tok, bool cpp, ExprIdMap& exprIdMap, nonneg int &id) { + if (!tok->astParent() || tok->astParent()->isControlFlowKeyword()) + return; + const Token* op1 = tok->astParent()->astOperand1(); + if (op1 && op1->exprId() == 0) + return; + const Token* op2 = tok->astParent()->astOperand2(); + if (op2 && op2->exprId() == 0) + return; + + if (tok->astParent()->isExpandedMacro() || Token::Match(tok->astParent(), "++|--")) { + tok->astParent()->exprId(id); + ++id; + setParentExprId(tok->astParent(), cpp, exprIdMap, id); + return; + } + + ExprIdKey key; + key.parentOp = tok->astParent()->str(); + key.operand1 = op1 ? op1->exprId() : 0; + key.operand2 = op2 ? op2->exprId() : 0; + + if (tok->astParent()->isCast() && tok->astParent()->str() == "(") { + const Token* typeStartToken; + const Token* typeEndToken; + if (tok->astParent()->astOperand2()) { + typeStartToken = tok->astParent()->astOperand1(); + typeEndToken = tok; + } else { + typeStartToken = tok->astParent()->next(); + typeEndToken = tok->astParent()->link(); + } + std::string type; + for (const Token* t = typeStartToken; t != typeEndToken; t = t->next()) { + type += " " + t->str(); + } + key.parentOp += type; + } + + for (const auto& ref: followAllReferences(op1)) { + if (ref.token->exprId() != 0) { // cppcheck-suppress useStlAlgorithm + key.operand1 = ref.token->exprId(); + break; + } + } + for (const auto& ref: followAllReferences(op2)) { + if (ref.token->exprId() != 0) { // cppcheck-suppress useStlAlgorithm + key.operand2 = ref.token->exprId(); + break; + } + } + + if (key.operand1 > key.operand2 && key.operand2 && + Token::Match(tok->astParent(), "%or%|%oror%|+|*|&|&&|^|==|!=")) { + // In C++ the order of operands of + might matter + if (!cpp || + key.parentOp != "+" || + !tok->astParent()->valueType() || + tok->astParent()->valueType()->isIntegral() || + tok->astParent()->valueType()->isFloat() || + tok->astParent()->valueType()->pointer > 0) + std::swap(key.operand1, key.operand2); + } + + const auto it = exprIdMap.find(key); + if (it == exprIdMap.end()) { + exprIdMap[key] = id; + tok->astParent()->exprId(id); + ++id; + } else { + tok->astParent()->exprId(it->second); + } + setParentExprId(tok->astParent(), cpp, exprIdMap, id); + } +} + void SymbolDatabase::createSymbolDatabaseExprIds() { - nonneg int base = 0; // Find highest varId - for (const Variable *var : mVariableList) { - if (!var) - continue; - base = std::max(base, var->declarationId()); + nonneg int maximumVarId = 0; + for (const Token* tok = mTokenizer.list.front(); tok; tok = tok->next()) { + if (tok->varId() > maximumVarId) + maximumVarId = tok->varId(); } - nonneg int id = base + 1; + nonneg int id = maximumVarId + 1; // Find incomplete vars that are used in constant context std::unordered_map unknownConstantIds; const Token* inConstExpr = nullptr; @@ -1611,7 +1696,6 @@ void SymbolDatabase::createSymbolDatabaseExprIds() }); for (const Scope * scope : exprScopes) { - nonneg int thisId = 0; std::unordered_map> exprs; std::unordered_map unknownIds; @@ -1638,62 +1722,65 @@ void SymbolDatabase::createSymbolDatabaseExprIds() } // Assign IDs + ExprIdMap exprIdMap; + std::map baseIds; for (Token* tok = const_cast(scope->bodyStart); tok != scope->bodyEnd; tok = tok->next()) { if (tok->varId() > 0) { tok->exprId(tok->varId()); - } else if (isExpression(tok)) { - exprs[tok->str()].push_back(tok); - tok->exprId(id++); + if (tok->astParent() && tok->astParent()->exprId() == 0) + setParentExprId(tok, mTokenizer.isCPP(), exprIdMap, id); + } else if (tok->astParent() && !tok->astOperand1() && !tok->astOperand2()) { + if (tok->tokType() == Token::Type::eBracket) + continue; + if (tok->astParent()->isAssignmentOp()) + continue; + if (tok->isControlFlowKeyword()) + continue; - if (id == std::numeric_limits::max() / 4) { - throw InternalError(nullptr, "Ran out of expression ids.", InternalError::INTERNAL); - } - } else if (isCPP() && Token::simpleMatch(tok, "this")) { - if (thisId == 0) - thisId = id++; - tok->exprId(thisId); - } - } - - // Apply CSE - for (const auto& p:exprs) { - const std::vector& tokens = p.second; - const std::size_t N = tokens.size(); - for (std::size_t i = 0; i < N; ++i) { - Token* const tok1 = tokens[i]; - for (std::size_t j = i + 1; j < N; ++j) { - Token* const tok2 = tokens[j]; - if (tok1->exprId() == tok2->exprId()) - continue; - if (!isSameExpression(isCPP(), true, tok1, tok2, mSettings.library, false, false)) - continue; - nonneg int const cid = std::min(tok1->exprId(), tok2->exprId()); - tok1->exprId(cid); - tok2->exprId(cid); + if (Token::Match(tok, "%name% <") && tok->next()->link()) { + tok->exprId(id); + ++id; + } else { + const auto it = baseIds.find(tok->str()); + if (it != baseIds.end()) { + tok->exprId(it->second); + } else { + baseIds[tok->str()] = id; + tok->exprId(id); + ++id; + } } + + setParentExprId(tok, mTokenizer.isCPP(), exprIdMap, id); } } - // Mark expressions that are unique - std::unordered_map exprMap; for (Token* tok = const_cast(scope->bodyStart); tok != scope->bodyEnd; tok = tok->next()) { - if (tok->exprId() == 0) - continue; - auto p = exprMap.emplace(tok->exprId(), tok); - // Already exists so set it to null - if (!p.second) { - p.first->second = nullptr; + if (tok->varId() == 0 && tok->exprId() > 0 && tok->astParent() && !tok->astOperand1() && !tok->astOperand2()) { + if (tok->isNumber() || tok->isKeyword() || Token::Match(tok->astParent(), ".|::") || + (Token::simpleMatch(tok->astParent(), "(") && precedes(tok, tok->astParent()))) + tok->exprId(0); } } - for (const auto& p : exprMap) { - if (!p.second) + } + + // Mark expressions that are unique + std::vector> uniqueExprId(id); + for (Token* tok = const_cast(mTokenizer.list.front()); tok; tok = tok->next()) { + const auto id2 = tok->exprId(); + if (id2 == 0 || id2 <= maximumVarId) + continue; + uniqueExprId[id2].first = tok; + uniqueExprId[id2].second++; + } + for (const auto& p : uniqueExprId) { + if (!p.first || p.second != 1) + continue; + if (p.first->variable()) { + const Variable* var = p.first->variable(); + if (var->nameToken() != p.first) continue; - if (p.second->variable()) { - const Variable* var = p.second->variable(); - if (var->nameToken() != p.second) - continue; - } - p.second->setUniqueExprId(); } + p.first->setUniqueExprId(); } } diff --git a/lib/token.cpp b/lib/token.cpp index e77b2dfcf38..6565b01e1d6 100644 --- a/lib/token.cpp +++ b/lib/token.cpp @@ -1269,7 +1269,10 @@ std::string Token::stringify(const stringifyOptions& options) const } else if (options.exprid && mImpl->mExprId != 0) { ret += '@'; ret += (options.idtype ? "expr" : ""); - ret += std::to_string(mImpl->mExprId); + if ((mImpl->mExprId & (1U << efIsUnique)) != 0) + ret += "UNIQUE"; + else + ret += std::to_string(mImpl->mExprId); } return ret; diff --git a/lib/token.h b/lib/token.h index 9d35486349c..cf7b30cd5ad 100644 --- a/lib/token.h +++ b/lib/token.h @@ -907,10 +907,7 @@ class CPPCHECKLIB Token { bool isUniqueExprId() const { - if (mImpl->mExprId > 0) { - return (mImpl->mExprId & (1 << efIsUnique)) != 0; - } - return false; + return (mImpl->mExprId & (1 << efIsUnique)) != 0; } /** diff --git a/test/cli/test-other.py b/test/cli/test-other.py index 2d39cc74179..a689bc92c08 100644 --- a/test/cli/test-other.py +++ b/test/cli/test-other.py @@ -153,115 +153,6 @@ def test_progress_j(tmpdir): assert stderr == "" -@pytest.mark.timeout(10) -def test_slow_array_many_floats(tmpdir): - # 11649 - # cppcheck valueflow takes a long time when an array has many floats - filename = os.path.join(tmpdir, 'hang.c') - with open(filename, 'wt') as f: - f.write("const float f[] = {\n") - for i in range(20000): - f.write(' 13.6f,\n') - f.write("};\n") - cppcheck([filename]) # should not take more than ~1 second - - -@pytest.mark.timeout(10) -def test_slow_array_many_strings(tmpdir): - # 11901 - # cppcheck valueflow takes a long time when analyzing a file with many strings - filename = os.path.join(tmpdir, 'hang.c') - with open(filename, 'wt') as f: - f.write("const char *strings[] = {\n") - for i in range(20000): - f.write(' "abc",\n') - f.write("};\n") - cppcheck([filename]) # should not take more than ~1 second - - -@pytest.mark.timeout(10) -def test_slow_long_line(tmpdir): - # simplecpp #314 - filename = os.path.join(tmpdir, 'hang.c') - with open(filename, 'wt') as f: - f.write("#define A() static const int a[] = {\\\n") - for i in range(5000): - f.write(" -123, 456, -789,\\\n") - f.write("};\n") - cppcheck([filename]) # should not take more than ~1 second - - -@pytest.mark.timeout(60) -def test_slow_large_constant_expression(tmpdir): - # 12182 - filename = os.path.join(tmpdir, 'hang.c') - with open(filename, 'wt') as f: - f.write(""" -#define FLAG1 0 -#define FLAG2 0 -#define FLAG3 0 -#define FLAG4 0 -#define FLAG5 0 -#define FLAG6 0 -#define FLAG7 0 -#define FLAG8 0 -#define FLAG9 0 -#define FLAG10 0 -#define FLAG11 0 -#define FLAG12 0 -#define FLAG13 0 -#define FLAG14 0 -#define FLAG15 0 -#define FLAG16 0 -#define FLAG17 0 -#define FLAG18 0 -#define FLAG19 0 -#define FLAG20 0 -#define FLAG21 0 -#define FLAG22 0 -#define FLAG23 0 -#define FLAG24 0 - -#define maxval(x, y) ((x) > (y) ? (x) : (y)) - -#define E_SAMPLE_SIZE maxval( FLAG1, \ - maxval( FLAG2, \ - maxval( FLAG3, \ - maxval( FLAG4, \ - maxval( FLAG5, \ - maxval( FLAG6, \ - maxval( FLAG7, \ - maxval( FLAG8, \ - maxval( FLAG9, \ - maxval( FLAG10, \ - maxval( FLAG11, \ - maxval( FLAG12, \ - maxval( FLAG13, \ - maxval( FLAG14, \ - FLAG15 )))))))))))))) - -#define SAMPLE_SIZE maxval( E_SAMPLE_SIZE, \ - maxval( sizeof(st), \ - maxval( FLAG16, \ - maxval( FLAG17, \ - maxval( FLAG18, \ - maxval( FLAG19, \ - maxval( FLAG20, \ - maxval( FLAG21, \ - maxval( FLAG22, \ - maxval( FLAG23, \ - FLAG24 )))))))))) - -typedef struct { - int n; -} st; - -x = SAMPLE_SIZE; - """) - - cppcheck([filename]) - - def test_execute_addon_failure(tmpdir): test_file = os.path.join(tmpdir, 'test.cpp') with open(test_file, 'wt') as f: @@ -852,7 +743,7 @@ def test_valueflow_debug(tmpdir): ##file {} 2: void f2 ( ) 3: {{ -4: int i@var1 ; i@var1 =@expr1073741828 0 ; +4: int i@var1 ; i@var1 = 0 ; 5: }} ##file {} @@ -861,7 +752,7 @@ def test_valueflow_debug(tmpdir): 2: 3: void f1 ( ) 4: {{ -5: int i@var2 ; i@var2 =@expr1073741829 0 ; +5: int i@var2 ; i@var2 = 0 ; 6: }} ##file {} @@ -871,7 +762,7 @@ def test_valueflow_debug(tmpdir): 3: 4: void f ( ) 5: {{ -6: int i@var3 ; i@var3 =@expr1073741830 0 ; +6: int i@var3 ; i@var3 = 0 ; 7: }} diff --git a/test/cli/test-performance.py b/test/cli/test-performance.py new file mode 100644 index 00000000000..305994869c5 --- /dev/null +++ b/test/cli/test-performance.py @@ -0,0 +1,149 @@ + +# python -m pytest test-other.py + +import os +import sys +import pytest + +from testutils import cppcheck, assert_cppcheck + + + +@pytest.mark.timeout(10) +def test_slow_array_many_floats(tmpdir): + # 11649 + # cppcheck valueflow takes a long time when an array has many floats + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write("const float f[] = {\n") + for i in range(20000): + f.write(' 13.6f,\n') + f.write("};\n") + cppcheck([filename]) # should not take more than ~1 second + + +@pytest.mark.timeout(10) +def test_slow_array_many_strings(tmpdir): + # 11901 + # cppcheck valueflow takes a long time when analyzing a file with many strings + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write("const char *strings[] = {\n") + for i in range(20000): + f.write(' "abc",\n') + f.write("};\n") + cppcheck([filename]) # should not take more than ~1 second + + +@pytest.mark.timeout(10) +def test_slow_long_line(tmpdir): + # simplecpp #314 + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write("#define A() static const int a[] = {\\\n") + for i in range(5000): + f.write(" -123, 456, -789,\\\n") + f.write("};\n") + cppcheck([filename]) # should not take more than ~1 second + + +@pytest.mark.timeout(60) +def test_slow_large_constant_expression(tmpdir): + # 12182 + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write(""" +#define FLAG1 0 +#define FLAG2 0 +#define FLAG3 0 +#define FLAG4 0 +#define FLAG5 0 +#define FLAG6 0 +#define FLAG7 0 +#define FLAG8 0 +#define FLAG9 0 +#define FLAG10 0 +#define FLAG11 0 +#define FLAG12 0 +#define FLAG13 0 +#define FLAG14 0 +#define FLAG15 0 +#define FLAG16 0 +#define FLAG17 0 +#define FLAG18 0 +#define FLAG19 0 +#define FLAG20 0 +#define FLAG21 0 +#define FLAG22 0 +#define FLAG23 0 +#define FLAG24 0 + +#define maxval(x, y) ((x) > (y) ? (x) : (y)) + +#define E_SAMPLE_SIZE maxval( FLAG1, \ + maxval( FLAG2, \ + maxval( FLAG3, \ + maxval( FLAG4, \ + maxval( FLAG5, \ + maxval( FLAG6, \ + maxval( FLAG7, \ + maxval( FLAG8, \ + maxval( FLAG9, \ + maxval( FLAG10, \ + maxval( FLAG11, \ + maxval( FLAG12, \ + maxval( FLAG13, \ + maxval( FLAG14, \ + FLAG15 )))))))))))))) + +#define SAMPLE_SIZE maxval( E_SAMPLE_SIZE, \ + maxval( sizeof(st), \ + maxval( FLAG16, \ + maxval( FLAG17, \ + maxval( FLAG18, \ + maxval( FLAG19, \ + maxval( FLAG20, \ + maxval( FLAG21, \ + maxval( FLAG22, \ + maxval( FLAG23, \ + FLAG24 )))))))))) + +typedef struct { + int n; +} st; + +x = SAMPLE_SIZE; + """) + + cppcheck([filename]) + +@pytest.mark.timeout(10) +def test_slow_exprid(tmpdir): + # 11885 + filename = os.path.join(tmpdir, 'hang.c') + with open(filename, 'wt') as f: + f.write(""" +int foo(int a, int b) +{ +#define A0(a, b) ((a) + (b)) +#define A1(a, b) ((a) > (b)) ? A0((a) - (b), (b)) : A0((b) - (a), (a)) +#define A2(a, b) ((a) > (b)) ? A1((a) - (b), (b)) : A1((b) - (a), (a)) +#define A3(a, b) ((a) > (b)) ? A2((a) - (b), (b)) : A2((b) - (a), (a)) +#define A4(a, b) ((a) > (b)) ? A3((a) - (b), (b)) : A3((b) - (a), (a)) +#define A5(a, b) ((a) > (b)) ? A4((a) - (b), (b)) : A4((b) - (a), (a)) +#define A6(a, b) ((a) > (b)) ? A5((a) - (b), (b)) : A5((b) - (a), (a)) +#define A7(a, b) ((a) > (b)) ? A6((a) - (b), (b)) : A6((b) - (a), (a)) +#define A8(a, b) ((a) > (b)) ? A7((a) - (b), (b)) : A7((b) - (a), (a)) +#define A9(a, b) ((a) > (b)) ? A8((a) - (b), (b)) : A8((b) - (a), (a)) +#define A10(a, b) ((a) > (b)) ? A9((a) - (b), (b)) : A9((b) - (a), (a)) +#define A11(a, b) ((a) > (b)) ? A10((a) - (b), (b)) : A10((b) - (a), (a)) + return A8(a, b); +} + """) + + my_env = os.environ.copy() + my_env["DISABLE_VALUEFLOW"] = "1" + cppcheck([filename], env=my_env) + + + diff --git a/test/testnullpointer.cpp b/test/testnullpointer.cpp index b1757014e71..e23df116241 100644 --- a/test/testnullpointer.cpp +++ b/test/testnullpointer.cpp @@ -990,7 +990,7 @@ class TestNullPointer : public TestFixture { check("struct S { struct T { char c; } *p; };\n" // #6541 "char f(S* s) { return s->p ? 'a' : s->p->c; }\n"); - ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (warning) Either the condition 's->p' is redundant or there is possible null pointer dereference: p.\n", + ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (warning) Either the condition 's->p' is redundant or there is possible null pointer dereference: s->p.\n", errout.str()); } diff --git a/test/testvarid.cpp b/test/testvarid.cpp index 6e835f2dfe9..29f46352ad4 100644 --- a/test/testvarid.cpp +++ b/test/testvarid.cpp @@ -17,6 +17,7 @@ */ #include "errortypes.h" +#include "helpers.h" #include "platform.h" #include "settings.h" #include "standards.h" @@ -236,6 +237,11 @@ class TestVarID : public TestFixture { TEST_CASE(exprid1); TEST_CASE(exprid2); + TEST_CASE(exprid3); + TEST_CASE(exprid4); + TEST_CASE(exprid5); + TEST_CASE(exprid6); + TEST_CASE(exprid7); TEST_CASE(structuredBindings); } @@ -260,9 +266,11 @@ class TestVarID : public TestFixture { std::string tokenizeExpr_(const char* file, int line, const char code[], const char filename[] = "test.cpp") { errout.str(""); + std::vector files(1, filename); Tokenizer tokenizer(&settings, this); - std::istringstream istr(code); - ASSERT_LOC((tokenizer.tokenize)(istr, filename), file, line); + PreprocessorHelper::preprocess(code, files, tokenizer); + + ASSERT_LOC(tokenizer.simplifyTokens1(""), file, line); // result.. Token::stringifyOptions options = Token::stringifyOptions::forDebugExprId(); @@ -3859,9 +3867,9 @@ class TestVarID : public TestFixture { "2: int x ; int y ;\n" "3: } ;\n" "4: int f ( A a , A b ) {\n" - "5: int x@5 ; x@5 =@9 a@3 .@10 x@6 +@11 b@4 .@12 x@7 ;\n" - "6: int y@8 ; y@8 =@1073741837 b@4 .@12 x@7 +@11 a@3 .@10 x@6 ;\n" - "7: return x@5 +@1073741841 y@8 +@1073741842 a@3 .@1073741843 y@9 +@1073741844 b@4 .@1073741845 y@10 ;\n" + "5: int x@5 ; x@5 =@UNIQUE a@3 .@11 x@6 +@13 b@4 .@12 x@7 ;\n" + "6: int y@8 ; y@8 =@UNIQUE b@4 .@12 x@7 +@13 a@3 .@11 x@6 ;\n" + "7: return x@5 +@UNIQUE y@8 +@UNIQUE a@3 .@UNIQUE y@9 +@UNIQUE b@4 .@UNIQUE y@10 ;\n" "8: }\n"; ASSERT_EQUALS(expected, actual); @@ -3878,14 +3886,77 @@ class TestVarID : public TestFixture { const char expected[] = "1: struct S { std :: unique_ptr < int > u ; } ;\n" "2: auto f ; f = [ ] ( const S & s ) . std :: unique_ptr < int > {\n" - "3: if (@5 auto p@4 =@1073741830 s@3 .@1073741831 u@5 .@1073741832 get (@1073741833 ) ) {\n" - "4: return std ::@1073741834 make_unique < int > (@1073741835 *@1073741836 p@4 ) ; }\n" + "3: if ( auto p@4 =@UNIQUE s@3 .@UNIQUE u@5 .@UNIQUE get (@UNIQUE ) ) {\n" + "4: return std ::@UNIQUE make_unique < int > (@UNIQUE *@UNIQUE p@4 ) ; }\n" "5: return nullptr ;\n" "6: } ;\n"; ASSERT_EQUALS(expected, actual); } + void exprid3() { + const char code[] = "void f(bool b, int y) {\n" + " if (b && y > 0) {}\n" + " while (b && y > 0) {}\n" + "}\n"; + const char expected[] = "1: void f ( bool b , int y ) {\n" + "2: if ( b@1 &&@5 y@2 >@4 0 ) { }\n" + "3: while ( b@1 &&@5 y@2 >@4 0 ) { }\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + + void exprid4() { + // expanded macro.. + const char code[] = "#define ADD(x,y) x+y\n" + "int f(int a, int b) {\n" + " return ADD(a,b) + ADD(a,b);\n" + "}\n"; + const char expected[] = "2: int f ( int a , int b ) {\n" + "3: return a@1 $+@UNIQUE b@2 +@UNIQUE a@1 $+@UNIQUE b@2 ;\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + + void exprid5() { + // references.. + const char code[] = "int foo(int a) {\n" + " int& r = a;\n" + " return (a+a)*(r+r);\n" + "}\n"; + const char expected[] = "1: int foo ( int a ) {\n" + "2: int & r@2 =@UNIQUE a@1 ;\n" + "3: return ( a@1 +@4 a@1 ) *@UNIQUE ( r@2 +@4 r@2 ) ;\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + + void exprid6() { + // ++ and -- should have UNIQUE exprid + const char code[] = "void foo(int *a) {\n" + " *a++ = 0;\n" + " if (*a++ == 32) {}\n" + "}\n"; + const char expected[] = "1: void foo ( int * a ) {\n" + "2: *@UNIQUE a@1 ++@UNIQUE = 0 ;\n" + "3: if ( *@UNIQUE a@1 ++@UNIQUE ==@UNIQUE 32 ) { }\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + + void exprid7() { + // different casts + const char code[] = "void foo(int a) {\n" + " if ((char)a == (short)a) {}\n" + " if ((char)a == (short)a) {}\n" + "}\n"; + const char expected[] = "1: void foo ( int a ) {\n" + "2: if ( (@2 char ) a@1 ==@4 (@3 short ) a@1 ) { }\n" + "3: if ( (@2 char ) a@1 ==@4 (@3 short ) a@1 ) { }\n" + "4: }\n"; + ASSERT_EQUALS(expected, tokenizeExpr(code)); + } + void structuredBindings() { const char code[] = "int foo() { auto [x,y] = xy(); return x+y; }"; ASSERT_EQUALS("1: int foo ( ) { auto [ x@1 , y@2 ] = xy ( ) ; return x@1 + y@2 ; }\n",