//===- 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(QualType)> SyntheticFieldCallback, std::function< void(const llvm::StringMap> &, 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 { if (Ty.getAsString() != "S") return {}; QualType IntTy = getFieldNamed(Ty->getAsRecordDecl(), "outer_int")->getType(); return {{"synth_int", IntTy}}; }, [](const llvm::StringMap> &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(ASTCtx, Env, "s1"); auto &S2 = getLocForDecl(ASTCtx, Env, "s2"); auto &Inner1 = *cast(S1.getChild(*InnerDecl)); auto &Inner2 = *cast(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 { if (Ty.getAsString() != "S") return {}; QualType IntTy = getFieldNamed(Ty->getAsRecordDecl(), "outer_int")->getType(); return {{"synth_int", IntTy}}; }, [](const llvm::StringMap> &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(ASTCtx, Env, "s1"); auto &S2 = getLocForDecl(ASTCtx, Env, "s2"); auto &Inner2 = *cast(S2.getChild(*InnerDecl)); Env.setValue(S1.getSyntheticField("synth_int"), Env.create()); // 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()); 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()); 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()); 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 { 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> &Results, ASTContext &ASTCtx) { Environment Env = getEnvironmentAtAnnotation(Results, "p").fork(); const ValueDecl *IDecl = findValueDecl(ASTCtx, "i"); auto &A = getLocForDecl(ASTCtx, Env, "a"); auto &B = getLocForDecl(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> &Results, ASTContext &ASTCtx) { Environment Env = getEnvironmentAtAnnotation(Results, "p").fork(); const ValueDecl *IDecl = findValueDecl(ASTCtx, "i"); auto &A = getLocForDecl(ASTCtx, Env, "a"); auto &B = getLocForDecl(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