| 1 | //===- unittests/Analysis/FlowSensitive/CachedConstAccessorsLatticeTest.cpp ==// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "clang/Analysis/FlowSensitive/CachedConstAccessorsLattice.h" |
| 10 | |
| 11 | #include <cassert> |
| 12 | #include <memory> |
| 13 | |
| 14 | #include "clang/AST/Decl.h" |
| 15 | #include "clang/AST/DeclBase.h" |
| 16 | #include "clang/AST/DeclCXX.h" |
| 17 | #include "clang/AST/Expr.h" |
| 18 | #include "clang/AST/Type.h" |
| 19 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 20 | #include "clang/ASTMatchers/ASTMatchers.h" |
| 21 | #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" |
| 22 | #include "clang/Analysis/FlowSensitive/DataflowLattice.h" |
| 23 | #include "clang/Analysis/FlowSensitive/NoopLattice.h" |
| 24 | #include "clang/Analysis/FlowSensitive/StorageLocation.h" |
| 25 | #include "clang/Analysis/FlowSensitive/Value.h" |
| 26 | #include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h" |
| 27 | #include "clang/Basic/LLVM.h" |
| 28 | #include "clang/Testing/TestAST.h" |
| 29 | #include "gmock/gmock.h" |
| 30 | #include "gtest/gtest.h" |
| 31 | |
| 32 | namespace clang::dataflow { |
| 33 | namespace { |
| 34 | |
| 35 | using ast_matchers::BoundNodes; |
| 36 | using ast_matchers::callee; |
| 37 | using ast_matchers::cxxMemberCallExpr; |
| 38 | using ast_matchers::functionDecl; |
| 39 | using ast_matchers::hasName; |
| 40 | using ast_matchers::match; |
| 41 | using ast_matchers::selectFirst; |
| 42 | |
| 43 | using dataflow::DataflowAnalysisContext; |
| 44 | using dataflow::Environment; |
| 45 | using dataflow::LatticeJoinEffect; |
| 46 | using dataflow::RecordStorageLocation; |
| 47 | using dataflow::Value; |
| 48 | using dataflow::WatchedLiteralsSolver; |
| 49 | |
| 50 | using testing::SizeIs; |
| 51 | |
| 52 | NamedDecl *lookup(StringRef Name, const DeclContext &DC) { |
| 53 | auto Result = DC.lookup(Name: &DC.getParentASTContext().Idents.get(Name)); |
| 54 | EXPECT_TRUE(Result.isSingleResult()) << Name; |
| 55 | return Result.front(); |
| 56 | } |
| 57 | |
| 58 | class CachedConstAccessorsLatticeTest : public ::testing::Test { |
| 59 | protected: |
| 60 | using LatticeT = CachedConstAccessorsLattice<NoopLattice>; |
| 61 | |
| 62 | DataflowAnalysisContext DACtx{std::make_unique<WatchedLiteralsSolver>()}; |
| 63 | Environment Env{DACtx}; |
| 64 | }; |
| 65 | |
| 66 | // Basic test AST with two const methods (return a value, and return a ref). |
| 67 | struct CommonTestInputs { |
| 68 | CommonTestInputs() |
| 69 | : AST(R"cpp( |
| 70 | struct S { |
| 71 | int *valProperty() const; |
| 72 | int &refProperty() const; |
| 73 | }; |
| 74 | void target() { |
| 75 | S s; |
| 76 | s.valProperty(); |
| 77 | S s2; |
| 78 | s2.refProperty(); |
| 79 | } |
| 80 | )cpp" ) { |
| 81 | auto *SDecl = cast<CXXRecordDecl>( |
| 82 | Val: lookup("S" , *AST.context().getTranslationUnitDecl())); |
| 83 | SType = AST.context().getRecordType(SDecl); |
| 84 | CallVal = selectFirst<CallExpr>( |
| 85 | BoundTo: "call" , |
| 86 | Results: match(Matcher: cxxMemberCallExpr(callee(InnerMatcher: functionDecl(hasName(Name: "valProperty" )))) |
| 87 | .bind(ID: "call" ), |
| 88 | Context&: AST.context())); |
| 89 | assert(CallVal != nullptr); |
| 90 | |
| 91 | CallRef = selectFirst<CallExpr>( |
| 92 | BoundTo: "call" , |
| 93 | Results: match(Matcher: cxxMemberCallExpr(callee(InnerMatcher: functionDecl(hasName(Name: "refProperty" )))) |
| 94 | .bind(ID: "call" ), |
| 95 | Context&: AST.context())); |
| 96 | assert(CallRef != nullptr); |
| 97 | } |
| 98 | |
| 99 | TestAST AST; |
| 100 | QualType SType; |
| 101 | const CallExpr *CallVal; |
| 102 | const CallExpr *CallRef; |
| 103 | }; |
| 104 | |
| 105 | TEST_F(CachedConstAccessorsLatticeTest, |
| 106 | SamePrimitiveValBeforeClearOrDiffAfterClear) { |
| 107 | CommonTestInputs Inputs; |
| 108 | auto *CE = Inputs.CallVal; |
| 109 | RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), |
| 110 | {}); |
| 111 | |
| 112 | LatticeT Lattice; |
| 113 | Value *Val1 = Lattice.getOrCreateConstMethodReturnValue(RecordLoc: Loc, CE, Env); |
| 114 | Value *Val2 = Lattice.getOrCreateConstMethodReturnValue(RecordLoc: Loc, CE, Env); |
| 115 | |
| 116 | EXPECT_EQ(Val1, Val2); |
| 117 | |
| 118 | Lattice.clearConstMethodReturnValues(RecordLoc: Loc); |
| 119 | Value *Val3 = Lattice.getOrCreateConstMethodReturnValue(RecordLoc: Loc, CE, Env); |
| 120 | |
| 121 | EXPECT_NE(Val3, Val1); |
| 122 | EXPECT_NE(Val3, Val2); |
| 123 | } |
| 124 | |
| 125 | TEST_F(CachedConstAccessorsLatticeTest, SameLocBeforeClearOrDiffAfterClear) { |
| 126 | CommonTestInputs Inputs; |
| 127 | auto *CE = Inputs.CallRef; |
| 128 | RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), |
| 129 | {}); |
| 130 | |
| 131 | LatticeT Lattice; |
| 132 | auto NopInit = [](StorageLocation &) {}; |
| 133 | const FunctionDecl *Callee = CE->getDirectCallee(); |
| 134 | ASSERT_NE(Callee, nullptr); |
| 135 | StorageLocation &Loc1 = Lattice.getOrCreateConstMethodReturnStorageLocation( |
| 136 | RecordLoc: Loc, Callee, Env, Initialize: NopInit); |
| 137 | auto NotCalled = [](StorageLocation &) { |
| 138 | ASSERT_TRUE(false) << "Not reached" ; |
| 139 | }; |
| 140 | StorageLocation &Loc2 = Lattice.getOrCreateConstMethodReturnStorageLocation( |
| 141 | RecordLoc: Loc, Callee, Env, Initialize: NotCalled); |
| 142 | |
| 143 | EXPECT_EQ(&Loc1, &Loc2); |
| 144 | |
| 145 | Lattice.clearConstMethodReturnStorageLocations(RecordLoc: Loc); |
| 146 | StorageLocation &Loc3 = Lattice.getOrCreateConstMethodReturnStorageLocation( |
| 147 | RecordLoc: Loc, Callee, Env, Initialize: NopInit); |
| 148 | |
| 149 | EXPECT_NE(&Loc3, &Loc1); |
| 150 | EXPECT_NE(&Loc3, &Loc2); |
| 151 | } |
| 152 | |
| 153 | TEST_F(CachedConstAccessorsLatticeTest, |
| 154 | SameStructValBeforeClearOrDiffAfterClear) { |
| 155 | TestAST AST(R"cpp( |
| 156 | struct S { |
| 157 | S structValProperty() const; |
| 158 | }; |
| 159 | void target() { |
| 160 | S s; |
| 161 | s.structValProperty(); |
| 162 | } |
| 163 | )cpp" ); |
| 164 | auto *SDecl = |
| 165 | cast<CXXRecordDecl>(Val: lookup("S" , *AST.context().getTranslationUnitDecl())); |
| 166 | QualType SType = AST.context().getRecordType(Decl: SDecl); |
| 167 | const CallExpr *CE = selectFirst<CallExpr>( |
| 168 | BoundTo: "call" , Results: match(Matcher: cxxMemberCallExpr( |
| 169 | callee(InnerMatcher: functionDecl(hasName(Name: "structValProperty" )))) |
| 170 | .bind(ID: "call" ), |
| 171 | Context&: AST.context())); |
| 172 | ASSERT_NE(CE, nullptr); |
| 173 | |
| 174 | RecordStorageLocation Loc(SType, RecordStorageLocation::FieldToLoc(), {}); |
| 175 | |
| 176 | LatticeT Lattice; |
| 177 | // Accessors that return a record by value are modeled by a record storage |
| 178 | // location (instead of a Value). |
| 179 | auto NopInit = [](StorageLocation &) {}; |
| 180 | const FunctionDecl *Callee = CE->getDirectCallee(); |
| 181 | ASSERT_NE(Callee, nullptr); |
| 182 | StorageLocation &Loc1 = Lattice.getOrCreateConstMethodReturnStorageLocation( |
| 183 | RecordLoc: Loc, Callee, Env, Initialize: NopInit); |
| 184 | auto NotCalled = [](StorageLocation &) { |
| 185 | ASSERT_TRUE(false) << "Not reached" ; |
| 186 | }; |
| 187 | StorageLocation &Loc2 = Lattice.getOrCreateConstMethodReturnStorageLocation( |
| 188 | RecordLoc: Loc, Callee, Env, Initialize: NotCalled); |
| 189 | |
| 190 | EXPECT_EQ(&Loc1, &Loc2); |
| 191 | |
| 192 | Lattice.clearConstMethodReturnStorageLocations(RecordLoc: Loc); |
| 193 | StorageLocation &Loc3 = Lattice.getOrCreateConstMethodReturnStorageLocation( |
| 194 | RecordLoc: Loc, Callee, Env, Initialize: NopInit); |
| 195 | |
| 196 | EXPECT_NE(&Loc3, &Loc1); |
| 197 | EXPECT_NE(&Loc3, &Loc1); |
| 198 | } |
| 199 | |
| 200 | TEST_F(CachedConstAccessorsLatticeTest, ClearDifferentLocs) { |
| 201 | CommonTestInputs Inputs; |
| 202 | auto *CE = Inputs.CallRef; |
| 203 | RecordStorageLocation LocS1(Inputs.SType, RecordStorageLocation::FieldToLoc(), |
| 204 | {}); |
| 205 | RecordStorageLocation LocS2(Inputs.SType, RecordStorageLocation::FieldToLoc(), |
| 206 | {}); |
| 207 | |
| 208 | LatticeT Lattice; |
| 209 | auto NopInit = [](StorageLocation &) {}; |
| 210 | const FunctionDecl *Callee = CE->getDirectCallee(); |
| 211 | ASSERT_NE(Callee, nullptr); |
| 212 | StorageLocation &RetLoc1 = |
| 213 | Lattice.getOrCreateConstMethodReturnStorageLocation(RecordLoc: LocS1, Callee, Env, |
| 214 | Initialize: NopInit); |
| 215 | Lattice.clearConstMethodReturnStorageLocations(RecordLoc: LocS2); |
| 216 | auto NotCalled = [](StorageLocation &) { |
| 217 | ASSERT_TRUE(false) << "Not reached" ; |
| 218 | }; |
| 219 | StorageLocation &RetLoc2 = |
| 220 | Lattice.getOrCreateConstMethodReturnStorageLocation(RecordLoc: LocS1, Callee, Env, |
| 221 | Initialize: NotCalled); |
| 222 | |
| 223 | EXPECT_EQ(&RetLoc1, &RetLoc2); |
| 224 | } |
| 225 | |
| 226 | TEST_F(CachedConstAccessorsLatticeTest, DifferentValsFromDifferentLocs) { |
| 227 | TestAST AST(R"cpp( |
| 228 | struct S { |
| 229 | int *valProperty() const; |
| 230 | }; |
| 231 | void target() { |
| 232 | S s1; |
| 233 | s1.valProperty(); |
| 234 | S s2; |
| 235 | s2.valProperty(); |
| 236 | } |
| 237 | )cpp" ); |
| 238 | auto *SDecl = |
| 239 | cast<CXXRecordDecl>(Val: lookup("S" , *AST.context().getTranslationUnitDecl())); |
| 240 | QualType SType = AST.context().getRecordType(Decl: SDecl); |
| 241 | SmallVector<BoundNodes, 1> valPropertyCalls = |
| 242 | match(Matcher: cxxMemberCallExpr(callee(InnerMatcher: functionDecl(hasName(Name: "valProperty" )))) |
| 243 | .bind(ID: "call" ), |
| 244 | Context&: AST.context()); |
| 245 | ASSERT_THAT(valPropertyCalls, SizeIs(2)); |
| 246 | |
| 247 | const CallExpr *CE1 = selectFirst<CallExpr>(BoundTo: "call" , Results: valPropertyCalls); |
| 248 | ASSERT_NE(CE1, nullptr); |
| 249 | |
| 250 | valPropertyCalls.erase(CI: valPropertyCalls.begin()); |
| 251 | const CallExpr *CE2 = selectFirst<CallExpr>(BoundTo: "call" , Results: valPropertyCalls); |
| 252 | ASSERT_NE(CE2, nullptr); |
| 253 | ASSERT_NE(CE1, CE2); |
| 254 | |
| 255 | RecordStorageLocation LocS1(SType, RecordStorageLocation::FieldToLoc(), {}); |
| 256 | RecordStorageLocation LocS2(SType, RecordStorageLocation::FieldToLoc(), {}); |
| 257 | |
| 258 | LatticeT Lattice; |
| 259 | Value *Val1 = Lattice.getOrCreateConstMethodReturnValue(RecordLoc: LocS1, CE: CE1, Env); |
| 260 | Value *Val2 = Lattice.getOrCreateConstMethodReturnValue(RecordLoc: LocS2, CE: CE2, Env); |
| 261 | |
| 262 | EXPECT_NE(Val1, Val2); |
| 263 | } |
| 264 | |
| 265 | TEST_F(CachedConstAccessorsLatticeTest, JoinSameNoop) { |
| 266 | CommonTestInputs Inputs; |
| 267 | auto *CE = Inputs.CallVal; |
| 268 | RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), |
| 269 | {}); |
| 270 | |
| 271 | LatticeT EmptyLattice; |
| 272 | LatticeT EmptyLattice2; |
| 273 | EXPECT_EQ(EmptyLattice.join(EmptyLattice2), LatticeJoinEffect::Unchanged); |
| 274 | |
| 275 | LatticeT Lattice1; |
| 276 | Lattice1.getOrCreateConstMethodReturnValue(RecordLoc: Loc, CE, Env); |
| 277 | EXPECT_EQ(Lattice1.join(Lattice1), LatticeJoinEffect::Unchanged); |
| 278 | } |
| 279 | |
| 280 | TEST_F(CachedConstAccessorsLatticeTest, ProducesNewValueAfterJoinDistinct) { |
| 281 | CommonTestInputs Inputs; |
| 282 | auto *CE = Inputs.CallVal; |
| 283 | RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), |
| 284 | {}); |
| 285 | |
| 286 | // L1 w/ v vs L2 empty |
| 287 | LatticeT Lattice1; |
| 288 | Value *Val1 = Lattice1.getOrCreateConstMethodReturnValue(RecordLoc: Loc, CE, Env); |
| 289 | |
| 290 | LatticeT EmptyLattice; |
| 291 | |
| 292 | EXPECT_EQ(Lattice1.join(EmptyLattice), LatticeJoinEffect::Changed); |
| 293 | Value *ValAfterJoin = |
| 294 | Lattice1.getOrCreateConstMethodReturnValue(RecordLoc: Loc, CE, Env); |
| 295 | |
| 296 | EXPECT_NE(ValAfterJoin, Val1); |
| 297 | |
| 298 | // L1 w/ v1 vs L3 w/ v2 |
| 299 | LatticeT Lattice3; |
| 300 | Value *Val3 = Lattice3.getOrCreateConstMethodReturnValue(RecordLoc: Loc, CE, Env); |
| 301 | |
| 302 | EXPECT_EQ(Lattice1.join(Lattice3), LatticeJoinEffect::Changed); |
| 303 | Value *ValAfterJoin2 = |
| 304 | Lattice1.getOrCreateConstMethodReturnValue(RecordLoc: Loc, CE, Env); |
| 305 | |
| 306 | EXPECT_NE(ValAfterJoin2, ValAfterJoin); |
| 307 | EXPECT_NE(ValAfterJoin2, Val3); |
| 308 | } |
| 309 | |
| 310 | } // namespace |
| 311 | } // namespace clang::dataflow |
| 312 | |