Skip to content
Open
31 changes: 17 additions & 14 deletions lib/astutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1088,29 +1088,32 @@ bool isAliasOf(const Token* tok, const Token* expr, nonneg int* indirect)
if (!tok)
return false;
const Token* r = nullptr;
std::vector<const Token*> aliasValues;
for (const ReferenceToken& ref : tok->refs()) {
const bool pointer = astIsPointer(ref.token);
const bool deref = expr && ref.token != tok && ref.token->isUnaryOp("*") && expr->exprId() == ref.token->astOperand1()->exprId();
aliasValues.clear();
for (const ValueFlow::Value& val : ref.token->values()) {
if (val.isImpossible())
continue;
if (val.isLocalLifetimeValue() || (pointer && val.isSymbolicValue() && val.intvalue == 0))
aliasValues.push_back(val.tokvalue);
}
if (!deref && aliasValues.empty())
continue;
r = findAstNode(expr, [&](const Token* childTok) {
if (childTok->exprId() == 0)
return false;
if (ref.token != tok && expr->exprId() == childTok->exprId() && ref.token->isUnaryOp("*") && expr->exprId() == ref.token->astOperand1()->exprId()) {
if (deref && expr->exprId() == childTok->exprId()) {
if (indirect)
*indirect = 0;
return true;
}
for (const ValueFlow::Value& val : ref.token->values()) {
if (val.isImpossible())
continue;
if (val.isLocalLifetimeValue() || (pointer && val.isSymbolicValue() && val.intvalue == 0)) {
if (findAstNode(val.tokvalue,
[&](const Token* aliasTok) {
return aliasTok != childTok && aliasTok->exprId() == childTok->exprId();
})) {
return true;
}
}
}
return false;
return std::any_of(aliasValues.cbegin(), aliasValues.cend(), [&](const Token* aliasValue) {
return findAstNode(aliasValue, [&](const Token* aliasTok) {
return aliasTok != childTok && aliasTok->exprId() == childTok->exprId();
}) != nullptr;
});
});
if (r)
break;
Expand Down
41 changes: 36 additions & 5 deletions lib/symboldatabase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6952,7 +6952,7 @@ void SymbolDatabase::setValueType(Token* tok, const Variable& var, const SourceL
}
}

static ValueType::Type getEnumType(const Scope* scope, const Platform& platform);
static ValueType::Type getEnumTypeCached(const Scope* scope, const Platform& platform);

void SymbolDatabase::setValueType(Token* tok, const Enumerator& enumerator, const SourceLocation &loc)
{
Expand Down Expand Up @@ -6982,7 +6982,7 @@ void SymbolDatabase::setValueType(Token* tok, const Enumerator& enumerator, cons
setValueType(tok, valuetype);
} else {
valuetype.sign = ValueType::SIGNED;
valuetype.type = getEnumType(enumerator.scope, mSettings.platform);
valuetype.type = getEnumTypeCached(enumerator.scope, mSettings.platform);
setValueType(tok, valuetype);
}
}
Expand Down Expand Up @@ -7547,7 +7547,7 @@ void SymbolDatabase::setValueType(Token* tok, const ValueType& valuetype, const
}
}

static ValueType::Type getEnumType(const Scope* scope, const Platform& platform) // TODO: also determine sign?
static ValueType::Type getEnumType(const Scope* scope, const Platform& platform, bool* complete = nullptr) // TODO: also determine sign?
{
ValueType::Type type = ValueType::Type::INT;
for (const Token* tok = scope->bodyStart; tok && tok != scope->bodyEnd; tok = tok->next()) {
Expand All @@ -7564,6 +7564,9 @@ static ValueType::Type getEnumType(const Scope* scope, const Platform& platform)
if (const ValueType* vt = vTok->valueType()) {
if ((vt->type > type && (vt->type == ValueType::Type::LONG || vt->type == ValueType::Type::LONGLONG)))
type = vt->type;
} else if (complete) {
// the literal does not have a valuetype yet, the result might change later
*complete = false;
}
continue;
}
Expand All @@ -7577,6 +7580,23 @@ static ValueType::Type getEnumType(const Scope* scope, const Platform& platform)
return type;
}

// Cache for getEnumType(), only active during SymbolDatabase::setValueTypeInTokenList()
static thread_local std::unordered_map<const Scope*, ValueType::Type>* enumTypeCache = nullptr;

