[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 01:39:18 PST 2024
https://github.com/hokein created https://github.com/llvm/llvm-project/pull/117690
Fixes #101957
>From c92065b91a6d1898c1d61ba95c1108501a259944 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] [clang] Diagnose dangling references for parethesized
aggregate initialization.
---
clang/docs/ReleaseNotes.rst | 2 +
clang/lib/Sema/CheckExprLifetime.cpp | 32 ++++++++++++++
clang/test/AST/ByteCode/records.cpp | 6 ++-
...xx20-warn-dangling-paren-list-agg-init.cpp | 42 +++++++++++++++++++
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..16b5fba40692e2 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,16 @@ 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);
+ }
+ }
switch (Init->getStmtClass()) {
case Stmt::UnaryOperatorClass: {
auto *UO = cast<UnaryOperator>(Init);
@@ -1057,6 +1069,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;
@@ -1193,6 +1206,24 @@ checkExprLifetimeImpl(Sema &SemaRef, const InitializedEntity *InitEntity,
if (pathContainsInit(Path))
return false;
+ // In C++20, parenthesized aggregate initialization does not extend the
+ // lifetime of a temporary object bound to a reference. This can lead to
+ // dangling references and clang diagnoses these cases:
+ //
+ // struct A { const int& r; };
+ // A a1(1); // well-formed, but results in a dangling reference.
+ //
+ // However, to reduce noise, we suppress diagnostics for cases where
+ // both the aggregate and the temporary object are destroyed at the end
+ // of the full expression:
+ // f(A(1));
+ if (!Path.empty() &&
+ llvm::any_of(llvm::ArrayRef(Path).drop_front(), [](auto &P) {
+ return P.Kind == IndirectLocalPathEntry::ParenAggInit;
+ })) {
+ return false;
+ }
+
SemaRef.Diag(DiagLoc, diag::warn_dangling_variable)
<< RK << !InitEntity->getParent()
<< ExtendingEntity->getDecl()->isImplicit()
@@ -1368,6 +1399,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..8db0d5407cdbce
--- /dev/null
+++ b/clang/test/SemaCXX/cxx20-warn-dangling-paren-list-agg-init.cpp
@@ -0,0 +1,42 @@
+// 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;
+
+// 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));
+}
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}}
More information about the cfe-commits
mailing list