Skip to content

Commit 16cdcfa

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 5cb5ae2 commit 16cdcfa

File tree

9 files changed

+222
-22
lines changed

9 files changed

+222
-22
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: 23 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"
@@ -706,6 +707,12 @@ Environment Environment::join(const Environment &EnvA, const Environment &EnvB,
706707
JoinedEnv.ExprToLoc = joinExprMaps(EnvA.ExprToLoc, EnvB.ExprToLoc);
707708
}
708709

710+
for (auto &Entry : EnvA.AtomToVal) {
711+
auto It = EnvB.AtomToVal.find(Entry.first);
712+
if (It != EnvB.AtomToVal.end() && Entry.second == It->second)
713+
JoinedEnv.AtomToVal.insert({Entry.first, Entry.second});
714+
}
715+
709716
return JoinedEnv;
710717
}
711718

@@ -1059,9 +1066,16 @@ void Environment::assume(const Formula &F) {
10591066
DACtx->addFlowConditionConstraint(FlowConditionToken, F);
10601067
}
10611068

1069+
#if 0
10621070
bool Environment::proves(const Formula &F) const {
10631071
return DACtx->flowConditionImplies(FlowConditionToken, F);
10641072
}
1073+
#else
1074+
bool Environment::proves(const Formula &F) const {
1075+
auto V = simple_bool_model::getLiteralValue(F, *this);
1076+
return V.has_value() && *V;
1077+
}
1078+
#endif
10651079

10661080
bool Environment::allows(const Formula &F) const {
10671081
return DACtx->flowConditionAllows(FlowConditionToken, F);
@@ -1119,6 +1133,15 @@ void Environment::dump() const {
11191133
dump(llvm::dbgs());
11201134
}
11211135

1136+
std::optional<bool> Environment::getAtomValue(Atom A) const {
1137+
auto It = AtomToVal.find(A);
1138+
if (It == AtomToVal.end())
1139+
return std::nullopt;
1140+
return It->second;
1141+
}
1142+
1143+
void Environment::setAtomValue(Atom A, bool b) { AtomToVal[A] = b; }
1144+
11221145
RecordStorageLocation *getImplicitObjectLocation(const CXXMemberCallExpr &MCE,
11231146
const Environment &Env) {
11241147
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
@@ -569,8 +569,7 @@ const Formula &evaluateEquality(Arena &A, const Formula &EqVal,
569569
// If neither is set, then they are equal.
570570
// We rewrite b) as !EqVal => (LHS v RHS), for a more compact formula.
571571
return A.makeAnd(
572-
A.makeImplies(EqVal, A.makeOr(A.makeAnd(LHS, RHS),
573-
A.makeAnd(A.makeNot(LHS), A.makeNot(RHS)))),
572+
A.makeImplies(EqVal, A.makeEquals(LHS, RHS)),
574573
A.makeImplies(A.makeNot(EqVal), A.makeOr(LHS, RHS)));
575574
}
576575

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: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,7 @@ computeBlockInputState(const CFGBlock &Block, AnalysisContext &AC) {
290290
// assert ourselves instead of asserting via `cast()` so that we get
291291
// a more meaningful line number if the assertion fails.
292292
assert(CondVal != nullptr);
293-
BoolValue *AssertedVal =
294-
BranchVal ? CondVal : &Copy.Env.makeNot(*CondVal);
295-
Copy.Env.assume(AssertedVal->formula());
293+
bool_model::transferBranch(BranchVal, *CondVal, Copy.Env);
296294
}
297295
AC.Analysis.transferBranchTypeErased(BranchVal, Cond, Copy.Lattice,
298296
Copy.Env);
@@ -463,7 +461,8 @@ transferCFGBlock(const CFGBlock &Block, AnalysisContext &AC,
463461
// we have *some* value for the condition expression. This ensures that
464462
// when we extend the flow condition, it actually changes.
465463
if (State.Env.getValue(*TerminatorCond) == nullptr)
466-
State.Env.setValue(*TerminatorCond, State.Env.makeAtomicBoolValue());
464+
State.Env.setValue(*TerminatorCond,
465+
bool_model::freshBoolValue(State.Env));
467466
AC.Log.recordState(State);
468467
}
469468

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
void runDataflow(
@@ -4331,12 +4333,12 @@ TEST(TransferTest, LoopWithAssignmentConverges) {
43314333
bool foo();
43324334
43334335
void target() {
4334-
do {
4336+
do {
43354337
bool Bar = foo();
43364338
if (Bar) break;
43374339
(void)Bar;
43384340
/*[[p]]*/
4339-
} while (true);
4341+
} while (true);
43404342
}
43414343
)";
43424344
// The key property that we are verifying is implicit in `runDataflow` --
@@ -4353,6 +4355,8 @@ TEST(TransferTest, LoopWithAssignmentConverges) {
43534355
ASSERT_THAT(BarDecl, NotNull());
43544356

43554357
auto &BarVal = getFormula(*BarDecl, Env);
4358+
ASSERT_EQ(BarVal.kind(), Formula::AtomRef);
4359+
ASSERT_THAT(Env.getAtomValue(BarVal.getAtom()), Optional(false));
43564360
EXPECT_TRUE(Env.proves(Env.arena().makeNot(BarVal)));
43574361
});
43584362
}

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)