Refactor: Use interval analysis for symbolic values for inferCondtion (#3488)

This commit is contained in:
Paul Fultz II 2021-10-09 09:19:06 -05:00 committed by GitHub
parent dd0f617d39
commit a1e0ef9b38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 466 additions and 458 deletions

View File

@ -194,6 +194,7 @@ LIBOBJ = $(libcppdir)/analyzerinfo.o \
$(libcppdir)/exprengine.o \ $(libcppdir)/exprengine.o \
$(libcppdir)/forwardanalyzer.o \ $(libcppdir)/forwardanalyzer.o \
$(libcppdir)/importproject.o \ $(libcppdir)/importproject.o \
$(libcppdir)/infer.o \
$(libcppdir)/library.o \ $(libcppdir)/library.o \
$(libcppdir)/mathlib.o \ $(libcppdir)/mathlib.o \
$(libcppdir)/path.o \ $(libcppdir)/path.o \
@ -521,6 +522,9 @@ $(libcppdir)/forwardanalyzer.o: lib/forwardanalyzer.cpp lib/analyzer.h lib/astut
$(libcppdir)/importproject.o: lib/importproject.cpp externals/picojson/picojson.h externals/tinyxml2/tinyxml2.h lib/astutils.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h $(libcppdir)/importproject.o: lib/importproject.cpp externals/picojson/picojson.h externals/tinyxml2/tinyxml2.h lib/astutils.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/valueflow.h
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/importproject.o $(libcppdir)/importproject.cpp $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/importproject.o $(libcppdir)/importproject.cpp
$(libcppdir)/infer.o: lib/infer.cpp lib/astutils.h lib/calculate.h lib/config.h lib/errortypes.h lib/infer.h lib/mathlib.h lib/utils.h lib/valueflow.h lib/valueptr.h
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/infer.o $(libcppdir)/infer.cpp
$(libcppdir)/library.o: lib/library.cpp externals/tinyxml2/tinyxml2.h lib/astutils.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/valueflow.h $(libcppdir)/library.o: lib/library.cpp externals/tinyxml2/tinyxml2.h lib/astutils.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/path.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenlist.h lib/utils.h lib/valueflow.h
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/library.o $(libcppdir)/library.cpp $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/library.o $(libcppdir)/library.cpp
@ -578,7 +582,7 @@ $(libcppdir)/tokenlist.o: lib/tokenlist.cpp externals/simplecpp/simplecpp.h lib/
$(libcppdir)/utils.o: lib/utils.cpp lib/config.h lib/utils.h $(libcppdir)/utils.o: lib/utils.cpp lib/config.h lib/utils.h
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/utils.o $(libcppdir)/utils.cpp $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/utils.o $(libcppdir)/utils.cpp
$(libcppdir)/valueflow.o: lib/valueflow.cpp lib/analyzer.h lib/astutils.h lib/calculate.h lib/check.h lib/checkuninitvar.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/forwardanalyzer.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/programmemory.h lib/reverseanalyzer.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/valueptr.h $(libcppdir)/valueflow.o: lib/valueflow.cpp lib/analyzer.h lib/astutils.h lib/calculate.h lib/check.h lib/checkuninitvar.h lib/color.h lib/config.h lib/ctu.h lib/errorlogger.h lib/errortypes.h lib/forwardanalyzer.h lib/importproject.h lib/infer.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/programmemory.h lib/reverseanalyzer.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/tokenlist.h lib/utils.h lib/valueflow.h lib/valueptr.h
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/valueflow.o $(libcppdir)/valueflow.cpp $(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/valueflow.o $(libcppdir)/valueflow.cpp
cli/cmdlineparser.o: cli/cmdlineparser.cpp cli/cmdlineparser.h cli/cppcheckexecutor.h cli/filelister.h cli/threadexecutor.h externals/tinyxml2/tinyxml2.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h cli/cmdlineparser.o: cli/cmdlineparser.cpp cli/cmdlineparser.h cli/cppcheckexecutor.h cli/filelister.h cli/threadexecutor.h externals/tinyxml2/tinyxml2.h lib/check.h lib/color.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/path.h lib/platform.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h

362
lib/infer.cpp Normal file
View File

@ -0,0 +1,362 @@
#include "infer.h"
#include "calculate.h"
#include "valueptr.h"
#include <iterator>
#include <unordered_set>
template<class Predicate, class Compare>
static const ValueFlow::Value* getCompareValue(const std::list<ValueFlow::Value>& values, Predicate pred, Compare compare)
{
const ValueFlow::Value* result = nullptr;
for (const ValueFlow::Value& value : values) {
if (!pred(value))
continue;
if (result)
result = &std::min(value, *result, [compare](const ValueFlow::Value& x, const ValueFlow::Value& y) {
return compare(x.intvalue, y.intvalue);
});
else
result = &value;
}
return result;
}
struct Interval {
std::vector<MathLib::bigint> minvalue = {};
std::vector<MathLib::bigint> maxvalue = {};
std::vector<const ValueFlow::Value*> minRef = {};
std::vector<const ValueFlow::Value*> maxRef = {};
std::string str() const
{
std::string result = "[";
if (minvalue.size() == 1)
result += std::to_string(minvalue.front());
else
result += "*";
result += ",";
if (maxvalue.size() == 1)
result += std::to_string(maxvalue.front());
else
result += "*";
result += "]";
return result;
}
void setMinValue(MathLib::bigint x, const ValueFlow::Value* ref = nullptr)
{
minvalue = {x};
if (ref)
minRef = {ref};
}
void setMaxValue(MathLib::bigint x, const ValueFlow::Value* ref = nullptr)
{
maxvalue = {x};
if (ref)
maxRef = {ref};
}
bool isLessThan(MathLib::bigint x, std::vector<const ValueFlow::Value*>* ref = nullptr) const
{
if (!this->maxvalue.empty() && this->maxvalue.front() < x) {
if (ref)
*ref = maxRef;
return true;
}
return false;
}
bool isGreaterThan(MathLib::bigint x, std::vector<const ValueFlow::Value*>* ref = nullptr) const
{
if (!this->minvalue.empty() && this->minvalue.front() > x) {
if (ref)
*ref = minRef;
return true;
}
return false;
}
bool isScalar() const {
return minvalue.size() == 1 && minvalue == maxvalue;
}
bool empty() const {
return minvalue.empty() && maxvalue.empty();
}
bool isScalarOrEmpty() const {
return empty() || isScalar();
}
MathLib::bigint getScalar() const
{
assert(isScalar());
return minvalue.front();
}
std::vector<const ValueFlow::Value*> getScalarRef() const
{
assert(isScalar());
if (minRef != maxRef)
return merge(minRef, maxRef);
return minRef;
}
static Interval fromInt(MathLib::bigint x, const ValueFlow::Value* ref = nullptr)
{
Interval result;
result.setMinValue(x, ref);
result.setMaxValue(x, ref);
return result;
}
template<class Predicate>
static Interval fromValues(const std::list<ValueFlow::Value>& values, Predicate predicate)
{
Interval result;
const ValueFlow::Value* minValue = getCompareValue(values, predicate, std::less<MathLib::bigint>{});
if (minValue) {
if (minValue->isImpossible() && minValue->bound == ValueFlow::Value::Bound::Upper)
result.setMinValue(minValue->intvalue + 1, minValue);
if (minValue->isPossible() && minValue->bound == ValueFlow::Value::Bound::Lower)
result.setMinValue(minValue->intvalue, minValue);
if (minValue->isKnown())
return Interval::fromInt(minValue->intvalue, minValue);
}
const ValueFlow::Value* maxValue = getCompareValue(values, predicate, std::greater<MathLib::bigint>{});
if (maxValue) {
if (maxValue->isImpossible() && maxValue->bound == ValueFlow::Value::Bound::Lower)
result.setMaxValue(maxValue->intvalue - 1, maxValue);
if (maxValue->isPossible() && maxValue->bound == ValueFlow::Value::Bound::Upper)
result.setMaxValue(maxValue->intvalue, maxValue);
assert(!maxValue->isKnown());
}
return result;
}
static Interval fromValues(const std::list<ValueFlow::Value>& values)
{
return Interval::fromValues(values, [](const ValueFlow::Value&) {
return true;
});
}
template<class F>
static std::vector<MathLib::bigint> apply(const std::vector<MathLib::bigint>& x,
const std::vector<MathLib::bigint>& y,
F f)
{
if (x.empty())
return {};
if (y.empty())
return {};
return {f(x.front(), y.front())};
}
static std::vector<const ValueFlow::Value*> merge(std::vector<const ValueFlow::Value*> x,
const std::vector<const ValueFlow::Value*>& y)
{
x.insert(x.end(), y.begin(), y.end());
return x;
}
friend Interval operator-(const Interval& lhs, const Interval& rhs)
{
Interval result;
result.minvalue = Interval::apply(lhs.minvalue, rhs.maxvalue, std::minus<MathLib::bigint>{});
result.maxvalue = Interval::apply(lhs.maxvalue, rhs.minvalue, std::minus<MathLib::bigint>{});
if (!result.minvalue.empty())
result.minRef = merge(lhs.minRef, rhs.maxRef);
if (!result.maxvalue.empty())
result.maxRef = merge(lhs.maxRef, rhs.minRef);
return result;
}
static std::vector<int> equal(const Interval& lhs,
const Interval& rhs,
std::vector<const ValueFlow::Value*>* ref = nullptr)
{
if (!lhs.isScalar())
return {};
if (!rhs.isScalar())
return {};
if (ref)
*ref = merge(lhs.getScalarRef(), rhs.getScalarRef());
return {lhs.minvalue == rhs.minvalue};
}
static std::vector<int> compare(const Interval& lhs,
const Interval& rhs,
std::vector<const ValueFlow::Value*>* ref = nullptr)
{
Interval diff = lhs - rhs;
if (diff.isGreaterThan(0, ref))
return {1};
if (diff.isLessThan(0, ref))
return {-1};
std::vector<int> eq = Interval::equal(lhs, rhs, ref);
if (!eq.empty()) {
if (eq.front() == 0)
return {1, -1};
else
return {0};
}
if (diff.isGreaterThan(-1, ref))
return {0, 1};
if (diff.isLessThan(1, ref))
return {0, -1};
return {};
}
static std::vector<bool> compare(const std::string& op,
const Interval& lhs,
const Interval& rhs,
std::vector<const ValueFlow::Value*>* ref = nullptr)
{
std::vector<int> r = compare(lhs, rhs, ref);
if (r.empty())
return {};
bool b = calculate(op, r.front(), 0);
if (std::all_of(r.begin() + 1, r.end(), [&](int i) {
return b == calculate(op, i, 0);
}))
return {b};
return {};
}
};
std::string toString(const Interval& i) {
return i.str();
}
static void addToErrorPath(ValueFlow::Value& value, const std::vector<const ValueFlow::Value*>& refs)
{
std::unordered_set<const Token*> locations;
for (const ValueFlow::Value* ref : refs) {
if (ref->condition && !value.condition)
value.condition = ref->condition;
std::copy_if(ref->errorPath.begin(),
ref->errorPath.end(),
std::back_inserter(value.errorPath),
[&](const ErrorPathItem& e) {
return locations.insert(e.first).second;
});
}
}
static void setValueKind(ValueFlow::Value& value, const std::vector<const ValueFlow::Value*>& refs)
{
bool isPossible = false;
bool isInconclusive = false;
for (const ValueFlow::Value* ref : refs) {
if (ref->isPossible())
isPossible = true;
if (ref->isInconclusive())
isInconclusive = true;
}
if (isInconclusive)
value.setInconclusive();
else if (isPossible)
value.setPossible();
else
value.setKnown();
}
static bool inferNotEqual(const std::list<ValueFlow::Value>& values, MathLib::bigint x)
{
return std::any_of(values.begin(), values.end(), [&](const ValueFlow::Value& value) {
return value.isImpossible() && value.intvalue == x;
});
}
std::vector<ValueFlow::Value> infer(const ValuePtr<InferModel>& model,
const std::string& op,
std::list<ValueFlow::Value> lhsValues,
std::list<ValueFlow::Value> rhsValues)
{
std::vector<ValueFlow::Value> result;
auto notMatch = [&](const ValueFlow::Value& value) {
return !model->match(value);
};
lhsValues.remove_if(notMatch);
rhsValues.remove_if(notMatch);
if (lhsValues.empty() || rhsValues.empty())
return result;
Interval lhs = Interval::fromValues(lhsValues);
Interval rhs = Interval::fromValues(rhsValues);
if (op == "-") {
Interval diff = lhs - rhs;
if (diff.isScalar()) {
std::vector<const ValueFlow::Value*> refs = diff.getScalarRef();
ValueFlow::Value value(diff.getScalar());
addToErrorPath(value, refs);
setValueKind(value, refs);
result.push_back(value);
} else {
if (!diff.minvalue.empty()) {
ValueFlow::Value value(diff.minvalue.front() - 1);
value.setImpossible();
value.bound = ValueFlow::Value::Bound::Upper;
addToErrorPath(value, diff.minRef);
result.push_back(value);
}
if (!diff.maxvalue.empty()) {
ValueFlow::Value value(diff.maxvalue.front() + 1);
value.setImpossible();
value.bound = ValueFlow::Value::Bound::Lower;
addToErrorPath(value, diff.maxRef);
result.push_back(value);
}
}
} else if ((op == "!=" || op == "==") && lhs.isScalarOrEmpty() && rhs.isScalarOrEmpty()) {
if (lhs.isScalar() && rhs.isScalar()) {
std::vector<const ValueFlow::Value*> refs = Interval::merge(lhs.getScalarRef(), rhs.getScalarRef());
ValueFlow::Value value(calculate(op, lhs.getScalar(), rhs.getScalar()));
addToErrorPath(value, refs);
setValueKind(value, refs);
result.push_back(value);
} else {
std::vector<const ValueFlow::Value*> refs;
if (lhs.isScalar() && inferNotEqual(rhsValues, lhs.getScalar()))
refs = lhs.getScalarRef();
else if (rhs.isScalar() && inferNotEqual(lhsValues, rhs.getScalar()))
refs = rhs.getScalarRef();
if (!refs.empty()) {
ValueFlow::Value value(op == "!=");
addToErrorPath(value, refs);
setValueKind(value, refs);
result.push_back(value);
}
}
} else {
std::vector<const ValueFlow::Value*> refs;
std::vector<bool> r = Interval::compare(op, lhs, rhs, &refs);
if (!r.empty()) {
ValueFlow::Value value(r.front());
addToErrorPath(value, refs);
setValueKind(value, refs);
result.push_back(value);
}
}
return result;
}
std::vector<ValueFlow::Value> infer(const ValuePtr<InferModel>& model,
const std::string& op,
MathLib::bigint lhs,
std::list<ValueFlow::Value> rhsValues)
{
return infer(model, op, {model->yield(lhs)}, std::move(rhsValues));
}
std::vector<ValueFlow::Value> infer(const ValuePtr<InferModel>& model,
const std::string& op,
std::list<ValueFlow::Value> lhsValues,
MathLib::bigint rhs)
{
return infer(model, op, std::move(lhsValues), {model->yield(rhs)});
}

52
lib/infer.h Normal file
View File

@ -0,0 +1,52 @@
/*
* Cppcheck - A tool for static C/C++ code analysis
* Copyright (C) 2007-2021 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 inferH
#define inferH
#include "mathlib.h"
#include "valueflow.h"
struct Interval;
template<class T>
class ValuePtr;
struct InferModel {
virtual bool match(const ValueFlow::Value& value) const = 0;
virtual ValueFlow::Value yield(MathLib::bigint value) const = 0;
virtual ~InferModel() {}
};
std::vector<ValueFlow::Value> infer(const ValuePtr<InferModel>& model,
const std::string& op,
std::list<ValueFlow::Value> lhsValues,
std::list<ValueFlow::Value> rhsValues);
std::vector<ValueFlow::Value> infer(const ValuePtr<InferModel>& model,
const std::string& op,
MathLib::bigint lhs,
std::list<ValueFlow::Value> rhsValues);
std::vector<ValueFlow::Value> infer(const ValuePtr<InferModel>& model,
const std::string& op,
std::list<ValueFlow::Value> lhsValues,
MathLib::bigint rhs);
std::string toString(const Interval& i);
#endif

View File

@ -41,6 +41,7 @@ HEADERS += $${PWD}/analyzerinfo.h \
$${PWD}/exprengine.h \ $${PWD}/exprengine.h \
$${PWD}/forwardanalyzer.h \ $${PWD}/forwardanalyzer.h \
$${PWD}/importproject.h \ $${PWD}/importproject.h \
$${PWD}/infer.h \
$${PWD}/library.h \ $${PWD}/library.h \
$${PWD}/mathlib.h \ $${PWD}/mathlib.h \
$${PWD}/path.h \ $${PWD}/path.h \
@ -100,6 +101,7 @@ SOURCES += $${PWD}/analyzerinfo.cpp \
$${PWD}/exprengine.cpp \ $${PWD}/exprengine.cpp \
$${PWD}/forwardanalyzer.cpp \ $${PWD}/forwardanalyzer.cpp \
$${PWD}/importproject.cpp \ $${PWD}/importproject.cpp \
$${PWD}/infer.cpp \
$${PWD}/library.cpp \ $${PWD}/library.cpp \
$${PWD}/mathlib.cpp \ $${PWD}/mathlib.cpp \
$${PWD}/path.cpp \ $${PWD}/path.cpp \

View File

@ -85,6 +85,7 @@
#include "errorlogger.h" #include "errorlogger.h"
#include "errortypes.h" #include "errortypes.h"
#include "forwardanalyzer.h" #include "forwardanalyzer.h"
#include "infer.h"
#include "library.h" #include "library.h"
#include "mathlib.h" #include "mathlib.h"
#include "path.h" #include "path.h"
@ -4424,322 +4425,6 @@ static void valueFlowSymbolicAbs(TokenList* tokenlist, SymbolDatabase* symboldat
} }
} }
template<class Predicate, class Compare>
static const ValueFlow::Value* getCompareValue(const std::list<ValueFlow::Value>& values,
Predicate pred,
Compare compare)
{
const ValueFlow::Value* result = nullptr;
for (const ValueFlow::Value& value : values) {
if (!pred(value))
continue;
if (result)
result = &std::min(value, *result, [compare](const ValueFlow::Value& x, const ValueFlow::Value& y) {
return compare(x.intvalue, y.intvalue);
});
else
result = &value;
}
return result;
}
struct Interval {
std::vector<MathLib::bigint> minvalue = {};
std::vector<MathLib::bigint> maxvalue = {};
std::vector<const ValueFlow::Value*> minRef = {};
std::vector<const ValueFlow::Value*> maxRef = {};
void setMinValue(MathLib::bigint x, const ValueFlow::Value* ref = nullptr)
{
minvalue = {x};
if (ref)
minRef = {ref};
}
void setMaxValue(MathLib::bigint x, const ValueFlow::Value* ref = nullptr)
{
maxvalue = {x};
if (ref)
maxRef = {ref};
}
bool isLessThan(MathLib::bigint x, std::vector<const ValueFlow::Value*>* ref = nullptr) const
{
if (!this->maxvalue.empty() && this->maxvalue.front() < x) {
if (ref)
*ref = maxRef;
return true;
}
return false;
}
bool isGreaterThan(MathLib::bigint x, std::vector<const ValueFlow::Value*>* ref = nullptr) const
{
if (!this->minvalue.empty() && this->minvalue.front() > x) {
if (ref)
*ref = minRef;
return true;
}
return false;
}
bool isScalar() const {
return minvalue.size() == 1 && minvalue == maxvalue;
}
bool empty() const {
return minvalue.empty() && maxvalue.empty();
}
bool isScalarOrEmpty() const {
return empty() || isScalar();
}
MathLib::bigint getScalar() const
{
assert(isScalar());
return minvalue.front();
}
std::vector<const ValueFlow::Value*> getScalarRef() const
{
assert(isScalar());
if (!minRef.empty())
return minRef;
if (!maxRef.empty())
return maxRef;
return {};
}
static Interval fromInt(MathLib::bigint x, const ValueFlow::Value* ref = nullptr)
{
Interval result;
result.setMinValue(x, ref);
result.setMaxValue(x, ref);
return result;
}
template<class Predicate>
static Interval fromValues(const std::list<ValueFlow::Value>& values, Predicate predicate)
{
Interval result;
const ValueFlow::Value* minValue = getCompareValue(values, predicate, std::less<MathLib::bigint>{});
if (minValue) {
if (minValue->isImpossible() && minValue->bound == ValueFlow::Value::Bound::Upper)
result.setMinValue(minValue->intvalue + 1, minValue);
if (minValue->isPossible() && minValue->bound == ValueFlow::Value::Bound::Lower)
result.setMinValue(minValue->intvalue, minValue);
if (minValue->isKnown())
return Interval::fromInt(minValue->intvalue, minValue);
}
const ValueFlow::Value* maxValue = getCompareValue(values, predicate, std::greater<MathLib::bigint>{});
if (maxValue) {
if (maxValue->isImpossible() && maxValue->bound == ValueFlow::Value::Bound::Lower)
result.setMaxValue(maxValue->intvalue - 1, maxValue);
if (maxValue->isPossible() && maxValue->bound == ValueFlow::Value::Bound::Upper)
result.setMaxValue(maxValue->intvalue, maxValue);
assert(!maxValue->isKnown());
}
return result;
}
static Interval fromValues(const std::list<ValueFlow::Value>& values)
{
return Interval::fromValues(values, [](const ValueFlow::Value&) {
return true;
});
}
template<class F>
static std::vector<MathLib::bigint> apply(const std::vector<MathLib::bigint>& x,
const std::vector<MathLib::bigint>& y,
F f)
{
if (x.empty())
return {};
if (y.empty())
return {};
return {f(x.front(), y.front())};
}
static std::vector<const ValueFlow::Value*> merge(std::vector<const ValueFlow::Value*> x,
const std::vector<const ValueFlow::Value*>& y)
{
x.insert(x.end(), y.begin(), y.end());
return x;
}
friend Interval operator-(const Interval& lhs, const Interval& rhs)
{
Interval result;
result.minvalue = Interval::apply(lhs.minvalue, rhs.maxvalue, std::minus<MathLib::bigint>{});
result.maxvalue = Interval::apply(lhs.maxvalue, rhs.minvalue, std::minus<MathLib::bigint>{});
if (!result.minvalue.empty())
result.minRef = merge(lhs.minRef, rhs.maxRef);
if (!result.maxvalue.empty())
result.maxRef = merge(lhs.maxRef, rhs.minRef);
return result;
}
static std::vector<int> equal(const Interval& lhs,
const Interval& rhs,
std::vector<const ValueFlow::Value*>* ref = nullptr)
{
if (!lhs.isScalar())
return {};
if (!rhs.isScalar())
return {};
if (ref)
*ref = merge(lhs.minRef, rhs.minRef);
return {lhs.minvalue == rhs.minvalue};
}
static std::vector<int> compare(const Interval& lhs,
const Interval& rhs,
std::vector<const ValueFlow::Value*>* ref = nullptr)
{
Interval diff = lhs - rhs;
if (diff.isGreaterThan(0, ref))
return {1};
if (diff.isLessThan(0, ref))
return {-1};
std::vector<int> eq = Interval::equal(lhs, rhs, ref);
if (!eq.empty() && eq.front() != 0)
return {0};
return {};
}
};
static void addToErrorPath(ValueFlow::Value& value, const std::vector<const ValueFlow::Value*>& refs)
{
for (const ValueFlow::Value* ref : refs) {
value.errorPath.insert(value.errorPath.end(), ref->errorPath.begin(), ref->errorPath.end());
}
}
static void setValueKind(ValueFlow::Value& value, const std::vector<const ValueFlow::Value*>& refs)
{
bool isPossible = false;
bool isInconclusive = false;
for (const ValueFlow::Value* ref : refs) {
if (ref->isPossible())
isPossible = true;
if (ref->isInconclusive())
isInconclusive = true;
}
if (isInconclusive)
value.setInconclusive();
else if (isPossible)
value.setPossible();
else
value.setKnown();
}
struct InferModel {
virtual bool match(const ValueFlow::Value& value) const = 0;
virtual ValueFlow::Value yield(MathLib::bigint value) const = 0;
virtual ~InferModel() {}
};
static bool inferNotEqual(const std::list<ValueFlow::Value>& values, MathLib::bigint x)
{
return std::any_of(values.begin(), values.end(), [&](const ValueFlow::Value& value) {
return value.isImpossible() && value.intvalue == x;
});
}
static std::vector<ValueFlow::Value> infer(const ValuePtr<InferModel>& model,
const std::string& op,
std::list<ValueFlow::Value> lhsValues,
std::list<ValueFlow::Value> rhsValues)
{
std::vector<ValueFlow::Value> result;
auto notMatch = [&](const ValueFlow::Value& value) {
return !model->match(value);
};
lhsValues.remove_if(notMatch);
rhsValues.remove_if(notMatch);
if (lhsValues.empty() || rhsValues.empty())
return result;
Interval lhs = Interval::fromValues(lhsValues);
Interval rhs = Interval::fromValues(rhsValues);
if (op == "-") {
Interval diff = lhs - rhs;
if (diff.isScalar()) {
std::vector<const ValueFlow::Value*> refs = diff.getScalarRef();
ValueFlow::Value value(diff.getScalar());
addToErrorPath(value, refs);
setValueKind(value, refs);
result.push_back(value);
} else {
if (!diff.minvalue.empty()) {
ValueFlow::Value value(diff.minvalue.front() - 1);
value.setImpossible();
value.bound = ValueFlow::Value::Bound::Upper;
addToErrorPath(value, diff.minRef);
result.push_back(value);
}
if (!diff.maxvalue.empty()) {
ValueFlow::Value value(diff.maxvalue.front() + 1);
value.setImpossible();
value.bound = ValueFlow::Value::Bound::Lower;
addToErrorPath(value, diff.maxRef);
result.push_back(value);
}
}
} else if ((op == "!=" || op == "==") && lhs.isScalarOrEmpty() && rhs.isScalarOrEmpty()) {
if (lhs.isScalar() && rhs.isScalar()) {
std::vector<const ValueFlow::Value*> refs = Interval::merge(lhs.getScalarRef(), rhs.getScalarRef());
ValueFlow::Value value(calculate(op, lhs.getScalar(), rhs.getScalar()));
addToErrorPath(value, refs);
setValueKind(value, refs);
result.push_back(value);
} else {
std::vector<const ValueFlow::Value*> refs;
if (lhs.isScalar() && inferNotEqual(rhsValues, lhs.getScalar()))
refs = lhs.getScalarRef();
else if (rhs.isScalar() && inferNotEqual(lhsValues, rhs.getScalar()))
refs = rhs.getScalarRef();
if (!refs.empty()) {
ValueFlow::Value value(op == "!=");
addToErrorPath(value, refs);
setValueKind(value, refs);
result.push_back(value);
}
}
} else {
std::vector<const ValueFlow::Value*> refs;
std::vector<int> r = Interval::compare(lhs, rhs, &refs);
if (!r.empty()) {
int x = r.front();
ValueFlow::Value value(calculate(op, x, 0));
addToErrorPath(value, refs);
setValueKind(value, refs);
result.push_back(value);
}
}
return result;
}
static std::vector<ValueFlow::Value> infer(const ValuePtr<InferModel>& model,
const std::string& op,
MathLib::bigint lhs,
std::list<ValueFlow::Value> rhsValues)
{
return infer(model, op, {model->yield(lhs)}, std::move(rhsValues));
}
static std::vector<ValueFlow::Value> infer(const ValuePtr<InferModel>& model,
const std::string& op,
std::list<ValueFlow::Value> lhsValues,
MathLib::bigint rhs)
{
return infer(model, op, std::move(lhsValues), {model->yield(rhs)});
}
struct SymbolicInferModel : InferModel { struct SymbolicInferModel : InferModel {
const Token* expr; const Token* expr;
explicit SymbolicInferModel(const Token* tok) : expr(tok) { explicit SymbolicInferModel(const Token* tok) : expr(tok) {
@ -5543,82 +5228,18 @@ struct SimpleConditionHandler : ConditionHandler {
} }
}; };
static bool isInBounds(const ValueFlow::Value& value, MathLib::bigint x) struct IntegralInferModel : InferModel {
{ virtual bool match(const ValueFlow::Value& value) const OVERRIDE {
if (value.intvalue == x) return value.isIntValue();
return true;
if (value.bound == ValueFlow::Value::Bound::Lower && value.intvalue > x)
return false;
if (value.bound == ValueFlow::Value::Bound::Upper && value.intvalue < x)
return false;
// Checking for equality is not necessary since we already know the value is not equal
if (value.bound == ValueFlow::Value::Bound::Point)
return false;
return true;
} }
virtual ValueFlow::Value yield(MathLib::bigint value) const OVERRIDE
static const ValueFlow::Value* getCompareIntValue(const std::list<ValueFlow::Value>& values, std::function<bool(MathLib::bigint, MathLib::bigint)> compare)
{ {
const ValueFlow::Value* result = nullptr; ValueFlow::Value result(value);
for (const ValueFlow::Value& value : values) { result.valueType = ValueFlow::Value::ValueType::INT;
if (!value.isIntValue()) result.setKnown();
continue;
if (result)
result = &std::min(value, *result, [compare](const ValueFlow::Value& x, const ValueFlow::Value& y) {
return compare(x.intvalue, y.intvalue);
});
else
result = &value;
}
return result;
}
static const ValueFlow::Value* proveLessThan(const std::list<ValueFlow::Value>& values, MathLib::bigint x)
{
const ValueFlow::Value* result = nullptr;
const ValueFlow::Value* maxValue = getCompareIntValue(values, std::greater<MathLib::bigint> {});
if (maxValue && maxValue->isImpossible() && maxValue->bound == ValueFlow::Value::Bound::Lower) {
if (maxValue->intvalue <= x)
result = maxValue;
}
return result;
}
static const ValueFlow::Value* proveGreaterThan(const std::list<ValueFlow::Value>& values, MathLib::bigint x)
{
const ValueFlow::Value* result = nullptr;
const ValueFlow::Value* minValue = getCompareIntValue(values, std::less<MathLib::bigint> {});
if (minValue && minValue->isImpossible() && minValue->bound == ValueFlow::Value::Bound::Upper) {
if (minValue->intvalue >= x)
result = minValue;
}
return result;
}
static const ValueFlow::Value* proveNotEqual(const std::list<ValueFlow::Value>& values, MathLib::bigint x)
{
const ValueFlow::Value* result = nullptr;
for (const ValueFlow::Value& value : values) {
if (value.valueType != ValueFlow::Value::ValueType::INT)
continue;
if (result && !isInBounds(value, result->intvalue))
continue;
if (value.isImpossible()) {
if (value.intvalue == x)
return &value;
if (!isInBounds(value, x))
continue;
result = &value;
} else {
if (value.intvalue == x)
return nullptr;
if (!isInBounds(value, x))
continue;
result = nullptr;
}
}
return result; return result;
} }
};
ValueFlow::Value inferCondition(const std::string& op, const Token* varTok, MathLib::bigint val) ValueFlow::Value inferCondition(const std::string& op, const Token* varTok, MathLib::bigint val)
{ {
@ -5626,52 +5247,22 @@ ValueFlow::Value inferCondition(const std::string& op, const Token* varTok, Math
return ValueFlow::Value{}; return ValueFlow::Value{};
if (varTok->hasKnownIntValue()) if (varTok->hasKnownIntValue())
return ValueFlow::Value{}; return ValueFlow::Value{};
if (std::none_of(varTok->values().begin(), varTok->values().end(), [](const ValueFlow::Value& v) { std::vector<ValueFlow::Value> r = infer(IntegralInferModel{}, op, varTok->values(), val);
return v.isImpossible() && v.valueType == ValueFlow::Value::ValueType::INT; if (r.size() == 1 && r.front().isKnown())
})) { return r.front();
return ValueFlow::Value{}; return ValueFlow::Value{};
} }
const ValueFlow::Value* result = nullptr;
bool known = false;
if (op == "==" || op == "!=") {
result = proveNotEqual(varTok->values(), val);
known = op == "!=";
} else if (op == "<" || op == ">=") {
result = proveLessThan(varTok->values(), val);
known = op == "<";
if (!result && !isSaturated(val)) {
result = proveGreaterThan(varTok->values(), val - 1);
known = op == ">=";
}
} else if (op == ">" || op == "<=") {
result = proveGreaterThan(varTok->values(), val);
known = op == ">";
if (!result && !isSaturated(val)) {
result = proveLessThan(varTok->values(), val + 1);
known = op == "<=";
}
}
if (!result)
return ValueFlow::Value{};
ValueFlow::Value value = *result;
value.intvalue = known;
value.bound = ValueFlow::Value::Bound::Point;
value.setKnown();
return value;
}
ValueFlow::Value inferCondition(std::string op, MathLib::bigint val, const Token* varTok) ValueFlow::Value inferCondition(std::string op, MathLib::bigint val, const Token* varTok)
{ {
// Flip the operator if (!varTok)
if (op == ">") return ValueFlow::Value{};
op = "<"; if (varTok->hasKnownIntValue())
else if (op == "<") return ValueFlow::Value{};
op = ">"; std::vector<ValueFlow::Value> r = infer(IntegralInferModel{}, op, val, varTok->values());
else if (op == ">=") if (r.size() == 1 && r.front().isKnown())
op = "<="; return r.front();
else if (op == "<=") return ValueFlow::Value{};
op = ">=";
return inferCondition(op, varTok, val);
} }
static void valueFlowInferCondition(TokenList* tokenlist, static void valueFlowInferCondition(TokenList* tokenlist,
@ -5684,33 +5275,23 @@ static void valueFlowInferCondition(TokenList* tokenlist,
continue; continue;
if (tok->variable() && (Token::Match(tok->astParent(), "?|&&|!|%oror%") || if (tok->variable() && (Token::Match(tok->astParent(), "?|&&|!|%oror%") ||
Token::Match(tok->astParent()->previous(), "if|while ("))) { Token::Match(tok->astParent()->previous(), "if|while ("))) {
const ValueFlow::Value* result = proveNotEqual(tok->values(), 0); std::vector<ValueFlow::Value> result = infer(IntegralInferModel{}, "!=", tok->values(), 0);
if (!result) if (result.size() != 1)
continue; continue;
ValueFlow::Value value = *result; ValueFlow::Value value = result.front();
value.intvalue = 1; value.intvalue = 1;
value.bound = ValueFlow::Value::Bound::Point; value.bound = ValueFlow::Value::Bound::Point;
value.setKnown(); value.setKnown();
setTokenValue(tok, value, settings); setTokenValue(tok, value, settings);
} else if (tok->isComparisonOp()) { } else if (Token::Match(tok, "%comp%|-") && tok->astOperand1() && tok->astOperand2()) {
ValueFlow::Value value{}; std::vector<ValueFlow::Value> result =
std::string op = tok->str(); infer(IntegralInferModel{}, tok->str(), tok->astOperand1()->values(), tok->astOperand2()->values());
if (tok->astOperand1()->hasKnownIntValue()) { for (const ValueFlow::Value& value : result) {
MathLib::bigint val = tok->astOperand1()->values().front().intvalue;
const Token* varTok = tok->astOperand2();
value = inferCondition(tok->str(), val, varTok);
} else if (tok->astOperand2()->hasKnownIntValue()) {
MathLib::bigint val = tok->astOperand2()->values().front().intvalue;
const Token* varTok = tok->astOperand1();
value = inferCondition(tok->str(), varTok, val);
}
if (!value.isKnown())
continue;
setTokenValue(tok, value, settings); setTokenValue(tok, value, settings);
} }
} }
} }
}
struct SymbolicConditionHandler : SimpleConditionHandler { struct SymbolicConditionHandler : SimpleConditionHandler {
virtual std::vector<Condition> parse(const Token* tok, const Settings*) const OVERRIDE virtual std::vector<Condition> parse(const Token* tok, const Settings*) const OVERRIDE

View File

@ -954,7 +954,7 @@ private:
" }\n" " }\n"
" return false;\n" " return false;\n"
"}"); "}");
ASSERT_EQUALS("[test.cpp:2]: (style) Condition 'c!=a' is always false\n", errout.str()); ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (style) Condition 'c!=a' is always false\n", errout.str());
} }
void incorrectLogicOperator2() { void incorrectLogicOperator2() {
@ -3330,7 +3330,7 @@ private:
" if (handle) return 1;\n" " if (handle) return 1;\n"
" else return 0;\n" " else return 0;\n"
"}"); "}");
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:3]: (style) Condition 'handle' is always true\n", errout.str()); ASSERT_EQUALS("[test.cpp:3]: (style) Condition 'handle' is always true\n", errout.str());
check("int f(void *handle) {\n" check("int f(void *handle) {\n"
" if (handle != 0) return 0;\n" " if (handle != 0) return 0;\n"
@ -3393,7 +3393,7 @@ private:
" if(array){}\n" " if(array){}\n"
" }\n" " }\n"
"}"); "}");
ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:5]: (style) Condition 'array' is always true\n", errout.str()); ASSERT_EQUALS("[test.cpp:5]: (style) Condition 'array' is always true\n", errout.str());
check("void f(int *array, int size ) {\n" check("void f(int *array, int size ) {\n"
" for(int i = 0; i < size; ++i) {\n" " for(int i = 0; i < size; ++i) {\n"
@ -3402,7 +3402,7 @@ private:
" else if(array){}\n" " else if(array){}\n"
" }\n" " }\n"
"}"); "}");
ASSERT_EQUALS("[test.cpp:3] -> [test.cpp:5]: (style) Condition 'array' is always true\n", errout.str()); ASSERT_EQUALS("[test.cpp:5]: (style) Condition 'array' is always true\n", errout.str());
// #9277 // #9277
check("int f() {\n" check("int f() {\n"
@ -3770,7 +3770,7 @@ private:
check("void f(int i) {\n" check("void f(int i) {\n"
" if(i < 0 && abs(i) == i) {}\n" " if(i < 0 && abs(i) == i) {}\n"
"}\n"); "}\n");
ASSERT_EQUALS("[test.cpp:2]: (style) Condition 'abs(i)==i' is always false\n", errout.str()); ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:2]: (style) Condition 'abs(i)==i' is always false\n", errout.str());
check("void f(int i) {\n" check("void f(int i) {\n"
" if(i > -3 && abs(i) == i) {}\n" " if(i > -3 && abs(i) == i) {}\n"
@ -3810,7 +3810,7 @@ private:
" if(x == y) {}\n" " if(x == y) {}\n"
" }\n" " }\n"
"}\n"); "}\n");
ASSERT_EQUALS("[test.cpp:4]: (style) Condition 'x==y' is always false\n", errout.str()); ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:4]: (style) Condition 'x==y' is always false\n", errout.str());
check("void f(bool a, bool b) { if (a == b && a && !b){} }"); check("void f(bool a, bool b) { if (a == b && a && !b){} }");
ASSERT_EQUALS("[test.cpp:1] -> [test.cpp:1]: (style) Condition '!b' is always false\n", errout.str()); ASSERT_EQUALS("[test.cpp:1] -> [test.cpp:1]: (style) Condition '!b' is always false\n", errout.str());
@ -3824,7 +3824,7 @@ private:
" if (z < 1) {}\n" " if (z < 1) {}\n"
" }\n" " }\n"
"}\n"); "}\n");
ASSERT_EQUALS("[test.cpp:4]: (style) Condition 'z<1' is always false\n", errout.str()); ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:4]: (style) Condition 'z<1' is always false\n", errout.str());
check("struct a {\n" check("struct a {\n"
" a *b() const;\n" " a *b() const;\n"
@ -4002,6 +4002,13 @@ private:
" if(i<=18) {}\n" " if(i<=18) {}\n"
"}\n"); "}\n");
ASSERT_EQUALS("[test.cpp:3]: (style) Condition 'i<=18' is always true\n", errout.str()); ASSERT_EQUALS("[test.cpp:3]: (style) Condition 'i<=18' is always true\n", errout.str());
check("void f(unsigned int u1, unsigned int u2) {\n"
" if (u1 <= 10 && u2 >= 20) {\n"
" if (u1 != u2) {}\n"
" }\n"
"}\n");
ASSERT_EQUALS("[test.cpp:2] -> [test.cpp:3]: (style) Condition 'u1!=u2' is always true\n", errout.str());
} }
void alwaysTrueContainer() { void alwaysTrueContainer() {