
Implement use-after-free detection in the lifetime safety analysis with two warning levels. - Added a `LifetimeSafetyReporter` interface for reporting lifetime safety issues - Created two warning levels: - Definite errors (reported with `-Wexperimental-lifetime-safety-permissive`) - Potential errors (reported with `-Wexperimental-lifetime-safety-strict`) - Implemented a `LifetimeChecker` class that analyzes loan propagation and expired loans to detect use-after-free issues. - Added tracking of use sites through a new `UseFact` class. - Enhanced the `ExpireFact` to track the expressions where objects are destroyed. - Added test cases for both definite and potential use-after-free scenarios. The implementation now tracks pointer uses and can determine when a pointer is dereferenced after its loan has been expired, with appropriate diagnostics. The two warning levels provide flexibility - definite errors for high-confidence issues and potential errors for cases that depend on control flow.
735 lines
21 KiB
C++
735 lines
21 KiB
C++
//===- LifetimeSafetyTest.cpp - Lifetime Safety Tests -*---------- C++-*-===//
|
|
//
|
|
// 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/Analyses/LifetimeSafety.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
|
#include "clang/Testing/TestAST.h"
|
|
#include "llvm/ADT/StringMap.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include <optional>
|
|
#include <vector>
|
|
|
|
namespace clang::lifetimes::internal {
|
|
namespace {
|
|
|
|
using namespace ast_matchers;
|
|
using ::testing::UnorderedElementsAreArray;
|
|
|
|
// A helper class to run the full lifetime analysis on a piece of code
|
|
// and provide an interface for querying the results.
|
|
class LifetimeTestRunner {
|
|
public:
|
|
LifetimeTestRunner(llvm::StringRef Code) {
|
|
std::string FullCode = R"(
|
|
#define POINT(name) void("__lifetime_test_point_" #name)
|
|
struct MyObj { ~MyObj() {} int i; };
|
|
)";
|
|
FullCode += Code.str();
|
|
|
|
Inputs = TestInputs(FullCode);
|
|
Inputs.Language = TestLanguage::Lang_CXX20;
|
|
AST = std::make_unique<clang::TestAST>(Inputs);
|
|
ASTCtx = &AST->context();
|
|
|
|
// Find the target function using AST matchers.
|
|
auto MatchResult =
|
|
match(functionDecl(hasName("target")).bind("target"), *ASTCtx);
|
|
auto *FD = selectFirst<FunctionDecl>("target", MatchResult);
|
|
if (!FD) {
|
|
ADD_FAILURE() << "Test case must have a function named 'target'";
|
|
return;
|
|
}
|
|
AnalysisCtx = std::make_unique<AnalysisDeclContext>(nullptr, FD);
|
|
CFG::BuildOptions &BuildOptions = AnalysisCtx->getCFGBuildOptions();
|
|
BuildOptions.setAllAlwaysAdd();
|
|
BuildOptions.AddImplicitDtors = true;
|
|
BuildOptions.AddTemporaryDtors = true;
|
|
|
|
// Run the main analysis.
|
|
Analysis = std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx, nullptr);
|
|
Analysis->run();
|
|
|
|
AnnotationToPointMap = Analysis->getTestPoints();
|
|
}
|
|
|
|
LifetimeSafetyAnalysis &getAnalysis() { return *Analysis; }
|
|
ASTContext &getASTContext() { return *ASTCtx; }
|
|
|
|
ProgramPoint getProgramPoint(llvm::StringRef Annotation) {
|
|
auto It = AnnotationToPointMap.find(Annotation);
|
|
if (It == AnnotationToPointMap.end()) {
|
|
ADD_FAILURE() << "Annotation '" << Annotation << "' not found.";
|
|
return nullptr;
|
|
}
|
|
return It->second;
|
|
}
|
|
|
|
private:
|
|
TestInputs Inputs;
|
|
std::unique_ptr<TestAST> AST;
|
|
ASTContext *ASTCtx = nullptr;
|
|
std::unique_ptr<AnalysisDeclContext> AnalysisCtx;
|
|
std::unique_ptr<LifetimeSafetyAnalysis> Analysis;
|
|
llvm::StringMap<ProgramPoint> AnnotationToPointMap;
|
|
};
|
|
|
|
// A convenience wrapper that uses the LifetimeSafetyAnalysis public API.
|
|
class LifetimeTestHelper {
|
|
public:
|
|
LifetimeTestHelper(LifetimeTestRunner &Runner)
|
|
: Runner(Runner), Analysis(Runner.getAnalysis()) {}
|
|
|
|
std::optional<OriginID> getOriginForDecl(llvm::StringRef VarName) {
|
|
auto *VD = findDecl<ValueDecl>(VarName);
|
|
if (!VD)
|
|
return std::nullopt;
|
|
auto OID = Analysis.getOriginIDForDecl(VD);
|
|
if (!OID)
|
|
ADD_FAILURE() << "Origin for '" << VarName << "' not found.";
|
|
return OID;
|
|
}
|
|
|
|
std::optional<LoanID> getLoanForVar(llvm::StringRef VarName) {
|
|
auto *VD = findDecl<VarDecl>(VarName);
|
|
if (!VD)
|
|
return std::nullopt;
|
|
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 LID[0];
|
|
}
|
|
|
|
std::optional<LoanSet> getLoansAtPoint(OriginID OID,
|
|
llvm::StringRef Annotation) {
|
|
ProgramPoint PP = Runner.getProgramPoint(Annotation);
|
|
if (!PP)
|
|
return std::nullopt;
|
|
return Analysis.getLoansAtPoint(OID, PP);
|
|
}
|
|
|
|
std::optional<llvm::DenseSet<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()};
|
|
}
|
|
|
|
private:
|
|
template <typename DeclT> DeclT *findDecl(llvm::StringRef Name) {
|
|
auto &Ctx = Runner.getASTContext();
|
|
auto Results = match(valueDecl(hasName(Name)).bind("v"), Ctx);
|
|
if (Results.empty()) {
|
|
ADD_FAILURE() << "Declaration '" << Name << "' not found in AST.";
|
|
return nullptr;
|
|
}
|
|
return const_cast<DeclT *>(selectFirst<DeclT>("v", Results));
|
|
}
|
|
|
|
LifetimeTestRunner &Runner;
|
|
LifetimeSafetyAnalysis &Analysis;
|
|
};
|
|
|
|
// ========================================================================= //
|
|
// GTest Matchers & Fixture
|
|
// ========================================================================= //
|
|
|
|
// A helper class to represent a set of loans, identified by variable names.
|
|
class LoanSetInfo {
|
|
public:
|
|
LoanSetInfo(const std::vector<std::string> &Vars, LifetimeTestHelper &H)
|
|
: LoanVars(Vars), Helper(H) {}
|
|
std::vector<std::string> LoanVars;
|
|
LifetimeTestHelper &Helper;
|
|
};
|
|
|
|
// It holds the name of the origin variable and a reference to the helper.
|
|
class OriginInfo {
|
|
public:
|
|
OriginInfo(llvm::StringRef OriginVar, LifetimeTestHelper &Helper)
|
|
: OriginVar(OriginVar), Helper(Helper) {}
|
|
llvm::StringRef OriginVar;
|
|
LifetimeTestHelper &Helper;
|
|
};
|
|
|
|
/// Matcher to verify the set of loans held by an origin at a specific
|
|
/// program point.
|
|
///
|
|
/// This matcher is intended to be used with an \c OriginInfo object.
|
|
///
|
|
/// \param LoanVars A vector of strings, where each string is the name of a
|
|
/// variable expected to be the source of a loan.
|
|
/// \param Annotation A string identifying the program point (created with
|
|
/// POINT()) where the check should be performed.
|
|
MATCHER_P2(HasLoansToImpl, LoanVars, Annotation, "") {
|
|
const OriginInfo &Info = arg;
|
|
std::optional<OriginID> OIDOpt = Info.Helper.getOriginForDecl(Info.OriginVar);
|
|
if (!OIDOpt) {
|
|
*result_listener << "could not find origin for '" << Info.OriginVar.str()
|
|
<< "'";
|
|
return false;
|
|
}
|
|
|
|
std::optional<LoanSet> ActualLoansSetOpt =
|
|
Info.Helper.getLoansAtPoint(*OIDOpt, Annotation);
|
|
if (!ActualLoansSetOpt) {
|
|
*result_listener << "could not get a valid loan set at point '"
|
|
<< Annotation << "'";
|
|
return false;
|
|
}
|
|
std::vector<LoanID> ActualLoans(ActualLoansSetOpt->begin(),
|
|
ActualLoansSetOpt->end());
|
|
|
|
std::vector<LoanID> ExpectedLoans;
|
|
for (const auto &LoanVar : LoanVars) {
|
|
std::optional<LoanID> ExpectedLIDOpt = Info.Helper.getLoanForVar(LoanVar);
|
|
if (!ExpectedLIDOpt) {
|
|
*result_listener << "could not find loan for var '" << LoanVar << "'";
|
|
return false;
|
|
}
|
|
ExpectedLoans.push_back(*ExpectedLIDOpt);
|
|
}
|
|
|
|
return ExplainMatchResult(UnorderedElementsAreArray(ExpectedLoans),
|
|
ActualLoans, result_listener);
|
|
}
|
|
|
|
/// Matcher to verify that the complete set of expired loans at a program point
|
|
/// matches the expected loan set.
|
|
MATCHER_P(AreExpiredAt, Annotation, "") {
|
|
const LoanSetInfo &Info = arg;
|
|
auto &Helper = Info.Helper;
|
|
|
|
auto ActualExpiredSetOpt = Helper.getExpiredLoansAtPoint(Annotation);
|
|
if (!ActualExpiredSetOpt) {
|
|
*result_listener << "could not get a valid expired loan set at point '"
|
|
<< Annotation << "'";
|
|
return false;
|
|
}
|
|
std::vector<LoanID> ActualExpiredLoans(ActualExpiredSetOpt->begin(),
|
|
ActualExpiredSetOpt->end());
|
|
std::vector<LoanID> ExpectedExpiredLoans;
|
|
for (const auto &VarName : Info.LoanVars) {
|
|
auto LoanIDOpt = Helper.getLoanForVar(VarName);
|
|
if (!LoanIDOpt) {
|
|
*result_listener << "could not find a loan for variable '" << VarName
|
|
<< "'";
|
|
return false;
|
|
}
|
|
ExpectedExpiredLoans.push_back(*LoanIDOpt);
|
|
}
|
|
return ExplainMatchResult(UnorderedElementsAreArray(ExpectedExpiredLoans),
|
|
ActualExpiredLoans, result_listener);
|
|
}
|
|
|
|
// Base test fixture to manage the runner and helper.
|
|
class LifetimeAnalysisTest : public ::testing::Test {
|
|
protected:
|
|
void SetupTest(llvm::StringRef Code) {
|
|
Runner = std::make_unique<LifetimeTestRunner>(Code);
|
|
Helper = std::make_unique<LifetimeTestHelper>(*Runner);
|
|
}
|
|
|
|
OriginInfo Origin(llvm::StringRef OriginVar) {
|
|
return OriginInfo(OriginVar, *Helper);
|
|
}
|
|
|
|
/// Factory function that hides the std::vector creation.
|
|
LoanSetInfo LoansTo(std::initializer_list<std::string> LoanVars) {
|
|
return LoanSetInfo({LoanVars}, *Helper);
|
|
}
|
|
|
|
/// A convenience helper for asserting that no loans are expired.
|
|
LoanSetInfo NoLoans() { return LoansTo({}); }
|
|
|
|
// Factory function that hides the std::vector creation.
|
|
auto HasLoansTo(std::initializer_list<std::string> LoanVars,
|
|
const char *Annotation) {
|
|
return HasLoansToImpl(std::vector<std::string>(LoanVars), Annotation);
|
|
}
|
|
|
|
std::unique_ptr<LifetimeTestRunner> Runner;
|
|
std::unique_ptr<LifetimeTestHelper> Helper;
|
|
};
|
|
|
|
// ========================================================================= //
|
|
// TESTS
|
|
// ========================================================================= //
|
|
|
|
TEST_F(LifetimeAnalysisTest, SimpleLoanAndOrigin) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
int x;
|
|
int* p = &x;
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"x"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, OverwriteOrigin) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj s1, s2;
|
|
|
|
MyObj* p = &s1;
|
|
POINT(after_s1);
|
|
|
|
p = &s2;
|
|
POINT(after_s2);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_s1"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "after_s2"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, ConditionalLoan) {
|
|
SetupTest(R"(
|
|
void target(bool cond) {
|
|
int a, b;
|
|
int *p = nullptr;
|
|
if (cond) {
|
|
p = &a;
|
|
POINT(after_then);
|
|
} else {
|
|
p = &b;
|
|
POINT(after_else);
|
|
}
|
|
POINT(after_if);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"a"}, "after_then"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"b"}, "after_else"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"a", "b"}, "after_if"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, PointerChain) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj y;
|
|
MyObj* ptr1 = &y;
|
|
POINT(p1);
|
|
|
|
MyObj* ptr2 = ptr1;
|
|
POINT(p2);
|
|
|
|
ptr2 = ptr1;
|
|
POINT(p3);
|
|
|
|
ptr2 = ptr2; // Self assignment
|
|
POINT(p4);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("ptr1"), HasLoansTo({"y"}, "p1"));
|
|
EXPECT_THAT(Origin("ptr2"), HasLoansTo({"y"}, "p2"));
|
|
EXPECT_THAT(Origin("ptr2"), HasLoansTo({"y"}, "p3"));
|
|
EXPECT_THAT(Origin("ptr2"), HasLoansTo({"y"}, "p4"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, ReassignToNull) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj s1;
|
|
MyObj* p = &s1;
|
|
POINT(before_null);
|
|
p = nullptr;
|
|
POINT(after_null);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "before_null"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({}, "after_null"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, ReassignInIf) {
|
|
SetupTest(R"(
|
|
void target(bool condition) {
|
|
MyObj s1, s2;
|
|
MyObj* p = &s1;
|
|
POINT(before_if);
|
|
if (condition) {
|
|
p = &s2;
|
|
POINT(after_reassign);
|
|
}
|
|
POINT(after_if);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "before_if"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "after_reassign"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"s1", "s2"}, "after_if"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, AssignInSwitch) {
|
|
SetupTest(R"(
|
|
void target(int mode) {
|
|
MyObj s1, s2, s3;
|
|
MyObj* p = nullptr;
|
|
switch (mode) {
|
|
case 1:
|
|
p = &s1;
|
|
POINT(case1);
|
|
break;
|
|
case 2:
|
|
p = &s2;
|
|
POINT(case2);
|
|
break;
|
|
default:
|
|
p = &s3;
|
|
POINT(case3);
|
|
break;
|
|
}
|
|
POINT(after_switch);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "case1"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "case2"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"s3"}, "case3"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"s1", "s2", "s3"}, "after_switch"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LoanInLoop) {
|
|
SetupTest(R"(
|
|
void target(bool condition) {
|
|
MyObj* p = nullptr;
|
|
while (condition) {
|
|
POINT(start_loop);
|
|
MyObj inner;
|
|
p = &inner;
|
|
POINT(end_loop);
|
|
}
|
|
POINT(after_loop);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "start_loop"));
|
|
EXPECT_THAT(LoansTo({"inner"}), AreExpiredAt("start_loop"));
|
|
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "end_loop"));
|
|
EXPECT_THAT(NoLoans(), AreExpiredAt("end_loop"));
|
|
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "after_loop"));
|
|
EXPECT_THAT(LoansTo({"inner"}), AreExpiredAt("after_loop"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LoopWithBreak) {
|
|
SetupTest(R"(
|
|
void target(int count) {
|
|
MyObj s1;
|
|
MyObj s2;
|
|
MyObj* p = &s1;
|
|
POINT(before_loop);
|
|
for (int i = 0; i < count; ++i) {
|
|
if (i == 5) {
|
|
p = &s2;
|
|
POINT(inside_if);
|
|
break;
|
|
}
|
|
POINT(after_if);
|
|
}
|
|
POINT(after_loop);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "before_loop"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"s2"}, "inside_if"));
|
|
// At the join point after if, s2 cannot make it to p without the if.
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"s1"}, "after_if"));
|
|
// At the join point after the loop, p could hold a loan to s1 (if the loop
|
|
// completed normally) or to s2 (if the loop was broken).
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"s1", "s2"}, "after_loop"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, PointersInACycle) {
|
|
SetupTest(R"(
|
|
void target(bool condition) {
|
|
MyObj v1, v2, v3;
|
|
MyObj *p1 = &v1, *p2 = &v2, *p3 = &v3;
|
|
|
|
POINT(before_while);
|
|
while (condition) {
|
|
MyObj* temp = p1;
|
|
p1 = p2;
|
|
p2 = p3;
|
|
p3 = temp;
|
|
}
|
|
POINT(after_loop);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("p1"), HasLoansTo({"v1"}, "before_while"));
|
|
EXPECT_THAT(Origin("p2"), HasLoansTo({"v2"}, "before_while"));
|
|
EXPECT_THAT(Origin("p3"), HasLoansTo({"v3"}, "before_while"));
|
|
|
|
// At the fixed point after the loop, all pointers could point to any of
|
|
// the three variables.
|
|
EXPECT_THAT(Origin("p1"), HasLoansTo({"v1", "v2", "v3"}, "after_loop"));
|
|
EXPECT_THAT(Origin("p2"), HasLoansTo({"v1", "v2", "v3"}, "after_loop"));
|
|
EXPECT_THAT(Origin("p3"), HasLoansTo({"v1", "v2", "v3"}, "after_loop"));
|
|
EXPECT_THAT(Origin("temp"), HasLoansTo({"v1", "v2", "v3"}, "after_loop"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, PointersAndExpirationInACycle) {
|
|
SetupTest(R"(
|
|
void target(bool condition) {
|
|
MyObj v1, v2;
|
|
MyObj *p1 = &v1, *p2 = &v2;
|
|
|
|
POINT(before_while);
|
|
while (condition) {
|
|
POINT(in_loop_before_temp);
|
|
MyObj temp;
|
|
p1 = &temp;
|
|
POINT(in_loop_after_temp);
|
|
|
|
MyObj* q = p1;
|
|
p1 = p2;
|
|
p2 = q;
|
|
}
|
|
POINT(after_loop);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("p1"), HasLoansTo({"v1"}, "before_while"));
|
|
EXPECT_THAT(Origin("p2"), HasLoansTo({"v2"}, "before_while"));
|
|
EXPECT_THAT(NoLoans(), AreExpiredAt("before_while"));
|
|
|
|
EXPECT_THAT(Origin("p1"),
|
|
HasLoansTo({"v1", "v2", "temp"}, "in_loop_before_temp"));
|
|
EXPECT_THAT(Origin("p2"), HasLoansTo({"v2", "temp"}, "in_loop_before_temp"));
|
|
EXPECT_THAT(LoansTo({"temp"}), AreExpiredAt("in_loop_before_temp"));
|
|
|
|
EXPECT_THAT(Origin("p1"), HasLoansTo({"temp"}, "in_loop_after_temp"));
|
|
EXPECT_THAT(Origin("p2"), HasLoansTo({"v2", "temp"}, "in_loop_after_temp"));
|
|
EXPECT_THAT(NoLoans(), AreExpiredAt("in_loop_after_temp"));
|
|
|
|
EXPECT_THAT(Origin("p1"), HasLoansTo({"v1", "v2", "temp"}, "after_loop"));
|
|
EXPECT_THAT(Origin("p2"), HasLoansTo({"v2", "temp"}, "after_loop"));
|
|
EXPECT_THAT(LoansTo({"temp"}), AreExpiredAt("after_loop"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, InfiniteLoopPrunesEdges) {
|
|
SetupTest(R"(
|
|
void target(MyObj out) {
|
|
MyObj *p = &out;
|
|
POINT(before_loop);
|
|
|
|
for (;;) {
|
|
POINT(begin);
|
|
MyObj in;
|
|
p = ∈
|
|
POINT(end);
|
|
}
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"out"}, "before_loop"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"in", "out"}, "begin"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"in"}, "end"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, NestedScopes) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj* p = nullptr;
|
|
{
|
|
MyObj outer;
|
|
p = &outer;
|
|
POINT(before_inner_scope);
|
|
{
|
|
MyObj inner;
|
|
p = &inner;
|
|
POINT(inside_inner_scope);
|
|
} // inner expires
|
|
POINT(after_inner_scope);
|
|
} // outer expires
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"outer"}, "before_inner_scope"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "inside_inner_scope"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"inner"}, "after_inner_scope"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, SimpleExpiry) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj* p = nullptr;
|
|
{
|
|
MyObj s;
|
|
p = &s;
|
|
POINT(before_expiry);
|
|
} // s goes out of scope here
|
|
POINT(after_expiry);
|
|
}
|
|
)");
|
|
EXPECT_THAT(NoLoans(), AreExpiredAt("before_expiry"));
|
|
EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("after_expiry"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, NestedExpiry) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj s1;
|
|
MyObj* p = &s1;
|
|
POINT(before_inner);
|
|
{
|
|
MyObj s2;
|
|
p = &s2;
|
|
POINT(in_inner);
|
|
} // s2 expires
|
|
POINT(after_inner);
|
|
}
|
|
)");
|
|
EXPECT_THAT(NoLoans(), AreExpiredAt("before_inner"));
|
|
EXPECT_THAT(NoLoans(), AreExpiredAt("in_inner"));
|
|
EXPECT_THAT(LoansTo({"s2"}), AreExpiredAt("after_inner"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, ConditionalExpiry) {
|
|
SetupTest(R"(
|
|
void target(bool cond) {
|
|
MyObj s1;
|
|
MyObj* p = &s1;
|
|
POINT(before_if);
|
|
if (cond) {
|
|
MyObj s2;
|
|
p = &s2;
|
|
POINT(then_block);
|
|
} // s2 expires here
|
|
POINT(after_if);
|
|
}
|
|
)");
|
|
EXPECT_THAT(NoLoans(), AreExpiredAt("before_if"));
|
|
EXPECT_THAT(NoLoans(), AreExpiredAt("then_block"));
|
|
EXPECT_THAT(LoansTo({"s2"}), AreExpiredAt("after_if"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LoopExpiry) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj *p = nullptr;
|
|
for (int i = 0; i < 2; ++i) {
|
|
POINT(start_loop);
|
|
MyObj s;
|
|
p = &s;
|
|
POINT(end_loop);
|
|
} // s expires here on each iteration
|
|
POINT(after_loop);
|
|
}
|
|
)");
|
|
EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("start_loop"));
|
|
EXPECT_THAT(NoLoans(), AreExpiredAt("end_loop"));
|
|
EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("after_loop"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, MultipleExpiredLoans) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj *p1, *p2, *p3;
|
|
{
|
|
MyObj s1;
|
|
p1 = &s1;
|
|
POINT(p1);
|
|
} // s1 expires
|
|
POINT(p2);
|
|
{
|
|
MyObj s2;
|
|
p2 = &s2;
|
|
MyObj s3;
|
|
p3 = &s3;
|
|
POINT(p3);
|
|
} // s2, s3 expire
|
|
POINT(p4);
|
|
}
|
|
)");
|
|
EXPECT_THAT(NoLoans(), AreExpiredAt("p1"));
|
|
EXPECT_THAT(LoansTo({"s1"}), AreExpiredAt("p2"));
|
|
EXPECT_THAT(LoansTo({"s1"}), AreExpiredAt("p3"));
|
|
EXPECT_THAT(LoansTo({"s1", "s2", "s3"}), AreExpiredAt("p4"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, GotoJumpsOutOfScope) {
|
|
SetupTest(R"(
|
|
void target(bool cond) {
|
|
MyObj *p = nullptr;
|
|
{
|
|
MyObj s;
|
|
p = &s;
|
|
POINT(before_goto);
|
|
if (cond) {
|
|
goto end;
|
|
}
|
|
} // `s` expires here on the path that doesn't jump
|
|
POINT(after_scope);
|
|
end:
|
|
POINT(after_goto);
|
|
}
|
|
)");
|
|
EXPECT_THAT(NoLoans(), AreExpiredAt("before_goto"));
|
|
EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("after_scope"));
|
|
EXPECT_THAT(LoansTo({"s"}), AreExpiredAt("after_goto"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, ContinueInLoop) {
|
|
SetupTest(R"(
|
|
void target(int count) {
|
|
MyObj *p = nullptr;
|
|
MyObj outer;
|
|
p = &outer;
|
|
POINT(before_loop);
|
|
|
|
for (int i = 0; i < count; ++i) {
|
|
if (i % 2 == 0) {
|
|
MyObj s_even;
|
|
p = &s_even;
|
|
POINT(in_even_iter);
|
|
continue;
|
|
}
|
|
MyObj s_odd;
|
|
p = &s_odd;
|
|
POINT(in_odd_iter);
|
|
}
|
|
POINT(after_loop);
|
|
}
|
|
)");
|
|
EXPECT_THAT(NoLoans(), AreExpiredAt("before_loop"));
|
|
EXPECT_THAT(LoansTo({"s_odd"}), AreExpiredAt("in_even_iter"));
|
|
EXPECT_THAT(LoansTo({"s_even"}), AreExpiredAt("in_odd_iter"));
|
|
EXPECT_THAT(LoansTo({"s_even", "s_odd"}), AreExpiredAt("after_loop"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, ReassignedPointerThenOriginalExpires) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj* p = nullptr;
|
|
{
|
|
MyObj s1;
|
|
p = &s1;
|
|
POINT(p_has_s1);
|
|
{
|
|
MyObj s2;
|
|
p = &s2;
|
|
POINT(p_has_s2);
|
|
}
|
|
POINT(p_after_s2_expires);
|
|
} // s1 expires here.
|
|
POINT(p_after_s1_expires);
|
|
}
|
|
)");
|
|
EXPECT_THAT(NoLoans(), AreExpiredAt("p_has_s1"));
|
|
EXPECT_THAT(NoLoans(), AreExpiredAt("p_has_s2"));
|
|
EXPECT_THAT(LoansTo({"s2"}), AreExpiredAt("p_after_s2_expires"));
|
|
EXPECT_THAT(LoansTo({"s1", "s2"}), AreExpiredAt("p_after_s1_expires"));
|
|
}
|
|
|
|
} // anonymous namespace
|
|
} // namespace clang::lifetimes::internal
|