//===- 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, SkipPast::None); if (!UnaryOpValue) { auto &Loc = State.Env.createStorageLocation(*UO); State.Env.setStorageLocation(*UO, Loc); UnaryOpValue = &State.Env.makeAtomicBoolValue(); State.Env.setValue(Loc, *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) { StorageLocation *Loc = State.Env.getStorageLocation(*BO, SkipPast::None); if (!Loc) { Loc = &State.Env.createStorageLocation(*BO); State.Env.setStorageLocation(*BO, *Loc); } BoolValue *Comp = cast_or_null(State.Env.getValue(*Loc)); if (!Comp) { Comp = &State.Env.makeAtomicBoolValue(); State.Env.setValue(*Loc, *Comp); } // FIXME Use this as well: // auto *NegatedComp = &State.Env.makeNot(*Comp); auto *LHS = State.Env.getValue(*BO->getLHS(), SkipPast::None); auto *RHS = State.Env.getValue(*BO->getRHS(), SkipPast::None); 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(State.Env.makeImplication( *Comp, State.Env.makeImplication(*RHSProps.Pos, *LHSProps.Pos))); // pos > zero State.Env.addToFlowCondition(State.Env.makeImplication( *Comp, State.Env.makeImplication(*RHSProps.Zero, *LHSProps.Pos))); break; case BO_LT: // neg < neg State.Env.addToFlowCondition(State.Env.makeImplication( *Comp, State.Env.makeImplication(*RHSProps.Neg, *LHSProps.Neg))); // neg < zero State.Env.addToFlowCondition(State.Env.makeImplication( *Comp, State.Env.makeImplication(*RHSProps.Zero, *LHSProps.Neg))); break; case BO_GE: // pos >= pos State.Env.addToFlowCondition(State.Env.makeImplication( *Comp, State.Env.makeImplication(*RHSProps.Pos, *LHSProps.Pos))); break; case BO_LE: // neg <= neg State.Env.addToFlowCondition(State.Env.makeImplication( *Comp, State.Env.makeImplication(*RHSProps.Neg, *LHSProps.Neg))); break; case BO_EQ: State.Env.addToFlowCondition(State.Env.makeImplication( *Comp, State.Env.makeImplication(*RHSProps.Neg, *LHSProps.Neg))); State.Env.addToFlowCondition(State.Env.makeImplication( *Comp, State.Env.makeImplication(*RHSProps.Zero, *LHSProps.Zero))); State.Env.addToFlowCondition(State.Env.makeImplication( *Comp, State.Env.makeImplication(*RHSProps.Pos, *LHSProps.Pos))); break; case BO_NE: // Noop. break; default: llvm_unreachable("not implemented"); } } void transferUnaryMinus(const UnaryOperator *UO, const MatchFinder::MatchResult &M, LatticeTransferState &State) { auto [UnaryOpValue, UnaryOpProps, OperandProps] = getValueAndSignProperties(UO, M, State); if (!UnaryOpValue) return; // a is pos ==> -a is neg State.Env.addToFlowCondition( State.Env.makeImplication(*OperandProps.Pos, *UnaryOpProps.Neg)); // a is neg ==> -a is pos State.Env.addToFlowCondition( State.Env.makeImplication(*OperandProps.Neg, *UnaryOpProps.Pos)); // a is zero ==> -a is zero State.Env.addToFlowCondition( State.Env.makeImplication(*OperandProps.Zero, *UnaryOpProps.Zero)); } void transferUnaryNot(const UnaryOperator *UO, const MatchFinder::MatchResult &M, LatticeTransferState &State) { auto [UnaryOpValue, UnaryOpProps, OperandProps] = getValueAndSignProperties(UO, M, State); if (!UnaryOpValue) return; // a is neg or pos ==> !a is zero State.Env.addToFlowCondition(State.Env.makeImplication( State.Env.makeOr(*OperandProps.Pos, *OperandProps.Neg), *UnaryOpProps.Zero)); // 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.addToFlowCondition( State.Env.makeIff(*UOBoolVal, *OperandProps.Zero)); // !a <==> !a is not zero State.Env.addToFlowCondition( State.Env.makeIff(*UOBoolVal, State.Env.makeNot(*UnaryOpProps.Zero))); } } void transferExpr(const Expr *E, const MatchFinder::MatchResult &M, LatticeTransferState &State) { const ASTContext &Context = *M.Context; StorageLocation *Loc = State.Env.getStorageLocation(*E, SkipPast::None); if (!Loc) { Loc = &State.Env.createStorageLocation(*E); State.Env.setStorageLocation(*E, *Loc); } Value *Val = State.Env.getValue(*Loc); if (!Val) { Val = State.Env.createValue(Context.IntTy); State.Env.setValue(*Loc, *Val); } // 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); } bool merge(QualType Type, const Value &Val1, const Environment &Env1, const Value &Val2, const Environment &Env2, Value &MergedVal, Environment &MergedEnv) override; private: CFGMatchSwitch> TransferMatchSwitch; }; // Copied from crubit. BoolValue &mergeBoolValues(BoolValue &Bool1, const Environment &Env1, BoolValue &Bool2, const Environment &Env2, Environment &MergedEnv) { if (&Bool1 == &Bool2) { return Bool1; } 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(Bool1) && Env2.flowConditionImplies(Bool2)) { MergedEnv.addToFlowCondition(MergedBool); } else if (Env1.flowConditionImplies(Env1.makeNot(Bool1)) && Env2.flowConditionImplies(Env2.makeNot(Bool2))) { MergedEnv.addToFlowCondition(MergedEnv.makeNot(MergedBool)); } 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 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.flowConditionImplies(*BVProp) != 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, 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