static ValueType::Type getEnumTypeCached(const Scope* scope, const Platform& platform)
{
if (!enumTypeCache)
return getEnumType(scope, platform);
const auto it = enumTypeCache->find(scope);
if (it != enumTypeCache->end())
return it->second;
bool complete = true;
const ValueType::Type type = getEnumType(scope, platform, &complete);
if (complete)
enumTypeCache->emplace(scope, type);
return type;
}

static const Token* parsedecl(const Token* type,
ValueType* const valuetype,
ValueType::Sign defaultSignedness,
Expand Down Expand Up @@ -7609,7 +7629,7 @@ static const Token* parsedecl(const Token* type,
else if (enum_type->isStandardType())
valuetype->fromLibraryType(enum_type->str(), settings);
} else
valuetype->type = getEnumType(valuetype->typeScope, settings.platform);
valuetype->type = getEnumTypeCached(valuetype->typeScope, settings.platform);
} else
valuetype->type = ValueType::Type::RECORD;
const bool cpp = type->isCpp();
Expand Down Expand Up @@ -7776,7 +7796,7 @@ static const Token* parsedecl(const Token* type,
else if (!valuetype->typeScope && type->type() && type->type()->classScope && valuetype->type != ValueType::Type::SMART_POINTER) {
if (type->type()->classScope->type == ScopeType::eEnum) {
valuetype->sign = ValueType::Sign::SIGNED;
valuetype->type = getEnumType(type->type()->classScope, settings.platform);
valuetype->type = getEnumTypeCached(type->type()->classScope, settings.platform);
} else {
valuetype->type = ValueType::Type::RECORD;
}
Expand Down Expand Up @@ -7966,6 +7986,17 @@ void SymbolDatabase::setValueTypeInTokenList(bool reportDebugWarnings, Token *to
if (!tokens)
tokens = mTokenizer.list.front();

std::unordered_map<const Scope*, ValueType::Type> enumTypes;
struct EnumTypeCacheGuard {
explicit EnumTypeCacheGuard(std::unordered_map<const Scope*, ValueType::Type>* cache) {
enumTypeCache = cache;
}
~EnumTypeCacheGuard() {
enumTypeCache = prev;
}
std::unordered_map<const Scope*, ValueType::Type>* prev{enumTypeCache};
} enumTypeCacheGuard(&enumTypes);

for (Token *tok = tokens; tok; tok = tok->next())
tok->setValueType(nullptr);

Expand Down
21 changes: 19 additions & 2 deletions lib/tokenize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4393,7 +4393,15 @@ void VariableMap::addVariable(const std::string& varname, bool globalNamespace)
/**
* @throws Token* thrown when closing brackets are missing
*/
static bool setVarIdParseDeclaration(Token*& tok, const VariableMap& variableMap, bool executableScope, Standards::cstd_t cStandard)
namespace {
// Result of the last backward search from a comma in setVarIdParseDeclaration()
struct CommaSearchCache {
const Token* comma = nullptr;
const Token* result = nullptr;
};
}

static bool setVarIdParseDeclaration(Token*& tok, const VariableMap& variableMap, bool executableScope, Standards::cstd_t cStandard, CommaSearchCache& commaCache)
{
const Token* const tok1 = tok;
Token* tok2 = tok;
Expand Down Expand Up @@ -4484,11 +4492,19 @@ static bool setVarIdParseDeclaration(Token*& tok, const VariableMap& variableMap
{
const Token *tok3 = tok->previous();
if (tok3 && tok3->str() == ",") {
// In a long comma separated list the search from the previous comma gives the same result
const Token* const comma = tok3;
while (tok3 && !Token::Match(tok3,";|(|[|{")) {
if (tok3 != comma && tok3 == commaCache.comma) {
tok3 = commaCache.result;
break;
}
if (Token::Match(tok3, ")|]"))
tok3 = tok3->link();
tok3 = tok3->previous();
}
commaCache.comma = comma;
commaCache.result = tok3;

if (tok3 && executableScope && Token::Match(tok3->previous(), "%name% (")) {
const Token *fdecl = tok3->previous();
Expand Down Expand Up @@ -4791,6 +4807,7 @@ void Tokenizer::setVarIdPass1()

VariableMap variableMap;
std::map<nonneg int, std::map<std::string, nonneg int>> structMembers;
CommaSearchCache commaCache;

std::stack<VarIdScopeInfo> scopeStack;

Expand Down Expand Up @@ -4991,7 +5008,7 @@ void Tokenizer::setVarIdPass1()
}

try { /* Ticket #8151 */
decl = setVarIdParseDeclaration(tok2, variableMap, scopeStack.top().isExecutable, mSettings.standards.c);
decl = setVarIdParseDeclaration(tok2, variableMap, scopeStack.top().isExecutable, mSettings.standards.c, commaCache);
} catch (const Token * errTok) {
syntaxError(errTok);
}
Expand Down
98 changes: 95 additions & 3 deletions lib/valueflow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
#include <limits>
#include <map>
#include <memory>
#include <numeric>
#include <set>
#include <string>
#include <unordered_map>
Expand Down Expand Up @@ -2007,6 +2008,12 @@ static void valueFlowForwardLifetime(Token * tok, const TokenList &tokenlist, Er
} else if (tok->variable() && tok->variable()->scope()) {
const Variable *var = tok->variable();
const Token *endOfVarScope = var->scope()->bodyEnd;
// Non-local variable: do not forward past the end of the current function
if (!var->isLocal() && !var->isArgument()) {
const Token* endOfFunction = ValueFlow::getEndOfExprScope(tok);
if (endOfFunction && (!endOfVarScope || precedes(endOfFunction, endOfVarScope)))
endOfVarScope = endOfFunction;
}

std::list<ValueFlow::Value> values = tok->values();
Token *nextExpression = nextAfterAstRightmostLeaf(parent);
Expand Down Expand Up @@ -2829,6 +2836,9 @@ static bool isDecayedPointer(const Token *tok)
return false;
if (!tok->astParent())
return false;
// Subscript 'arr[i]': the element is accessed, the array itself is not used as a pointer
if (Token::simpleMatch(tok->astParent(), "[") && astIsLHS(tok))
return false;
if (Token::simpleMatch(tok->astParent(), "=") && astIsRHS(tok))
return true;
if (astIsPointer(tok->astParent()) && !Token::simpleMatch(tok->astParent(), "return"))
Expand Down Expand Up @@ -7268,6 +7278,8 @@ struct ValueFlowPass {
virtual void run(const ValueFlowState& state) const = 0;
// Returns true if pass needs C++
virtual bool cpp() const = 0;
// Returns true if pass does not analyse the skipped functions
virtual bool usesSkippedFunctions() const = 0;
virtual ~ValueFlowPass() noexcept = default;
};

Expand All @@ -7288,24 +7300,53 @@ struct ValueFlowPassRunner {
});
}

bool run(std::initializer_list<ValuePtr<ValueFlowPass>> passes) const
bool run(std::initializer_list<ValuePtr<ValueFlowPass>> passes)
{
std::size_t values = 0;
std::size_t n = state.settings().vfOptions.maxIterations;
const bool doProgress = state.settings().reportProgress >= 0;
// A function is skipped in the next iteration when no value changed in the previous iteration
// and the passes that can skip it did not change it either. Other passes can remove a value that
// a skipping pass adds again; such a function must not be skipped.
const std::set<const Scope*> skippedFunctions = state.skippedFunctions;
const std::vector<const Scope*>& functionScopes = state.symboldatabase().functionScopes;
std::vector<std::size_t> functionValues = getFunctionValues();
std::vector<bool> changedBySkippingPass(functionScopes.size(), false);
bool first = true;
while (n > 0 && values != getTotalValues()) {
if (!first) {
std::vector<std::size_t> newFunctionValues = getFunctionValues();
state.skippedFunctions = skippedFunctions;
for (std::size_t i = 0; i < functionScopes.size(); ++i) {
if (newFunctionValues[i] == functionValues[i] && !changedBySkippingPass[i])
state.skippedFunctions.insert(functionScopes[i]);
}
functionValues = std::move(newFunctionValues);
}
first = false;
changedBySkippingPass.assign(functionScopes.size(), false);
values = getTotalValues();
const std::size_t passnum = state.settings().vfOptions.maxIterations - n + 1;
const std::string passnum_s = std::to_string(passnum);
if (std::any_of(passes.begin(), passes.end(), [&](const ValuePtr<ValueFlowPass>& pass) {
// the string concatination is a hot spot in TestIO::testScanfArgument and TestIO::testPrintfArgumentVariables
std::string stage = doProgress ? std::string("ValueFlow::") + pass->name() + (' ' + passnum_s) : "";
ProgressReporter progressReporter(state.errorLogger, state.settings().reportProgress, state.tokenlist().getSourceFilePath(), std::move(stage));
return run(pass, passnum);
if (!pass->usesSkippedFunctions())
return run(pass, passnum);
const std::vector<std::size_t> before = getFunctionValues(true);
const bool ret = run(pass, passnum);
const std::vector<std::size_t> after = getFunctionValues(true);
for (std::size_t i = 0; i < functionScopes.size(); ++i) {
if (before[i] != after[i])
changedBySkippingPass[i] = true;
}
return ret;
}))
return true;
--n;
}
state.skippedFunctions = skippedFunctions;
if (state.settings().debugwarnings) {
if (n == 0 && values != getTotalValues()) {
ErrorMessage::FileLocation loc(state.tokenlist().getFiles()[0], 0, 0);
Expand Down Expand Up @@ -7344,6 +7385,52 @@ struct ValueFlowPassRunner {
return false;
}

static std::size_t hashValue(const ValueFlow::Value& v)
{
std::size_t h = std::hash<MathLib::bigint>{}(v.intvalue);
const auto combine = [&h](std::size_t x) {
h ^= x + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
};
combine(static_cast<std::size_t>(v.valueType));
combine(static_cast<std::size_t>(v.valueKind));
combine(static_cast<std::size_t>(v.bound));
combine(std::hash<const Token*>{}(v.tokvalue));
combine(std::hash<const Token*>{}(v.condition));
combine(std::hash<double>{}(v.floatValue));
combine(std::hash<MathLib::bigint>{}(v.varvalue));
combine(std::hash<MathLib::bigint>{}(v.path));
combine(v.varId);
combine(static_cast<std::size_t>(v.conditional));
combine(static_cast<std::size_t>(v.moveKind));
combine(static_cast<std::size_t>(v.lifetimeKind));
combine(static_cast<std::size_t>(v.lifetimeScope));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

The hash leaves out indirect, container and a few other fields of ValueFlow::Value. If a pass replaces a value with one that differs only in indirect (for example indirect 0 vs 1 for uninit or null values) or only in container, the function's hash stays the same. The function is then skipped in the next iteration even though its values changed. Consider adding at least indirect and container to the hash.

return h;
}

// Hash of the values in each function scope (same order as symboldatabase.functionScopes)
// onlyAnalysed: the skipped functions get hash 0
std::vector<std::size_t> getFunctionValues(bool onlyAnalysed = false) const
{
const std::vector<const Scope*>& functionScopes = state.symboldatabase().functionScopes;
std::vector<std::size_t> result(functionScopes.size());
for (std::size_t i = 0; i < functionScopes.size(); ++i) {
const Scope* scope = functionScopes[i];
if (!scope->bodyStart)
continue;
if (onlyAnalysed && state.skippedFunctions.count(scope) != 0)
continue;
std::size_t h = 0;
for (const Token* tok = scope->bodyStart; tok != scope->bodyEnd; tok = tok->next()) {
const std::size_t k = (2 * reinterpret_cast<std::uintptr_t>(tok)) + 1;
h = std::accumulate(tok->values().cbegin(), tok->values().cend(), h, [&](std::size_t acc, const ValueFlow::Value& v) {
return acc + (hashValue(v) * k);
});
}
result[i] = h;
}
return result;
}

std::size_t getTotalValues() const
{
std::size_t n = 1;
Expand Down Expand Up @@ -7404,8 +7491,10 @@ template<class F>
struct ValueFlowPassAdaptor : ValueFlowPass {
const char* mName = nullptr;
bool mCPP = false;
bool mUsesSkippedFunctions = false;
F mRun;
ValueFlowPassAdaptor(const char* pname, bool pcpp, F prun) : ValueFlowPass(), mName(pname), mCPP(pcpp), mRun(prun) {}
ValueFlowPassAdaptor(const char* pname, bool pcpp, F prun)
: ValueFlowPass(), mName(pname), mCPP(pcpp), mUsesSkippedFunctions(std::strstr(pname, "skippedFunctions") != nullptr), mRun(prun) {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI review

Whether a pass honours skippedFunctions is found by searching for the text "skippedFunctions" in its stringified name. A pass that is renamed, or that gets the set through a helper without that literal text in the VFA(...) argument, is quietly misclassified. Then changedBySkippingPass is not tracked for it, which brings back the iteration problem that commit 4cc4ad2 fixed. An explicit flag in the VFA macro, such as a VFA_SKIP variant, would be more robust.

const char* name() const override {
return mName;
}
Expand All @@ -7416,6 +7505,9 @@ struct ValueFlowPassAdaptor : ValueFlowPass {
bool cpp() const override {
return mCPP;
}
bool usesSkippedFunctions() const override {
return mUsesSkippedFunctions;
}
};

template<class F>
Expand Down
Loading
Loading