Skip to content

[clang] Emit -Wdangling diagnoses for cases where a gsl-pointer is construct from a gsl-owner object in a container. #104556

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 6, 2024

Conversation

hokein
Copy link
Collaborator

@hokein hokein commented Aug 16, 2024

The warning is not emitted for the case string_view c = std::vector<std::string>({""}).at(0); because we bail out during the visit of the LHS at this point in the code.

This bailout was introduced in this commit to address a false positive with vector<vector::iterator>({""}).at(0);. This PR refines that fix by ensuring that, for initialization involving a gsl-pointer, we only consider constructor calls that take the gsl-owner object.

Fixes #100384.

@hokein hokein requested review from Xazax-hun and usx95 August 16, 2024 07:06
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Aug 16, 2024
@llvmbot
Copy link
Member

llvmbot commented Aug 16, 2024

@llvm/pr-subscribers-clang

Author: Haojian Wu (hokein)

Changes

The warning is not emitted for the case string_view c = std::vector&lt;std::string&gt;({""}).at(0); because we bail out during the visit of the LHS at this point in the code.

This bailout was introduced in this commit to address a false positive with vector&lt;vector::iterator&gt;({""}).at(0);. This PR refines that fix by ensuring that, for initialization involving a gsl-pointer, we only consider constructor calls that take the gsl-owner object.

Fixes #100384.


Full diff: https://github.com/llvm/llvm-project/pull/104556.diff

3 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+2)
  • (modified) clang/lib/Sema/CheckExprLifetime.cpp (+10-27)
  • (modified) clang/test/Sema/warn-lifetime-analysis-nocfg.cpp (+12)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ffdd063ec99037..c9864abc593e5e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -214,6 +214,8 @@ Improvements to Clang's diagnostics
 
 - Clang now diagnoses the use of ``main`` in an ``extern`` context as invalid according to [basic.start.main] p3. Fixes #GH101512.
 
