Skip to content

Commit 39b9d4f

Browse files
committed
[clang][dataflow] Add support for a Top value in boolean formulas.
Currently, our boolean formulas (`BoolValue`) don't form a lattice, since they have no Top element. This patch adds such an element, thereby "completing" the built-in model of bools to be a proper semi-lattice. It still has infinite height, which is its own problem, but that can be solved separately, through widening and the like. Patch 1 for Issue llvm#56931. Differential Revision: https://reviews.llvm.org/D135397
1 parent 28f7087 commit 39b9d4f

File tree

11 files changed

+593
-33
lines changed

11 files changed

+593
-33
lines changed

clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,18 @@ class DataflowAnalysisContext {
153153
return takeOwnership(std::make_unique<AtomicBoolValue>());
154154
}
155155

156+
/// Creates a Top value for booleans. Each instance is unique and can be
157+
/// assigned a distinct truth value during solving.
158+
///
159+
/// FIXME: `Top iff Top` is true when both Tops are identical (by pointer
160+
/// equality), but not when they are distinct values. We should improve the
161+
/// implementation so that `Top iff Top` has a consistent meaning, regardless
162+
/// of the identity of `Top`. Moreover, I think the meaning should be
163+
/// `false`.
164+
TopBoolValue &createTopBoolValue() {
165+
return takeOwnership(std::make_unique<TopBoolValue>());
166+
}
167+
156168
/// Returns a boolean value that represents the conjunction of `LHS` and
157169
/// `RHS`. Subsequent calls with the same arguments, regardless of their
158170
/// order, will return the same result. If the given boolean values represent

clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,11 @@ class Environment {
288288
return DACtx->createAtomicBoolValue();
289289
}
290290

291+
/// Returns a unique instance of boolean Top.
292+
BoolValue &makeTopBoolValue() const {
293+
return DACtx->createTopBoolValue();
294+
}
295+
291296
/// Returns a boolean value that represents the conjunction of `LHS` and
292297
/// `RHS`. Subsequent calls with the same arguments, regardless of their
293298
/// order, will return the same result. If the given boolean values represent

