From 765b0160e34d9d5f1dfba81cf11798f149d14f7f Mon Sep 17 00:00:00 2001 From: KiritoYG <118127895+KiritoYG@users.noreply.github.com> Date: Thu, 24 Sep 2026 21:53:24 +0900 Subject: [PATCH 1/2] Unify C file-scope redeclarations in variable identities --- lib/symboldatabase.cpp | 47 +++++++++++ lib/tokenize.cpp | 14 +++- lib/valueflow.cpp | 19 +++-- test/testbufferoverrun.cpp | 25 ++++++ test/testother.cpp | 2 +- test/testsymboldatabase.cpp | 161 +++++++++++++++++++++++++++++++++++- test/testvalueflow.cpp | 21 +++++ test/testvarid.cpp | 67 ++++++++++++++- 8 files changed, 345 insertions(+), 11 deletions(-) diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index c2ce2db812b..b936e22543b 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -893,6 +893,53 @@ void SymbolDatabase::createSymbolDatabaseVariableInfo() for (Scope& scope : scopeList) { // find variables scope.getVariableList(); + + if (mTokenizer.isC() && scope.type == ScopeType::eGlobal) { + // Canonicalize before assigning Variable pointers to tokens. C + // declarations may contribute an initializer or complete an array + // type without introducing another object. + std::unordered_map variables; + nonneg int index = 0; + for (auto it = scope.varlist.begin(); it != scope.varlist.end();) { + const nonneg int id = it->declarationId(); + const auto previous = variables.find(id); + if (id == 0 || previous == variables.end()) { + it->mIndex = index++; + if (id != 0) + variables.emplace(id, &*it); + ++it; + continue; + } + + Variable& var = *previous->second; + const bool isStatic = var.isStatic() || it->isStatic(); + const bool isExtern = var.isExtern() && it->isExtern(); + const bool maybeUnused = var.isMaybeUnused() || it->isMaybeUnused(); + auto dimensions = var.dimensions(); + if (dimensions.size() == it->dimensions().size()) { + for (std::size_t i = 0; i < dimensions.size(); ++i) { + const Dimension& dimension = it->dimensions()[i]; + if (!dimensions[i].known && !dimensions[i].tok) + dimensions[i] = dimension; + } + } + + // Keep the initialized definition, or a tentative definition + // in preference to an extern-only declaration. + if ((!var.isInit() && it->isInit()) || + (!var.isInit() && var.isExtern() && !it->isExtern())) { + const nonneg int originalIndex = var.index(); + var = *it; + var.mIndex = originalIndex; + } + var.setFlag(Variable::fIsStatic, isStatic); + var.setFlag(Variable::fIsExtern, isExtern); + var.setFlag(Variable::fIsMaybeUnused, maybeUnused); + if (dimensions.size() == var.dimensions().size()) + var.setDimensions(dimensions); + it = scope.varlist.erase(it); + } + } } // fill in function arguments diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index d67356ad086..58d03efb04f 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -4042,6 +4042,10 @@ void Tokenizer::arraySizeAfterValueFlow() continue; if (!Token::Match(var->nameToken(), "%name% [ ] = { [")) continue; + // A C redeclaration may inherit a complete bound from an earlier + // declaration. The initializer does not shrink that composite type. + if (!var->dimensions().empty() && (var->dimensions().front().known || var->dimensions().front().tok)) + continue; MathLib::bigint maxIndex = -1; const Token* const startToken = var->nameToken()->tokAt(4); const Token* const endToken = startToken->link(); @@ -4324,7 +4328,7 @@ namespace { VariableMap() = default; void enterScope(); bool leaveScope(); - void addVariable(const std::string& varname, bool globalNamespace); + void addVariable(const std::string& varname, bool globalNamespace, bool reuseGlobal = false); bool hasVariable(const std::string& varname) const { return mVariableId.find(varname) != mVariableId.end(); } @@ -4362,9 +4366,13 @@ bool VariableMap::leaveScope() return true; } -void VariableMap::addVariable(const std::string& varname, bool globalNamespace) +void VariableMap::addVariable(const std::string& varname, bool globalNamespace, bool reuseGlobal) { if (mScopeInfo.empty()) { + // C file-scope redeclarations name the same object. Parameters and + // local declarations enter their own VariableMap scope. + if (reuseGlobal && globalNamespace && hasVariable(varname)) + return; mVariableId[varname].id = ++mVarId; if (globalNamespace) mVariableId_global[varname] = mVariableId[varname]; @@ -5039,7 +5047,7 @@ void Tokenizer::setVarIdPass1() if (decl) { if (isC() && Token::Match(prev2->previous(), "&|&&")) syntaxErrorC(prev2, prev2->strAt(-2) + prev2->strAt(-1) + " " + prev2->str()); - variableMap.addVariable(prev2->str(), scopeStack.size() <= 1); + variableMap.addVariable(prev2->str(), scopeStack.size() <= 1, isC()); if (Token::simpleMatch(tok->previous(), "for (") && Token::Match(prev2, "%name% [=[({,]")) { for (const Token *tok3 = prev2->next(); tok3 && tok3->str() != ";"; tok3 = tok3->next()) { diff --git a/lib/valueflow.cpp b/lib/valueflow.cpp index aa0b2c91c29..6ff3aee1ac6 100644 --- a/lib/valueflow.cpp +++ b/lib/valueflow.cpp @@ -1246,6 +1246,7 @@ static void valueFlowGlobalStaticVar(TokenList& tokenList, const Settings& setti { // Get variable values... std::map vars; + std::unordered_set modified; for (const Token* tok = tokenList.front(); tok; tok = tok->next()) { if (!tok->variable()) continue; @@ -1254,22 +1255,30 @@ static void valueFlowGlobalStaticVar(TokenList& tokenList, const Settings& setti tok->valueType() && tok->valueType()->isIntegral() && tok->valueType()->pointer == 0 && tok->valueType()->constness == 0 && Token::Match(tok, "%name% =") && tok->next()->astOperand2() && tok->next()->astOperand2()->hasKnownIntValue()) { - vars[tok->variable()] = *tok->next()->astOperand2()->getKnownValue(ValueFlow::Value::ValueType::INT); + // A C definition can follow uses of a tentative declaration. + // Do not forget writes encountered before the initializer. + if (modified.count(tok->variable()) == 0) + vars[tok->variable()] = *tok->next()->astOperand2()->getKnownValue(ValueFlow::Value::ValueType::INT); } else { // If variable is written anywhere in TU then remove it from vars if (!tok->astParent()) continue; + bool changed = false; if (Token::Match(tok->astParent(), "++|--|&") && !tok->astParent()->astOperand2()) - vars.erase(tok->variable()); + changed = true; else if (tok->astParent()->isAssignmentOp()) { if (tok == tok->astParent()->astOperand1()) - vars.erase(tok->variable()); + changed = true; else if (tok->isCpp() && Token::Match(tok->astParent()->tokAt(-2), "& %name% =")) - vars.erase(tok->variable()); + changed = true; } else if (isLikelyStreamRead(tok->astParent())) { - vars.erase(tok->variable()); + changed = true; } else if (Token::Match(tok->astParent(), "[(,]")) + changed = true; + if (changed) { vars.erase(tok->variable()); + modified.insert(tok->variable()); + } } } diff --git a/test/testbufferoverrun.cpp b/test/testbufferoverrun.cpp index f6a2e1a5ec1..57ee7237aa3 100644 --- a/test/testbufferoverrun.cpp +++ b/test/testbufferoverrun.cpp @@ -93,6 +93,7 @@ class TestBufferOverrun : public TestFixture { TEST_CASE(sizeof3); TEST_CASE(array_index_1); + TEST_CASE(array_redeclaration_c); TEST_CASE(array_index_2); TEST_CASE(array_index_3); TEST_CASE(array_index_4); @@ -386,6 +387,30 @@ class TestBufferOverrun : public TestFixture { ASSERT_EQUALS("", errout_str()); } + void array_redeclaration_c() { + check("extern int a[];\n" + "int before(void) { return a[4]; }\n" + "int a[4];\n" + "int after(void) { return a[4]; }\n", dinit(CheckOptions, $.cpp = false)); + ASSERT_EQUALS("[test.c:2:28]: (error) Array 'a[4]' accessed at index 4, which is out of bounds. [arrayIndexOutOfBounds]\n" + "[test.c:4:27]: (error) Array 'a[4]' accessed at index 4, which is out of bounds. [arrayIndexOutOfBounds]\n", errout_str()); + + check("int a[4];\n" + "int a[] = {1};\n" + "int f(void) { return a[3]; }\n", dinit(CheckOptions, $.cpp = false)); + ASSERT_EQUALS("", errout_str()); + + check("extern int a[4];\n" + "int a[] = {1};\n" + "int f(void) { return a[3]; }\n", dinit(CheckOptions, $.cpp = false)); + ASSERT_EQUALS("", errout_str()); + + check("enum { I = 0 }; int a[4];\n" + "int a[] = {[I] = 1};\n" + "int f(void) { return a[3]; }\n", dinit(CheckOptions, $.cpp = false)); + ASSERT_EQUALS("", errout_str()); + } + void array_index_1() { check("void f()\n" "{\n" diff --git a/test/testother.cpp b/test/testother.cpp index d115acc2163..fba3568b730 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -13370,7 +13370,7 @@ class TestOther : public TestFixture { " int a;\n" " return 0;\n" "}\n", dinit(CheckOptions, $.cpp = false)); - ASSERT_EQUALS("[test.c:1:12] -> [test.c:4:9]: (style) Local variable 'a' shadows outer variable [shadowVariable]\n", errout_str()); + ASSERT_EQUALS("[test.c:2:5] -> [test.c:4:9]: (style) Local variable 'a' shadows outer variable [shadowVariable]\n", errout_str()); check("int f() {\n" // #12591 " int g = 0;\n" diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index 42b858ba4f9..35861c2a2b0 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -282,6 +282,10 @@ class TestSymbolDatabase : public TestFixture { TEST_CASE(hasGlobalVariables1); TEST_CASE(hasGlobalVariables2); TEST_CASE(hasGlobalVariables3); + TEST_CASE(globalVariableRedeclarations); // #6418 + TEST_CASE(globalVariableRedeclarationMetadata); + TEST_CASE(globalVariableRedeclarationArrayBounds); + TEST_CASE(globalVariableRedeclarationScopes); TEST_CASE(checkTypeStartEndToken1); TEST_CASE(checkTypeStartEndToken2); // handling for unknown macro: 'void f() MACRO {..' @@ -1858,9 +1862,11 @@ class TestSymbolDatabase : public TestFixture { GET_SYMBOL_DB_C("extern alignas(16) int x;\n" "alignas(16) int x;\n"); ASSERT(db); - ASSERT_EQUALS(2, db->scopeList.front().varlist.size()); + ASSERT_EQUALS(1, db->scopeList.front().varlist.size()); const Variable *x1 = Token::findsimplematch(tokenizer.tokens(), "x")->variable(); ASSERT(x1 && Token::simpleMatch(x1->typeStartToken(), "int x ;")); + const Token *x2 = findToken(tokenizer, "x ;", 2); + ASSERT(x2 && x2->variable() == x1); } void memberVar1() { @@ -2696,6 +2702,159 @@ class TestSymbolDatabase : public TestFixture { ASSERT(var->typeStartToken()->str() == "int"); } + void globalVariableRedeclarations() { // #6418 + GET_SYMBOL_DB_C("int tentative;\n" + "int tentative;\n" + "extern int defined;\n" + "int before(void) { return defined; }\n" + "int defined;\n" + "extern int defined;\n" + "int after(void) { return defined; }\n" + "int reverse;\n" + "extern int reverse;\n"); + ASSERT(db); + ASSERT_EQUALS(3, db->scopeList.front().varlist.size()); + for (const Variable& var : db->scopeList.front().varlist) { + ASSERT(var.isGlobal()); + ASSERT(!var.isExtern()); + ASSERT(var.declarationId() != 0); + ASSERT(db->getVariableFromVarId(var.declarationId()) == &var); + for (const Token* tok = tokenizer.tokens(); tok; tok = tok->next()) { + if (tok->str() != var.name()) + continue; + ASSERT_EQUALS(var.declarationId(), tok->varId()); + ASSERT(tok->variable() == &var); + } + } + } + + void globalVariableRedeclarationMetadata() { + GET_SYMBOL_DB_C("extern int value;\n" + "int value = 7;\n" + "extern int value;\n" + "int reverse = 9;\n" + "extern int reverse;\n" + "extern int data[];\n" + "int before(void) { return data[0]; }\n" + "int data[4];\n" + "extern int data[];\n" + "int after(void) { return data[0]; }\n" + "static int internal;\n" + "extern int internal = 1;\n"); + ASSERT(db); + ASSERT_EQUALS(4, db->scopeList.front().varlist.size()); + + const Variable* value = Token::findsimplematch(tokenizer.tokens(), "value")->variable(); + ASSERT(value && value->isInit() && !value->isExtern()); + ASSERT_EQUALS(2, value->nameToken()->linenr()); + const Variable* reverse = Token::findsimplematch(tokenizer.tokens(), "reverse")->variable(); + ASSERT(reverse && reverse->isInit() && !reverse->isExtern()); + ASSERT_EQUALS(4, reverse->nameToken()->linenr()); + + const Variable* data = Token::findsimplematch(tokenizer.tokens(), "data")->variable(); + ASSERT(data && data->isArray() && !data->isExtern()); + ASSERT_EQUALS(1U, data->dimensions().size()); + ASSERT(data->dimensions()[0].known); + ASSERT_EQUALS(4, data->dimension(0)); + const Variable* internal = Token::findsimplematch(tokenizer.tokens(), "internal")->variable(); + ASSERT(internal && internal->isStatic() && internal->isInit()); + ASSERT_EQUALS(12, internal->nameToken()->linenr()); + + for (const Variable& var : db->scopeList.front().varlist) { + ASSERT(db->getVariableFromVarId(var.declarationId()) == &var); + for (const Token* tok = tokenizer.tokens(); tok; tok = tok->next()) { + if (tok->str() == var.name()) + ASSERT(tok->variable() == &var); + } + } + } + + void globalVariableRedeclarationArrayBounds() { + GET_SYMBOL_DB_C("int a[4];\n" + "int a[] = { 1 };\n" + "extern int b[4];\n" + "int b[] = { 1 };\n"); + ASSERT(db); + ASSERT_EQUALS(2, db->scopeList.front().varlist.size()); + for (const Variable& var : db->scopeList.front().varlist) { + ASSERT(var.isArray() && var.isInit() && !var.isExtern()); + ASSERT_EQUALS(1U, var.dimensions().size()); + ASSERT(var.dimensions()[0].known); + ASSERT_EQUALS(4, var.dimension(0)); + for (const Token* tok = tokenizer.tokens(); tok; tok = tok->next()) { + if (tok->str() == var.name()) + ASSERT(tok->variable() == &var); + } + } + } + + void globalVariableRedeclarationScopes() { + { + GET_SYMBOL_DB_C("int x;\n" + "struct A { int x; };\n" + "struct B { int x; };\n" + "void f(void) { int x; { int x; } }\n" + "void g(void) { int x; }\n" + "int x;\n"); + ASSERT(db); + ASSERT_EQUALS(1, db->scopeList.front().varlist.size()); + const Variable* global = &db->scopeList.front().varlist.front(); + std::set variables; + for (const Token* tok = tokenizer.tokens(); tok; tok = tok->next()) { + if (tok->str() != "x") + continue; + ASSERT(tok->variable()); + variables.insert(tok->variable()); + if (tok->linenr() == 1 || tok->linenr() == 6) + ASSERT(tok->variable() == global); + else + ASSERT(tok->variable() != global); + } + ASSERT_EQUALS(6, variables.size()); + } + { + GET_SYMBOL_DB_C("int x;\n" + "int f(int x);\n" + "int f(int x) { return x; }\n" + "int x;\n"); + ASSERT(db); + ASSERT_EQUALS(1, db->scopeList.front().varlist.size()); + const Variable* global = &db->scopeList.front().varlist.front(); + const Token* prototypeArg = findToken(tokenizer, "x )", 2); + ASSERT(prototypeArg && prototypeArg->varId() != 0); + ASSERT(prototypeArg->varId() != global->declarationId()); + const Token* arg = findToken(tokenizer, "x )", 3); + const Token* use = findToken(tokenizer, "x ;", 3); + ASSERT(arg && arg->variable() && arg->variable()->isArgument()); + ASSERT(arg->variable() != global); + ASSERT(use && use->variable() == arg->variable()); + } + { + GET_SYMBOL_DB_C("int x;\n" + "struct { int x; } s;\n" + "int x;\n"); + ASSERT(db); + ASSERT_EQUALS(2, db->scopeList.front().varlist.size()); + const Token* global = findToken(tokenizer, "x ;", 1); + const Token* member = findToken(tokenizer, "x ;", 2); + const Token* redeclaration = findToken(tokenizer, "x ;", 3); + ASSERT(global && global->variable() && global->variable()->isGlobal()); + ASSERT(member && member->variable() && member->variable()->isMember()); + ASSERT(redeclaration && redeclaration->variable() == global->variable()); + ASSERT(member->variable() != global->variable()); + } + { + // Preserve the existing C++ handling. + GET_SYMBOL_DB("extern int x; int x;\n"); + ASSERT_EQUALS(2, db->scopeList.front().varlist.size()); + const Token* first = Token::findsimplematch(tokenizer.tokens(), "x ;"); + ASSERT(first && first->variable()); + const Token* second = Token::findsimplematch(first->next(), "x ;"); + ASSERT(second && second->variable()); + ASSERT(first->variable() != second->variable()); + } + } + void checkTypeStartEndToken1() { GET_SYMBOL_DB("static std::string i;\n" "static const std::string j;\n" diff --git a/test/testvalueflow.cpp b/test/testvalueflow.cpp index 69359c9bdf3..27816a39c15 100644 --- a/test/testvalueflow.cpp +++ b/test/testvalueflow.cpp @@ -120,6 +120,7 @@ class TestValueFlow : public TestFixture { TEST_CASE(valueFlowGlobalConstVar); TEST_CASE(valueFlowGlobalStaticVar); + TEST_CASE(valueFlowGlobalStaticRedeclarationC); TEST_CASE(valueFlowInlineAssembly); @@ -6027,6 +6028,26 @@ class TestValueFlow : public TestFixture { ASSERT_EQUALS(false, testValueOfX(code, 3U, 1)); } + void valueFlowGlobalStaticRedeclarationC() { + for (const std::string& change : {"", "x = 2;", "++x;", "int *p = &x; *p = 2;"}) { + const std::string code = "static int x;\n" + "void update(void) { " + change + " }\n" + "int f(void) { update(); return x - 1; }\n" + "static int x = 1;\n"; + SimpleTokenizer tokenizer(settings, *this, false); + ASSERT(tokenizer.tokenize(code)); + const Token* ret = Token::findsimplematch(tokenizer.tokens(), "return x -"); + ASSERT(ret); + const Token* use = ret->next(); + if (change.empty()) { + ASSERT(use->hasKnownIntValue()); + ASSERT_EQUALS(1, use->getKnownIntValue()); + } else { + ASSERT(!use->hasKnownIntValue()); + } + } + } + void valueFlowInlineAssembly() { const char* code = "void f() {\n" " int x = 42;\n" diff --git a/test/testvarid.cpp b/test/testvarid.cpp index 4186917cc35..ec6453c6f3b 100644 --- a/test/testvarid.cpp +++ b/test/testvarid.cpp @@ -199,6 +199,8 @@ class TestVarID : public TestFixture { TEST_CASE(varid_not); // #9689 'not x' TEST_CASE(varid_declInIfCondition); TEST_CASE(varid_globalScope); + TEST_CASE(varid_globalRedeclarationC); // #6418 + TEST_CASE(varid_globalRedeclarationScope); TEST_CASE(varid_function_pointer_args); TEST_CASE(varid_alignas); @@ -3525,6 +3527,69 @@ class TestVarID : public TestFixture { ASSERT_EQUALS(exp, tokenize(code)); } + void varid_globalRedeclarationC() { // #6418 + ASSERT_EQUALS("1: int x@1 ; int x@1 ;\n", + tokenize("int x; int x;\n", dinit(TokenizeOptions, $.cpp = false))); + ASSERT_EQUALS("1: extern int x@1 ; int x@1 ;\n", + tokenize("extern int x; int x;\n", dinit(TokenizeOptions, $.cpp = false))); + ASSERT_EQUALS("1: int x@1 ; extern int x@1 ;\n", + tokenize("int x; extern int x;\n", dinit(TokenizeOptions, $.cpp = false))); + + ASSERT_EQUALS("1: extern int x@1 ;\n" + "2: int before ( ) { return x@1 ; }\n" + "3: int x@1 ; x@1 = 7 ;\n" + "4: extern int x@1 ;\n" + "5: int after ( ) { return x@1 ; }\n", + tokenize("extern int x;\n" + "int before(void) { return x; }\n" + "int x = 7;\n" + "extern int x;\n" + "int after(void) { return x; }\n", dinit(TokenizeOptions, $.cpp = false))); + } + + void varid_globalRedeclarationScope() { + ASSERT_EQUALS("1: int x@1 ;\n" + "2: struct A { int x@2 ; } ;\n" + "3: struct B { int x@3 ; } ;\n" + "4: void f ( ) { int x@4 ; { int x@5 ; x@5 = 1 ; } x@4 = 2 ; }\n" + "5: void g ( ) { int x@6 ; x@6 = 3 ; }\n" + "6: int x@1 ;\n", + tokenize("int x;\n" + "struct A { int x; };\n" + "struct B { int x; };\n" + "void f(void) { int x; { int x; x = 1; } x = 2; }\n" + "void g(void) { int x; x = 3; }\n" + "int x;\n", dinit(TokenizeOptions, $.cpp = false))); + + ASSERT_EQUALS("1: int x@1 ;\n" + "2: int f ( int x@2 ) ;\n" + "3: int f ( int x@3 ) { return x@3 ; }\n" + "4: int x@1 ;\n", + tokenize("int x;\n" + "int f(int x);\n" + "int f(int x) { return x; }\n" + "int x;\n", dinit(TokenizeOptions, $.cpp = false))); + + { + SimpleTokenizer tokenizer(settings, *this, false); + ASSERT((tokenizer.tokenize)("int x;\n" + "struct { int x; } s;\n" + "int x;\n")); + const Token* global = Token::findsimplematch(tokenizer.tokens(), "x ;"); + ASSERT(global && global->varId() != 0); + const Token* member = Token::findsimplematch(global->next(), "x ;"); + ASSERT(member && member->varId() != 0); + const Token* redeclaration = Token::findsimplematch(member->next(), "x ;"); + ASSERT(redeclaration); + ASSERT_EQUALS(global->varId(), redeclaration->varId()); + ASSERT(global->varId() != member->varId()); + } + + // Preserve the existing C++ handling. + ASSERT_EQUALS("1: extern int x@1 ; int x@2 ;\n", + tokenize("extern int x; int x;\n")); + } + void varid_function_pointer_args() { const char code1[] = "void foo() {\n" " char *text;\n" @@ -3568,7 +3633,7 @@ class TestVarID : public TestFixture { const char code[] = "extern alignas(16) int x;\n" "alignas(16) int x;\n"; const char expected[] = "1: extern alignas ( 16 ) int x@1 ;\n" - "2: alignas ( 16 ) int x@2 ;\n"; + "2: alignas ( 16 ) int x@1 ;\n"; ASSERT_EQUALS(expected, tokenize(code, dinit(TokenizeOptions, $.cpp = false))); } From f775f03c9eecf26a09d00076bb5e39a701e24c4d Mon Sep 17 00:00:00 2001 From: KiritoYG <118127895+KiritoYG@users.noreply.github.com> Date: Thu, 24 Sep 2026 23:31:18 +0900 Subject: [PATCH 2/2] Preserve C declaration metadata for MISRA addon checks --- addons/cppcheckdata.py | 31 +++ addons/misra.py | 221 ++++++++++-------- .../misra-regression-redeclarations-other.c | 8 + .../misra/misra-regression-redeclarations.c | 27 +++ .../misra/misra-regression-redeclarations.h | 4 + addons/test/misra_redeclarations_test.py | 138 +++++++++++ lib/symboldatabase.cpp | 30 ++- lib/symboldatabase.h | 17 ++ test/testsymboldatabase.cpp | 41 +++- test/testvalueflow.cpp | 2 +- 10 files changed, 414 insertions(+), 105 deletions(-) create mode 100644 addons/test/misra/misra-regression-redeclarations-other.c create mode 100644 addons/test/misra/misra-regression-redeclarations.c create mode 100644 addons/test/misra/misra-regression-redeclarations.h create mode 100644 addons/test/misra_redeclarations_test.py diff --git a/addons/cppcheckdata.py b/addons/cppcheckdata.py index 06028e2005c..489750bf9c2 100755 --- a/addons/cppcheckdata.py +++ b/addons/cppcheckdata.py @@ -702,6 +702,26 @@ def setId(self, IdMap): #symboldatabase.cpp/SymbolDatabase::printXml +class VariableDeclaration: + """One source declaration of a variable with shared object identity.""" + + def __init__(self, element): + self.nameTokenId = element.get('nameToken') + self.nameToken = None + self.typeStartTokenId = element.get('typeStartToken') + self.typeStartToken = None + self.typeEndTokenId = element.get('typeEndToken') + self.typeEndToken = None + self.isExtern = element.get('isExtern') == 'true' + self.isStatic = element.get('isStatic') == 'true' + self.isInit = element.get('isInit') == 'true' + + def setId(self, IdMap): + self.nameToken = IdMap[self.nameTokenId] + self.typeStartToken = IdMap[self.typeStartTokenId] + self.typeEndToken = IdMap[self.typeEndTokenId] + + class Variable: """ Information about a variable @@ -726,6 +746,8 @@ class Variable: isReference Is this variable a reference isStatic Is this variable static? isVolatile Is this variable volatile? + declarations Original declarations when several declarations name this object. + Empty for older dumps and variables without merged declarations. """ #symboldatabase.cpp/SymbolDatabase::printXml @@ -753,6 +775,7 @@ class Variable: constness = 0 def __init__(self, element): + self.declarations = [] self.Id = element.get('id') self.nameTokenId = element.get('nameToken') self.nameToken = None @@ -791,6 +814,8 @@ def setId(self, IdMap): self.typeStartToken = IdMap[self.typeStartTokenId] self.typeEndToken = IdMap[self.typeEndTokenId] self.scope = IdMap[self.scopeId] + for declaration in self.declarations: + declaration.setId(IdMap) class Container: """ @@ -1282,6 +1307,7 @@ def iterconfigurations(self): cfg = None cfg_arguments = [] # function arguments for Configuration node initialization cfg_function = None + cfg_variable = None cfg_valueflow = None # Iterating in a . @@ -1378,10 +1404,15 @@ def iterconfigurations(self): cfg.scopes[-1].varlistId.append(node.get('id')) else: var = Variable(node) + cfg_variable = var if var.nameTokenId: cfg.variables.append(var) else: cfg_arguments.append(var) + elif node.tag == 'var' and event == 'end': + cfg_variable = None + elif node.tag == 'declaration' and event == 'start' and cfg_variable is not None: + cfg_variable.declarations.append(VariableDeclaration(node)) # Parse containers elif node.tag == 'containers' and event == 'start': diff --git a/addons/misra.py b/addons/misra.py index 7abc99f365a..c65c0dbef9b 100755 --- a/addons/misra.py +++ b/addons/misra.py @@ -1662,9 +1662,18 @@ def identifier(nameToken): internal_identifiers.append(i) else: names.append(var.nameToken.str) - i = identifier(var.nameToken) - i['decl'] = var.isExtern - external_identifiers.append(i) + has_definition = False + for declaration in var.declarations or [var]: + if declaration.isExtern and not getattr(declaration, 'isInit', False): + i = identifier(declaration.nameToken) + i['decl'] = True + external_identifiers.append(i) + else: + has_definition = True + if has_definition: + i = identifier(var.nameToken) + i['decl'] = False + external_identifiers.append(i) for func in cfg.functions: if func.tokenDef is None: @@ -1690,21 +1699,31 @@ def _save_ctu_summary_usage(self, dumpfile, cfg): return self._ctu_summary_usage = True - names = [] + declaration_tokens = {declaration.nameToken + for var in cfg.variables + for declaration in (var.declarations or [var])} + names = set() + usage = [] for token in cfg.tokenlist: if not token.isName: continue if token.function and token != token.function.tokenDef: - if (not token.function.isStatic) and (token.str not in names): - names.append({'name': token.str, 'file': token.file}) + if token.function.isStatic: + continue elif token.variable: - if token == token.variable.nameToken: + if token in declaration_tokens: + continue + if token.variable.access != 'Global' or token.variable.isStatic: continue - if token.variable.access == 'Global' and (not token.variable.isStatic) and (token.str not in names): - names.append({'name': token.str, 'file': token.file}) + else: + continue + key = (token.str, token.file) + if key not in names: + names.add(key) + usage.append({'name': token.str, 'file': token.file}) - if len(names) > 0: - cppcheckdata.reportSummary(dumpfile, 'MisraUsage', names) + if len(usage) > 0: + cppcheckdata.reportSummary(dumpfile, 'MisraUsage', usage) def misra_1_2(self, cfg): @@ -2277,30 +2296,28 @@ def misra_8_4(self, cfg): continue if var.nameToken is None: continue - tok = var.nameToken - if tok.next.str == ";": - if tok.next.isSplittedVarDeclEq: - self.insert_in_dict(extern_var_with_def, tok.str, tok) + for declaration in var.declarations or [var]: + tok = declaration.nameToken + if tok.next.str == ";" and tok.next.isSplittedVarDeclEq: + self.insert_in_dict(extern_var_with_def, tok.str, declaration) else: - self.insert_in_dict(extern_var_without_def, tok.str, tok) - else: - self.insert_in_dict(extern_var_without_def, var.nameToken.str, var.nameToken) + self.insert_in_dict(extern_var_without_def, tok.str, declaration) for var in extern_var_with_def: if var not in extern_var_without_def: for t in extern_var_with_def[var]: - self.reportError(t, 8, 4) + self.reportError(t.nameToken, 8, 4) for var_str, var_tok in extern_var_without_def.items(): warn = True if var_str not in extern_var_with_def: for t in var_tok: - if t.variable.isExtern: + if t.isExtern: warn = False break if warn: for t in var_tok: - self.reportError(t, 8, 4) + self.reportError(t.nameToken, 8, 4) def misra_8_5(self, dumpfile, cfg): self._save_ctu_summary_identifiers(dumpfile, cfg) @@ -2320,9 +2337,9 @@ def misra_8_8(self, cfg): continue varname = var.nameToken.str if varname in vars: - vars[varname].append(var) + vars[varname].extend(var.declarations or [var]) else: - vars[varname] = [var] + vars[varname] = list(var.declarations or [var]) for varname, varlist in vars.items(): static_var = None extern_var = None @@ -2363,8 +2380,9 @@ def misra_8_10(self, cfg): def misra_8_11(self, data): for var in data.variables: - if var.isExtern and simpleMatch(var.nameToken.next, '[ ]') and var.nameToken.scope.type == 'Global': - self.reportError(var.nameToken, 8, 11) + for declaration in var.declarations or [var]: + if declaration.isExtern and simpleMatch(declaration.nameToken.next, '[ ]') and declaration.nameToken.scope.type == 'Global': + self.reportError(declaration.nameToken, 8, 11) def misra_8_12(self, data): for scope in data.scopes: @@ -4908,84 +4926,85 @@ def is_different_file(loc1, loc2): try: for filename in ctu_info_files: - for line in open(filename, 'rt'): - s = self.read_ctu_info_line(line) - if s is None: - continue - summary_type = s.get('summary', '') - summary_data = s.get('data', None) - - if summary_type == 'MisraTypedefInfo': - for new_typedef_info in summary_data: - key = new_typedef_info['name'] - existing_typedef_info = all_typedef_info.get(key, None) - if existing_typedef_info: - if is_different_location(existing_typedef_info, new_typedef_info): - self.reportError(Location(existing_typedef_info), 5, 6) - self.reportError(Location(new_typedef_info), 5, 6) + with open(filename, 'rt') as ctu_file: + for line in ctu_file: + s = self.read_ctu_info_line(line) + if s is None: + continue + summary_type = s.get('summary', '') + summary_data = s.get('data', None) + + if summary_type == 'MisraTypedefInfo': + for new_typedef_info in summary_data: + key = new_typedef_info['name'] + existing_typedef_info = all_typedef_info.get(key, None) + if existing_typedef_info: + if is_different_location(existing_typedef_info, new_typedef_info): + self.reportError(Location(existing_typedef_info), 5, 6) + self.reportError(Location(new_typedef_info), 5, 6) + else: + existing_typedef_info['used'] = existing_typedef_info['used'] or new_typedef_info['used'] + else: + all_typedef_info[key] = new_typedef_info + + if summary_type == 'MisraTagName': + for new_tagname_info in summary_data: + key = new_tagname_info['name'] + existing_tagname_info = all_tagname_info.get(key, None) + if existing_tagname_info: + if is_different_location(existing_tagname_info, new_tagname_info): + self.reportError(Location(existing_tagname_info), 5, 7) + self.reportError(Location(new_tagname_info), 5, 7) + else: + existing_tagname_info['used'] = existing_tagname_info['used'] or new_tagname_info['used'] + else: + all_tagname_info[key] = new_tagname_info + + if summary_type == 'MisraMacro': + for new_macro in summary_data: + key = new_macro['name'] + existing_macro = all_macro_info.get(key, None) + if existing_macro: + existing_macro['used'] = existing_macro['used'] or new_macro['used'] + else: + all_macro_info[key] = new_macro + + if summary_type == 'MisraExternalIdentifiers': + for s in sorted(summary_data, key=lambda d: "%s %s %s" %(d['file'],d['line'], d['column'] )): + is_declaration = s['decl'] + if is_declaration: + all_external_identifiers = all_external_identifiers_decl else: - existing_typedef_info['used'] = existing_typedef_info['used'] or new_typedef_info['used'] - else: - all_typedef_info[key] = new_typedef_info - - if summary_type == 'MisraTagName': - for new_tagname_info in summary_data: - key = new_tagname_info['name'] - existing_tagname_info = all_tagname_info.get(key, None) - if existing_tagname_info: - if is_different_location(existing_tagname_info, new_tagname_info): - self.reportError(Location(existing_tagname_info), 5, 7) - self.reportError(Location(new_tagname_info), 5, 7) + all_external_identifiers = all_external_identifiers_def + + name = s['name'] + if name in all_external_identifiers: + if is_declaration and is_different_location(s, all_external_identifiers[name]): + self.reportError(Location(s), 8, 5) + self.reportError(Location(all_external_identifiers[name]), 8, 5) + elif is_different_file(s, all_external_identifiers[name]): + self.reportError(Location(s), 8, 6) + self.reportError(Location(all_external_identifiers[name]), 8, 6) + all_external_identifiers[name] = s + + if summary_type == 'MisraInternalIdentifiers': + for s in summary_data: + if s['name'] in all_internal_identifiers: + if not s['inlinefunc'] or s['file'] != all_internal_identifiers[s['name']]['file']: + self.reportError(Location(s), 5, 9) + self.reportError(Location(all_internal_identifiers[s['name']]), 5, 9) + all_internal_identifiers[s['name']] = s + + if summary_type == 'MisraLocalIdentifiers': + for s in summary_data: + all_local_identifiers[s['name']] = s + + if summary_type == 'MisraUsage': + for s in summary_data: + if s['name'] in all_usage_files: + all_usage_files[s['name']].append(s['file']) else: - existing_tagname_info['used'] = existing_tagname_info['used'] or new_tagname_info['used'] - else: - all_tagname_info[key] = new_tagname_info - - if summary_type == 'MisraMacro': - for new_macro in summary_data: - key = new_macro['name'] - existing_macro = all_macro_info.get(key, None) - if existing_macro: - existing_macro['used'] = existing_macro['used'] or new_macro['used'] - else: - all_macro_info[key] = new_macro - - if summary_type == 'MisraExternalIdentifiers': - for s in sorted(summary_data, key=lambda d: "%s %s %s" %(d['file'],d['line'], d['column'] )): - is_declaration = s['decl'] - if is_declaration: - all_external_identifiers = all_external_identifiers_decl - else: - all_external_identifiers = all_external_identifiers_def - - name = s['name'] - if name in all_external_identifiers: - if is_declaration and is_different_location(s, all_external_identifiers[name]): - self.reportError(Location(s), 8, 5) - self.reportError(Location(all_external_identifiers[name]), 8, 5) - elif is_different_file(s, all_external_identifiers[name]): - self.reportError(Location(s), 8, 6) - self.reportError(Location(all_external_identifiers[name]), 8, 6) - all_external_identifiers[name] = s - - if summary_type == 'MisraInternalIdentifiers': - for s in summary_data: - if s['name'] in all_internal_identifiers: - if not s['inlinefunc'] or s['file'] != all_internal_identifiers[s['name']]['file']: - self.reportError(Location(s), 5, 9) - self.reportError(Location(all_internal_identifiers[s['name']]), 5, 9) - all_internal_identifiers[s['name']] = s - - if summary_type == 'MisraLocalIdentifiers': - for s in summary_data: - all_local_identifiers[s['name']] = s - - if summary_type == 'MisraUsage': - for s in summary_data: - if s['name'] in all_usage_files: - all_usage_files[s['name']].append(s['file']) - else: - all_usage_files[s['name']] = [s['file']] + all_usage_files[s['name']] = [s['file']] except FileNotFoundError: return diff --git a/addons/test/misra/misra-regression-redeclarations-other.c b/addons/test/misra/misra-regression-redeclarations-other.c new file mode 100644 index 00000000000..56b9512ffec --- /dev/null +++ b/addons/test/misra/misra-regression-redeclarations-other.c @@ -0,0 +1,8 @@ +#include "misra-regression-redeclarations.h" + +int two_definitions = 10; + +static int read_other(void) +{ + return elsewhere; +} diff --git a/addons/test/misra/misra-regression-redeclarations.c b/addons/test/misra/misra-regression-redeclarations.c new file mode 100644 index 00000000000..cdae81589f8 --- /dev/null +++ b/addons/test/misra/misra-regression-redeclarations.c @@ -0,0 +1,27 @@ +#include "misra-regression-redeclarations.h" + +int prior_extern = 1; +int tentative; +int tentative = 2; +int missing = 3; +static int internal; +extern int internal; +static int internal; +int incomplete[4]; +extern int duplicate_decl; +extern int duplicate_decl; +int duplicate_decl = 5; +int two_definitions = 7; +int elsewhere = 8; +int local_only; +int local_only = 9; +extern int declarations_only; +extern int declarations_only; +extern int initialized_extern; +extern int initialized_extern = 11; + +static int read_main(void) +{ + return local_only + local_only + prior_extern + incomplete[0] + + duplicate_decl + two_definitions + elsewhere; +} diff --git a/addons/test/misra/misra-regression-redeclarations.h b/addons/test/misra/misra-regression-redeclarations.h new file mode 100644 index 00000000000..9ea0f03f30a --- /dev/null +++ b/addons/test/misra/misra-regression-redeclarations.h @@ -0,0 +1,4 @@ +extern int prior_extern; +extern int incomplete[]; +extern int two_definitions; +extern int elsewhere; diff --git a/addons/test/misra_redeclarations_test.py b/addons/test/misra_redeclarations_test.py new file mode 100644 index 00000000000..8faecfc2a8a --- /dev/null +++ b/addons/test/misra_redeclarations_test.py @@ -0,0 +1,138 @@ +import json +from pathlib import Path +import shutil + +import pytest + +import cppcheckdata +from addons.misra import MisraChecker, MisraSettings, get_args_parser +from .util import dump_create, dump_remove + + +@pytest.fixture +def checker(): + return MisraChecker(MisraSettings(get_args_parser().parse_args([]))) + + +@pytest.fixture +def redeclarations(tmp_path): + source_dir = Path(__file__).parent / 'misra' + stem = 'misra-regression-redeclarations' + for suffix in ('.h', '.c', '-other.c'): + shutil.copyfile(source_dir / (stem + suffix), tmp_path / (stem + suffix)) + configurations = [] + for suffix in ('.c', '-other.c'): + source = str(tmp_path / (stem + suffix)) + dump_create(source) + try: + configurations.append(cppcheckdata.CppcheckData(source + '.dump').configurations[0]) + finally: + dump_remove(source) + return configurations + + +@pytest.mark.parametrize('with_declarations', [False, True]) +def test_dump_variable_declarations(tmp_path, with_declarations): + declarations = ''' + + + ''' if with_declarations else '' + variable = '''' if with_declarations else '/>' + dump = tmp_path / 'declarations.dump' + dump.write_text(''' + + + + + + + + + ''' + variable + ''' + + ''', encoding='utf-8') + cfg = cppcheckdata.CppcheckData(str(dump)).configurations[0] + var, other = cfg.variables + assert cfg.scopes[0].varlist == [var, other] + assert cfg.tokenlist[1].variable is var + assert cfg.tokenlist[3].variable is var + assert other.declarations == [] + if with_declarations: + assert [d.nameToken for d in var.declarations] == [cfg.tokenlist[1], cfg.tokenlist[3]] + assert [d.typeStartToken for d in var.declarations] == [cfg.tokenlist[0], cfg.tokenlist[2]] + assert [d.typeEndToken for d in var.declarations] == [cfg.tokenlist[0], cfg.tokenlist[2]] + assert [d.isExtern for d in var.declarations] == [True, False] + assert [d.isStatic for d in var.declarations] == [False, False] + assert [d.isInit for d in var.declarations] == [False, True] + else: + assert var.declarations == [] + assert var.nameToken is cfg.tokenlist[3] + + +def test_redeclaration_rules(checker, monkeypatch, redeclarations): + errors = [] + monkeypatch.setattr(checker, 'reportError', lambda token, major, minor: errors.append((major, minor, token.str))) + cfg = redeclarations[0] + variables = {var.nameToken.str: var for var in cfg.variables} + assert len(variables['internal'].declarations) == 3 + assert variables['internal'].isStatic + assert not variables['internal'].isExtern + for name in ('prior_extern', 'tentative', 'internal', 'incomplete'): + var = variables[name] + assert all(d.nameToken.variable is var for d in var.declarations) + checker.misra_8_4(cfg) + checker.misra_8_8(cfg) + checker.misra_8_11(cfg) + assert errors == [(8, 4, 'missing'), (8, 8, 'internal'), (8, 11, 'incomplete')] + + +def test_redeclaration_ctu_summaries(checker, monkeypatch, redeclarations, tmp_path): + summaries = {} + monkeypatch.setattr(cppcheckdata, 'reportSummary', + lambda dump, name, data: summaries.setdefault(dump, []).append({'summary': name, 'data': data})) + for index, cfg in enumerate(redeclarations): + current = MisraChecker(MisraSettings(get_args_parser().parse_args([]))) + current._save_ctu_summary_identifiers(str(index), cfg) + current._save_ctu_summary_usage(str(index), cfg) + + main = {s['summary']: s['data'] for s in summaries['0']} + external = main['MisraExternalIdentifiers'] + assert len([i for i in external if i['name'] == 'duplicate_decl' and i['decl']]) == 2 + assert len([i for i in external if i['name'] == 'duplicate_decl' and not i['decl']]) == 1 + assert len([i for i in external if i['name'] == 'declarations_only']) == 2 + assert not any(i['name'] == 'declarations_only' and not i['decl'] for i in external) + assert len([i for i in external if i['name'] == 'initialized_extern' and i['decl']]) == 1 + assert len([i for i in external if i['name'] == 'initialized_extern' and not i['decl']]) == 1 + assert not any(i['name'] == 'internal' for i in external) + assert len([i for i in main['MisraInternalIdentifiers'] if i['name'] == 'internal']) == 1 + usage = main['MisraUsage'] + assert len([i for i in usage if i['name'] == 'local_only']) == 1 + assert not any(i['name'] == 'declarations_only' for i in usage) + assert not any(i['file'].endswith('.h') for i in usage) + + ctu_files = [] + for name, data in summaries.items(): + path = tmp_path / (name + '.ctu-info') + path.write_text('\n'.join(json.dumps(item) for item in data), encoding='utf-8') + ctu_files.append(str(path)) + errors = [] + monkeypatch.setattr(checker, 'reportError', + lambda location, major, minor: errors.append((major, minor, location.file, location.linenr))) + checker.analyse_ctu_info(ctu_files) + + def location(name, configuration=0): + var = next(v for v in redeclarations[configuration].variables if v.nameToken.str == name) + return var.nameToken.file, var.nameToken.linenr + + duplicate = next(v for v in redeclarations[0].variables if v.nameToken.str == 'duplicate_decl') + for declaration in duplicate.declarations[:2]: + assert (8, 5, declaration.nameToken.file, declaration.nameToken.linenr) in errors + assert (8, 6, *location('two_definitions')) in errors + assert (8, 6, *location('two_definitions', 1)) in errors + assert (8, 7, *location('local_only')) in errors + assert (8, 7, *location('elsewhere')) not in errors + assert not any(error[:2] == (5, 9) for error in errors) diff --git a/lib/symboldatabase.cpp b/lib/symboldatabase.cpp index b936e22543b..c827587c1e3 100644 --- a/lib/symboldatabase.cpp +++ b/lib/symboldatabase.cpp @@ -912,6 +912,12 @@ void SymbolDatabase::createSymbolDatabaseVariableInfo() } Variable& var = *previous->second; + auto declarations = std::move(var.mDeclarations); + if (declarations.empty()) + declarations.push_back({var.nameToken(), var.typeStartToken(), var.typeEndToken(), + var.isExtern(), var.isStatic(), var.isInit()}); + declarations.push_back({it->nameToken(), it->typeStartToken(), it->typeEndToken(), + it->isExtern(), it->isStatic(), it->isInit()}); const bool isStatic = var.isStatic() || it->isStatic(); const bool isExtern = var.isExtern() && it->isExtern(); const bool maybeUnused = var.isMaybeUnused() || it->isMaybeUnused(); @@ -937,6 +943,7 @@ void SymbolDatabase::createSymbolDatabaseVariableInfo() var.setFlag(Variable::fIsMaybeUnused, maybeUnused); if (dimensions.size() == var.dimensions().size()) var.setDimensions(dimensions); + var.mDeclarations = std::move(declarations); it = scope.varlist.erase(it); } } @@ -2550,6 +2557,7 @@ Variable& Variable::operator=(const Variable &var) & mType = var.mType; mScope = var.mScope; mDimensions = var.mDimensions; + mDeclarations = var.mDeclarations; delete mValueType; mValueType = vt; @@ -4726,7 +4734,27 @@ void SymbolDatabase::printXml(std::ostream &out) const outs += " isVolatile=\""; outs += bool_to_string(var->isVolatile()); outs += '\"'; - outs += "/>\n"; + if (var->declarations().empty()) { + outs += "/>\n"; + } else { + outs += ">\n"; + for (const Variable::Declaration& declaration : var->declarations()) { + outs += " \n"; + } + outs += " \n"; + } } outs += " \n"; diff --git a/lib/symboldatabase.h b/lib/symboldatabase.h index 08c5382929e..5cbc289315f 100644 --- a/lib/symboldatabase.h +++ b/lib/symboldatabase.h @@ -219,6 +219,15 @@ class CPPCHECKLIB Variable { bool arrayDimensions(const Settings& settings, bool& isContainer); public: + struct Declaration { + const Token* nameToken; + const Token* typeStartToken; + const Token* typeEndToken; + bool isExtern; + bool isStatic; + bool isInit; + }; + Variable(const Token *name_, const Token *start_, const Token *end_, nonneg int index_, AccessControl access_, const Type *type_, const Scope *scope_, const Settings& settings) @@ -253,6 +262,12 @@ class CPPCHECKLIB Variable { return mNameToken; } + // Original occurrences for a merged C declaration. Empty for an unmerged + // variable, whose declaration is described by its ordinary accessors. + const std::vector& declarations() const { + return mDeclarations; + } + /** * Get type start token. * The type start token doesn't account 'static' and 'const' qualifiers @@ -700,6 +715,8 @@ class CPPCHECKLIB Variable { /** @brief array dimensions */ std::vector mDimensions; + std::vector mDeclarations; + /** @brief fill in information, depending on Tokens given at instantiation */ void evaluate(const Settings& settings); }; diff --git a/test/testsymboldatabase.cpp b/test/testsymboldatabase.cpp index 35861c2a2b0..e236e0dfac0 100644 --- a/test/testsymboldatabase.cpp +++ b/test/testsymboldatabase.cpp @@ -2740,13 +2740,31 @@ class TestSymbolDatabase : public TestFixture { "extern int data[];\n" "int after(void) { return data[0]; }\n" "static int internal;\n" - "extern int internal = 1;\n"); + "extern int internal = 1;\n" + "extern int chained;\n" + "int chained;\n" + "int chained = 3;\n" + "extern int chained;\n"); ASSERT(db); - ASSERT_EQUALS(4, db->scopeList.front().varlist.size()); + ASSERT_EQUALS(5, db->scopeList.front().varlist.size()); const Variable* value = Token::findsimplematch(tokenizer.tokens(), "value")->variable(); ASSERT(value && value->isInit() && !value->isExtern()); ASSERT_EQUALS(2, value->nameToken()->linenr()); + ASSERT_EQUALS(3U, value->declarations().size()); + for (std::size_t i = 0; i < value->declarations().size(); ++i) { + const Variable::Declaration& declaration = value->declarations()[i]; + ASSERT_EQUALS(i + 1, declaration.nameToken->linenr()); + ASSERT_EQUALS(i != 1, declaration.isExtern); + ASSERT_EQUALS(i == 1, declaration.isInit); + ASSERT(!declaration.isStatic); + ASSERT_EQUALS("int", declaration.typeStartToken->str()); + ASSERT_EQUALS("int", declaration.typeEndToken->str()); + ASSERT(declaration.nameToken->variable() == value); + } + const Variable copy(*value); + ASSERT_EQUALS(value->declarations().size(), copy.declarations().size()); + ASSERT(copy.declarations().front().nameToken == value->declarations().front().nameToken); const Variable* reverse = Token::findsimplematch(tokenizer.tokens(), "reverse")->variable(); ASSERT(reverse && reverse->isInit() && !reverse->isExtern()); ASSERT_EQUALS(4, reverse->nameToken()->linenr()); @@ -2759,6 +2777,25 @@ class TestSymbolDatabase : public TestFixture { const Variable* internal = Token::findsimplematch(tokenizer.tokens(), "internal")->variable(); ASSERT(internal && internal->isStatic() && internal->isInit()); ASSERT_EQUALS(12, internal->nameToken()->linenr()); + ASSERT_EQUALS(2U, internal->declarations().size()); + ASSERT(internal->declarations()[0].isStatic); + ASSERT(!internal->declarations()[0].isExtern); + ASSERT(!internal->declarations()[0].isInit); + ASSERT(!internal->declarations()[1].isStatic); + ASSERT(internal->declarations()[1].isExtern); + ASSERT(internal->declarations()[1].isInit); + + const Variable* chained = Token::findsimplematch(tokenizer.tokens(), "chained")->variable(); + ASSERT(chained && chained->isInit() && !chained->isExtern()); + ASSERT_EQUALS(15, chained->nameToken()->linenr()); + ASSERT_EQUALS(4U, chained->declarations().size()); + for (std::size_t i = 0; i < chained->declarations().size(); ++i) { + const Variable::Declaration& declaration = chained->declarations()[i]; + ASSERT_EQUALS(13 + i, declaration.nameToken->linenr()); + ASSERT_EQUALS(i == 0 || i == 3, declaration.isExtern); + ASSERT_EQUALS(i == 2, declaration.isInit); + ASSERT(declaration.nameToken->variable() == chained); + } for (const Variable& var : db->scopeList.front().varlist) { ASSERT(db->getVariableFromVarId(var.declarationId()) == &var); diff --git a/test/testvalueflow.cpp b/test/testvalueflow.cpp index 27816a39c15..90f661a98bb 100644 --- a/test/testvalueflow.cpp +++ b/test/testvalueflow.cpp @@ -6029,7 +6029,7 @@ class TestValueFlow : public TestFixture { } void valueFlowGlobalStaticRedeclarationC() { - for (const std::string& change : {"", "x = 2;", "++x;", "int *p = &x; *p = 2;"}) { + for (const std::string change : {"", "x = 2;", "++x;", "int *p = &x; *p = 2;"}) { const std::string code = "static int x;\n" "void update(void) { " + change + " }\n" "int f(void) { update(); return x - 1; }\n"