| //===- unittests/Analysis/FlowSensitive/SignAnalysisTest.cpp --===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file defines a simplistic version of Sign Analysis as a demo of a |
| // forward, monotonic dataflow analysis. The implementation uses 3 boolean |
| // values to represent the sign lattice (negative, zero, positive). In |
| // practice, 2 booleans would be enough, however, this approach has the |
| // advantage of clarity over the optimized solution. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "TestingSupport.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/ASTMatchers/ASTMatchers.h" |
| #include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h" |
| #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" |
| #include "clang/Analysis/FlowSensitive/NoopLattice.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/Testing/Annotations/Annotations.h" |
| #include "llvm/Testing/Support/Error.h" |
| #include "gtest/gtest.h" |
| #include <memory> |
| |
| namespace { |
| |
| using namespace clang; |
| using namespace dataflow; |
| using namespace ast_matchers; |
| using namespace test; |
| using ::testing::UnorderedElementsAre; |
| |
| enum class Sign : int { Negative, Zero, Positive }; |
| |
| Sign getSign(int64_t V) { |
| return V == 0 ? Sign::Zero : (V < 0 ? Sign::Negative : Sign::Positive); |
| } |
| |
| using LatticeTransferState = TransferState<NoopLattice>; |
| |
| constexpr char kVar[] = "var"; |
| |
| void initNegative(Value &Val, Environment &Env) { |
| Val.setProperty("neg", Env.getBoolLiteralValue(true)); |
| Val.setProperty("zero", Env.getBoolLiteralValue(false)); |
| Val.setProperty("pos", Env.getBoolLiteralValue(false)); |
| } |
| void initPositive(Value &Val, Environment &Env) { |
| Val.setProperty("neg", Env.getBoolLiteralValue(false)); |
| Val.setProperty("zero", Env.getBoolLiteralValue(false)); |
| Val.setProperty("pos", Env.getBoolLiteralValue(true)); |
| } |
| void initZero(Value &Val, Environment &Env) { |
| Val.setProperty("neg", Env.getBoolLiteralValue(false)); |
| Val.setProperty("zero", Env.getBoolLiteralValue(true)); |
| Val.setProperty("pos", Env.getBoolLiteralValue(false)); |
| } |
| |
| // The boolean properties that are associated to a Value. If a property is not |
| // set then these are null pointers, otherwise, the pointed BoolValues are |
| // owned by the Environment. |
| struct SignProperties { |
| BoolValue *Neg, *Zero, *Pos; |
| }; |
| void setSignProperties(Value &Val, const SignProperties &Ps) { |
| Val.setProperty("neg", *Ps.Neg); |
| Val.setProperty("zero", *Ps.Zero); |
| Val.setProperty("pos", *Ps.Pos); |
| } |
| SignProperties initUnknown(Value &Val, Environment &Env) { |
| SignProperties Ps{&Env.makeAtomicBoolValue(), &Env.makeAtomicBoolValue(), |
| &Env.makeAtomicBoolValue()}; |
| setSignProperties(Val, Ps); |
| return Ps; |
| } |
| SignProperties getSignProperties(const Value &Val, const Environment &Env) { |
| return {dyn_cast_or_null<BoolValue>(Val.getProperty("neg")), |
| dyn_cast_or_null<BoolValue>(Val.getProperty("zero")), |
| dyn_cast_or_null<BoolValue>(Val.getProperty("pos"))}; |
| } |
| |
| void transferUninitializedInt(const DeclStmt *D, |
| const MatchFinder::MatchResult &M, |
| LatticeTransferState &State) { |
| const auto *Var = M.Nodes.getNodeAs<clang::VarDecl>(kVar); |
| assert(Var != nullptr); |
| const StorageLocation *Loc = State.Env.getStorageLocation(*Var); |
| Value *Val = State.Env.getValue(*Loc); |
| initUnknown(*Val, State.Env); |
| } |
| |
| // Get the Value (1), the properties for the operand (2), and the properties |
| // for the unary operator (3). The return value is a tuple of (1,2,3). |
| // |
| // The returned Value (1) is a nullptr, if there is no Value associated to the |
| // operand of the unary operator, or if the properties are not set for that |
| // operand. |
| // Other than that, new sign properties are created for the Value of the |
| // unary operator and a new Value is created for the unary operator itself if |
| // it hadn't have any previously. |
| std::tuple<Value *, SignProperties, SignProperties> |
| getValueAndSignProperties(const UnaryOperator *UO, |
| const MatchFinder::MatchResult &M, |
| LatticeTransferState &State) { |
| // The DeclRefExpr refers to this variable in the operand. |
| const auto *OperandVar = M.Nodes.getNodeAs<clang::VarDecl>(kVar); |
| assert(OperandVar != nullptr); |
| const auto *OperandValue = State.Env.getValue(*OperandVar); |
| if (!OperandValue) |
| return {nullptr, {}, {}}; |
| |
| // Value of the unary op. |
| auto *UnaryOpValue = State.Env.getValueStrict(*UO); |
| if (!UnaryOpValue) { |
| UnaryOpValue = &State.Env.makeAtomicBoolValue(); |
| State.Env.setValueStrict(*UO, *UnaryOpValue); |
| } |
| |
| // Properties for the operand (sub expression). |
| SignProperties OperandProps = getSignProperties(*OperandValue, State.Env); |
| if (OperandProps.Neg == nullptr) |
| return {nullptr, {}, {}}; |
| // Properties for the operator expr itself. |
| SignProperties UnaryOpProps = initUnknown(*UnaryOpValue, State.Env); |
| return {UnaryOpValue, UnaryOpProps, OperandProps}; |
| } |
| |
| void transferBinary(const BinaryOperator *BO, const MatchFinder::MatchResult &M, |
| LatticeTransferState &State) { |
| auto &A = State.Env.arena(); |
| const Formula *Comp; |
| if (BoolValue *V = cast_or_null<BoolValue>(State.Env.getValueStrict(*BO))) { |
| Comp = &V->formula(); |
| } else { |
| Comp = &A.makeAtomRef(A.makeAtom()); |
| State.Env.setValueStrict(*BO, A.makeBoolValue(*Comp)); |
| } |
| |
| // FIXME Use this as well: |
| // auto *NegatedComp = &State.Env.makeNot(*Comp); |
| |
| auto *LHS = State.Env.getValueStrict(*BO->getLHS()); |
| auto *RHS = State.Env.getValueStrict(*BO->getRHS()); |
| |
| if (!LHS || !RHS) |
| return; |
| |
| SignProperties LHSProps = getSignProperties(*LHS, State.Env); |
| SignProperties RHSProps = getSignProperties(*RHS, State.Env); |
| if (LHSProps.Neg == nullptr || RHSProps.Neg == nullptr) |
| return; |
| |
| switch (BO->getOpcode()) { |
| case BO_GT: |
| // pos > pos |
| State.Env.addToFlowCondition( |
| A.makeImplies(*Comp, A.makeImplies(RHSProps.Pos->formula(), |
| LHSProps.Pos->formula()))); |
| // pos > zero |
| State.Env.addToFlowCondition( |
| A.makeImplies(*Comp, A.makeImplies(RHSProps.Zero->formula(), |
| LHSProps.Pos->formula()))); |
| break; |
| case BO_LT: |
| // neg < neg |
| State.Env.addToFlowCondition( |
| A.makeImplies(*Comp, A.makeImplies(RHSProps.Neg->formula(), |
| LHSProps.Neg->formula()))); |
| // neg < zero |
| State.Env.addToFlowCondition( |
| A.makeImplies(*Comp, A.makeImplies(RHSProps.Zero->formula(), |
| LHSProps.Neg->formula()))); |
| break; |
| case BO_GE: |
| // pos >= pos |
| State.Env.addToFlowCondition( |
| A.makeImplies(*Comp, A.makeImplies(RHSProps.Pos->formula(), |
| LHSProps.Pos->formula()))); |
| break; |
| case BO_LE: |
| // neg <= neg |
| State.Env.addToFlowCondition( |
| A.makeImplies(*Comp, A.makeImplies(RHSProps.Neg->formula(), |
| LHSProps.Neg->formula()))); |
| break; |
| case BO_EQ: |
| State.Env.addToFlowCondition( |
| A.makeImplies(*Comp, A.makeImplies(RHSProps.Neg->formula(), |
| LHSProps.Neg->formula()))); |
| State.Env.addToFlowCondition( |
| A.makeImplies(*Comp, A.makeImplies(RHSProps.Zero->formula(), |
| LHSProps.Zero->formula()))); |
| State.Env.addToFlowCondition( |
| A.makeImplies(*Comp, A.makeImplies(RHSProps.Pos->formula(), |
| LHSProps.Pos->formula()))); |
| break; |
| case BO_NE: // Noop. |
| break; |
| default: |
| llvm_unreachable("not implemented"); |
| } |
| } |
| |
| void transferUnaryMinus(const UnaryOperator *UO, |
| const MatchFinder::MatchResult &M, |
| LatticeTransferState &State) { |
| auto &A = State.Env.arena(); |
| auto [UnaryOpValue, UnaryOpProps, OperandProps] = |
| getValueAndSignProperties(UO, M, State); |
| if (!UnaryOpValue) |
| return; |
| |
| // a is pos ==> -a is neg |
| State.Env.addToFlowCondition( |
| A.makeImplies(OperandProps.Pos->formula(), UnaryOpProps.Neg->formula())); |
| // a is neg ==> -a is pos |
| State.Env.addToFlowCondition( |
| A.makeImplies(OperandProps.Neg->formula(), UnaryOpProps.Pos->formula())); |
| // a is zero ==> -a is zero |
| State.Env.addToFlowCondition(A.makeImplies(OperandProps.Zero->formula(), |
| UnaryOpProps.Zero->formula())); |
| } |
| |
| void transferUnaryNot(const UnaryOperator *UO, |
| const MatchFinder::MatchResult &M, |
| LatticeTransferState &State) { |
| auto &A = State.Env.arena(); |
| auto [UnaryOpValue, UnaryOpProps, OperandProps] = |
| getValueAndSignProperties(UO, M, State); |
| if (!UnaryOpValue) |
| return; |
| |
| // a is neg or pos ==> !a is zero |
| State.Env.addToFlowCondition(A.makeImplies( |
| A.makeOr(OperandProps.Pos->formula(), OperandProps.Neg->formula()), |
| UnaryOpProps.Zero->formula())); |
| |
| // FIXME Handle this logic universally, not just for unary not. But Where to |
| // put the generic handler, transferExpr maybe? |
| if (auto *UOBoolVal = dyn_cast<BoolValue>(UnaryOpValue)) { |
| // !a <==> a is zero |
| State.Env.addToFlowCondition( |
| A.makeEquals(UOBoolVal->formula(), OperandProps.Zero->formula())); |
| // !a <==> !a is not zero |
| State.Env.addToFlowCondition(A.makeEquals( |
| UOBoolVal->formula(), A.makeNot(UnaryOpProps.Zero->formula()))); |
| } |
| } |
| |
| // Returns the `Value` associated with `E` (which may be either a prvalue or |
| // glvalue). Creates a `Value` or `StorageLocation` as needed if `E` does not |
| // have either of these associated with it yet. |
| // |
| // If this functionality turns out to be needed in more cases, this function |
| // should be moved to a more central location. |
| Value *getOrCreateValue(const Expr *E, Environment &Env) { |
| Value *Val = nullptr; |
| if (E->isGLValue()) { |
| StorageLocation *Loc = Env.getStorageLocationStrict(*E); |
| if (!Loc) { |
| Loc = &Env.createStorageLocation(*E); |
| Env.setStorageLocationStrict(*E, *Loc); |
| } |
| Val = Env.getValue(*Loc); |
| if (!Val) { |
| Val = Env.createValue(E->getType()); |
| Env.setValue(*Loc, *Val); |
| } |
| } else { |
| Val = Env.getValueStrict(*E); |
| if (!Val) { |
| Val = Env.createValue(E->getType()); |
| Env.setValueStrict(*E, *Val); |
| } |
| } |
| assert(Val != nullptr); |
| |
| return Val; |
| } |
| |
| void transferExpr(const Expr *E, const MatchFinder::MatchResult &M, |
| LatticeTransferState &State) { |
| const ASTContext &Context = *M.Context; |
| |
| Value *Val = getOrCreateValue(E, State.Env); |
| |
| // The sign symbolic values have been initialized already. |
| if (Val->getProperty("neg")) |
| return; |
| |
| Expr::EvalResult R; |
| // An integer expression which we cannot evaluate. |
| if (!(E->EvaluateAsInt(R, Context) && R.Val.isInt())) { |
| initUnknown(*Val, State.Env); |
| return; |
| } |
| |
| const Sign S = getSign(R.Val.getInt().getExtValue()); |
| switch (S) { |
| case Sign::Negative: |
| initNegative(*Val, State.Env); |
| break; |
| case Sign::Zero: |
| initZero(*Val, State.Env); |
| break; |
| case Sign::Positive: |
| initPositive(*Val, State.Env); |
| break; |
| } |
| } |
| |
| auto refToVar() { return declRefExpr(to(varDecl().bind(kVar))); } |
| |
| auto buildTransferMatchSwitch() { |
| // Note, the order of the cases is important, the most generic should be |
| // added last. |
| // FIXME Discover what happens if there are multiple matching ASTMatchers for |
| // one Stmt? All matching case's handler should be called and in what order? |
| return CFGMatchSwitchBuilder<LatticeTransferState>() |
| // a op b (comparison) |
| .CaseOfCFGStmt<BinaryOperator>(binaryOperator(isComparisonOperator()), |
| transferBinary) |
| |
| // FIXME handle binop +,-,*,/ |
| |
| // -a |
| .CaseOfCFGStmt<UnaryOperator>( |
| unaryOperator(hasOperatorName("-"), |
| hasUnaryOperand(hasDescendant(refToVar()))), |
| transferUnaryMinus) |
| |
| // !a |
| .CaseOfCFGStmt<UnaryOperator>( |
| unaryOperator(hasOperatorName("!"), |
| hasUnaryOperand(hasDescendant(refToVar()))), |
| transferUnaryNot) |
| |
| // int a; |
| .CaseOfCFGStmt<DeclStmt>(declStmt(hasSingleDecl(varDecl( |
| decl().bind(kVar), hasType(isInteger()), |
| unless(hasInitializer(expr()))))), |
| transferUninitializedInt) |
| |
| // constexpr int |
| .CaseOfCFGStmt<Expr>(expr(hasType(isInteger())), transferExpr) |
| |
| .Build(); |
| } |
| |
| class SignPropagationAnalysis |
| : public DataflowAnalysis<SignPropagationAnalysis, NoopLattice> { |
| public: |
| SignPropagationAnalysis(ASTContext &Context) |
| : DataflowAnalysis<SignPropagationAnalysis, NoopLattice>(Context), |
| TransferMatchSwitch(buildTransferMatchSwitch()) {} |
| |
| static NoopLattice initialElement() { return {}; } |
| |
| void transfer(const CFGElement &Elt, NoopLattice &L, Environment &Env) { |
| LatticeTransferState State(L, Env); |
| TransferMatchSwitch(Elt, getASTContext(), State); |
| } |
| bool merge(QualType Type, const Value &Val1, const Environment &Env1, |
| const Value &Val2, const Environment &Env2, Value &MergedVal, |
| Environment &MergedEnv) override; |
| |
| private: |
| CFGMatchSwitch<TransferState<NoopLattice>> TransferMatchSwitch; |
| }; |
| |
| // Copied from crubit. |
| BoolValue &mergeBoolValues(BoolValue &Bool1, const Environment &Env1, |
| BoolValue &Bool2, const Environment &Env2, |
| Environment &MergedEnv) { |
| if (&Bool1 == &Bool2) { |
| return Bool1; |
| } |
| |
| auto &B1 = Bool1.formula(); |
| auto &B2 = Bool2.formula(); |
| |
| auto &A = MergedEnv.arena(); |
| auto &MergedBool = MergedEnv.makeAtomicBoolValue(); |
| |
| // If `Bool1` and `Bool2` is constrained to the same true / false value, |
| // `MergedBool` can be constrained similarly without needing to consider the |
| // path taken - this simplifies the flow condition tracked in `MergedEnv`. |
| // Otherwise, information about which path was taken is used to associate |
| // `MergedBool` with `Bool1` and `Bool2`. |
| if (Env1.flowConditionImplies(B1) && Env2.flowConditionImplies(B2)) { |
| MergedEnv.addToFlowCondition(MergedBool.formula()); |
| } else if (Env1.flowConditionImplies(A.makeNot(B1)) && |
| Env2.flowConditionImplies(A.makeNot(B2))) { |
| MergedEnv.addToFlowCondition(A.makeNot(MergedBool.formula())); |
| } |
| return MergedBool; |
| } |
| |
| bool SignPropagationAnalysis::merge(QualType Type, const Value &Val1, |
| const Environment &Env1, const Value &Val2, |
| const Environment &Env2, Value &MergedVal, |
| Environment &MergedEnv) { |
| if (!Type->isIntegerType()) |
| return false; |
| SignProperties Ps1 = getSignProperties(Val1, Env1); |
| SignProperties Ps2 = getSignProperties(Val2, Env2); |
| if (!Ps1.Neg || !Ps2.Neg) |
| return false; |
| BoolValue &MergedNeg = |
| mergeBoolValues(*Ps1.Neg, Env1, *Ps2.Neg, Env2, MergedEnv); |
| BoolValue &MergedZero = |
| mergeBoolValues(*Ps1.Zero, Env1, *Ps2.Zero, Env2, MergedEnv); |
| BoolValue &MergedPos = |
| mergeBoolValues(*Ps1.Pos, Env1, *Ps2.Pos, Env2, MergedEnv); |
| setSignProperties(MergedVal, |
| SignProperties{&MergedNeg, &MergedZero, &MergedPos}); |
| return true; |
| } |
| |
| template <typename Matcher> |
| void runDataflow(llvm::StringRef Code, Matcher Match, |
| LangStandard::Kind Std = LangStandard::lang_cxx17, |
| llvm::StringRef TargetFun = "fun") { |
| using ast_matchers::hasName; |
| ASSERT_THAT_ERROR( |
| checkDataflow<SignPropagationAnalysis>( |
| AnalysisInputs<SignPropagationAnalysis>( |
| Code, hasName(TargetFun), |
| [](ASTContext &C, Environment &) { |
| return SignPropagationAnalysis(C); |
| }) |
| .withASTBuildArgs( |
| {"-fsyntax-only", "-fno-delayed-template-parsing", |
| "-std=" + |
| std::string(LangStandard::getLangStandardForKind(Std) |
| .getName())}), |
| /*VerifyResults=*/ |
| [&Match](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> |
| &Results, |
| const AnalysisOutputs &AO) { Match(Results, AO.ASTCtx); }), |
| llvm::Succeeded()); |
| } |
| |
| // FIXME add this to testing support. |
| template <typename NodeType, typename MatcherType> |
| const NodeType *findFirst(ASTContext &ASTCtx, const MatcherType &M) { |
| auto TargetNodes = match(M.bind("v"), ASTCtx); |
| assert(TargetNodes.size() == 1 && "Match must be unique"); |
| auto *const Result = selectFirst<NodeType>("v", TargetNodes); |
| assert(Result != nullptr); |
| return Result; |
| } |
| |
| template <typename Node> |
| std::pair<testing::AssertionResult, Value *> |
| getProperty(const Environment &Env, ASTContext &ASTCtx, const Node *N, |
| StringRef Property) { |
| if (!N) |
| return {testing::AssertionFailure() << "No node", nullptr}; |
| const StorageLocation *Loc = Env.getStorageLocation(*N); |
| if (!isa_and_nonnull<ScalarStorageLocation>(Loc)) |
| return {testing::AssertionFailure() << "No location", nullptr}; |
| const Value *Val = Env.getValue(*Loc); |
| if (!Val) |
| return {testing::AssertionFailure() << "No value", nullptr}; |
| auto *Prop = Val->getProperty(Property); |
| if (!isa_and_nonnull<BoolValue>(Prop)) |
| return {testing::AssertionFailure() << "No property for " << Property, |
| nullptr}; |
| return {testing::AssertionSuccess(), Prop}; |
| } |
| |
| // Test if the given property of the given node is implied by the flow |
| // condition. If 'Implies' is false then check if it is not implied. |
| template <typename Node> |
| testing::AssertionResult isPropertyImplied(const Environment &Env, |
| ASTContext &ASTCtx, const Node *N, |
| StringRef Property, bool Implies) { |
| auto [Result, Prop] = getProperty(Env, ASTCtx, N, Property); |
| if (!Prop) |
| return Result; |
| auto *BVProp = cast<BoolValue>(Prop); |
| if (Env.flowConditionImplies(BVProp->formula()) != Implies) |
| return testing::AssertionFailure() |
| << Property << " is " << (Implies ? "not" : "") << " implied" |
| << ", but should " << (Implies ? "" : "not ") << "be"; |
| return testing::AssertionSuccess(); |
| } |
| |
| template <typename Node> |
| testing::AssertionResult isNegative(const Node *N, ASTContext &ASTCtx, |
| const Environment &Env) { |
| testing::AssertionResult R = isPropertyImplied(Env, ASTCtx, N, "neg", true); |
| if (!R) |
| return R; |
| R = isPropertyImplied(Env, ASTCtx, N, "zero", false); |
| if (!R) |
| return R; |
| return isPropertyImplied(Env, ASTCtx, N, "pos", false); |
| } |
| template <typename Node> |
| testing::AssertionResult isPositive(const Node *N, ASTContext &ASTCtx, |
| const Environment &Env) { |
| testing::AssertionResult R = isPropertyImplied(Env, ASTCtx, N, "pos", true); |
| if (!R) |
| return R; |
| R = isPropertyImplied(Env, ASTCtx, N, "zero", false); |
| if (!R) |
| return R; |
| return isPropertyImplied(Env, ASTCtx, N, "neg", false); |
| } |
| template <typename Node> |
| testing::AssertionResult isZero(const Node *N, ASTContext &ASTCtx, |
| const Environment &Env) { |
| testing::AssertionResult R = isPropertyImplied(Env, ASTCtx, N, "zero", true); |
| if (!R) |
| return R; |
| R = isPropertyImplied(Env, ASTCtx, N, "pos", false); |
| if (!R) |
| return R; |
| return isPropertyImplied(Env, ASTCtx, N, "neg", false); |
| } |
| template <typename Node> |
| testing::AssertionResult isTop(const Node *N, ASTContext &ASTCtx, |
| const Environment &Env) { |
| testing::AssertionResult R = isPropertyImplied(Env, ASTCtx, N, "zero", false); |
| if (!R) |
| return R; |
| R = isPropertyImplied(Env, ASTCtx, N, "pos", false); |
| if (!R) |
| return R; |
| return isPropertyImplied(Env, ASTCtx, N, "neg", false); |
| } |
| |
| TEST(SignAnalysisTest, Init) { |
| std::string Code = R"( |
| int foo(); |
| void fun() { |
| int a = -1; |
| int b = 0; |
| int c = 1; |
| int d; |
| int e = foo(); |
| int f = c; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| // ASTCtx.getTranslationUnitDecl()->dump(); |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *A = findValueDecl(ASTCtx, "a"); |
| const ValueDecl *B = findValueDecl(ASTCtx, "b"); |
| const ValueDecl *C = findValueDecl(ASTCtx, "c"); |
| const ValueDecl *D = findValueDecl(ASTCtx, "d"); |
| const ValueDecl *E = findValueDecl(ASTCtx, "e"); |
| const ValueDecl *F = findValueDecl(ASTCtx, "f"); |
| |
| EXPECT_TRUE(isNegative(A, ASTCtx, Env)); |
| EXPECT_TRUE(isZero(B, ASTCtx, Env)); |
| EXPECT_TRUE(isPositive(C, ASTCtx, Env)); |
| EXPECT_TRUE(isTop(D, ASTCtx, Env)); |
| EXPECT_TRUE(isTop(E, ASTCtx, Env)); |
| EXPECT_TRUE(isPositive(F, ASTCtx, Env)); |
| }, |
| LangStandard::lang_cxx17); |
| } |
| |
| TEST(SignAnalysisTest, UnaryMinus) { |
| std::string Code = R"( |
| void fun() { |
| int a = 1; |
| int b = -a; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *A = findValueDecl(ASTCtx, "a"); |
| const ValueDecl *B = findValueDecl(ASTCtx, "b"); |
| EXPECT_TRUE(isPositive(A, ASTCtx, Env)); |
| EXPECT_TRUE(isNegative(B, ASTCtx, Env)); |
| }, |
| LangStandard::lang_cxx17); |
| } |
| |
| TEST(SignAnalysisTest, UnaryNot) { |
| std::string Code = R"( |
| void fun() { |
| int a = 2; |
| int b = !a; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p")); |
| const Environment &Env = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *A = findValueDecl(ASTCtx, "a"); |
| const ValueDecl *B = findValueDecl(ASTCtx, "b"); |
| EXPECT_TRUE(isPositive(A, ASTCtx, Env)); |
| EXPECT_TRUE(isZero(B, ASTCtx, Env)); |
| }, |
| LangStandard::lang_cxx17); |
| } |
| |
| TEST(SignAnalysisTest, UnaryNotInIf) { |
| std::string Code = R"( |
| int foo(); |
| void fun() { |
| int a = foo(); |
| if (!a) { |
| int b1; |
| int p_a = a; |
| int p_not_a = !a; |
| // [[p]] |
| } else { |
| int q_a = a; |
| int q_not_a = !a; |
| // [[q]] |
| } |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p", "q")); |
| const Environment &EnvP = getEnvironmentAtAnnotation(Results, "p"); |
| const Environment &EnvQ = getEnvironmentAtAnnotation(Results, "q"); |
| |
| const ValueDecl *A = findValueDecl(ASTCtx, "a"); |
| const ValueDecl *PA = findValueDecl(ASTCtx, "p_a"); |
| const ValueDecl *PNA = findValueDecl(ASTCtx, "p_not_a"); |
| const ValueDecl *QA = findValueDecl(ASTCtx, "q_a"); |
| const ValueDecl *QNA = findValueDecl(ASTCtx, "q_not_a"); |
| |
| // p |
| EXPECT_TRUE(isZero(A, ASTCtx, EnvP)); |
| EXPECT_TRUE(isZero(PA, ASTCtx, EnvP)); |
| EXPECT_TRUE(isTop(PNA, ASTCtx, EnvP)); |
| |
| // q |
| EXPECT_TRUE(isTop(A, ASTCtx, EnvQ)); |
| EXPECT_TRUE(isTop(QA, ASTCtx, EnvQ)); |
| EXPECT_TRUE(isZero(QNA, ASTCtx, EnvQ)); |
| }, |
| LangStandard::lang_cxx17); |
| } |
| |
| TEST(SignAnalysisTest, BinaryGT) { |
| std::string Code = R"( |
| int foo(); |
| void fun() { |
| int a = foo(); |
| int b = 1; |
| if (a > 1) { |
| (void)0; |
| // [[p]] |
| } |
| if (a > 0) { |
| (void)0; |
| // [[q]] |
| } |
| if (a > -1) { |
| (void)0; |
| // [[r]] |
| } |
| if (a > b) { |
| (void)0; |
| // [[s]] |
| } |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p", "q", "r", "s")); |
| const Environment &EnvP = getEnvironmentAtAnnotation(Results, "p"); |
| const Environment &EnvQ = getEnvironmentAtAnnotation(Results, "q"); |
| const Environment &EnvR = getEnvironmentAtAnnotation(Results, "r"); |
| const Environment &EnvS = getEnvironmentAtAnnotation(Results, "s"); |
| |
| const ValueDecl *A = findValueDecl(ASTCtx, "a"); |
| |
| // p |
| EXPECT_TRUE(isPositive(A, ASTCtx, EnvP)); |
| // q |
| EXPECT_TRUE(isPositive(A, ASTCtx, EnvQ)); |
| // r |
| EXPECT_TRUE(isTop(A, ASTCtx, EnvR)); |
| // s |
| EXPECT_TRUE(isPositive(A, ASTCtx, EnvS)); |
| }, |
| LangStandard::lang_cxx17); |
| } |
| |
| TEST(SignAnalysisTest, BinaryLT) { |
| std::string Code = R"( |
| int foo(); |
| void fun() { |
| int a = foo(); |
| int b = -1; |
| if (a < -1) { |
| (void)0; |
| // [[p]] |
| } |
| if (a < 0) { |
| (void)0; |
| // [[q]] |
| } |
| if (a < 1) { |
| (void)0; |
| // [[r]] |
| } |
| if (a < b) { |
| (void)0; |
| // [[s]] |
| } |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p", "q", "r", "s")); |
| const Environment &EnvP = getEnvironmentAtAnnotation(Results, "p"); |
| const Environment &EnvQ = getEnvironmentAtAnnotation(Results, "q"); |
| const Environment &EnvR = getEnvironmentAtAnnotation(Results, "r"); |
| const Environment &EnvS = getEnvironmentAtAnnotation(Results, "s"); |
| |
| const ValueDecl *A = findValueDecl(ASTCtx, "a"); |
| |
| // p |
| EXPECT_TRUE(isNegative(A, ASTCtx, EnvP)); |
| // q |
| EXPECT_TRUE(isNegative(A, ASTCtx, EnvQ)); |
| // r |
| EXPECT_TRUE(isTop(A, ASTCtx, EnvR)); |
| // s |
| EXPECT_TRUE(isNegative(A, ASTCtx, EnvS)); |
| }, |
| LangStandard::lang_cxx17); |
| } |
| |
| TEST(SignAnalysisTest, BinaryGE) { |
| std::string Code = R"( |
| int foo(); |
| void fun() { |
| int a = foo(); |
| int b = 1; |
| if (a >= 1) { |
| (void)0; |
| // [[p]] |
| } |
| if (a >= 0) { |
| (void)0; |
| // [[q]] |
| } |
| if (a >= -1) { |
| (void)0; |
| // [[r]] |
| } |
| if (a >= b) { |
| (void)0; |
| // [[s]] |
| } |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p", "q", "r", "s")); |
| const Environment &EnvP = getEnvironmentAtAnnotation(Results, "p"); |
| const Environment &EnvQ = getEnvironmentAtAnnotation(Results, "q"); |
| const Environment &EnvR = getEnvironmentAtAnnotation(Results, "r"); |
| const Environment &EnvS = getEnvironmentAtAnnotation(Results, "s"); |
| |
| const ValueDecl *A = findValueDecl(ASTCtx, "a"); |
| |
| // p |
| EXPECT_TRUE(isPositive(A, ASTCtx, EnvP)); |
| // q |
| EXPECT_TRUE(isTop(A, ASTCtx, EnvQ)); |
| // r |
| EXPECT_TRUE(isTop(A, ASTCtx, EnvR)); |
| // s |
| EXPECT_TRUE(isPositive(A, ASTCtx, EnvS)); |
| }, |
| LangStandard::lang_cxx17); |
| } |
| |
| TEST(SignAnalysisTest, BinaryLE) { |
| std::string Code = R"( |
| int foo(); |
| void fun() { |
| int a = foo(); |
| int b = -1; |
| if (a <= -1) { |
| (void)0; |
| // [[p]] |
| } |
| if (a <= 0) { |
| (void)0; |
| // [[q]] |
| } |
| if (a <= 1) { |
| (void)0; |
| // [[r]] |
| } |
| if (a <= b) { |
| (void)0; |
| // [[s]] |
| } |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p", "q", "r", "s")); |
| const Environment &EnvP = getEnvironmentAtAnnotation(Results, "p"); |
| const Environment &EnvQ = getEnvironmentAtAnnotation(Results, "q"); |
| const Environment &EnvR = getEnvironmentAtAnnotation(Results, "r"); |
| const Environment &EnvS = getEnvironmentAtAnnotation(Results, "s"); |
| |
| const ValueDecl *A = findValueDecl(ASTCtx, "a"); |
| |
| // p |
| EXPECT_TRUE(isNegative(A, ASTCtx, EnvP)); |
| // q |
| EXPECT_TRUE(isTop(A, ASTCtx, EnvQ)); |
| // r |
| EXPECT_TRUE(isTop(A, ASTCtx, EnvR)); |
| // s |
| EXPECT_TRUE(isNegative(A, ASTCtx, EnvS)); |
| }, |
| LangStandard::lang_cxx17); |
| } |
| |
| TEST(SignAnalysisTest, BinaryEQ) { |
| std::string Code = R"( |
| int foo(); |
| void fun() { |
| int a = foo(); |
| if (a == -1) { |
| (void)0; |
| // [[n]] |
| } |
| if (a == 0) { |
| (void)0; |
| // [[z]] |
| } |
| if (a == 1) { |
| (void)0; |
| // [[p]] |
| } |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("n", "z", "p")); |
| const Environment &EnvN = getEnvironmentAtAnnotation(Results, "n"); |
| const Environment &EnvZ = getEnvironmentAtAnnotation(Results, "z"); |
| const Environment &EnvP = getEnvironmentAtAnnotation(Results, "p"); |
| |
| const ValueDecl *A = findValueDecl(ASTCtx, "a"); |
| |
| // n |
| EXPECT_TRUE(isNegative(A, ASTCtx, EnvN)); |
| // z |
| EXPECT_TRUE(isZero(A, ASTCtx, EnvZ)); |
| // p |
| EXPECT_TRUE(isPositive(A, ASTCtx, EnvP)); |
| }, |
| LangStandard::lang_cxx17); |
| } |
| |
| TEST(SignAnalysisTest, JoinToTop) { |
| std::string Code = R"( |
| int foo(); |
| void fun(bool b) { |
| int a = foo(); |
| if (b) { |
| a = -1; |
| (void)0; |
| // [[p]] |
| } else { |
| a = 1; |
| (void)0; |
| // [[q]] |
| } |
| (void)0; |
| // [[r]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p", "q", "r")); |
| const Environment &EnvP = getEnvironmentAtAnnotation(Results, "p"); |
| const Environment &EnvQ = getEnvironmentAtAnnotation(Results, "q"); |
| const Environment &EnvR = getEnvironmentAtAnnotation(Results, "r"); |
| |
| const ValueDecl *A = findValueDecl(ASTCtx, "a"); |
| |
| // p |
| EXPECT_TRUE(isNegative(A, ASTCtx, EnvP)); |
| // q |
| EXPECT_TRUE(isPositive(A, ASTCtx, EnvQ)); |
| // r |
| EXPECT_TRUE(isTop(A, ASTCtx, EnvR)); |
| }, |
| LangStandard::lang_cxx17); |
| } |
| |
| TEST(SignAnalysisTest, JoinToNeg) { |
| std::string Code = R"( |
| int foo(); |
| void fun() { |
| int a = foo(); |
| if (a < 1) { |
| a = -1; |
| (void)0; |
| // [[p]] |
| } else { |
| a = -1; |
| (void)0; |
| // [[q]] |
| } |
| (void)0; |
| // [[r]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p", "q", "r")); |
| const Environment &EnvP = getEnvironmentAtAnnotation(Results, "p"); |
| const Environment &EnvQ = getEnvironmentAtAnnotation(Results, "q"); |
| const Environment &EnvR = getEnvironmentAtAnnotation(Results, "r"); |
| |
| const ValueDecl *A = findValueDecl(ASTCtx, "a"); |
| |
| // p |
| EXPECT_TRUE(isNegative(A, ASTCtx, EnvP)); |
| // q |
| EXPECT_TRUE(isNegative(A, ASTCtx, EnvQ)); |
| // r |
| EXPECT_TRUE(isNegative(A, ASTCtx, EnvR)); |
| }, |
| LangStandard::lang_cxx17); |
| } |
| |
| TEST(SignAnalysisTest, NestedIfs) { |
| std::string Code = R"( |
| int foo(); |
| void fun() { |
| int a = foo(); |
| if (a >= 0) { |
| (void)0; |
| // [[p]] |
| if (a == 0) { |
| (void)0; |
| // [[q]] |
| } |
| } |
| (void)0; |
| // [[r]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| ASSERT_THAT(Results.keys(), UnorderedElementsAre("p", "q", "r")); |
| const Environment &EnvP = getEnvironmentAtAnnotation(Results, "p"); |
| const Environment &EnvQ = getEnvironmentAtAnnotation(Results, "q"); |
| const Environment &EnvR = getEnvironmentAtAnnotation(Results, "r"); |
| |
| const ValueDecl *A = findValueDecl(ASTCtx, "a"); |
| |
| // p |
| EXPECT_TRUE(isTop(A, ASTCtx, EnvP)); |
| // q |
| EXPECT_TRUE(isZero(A, ASTCtx, EnvQ)); |
| // r |
| EXPECT_TRUE(isTop(A, ASTCtx, EnvR)); |
| }, |
| LangStandard::lang_cxx17); |
| } |
| |
| } // namespace |