Add library configurations for free functions like std::size, std::empty, etc (#3410)

This commit is contained in:
Paul Fultz II 2021-08-23 02:03:48 -05:00 committed by GitHub
parent 4ff0db1ec4
commit 865163b2ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 381 additions and 150 deletions

View File

@ -542,7 +542,7 @@ $(libcppdir)/platform.o: lib/platform.cpp externals/tinyxml2/tinyxml2.h lib/conf
$(libcppdir)/preprocessor.o: lib/preprocessor.cpp externals/simplecpp/simplecpp.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/preprocessor.h lib/settings.h lib/standards.h lib/suppressions.h lib/timer.h lib/utils.h
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/preprocessor.o $(libcppdir)/preprocessor.cpp
$(libcppdir)/programmemory.o: lib/programmemory.cpp lib/astutils.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/programmemory.h lib/standards.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/utils.h lib/valueflow.h
$(libcppdir)/programmemory.o: lib/programmemory.cpp lib/astutils.h lib/config.h lib/errortypes.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.h lib/programmemory.h lib/settings.h lib/standards.h lib/suppressions.h lib/symboldatabase.h lib/templatesimplifier.h lib/timer.h lib/token.h lib/utils.h lib/valueflow.h
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CPPFILESDIR) $(CXXFLAGS) $(UNDEF_STRICT_ANSI) -c -o $(libcppdir)/programmemory.o $(libcppdir)/programmemory.cpp
$(libcppdir)/reverseanalyzer.o: lib/reverseanalyzer.cpp lib/analyzer.h lib/astutils.h lib/config.h lib/errortypes.h lib/forwardanalyzer.h lib/importproject.h lib/library.h lib/mathlib.h lib/platform.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/utils.h lib/valueflow.h lib/valueptr.h

View File

@ -109,6 +109,21 @@
</choice>
</element>
</optional>
<optional>
<element name="container">
<optional>
<attribute name="action">
<ref name="CONTAINER-ACTION"/>
</attribute>
</optional>
<optional>
<attribute name="yields">
<ref name="CONTAINER-YIELDS"/>
</attribute>
</optional>
<empty/>
</element>
</optional>
<optional>
<element name="pure"><empty/></element>
</optional>

View File

