diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index 766742e98101a..f3eadc16473a4 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -91,6 +91,11 @@ class FactsGenerator : public ConstStmtVisitor { void handleMovedArgsInCall(const FunctionDecl *FD, ArrayRef Args); + // Handles [[clang::lifetime_capture_by(X)]] annotations on a function call to + // create flow facts from captured arguments to the capturer + void handleLifetimeCaptureBy(const FunctionDecl *FD, + ArrayRef Args); + /// Checks if a call-like expression creates a borrow by passing a value to a /// reference parameter, creating an IssueFact if it does. /// \param IsGslConstruction True if this is a GSL construction where all diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index dc80d2783fc03..e126fcd6101a3 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -864,6 +864,65 @@ void FactsGenerator::handleImplicitObjectFieldUses(const Expr *Call, }); } +void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD, + ArrayRef Args) { + if (Args.empty()) + return; + // FIXME: Add support for capture_by on constructors. + if (isa(FD)) + return; + const auto *Method = dyn_cast(FD); + bool IsInstance = + Method && Method->isInstance() && !isa(FD); + auto getArgCaptureBy = [FD, + IsInstance](unsigned I) -> LifetimeCaptureByAttr * { + const ParmVarDecl *PVD = nullptr; + if (IsInstance) { + // FIXME: Add support for capture_by on function declarations + if (I > 0 && I - 1 < FD->getNumParams()) + PVD = FD->getParamDecl(I - 1); + } else { + if (I < FD->getNumParams()) + PVD = FD->getParamDecl(I); + } + return PVD ? PVD->getAttr() : nullptr; + }; + for (unsigned I = 0; I < Args.size(); ++I) { + const LifetimeCaptureByAttr *Attr = getArgCaptureBy(I); + if (!Attr) + continue; + OriginList *CapturedOriginList = getOriginsList(*Args[I]); + if (!CapturedOriginList) + continue; + if (isGslPointerType(Args[I]->getType())) { + assert(!Args[I]->isGLValue() || CapturedOriginList->getLength() >= 2); + CapturedOriginList = getRValueOrigins(Args[I], CapturedOriginList); + } + if (!CapturedOriginList) + continue; + for (int CapturedByIdx : Attr->params()) { + // FIXME: Add support for capturing to Global/unknown. + if (CapturedByIdx == LifetimeCaptureByAttr::Global || + CapturedByIdx == LifetimeCaptureByAttr::Unknown || + CapturedByIdx == LifetimeCaptureByAttr::Invalid) + continue; + ArrayRef CallArgs = IsInstance ? Args.slice(1) : Args; + const Expr *CapturedByArg = (CapturedByIdx == LifetimeCaptureByAttr::This) + ? Args[0] + : CallArgs[CapturedByIdx]; + assert(CapturedByArg && "Capturer expression must be valid"); + + OriginList *CapturedByOriginList = getOriginsList(*CapturedByArg); + OriginList *Dest = getRValueOrigins(CapturedByArg, CapturedByOriginList); + if (!Dest) + continue; + CurrentBlockFacts.push_back(FactMgr.createFact( + Dest->getOuterOriginID(), CapturedOriginList->getOuterOriginID(), + /*KillDest=*/false)); + } + } +} + void FactsGenerator::handleFunctionCall(const Expr *Call, const FunctionDecl *FD, ArrayRef Args, @@ -880,6 +939,7 @@ void FactsGenerator::handleFunctionCall(const Expr *Call, handleDestructiveCall(Call, FD, Args); handleMovedArgsInCall(FD, Args); handleImplicitObjectFieldUses(Call, FD); + handleLifetimeCaptureBy(FD, Args); if (!CallList) return; auto IsArgLifetimeBound = [FD](unsigned I) -> bool { diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 30b450c333fbd..fb581f1d00f1b 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -3284,3 +3284,125 @@ void assign_non_capturing_to_function_ref(function_ref &r) { } } // namespace GH126600 + +//===----------------------------------------------------------------------===// +// lifetime-capture-by +//===----------------------------------------------------------------------===// + +void setCaptureBy(View& res, View in [[clang::lifetime_capture_by(res)]]); + +void use_after_free_capture_by() { + View res; + { + MyObj a; + setCaptureBy(res, a); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)res; // expected-note {{later used here}} +} + +View use_after_return_capture_by() { + MyObj a; + View res; + setCaptureBy(res, a); // expected-warning {{address of stack memory is returned later}} + return res; // expected-note {{returned here}} + +} + +void transitive_capture() { + View v1, v2; + { + MyObj local; + setCaptureBy(v1, local); // expected-warning {{object whose reference is captured does not live long enough}} + setCaptureBy(v2, v1); + } // expected-note {{destroyed here}} + (void)v2; // expected-note {{later used here}} +} + +void set1(View& res, const MyObj& in [[clang::lifetime_capture_by(res)]]); + +void test_reference_to_view() { + View v; + { + MyObj local; + set1(v, local); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)v; // expected-note {{later used here}} +} + +// FIXME: Add special handling for multi-level pointers and lvalue expressions which are not DeclRefExpr. +void set2(MyObj** res, const MyObj& in [[clang::lifetime_capture_by(res)]]); + +void test_pointer_to_pointer() { + MyObj *ptr = nullptr; + { + MyObj local; + set2(&ptr, local); + } + (void)ptr; +} + +void set3(MyObj*& res, const MyObj& in [[clang::lifetime_capture_by(res)]]); + +void test_reference_to_pointer() { + MyObj *ptr = nullptr; + { + MyObj local; + set3(ptr, local); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)ptr; // expected-note {{later used here}} +} + +struct [[gsl::Pointer]] MyContainer { + View stored; + void set(View s [[clang::lifetime_capture_by(this)]]); +}; + +void member_capture() { + MyContainer c; + { + MyObj local; + c.set(local); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)c.stored; // expected-note {{later used here}} +} + +// FIXME: Add support for simple containers without annotations. +struct SimpleContainer { + View stored; + void set(View s [[clang::lifetime_capture_by(this)]]); +}; + +void member_capture_simple_container() { + SimpleContainer c; + { + MyObj local; + c.set(local); + } + (void)c.stored; +} + +void captureTwo(View& into, + View a [[clang::lifetime_capture_by(into)]], + View b [[clang::lifetime_capture_by(into)]]); + +void multiple_captures() { + View res; + MyObj val1; + { + MyObj val2; + captureTwo(res, val1, val2); // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)res; // expected-note {{later used here}} +} + +void multiple_local_captures() { + View res; + { + MyObj val1; + MyObj val2; + captureTwo(res, val1, val2); // expected-warning 2 {{object whose reference is captured does not live long enough}} + } // expected-note 2 {{destroyed here}} + (void)res; // expected-note 2 {{later used here}} +} + +