From 2d651b09fce16324856a165fe8c62b0736c30c04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 17 Sep 2019 21:00:59 +0200 Subject: [PATCH] ExprEngine: Add new experimental path-sensitive data flow analysis. Initially used for 'verification' but could possibly later be used as a complement in the normal analysis. The code is work-in-progress and hacky! --- Makefile | 8 + cli/cmdlineparser.cpp | 4 + lib/cppcheck.cpp | 7 + lib/exprengine.cpp | 554 ++++++++++++++++++++++++++++++++++++++++ lib/exprengine.h | 213 +++++++++++++++ lib/lib.pri | 2 + lib/settings.cpp | 1 + lib/settings.h | 3 + test/testexprengine.cpp | 143 +++++++++++ 9 files changed, 935 insertions(+) create mode 100644 lib/exprengine.cpp create mode 100644 lib/exprengine.h create mode 100644 test/testexprengine.cpp diff --git a/Makefile b/Makefile index 4ffc003fb..b27e83020 100644 --- a/Makefile +++ b/Makefile @@ -171,6 +171,7 @@ LIBOBJ = $(libcppdir)/analyzerinfo.o \ $(libcppdir)/cppcheck.o \ $(libcppdir)/ctu.o \ $(libcppdir)/errorlogger.o \ + $(libcppdir)/exprengine.o \ $(libcppdir)/importproject.o \ $(libcppdir)/library.o \ $(libcppdir)/mathlib.o \ @@ -213,6 +214,7 @@ TESTOBJ = test/options.o \ test/testcppcheck.o \ test/testerrorlogger.o \ test/testexceptionsafety.o \ + test/testexprengine.o \ test/testfilelister.o \ test/testfunctions.o \ test/testgarbage.o \ @@ -463,6 +465,9 @@ $(libcppdir)/ctu.o: lib/ctu.cpp lib/ctu.h lib/check.h lib/config.h lib/errorlogg $(libcppdir)/errorlogger.o: lib/errorlogger.cpp lib/errorlogger.h lib/config.h lib/suppressions.h lib/cppcheck.h lib/analyzerinfo.h lib/importproject.h lib/platform.h lib/utils.h lib/check.h lib/settings.h lib/library.h lib/mathlib.h lib/standards.h lib/timer.h lib/token.h lib/valueflow.h lib/templatesimplifier.h lib/tokenize.h lib/tokenlist.h lib/path.h externals/tinyxml/tinyxml2.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/errorlogger.o $(libcppdir)/errorlogger.cpp +$(libcppdir)/exprengine.o: lib/exprengine.cpp lib/exprengine.h lib/mathlib.h lib/config.h lib/settings.h lib/errorlogger.h lib/suppressions.h lib/importproject.h lib/platform.h lib/utils.h lib/library.h lib/standards.h lib/timer.h lib/symboldatabase.h lib/token.h lib/valueflow.h lib/templatesimplifier.h lib/tokenize.h lib/tokenlist.h + $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/exprengine.o $(libcppdir)/exprengine.cpp + $(libcppdir)/importproject.o: lib/importproject.cpp lib/importproject.h lib/config.h lib/platform.h lib/utils.h lib/path.h lib/settings.h lib/errorlogger.h lib/suppressions.h lib/library.h lib/mathlib.h lib/standards.h lib/timer.h externals/tinyxml/tinyxml2.h lib/token.h lib/valueflow.h lib/templatesimplifier.h lib/tokenize.h lib/tokenlist.h externals/picojson.h $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/importproject.o $(libcppdir)/importproject.cpp @@ -574,6 +579,9 @@ test/testerrorlogger.o: test/testerrorlogger.cpp lib/config.h lib/cppcheck.h lib test/testexceptionsafety.o: test/testexceptionsafety.cpp lib/checkexceptionsafety.h lib/check.h lib/config.h lib/errorlogger.h lib/suppressions.h lib/settings.h lib/importproject.h lib/platform.h lib/utils.h lib/library.h lib/mathlib.h lib/standards.h lib/timer.h lib/token.h lib/valueflow.h lib/templatesimplifier.h lib/tokenize.h lib/tokenlist.h test/testsuite.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testexceptionsafety.o test/testexceptionsafety.cpp +test/testexprengine.o: test/testexprengine.cpp lib/exprengine.h lib/mathlib.h lib/config.h lib/settings.h lib/errorlogger.h lib/suppressions.h lib/importproject.h lib/platform.h lib/utils.h lib/library.h lib/standards.h lib/timer.h lib/symboldatabase.h lib/token.h lib/valueflow.h lib/templatesimplifier.h lib/tokenize.h lib/tokenlist.h test/testsuite.h + $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testexprengine.o test/testexprengine.cpp + test/testfilelister.o: test/testfilelister.cpp cli/filelister.h lib/pathmatch.h lib/config.h test/testsuite.h lib/errorlogger.h lib/suppressions.h $(CXX) ${INCLUDE_FOR_TEST} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o test/testfilelister.o test/testfilelister.cpp diff --git a/cli/cmdlineparser.cpp b/cli/cmdlineparser.cpp index 56f0bcbc0..be88d9781 100644 --- a/cli/cmdlineparser.cpp +++ b/cli/cmdlineparser.cpp @@ -189,6 +189,10 @@ bool CmdLineParser::parseFromArgs(int argc, const char* const argv[]) else if (std::strcmp(argv[i], "--safe-functions") == 0) mSettings->safeChecks.externalFunctions = mSettings->safeChecks.internalFunctions = true; + // Experimental: Verify + else if (std::strcmp(argv[i], "--verify") == 0) + mSettings->verification = true; + // Enforce language (--language=, -x) else if (std::strncmp(argv[i], "--language=", 11) == 0 || std::strcmp(argv[i], "-x") == 0) { std::string str; diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 49c7ba497..55023ac14 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -32,6 +32,8 @@ #include "tokenlist.h" #include "version.h" +#include "exprengine.h" + #define PICOJSON_USE_INT64 #include #include @@ -725,6 +727,11 @@ void CppCheck::checkNormalTokens(const Tokenizer &tokenizer) check->runChecks(&tokenizer, &mSettings, this); } + // Verification using ExprEngine.. + if (mSettings.verification) { + ExprEngine::runChecks(this, &tokenizer, &mSettings); + } + // Analyse the tokens.. CTU::FileInfo *fi1 = CTU::getFileInfo(&tokenizer); diff --git a/lib/exprengine.cpp b/lib/exprengine.cpp new file mode 100644 index 000000000..5f693d9fd --- /dev/null +++ b/lib/exprengine.cpp @@ -0,0 +1,554 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2019 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 "exprengine.h" +#include "settings.h" +#include "symboldatabase.h" +#include "tokenize.h" + +#include +#include + +std::string ExprEngine::str(int128_t value) +{ + std::ostringstream ostr; + if (value == (int)value) { + ostr << (int) value; + return ostr.str(); + } + if (value < 0) { + ostr << "-"; + value = -value; + } + + uint64_t high = value >> 64; + uint64_t low = value; + if (high > 0) + ostr << "h" << std::hex << high << "l"; + ostr << std::hex << low; + return ostr.str(); +} + +static ExprEngine::ValuePtr getValueRangeFromValueType(const std::string &name, const ValueType *vt, const cppcheck::Platform &platform); + +namespace { + class Data { + public: + Data(int *symbolValueIndex, const Tokenizer *tokenizer, const Settings *settings, const std::vector &callbacks) + : symbolValueIndex(symbolValueIndex) + , tokenizer(tokenizer) + , settings(settings) + , callbacks(callbacks) {} + typedef std::map> Memory; + Memory memory; + int *symbolValueIndex; + const Tokenizer *tokenizer; + const Settings *settings; + const std::vector &callbacks; + + void printMemory() const { + const SymbolDatabase *symbolDatabase = tokenizer->getSymbolDatabase(); + for (Memory::const_iterator mem = memory.cbegin(); mem != memory.cend(); ++mem) { + std::cout << symbolDatabase->getVariableFromVarId(mem->first)->name() << " name:" << mem->second->name << " range:" << mem->second->getRange() << "\n"; + } + } + + Data getData(const Token *cond, bool trueData) { + Data ret(symbolValueIndex, tokenizer, settings, callbacks); + for (Memory::const_iterator mem = memory.cbegin(); mem != memory.cend(); ++mem) { + ret.memory[mem->first] = mem->second; + + if (cond->isComparisonOp() && cond->astOperand1()->varId() == mem->first && cond->astOperand2()->isNumber()) { + const int128_t rhsValue = MathLib::toLongNumber(cond->astOperand2()->str()); + if (auto intRange = std::dynamic_pointer_cast(mem->second)) { + if (cond->str() == ">") { + if (trueData && intRange->minValue <= rhsValue) + ret.memory[mem->first] = std::make_shared(getNewSymbolName(), rhsValue + 1, intRange->maxValue); + else if (!trueData && intRange->maxValue > rhsValue) + ret.memory[mem->first] = std::make_shared(getNewSymbolName(), intRange->minValue, rhsValue); + } + } + } + } + return ret; + } + + std::string getNewSymbolName() { + return "$" + std::to_string(++(*symbolValueIndex)); + } + + std::shared_ptr getArrayValue(const Token *tok) { + const Memory::iterator it = memory.find(tok->varId()); + if (it != memory.end()) + return std::dynamic_pointer_cast(it->second); + return std::shared_ptr(); + } + + ExprEngine::ValuePtr getValue(unsigned int varId, const ValueType *valueType) { + const Memory::const_iterator it = memory.find(varId); + if (it != memory.end()) + return it->second; + if (!valueType) + return ExprEngine::ValuePtr(); + ExprEngine::ValuePtr value = getValueRangeFromValueType(getNewSymbolName(), valueType, *settings); + if (value) + memory[varId] = value; + return value; + } + + std::string __attribute__((noinline)) dump() const { + std::ostringstream ret; + for (Memory::const_iterator mem = memory.cbegin(); mem != memory.cend(); ++mem) { + ret << mem->first << "=" << mem->second->name << " " << mem->second->getRange() << "\n"; + } + return ret.str(); + } + }; +} + +void ExprEngine::ArrayValue::assign(ExprEngine::ValuePtr index, ExprEngine::ValuePtr value) +{ + auto i1 = std::dynamic_pointer_cast(index); + if (i1) { + if (i1->minValue == i1->maxValue && i1->minValue >= 0 && i1->maxValue < data.size()) + data[i1->minValue] = value; + } +} + +ExprEngine::ValuePtr ExprEngine::ArrayValue::read(ExprEngine::ValuePtr index) +{ + auto i1 = std::dynamic_pointer_cast(index); + if (i1) { + if (i1->minValue == i1->maxValue && i1->minValue >= 0 && i1->maxValue < data.size()) + return data[i1->minValue]; + } + return ExprEngine::ValuePtr(); +} + +std::string ExprEngine::BinOpResult::getRange() const +{ + int128_t minValue, maxValue; + getRange(&minValue, &maxValue); + return "[" + str(minValue) + ":" + str(maxValue) + "]"; +} + +void ExprEngine::BinOpResult::getRange(int128_t *minValue, int128_t *maxValue) const +{ + std::map valueBit; + // Assign a bit number for each leaf + int bit = 0; + for (ValuePtr v : mLeafs) { + if (auto intRange = std::dynamic_pointer_cast(v)) { + if (intRange->minValue == intRange->maxValue) { + valueBit[v] = 30; + continue; + } + } + + valueBit[v] = bit++; + } + + if (bit > 24) + throw std::runtime_error("Internal error: bits"); + + for (int test = 0; test < (1 << bit); ++test) { + int128_t result = evaluate(test, valueBit); + if (test == 0) + *minValue = *maxValue = result; + else if (result < *minValue) + *minValue = result; + else if (result > *maxValue) + *maxValue = result; + } +} + +bool ExprEngine::BinOpResult::isIntValueInRange(int value) const +{ + int128_t minValue, maxValue; + getRange(&minValue, &maxValue); + return value >= minValue && value <= maxValue; +} + +int128_t ExprEngine::BinOpResult::evaluate(int test, const std::map &valueBit) const +{ + const int128_t lhs = evaluateOperand(test, valueBit, op1); + const int128_t rhs = evaluateOperand(test, valueBit, op2); + if (binop == "+") + return lhs + rhs; + if (binop == "-") + return lhs - rhs; + if (binop == "*") + return lhs * rhs; + if (binop == "/" && rhs != 0) + return lhs / rhs; + if (binop == "%" && rhs != 0) + return lhs % rhs; + if (binop == "&") + return lhs & rhs; + if (binop == "|") + return lhs | rhs; + if (binop == "^") + return lhs ^ rhs; + if (binop == "<<") + return lhs << rhs; + if (binop == ">>") + return lhs >> rhs; + throw std::runtime_error("Internal error: Unhandled operator;" + binop); +} + +int128_t ExprEngine::BinOpResult::evaluateOperand(int test, const std::map &valueBit, ExprEngine::ValuePtr value) const +{ + auto binOpResult = std::dynamic_pointer_cast(value); + if (binOpResult) + return binOpResult->evaluate(test, valueBit); + + auto it = valueBit.find(value); + if (it == valueBit.end()) + throw std::runtime_error("Internal error: valueBit not set properly"); + + bool valueType = test & (1 << it->second); + if (auto intRange = std::dynamic_pointer_cast(value)) + return valueType ? intRange->minValue : intRange->maxValue; + throw std::runtime_error("Internal error: Unhandled value:" + std::to_string((int)value->type())); +} + +// Todo: This is taken from ValueFlow and modified.. we should reuse it +static ExprEngine::ValuePtr getValueRangeFromValueType(const std::string &name, const ValueType *vt, const cppcheck::Platform &platform) +{ + if (!vt || !vt->isIntegral() || vt->pointer) + return ExprEngine::ValuePtr(); + + int bits; + switch (vt->type) { + case ValueType::Type::BOOL: + bits = 1; + break; + case ValueType::Type::CHAR: + bits = platform.char_bit; + break; + case ValueType::Type::SHORT: + bits = platform.short_bit; + break; + case ValueType::Type::INT: + bits = platform.int_bit; + break; + case ValueType::Type::LONG: + bits = platform.long_bit; + break; + case ValueType::Type::LONGLONG: + bits = platform.long_long_bit; + break; + default: + return ExprEngine::ValuePtr(); + }; + + if (bits == 1) { + return std::make_shared(name, 0, 1); + } else { + if (vt->sign == ValueType::Sign::UNSIGNED) { + return std::make_shared(name, 0, ((int128_t)1 << bits) - 1); + } else { + return std::make_shared(name, -((int128_t)1 << (bits - 1)), ((int128_t)1 << (bits - 1)) - 1); + } + } + + return ExprEngine::ValuePtr(); +} + +static void call(const std::vector &callbacks, const Token *tok, ExprEngine::ValuePtr value) +{ + if (value) { + for (ExprEngine::Callback f : callbacks) { + f(tok, *value); + } + } +} + +static ExprEngine::ValuePtr executeExpression(const Token *tok, Data &data); + +static ExprEngine::ValuePtr executeReturn(const Token *tok, Data &data) +{ + ExprEngine::ValuePtr retval = executeExpression(tok->astOperand1(), data); + call(data.callbacks, tok, retval); + return retval; +} + +static ExprEngine::ValuePtr executeAssign(const Token *tok, Data &data) +{ + ExprEngine::ValuePtr rhsValue = executeExpression(tok->astOperand2(), data); + call(data.callbacks, tok, rhsValue); + + const Token *lhsToken = tok->astOperand1(); + if (lhsToken->varId() > 0) { + data.memory[lhsToken->varId()] = rhsValue; + } else if (lhsToken->str() == "[") { + auto arrayValue = data.getArrayValue(lhsToken->astOperand1()); + if (arrayValue) { + auto indexValue = executeExpression(lhsToken->astOperand2(), data); + arrayValue->assign(indexValue, rhsValue); + } + } else if (lhsToken->isUnaryOp("*")) { + auto pval = executeExpression(lhsToken->astOperand1(), data); + if (pval && pval->type() == ExprEngine::ValueType::AddressOfValue) { + auto val = std::dynamic_pointer_cast(pval); + if (val) + data.memory[val->varId] = rhsValue; + } + } + return rhsValue; +} + +static ExprEngine::ValuePtr executeFunctionCall(const Token *tok, Data &data) +{ + (void)executeExpression(tok->astOperand2(), data); + auto val = getValueRangeFromValueType(data.getNewSymbolName(), tok->valueType(), *data.settings); + call(data.callbacks, tok, val); + return val; +} + +static ExprEngine::ValuePtr executeArrayIndex(const Token *tok, Data &data) +{ + auto arrayValue = data.getArrayValue(tok->astOperand1()); + if (arrayValue) { + auto indexValue = executeExpression(tok->astOperand2(), data); + auto value = arrayValue->read(indexValue); + call(data.callbacks, tok, value); + return value; + } + return ExprEngine::ValuePtr(); +} + +static ExprEngine::ValuePtr executeDot(const Token *tok, Data &data) +{ + if (!tok->astOperand1() || !tok->astOperand1()->varId()) + return ExprEngine::ValuePtr(); + std::shared_ptr structValue = std::dynamic_pointer_cast(data.getValue(tok->astOperand1()->varId(), nullptr)); + if (!structValue) + return ExprEngine::ValuePtr(); + return structValue->getValueOfMember(tok->astOperand2()->str()); +} + +static ExprEngine::ValuePtr executeBinaryOp(const Token *tok, Data &data) +{ + ExprEngine::ValuePtr v1 = executeExpression(tok->astOperand1(), data); + ExprEngine::ValuePtr v2 = executeExpression(tok->astOperand2(), data); + if (v1 && v2) { + auto result = std::make_shared(tok->str(), v1, v2); + call(data.callbacks, tok, result); + return result; + } + return ExprEngine::ValuePtr(); +} + +static ExprEngine::ValuePtr executeAddressOf(const Token *tok, Data &data) +{ + auto addr = std::make_shared(data.getNewSymbolName(), tok->astOperand1()->varId()); + call(data.callbacks, tok, addr); + return addr; +} + +static ExprEngine::ValuePtr executeDeref(const Token *tok, Data &data) +{ + ExprEngine::ValuePtr pval = executeExpression(tok->astOperand1(), data); + if (pval) { + auto addressOf = std::dynamic_pointer_cast(pval); + if (addressOf) { + auto val = data.getValue(addressOf->varId, tok->valueType()); + call(data.callbacks, tok, val); + return val; + } + auto pointer = std::dynamic_pointer_cast(pval); + if (pointer) { + auto val = pointer->data; + call(data.callbacks, tok, val); + return val; + } + } + return ExprEngine::ValuePtr(); +} + +static ExprEngine::ValuePtr executeVariable(const Token *tok, Data &data) +{ + auto val = data.getValue(tok->varId(), tok->valueType()); + call(data.callbacks, tok, val); + return val; +} + +static ExprEngine::ValuePtr executeNumber(const Token *tok) +{ + int128_t value = MathLib::toLongNumber(tok->str()); + return std::make_shared(tok->str(), value, value); +} + +static ExprEngine::ValuePtr executeExpression(const Token *tok, Data &data) +{ + if (tok->str() == "return") + return executeReturn(tok, data); + + if (tok->str() == "=") + return executeAssign(tok, data); + + if (tok->astOperand1() && tok->astOperand2() && tok->str() == "[") + return executeArrayIndex(tok, data); + + if (tok->str() == "(" && tok->astOperand2()) + return executeFunctionCall(tok, data); + + if (tok->str() == ".") + return executeDot(tok, data); + + if (tok->astOperand1() && tok->astOperand2()) + return executeBinaryOp(tok, data); + + if (tok->isUnaryOp("&") && Token::Match(tok->astOperand1(), "%var%")) + return executeAddressOf(tok, data); + + if (tok->isUnaryOp("*")) + return executeDeref(tok, data); + + if (tok->varId()) + return executeVariable(tok, data); + + if (tok->isNumber()) + return executeNumber(tok); + + return ExprEngine::ValuePtr(); +} + +static void execute(const Token *start, const Token *end, Data &data) +{ + for (const Token *tok = start; tok != end; tok = tok->next()) { + if (tok->variable() && tok->variable()->nameToken() == tok) { + if (tok->variable()->isArray() && tok->variable()->dimensions().size() == 1 && tok->variable()->dimensions()[0].known) { + data.memory[tok->varId()] = std::make_shared(data.getNewSymbolName(), tok->variable()->dimension(0)); + } + if (Token::Match(tok, "%name% [")) + tok = tok->linkAt(1); + } + if (!tok->astParent() && (tok->astOperand1() || tok->astOperand2())) + executeExpression(tok, data); + + if (Token::simpleMatch(tok, "if (")) { + const Token *cond = tok->next()->astOperand2(); + /*const ExprEngine::ValuePtr condValue =*/ executeExpression(cond,data); + Data trueData = data.getData(cond, true); + Data falseData = data.getData(cond, false); + const Token *thenStart = tok->linkAt(1)->next(); + const Token *thenEnd = thenStart->link(); + execute(thenStart->next(), end, trueData); + if (Token::simpleMatch(thenEnd, "} else {")) { + const Token *elseStart = thenEnd->tokAt(2); + execute(elseStart->next(), end, falseData); + } else { + execute(thenEnd->next(), end, falseData); + } + return; + } + + if (Token::simpleMatch(tok, "} else {")) + tok = tok->linkAt(2); + } +} + +void ExprEngine::executeAllFunctions(const Tokenizer *tokenizer, const Settings *settings, const std::vector &callbacks) +{ + const SymbolDatabase *symbolDatabase = tokenizer->getSymbolDatabase(); + for (const Scope *functionScope : symbolDatabase->functionScopes) { + executeFunction(functionScope, tokenizer, settings, callbacks); + } +} + +static ExprEngine::ValuePtr createVariableValue(const Variable &var, Data &data); + +static ExprEngine::ValuePtr createStructVal(const Scope *structScope, Data &data) +{ + std::shared_ptr structValue = std::make_shared(data.getNewSymbolName()); + for (const Variable &member : structScope->varlist) { + ExprEngine::ValuePtr memberValue = createVariableValue(member, data); + if (memberValue) + structValue->member[member.name()] = memberValue; + } + return structValue; +} + +static ExprEngine::ValuePtr createVariableValue(const Variable &var, Data &data) +{ + if (!var.nameToken() || !var.valueType()) + return ExprEngine::ValuePtr(); + if (var.valueType()->pointer > 0) + return std::make_shared(data.getNewSymbolName(), std::make_shared()); + if (var.valueType()->isIntegral()) + return getValueRangeFromValueType(data.getNewSymbolName(), var.valueType(), *data.settings); + if (var.valueType()->type == ValueType::Type::RECORD) + return createStructVal(var.valueType()->typeScope, data); + return ExprEngine::ValuePtr(); +} + +void ExprEngine::executeFunction(const Scope *functionScope, const Tokenizer *tokenizer, const Settings *settings, const std::vector &callbacks) +{ + if (!functionScope->bodyStart) + return; + const Function *function = functionScope->function; + if (!function) + return; + + int symbolValueIndex = 0; + Data data(&symbolValueIndex, tokenizer, settings, callbacks); + data.printMemory(); + + for (const Variable &arg : function->argumentList) { + ValuePtr val = createVariableValue(arg, data); + if (val) + data.memory[arg.declarationId()] = val; + } + + execute(functionScope->bodyStart, functionScope->bodyEnd, data); +} + +void ExprEngine::runChecks(ErrorLogger *errorLogger, const Tokenizer *tokenizer, const Settings *settings) +{ + std::function divByZero = [&](const Token *tok, const ExprEngine::Value &value) { + if (!Token::simpleMatch(tok->astParent(), "/")) + return; + if (tok->astParent()->astOperand2() == tok && value.isIntValueInRange(0)) { + std::list callstack{tok->astParent()}; + ErrorLogger::ErrorMessage errmsg(callstack, &tokenizer->list, Severity::SeverityType::error, "verificationDivByZero", "Division by zero", false); + errorLogger->reportErr(errmsg); + } + }; + + std::function integerOverflow = [&](const Token *tok, const ExprEngine::Value &value) { + // Integer overflow.. + if (value.type() != ExprEngine::ValueType::BinOpResult) + return; + if (!tok->valueType() || tok->valueType()->pointer != 0 || tok->valueType()->type != ::ValueType::Type::INT) + return; + const ExprEngine::BinOpResult &b = static_cast(value); + int128_t minValue, maxValue; + b.getRange(&minValue, &maxValue); + if (tok->valueType()->sign == ::ValueType::Sign::UNSIGNED && (minValue < 0 || maxValue >= (1LL << 32))) { + std::list callstack{tok}; + ErrorLogger::ErrorMessage errmsg(callstack, &tokenizer->list, Severity::SeverityType::warning, "verificationIntegerOverflow", "Unsigned integer overflow", false); + errorLogger->reportErr(errmsg); + } + }; + std::vector callbacks; + callbacks.push_back(divByZero); + callbacks.push_back(integerOverflow); + ExprEngine::executeAllFunctions(tokenizer, settings, callbacks); +} diff --git a/lib/exprengine.h b/lib/exprengine.h new file mode 100644 index 000000000..2ea277ac3 --- /dev/null +++ b/lib/exprengine.h @@ -0,0 +1,213 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2019 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 . + */ + +//--------------------------------------------------------------------------- +#ifndef exprengineH +#define exprengineH +//--------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include + +class ErrorLogger; +class Tokenizer; +class Scope; +class Settings; +class Token; + +#ifdef __GNUC__ +typedef __int128_t int128_t; +#else +typedef long long int128_t; +#warning TODO No 128-bit integer type is available => Limited analysis of large integers +#endif + +namespace ExprEngine { + std::string str(int128_t); + + // TODO we need to handle floats, containers, pointers, aliases and structs and stuff + enum class ValueType { UninitValue, IntRange, PointerValue, ArrayValue, StructValue, AddressOfValue, BinOpResult }; + + class Value; + typedef std::shared_ptr ValuePtr; + + class Value { + public: + Value(const std::string &name) : name(name) {} + virtual ~Value() {} + virtual ValueType type() const = 0; + virtual std::string getRange() const = 0; + virtual bool isIntValueInRange(int value) const { + (void)value; + return false; + } + const std::string name; + }; + + class UninitValue: public Value { + public: + UninitValue() : Value("?") {} + ValueType type() const override { + return ValueType::UninitValue; + } + std::string getRange() const override { + return "?"; + } + }; + + class IntRange : public Value { + public: + IntRange(const std::string &name, int128_t minValue, int128_t maxValue) + : Value(name) + , minValue(minValue) + , maxValue(maxValue) { + } + ~IntRange() {} + + ValueType type() const override { + return ValueType::IntRange; + } + std::string getRange() const override { + return "[" + str(minValue) + ":" + str(maxValue) + "]"; + } + bool isIntValueInRange(int value) const override { + return value >= minValue && value <= maxValue; + } + + int128_t minValue; + int128_t maxValue; + }; + + class PointerValue: public Value { + public: + PointerValue(const std::string &name, ValuePtr data) : Value(name), data(data) {} + ValueType type() const override { + return ValueType::PointerValue; + } + std::string getRange() const override { + return "*" + data->getRange(); + } + ValuePtr data; + }; + + class ArrayValue: public Value { + public: + const int MAXSIZE = 0x100000; + + ArrayValue(const std::string &name, size_t size) + : Value(name) { + data.resize((size < MAXSIZE) ? size : MAXSIZE, + std::make_shared()); + } + + ValueType type() const override { + return ValueType::ArrayValue; + } + std::string getRange() const override { + return "[" + std::to_string(data.size()) + "]"; + } + + void assign(ValuePtr index, ValuePtr value); + ValuePtr read(ValuePtr index); + + std::vector data; + }; + + class StructValue: public Value { + public: + StructValue(const std::string &name) : Value(name) {} + ValueType type() const override { + return ValueType::StructValue; + } + std::string getRange() const override { + return name; + } + ValuePtr getValueOfMember(const std::string &name) const { + auto it = member.find(name); + return (it == member.end()) ? ValuePtr() : it->second; + } + std::map member; + }; + + class AddressOfValue: public Value { + public: + AddressOfValue(const std::string &name, int varId) + : Value(name) + , varId(varId) + {} + + ValueType type() const override { + return ValueType::AddressOfValue; + } + std::string getRange() const override { + return "(&@" + std::to_string(varId); + } + + int varId; + }; + + class BinOpResult : public Value { + public: + BinOpResult(const std::string &binop, ValuePtr op1, ValuePtr op2) + : Value("(" + op1->name + ")" + binop + "(" + op2->name + ")") + , binop(binop) + , op1(op1) + , op2(op2) { + auto b1 = std::dynamic_pointer_cast(op1); + if (b1) + mLeafs = b1->mLeafs; + else + mLeafs.insert(op1); + + auto b2 = std::dynamic_pointer_cast(op2); + if (b2) + mLeafs.insert(b2->mLeafs.begin(), b2->mLeafs.end()); + else + mLeafs.insert(op2); + } + + ValueType type() const override { + return ValueType::BinOpResult; + } + std::string getRange() const override; + void getRange(int128_t *minValue, int128_t *maxValue) const; + bool isIntValueInRange(int value) const override; + + std::string binop; + ValuePtr op1; + ValuePtr op2; + private: + int128_t evaluate(int test, const std::map &valueBit) const; + int128_t evaluateOperand(int test, const std::map &valueBit, ValuePtr value) const; + std::set mLeafs; + }; + + typedef std::function Callback; + + /** Execute all functions */ + void executeAllFunctions(const Tokenizer *tokenizer, const Settings *settings, const std::vector &callbacks); + void executeFunction(const Scope *functionScope, const Tokenizer *tokenizer, const Settings *settings, const std::vector &callbacks); + + void runChecks(ErrorLogger *errorLogger, const Tokenizer *tokenizer, const Settings *settings); +} +#endif // exprengineH diff --git a/lib/lib.pri b/lib/lib.pri index 74a5cc83d..5c61e285c 100644 --- a/lib/lib.pri +++ b/lib/lib.pri @@ -34,6 +34,7 @@ HEADERS += $${PWD}/analyzerinfo.h \ $${PWD}/cppcheck.h \ $${PWD}/ctu.h \ $${PWD}/errorlogger.h \ + $${PWD}/exprengine.h \ $${PWD}/importproject.h \ $${PWD}/library.h \ $${PWD}/mathlib.h \ @@ -82,6 +83,7 @@ SOURCES += $${PWD}/analyzerinfo.cpp \ $${PWD}/cppcheck.cpp \ $${PWD}/ctu.cpp \ $${PWD}/errorlogger.cpp \ + $${PWD}/exprengine.cpp \ $${PWD}/importproject.cpp \ $${PWD}/library.cpp \ $${PWD}/mathlib.cpp \ diff --git a/lib/settings.cpp b/lib/settings.cpp index ef17678b4..661b7fa6d 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -45,6 +45,7 @@ Settings::Settings() experimental(false), force(false), inconclusive(false), + verification(false), inlineSuppressions(false), jobs(1), jointSuppressionReport(false), diff --git a/lib/settings.h b/lib/settings.h index 947519525..c6cdfc282 100644 --- a/lib/settings.h +++ b/lib/settings.h @@ -187,6 +187,9 @@ public: SafeChecks safeChecks; + /** @brief Enable verification analysis */ + bool verification; + /** @brief check unknown function return values */ std::set checkUnknownFunctionReturn; diff --git a/test/testexprengine.cpp b/test/testexprengine.cpp new file mode 100644 index 000000000..208ea9f3a --- /dev/null +++ b/test/testexprengine.cpp @@ -0,0 +1,143 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2019 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 "exprengine.h" +#include "settings.h" +#include "symboldatabase.h" +#include "tokenize.h" +#include "testsuite.h" + +class TestExprEngine : public TestFixture { +public: + TestExprEngine() : TestFixture("TestExprEngine") { + } + +private: + void run() OVERRIDE { + TEST_CASE(argPointer); + TEST_CASE(argStruct); + + TEST_CASE(expr1); + TEST_CASE(expr2); + TEST_CASE(expr3); + TEST_CASE(expr4); + TEST_CASE(expr5); + + TEST_CASE(functionCall1); + + TEST_CASE(if1); + TEST_CASE(if2); + TEST_CASE(if3); + + TEST_CASE(ifelse1); + + TEST_CASE(localArray1); + TEST_CASE(localArrayUninit); + + TEST_CASE(pointerAlias1); + TEST_CASE(pointerAlias2); + } + + std::string getRange(const char code[], const std::string &str) { + Settings settings; + settings.platform(cppcheck::Platform::Unix64); + Tokenizer tokenizer(&settings, this); + std::istringstream istr(code); + tokenizer.tokenize(istr, "test.cpp"); + std::string ret; + std::function f = [&](const Token *tok, const ExprEngine::Value &value) { + if (tok->expressionString() == str) + ret += value.getRange(); + }; + std::vector callbacks; + callbacks.push_back(f); + ExprEngine::executeAllFunctions(&tokenizer, &settings, callbacks); + return ret; + } + + void argPointer() { + ASSERT_EQUALS("?", getRange("void f(unsigned char *p) { a = *p; }", "*p")); + } + + void argStruct() { + ASSERT_EQUALS("[0:510]", + getRange("struct S {\n" + " unsigned char a;\n" + " unsigned char b;\n" + "};\n" + "void f(struct S s) { return s.a + s.b; }", "s.a+s.b")); + } + + void expr1() { + ASSERT_EQUALS("[-32768:32767]", getRange("void f(short x) { a = x; }", "x")); + } + + void expr2() { + ASSERT_EQUALS("[-65536:65534]", getRange("void f(short x) { a = x + x; }", "x+x")); + } + + void expr3() { + ASSERT_EQUALS("[-65536:65534]", getRange("int f(short x) { int a = x + x; return a; }", "return a")); + } + + void expr4() { + ASSERT_EQUALS("[0:0]", getRange("int f(short x) { int a = x - x; return a; }", "return a")); + } + + void expr5() { + ASSERT_EQUALS("[-65536:65534]", getRange("void f(short a, short b, short c, short d) { if (a+b 5) a = x + 1; }", "x+1")); + } + + void if2() { + ASSERT_EQUALS("[7:32768][-32767:6]", getRange("inf f(short x) { if (x > 5) {} a = x + 1; }", "x+1")); + } + + void if3() { + ASSERT_EQUALS("[1:1][-2147483648:2147483647][-2147483648:2147483647]", getRange("void f() { int x; if (a) { if (b) x=1; } a=x; }", "a=x")); + } + + void ifelse1() { + ASSERT_EQUALS("[-32767:6]", getRange("inf f(short x) { if (x > 5) ; else a = x + 1; }", "x+1")); + } + + void localArray1() { + ASSERT_EQUALS("[5:5]", getRange("inf f() { int arr[10]; arr[4] = 5; return arr[4]; }", "arr[4]")); + } + + void localArrayUninit() { + ASSERT_EQUALS("?", getRange("inf f() { int arr[10]; return arr[4]; }", "arr[4]")); + } + + void pointerAlias1() { + ASSERT_EQUALS("[3:3]", getRange("inf f() { int x; int *p = &x; x = 3; return *p; }", "return*p")); + } + + void pointerAlias2() { + ASSERT_EQUALS("[1:1]", getRange("inf f() { int x; int *p = &x; *p = 1; return *p; }", "return*p")); + } +}; + +REGISTER_TEST(TestExprEngine)