Handle for loop conditions in afterCondition (#3561)

This commit is contained in:
Paul Fultz II 2021-11-14 11:30:36 -06:00 committed by GitHub
parent 112363c9d1
commit a0d3c2c719
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 253 additions and 158 deletions

46
lib/astutils.cpp Executable file → Normal file
View File

@ -595,6 +595,38 @@ static T* getCondTokFromEndImpl(T* endBlock)
return nullptr; return nullptr;
} }
template<class T, REQUIRES("T must be a Token class", std::is_convertible<T*, const Token*> )>
static T* getInitTokImpl(T* tok)
{
if (!tok)
return nullptr;
if (Token::Match(tok, "%name% ("))
return getInitTokImpl(tok->next());
if (tok->str() != "(")
return nullptr;
if (!Token::simpleMatch(tok->astOperand2(), ";"))
return nullptr;
if (Token::simpleMatch(tok->astOperand2()->astOperand1(), ";"))
return nullptr;
return tok->astOperand2()->astOperand1();
}
template<class T, REQUIRES("T must be a Token class", std::is_convertible<T*, const Token*> )>
static T* getStepTokImpl(T* tok)
{
if (!tok)
return nullptr;
if (Token::Match(tok, "%name% ("))
return getStepTokImpl(tok->next());
if (tok->str() != "(")
return nullptr;
if (!Token::simpleMatch(tok->astOperand2(), ";"))
return nullptr;
if (!Token::simpleMatch(tok->astOperand2()->astOperand2(), ";"))
return nullptr;
return tok->astOperand2()->astOperand2()->astOperand2();
}
Token* getCondTok(Token* tok) Token* getCondTok(Token* tok)
{ {
return getCondTokImpl(tok); return getCondTokImpl(tok);
@ -613,6 +645,20 @@ const Token* getCondTokFromEnd(const Token* endBlock)
return getCondTokFromEndImpl(endBlock); return getCondTokFromEndImpl(endBlock);
} }
Token* getInitTok(Token* tok) {
return getInitTokImpl(tok);
}
const Token* getInitTok(const Token* tok) {
return getInitTokImpl(tok);
}
Token* getStepTok(Token* tok) {
return getStepTokImpl(tok);
}
const Token* getStepTok(const Token* tok) {
return getStepTokImpl(tok);
}
const Token *findNextTokenFromBreak(const Token *breakToken) const Token *findNextTokenFromBreak(const Token *breakToken)
{ {
const Scope *scope = breakToken->scope(); const Scope *scope = breakToken->scope();

View File

@ -126,6 +126,12 @@ bool astIsRHS(const Token* tok);
Token* getCondTok(Token* tok); Token* getCondTok(Token* tok);
const Token* getCondTok(const Token* tok); const Token* getCondTok(const Token* tok);
Token* getInitTok(Token* tok);
const Token* getInitTok(const Token* tok);
Token* getStepTok(Token* tok);
const Token* getStepTok(const Token* tok);
Token* getCondTokFromEnd(Token* endBlock); Token* getCondTokFromEnd(Token* endBlock);
const Token* getCondTokFromEnd(const Token* endBlock); const Token* getCondTokFromEnd(const Token* endBlock);

View File

@ -784,34 +784,6 @@ struct ForwardTraversal {
return parent && (parent->str() == ":" || parent->astOperand2() == tok); return parent && (parent->str() == ":" || parent->astOperand2() == tok);
} }
static Token* getInitTok(Token* tok) {
if (!tok)
return nullptr;
if (Token::Match(tok, "%name% ("))
return getInitTok(tok->next());
if (tok->str() != "(")
return nullptr;
if (!Token::simpleMatch(tok->astOperand2(), ";"))
return nullptr;
if (Token::simpleMatch(tok->astOperand2()->astOperand1(), ";"))
return nullptr;
return tok->astOperand2()->astOperand1();
}
static Token* getStepTok(Token* tok) {
if (!tok)
return nullptr;
if (Token::Match(tok, "%name% ("))
return getStepTok(tok->next());
if (tok->str() != "(")
return nullptr;
if (!Token::simpleMatch(tok->astOperand2(), ";"))
return nullptr;
if (!Token::simpleMatch(tok->astOperand2()->astOperand2(), ";"))
return nullptr;
return tok->astOperand2()->astOperand2()->astOperand2();
}
static Token* getStepTokFromEnd(Token* tok) { static Token* getStepTokFromEnd(Token* tok) {
if (!Token::simpleMatch(tok, "}")) if (!Token::simpleMatch(tok, "}"))
return nullptr; return nullptr;

View File

@ -692,8 +692,9 @@ static ValueFlow::Value execute(const Token* expr, ProgramMemory& pm)
void execute(const Token* expr, ProgramMemory* const programMemory, MathLib::bigint* result, bool* error) void execute(const Token* expr, ProgramMemory* const programMemory, MathLib::bigint* result, bool* error)
{ {
ValueFlow::Value v = execute(expr, *programMemory); ValueFlow::Value v = execute(expr, *programMemory);
if (!v.isIntValue() || v.isImpossible()) if (!v.isIntValue() || v.isImpossible()) {
*error = true; if (error)
else *error = true;
} else if (result)
*result = v.intvalue; *result = v.intvalue;
} }

313
lib/valueflow.cpp Executable file → Normal file
View File

@ -1695,7 +1695,7 @@ static bool isConditionKnown(const Token* tok, bool then)
const Token* parent = tok->astParent(); const Token* parent = tok->astParent();
while (parent && (parent->str() == op || parent->str() == "!")) while (parent && (parent->str() == op || parent->str() == "!"))
parent = parent->astParent(); parent = parent->astParent();
return (parent && parent->str() == "("); return Token::Match(parent, "(|;");
} }
static const std::string& invertAssign(const std::string& assign) static const std::string& invertAssign(const std::string& assign)
@ -5221,144 +5221,205 @@ struct ConditionHandler {
} }
} }
if (top && Token::Match(top->previous(), "if|while (") && !top->previous()->isExpandedMacro()) { if (!top)
// if astParent is "!" we need to invert codeblock return;
{
const Token* tok2 = tok; if (top->previous()->isExpandedMacro())
while (tok2->astParent()) { return;
const Token* parent = tok2->astParent();
while (parent && parent->str() == "&&") if (!Token::Match(top->previous(), "if|while|for ("))
parent = parent->astParent(); return;
if (parent && (parent->str() == "!" || Token::simpleMatch(parent, "== false")))
std::swap(thenValues, elseValues); if (top->previous()->str() == "for") {
tok2 = parent; if (!Token::Match(tok, "%comp%"))
return;
if (!Token::simpleMatch(tok->astParent(), ";"))
return;
const Token* stepTok = getStepTok(top);
if (cond.vartok->varId() == 0)
return;
if (!cond.vartok->variable())
return;
if (!Token::Match(stepTok, "++|--"))
return;
std::set<ValueFlow::Value::Bound> bounds;
for (const ValueFlow::Value& v : thenValues) {
if (v.bound != ValueFlow::Value::Bound::Point && v.isImpossible())
continue;
bounds.insert(v.bound);
}
if (Token::simpleMatch(stepTok, "++") && bounds.count(ValueFlow::Value::Bound::Lower) > 0)
return;
if (Token::simpleMatch(stepTok, "--") && bounds.count(ValueFlow::Value::Bound::Upper) > 0)
return;
const Token* childTok = tok->astOperand1();
if (!childTok)
childTok = tok->astOperand2();
if (!childTok)
return;
if (childTok->varId() != cond.vartok->varId())
return;
const Token* startBlock = top->link()->next();
if (isVariableChanged(startBlock,
startBlock->link(),
cond.vartok->varId(),
cond.vartok->variable()->isGlobal(),
settings,
tokenlist->isCPP()))
return;
// Check if condition in for loop is always false
const Token* initTok = getInitTok(top);
ProgramMemory pm;
execute(initTok, &pm, nullptr, nullptr);
MathLib::bigint result = 1;
execute(tok, &pm, &result, nullptr);
if (result == 0)
return;
// Remove condition since for condition is not redundant
for (std::list<ValueFlow::Value>* values : {&thenValues, &elseValues}) {
for (ValueFlow::Value& v : *values) {
v.condition = nullptr;
v.conditional = true;
} }
} }
}
bool deadBranch[] = {false, false}; // if astParent is "!" we need to invert codeblock
// start token of conditional code {
Token* startTokens[] = {nullptr, nullptr}; const Token* tok2 = tok;
// determine startToken(s) while (tok2->astParent()) {
if (Token::simpleMatch(top->link(), ") {")) const Token* parent = tok2->astParent();
startTokens[0] = top->link()->next(); while (parent && parent->str() == "&&")
if (Token::simpleMatch(top->link()->linkAt(1), "} else {")) parent = parent->astParent();
startTokens[1] = top->link()->linkAt(1)->tokAt(2); if (parent && (parent->str() == "!" || Token::simpleMatch(parent, "== false")))
std::swap(thenValues, elseValues);
int changeBlock = -1; tok2 = parent;
int bailBlock = -1;
for (int i = 0; i < 2; i++) {
const Token* const startToken = startTokens[i];
if (!startToken)
continue;
std::list<ValueFlow::Value>& values = (i == 0 ? thenValues : elseValues);
valueFlowSetConditionToKnown(tok, values, i == 0);
Analyzer::Result r =
forward(startTokens[i], startTokens[i]->link(), cond.vartok, values, tokenlist, settings);
deadBranch[i] = r.terminate == Analyzer::Terminate::Escape;
if (r.action.isModified() && !deadBranch[i])
changeBlock = i;
if (r.terminate != Analyzer::Terminate::None && r.terminate != Analyzer::Terminate::Escape &&
r.terminate != Analyzer::Terminate::Modified)
bailBlock = i;
changeKnownToPossible(values);
} }
if (changeBlock >= 0 && !Token::simpleMatch(top->previous(), "while (")) { }
bool deadBranch[] = {false, false};
// start token of conditional code
Token* startTokens[] = {nullptr, nullptr};
// determine startToken(s)
if (Token::simpleMatch(top->link(), ") {"))
startTokens[0] = top->link()->next();
if (Token::simpleMatch(top->link()->linkAt(1), "} else {"))
startTokens[1] = top->link()->linkAt(1)->tokAt(2);
int changeBlock = -1;
int bailBlock = -1;
for (int i = 0; i < 2; i++) {
const Token* const startToken = startTokens[i];
if (!startToken)
continue;
std::list<ValueFlow::Value>& values = (i == 0 ? thenValues : elseValues);
valueFlowSetConditionToKnown(tok, values, i == 0);
Analyzer::Result r =
forward(startTokens[i], startTokens[i]->link(), cond.vartok, values, tokenlist, settings);
deadBranch[i] = r.terminate == Analyzer::Terminate::Escape;
if (r.action.isModified() && !deadBranch[i])
changeBlock = i;
if (r.terminate != Analyzer::Terminate::None && r.terminate != Analyzer::Terminate::Escape &&
r.terminate != Analyzer::Terminate::Modified)
bailBlock = i;
changeKnownToPossible(values);
}
if (changeBlock >= 0 && !Token::simpleMatch(top->previous(), "while (")) {
if (settings->debugwarnings)
bailout(tokenlist,
errorLogger,
startTokens[changeBlock]->link(),
"valueFlowAfterCondition: " + cond.vartok->expressionString() +
" is changed in conditional block");
return;
} else if (bailBlock >= 0) {
if (settings->debugwarnings)
bailout(tokenlist,
errorLogger,
startTokens[bailBlock]->link(),
"valueFlowAfterCondition: bailing in conditional block");
return;
}
// After conditional code..
if (Token::simpleMatch(top->link(), ") {")) {
Token* after = top->link()->linkAt(1);
bool dead_if = deadBranch[0];
bool dead_else = deadBranch[1];
const Token* unknownFunction = nullptr;
if (tok->astParent() && Token::simpleMatch(tok->astParent()->previous(), "while ("))
dead_if = !isBreakScope(after);
else if (!dead_if)
dead_if = isReturnScope(after, &settings->library, &unknownFunction);
if (!dead_if && unknownFunction) {
if (settings->debugwarnings) if (settings->debugwarnings)
bailout(tokenlist, bailout(tokenlist, errorLogger, unknownFunction, "possible noreturn scope");
errorLogger,
startTokens[changeBlock]->link(),
"valueFlowAfterCondition: " + cond.vartok->expressionString() +
" is changed in conditional block");
return;
} else if (bailBlock >= 0) {
if (settings->debugwarnings)
bailout(tokenlist,
errorLogger,
startTokens[bailBlock]->link(),
"valueFlowAfterCondition: bailing in conditional block");
return; return;
} }
// After conditional code.. if (Token::simpleMatch(after, "} else {")) {
if (Token::simpleMatch(top->link(), ") {")) { after = after->linkAt(2);
Token* after = top->link()->linkAt(1); unknownFunction = nullptr;
bool dead_if = deadBranch[0]; if (!dead_else)
bool dead_else = deadBranch[1]; dead_else = isReturnScope(after, &settings->library, &unknownFunction);
const Token* unknownFunction = nullptr; if (!dead_else && unknownFunction) {
if (tok->astParent() && Token::simpleMatch(tok->astParent()->previous(), "while ("))
dead_if = !isBreakScope(after);
else if (!dead_if)
dead_if = isReturnScope(after, &settings->library, &unknownFunction);
if (!dead_if && unknownFunction) {
if (settings->debugwarnings) if (settings->debugwarnings)
bailout(tokenlist, errorLogger, unknownFunction, "possible noreturn scope"); bailout(tokenlist, errorLogger, unknownFunction, "possible noreturn scope");
return; return;
} }
if (Token::simpleMatch(after, "} else {")) {
after = after->linkAt(2);
unknownFunction = nullptr;
if (!dead_else)
dead_else = isReturnScope(after, &settings->library, &unknownFunction);
if (!dead_else && unknownFunction) {
if (settings->debugwarnings)
bailout(tokenlist, errorLogger, unknownFunction, "possible noreturn scope");
return;
}
}
if (dead_if && dead_else)
return;
std::list<ValueFlow::Value> values;
if (dead_if) {
values = elseValues;
} else if (dead_else) {
values = thenValues;
} else {
std::copy_if(thenValues.begin(),
thenValues.end(),
std::back_inserter(values),
std::mem_fn(&ValueFlow::Value::isPossible));
std::copy_if(elseValues.begin(),
elseValues.end(),
std::back_inserter(values),
std::mem_fn(&ValueFlow::Value::isPossible));
}
if (values.empty())
return;
if (dead_if || dead_else) {
const Token* parent = tok->astParent();
// Skip the not operator
while (Token::simpleMatch(parent, "!"))
parent = parent->astParent();
bool possible = false;
if (Token::Match(parent, "&&|%oror%")) {
std::string op = parent->str();
while (parent && parent->str() == op)
parent = parent->astParent();
if (Token::simpleMatch(parent, "!") || Token::simpleMatch(parent, "== false"))
possible = op == "||";
else
possible = op == "&&";
}
if (possible) {
values.remove_if(std::mem_fn(&ValueFlow::Value::isImpossible));
changeKnownToPossible(values);
} else {
valueFlowSetConditionToKnown(tok, values, true);
valueFlowSetConditionToKnown(tok, values, false);
}
}
if (values.empty())
return;
forward(after, scope->bodyEnd, cond.vartok, values, tokenlist, settings);
} }
if (dead_if && dead_else)
return;
std::list<ValueFlow::Value> values;
if (dead_if) {
values = elseValues;
} else if (dead_else) {
values = thenValues;
} else {
std::copy_if(thenValues.begin(),
thenValues.end(),
std::back_inserter(values),
std::mem_fn(&ValueFlow::Value::isPossible));
std::copy_if(elseValues.begin(),
elseValues.end(),
std::back_inserter(values),
std::mem_fn(&ValueFlow::Value::isPossible));
}
if (values.empty())
return;
if (dead_if || dead_else) {
const Token* parent = tok->astParent();
// Skip the not operator
while (Token::simpleMatch(parent, "!"))
parent = parent->astParent();
bool possible = false;
if (Token::Match(parent, "&&|%oror%")) {
std::string op = parent->str();
while (parent && parent->str() == op)
parent = parent->astParent();
if (Token::simpleMatch(parent, "!") || Token::simpleMatch(parent, "== false"))
possible = op == "||";
else
possible = op == "&&";
}
if (possible) {
values.remove_if(std::mem_fn(&ValueFlow::Value::isImpossible));
changeKnownToPossible(values);
} else {
valueFlowSetConditionToKnown(tok, values, true);
valueFlowSetConditionToKnown(tok, values, false);
}
}
if (values.empty())
return;
forward(after, scope->bodyEnd, cond.vartok, values, tokenlist, settings);
} }
}); });
} }

