This is an archive of the discontinued LLVM Phabricator instance.

[analyzer] Suppress bugreports from constexpr contexts
Needs ReviewPublic

Authored by steakhal on Jan 18 2022, 3:13 AM.

Details

Summary

Assuming that the code compiles, bugreports coming from constexpr
variable initializer expressions are by definition false-positives.
There is code smell-ish bugreports, like
alpha.deadcode.UnreachableCode but suppressing everything doesn't feel
that bad overall.

Ideally, we should outright skip the evaluation of the constexpr
variable initializer expression evaluation, but I could not find a grasp
achieving that.
Since we walk the CFG block, the expression-tree of the initializer is
sequenced before the variable declaration. We could only infer from the
variable declaration that the evaluation of the previous instruction
must have been evaluated in constexpr context.

What we could do is to replace the SVal associated with the
initializer expression, saying that the Expr::EvaluateAsConstantExpr()
is either the same or more accurate than the analyzer engine itself.
According to the analyzer tests, I could not see any difference though.

Diff Detail

Event Timeline

steakhal created this revision.Jan 18 2022, 3:13 AM
steakhal requested review of this revision.Jan 18 2022, 3:13 AM
NoQ added a comment.Jan 18 2022, 3:17 PM

Assuming that the code compiles, bugreports coming from constexpr variable initializer expressions are by definition false-positives.

Can you elaborate, like describe specific false positives you've encountered? Why is this specific to variables?

I also wonder if we should be symbolically executing these contexts to begin with, maybe we should rely on the compiler to give us the answer in one step?

Assuming that the code compiles, bugreports coming from constexpr variable initializer expressions are by definition false-positives.

Can you elaborate, like describe specific false positives you've encountered?

There are checkers which produce more false-positive reports than others. Such as CStringChecker or OOB access reports.
But if we take one step back we can argue that any reports produced by any checker are highly likely a false-positive. Except for those checkers which are enforcing coding style rules, which won't produce the constexpr evaluator to halt.

Why is this specific to variables?

AFAIK the only way of enforcing the compiler to evaluate some expression in constexpr-context is to initialize a constexpr variable by the expression in question.
If the constexpr evaluator engine finds undefined behavior, the code should not compile. And this is the fact that the analyzer should take into consideration.

I also wonder if we should be symbolically executing these contexts to begin with, maybe we should rely on the compiler to give us the answer in one step?

I agree. However, it doesn't seem to be that easy to achieve.
In the engine AFAIK we evaluate CFGElements in the order they are produced by the CFG builder. So, by the time we arrive at the DeclStmt from which we could query VarDecl->isConstexpr() the exprengine already evaluated all the expressions of the initializer expression.
By inspecting the CFGElement stream it's hard to guess which instruction is the first instruction contributing to the initializer expression.

Prior building the CFG we could set the AddScopes to have markers immediately before the first CFGElement constituting to the initializer expression. The elements of the initializer expression should finish exactly by the time we reach the DeclStmt they are initializing.
We could exploit this to skip these instructions and bind the constexpr evaluated result of the initializer expression by calling Expr::EvaluateAsConstantExpr().

I experimented with this idea, but it seems like the CFG looks surprising to me when I enable the AddScopes option. Unlike its name suggests, the produced CFG will look completely different, most just add the scope-begin and scope-end CFGElements.
I think it's a bug somewhere. There is a flag in the analyzer options basically flipping this flag: -analyzer-config cfg-scopes=true
I assume that the analyzer should behave exactly the same and pass all the tests if we enable this option by default, but it will fail all the tests - even though ExprEngine::processCFGElement() explicitly ignores the ScopeBegin and ScopeEnd entries.
This is surprising as well.

So, I decided to simply suppress these reports. I must admit, I wanted to spare some time and deliver something that is useful for the users for now and tweak these ideas in the future.

NoQ added a comment.Jan 19 2022, 3:08 PM

Oh interesting, so it's the same problem that causes ConstructionContexts to be necessary: evaluation of a function depends on its AST parents that we didn't yet encounter in the CFG.

You're saying there's ultimately only one context: local variable initialization. In this case can we simply reuse the ParentMap trick that you have already implemented in this patch (I mean hasAncestor() works through ParentMap, they're all the same to me), but at evaluation-time?

Oh interesting, so it's the same problem that causes ConstructionContexts to be necessary: evaluation of a function depends on its AST parents that we didn't yet encounter in the CFG.

You're saying there's ultimately only one context: local variable initialization. In this case can we simply reuse the ParentMap trick that you have already implemented in this patch (I mean hasAncestor() works through ParentMap, they're all the same to me), but at evaluation-time?

Yes, we could implement it if the CFG would not be broken with AddScopes=true. But as of now, suppression is the best we can come up with IMO.