436 lines
17 KiB
C++
436 lines
17 KiB
C++
/*
|
|
* Cppcheck - A tool for static C/C++ code analysis
|
|
* Copyright (C) 2007-2018 Cppcheck team.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
#include "ctu.h"
|
|
#include "astutils.h"
|
|
#include "symboldatabase.h"
|
|
#include <tinyxml2.h>
|
|
//---------------------------------------------------------------------------
|
|
|
|
std::string CTU::getFunctionId(const Tokenizer *tokenizer, const Function *function)
|
|
{
|
|
return tokenizer->list.file(function->tokenDef) + ':' + MathLib::toString(function->tokenDef->linenr());
|
|
}
|
|
|
|
CTU::FileInfo::Location::Location(const Tokenizer *tokenizer, const Token *tok)
|
|
{
|
|
fileName = tokenizer->list.file(tok);
|
|
linenr = tok->linenr();
|
|
}
|
|
|
|
std::string CTU::FileInfo::toString() const
|
|
{
|
|
std::ostringstream out;
|
|
|
|
// Function calls..
|
|
for (const CTU::FileInfo::FunctionCall &functionCall : functionCalls) {
|
|
out << " <function-call"
|
|
<< " id=\"" << functionCall.functionId << '\"'
|
|
<< " functionName=\"" << functionCall.functionName << '\"'
|
|
<< " argnr=\"" << functionCall.argnr << '\"'
|
|
<< " argExpr=\"" << functionCall.argumentExpression << '\"'
|
|
<< " argvalue=\"" << functionCall.argvalue << '\"'
|
|
<< " valueType=\"" << functionCall.valueType << '\"'
|
|
<< " fileName=\"" << functionCall.location.fileName << '\"'
|
|
<< " linenr=\"" << functionCall.location.linenr << '\"'
|
|
<< "/>\n";
|
|
}
|
|
|
|
// Nested calls..
|
|
for (const CTU::FileInfo::NestedCall &nestedCall : nestedCalls) {
|
|
out << " <nested-call"
|
|
<< " id=\"" << nestedCall.id << '\"'
|
|
<< " id2=\"" << nestedCall.id2 << '\"'
|
|
<< " functionName=\"" << nestedCall.functionName << '\"'
|
|
<< " argnr=\"" << nestedCall.argnr << '\"'
|
|
<< " argnr2=\"" << nestedCall.argnr2 << '\"'
|
|
<< " fileName=\"" << nestedCall.location.fileName << '\"'
|
|
<< " linenr=\"" << nestedCall.location.linenr << '\"'
|
|
<< "/>\n";
|
|
}
|
|
|
|
return out.str();
|
|
}
|
|
|
|
std::string CTU::FileInfo::UnsafeUsage::toString() const
|
|
{
|
|
std::ostringstream out;
|
|
out << " <unsafe-usage"
|
|
<< " id=\"" << functionId << '\"'
|
|
<< " argnr=\"" << argnr << '\"'
|
|
<< " argname=\"" << argumentName << '\"'
|
|
<< " fileName=\"" << location.fileName << '\"'
|
|
<< " linenr=\"" << location.linenr << '\"'
|
|
<< "/>\n";
|
|
return out.str();
|
|
}
|
|
|
|
std::string CTU::toString(const std::list<CTU::FileInfo::UnsafeUsage> &unsafeUsage)
|
|
{
|
|
std::ostringstream ret;
|
|
for (const CTU::FileInfo::UnsafeUsage &u : unsafeUsage)
|
|
ret << u.toString();
|
|
return ret.str();
|
|
}
|
|
|
|
CTU::FileInfo::NestedCall::NestedCall(const Tokenizer *tokenizer, const Scope *scope, unsigned int argnr_, const Token *tok)
|
|
:
|
|
id(getFunctionId(tokenizer, scope->function)),
|
|
functionName(scope->className),
|
|
argnr(argnr_),
|
|
argnr2(0),
|
|
location(CTU::FileInfo::Location(tokenizer, tok))
|
|
{
|
|
}
|
|
|
|
void CTU::FileInfo::loadFromXml(const tinyxml2::XMLElement *xmlElement)
|
|
{
|
|
for (const tinyxml2::XMLElement *e = xmlElement->FirstChildElement(); e; e = e->NextSiblingElement()) {
|
|
const char *id = e->Attribute("id");
|
|
if (!id)
|
|
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("linenr");
|
|
if (!linenr || !MathLib::isInt(linenr))
|
|
continue;
|
|
|
|
if (std::strcmp(e->Name(), "function-call") == 0) {
|
|
FunctionCall functionCall;
|
|
functionCall.functionId = id;
|
|
functionCall.functionName = functionName;
|
|
functionCall.argnr = std::atoi(argnr);
|
|
const char *argExpr = e->Attribute("argExpr");
|
|
if (!argExpr)
|
|
continue;
|
|
functionCall.argumentExpression =
|
|
functionCall.valueType = (ValueFlow::Value::ValueType)std::atoi(e->Attribute("valueType"));
|
|
functionCall.argvalue = MathLib::toLongNumber(e->Attribute("argvalue"));
|
|
functionCall.location.fileName = fileName;
|
|
functionCall.location.linenr = std::atoi(linenr);
|
|
functionCalls.push_back(functionCall);
|
|
} else if (std::strcmp(e->Name(), "nested-call") == 0) {
|
|
NestedCall nestedCall;
|
|
nestedCall.functionName = functionName;
|
|
nestedCall.id = id;
|
|
const char *id2 = e->Attribute("id2");
|
|
if (!id2)
|
|
continue;
|
|
nestedCall.id2 = id2;
|
|
nestedCall.argnr = std::atoi(argnr);
|
|
const char *argnr2 = e->Attribute("argnr2");
|
|
if (!argnr2 || !MathLib::isInt(argnr2))
|
|
continue;
|
|
nestedCall.argnr2 = std::atoi(argnr2);
|
|
nestedCall.location.fileName = fileName;
|
|
nestedCall.location.linenr = std::atoi(linenr);
|
|
nestedCalls.push_back(nestedCall);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::map<std::string, std::list<CTU::FileInfo::NestedCall>> CTU::FileInfo::getNestedCallsMap() const
|
|
{
|
|
std::map<std::string, std::list<CTU::FileInfo::NestedCall>> ret;
|
|
for (const CTU::FileInfo::NestedCall &nc : nestedCalls)
|
|
ret[nc.id].push_back(nc);
|
|
return ret;
|
|
}
|
|
|
|
std::list<CTU::FileInfo::UnsafeUsage> CTU::loadUnsafeUsageListFromXml(const tinyxml2::XMLElement *xmlElement)
|
|
{
|
|
std::list<CTU::FileInfo::UnsafeUsage> ret;
|
|
for (const tinyxml2::XMLElement *e = xmlElement->FirstChildElement(); e; e = e->NextSiblingElement()) {
|
|
if (std::strcmp(e->Name(), "unsafe-usage") != 0)
|
|
continue;
|
|
const char *id = e->Attribute("id");
|
|
if (!id)
|
|
continue;
|
|
const char *argnr = e->Attribute("argnr");
|
|
if (!argnr || !MathLib::isInt(argnr))
|
|
continue;
|
|
const char *argname = e->Attribute("argname");
|
|
if (!argname)
|
|
continue;
|
|
const char *fileName = e->Attribute("fileName");
|
|
if (!fileName)
|
|
continue;
|
|
const char *linenr = e->Attribute("linenr");
|
|
if (!linenr || !MathLib::isInt(linenr))
|
|
continue;
|
|
ret.push_back(FileInfo::UnsafeUsage(id, std::atoi(argnr), argname, FileInfo::Location(fileName, std::atoi(linenr))));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int isCallFunction(const Scope *scope, int argnr, const Token **tok)
|
|
{
|
|
const Variable * const argvar = scope->function->getArgumentVar(argnr);
|
|
if (!argvar->isPointer())
|
|
return -1;
|
|
for (const Token *tok2 = scope->bodyStart; tok2 != scope->bodyEnd; tok2 = tok2->next()) {
|
|
if (tok2->variable() != argvar)
|
|
continue;
|
|
if (!Token::Match(tok2->previous(), "[(,] %var% [,)]"))
|
|
break;
|
|
int argnr2 = 1;
|
|
const Token *prev = tok2;
|
|
while (prev && prev->str() != "(") {
|
|
if (Token::Match(prev,"]|)"))
|
|
prev = prev->link();
|
|
else if (prev->str() == ",")
|
|
++argnr2;
|
|
prev = prev->previous();
|
|
}
|
|
if (!prev || !Token::Match(prev->previous(), "%name% ("))
|
|
break;
|
|
if (!prev->astOperand1() || !prev->astOperand1()->function())
|
|
break;
|
|
*tok = prev->previous();
|
|
return argnr2;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
CTU::FileInfo *CTU::getFileInfo(const Tokenizer *tokenizer)
|
|
{
|
|
const SymbolDatabase * const symbolDatabase = tokenizer->getSymbolDatabase();
|
|
|
|
FileInfo *fileInfo = new FileInfo;
|
|
|
|
// Parse all functions in TU
|
|
for (const Scope &scope : symbolDatabase->scopeList) {
|
|
if (!scope.isExecutable() || scope.type != Scope::eFunction || !scope.function)
|
|
continue;
|
|
const Function *const function = scope.function;
|
|
|
|
// source function calls
|
|
for (const Token *tok = scope.bodyStart; tok != scope.bodyEnd; tok = tok->next()) {
|
|
if (tok->str() != "(" || !tok->astOperand1() || !tok->astOperand2())
|
|
continue;
|
|
if (!tok->astOperand1()->function())
|
|
continue;
|
|
const std::vector<const Token *> args(getArguments(tok->previous()));
|
|
for (int argnr = 0; argnr < args.size(); ++argnr) {
|
|
const Token *argtok = args[argnr];
|
|
if (!argtok)
|
|
continue;
|
|
if (argtok->hasKnownIntValue()) {
|
|
struct FileInfo::FunctionCall functionCall;
|
|
functionCall.valueType = ValueFlow::Value::INT;
|
|
functionCall.functionId = getFunctionId(tokenizer, tok->astOperand1()->function());
|
|
functionCall.functionName = tok->astOperand1()->expressionString();
|
|
functionCall.location.fileName = tokenizer->list.file(tok);
|
|
functionCall.location.linenr = tok->linenr();
|
|
functionCall.argnr = argnr + 1;
|
|
functionCall.argumentExpression = argtok->expressionString();
|
|
functionCall.argvalue = argtok->values().front().intvalue;
|
|
fileInfo->functionCalls.push_back(functionCall);
|
|
continue;
|
|
}
|
|
// pointer to uninitialized data..
|
|
if (!argtok->isUnaryOp("&"))
|
|
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()) {
|
|
struct FileInfo::FunctionCall functionCall;
|
|
functionCall.valueType = ValueFlow::Value::UNINIT;
|
|
functionCall.functionId = getFunctionId(tokenizer, tok->astOperand1()->function());
|
|
functionCall.functionName = tok->astOperand1()->expressionString();
|
|
functionCall.location.fileName = tokenizer->list.file(tok);
|
|
functionCall.location.linenr = tok->linenr();
|
|
functionCall.argnr = argnr + 1;
|
|
functionCall.argvalue = 0;
|
|
functionCall.argumentExpression = argtok->expressionString();
|
|
fileInfo->functionCalls.push_back(functionCall);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Nested function calls
|
|
for (int argnr = 0; argnr < function->argCount(); ++argnr) {
|
|
const Token *tok;
|
|
int argnr2 = isCallFunction(&scope, argnr, &tok);
|
|
if (argnr2 > 0) {
|
|
FileInfo::NestedCall nestedCall(tokenizer, &scope, argnr+1, tok);
|
|
nestedCall.id = getFunctionId(tokenizer, function);
|
|
nestedCall.id2 = getFunctionId(tokenizer, tok->function());
|
|
nestedCall.argnr2 = argnr2;
|
|
fileInfo->nestedCalls.push_back(nestedCall);
|
|
}
|
|
}
|
|
}
|
|
|
|
return fileInfo;
|
|
}
|
|
|
|
static bool isUnsafeFunction(const Tokenizer *tokenizer, const Settings *settings, const Scope *scope, int argnr, const Token **tok, const Check *check, bool (*isUnsafeUsage)(const Check *check, const Token *argtok))
|
|
{
|
|
const Variable * const argvar = scope->function->getArgumentVar(argnr);
|
|
if (!argvar->isPointer())
|
|
return false;
|
|
for (const Token *tok2 = scope->bodyStart; tok2 != scope->bodyEnd; tok2 = tok2->next()) {
|
|
if (Token::simpleMatch(tok2, ") {")) {
|
|
tok2 = tok2->linkAt(1);
|
|
if (Token::findmatch(tok2->link(), "return|throw", tok2))
|
|
return false;
|
|
if (isVariableChanged(tok2->link(), tok2, argvar->declarationId(), false, settings, tokenizer->isCPP()))
|
|
return false;
|
|
}
|
|
if (tok2->variable() != argvar)
|
|
continue;
|
|
if (!isUnsafeUsage(check, tok2))
|
|
return false;
|
|
*tok = tok2;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::list<CTU::FileInfo::UnsafeUsage> CTU::getUnsafeUsage(const Tokenizer *tokenizer, const Settings *settings, const Check *check, bool (*isUnsafeUsage)(const Check *check, const Token *argtok))
|
|
{
|
|
std::list<CTU::FileInfo::UnsafeUsage> unsafeUsage;
|
|
|
|
// Parse all functions in TU
|
|
const SymbolDatabase * const symbolDatabase = tokenizer->getSymbolDatabase();
|
|
|
|
for (const Scope &scope : symbolDatabase->scopeList) {
|
|
if (!scope.isExecutable() || scope.type != Scope::eFunction || !scope.function)
|
|
continue;
|
|
const Function *const function = scope.function;
|
|
|
|
// "Unsafe" functions unconditionally reads data before it is written..
|
|
for (int argnr = 0; argnr < function->argCount(); ++argnr) {
|
|
const Token *tok;
|
|
if (isUnsafeFunction(tokenizer, settings, &scope, argnr, &tok, check, isUnsafeUsage))
|
|
unsafeUsage.push_back(CTU::FileInfo::UnsafeUsage(CTU::getFunctionId(tokenizer, function), argnr+1, tok->str(), CTU::FileInfo::Location(tokenizer,tok)));
|
|
}
|
|
}
|
|
|
|
return unsafeUsage;
|
|
}
|
|
|
|
static bool findPath(const CTU::FileInfo::FunctionCall &from,
|
|
const CTU::FileInfo::UnsafeUsage &to,
|
|
const std::map<std::string, std::list<CTU::FileInfo::NestedCall>> &nestedCalls)
|
|
{
|
|
if (from.functionId == to.functionId && from.argnr == to.argnr)
|
|
return true;
|
|
|
|
const std::map<std::string, std::list<CTU::FileInfo::NestedCall>>::const_iterator nc = nestedCalls.find(from.functionId);
|
|
if (nc == nestedCalls.end())
|
|
return false;
|
|
|
|
for (const CTU::FileInfo::NestedCall &nestedCall : nc->second) {
|
|
if (from.functionId == nestedCall.id && from.argnr == nestedCall.argnr && nestedCall.id2 == to.functionId && nestedCall.argnr2 == to.argnr)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static std::string replacestr(std::string s, const std::string &from, const std::string &to)
|
|
{
|
|
std::string::size_type pos1 = 0;
|
|
while (pos1 < s.size()) {
|
|
pos1 = s.find(from, pos1);
|
|
if (pos1 == std::string::npos)
|
|
return s;
|
|
if (pos1 > 0 && (s[pos1-1] == '_' || std::isalnum(s[pos1-1]))) {
|
|
pos1++;
|
|
continue;
|
|
}
|
|
const std::string::size_type pos2 = pos1 + from.size();
|
|
if (pos2 >= s.size())
|
|
return s.substr(0,pos1) + to;
|
|
if (s[pos2] == '_' || std::isalnum(s[pos2])) {
|
|
pos1++;
|
|
continue;
|
|
}
|
|
s = s.substr(0,pos1) + to + s.substr(pos2);
|
|
pos1 += to.size();
|
|
}
|
|
return s;
|
|
}
|
|
|
|
std::list<ErrorLogger::ErrorMessage::FileLocation> CTU::FileInfo::getErrorPath(InvalidValueType invalidValue,
|
|
const CTU::FileInfo::UnsafeUsage &unsafeUsage,
|
|
const std::map<std::string, std::list<CTU::FileInfo::NestedCall>> &nestedCallsMap,
|
|
const char info[],
|
|
const FunctionCall * * const functionCallPtr) const
|
|
{
|
|
std::list<ErrorLogger::ErrorMessage::FileLocation> locationList;
|
|
|
|
for (const FunctionCall &functionCall : functionCalls) {
|
|
|
|
if (invalidValue == CTU::FileInfo::InvalidValueType::null &&
|
|
(functionCall.valueType != ValueFlow::Value::ValueType::INT || functionCall.argvalue != 0)) {
|
|
continue;
|
|
}
|
|
if (invalidValue == CTU::FileInfo::InvalidValueType::uninit &&
|
|
functionCall.valueType != ValueFlow::Value::ValueType::UNINIT) {
|
|
continue;
|
|
}
|
|
|
|
if (!findPath(functionCall, unsafeUsage, nestedCallsMap))
|
|
continue;
|
|
|
|
if (functionCallPtr)
|
|
*functionCallPtr = &functionCall;
|
|
|
|
std::string value1;
|
|
if (functionCall.valueType == ValueFlow::Value::ValueType::INT)
|
|
value1 = "null";
|
|
else if (functionCall.valueType == ValueFlow::Value::ValueType::UNINIT)
|
|
value1 = "uninitialized";
|
|
|
|
ErrorLogger::ErrorMessage::FileLocation fileLoc1;
|
|
fileLoc1.setfile(functionCall.location.fileName);
|
|
fileLoc1.line = functionCall.location.linenr;
|
|
fileLoc1.setinfo("Calling function " + functionCall.functionName + ", " + MathLib::toString(functionCall.argnr) + getOrdinalText(functionCall.argnr) + " argument is " + value1);
|
|
|
|
ErrorLogger::ErrorMessage::FileLocation fileLoc2;
|
|
fileLoc2.setfile(unsafeUsage.location.fileName);
|
|
fileLoc2.line = unsafeUsage.location.linenr;
|
|
fileLoc2.setinfo(replacestr(info, "ARG", unsafeUsage.argumentName));
|
|
|
|
locationList.push_back(fileLoc1);
|
|
locationList.push_back(fileLoc2);
|
|
|
|
return locationList;
|
|
}
|
|
|
|
return locationList;
|
|
}
|