forked from OSchip/llvm-project
[clang][dataflow] Extend flow conditions from block terminators
This is part of the implementation of the dataflow analysis framework. See "[RFC] A dataflow analysis framework for Clang AST" on cfe-dev. Reviewed-by: ymandel, xazax.hun Differential Revision: https://reviews.llvm.org/D120984
This commit is contained in:
parent
ad1c32e9b3
commit
1e5715857a
|
@ -69,25 +69,38 @@ public:
|
|||
/// `Val1` and `Val2` must be distinct.
|
||||
///
|
||||
/// `Val1` and `Val2` must model values of type `Type`.
|
||||
///
|
||||
/// `Val1` and `Val2` must be assigned to the same storage location in
|
||||
/// `Env1` and `Env2` respectively.
|
||||
virtual bool compareEquivalent(QualType Type, const Value &Val1,
|
||||
const Value &Val2) {
|
||||
const Environment &Env1, const Value &Val2,
|
||||
const Environment &Env2) {
|
||||
// FIXME: Consider adding QualType to StructValue and removing the Type
|
||||
// argument here.
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Modifies `MergedVal` to approximate both `Val1` and `Val2`. This could
|
||||
/// be a strict lattice join or a more general widening operation. If this
|
||||
/// function returns true, `MergedVal` will be assigned to a storage
|
||||
/// location of type `Type` in `Env`.
|
||||
/// be a strict lattice join or a more general widening operation.
|
||||
///
|
||||
/// If this function returns true, `MergedVal` will be assigned to a storage
|
||||
/// location of type `Type` in `MergedEnv`.
|
||||
///
|
||||
/// `Env1` and `Env2` can be used to query child values and path condition
|
||||
/// implications of `Val1` and `Val2` respectively.
|
||||
///
|
||||
/// Requirements:
|
||||
///
|
||||
/// `Val1` and `Val2` must be distinct.
|
||||
///
|
||||
/// `Val1`, `Val2`, and `MergedVal` must model values of type `Type`.
|
||||
virtual bool merge(QualType Type, const Value &Val1, const Value &Val2,
|
||||
Value &MergedVal, Environment &Env) {
|
||||
///
|
||||
/// `Val1` and `Val2` must be assigned to the same storage location in
|
||||
/// `Env1` and `Env2` respectively.
|
||||
virtual bool merge(QualType Type, const Value &Val1,
|
||||
const Environment &Env1, const Value &Val2,
|
||||
const Environment &Env2, Value &MergedVal,
|
||||
Environment &MergedEnv) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
@ -236,13 +249,15 @@ public:
|
|||
}
|
||||
|
||||
/// Returns an atomic boolean value.
|
||||
BoolValue &makeAtomicBoolValue() { return DACtx->createAtomicBoolValue(); }
|
||||
BoolValue &makeAtomicBoolValue() const {
|
||||
return DACtx->createAtomicBoolValue();
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// the same value, the result will be the value itself.
|
||||
BoolValue &makeAnd(BoolValue &LHS, BoolValue &RHS) {
|
||||
BoolValue &makeAnd(BoolValue &LHS, BoolValue &RHS) const {
|
||||
return DACtx->getOrCreateConjunctionValue(LHS, RHS);
|
||||
}
|
||||
|
||||
|
@ -250,13 +265,13 @@ public:
|
|||
/// `RHS`. Subsequent calls with the same arguments, regardless of their
|
||||
/// order, will return the same result. If the given boolean values represent
|
||||
/// the same value, the result will be the value itself.
|
||||
BoolValue &makeOr(BoolValue &LHS, BoolValue &RHS) {
|
||||
BoolValue &makeOr(BoolValue &LHS, BoolValue &RHS) const {
|
||||
return DACtx->getOrCreateDisjunctionValue(LHS, RHS);
|
||||
}
|
||||
|
||||
/// Returns a boolean value that represents the negation of `Val`. Subsequent
|
||||
/// calls with the same argument will return the same result.
|
||||
BoolValue &makeNot(BoolValue &Val) {
|
||||
BoolValue &makeNot(BoolValue &Val) const {
|
||||
return DACtx->getOrCreateNegationValue(Val);
|
||||
}
|
||||
|
||||
|
@ -264,7 +279,7 @@ public:
|
|||
/// the same arguments, regardless of their order, will return the same
|
||||
/// result. If the given boolean values represent the same value, the result
|
||||
/// will be a value that represents the true boolean literal.
|
||||
BoolValue &makeImplication(BoolValue &LHS, BoolValue &RHS) {
|
||||
BoolValue &makeImplication(BoolValue &LHS, BoolValue &RHS) const {
|
||||
return &LHS == &RHS ? getBoolLiteralValue(true) : makeOr(makeNot(LHS), RHS);
|
||||
}
|
||||
|
||||
|
@ -272,7 +287,7 @@ public:
|
|||
/// the same arguments, regardless of their order, will return the same
|
||||
/// result. If the given boolean values represent the same value, the result
|
||||
/// will be a value that represents the true boolean literal.
|
||||
BoolValue &makeIff(BoolValue &LHS, BoolValue &RHS) {
|
||||
BoolValue &makeIff(BoolValue &LHS, BoolValue &RHS) const {
|
||||
return &LHS == &RHS
|
||||
? getBoolLiteralValue(true)
|
||||
: makeAnd(makeImplication(LHS, RHS), makeImplication(RHS, LHS));
|
||||
|
@ -283,7 +298,7 @@ public:
|
|||
|
||||
/// Returns true if and only if the clauses that constitute the flow condition
|
||||
/// imply that `Val` is true.
|
||||
bool flowConditionImplies(BoolValue &Val);
|
||||
bool flowConditionImplies(BoolValue &Val) const;
|
||||
|
||||
private:
|
||||
/// Creates a value appropriate for `Type`, if `Type` is supported, otherwise
|
||||
|
|
|
@ -49,7 +49,9 @@ llvm::DenseMap<K, V> intersectDenseMaps(const llvm::DenseMap<K, V> &Map1,
|
|||
}
|
||||
|
||||
/// Returns true if and only if `Val1` is equivalent to `Val2`.
|
||||
static bool equivalentValues(QualType Type, Value *Val1, Value *Val2,
|
||||
static bool equivalentValues(QualType Type, Value *Val1,
|
||||
const Environment &Env1, Value *Val2,
|
||||
const Environment &Env2,
|
||||
Environment::ValueModel &Model) {
|
||||
if (Val1 == Val2)
|
||||
return true;
|
||||
|
@ -60,7 +62,7 @@ static bool equivalentValues(QualType Type, Value *Val1, Value *Val2,
|
|||
return &IndVal1->getPointeeLoc() == &IndVal2->getPointeeLoc();
|
||||
}
|
||||
|
||||
return Model.compareEquivalent(Type, *Val1, *Val2);
|
||||
return Model.compareEquivalent(Type, *Val1, Env1, *Val2, Env2);
|
||||
}
|
||||
|
||||
/// Initializes a global storage value.
|
||||
|
@ -105,6 +107,63 @@ static void initGlobalVars(const Stmt &S, Environment &Env) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns constraints that represent the disjunction of `Constraints1` and
|
||||
/// `Constraints2`.
|
||||
///
|
||||
/// Requirements:
|
||||
///
|
||||
/// The elements of `Constraints1` and `Constraints2` must not be null.
|
||||
llvm::DenseSet<BoolValue *>
|
||||
joinConstraints(DataflowAnalysisContext *Context,
|
||||
const llvm::DenseSet<BoolValue *> &Constraints1,
|
||||
const llvm::DenseSet<BoolValue *> &Constraints2) {
|
||||
// `(X ^ Y) v (X ^ Z)` is logically equivalent to `X ^ (Y v Z)`. Therefore, to
|
||||
// avoid unnecessarily expanding the resulting set of constraints, we will add
|
||||
// all common constraints of `Constraints1` and `Constraints2` directly and
|
||||
// add a disjunction of the constraints that are not common.
|
||||
|
||||
llvm::DenseSet<BoolValue *> JoinedConstraints;
|
||||
|
||||
if (Constraints1.empty() || Constraints2.empty()) {
|
||||
// Disjunction of empty set and non-empty set is represented as empty set.
|
||||
return JoinedConstraints;
|
||||
}
|
||||
|
||||
BoolValue *Val1 = nullptr;
|
||||
for (BoolValue *Constraint : Constraints1) {
|
||||
if (Constraints2.contains(Constraint)) {
|
||||
// Add common constraints directly to `JoinedConstraints`.
|
||||
JoinedConstraints.insert(Constraint);
|
||||
} else if (Val1 == nullptr) {
|
||||
Val1 = Constraint;
|
||||
} else {
|
||||
Val1 = &Context->getOrCreateConjunctionValue(*Val1, *Constraint);
|
||||
}
|
||||
}
|
||||
|
||||
BoolValue *Val2 = nullptr;
|
||||
for (BoolValue *Constraint : Constraints2) {
|
||||
// Common constraints are added to `JoinedConstraints` above.
|
||||
if (Constraints1.contains(Constraint)) {
|
||||
continue;
|
||||
}
|
||||
if (Val2 == nullptr) {
|
||||
Val2 = Constraint;
|
||||
} else {
|
||||
Val2 = &Context->getOrCreateConjunctionValue(*Val2, *Constraint);
|
||||
}
|
||||
}
|
||||
|
||||
// An empty set of constraints (represented as a null value) is interpreted as
|
||||
// `true` and `true v X` is logically equivalent to `true` so we need to add a
|
||||
// constraint only if both `Val1` and `Val2` are not null.
|
||||
if (Val1 != nullptr && Val2 != nullptr)
|
||||
JoinedConstraints.insert(
|
||||
&Context->getOrCreateDisjunctionValue(*Val1, *Val2));
|
||||
|
||||
return JoinedConstraints;
|
||||
}
|
||||
|
||||
Environment::Environment(DataflowAnalysisContext &DACtx,
|
||||
const DeclContext &DeclCtx)
|
||||
: Environment(DACtx) {
|
||||
|
@ -162,7 +221,7 @@ bool Environment::equivalentTo(const Environment &Other,
|
|||
return false;
|
||||
assert(It->second != nullptr);
|
||||
|
||||
if (!equivalentValues(Loc->getType(), Val, It->second, Model))
|
||||
if (!equivalentValues(Loc->getType(), Val, *this, It->second, Other, Model))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -208,7 +267,8 @@ LatticeJoinEffect Environment::join(const Environment &Other,
|
|||
continue;
|
||||
assert(It->second != nullptr);
|
||||
|
||||
if (equivalentValues(Loc->getType(), Val, It->second, Model)) {
|
||||
if (equivalentValues(Loc->getType(), Val, *this, It->second, Other,
|
||||
Model)) {
|
||||
LocToVal.insert({Loc, Val});
|
||||
continue;
|
||||
}
|
||||
|
@ -217,12 +277,16 @@ LatticeJoinEffect Environment::join(const Environment &Other,
|
|||
// `ValueModel::merge` returns false to avoid storing unneeded values in
|
||||
// `DACtx`.
|
||||
if (Value *MergedVal = createValue(Loc->getType()))
|
||||
if (Model.merge(Loc->getType(), *Val, *It->second, *MergedVal, *this))
|
||||
if (Model.merge(Loc->getType(), *Val, *this, *It->second, Other,
|
||||
*MergedVal, *this))
|
||||
LocToVal.insert({Loc, MergedVal});
|
||||
}
|
||||
if (OldLocToVal.size() != LocToVal.size())
|
||||
Effect = LatticeJoinEffect::Changed;
|
||||
|
||||
FlowConditionConstraints = joinConstraints(DACtx, FlowConditionConstraints,
|
||||
Other.FlowConditionConstraints);
|
||||
|
||||
return Effect;
|
||||
}
|
||||
|
||||
|
@ -362,6 +426,11 @@ Value *Environment::createValueUnlessSelfReferential(
|
|||
Depth > MaxCompositeValueDepth)
|
||||
return nullptr;
|
||||
|
||||
if (Type->isBooleanType()) {
|
||||
CreatedValuesCount++;
|
||||
return &makeAtomicBoolValue();
|
||||
}
|
||||
|
||||
if (Type->isIntegerType()) {
|
||||
CreatedValuesCount++;
|
||||
return &takeOwnership(std::make_unique<IntegerValue>());
|
||||
|
@ -457,7 +526,7 @@ void Environment::addToFlowCondition(BoolValue &Val) {
|
|||
FlowConditionConstraints.insert(&Val);
|
||||
}
|
||||
|
||||
bool Environment::flowConditionImplies(BoolValue &Val) {
|
||||
bool Environment::flowConditionImplies(BoolValue &Val) const {
|
||||
// Returns true if and only if truth assignment of the flow condition implies
|
||||
// that `Val` is also true. We prove whether or not this property holds by
|
||||
// reducing the problem to satisfiability checking. In other words, we attempt
|
||||
|
|
|
@ -103,11 +103,9 @@ public:
|
|||
auto &Loc = Env.createStorageLocation(*S);
|
||||
Env.setStorageLocation(*S, Loc);
|
||||
if (S->getOpcode() == BO_LAnd)
|
||||
Env.setValue(Loc, Env.takeOwnership(std::make_unique<ConjunctionValue>(
|
||||
*LHSVal, *RHSVal)));
|
||||
Env.setValue(Loc, Env.makeAnd(*LHSVal, *RHSVal));
|
||||
else
|
||||
Env.setValue(Loc, Env.takeOwnership(std::make_unique<DisjunctionValue>(
|
||||
*LHSVal, *RHSVal)));
|
||||
Env.setValue(Loc, Env.makeOr(*LHSVal, *RHSVal));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -269,8 +267,7 @@ public:
|
|||
|
||||
auto &ExprLoc = Env.createStorageLocation(*S);
|
||||
Env.setStorageLocation(*S, ExprLoc);
|
||||
Env.setValue(ExprLoc, Env.takeOwnership(
|
||||
std::make_unique<NegationValue>(*SubExprVal)));
|
||||
Env.setValue(ExprLoc, Env.makeNot(*SubExprVal));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <vector>
|
||||
|
||||
#include "clang/AST/DeclCXX.h"
|
||||
#include "clang/AST/OperationKinds.h"
|
||||
#include "clang/AST/StmtVisitor.h"
|
||||
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
|
||||
#include "clang/Analysis/CFG.h"
|
||||
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
|
||||
|
@ -28,6 +30,7 @@
|
|||
#include "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/ADT/None.h"
|
||||
#include "llvm/ADT/Optional.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
|
||||
namespace clang {
|
||||
|
@ -54,6 +57,73 @@ private:
|
|||
llvm::ArrayRef<llvm::Optional<TypeErasedDataflowAnalysisState>> BlockToState;
|
||||
};
|
||||
|
||||
/// Returns the index of `Block` in the successors of `Pred`.
|
||||
static int blockIndexInPredecessor(const CFGBlock &Pred,
|
||||
const CFGBlock &Block) {
|
||||
auto BlockPos = llvm::find_if(
|
||||
Pred.succs(), [&Block](const CFGBlock::AdjacentBlock &Succ) {
|
||||
return Succ && Succ->getBlockID() == Block.getBlockID();
|
||||
});
|
||||
return BlockPos - Pred.succ_begin();
|
||||
}
|
||||
|
||||
/// Extends the flow condition of an environment based on a terminator
|
||||
/// statement.
|
||||
class TerminatorVisitor : public ConstStmtVisitor<TerminatorVisitor> {
|
||||
public:
|
||||
TerminatorVisitor(const StmtToEnvMap &StmtToEnv, Environment &Env,
|
||||
int BlockSuccIdx)
|
||||
: StmtToEnv(StmtToEnv), Env(Env), BlockSuccIdx(BlockSuccIdx) {}
|
||||
|
||||
void VisitIfStmt(const IfStmt *S) {
|
||||
auto *Cond = S->getCond()->IgnoreParenImpCasts();
|
||||
assert(Cond != nullptr);
|
||||
extendFlowCondition(*Cond);
|
||||
}
|
||||
|
||||
void VisitWhileStmt(const WhileStmt *S) {
|
||||
auto *Cond = S->getCond()->IgnoreParenImpCasts();
|
||||
assert(Cond != nullptr);
|
||||
extendFlowCondition(*Cond);
|
||||
}
|
||||
|
||||
void VisitBinaryOperator(const BinaryOperator *S) {
|
||||
assert(S->getOpcode() == BO_LAnd || S->getOpcode() == BO_LOr);
|
||||
auto *LHS = S->getLHS()->IgnoreParenImpCasts();
|
||||
assert(LHS != nullptr);
|
||||
extendFlowCondition(*LHS);
|
||||
}
|
||||
|
||||
void VisitConditionalOperator(const ConditionalOperator *S) {
|
||||
auto *Cond = S->getCond()->IgnoreParenImpCasts();
|
||||
assert(Cond != nullptr);
|
||||
extendFlowCondition(*Cond);
|
||||
}
|
||||
|
||||
private:
|
||||
void extendFlowCondition(const Expr &Cond) {
|
||||
// The terminator sub-expression might not be evaluated.
|
||||
if (Env.getValue(Cond, SkipPast::None) == nullptr)
|
||||
transfer(StmtToEnv, Cond, Env);
|
||||
|
||||
auto *Val =
|
||||
cast_or_null<BoolValue>(Env.getValue(Cond, SkipPast::Reference));
|
||||
if (Val == nullptr)
|
||||
return;
|
||||
|
||||
// The condition must be inverted for the successor that encompasses the
|
||||
// "else" branch, if such exists.
|
||||
if (BlockSuccIdx == 1)
|
||||
Val = &Env.makeNot(*Val);
|
||||
|
||||
Env.addToFlowCondition(*Val);
|
||||
}
|
||||
|
||||
const StmtToEnvMap &StmtToEnv;
|
||||
Environment &Env;
|
||||
int BlockSuccIdx;
|
||||
};
|
||||
|
||||
/// Computes the input state for a given basic block by joining the output
|
||||
/// states of its predecessors.
|
||||
///
|
||||
|
@ -64,7 +134,7 @@ private:
|
|||
/// `llvm::None` represent basic blocks that are not evaluated yet.
|
||||
static TypeErasedDataflowAnalysisState computeBlockInputState(
|
||||
const ControlFlowContext &CFCtx,
|
||||
llvm::ArrayRef<llvm::Optional<TypeErasedDataflowAnalysisState>> BlockStates,
|
||||
std::vector<llvm::Optional<TypeErasedDataflowAnalysisState>> &BlockStates,
|
||||
const CFGBlock &Block, const Environment &InitEnv,
|
||||
TypeErasedDataflowAnalysis &Analysis) {
|
||||
llvm::DenseSet<const CFGBlock *> Preds;
|
||||
|
@ -112,13 +182,19 @@ static TypeErasedDataflowAnalysisState computeBlockInputState(
|
|||
if (!MaybePredState.hasValue())
|
||||
continue;
|
||||
|
||||
const TypeErasedDataflowAnalysisState &PredState =
|
||||
MaybePredState.getValue();
|
||||
TypeErasedDataflowAnalysisState PredState = MaybePredState.getValue();
|
||||
if (const Stmt *PredTerminatorStmt = Pred->getTerminatorStmt()) {
|
||||
const StmtToEnvMapImpl StmtToEnv(CFCtx, BlockStates);
|
||||
TerminatorVisitor(StmtToEnv, PredState.Env,
|
||||
blockIndexInPredecessor(*Pred, Block))
|
||||
.Visit(PredTerminatorStmt);
|
||||
}
|
||||
|
||||
if (MaybeState.hasValue()) {
|
||||
Analysis.joinTypeErased(MaybeState->Lattice, PredState.Lattice);
|
||||
MaybeState->Env.join(PredState.Env, Analysis);
|
||||
} else {
|
||||
MaybeState = PredState;
|
||||
MaybeState = std::move(PredState);
|
||||
}
|
||||
}
|
||||
if (!MaybeState.hasValue()) {
|
||||
|
|
|
@ -86,6 +86,33 @@ TEST_F(TransferTest, IntVarDeclNotTrackedWhenTransferDisabled) {
|
|||
/*ApplyBuiltinTransfer=*/false);
|
||||
}
|
||||
|
||||
TEST_F(TransferTest, BoolVarDecl) {
|
||||
std::string Code = R"(
|
||||
void target() {
|
||||
bool Foo;
|
||||
// [[p]]
|
||||
}
|
||||
)";
|
||||
runDataflow(Code,
|
||||
[](llvm::ArrayRef<
|
||||
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
|
||||
Results,
|
||||
ASTContext &ASTCtx) {
|
||||
ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
|
||||
const Environment &Env = Results[0].second.Env;
|
||||
|
||||
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
|
||||
ASSERT_THAT(FooDecl, NotNull());
|
||||
|
||||
const StorageLocation *FooLoc =
|
||||
Env.getStorageLocation(*FooDecl, SkipPast::None);
|
||||
ASSERT_TRUE(isa_and_nonnull<ScalarStorageLocation>(FooLoc));
|
||||
|
||||
const Value *FooVal = Env.getValue(*FooLoc);
|
||||
EXPECT_TRUE(isa_and_nonnull<BoolValue>(FooVal));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(TransferTest, IntVarDecl) {
|
||||
std::string Code = R"(
|
||||
void target() {
|
||||
|
@ -2035,9 +2062,7 @@ TEST_F(TransferTest, AssignFromBoolLiteral) {
|
|||
|
||||
TEST_F(TransferTest, AssignFromBoolConjunction) {
|
||||
std::string Code = R"(
|
||||
void target() {
|
||||
bool Foo = true;
|
||||
bool Bar = true;
|
||||
void target(bool Foo, bool Bar) {
|
||||
bool Baz = (Foo) && (Bar);
|
||||
// [[p]]
|
||||
}
|
||||
|
@ -2078,9 +2103,7 @@ TEST_F(TransferTest, AssignFromBoolConjunction) {
|
|||
|
||||
TEST_F(TransferTest, AssignFromBoolDisjunction) {
|
||||
std::string Code = R"(
|
||||
void target() {
|
||||
bool Foo = true;
|
||||
bool Bar = true;
|
||||
void target(bool Foo, bool Bar) {
|
||||
bool Baz = (Foo) || (Bar);
|
||||
// [[p]]
|
||||
}
|
||||
|
|
|
@ -336,7 +336,8 @@ public:
|
|||
}
|
||||
|
||||
bool compareEquivalent(QualType Type, const Value &Val1,
|
||||
const Value &Val2) final {
|
||||
const Environment &Env1, const Value &Val2,
|
||||
const Environment &Env2) final {
|
||||
// Nothing to say about a value that does not model an `OptionalInt`.
|
||||
if (!Type->isRecordType() ||
|
||||
Type->getAsCXXRecordDecl()->getQualifiedNameAsString() != "OptionalInt")
|
||||
|
@ -346,8 +347,9 @@ public:
|
|||
cast<StructValue>(&Val2)->getProperty("has_value");
|
||||
}
|
||||
|
||||
bool merge(QualType Type, const Value &Val1, const Value &Val2,
|
||||
Value &MergedVal, Environment &Env) final {
|
||||
bool merge(QualType Type, const Value &Val1, const Environment &Env1,
|
||||
const Value &Val2, const Environment &Env2, Value &MergedVal,
|
||||
Environment &MergedEnv) final {
|
||||
// Nothing to say about a value that does not model an `OptionalInt`.
|
||||
if (!Type->isRecordType() ||
|
||||
Type->getAsCXXRecordDecl()->getQualifiedNameAsString() != "OptionalInt")
|
||||
|
@ -559,4 +561,319 @@ TEST_F(WideningTest, DistinctValuesWithSamePropertiesAreEquivalent) {
|
|||
});
|
||||
}
|
||||
|
||||
class FlowConditionTest : public Test {
|
||||
protected:
|
||||
template <typename Matcher>
|
||||
void runDataflow(llvm::StringRef Code, Matcher Match) {
|
||||
ASSERT_THAT_ERROR(
|
||||
test::checkDataflow<NoopAnalysis>(
|
||||
Code, "target",
|
||||
[](ASTContext &Context, Environment &Env) {
|
||||
return NoopAnalysis(Context, true);
|
||||
},
|
||||
[&Match](
|
||||
llvm::ArrayRef<
|
||||
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
|
||||
Results,
|
||||
ASTContext &ASTCtx) { Match(Results, ASTCtx); },
|
||||
{"-fsyntax-only", "-std=c++17"}),
|
||||
llvm::Succeeded());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(FlowConditionTest, IfStmtSingleVar) {
|
||||
std::string Code = R"(
|
||||
void target(bool Foo) {
|
||||
if (Foo) {
|
||||
(void)0;
|
||||
/*[[p1]]*/
|
||||
} else {
|
||||
(void)1;
|
||||
/*[[p2]]*/
|
||||
}
|
||||
}
|
||||
)";
|
||||
runDataflow(Code,
|
||||
[](llvm::ArrayRef<
|
||||
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
|
||||
Results,
|
||||
ASTContext &ASTCtx) {
|
||||
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
|
||||
ASSERT_THAT(FooDecl, NotNull());
|
||||
|
||||
ASSERT_THAT(Results, ElementsAre(Pair("p2", _), Pair("p1", _)));
|
||||
|
||||
const Environment &Env1 = Results[1].second.Env;
|
||||
auto *FooVal1 =
|
||||
cast<BoolValue>(Env1.getValue(*FooDecl, SkipPast::None));
|
||||
EXPECT_TRUE(Env1.flowConditionImplies(*FooVal1));
|
||||
|
||||
const Environment &Env2 = Results[0].second.Env;
|
||||
auto *FooVal2 =
|
||||
cast<BoolValue>(Env2.getValue(*FooDecl, SkipPast::None));
|
||||
EXPECT_FALSE(Env2.flowConditionImplies(*FooVal2));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(FlowConditionTest, IfStmtSingleNegatedVar) {
|
||||
std::string Code = R"(
|
||||
void target(bool Foo) {
|
||||
if (!Foo) {
|
||||
(void)0;
|
||||
/*[[p1]]*/
|
||||
} else {
|
||||
(void)1;
|
||||
/*[[p2]]*/
|
||||
}
|
||||
}
|
||||
)";
|
||||
runDataflow(Code,
|
||||
[](llvm::ArrayRef<
|
||||
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
|
||||
Results,
|
||||
ASTContext &ASTCtx) {
|
||||
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
|
||||
ASSERT_THAT(FooDecl, NotNull());
|
||||
|
||||
ASSERT_THAT(Results, ElementsAre(Pair("p2", _), Pair("p1", _)));
|
||||
|
||||
const Environment &Env1 = Results[1].second.Env;
|
||||
auto *FooVal1 =
|
||||
cast<BoolValue>(Env1.getValue(*FooDecl, SkipPast::None));
|
||||
EXPECT_FALSE(Env1.flowConditionImplies(*FooVal1));
|
||||
|
||||
const Environment &Env2 = Results[0].second.Env;
|
||||
auto *FooVal2 =
|
||||
cast<BoolValue>(Env2.getValue(*FooDecl, SkipPast::None));
|
||||
EXPECT_TRUE(Env2.flowConditionImplies(*FooVal2));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(FlowConditionTest, WhileStmt) {
|
||||
std::string Code = R"(
|
||||
void target(bool Foo) {
|
||||
while (Foo) {
|
||||
(void)0;
|
||||
/*[[p]]*/
|
||||
}
|
||||
}
|
||||
)";
|
||||
runDataflow(
|
||||
Code, [](llvm::ArrayRef<
|
||||
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
|
||||
Results,
|
||||
ASTContext &ASTCtx) {
|
||||
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
|
||||
ASSERT_THAT(FooDecl, NotNull());
|
||||
|
||||
ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
|
||||
const Environment &Env = Results[0].second.Env;
|
||||
|
||||
auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
|
||||
EXPECT_TRUE(Env.flowConditionImplies(*FooVal));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(FlowConditionTest, Conjunction) {
|
||||
std::string Code = R"(
|
||||
void target(bool Foo, bool Bar) {
|
||||
if (Foo && Bar) {
|
||||
(void)0;
|
||||
/*[[p1]]*/
|
||||
} else {
|
||||
(void)1;
|
||||
/*[[p2]]*/
|
||||
}
|
||||
}
|
||||
)";
|
||||
runDataflow(Code,
|
||||
[](llvm::ArrayRef<
|
||||
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
|
||||
Results,
|
||||
ASTContext &ASTCtx) {
|
||||
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
|
||||
ASSERT_THAT(FooDecl, NotNull());
|
||||
|
||||
const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
|
||||
ASSERT_THAT(BarDecl, NotNull());
|
||||
|
||||
ASSERT_THAT(Results, ElementsAre(Pair("p2", _), Pair("p1", _)));
|
||||
|
||||
const Environment &Env1 = Results[1].second.Env;
|
||||
auto *FooVal1 =
|
||||
cast<BoolValue>(Env1.getValue(*FooDecl, SkipPast::None));
|
||||
auto *BarVal1 =
|
||||
cast<BoolValue>(Env1.getValue(*BarDecl, SkipPast::None));
|
||||
EXPECT_TRUE(Env1.flowConditionImplies(*FooVal1));
|
||||
EXPECT_TRUE(Env1.flowConditionImplies(*BarVal1));
|
||||
|
||||
const Environment &Env2 = Results[0].second.Env;
|
||||
auto *FooVal2 =
|
||||
cast<BoolValue>(Env2.getValue(*FooDecl, SkipPast::None));
|
||||
auto *BarVal2 =
|
||||
cast<BoolValue>(Env2.getValue(*BarDecl, SkipPast::None));
|
||||
EXPECT_FALSE(Env2.flowConditionImplies(*FooVal2));
|
||||
EXPECT_FALSE(Env2.flowConditionImplies(*BarVal2));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(FlowConditionTest, Disjunction) {
|
||||
std::string Code = R"(
|
||||
void target(bool Foo, bool Bar) {
|
||||
if (Foo || Bar) {
|
||||
(void)0;
|
||||
/*[[p1]]*/
|
||||
} else {
|
||||
(void)1;
|
||||
/*[[p2]]*/
|
||||
}
|
||||
}
|
||||
)";
|
||||
runDataflow(Code,
|
||||
[](llvm::ArrayRef<
|
||||
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
|
||||
Results,
|
||||
ASTContext &ASTCtx) {
|
||||
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
|
||||
ASSERT_THAT(FooDecl, NotNull());
|
||||
|
||||
const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
|
||||
ASSERT_THAT(BarDecl, NotNull());
|
||||
|
||||
ASSERT_THAT(Results, ElementsAre(Pair("p2", _), Pair("p1", _)));
|
||||
|
||||
const Environment &Env1 = Results[1].second.Env;
|
||||
auto *FooVal1 =
|
||||
cast<BoolValue>(Env1.getValue(*FooDecl, SkipPast::None));
|
||||
auto *BarVal1 =
|
||||
cast<BoolValue>(Env1.getValue(*BarDecl, SkipPast::None));
|
||||
EXPECT_FALSE(Env1.flowConditionImplies(*FooVal1));
|
||||
EXPECT_FALSE(Env1.flowConditionImplies(*BarVal1));
|
||||
|
||||
const Environment &Env2 = Results[0].second.Env;
|
||||
auto *FooVal2 =
|
||||
cast<BoolValue>(Env2.getValue(*FooDecl, SkipPast::None));
|
||||
auto *BarVal2 =
|
||||
cast<BoolValue>(Env2.getValue(*BarDecl, SkipPast::None));
|
||||
EXPECT_FALSE(Env2.flowConditionImplies(*FooVal2));
|
||||
EXPECT_FALSE(Env2.flowConditionImplies(*BarVal2));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(FlowConditionTest, NegatedConjunction) {
|
||||
std::string Code = R"(
|
||||
void target(bool Foo, bool Bar) {
|
||||
if (!(Foo && Bar)) {
|
||||
(void)0;
|
||||
/*[[p1]]*/
|
||||
} else {
|
||||
(void)1;
|
||||
/*[[p2]]*/
|
||||
}
|
||||
}
|
||||
)";
|
||||
runDataflow(Code,
|
||||
[](llvm::ArrayRef<
|
||||
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
|
||||
Results,
|
||||
ASTContext &ASTCtx) {
|
||||
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
|
||||
ASSERT_THAT(FooDecl, NotNull());
|
||||
|
||||
const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
|
||||
ASSERT_THAT(BarDecl, NotNull());
|
||||
|
||||
ASSERT_THAT(Results, ElementsAre(Pair("p2", _), Pair("p1", _)));
|
||||
|
||||
const Environment &Env1 = Results[1].second.Env;
|
||||
auto *FooVal1 =
|
||||
cast<BoolValue>(Env1.getValue(*FooDecl, SkipPast::None));
|
||||
auto *BarVal1 =
|
||||
cast<BoolValue>(Env1.getValue(*BarDecl, SkipPast::None));
|
||||
EXPECT_FALSE(Env1.flowConditionImplies(*FooVal1));
|
||||
EXPECT_FALSE(Env1.flowConditionImplies(*BarVal1));
|
||||
|
||||
const Environment &Env2 = Results[0].second.Env;
|
||||
auto *FooVal2 =
|
||||
cast<BoolValue>(Env2.getValue(*FooDecl, SkipPast::None));
|
||||
auto *BarVal2 =
|
||||
cast<BoolValue>(Env2.getValue(*BarDecl, SkipPast::None));
|
||||
EXPECT_TRUE(Env2.flowConditionImplies(*FooVal2));
|
||||
EXPECT_TRUE(Env2.flowConditionImplies(*BarVal2));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(FlowConditionTest, DeMorgan) {
|
||||
std::string Code = R"(
|
||||
void target(bool Foo, bool Bar) {
|
||||
if (!(!Foo || !Bar)) {
|
||||
(void)0;
|
||||
/*[[p1]]*/
|
||||
} else {
|
||||
(void)1;
|
||||
/*[[p2]]*/
|
||||
}
|
||||
}
|
||||
)";
|
||||
runDataflow(Code,
|
||||
[](llvm::ArrayRef<
|
||||
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
|
||||
Results,
|
||||
ASTContext &ASTCtx) {
|
||||
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
|
||||
ASSERT_THAT(FooDecl, NotNull());
|
||||
|
||||
const ValueDecl *BarDecl = findValueDecl(ASTCtx, "Bar");
|
||||
ASSERT_THAT(BarDecl, NotNull());
|
||||
|
||||
ASSERT_THAT(Results, ElementsAre(Pair("p2", _), Pair("p1", _)));
|
||||
|
||||
const Environment &Env1 = Results[1].second.Env;
|
||||
auto *FooVal1 =
|
||||
cast<BoolValue>(Env1.getValue(*FooDecl, SkipPast::None));
|
||||
auto *BarVal1 =
|
||||
cast<BoolValue>(Env1.getValue(*BarDecl, SkipPast::None));
|
||||
EXPECT_TRUE(Env1.flowConditionImplies(*FooVal1));
|
||||
EXPECT_TRUE(Env1.flowConditionImplies(*BarVal1));
|
||||
|
||||
const Environment &Env2 = Results[0].second.Env;
|
||||
auto *FooVal2 =
|
||||
cast<BoolValue>(Env2.getValue(*FooDecl, SkipPast::None));
|
||||
auto *BarVal2 =
|
||||
cast<BoolValue>(Env2.getValue(*BarDecl, SkipPast::None));
|
||||
EXPECT_FALSE(Env2.flowConditionImplies(*FooVal2));
|
||||
EXPECT_FALSE(Env2.flowConditionImplies(*BarVal2));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(FlowConditionTest, Join) {
|
||||
std::string Code = R"(
|
||||
void target(bool Foo, bool Bar) {
|
||||
if (Bar) {
|
||||
if (!Foo)
|
||||
return;
|
||||
} else {
|
||||
if (!Foo)
|
||||
return;
|
||||
}
|
||||
(void)0;
|
||||
/*[[p]]*/
|
||||
}
|
||||
)";
|
||||
runDataflow(
|
||||
Code, [](llvm::ArrayRef<
|
||||
std::pair<std::string, DataflowAnalysisState<NoopLattice>>>
|
||||
Results,
|
||||
ASTContext &ASTCtx) {
|
||||
ASSERT_THAT(Results, ElementsAre(Pair("p", _)));
|
||||
|
||||
const ValueDecl *FooDecl = findValueDecl(ASTCtx, "Foo");
|
||||
ASSERT_THAT(FooDecl, NotNull());
|
||||
|
||||
const Environment &Env = Results[0].second.Env;
|
||||
auto *FooVal = cast<BoolValue>(Env.getValue(*FooDecl, SkipPast::None));
|
||||
EXPECT_TRUE(Env.flowConditionImplies(*FooVal));
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
Loading…
Reference in New Issue