llvm-project/clang/test/Analysis/live-stmts.cpp
Arseniy Zaostrovnykh 73c72f2c65
[analyzer] Keep alive short-circuiting condition subexpressions in a conditional (#100745)
Fix the false negative caused by state merging in the evaluation of a
short-circuiting expression inside the condition of a ternary operator.
The fixed symptom is that CSA always evaluates `(x || x) ? n : m` to
`m`.

This change forces the analyzer to consider all logical expressions
prone to short-circuiting alive until the entire conditional expression
is evaluated. Here is why.

By default, LiveVariables analysis marks only direct subexpressions as
live relative to any expression. So for `a ? b : c` it will consider
`a`, `b`, and `c` alive when evaluating the ternary operator expression.

To explore both possibilities opened by a ternary operator, it is
important to keep something different about the exploded nodes created
after the evaluation of its branches. These two nodes come to the same
location, so they must have different states. Otherwise, they will be
considered identical and can engender only one outcome.
`ExprEngine::visitGuardedExpr` chooses the first predecessor exploded
node to carry the value of the conditional expression. It works well in
the case of a simple condition, because when `a ? b : c` is evaluated,
`a` is kept alive, so the two branches differ in the value of `a`.

However, before this patch is applied, this strategy breaks for `(x ||
x) ? n : m`. `x` is not a direct child of the ternary expression. Due to
short-circuiting, once `x` is assumed to be `true`, evaluation jumps
directly to `n` and then to the result of the entire ternary expression.
Given that the result of the entire condition `(x || x)` is not
constructed, and `x` is not kept alive, the difference between the path
coming through `n` and through `m` disappears. As a result, exploded
nodes coming from the "true expression" and the "false expression"
engender identical successors and merge the execution paths.
2024-07-29 08:12:22 +02:00

305 lines
8.9 KiB
C++

// RUN: %clang_analyze_cc1 -w -analyzer-checker=debug.DumpLiveExprs %s 2>&1\
// RUN: | FileCheck %s
int coin();
int testThatDumperWorks(int x, int y, int z) {
return x ? y : z;
}
// [B5 (ENTRY)]
// |
// V
// [B4 (x)] ? [B2 (y)] : [B3 (z)]
// \ /
// ---|----
// V
// [B1] --> [B0 (EXIT)]
// return
// CHECK: [ B0 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B1 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B2 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-NEXT: DeclRefExpr {{.*}} 'y' 'int'
// CHECK-EMPTY:
// CHECK-NEXT: DeclRefExpr {{.*}} 'z' 'int'
// CHECK-EMPTY:
// CHECK-NEXT: ImplicitCastExpr {{.*}} <IntegralToBoolean>
// CHECK-NEXT: `-ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK-NEXT: `-DeclRefExpr {{.*}} 'x' 'int'
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B3 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-NEXT: DeclRefExpr {{.*}} 'y' 'int'
// CHECK-EMPTY:
// CHECK-NEXT: DeclRefExpr {{.*}} 'z' 'int'
// CHECK-EMPTY:
// CHECK-NEXT: ImplicitCastExpr {{.*}} <IntegralToBoolean>
// CHECK-NEXT: `-ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK-NEXT: `-DeclRefExpr {{.*}} 'x' 'int'
// CHECK: [ B4 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-NEXT: DeclRefExpr {{.*}} 'y' 'int'
// CHECK-EMPTY:
// CHECK-NEXT: DeclRefExpr {{.*}} 'z' 'int'
// CHECK-EMPTY:
// CHECK-NEXT: ImplicitCastExpr {{.*}} <IntegralToBoolean>
// CHECK-NEXT: `-ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK-NEXT: `-DeclRefExpr {{.*}} 'x' 'int'
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B5 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-NEXT: DeclRefExpr {{.*}} 'y' 'int'
// CHECK-EMPTY:
// CHECK-NEXT: DeclRefExpr {{.*}} 'z' 'int'
// CHECK-EMPTY:
// CHECK-EMPTY:
void testIfBranchExpression(bool flag) {
// No expressions should be carried over from one block to another here.
while (flag) {
int e = 1;
if (true)
e;
}
}
// CHECK: [ B0 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B1 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B2 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B3 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B4 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B5 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
void testWhileBodyExpression(bool flag) {
// No expressions should be carried over from one block to another here.
while (flag) {
int e = 1;
while (coin())
e;
}
}
// CHECK: [ B0 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B1 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B2 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B3 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B4 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B5 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
void testDoWhileBodyExpression(bool flag) {
// No expressions should be carried over from one block to another here.
while (flag) {
int e = 1;
do
e;
while (coin());
}
}
// CHECK: [ B0 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B1 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B2 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B3 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B4 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B5 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
void testForBodyExpression(bool flag) {
// No expressions should be carried over from one block to another here.
while (flag) {
int e = 1;
for (; coin();)
e;
}
}
// CHECK: [ B0 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B1 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B2 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B3 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B4 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B5 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
void clang_analyzer_eval(bool);
void test_lambda_refcapture() {
int a = 6;
[&](int &a) { a = 42; }(a);
clang_analyzer_eval(a == 42); // expected-warning{{TRUE}}
}
// CHECK: [ B0 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK-NEXT: [ B1 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK-NEXT: [ B2 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
int logicalOpInTernary(bool b) {
return (b || b) ? 0 : 1;
}
// [B6 (ENTRY)]
// |
// V
// [B5 (b || ...)]
// | \
// | |
// V V
// [B4 (b||b)] ? [B2 (0)] : [B3 (1)]
// \ /
// ---|----
// V
// [B1] --> [B0 (EXIT)]
// return
// CHECK: [ B0 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B1 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B2 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK: ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK-EMPTY:
// CHECK: ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK-EMPTY:
// CHECK: BinaryOperator {{.*}} '_Bool' '||'
// CHECK: |-ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: | `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK: `-ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK-EMPTY:
// CHECK: IntegerLiteral {{.*}} 'int' 0
// CHECK-EMPTY:
// CHECK: IntegerLiteral {{.*}} 'int' 1
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B3 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK: ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK-EMPTY:
// CHECK: ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK-EMPTY:
// CHECK: BinaryOperator {{.*}} '_Bool' '||'
// CHECK: |-ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: | `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK: `-ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK-EMPTY:
// CHECK: IntegerLiteral {{.*}} 'int' 0
// CHECK-EMPTY:
// CHECK: IntegerLiteral {{.*}} 'int' 1
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B4 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK: ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK-EMPTY:
// CHECK: ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK-EMPTY:
// CHECK: BinaryOperator {{.*}} '_Bool' '||'
// CHECK: |-ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: | `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK: `-ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK-EMPTY:
// CHECK: IntegerLiteral {{.*}} 'int' 0
// CHECK-EMPTY:
// CHECK: IntegerLiteral {{.*}} 'int' 1
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B5 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK: ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK-EMPTY:
// CHECK: ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK-EMPTY:
// CHECK: BinaryOperator {{.*}} '_Bool' '||'
// CHECK: |-ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: | `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK: `-ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK-EMPTY:
// CHECK: IntegerLiteral {{.*}} 'int' 0
// CHECK-EMPTY:
// CHECK: IntegerLiteral {{.*}} 'int' 1
// CHECK-EMPTY:
// CHECK-EMPTY:
// CHECK: [ B6 (live expressions at block exit) ]
// CHECK-EMPTY:
// CHECK: ImplicitCastExpr {{.*}} '_Bool' <LValueToRValue>
// CHECK: `-DeclRefExpr {{.*}} '_Bool' lvalue ParmVar {{.*}} 'b' '_Bool'
// CHECK-EMPTY:
// CHECK: IntegerLiteral {{.*}} 'int' 0
// CHECK-EMPTY:
// CHECK: IntegerLiteral {{.*}} 'int' 1