[LifetimeSafety] Track origins for lifetimebound calls returning record types (#187917)

- Move `hasOrigins` from free function to `OriginManager` method
- Add pre-scan (`collectLifetimeboundOriginTypes`) to register return
types of `[[clang::lifetimebound]]` calls before fact generation
- Generalize copy/move constructor origin propagation from lambda-only
to all types with `isDefaulted()` and `hasOrigins()` guard
- `isDefaulted()` is a heuristic: it avoids false positives from
user-defined copies with opaque semantics, but can still false-positive
when a defaulted outer copy invokes a user-defined inner copy that
breaks the propagate chain. See
`nested_defaulted_outer_with_user_defined_inner`
- Guard `operator=` origin propagation: pointer-like types always
propagate; other tracked types only when defaulted
- Defer `ThisOrigins` construction until after the pre-scan to avoid
origin list depth mismatch
- Fix `IsArgLifetimeBound` to exclude constructors from the
instance-method branch (latent bug exposed by this change)

Limitations (documented with FIXME tests):
- User-defined copy/move that shallow-copies: false negative
- Defaulted outer copy invoking user-defined inner copy: false positive
- Non-pointer/ref/gsl::Pointer parameter types with
`[[clang::lifetimebound]]`: not registered

Fixes #163600
This commit is contained in:
Zhijie Wang 2026-03-30 11:13:16 -07:00 committed by GitHub
parent 38a46a12c4
commit 34f5b80731
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 465 additions and 42 deletions

View File

@ -317,8 +317,7 @@ public:
class FactManager {
public:
FactManager(const AnalysisDeclContext &AC, const CFG &Cfg)
: OriginMgr(AC.getASTContext(), AC.getDecl()) {
FactManager(const AnalysisDeclContext &AC, const CFG &Cfg) : OriginMgr(AC) {
BlockToFacts.resize(Cfg.getNumBlockIDs());
}

View File

@ -57,6 +57,9 @@ private:
OriginList *getOriginsList(const ValueDecl &D);
OriginList *getOriginsList(const Expr &E);
bool hasOrigins(QualType QT) const;
bool hasOrigins(const Expr *E) const;
void flow(OriginList *Dst, OriginList *Src, bool Kill);
void handleAssignment(const Expr *LHSExpr, const Expr *RHSExpr);

View File

@ -20,6 +20,7 @@
#include "clang/AST/TypeBase.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeStats.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Utils.h"
#include "clang/Analysis/AnalysisDeclContext.h"
#include "llvm/Support/raw_ostream.h"
namespace clang::lifetimes::internal {
@ -117,15 +118,13 @@ private:
OriginList *InnerList = nullptr;
};
bool hasOrigins(QualType QT);
bool hasOrigins(const Expr *E);
bool doesDeclHaveStorage(const ValueDecl *D);
/// Manages the creation, storage, and retrieval of origins for pointer-like
/// variables and expressions.
class OriginManager {
public:
explicit OriginManager(ASTContext &AST, const Decl *D);
explicit OriginManager(const AnalysisDeclContext &AC);
/// Gets or creates the OriginList for a given ValueDecl.
///
@ -155,6 +154,9 @@ public:
unsigned getNumOrigins() const { return NextOriginID.Value; }
bool hasOrigins(QualType QT) const;
bool hasOrigins(const Expr *E) const;
void dump(OriginID OID, llvm::raw_ostream &OS) const;
/// Collects statistics about expressions that lack associated origins.
@ -169,6 +171,14 @@ private:
template <typename T>
OriginList *buildListForType(QualType QT, const T *Node);
void initializeThisOrigins(const Decl *D);
/// Pre-scans the function body (and constructor init lists) to discover
/// return types of lifetime-annotated calls (currently
/// [[clang::lifetimebound]]), registering them for origin tracking.
void collectLifetimeAnnotatedOriginTypes(const AnalysisDeclContext &AC);
void registerLifetimeAnnotatedOriginType(QualType QT);
ASTContext &AST;
OriginID NextOriginID{0};
/// TODO(opt): Profile and evaluate the usefulness of small buffer
@ -178,6 +188,10 @@ private:
llvm::DenseMap<const clang::ValueDecl *, OriginList *> DeclToList;
llvm::DenseMap<const clang::Expr *, OriginList *> ExprToList;
std::optional<OriginList *> ThisOrigins;
/// Types that are not inherently pointer-like but require origin tracking
/// because of lifetime annotations (currently [[clang::lifetimebound]]) on
/// functions that return them.
llvm::DenseSet<const Type *> LifetimeAnnotatedOriginTypes;
};
} // namespace clang::lifetimes::internal

View File

@ -37,6 +37,14 @@ OriginList *FactsGenerator::getOriginsList(const Expr &E) {
return FactMgr.getOriginMgr().getOrCreateList(&E);
}
bool FactsGenerator::hasOrigins(QualType QT) const {
return FactMgr.getOriginMgr().hasOrigins(QT);
}
bool FactsGenerator::hasOrigins(const Expr *E) const {
return FactMgr.getOriginMgr().hasOrigins(E);
}
/// Propagates origin information from Src to Dst through all levels of
/// indirection, creating OriginFlowFacts at each level.
///
@ -180,14 +188,13 @@ void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
handleGSLPointerConstruction(CCE);
return;
}
// Implicit copy/move constructors of lambda closures lack
// [[clang::lifetimebound]], so `handleFunctionCall` cannot propagate origins.
// Handle them directly to keep the origin chain intact (e.g., `return
// lambda;` copies the closure).
if (const auto *RD = CCE->getType()->getAsCXXRecordDecl();
RD && RD->isLambda() &&
CCE->getConstructor()->isCopyOrMoveConstructor() &&
CCE->getNumArgs() == 1) {
// For defaulted (implicit or `= default`) copy/move constructors, propagate
// origins directly. User-defined copy/move constructors have opaque semantics
// and fall through to `handleFunctionCall`, where [[clang::lifetimebound]] is
// needed to propagate origins.
if (CCE->getConstructor()->isCopyOrMoveConstructor() &&
CCE->getConstructor()->isDefaulted() && CCE->getNumArgs() == 1 &&
hasOrigins(CCE->getType())) {
const Expr *Arg = CCE->getArg(0);
if (OriginList *ArgList = getRValueOrigins(Arg, getOriginsList(*Arg))) {
flow(getOriginsList(*CCE), ArgList, /*Kill=*/true);
@ -396,8 +403,20 @@ void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
// and are handled separately.
if (OCE->getOperator() == OO_Equal && OCE->getNumArgs() == 2 &&
hasOrigins(OCE->getArg(0)->getType())) {
handleAssignment(OCE->getArg(0), OCE->getArg(1));
return;
// Pointer-like types: assignment inherently propagates origins.
QualType LHSTy = OCE->getArg(0)->getType();
if (LHSTy->isPointerOrReferenceType() || isGslPointerType(LHSTy)) {
handleAssignment(OCE->getArg(0), OCE->getArg(1));
return;
}
// Other tracked types: only defaulted operator= propagates origins.
// User-defined operator= has opaque semantics, so don't handle them now.
if (const auto *MD =
dyn_cast_or_null<CXXMethodDecl>(OCE->getDirectCallee());
MD && MD->isDefaulted()) {
handleAssignment(OCE->getArg(0), OCE->getArg(1));
return;
}
}
ArrayRef Args = {OCE->getArgs(), OCE->getNumArgs()};
@ -644,7 +663,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
auto IsArgLifetimeBound = [FD](unsigned I) -> bool {
const ParmVarDecl *PVD = nullptr;
if (const auto *Method = dyn_cast<CXXMethodDecl>(FD);
Method && Method->isInstance()) {
Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD)) {
if (I == 0)
// For the 'this' argument, the attribute is on the method itself.
return implicitObjectParamIsLifetimeBound(Method) ||
@ -686,9 +705,12 @@ void FactsGenerator::handleFunctionCall(const Expr *Call,
ArgList = getRValueOrigins(Args[I], ArgList);
}
if (isGslOwnerType(Args[I]->getType())) {
// GSL construction creates a view that borrows from arguments.
// This implies flowing origins through the list structure.
flow(CallList, ArgList, KillSrc);
// The constructed gsl::Pointer borrows from the Owner's storage, not
// from what the Owner itself borrows, so only the outermost origin is
// needed.
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
CallList->getOuterOriginID(), ArgList->getOuterOriginID(),
KillSrc));
KillSrc = false;
}
} else if (shouldTrackPointerImplicitObjectArg(I)) {

View File

@ -18,6 +18,7 @@
#include "clang/AST/TypeBase.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeStats.h"
#include "clang/Analysis/AnalysisDeclContext.h"
#include "llvm/ADT/StringMap.h"
namespace clang::lifetimes::internal {
@ -29,10 +30,10 @@ class MissingOriginCollector
public:
MissingOriginCollector(
const llvm::DenseMap<const clang::Expr *, OriginList *> &ExprToOriginList,
LifetimeSafetyStats &LSStats)
: ExprToOriginList(ExprToOriginList), LSStats(LSStats) {}
const OriginManager &OM, LifetimeSafetyStats &LSStats)
: ExprToOriginList(ExprToOriginList), OM(OM), LSStats(LSStats) {}
bool VisitExpr(Expr *E) {
if (!hasOrigins(E))
if (!OM.hasOrigins(E))
return true;
// Check if we have an origin for this expression.
if (!ExprToOriginList.contains(E)) {
@ -46,13 +47,63 @@ public:
private:
const llvm::DenseMap<const clang::Expr *, OriginList *> &ExprToOriginList;
const OriginManager &OM;
LifetimeSafetyStats &LSStats;
};
class LifetimeAnnotatedOriginTypeCollector
: public RecursiveASTVisitor<LifetimeAnnotatedOriginTypeCollector> {
public:
bool VisitCallExpr(const CallExpr *CE) {
// Indirect calls (e.g., function pointers) are skipped because lifetime
// annotations currently apply to declarations, not types.
if (const auto *FD = CE->getDirectCallee())
collect(FD, FD->getReturnType());
return true;
}
bool VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
collect(CCE->getConstructor(), CCE->getType());
return true;
}
bool shouldVisitLambdaBody() const { return false; }
const llvm::SmallVector<QualType> &getCollectedTypes() const {
return CollectedTypes;
}
private:
llvm::SmallVector<QualType> CollectedTypes;
void collect(const FunctionDecl *FD, QualType RetType) {
if (!FD)
return;
FD = getDeclWithMergedLifetimeBoundAttrs(FD);
if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
MD && MD->isInstance() && !isa<CXXConstructorDecl>(MD) &&
implicitObjectParamIsLifetimeBound(MD)) {
CollectedTypes.push_back(RetType);
return;
}
for (const auto *Param : FD->parameters()) {
if (Param->hasAttr<LifetimeBoundAttr>()) {
CollectedTypes.push_back(RetType);
return;
}
}
}
};
} // namespace
bool hasOrigins(QualType QT) {
bool OriginManager::hasOrigins(QualType QT) const {
if (QT->isPointerOrReferenceType() || isGslPointerType(QT))
return true;
if (LifetimeAnnotatedOriginTypes.contains(QT.getCanonicalType().getTypePtr()))
return true;
const auto *RD = QT->getAsCXXRecordDecl();
if (!RD)
return false;
@ -70,7 +121,9 @@ bool hasOrigins(QualType QT) {
///
/// An expression has origins if:
/// - It's a glvalue (has addressable storage), OR
/// - Its type is pointer-like (pointer, reference, or gsl::Pointer)
/// - Its type is pointer-like (pointer, reference, or gsl::Pointer), OR
/// - Its type is registered for origin tracking (e.g., return type of a
/// [[clang::lifetimebound]] function)
///
/// Examples:
/// - `int x; x` : has origin (glvalue)
@ -78,7 +131,7 @@ bool hasOrigins(QualType QT) {
/// - `std::string_view{}` : has 1 origin (prvalue of pointer type)
/// - `42` : no origin (prvalue of non-pointer type)
/// - `x + y` : (where x, y are int) → no origin (prvalue of non-pointer type)
bool hasOrigins(const Expr *E) {
bool OriginManager::hasOrigins(const Expr *E) const {
return E->isGLValue() || hasOrigins(E->getType());
}
@ -99,8 +152,13 @@ bool doesDeclHaveStorage(const ValueDecl *D) {
return !D->getType()->isReferenceType();
}
OriginManager::OriginManager(ASTContext &AST, const Decl *D) : AST(AST) {
// Create OriginList for 'this' expr.
OriginManager::OriginManager(const AnalysisDeclContext &AC)
: AST(AC.getASTContext()) {
collectLifetimeAnnotatedOriginTypes(AC);
initializeThisOrigins(AC.getDecl());
}
void OriginManager::initializeThisOrigins(const Decl *D) {
const auto *MD = llvm::dyn_cast_or_null<CXXMethodDecl>(D);
if (!MD || !MD->isInstance())
return;
@ -232,8 +290,27 @@ const Origin &OriginManager::getOrigin(OriginID ID) const {
void OriginManager::collectMissingOrigins(Stmt &FunctionBody,
LifetimeSafetyStats &LSStats) {
MissingOriginCollector Collector(this->ExprToList, LSStats);
MissingOriginCollector Collector(this->ExprToList, *this, LSStats);
Collector.TraverseStmt(const_cast<Stmt *>(&FunctionBody));
}
void OriginManager::collectLifetimeAnnotatedOriginTypes(
const AnalysisDeclContext &AC) {
LifetimeAnnotatedOriginTypeCollector Collector;
if (Stmt *Body = AC.getBody())
Collector.TraverseStmt(Body);
if (const auto *CD = dyn_cast<CXXConstructorDecl>(AC.getDecl()))
for (const auto *Init : CD->inits())
Collector.TraverseStmt(Init->getInit());
for (QualType QT : Collector.getCollectedTypes())
registerLifetimeAnnotatedOriginType(QT);
}
void OriginManager::registerLifetimeAnnotatedOriginType(QualType QT) {
if (!QT->getAsCXXRecordDecl() || hasOrigins(QT))
return;
LifetimeAnnotatedOriginTypes.insert(QT.getCanonicalType().getTypePtr());
}
} // namespace clang::lifetimes::internal

View File

@ -156,6 +156,12 @@ struct basic_string_view {
};
using string_view = basic_string_view<char>;
template<typename T>
struct span {
span();
span(const vector<T>&);
};
template<class _Mystr> struct iter {
iter& operator-=(int);
@ -191,6 +197,7 @@ template<typename T>
struct unique_ptr {
unique_ptr();
unique_ptr(unique_ptr<T>&&);
unique_ptr& operator=(unique_ptr<T>&&);
~unique_ptr();
T* release();
T &operator*();

View File

@ -576,12 +576,13 @@ struct FooView {
FooView(const Foo& foo [[clang::lifetimebound]]);
};
FooView test3(int i, std::optional<Foo> a) {
// FIXME: Detect this using the CFG-based lifetime analysis.
// Origin tracking for non-pointers type retured from lifetimebound fn is missing.
// https://github.com/llvm/llvm-project/issues/163600
if (i)
return *a; // expected-warning {{address of stack memory}}
return a.value(); // expected-warning {{address of stack memory}}
return *a; // expected-warning {{address of stack memory}} \
// cfg-warning {{address of stack memory is returned later}} \
// cfg-note {{returned here}}
return a.value(); // expected-warning {{address of stack memory}} \
// cfg-warning {{address of stack memory is returned later}} \
// cfg-note {{returned here}}
}
} // namespace GH93386
@ -591,11 +592,10 @@ struct UrlAnalyzed {
};
std::string StrCat(std::string_view, std::string_view);
void test1() {
// FIXME: Detect this using the CFG-based lifetime analysis.
// Origin tracking for non-pointers type retured from lifetimebound fn is missing.
// https://github.com/llvm/llvm-project/issues/163600
UrlAnalyzed url(StrCat("abc", "bcd")); // expected-warning {{object backing the pointer will be destroyed}}
use(url);
UrlAnalyzed url(StrCat("abc", "bcd")); // expected-warning {{object backing the pointer will be destroyed}} \
// cfg-warning {{object whose reference is captured does not live long enough}} \
// cfg-note {{destroyed here}}
use(url); // cfg-note {{later used here}}
}
std::string_view ReturnStringView(std::string_view abc [[clang::lifetimebound]]);

View File

@ -439,3 +439,17 @@ struct MemberArrayReturn {
};
} // namespace array
namespace track_origins_for_lifetimebound_record_type {
struct S {
View view;
};
S getS(const MyObj &obj [[clang::lifetimebound]]);
S forward(const MyObj &obj) { // expected-warning {{parameter in intra-TU function should be marked [[clang::lifetimebound]]}}
return getS(obj); // expected-note {{param returned here}}
}
} // namespace track_origins_for_lifetimebound_record_type

View File

@ -909,7 +909,6 @@ void lifetimebound_return_reference() {
(void)*ptr; // expected-note {{later used here}}
}
// FIXME: No warning for non gsl::Pointer types. Origin tracking is only supported for pointer types.
struct LifetimeBoundCtor {
LifetimeBoundCtor();
LifetimeBoundCtor(const MyObj& obj [[clang::lifetimebound]]);
@ -919,9 +918,9 @@ void lifetimebound_ctor() {
LifetimeBoundCtor v;
{
MyObj obj;
v = obj;
}
(void)v;
v = obj; // expected-warning {{object whose reference is captured does not live long enough}}
} // expected-note {{destroyed here}}
(void)v; // expected-note {{later used here}}
}
View lifetimebound_return_of_local() {
@ -2126,3 +2125,291 @@ void indexing_with_static_operator() {
}
} // namespace static_call_operator
namespace track_origins_for_lifetimebound_record_type {
template <class T> void use(T);
struct S {
S();
S(const std::string &s [[clang::lifetimebound]]);
S return_self_after_registration() const;
std::string_view getData() const [[clang::lifetimebound]];
};
S getS(const std::string &s [[clang::lifetimebound]]);
void from_free_function() {
S s = getS(std::string("temp")); // expected-warning {{object whose reference is captured does not live long enough}} \
// expected-note {{destroyed here}}
use(s); // expected-note {{later used here}}
}
void from_constructor() {
S s(std::string("temp")); // expected-warning {{object whose reference is captured does not live long enough}} \
// expected-note {{destroyed here}}
use(s); // expected-note {{later used here}}
}
struct Factory {
S make(const std::string &s [[clang::lifetimebound]]);
static S create(const std::string &s [[clang::lifetimebound]]);
S makeThis() const [[clang::lifetimebound]];
};
void from_method() {
Factory f;
S s = f.make(std::string("temp")); // expected-warning {{object whose reference is captured does not live long enough}} \
// expected-note {{destroyed here}}
use(s); // expected-note {{later used here}}
}
void from_static_method() {
S s = Factory::create(std::string("temp")); // expected-warning {{object whose reference is captured does not live long enough}} \
// expected-note {{destroyed here}}
use(s); // expected-note {{later used here}}
}
void from_lifetimebound_this_method() {
S value;
{
Factory f;
value = f.makeThis(); // expected-warning {{object whose reference is captured does not live long enough}}
} // expected-note {{destroyed here}}
use(value); // expected-note {{later used here}}
}
void across_scope() {
S s{};
{
std::string str{"abc"};
s = getS(str); // expected-warning {{object whose reference is captured does not live long enough}}
} // expected-note {{destroyed here}}
use(s); // expected-note {{later used here}}
}
void same_scope() {
std::string str{"abc"};
S s = getS(str);
use(s);
}
S copy_propagation() {
std::string str{"abc"};
S a = getS(str); // expected-warning {{address of stack memory is returned later}}
S b = a;
return b; // expected-note {{returned here}}
}
void assignment_propagation() {
S a, b;
{
std::string str{"abc"};
a = getS(str); // expected-warning {{object whose reference is captured does not live long enough}}
b = a;
} // expected-note {{destroyed here}}
use(b); // expected-note {{later used here}}
}
S getSNoAnnotation(const std::string &s);
void no_annotation() {
S s = getSNoAnnotation(std::string("temp"));
use(s);
}
void mix_annotated_and_not() {
S s1 = getS(std::string("temp")); // expected-warning {{object whose reference is captured does not live long enough}} \
// expected-note {{destroyed here}}
S s2 = getSNoAnnotation(std::string("temp"));
use(s1); // expected-note {{later used here}}
use(s2);
}
S getS2(const std::string &a [[clang::lifetimebound]], const std::string &b [[clang::lifetimebound]]);
S multiple_lifetimebound_params() {
std::string str{"abc"};
S s = getS2(str, std::string("temp")); // expected-warning {{address of stack memory is returned later}} \
// expected-warning {{object whose reference is captured does not live long enough}} \
// expected-note {{destroyed here}}
return s; // expected-note {{returned here}} \
// expected-note {{later used here}}
}
// TODO: Diagnose [[clang::lifetimebound]] on functions whose return value
// cannot refer to any object (e.g., returning int or enum).
int getInt(const std::string &s [[clang::lifetimebound]]);
void primitive_return() {
int i = getInt(std::string("temp"));
use(i);
}
template <class T>
T make(const std::string &s [[clang::lifetimebound]]);
void from_template_instantiation() {
S s = make<S>(std::string("temp")); // expected-warning {{object whose reference is captured does not live long enough}} \
// expected-note {{destroyed here}}
use(s); // expected-note {{later used here}}
}
struct FieldInitFromLifetimebound {
S value; // function-note {{this field dangles}}
FieldInitFromLifetimebound() : value(getS(std::string("temp"))) {} // function-warning {{address of stack memory escapes to a field}}
};
S S::return_self_after_registration() const {
std::string s{"abc"};
getS(s);
return *this;
}
struct SWithUserDefinedCopyLikeOps {
SWithUserDefinedCopyLikeOps();
SWithUserDefinedCopyLikeOps(const std::string &s [[clang::lifetimebound]]) : owned(s), data(s) {}
SWithUserDefinedCopyLikeOps(const SWithUserDefinedCopyLikeOps &other) : owned("copy"), data(owned) {}
SWithUserDefinedCopyLikeOps &operator=(const SWithUserDefinedCopyLikeOps &) {
owned = "copy";
data = owned;
return *this;
}
std::string owned;
std::string_view data;
};
SWithUserDefinedCopyLikeOps getSWithUserDefinedCopyLikeOps(const std::string &s [[clang::lifetimebound]]);
SWithUserDefinedCopyLikeOps user_defined_copy_ctor_should_not_assume_origin_propagation() {
std::string str{"abc"};
SWithUserDefinedCopyLikeOps s = getSWithUserDefinedCopyLikeOps(str);
SWithUserDefinedCopyLikeOps copy = s; // Copy is rescued by user-defined copy constructor, so should not warn.
return copy;
}
void user_defined_assignment_should_not_assume_origin_propagation() {
SWithUserDefinedCopyLikeOps dst;
{
std::string str{"abc"};
SWithUserDefinedCopyLikeOps src = getSWithUserDefinedCopyLikeOps(str);
dst = src;
}
use(dst);
}
const S &getRef(const std::string &s [[clang::lifetimebound]]);
S from_ref() {
std::string str{"abc"};
S s = getRef(str);
return s;
}
using SAlias = S;
SAlias getSAlias(const std::string &s [[clang::lifetimebound]]);
void from_typedef_return() {
SAlias s = getSAlias(std::string("temp")); // expected-warning {{object whose reference is captured does not live long enough}} \
// expected-note {{destroyed here}}
use(s); // expected-note {{later used here}}
}
struct SWithOriginPropagatingCopy {
SWithOriginPropagatingCopy();
SWithOriginPropagatingCopy(const std::string &s [[clang::lifetimebound]]) : data(s) {}
SWithOriginPropagatingCopy(const SWithOriginPropagatingCopy &other) : data(other.data) {}
std::string_view data;
};
SWithOriginPropagatingCopy getSWithOriginPropagatingCopy(const std::string &s [[clang::lifetimebound]]);
// FIXME: False negative. User-defined copy ctor may propagate origins.
SWithOriginPropagatingCopy user_defined_copy_with_origin_propagation() {
std::string str{"abc"};
SWithOriginPropagatingCopy s = getSWithOriginPropagatingCopy(str);
SWithOriginPropagatingCopy copy = s;
return copy; // Should warn.
}
struct DefaultedOuter {
DefaultedOuter();
DefaultedOuter(const std::string &s [[clang::lifetimebound]]) : inner(s) {}
SWithUserDefinedCopyLikeOps inner;
};
DefaultedOuter getDefaultedOuter(const std::string &s [[clang::lifetimebound]]);
// The defaulted outer copy ctor propagates origins even though the inner
// user-defined copy ctor may break the borrow. This is intentional: this
// pattern does not fit the ownership model this analysis supports.
DefaultedOuter nested_defaulted_outer_with_user_defined_inner() {
std::string str{"abc"};
DefaultedOuter o = getDefaultedOuter(str); // expected-warning {{address of stack memory is returned later}}
DefaultedOuter copy = o;
return copy; // expected-note {{returned here}}
}
std::string_view getSV(S s [[clang::lifetimebound]]);
// FIXME: False negative. Non-pointer/ref/gsl::Pointer parameter types marked
// [[clang::lifetimebound]] are not registered for origin tracking.
void dangling_view_from_non_pointer_param() {
std::string_view sv;
{
S s;
sv = getSV(s);
}
use(sv); // Should warn.
}
MyObj getMyObj(const MyObj &obj [[clang::lifetimebound]]);
void gsl_owner_return() {
MyObj obj;
View v = obj;
getMyObj(obj);
use(v);
}
std::vector<std::string_view> createViews(const std::string &s [[clang::lifetimebound]]);
std::span<std::string_view> owner_to_pointer_via_gsl_construction() {
std::string local;
auto views = createViews(local);
return views; // expected-warning {{address of stack memory is returned later}} \
// expected-note {{returned here}}
}
std::unique_ptr<S> getUniqueS(const std::string &s [[clang::lifetimebound]]);
void owner_return_unique_ptr_s() {
auto ptr = getUniqueS(std::string("temp")); // expected-warning {{object whose reference is captured does not live long enough}} \
// expected-note {{destroyed here}}
(void)ptr; // expected-note {{later used here}}
}
std::string_view return_dangling_view_through_owner() {
std::string local;
auto ups = getUniqueS(local);
S* s = ups.get(); // expected-warning {{address of stack memory is returned later}}
std::string_view sv = s->getData();
return sv; // expected-note {{returned here}}
}
// FIXME: False negative. Move assignment of unique_ptr is not defaulted,
// so origins from `local` don't propagate to `ups`.
void owner_outlives_lifetimebound_source() {
std::unique_ptr<S> ups;
{
std::string local;
ups = getUniqueS(local);
}
(void)ups; // Should warn.
}
} // namespace track_origins_for_lifetimebound_record_type