View File

@ -1317,7 +1317,8 @@ private:
" for (x = a; x < 50; x++) {}\n" " for (x = a; x < 50; x++) {}\n"
" b = x;\n" " b = x;\n"
"}\n"; "}\n";
ASSERT_EQUALS("3,After for loop, x has value 50\n", ASSERT_EQUALS("3,Assuming that condition 'x<50' is not redundant\n"
"3,Assuming that condition 'x<50' is not redundant\n",
getErrorPathForX(code, 4U)); getErrorPathForX(code, 4U));
} }
@ -6646,6 +6647,14 @@ private:
"}\n"; "}\n";
ASSERT_EQUALS(true, testValueOfX(code, 11U, "d", 0)); ASSERT_EQUALS(true, testValueOfX(code, 11U, "d", 0));
ASSERT_EQUALS(false, testValueOfXImpossible(code, 11U, 0)); ASSERT_EQUALS(false, testValueOfXImpossible(code, 11U, 0));
code = "void f(int * p, int len) {\n"
" for(int x = 0; x < len; ++x) {\n"
" p[x] = 1;\n"
" }\n"
"}\n";
ASSERT_EQUALS(true, testValueOfX(code, 3U, "len", -1));
ASSERT_EQUALS(true, testValueOfXImpossible(code, 3U, "len", 0));
} }
void valueFlowSymbolicIdentity() void valueFlowSymbolicIdentity()