@@ -285,6 +285,18 @@ static bool isContainerOfPointer(const RecordDecl *Container) {
285
285
}
286
286
return false ;
287
287
}
288
+ static bool isContainerOfOwner (const RecordDecl *Container) {
289
+ if (const auto *CTSD =
290
+ dyn_cast_if_present<ClassTemplateSpecializationDecl>(Container)) {
291
+ if (!CTSD->hasAttr <OwnerAttr>()) // Container must be a GSL owner type.
292
+ return false ;
293
+ const auto &TAs = CTSD->getTemplateArgs ();
294
+ return TAs.size () > 0 && TAs[0 ].getKind () == TemplateArgument::Type &&
295
+ isRecordWithAttr<OwnerAttr>(TAs[0 ].getAsType ());
296
+ }
297
+ return false ;
298
+ }
299
+
288
300
// Returns true if the given Record is `std::initializer_list<pointer>`.
289
301
static bool isStdInitializerListOfPointer (const RecordDecl *RD) {
290
302
if (const auto *CTSD =
@@ -357,35 +369,101 @@ static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
357
369
return false ;
358
370
}
359
371
372
+ // Returns true if the given constructor is a copy-like constructor, such as
373
+ // `Ctor(Owner<U>&&)` or `Ctor(const Owner<U>&)`.
374
+ static bool isCopyLikeConstructor (const CXXConstructorDecl *Ctor) {
375
+ if (!Ctor || Ctor->param_size () != 1 )
376
+ return false ;
377
+ const auto *ParamRefType =
378
+ Ctor->getParamDecl (0 )->getType ()->getAs <ReferenceType>();
379
+ if (!ParamRefType)
380
+ return false ;
381
+
382
+ // Check if the first parameter type "Owner<U>".
383
+ if (const auto *TST =
384
+ ParamRefType->getPointeeType ()->getAs <TemplateSpecializationType>())
385
+ return TST->getTemplateName ()
386
+ .getAsTemplateDecl ()
387
+ ->getTemplatedDecl ()
388
+ ->hasAttr <OwnerAttr>();
389
+ return false ;
390
+ }
391
+
360
392
// Returns true if we should perform the GSL analysis on the first argument for
361
393
// the given constructor.
362
394
static bool
363
395
shouldTrackFirstArgumentForConstructor (const CXXConstructExpr *Ctor) {
364
- const auto *ClassD = Ctor->getConstructor ()->getParent ();
396
+ const auto *LHSRecordDecl = Ctor->getConstructor ()->getParent ();
365
397
366
398
// Case 1, construct a GSL pointer, e.g. std::string_view
367
- if (ClassD->hasAttr <PointerAttr>())
399
+ // Always inspect when LHS is a pointer.
400
+ if (LHSRecordDecl->hasAttr <PointerAttr>())
368
401
return true ;
369
402
370
- auto FirstArgType = Ctor->getArg (0 )->getType ();
371
- // Case 2, construct a container of pointer (std::vector<std::string_view>)
372
- // from a std::initilizer_list or an GSL owner
373
403
if (Ctor->getConstructor ()->getNumParams () != 1 ||
374
- !isContainerOfPointer (ClassD ))
404
+ !isContainerOfPointer (LHSRecordDecl ))
375
405
return false ;
376
406
377
- // For the typical case: `std::vector<std::string_view> abc = {std::string()};`
407
+ // Now, the LHS is an Owner<Pointer> type, e.g., std::vector<string_view>.
408
+ //
409
+ // At a high level, we cannot precisely determine what the nested pointer
410
+ // owns. However, by analyzing the RHS owner type, we can use heuristics to
411
+ // infer ownership information. These heuristics are designed to be
412
+ // conservative, minimizing false positives while still providing meaningful
413
+ // diagnostics.
414
+ //
415
+ // While this inference isn't perfect, it helps catch common use-after-free
416
+ // patterns.
417
+ auto RHSArgType = Ctor->getArg (0 )->getType ();
418
+ const auto *RHSRD = RHSArgType->getAsRecordDecl ();
419
+ // LHS is constructed from an intializer_list.
420
+ //
378
421
// std::initializer_list is a proxy object that provides access to the backing
379
422
// array. We perform analysis on it to determine if there are any dangling
380
423
// temporaries in the backing array.
381
- if (isStdInitializerListOfPointer (FirstArgType->getAsRecordDecl ()))
424
+ // E.g. std::vector<string_view> abc = {string()};
425
+ if (isStdInitializerListOfPointer (RHSRD))
426
+ return true ;
427
+
428
+ // RHS must be an owner.
429
+ if (!isRecordWithAttr<OwnerAttr>(RHSArgType))
430
+ return false ;
431
+
432
+ // Bail out if the RHS is Owner<Pointer>.
433
+ //
434
+ // We cannot reliably determine what the LHS nested pointer owns -- it could
435
+ // be the entire RHS or the nested pointer in RHS. To avoid false positives,
436
+ // we skip this case, such as:
437
+ // std::stack<std::string_view> s(std::deque<std::string_view>{});
438
+ //
439
+ // TODO: this also has a false negative, it doesn't catch the case like:
440
+ // std::optional<span<int*>> os = std::vector<int*>{}
441
+ if (isContainerOfPointer (RHSRD))
442
+ return false ;
443
+
444
+ // Assume that the nested Pointer is constructed from the nested Owner.
445
+ // E.g. std::optional<string_view> sv = std::optional<string>(s);
446
+ if (isContainerOfOwner (RHSRD))
382
447
return true ;
383
- // For the case: `std::optional<std::string_view> abc = std::string();`
384
- // When constructing from a container of pointers, it's less likely to result
385
- // in a dangling pointer. Therefore, we try to be conservative to not track
386
- // the argument futher to avoid false positives.
387
- return isRecordWithAttr<OwnerAttr>(FirstArgType) &&
388
- !isContainerOfPointer (FirstArgType->getAsRecordDecl ());
448
+
449
+ // Now, the LHS is an Owner<Pointer> and the RHS is an Owner<X>, where X is
450
+ // neither an `Owner` nor a `Pointer`.
451
+ //
452
+ // Use the constructor's signature as a hint. If it is a copy-like constructor
453
+ // `Owner1<Pointer>(Owner2<X>&&)`, we assume that the nested pointer is
454
+ // constructed from X. In such cases, we do not diagnose, as `X` is not an
455
+ // owner, e.g.
456
+ // std::optional<string_view> sv = std::optional<Foo>();
457
+ if (const auto *PrimaryCtorTemplate =
458
+ Ctor->getConstructor ()->getPrimaryTemplate ();
459
+ PrimaryCtorTemplate &&
460
+ isCopyLikeConstructor (dyn_cast_if_present<CXXConstructorDecl>(
461
+ PrimaryCtorTemplate->getTemplatedDecl ()))) {
462
+ return false ;
463
+ }
464
+ // Assume that the nested pointer is constructed from the whole RHS.
465
+ // E.g. optional<string_view> s = std::string();
466
+ return true ;
389
467
}
390
468
391
469
// Return true if this is an "normal" assignment operator.
0 commit comments