diff options
Diffstat (limited to 'clang/unittests/Analysis')
4 files changed, 209 insertions, 22 deletions
diff --git a/clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp b/clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp index 88b92668c850..57162cd2efcc 100644 --- a/clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp @@ -8,8 +8,15 @@ #include "clang/Analysis/FlowSensitive/RecordOps.h" #include "TestingSupport.h" +#include "clang/AST/Type.h" +#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" +#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" +#include "clang/Analysis/FlowSensitive/NoopLattice.h" +#include "clang/Analysis/FlowSensitive/StorageLocation.h" +#include "llvm/ADT/StringMap.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" +#include <string> namespace clang { namespace dataflow { @@ -190,7 +197,7 @@ TEST(RecordOpsTest, RecordsEqual) { }); } -TEST(TransferTest, CopyRecordBetweenDerivedAndBase) { +TEST(RecordOpsTest, CopyRecordBetweenDerivedAndBase) { std::string Code = R"( struct A { int i; @@ -266,6 +273,67 @@ TEST(TransferTest, CopyRecordBetweenDerivedAndBase) { }); } +TEST(RecordOpsTest, CopyRecordWithExplicitSharedBaseTypeToCopy) { + std::string Code = R"( + struct Base { + bool BaseField; + char UnmodeledField; + }; + + struct DerivedOne : public Base { + int DerivedOneField; + }; + + struct DerivedTwo : public Base { + int DerivedTwoField; + }; + + void target(Base B, DerivedOne D1, DerivedTwo D2) { + (void) B.BaseField; + // [[p]] + } + )"; + auto SyntheticFieldCallback = [](QualType Ty) -> llvm::StringMap<QualType> { + CXXRecordDecl *BaseDecl = nullptr; + std::string TypeAsString = Ty.getAsString(); + if (TypeAsString == "Base") + BaseDecl = Ty->getAsCXXRecordDecl(); + else if (TypeAsString == "DerivedOne" || TypeAsString == "DerivedTwo") + BaseDecl = Ty->getAsCXXRecordDecl() + ->bases_begin() + ->getType() + ->getAsCXXRecordDecl(); + else + return {}; + QualType FieldType = getFieldNamed(BaseDecl, "BaseField")->getType(); + return {{"synth_field", FieldType}}; + }; + // 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 *BaseFieldDecl = findValueDecl(ASTCtx, "BaseField"); + auto &B = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "B"); + auto &D1 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "D1"); + auto &D2 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, "D2"); + + EXPECT_NE(Env.getValue(*D1.getChild(*BaseFieldDecl)), + Env.getValue(*D2.getChild(*BaseFieldDecl))); + EXPECT_NE(Env.getValue(D1.getSyntheticField("synth_field")), + Env.getValue(D2.getSyntheticField("synth_field"))); + + copyRecord(D1, D2, Env, B.getType()); + + EXPECT_EQ(Env.getValue(*D1.getChild(*BaseFieldDecl)), + Env.getValue(*D2.getChild(*BaseFieldDecl))); + EXPECT_EQ(Env.getValue(D1.getSyntheticField("synth_field")), + Env.getValue(D2.getSyntheticField("synth_field"))); + }); +} + } // namespace } // namespace test } // namespace dataflow diff --git a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp index 96e759e73c15..d97e2b0c2425 100644 --- a/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/TransferTest.cpp @@ -1536,6 +1536,40 @@ TEST(TransferTest, BaseClassInitializer) { llvm::Succeeded()); } +TEST(TransferTest, BaseClassInitializerFromSiblingDerivedInstance) { + using ast_matchers::cxxConstructorDecl; + using ast_matchers::hasName; + using ast_matchers::ofClass; + + std::string Code = R"( + struct Base { + bool BaseField; + char UnmodeledField; + }; + + struct DerivedOne : public Base { + int DerivedOneField; + }; + + struct DerivedTwo : public Base { + int DerivedTwoField; + + DerivedTwo(const DerivedOne& d1) + : Base(d1), DerivedTwoField(d1.DerivedOneField) { + (void)BaseField; + } + }; + )"; + runDataflow( + Code, + [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, + ASTContext &ASTCtx) { + // Regression test only; we used to crash when transferring the base + // class initializer from the DerivedToBase-cast `d1`. + }, + LangStandard::lang_cxx17, /*ApplyBuiltinTransfer=*/true, "DerivedTwo"); +} + TEST(TransferTest, FieldsDontHaveValuesInConstructor) { // In a constructor, unlike in regular member functions, we don't want fields // to be pre-initialized with values, because doing so is the job of the diff --git a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp index 9fb7bebdbe41..d1dd4ff3ea33 100644 --- a/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp +++ b/clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp @@ -693,6 +693,80 @@ TEST_F(NoreturnDestructorTest, ConditionalOperatorNestedBranchReturns) { // FIXME: Called functions at point `p` should contain only "foo". } +class AnalyzerNoreturnTest : public Test { +protected: + template <typename Matcher> + void runDataflow(llvm::StringRef Code, Matcher Expectations) { + tooling::FileContentMappings FilesContents; + FilesContents.push_back( + std::make_pair<std::string, std::string>("noreturn_test_defs.h", R"( + void assertionHandler() __attribute__((analyzer_noreturn)); + + void trap() {} + )")); + + ASSERT_THAT_ERROR( + test::checkDataflow<FunctionCallAnalysis>( + AnalysisInputs<FunctionCallAnalysis>( + Code, ast_matchers::hasName("target"), + [](ASTContext &C, Environment &) { + return FunctionCallAnalysis(C); + }) + .withASTBuildArgs({"-fsyntax-only", "-std=c++17"}) + .withASTBuildVirtualMappedFiles(std::move(FilesContents)), + /*VerifyResults=*/ + [&Expectations]( + const llvm::StringMap< + DataflowAnalysisState<FunctionCallLattice>> &Results, + const AnalysisOutputs &) { + EXPECT_THAT(Results, Expectations); + }), + llvm::Succeeded()); + } +}; + +TEST_F(AnalyzerNoreturnTest, Breathing) { + std::string Code = R"( + #include "noreturn_test_defs.h" + + void target() { + trap(); + // [[p]] + } + )"; + runDataflow(Code, UnorderedElementsAre(IsStringMapEntry( + "p", HoldsFunctionCallLattice(HasCalledFunctions( + UnorderedElementsAre("trap")))))); +} + +TEST_F(AnalyzerNoreturnTest, DirectNoReturnCall) { + std::string Code = R"( + #include "noreturn_test_defs.h" + + void target() { + assertionHandler(); + trap(); + // [[p]] + } + )"; + runDataflow(Code, IsEmpty()); +} + +TEST_F(AnalyzerNoreturnTest, CanonicalDeclCallCheck) { + std::string Code = R"( + #include "noreturn_test_defs.h" + + extern void assertionHandler(); + + void target() { + assertionHandler(); + trap(); + // [[p]] + } + )"; + runDataflow(Code, IsEmpty()); +} + // Models an analysis that uses flow conditions. class SpecialBoolAnalysis final : public DataflowAnalysis<SpecialBoolAnalysis, NoopLattice> { diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index c8d88b4ea227..13e5832d7005 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -11,6 +11,7 @@ #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Testing/TestAST.h" #include "llvm/ADT/StringMap.h" +#include "llvm/Testing/Support/Error.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include <optional> @@ -20,6 +21,7 @@ namespace clang::lifetimes::internal { namespace { using namespace ast_matchers; +using ::testing::SizeIs; using ::testing::UnorderedElementsAreArray; // A helper class to run the full lifetime analysis on a piece of code @@ -96,21 +98,18 @@ public: return OID; } - std::optional<LoanID> getLoanForVar(llvm::StringRef VarName) { + std::vector<LoanID> getLoansForVar(llvm::StringRef VarName) { auto *VD = findDecl<VarDecl>(VarName); - if (!VD) - return std::nullopt; + if (!VD) { + ADD_FAILURE() << "No VarDecl found for '" << VarName << "'"; + return {}; + } std::vector<LoanID> LID = Analysis.getLoanIDForVar(VD); if (LID.empty()) { ADD_FAILURE() << "Loan for '" << VarName << "' not found."; - return std::nullopt; - } - // TODO: Support retrieving more than one loans to a var. - if (LID.size() > 1) { - ADD_FAILURE() << "More than 1 loans found for '" << VarName; - return std::nullopt; + return {}; } - return LID[0]; + return LID; } std::optional<LoanSet> getLoansAtPoint(OriginID OID, @@ -121,13 +120,12 @@ public: return Analysis.getLoansAtPoint(OID, PP); } - std::optional<llvm::DenseSet<LoanID>> + std::optional<std::vector<LoanID>> getExpiredLoansAtPoint(llvm::StringRef Annotation) { ProgramPoint PP = Runner.getProgramPoint(Annotation); if (!PP) return std::nullopt; - auto Expired = Analysis.getExpiredLoansAtPoint(PP); - return llvm::DenseSet<LoanID>{Expired.begin(), Expired.end()}; + return Analysis.getExpiredLoansAtPoint(PP); } private: @@ -197,12 +195,13 @@ MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") { std::vector<LoanID> ExpectedLoans; for (const auto &LoanVar : LoanVars) { - std::optional<LoanID> ExpectedLIDOpt = Info.Helper.getLoanForVar(LoanVar); - if (!ExpectedLIDOpt) { + std::vector<LoanID> ExpectedLIDs = Info.Helper.getLoansForVar(LoanVar); + if (ExpectedLIDs.empty()) { *result_listener << "could not find loan for var '" << LoanVar << "'"; return false; } - ExpectedLoans.push_back(*ExpectedLIDOpt); + ExpectedLoans.insert(ExpectedLoans.end(), ExpectedLIDs.begin(), + ExpectedLIDs.end()); } return ExplainMatchResult(UnorderedElementsAreArray(ExpectedLoans), @@ -221,17 +220,17 @@ MATCHER_P(AreExpiredAt, Annotation, "") { << Annotation << "'"; return false; } - std::vector<LoanID> ActualExpiredLoans(ActualExpiredSetOpt->begin(), - ActualExpiredSetOpt->end()); + std::vector<LoanID> ActualExpiredLoans = *ActualExpiredSetOpt; std::vector<LoanID> ExpectedExpiredLoans; for (const auto &VarName : Info.LoanVars) { - auto LoanIDOpt = Helper.getLoanForVar(VarName); - if (!LoanIDOpt) { + auto LoanIDs = Helper.getLoansForVar(VarName); + if (LoanIDs.empty()) { *result_listener << "could not find a loan for variable '" << VarName << "'"; return false; } - ExpectedExpiredLoans.push_back(*LoanIDOpt); + ExpectedExpiredLoans.insert(ExpectedExpiredLoans.end(), LoanIDs.begin(), + LoanIDs.end()); } return ExplainMatchResult(UnorderedElementsAreArray(ExpectedExpiredLoans), ActualExpiredLoans, result_listener); @@ -730,5 +729,17 @@ TEST_F(LifetimeAnalysisTest, ReassignedPointerThenOriginalExpires) { EXPECT_THAT(LoansTo({"s1", "s2"}), AreExpiredAt("p_after_s1_expires")); } +TEST_F(LifetimeAnalysisTest, NoDuplicateLoansForImplicitCastToConst) { + SetupTest(R"( + void target() { + MyObj a; + const MyObj* p = &a; + const MyObj* q = &a; + POINT(at_end); + } + )"); + EXPECT_THAT(Helper->getLoansForVar("a"), SizeIs(2)); +} + } // anonymous namespace } // namespace clang::lifetimes::internal |
