Fix #9279 Missing --check-library warning when memory leaks check assumes function is noreturn (#4937)
* Fix #9279 Missing --check-library warning when memory leaks check assumes function is noreturn * Format * Fix check, add tests * Format
This commit is contained in:
parent
e22a740f0c
commit
1b00a0f06a
|
@ -96,10 +96,9 @@ void VarInfo::print()
|
||||||
std::cout << "size=" << alloctype.size() << std::endl;
|
std::cout << "size=" << alloctype.size() << std::endl;
|
||||||
for (std::map<int, AllocInfo>::const_iterator it = alloctype.cbegin(); it != alloctype.cend(); ++it) {
|
for (std::map<int, AllocInfo>::const_iterator it = alloctype.cbegin(); it != alloctype.cend(); ++it) {
|
||||||
std::string strusage;
|
std::string strusage;
|
||||||
const std::map<int, std::string>::const_iterator use =
|
const auto use = possibleUsage.find(it->first);
|
||||||
possibleUsage.find(it->first);
|
|
||||||
if (use != possibleUsage.end())
|
if (use != possibleUsage.end())
|
||||||
strusage = use->second;
|
strusage = use->second.first;
|
||||||
|
|
||||||
std::string status;
|
std::string status;
|
||||||
switch (it->second.status) {
|
switch (it->second.status) {
|
||||||
|
@ -133,11 +132,11 @@ void VarInfo::print()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VarInfo::possibleUsageAll(const std::string &functionName)
|
void VarInfo::possibleUsageAll(const std::pair<std::string, Usage>& functionUsage)
|
||||||
{
|
{
|
||||||
possibleUsage.clear();
|
possibleUsage.clear();
|
||||||
for (std::map<int, AllocInfo>::const_iterator it = alloctype.cbegin(); it != alloctype.cend(); ++it)
|
for (std::map<int, AllocInfo>::const_iterator it = alloctype.cbegin(); it != alloctype.cend(); ++it)
|
||||||
possibleUsage[it->first] = functionName;
|
possibleUsage[it->first] = functionUsage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -169,13 +168,13 @@ void CheckLeakAutoVar::deallocReturnError(const Token *tok, const Token *dealloc
|
||||||
reportError(locations, Severity::error, "deallocret", "$symbol:" + varname + "\nReturning/dereferencing '$symbol' after it is deallocated / released", CWE672, Certainty::normal);
|
reportError(locations, Severity::error, "deallocret", "$symbol:" + varname + "\nReturning/dereferencing '$symbol' after it is deallocated / released", CWE672, Certainty::normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CheckLeakAutoVar::configurationInfo(const Token* tok, const std::string &functionName)
|
void CheckLeakAutoVar::configurationInfo(const Token* tok, const std::pair<std::string, VarInfo::Usage>& functionUsage)
|
||||||
{
|
{
|
||||||
if (mSettings->checkLibrary) {
|
if (mSettings->checkLibrary && functionUsage.second == VarInfo::USED) {
|
||||||
reportError(tok,
|
reportError(tok,
|
||||||
Severity::information,
|
Severity::information,
|
||||||
"checkLibraryUseIgnore",
|
"checkLibraryUseIgnore",
|
||||||
"--check-library: Function " + functionName + "() should have <use>/<leak-ignore> configuration");
|
"--check-library: Function " + functionUsage.first + "() should have <use>/<leak-ignore> configuration");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,7 +299,7 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken,
|
||||||
throw InternalError(startToken, "Internal limit: CheckLeakAutoVar::checkScope() Maximum recursive count of 1000 reached.", InternalError::LIMIT);
|
throw InternalError(startToken, "Internal limit: CheckLeakAutoVar::checkScope() Maximum recursive count of 1000 reached.", InternalError::LIMIT);
|
||||||
|
|
||||||
std::map<int, VarInfo::AllocInfo> &alloctype = varInfo.alloctype;
|
std::map<int, VarInfo::AllocInfo> &alloctype = varInfo.alloctype;
|
||||||
std::map<int, std::string> &possibleUsage = varInfo.possibleUsage;
|
auto& possibleUsage = varInfo.possibleUsage;
|
||||||
const std::set<int> conditionalAlloc(varInfo.conditionalAlloc);
|
const std::set<int> conditionalAlloc(varInfo.conditionalAlloc);
|
||||||
|
|
||||||
// Parse all tokens
|
// Parse all tokens
|
||||||
|
@ -671,8 +670,15 @@ bool CheckLeakAutoVar::checkScope(const Token * const startToken,
|
||||||
if (mTokenizer->isScopeNoReturn(tok->tokAt(2), &unknown)) {
|
if (mTokenizer->isScopeNoReturn(tok->tokAt(2), &unknown)) {
|
||||||
if (!unknown)
|
if (!unknown)
|
||||||
varInfo.clear();
|
varInfo.clear();
|
||||||
else if (!mSettings->library.isLeakIgnore(functionName) && !mSettings->library.isUse(functionName))
|
else {
|
||||||
varInfo.possibleUsageAll(functionName);
|
if (ftok->function() && !ftok->function()->isAttributeNoreturn() &&
|
||||||
|
!(ftok->function()->functionScope && mTokenizer->isScopeNoReturn(ftok->function()->functionScope->bodyEnd))) // check function scope
|
||||||
|
continue;
|
||||||
|
if (!mSettings->library.isLeakIgnore(functionName) && !mSettings->library.isUse(functionName)) {
|
||||||
|
const VarInfo::Usage usage = Token::simpleMatch(openingPar, "( )") ? VarInfo::NORET : VarInfo::USED; // TODO: check parameters passed to function
|
||||||
|
varInfo.possibleUsageAll({ functionName, usage });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -870,7 +876,7 @@ void CheckLeakAutoVar::changeAllocStatus(VarInfo &varInfo, const VarInfo::AllocI
|
||||||
if (var != alloctype.end()) {
|
if (var != alloctype.end()) {
|
||||||
if (allocation.status == VarInfo::NOALLOC) {
|
if (allocation.status == VarInfo::NOALLOC) {
|
||||||
// possible usage
|
// possible usage
|
||||||
varInfo.possibleUsage[arg->varId()] = tok->str();
|
varInfo.possibleUsage[arg->varId()] = { tok->str(), VarInfo::USED };
|
||||||
if (var->second.status == VarInfo::DEALLOC && arg->previous()->str() == "&")
|
if (var->second.status == VarInfo::DEALLOC && arg->previous()->str() == "&")
|
||||||
varInfo.erase(arg->varId());
|
varInfo.erase(arg->varId());
|
||||||
} else if (var->second.managed()) {
|
} else if (var->second.managed()) {
|
||||||
|
@ -1006,11 +1012,11 @@ void CheckLeakAutoVar::leakIfAllocated(const Token *vartok,
|
||||||
const VarInfo &varInfo)
|
const VarInfo &varInfo)
|
||||||
{
|
{
|
||||||
const std::map<int, VarInfo::AllocInfo> &alloctype = varInfo.alloctype;
|
const std::map<int, VarInfo::AllocInfo> &alloctype = varInfo.alloctype;
|
||||||
const std::map<int, std::string> &possibleUsage = varInfo.possibleUsage;
|
const auto& possibleUsage = varInfo.possibleUsage;
|
||||||
|
|
||||||
const std::map<int, VarInfo::AllocInfo>::const_iterator var = alloctype.find(vartok->varId());
|
const std::map<int, VarInfo::AllocInfo>::const_iterator var = alloctype.find(vartok->varId());
|
||||||
if (var != alloctype.cend() && var->second.status == VarInfo::ALLOC) {
|
if (var != alloctype.cend() && var->second.status == VarInfo::ALLOC) {
|
||||||
const std::map<int, std::string>::const_iterator use = possibleUsage.find(vartok->varId());
|
const auto use = possibleUsage.find(vartok->varId());
|
||||||
if (use == possibleUsage.end()) {
|
if (use == possibleUsage.end()) {
|
||||||
leakError(vartok, vartok->str(), var->second.type);
|
leakError(vartok, vartok->str(), var->second.type);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1022,7 +1028,7 @@ void CheckLeakAutoVar::leakIfAllocated(const Token *vartok,
|
||||||
void CheckLeakAutoVar::ret(const Token *tok, VarInfo &varInfo, const bool isEndOfScope)
|
void CheckLeakAutoVar::ret(const Token *tok, VarInfo &varInfo, const bool isEndOfScope)
|
||||||
{
|
{
|
||||||
const std::map<int, VarInfo::AllocInfo> &alloctype = varInfo.alloctype;
|
const std::map<int, VarInfo::AllocInfo> &alloctype = varInfo.alloctype;
|
||||||
const std::map<int, std::string> &possibleUsage = varInfo.possibleUsage;
|
const auto& possibleUsage = varInfo.possibleUsage;
|
||||||
std::vector<int> toRemove;
|
std::vector<int> toRemove;
|
||||||
|
|
||||||
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
|
const SymbolDatabase *symbolDatabase = mTokenizer->getSymbolDatabase();
|
||||||
|
@ -1071,7 +1077,7 @@ void CheckLeakAutoVar::ret(const Token *tok, VarInfo &varInfo, const bool isEndO
|
||||||
deallocReturnError(tok, it->second.allocTok, var->name());
|
deallocReturnError(tok, it->second.allocTok, var->name());
|
||||||
|
|
||||||
else if (!used && !it->second.managed()) {
|
else if (!used && !it->second.managed()) {
|
||||||
const std::map<int, std::string>::const_iterator use = possibleUsage.find(varid);
|
const auto use = possibleUsage.find(varid);
|
||||||
if (use == possibleUsage.end()) {
|
if (use == possibleUsage.end()) {
|
||||||
leakError(tok, var->name(), it->second.type);
|
leakError(tok, var->name(), it->second.type);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -55,8 +55,9 @@ public:
|
||||||
return status < 0;
|
return status < 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
enum Usage { USED, NORET };
|
||||||
std::map<int, AllocInfo> alloctype;
|
std::map<int, AllocInfo> alloctype;
|
||||||
std::map<int, std::string> possibleUsage;
|
std::map<int, std::pair<std::string, Usage>> possibleUsage;
|
||||||
std::set<int> conditionalAlloc;
|
std::set<int> conditionalAlloc;
|
||||||
std::set<int> referenced;
|
std::set<int> referenced;
|
||||||
|
|
||||||
|
@ -92,7 +93,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
/** set possible usage for all variables */
|
/** set possible usage for all variables */
|
||||||
void possibleUsageAll(const std::string &functionName);
|
void possibleUsageAll(const std::pair<std::string, Usage>& functionUsage);
|
||||||
|
|
||||||
void print();
|
void print();
|
||||||
};
|
};
|
||||||
|
@ -159,12 +160,12 @@ private:
|
||||||
void doubleFreeError(const Token *tok, const Token *prevFreeTok, const std::string &varname, int type);
|
void doubleFreeError(const Token *tok, const Token *prevFreeTok, const std::string &varname, int type);
|
||||||
|
|
||||||
/** message: user configuration is needed to complete analysis */
|
/** message: user configuration is needed to complete analysis */
|
||||||
void configurationInfo(const Token* tok, const std::string &functionName);
|
void configurationInfo(const Token* tok, const std::pair<std::string, VarInfo::Usage>& functionUsage);
|
||||||
|
|
||||||
void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override {
|
void getErrorMessages(ErrorLogger *errorLogger, const Settings *settings) const override {
|
||||||
CheckLeakAutoVar c(nullptr, settings, errorLogger);
|
CheckLeakAutoVar c(nullptr, settings, errorLogger);
|
||||||
c.deallocReturnError(nullptr, nullptr, "p");
|
c.deallocReturnError(nullptr, nullptr, "p");
|
||||||
c.configurationInfo(nullptr, "f"); // user configuration is needed to complete analysis
|
c.configurationInfo(nullptr, { "f", VarInfo::USED }); // user configuration is needed to complete analysis
|
||||||
c.doubleFreeError(nullptr, nullptr, "varname", 0);
|
c.doubleFreeError(nullptr, nullptr, "varname", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,6 @@ private:
|
||||||
TEST_CASE(freopen2);
|
TEST_CASE(freopen2);
|
||||||
|
|
||||||
TEST_CASE(deallocuse1);
|
TEST_CASE(deallocuse1);
|
||||||
TEST_CASE(deallocuse2);
|
|
||||||
TEST_CASE(deallocuse3);
|
TEST_CASE(deallocuse3);
|
||||||
TEST_CASE(deallocuse4);
|
TEST_CASE(deallocuse4);
|
||||||
TEST_CASE(deallocuse5); // #4018: FP. free(p), p = 0;
|
TEST_CASE(deallocuse5); // #4018: FP. free(p), p = 0;
|
||||||
|
@ -508,8 +507,8 @@ private:
|
||||||
settings = s;
|
settings = s;
|
||||||
}
|
}
|
||||||
|
|
||||||
void assign24() { // #7440
|
void assign24() {
|
||||||
check("void f() {\n"
|
check("void f() {\n" // #7440
|
||||||
" char* data = new char[100];\n"
|
" char* data = new char[100];\n"
|
||||||
" char** dataPtr = &data;\n"
|
" char** dataPtr = &data;\n"
|
||||||
" delete[] *dataPtr;\n"
|
" delete[] *dataPtr;\n"
|
||||||
|
@ -523,6 +522,46 @@ private:
|
||||||
" delete[] *dataPtr;\n"
|
" delete[] *dataPtr;\n"
|
||||||
"}\n", true);
|
"}\n", true);
|
||||||
ASSERT_EQUALS("", errout.str());
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("void f() {\n" // #9279
|
||||||
|
" int* p = new int;\n"
|
||||||
|
" *p = 42;\n"
|
||||||
|
" g();\n"
|
||||||
|
"}\n", /*cpp*/ true);
|
||||||
|
ASSERT_EQUALS("[test.cpp:4]: (information) --check-library: Function g() should have <noreturn> configuration\n",
|
||||||
|
errout.str());
|
||||||
|
|
||||||
|
check("void g();\n"
|
||||||
|
"void f() {\n"
|
||||||
|
" int* p = new int;\n"
|
||||||
|
" *p = 42;\n"
|
||||||
|
" g();\n"
|
||||||
|
"}\n", /*cpp*/ true);
|
||||||
|
ASSERT_EQUALS("[test.cpp:6]: (error) Memory leak: p\n", errout.str());
|
||||||
|
|
||||||
|
check("void g() {}\n"
|
||||||
|
"void f() {\n"
|
||||||
|
" int* p = new int;\n"
|
||||||
|
" *p = 42;\n"
|
||||||
|
" g();\n"
|
||||||
|
"}\n", /*cpp*/ true);
|
||||||
|
ASSERT_EQUALS("[test.cpp:6]: (error) Memory leak: p\n", errout.str());
|
||||||
|
|
||||||
|
check("[[noreturn]] void g();\n"
|
||||||
|
"void f() {\n"
|
||||||
|
" int* p = new int;\n"
|
||||||
|
" *p = 42;\n"
|
||||||
|
" g();\n"
|
||||||
|
"}\n", /*cpp*/ true);
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
||||||
|
check("void g() { exit(1); }\n"
|
||||||
|
"void f() {\n"
|
||||||
|
" int* p = new int;\n"
|
||||||
|
" *p = 42;\n"
|
||||||
|
" g();\n"
|
||||||
|
"}\n", /*cpp*/ true);
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void isAutoDealloc() {
|
void isAutoDealloc() {
|
||||||
|
@ -647,20 +686,6 @@ private:
|
||||||
ASSERT_EQUALS("[test.c:3]: (error) Dereferencing 'p' after it is deallocated / released\n", errout.str());
|
ASSERT_EQUALS("[test.c:3]: (error) Dereferencing 'p' after it is deallocated / released\n", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void deallocuse2() {
|
|
||||||
check("void f(char *p) {\n"
|
|
||||||
" free(p);\n"
|
|
||||||
" strcpy(a, p);\n"
|
|
||||||
"}");
|
|
||||||
TODO_ASSERT_EQUALS("error (free,use)", "[test.c:3]: (information) --check-library: Function strcpy() should have <noreturn> configuration\n", errout.str());
|
|
||||||
|
|
||||||
check("void f(char *p) {\n" // #3041 - assigning pointer when it's used
|
|
||||||
" free(p);\n"
|
|
||||||
" strcpy(a, p=b());\n"
|
|
||||||
"}");
|
|
||||||
TODO_ASSERT_EQUALS("", "[test.c:3]: (information) --check-library: Function strcpy() should have <noreturn> configuration\n", errout.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void deallocuse3() {
|
void deallocuse3() {
|
||||||
check("void f(struct str *p) {\n"
|
check("void f(struct str *p) {\n"
|
||||||
" free(p);\n"
|
" free(p);\n"
|
||||||
|
@ -1437,8 +1462,7 @@ private:
|
||||||
" char *p = malloc(10);\n"
|
" char *p = malloc(10);\n"
|
||||||
" fatal_error();\n"
|
" fatal_error();\n"
|
||||||
"}");
|
"}");
|
||||||
ASSERT_EQUALS("[test.c:3]: (information) --check-library: Function fatal_error() should have <noreturn> configuration\n"
|
ASSERT_EQUALS("[test.c:3]: (information) --check-library: Function fatal_error() should have <noreturn> configuration\n",
|
||||||
"[test.c:4]: (information) --check-library: Function fatal_error() should have <use>/<leak-ignore> configuration\n",
|
|
||||||
errout.str());
|
errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2711,6 +2735,7 @@ private:
|
||||||
LOAD_LIB_2(settings.library, "std.cfg");
|
LOAD_LIB_2(settings.library, "std.cfg");
|
||||||
|
|
||||||
TEST_CASE(returnedValue); // #9298
|
TEST_CASE(returnedValue); // #9298
|
||||||
|
TEST_CASE(deallocuse2);
|
||||||
TEST_CASE(fclose_false_positive); // #9575
|
TEST_CASE(fclose_false_positive); // #9575
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2724,6 +2749,20 @@ private:
|
||||||
ASSERT_EQUALS("", errout.str());
|
ASSERT_EQUALS("", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void deallocuse2() {
|
||||||
|
check("void f(char *p) {\n"
|
||||||
|
" free(p);\n"
|
||||||
|
" strcpy(a, p);\n"
|
||||||
|
"}");
|
||||||
|
TODO_ASSERT_EQUALS("error (free,use)", "", errout.str());
|
||||||
|
|
||||||
|
check("void f(char *p) {\n" // #3041 - assigning pointer when it's used
|
||||||
|
" free(p);\n"
|
||||||
|
" strcpy(a, p=b());\n"
|
||||||
|
"}");
|
||||||
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
}
|
||||||
|
|
||||||
void fclose_false_positive() { // #9575
|
void fclose_false_positive() { // #9575
|
||||||
check("int f(FILE *fp) { return fclose(fp); }");
|
check("int f(FILE *fp) { return fclose(fp); }");
|
||||||
ASSERT_EQUALS("", errout.str());
|
ASSERT_EQUALS("", errout.str());
|
||||||
|
|
Loading…
Reference in New Issue