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!
This commit is contained in:
parent
a8456b56f3
commit
2d651b09fc
8
Makefile
8
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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
#include "tokenlist.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "exprengine.h"
|
||||
|
||||
#define PICOJSON_USE_INT64
|
||||
#include <picojson.h>
|
||||
#include <simplecpp.h>
|
||||
|
@ -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);
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "exprengine.h"
|
||||
#include "settings.h"
|
||||
#include "symboldatabase.h"
|
||||
#include "tokenize.h"
|
||||
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
|
||||
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<ExprEngine::Callback> &callbacks)
|
||||
: symbolValueIndex(symbolValueIndex)
|
||||
, tokenizer(tokenizer)
|
||||
, settings(settings)
|
||||
, callbacks(callbacks) {}
|
||||
typedef std::map<nonneg int, std::shared_ptr<ExprEngine::Value>> Memory;
|
||||
Memory memory;
|
||||
int *symbolValueIndex;
|
||||
const Tokenizer *tokenizer;
|
||||
const Settings *settings;
|
||||
const std::vector<ExprEngine::Callback> &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<ExprEngine::IntRange>(mem->second)) {
|
||||
if (cond->str() == ">") {
|
||||
if (trueData && intRange->minValue <= rhsValue)
|
||||
ret.memory[mem->first] = std::make_shared<ExprEngine::IntRange>(getNewSymbolName(), rhsValue + 1, intRange->maxValue);
|
||||
else if (!trueData && intRange->maxValue > rhsValue)
|
||||
ret.memory[mem->first] = std::make_shared<ExprEngine::IntRange>(getNewSymbolName(), intRange->minValue, rhsValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string getNewSymbolName() {
|
||||
return "$" + std::to_string(++(*symbolValueIndex));
|
||||
}
|
||||
|
||||
std::shared_ptr<ExprEngine::ArrayValue> getArrayValue(const Token *tok) {
|
||||
const Memory::iterator it = memory.find(tok->varId());
|
||||
if (it != memory.end())
|
||||
return std::dynamic_pointer_cast<ExprEngine::ArrayValue>(it->second);
|
||||
return std::shared_ptr<ExprEngine::ArrayValue>();
|
||||
}
|
||||
|
||||
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<ExprEngine::IntRange>(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<ExprEngine::IntRange>(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<ValuePtr, int> valueBit;
|
||||
// Assign a bit number for each leaf
|
||||
int bit = 0;
|
||||
for (ValuePtr v : mLeafs) {
|
||||
if (auto intRange = std::dynamic_pointer_cast<IntRange>(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<ExprEngine::ValuePtr, int> &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<ExprEngine::ValuePtr, int> &valueBit, ExprEngine::ValuePtr value) const
|
||||
{
|
||||
auto binOpResult = std::dynamic_pointer_cast<ExprEngine::BinOpResult>(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<IntRange>(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<ExprEngine::IntRange>(name, 0, 1);
|
||||
} else {
|
||||
if (vt->sign == ValueType::Sign::UNSIGNED) {
|
||||
return std::make_shared<ExprEngine::IntRange>(name, 0, ((int128_t)1 << bits) - 1);
|
||||
} else {
|
||||
return std::make_shared<ExprEngine::IntRange>(name, -((int128_t)1 << (bits - 1)), ((int128_t)1 << (bits - 1)) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return ExprEngine::ValuePtr();
|
||||
}
|
||||
|
||||
static void call(const std::vector<ExprEngine::Callback> &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<ExprEngine::AddressOfValue>(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<ExprEngine::StructValue> structValue = std::dynamic_pointer_cast<ExprEngine::StructValue>(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<ExprEngine::BinOpResult>(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<ExprEngine::AddressOfValue>(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<ExprEngine::AddressOfValue>(pval);
|
||||
if (addressOf) {
|
||||
auto val = data.getValue(addressOf->varId, tok->valueType());
|
||||
call(data.callbacks, tok, val);
|
||||
return val;
|
||||
}
|
||||
auto pointer = std::dynamic_pointer_cast<ExprEngine::PointerValue>(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<ExprEngine::IntRange>(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<ExprEngine::ArrayValue>(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<ExprEngine::Callback> &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<ExprEngine::StructValue> structValue = std::make_shared<ExprEngine::StructValue>(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<ExprEngine::PointerValue>(data.getNewSymbolName(), std::make_shared<ExprEngine::UninitValue>());
|
||||
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<ExprEngine::Callback> &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<void(const Token *, const ExprEngine::Value &)> divByZero = [&](const Token *tok, const ExprEngine::Value &value) {
|
||||
if (!Token::simpleMatch(tok->astParent(), "/"))
|
||||
return;
|
||||
if (tok->astParent()->astOperand2() == tok && value.isIntValueInRange(0)) {
|
||||
std::list<const Token*> callstack{tok->astParent()};
|
||||
ErrorLogger::ErrorMessage errmsg(callstack, &tokenizer->list, Severity::SeverityType::error, "verificationDivByZero", "Division by zero", false);
|
||||
errorLogger->reportErr(errmsg);
|
||||
}
|
||||
};
|
||||
|
||||
std::function<void(const Token *, const ExprEngine::Value &)> 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<const ExprEngine::BinOpResult &>(value);
|
||||
int128_t minValue, maxValue;
|
||||
b.getRange(&minValue, &maxValue);
|
||||
if (tok->valueType()->sign == ::ValueType::Sign::UNSIGNED && (minValue < 0 || maxValue >= (1LL << 32))) {
|
||||
std::list<const Token*> callstack{tok};
|
||||
ErrorLogger::ErrorMessage errmsg(callstack, &tokenizer->list, Severity::SeverityType::warning, "verificationIntegerOverflow", "Unsigned integer overflow", false);
|
||||
errorLogger->reportErr(errmsg);
|
||||
}
|
||||
};
|
||||
std::vector<ExprEngine::Callback> callbacks;
|
||||
callbacks.push_back(divByZero);
|
||||
callbacks.push_back(integerOverflow);
|
||||
ExprEngine::executeAllFunctions(tokenizer, settings, callbacks);
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
#ifndef exprengineH
|
||||
#define exprengineH
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
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<Value> 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<UninitValue>());
|
||||
}
|
||||
|
||||
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<ValuePtr> 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<std::string, ValuePtr> 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<BinOpResult>(op1);
|
||||
if (b1)
|
||||
mLeafs = b1->mLeafs;
|
||||
else
|
||||
mLeafs.insert(op1);
|
||||
|
||||
auto b2 = std::dynamic_pointer_cast<BinOpResult>(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<ValuePtr, int> &valueBit) const;
|
||||
int128_t evaluateOperand(int test, const std::map<ValuePtr, int> &valueBit, ValuePtr value) const;
|
||||
std::set<ValuePtr> mLeafs;
|
||||
};
|
||||
|
||||
typedef std::function<void(const Token *, const ExprEngine::Value &)> Callback;
|
||||
|
||||
/** Execute all functions */
|
||||
void executeAllFunctions(const Tokenizer *tokenizer, const Settings *settings, const std::vector<Callback> &callbacks);
|
||||
void executeFunction(const Scope *functionScope, const Tokenizer *tokenizer, const Settings *settings, const std::vector<Callback> &callbacks);
|
||||
|
||||
void runChecks(ErrorLogger *errorLogger, const Tokenizer *tokenizer, const Settings *settings);
|
||||
}
|
||||
#endif // exprengineH
|
|
@ -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 \
|
||||
|
|
|
@ -45,6 +45,7 @@ Settings::Settings()
|
|||
experimental(false),
|
||||
force(false),
|
||||
inconclusive(false),
|
||||
verification(false),
|
||||
inlineSuppressions(false),
|
||||
jobs(1),
|
||||
jointSuppressionReport(false),
|
||||
|
|
|
@ -187,6 +187,9 @@ public:
|
|||
|
||||
SafeChecks safeChecks;
|
||||
|
||||
/** @brief Enable verification analysis */
|
||||
bool verification;
|
||||
|
||||
/** @brief check unknown function return values */
|
||||
std::set<std::string> checkUnknownFunctionReturn;
|
||||
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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<void(const Token *, const ExprEngine::Value &)> f = [&](const Token *tok, const ExprEngine::Value &value) {
|
||||
if (tok->expressionString() == str)
|
||||
ret += value.getRange();
|
||||
};
|
||||
std::vector<ExprEngine::Callback> 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<c+d) {} }", "a+b"));
|
||||
}
|
||||
|
||||
void functionCall1() {
|
||||
ASSERT_EQUALS("[-2147483648:2147483647]", getRange("int atoi(const char *p); void f() { int x = atoi(a); x = x; }", "x=x"));
|
||||
}
|
||||
|
||||
void if1() {
|
||||
ASSERT_EQUALS("[7:32768]", getRange("inf f(short x) { if (x > 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)
|
Loading…
Reference in New Issue