//===- unittests/Analysis/FlowSensitive/CachedConstAccessorsLatticeTest.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 // //===----------------------------------------------------------------------===// #include "clang/Analysis/FlowSensitive/CachedConstAccessorsLattice.h" #include #include #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/Expr.h" #include "clang/AST/Type.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" #include "clang/Analysis/FlowSensitive/DataflowLattice.h" #include "clang/Analysis/FlowSensitive/NoopLattice.h" #include "clang/Analysis/FlowSensitive/StorageLocation.h" #include "clang/Analysis/FlowSensitive/Value.h" #include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h" #include "clang/Basic/LLVM.h" #include "clang/Testing/TestAST.h" #include "gmock/gmock.h" #include "gtest/gtest.h" namespace clang::dataflow { namespace { using ast_matchers::BoundNodes; using ast_matchers::callee; using ast_matchers::cxxMemberCallExpr; using ast_matchers::functionDecl; using ast_matchers::hasName; using ast_matchers::match; using ast_matchers::selectFirst; using dataflow::DataflowAnalysisContext; using dataflow::Environment; using dataflow::LatticeJoinEffect; using dataflow::RecordStorageLocation; using dataflow::Value; using dataflow::WatchedLiteralsSolver; using testing::SizeIs; NamedDecl *lookup(StringRef Name, const DeclContext &DC) { auto Result = DC.lookup(&DC.getParentASTContext().Idents.get(Name)); EXPECT_TRUE(Result.isSingleResult()) << Name; return Result.front(); } class CachedConstAccessorsLatticeTest : public ::testing::Test { protected: using LatticeT = CachedConstAccessorsLattice; DataflowAnalysisContext DACtx{std::make_unique()}; Environment Env{DACtx}; }; // Basic test AST with two const methods (return a value, and return a ref). struct CommonTestInputs { CommonTestInputs() : AST(R"cpp( struct S { int *valProperty() const; int &refProperty() const; }; void target() { S s; s.valProperty(); S s2; s2.refProperty(); } )cpp") { auto *SDecl = cast( lookup("S", *AST.context().getTranslationUnitDecl())); SType = AST.context().getRecordType(SDecl); CallVal = selectFirst( "call", match(cxxMemberCallExpr(callee(functionDecl(hasName("valProperty")))) .bind("call"), AST.context())); assert(CallVal != nullptr); CallRef = selectFirst( "call", match(cxxMemberCallExpr(callee(functionDecl(hasName("refProperty")))) .bind("call"), AST.context())); assert(CallRef != nullptr); } TestAST AST; QualType SType; const CallExpr *CallVal; const CallExpr *CallRef; }; TEST_F(CachedConstAccessorsLatticeTest, SamePrimitiveValBeforeClearOrDiffAfterClear) { CommonTestInputs Inputs; auto *CE = Inputs.CallVal; RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), {}); LatticeT Lattice; Value *Val1 = Lattice.getOrCreateConstMethodReturnValue(Loc, CE, Env); Value *Val2 = Lattice.getOrCreateConstMethodReturnValue(Loc, CE, Env); EXPECT_EQ(Val1, Val2); Lattice.clearConstMethodReturnValues(Loc); Value *Val3 = Lattice.getOrCreateConstMethodReturnValue(Loc, CE, Env); EXPECT_NE(Val3, Val1); EXPECT_NE(Val3, Val2); } TEST_F(CachedConstAccessorsLatticeTest, SameLocBeforeClearOrDiffAfterClear) { CommonTestInputs Inputs; auto *CE = Inputs.CallRef; RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), {}); LatticeT Lattice; auto NopInit = [](StorageLocation &) {}; StorageLocation *Loc1 = Lattice.getOrCreateConstMethodReturnStorageLocation( Loc, CE, Env, NopInit); auto NotCalled = [](StorageLocation &) { ASSERT_TRUE(false) << "Not reached"; }; StorageLocation *Loc2 = Lattice.getOrCreateConstMethodReturnStorageLocation( Loc, CE, Env, NotCalled); EXPECT_EQ(Loc1, Loc2); Lattice.clearConstMethodReturnStorageLocations(Loc); StorageLocation *Loc3 = Lattice.getOrCreateConstMethodReturnStorageLocation( Loc, CE, Env, NopInit); EXPECT_NE(Loc3, Loc1); EXPECT_NE(Loc3, Loc2); } TEST_F(CachedConstAccessorsLatticeTest, SameLocBeforeClearOrDiffAfterClearWithCallee) { CommonTestInputs Inputs; auto *CE = Inputs.CallRef; RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), {}); LatticeT Lattice; auto NopInit = [](StorageLocation &) {}; const FunctionDecl *Callee = CE->getDirectCallee(); ASSERT_NE(Callee, nullptr); StorageLocation &Loc1 = Lattice.getOrCreateConstMethodReturnStorageLocation( Loc, Callee, Env, NopInit); auto NotCalled = [](StorageLocation &) { ASSERT_TRUE(false) << "Not reached"; }; StorageLocation &Loc2 = Lattice.getOrCreateConstMethodReturnStorageLocation( Loc, Callee, Env, NotCalled); EXPECT_EQ(&Loc1, &Loc2); Lattice.clearConstMethodReturnStorageLocations(Loc); StorageLocation &Loc3 = Lattice.getOrCreateConstMethodReturnStorageLocation( Loc, Callee, Env, NopInit); EXPECT_NE(&Loc3, &Loc1); EXPECT_NE(&Loc3, &Loc2); } TEST_F(CachedConstAccessorsLatticeTest, SameStructValBeforeClearOrDiffAfterClear) { TestAST AST(R"cpp( struct S { S structValProperty() const; }; void target() { S s; s.structValProperty(); } )cpp"); auto *SDecl = cast(lookup("S", *AST.context().getTranslationUnitDecl())); QualType SType = AST.context().getRecordType(SDecl); const CallExpr *CE = selectFirst( "call", match(cxxMemberCallExpr( callee(functionDecl(hasName("structValProperty")))) .bind("call"), AST.context())); ASSERT_NE(CE, nullptr); RecordStorageLocation Loc(SType, RecordStorageLocation::FieldToLoc(), {}); LatticeT Lattice; // Accessors that return a record by value are modeled by a record storage // location (instead of a Value). auto NopInit = [](StorageLocation &) {}; StorageLocation *Loc1 = Lattice.getOrCreateConstMethodReturnStorageLocation( Loc, CE, Env, NopInit); auto NotCalled = [](StorageLocation &) { ASSERT_TRUE(false) << "Not reached"; }; StorageLocation *Loc2 = Lattice.getOrCreateConstMethodReturnStorageLocation( Loc, CE, Env, NotCalled); EXPECT_EQ(Loc1, Loc2); Lattice.clearConstMethodReturnStorageLocations(Loc); StorageLocation *Loc3 = Lattice.getOrCreateConstMethodReturnStorageLocation( Loc, CE, Env, NopInit); EXPECT_NE(Loc3, Loc1); EXPECT_NE(Loc3, Loc1); } TEST_F(CachedConstAccessorsLatticeTest, ClearDifferentLocs) { CommonTestInputs Inputs; auto *CE = Inputs.CallRef; RecordStorageLocation LocS1(Inputs.SType, RecordStorageLocation::FieldToLoc(), {}); RecordStorageLocation LocS2(Inputs.SType, RecordStorageLocation::FieldToLoc(), {}); LatticeT Lattice; auto NopInit = [](StorageLocation &) {}; StorageLocation *RetLoc1 = Lattice.getOrCreateConstMethodReturnStorageLocation(LocS1, CE, Env, NopInit); Lattice.clearConstMethodReturnStorageLocations(LocS2); auto NotCalled = [](StorageLocation &) { ASSERT_TRUE(false) << "Not reached"; }; StorageLocation *RetLoc2 = Lattice.getOrCreateConstMethodReturnStorageLocation(LocS1, CE, Env, NotCalled); EXPECT_EQ(RetLoc1, RetLoc2); } TEST_F(CachedConstAccessorsLatticeTest, DifferentValsFromDifferentLocs) { TestAST AST(R"cpp( struct S { int *valProperty() const; }; void target() { S s1; s1.valProperty(); S s2; s2.valProperty(); } )cpp"); auto *SDecl = cast(lookup("S", *AST.context().getTranslationUnitDecl())); QualType SType = AST.context().getRecordType(SDecl); SmallVector valPropertyCalls = match(cxxMemberCallExpr(callee(functionDecl(hasName("valProperty")))) .bind("call"), AST.context()); ASSERT_THAT(valPropertyCalls, SizeIs(2)); const CallExpr *CE1 = selectFirst("call", valPropertyCalls); ASSERT_NE(CE1, nullptr); valPropertyCalls.erase(valPropertyCalls.begin()); const CallExpr *CE2 = selectFirst("call", valPropertyCalls); ASSERT_NE(CE2, nullptr); ASSERT_NE(CE1, CE2); RecordStorageLocation LocS1(SType, RecordStorageLocation::FieldToLoc(), {}); RecordStorageLocation LocS2(SType, RecordStorageLocation::FieldToLoc(), {}); LatticeT Lattice; Value *Val1 = Lattice.getOrCreateConstMethodReturnValue(LocS1, CE1, Env); Value *Val2 = Lattice.getOrCreateConstMethodReturnValue(LocS2, CE2, Env); EXPECT_NE(Val1, Val2); } TEST_F(CachedConstAccessorsLatticeTest, JoinSameNoop) { CommonTestInputs Inputs; auto *CE = Inputs.CallVal; RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), {}); LatticeT EmptyLattice; LatticeT EmptyLattice2; EXPECT_EQ(EmptyLattice.join(EmptyLattice2), LatticeJoinEffect::Unchanged); LatticeT Lattice1; Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env); EXPECT_EQ(Lattice1.join(Lattice1), LatticeJoinEffect::Unchanged); } TEST_F(CachedConstAccessorsLatticeTest, ProducesNewValueAfterJoinDistinct) { CommonTestInputs Inputs; auto *CE = Inputs.CallVal; RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), {}); // L1 w/ v vs L2 empty LatticeT Lattice1; Value *Val1 = Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env); LatticeT EmptyLattice; EXPECT_EQ(Lattice1.join(EmptyLattice), LatticeJoinEffect::Changed); Value *ValAfterJoin = Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env); EXPECT_NE(ValAfterJoin, Val1); // L1 w/ v1 vs L3 w/ v2 LatticeT Lattice3; Value *Val3 = Lattice3.getOrCreateConstMethodReturnValue(Loc, CE, Env); EXPECT_EQ(Lattice1.join(Lattice3), LatticeJoinEffect::Changed); Value *ValAfterJoin2 = Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env); EXPECT_NE(ValAfterJoin2, ValAfterJoin); EXPECT_NE(ValAfterJoin2, Val3); } } // namespace } // namespace clang::dataflow