[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:
parent
38a46a12c4
commit
34f5b80731
@ -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());
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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*();
|
||||
|
||||
@ -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]]);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user