@@ -289,6 +289,18 @@ static bool isContainerOfPointer(const RecordDecl *Container) {
289
289
}
290
290
return false ;
291
291
}
292
+ static bool isContainerOfOwner (const RecordDecl *Container) {
293
+ if (const auto *CTSD =
294
+ dyn_cast_if_present<ClassTemplateSpecializationDecl>(Container)) {
295
+ if (!CTSD->hasAttr <OwnerAttr>()) // Container must be a GSL owner type.
296
+ return false ;
297
+ const auto &TAs = CTSD->getTemplateArgs ();
298
+ return TAs.size () > 0 && TAs[0 ].getKind () == TemplateArgument::Type &&
299
+ isRecordWithAttr<OwnerAttr>(TAs[0 ].getAsType ());
300
+ }
301
+ return false ;
302
+ }
303
+
292
304
// Returns true if the given Record is `std::initializer_list<pointer>`.
293
305
static bool isStdInitializerListOfPointer (const RecordDecl *RD) {
294
306
if (const auto *CTSD =
@@ -361,35 +373,101 @@ static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
361
373
return false ;
362
374
}
363
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 "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
+
364
396
// Returns true if we should perform the GSL analysis on the first argument for
365
397
// the given constructor.
366
398
static bool
367
399
shouldTrackFirstArgumentForConstructor (const CXXConstructExpr *Ctor) {
368
- const auto *ClassD = Ctor->getConstructor ()->getParent ();
400
+ const auto *LHSRecordDecl = Ctor->getConstructor ()->getParent ();
369
401
370
402
// Case 1, construct a GSL pointer, e.g. std::string_view
371
- if (ClassD->hasAttr <PointerAttr>())
403
+ // Always inspect when LHS is a pointer.
404
+ if (LHSRecordDecl->hasAttr <PointerAttr>())
372
405
return true ;
373
406
374
- auto FirstArgType = Ctor->getArg (0 )->getType ();
375
- // Case 2, construct a container of pointer (std::vector<std::string_view>)
376
- // from a std::initilizer_list or an GSL owner
377
407
if (Ctor->getConstructor ()->getNumParams () != 1 ||
378
- !isContainerOfPointer (ClassD ))
408
+ !isContainerOfPointer (LHSRecordDecl ))
379
409
return false ;
380
410
381
- // For the typical case: `std::vector<std::string_view> abc = {std::string()};`
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
+ //
382
425
// std::initializer_list is a proxy object that provides access to the backing
383
426
// array. We perform analysis on it to determine if there are any dangling
384
427
// temporaries in the backing array.
385
- if (isStdInitializerListOfPointer (FirstArgType->getAsRecordDecl ()))
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))
386
451
return true ;
387
- // For the case: `std::optional<std::string_view> abc = std::string();`
388
- // When constructing from a container of pointers, it's less likely to result
389
- // in a dangling pointer. Therefore, we try to be conservative to not track
390
- // the argument futher to avoid false positives.
391
- return isRecordWithAttr<OwnerAttr>(FirstArgType) &&
392
- !isContainerOfPointer (FirstArgType->getAsRecordDecl ());
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 ;
393
471
}
394
472
395
473
// Return true if this is an "normal" assignment operator.
0 commit comments