@ -7772,6 +7772,45 @@ initializer list (7) string& replace (const_iterator i1, const_iterator i2, init
<not-uninit/>
</arg>
</function>
<function name="std::size">
<noreturn>false</noreturn>
<use-retval/>
<leak-ignore/>
<container yields="size"/>
<returnValue type="size_t"/>
<arg nr="1" direction="in">
<not-uninit/>
</arg>
</function>
<function name="std::ssize">
<noreturn>false</noreturn>
<use-retval/>
<leak-ignore/>
<container yields="size"/>
<returnValue type="long"/>
<arg nr="1" direction="in">
<not-uninit/>
</arg>
</function>
<function name="std::empty">
<noreturn>false</noreturn>
<use-retval/>
<leak-ignore/>
<container yields="empty"/>
<returnValue type="bool"/>
<arg nr="1" direction="in">
<not-uninit/>
</arg>
</function>
<function name="std::data">
<noreturn>false</noreturn>
<use-retval/>
<leak-ignore/>
<container yields="buffer"/>
<arg nr="1" direction="in">
<not-uninit/>
</arg>
</function>
<!-- template< class F, class... Args > /*unspecified*/ bind( F&& f, Args&&... args ); // since C++11 -->
<!-- template< class R, class F, class... Args > /*unspecified*/ bind( F&& f, Args&&... args ); // since C++11 -->
<function name="std::bind">

View File

@ -34,6 +34,7 @@
#include <functional>
#include <iterator>
#include <list>
#include <set>
#include <stack>
#include <utility>
@ -2065,7 +2066,7 @@ bool isVariableChanged(const Token *start, const Token *end, int indirect, const
return findVariableChanged(start, end, indirect, exprid, globalvar, settings, cpp, depth) != nullptr;
}
static const Token* findExpression(const Token* start, const nonneg int exprid)
const Token* findExpression(const Token* start, const nonneg int exprid)
{
Function * f = Scope::nestedInFunction(start->scope());
if (!f)
@ -2096,6 +2097,58 @@ static std::function<R()> memoize(F f)
};
}
template<class F,
REQUIRES("F must be a function that returns a Token class",
std::is_convertible<decltype(std::declval<F>()()), const Token*> )>
static bool isExpressionChangedAt(const F& getExprTok,
const Token* tok,
int indirect,
const nonneg int exprid,
bool globalvar,
const Settings* settings,
bool cpp,
int depth)
{
if (tok->exprId() != exprid) {
if (globalvar && Token::Match(tok, "%name% ("))
// TODO: Is global variable really changed by function call?
return true;
// Is aliased function call or alias passed to function
if ((Token::Match(tok, "%var% (") || isVariableChangedByFunctionCall(tok, 1, settings)) &&
std::any_of(tok->values().begin(), tok->values().end(), std::mem_fn(&ValueFlow::Value::isLifetimeValue))) {
bool aliased = false;
// If we can't find the expression then assume it was modified
if (!getExprTok())
return true;
visitAstNodes(getExprTok(), [&](const Token* childTok) {
if (childTok->varId() > 0 && isAliasOf(tok, childTok->varId())) {
aliased = true;
return ChildrenToVisit::done;
}
return ChildrenToVisit::op1_and_op2;
});
// TODO: Try to traverse the lambda function
if (aliased)
return true;
}
return false;
}
return (isVariableChanged(tok, indirect, settings, cpp, depth));
}
bool isExpressionChangedAt(const Token* expr,
const Token* tok,
int indirect,
bool globalvar,
const Settings* settings,
bool cpp,
int depth)
{
return isExpressionChangedAt([&] {
return expr;
}, tok, indirect, expr->exprId(), globalvar, settings, cpp, depth);
}
Token* findVariableChanged(Token *start, const Token *end, int indirect, const nonneg int exprid, bool globalvar, const Settings *settings, bool cpp, int depth)
{
if (!precedes(start, end))
@ -2106,31 +2159,7 @@ Token* findVariableChanged(Token *start, const Token *end, int indirect, const n
return findExpression(start, exprid);
});
for (Token *tok = start; tok != end; tok = tok->next()) {
if (tok->exprId() != exprid) {
if (globalvar && Token::Match(tok, "%name% ("))
// TODO: Is global variable really changed by function call?
return tok;
// Is aliased function call or alias passed to function
if ((Token::Match(tok, "%var% (") || isVariableChangedByFunctionCall(tok, 1, settings)) &&
std::any_of(tok->values().begin(), tok->values().end(), std::mem_fn(&ValueFlow::Value::isLifetimeValue))) {
bool aliased = false;
// If we can't find the expression then assume it was modified
if (!getExprTok())
return tok;
visitAstNodes(getExprTok(), [&](const Token* childTok) {
if (childTok->varId() > 0 && isAliasOf(tok, childTok->varId())) {
aliased = true;
return ChildrenToVisit::done;
}
return ChildrenToVisit::op1_and_op2;
});
// TODO: Try to traverse the lambda function
if (aliased)
return tok;
}
continue;
}
if (isVariableChanged(tok, indirect, settings, cpp, depth))
if (isExpressionChangedAt(getExprTok, tok, indirect, exprid, globalvar, settings, cpp, depth))
return tok;
}
return nullptr;
@ -2223,10 +2252,14 @@ bool isExpressionChanged(const Token* expr, const Token* start, const Token* end
return false;
global = !tok->variable()->isLocal() && !tok->variable()->isArgument();
}
if (tok->exprId() > 0 &&
isVariableChanged(
start, end, tok->valueType() ? tok->valueType()->pointer : 0, tok->exprId(), global, settings, cpp, depth))
return true;
if (tok->exprId() > 0) {
for (const Token* tok2 = start; tok2 != end; tok2 = tok2->next()) {
if (isExpressionChangedAt(
tok, tok2, tok->valueType() ? tok->valueType()->pointer : 0, global, settings, cpp, depth))
return true;
}
}
return false;
});
return result;

View File

