//===- 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 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; 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(Val.getProperty("neg")), dyn_cast_or_null(Val.getProperty("zero")), dyn_cast_or_null(Val.getProperty("pos"))}; } void transferUninitializedInt(const DeclStmt *D, const MatchFinder::MatchResult &M, LatticeTransferState &State) { const auto *Var = M.Nodes.getNodeAs(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 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(kVar); assert(OperandVar != nullptr); const auto *OperandValue = State.Env.getValue(*OperandVar); if (!OperandValue) return {nullptr, {}, {}}; // Value of the unary op. auto *UnaryOpValue = State.Env.getValue(*UO); if (!UnaryOpValue) { UnaryOpValue = &State.Env.makeAtomicBoolValue(); State.Env.setValue(*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 = State.Env.get(*BO)) { Comp = &V->formula(); } else { Comp = &A.makeAtomRef(A.makeAtom()); State.Env.setValue(*BO, A.makeBoolValue(*Comp)); } // FIXME Use this as well: // auto *NegatedComp = &State.Env.makeNot(*Comp); auto *LHS = State.Env.getValue(*BO->getLHS()); auto *RHS = State.Env.getValue(*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.assume( A.makeImplies(*Comp, A.makeImplies(RHSProps.Pos->formula(), LHSProps.Pos->formula()))); // pos > zero State.Env.assume( A.makeImplies(*Comp, A.makeImplies(RHSProps.Zero->formula(), LHSProps.Pos->formula()))); break; case BO_LT: // neg < neg State.Env.assume( A.makeImplies(*Comp, A.makeImplies(RHSProps.Neg->formula(), LHSProps.Neg->formula()))); // neg < zero State.Env.assume( A.makeImplies(*Comp, A.makeImplies(RHSProps.Zero->formula(), LHSProps.Neg->formula()))); break; case BO_GE: // pos >= pos State.Env.assume( A.makeImplies(*Comp, A.makeImplies(RHSProps.Pos->formula(), LHSProps.Pos->formula()))); break; case BO_LE: // neg <= neg State.Env.assume( A.makeImplies(*Comp, A.makeImplies(RHSProps.Neg->formula(), LHSProps.Neg->formula()))); break; case BO_EQ: State.Env.assume( A.makeImplies(*Comp, A.makeImplies(RHSProps.Neg->formula(), LHSProps.Neg->formula()))); State.Env.assume( A.makeImplies(*Comp, A.makeImplies(RHSProps.Zero->formula(), LHSProps.Zero->formula()))); State.Env.assume( 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.assume( A.makeImplies(OperandProps.Pos->formula(), UnaryOpProps.Neg->formula())); // a is neg ==> -a is pos State.Env.assume( A.makeImplies(OperandProps.Neg->formula(), UnaryOpProps.Pos->formula())); // a is zero ==> -a is zero State.Env.assume(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.assume(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(UnaryOpValue)) { // !a <==> a is zero State.Env.assume( A.makeEquals(UOBoolVal->formula(), OperandProps.Zero->formula())); // !a <==> !a is not zero State.Env.assume(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.getStorageLocation(*E); if (!Loc) { Loc = &Env.createStorageLocation(*E); Env.setStorageLocation(*E, *Loc); } Val = Env.getValue(*Loc); if (!Val) { Val = Env.createValue(E->getType()); Env.setValue(*Loc, *Val); } } else { Val = Env.getValue(*E); if (!Val) { Val = Env.createValue(E->getType()); Env.setValue(*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() // a op b (comparison) .CaseOfCFGStmt(binaryOperator(isComparisonOperator()), transferBinary) // FIXME handle binop +,-,*,/ // -a .CaseOfCFGStmt( unaryOperator(hasOperatorName("-"), hasUnaryOperand(hasDescendant(refToVar()))), transferUnaryMinus) // !a .CaseOfCFGStmt( unaryOperator(hasOperatorName("!"), hasUnaryOperand(hasDescendant(refToVar()))), transferUnaryNot) // int a; .CaseOfCFGStmt(declStmt(hasSingleDecl(varDecl( decl().bind(kVar), hasType(isInteger()), unless(hasInitializer(expr()))))), transferUninitializedInt) // constexpr int .CaseOfCFGStmt(expr(hasType(isInteger())), transferExpr) .Build(); } class SignPropagationAnalysis : public DataflowAnalysis { public: SignPropagationAnalysis(ASTContext &Context) : DataflowAnalysis(Context), TransferMatchSwitch(buildTransferMatchSwitch()) {} static NoopLattice initialElement() { return {}; } void transfer(const CFGElement &Elt, NoopLattice &L, Environment &Env) { LatticeTransferState State(L, Env); TransferMatchSwitch(Elt, getASTContext(), State); } void join(QualType Type, const Value &Val1, const Environment &Env1, const Value &Val2, const Environment &Env2, Value &MergedVal, Environment &MergedEnv) override; private: CFGMatchSwitch> TransferMatchSwitch; }; BoolValue &joinBoolValues(BoolValue &Bool1, const Environment &Env1, BoolValue &Bool2, const Environment &Env2, Environment &JoinedEnv) { if (&Bool1 == &Bool2) { return Bool1; } auto &B1 = Bool1.formula(); auto &B2 = Bool2.formula(); auto &A = JoinedEnv.arena(); auto &JoinedBool = JoinedEnv.makeAtomicBoolValue(); // If `Bool1` and `Bool2` is constrained to the same true / false value, // `JoinedBool` can be constrained similarly without needing to consider the // path taken - this simplifies the flow condition tracked in `JoinedEnv`. // Otherwise, information about which path was taken is used to associate // `JoinedBool` with `Bool1` and `Bool2`. if (Env1.proves(B1) && Env2.proves(B2)) { JoinedEnv.assume(JoinedBool.formula()); } else if (Env1.proves(A.makeNot(B1)) && Env2.proves(A.makeNot(B2))) { JoinedEnv.assume(A.makeNot(JoinedBool.formula())); } return JoinedBool; } void SignPropagationAnalysis::join(QualType Type, const Value &Val1, const Environment &Env1, const Value &Val2, const Environment &Env2, Value &JoinedVal, Environment &JoinedEnv) { if (!Type->isIntegerType()) return; SignProperties Ps1 = getSignProperties(Val1, Env1); SignProperties Ps2 = getSignProperties(Val2, Env2); if (!Ps1.Neg || !Ps2.Neg) return; BoolValue &JoinedNeg = joinBoolValues(*Ps1.Neg, Env1, *Ps2.Neg, Env2, JoinedEnv); BoolValue &JoinedZero = joinBoolValues(*Ps1.Zero, Env1, *Ps2.Zero, Env2, JoinedEnv); BoolValue &JoinedPos = joinBoolValues(*Ps1.Pos, Env1, *Ps2.Pos, Env2, JoinedEnv); setSignProperties(JoinedVal, SignProperties{&JoinedNeg, &JoinedZero, &JoinedPos}); } template 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( AnalysisInputs( 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> &Results, const AnalysisOutputs &AO) { Match(Results, AO.ASTCtx); }), llvm::Succeeded()); } // FIXME add this to testing support. template 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("v", TargetNodes); assert(Result != nullptr); return Result; } template std::pair 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(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(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 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(Prop); if (Env.proves(BVProp->formula()) != Implies) return testing::AssertionFailure() << Property << " is " << (Implies ? "not" : "") << " implied" << ", but should " << (Implies ? "" : "not ") << "be"; return testing::AssertionSuccess(); } template 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 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 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 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> &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> &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> &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> &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> &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> &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> &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> &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> &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, ComplexLoopCondition) { std::string Code = R"( int foo(); void fun() { int a, b; while ((a = foo()) > 0 && (b = foo()) > 0) { a; b; // [[p]] } } )"; runDataflow( Code, [](const llvm::StringMap> &Results, ASTContext &ASTCtx) { 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(isPositive(B, ASTCtx, Env)); }, 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> &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> &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> &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