summaryrefslogtreecommitdiff
path: root/clang/unittests/Analysis
diff options
context:
space:
mode:
Diffstat (limited to 'clang/unittests/Analysis')
-rw-r--r--clang/unittests/Analysis/FlowSensitive/RecordOpsTest.cpp70
-rw-r--r--clang/unittests/Analysis/FlowSensitive/TransferTest.cpp34
-rw-r--r--clang/unittests/Analysis/FlowSensitive/TypeErasedDataflowAnalysisTest.cpp74
-rw-r--r--clang/unittests/Analysis/LifetimeSafetyTest.cpp53
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