CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
2078 lines
55 KiB
C++
2078 lines
55 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/LifetimeSafety.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
|
#include "clang/Analysis/Analyses/LifetimeSafety/Loans.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::Contains;
|
|
using ::testing::Not;
|
|
using ::testing::SizeIs;
|
|
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 [[gsl::Owner]] MyObj { ~MyObj() {} int i; };
|
|
|
|
struct [[gsl::Pointer()]] View {
|
|
View(const MyObj&);
|
|
View();
|
|
};
|
|
)";
|
|
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;
|
|
BuildOptions.AddLifetime = true;
|
|
|
|
// Run the main analysis.
|
|
LifetimeSafetyOpts LSOpts;
|
|
LSOpts.MaxCFGBlocks = 0;
|
|
Analysis =
|
|
std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx, nullptr, LSOpts);
|
|
Analysis->run();
|
|
|
|
AnnotationToPointMap = Analysis->getFactManager().getTestPoints();
|
|
}
|
|
|
|
LifetimeSafetyAnalysis &getAnalysis() { return *Analysis; }
|
|
ASTContext &getASTContext() { return *ASTCtx; }
|
|
AnalysisDeclContext &getAnalysisContext() { return *AnalysisCtx; }
|
|
|
|
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;
|
|
// This assumes the OriginManager's `get` can find an existing origin.
|
|
// We might need a `find` method on OriginManager to avoid `getOrCreate`
|
|
// logic in a const-query context if that becomes an issue.
|
|
OriginList *List =
|
|
const_cast<OriginManager &>(Analysis.getFactManager().getOriginMgr())
|
|
.getOrCreateList(VD);
|
|
if (!List) {
|
|
ADD_FAILURE() << "No origin list found for Var '" << VarName << "'";
|
|
return std::nullopt;
|
|
}
|
|
return List->getOuterOriginID();
|
|
}
|
|
|
|
std::vector<LoanID> getLoansForVar(llvm::StringRef VarName) {
|
|
auto *VD = findDecl<VarDecl>(VarName);
|
|
if (!VD) {
|
|
ADD_FAILURE() << "Failed to find VarDecl for '" << VarName << "'";
|
|
return {};
|
|
}
|
|
std::vector<LoanID> LID;
|
|
for (const Loan *L : Analysis.getFactManager().getLoanMgr().getLoans())
|
|
if (L->getAccessPath().getAsValueDecl() == VD)
|
|
LID.push_back(L->getID());
|
|
if (LID.empty()) {
|
|
ADD_FAILURE() << "Loan for '" << VarName << "' not found.";
|
|
return {};
|
|
}
|
|
return LID;
|
|
}
|
|
|
|
bool isLoanToATemporary(LoanID LID) {
|
|
return Analysis.getFactManager()
|
|
.getLoanMgr()
|
|
.getLoan(LID)
|
|
->getAccessPath()
|
|
.getAsMaterializeTemporaryExpr() != nullptr;
|
|
}
|
|
|
|
// Gets the set of loans that are live at the given program point. A loan is
|
|
// considered live at point P if there is a live origin which contains this
|
|
// loan.
|
|
std::optional<LoanSet> getLiveLoansAtPoint(ProgramPoint P) const {
|
|
const auto &LiveOriginsAnalysis = Runner.getAnalysis().getLiveOrigins();
|
|
const auto &LoanPropagation = Runner.getAnalysis().getLoanPropagation();
|
|
|
|
LivenessMap LiveOriginsMap = LiveOriginsAnalysis.getLiveOriginsAt(P);
|
|
|
|
LoanSet::Factory F;
|
|
LoanSet Result = F.getEmptySet();
|
|
|
|
for (const auto &[OID, LI] : LiveOriginsMap) {
|
|
LoanSet Loans = LoanPropagation.getLoans(OID, P);
|
|
Result = clang::lifetimes::internal::utils::join(Result, Loans, F);
|
|
}
|
|
|
|
if (Result.isEmpty())
|
|
return std::nullopt;
|
|
|
|
return Result;
|
|
}
|
|
|
|
const ExpireFact *
|
|
getExpireFactFromAllFacts(const llvm::ArrayRef<const Fact *> &FactsInBlock,
|
|
const LoanID &loanID) {
|
|
const Loan *L = Analysis.getFactManager().getLoanMgr().getLoan(loanID);
|
|
for (const Fact *F : FactsInBlock) {
|
|
if (auto const *CurrentEF = F->getAs<ExpireFact>())
|
|
if (CurrentEF->getAccessPath() == L->getAccessPath())
|
|
return CurrentEF;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::optional<LoanSet> getLoansAtPoint(OriginID OID,
|
|
llvm::StringRef Annotation) {
|
|
ProgramPoint PP = Runner.getProgramPoint(Annotation);
|
|
if (!PP)
|
|
return std::nullopt;
|
|
return Analysis.getLoanPropagation().getLoans(OID, PP);
|
|
}
|
|
|
|
std::optional<std::vector<std::pair<OriginID, LivenessKind>>>
|
|
getLiveOriginsAtPoint(llvm::StringRef Annotation) {
|
|
ProgramPoint PP = Runner.getProgramPoint(Annotation);
|
|
if (!PP)
|
|
return std::nullopt;
|
|
std::vector<std::pair<OriginID, LivenessKind>> Result;
|
|
for (auto &[OID, Info] : Analysis.getLiveOrigins().getLiveOriginsAt(PP))
|
|
Result.push_back({OID, Info.Kind});
|
|
return Result;
|
|
}
|
|
|
|
ProgramPoint getProgramPoint(llvm::StringRef Annotation) {
|
|
return Runner.getProgramPoint(Annotation);
|
|
}
|
|
|
|
llvm::ArrayRef<const Fact *> getBlockContaining(ProgramPoint P) {
|
|
return Runner.getAnalysis().getFactManager().getBlockContaining(P);
|
|
}
|
|
|
|
llvm::SmallVector<OriginID>
|
|
buildOriginFlowChainInOneBlock(llvm::StringRef StartOriginVar,
|
|
llvm::StringRef EndLoanVar,
|
|
llvm::StringRef Annotation) {
|
|
std::optional<OriginID> StartOriginID = getOriginForDecl(StartOriginVar);
|
|
std::vector<LoanID> EndLoanIDs = getLoansForVar(EndLoanVar);
|
|
|
|
for (LoanID LID : EndLoanIDs) {
|
|
const llvm::SmallVector<OriginID> OriginFlowChain =
|
|
Runner.getAnalysis().getLoanPropagation().buildOriginFlowChain(
|
|
getProgramPoint(Annotation), *StartOriginID, LID);
|
|
if (!OriginFlowChain.empty())
|
|
return OriginFlowChain;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
private:
|
|
template <typename DeclT> DeclT *findDecl(llvm::StringRef Name) {
|
|
auto &Ctx = Runner.getASTContext();
|
|
const auto *TargetFunc = Runner.getAnalysisContext().getDecl();
|
|
auto Results =
|
|
match(valueDecl(hasName(Name),
|
|
hasAncestor(functionDecl(equalsNode(TargetFunc))))
|
|
.bind("v"),
|
|
Ctx);
|
|
if (Results.empty()) {
|
|
ADD_FAILURE() << "Declaration '" << Name << "' not found in AST.";
|
|
return nullptr;
|
|
}
|
|
if (Results.size() > 1) {
|
|
ADD_FAILURE() << "Multiple declarations found for '" << Name << "'";
|
|
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;
|
|
};
|
|
|
|
// A helper class to represent a set of origins, identified by variable names.
|
|
class OriginsInfo {
|
|
public:
|
|
OriginsInfo(const std::vector<std::string> &Vars, LifetimeTestHelper &H)
|
|
: OriginVars(Vars), Helper(H) {}
|
|
std::vector<std::string> OriginVars;
|
|
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::vector<LoanID> ExpectedLIDs = Info.Helper.getLoansForVar(LoanVar);
|
|
if (ExpectedLIDs.empty()) {
|
|
*result_listener << "could not find loan for var '" << LoanVar << "'";
|
|
return false;
|
|
}
|
|
ExpectedLoans.insert(ExpectedLoans.end(), ExpectedLIDs.begin(),
|
|
ExpectedLIDs.end());
|
|
}
|
|
std::sort(ExpectedLoans.begin(), ExpectedLoans.end());
|
|
std::sort(ActualLoans.begin(), ActualLoans.end());
|
|
if (ExpectedLoans != ActualLoans) {
|
|
*result_listener << "Expected: {";
|
|
for (const auto &LoanID : ExpectedLoans) {
|
|
*result_listener << LoanID.Value << ", ";
|
|
}
|
|
*result_listener << "} Actual: {";
|
|
for (const auto &LoanID : ActualLoans) {
|
|
*result_listener << LoanID.Value << ", ";
|
|
}
|
|
*result_listener << "}";
|
|
return false;
|
|
}
|
|
|
|
return ExplainMatchResult(UnorderedElementsAreArray(ExpectedLoans),
|
|
ActualLoans, result_listener);
|
|
}
|
|
|
|
enum class LivenessKindFilter { Maybe, Must, All };
|
|
|
|
/// Matcher to verify the complete set of live origins at a program point.
|
|
MATCHER_P2(AreLiveAtImpl, Annotation, ConfFilter, "") {
|
|
const OriginsInfo &Info = arg;
|
|
auto &Helper = Info.Helper;
|
|
auto ActualLiveSetOpt = Helper.getLiveOriginsAtPoint(Annotation);
|
|
if (!ActualLiveSetOpt) {
|
|
*result_listener << "could not get a valid live origin set at point '"
|
|
<< Annotation << "'";
|
|
return false;
|
|
}
|
|
std::vector<OriginID> ActualLiveOrigins;
|
|
for (const auto [OID, ActualConfidence] : ActualLiveSetOpt.value()) {
|
|
if (ConfFilter == LivenessKindFilter::All)
|
|
ActualLiveOrigins.push_back(OID);
|
|
if (ActualConfidence == LivenessKind::Maybe &&
|
|
ConfFilter == LivenessKindFilter::Maybe)
|
|
ActualLiveOrigins.push_back(OID);
|
|
if (ActualConfidence == LivenessKind::Must &&
|
|
ConfFilter == LivenessKindFilter::Must)
|
|
ActualLiveOrigins.push_back(OID);
|
|
}
|
|
|
|
std::vector<OriginID> ExpectedLiveOrigins;
|
|
for (const auto &VarName : Info.OriginVars) {
|
|
auto OriginIDOpt = Helper.getOriginForDecl(VarName);
|
|
if (!OriginIDOpt) {
|
|
*result_listener << "could not find an origin for variable '" << VarName
|
|
<< "'";
|
|
return false;
|
|
}
|
|
ExpectedLiveOrigins.push_back(*OriginIDOpt);
|
|
}
|
|
std::sort(ExpectedLiveOrigins.begin(), ExpectedLiveOrigins.end());
|
|
std::sort(ActualLiveOrigins.begin(), ActualLiveOrigins.end());
|
|
if (ExpectedLiveOrigins != ActualLiveOrigins) {
|
|
*result_listener << "Expected: {";
|
|
for (const auto &OriginID : ExpectedLiveOrigins) {
|
|
*result_listener << OriginID.Value << ", ";
|
|
}
|
|
*result_listener << "} Actual: {";
|
|
for (const auto &OriginID : ActualLiveOrigins) {
|
|
*result_listener << OriginID.Value << ", ";
|
|
}
|
|
*result_listener << "}";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
MATCHER_P2(HasLiveLoanAtExpiryImpl, HelperPtr, Annotation, "") {
|
|
llvm::StringRef VarName = arg;
|
|
LifetimeTestHelper &Helper = *HelperPtr;
|
|
|
|
std::vector<LoanID> Loans = Helper.getLoansForVar(VarName);
|
|
if (Loans.empty()) {
|
|
*result_listener << "No loans found for variable" << VarName.str();
|
|
return false;
|
|
}
|
|
|
|
ProgramPoint PP = Helper.getProgramPoint(Annotation);
|
|
llvm::ArrayRef<const Fact *> AllFactsInBlock = Helper.getBlockContaining(PP);
|
|
|
|
bool NoExpireFactLive = false;
|
|
for (const LoanID CurrentLoanID : Loans) {
|
|
const ExpireFact *EF =
|
|
Helper.getExpireFactFromAllFacts(AllFactsInBlock, CurrentLoanID);
|
|
if (!EF) {
|
|
NoExpireFactLive = true;
|
|
continue;
|
|
}
|
|
std::optional<LoanSet> LiveLoans = Helper.getLiveLoansAtPoint(EF);
|
|
if (!LiveLoans.has_value()) {
|
|
*result_listener << "No Live Loans At Expiry Location.";
|
|
continue;
|
|
}
|
|
if (LiveLoans->contains({CurrentLoanID}))
|
|
return true;
|
|
}
|
|
if (NoExpireFactLive) {
|
|
*result_listener << "No Expire Fact for loan of " << VarName.str();
|
|
return false;
|
|
}
|
|
*result_listener << "No loans of " << VarName.str() << " are live";
|
|
return false;
|
|
}
|
|
|
|
MATCHER_P(MustBeLiveAt, Annotation, "") {
|
|
return ExplainMatchResult(AreLiveAtImpl(Annotation, LivenessKindFilter::Must),
|
|
arg, result_listener);
|
|
}
|
|
|
|
MATCHER_P(MaybeLiveAt, Annotation, "") {
|
|
return ExplainMatchResult(
|
|
AreLiveAtImpl(Annotation, LivenessKindFilter::Maybe), arg,
|
|
result_listener);
|
|
}
|
|
|
|
MATCHER_P(AreLiveAt, Annotation, "") {
|
|
return ExplainMatchResult(AreLiveAtImpl(Annotation, LivenessKindFilter::All),
|
|
arg, result_listener);
|
|
}
|
|
|
|
MATCHER_P(HasLoanToATemporary, Annotation, "") {
|
|
const OriginInfo &Info = arg;
|
|
auto &Helper = Info.Helper;
|
|
std::optional<OriginID> OIDOpt = Helper.getOriginForDecl(Info.OriginVar);
|
|
if (!OIDOpt) {
|
|
*result_listener << "could not find origin for '" << Info.OriginVar.str()
|
|
<< "'";
|
|
return false;
|
|
}
|
|
|
|
std::optional<LoanSet> LoansSetOpt =
|
|
Helper.getLoansAtPoint(*OIDOpt, Annotation);
|
|
if (!LoansSetOpt) {
|
|
*result_listener << "could not get a valid loan set at point '"
|
|
<< Annotation << "'";
|
|
return false;
|
|
}
|
|
|
|
std::vector<LoanID> Loans(LoansSetOpt->begin(), LoansSetOpt->end());
|
|
|
|
for (LoanID LID : Loans)
|
|
if (Helper.isLoanToATemporary(LID))
|
|
return true;
|
|
*result_listener << "could not find loan to a temporary for '"
|
|
<< Info.OriginVar.str() << "'";
|
|
return false;
|
|
}
|
|
|
|
// 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.
|
|
OriginsInfo Origins(std::initializer_list<std::string> OriginVars) {
|
|
return OriginsInfo({OriginVars}, *Helper);
|
|
}
|
|
|
|
OriginsInfo NoOrigins() { return Origins({}); }
|
|
|
|
/// 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);
|
|
}
|
|
|
|
auto HasLiveLoanAtExpiry(const char *Annotation) {
|
|
return HasLiveLoanAtExpiryImpl(Helper.get(), 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, 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(in_loop);
|
|
}
|
|
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"}, "in_loop"));
|
|
// 'temp' is a block-local origin and it's loans are not tracked outside the
|
|
// block.
|
|
EXPECT_THAT(Origin("temp"), HasLoansTo({}, "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(Origin("p1"),
|
|
HasLoansTo({"v1", "v2", "temp"}, "in_loop_before_temp"));
|
|
EXPECT_THAT(Origin("p2"), HasLoansTo({"v2", "temp"}, "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(Origin("p1"), HasLoansTo({"v1", "v2", "temp"}, "after_loop"));
|
|
EXPECT_THAT(Origin("p2"), HasLoansTo({"v2", "temp"}, "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, 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));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, GslPointerSimpleLoan) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj a;
|
|
View x = a;
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, GslPointerConstructFromOwner) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj al, bl, cl, dl, el, fl;
|
|
View a = View(al);
|
|
View b = View{bl};
|
|
View c = View(View(View(cl)));
|
|
View d = View{View(View(dl))};
|
|
View e = View{View{View{el}}};
|
|
View f = {fl};
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("a"), HasLoansTo({"al"}, "p1"));
|
|
EXPECT_THAT(Origin("b"), HasLoansTo({"bl"}, "p1"));
|
|
EXPECT_THAT(Origin("c"), HasLoansTo({"cl"}, "p1"));
|
|
EXPECT_THAT(Origin("d"), HasLoansTo({"dl"}, "p1"));
|
|
EXPECT_THAT(Origin("e"), HasLoansTo({"el"}, "p1"));
|
|
EXPECT_THAT(Origin("f"), HasLoansTo({"fl"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, GslPointerConstructFromView) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj a;
|
|
View x = View(a);
|
|
View y = View{x};
|
|
View z = View(View(View(y)));
|
|
View p = View{View(View(x))};
|
|
View q = {x};
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1"));
|
|
EXPECT_THAT(Origin("y"), HasLoansTo({"a"}, "p1"));
|
|
EXPECT_THAT(Origin("z"), HasLoansTo({"a"}, "p1"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"a"}, "p1"));
|
|
EXPECT_THAT(Origin("q"), HasLoansTo({"a"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, GslPointerInConditionalOperator) {
|
|
SetupTest(R"(
|
|
void target(bool cond) {
|
|
MyObj a, b;
|
|
View v = cond ? a : b;
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v"), HasLoansTo({"a", "b"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, ExtraParenthesis) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj a;
|
|
View x = ((View((((a))))));
|
|
View y = ((View{(((x)))}));
|
|
View z = ((View(((y)))));
|
|
View p = ((View{((x))}));
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1"));
|
|
EXPECT_THAT(Origin("y"), HasLoansTo({"a"}, "p1"));
|
|
EXPECT_THAT(Origin("z"), HasLoansTo({"a"}, "p1"));
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"a"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, ViewFromTemporary) {
|
|
SetupTest(R"(
|
|
MyObj temporary();
|
|
void use(View);
|
|
void target() {
|
|
View a;
|
|
a = temporary();
|
|
POINT(p1);
|
|
use(a);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("a"), HasLoanToATemporary("p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, GslPointerWithConstAndAuto) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj a;
|
|
const View v1 = a;
|
|
auto v2 = v1;
|
|
const auto& v3 = v2;
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
|
|
EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p1"));
|
|
EXPECT_THAT(Origin("v3"), HasLoansTo({"v2"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, GslPointerPropagation) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj a;
|
|
View x = a;
|
|
POINT(p1);
|
|
|
|
View y = x; // Propagation via copy-construction
|
|
POINT(p2);
|
|
|
|
View z;
|
|
z = x; // Propagation via copy-assignment
|
|
POINT(p3);
|
|
}
|
|
)");
|
|
|
|
EXPECT_THAT(Origin("x"), HasLoansTo({"a"}, "p1"));
|
|
EXPECT_THAT(Origin("y"), HasLoansTo({"a"}, "p2"));
|
|
EXPECT_THAT(Origin("z"), HasLoansTo({"a"}, "p3"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, GslPointerReassignment) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj safe;
|
|
View v;
|
|
v = safe;
|
|
POINT(p1);
|
|
{
|
|
MyObj unsafe;
|
|
v = unsafe;
|
|
POINT(p2);
|
|
} // `unsafe` expires here.
|
|
POINT(p3);
|
|
}
|
|
)");
|
|
|
|
EXPECT_THAT(Origin("v"), HasLoansTo({"safe"}, "p1"));
|
|
EXPECT_THAT(Origin("v"), HasLoansTo({"unsafe"}, "p2"));
|
|
EXPECT_THAT(Origin("v"), HasLoansTo({"unsafe"}, "p3"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, GslPointerConversionOperator) {
|
|
SetupTest(R"(
|
|
struct String;
|
|
|
|
struct [[gsl::Pointer()]] StringView {
|
|
StringView() = default;
|
|
};
|
|
|
|
struct [[gsl::Owner]] String {
|
|
~String() {}
|
|
operator StringView() const;
|
|
};
|
|
|
|
void target() {
|
|
String xl, yl;
|
|
StringView x = xl;
|
|
StringView y;
|
|
y = yl;
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("x"), HasLoansTo({"xl"}, "p1"));
|
|
EXPECT_THAT(Origin("y"), HasLoansTo({"yl"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LifetimeboundSimple) {
|
|
SetupTest(R"(
|
|
View Identity(View v [[clang::lifetimebound]]);
|
|
void target() {
|
|
MyObj a, b;
|
|
View v1 = a;
|
|
POINT(p1);
|
|
|
|
View v2 = Identity(v1);
|
|
View v3 = Identity(b);
|
|
POINT(p2);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
|
|
// The origin of v2 should now contain the loan to 'o' from v1.
|
|
EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2"));
|
|
EXPECT_THAT(Origin("v3"), HasLoansTo({"b"}, "p2"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LifetimeboundMemberFunctionOfAView) {
|
|
SetupTest(R"(
|
|
struct [[gsl::Pointer()]] MyView {
|
|
MyView(const MyObj& o) {}
|
|
MyView& pass() [[clang::lifetimebound]] { return *this; }
|
|
};
|
|
void target() {
|
|
MyObj o;
|
|
MyView v1 = o;
|
|
POINT(p1);
|
|
MyView* v2 = &v1.pass();
|
|
POINT(p2);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v1"), HasLoansTo({"o"}, "p1"));
|
|
// The call v1.pass() is bound to 'v1'.
|
|
EXPECT_THAT(Origin("v2"), HasLoansTo({"v1"}, "p2"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LifetimeboundMemberFunction) {
|
|
SetupTest(R"(
|
|
struct LifetimeboundMember {
|
|
View get() [[clang::lifetimebound]];
|
|
};
|
|
void target() {
|
|
LifetimeboundMember o;
|
|
View v1 = o.get();
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v1"), HasLoansTo({"o"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LifetimeboundMultipleArgs) {
|
|
SetupTest(R"(
|
|
View Choose(bool cond, View a [[clang::lifetimebound]], View b [[clang::lifetimebound]]);
|
|
void target() {
|
|
MyObj o1, o2;
|
|
View v1 = o1;
|
|
View v2 = o2;
|
|
POINT(p1);
|
|
|
|
View v3 = Choose(true, v1, v2);
|
|
POINT(p2);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v1"), HasLoansTo({"o1"}, "p1"));
|
|
EXPECT_THAT(Origin("v2"), HasLoansTo({"o2"}, "p2"));
|
|
// v3 should have loans from both v1 and v2, demonstrating the union of
|
|
// loans.
|
|
EXPECT_THAT(Origin("v3"), HasLoansTo({"o1", "o2"}, "p2"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LifetimeboundMixedArgs) {
|
|
SetupTest(R"(
|
|
View Choose(bool cond, View a [[clang::lifetimebound]], View b);
|
|
void target() {
|
|
MyObj o1, o2;
|
|
View v1 = o1;
|
|
View v2 = o2;
|
|
POINT(p1);
|
|
|
|
View v3 = Choose(true, v1, v2);
|
|
POINT(p2);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v1"), HasLoansTo({"o1"}, "p1"));
|
|
EXPECT_THAT(Origin("v2"), HasLoansTo({"o2"}, "p1"));
|
|
// v3 should only have loans from v1, as v2 is not lifetimebound.
|
|
EXPECT_THAT(Origin("v3"), HasLoansTo({"o1"}, "p2"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LifetimeboundChainOfViews) {
|
|
SetupTest(R"(
|
|
View Identity(View v [[clang::lifetimebound]]);
|
|
View DoubleIdentity(View v [[clang::lifetimebound]]);
|
|
|
|
void target() {
|
|
MyObj obj;
|
|
View v1 = obj;
|
|
POINT(p1);
|
|
View v2 = DoubleIdentity(Identity(v1));
|
|
POINT(p2);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v1"), HasLoansTo({"obj"}, "p1"));
|
|
// v2 should inherit the loan from v1 through the chain of calls.
|
|
EXPECT_THAT(Origin("v2"), HasLoansTo({"obj"}, "p2"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LifetimeboundRawPointerParameter) {
|
|
SetupTest(R"(
|
|
View ViewFromPtr(const MyObj* p [[clang::lifetimebound]]);
|
|
MyObj* PtrFromPtr(const MyObj* p [[clang::lifetimebound]]);
|
|
MyObj* PtrFromView(View v [[clang::lifetimebound]]);
|
|
|
|
void target() {
|
|
MyObj a;
|
|
View v = ViewFromPtr(&a);
|
|
POINT(p1);
|
|
|
|
MyObj b;
|
|
MyObj* ptr1 = PtrFromPtr(&b);
|
|
MyObj* ptr2 = PtrFromPtr(PtrFromPtr(PtrFromPtr(ptr1)));
|
|
POINT(p2);
|
|
|
|
MyObj c;
|
|
View v2 = ViewFromPtr(PtrFromView(c));
|
|
POINT(p3);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v"), HasLoansTo({"a"}, "p1"));
|
|
EXPECT_THAT(Origin("ptr1"), HasLoansTo({"b"}, "p2"));
|
|
EXPECT_THAT(Origin("ptr2"), HasLoansTo({"b"}, "p2"));
|
|
EXPECT_THAT(Origin("v2"), HasLoansTo({"c"}, "p3"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LifetimeboundConstRefViewParameter) {
|
|
SetupTest(R"(
|
|
View Identity(const View& v [[clang::lifetimebound]]);
|
|
void target() {
|
|
MyObj o;
|
|
View v1 = o;
|
|
View v2 = Identity(v1);
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v1"), HasLoansTo({"o"}, "p1"));
|
|
EXPECT_THAT(Origin("v2"), HasLoansTo({"v1"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LifetimeboundConstRefObjParam) {
|
|
SetupTest(R"(
|
|
View Identity(const MyObj& o [[clang::lifetimebound]]);
|
|
void target() {
|
|
MyObj a;
|
|
View v1 = Identity(a);
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LifetimeboundReturnReference) {
|
|
SetupTest(R"(
|
|
const MyObj& Identity(View v [[clang::lifetimebound]]);
|
|
void target() {
|
|
MyObj a;
|
|
View v1 = a;
|
|
POINT(p1);
|
|
|
|
View v2 = Identity(v1);
|
|
|
|
const MyObj& b = Identity(v1);
|
|
View v3 = Identity(b);
|
|
POINT(p2);
|
|
|
|
MyObj c;
|
|
View v4 = Identity(c);
|
|
POINT(p3);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
|
|
EXPECT_THAT(Origin("v2"), HasLoansTo({"a"}, "p2"));
|
|
|
|
EXPECT_THAT(Origin("v3"), HasLoansTo({"a"}, "p2"));
|
|
|
|
EXPECT_THAT(Origin("v4"), HasLoansTo({"c"}, "p3"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateFunctionReturnRef) {
|
|
SetupTest(R"(
|
|
template <typename T>
|
|
const T& Identity(T&& v [[clang::lifetimebound]]);
|
|
void target() {
|
|
MyObj a;
|
|
View v1 = Identity(a);
|
|
POINT(p1);
|
|
|
|
View v2 = Identity(v1);
|
|
const View& v3 = Identity(v2);
|
|
POINT(p2);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v1"), HasLoansTo({"a"}, "p1"));
|
|
EXPECT_THAT(Origin("v2"), HasLoansTo({}, "p2"));
|
|
EXPECT_THAT(Origin("v3"), HasLoansTo({"v2"}, "p2"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LifetimeboundTemplateFunctionReturnVal) {
|
|
SetupTest(R"(
|
|
template <typename T>
|
|
T Identity(const T& v [[clang::lifetimebound]]);
|
|
|
|
void target() {
|
|
MyObj a;
|
|
View v1 = Identity(a);
|
|
POINT(p1);
|
|
|
|
MyObj b;
|
|
View v2 = b;
|
|
View v3 = Identity(v2);
|
|
const View& v4 = Identity(v3);
|
|
POINT(p2);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v1"), HasLoanToATemporary("p1"));
|
|
|
|
EXPECT_THAT(Origin("v2"), HasLoansTo({"b"}, "p2"));
|
|
EXPECT_THAT(Origin("v3"), HasLoansTo({"v2"}, "p2"));
|
|
// View temporary on RHS is lifetime-extended.
|
|
EXPECT_THAT(Origin("v4"), HasLoansTo({}, "p2"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LifetimeboundConversionOperator) {
|
|
SetupTest(R"(
|
|
struct MyOwner {
|
|
MyObj o;
|
|
operator View() const [[clang::lifetimebound]];
|
|
};
|
|
|
|
void target() {
|
|
MyOwner owner;
|
|
View v = owner;
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("v"), HasLoansTo({"owner"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LivenessDeadPointer) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
POINT(p1);
|
|
MyObj s;
|
|
MyObj* p = &s;
|
|
POINT(p2);
|
|
}
|
|
)");
|
|
EXPECT_THAT(NoOrigins(), AreLiveAt("p2"));
|
|
EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LivenessSimpleReturn) {
|
|
SetupTest(R"(
|
|
MyObj* target() {
|
|
MyObj s;
|
|
MyObj* p = &s;
|
|
POINT(p1);
|
|
return p;
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LivenessKilledByReassignment) {
|
|
SetupTest(R"(
|
|
MyObj* target() {
|
|
MyObj s1, s2;
|
|
MyObj* p = &s1;
|
|
POINT(p1);
|
|
p = &s2;
|
|
POINT(p2);
|
|
return p;
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
|
|
EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LivenessAcrossBranches) {
|
|
SetupTest(R"(
|
|
MyObj* target(bool c) {
|
|
MyObj x, y;
|
|
MyObj* p = nullptr;
|
|
POINT(p1);
|
|
if (c) {
|
|
p = &x;
|
|
POINT(p2);
|
|
} else {
|
|
p = &y;
|
|
POINT(p3);
|
|
}
|
|
return p;
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
|
|
EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p3"));
|
|
// Before the `if`, the value of `p` (`nullptr`) is always overwritten before.
|
|
EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LivenessInLoop) {
|
|
SetupTest(R"(
|
|
MyObj* target(bool c) {
|
|
MyObj s1, s2;
|
|
MyObj* p = &s1;
|
|
MyObj* q = &s2;
|
|
POINT(p1);
|
|
while(c) {
|
|
POINT(p2);
|
|
|
|
p = q;
|
|
POINT(p3);
|
|
}
|
|
POINT(p4);
|
|
return p;
|
|
}
|
|
)");
|
|
|
|
EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p4"));
|
|
EXPECT_THAT(NoOrigins(), MaybeLiveAt("p4"));
|
|
|
|
EXPECT_THAT(Origins({"p", "q"}), MaybeLiveAt("p3"));
|
|
|
|
EXPECT_THAT(Origins({"q"}), MustBeLiveAt("p2"));
|
|
EXPECT_THAT(NoOrigins(), MaybeLiveAt("p2"));
|
|
|
|
EXPECT_THAT(Origins({"p", "q"}), MaybeLiveAt("p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LivenessInLoopAndIf) {
|
|
// See https://github.com/llvm/llvm-project/issues/156959.
|
|
SetupTest(R"(
|
|
void target(bool cond) {
|
|
MyObj b;
|
|
while (cond) {
|
|
POINT(p1);
|
|
|
|
MyObj a;
|
|
View p = b;
|
|
|
|
POINT(p2);
|
|
|
|
if (cond) {
|
|
POINT(p3);
|
|
p = a;
|
|
}
|
|
POINT(p4);
|
|
(void)p;
|
|
POINT(p5);
|
|
}
|
|
}
|
|
)");
|
|
EXPECT_THAT(NoOrigins(), AreLiveAt("p5"));
|
|
EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p4"));
|
|
EXPECT_THAT(NoOrigins(), AreLiveAt("p3"));
|
|
EXPECT_THAT(Origins({"p"}), MaybeLiveAt("p2"));
|
|
EXPECT_THAT(NoOrigins(), AreLiveAt("p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LivenessInLoopAndIf2) {
|
|
SetupTest(R"(
|
|
void target(MyObj safe, bool condition) {
|
|
MyObj* p = &safe;
|
|
MyObj* q = &safe;
|
|
POINT(p1);
|
|
|
|
while (condition) {
|
|
POINT(p2);
|
|
MyObj x;
|
|
p = &x;
|
|
|
|
POINT(p3);
|
|
|
|
if (condition) {
|
|
q = p;
|
|
POINT(p4);
|
|
}
|
|
|
|
POINT(p5);
|
|
(void)*p;
|
|
(void)*q;
|
|
POINT(p6);
|
|
}
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origins({"q"}), MaybeLiveAt("p6"));
|
|
EXPECT_THAT(NoOrigins(), MustBeLiveAt("p6"));
|
|
|
|
EXPECT_THAT(Origins({"p", "q"}), MustBeLiveAt("p5"));
|
|
|
|
EXPECT_THAT(Origins({"p", "q"}), MustBeLiveAt("p4"));
|
|
|
|
EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p3"));
|
|
EXPECT_THAT(Origins({"q"}), MaybeLiveAt("p3"));
|
|
|
|
EXPECT_THAT(Origins({"q"}), MaybeLiveAt("p2"));
|
|
EXPECT_THAT(NoOrigins(), MustBeLiveAt("p2"));
|
|
|
|
EXPECT_THAT(Origins({"q"}), MaybeLiveAt("p1"));
|
|
EXPECT_THAT(NoOrigins(), MustBeLiveAt("p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LivenessOutsideLoop) {
|
|
SetupTest(R"(
|
|
void target(MyObj safe) {
|
|
MyObj* p = &safe;
|
|
for (int i = 0; i < 1; ++i) {
|
|
MyObj s;
|
|
p = &s;
|
|
POINT(p1);
|
|
}
|
|
POINT(p2);
|
|
(void)*p;
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
|
|
EXPECT_THAT(Origins({"p"}), MaybeLiveAt("p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, TrivialDestructorsUAF) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
int *ptr;
|
|
{
|
|
int s = 1;
|
|
ptr = &s;
|
|
}
|
|
POINT(p1);
|
|
(void)*ptr;
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("ptr"), HasLoansTo({"s"}, "p1"));
|
|
EXPECT_THAT(Origins({"ptr"}), MustBeLiveAt("p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAF) {
|
|
SetupTest(R"(
|
|
class S {
|
|
View a, b;
|
|
};
|
|
|
|
void target() {
|
|
S* ptr;
|
|
{
|
|
S s;
|
|
ptr = &s;
|
|
}
|
|
POINT(p1);
|
|
(void)ptr;
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("ptr"), HasLoansTo({"s"}, "p1"));
|
|
EXPECT_THAT(Origins({"ptr"}), MustBeLiveAt("p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, SimpleReturnStackAddress) {
|
|
SetupTest(R"(
|
|
MyObj* target() {
|
|
MyObj s;
|
|
MyObj* p = &s;
|
|
POINT(p1);
|
|
return p;
|
|
}
|
|
)");
|
|
EXPECT_THAT("s", HasLiveLoanAtExpiry("p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, DirectReturn) {
|
|
SetupTest(R"(
|
|
MyObj* target() {
|
|
MyObj s;
|
|
POINT(P);
|
|
return &s;
|
|
}
|
|
)");
|
|
EXPECT_THAT("s", HasLiveLoanAtExpiry("P"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, ConditionalAssignUnconditionalReturn) {
|
|
SetupTest(R"(
|
|
MyObj* target(bool c) {
|
|
MyObj s1;
|
|
MyObj* p = nullptr;
|
|
if (c) {
|
|
p = &s1;
|
|
}
|
|
POINT(P);
|
|
return p;
|
|
}
|
|
)");
|
|
EXPECT_THAT("s1", HasLiveLoanAtExpiry("P"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, MultipleAssignments) {
|
|
SetupTest(R"(
|
|
MyObj* target() {
|
|
MyObj s;
|
|
MyObj* p1 = &s;
|
|
MyObj* p2 = &s;
|
|
POINT(P);
|
|
return p2;
|
|
}
|
|
)");
|
|
// Test if atleast one loan to "s" is live;
|
|
EXPECT_THAT("s", HasLiveLoanAtExpiry("P"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, ConditionalAssignBothBranches) {
|
|
SetupTest(R"(
|
|
MyObj* target(bool c) {
|
|
MyObj s1;
|
|
static MyObj s2;
|
|
MyObj* p = nullptr;
|
|
if (c) {
|
|
p = &s1;
|
|
} else {
|
|
p = &s2;
|
|
}
|
|
POINT(P);
|
|
return p;
|
|
}
|
|
)");
|
|
EXPECT_THAT("s1", HasLiveLoanAtExpiry("P"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, ReassignFromSafeToLocalThenReturn) {
|
|
SetupTest(R"(
|
|
MyObj* target() {
|
|
static MyObj safe_obj;
|
|
MyObj local_obj;
|
|
MyObj* p = &safe_obj;
|
|
|
|
p = &local_obj;
|
|
POINT(P);
|
|
return p;
|
|
}
|
|
)");
|
|
EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("P"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, PointerChainToLocal) {
|
|
SetupTest(R"(
|
|
MyObj* target() {
|
|
MyObj local_obj;
|
|
MyObj* p1 = &local_obj;
|
|
MyObj* p2 = p1;
|
|
POINT(P);
|
|
return p2;
|
|
}
|
|
)");
|
|
EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("P"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, MultipleAssignmentMultipleReturn) {
|
|
SetupTest(R"(
|
|
MyObj* target(bool c1, bool c2) {
|
|
static MyObj global_obj;
|
|
MyObj local_obj1;
|
|
MyObj local_obj2;
|
|
MyObj* p = nullptr;
|
|
if(c1){
|
|
p = &local_obj1;
|
|
POINT(C1);
|
|
return p;
|
|
}
|
|
else if(c2){
|
|
p = &local_obj2;
|
|
POINT(C2);
|
|
return p;
|
|
}
|
|
p = &global_obj;
|
|
POINT(C3);
|
|
return p;
|
|
}
|
|
)");
|
|
|
|
EXPECT_THAT("local_obj1", HasLiveLoanAtExpiry("C1"));
|
|
EXPECT_THAT("local_obj2", HasLiveLoanAtExpiry("C2"));
|
|
|
|
EXPECT_THAT("local_obj1", Not(HasLiveLoanAtExpiry("C3")));
|
|
EXPECT_THAT("local_obj2", Not(HasLiveLoanAtExpiry("C3")));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, MultipleAssignmentsSingleReturn) {
|
|
SetupTest(R"(
|
|
MyObj* target(bool c1, bool c2) {
|
|
static MyObj global_obj;
|
|
MyObj local_obj1;
|
|
MyObj local_obj2;
|
|
MyObj* p = nullptr;
|
|
if(c1){
|
|
p = &local_obj1;
|
|
}
|
|
else if(c2){
|
|
p = &local_obj2;
|
|
}
|
|
else{
|
|
p = &global_obj;
|
|
}
|
|
POINT(P);
|
|
return p;
|
|
}
|
|
)");
|
|
EXPECT_THAT("local_obj1", HasLiveLoanAtExpiry("P"));
|
|
EXPECT_THAT("local_obj2", HasLiveLoanAtExpiry("P"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, UseAfterScopeThenReturn) {
|
|
SetupTest(R"(
|
|
MyObj* target() {
|
|
MyObj* p;
|
|
{
|
|
MyObj local_obj;
|
|
p = &local_obj;
|
|
POINT(p1);
|
|
}
|
|
POINT(p2);
|
|
return p;
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p2"));
|
|
EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p2"));
|
|
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p1"));
|
|
EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
|
|
|
|
EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("p2"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, ReturnBeforeUseAfterScope) {
|
|
SetupTest(R"(
|
|
MyObj* target(bool c) {
|
|
MyObj* p;
|
|
static MyObj global_obj;
|
|
{
|
|
MyObj local_obj;
|
|
p = &local_obj;
|
|
if(c){
|
|
POINT(p1);
|
|
return p;
|
|
}
|
|
}
|
|
POINT(p2);
|
|
return &global_obj;
|
|
}
|
|
)");
|
|
EXPECT_THAT("local_obj", HasLiveLoanAtExpiry("p1"));
|
|
|
|
EXPECT_THAT(NoOrigins(), AreLiveAt("p2"));
|
|
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"local_obj"}, "p1"));
|
|
EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, TrivialDestructorsUAR) {
|
|
SetupTest(R"(
|
|
int* target() {
|
|
int s = 10;
|
|
int* p = &s;
|
|
POINT(p1);
|
|
return p;
|
|
}
|
|
)");
|
|
EXPECT_THAT("s", HasLiveLoanAtExpiry("p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAR) {
|
|
SetupTest(R"(
|
|
class S {
|
|
View a, b;
|
|
};
|
|
|
|
S* target() {
|
|
S *ptr;
|
|
S s;
|
|
ptr = &s;
|
|
POINT(p1);
|
|
return ptr;
|
|
}
|
|
)");
|
|
EXPECT_THAT("s", HasLiveLoanAtExpiry("p1"));
|
|
}
|
|
|
|
// ========================================================================= //
|
|
// Tests for shouldTrackImplicitObjectArg
|
|
// ========================================================================= //
|
|
|
|
TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_STLBegin) {
|
|
SetupTest(R"(
|
|
namespace std {
|
|
template<typename T>
|
|
struct vector {
|
|
struct iterator {};
|
|
iterator begin();
|
|
};
|
|
}
|
|
|
|
void target() {
|
|
std::vector<int> vec;
|
|
auto it = vec.begin();
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("it"), HasLoansTo({"vec"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_OwnerDeref) {
|
|
SetupTest(R"(
|
|
namespace std {
|
|
template<typename T>
|
|
struct optional {
|
|
T& operator*();
|
|
};
|
|
}
|
|
|
|
void target() {
|
|
std::optional<int> opt;
|
|
int& r = *opt;
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("r"), HasLoansTo({"opt"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_Value) {
|
|
SetupTest(R"(
|
|
namespace std {
|
|
template<typename T>
|
|
struct optional {
|
|
T& value();
|
|
};
|
|
}
|
|
|
|
void target() {
|
|
std::optional<int> opt;
|
|
int& r = opt.value();
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("r"), HasLoansTo({"opt"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_UniquePtr_Get) {
|
|
SetupTest(R"(
|
|
namespace std {
|
|
template<typename T>
|
|
struct unique_ptr {
|
|
T *get() const;
|
|
};
|
|
}
|
|
|
|
void target() {
|
|
std::unique_ptr<int> up;
|
|
int* r = up.get();
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("r"), HasLoansTo({"up"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_ConversionOperator) {
|
|
SetupTest(R"(
|
|
struct [[gsl::Pointer(int)]] IntPtr {
|
|
int& operator*();
|
|
};
|
|
|
|
struct [[gsl::Owner(int)]] OwnerWithConversion {
|
|
operator IntPtr();
|
|
};
|
|
|
|
void target() {
|
|
OwnerWithConversion owner;
|
|
IntPtr ptr = owner;
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("ptr"), HasLoansTo({"owner"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_MapFind) {
|
|
SetupTest(R"(
|
|
namespace std {
|
|
template<typename K, typename V>
|
|
struct map {
|
|
struct iterator {};
|
|
iterator find(const K&);
|
|
};
|
|
}
|
|
|
|
void target() {
|
|
std::map<int, int> m;
|
|
auto it = m.find(42);
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("it"), HasLoansTo({"m"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, TrackImplicitObjectArg_GSLPointerArg) {
|
|
SetupTest(R"(
|
|
namespace std {
|
|
|
|
template<typename T>
|
|
struct basic_string_view {
|
|
basic_string_view();
|
|
basic_string_view(const T *);
|
|
const T *begin() const;
|
|
const T *data() const;
|
|
};
|
|
using string_view = basic_string_view<char>;
|
|
|
|
template<typename T>
|
|
struct basic_string {
|
|
basic_string();
|
|
basic_string(const T *);
|
|
const T *c_str() const;
|
|
operator basic_string_view<T> () const;
|
|
const T *data() const;
|
|
};
|
|
using string = basic_string<char>;
|
|
}
|
|
|
|
void target() {
|
|
std::string s1;
|
|
std::string_view sv1 = s1;
|
|
|
|
std::string s2;
|
|
const char* sv2 = std::string_view(s2).begin();
|
|
|
|
std::string s3;
|
|
const char* sv3 = std::string_view(s3).data();
|
|
|
|
std::string s4;
|
|
std::string_view sv4 = std::string_view{std::string_view(s4).data()};
|
|
|
|
std::string s5;
|
|
const char* data5 = std::string_view(s5).data();
|
|
std::string_view sv5 = data5;
|
|
POINT(end);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("sv1"), HasLoansTo({"s1"}, "end"));
|
|
EXPECT_THAT(Origin("sv2"), HasLoansTo({"s2"}, "end"));
|
|
EXPECT_THAT(Origin("sv3"), HasLoansTo({"s3"}, "end"));
|
|
EXPECT_THAT(Origin("sv4"), HasLoansTo({"s4"}, "end"));
|
|
EXPECT_THAT(Origin("sv5"), HasLoansTo({"s5"}, "end"));
|
|
}
|
|
|
|
// ========================================================================= //
|
|
// Tests for shouldTrackFirstArgument
|
|
// ========================================================================= //
|
|
|
|
TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdBegin) {
|
|
SetupTest(R"(
|
|
namespace std {
|
|
template<typename T>
|
|
struct vector {
|
|
struct iterator {};
|
|
iterator begin();
|
|
};
|
|
|
|
template<typename C>
|
|
auto begin(C& c) -> decltype(c.begin());
|
|
}
|
|
|
|
void target() {
|
|
std::vector<int> vec;
|
|
auto it = std::begin(vec);
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("it"), HasLoansTo({"vec"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdData) {
|
|
SetupTest(R"(
|
|
namespace std {
|
|
template<typename T>
|
|
struct vector {
|
|
const T* data() const;
|
|
};
|
|
|
|
template<typename C>
|
|
auto data(C& c) -> decltype(c.data());
|
|
}
|
|
|
|
void target() {
|
|
std::vector<int> vec;
|
|
const int* p = std::data(vec);
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("p"), HasLoansTo({"vec"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, TrackFirstArgument_StdAnyCast) {
|
|
SetupTest(R"(
|
|
namespace std {
|
|
struct any {};
|
|
|
|
template<typename T>
|
|
T any_cast(const any& op);
|
|
}
|
|
|
|
void target() {
|
|
std::any a;
|
|
int& r = std::any_cast<int&>(a);
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("r"), HasLoansTo({"a"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, DerivedToBaseThisArg) {
|
|
SetupTest(R"(
|
|
template <typename T>
|
|
struct OperatorBase {
|
|
const T& value() const& [[clang::lifetimebound]];
|
|
const T&& value() const&& [[clang::lifetimebound]];
|
|
};
|
|
|
|
template <typename T>
|
|
class StatusOr : private OperatorBase<T> {
|
|
public:
|
|
using StatusOr::OperatorBase::value;
|
|
};
|
|
|
|
void target() {
|
|
View view;
|
|
StatusOr<MyObj> my_obj_or;
|
|
view = my_obj_or.value();
|
|
POINT(p1);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("view"), HasLoansTo({"my_obj_or"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, DerivedViewWithNoAnnotation) {
|
|
SetupTest(R"(
|
|
struct DerivedView : View {};
|
|
View target() {
|
|
DerivedView derived;
|
|
return derived;
|
|
}
|
|
)");
|
|
// EXPECT_THAT(Origin("view"), HasLoansTo({"my_obj_or"}, "p1"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LambdaCaptureByRef) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
int x;
|
|
int* p = &x;
|
|
auto lambda = [&p]() { return p; };
|
|
POINT(after_lambda);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("lambda"), HasLoansTo({"p"}, "after_lambda"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LambdaCaptureViewByValue) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj obj;
|
|
View v(obj);
|
|
auto lambda = [v]() { return v; };
|
|
POINT(after_lambda);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("lambda"), HasLoansTo({"obj"}, "after_lambda"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LambdaInitCaptureRawPointerByValue) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
int x;
|
|
int* p = &x;
|
|
auto lambda = [q = p]() { return q; };
|
|
POINT(after_lambda);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("lambda"), HasLoansTo({"x"}, "after_lambda"));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, LambdaInitCaptureViewByValue) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
MyObj obj;
|
|
View v(obj);
|
|
auto lambda = [w = v]() { return w; };
|
|
POINT(after_lambda);
|
|
}
|
|
)");
|
|
EXPECT_THAT(Origin("lambda"), HasLoansTo({"obj"}, "after_lambda"));
|
|
}
|
|
|
|
// ========================================================================= //
|
|
// Tests for buildOriginFlowChain
|
|
// ========================================================================= //
|
|
|
|
TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainWithErrorTargetLoan) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
int tgt = 2;
|
|
int *a = &tgt;
|
|
int *s = a;
|
|
POINT(after_use);
|
|
}
|
|
)");
|
|
|
|
#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST
|
|
EXPECT_DEATH(Helper->buildOriginFlowChainInOneBlock("s", "a", "after_use"),
|
|
"TargetLoan must be present in the StartOID at the StartPoint");
|
|
#endif
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainWithSelfAssignment) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
int tgt = 2;
|
|
int *a = &tgt;
|
|
int *b = a;
|
|
a = b;
|
|
a = a;
|
|
int *s = a;
|
|
POINT(after_use);
|
|
}
|
|
)");
|
|
|
|
const llvm::SmallVector<OriginID> OriginFlowChain =
|
|
Helper->buildOriginFlowChainInOneBlock("s", "tgt", "after_use");
|
|
|
|
EXPECT_THAT(OriginFlowChain, Contains(*Helper->getOriginForDecl("a")));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainWithMultiAssignInSameStmt) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
int tgt = 2;
|
|
int *a, *b, *c;
|
|
a = b = c = &tgt;
|
|
int *s = a;
|
|
POINT(after_use);
|
|
}
|
|
)");
|
|
|
|
const llvm::SmallVector<OriginID> OriginFlowChain =
|
|
Helper->buildOriginFlowChainInOneBlock("s", "tgt", "after_use");
|
|
|
|
EXPECT_THAT(OriginFlowChain, Contains(*Helper->getOriginForDecl("a")));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainWithOverwritingAssignments) {
|
|
SetupTest(R"(
|
|
void target() {
|
|
int tgt1 = 1, tgt2 = 2;
|
|
int *a = &tgt1;
|
|
int *b = a;
|
|
int *c = b;
|
|
b = &tgt2;
|
|
int *s = c;
|
|
POINT(after_use);
|
|
}
|
|
)");
|
|
|
|
const llvm::SmallVector<OriginID> OriginFlowChain =
|
|
Helper->buildOriginFlowChainInOneBlock("s", "tgt1", "after_use");
|
|
|
|
EXPECT_THAT(OriginFlowChain, Contains(*Helper->getOriginForDecl("a")));
|
|
}
|
|
|
|
TEST_F(LifetimeAnalysisTest, BuildOriginFlowChainWithLifetimeBound) {
|
|
SetupTest(R"(
|
|
int* choose(int* a [[clang::lifetimebound]], int* b [[clang::lifetimebound]]);
|
|
void target() {
|
|
int tgta = 1, tgtb = 2;
|
|
int *a = &tgta;
|
|
int *b = &tgtb;
|
|
int *result = choose(a, b);
|
|
result = choose(result , result);
|
|
int *s = result;
|
|
POINT(after_use);
|
|
}
|
|
)");
|
|
|
|
llvm::SmallVector<OriginID> ChainForTgtA =
|
|
Helper->buildOriginFlowChainInOneBlock("s", "tgta", "after_use");
|
|
llvm::SmallVector<OriginID> ChainForTgtB =
|
|
Helper->buildOriginFlowChainInOneBlock("s", "tgtb", "after_use");
|
|
|
|
EXPECT_THAT(ChainForTgtA, Contains(*Helper->getOriginForDecl("a")));
|
|
EXPECT_THAT(ChainForTgtA, Contains(*Helper->getOriginForDecl("result")));
|
|
EXPECT_THAT(ChainForTgtA, Not(Contains(*Helper->getOriginForDecl("b"))));
|
|
|
|
EXPECT_THAT(ChainForTgtB, Contains(*Helper->getOriginForDecl("b")));
|
|
EXPECT_THAT(ChainForTgtB, Contains(*Helper->getOriginForDecl("result")));
|
|
EXPECT_THAT(ChainForTgtB, Not(Contains(*Helper->getOriginForDecl("a"))));
|
|
}
|
|
} // anonymous namespace
|
|
} // namespace clang::lifetimes::internal
|