@@ -271,6 +271,49 @@ static bool isInStlNamespace(const Decl *D) {
271271 return DC->isStdNamespace ();
272272}
273273
274+ static bool isPointerLikeType (QualType Type) {
275+ return isRecordWithAttr<PointerAttr>(Type) || Type->isPointerType () ||
276+ Type->isNullPtrType ();
277+ }
278+
279+ // Returns true if the given Record decl is a form of `GSLOwner<Pointer>`
280+ // type, e.g. std::vector<string_view>, std::optional<string_view>.
281+ static bool isContainerOfPointer (const RecordDecl *Container) {
282+ if (const auto *CTSD =
283+ dyn_cast_if_present<ClassTemplateSpecializationDecl>(Container)) {
284+ if (!CTSD->hasAttr <OwnerAttr>()) // Container must be a GSL owner type.
285+ return false ;
286+ const auto &TAs = CTSD->getTemplateArgs ();
287+ return TAs.size () > 0 && TAs[0 ].getKind () == TemplateArgument::Type &&
288+ isPointerLikeType (TAs[0 ].getAsType ());
289+ }
290+ return false ;
291+ }
292+ static bool isContainerOfOwner (const RecordDecl *Container) {
293+ const auto *CTSD =
294+ dyn_cast_if_present<ClassTemplateSpecializationDecl>(Container);
295+ if (!CTSD)
296+ return false ;
297+ if (!CTSD->hasAttr <OwnerAttr>()) // Container must be a GSL owner type.
298+ return false ;
299+ const auto &TAs = CTSD->getTemplateArgs ();
300+ return TAs.size () > 0 && TAs[0 ].getKind () == TemplateArgument::Type &&
301+ isRecordWithAttr<OwnerAttr>(TAs[0 ].getAsType ());
302+ }
303+
304+ // Returns true if the given Record is `std::initializer_list<pointer>`.
305+ static bool isStdInitializerListOfPointer (const RecordDecl *RD) {
306+ if (const auto *CTSD =
307+ dyn_cast_if_present<ClassTemplateSpecializationDecl>(RD)) {
308+ const auto &TAs = CTSD->getTemplateArgs ();
309+ return isInStlNamespace (RD) && RD->getIdentifier () &&
310+ RD->getName () == " initializer_list" && TAs.size () > 0 &&
311+ TAs[0 ].getKind () == TemplateArgument::Type &&
312+ isPointerLikeType (TAs[0 ].getAsType ());
313+ }
314+ return false ;
315+ }
316+
274317static bool shouldTrackImplicitObjectArg (const CXXMethodDecl *Callee) {
275318 if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
276319 if (isRecordWithAttr<PointerAttr>(Conv->getConversionType ()) &&
@@ -282,8 +325,7 @@ static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
282325 Callee->getFunctionObjectParameterType ()) &&
283326 !isRecordWithAttr<OwnerAttr>(Callee->getFunctionObjectParameterType ()))
284327 return false ;
285- if (Callee->getReturnType ()->isPointerType () ||
286- isRecordWithAttr<PointerAttr>(Callee->getReturnType ())) {
328+ if (isPointerLikeType (Callee->getReturnType ())) {
287329 if (!Callee->getIdentifier ())
288330 return false ;
289331 return llvm::StringSwitch<bool >(Callee->getName ())
@@ -331,6 +373,103 @@ static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
331373 return false ;
332374}
333375
376+ // Returns true if the given constructor is a copy-like constructor, such as
377+ // `Ctor(Owner<U>&&)` or `Ctor(const Owner<U>&)`.
378+ static bool isCopyLikeConstructor (const CXXConstructorDecl *Ctor) {
379+ if (!Ctor || Ctor->param_size () != 1 )
380+ return false ;
381+ const auto *ParamRefType =
382+ Ctor->getParamDecl (0 )->getType ()->getAs <ReferenceType>();
383+ if (!ParamRefType)
384+ return false ;
385+
386+ // Check if the first parameter type is "Owner<U>".
387+ if (const auto *TST =
388+ ParamRefType->getPointeeType ()->getAs <TemplateSpecializationType>())
389+ return TST->getTemplateName ()
390+ .getAsTemplateDecl ()
391+ ->getTemplatedDecl ()
392+ ->hasAttr <OwnerAttr>();
393+ return false ;
394+ }
395+
396+ // Returns true if we should perform the GSL analysis on the first argument for
397+ // the given constructor.
398+ static bool
399+ shouldTrackFirstArgumentForConstructor (const CXXConstructExpr *Ctor) {
400+ const auto *LHSRecordDecl = Ctor->getConstructor ()->getParent ();
401+
402+ // Case 1, construct a GSL pointer, e.g. std::string_view
403+ // Always inspect when LHS is a pointer.
404+ if (LHSRecordDecl->hasAttr <PointerAttr>())
405+ return true ;
406+
407+ if (Ctor->getConstructor ()->getNumParams () != 1 ||
408+ !isContainerOfPointer (LHSRecordDecl))
409+ return false ;
410+
411+ // Now, the LHS is an Owner<Pointer> type, e.g., std::vector<string_view>.
412+ //
413+ // At a high level, we cannot precisely determine what the nested pointer
414+ // owns. However, by analyzing the RHS owner type, we can use heuristics to
415+ // infer ownership information. These heuristics are designed to be
416+ // conservative, minimizing false positives while still providing meaningful
417+ // diagnostics.
418+ //
419+ // While this inference isn't perfect, it helps catch common use-after-free
420+ // patterns.
421+ auto RHSArgType = Ctor->getArg (0 )->getType ();
422+ const auto *RHSRD = RHSArgType->getAsRecordDecl ();
423+ // LHS is constructed from an intializer_list.
424+ //
425+ // std::initializer_list is a proxy object that provides access to the backing
426+ // array. We perform analysis on it to determine if there are any dangling
427+ // temporaries in the backing array.
428+ // E.g. std::vector<string_view> abc = {string()};
429+ if (isStdInitializerListOfPointer (RHSRD))
430+ return true ;
431+
432+ // RHS must be an owner.
433+ if (!isRecordWithAttr<OwnerAttr>(RHSArgType))
434+ return false ;
435+
436+ // Bail out if the RHS is Owner<Pointer>.
437+ //
438+ // We cannot reliably determine what the LHS nested pointer owns -- it could
439+ // be the entire RHS or the nested pointer in RHS. To avoid false positives,
440+ // we skip this case, such as:
441+ // std::stack<std::string_view> s(std::deque<std::string_view>{});
442+ //
443+ // TODO: this also has a false negative, it doesn't catch the case like:
444+ // std::optional<span<int*>> os = std::vector<int*>{}
445+ if (isContainerOfPointer (RHSRD))
446+ return false ;
447+
448+ // Assume that the nested Pointer is constructed from the nested Owner.
449+ // E.g. std::optional<string_view> sv = std::optional<string>(s);
450+ if (isContainerOfOwner (RHSRD))
451+ return true ;
452+
453+ // Now, the LHS is an Owner<Pointer> and the RHS is an Owner<X>, where X is
454+ // neither an `Owner` nor a `Pointer`.
455+ //
456+ // Use the constructor's signature as a hint. If it is a copy-like constructor
457+ // `Owner1<Pointer>(Owner2<X>&&)`, we assume that the nested pointer is
458+ // constructed from X. In such cases, we do not diagnose, as `X` is not an
459+ // owner, e.g.
460+ // std::optional<string_view> sv = std::optional<Foo>();
461+ if (const auto *PrimaryCtorTemplate =
462+ Ctor->getConstructor ()->getPrimaryTemplate ();
463+ PrimaryCtorTemplate &&
464+ isCopyLikeConstructor (dyn_cast_if_present<CXXConstructorDecl>(
465+ PrimaryCtorTemplate->getTemplatedDecl ()))) {
466+ return false ;
467+ }
468+ // Assume that the nested pointer is constructed from the whole RHS.
469+ // E.g. optional<string_view> s = std::string();
470+ return true ;
471+ }
472+
334473// Return true if this is an "normal" assignment operator.
335474// We assuments that a normal assingment operator always returns *this, that is,
336475// an lvalue reference that is the same type as the implicit object parameter
@@ -473,12 +612,12 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
473612 if (CheckCoroCall || Callee->getParamDecl (I)->hasAttr <LifetimeBoundAttr>())
474613 VisitLifetimeBoundArg (Callee->getParamDecl (I), Args[I]);
475614 else if (EnableGSLAnalysis && I == 0 ) {
615+ // Perform GSL analysis for the first argument
476616 if (shouldTrackFirstArgument (Callee)) {
477617 VisitGSLPointerArg (Callee, Args[0 ]);
478- } else if (auto *CCE = dyn_cast<CXXConstructExpr>(Call);
479- CCE &&
480- CCE->getConstructor ()->getParent ()->hasAttr <PointerAttr>()) {
481- VisitGSLPointerArg (CCE->getConstructor (), Args[0 ]);
618+ } else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call);
619+ Ctor && shouldTrackFirstArgumentForConstructor (Ctor)) {
620+ VisitGSLPointerArg (Ctor->getConstructor (), Args[0 ]);
482621 }
483622 }
484623 }
0 commit comments