clang/include/clang/Analysis/FlowSensitive/Value.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class Value {
3838
Struct,
3939

4040
// Synthetic boolean values are either atomic values or logical connectives.
41+
TopBool,
4142
AtomicBool,
4243
Conjunction,
4344
Disjunction,
@@ -82,7 +83,8 @@ class BoolValue : public Value {
8283
explicit BoolValue(Kind ValueKind) : Value(ValueKind) {}
8384

8485
static bool classof(const Value *Val) {
85-
return Val->getKind() == Kind::AtomicBool ||
86+
return Val->getKind() == Kind::TopBool ||
87+
Val->getKind() == Kind::AtomicBool ||
8688
Val->getKind() == Kind::Conjunction ||
8789
Val->getKind() == Kind::Disjunction ||
8890
Val->getKind() == Kind::Negation ||
@@ -91,6 +93,20 @@ class BoolValue : public Value {
9193
}
9294
};
9395

96+
/// Models the trivially true formula, which is Top in the lattice of boolean
97+
/// formulas.
98+
///
99+
/// FIXME: Given the subtlety of comparison involving `TopBoolValue`, define
100+
/// `operator==` for `Value`.
101+
class TopBoolValue final : public BoolValue {
102+
public:
103+
TopBoolValue() : BoolValue(Kind::TopBool) {}
104+
105+
static bool classof(const Value *Val) {
106+
return Val->getKind() == Kind::TopBool;
107+
}
108+
};
109+
94110
/// Models an atomic boolean.
95111
class AtomicBoolValue : public BoolValue {
96112
public:

clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ static bool equivalentValues(QualType Type, Value *Val1,
6666
const Environment &Env1, Value *Val2,
6767
const Environment &Env2,
6868
Environment::ValueModel &Model) {
69-
return Val1 == Val2 || areEquivalentIndirectionValues(Val1, Val2) ||
69+
return Val1 == Val2 || (isa<TopBoolValue>(Val1) && isa<TopBoolValue>(Val2)) ||
70+
areEquivalentIndirectionValues(Val1, Val2) ||
7071
Model.compareEquivalent(Type, *Val1, Env1, *Val2, Env2);
7172
}
7273

@@ -371,7 +372,8 @@ LatticeJoinEffect Environment::join(const Environment &Other,
371372
continue;
372373
assert(It->second != nullptr);
373374

374-
if (Val == It->second) {
375+
if (Val == It->second ||
376+
(isa<TopBoolValue>(Val) && isa<TopBoolValue>(It->second))) {
375377
JoinedEnv.LocToVal.insert({Loc, Val});
376378
continue;
377379
}

clang/lib/Analysis/FlowSensitive/DebugSupport.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ llvm::StringRef debugString(Value::Kind Kind) {
4444
return "Struct";
4545
case Value::Kind::AtomicBool:
4646
return "AtomicBool";
47+
case Value::Kind::TopBool:
48+
return "TopBool";
4749
case Value::Kind::Conjunction:
4850
return "Conjunction";
4951
case Value::Kind::Disjunction:

clang/lib/Analysis/FlowSensitive/Transfer.cpp

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "clang/Basic/OperatorKinds.h"
2929
#include "llvm/ADT/STLExtras.h"
3030
#include "llvm/Support/Casting.h"
31+
#include "llvm/Support/ErrorHandling.h"
3132
#include <cassert>
3233
#include <memory>
3334
#include <tuple>
@@ -46,6 +47,84 @@ static BoolValue &evaluateBooleanEquality(const Expr &LHS, const Expr &RHS,
4647
return Env.makeAtomicBoolValue();
4748
}
4849

50+
// Functionally updates `V` such that any instances of `TopBool` are replaced
51+
// with fresh atomic bools. Note: This implementation assumes that `B` is a
52+
// tree; if `B` is a DAG, it will lose any sharing between subvalues that was
53+
// present in the original .
54+
static BoolValue &unpackValue(BoolValue &V, Environment &Env);
55+
56+
template <typename Derived, typename M>
57+
BoolValue &unpackBinaryBoolValue(Environment &Env, BoolValue &B, M build) {
58+
auto &V = *cast<Derived>(&B);
59+
BoolValue &Left = V.getLeftSubValue();
60+
BoolValue &Right = V.getRightSubValue();
61+
BoolValue &ULeft = unpackValue(Left, Env);
62+
BoolValue &URight = unpackValue(Right, Env);
63+
64+
if (&ULeft == &Left && &URight == &Right)
65+
return V;
66+
67+
return (Env.*build)(ULeft, URight);
68+
}
69+
70+
static BoolValue &unpackValue(BoolValue &V, Environment &Env) {
71+
switch (V.getKind()) {
72+
case Value::Kind::Integer:
73+
case Value::Kind::Reference:
74+
case Value::Kind::Pointer:
75+
case Value::Kind::Struct:
76+
llvm_unreachable("BoolValue cannot have any of these kinds.");
77+
78+
case Value::Kind::AtomicBool:
79+
return V;
80+
81+
case Value::Kind::TopBool:
82+
// Unpack `TopBool` into a fresh atomic bool.
83+
return Env.makeAtomicBoolValue();
84+
85+
case Value::Kind::Negation: {
86+
auto &N = *cast<NegationValue>(&V);
87+
BoolValue &Sub = N.getSubVal();
88+
BoolValue &USub = unpackValue(Sub, Env);
89+
90+
if (&USub == &Sub)
91+
return V;
92+
return Env.makeNot(USub);
93+
}
94+
case Value::Kind::Conjunction:
95+
return unpackBinaryBoolValue<ConjunctionValue>(Env, V,
96+
&Environment::makeAnd);
97+
case Value::Kind::Disjunction:
98+
return unpackBinaryBoolValue<DisjunctionValue>(Env, V,
99+
&Environment::makeOr);
100+
case Value::Kind::Implication:
101+
return unpackBinaryBoolValue<ImplicationValue>(
102+
Env, V, &Environment::makeImplication);
103+
case Value::Kind::Biconditional:
104+
return unpackBinaryBoolValue<BiconditionalValue>(Env, V,
105+
&Environment::makeIff);
106+
}
107+
}
108+
109+
// Unpacks the value (if any) associated with `E` and updates `E` to the new
110+
// value, if any unpacking occured.
111+
static Value *maybeUnpackLValueExpr(const Expr &E, Environment &Env) {
112+
auto *Loc = Env.getStorageLocation(E, SkipPast::Reference);
113+
if (Loc == nullptr)
114+
return nullptr;
115+
auto *Val = Env.getValue(*Loc);
116+
117+
auto *B = dyn_cast_or_null<BoolValue>(Val);
118+
if (B == nullptr)
119+
return Val;
120+
121+
auto &UnpackedVal = unpackValue(*B, Env);
122+
if (&UnpackedVal == Val)
123+
return Val;
124+
Env.setValue(*Loc, UnpackedVal);
125+
return &UnpackedVal;
126+
}
127+
49128
class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
50129
public:
51130
TransferVisitor(const StmtToEnvMap &StmtToEnv, Environment &Env,
@@ -222,7 +301,9 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
222301
}
223302

224303
case CK_LValueToRValue: {
225-
auto *SubExprVal = Env.getValue(*SubExpr, SkipPast::Reference);
304+
// When an L-value is used as an R-value, it may result in sharing, so we
305+
// need to unpack any nested `Top`s.
306+
auto *SubExprVal = maybeUnpackLValueExpr(*SubExpr, Env);
226307
if (SubExprVal == nullptr)
227308
break;
228309

clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,11 @@ BooleanFormula buildBooleanFormula(const llvm::DenseSet<BoolValue *> &Vals) {
233233
UnprocessedSubVals.push(&B->getRightSubValue());
234234
break;
235235
}
236+
case Value::Kind::TopBool:
237+
// Nothing more to do. This `TopBool` instance has already been mapped
238+
// to a fresh solver variable (`NextVar`, above) and is thereafter
239+
// anonymous. The solver never sees `Top`.
240+
break;
236241
case Value::Kind::AtomicBool: {
237242
Atomics[Var] = cast<AtomicBoolValue>(Val);
238243
break;

clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ TEST_F(DataflowAnalysisContextTest,
3232
EXPECT_NE(&X, &Y);
3333
}
3434

35+
TEST_F(DataflowAnalysisContextTest,
36+
CreateTopBoolValueReturnsDistinctValues) {
37+
auto &X = Context.createTopBoolValue();
38+
auto &Y = Context.createTopBoolValue();
39+
EXPECT_NE(&X, &Y);
40+
}
41+
42+
TEST_F(DataflowAnalysisContextTest, DistinctTopsNotEquivalent) {
43+
auto &X = Context.createTopBoolValue();
44+
auto &Y = Context.createTopBoolValue();
45+
EXPECT_FALSE(Context.equivalentBoolValues(X, Y));
46+
}
47+
3548
TEST_F(DataflowAnalysisContextTest,
3649
GetOrCreateConjunctionReturnsSameExprGivenSameArgs) {
3750
auto &X = Context.createAtomicBoolValue();

clang/unittests/Analysis/FlowSensitive/SolverTest.cpp

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
#include "TestingSupport.h"
1212
#include "clang/Analysis/FlowSensitive/Solver.h"
13-
#include "TestingSupport.h"
1413
#include "clang/Analysis/FlowSensitive/Value.h"
1514
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
1615
#include "gmock/gmock.h"
@@ -24,6 +23,7 @@ using namespace dataflow;
2423
using test::ConstraintContext;
2524
using testing::_;
2625
using testing::AnyOf;
26+
using testing::IsEmpty;
2727
using testing::Optional;
2828
using testing::Pair;
2929
using testing::UnorderedElementsAre;
@@ -88,6 +88,32 @@ TEST(SolverTest, DistinctVars) {
8888
Pair(Y, Solver::Result::Assignment::AssignedFalse)));
8989
}
9090

91+
TEST(SolverTest, Top) {
92+
ConstraintContext Ctx;
93+
auto X = Ctx.top();
94+
95+
// X. Since Top is anonymous, we do not get any results in the solution.
96+
expectSatisfiable(solve({X}), IsEmpty());
97+
}
98+
99+
TEST(SolverTest, NegatedTop) {
100+
ConstraintContext Ctx;
101+
auto X = Ctx.top();
102+
103+
// !X
104+
expectSatisfiable(solve({Ctx.neg(X)}), IsEmpty());
105+
}
106+
107+
TEST(SolverTest, DistinctTops) {
108+
ConstraintContext Ctx;
109+
auto X = Ctx.top();
110+
auto Y = Ctx.top();
111+
auto NotY = Ctx.neg(Y);
112+
113+
// X ^ !Y
114+
expectSatisfiable(solve({X, NotY}), IsEmpty());
115+
}
116+
91117
TEST(SolverTest, DoubleNegation) {
92118
ConstraintContext Ctx;
93119
auto X = Ctx.atom();

clang/unittests/Analysis/FlowSensitive/TestingSupport.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,12 @@ class ConstraintContext {
372372
return Vals.back().get();
373373
}
374374

375+
// Creates an instance of the Top boolean value.
376+
BoolValue *top() {
377+
Vals.push_back(std::make_unique<TopBoolValue>());
378+
return Vals.back().get();
379+
}
380+
375381
// Creates a boolean conjunction value.
376382
BoolValue *conj(BoolValue *LeftSubVal, BoolValue *RightSubVal) {
377383
Vals.push_back(

0 commit comments

Comments
 (0)