Skip to content

Commit ed9bb21

Browse files
committed
[clang][dataflow] Add implementation of a simple boolean model
The new model still uses atomic variables in boolean formulas, but it limits the environment to accumulating truth values for atomic variables, rather than the arbitrary formula allowed by the flow condition.
1 parent 33f753d commit ed9bb21

File tree

9 files changed

+228
-20
lines changed

9 files changed

+228
-20
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,9 @@ class Environment {
500500
return cast_or_null<T>(getValue(E));
501501
}
502502

503+
std::optional<bool> getAtomValue(Atom A) const;
504+
void setAtomValue(Atom A, bool);
505+
503506
// FIXME: should we deprecate the following & call arena().create() directly?
504507

505508
/// Creates a `T` (some subclass of `Value`), forwarding `args` to the
@@ -720,6 +723,7 @@ class Environment {
720723
// deterministic sequence. This in turn produces deterministic SAT formulas.
721724
llvm::MapVector<const Expr *, Value *> ExprToVal;
722725
llvm::MapVector<const StorageLocation *, Value *> LocToVal;
726+
llvm::MapVector<Atom, bool> AtomToVal;
723727

724728
Atom FlowConditionToken;
725729
};

clang/include/clang/Analysis/FlowSensitive/Transfer.h

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class StmtToEnvMap {
4848
const TypeErasedDataflowAnalysisState &CurState;
4949
};
5050

51-
namespace bool_model {
51+
namespace sat_bool_model {
5252

5353
BoolValue &freshBoolValue(Environment &Env);
5454

@@ -70,7 +70,35 @@ BoolValue &notOp(BoolValue &Sub, Environment &Env);
7070
// CondVal -- the abstract value representing the condition.
7171
void transferBranch(bool BranchVal, BoolValue &CondVal, Environment &Env);
7272

73-
} // namespace bool_model
73+
} // namespace sat_bool_model
74+
75+
namespace simple_bool_model {
76+
77+
std::optional<bool> getLiteralValue(const Formula &F, const Environment &Env);
78+
79+
BoolValue &freshBoolValue(Environment &Env);
80+
81+
BoolValue &rValueFromLValue(BoolValue &V, Environment &Env);
82+
83+
BoolValue &logicalOrOp(BoolValue &LHS, BoolValue &RHS, Environment &Env);
84+
85+
BoolValue &logicalAndOp(BoolValue &LHS, BoolValue &RHS, Environment &Env);
86+
87+
BoolValue &eqOp(BoolValue &LHS, BoolValue &RHS, Environment &Env);
88+
89+
BoolValue &neOp(BoolValue &LHS, BoolValue &RHS, Environment &Env);
90+
91+
BoolValue &notOp(BoolValue &Sub, Environment &Env);
92+
93+
// Models the transition along a branch edge in the CFG.
94+
// BranchVal -- the concrete, dynamic branch value -- true for `then` and false
95+
// for `else`.
96+
// CondVal -- the abstract value representing the condition.
97+
void transferBranch(bool BranchVal, BoolValue &CondVal, Environment &Env);
98+
99+
} // namespace simple_bool_model
100+
101+
namespace bool_model = simple_bool_model;
74102

75103
/// Evaluates `S` and updates `Env` accordingly.
76104
///

clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "clang/AST/DeclCXX.h"
1818
#include "clang/AST/Type.h"
1919
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
20+
#include "clang/Analysis/FlowSensitive/Transfer.h"
2021
#include "clang/Analysis/FlowSensitive/Value.h"
2122
#include "llvm/ADT/DenseMap.h"
2223
#include "llvm/ADT/DenseSet.h"
@@ -132,6 +133,13 @@ static Value *joinDistinctValues(QualType Type, Value &Val1,
132133
auto &Expr1 = cast<BoolValue>(Val1).formula();
133134
auto &Expr2 = cast<BoolValue>(Val2).formula();
134135
auto &A = JoinedEnv.arena();
136+
137+
#if 1
138+
if (auto V1 = simple_bool_model::getLiteralValue(Expr1, Env1);
139+
V1.has_value() && V1 == simple_bool_model::getLiteralValue(Expr2, Env2))
140+
return &JoinedEnv.getBoolLiteralValue(*V1);
141+
#endif
142+
135143
auto &JoinedVal = A.makeAtomRef(A.makeAtom());
136144
JoinedEnv.assume(
137145
A.makeOr(A.makeAnd(A.makeAtomRef(Env1.getFlowConditionToken()),
@@ -706,6 +714,12 @@ Environment Environment::join(const Environment &EnvA, const Environment &EnvB,
706714
JoinedEnv.ExprToLoc = joinExprMaps(EnvA.ExprToLoc, EnvB.ExprToLoc);
707715
}
708716

717+
for (auto &Entry : EnvA.AtomToVal) {
718+
auto It = EnvB.AtomToVal.find(Entry.first);
719+
if (It != EnvB.AtomToVal.end() && Entry.second == It->second)
720+
JoinedEnv.AtomToVal.insert({Entry.first, Entry.second});
721+
}
722+
709723
return JoinedEnv;
710724
}
711725

@@ -1060,9 +1074,16 @@ void Environment::assume(const Formula &F) {
10601074
DACtx->addFlowConditionConstraint(FlowConditionToken, F);
10611075
}
10621076

1077+
#if 0
10631078
bool Environment::proves(const Formula &F) const {
10641079
return DACtx->flowConditionImplies(FlowConditionToken, F);
10651080
}
1081+
#else
1082+
bool Environment::proves(const Formula &F) const {
1083+
auto V = simple_bool_model::getLiteralValue(F, *this);
1084+
return V.has_value() && *V;
1085+
}
1086+
#endif
10661087

10671088
bool Environment::allows(const Formula &F) const {
10681089
return DACtx->flowConditionAllows(FlowConditionToken, F);
@@ -1120,6 +1141,15 @@ void Environment::dump() const {
11201141
dump(llvm::dbgs());
11211142
}
11221143

1144+
std::optional<bool> Environment::getAtomValue(Atom A) const {
1145+
auto It = AtomToVal.find(A);
1146+
if (It == AtomToVal.end())
1147+
return std::nullopt;
1148+
return It->second;
1149+
}
1150+
1151+
void Environment::setAtomValue(Atom A, bool b) { AtomToVal[A] = b; }
1152+
11231153
RecordStorageLocation *getImplicitObjectLocation(const CXXMemberCallExpr &MCE,
11241154
const Environment &Env) {
11251155
Expr *ImplicitObject = MCE.getImplicitObjectArgument();

clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,8 +653,7 @@ const Formula &evaluateEquality(Arena &A, const Formula &EqVal,
653653
// If neither is set, then they are equal.
654654
// We rewrite b) as !EqVal => (LHS v RHS), for a more compact formula.
655655
return A.makeAnd(
656-
A.makeImplies(EqVal, A.makeOr(A.makeAnd(LHS, RHS),
657-
A.makeAnd(A.makeNot(LHS), A.makeNot(RHS)))),
656+
A.makeImplies(EqVal, A.makeEquals(LHS, RHS)),
658657
A.makeImplies(A.makeNot(EqVal), A.makeOr(LHS, RHS)));
659658
}
660659

clang/lib/Analysis/FlowSensitive/Transfer.cpp

Lines changed: 138 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ static void propagateValueOrStorageLocation(const Expr &From, const Expr &To,
8181
propagateValue(From, To, Env);
8282
}
8383

84-
namespace bool_model {
84+
namespace sat_bool_model {
8585

8686
BoolValue &freshBoolValue(Environment &Env) {
8787
return Env.makeAtomicBoolValue();
@@ -110,15 +110,145 @@ BoolValue &neOp(BoolValue &LHS, BoolValue &RHS, Environment &Env) {
110110
BoolValue &notOp(BoolValue &Sub, Environment &Env) { return Env.makeNot(Sub); }
111111

112112
void transferBranch(bool BranchVal, BoolValue &CondVal, Environment &Env) {
113-
if (BranchVal)
114-
Env.assume(CondVal.formula());
115-
else
116-
// The condition must be inverted for the successor that encompasses the
117-
// "else" branch, if such exists.
118-
Env.assume(Env.makeNot(CondVal).formula());
113+
// The condition must be inverted for the successor that encompasses the
114+
// "else" branch, if such exists.
115+
BoolValue &AssertedVal = BranchVal ? CondVal : Env.makeNot(CondVal);
116+
Env.assume(AssertedVal.formula());
117+
}
118+
119+
} // namespace sat_bool_model
120+
121+
namespace simple_bool_model {
122+
123+
std::optional<bool> getLiteralValue(const Formula &F, const Environment &Env) {
124+
switch (F.kind()) {
125+
case Formula::AtomRef:
126+
return Env.getAtomValue(F.getAtom());
127+
case Formula::Literal:
128+
return F.literal();
129+
case Formula::Not: {
130+
ArrayRef<const Formula *> Operands = F.operands();
131+
assert(Operands.size() == 1);
132+
if (std::optional<bool> Maybe = getLiteralValue(*Operands[0], Env))
133+
return !*Maybe;
134+
return std::nullopt;
135+
}
136+
default:
137+
return std::nullopt;
138+
}
139+
}
140+
141+
BoolValue &freshBoolValue(Environment &Env) {
142+
return Env.makeAtomicBoolValue();
143+
}
144+
145+
BoolValue &rValueFromLValue(BoolValue &V, Environment &Env) {
146+
return unpackValue(V, Env);
147+
}
148+
149+
BoolValue &logicalOrOp(BoolValue &LHS, BoolValue &RHS, Environment &Env) {
150+
// FIXME: check if these optimizations are already built in to the formula
151+
// generation.
152+
if (auto V = getLiteralValue(LHS.formula(), Env))
153+
return *V ? Env.getBoolLiteralValue(true) : RHS;
154+
if (auto V = getLiteralValue(RHS.formula(), Env))
155+
return *V ? Env.getBoolLiteralValue(true) : LHS;
156+
return Env.makeOr(LHS, RHS);
157+
}
158+
159+
BoolValue &logicalAndOp(BoolValue &LHS, BoolValue &RHS, Environment &Env) {
160+
if (auto V = getLiteralValue(LHS.formula(), Env))
161+
return *V ? RHS : Env.getBoolLiteralValue(false);
162+
if (auto V = getLiteralValue(RHS.formula(), Env))
163+
return *V ? LHS : Env.getBoolLiteralValue(false);
164+
return Env.makeAnd(LHS, RHS);
165+
}
166+
167+
BoolValue &eqOp(BoolValue &LHS, BoolValue &RHS, Environment &Env) {
168+
if (&LHS.formula() == &RHS.formula())
169+
return Env.getBoolLiteralValue(true);
170+
if (auto V = getLiteralValue(LHS.formula(), Env))
171+
return *V ? RHS : Env.makeNot(RHS);
172+
if (auto V = getLiteralValue(RHS.formula(), Env))
173+
return *V ? LHS : Env.makeNot(LHS);
174+
return Env.makeIff(LHS, RHS);
175+
}
176+
177+
// FIXME: i think this could be implemented in terms of eqOp.
178+
BoolValue &neOp(BoolValue &LHS, BoolValue &RHS, Environment &Env) {
179+
if (&LHS.formula() == &RHS.formula())
180+
return Env.getBoolLiteralValue(false);
181+
if (auto V = getLiteralValue(LHS.formula(), Env))
182+
return *V ? Env.makeNot(RHS) : RHS;
183+
if (auto V = getLiteralValue(RHS.formula(), Env))
184+
return *V ? Env.makeNot(LHS) : LHS;
185+
return Env.makeNot(Env.makeIff(LHS, RHS));
186+
}
187+
188+
BoolValue &notOp(BoolValue &Sub, Environment &Env) { return Env.makeNot(Sub); }
189+
190+
// Updates atom settings in `Env` based on the formula. `AtomVal` indicates the
191+
// value to use for atoms encountered in the formula.
192+
static void assumeFormula(bool AtomVal, const Formula &F, Environment &Env) {
193+
// TODO: handle literals directly rather than calling getLiteralValue.
194+
std::optional<bool> Lit = getLiteralValue(F, Env);
195+
if (Lit.has_value())
196+
// FIXME: Nothing to do. Could verify no contradiction, but not sure what
197+
// we'd do with that here. Need to poison the Env.
198+
return;
199+
200+
switch (F.kind()) {
201+
case Formula::AtomRef:
202+
// FIXME: check for contradictions
203+
Env.setAtomValue(F.getAtom(), AtomVal);
204+
break;
205+
case Formula::Not: {
206+
ArrayRef<const Formula *> Operands = F.operands();
207+
assert(Operands.size() == 1);
208+
assumeFormula(!AtomVal, *Operands[0], Env);
209+
break;
210+
}
211+
case Formula::And: {
212+
if (AtomVal == true) {
213+
ArrayRef<const Formula *> Operands = F.operands();
214+
assert(Operands.size() == 2);
215+
assumeFormula(true, *Operands[0], Env);
216+
assumeFormula(true, *Operands[1], Env);
217+
}
218+
break;
219+
}
220+
case Formula::Or: {
221+
if (AtomVal == false) {
222+
// Interpret the negated "or" as "and" of negated operands.
223+
ArrayRef<const Formula *> Operands = F.operands();
224+
assert(Operands.size() == 2);
225+
assumeFormula(false, *Operands[0], Env);
226+
assumeFormula(false, *Operands[1], Env);
227+
}
228+
break;
229+
}
230+
case Formula::Equal: {
231+
ArrayRef<const Formula *> Operands = F.operands();
232+
assert(Operands.size() == 2);
233+
auto &LHS = *Operands[0];
234+
auto &RHS = *Operands[1];
235+
if (auto V = getLiteralValue(LHS, Env)) {
236+
assumeFormula(AtomVal == *V, RHS, Env);
237+
} else if (auto V = getLiteralValue(RHS, Env)) {
238+
assumeFormula(AtomVal == *V, LHS, Env);
239+
}
240+
break;
241+
}
242+
default:
243+
break;
244+
}
245+
}
246+
247+
void transferBranch(bool BranchVal, BoolValue &CondVal, Environment &Env) {
248+
assumeFormula(BranchVal, CondVal.formula(), Env);
119249
}
120250

121-
} // namespace bool_model
251+
} // namespace simple_bool_model
122252

123253
// Unpacks the value (if any) associated with `E` and updates `E` to the new
124254
// value, if any unpacking occured. Also, does the lvalue-to-rvalue conversion,

clang/lib/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,6 @@ computeBlockInputState(const CFGBlock &Block, AnalysisContext &AC) {
284284
Builder.addUnowned(PredState);
285285
continue;
286286
}
287-
288287
bool BranchVal = blockIndexInPredecessor(*Pred, Block) == 0;
289288

290289
// `transferBranch` may need to mutate the environment to describe the
@@ -465,7 +464,8 @@ transferCFGBlock(const CFGBlock &Block, AnalysisContext &AC,
465464
// we have *some* value for the condition expression. This ensures that
466465
// when we extend the flow condition, it actually changes.
467466
if (State.Env.getValue(*TerminatorCond) == nullptr)
468-
State.Env.setValue(*TerminatorCond, State.Env.makeAtomicBoolValue());
467+
State.Env.setValue(*TerminatorCond,
468+
bool_model::freshBoolValue(State.Env));
469469
AC.Log.recordState(State);
470470
}
471471

clang/unittests/Analysis/FlowSensitive/TransferTest.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9+
#include "clang/Analysis/FlowSensitive/Transfer.h"
910
#include "TestingSupport.h"
1011
#include "clang/AST/ASTContext.h"
1112
#include "clang/AST/Decl.h"
@@ -35,6 +36,7 @@ using ::testing::Eq;
3536
using ::testing::IsNull;
3637
using ::testing::Ne;
3738
using ::testing::NotNull;
39+
using ::testing::Optional;
3840
using ::testing::UnorderedElementsAre;
3941

4042
// Declares a minimal coroutine library.
@@ -4412,12 +4414,12 @@ TEST(TransferTest, LoopWithAssignmentConverges) {
44124414
bool foo();
44134415
44144416
void target() {
4415-
do {
4417+
do {
44164418
bool Bar = foo();
44174419
if (Bar) break;
44184420
(void)Bar;
44194421
/*[[p]]*/
4420-
} while (true);
4422+
} while (true);
44214423
}
44224424
)";
44234425
// The key property that we are verifying is implicit in `runDataflow` --
@@ -4434,6 +4436,8 @@ TEST(TransferTest, LoopWithAssignmentConverges) {
44344436
ASSERT_THAT(BarDecl, NotNull());
44354437

44364438
auto &BarVal = getFormula(*BarDecl, Env);
4439+
ASSERT_EQ(BarVal.kind(), Formula::AtomRef);
4440+
ASSERT_THAT(Env.getAtomValue(BarVal.getAtom()), Optional(false));
44374441
EXPECT_TRUE(Env.proves(Env.arena().makeNot(BarVal)));
44384442
});
44394443
}

clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,10 @@ TEST_F(DiscardExprStateTest, BooleanOperator) {
286286
const auto &AndOpState = blockStateForStmt(BlockStates, AndOp);
287287
EXPECT_EQ(AndOpState.Env.getValue(*AndOp.getLHS()), LHSValue);
288288
EXPECT_EQ(AndOpState.Env.getValue(*AndOp.getRHS()), RHSValue);
289+
// FIXME: this test is too strict. We want to check equivalence not equality;
290+
// as is, its a change detector test. Notice that we only evaluate `b1 && b2`
291+
// in a context where we know that `b1 is true, so there's a potential
292+
// optimization to store only `RHSValue` as the operation's value.
289293
EXPECT_EQ(AndOpState.Env.getValue(AndOp),
290294
&AndOpState.Env.makeAnd(*LHSValue, *RHSValue));
291295

0 commit comments

Comments
 (0)