Uninitialized variables: Whole program analysis for function calls
This commit is contained in:
parent
f027dff5ca
commit
100887429d
|
@ -31,6 +31,8 @@
|
||||||
#include "tokenize.h"
|
#include "tokenize.h"
|
||||||
#include "valueflow.h"
|
#include "valueflow.h"
|
||||||
|
|
||||||
|
#include <tinyxml2.h>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
@ -1280,3 +1282,166 @@ void CheckUninitVar::deadPointerError(const Token *pointer, const Token *alias)
|
||||||
"deadpointer",
|
"deadpointer",
|
||||||
"Dead pointer usage. Pointer '" + strpointer + "' is dead if it has been assigned '" + stralias + "' at line " + MathLib::toString(alias ? alias->linenr() : 0U) + ".", CWE825, false);
|
"Dead pointer usage. Pointer '" + strpointer + "' is dead if it has been assigned '" + stralias + "' at line " + MathLib::toString(alias ? alias->linenr() : 0U) + ".", CWE825, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string CheckUninitVar::MyFileInfo::toString() const
|
||||||
|
{
|
||||||
|
std::ostringstream ret;
|
||||||
|
for (std::list<CheckUninitVar::MyFileInfo::FunctionArg>::const_iterator it = unsafeFunctionArgs.begin(); it != unsafeFunctionArgs.end(); ++it) {
|
||||||
|
ret << " <unsafefunctionarg"
|
||||||
|
<< " functionName=\"" << it->functionName << '\"'
|
||||||
|
<< " argnr=\"" << it->argnr << '\"'
|
||||||
|
<< " fileName=\"" << it->location.fileName << '\"'
|
||||||
|
<< " linenr=\"" << it->location.linenr << '\"'
|
||||||
|
<< "/>\n";
|
||||||
|
}
|
||||||
|
for (std::list<CheckUninitVar::MyFileInfo::FunctionArg>::const_iterator it = uninitializedFunctionArgs.begin(); it != uninitializedFunctionArgs.end(); ++it) {
|
||||||
|
ret << " <uninitializedFunctionArgs"
|
||||||
|
<< " functionName=\"" << it->functionName << '\"'
|
||||||
|
<< " argnr=\"" << it->argnr << '\"'
|
||||||
|
<< " fileName=\"" << it->location.fileName << '\"'
|
||||||
|
<< " linenr=\"" << it->location.linenr << '\"'
|
||||||
|
<< "/>\n";
|
||||||
|
}
|
||||||
|
return ret.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
Check::FileInfo *CheckUninitVar::getFileInfo(const Tokenizer *tokenizer, const Settings * /*settings*/) const
|
||||||
|
{
|
||||||
|
const SymbolDatabase * const symbolDatabase = tokenizer->getSymbolDatabase();
|
||||||
|
std::list<Scope>::const_iterator scope;
|
||||||
|
|
||||||
|
MyFileInfo *fileInfo = new MyFileInfo;
|
||||||
|
|
||||||
|
// Parse all functions in TU
|
||||||
|
for (scope = symbolDatabase->scopeList.begin(); scope != symbolDatabase->scopeList.end(); ++scope) {
|
||||||
|
if (!scope->isExecutable() || scope->type != Scope::eFunction)
|
||||||
|
continue;
|
||||||
|
const Function *const function = scope->function;
|
||||||
|
|
||||||
|
// Unsafe arguments..
|
||||||
|
for (int argnr = 0; argnr < function->argCount(); ++argnr) {
|
||||||
|
const Variable * const argvar = function->getArgumentVar(argnr);
|
||||||
|
if (!argvar->isPointer())
|
||||||
|
continue;
|
||||||
|
for (const Token *tok = scope->classStart; tok != scope->classEnd; tok = tok->next()) {
|
||||||
|
if (tok->variable() != argvar)
|
||||||
|
continue;
|
||||||
|
if (!Token::Match(tok->astParent(), "*|["))
|
||||||
|
break;
|
||||||
|
while (Token::Match(tok->astParent(), "*|["))
|
||||||
|
tok = tok->astParent();
|
||||||
|
if (Token::Match(tok->astParent(),"%cop%"))
|
||||||
|
fileInfo->unsafeFunctionArgs.push_back(MyFileInfo::FunctionArg(scope->className, argnr, tokenizer->list.file(tok), tok->linenr(), argvar->name()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsafe calls..
|
||||||
|
for (const Token *tok = scope->classStart; tok != scope->classEnd; tok = tok->next()) {
|
||||||
|
if (tok->str() != "(" || !tok->astOperand1() || !tok->astOperand2())
|
||||||
|
continue;
|
||||||
|
if (Token::Match(tok->astOperand1(), "if|while|for"))
|
||||||
|
continue;
|
||||||
|
const std::vector<const Token *> args(getArguments(tok->previous()));
|
||||||
|
for (int i = 0; i < args.size(); ++i) {
|
||||||
|
const Token *argtok = args[i];
|
||||||
|
if (!argtok || argtok->str() != "&" || argtok->astOperand2())
|
||||||
|
continue;
|
||||||
|
argtok = argtok->astOperand1();
|
||||||
|
if (!argtok || !argtok->valueType() || argtok->valueType()->pointer != 0)
|
||||||
|
continue;
|
||||||
|
if (argtok->values().size() != 1U)
|
||||||
|
continue;
|
||||||
|
const ValueFlow::Value &v = argtok->values().front();
|
||||||
|
if (v.valueType != ValueFlow::Value::UNINIT || v.isInconclusive())
|
||||||
|
continue;
|
||||||
|
fileInfo->uninitializedFunctionArgs.push_back(MyFileInfo::FunctionArg(tok->astOperand1()->str(), i, tokenizer->list.file(argtok), argtok->linenr(), argtok->str()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
Check::FileInfo * CheckUninitVar::loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement) const
|
||||||
|
{
|
||||||
|
MyFileInfo *fileInfo = new MyFileInfo;
|
||||||
|
for (const tinyxml2::XMLElement *e = xmlElement->FirstChildElement(); e; e = e->NextSiblingElement()) {
|
||||||
|
if (std::strcmp(e->Name(),"unsafefunction")!=0 && std::strcmp(e->Name(),"uninitializedFunctionArgs")!=0)
|
||||||
|
continue;
|
||||||
|
const char *functionName = e->Attribute("functionName");
|
||||||
|
if (!functionName)
|
||||||
|
continue;
|
||||||
|
const char *argnr = e->Attribute("argnr");
|
||||||
|
if (!argnr || !MathLib::isInt(argnr))
|
||||||
|
continue;
|
||||||
|
const char *fileName = e->Attribute("fileName");
|
||||||
|
if (!fileName)
|
||||||
|
continue;
|
||||||
|
const char *linenr = e->Attribute("argnr");
|
||||||
|
if (!linenr || !MathLib::isInt(linenr))
|
||||||
|
continue;
|
||||||
|
const char *variableName = e->Attribute("variableName");
|
||||||
|
if (!variableName)
|
||||||
|
continue;
|
||||||
|
const MyFileInfo::FunctionArg fa(functionName, MathLib::toLongNumber(argnr), fileName, MathLib::toLongNumber(linenr), variableName);
|
||||||
|
if (std::strcmp(e->Name(), "unsafefunction") == 0)
|
||||||
|
fileInfo->unsafeFunctionArgs.push_back(fa);
|
||||||
|
else
|
||||||
|
fileInfo->uninitializedFunctionArgs.push_back(fa);
|
||||||
|
}
|
||||||
|
return fileInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckUninitVar::analyseWholeProgram(const std::list<Check::FileInfo*> &fileInfo, const Settings& settings, ErrorLogger &errorLogger)
|
||||||
|
{
|
||||||
|
(void)settings; // This argument is unused
|
||||||
|
|
||||||
|
// Merge all fileinfo..
|
||||||
|
MyFileInfo all;
|
||||||
|
for (std::list<Check::FileInfo *>::const_iterator it = fileInfo.begin(); it != fileInfo.end(); ++it) {
|
||||||
|
const MyFileInfo *fi = dynamic_cast<MyFileInfo*>(*it);
|
||||||
|
if (!fi)
|
||||||
|
continue;
|
||||||
|
all.unsafeFunctionArgs.insert(all.unsafeFunctionArgs.end(), fi->unsafeFunctionArgs.begin(), fi->unsafeFunctionArgs.end());
|
||||||
|
all.uninitializedFunctionArgs.insert(all.uninitializedFunctionArgs.end(), fi->uninitializedFunctionArgs.begin(), fi->uninitializedFunctionArgs.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool foundErrors = false;
|
||||||
|
|
||||||
|
for (std::list<CheckUninitVar::MyFileInfo::FunctionArg>::const_iterator it1 = all.uninitializedFunctionArgs.begin(); it1 != all.uninitializedFunctionArgs.end(); ++it1) {
|
||||||
|
const CheckUninitVar::MyFileInfo::FunctionArg &uninitializedFunctionArg = *it1;
|
||||||
|
for (std::list<CheckUninitVar::MyFileInfo::FunctionArg>::const_iterator it2 = all.unsafeFunctionArgs.begin(); it2 != all.unsafeFunctionArgs.end(); ++it2) {
|
||||||
|
const CheckUninitVar::MyFileInfo::FunctionArg &unsafeFunctionArg = *it2;
|
||||||
|
if (uninitializedFunctionArg.functionName != unsafeFunctionArg.functionName || uninitializedFunctionArg.argnr != unsafeFunctionArg.argnr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ErrorLogger::ErrorMessage::FileLocation fileLoc1;
|
||||||
|
fileLoc1.setfile(uninitializedFunctionArg.location.fileName);
|
||||||
|
fileLoc1.line = uninitializedFunctionArg.location.linenr;
|
||||||
|
fileLoc1.setinfo("Calling function " + uninitializedFunctionArg.functionName + ", variable " + uninitializedFunctionArg.variableName + " is uninitialized");
|
||||||
|
|
||||||
|
ErrorLogger::ErrorMessage::FileLocation fileLoc2;
|
||||||
|
fileLoc2.setfile(unsafeFunctionArg.location.fileName);
|
||||||
|
fileLoc2.line = unsafeFunctionArg.location.linenr;
|
||||||
|
fileLoc2.setinfo("Using argument " + unsafeFunctionArg.variableName);
|
||||||
|
|
||||||
|
std::list<ErrorLogger::ErrorMessage::FileLocation> locationList;
|
||||||
|
locationList.push_back(fileLoc1);
|
||||||
|
locationList.push_back(fileLoc2);
|
||||||
|
|
||||||
|
const ErrorLogger::ErrorMessage errmsg(locationList,
|
||||||
|
emptyString,
|
||||||
|
Severity::error,
|
||||||
|
"using argument " + unsafeFunctionArg.variableName + " that points at uninitialized variable " + uninitializedFunctionArg.variableName,
|
||||||
|
"uninitvar",
|
||||||
|
CWE908, false);
|
||||||
|
errorLogger.reportErr(errmsg);
|
||||||
|
|
||||||
|
foundErrors = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundErrors;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,13 +91,37 @@ public:
|
||||||
/* data for multifile checking */
|
/* data for multifile checking */
|
||||||
class MyFileInfo : public Check::FileInfo {
|
class MyFileInfo : public Check::FileInfo {
|
||||||
public:
|
public:
|
||||||
/* functions that must have initialized data */
|
struct FunctionArg {
|
||||||
std::set<std::string> uvarFunctions;
|
FunctionArg(const std::string &s, unsigned int i, const std::string &fileName, unsigned int linenr, const std::string &varname) : functionName(s), argnr(i), variableName(varname) {
|
||||||
|
location.fileName = fileName;
|
||||||
|
location.linenr = linenr;
|
||||||
|
}
|
||||||
|
std::string functionName;
|
||||||
|
unsigned int argnr;
|
||||||
|
std::string variableName;
|
||||||
|
struct {
|
||||||
|
std::string fileName;
|
||||||
|
unsigned int linenr;
|
||||||
|
} location;
|
||||||
|
};
|
||||||
|
|
||||||
/* functions calls with uninitialized data */
|
/* function arguments that must be initialized */
|
||||||
std::set<std::string> functionCalls;
|
std::list<FunctionArg> unsafeFunctionArgs;
|
||||||
|
|
||||||
|
/* uninitialized function args */
|
||||||
|
std::list<FunctionArg> uninitializedFunctionArgs;
|
||||||
|
|
||||||
|
std::string toString() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** @brief Parse current TU and extract file info */
|
||||||
|
Check::FileInfo *getFileInfo(const Tokenizer *tokenizer, const Settings *settings) const;
|
||||||
|
|
||||||
|
Check::FileInfo * loadFileInfoFromXml(const tinyxml2::XMLElement *xmlElement) const;
|
||||||
|
|
||||||
|
/** @brief Analyse all file infos for all TU */
|
||||||
|
bool analyseWholeProgram(const std::list<Check::FileInfo*> &fileInfo, const Settings& settings, ErrorLogger &errorLogger);
|
||||||
|
|
||||||
void uninitstringError(const Token *tok, const std::string &varname, bool strncpy_);
|
void uninitstringError(const Token *tok, const std::string &varname, bool strncpy_);
|
||||||
void uninitdataError(const Token *tok, const std::string &varname);
|
void uninitdataError(const Token *tok, const std::string &varname);
|
||||||
void uninitvarError(const Token *tok, const std::string &varname);
|
void uninitvarError(const Token *tok, const std::string &varname);
|
||||||
|
|
|
@ -84,6 +84,9 @@ private:
|
||||||
|
|
||||||
// dead pointer
|
// dead pointer
|
||||||
TEST_CASE(deadPointer);
|
TEST_CASE(deadPointer);
|
||||||
|
|
||||||
|
// whole program analysis
|
||||||
|
TEST_CASE(ctu);
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkUninitVar(const char code[], const char fname[] = "test.cpp", bool debugwarnings = false) {
|
void checkUninitVar(const char code[], const char fname[] = "test.cpp", bool debugwarnings = false) {
|
||||||
|
@ -3969,6 +3972,38 @@ private:
|
||||||
"}");
|
"}");
|
||||||
ASSERT_EQUALS("", errout.str());
|
ASSERT_EQUALS("", errout.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ctu(const char code[]) {
|
||||||
|
// Clear the error buffer..
|
||||||
|
errout.str("");
|
||||||
|
|
||||||
|
// Tokenize..
|
||||||
|
Tokenizer tokenizer(&settings, this);
|
||||||
|
std::istringstream istr(code);
|
||||||
|
tokenizer.tokenize(istr, "test.cpp");
|
||||||
|
tokenizer.simplifyTokenList2();
|
||||||
|
|
||||||
|
// Check code..
|
||||||
|
std::list<Check::FileInfo*> fileInfo;
|
||||||
|
CheckUninitVar check(&tokenizer, &settings, this);
|
||||||
|
fileInfo.push_back(check.getFileInfo(&tokenizer, &settings));
|
||||||
|
check.analyseWholeProgram(fileInfo, settings, *this);
|
||||||
|
while (!fileInfo.empty()) {
|
||||||
|
delete fileInfo.back();
|
||||||
|
fileInfo.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ctu() {
|
||||||
|
ctu("void f(int *p) {\n"
|
||||||
|
" a = *p + 3;\n"
|
||||||
|
"}\n"
|
||||||
|
"int main() {\n"
|
||||||
|
" int x;\n"
|
||||||
|
" f(&x);\n"
|
||||||
|
"}");
|
||||||
|
ASSERT_EQUALS("[test.cpp:6] -> [test.cpp:2]: (error) using argument p that points at uninitialized variable x\n", errout.str());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
REGISTER_TEST(TestUninitVar)
|
REGISTER_TEST(TestUninitVar)
|
||||||
|
|
Loading…
Reference in New Issue