@@ -271,6 +271,49 @@ static bool isInStlNamespace(const Decl *D) {
271
271
return DC->isStdNamespace ();
272
272
}
273
273
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
+
274
317
static bool shouldTrackImplicitObjectArg (const CXXMethodDecl *Callee) {
275
318
if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
276
319
if (isRecordWithAttr<PointerAttr>(Conv->getConversionType ()) &&
@@ -282,8 +325,7 @@ static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
282
325
Callee->getFunctionObjectParameterType ()) &&
283
326
!isRecordWithAttr<OwnerAttr>(Callee->getFunctionObjectParameterType ()))
284
327
return false ;
285
- if (Callee->getReturnType ()->isPointerType () ||
286
- isRecordWithAttr<PointerAttr>(Callee->getReturnType ())) {
328
+ if (isPointerLikeType (Callee->getReturnType ())) {
287
329
if (!Callee->getIdentifier ())
288
330
return false ;
289
331
return llvm::StringSwitch<bool >(Callee->getName ())
@@ -331,6 +373,103 @@ static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
331
373
return false ;
332
374
}
333
375
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
+
334
473
// Return true if this is an "normal" assignment operator.
335
474
// We assuments that a normal assingment operator always returns *this, that is,
336
475
// an lvalue reference that is the same type as the implicit object parameter
@@ -473,12 +612,12 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
473
612
if (CheckCoroCall || Callee->getParamDecl (I)->hasAttr <LifetimeBoundAttr>())
474
613
VisitLifetimeBoundArg (Callee->getParamDecl (I), Args[I]);
475
614
else if (EnableGSLAnalysis && I == 0 ) {
615
+ // Perform GSL analysis for the first argument
476
616
if (shouldTrackFirstArgument (Callee)) {
477
617
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 ]);
482
621
}
483
622
}
484
623
}
0 commit comments