| //===- unittests/Analysis/FlowSensitive/RecordOpsTest.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/RecordOps.h" |
| #include "TestingSupport.h" |
| #include "llvm/Testing/Support/Error.h" |
| #include "gtest/gtest.h" |
| |
| namespace clang { |
| namespace dataflow { |
| namespace test { |
| namespace { |
| |
| void runDataflow( |
| llvm::StringRef Code, |
| std::function<llvm::StringMap<QualType>(QualType)> SyntheticFieldCallback, |
| std::function< |
| void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
| ASTContext &)> |
| VerifyResults) { |
| ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis( |
| Code, ast_matchers::hasName("target"), VerifyResults, |
| {BuiltinOptions()}, LangStandard::lang_cxx17, |
| SyntheticFieldCallback), |
| llvm::Succeeded()); |
| } |
| |
| const FieldDecl *getFieldNamed(RecordDecl *RD, llvm::StringRef Name) { |
| for (const FieldDecl *FD : RD->fields()) |
| if (FD->getName() == Name) |
| return FD; |
| assert(false); |
| return nullptr; |
| } |
| |
| TEST(RecordOpsTest, CopyRecord) { |
| std::string Code = R"( |
| struct S { |
| int outer_int; |
| int &ref; |
| struct { |
| int inner_int; |
| } inner; |
| }; |
| void target(S s1, S s2) { |
| (void)s1.outer_int; |
| (void)s1.ref; |
| (void)s1.inner.inner_int; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](QualType Ty) -> llvm::StringMap<QualType> { |
| if (Ty.getAsString() != "S") |
| return {}; |
| QualType IntTy = |
| getFieldNamed(Ty->getAsRecordDecl(), "outer_int")->getType(); |
| return {{"synth_int", IntTy}}; |
| }, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| Environment Env = getEnvironmentAtAnnotation(Results, "p").fork(); |
| |
| const ValueDecl *OuterIntDecl = findValueDecl(ASTCtx, "outer_int"); |
| const ValueDecl *RefDecl = findValueDecl(ASTCtx, "ref"); |
| const ValueDecl *InnerDecl = findValueDecl(ASTCtx, "inner"); |
| const ValueDecl *InnerIntDecl = findValueDecl(ASTCtx, "inner_int"); |
| |
| auto &S1 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s1"); |
| auto &S2 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s2"); |
| auto &Inner1 = *cast<RecordStorageLocation>(S1.getChild(*InnerDecl)); |
| auto &Inner2 = *cast<RecordStorageLocation>(S2.getChild(*InnerDecl)); |
| |
| EXPECT_NE(getFieldValue(&S1, *OuterIntDecl, Env), |
| getFieldValue(&S2, *OuterIntDecl, Env)); |
| EXPECT_NE(S1.getChild(*RefDecl), S2.getChild(*RefDecl)); |
| EXPECT_NE(getFieldValue(&Inner1, *InnerIntDecl, Env), |
| getFieldValue(&Inner2, *InnerIntDecl, Env)); |
| EXPECT_NE(Env.getValue(S1.getSyntheticField("synth_int")), |
| Env.getValue(S2.getSyntheticField("synth_int"))); |
| |
| copyRecord(S1, S2, Env); |
| |
| EXPECT_EQ(getFieldValue(&S1, *OuterIntDecl, Env), |
| getFieldValue(&S2, *OuterIntDecl, Env)); |
| EXPECT_EQ(S1.getChild(*RefDecl), S2.getChild(*RefDecl)); |
| EXPECT_EQ(getFieldValue(&Inner1, *InnerIntDecl, Env), |
| getFieldValue(&Inner2, *InnerIntDecl, Env)); |
| EXPECT_EQ(Env.getValue(S1.getSyntheticField("synth_int")), |
| Env.getValue(S2.getSyntheticField("synth_int"))); |
| }); |
| } |
| |
| TEST(RecordOpsTest, RecordsEqual) { |
| std::string Code = R"( |
| struct S { |
| int outer_int; |
| int &ref; |
| struct { |
| int inner_int; |
| } inner; |
| }; |
| void target(S s1, S s2) { |
| (void)s1.outer_int; |
| (void)s1.ref; |
| (void)s1.inner.inner_int; |
| // [[p]] |
| } |
| )"; |
| runDataflow( |
| Code, |
| [](QualType Ty) -> llvm::StringMap<QualType> { |
| if (Ty.getAsString() != "S") |
| return {}; |
| QualType IntTy = |
| getFieldNamed(Ty->getAsRecordDecl(), "outer_int")->getType(); |
| return {{"synth_int", IntTy}}; |
| }, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| Environment Env = getEnvironmentAtAnnotation(Results, "p").fork(); |
| |
| const ValueDecl *OuterIntDecl = findValueDecl(ASTCtx, "outer_int"); |
| const ValueDecl *RefDecl = findValueDecl(ASTCtx, "ref"); |
| const ValueDecl *InnerDecl = findValueDecl(ASTCtx, "inner"); |
| const ValueDecl *InnerIntDecl = findValueDecl(ASTCtx, "inner_int"); |
| |
| auto &S1 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s1"); |
| auto &S2 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "s2"); |
| auto &Inner2 = *cast<RecordStorageLocation>(S2.getChild(*InnerDecl)); |
| |
| Env.setValue(S1.getSyntheticField("synth_int"), |
| Env.create<IntegerValue>()); |
| |
| // Strategy: Create two equal records, then verify each of the various |
| // ways in which records can differ causes recordsEqual to return false. |
| // changes we can make to the record. |
| |
| // This test reuses the same objects for multiple checks, which isn't |
| // great, but seems better than duplicating the setup code for every |
| // check. |
| |
| copyRecord(S1, S2, Env); |
| EXPECT_TRUE(recordsEqual(S1, S2, Env)); |
| |
| // S2 has a different outer_int. |
| Env.setValue(*S2.getChild(*OuterIntDecl), Env.create<IntegerValue>()); |
| EXPECT_FALSE(recordsEqual(S1, S2, Env)); |
| copyRecord(S1, S2, Env); |
| EXPECT_TRUE(recordsEqual(S1, S2, Env)); |
| |
| // S2 doesn't have outer_int at all. |
| Env.clearValue(*S2.getChild(*OuterIntDecl)); |
| EXPECT_FALSE(recordsEqual(S1, S2, Env)); |
| copyRecord(S1, S2, Env); |
| EXPECT_TRUE(recordsEqual(S1, S2, Env)); |
| |
| // S2 has a different ref. |
| S2.setChild(*RefDecl, &Env.createStorageLocation( |
| RefDecl->getType().getNonReferenceType())); |
| EXPECT_FALSE(recordsEqual(S1, S2, Env)); |
| copyRecord(S1, S2, Env); |
| EXPECT_TRUE(recordsEqual(S1, S2, Env)); |
| |
| // S2 as a different inner_int. |
| Env.setValue(*Inner2.getChild(*InnerIntDecl), |
| Env.create<IntegerValue>()); |
| EXPECT_FALSE(recordsEqual(S1, S2, Env)); |
| copyRecord(S1, S2, Env); |
| EXPECT_TRUE(recordsEqual(S1, S2, Env)); |
| |
| // S2 has a different synth_int. |
| Env.setValue(S2.getSyntheticField("synth_int"), |
| Env.create<IntegerValue>()); |
| EXPECT_FALSE(recordsEqual(S1, S2, Env)); |
| copyRecord(S1, S2, Env); |
| EXPECT_TRUE(recordsEqual(S1, S2, Env)); |
| |
| // S2 doesn't have a value for synth_int. |
| Env.clearValue(S2.getSyntheticField("synth_int")); |
| EXPECT_FALSE(recordsEqual(S1, S2, Env)); |
| copyRecord(S1, S2, Env); |
| EXPECT_TRUE(recordsEqual(S1, S2, Env)); |
| }); |
| } |
| |
| TEST(TransferTest, CopyRecordBetweenDerivedAndBase) { |
| std::string Code = R"( |
| struct A { |
| int i; |
| }; |
| |
| struct B : public A { |
| }; |
| |
| void target(A a, B b) { |
| (void)a.i; |
| // [[p]] |
| } |
| )"; |
| auto SyntheticFieldCallback = [](QualType Ty) -> llvm::StringMap<QualType> { |
| CXXRecordDecl *ADecl = nullptr; |
| if (Ty.getAsString() == "A") |
| ADecl = Ty->getAsCXXRecordDecl(); |
| else if (Ty.getAsString() == "B") |
| ADecl = Ty->getAsCXXRecordDecl() |
| ->bases_begin() |
| ->getType() |
| ->getAsCXXRecordDecl(); |
| else |
| return {}; |
| QualType IntTy = getFieldNamed(ADecl, "i")->getType(); |
| return {{"synth_int", IntTy}}; |
| }; |
| // Test copying derived to base class. |
| runDataflow( |
| Code, SyntheticFieldCallback, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| Environment Env = getEnvironmentAtAnnotation(Results, "p").fork(); |
| |
| const ValueDecl *IDecl = findValueDecl(ASTCtx, "i"); |
| auto &A = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "a"); |
| auto &B = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "b"); |
| |
| EXPECT_NE(Env.getValue(*A.getChild(*IDecl)), |
| Env.getValue(*B.getChild(*IDecl))); |
| EXPECT_NE(Env.getValue(A.getSyntheticField("synth_int")), |
| Env.getValue(B.getSyntheticField("synth_int"))); |
| |
| copyRecord(B, A, Env); |
| |
| EXPECT_EQ(Env.getValue(*A.getChild(*IDecl)), |
| Env.getValue(*B.getChild(*IDecl))); |
| EXPECT_EQ(Env.getValue(A.getSyntheticField("synth_int")), |
| Env.getValue(B.getSyntheticField("synth_int"))); |
| }); |
| // Test copying base to derived class. |
| runDataflow( |
| Code, SyntheticFieldCallback, |
| [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
| ASTContext &ASTCtx) { |
| Environment Env = getEnvironmentAtAnnotation(Results, "p").fork(); |
| |
| const ValueDecl *IDecl = findValueDecl(ASTCtx, "i"); |
| auto &A = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "a"); |
| auto &B = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "b"); |
| |
| EXPECT_NE(Env.getValue(*A.getChild(*IDecl)), |
| Env.getValue(*B.getChild(*IDecl))); |
| EXPECT_NE(Env.getValue(A.getSyntheticField("synth_int")), |
| Env.getValue(B.getSyntheticField("synth_int"))); |
| |
| copyRecord(A, B, Env); |
| |
| EXPECT_EQ(Env.getValue(*A.getChild(*IDecl)), |
| Env.getValue(*B.getChild(*IDecl))); |
| EXPECT_EQ(Env.getValue(A.getSyntheticField("synth_int")), |
| Env.getValue(B.getSyntheticField("synth_int"))); |
| }); |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace dataflow |
| } // namespace clang |