[clang] 39b9d4f - [clang][dataflow] Add support for a Top value in boolean formulas.
Yitzhak Mandelbaum via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 14 10:42:13 PDT 2022
Author: Yitzhak Mandelbaum
Date: 2022-10-14T17:41:53Z
New Revision: 39b9d4f188ca1f99515658334d57c2961db33289
URL: https://github.com/llvm/llvm-project/commit/39b9d4f188ca1f99515658334d57c2961db33289
DIFF: https://github.com/llvm/llvm-project/commit/39b9d4f188ca1f99515658334d57c2961db33289.diff
LOG: [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 #56931.
Differential Revision: https://reviews.llvm.org/D135397
Added:
Modified:
clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
clang/include/clang/Analysis/FlowSensitive/Value.h
clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
clang/lib/Analysis/FlowSensitive/DebugSupport.cpp
clang/lib/Analysis/FlowSensitive/Transfer.cpp
clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp
clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp
clang/unittests/Analysis/FlowSensitive/SolverTest.cpp
clang/unittests/Analysis/FlowSensitive/TestingSupport.h
clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
index f73ea7985d79e..f8ee5864482b3 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowAnalysisContext.h
@@ -153,6 +153,18 @@ class DataflowAnalysisContext {
return takeOwnership(std::make_unique<AtomicBoolValue>());
}
+ /// Creates a Top value for booleans. Each instance is unique and can be
+ /// assigned a distinct truth value during solving.
+ ///
+ /// FIXME: `Top iff Top` is true when both Tops are identical (by pointer
+ /// equality), but not when they are distinct values. We should improve the
+ /// implementation so that `Top iff Top` has a consistent meaning, regardless
+ /// of the identity of `Top`. Moreover, I think the meaning should be
+ /// `false`.
+ TopBoolValue &createTopBoolValue() {
+ return takeOwnership(std::make_unique<TopBoolValue>());
+ }
+
/// Returns a boolean value that represents the conjunction of `LHS` and
/// `RHS`. Subsequent calls with the same arguments, regardless of their
/// order, will return the same result. If the given boolean values represent
diff --git a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
index d093967e193a1..efea46b4a0c5b 100644
--- a/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
+++ b/clang/include/clang/Analysis/FlowSensitive/DataflowEnvironment.h
@@ -288,6 +288,11 @@ class Environment {
return DACtx->createAtomicBoolValue();
}
+ /// Returns a unique instance of boolean Top.
+ BoolValue &makeTopBoolValue() const {
+ return DACtx->createTopBoolValue();
+ }
+
/// Returns a boolean value that represents the conjunction of `LHS` and
/// `RHS`. Subsequent calls with the same arguments, regardless of their
/// order, will return the same result. If the given boolean values represent
diff --git a/clang/include/clang/Analysis/FlowSensitive/Value.h b/clang/include/clang/Analysis/FlowSensitive/Value.h
index c63799fe6a464..b60f93a51965b 100644
--- a/clang/include/clang/Analysis/FlowSensitive/Value.h
+++ b/clang/include/clang/Analysis/FlowSensitive/Value.h
@@ -38,6 +38,7 @@ class Value {
Struct,
// Synthetic boolean values are either atomic values or logical connectives.
+ TopBool,
AtomicBool,
Conjunction,
Disjunction,
@@ -82,7 +83,8 @@ class BoolValue : public Value {
explicit BoolValue(Kind ValueKind) : Value(ValueKind) {}
static bool classof(const Value *Val) {
- return Val->getKind() == Kind::AtomicBool ||
+ return Val->getKind() == Kind::TopBool ||
+ Val->getKind() == Kind::AtomicBool ||
Val->getKind() == Kind::Conjunction ||
Val->getKind() == Kind::Disjunction ||
Val->getKind() == Kind::Negation ||
@@ -91,6 +93,20 @@ class BoolValue : public Value {
}
};
+/// Models the trivially true formula, which is Top in the lattice of boolean
+/// formulas.
+///
+/// FIXME: Given the subtlety of comparison involving `TopBoolValue`, define
+/// `operator==` for `Value`.
+class TopBoolValue final : public BoolValue {
+public:
+ TopBoolValue() : BoolValue(Kind::TopBool) {}
+
+ static bool classof(const Value *Val) {
+ return Val->getKind() == Kind::TopBool;
+ }
+};
+
/// Models an atomic boolean.
class AtomicBoolValue : public BoolValue {
public:
diff --git a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
index 71ab89d56ed90..29fb2b7603e30 100644
--- a/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
+++ b/clang/lib/Analysis/FlowSensitive/DataflowEnvironment.cpp
@@ -66,7 +66,8 @@ static bool equivalentValues(QualType Type, Value *Val1,
const Environment &Env1, Value *Val2,
const Environment &Env2,
Environment::ValueModel &Model) {
- return Val1 == Val2 || areEquivalentIndirectionValues(Val1, Val2) ||
+ return Val1 == Val2 || (isa<TopBoolValue>(Val1) && isa<TopBoolValue>(Val2)) ||
+ areEquivalentIndirectionValues(Val1, Val2) ||
Model.compareEquivalent(Type, *Val1, Env1, *Val2, Env2);
}
@@ -371,7 +372,8 @@ LatticeJoinEffect Environment::join(const Environment &Other,
continue;
assert(It->second != nullptr);
- if (Val == It->second) {
+ if (Val == It->second ||
+ (isa<TopBoolValue>(Val) && isa<TopBoolValue>(It->second))) {
JoinedEnv.LocToVal.insert({Loc, Val});
continue;
}
diff --git a/clang/lib/Analysis/FlowSensitive/DebugSupport.cpp b/clang/lib/Analysis/FlowSensitive/DebugSupport.cpp
index 220617ab9e30d..1e3ecf46e3112 100644
--- a/clang/lib/Analysis/FlowSensitive/DebugSupport.cpp
+++ b/clang/lib/Analysis/FlowSensitive/DebugSupport.cpp
@@ -44,6 +44,8 @@ llvm::StringRef debugString(Value::Kind Kind) {
return "Struct";
case Value::Kind::AtomicBool:
return "AtomicBool";
+ case Value::Kind::TopBool:
+ return "TopBool";
case Value::Kind::Conjunction:
return "Conjunction";
case Value::Kind::Disjunction:
diff --git a/clang/lib/Analysis/FlowSensitive/Transfer.cpp b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
index 6f490ed624661..4aa297d8e26a6 100644
--- a/clang/lib/Analysis/FlowSensitive/Transfer.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Transfer.cpp
@@ -28,6 +28,7 @@
#include "clang/Basic/OperatorKinds.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Casting.h"
+#include "llvm/Support/ErrorHandling.h"
#include <cassert>
#include <memory>
#include <tuple>
@@ -46,6 +47,84 @@ static BoolValue &evaluateBooleanEquality(const Expr &LHS, const Expr &RHS,
return Env.makeAtomicBoolValue();
}
+// Functionally updates `V` such that any instances of `TopBool` are replaced
+// with fresh atomic bools. Note: This implementation assumes that `B` is a
+// tree; if `B` is a DAG, it will lose any sharing between subvalues that was
+// present in the original .
+static BoolValue &unpackValue(BoolValue &V, Environment &Env);
+
+template <typename Derived, typename M>
+BoolValue &unpackBinaryBoolValue(Environment &Env, BoolValue &B, M build) {
+ auto &V = *cast<Derived>(&B);
+ BoolValue &Left = V.getLeftSubValue();
+ BoolValue &Right = V.getRightSubValue();
+ BoolValue &ULeft = unpackValue(Left, Env);
+ BoolValue &URight = unpackValue(Right, Env);
+
+ if (&ULeft == &Left && &URight == &Right)
+ return V;
+
+ return (Env.*build)(ULeft, URight);
+}
+
+static BoolValue &unpackValue(BoolValue &V, Environment &Env) {
+ switch (V.getKind()) {
+ case Value::Kind::Integer:
+ case Value::Kind::Reference:
+ case Value::Kind::Pointer:
+ case Value::Kind::Struct:
+ llvm_unreachable("BoolValue cannot have any of these kinds.");
+
+ case Value::Kind::AtomicBool:
+ return V;
+
+ case Value::Kind::TopBool:
+ // Unpack `TopBool` into a fresh atomic bool.
+ return Env.makeAtomicBoolValue();
+
+ case Value::Kind::Negation: {
+ auto &N = *cast<NegationValue>(&V);
+ BoolValue &Sub = N.getSubVal();
+ BoolValue &USub = unpackValue(Sub, Env);
+
+ if (&USub == &Sub)
+ return V;
+ return Env.makeNot(USub);
+ }
+ case Value::Kind::Conjunction:
+ return unpackBinaryBoolValue<ConjunctionValue>(Env, V,
+ &Environment::makeAnd);
+ case Value::Kind::Disjunction:
+ return unpackBinaryBoolValue<DisjunctionValue>(Env, V,
+ &Environment::makeOr);
+ case Value::Kind::Implication:
+ return unpackBinaryBoolValue<ImplicationValue>(
+ Env, V, &Environment::makeImplication);
+ case Value::Kind::Biconditional:
+ return unpackBinaryBoolValue<BiconditionalValue>(Env, V,
+ &Environment::makeIff);
+ }
+}
+
+// Unpacks the value (if any) associated with `E` and updates `E` to the new
+// value, if any unpacking occured.
+static Value *maybeUnpackLValueExpr(const Expr &E, Environment &Env) {
+ auto *Loc = Env.getStorageLocation(E, SkipPast::Reference);
+ if (Loc == nullptr)
+ return nullptr;
+ auto *Val = Env.getValue(*Loc);
+
+ auto *B = dyn_cast_or_null<BoolValue>(Val);
+ if (B == nullptr)
+ return Val;
+
+ auto &UnpackedVal = unpackValue(*B, Env);
+ if (&UnpackedVal == Val)
+ return Val;
+ Env.setValue(*Loc, UnpackedVal);
+ return &UnpackedVal;
+}
+
class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
public:
TransferVisitor(const StmtToEnvMap &StmtToEnv, Environment &Env,
@@ -222,7 +301,9 @@ class TransferVisitor : public ConstStmtVisitor<TransferVisitor> {
}
case CK_LValueToRValue: {
- auto *SubExprVal = Env.getValue(*SubExpr, SkipPast::Reference);
+ // When an L-value is used as an R-value, it may result in sharing, so we
+ // need to unpack any nested `Top`s.
+ auto *SubExprVal = maybeUnpackLValueExpr(*SubExpr, Env);
if (SubExprVal == nullptr)
break;
diff --git a/clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp b/clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp
index cd1fd708a43aa..caa1ed266c5f2 100644
--- a/clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp
+++ b/clang/lib/Analysis/FlowSensitive/WatchedLiteralsSolver.cpp
@@ -233,6 +233,11 @@ BooleanFormula buildBooleanFormula(const llvm::DenseSet<BoolValue *> &Vals) {
UnprocessedSubVals.push(&B->getRightSubValue());
break;
}
+ case Value::Kind::TopBool:
+ // Nothing more to do. This `TopBool` instance has already been mapped
+ // to a fresh solver variable (`NextVar`, above) and is thereafter
+ // anonymous. The solver never sees `Top`.
+ break;
case Value::Kind::AtomicBool: {
Atomics[Var] = cast<AtomicBoolValue>(Val);
break;
diff --git a/clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp b/clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp
index 926e871a8ee2a..0efb341cc06e7 100644
--- a/clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/DataflowAnalysisContextTest.cpp
@@ -32,6 +32,19 @@ TEST_F(DataflowAnalysisContextTest,
EXPECT_NE(&X, &Y);
}
+TEST_F(DataflowAnalysisContextTest,
+ CreateTopBoolValueReturnsDistinctValues) {
+ auto &X = Context.createTopBoolValue();
+ auto &Y = Context.createTopBoolValue();
+ EXPECT_NE(&X, &Y);
+}
+
+TEST_F(DataflowAnalysisContextTest, DistinctTopsNotEquivalent) {
+ auto &X = Context.createTopBoolValue();
+ auto &Y = Context.createTopBoolValue();
+ EXPECT_FALSE(Context.equivalentBoolValues(X, Y));
+}
+
TEST_F(DataflowAnalysisContextTest,
GetOrCreateConjunctionReturnsSameExprGivenSameArgs) {
auto &X = Context.createAtomicBoolValue();
diff --git a/clang/unittests/Analysis/FlowSensitive/SolverTest.cpp b/clang/unittests/Analysis/FlowSensitive/SolverTest.cpp
index 1de1c3a22e488..b56ad9a201ce5 100644
--- a/clang/unittests/Analysis/FlowSensitive/SolverTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/SolverTest.cpp
@@ -10,7 +10,6 @@
#include "TestingSupport.h"
#include "clang/Analysis/FlowSensitive/Solver.h"
-#include "TestingSupport.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
#include "gmock/gmock.h"
@@ -24,6 +23,7 @@ using namespace dataflow;
using test::ConstraintContext;
using testing::_;
using testing::AnyOf;
+using testing::IsEmpty;
using testing::Optional;
using testing::Pair;
using testing::UnorderedElementsAre;
@@ -88,6 +88,32 @@ TEST(SolverTest, DistinctVars) {
Pair(Y, Solver::Result::Assignment::AssignedFalse)));
}
+TEST(SolverTest, Top) {
+ ConstraintContext Ctx;
+ auto X = Ctx.top();
+
+ // X. Since Top is anonymous, we do not get any results in the solution.
+ expectSatisfiable(solve({X}), IsEmpty());
+}
+
+TEST(SolverTest, NegatedTop) {
+ ConstraintContext Ctx;
+ auto X = Ctx.top();
+
+ // !X
+ expectSatisfiable(solve({Ctx.neg(X)}), IsEmpty());
+}
+
+TEST(SolverTest, DistinctTops) {
+ ConstraintContext Ctx;
+ auto X = Ctx.top();
+ auto Y = Ctx.top();
+ auto NotY = Ctx.neg(Y);
+
+ // X ^ !Y
+ expectSatisfiable(solve({X, NotY}), IsEmpty());
+}
+
TEST(SolverTest, DoubleNegation) {
ConstraintContext Ctx;
auto X = Ctx.atom();
diff --git a/clang/unittests/Analysis/FlowSensitive/TestingSupport.h b/clang/unittests/Analysis/FlowSensitive/TestingSupport.h
index 909a475bbf9e1..bc5a2bf6cbc6f 100644
--- a/clang/unittests/Analysis/FlowSensitive/TestingSupport.h
+++ b/clang/unittests/Analysis/FlowSensitive/TestingSupport.h
@@ -372,6 +372,12 @@ class ConstraintContext {
return Vals.back().get();
}
+ // Creates an instance of the Top boolean value.
+ BoolValue *top() {
+ Vals.push_back(std::make_unique<TopBoolValue>());
+ return Vals.back().get();
+ }
+
// Creates a boolean conjunction value.
BoolValue *conj(BoolValue *LeftSubVal, BoolValue *RightSubVal) {
Vals.push_back(
diff --git a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp
index 90f898e3bc562..341e2f2da833d 100644
--- a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp
@@ -17,6 +17,7 @@
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
+#include "clang/Analysis/FlowSensitive/DebugSupport.h"
#include "clang/Analysis/FlowSensitive/NoopAnalysis.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
@@ -166,7 +167,7 @@ class FunctionCallAnalysis
auto CS = Elt->getAs<CFGStmt>();
if (!CS)
return;
- auto S = CS->getStmt();
+ const auto *S = CS->getStmt();
if (auto *C = dyn_cast<CallExpr>(S)) {
if (auto *F = dyn_cast<FunctionDecl>(C->getCalleeDecl())) {
E.CalledFunctions.insert(F->getNameInfo().getAsString());
@@ -310,6 +311,9 @@ TEST_F(NoreturnDestructorTest, ConditionalOperatorNestedBranchReturns) {
}
// Models an analysis that uses flow conditions.
+//
+// FIXME: Here and below: change class to final and final methods to override,
+// since we're marking them all as final.
class SpecialBoolAnalysis
: public DataflowAnalysis<SpecialBoolAnalysis, NoopLattice> {
public:
@@ -322,7 +326,7 @@ class SpecialBoolAnalysis
auto CS = Elt->getAs<CFGStmt>();
if (!CS)
return;
- auto S = CS->getStmt();
+ const auto *S = CS->getStmt();
auto SpecialBoolRecordDecl = recordDecl(hasName("SpecialBool"));
auto HasSpecialBoolType = hasType(SpecialBoolRecordDecl);
@@ -468,9 +472,8 @@ TEST_F(JoinFlowConditionsTest, JoinDistinctButProvablyEquivalentValues) {
class OptionalIntAnalysis
: public DataflowAnalysis<OptionalIntAnalysis, NoopLattice> {
public:
- explicit OptionalIntAnalysis(ASTContext &Context, BoolValue &HasValueTop)
- : DataflowAnalysis<OptionalIntAnalysis, NoopLattice>(Context),
- HasValueTop(HasValueTop) {}
+ explicit OptionalIntAnalysis(ASTContext &Context)
+ : DataflowAnalysis<OptionalIntAnalysis, NoopLattice>(Context) {}
static NoopLattice initialElement() { return {}; }
@@ -478,22 +481,23 @@ class OptionalIntAnalysis
auto CS = Elt->getAs<CFGStmt>();
if (!CS)
return;
- auto S = CS->getStmt();
+ const Stmt *S = CS->getStmt();
auto OptionalIntRecordDecl = recordDecl(hasName("OptionalInt"));
auto HasOptionalIntType = hasType(OptionalIntRecordDecl);
+ SmallVector<BoundNodes, 1> Matches = match(
+ stmt(anyOf(cxxConstructExpr(HasOptionalIntType).bind("construct"),
+ cxxOperatorCallExpr(
+ callee(cxxMethodDecl(ofClass(OptionalIntRecordDecl))))
+ .bind("operator"))),
+ *S, getASTContext());
if (const auto *E = selectFirst<CXXConstructExpr>(
- "call", match(cxxConstructExpr(HasOptionalIntType).bind("call"), *S,
- getASTContext()))) {
+ "construct", Matches)) {
auto &ConstructorVal = *Env.createValue(E->getType());
ConstructorVal.setProperty("has_value", Env.getBoolLiteralValue(false));
Env.setValue(*Env.getStorageLocation(*E, SkipPast::None), ConstructorVal);
- } else if (const auto *E = selectFirst<CXXOperatorCallExpr>(
- "call",
- match(cxxOperatorCallExpr(callee(cxxMethodDecl(ofClass(
- OptionalIntRecordDecl))))
- .bind("call"),
- *S, getASTContext()))) {
+ } else if (const auto *E =
+ selectFirst<CXXOperatorCallExpr>("operator", Matches)) {
assert(E->getNumArgs() > 0);
auto *Object = E->getArg(0);
assert(Object != nullptr);
@@ -516,7 +520,11 @@ class OptionalIntAnalysis
Type->getAsCXXRecordDecl()->getQualifiedNameAsString() != "OptionalInt")
return false;
- return Val1.getProperty("has_value") == Val2.getProperty("has_value");
+ auto *Prop1 = Val1.getProperty("has_value");
+ auto *Prop2 = Val2.getProperty("has_value");
+ return Prop1 == Prop2 ||
+ (Prop1 != nullptr && Prop2 != nullptr && isa<TopBoolValue>(Prop1) &&
+ isa<TopBoolValue>(Prop2));
}
bool merge(QualType Type, const Value &Val1, const Environment &Env1,
@@ -538,11 +546,9 @@ class OptionalIntAnalysis
if (HasValue1 == HasValue2)
MergedVal.setProperty("has_value", *HasValue1);
else
- MergedVal.setProperty("has_value", HasValueTop);
+ MergedVal.setProperty("has_value", MergedEnv.makeTopBoolValue());
return true;
}
-
- BoolValue &HasValueTop;
};
class WideningTest : public Test {
@@ -561,11 +567,8 @@ class WideningTest : public Test {
checkDataflow<OptionalIntAnalysis>(
AnalysisInputs<OptionalIntAnalysis>(
Code, ast_matchers::hasName("target"),
- [this](ASTContext &Context, Environment &Env) {
- assert(HasValueTop == nullptr);
- HasValueTop =
- &Env.takeOwnership(std::make_unique<AtomicBoolValue>());
- return OptionalIntAnalysis(Context, *HasValueTop);
+ [](ASTContext &Context, Environment &Env) {
+ return OptionalIntAnalysis(Context);
})
.withASTBuildArgs({"-fsyntax-only", "-std=c++17"})
.withASTBuildVirtualMappedFiles(std::move(FilesContents)),
@@ -576,8 +579,6 @@ class WideningTest : public Test {
&AO) { Match(Results, AO.ASTCtx); }),
llvm::Succeeded());
}
-
- BoolValue *HasValueTop = nullptr;
};
TEST_F(WideningTest, JoinDistinctValuesWithDistinctProperties) {
@@ -597,8 +598,8 @@ TEST_F(WideningTest, JoinDistinctValuesWithDistinctProperties) {
)";
runDataflow(
Code,
- [this](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
- ASTContext &ASTCtx) {
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ ASTContext &ASTCtx) {
ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2", "p3"));
const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");
@@ -615,7 +616,8 @@ TEST_F(WideningTest, JoinDistinctValuesWithDistinctProperties) {
&Env1.getBoolLiteralValue(false));
EXPECT_EQ(GetFooValue(Env2)->getProperty("has_value"),
&Env2.getBoolLiteralValue(true));
- EXPECT_EQ(GetFooValue(Env3)->getProperty("has_value"), HasValueTop);
+ EXPECT_TRUE(
+ isa<TopBoolValue>(GetFooValue(Env3)->getProperty("has_value")));
});
}
@@ -1162,4 +1164,394 @@ TEST_F(FlowConditionTest, PointerToBoolImplicitCast) {
});
}
+class TopAnalysis final : public DataflowAnalysis<TopAnalysis, NoopLattice> {
+public:
+ explicit TopAnalysis(ASTContext &Context)
+ : DataflowAnalysis<TopAnalysis, NoopLattice>(Context) {}
+
+ static NoopLattice initialElement() { return {}; }
+
+ void transfer(const CFGElement *Elt, NoopLattice &, Environment &Env) {
+ auto CS = Elt->getAs<CFGStmt>();
+ if (!CS)
+ return;
+ const Stmt *S = CS->getStmt();
+ SmallVector<BoundNodes, 1> Matches =
+ match(callExpr(callee(functionDecl(hasName("makeTop")))).bind("top"),
+ *S, getASTContext());
+ if (const auto *E = selectFirst<CallExpr>("top", Matches)) {
+ auto &Loc = Env.createStorageLocation(*E);
+ Env.setValue(Loc, Env.makeTopBoolValue());
+ Env.setStorageLocation(*E, Loc);
+ }
+ }
+
+ bool compareEquivalent(QualType Type, const Value &Val1,
+ const Environment &Env1, const Value &Val2,
+ const Environment &Env2) override {
+ // Changes to a sound approximation, which allows us to test whether we can
+ // (soundly) converge for some loops.
+ return false;
+ }
+};
+
+class TopTest : public Test {
+protected:
+ template <typename Matcher>
+ void runDataflow(llvm::StringRef Code, Matcher VerifyResults) {
+ ASSERT_THAT_ERROR(
+ checkDataflow<TopAnalysis>(
+ AnalysisInputs<TopAnalysis>(
+ Code, ast_matchers::hasName("target"),
+ [](ASTContext &Context, Environment &Env) {
+ return TopAnalysis(Context);
+ })
+ .withASTBuildArgs({"-fsyntax-only", "-std=c++17"}),
+ VerifyResults),
+ llvm::Succeeded());
+ }
+};
+
+// Tests that when Top is unused it remains Top.
+TEST_F(TopTest, UnusedTopInitializer) {
+ std::string Code = R"(
+ bool makeTop();
+
+ void target() {
+ bool Foo = makeTop();
+ /*[[p1]]*/
+ (void)0;
+ /*[[p2]]*/
+ }
+ )";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ const AnalysisOutputs &AO) {
+ ASSERT_THAT(Results.keys(),
+ UnorderedElementsAre("p1", "p2"));
+ const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
+ const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");
+
+ const ValueDecl *FooDecl = findValueDecl(AO.ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+
+ auto GetFooValue = [FooDecl](const Environment &Env) {
+ return Env.getValue(*FooDecl, SkipPast::None);
+ };
+
+ Value *FooVal1 = GetFooValue(Env1);
+ ASSERT_THAT(FooVal1, NotNull());
+ EXPECT_TRUE(isa<TopBoolValue>(FooVal1))
+ << debugString(FooVal1->getKind());
+
+ Value *FooVal2 = GetFooValue(Env2);
+ ASSERT_THAT(FooVal2, NotNull());
+ EXPECT_TRUE(isa<TopBoolValue>(FooVal2))
+ << debugString(FooVal2->getKind());
+
+ EXPECT_EQ(FooVal1, FooVal2);
+ });
+}
+
+// Tests that when Top is unused it remains Top. Like above, but uses the
+// assignment form rather than initialization, which uses Top as an lvalue that
+// is *not* in an rvalue position.
+TEST_F(TopTest, UnusedTopAssignment) {
+ std::string Code = R"(
+ bool makeTop();
+
+ void target() {
+ bool Foo;
+ Foo = makeTop();
+ /*[[p1]]*/
+ (void)0;
+ /*[[p2]]*/
+ }
+ )";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ const AnalysisOutputs &AO) {
+ ASSERT_THAT(Results.keys(),
+ UnorderedElementsAre("p1", "p2"));
+ const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
+ const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");
+
+ const ValueDecl *FooDecl = findValueDecl(AO.ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+
+ auto GetFooValue = [FooDecl](const Environment &Env) {
+ return Env.getValue(*FooDecl, SkipPast::None);
+ };
+
+ Value *FooVal1 = GetFooValue(Env1);
+ ASSERT_THAT(FooVal1, NotNull());
+ EXPECT_TRUE(isa<TopBoolValue>(FooVal1))
+ << debugString(FooVal1->getKind());
+
+ Value *FooVal2 = GetFooValue(Env2);
+ ASSERT_THAT(FooVal2, NotNull());
+ EXPECT_TRUE(isa<TopBoolValue>(FooVal2))
+ << debugString(FooVal2->getKind());
+
+ EXPECT_EQ(FooVal1, FooVal2);
+ });
+}
+
+TEST_F(TopTest, UnusedTopJoinsToTop) {
+ std::string Code = R"(
+ bool makeTop();
+
+ void target(bool Cond, bool F) {
+ bool Foo = makeTop();
+ // Force a new CFG block.
+ if (F) return;
+ (void)0;
+ /*[[p1]]*/
+
+ bool Zab1;
+ bool Zab2;
+ if (Cond) {
+ Zab1 = true;
+ } else {
+ Zab2 = true;
+ }
+ (void)0;
+ /*[[p2]]*/
+ }
+ )";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ const AnalysisOutputs &AO) {
+ ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2"));
+ const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
+ const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");
+
+ const ValueDecl *FooDecl = findValueDecl(AO.ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ auto GetFooValue = [FooDecl](const Environment &Env) {
+ return Env.getValue(*FooDecl, SkipPast::None);
+ };
+
+ Value *FooVal1 = GetFooValue(Env1);
+ ASSERT_THAT(FooVal1, NotNull());
+ EXPECT_TRUE(isa<TopBoolValue>(FooVal1))
+ << debugString(FooVal1->getKind());
+
+ Value *FooVal2 = GetFooValue(Env2);
+ ASSERT_THAT(FooVal2, NotNull());
+ EXPECT_TRUE(isa<TopBoolValue>(FooVal2))
+ << debugString(FooVal2->getKind());
+ });
+}
+
+TEST_F(TopTest, TopUsedBeforeBranchJoinsToSameAtomicBool) {
+ std::string Code = R"(
+ bool makeTop();
+
+ void target(bool Cond, bool F) {
+ bool Foo = makeTop();
+ /*[[p0]]*/
+
+ // Use `Top`.
+ bool Bar = Foo;
+ // Force a new CFG block.
+ if (F) return;
+ (void)0;
+ /*[[p1]]*/
+
+ bool Zab1;
+ bool Zab2;
+ if (Cond) {
+ Zab1 = true;
+ } else {
+ Zab2 = true;
+ }
+ (void)0;
+ /*[[p2]]*/
+ }
+ )";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ const AnalysisOutputs &AO) {
+ ASSERT_THAT(Results.keys(),
+ UnorderedElementsAre("p0", "p1", "p2"));
+ const Environment &Env0 = getEnvironmentAtAnnotation(Results, "p0");
+ const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
+ const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");
+
+ const ValueDecl *FooDecl = findValueDecl(AO.ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ auto GetFooValue = [FooDecl](const Environment &Env) {
+ return Env.getValue(*FooDecl, SkipPast::None);
+ };
+
+ Value *FooVal0 = GetFooValue(Env0);
+ ASSERT_THAT(FooVal0, NotNull());
+ EXPECT_TRUE(isa<TopBoolValue>(FooVal0))
+ << debugString(FooVal0->getKind());
+
+ Value *FooVal1 = GetFooValue(Env1);
+ ASSERT_THAT(FooVal1, NotNull());
+ EXPECT_TRUE(isa<AtomicBoolValue>(FooVal1))
+ << debugString(FooVal1->getKind());
+
+ Value *FooVal2 = GetFooValue(Env2);
+ ASSERT_THAT(FooVal2, NotNull());
+ EXPECT_TRUE(isa<AtomicBoolValue>(FooVal2))
+ << debugString(FooVal2->getKind());
+
+ EXPECT_EQ(FooVal2, FooVal1);
+ });
+}
+
+TEST_F(TopTest, TopUsedInBothBranchesJoinsToAtomic) {
+ std::string Code = R"(
+ bool makeTop();
+
+ void target(bool Cond, bool F) {
+ bool Foo = makeTop();
+ // Force a new CFG block.
+ if (F) return;
+ (void)0;
+ /*[[p1]]*/
+
+ bool Zab1;
+ bool Zab2;
+ if (Cond) {
+ Zab1 = Foo;
+ } else {
+ Zab2 = Foo;
+ }
+ (void)0;
+ /*[[p2]]*/
+ }
+ )";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ const AnalysisOutputs &AO) {
+ ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2"));
+ const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
+ const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");
+
+ const ValueDecl *FooDecl = findValueDecl(AO.ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ auto GetFooValue = [FooDecl](const Environment &Env) {
+ return Env.getValue(*FooDecl, SkipPast::None);
+ };
+
+ Value *FooVal1 = GetFooValue(Env1);
+ ASSERT_THAT(FooVal1, NotNull());
+ EXPECT_TRUE(isa<TopBoolValue>(FooVal1))
+ << debugString(FooVal1->getKind());
+
+ Value *FooVal2 = GetFooValue(Env2);
+ ASSERT_THAT(FooVal2, NotNull());
+ EXPECT_TRUE(isa<AtomicBoolValue>(FooVal2))
+ << debugString(FooVal2->getKind());
+ });
+}
+
+TEST_F(TopTest, TopUsedInBothBranchesWithoutPrecisionLoss) {
+ std::string Code = R"(
+ bool makeTop();
+
+ void target(bool Cond, bool F) {
+ bool Foo = makeTop();
+ // Force a new CFG block.
+ if (F) return;
+ (void)0;
+
+ bool Bar;
+ if (Cond) {
+ Bar = Foo;
+ } else {
+ Bar = Foo;
+ }
+ (void)0;
+ /*[[p]]*/
+ }
+ )";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ const AnalysisOutputs &AO) {
+ ASSERT_THAT(Results.keys(), UnorderedElementsAre("p"));
+ const Environment &Env = getEnvironmentAtAnnotation(Results, "p");
+
+ const ValueDecl *FooDecl = findValueDecl(AO.ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ const ValueDecl *BarDecl = findValueDecl(AO.ASTCtx, "Bar");
+ ASSERT_THAT(BarDecl, NotNull());
+
+ auto *FooVal =
+ dyn_cast_or_null<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
+ ASSERT_THAT(FooVal, NotNull());
+
+ auto *BarVal =
+ dyn_cast_or_null<BoolValue>(Env.getValue(*BarDecl, SkipPast::None));
+ ASSERT_THAT(BarVal, NotNull());
+
+ EXPECT_TRUE(Env.flowConditionImplies(Env.makeIff(*FooVal, *BarVal)));
+ });
+}
+
+TEST_F(TopTest, TopUnusedBeforeLoopHeadJoinsToTop) {
+ std::string Code = R"(
+ bool makeTop();
+
+ void target(bool Cond, bool F) {
+ bool Foo = makeTop();
+ // Force a new CFG block.
+ if (F) return;
+ (void)0;
+ /*[[p1]]*/
+
+ while (Cond) {
+ // Use `Foo`.
+ bool Zab = Foo;
+ Zab = false;
+ Foo = makeTop();
+ }
+ (void)0;
+ /*[[p2]]*/
+ }
+ )";
+ runDataflow(
+ Code,
+ [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results,
+ const AnalysisOutputs &AO) {
+ ASSERT_THAT(Results.keys(), UnorderedElementsAre("p1", "p2"));
+ const Environment &Env1 = getEnvironmentAtAnnotation(Results, "p1");
+ const Environment &Env2 = getEnvironmentAtAnnotation(Results, "p2");
+
+ const ValueDecl *FooDecl = findValueDecl(AO.ASTCtx, "Foo");
+ ASSERT_THAT(FooDecl, NotNull());
+
+ auto GetFooValue = [FooDecl](const Environment &Env) {
+ return Env.getValue(*FooDecl, SkipPast::None);
+ };
+
+ Value *FooVal1 = GetFooValue(Env1);
+ ASSERT_THAT(FooVal1, NotNull());
+ EXPECT_TRUE(isa<TopBoolValue>(FooVal1))
+ << debugString(FooVal1->getKind());
+
+ Value *FooVal2 = GetFooValue(Env2);
+ ASSERT_THAT(FooVal2, NotNull());
+ EXPECT_TRUE(isa<TopBoolValue>(FooVal2))
+ << debugString(FooVal2->getKind());
+
+ });
+}
} // namespace
More information about the cfe-commits
mailing list