@ -55,6 +55,7 @@ const Token* findExpression(const nonneg int exprid,
const Token* start,
const Token* end,
const std::function<bool(const Token*)>& pred);
const Token* findExpression(const Token* start, const nonneg int exprid);
std::vector<const Token*> astFlatten(const Token* tok, const char* op);
@ -241,6 +242,14 @@ bool isExpressionChanged(const Token* expr,
bool cpp,
int depth = 20);
bool isExpressionChangedAt(const Token* expr,
const Token* tok,
int indirect,
bool globalvar,
const Settings* settings,
bool cpp,
int depth = 20);
/// If token is an alias if another variable
bool isAliasOf(const Token *tok, nonneg int varid, bool* inconclusive = nullptr);

View File

@ -135,6 +135,55 @@ Library::Error Library::load(const char exename[], const char path[])
}
}
Library::Container::Yield Library::Container::yieldFrom(const std::string& yieldName)
{
if (yieldName == "at_index")
return Container::Yield::AT_INDEX;
else if (yieldName == "item")
return Container::Yield::ITEM;
else if (yieldName == "buffer")
return Container::Yield::BUFFER;
else if (yieldName == "buffer-nt")
return Container::Yield::BUFFER_NT;
else if (yieldName == "start-iterator")
return Container::Yield::START_ITERATOR;
else if (yieldName == "end-iterator")
return Container::Yield::END_ITERATOR;
else if (yieldName == "iterator")
return Container::Yield::ITERATOR;
else if (yieldName == "size")
return Container::Yield::SIZE;
else if (yieldName == "empty")
return Container::Yield::EMPTY;
else
return Container::Yield::NO_YIELD;
}
Library::Container::Action Library::Container::actionFrom(const std::string& actionName)
{
if (actionName == "resize")
return Container::Action::RESIZE;
else if (actionName == "clear")
return Container::Action::CLEAR;
else if (actionName == "push")
return Container::Action::PUSH;
else if (actionName == "pop")
return Container::Action::POP;
else if (actionName == "find")
return Container::Action::FIND;
else if (actionName == "insert")
return Container::Action::INSERT;
else if (actionName == "erase")
return Container::Action::ERASE;
else if (actionName == "change-content")
return Container::Action::CHANGE_CONTENT;
else if (actionName == "change-internal")
return Container::Action::CHANGE_INTERNAL;
else if (actionName == "change")
return Container::Action::CHANGE;
else
return Container::Action::NO_ACTION;
}
bool Library::loadxmldata(const char xmldata[], std::size_t len)
{
tinyxml2::XMLDocument doc;
@ -408,27 +457,8 @@ Library::Error Library::load(const tinyxml2::XMLDocument &doc)
Container::Action action = Container::Action::NO_ACTION;
if (action_ptr) {
std::string actionName = action_ptr;
if (actionName == "resize")
action = Container::Action::RESIZE;
else if (actionName == "clear")
action = Container::Action::CLEAR;
else if (actionName == "push")
action = Container::Action::PUSH;
else if (actionName == "pop")
action = Container::Action::POP;
else if (actionName == "find")
action = Container::Action::FIND;
else if (actionName == "insert")
action = Container::Action::INSERT;
else if (actionName == "erase")
action = Container::Action::ERASE;
else if (actionName == "change-content")
action = Container::Action::CHANGE_CONTENT;
else if (actionName == "change-internal")
action = Container::Action::CHANGE_INTERNAL;
else if (actionName == "change")
action = Container::Action::CHANGE;
else
action = Container::actionFrom(actionName);
if (action == Container::Action::NO_ACTION)
return Error(ErrorCode::BAD_ATTRIBUTE_VALUE, actionName);
}
@ -436,25 +466,8 @@ Library::Error Library::load(const tinyxml2::XMLDocument &doc)
Container::Yield yield = Container::Yield::NO_YIELD;
if (yield_ptr) {
std::string yieldName = yield_ptr;
if (yieldName == "at_index")
yield = Container::Yield::AT_INDEX;
else if (yieldName == "item")
yield = Container::Yield::ITEM;
else if (yieldName == "buffer")
yield = Container::Yield::BUFFER;
else if (yieldName == "buffer-nt")
yield = Container::Yield::BUFFER_NT;
else if (yieldName == "start-iterator")
yield = Container::Yield::START_ITERATOR;
else if (yieldName == "end-iterator")
yield = Container::Yield::END_ITERATOR;
else if (yieldName == "iterator")
yield = Container::Yield::ITERATOR;
else if (yieldName == "size")
yield = Container::Yield::SIZE;
else if (yieldName == "empty")
yield = Container::Yield::EMPTY;
else
yield = Container::yieldFrom(yieldName);
if (yield == Container::Yield::NO_YIELD)
return Error(ErrorCode::BAD_ATTRIBUTE_VALUE, yieldName);
}
@ -835,6 +848,26 @@ Library::Error Library::loadFunction(const tinyxml2::XMLElement * const node, co
}
functionwarn[name] = wi;
} else if (functionnodename == "container") {
const char* const action_ptr = functionnode->Attribute("action");
Container::Action action = Container::Action::NO_ACTION;
if (action_ptr) {
std::string actionName = action_ptr;
action = Container::actionFrom(actionName);
if (action == Container::Action::NO_ACTION)
return Error(ErrorCode::BAD_ATTRIBUTE_VALUE, actionName);
}
func.containerAction = action;
const char* const yield_ptr = functionnode->Attribute("yields");
Container::Yield yield = Container::Yield::NO_YIELD;
if (yield_ptr) {
std::string yieldName = yield_ptr;
yield = Container::yieldFrom(yieldName);
if (yield == Container::Yield::NO_YIELD)
return Error(ErrorCode::BAD_ATTRIBUTE_VALUE, yieldName);
}
func.containerYield = yield;
} else
unknown_elements.insert(functionnodename);
}
@ -1508,6 +1541,53 @@ bool Library::isimporter(const std::string& file, const std::string &importer) c
return (it != mImporters.end() && it->second.count(importer) > 0);
}
const Token* Library::getContainerFromYield(const Token* tok, Library::Container::Yield yield) const
{
if (!tok)
return nullptr;
if (Token::Match(tok->tokAt(-2), ". %name% (")) {
const Token* containerTok = tok->tokAt(-2)->astOperand1();
if (!astIsContainer(containerTok))
return nullptr;
if (containerTok->valueType()->container &&
containerTok->valueType()->container->getYield(tok->strAt(-1)) == yield)
return containerTok;
if (yield == Library::Container::Yield::EMPTY && Token::simpleMatch(tok->tokAt(-1), "empty ( )"))
return containerTok;
if (yield == Library::Container::Yield::SIZE && Token::Match(tok->tokAt(-1), "size|length ( )"))
return containerTok;
} else if (Token::Match(tok->previous(), "%name% (")) {
if (const Library::Function* f = this->getFunction(tok->previous())) {
if (f->containerYield == yield) {
return tok->astOperand2();
}
}
}
return nullptr;
}
const Token* Library::getContainerFromAction(const Token* tok, Library::Container::Action action) const
{
if (!tok)
return nullptr;
if (Token::Match(tok->tokAt(-2), ". %name% (")) {
const Token* containerTok = tok->tokAt(-2)->astOperand1();
if (!astIsContainer(containerTok))
return nullptr;
if (containerTok->valueType()->container &&
containerTok->valueType()->container->getAction(tok->strAt(-1)) == action)
return containerTok;
if (Token::simpleMatch(tok->tokAt(-1), "empty ( )"))
return containerTok;
} else if (Token::Match(tok->previous(), "%name% (")) {
if (const Library::Function* f = this->getFunction(tok->previous())) {
if (f->containerAction == action) {
return tok->astOperand2();
}
}
}
return nullptr;
}
bool Library::isSmartPointer(const Token* tok) const
{
return detectSmartPointer(tok);

View File

@ -252,6 +252,9 @@ public:
return i->second.yield;
return Yield::NO_YIELD;
}
static Yield yieldFrom(const std::string& yieldName);
static Action actionFrom(const std::string& actionName);
};
std::map<std::string, Container> containers;
const Container* detectContainer(const Token* typeStart, bool iterator = false) const;
@ -320,7 +323,21 @@ public:
bool formatstr;
bool formatstr_scan;
bool formatstr_secure;
Function() : use(false), leakignore(false), isconst(false), ispure(false), useretval(UseRetValType::NONE), ignore(false), formatstr(false), formatstr_scan(false), formatstr_secure(false) {}
Container::Action containerAction;
Container::Yield containerYield;
Function()
: use(false),
leakignore(false),
isconst(false),
ispure(false),
useretval(UseRetValType::NONE),
ignore(false),
formatstr(false),
formatstr_scan(false),
formatstr_secure(false),
containerAction(Container::Action::NO_ACTION),
containerYield(Container::Yield::NO_YIELD)
{}
};
const Function *getFunction(const Token *ftok) const;
@ -417,6 +434,9 @@ public:
bool isimporter(const std::string& file, const std::string &importer) const;
const Token* getContainerFromYield(const Token* tok, Container::Yield yield) const;
const Token* getContainerFromAction(const Token* tok, Container::Action action) const;
bool isreflection(const std::string &token) const {
return mReflection.find(token) != mReflection.end();
}