+- Clang now diagnoses dangling cases where a gsl-pointer is constructed from a gsl-owner object inside a container (#GH100384).
+
 Improvements to Clang's time-trace
 ----------------------------------
 
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index 7389046eaddde1..b543c5e67f9550 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -191,7 +191,6 @@ struct IndirectLocalPathEntry {
     LifetimeBoundCall,
     TemporaryCopy,
     LambdaCaptureInit,
-    GslReferenceInit,
     GslPointerInit,
     GslPointerAssignment,
   } Kind;
@@ -328,25 +327,13 @@ static bool shouldTrackFirstArgument(const FunctionDecl *FD) {
 
 static void handleGslAnnotatedTypes(IndirectLocalPath &Path, Expr *Call,
                                     LocalVisitor Visit) {
-  auto VisitPointerArg = [&](const Decl *D, Expr *Arg, bool Value) {
+  auto VisitPointerArg = [&](const Decl *D, Expr *Arg) {
     // We are not interested in the temporary base objects of gsl Pointers:
     //   Temp().ptr; // Here ptr might not dangle.
     if (isa<MemberExpr>(Arg->IgnoreImpCasts()))
       return;
-    // Once we initialized a value with a reference, it can no longer dangle.
-    if (!Value) {
-      for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
-        if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit)
-          continue;
-        if (PE.Kind == IndirectLocalPathEntry::GslPointerInit ||
-            PE.Kind == IndirectLocalPathEntry::GslPointerAssignment)
-          return;
-        break;
-      }
-    }
-    Path.push_back({Value ? IndirectLocalPathEntry::GslPointerInit
-                          : IndirectLocalPathEntry::GslReferenceInit,
-                    Arg, D});
+
+    Path.push_back({IndirectLocalPathEntry::GslPointerInit, Arg, D});
     if (Arg->isGLValue())
       visitLocalsRetainedByReferenceBinding(Path, Arg, RK_ReferenceBinding,
                                             Visit,
@@ -360,29 +347,28 @@ static void handleGslAnnotatedTypes(IndirectLocalPath &Path, Expr *Call,
   if (auto *MCE = dyn_cast<CXXMemberCallExpr>(Call)) {
     const auto *MD = cast_or_null<CXXMethodDecl>(MCE->getDirectCallee());
     if (MD && shouldTrackImplicitObjectArg(MD))
-      VisitPointerArg(MD, MCE->getImplicitObjectArgument(),
-                      !MD->getReturnType()->isReferenceType());
+      VisitPointerArg(MD, MCE->getImplicitObjectArgument());
     return;
   } else if (auto *OCE = dyn_cast<CXXOperatorCallExpr>(Call)) {
     FunctionDecl *Callee = OCE->getDirectCallee();
     if (Callee && Callee->isCXXInstanceMember() &&
         shouldTrackImplicitObjectArg(cast<CXXMethodDecl>(Callee)))
-      VisitPointerArg(Callee, OCE->getArg(0),
-                      !Callee->getReturnType()->isReferenceType());
+      VisitPointerArg(Callee, OCE->getArg(0));
     return;
   } else if (auto *CE = dyn_cast<CallExpr>(Call)) {
     FunctionDecl *Callee = CE->getDirectCallee();
     if (Callee && shouldTrackFirstArgument(Callee))
-      VisitPointerArg(Callee, CE->getArg(0),
-                      !Callee->getReturnType()->isReferenceType());
+      VisitPointerArg(Callee, CE->getArg(0));
     return;
   }
 
   if (auto *CCE = dyn_cast<CXXConstructExpr>(Call)) {
     const auto *Ctor = CCE->getConstructor();
     const CXXRecordDecl *RD = Ctor->getParent();
-    if (CCE->getNumArgs() > 0 && RD->hasAttr<PointerAttr>())
-      VisitPointerArg(Ctor->getParamDecl(0), CCE->getArgs()[0], true);
+    if (CCE->getNumArgs() > 0 && RD->hasAttr<PointerAttr>() &&
+        isRecordWithAttr<OwnerAttr>(
+            CCE->getArg(0)->IgnoreImpCasts()->getType()))
+      VisitPointerArg(Ctor->getParamDecl(0), CCE->getArgs()[0]);
   }
 }
 
@@ -944,7 +930,6 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
     case IndirectLocalPathEntry::LValToRVal:
     case IndirectLocalPathEntry::LifetimeBoundCall:
     case IndirectLocalPathEntry::TemporaryCopy:
-    case IndirectLocalPathEntry::GslReferenceInit:
     case IndirectLocalPathEntry::GslPointerInit:
     case IndirectLocalPathEntry::GslPointerAssignment:
       // These exist primarily to mark the path as not permitting or
@@ -975,7 +960,6 @@ static bool pathOnlyHandlesGslPointer(IndirectLocalPath &Path) {
     case IndirectLocalPathEntry::LifetimeBoundCall:
       continue;
     case IndirectLocalPathEntry::GslPointerInit:
-    case IndirectLocalPathEntry::GslReferenceInit:
     case IndirectLocalPathEntry::GslPointerAssignment:
       return true;
     default:
@@ -1242,7 +1226,6 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
       case IndirectLocalPathEntry::LifetimeBoundCall:
       case IndirectLocalPathEntry::TemporaryCopy:
       case IndirectLocalPathEntry::GslPointerInit:
-      case IndirectLocalPathEntry::GslReferenceInit:
       case IndirectLocalPathEntry::GslPointerAssignment:
         // FIXME: Consider adding a note for these.
         break;
diff --git a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
index 09dfb2b5d96a89..c0fb2ed042cc7b 100644
--- a/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
+++ b/clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
@@ -475,6 +475,18 @@ std::vector<int>::iterator noFalsePositiveWithVectorOfPointers() {
   return iters.at(0);
 }
 
+// GH100384
+std::string_view ContainerWithAnnotatedElements() {
+  std::string_view c1 = std::vector<std::string>().at(0); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
+  c1 = std::vector<std::string>().at(0); // expected-warning {{object backing the pointer}}
+
+  // no warning on constructing from gsl-pointer
+  std::string_view c2 = std::vector<std::string_view>().at(0);
+
+  std::vector<std::string> local;
+  return local.at(0); // expected-warning {{address of stack memory associated with local variable 'local' returned}}
+}
+
 void testForBug49342()
 {
   auto it = std::iter<char>{} - 2; // Used to be false positive.

@hokein
Copy link
Collaborator Author

hokein commented Aug 16, 2024

Testing it a bit more, unfortunately, it has a false positive on the following case:

const char* kk() {
    std::optional<string_view> k;
    return k.value().data(); // dangling warning, but we should not diagnose it.
}

@hokein hokein force-pushed the lb-owner-container branch 2 times, most recently from e0f1fca to ea86000 Compare August 19, 2024 12:07
@hokein
Copy link
Collaborator Author

hokein commented Aug 19, 2024

I've refined the PR, and the new implementation eliminates the false positive mentioned above (I've also added a few more tests to verify this). Please take a look. The changes include some refactoring (I'm happy to split those out if they make the review process harder).

Copy link
Collaborator

@Xazax-hun Xazax-hun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for figuring this out!

Comment on lines 351 to 353
Path.push_back({!ReturnType->isReferenceType()
? IndirectLocalPathEntry::GslPointerInit
: IndirectLocalPathEntry::GslReferenceInit,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: simplify without !
ReturnType->isReferenceType() ? IndirectLocalPathEntry::GslReferenceInit: IndirectLocalPathEntry::GslPointerInit

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return;
}

if (auto *CCE = dyn_cast<CXXConstructExpr>(Call)) {
const auto *Ctor = CCE->getConstructor();
const CXXRecordDecl *RD = Ctor->getParent();
if (CCE->getNumArgs() > 0 && RD->hasAttr<PointerAttr>())
VisitPointerArg(Ctor->getParamDecl(0), CCE->getArgs()[0], true);
VisitPointerArg(Ctor, CCE->getArgs()[0]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a no-op change ? i.e., passing Ctor instead of the param decl ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we changed the Decl here, but I think it is NFC -- the lifetimebound code path uses it for emitting diagnostic notes, but we don't use it for GSL code path. And it is unclear why we only pass the parameter del for this particular case (the other cases in this function, we all pass the Callee).

construct from a gsl-owner object in a container.
@hokein hokein force-pushed the lb-owner-container branch from aa8dda3 to aba7fdb Compare September 6, 2024 09:52
@hokein hokein merged commit a918fa1 into llvm:main Sep 6, 2024
7 of 9 checks passed
@hokein hokein deleted the lb-owner-container branch September 6, 2024 10:37
@llvm-ci
Copy link
Collaborator

llvm-ci commented Sep 6, 2024

LLVM Buildbot has detected a new failure on builder sanitizer-x86_64-linux-qemu running on sanitizer-buildbot4 while building clang at step 2 "annotate".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/139/builds/3380

Here is the relevant piece of the build log for the reference
Step 2 (annotate) failure: 'python ../sanitizer_buildbot/sanitizers/zorg/buildbot/builders/sanitizers/buildbot_selector.py' (failure) (timed out)
...
[    5.134722] pci_bus 0000:00: root bus resource [mem 0x000a0000-0x000bffff window]
[    5.134722] pci_bus 0000:00: root bus resource [mem 0x80000000-0xafffffff window]
[    5.134722] pci_bus 0000:00: root bus resource [mem 0xc0000000-0xfebfffff window]
[    5.134722] pci_bus 0000:00: root bus resource [mem 0x480000000-0xc7fffffff window]
[    5.135722] pci_bus 0000:00: root bus resource [bus 00-ff]
[    5.143722] pci 0000:00:00.0: [8086:29c0] type 00 class 0x060000 conventional PCI endpoint
[    5.171994] pci 0000:00:01.0: [1234:1111] type 00 class 0x030000 conventional PCI endpoint
[    5.175170] pci 0000:00:01.0: BAR 0 [mem 0xfd000000-0xfdffffff pref]
[    5.176722] pci 0000:00:01.0: BAR 2 [mem 0xfebf0000-0xfebf0fff]
+ ssh -p 9837 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlPersist=30m -M -S /home/b/sanitizer-x86_64-linux-qemu/build/qemu_tmp/ssh-control-socket -i /home/b/qemu_image/debian.id_rsa root@localhost 'uname -a'
command timed out: 1200 seconds without output running [b'python', b'../sanitizer_buildbot/sanitizers/zorg/buildbot/builders/sanitizers/buildbot_selector.py'], attempting to kill
process killed by signal 9
program finished with exit code -1
elapsedTime=3970.558748
Step 29 (start LAM QEMU) failure: start LAM QEMU (failure)
...
[    4.797722] NET: Registered PF_NETLINK/PF_ROUTE protocol family
[    4.805401] audit: initializing netlink subsys (disabled)
[    4.812804] audit: type=2000 audit(1725621947.833:1): state=initialized audit_enabled=0 res=1
[    4.818022] thermal_sys: Registered thermal governor 'step_wise'
[    4.818835] thermal_sys: Registered thermal governor 'user_space'
[    4.825948] cpuidle: using governor menu
[    4.849832] PCI: ECAM [mem 0xb0000000-0xbfffffff] (base 0xb0000000) for domain 0000 [bus 00-ff]
[    4.851722] PCI: ECAM [mem 0xb0000000-0xbfffffff] reserved as E820 entry
[    4.852722] PCI: Using configuration type 1 for base access
[    4.854139] mtrr: your CPUs had inconsistent fixed MTRR settings
[    4.854825] mtrr: your CPUs had inconsistent variable MTRR settings
[    4.855174] mtrr: your CPUs had inconsistent MTRRdefType settings
[    4.855646] mtrr: probably your BIOS does not setup all CPUs.
[    4.855783] mtrr: corrected configuration.
[    4.866034] kprobes: kprobe jump-optimization is enabled. All kprobes are optimized if possible.
[    4.884278] HugeTLB: registered 2.00 MiB page size, pre-allocated 0 pages
[    4.884722] HugeTLB: 28 KiB vmemmap can be freed for a 2.00 MiB page
[    4.900680] ACPI: Added _OSI(Module Device)
[    4.900722] ACPI: Added _OSI(Processor Device)
[    4.900722] ACPI: Added _OSI(3.0 _SCP Extensions)
[    4.900722] ACPI: Added _OSI(Processor Aggregator Device)
[    4.933185] ACPI: 1 ACPI AML tables successfully acquired and loaded
[    4.984496] ACPI: Interpreter enabled
[    4.987722] ACPI: PM: (supports S0 S3 S4 S5)
[    4.988809] ACPI: Using IOAPIC for interrupt routing
[    4.990722] PCI: Using host bridge windows from ACPI; if necessary, use "pci=nocrs" and report a bug
[    4.990722] PCI: Using E820 reservations for host bridge windows
[    4.994722] ACPI: Enabled 2 GPEs in block 00 to 3F
[    5.112119] ACPI: PCI Root Bridge [PCI0] (domain 0000 [bus 00-ff])
[    5.112722] acpi PNP0A08:00: _OSC: OS supports [ExtendedConfig ASPM ClockPM Segments MSI HPX-Type3]
[    5.115722] acpi PNP0A08:00: _OSC: platform does not support [LTR]
[    5.115722] acpi PNP0A08:00: _OSC: OS now controls [PME PCIeCapability]
[    5.132176] PCI host bridge to bus 0000:00
[    5.132867] pci_bus 0000:00: root bus resource [io  0x0000-0x0cf7 window]
[    5.134722] pci_bus 0000:00: root bus resource [io  0x0d00-0xffff window]
[    5.134722] pci_bus 0000:00: root bus resource [mem 0x000a0000-0x000bffff window]
[    5.134722] pci_bus 0000:00: root bus resource [mem 0x80000000-0xafffffff window]
[    5.134722] pci_bus 0000:00: root bus resource [mem 0xc0000000-0xfebfffff window]
[    5.134722] pci_bus 0000:00: root bus resource [mem 0x480000000-0xc7fffffff window]
[    5.135722] pci_bus 0000:00: root bus resource [bus 00-ff]
[    5.143722] pci 0000:00:00.0: [8086:29c0] type 00 class 0x060000 conventional PCI endpoint
[    5.171994] pci 0000:00:01.0: [1234:1111] type 00 class 0x030000 conventional PCI endpoint
[    5.175170] pci 0000:00:01.0: BAR 0 [mem 0xfd000000-0xfdffffff pref]
[    5.176722] pci 0000:00:01.0: BAR 2 [mem 0xfebf0000-0xfebf0fff]
+ ssh -p 9837 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ControlPersist=30m -M -S /home/b/sanitizer-x86_64-linux-qemu/build/qemu_tmp/ssh-control-socket -i /home/b/qemu_image/debian.id_rsa root@localhost 'uname -a'

command timed out: 1200 seconds without output running [b'python', b'../sanitizer_buildbot/sanitizers/zorg/buildbot/builders/sanitizers/buildbot_selector.py'], attempting to kill
process killed by signal 9
program finished with exit code -1
elapsedTime=3970.558748

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

No -Wdangling-gsl warning if a gsl-pointer object is construct from a gsl-owner object in a container.
5 participants