Parse `extern "C"`, and use it to avoid FP reference warnings (#2234)

Previously, cppcheck discarded the `extern "C"` specifier.  This patch modifies cppcheck to parse each as a Scope in the symbol database, then uses that scope to avoid false positives when making recommendations about changing a function argument to be a reference (since variable references is a C++ feature, unavailable in C, and thus unavailable in `extern "C"`).
This commit is contained in:
Steve Mokris 2019-10-08 11:48:09 -04:00 committed by Daniel Marjamäki
parent 9f40341ba8
commit 506a952ad2
5 changed files with 71 additions and 5 deletions

View File

@ -1232,6 +1232,10 @@ void CheckOther::checkPassByReference()
if (var->scope() && var->scope()->function->arg->link()->strAt(-1) == "...") if (var->scope() && var->scope()->function->arg->link()->strAt(-1) == "...")
continue; // references could not be used as va_start parameters (#5824) continue; // references could not be used as va_start parameters (#5824)
if ((var->declEndToken() && var->declEndToken()->isExternC()) ||
(var->scope() && var->scope()->function && var->scope()->function->tokenDef && var->scope()->function->tokenDef->isExternC()))
continue; // references cannot be used in functions in extern "C" blocks
bool inconclusive = false; bool inconclusive = false;
if (var->valueType()->type == ValueType::Type::CONTAINER) { if (var->valueType()->type == ValueType::Type::CONTAINER) {

View File

@ -578,6 +578,13 @@ public:
setFlag(fConstexpr, b); setFlag(fConstexpr, b);
} }
bool isExternC() const {
return getFlag(fExternC);
}
void isExternC(bool b) {
setFlag(fExternC, b);
}
bool isBitfield() const { bool isBitfield() const {
return mImpl->mBits > 0; return mImpl->mBits > 0;
@ -1114,6 +1121,7 @@ private:
fAtAddress = (1 << 24), // @ 0x4000 fAtAddress = (1 << 24), // @ 0x4000
fIncompleteVar = (1 << 25), fIncompleteVar = (1 << 25),
fConstexpr = (1 << 26), fConstexpr = (1 << 26),
fExternC = (1 << 27),
}; };
Token::Type mTokType; Token::Type mTokType;

View File

@ -2499,14 +2499,23 @@ void Tokenizer::simplifyExternC()
{ {
if (isC()) if (isC())
return; return;
// Add attributes to all tokens within `extern "C"` inlines and blocks, and remove the `extern "C"` tokens.
for (Token *tok = list.front(); tok; tok = tok->next()) { for (Token *tok = list.front(); tok; tok = tok->next()) {
if (Token::simpleMatch(tok, "extern \"C\"")) { if (Token::simpleMatch(tok, "extern \"C\"")) {
Token *tok2 = tok->next();
if (tok->strAt(2) == "{") { if (tok->strAt(2) == "{") {
tok->linkAt(2)->deleteThis(); tok2 = tok2->next(); // skip {
tok->deleteNext(2); while ((tok2 = tok2->next()) && tok2 != tok->linkAt(2))
} else tok2->isExternC(true);
tok->deleteNext(); tok->linkAt(2)->deleteThis(); // }
tok->deleteThis(); tok->deleteNext(2); // "C" {
} else {
while ((tok2 = tok2->next()) && !Token::simpleMatch(tok2, ";"))
tok2->isExternC(true);
tok->deleteNext(); // "C"
}
tok->deleteThis(); // extern
} }
} }
} }

View File

@ -92,6 +92,7 @@ private:
TEST_CASE(passedByValue); TEST_CASE(passedByValue);
TEST_CASE(passedByValue_nonConst); TEST_CASE(passedByValue_nonConst);
TEST_CASE(passedByValue_externC);
TEST_CASE(constVariable); TEST_CASE(constVariable);
@ -1707,6 +1708,26 @@ private:
} }
} }
void passedByValue_externC() {
check("struct X { int a[5]; }; void f(X v) { }");
ASSERT_EQUALS("[test.cpp:1]: (performance) Function parameter 'v' should be passed by const reference.\n", errout.str());
check("extern \"C\" { struct X { int a[5]; }; void f(X v) { } }");
ASSERT_EQUALS("", errout.str());
check("struct X { int a[5]; }; extern \"C\" void f(X v) { }");
ASSERT_EQUALS("", errout.str());
check("struct X { int a[5]; }; void f(const X v);");
ASSERT_EQUALS("[test.cpp:1]: (performance) Function parameter 'v' should be passed by const reference.\n", errout.str());
check("extern \"C\" { struct X { int a[5]; }; void f(const X v); }");
ASSERT_EQUALS("", errout.str());
check("struct X { int a[5]; }; extern \"C\" void f(const X v) { }");
ASSERT_EQUALS("", errout.str());
}
void constVariable() { void constVariable() {
check("int f(std::vector<int> x) {\n" check("int f(std::vector<int> x) {\n"
" int& i = x[0];\n" " int& i = x[0];\n"

View File

@ -154,6 +154,7 @@ private:
TEST_CASE(findVariableType1); TEST_CASE(findVariableType1);
TEST_CASE(findVariableType2); TEST_CASE(findVariableType2);
TEST_CASE(findVariableType3); TEST_CASE(findVariableType3);
TEST_CASE(findVariableTypeExternC);
TEST_CASE(rangeBasedFor); TEST_CASE(rangeBasedFor);
@ -348,6 +349,7 @@ private:
TEST_CASE(findFunction27); TEST_CASE(findFunction27);
TEST_CASE(findFunction28); TEST_CASE(findFunction28);
TEST_CASE(findFunctionContainer); TEST_CASE(findFunctionContainer);
TEST_CASE(findFunctionExternC);
TEST_CASE(valueTypeMatchParameter); // ValueType::matchParameter TEST_CASE(valueTypeMatchParameter); // ValueType::matchParameter
@ -950,6 +952,18 @@ private:
ASSERT(avar && avar->type() != nullptr); ASSERT(avar && avar->type() != nullptr);
} }
void findVariableTypeExternC() {
GET_SYMBOL_DB("extern \"C\" { typedef int INT; }\n"
"void bar() {\n"
" INT x = 3;\n"
"}");
(void)db;
const Variable* avar = Token::findsimplematch(tokenizer.tokens(), "x")->variable();
ASSERT(avar);
ASSERT(avar->valueType() != nullptr);
ASSERT(avar->valueType()->str() == "signed int");
}
void rangeBasedFor() { void rangeBasedFor() {
GET_SYMBOL_DB("void reset() {\n" GET_SYMBOL_DB("void reset() {\n"
" for(auto& e : array)\n" " for(auto& e : array)\n"
@ -5624,6 +5638,16 @@ private:
} }
} }
void findFunctionExternC() {
GET_SYMBOL_DB("extern \"C\" { void foo(int); }\n"
"void bar() {\n"
" foo(42);\n"
"}");
const Token *a = Token::findsimplematch(tokenizer.tokens(), "foo ( 42 )");
ASSERT(a);
ASSERT(a->function());
}
void valueTypeMatchParameter() { void valueTypeMatchParameter() {
ValueType vt_int(ValueType::Sign::SIGNED, ValueType::Type::INT, 0); ValueType vt_int(ValueType::Sign::SIGNED, ValueType::Type::INT, 0);
ValueType vt_const_int(ValueType::Sign::SIGNED, ValueType::Type::INT, 0, 1); ValueType vt_const_int(ValueType::Sign::SIGNED, ValueType::Type::INT, 0, 1);