View File

@ -3,6 +3,7 @@
#include "astutils.h"
#include "mathlib.h"
#include "symboldatabase.h"
#include "settings.h"
#include "token.h"
#include "valueflow.h"
#include <algorithm>
@ -152,36 +153,6 @@ bool conditionIsTrue(const Token *condition, const ProgramMemory &programMemory)
return !error && result == 1;
}
static const Token* getContainerFromEmpty(const Token* tok)
{
if (!Token::Match(tok->tokAt(-2), ". %name% ("))
return nullptr;
const Token* containerTok = tok->tokAt(-2)->astOperand1();
if (!astIsContainer(containerTok))
return nullptr;
if (containerTok->valueType()->container &&
containerTok->valueType()->container->getYield(tok->strAt(-1)) == Library::Container::Yield::EMPTY)
return containerTok;
if (Token::simpleMatch(tok->tokAt(-1), "empty ( )"))
return containerTok;
return nullptr;
}
static const Token* getContainerFromSize(const Token* tok)
{
if (!Token::Match(tok->tokAt(-2), ". %name% ("))
return nullptr;
const Token* containerTok = tok->tokAt(-2)->astOperand1();
if (!astIsContainer(containerTok))
return nullptr;
if (containerTok->valueType()->container &&
containerTok->valueType()->container->getYield(tok->strAt(-1)) == Library::Container::Yield::SIZE)
return containerTok;
if (Token::Match(tok->tokAt(-1), "size|length ( )"))
return containerTok;
return nullptr;
}
void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, const Token* endTok, const Settings* settings, bool then)
{
if (Token::Match(tok, "==|>=|<=|<|>|!=")) {
@ -212,7 +183,7 @@ void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, const Toke
bool impossible = (tok->str() == "==" && !then) || (tok->str() == "!=" && then);
if (!impossible)
pm.setIntValue(vartok->exprId(), then ? truevalue.intvalue : falsevalue.intvalue);
const Token* containerTok = getContainerFromSize(vartok);
const Token* containerTok = settings->library.getContainerFromYield(vartok, Library::Container::Yield::SIZE);
if (containerTok)
pm.setContainerSizeValue(containerTok->exprId(), then ? truevalue.intvalue : falsevalue.intvalue, !impossible);
} else if (Token::simpleMatch(tok, "!")) {
@ -229,7 +200,7 @@ void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, const Toke
if (endTok && isExpressionChanged(tok, tok->next(), endTok, settings, true))
return;
pm.setIntValue(tok->exprId(), then);
const Token* containerTok = getContainerFromEmpty(tok);
const Token* containerTok = settings->library.getContainerFromYield(tok, Library::Container::Yield::EMPTY);
if (containerTok)
pm.setContainerSizeValue(containerTok->exprId(), 0, then);
}
@ -347,6 +318,8 @@ static ProgramMemory getInitialProgramState(const Token* tok,
return pm;
}
ProgramMemoryState::ProgramMemoryState(const Settings* s) : state(), origins(), settings(s) {}
void ProgramMemoryState::insert(const ProgramMemory &pm, const Token* origin)
{
if (origin)
@ -366,7 +339,7 @@ void ProgramMemoryState::replace(const ProgramMemory &pm, const Token* origin)
void ProgramMemoryState::addState(const Token* tok, const ProgramMemory::Map& vars)
{
ProgramMemory pm = state;
fillProgramMemoryFromConditions(pm, tok, nullptr);
fillProgramMemoryFromConditions(pm, tok, settings);
for (const auto& p:vars) {
nonneg int exprid = p.first;
const ValueFlow::Value &value = p.second;
@ -385,7 +358,7 @@ void ProgramMemoryState::assume(const Token* tok, bool b, bool isEmpty)
if (isEmpty)
pm.setContainerSizeValue(tok->exprId(), 0, b);
else
programMemoryParseCondition(pm, tok, nullptr, nullptr, b);
programMemoryParseCondition(pm, tok, nullptr, settings, b);
const Token* origin = tok;
const Token* top = tok->astTop();
if (top && Token::Match(top->previous(), "for|while ("))
@ -396,7 +369,9 @@ void ProgramMemoryState::assume(const Token* tok, bool b, bool isEmpty)
void ProgramMemoryState::removeModifiedVars(const Token* tok)
{
for (auto i = state.values.begin(), last = state.values.end(); i != last;) {
if (isVariableChanged(origins[i->first], tok, i->first, false, nullptr, true)) {
const Token* start = origins[i->first];
const Token* expr = findExpression(start ? start : tok, i->first);
if (!expr || isExpressionChanged(expr, start, tok, settings, true)) {
origins.erase(i->first);
i = state.values.erase(i);
} else {

View File

@ -2,6 +2,7 @@
#define GUARD_PROGRAMMEMORY_H
#include "mathlib.h"
#include "settings.h"
#include "utils.h"
#include "valueflow.h" // needed for alias
#include <functional>
@ -45,6 +46,9 @@ void programMemoryParseCondition(ProgramMemory& pm, const Token* tok, const Toke
struct ProgramMemoryState {
ProgramMemory state;
std::map<nonneg int, const Token*> origins;
const Settings* settings;
explicit ProgramMemoryState(const Settings* s);
void insert(const ProgramMemory &pm, const Token* origin = nullptr);
void replace(const ProgramMemory &pm, const Token* origin = nullptr);

View File

@ -271,7 +271,6 @@ const Token *parseCompareInt(const Token *tok, ValueFlow::Value &true_value, Val
});
}
static bool isEscapeScope(const Token* tok, TokenList * tokenlist, bool unknown = false)
{
if (!Token::simpleMatch(tok, "{"))
@ -580,6 +579,19 @@ static void setTokenValue(Token* tok, ValueFlow::Value value, const Settings* se
v.valueType = ValueFlow::Value::ValueType::INT;
setTokenValue(parent->astParent(), v, settings);
}
} else if (Token::Match(parent->previous(), "%name% (")) {
if (const Library::Function* f = settings->library.getFunction(parent->previous())) {
if (f->containerYield == Library::Container::Yield::SIZE) {
ValueFlow::Value v(value);
v.valueType = ValueFlow::Value::ValueType::INT;
setTokenValue(parent, v, settings);
} else if (f->containerYield == Library::Container::Yield::EMPTY) {
ValueFlow::Value v(value);
v.intvalue = !v.intvalue;
v.valueType = ValueFlow::Value::ValueType::INT;
setTokenValue(parent, v, settings);
}
}
}
return;
@ -1949,9 +1961,9 @@ struct ValueFlowAnalyzer : Analyzer {
const TokenList* tokenlist;
ProgramMemoryState pms;
ValueFlowAnalyzer() : tokenlist(nullptr), pms() {}
ValueFlowAnalyzer() : tokenlist(nullptr), pms(nullptr) {}
explicit ValueFlowAnalyzer(const TokenList* t) : tokenlist(t), pms() {}
explicit ValueFlowAnalyzer(const TokenList* t) : tokenlist(t), pms(tokenlist->getSettings()) {}
virtual const ValueFlow::Value* getValue(const Token* tok) const = 0;
virtual ValueFlow::Value* getValue(const Token* tok) = 0;
@ -6571,36 +6583,15 @@ static void valueFlowUninit(TokenList* tokenlist, SymbolDatabase* /*symbolDataba
}
}
static bool isContainerSize(const Token* tok)
{
if (!Token::Match(tok, "%var% . %name% ("))
return false;
if (!astIsContainer(tok))
return false;
if (tok->valueType()->container && tok->valueType()->container->getYield(tok->strAt(2)) == Library::Container::Yield::SIZE)
return true;
if (Token::Match(tok->tokAt(2), "size|length ( )"))
return true;
return false;
}
static bool isContainerSizeChanged(const Token* tok, const Settings* settings = nullptr, int depth = 20);
static bool isContainerEmpty(const Token* tok)
{
if (!Token::Match(tok, "%var% . %name% ("))
return false;
if (!astIsContainer(tok))
return false;
if (tok->valueType()->container && tok->valueType()->container->getYield(tok->strAt(2)) == Library::Container::Yield::EMPTY)
return true;
if (Token::simpleMatch(tok->tokAt(2), "empty ( )"))
return true;
return false;
}
static bool isContainerSizeChanged(const Token *tok, int depth=20);
static bool isContainerSizeChanged(nonneg int varId,
const Token* start,
const Token* end,
const Settings* settings = nullptr,
int depth = 20);
static bool isContainerSizeChanged(nonneg int varId, const Token *start, const Token *end, int depth = 20);
static bool isContainerSizeChangedByFunction(const Token *tok, int depth = 20)
static bool isContainerSizeChangedByFunction(const Token* tok, const Settings* settings = nullptr, int depth = 20)
{
if (!tok->valueType() || !tok->valueType()->container)
return false;
@ -6636,7 +6627,8 @@ static bool isContainerSizeChangedByFunction(const Token *tok, int depth = 20)
if (!arg->nameToken())
return false;
if (depth > 0)
return isContainerSizeChanged(arg->declarationId(), scope->bodyStart, scope->bodyEnd, depth - 1);
return isContainerSizeChanged(
arg->declarationId(), scope->bodyStart, scope->bodyEnd, settings, depth - 1);
}
// Don't know => Safe guess
return true;
@ -6644,7 +6636,7 @@ static bool isContainerSizeChangedByFunction(const Token *tok, int depth = 20)
}
bool inconclusive = false;
const bool isChanged = isVariableChangedByFunctionCall(tok, 0, nullptr, &inconclusive);
const bool isChanged = isVariableChangedByFunctionCall(tok, 0, settings, &inconclusive);
return (isChanged || inconclusive);
}
@ -6731,7 +6723,7 @@ struct ContainerExpressionAnalyzer : ExpressionAnalyzer {
return Action::Invalid;
if (isLikelyStreamRead(isCPP(), tok->astParent()))
return Action::Invalid;
if (astIsContainer(tok) && isContainerSizeChanged(tok))
if (astIsContainer(tok) && isContainerSizeChanged(tok, getSettings()))
return Action::Invalid;
return read;
}
@ -6781,7 +6773,7 @@ static void valueFlowContainerReverse(Token* tok,
}
}
static bool isContainerSizeChanged(const Token *tok, int depth)
static bool isContainerSizeChanged(const Token* tok, const Settings* settings, int depth)
{
if (!tok)
return false;
@ -6811,17 +6803,21 @@ static bool isContainerSizeChanged(const Token *tok, int depth)
break;
}
}
if (isContainerSizeChangedByFunction(tok, depth))
if (isContainerSizeChangedByFunction(tok, settings, depth))
return true;
return false;
}
static bool isContainerSizeChanged(nonneg int varId, const Token *start, const Token *end, int depth)
static bool isContainerSizeChanged(nonneg int varId,
const Token* start,
const Token* end,
const Settings* settings,
int depth)
{
for (const Token *tok = start; tok != end; tok = tok->next()) {
if (tok->varId() != varId)
continue;
if (isContainerSizeChanged(tok, depth))
if (isContainerSizeChanged(tok, settings, depth))
return true;
}
return false;
@ -7149,14 +7145,15 @@ struct ContainerConditionHandler : ConditionHandler {
return valueFlowContainerReverse(start, endTok, exprTok, values, tokenlist, settings);
}
virtual std::vector<Condition> parse(const Token* tok, const Settings*) const OVERRIDE {
virtual std::vector<Condition> parse(const Token* tok, const Settings* settings) const OVERRIDE
{
Condition cond;
ValueFlow::Value true_value;
ValueFlow::Value false_value;
const Token *vartok = parseCompareInt(tok, true_value, false_value);
if (vartok) {
vartok = vartok->tokAt(-3);
if (!isContainerSize(vartok))
vartok = settings->library.getContainerFromYield(vartok, Library::Container::Yield::SIZE);
if (!vartok)
return {};
true_value.valueType = ValueFlow::Value::ValueType::CONTAINER_SIZE;
false_value.valueType = ValueFlow::Value::ValueType::CONTAINER_SIZE;
@ -7168,9 +7165,9 @@ struct ContainerConditionHandler : ConditionHandler {
// Empty check
if (tok->str() == "(") {
vartok = tok->tokAt(-3);
vartok = settings->library.getContainerFromYield(tok, Library::Container::Yield::EMPTY);
// TODO: Handle .size()
if (!isContainerEmpty(vartok))
if (!vartok)
return {};
const Token *parent = tok->astParent();
while (parent) {

View File

@ -263,6 +263,21 @@ printf - format string follows the printf rules
scanf - format string follows the scanf rules
### Container inputs
If this is a free function for containers(like for `std::size` or `std::erase_if`) then the `<container>` tag can be used to specify the `yield` or `action`. Here is an example of `std::size`:
<function name="std::size">
<noreturn>false</noreturn>
<use-retval/>
<leak-ignore/>
<container yields="size"/>
<returnValue type="size_t"/>
<arg nr="1" direction="in">
<not-uninit/>
</arg>
</function>
### Value range
The valid values can be defined. Imagine:

View File

@ -2395,6 +2395,15 @@ private:
" if (*i == 1) {}\n"
"}\n");
ASSERT_EQUALS("", errout.str());
check("bool h(int*);\n"
"void f(int* x) {\n"
" int* i = x;\n"
" if (h(i))\n"
" i = nullptr;\n"
" if (h(i) && *i == 1) {}\n"
"}\n");
ASSERT_EQUALS("", errout.str());
}
void nullpointer78() // #7802

View File

@ -4924,6 +4924,13 @@ private:
"}\n";
ASSERT_EQUALS("", isKnownContainerSizeValue(tokenValues(code, "v . front"), 0));
code = "void f(const std::vector<std::string>& v) {\n"
" if(std::empty(v)) {\n"
" v.front();\n"
" }\n"
"}\n";
ASSERT_EQUALS("", isKnownContainerSizeValue(tokenValues(code, "v . front"), 0));
code = "void f(const std::vector<std::string>& v) {\n"
" if(!v.empty()) {\n"
" v.front();\n"
@ -5455,6 +5462,34 @@ private:
ASSERT_EQUALS(false, testValueOfXKnown(code, 4U, 1));
ASSERT_EQUALS(false, testValueOfXImpossible(code, 4U, 0));
code = "void f() {\n"
" std::vector<int> v;\n"
" int x = v.size();\n"
" return x;\n"
"}\n";
ASSERT_EQUALS(true, testValueOfXKnown(code, 4U, 0));
code = "void f() {\n"
" std::vector<int> v;\n"
" int x = v.empty();\n"
" return x;\n"
"}\n";
ASSERT_EQUALS(true, testValueOfXKnown(code, 4U, 1));
code = "void f() {\n"
" std::vector<int> v;\n"
" int x = std::size(v);\n"
" return x;\n"
"}\n";
ASSERT_EQUALS(true, testValueOfXKnown(code, 4U, 0));
code = "void f() {\n"
" std::vector<int> v;\n"
" int x = std::empty(v);\n"
" return x;\n"
"}\n";
ASSERT_EQUALS(true, testValueOfXKnown(code, 4U, 1));
code = "bool f() {\n"
" std::list<int> x1;\n"
" std::list<int> x2;\n"