/* * 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 . */ //--------------------------------------------------------------------------- #include "ctu.h" #include "astutils.h" #include "symboldatabase.h" #include //--------------------------------------------------------------------------- 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 << " \n"; } // Nested calls.. for (const CTU::FileInfo::NestedCall &nestedCall : nestedCalls) { out << " \n"; } return out.str(); } std::string CTU::FileInfo::UnsafeUsage::toString() const { std::ostringstream out; out << " \n"; return out.str(); } std::string CTU::toString(const std::list &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> CTU::FileInfo::getNestedCallsMap() const { std::map> ret; for (const CTU::FileInfo::NestedCall &nc : nestedCalls) ret[nc.id].push_back(nc); return ret; } std::list CTU::loadUnsafeUsageListFromXml(const tinyxml2::XMLElement *xmlElement) { std::list 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 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::getUnsafeUsage(const Tokenizer *tokenizer, const Settings *settings, const Check *check, bool (*isUnsafeUsage)(const Check *check, const Token *argtok)) { std::list 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> &nestedCalls) { if (from.functionId == to.functionId && from.argnr == to.argnr) return true; const std::map>::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 CTU::FileInfo::getErrorPath(InvalidValueType invalidValue, const CTU::FileInfo::UnsafeUsage &unsafeUsage, const std::map> &nestedCallsMap, const char info[], const FunctionCall * * const functionCallPtr) const { std::list 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; }