[clang] [clang] Diagnose dangling references for parenthesized aggregate initialization. (PR #117690)

Haojian Wu via cfe-commits cfe-commits at lists.llvm.org
Tue Nov 26 07:08:23 PST 2024


https://github.com/hokein updated https://github.com/llvm/llvm-project/pull/117690

>From 8d159e95f67b4954e1a273658e612d05498748ab Mon Sep 17 00:00:00 2001
From: Haojian Wu <hokein.wu at gmail.com>
Date: Tue, 26 Nov 2024 10:31:12 +0100
Subject: [PATCH 1/2] [clang] Diagnose dangling references for parethesized
 aggregate initialization.

---
 clang/docs/ReleaseNotes.rst                   |  2 +
 clang/lib/Sema/CheckExprLifetime.cpp          | 15 +++++
 clang/test/AST/ByteCode/records.cpp           |  6 +-
 ...xx20-warn-dangling-paren-list-agg-init.cpp | 59 +++++++++++++++++++
 clang/test/SemaCXX/paren-list-agg-init.cpp    |  4 ++
 5 files changed, 84 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/SemaCXX/cxx20-warn-dangling-paren-list-agg-init.cpp

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 954fe61f3d1d69..af9ab752a0e22e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -587,6 +587,8 @@ Improvements to Clang's diagnostics
 - For an rvalue reference bound to a temporary struct with an integer member, Clang will detect constant integer overflow
   in the initializer for the integer member (#GH46755).
 
+- Clang now diagnoses dangling references for C++20's parenthesized aggregate initialization (#101957).
+
 Improvements to Clang's time-trace
 ----------------------------------
 
diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index 6cdd4dc629e50a..b12c1eb139aa57 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -13,6 +13,7 @@
 #include "clang/Sema/Initialization.h"
 #include "clang/Sema/Sema.h"
 #include "llvm/ADT/PointerIntPair.h"
+#include "llvm/ADT/STLExtras.h"
 
 namespace clang::sema {
 namespace {
@@ -203,6 +204,7 @@ struct IndirectLocalPathEntry {
     GslPointerInit,
     GslPointerAssignment,
     DefaultArg,
+    ParenAggInit,
   } Kind;
   Expr *E;
   union {
@@ -961,6 +963,17 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
   if (isa<CallExpr>(Init) || isa<CXXConstructExpr>(Init))
     return visitFunctionCallArguments(Path, Init, Visit);
 
+  if (auto *CPE = dyn_cast<CXXParenListInitExpr>(Init)) {
+    Path.push_back({IndirectLocalPathEntry::ParenAggInit, CPE});
+    for (auto *I : CPE->getInitExprs()) {
+      if (I->isGLValue())
+        visitLocalsRetainedByReferenceBinding(Path, I, RK_ReferenceBinding,
+                                              Visit);
+      else
+        visitLocalsRetainedByInitializer(Path, I, Visit, true);
+    }
+    Path.pop_back();
+  }
   switch (Init->getStmtClass()) {
   case Stmt::UnaryOperatorClass: {
     auto *UO = cast<UnaryOperator>(Init);
@@ -1057,6 +1070,7 @@ static SourceRange nextPathEntryRange(const IndirectLocalPath &Path, unsigned I,
     case IndirectLocalPathEntry::GslReferenceInit:
     case IndirectLocalPathEntry::GslPointerInit:
     case IndirectLocalPathEntry::GslPointerAssignment:
+    case IndirectLocalPathEntry::ParenAggInit:
       // These exist primarily to mark the path as not permitting or
       // supporting lifetime extension.
       break;
@@ -1368,6 +1382,7 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
       switch (Elem.Kind) {
       case IndirectLocalPathEntry::AddressOf:
       case IndirectLocalPathEntry::LValToRVal:
+      case IndirectLocalPathEntry::ParenAggInit:
         // These exist primarily to mark the path as not permitting or
         // supporting lifetime extension.
         break;
diff --git a/clang/test/AST/ByteCode/records.cpp b/clang/test/AST/ByteCode/records.cpp
index 2eeaafc04c516c..4601aface135ee 100644
--- a/clang/test/AST/ByteCode/records.cpp
+++ b/clang/test/AST/ByteCode/records.cpp
@@ -1015,11 +1015,13 @@ namespace ParenInit {
   };
 
   /// Not constexpr!
-  O o1(0);
+  O o1(0); // both-warning {{temporary whose address is used as value of}}
+  // FIXME: the secondary warning message is bogus, would be nice to suppress it.
   constinit O o2(0); // both-error {{variable does not have a constant initializer}} \
                      // both-note {{required by 'constinit' specifier}} \
                      // both-note {{reference to temporary is not a constant expression}} \
-                     // both-note {{temporary created here}}
+                     // both-note {{temporary created here}} \
+                     // both-warning {{temporary whose address is used as value}}
 
 
   /// Initializing an array.
diff --git a/clang/test/SemaCXX/cxx20-warn-dangling-paren-list-agg-init.cpp b/clang/test/SemaCXX/cxx20-warn-dangling-paren-list-agg-init.cpp
new file mode 100644
index 00000000000000..645a7caba714e2
--- /dev/null
+++ b/clang/test/SemaCXX/cxx20-warn-dangling-paren-list-agg-init.cpp
@@ -0,0 +1,59 @@
+// RUN: %clang_cc1 -verify -std=c++20 %s -fsyntax-only
+
+namespace std {
+template <class T> struct remove_reference { typedef T type; };
+template <class T> struct remove_reference<T&> { typedef T type; };
+template <class T> struct remove_reference<T&&> { typedef T type; };
+
+template <class T> typename remove_reference<T>::type &&move(T &&t);
+} // namespace std
+
+// dcl.init 16.6.2.2
+struct A {
+  int a;
+  int&& r;
+};
+
+int f();
+int n = 10;
+
+A a1{1, f()}; // OK, lifetime is extended for direct-list-initialization
+// well-formed, but dangling reference
+A a2(1, f()); // expected-warning {{temporary whose address is used as value}}
+// well-formed, but dangling reference
+A a4(1.0, 1); // expected-warning {{temporary whose address is used as value}}
+A a5(1.0, std::move(n));  // OK
+
+
+
+struct B {
+  const int& r;
+};
+B test(int local) {
+  return B(1); // expected-warning {{returning address}}
+  return B(local); // expected-warning {{address of stack memory}}
+}
+
+void f(B b);
+void test2(int local) {
+  // No diagnostic on the following cases where both the aggregate object and
+  // temporary end at the end of the full expression.
+  f(B(1));
+  f(B(local));
+}
+
+// Test nested struct.
+struct C {
+  B b;
+};
+
+struct D {
+  C c;
+};
+
+C c1(B(
+  1  // expected-warning {{temporary whose address is used as value}}
+)); 
+D d1(C(B(
+  1  // expected-warning {{temporary whose address is used as value}}
+)));
diff --git a/clang/test/SemaCXX/paren-list-agg-init.cpp b/clang/test/SemaCXX/paren-list-agg-init.cpp
index cc2a9d88dd4a6e..61afba85e1dff9 100644
--- a/clang/test/SemaCXX/paren-list-agg-init.cpp
+++ b/clang/test/SemaCXX/paren-list-agg-init.cpp
@@ -116,6 +116,7 @@ void foo(int n) { // expected-note {{declared here}}
   B b2(A(1), {}, 1);
   // beforecxx20-warning at -1 {{aggregate initialization of type 'A' from a parenthesized list of values is a C++20 extension}}
   // beforecxx20-warning at -2 {{aggregate initialization of type 'B' from a parenthesized list of values is a C++20 extension}}
+  // expected-warning at -3 {{temporary whose address is used as value of local variable 'b2' will be destroyed at the end of the full-expression}}
 
   C c(A(1), 1, 2, 3, 4);
   // expected-error at -1 {{array initializer must be an initializer list}}
@@ -262,9 +263,12 @@ struct O {
 
 O o1(0, 0, 0); // no-error
 // beforecxx20-warning at -1 {{aggregate initialization of type 'O' from a parenthesized list of values is a C++20 extension}}
+// expected-warning at -2 {{temporary whose address is used as value of local variable 'o1' will be destroyed at the end of the full-expression}}
+// expected-warning at -3 {{temporary whose address is used as value of local variable 'o1' will be destroyed at the end of the full-expression}}
 
 O o2(0, 0); // no-error
 // beforecxx20-warning at -1 {{aggregate initialization of type 'O' from a parenthesized list of values is a C++20 extension}}
+// expected-warning at -2 {{temporary whose address is used as value of local variable 'o2' will be destroyed at the end of the full-expression}}
 
 O o3(0);
 // expected-error at -1 {{reference member of type 'int &&' uninitialized}}

>From 83e8aa68c98a1ddb90f2433f3863ed432d9c40d3 Mon Sep 17 00:00:00 2001
From: Haojian Wu <hokein.wu at gmail.com>
Date: Tue, 26 Nov 2024 16:07:52 +0100
Subject: [PATCH 2/2] Use RevertToOldSizeRAII

---
 clang/lib/Sema/CheckExprLifetime.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp
index b12c1eb139aa57..8f19b1d0d8379c 100644
--- a/clang/lib/Sema/CheckExprLifetime.cpp
+++ b/clang/lib/Sema/CheckExprLifetime.cpp
@@ -13,7 +13,6 @@
 #include "clang/Sema/Initialization.h"
 #include "clang/Sema/Sema.h"
 #include "llvm/ADT/PointerIntPair.h"
-#include "llvm/ADT/STLExtras.h"
 
 namespace clang::sema {
 namespace {
@@ -964,6 +963,7 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
     return visitFunctionCallArguments(Path, Init, Visit);
 
   if (auto *CPE = dyn_cast<CXXParenListInitExpr>(Init)) {
+    RevertToOldSizeRAII RAII(Path);
     Path.push_back({IndirectLocalPathEntry::ParenAggInit, CPE});
     for (auto *I : CPE->getInitExprs()) {
       if (I->isGLValue())
@@ -972,7 +972,6 @@ static void visitLocalsRetainedByInitializer(IndirectLocalPath &Path,
       else
         visitLocalsRetainedByInitializer(Path, I, Visit, true);
     }
-    Path.pop_back();
   }
   switch (Init->getStmtClass()) {
   case Stmt::UnaryOperatorClass: {



More information about the cfe-commits mailing list