[clang] [clang-tools-extra] Reapply "[clang] Limit lifetimes of temporaries to the full expression (#170517)" (PR #175816)
Paul Kirth via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 8 10:50:56 PDT 2026
https://github.com/ilovepi updated https://github.com/llvm/llvm-project/pull/175816
>From 97a30af46c02440f2498ff90f9befab99d6eca9b Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Fri, 12 Dec 2025 09:03:44 -0800
Subject: [PATCH 1/8] Reapply "[clang] Limit lifetimes of temporaries to the
full expression (#170517)"
This reverts commit 6d38c876478dac4a42f9d6e37692348deabf6a25. The
current version only works when exceptions are not enabled until we
determine how to resolve issues around broken dominance relationships
with the def-use chain.
---
clang/docs/ReleaseNotes.rst | 11 +++
clang/include/clang/Basic/CodeGenOptions.def | 4 +
clang/include/clang/Options/Options.td | 5 +
clang/lib/CodeGen/CGCall.cpp | 25 ++++-
clang/lib/CodeGen/CGCleanup.cpp | 7 +-
clang/test/CodeGen/lifetime-bug-2.c | 58 +++++++++++
clang/test/CodeGen/lifetime-bug.cpp | 58 +++++++++++
clang/test/CodeGen/lifetime-call-temp.c | 98 +++++++++++++++++++
clang/test/CodeGen/lifetime-invoke-c.c | 36 +++++++
.../CodeGenCXX/aggregate-lifetime-invoke.cpp | 40 ++++++++
.../CodeGenCXX/amdgcn-call-with-aggarg.cc | 19 ++++
.../CodeGenCXX/stack-reuse-miscompile.cpp | 4 +
clang/test/CodeGenCoroutines/pr59181.cpp | 4 +
13 files changed, 365 insertions(+), 4 deletions(-)
create mode 100644 clang/test/CodeGen/lifetime-bug-2.c
create mode 100644 clang/test/CodeGen/lifetime-bug.cpp
create mode 100644 clang/test/CodeGen/lifetime-call-temp.c
create mode 100644 clang/test/CodeGen/lifetime-invoke-c.c
create mode 100644 clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp
create mode 100644 clang/test/CodeGenCXX/amdgcn-call-with-aggarg.cc
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 2da7175b51ea3..9f13c8329dd73 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -36,6 +36,17 @@ latest release, please see the `Clang Web Site <https://clang.llvm.org>`_ or the
Potentially Breaking Changes
============================
+- When exceptions are disabled, Clang is now more precise with regards to the
+ lifetime of temporary objects such as when aggregates are passed by value to
+ a function, resulting in better sharing of stack slots and reduced stack
+ usage. This change can lead to use-after-scope related issues in code that
+ unintentionally relied on the previous behavior. If recompiling with
+ ``-fsanitize=address`` shows a use-after-scope warning, then this is likely
+ the case, and the report printed should be able to help users pinpoint where
+ the use-after-scope is occurring. Users can use ``-Xclang
+ -sloppy-temporary-lifetimes`` to retain the old behavior until they are able
+ to find and resolve issues in their code.
+
C/C++ Language Potentially Breaking Changes
-------------------------------------------
diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index 6cee3e8acda3c..4999f3eb32d77 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -498,6 +498,10 @@ ENUM_CODEGENOPT(ZeroCallUsedRegs, ZeroCallUsedRegsKind,
/// non-deleting destructors. (No effect on Microsoft ABI.)
CODEGENOPT(CtorDtorReturnThis, 1, 0, Benign)
+/// Set via -Xclang -sloppy-temporary-lifetimes to disable emission of lifetime
+/// marker intrinsic calls.
+CODEGENOPT(NoLifetimeMarkersForTemporaries, 1, 0, Benign)
+
/// Enables emitting Import Call sections on supported targets that can be used
/// by the Windows kernel to enable import call optimization.
CODEGENOPT(ImportCallOptimization, 1, 0, Benign)
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index bffb3dfb27485..e5098d1957faa 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -8496,6 +8496,11 @@ def import_call_optimization : Flag<["-"], "import-call-optimization">,
def replaceable_function: Joined<["-"], "loader-replaceable-function=">,
MarshallingInfoStringVector<CodeGenOpts<"LoaderReplaceableFunctionNames">>;
+def sloppy_temporary_lifetimes
+ : Flag<["-"], "sloppy-temporary-lifetimes">,
+ HelpText<"Don't emit lifetime markers for temporary objects">,
+ MarshallingInfoFlag<CodeGenOpts<"NoLifetimeMarkersForTemporaries">>;
+
} // let Visibility = [CC1Option]
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index b7b79e7051181..6f09d5a9aadbf 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5067,7 +5067,30 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
return;
}
- args.add(EmitAnyExprToTemp(E), type);
+ AggValueSlot ArgSlot = AggValueSlot::ignored();
+ // For arguments with aggregate type, create an alloca to store
+ // the value. If the argument's type has a destructor, that destructor
+ // will run at the end of the full-expression; emit matching lifetime
+ // markers.
+ //
+ // FIXME: For types which don't have a destructor, consider using a
+ // narrower lifetime bound.
+ // FIXME: This should work fine w/ exceptions, but somehow breaks the
+ // dominance relationship in the def-use chain.
+ if (!CGM.getLangOpts().Exceptions &&
+ hasAggregateEvaluationKind(E->getType())) {
+ RawAddress ArgSlotAlloca = Address::invalid();
+ ArgSlot = CreateAggTemp(E->getType(), "agg.tmp", &ArgSlotAlloca);
+
+ // Emit a lifetime start/end for this temporary at the end of the full
+ // expression.
+ if (!CGM.getCodeGenOpts().NoLifetimeMarkersForTemporaries &&
+ EmitLifetimeStart(ArgSlotAlloca.getPointer()))
+ pushFullExprCleanup<CallLifetimeEnd>(CleanupKind::NormalEHLifetimeMarker,
+ ArgSlotAlloca);
+ }
+
+ args.add(EmitAnyExpr(E, ArgSlot), type);
}
QualType CodeGenFunction::getVarArgType(const Expr *Arg) {
diff --git a/clang/lib/CodeGen/CGCleanup.cpp b/clang/lib/CodeGen/CGCleanup.cpp
index 9adc928c11585..7d6c50aaab415 100644
--- a/clang/lib/CodeGen/CGCleanup.cpp
+++ b/clang/lib/CodeGen/CGCleanup.cpp
@@ -700,11 +700,12 @@ void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough,
// If this is a normal cleanup, then having a prebranched
// fallthrough implies that the fallthrough source unconditionally
- // jumps here.
+ // jumps here, unless its for a lifetime marker.
assert(!Scope.isNormalCleanup() || !HasPrebranchedFallthrough ||
+ Scope.isLifetimeMarker() ||
(Scope.getNormalBlock() &&
- FallthroughSource->getTerminator()->getSuccessor(0)
- == Scope.getNormalBlock()));
+ FallthroughSource->getTerminator()->getSuccessor(0) ==
+ Scope.getNormalBlock()));
bool RequiresNormalCleanup = false;
if (Scope.isNormalCleanup() &&
diff --git a/clang/test/CodeGen/lifetime-bug-2.c b/clang/test/CodeGen/lifetime-bug-2.c
new file mode 100644
index 0000000000000..76dd5b95d0216
--- /dev/null
+++ b/clang/test/CodeGen/lifetime-bug-2.c
@@ -0,0 +1,58 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --check-globals none --version 6
+// RUN: %clang_cc1 -O2 -emit-llvm -disable-llvm-passes %s -o - | FileCheck %s
+
+struct {
+} __trans_tmp_85;
+struct bkey_s_c {
+} bch2_trans_update_extent_k;
+int bch2_trans_update_extent_k_0;
+struct bkey_s_c bch2_btree_iter_peek_max();
+// CHECK-LABEL: define dso_local void @bch2_trans_update_extent(
+// CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[DONE:%.*]] = alloca i8, align 1
+// CHECK-NEXT: [[TMP:%.*]] = alloca [[STRUCT_BKEY_S_C:%.*]], align 1
+// CHECK-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_ANON:%.*]], align 1
+// CHECK-NEXT: [[UNDEF_AGG_TMP:%.*]] = alloca [[STRUCT_BKEY_S_C]], align 1
+// CHECK-NEXT: [[CLEANUP_DEST_SLOT:%.*]] = alloca i32, align 4
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[DONE]]) #[[ATTR4:[0-9]+]]
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP]]) #[[ATTR4]]
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG_TMP]]) #[[ATTR4]]
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 [[AGG_TMP]], ptr align 1 @__trans_tmp_85, i64 0, i1 false), !tbaa.struct [[TBAA_STRUCT6:![0-9]+]]
+// CHECK-NEXT: call void (...) @bch2_btree_iter_peek_max()
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP]]) #[[ATTR4]]
+// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 @bch2_trans_update_extent_k, ptr align 1 [[UNDEF_AGG_TMP]], i64 0, i1 false), !tbaa.struct [[TBAA_STRUCT6]]
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP]]) #[[ATTR4]]
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr @bch2_trans_update_extent_k_0, align 4, !tbaa [[INT_TBAA2:![0-9]+]]
+// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// CHECK-NEXT: br i1 [[TOBOOL]], label %[[IF_THEN:.*]], label %[[IF_END:.*]]
+// CHECK: [[IF_THEN]]:
+// CHECK-NEXT: store i32 2, ptr [[CLEANUP_DEST_SLOT]], align 4
+// CHECK-NEXT: br label %[[CLEANUP:.*]]
+// CHECK: [[IF_END]]:
+// CHECK-NEXT: store i32 0, ptr [[CLEANUP_DEST_SLOT]], align 4
+// CHECK-NEXT: br label %[[CLEANUP]]
+// CHECK: [[CLEANUP]]:
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[DONE]]) #[[ATTR4]]
+// CHECK-NEXT: [[CLEANUP_DEST:%.*]] = load i32, ptr [[CLEANUP_DEST_SLOT]], align 4
+// CHECK-NEXT: switch i32 [[CLEANUP_DEST]], label %[[UNREACHABLE:.*]] [
+// CHECK-NEXT: i32 0, label %[[CLEANUP_CONT:.*]]
+// CHECK-NEXT: i32 2, label %[[ERR:.*]]
+// CHECK-NEXT: ]
+// CHECK: [[CLEANUP_CONT]]:
+// CHECK-NEXT: br label %[[ERR]]
+// CHECK: [[ERR]]:
+// CHECK-NEXT: ret void
+// CHECK: [[UNREACHABLE]]:
+// CHECK-NEXT: unreachable
+//
+void bch2_trans_update_extent() {
+ {
+ _Bool done;
+ bch2_trans_update_extent_k = bch2_btree_iter_peek_max((0, __trans_tmp_85));
+ if (bch2_trans_update_extent_k_0)
+ goto err;
+ }
+err:
+}
+
diff --git a/clang/test/CodeGen/lifetime-bug.cpp b/clang/test/CodeGen/lifetime-bug.cpp
new file mode 100644
index 0000000000000..641b3ae7762ae
--- /dev/null
+++ b/clang/test/CodeGen/lifetime-bug.cpp
@@ -0,0 +1,58 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --check-globals none --version 6
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -emit-llvm -disable-llvm-passes -target-cpu x86-64 -fexceptions -x c++ %s -o - | FileCheck %s
+
+void a();
+struct b {};
+typedef enum {} c;
+c d;
+struct e {
+ e(b = b());
+};
+// CHECK-LABEL: define dso_local void @_Z1fv(
+// CHECK-SAME: ) #[[ATTR0:[0-9]+]] personality ptr @__gxx_personality_v0 {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[AGG_TEMP:%.*]] = alloca [[STRUCT_B:%.*]], align 1
+// CHECK-NEXT: [[EXN_SLOT:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: [[EHSELECTOR_SLOT:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr @d, align 4, !tbaa [[_ZTS1C_TBAA6:![0-9]+]]
+// CHECK-NEXT: switch i32 [[TMP0]], label %[[SW_DEFAULT:.*]] [
+// CHECK-NEXT: i32 1, label %[[SW_BB:.*]]
+// CHECK-NEXT: ]
+// CHECK: [[SW_BB]]:
+// CHECK-NEXT: [[CALL:%.*]] = call noalias noundef nonnull ptr @_Znwm(i64 noundef 1) #[[ATTR4:[0-9]+]]
+// CHECK-NEXT: invoke void @_ZN1eC1E1b(ptr noundef nonnull align 1 dereferenceable(1) [[CALL]])
+// CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[LPAD:.*]]
+// CHECK: [[INVOKE_CONT]]:
+// CHECK-NEXT: call void @_Z1av()
+// CHECK-NEXT: br label %[[SW_EPILOG:.*]]
+// CHECK: [[LPAD]]:
+// CHECK-NEXT: [[TMP1:%.*]] = landingpad { ptr, i32 }
+// CHECK-NEXT: cleanup
+// CHECK-NEXT: [[TMP2:%.*]] = extractvalue { ptr, i32 } [[TMP1]], 0
+// CHECK-NEXT: store ptr [[TMP2]], ptr [[EXN_SLOT]], align 8
+// CHECK-NEXT: [[TMP3:%.*]] = extractvalue { ptr, i32 } [[TMP1]], 1
+// CHECK-NEXT: store i32 [[TMP3]], ptr [[EHSELECTOR_SLOT]], align 4
+// CHECK-NEXT: call void @_ZdlPvm(ptr noundef [[CALL]], i64 noundef 1) #[[ATTR5:[0-9]+]]
+// CHECK-NEXT: br label %[[EH_RESUME:.*]]
+// CHECK: [[SW_DEFAULT]]:
+// CHECK-NEXT: call void @_Z1av()
+// CHECK-NEXT: br label %[[SW_EPILOG]]
+// CHECK: [[SW_EPILOG]]:
+// CHECK-NEXT: ret void
+// CHECK: [[EH_RESUME]]:
+// CHECK-NEXT: [[EXN:%.*]] = load ptr, ptr [[EXN_SLOT]], align 8
+// CHECK-NEXT: [[SEL:%.*]] = load i32, ptr [[EHSELECTOR_SLOT]], align 4
+// CHECK-NEXT: [[LPAD_VAL:%.*]] = insertvalue { ptr, i32 } poison, ptr [[EXN]], 0
+// CHECK-NEXT: [[LPAD_VAL1:%.*]] = insertvalue { ptr, i32 } [[LPAD_VAL]], i32 [[SEL]], 1
+// CHECK-NEXT: resume { ptr, i32 } [[LPAD_VAL1]]
+//
+void f() {
+ switch (d) {
+ case 1:
+ new e;
+ a();
+ break;
+ default:
+ a();
+ }
+}
diff --git a/clang/test/CodeGen/lifetime-call-temp.c b/clang/test/CodeGen/lifetime-call-temp.c
new file mode 100644
index 0000000000000..3bc68b5e8024a
--- /dev/null
+++ b/clang/test/CodeGen/lifetime-call-temp.c
@@ -0,0 +1,98 @@
+// RUN: %clang -cc1 -triple x86_64-apple-macos -O1 -disable-llvm-passes %s \
+// RUN: -emit-llvm -o - | FileCheck %s --implicit-check-not=llvm.lifetime
+// RUN: %clang -cc1 -xc++ -std=c++17 -triple x86_64-apple-macos -O1 \
+// RUN: -disable-llvm-passes %s -emit-llvm -o - -Wno-return-type-c-linkage | \
+// RUN: FileCheck %s --implicit-check-not=llvm.lifetime --check-prefixes=CHECK,CXX
+// RUN: %clang -cc1 -xobjective-c -triple x86_64-apple-macos -O1 \
+// RUN: -disable-llvm-passes %s -emit-llvm -o - | \
+// RUN: FileCheck %s --implicit-check-not=llvm.lifetime --check-prefixes=CHECK,OBJC
+// RUN: %clang -cc1 -triple x86_64-apple-macos -O1 -disable-llvm-passes %s \
+// RUN: -emit-llvm -o - -sloppy-temporary-lifetimes | \
+// RUN: FileCheck %s --implicit-check-not=llvm.lifetime --check-prefixes=SLOPPY
+
+typedef struct { int x[100]; } aggregate;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void takes_aggregate(aggregate);
+aggregate gives_aggregate();
+
+// CHECK-LABEL: define void @t1
+void t1() {
+ takes_aggregate(gives_aggregate());
+
+ // CHECK: [[AGGTMP:%.*]] = alloca %struct.aggregate, align 8
+ // CHECK: call void @llvm.lifetime.start.p0(ptr [[AGGTMP]])
+ // CHECK: call void{{.*}} @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGGTMP]])
+ // CHECK: call void @takes_aggregate(ptr noundef byval(%struct.aggregate) align 8 [[AGGTMP]])
+ // CHECK: call void @llvm.lifetime.end.p0(ptr [[AGGTMP]])
+
+ // SLOPPY: [[AGGTMP:%.*]] = alloca %struct.aggregate, align 8
+ // SLOPPY-NEXT: call void (ptr, ...) @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGGTMP]])
+ // SLOPPY-NEXT: call void @takes_aggregate(ptr noundef byval(%struct.aggregate) align 8 [[AGGTMP]])
+}
+
+// CHECK: declare {{.*}}llvm.lifetime.start
+// CHECK: declare {{.*}}llvm.lifetime.end
+
+#ifdef __cplusplus
+// CXX: define void @t2
+void t2() {
+ struct S {
+ S(aggregate) {}
+ };
+ S{gives_aggregate()};
+
+ // CXX: [[AGG:%.*]] = alloca %struct.aggregate
+ // CXX: call void @llvm.lifetime.start.p0(ptr [[AGG]]
+ // CXX: call void @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGG]])
+ // CXX: call void @_ZZ2t2EN1SC1E9aggregate(ptr {{.*}}, ptr {{.*}} byval(%struct.aggregate) align 8 [[AGG]])
+ // CXX: call void @llvm.lifetime.end.p0(ptr [[AGG]]
+}
+
+struct Dtor {
+ ~Dtor();
+};
+
+void takes_dtor(Dtor);
+Dtor gives_dtor();
+
+// CXX: define void @t3
+void t3() {
+ takes_dtor(gives_dtor());
+
+ // CXX: [[AGG:%.*]] = alloca %struct.Dtor
+ // CXX: call void @llvm.lifetime.start.p0(ptr [[AGG]])
+ // CXX: call void @gives_dtor(ptr{{.*}}sret(%struct.Dtor) align 1 [[AGG]])
+ // CXX: call void @takes_dtor(ptr noundef [[AGG]])
+ // CXX: call void @_ZN4DtorD1Ev(ptr {{.*}} [[AGG]])
+ // CXX: call void @llvm.lifetime.end.p0(ptr [[AGG]])
+ // CXX: ret void
+}
+
+#endif
+
+#ifdef __OBJC__
+
+ at interface X
+-m:(aggregate)x;
+ at end
+
+// OBJC: define void @t4
+void t4(X *x) {
+ [x m: gives_aggregate()];
+
+ // OBJC: [[AGG:%.*]] = alloca %struct.aggregate
+ // OBJC: call void @llvm.lifetime.start.p0(ptr [[AGG]]
+ // OBJC: call void{{.*}} @gives_aggregate(ptr{{.*}}sret(%struct.aggregate) align 4 [[AGGTMP]])
+ // OBJC: call {{.*}}@objc_msgSend
+ // OBJC: call void @llvm.lifetime.end.p0(ptr [[AGG]]
+}
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/clang/test/CodeGen/lifetime-invoke-c.c b/clang/test/CodeGen/lifetime-invoke-c.c
new file mode 100644
index 0000000000000..6a43b3562a2e8
--- /dev/null
+++ b/clang/test/CodeGen/lifetime-invoke-c.c
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -disable-llvm-passes | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -disable-llvm-passes -fexceptions | FileCheck %s --check-prefix=EXCEPTIONS
+
+struct Trivial {
+ int x[100];
+};
+
+void cleanup(int *p) {}
+void func(struct Trivial t);
+struct Trivial gen(void);
+
+// CHECK-LABEL: define dso_local void @test()
+void test() {
+ int x __attribute__((cleanup(cleanup)));
+
+ // CHECK: %[[AGG1:.*]] = alloca %struct.Trivial
+ // CHECK: %[[AGG2:.*]] = alloca %struct.Trivial
+ // CHECK: call void @llvm.lifetime.start.p0(ptr %[[AGG1]])
+ // CHECK: call void @gen(ptr{{.*}} sret(%struct.Trivial){{.*}} %[[AGG1]])
+ // CHECK: call void @func(ptr{{.*}} %[[AGG1]])
+ // CHECK: call void @llvm.lifetime.start.p0(ptr %[[AGG2]])
+ // CHECK: call void @gen(ptr{{.*}} sret(%struct.Trivial){{.*}} %[[AGG2]])
+ // CHECK: call void @func(ptr{{.*}} %[[AGG2]])
+ // CHECK: call void @llvm.lifetime.end.p0(ptr %[[AGG2]])
+ // CHECK: call void @llvm.lifetime.end.p0(ptr %[[AGG1]])
+ // CHECK: call void @cleanup
+
+ // EXCEPTIONS: %[[AGG1:.*]] = alloca %struct.Trivial
+ // EXCEPTIONS: %[[AGG2:.*]] = alloca %struct.Trivial
+ // EXCEPTIONS-NOT: call void @llvm.lifetime.start.p0(ptr %[[AGG1]])
+ // EXCEPTIONS-NOT: call void @llvm.lifetime.start.p0(ptr %[[AGG2]])
+ // EXCEPTIONS-NOT: call void @llvm.lifetime.end.p0(ptr %[[AGG2]])
+ // EXCEPTIONS-NOT: call void @llvm.lifetime.end.p0(ptr %[[AGG1]])
+ func(gen());
+ func(gen());
+}
diff --git a/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp
new file mode 100644
index 0000000000000..c3ce91aea73f3
--- /dev/null
+++ b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp
@@ -0,0 +1,40 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -fexceptions -fcxx-exceptions | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -fexceptions -fcxx-exceptions -sloppy-temporary-lifetimes | FileCheck %s
+
+// COM: Note that this test case would break if we allowed tighter lifetimes to
+// run when exceptions were enabled. If we make them work together this test
+// will need to be updated.
+
+extern "C" {
+
+struct Trivial {
+ int x[100];
+};
+
+void func_that_throws(Trivial t);
+
+// CHECK-LABEL: define{{.*}} void @test()
+void test() {
+ // CHECK: %[[AGG1:.*]] = alloca %struct.Trivial
+ // CHECK: %[[AGG2:.*]] = alloca %struct.Trivial
+
+ // CHECK: invoke void @func_that_throws(ptr{{.*}} %[[AGG1]])
+ // CHECK-NEXT: to label %[[CONT:.*]] unwind label %[[LPAD:.*]]
+
+ // CHECK: [[CONT]]:
+ // CHECK: invoke void @func_that_throws(ptr{{.*}} %[[AGG2]])
+ // CHECK-NEXT: to label %[[CONT:.*]] unwind label %[[LPAD:.*]]
+
+ // CHECK: [[LPAD]]:
+ // CHECK: landingpad
+
+ // CHECK-NOT: llvm.lifetime.start
+ // CHECK-NOT: llvm.lifetime.end
+
+ try {
+ func_that_throws(Trivial{0});
+ func_that_throws(Trivial{0});
+ } catch (...) {
+ }
+}
+} // end extern "C"
diff --git a/clang/test/CodeGenCXX/amdgcn-call-with-aggarg.cc b/clang/test/CodeGenCXX/amdgcn-call-with-aggarg.cc
new file mode 100644
index 0000000000000..9b598a48f6436
--- /dev/null
+++ b/clang/test/CodeGenCXX/amdgcn-call-with-aggarg.cc
@@ -0,0 +1,19 @@
+// RUN: %clang_cc1 -triple amdgcn-amd-amdhsa -emit-llvm -O3 -disable-llvm-passes -o - %s | FileCheck %s
+
+struct A {
+ float x, y, z, w;
+};
+
+void foo(A a);
+
+// CHECK-LABEL: @_Z4testv
+// CHECK: [[A:%.*]] = alloca [[STRUCT_A:%.*]], align 4, addrspace(5)
+// CHECK-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_A]], align 4, addrspace(5)
+// CHECK-NEXT: [[A_ASCAST:%.*]] = addrspacecast ptr addrspace(5) [[A]] to ptr
+// CHECK-NEXT: [[AGG_TMP_ASCAST:%.*]] = addrspacecast ptr addrspace(5) [[AGG_TMP]] to ptr
+// CHECK-NEXT: call void @llvm.lifetime.start.p5(i64 16, ptr addrspace(5) [[A]]) #[[ATTR4:[0-9]+]]
+// CHECK-NEXT: call void @llvm.lifetime.start.p5(i64 16, ptr addrspace(5) [[AGG_TMP]]) #[[ATTR4]]
+void test() {
+ A a;
+ foo(a);
+}
diff --git a/clang/test/CodeGenCXX/stack-reuse-miscompile.cpp b/clang/test/CodeGenCXX/stack-reuse-miscompile.cpp
index 67fa9f9c9cd98..50c374d2710f4 100644
--- a/clang/test/CodeGenCXX/stack-reuse-miscompile.cpp
+++ b/clang/test/CodeGenCXX/stack-reuse-miscompile.cpp
@@ -26,6 +26,8 @@ const char * f(S s)
// CHECK: [[T2:%.*]] = alloca %class.T, align 4
// CHECK: [[T3:%.*]] = alloca %class.T, align 4
//
+// CHECK: [[AGG:%.*]] = alloca %class.S, align 4
+//
// FIXME: We could defer starting the lifetime of the return object of concat
// until the call.
// CHECK: call void @llvm.lifetime.start.p0(ptr [[T1]])
@@ -34,10 +36,12 @@ const char * f(S s)
// CHECK: [[T4:%.*]] = call noundef ptr @_ZN1TC1EPKc(ptr {{[^,]*}} [[T2]], ptr noundef @.str)
//
// CHECK: call void @llvm.lifetime.start.p0(ptr [[T3]])
+// CHECK: call void @llvm.lifetime.start.p0(ptr [[AGG]])
// CHECK: [[T5:%.*]] = call noundef ptr @_ZN1TC1E1S(ptr {{[^,]*}} [[T3]], [2 x i32] %{{.*}})
//
// CHECK: call void @_ZNK1T6concatERKS_(ptr dead_on_unwind writable sret(%class.T) align 4 [[T1]], ptr {{[^,]*}} [[T2]], ptr noundef nonnull align 4 dereferenceable(16) [[T3]])
// CHECK: [[T6:%.*]] = call noundef ptr @_ZNK1T3strEv(ptr {{[^,]*}} [[T1]])
+// CHECK: call void @llvm.lifetime.end.p0(ptr [[AGG]])
//
// CHECK: call void @llvm.lifetime.end.p0(
// CHECK: call void @llvm.lifetime.end.p0(
diff --git a/clang/test/CodeGenCoroutines/pr59181.cpp b/clang/test/CodeGenCoroutines/pr59181.cpp
index 21e784e0031de..bb5eba1c3252d 100644
--- a/clang/test/CodeGenCoroutines/pr59181.cpp
+++ b/clang/test/CodeGenCoroutines/pr59181.cpp
@@ -49,6 +49,7 @@ void foo() {
}
// CHECK: cleanup.cont:{{.*}}
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG:%agg.tmp]])
// CHECK-NEXT: load i8
// CHECK-NEXT: trunc
// CHECK-NEXT: store i1 false
@@ -57,3 +58,6 @@ void foo() {
// CHECK-NOT: call void @llvm.lifetime
// CHECK: call void @llvm.coro.await.suspend.void(
// CHECK-NEXT: %{{[0-9]+}} = call i8 @llvm.coro.suspend(
+
+// CHECK-LABEL: cleanup.done20:
+// CHECK: call void @llvm.lifetime.end.p0(ptr [[AGG]])
>From b5e8148595bd127fe25cb7cb14337247171e11db Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Tue, 13 Jan 2026 15:42:58 -0800
Subject: [PATCH 2/8] Remove test that no longer repros error case
Something changed w/in clang to prevent this crash from happening.
---
clang/test/CodeGen/lifetime-bug-2.c | 58 -----------------------------
1 file changed, 58 deletions(-)
delete mode 100644 clang/test/CodeGen/lifetime-bug-2.c
diff --git a/clang/test/CodeGen/lifetime-bug-2.c b/clang/test/CodeGen/lifetime-bug-2.c
deleted file mode 100644
index 76dd5b95d0216..0000000000000
--- a/clang/test/CodeGen/lifetime-bug-2.c
+++ /dev/null
@@ -1,58 +0,0 @@
-// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --check-globals none --version 6
-// RUN: %clang_cc1 -O2 -emit-llvm -disable-llvm-passes %s -o - | FileCheck %s
-
-struct {
-} __trans_tmp_85;
-struct bkey_s_c {
-} bch2_trans_update_extent_k;
-int bch2_trans_update_extent_k_0;
-struct bkey_s_c bch2_btree_iter_peek_max();
-// CHECK-LABEL: define dso_local void @bch2_trans_update_extent(
-// CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
-// CHECK-NEXT: [[ENTRY:.*:]]
-// CHECK-NEXT: [[DONE:%.*]] = alloca i8, align 1
-// CHECK-NEXT: [[TMP:%.*]] = alloca [[STRUCT_BKEY_S_C:%.*]], align 1
-// CHECK-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_ANON:%.*]], align 1
-// CHECK-NEXT: [[UNDEF_AGG_TMP:%.*]] = alloca [[STRUCT_BKEY_S_C]], align 1
-// CHECK-NEXT: [[CLEANUP_DEST_SLOT:%.*]] = alloca i32, align 4
-// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[DONE]]) #[[ATTR4:[0-9]+]]
-// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[TMP]]) #[[ATTR4]]
-// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG_TMP]]) #[[ATTR4]]
-// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 [[AGG_TMP]], ptr align 1 @__trans_tmp_85, i64 0, i1 false), !tbaa.struct [[TBAA_STRUCT6:![0-9]+]]
-// CHECK-NEXT: call void (...) @bch2_btree_iter_peek_max()
-// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP]]) #[[ATTR4]]
-// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 1 @bch2_trans_update_extent_k, ptr align 1 [[UNDEF_AGG_TMP]], i64 0, i1 false), !tbaa.struct [[TBAA_STRUCT6]]
-// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[TMP]]) #[[ATTR4]]
-// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr @bch2_trans_update_extent_k_0, align 4, !tbaa [[INT_TBAA2:![0-9]+]]
-// CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
-// CHECK-NEXT: br i1 [[TOBOOL]], label %[[IF_THEN:.*]], label %[[IF_END:.*]]
-// CHECK: [[IF_THEN]]:
-// CHECK-NEXT: store i32 2, ptr [[CLEANUP_DEST_SLOT]], align 4
-// CHECK-NEXT: br label %[[CLEANUP:.*]]
-// CHECK: [[IF_END]]:
-// CHECK-NEXT: store i32 0, ptr [[CLEANUP_DEST_SLOT]], align 4
-// CHECK-NEXT: br label %[[CLEANUP]]
-// CHECK: [[CLEANUP]]:
-// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[DONE]]) #[[ATTR4]]
-// CHECK-NEXT: [[CLEANUP_DEST:%.*]] = load i32, ptr [[CLEANUP_DEST_SLOT]], align 4
-// CHECK-NEXT: switch i32 [[CLEANUP_DEST]], label %[[UNREACHABLE:.*]] [
-// CHECK-NEXT: i32 0, label %[[CLEANUP_CONT:.*]]
-// CHECK-NEXT: i32 2, label %[[ERR:.*]]
-// CHECK-NEXT: ]
-// CHECK: [[CLEANUP_CONT]]:
-// CHECK-NEXT: br label %[[ERR]]
-// CHECK: [[ERR]]:
-// CHECK-NEXT: ret void
-// CHECK: [[UNREACHABLE]]:
-// CHECK-NEXT: unreachable
-//
-void bch2_trans_update_extent() {
- {
- _Bool done;
- bch2_trans_update_extent_k = bch2_btree_iter_peek_max((0, __trans_tmp_85));
- if (bch2_trans_update_extent_k_0)
- goto err;
- }
-err:
-}
-
>From d822458551497d369555eabf2e8e0cae3e6711f4 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Wed, 11 Feb 2026 17:24:58 -0800
Subject: [PATCH 3/8] Improve exception compatibility.
---
clang/lib/CodeGen/CGCall.cpp | 5 +-
clang/lib/Sema/SemaExprCXX.cpp | 4 +
clang/test/CodeGen/lifetime-bug.cpp | 21 +++-
clang/test/CodeGen/lifetime-invoke-c.c | 111 +++++++++++++++---
.../CodeGenCXX/aggregate-lifetime-invoke.cpp | 79 ++++++++++---
5 files changed, 176 insertions(+), 44 deletions(-)
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 6f09d5a9aadbf..419ad20a74465 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5075,10 +5075,7 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
//
// FIXME: For types which don't have a destructor, consider using a
// narrower lifetime bound.
- // FIXME: This should work fine w/ exceptions, but somehow breaks the
- // dominance relationship in the def-use chain.
- if (!CGM.getLangOpts().Exceptions &&
- hasAggregateEvaluationKind(E->getType())) {
+ if (hasAggregateEvaluationKind(E->getType())) {
RawAddress ArgSlotAlloca = Address::invalid();
ArgSlot = CreateAggTemp(E->getType(), "agg.tmp", &ArgSlotAlloca);
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 39c5e3b0671bb..70c96c07ff7a9 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -2664,6 +2664,10 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal,
}
}
+ if (OperatorDelete && !OperatorDelete->isReservedGlobalPlacementOperator() &&
+ Initializer && canThrow(Initializer))
+ Cleanup.setExprNeedsCleanups(true);
+
return CXXNewExpr::Create(Context, UseGlobal, OperatorNew, OperatorDelete,
IAP, UsualArrayDeleteWantsSize, PlacementArgs,
TypeIdParens, ArraySize, InitStyle, Initializer,
diff --git a/clang/test/CodeGen/lifetime-bug.cpp b/clang/test/CodeGen/lifetime-bug.cpp
index 641b3ae7762ae..d9d5350fd4cbd 100644
--- a/clang/test/CodeGen/lifetime-bug.cpp
+++ b/clang/test/CodeGen/lifetime-bug.cpp
@@ -1,6 +1,10 @@
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --check-globals none --version 6
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -emit-llvm -disable-llvm-passes -target-cpu x86-64 -fexceptions -x c++ %s -o - | FileCheck %s
+/// COM: When introducing tighter lifetimes, this test used to cause an
+/// COM: assertion failure, and generate invalid IR, which would fail to
+/// COM: compile.
+
void a();
struct b {};
typedef enum {} c;
@@ -11,18 +15,23 @@ struct e {
// CHECK-LABEL: define dso_local void @_Z1fv(
// CHECK-SAME: ) #[[ATTR0:[0-9]+]] personality ptr @__gxx_personality_v0 {
// CHECK-NEXT: [[ENTRY:.*:]]
-// CHECK-NEXT: [[AGG_TEMP:%.*]] = alloca [[STRUCT_B:%.*]], align 1
+// CHECK-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_B:%.*]], align 1
// CHECK-NEXT: [[EXN_SLOT:%.*]] = alloca ptr, align 8
// CHECK-NEXT: [[EHSELECTOR_SLOT:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[CLEANUP_ISACTIVE:%.*]] = alloca i1, align 1
// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr @d, align 4, !tbaa [[_ZTS1C_TBAA6:![0-9]+]]
// CHECK-NEXT: switch i32 [[TMP0]], label %[[SW_DEFAULT:.*]] [
// CHECK-NEXT: i32 1, label %[[SW_BB:.*]]
// CHECK-NEXT: ]
// CHECK: [[SW_BB]]:
-// CHECK-NEXT: [[CALL:%.*]] = call noalias noundef nonnull ptr @_Znwm(i64 noundef 1) #[[ATTR4:[0-9]+]]
+// CHECK-NEXT: [[CALL:%.*]] = call noalias noundef nonnull ptr @_Znwm(i64 noundef 1) #[[ATTR5:[0-9]+]]
+// CHECK-NEXT: store i1 true, ptr [[CLEANUP_ISACTIVE]], align 1
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG_TMP]]) #[[ATTR6:[0-9]+]]
// CHECK-NEXT: invoke void @_ZN1eC1E1b(ptr noundef nonnull align 1 dereferenceable(1) [[CALL]])
// CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[LPAD:.*]]
// CHECK: [[INVOKE_CONT]]:
+// CHECK-NEXT: store i1 false, ptr [[CLEANUP_ISACTIVE]], align 1
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP]]) #[[ATTR6]]
// CHECK-NEXT: call void @_Z1av()
// CHECK-NEXT: br label %[[SW_EPILOG:.*]]
// CHECK: [[LPAD]]:
@@ -32,7 +41,13 @@ struct e {
// CHECK-NEXT: store ptr [[TMP2]], ptr [[EXN_SLOT]], align 8
// CHECK-NEXT: [[TMP3:%.*]] = extractvalue { ptr, i32 } [[TMP1]], 1
// CHECK-NEXT: store i32 [[TMP3]], ptr [[EHSELECTOR_SLOT]], align 4
-// CHECK-NEXT: call void @_ZdlPvm(ptr noundef [[CALL]], i64 noundef 1) #[[ATTR5:[0-9]+]]
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP]]) #[[ATTR6]]
+// CHECK-NEXT: [[CLEANUP_IS_ACTIVE:%.*]] = load i1, ptr [[CLEANUP_ISACTIVE]], align 1
+// CHECK-NEXT: br i1 [[CLEANUP_IS_ACTIVE]], label %[[CLEANUP_ACTION:.*]], label %[[CLEANUP_DONE:.*]]
+// CHECK: [[CLEANUP_ACTION]]:
+// CHECK-NEXT: call void @_ZdlPvm(ptr noundef [[CALL]], i64 noundef 1) #[[ATTR7:[0-9]+]]
+// CHECK-NEXT: br label %[[CLEANUP_DONE]]
+// CHECK: [[CLEANUP_DONE]]:
// CHECK-NEXT: br label %[[EH_RESUME:.*]]
// CHECK: [[SW_DEFAULT]]:
// CHECK-NEXT: call void @_Z1av()
diff --git a/clang/test/CodeGen/lifetime-invoke-c.c b/clang/test/CodeGen/lifetime-invoke-c.c
index 6a43b3562a2e8..77514dc80e9e6 100644
--- a/clang/test/CodeGen/lifetime-invoke-c.c
+++ b/clang/test/CodeGen/lifetime-invoke-c.c
@@ -1,3 +1,4 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --check-globals none --version 6
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -disable-llvm-passes | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -disable-llvm-passes -fexceptions | FileCheck %s --check-prefix=EXCEPTIONS
@@ -5,32 +6,104 @@ struct Trivial {
int x[100];
};
+
+// CHECK-LABEL: define dso_local void @cleanup(
+// CHECK-SAME: ptr noundef [[P:%.*]]) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[P_ADDR:%.*]] = alloca ptr, align 8
+// CHECK-NEXT: store ptr [[P]], ptr [[P_ADDR]], align 8, !tbaa [[INTPTR_TBAA6:![0-9]+]]
+// CHECK-NEXT: ret void
+//
+// EXCEPTIONS-LABEL: define dso_local void @cleanup(
+// EXCEPTIONS-SAME: ptr noundef [[P:%.*]]) #[[ATTR0:[0-9]+]] {
+// EXCEPTIONS-NEXT: [[ENTRY:.*:]]
+// EXCEPTIONS-NEXT: [[P_ADDR:%.*]] = alloca ptr, align 8
+// EXCEPTIONS-NEXT: store ptr [[P]], ptr [[P_ADDR]], align 8, !tbaa [[INTPTR_TBAA6:![0-9]+]]
+// EXCEPTIONS-NEXT: ret void
+//
void cleanup(int *p) {}
void func(struct Trivial t);
struct Trivial gen(void);
-// CHECK-LABEL: define dso_local void @test()
+// CHECK-LABEL: define dso_local void @test(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[X:%.*]] = alloca i32, align 4
+// CHECK-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_TRIVIAL:%.*]], align 8
+// CHECK-NEXT: [[AGG_TMP1:%.*]] = alloca [[STRUCT_TRIVIAL]], align 8
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[X]]) #[[ATTR3:[0-9]+]]
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG_TMP]]) #[[ATTR3]]
+// CHECK-NEXT: call void @gen(ptr dead_on_unwind writable sret([[STRUCT_TRIVIAL]]) align 4 [[AGG_TMP]])
+// CHECK-NEXT: call void @func(ptr noundef byval([[STRUCT_TRIVIAL]]) align 8 [[AGG_TMP]])
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG_TMP1]]) #[[ATTR3]]
+// CHECK-NEXT: call void @gen(ptr dead_on_unwind writable sret([[STRUCT_TRIVIAL]]) align 4 [[AGG_TMP1]])
+// CHECK-NEXT: call void @func(ptr noundef byval([[STRUCT_TRIVIAL]]) align 8 [[AGG_TMP1]])
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP1]]) #[[ATTR3]]
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP]]) #[[ATTR3]]
+// CHECK-NEXT: call void @cleanup(ptr noundef [[X]])
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr [[X]]) #[[ATTR3]]
+// CHECK-NEXT: ret void
+//
+// EXCEPTIONS-LABEL: define dso_local void @test(
+// EXCEPTIONS-SAME: ) #[[ATTR1:[0-9]+]] personality ptr @__gcc_personality_v0 {
+// EXCEPTIONS-NEXT: [[ENTRY:.*:]]
+// EXCEPTIONS-NEXT: [[X:%.*]] = alloca i32, align 4
+// EXCEPTIONS-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_TRIVIAL:%.*]], align 8
+// EXCEPTIONS-NEXT: [[EXN_SLOT:%.*]] = alloca ptr, align 8
+// EXCEPTIONS-NEXT: [[EHSELECTOR_SLOT:%.*]] = alloca i32, align 4
+// EXCEPTIONS-NEXT: [[AGG_TMP2:%.*]] = alloca [[STRUCT_TRIVIAL]], align 8
+// EXCEPTIONS-NEXT: call void @llvm.lifetime.start.p0(ptr [[X]]) #[[ATTR4:[0-9]+]]
+// EXCEPTIONS-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG_TMP]]) #[[ATTR4]]
+// EXCEPTIONS-NEXT: invoke void @gen(ptr dead_on_unwind writable sret([[STRUCT_TRIVIAL]]) align 4 [[AGG_TMP]])
+// EXCEPTIONS-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[LPAD:.*]]
+// EXCEPTIONS: [[INVOKE_CONT]]:
+// EXCEPTIONS-NEXT: invoke void @func(ptr noundef byval([[STRUCT_TRIVIAL]]) align 8 [[AGG_TMP]])
+// EXCEPTIONS-NEXT: to label %[[INVOKE_CONT1:.*]] unwind label %[[LPAD]]
+// EXCEPTIONS: [[INVOKE_CONT1]]:
+// EXCEPTIONS-NEXT: call void @llvm.lifetime.start.p0(ptr [[AGG_TMP2]]) #[[ATTR4]]
+// EXCEPTIONS-NEXT: invoke void @gen(ptr dead_on_unwind writable sret([[STRUCT_TRIVIAL]]) align 4 [[AGG_TMP2]])
+// EXCEPTIONS-NEXT: to label %[[INVOKE_CONT4:.*]] unwind label %[[LPAD3:.*]]
+// EXCEPTIONS: [[INVOKE_CONT4]]:
+// EXCEPTIONS-NEXT: invoke void @func(ptr noundef byval([[STRUCT_TRIVIAL]]) align 8 [[AGG_TMP2]])
+// EXCEPTIONS-NEXT: to label %[[INVOKE_CONT5:.*]] unwind label %[[LPAD3]]
+// EXCEPTIONS: [[INVOKE_CONT5]]:
+// EXCEPTIONS-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP2]]) #[[ATTR4]]
+// EXCEPTIONS-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP]]) #[[ATTR4]]
+// EXCEPTIONS-NEXT: call void @cleanup(ptr noundef [[X]])
+// EXCEPTIONS-NEXT: call void @llvm.lifetime.end.p0(ptr [[X]]) #[[ATTR4]]
+// EXCEPTIONS-NEXT: ret void
+// EXCEPTIONS: [[LPAD]]:
+// EXCEPTIONS-NEXT: [[TMP0:%.*]] = landingpad { ptr, i32 }
+// EXCEPTIONS-NEXT: cleanup
+// EXCEPTIONS-NEXT: [[TMP1:%.*]] = extractvalue { ptr, i32 } [[TMP0]], 0
+// EXCEPTIONS-NEXT: store ptr [[TMP1]], ptr [[EXN_SLOT]], align 8
+// EXCEPTIONS-NEXT: [[TMP2:%.*]] = extractvalue { ptr, i32 } [[TMP0]], 1
+// EXCEPTIONS-NEXT: store i32 [[TMP2]], ptr [[EHSELECTOR_SLOT]], align 4
+// EXCEPTIONS-NEXT: br label %[[EHCLEANUP:.*]]
+// EXCEPTIONS: [[LPAD3]]:
+// EXCEPTIONS-NEXT: [[TMP3:%.*]] = landingpad { ptr, i32 }
+// EXCEPTIONS-NEXT: cleanup
+// EXCEPTIONS-NEXT: [[TMP4:%.*]] = extractvalue { ptr, i32 } [[TMP3]], 0
+// EXCEPTIONS-NEXT: store ptr [[TMP4]], ptr [[EXN_SLOT]], align 8
+// EXCEPTIONS-NEXT: [[TMP5:%.*]] = extractvalue { ptr, i32 } [[TMP3]], 1
+// EXCEPTIONS-NEXT: store i32 [[TMP5]], ptr [[EHSELECTOR_SLOT]], align 4
+// EXCEPTIONS-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP2]]) #[[ATTR4]]
+// EXCEPTIONS-NEXT: br label %[[EHCLEANUP]]
+// EXCEPTIONS: [[EHCLEANUP]]:
+// EXCEPTIONS-NEXT: call void @llvm.lifetime.end.p0(ptr [[AGG_TMP]]) #[[ATTR4]]
+// EXCEPTIONS-NEXT: call void @cleanup(ptr noundef [[X]])
+// EXCEPTIONS-NEXT: call void @llvm.lifetime.end.p0(ptr [[X]]) #[[ATTR4]]
+// EXCEPTIONS-NEXT: br label %[[EH_RESUME:.*]]
+// EXCEPTIONS: [[EH_RESUME]]:
+// EXCEPTIONS-NEXT: [[EXN:%.*]] = load ptr, ptr [[EXN_SLOT]], align 8
+// EXCEPTIONS-NEXT: [[SEL:%.*]] = load i32, ptr [[EHSELECTOR_SLOT]], align 4
+// EXCEPTIONS-NEXT: [[LPAD_VAL:%.*]] = insertvalue { ptr, i32 } poison, ptr [[EXN]], 0
+// EXCEPTIONS-NEXT: [[LPAD_VAL8:%.*]] = insertvalue { ptr, i32 } [[LPAD_VAL]], i32 [[SEL]], 1
+// EXCEPTIONS-NEXT: resume { ptr, i32 } [[LPAD_VAL8]]
+//
void test() {
int x __attribute__((cleanup(cleanup)));
- // CHECK: %[[AGG1:.*]] = alloca %struct.Trivial
- // CHECK: %[[AGG2:.*]] = alloca %struct.Trivial
- // CHECK: call void @llvm.lifetime.start.p0(ptr %[[AGG1]])
- // CHECK: call void @gen(ptr{{.*}} sret(%struct.Trivial){{.*}} %[[AGG1]])
- // CHECK: call void @func(ptr{{.*}} %[[AGG1]])
- // CHECK: call void @llvm.lifetime.start.p0(ptr %[[AGG2]])
- // CHECK: call void @gen(ptr{{.*}} sret(%struct.Trivial){{.*}} %[[AGG2]])
- // CHECK: call void @func(ptr{{.*}} %[[AGG2]])
- // CHECK: call void @llvm.lifetime.end.p0(ptr %[[AGG2]])
- // CHECK: call void @llvm.lifetime.end.p0(ptr %[[AGG1]])
- // CHECK: call void @cleanup
-
- // EXCEPTIONS: %[[AGG1:.*]] = alloca %struct.Trivial
- // EXCEPTIONS: %[[AGG2:.*]] = alloca %struct.Trivial
- // EXCEPTIONS-NOT: call void @llvm.lifetime.start.p0(ptr %[[AGG1]])
- // EXCEPTIONS-NOT: call void @llvm.lifetime.start.p0(ptr %[[AGG2]])
- // EXCEPTIONS-NOT: call void @llvm.lifetime.end.p0(ptr %[[AGG2]])
- // EXCEPTIONS-NOT: call void @llvm.lifetime.end.p0(ptr %[[AGG1]])
func(gen());
func(gen());
}
diff --git a/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp
index c3ce91aea73f3..2d1075bbcbbb0 100644
--- a/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp
+++ b/clang/test/CodeGenCXX/aggregate-lifetime-invoke.cpp
@@ -1,5 +1,6 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --check-globals none --version 6
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -fexceptions -fcxx-exceptions | FileCheck %s
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -fexceptions -fcxx-exceptions -sloppy-temporary-lifetimes | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -o - %s -O1 -fexceptions -fcxx-exceptions -sloppy-temporary-lifetimes | FileCheck %s --check-prefix=SLOPPY
// COM: Note that this test case would break if we allowed tighter lifetimes to
// run when exceptions were enabled. If we make them work together this test
@@ -13,24 +14,66 @@ struct Trivial {
void func_that_throws(Trivial t);
-// CHECK-LABEL: define{{.*}} void @test()
+// CHECK-LABEL: define dso_local void @test(
+// CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] personality ptr @__gxx_personality_v0 {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_TRIVIAL:%.*]], align 8
+// CHECK-NEXT: [[AGG_TMP1:%.*]] = alloca [[STRUCT_TRIVIAL]], align 8
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[AGG_TMP]]) #[[ATTR4:[0-9]+]]
+// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 8 dereferenceable(400) [[AGG_TMP]], i8 0, i64 400, i1 false)
+// CHECK-NEXT: invoke void @func_that_throws(ptr noundef nonnull byval([[STRUCT_TRIVIAL]]) align 8 [[AGG_TMP]])
+// CHECK-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[LPAD:.*]]
+// CHECK: [[INVOKE_CONT]]:
+// CHECK-NEXT: call void @llvm.lifetime.start.p0(ptr nonnull [[AGG_TMP1]]) #[[ATTR4]]
+// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 8 dereferenceable(400) [[AGG_TMP1]], i8 0, i64 400, i1 false)
+// CHECK-NEXT: invoke void @func_that_throws(ptr noundef nonnull byval([[STRUCT_TRIVIAL]]) align 8 [[AGG_TMP1]])
+// CHECK-NEXT: to label %[[INVOKE_CONT4:.*]] unwind label %[[LPAD3:.*]]
+// CHECK: [[INVOKE_CONT4]]:
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[AGG_TMP1]]) #[[ATTR4]]
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[AGG_TMP]]) #[[ATTR4]]
+// CHECK-NEXT: br label %[[TRY_CONT:.*]]
+// CHECK: [[LPAD]]:
+// CHECK-NEXT: [[TMP0:%.*]] = landingpad { ptr, i32 }
+// CHECK-NEXT: catch ptr null
+// CHECK-NEXT: br label %[[EHCLEANUP:.*]]
+// CHECK: [[LPAD3]]:
+// CHECK-NEXT: [[TMP1:%.*]] = landingpad { ptr, i32 }
+// CHECK-NEXT: catch ptr null
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[AGG_TMP1]]) #[[ATTR4]]
+// CHECK-NEXT: br label %[[EHCLEANUP]]
+// CHECK: [[EHCLEANUP]]:
+// CHECK-NEXT: [[DOTPN:%.*]] = phi { ptr, i32 } [ [[TMP1]], %[[LPAD3]] ], [ [[TMP0]], %[[LPAD]] ]
+// CHECK-NEXT: [[EXN_SLOT_0:%.*]] = extractvalue { ptr, i32 } [[DOTPN]], 0
+// CHECK-NEXT: call void @llvm.lifetime.end.p0(ptr nonnull [[AGG_TMP]]) #[[ATTR4]]
+// CHECK-NEXT: [[TMP2:%.*]] = tail call ptr @__cxa_begin_catch(ptr [[EXN_SLOT_0]]) #[[ATTR4]]
+// CHECK-NEXT: tail call void @__cxa_end_catch()
+// CHECK-NEXT: br label %[[TRY_CONT]]
+// CHECK: [[TRY_CONT]]:
+// CHECK-NEXT: ret void
+//
+// SLOPPY-LABEL: define dso_local void @test(
+// SLOPPY-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] personality ptr @__gxx_personality_v0 {
+// SLOPPY-NEXT: [[ENTRY:.*:]]
+// SLOPPY-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_TRIVIAL:%.*]], align 8
+// SLOPPY-NEXT: [[AGG_TMP1:%.*]] = alloca [[STRUCT_TRIVIAL]], align 8
+// SLOPPY-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 8 dereferenceable(400) [[AGG_TMP]], i8 0, i64 400, i1 false)
+// SLOPPY-NEXT: invoke void @func_that_throws(ptr noundef nonnull byval([[STRUCT_TRIVIAL]]) align 8 [[AGG_TMP]])
+// SLOPPY-NEXT: to label %[[INVOKE_CONT:.*]] unwind label %[[LPAD:.*]]
+// SLOPPY: [[INVOKE_CONT]]:
+// SLOPPY-NEXT: call void @llvm.memset.p0.i64(ptr noundef nonnull align 8 dereferenceable(400) [[AGG_TMP1]], i8 0, i64 400, i1 false)
+// SLOPPY-NEXT: invoke void @func_that_throws(ptr noundef nonnull byval([[STRUCT_TRIVIAL]]) align 8 [[AGG_TMP1]])
+// SLOPPY-NEXT: to label %[[TRY_CONT:.*]] unwind label %[[LPAD]]
+// SLOPPY: [[LPAD]]:
+// SLOPPY-NEXT: [[TMP0:%.*]] = landingpad { ptr, i32 }
+// SLOPPY-NEXT: catch ptr null
+// SLOPPY-NEXT: [[TMP1:%.*]] = extractvalue { ptr, i32 } [[TMP0]], 0
+// SLOPPY-NEXT: [[TMP2:%.*]] = tail call ptr @__cxa_begin_catch(ptr [[TMP1]]) #[[ATTR3:[0-9]+]]
+// SLOPPY-NEXT: tail call void @__cxa_end_catch()
+// SLOPPY-NEXT: br label %[[TRY_CONT]]
+// SLOPPY: [[TRY_CONT]]:
+// SLOPPY-NEXT: ret void
+//
void test() {
- // CHECK: %[[AGG1:.*]] = alloca %struct.Trivial
- // CHECK: %[[AGG2:.*]] = alloca %struct.Trivial
-
- // CHECK: invoke void @func_that_throws(ptr{{.*}} %[[AGG1]])
- // CHECK-NEXT: to label %[[CONT:.*]] unwind label %[[LPAD:.*]]
-
- // CHECK: [[CONT]]:
- // CHECK: invoke void @func_that_throws(ptr{{.*}} %[[AGG2]])
- // CHECK-NEXT: to label %[[CONT:.*]] unwind label %[[LPAD:.*]]
-
- // CHECK: [[LPAD]]:
- // CHECK: landingpad
-
- // CHECK-NOT: llvm.lifetime.start
- // CHECK-NOT: llvm.lifetime.end
-
try {
func_that_throws(Trivial{0});
func_that_throws(Trivial{0});
>From e0e1f5e7bb12dba9c093f7732feb3fa854d836c4 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Fri, 20 Feb 2026 13:51:33 -0800
Subject: [PATCH 4/8] Remove unneeded conditions from CGCleanup.cpp
---
clang/lib/CodeGen/CGCleanup.cpp | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/clang/lib/CodeGen/CGCleanup.cpp b/clang/lib/CodeGen/CGCleanup.cpp
index 7d6c50aaab415..9adc928c11585 100644
--- a/clang/lib/CodeGen/CGCleanup.cpp
+++ b/clang/lib/CodeGen/CGCleanup.cpp
@@ -700,12 +700,11 @@ void CodeGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough,
// If this is a normal cleanup, then having a prebranched
// fallthrough implies that the fallthrough source unconditionally
- // jumps here, unless its for a lifetime marker.
+ // jumps here.
assert(!Scope.isNormalCleanup() || !HasPrebranchedFallthrough ||
- Scope.isLifetimeMarker() ||
(Scope.getNormalBlock() &&
- FallthroughSource->getTerminator()->getSuccessor(0) ==
- Scope.getNormalBlock()));
+ FallthroughSource->getTerminator()->getSuccessor(0)
+ == Scope.getNormalBlock()));
bool RequiresNormalCleanup = false;
if (Scope.isNormalCleanup() &&
>From 13bd41a80973df3fc07e9a2129b820198252326a Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Tue, 24 Feb 2026 13:07:12 -0800
Subject: [PATCH 5/8] Avoid canThrow, and just check for Exceptions being
enabled
---
clang/lib/Sema/SemaExprCXX.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 70c96c07ff7a9..2dc026ec4af0e 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -2665,7 +2665,7 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal,
}
if (OperatorDelete && !OperatorDelete->isReservedGlobalPlacementOperator() &&
- Initializer && canThrow(Initializer))
+ Initializer && (getLangOpts().Exceptions || getLangOpts().CXXExceptions))
Cleanup.setExprNeedsCleanups(true);
return CXXNewExpr::Create(Context, UseGlobal, OperatorNew, OperatorDelete,
>From 5f72e008d35ba4daf3835b56447f07f4192691a6 Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Wed, 1 Apr 2026 15:03:16 -0700
Subject: [PATCH 6/8] Add test case
---
clang/test/CodeGen/lifetime-bug-2.cpp | 30 +++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
create mode 100644 clang/test/CodeGen/lifetime-bug-2.cpp
diff --git a/clang/test/CodeGen/lifetime-bug-2.cpp b/clang/test/CodeGen/lifetime-bug-2.cpp
new file mode 100644
index 0000000000000..aa7f0802fbad3
--- /dev/null
+++ b/clang/test/CodeGen/lifetime-bug-2.cpp
@@ -0,0 +1,30 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O1 -emit-llvm -disable-llvm-passes -target-cpu x86-64 -fexceptions -fcxx-exceptions -x c++ %s -o - 2>&1 | FileCheck %s
+
+extern "C" {
+struct X {};
+
+X f1(X x);
+X f2(X x);
+
+void foo(){
+ try{
+ f2(f1(X{}));
+ } catch(int e){
+ return;
+ }
+ return;
+}
+}
+// CHECK-LABEL: define{{.*}} void @foo
+// CHECK: [[TMP1:%.*]] = alloca %struct.X
+// CHECK: [[TMP2:%.*]] = alloca %struct.X
+// CHECK: llvm.lifetime.start.p0(ptr [[TMP1]])
+// CHECK-NEXT: llvm.lifetime.start.p0(ptr [[TMP2]])
+// CHECK-NEXT: invoke void @f1
+// CHECK-NEXT: to label %[[CONT:.*]] unwind label %[[LPAD1:.*]]
+//
+// CHECK: [[LPAD1]]:
+// CHECK-NEXT: landingpad
+// CHECK: llvm.lifetime.end.p0(ptr [[TMP2]])
+// CHECK: llvm.lifetime.end.p0(ptr [[TMP1]])
+
>From b95365f86e1edfe04f37b1f5a082dfe4460e278d Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Thu, 2 Apr 2026 17:56:36 -0700
Subject: [PATCH 7/8] Fix tidy checks
---
.../bugprone/SmartPtrArrayMismatchCheck.cpp | 21 ++++++++++--------
.../cppcoreguidelines/OwningMemoryCheck.cpp | 10 +++++----
.../modernize/MakeSmartPtrCheck.cpp | 22 ++++++++++++-------
clang-tools-extra/clang-tidy/utils/Matchers.h | 16 ++++++++++++++
4 files changed, 48 insertions(+), 21 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/SmartPtrArrayMismatchCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SmartPtrArrayMismatchCheck.cpp
index a0db39ab6f90d..11028ded61239 100644
--- a/clang-tools-extra/clang-tidy/bugprone/SmartPtrArrayMismatchCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/SmartPtrArrayMismatchCheck.cpp
@@ -8,10 +8,12 @@
#include "SmartPtrArrayMismatchCheck.h"
#include "../utils/ASTUtils.h"
+#include "../utils/Matchers.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"
using namespace clang::ast_matchers;
+using namespace clang::tidy::matchers;
namespace clang::tidy::bugprone {
@@ -59,21 +61,22 @@ void SmartPtrArrayMismatchCheck::registerMatchers(MatchFinder *Finder) {
parameterCountIs(1), isExplicit())
.bind(ConstructorN);
auto FindConstructExpr =
- cxxConstructExpr(
- hasDeclaration(FindConstructor), argumentCountIs(1),
- hasArgument(0,
- cxxNewExpr(isArray(),
- hasType(hasCanonicalType(pointerType(
- pointee(equalsBoundNode(PointerTypeN))))))
- .bind(NewExprN)))
- .bind(ConstructExprN);
+ expr(ignoringCleanups(
+ cxxConstructExpr(
+ hasDeclaration(FindConstructor), argumentCountIs(1),
+ hasArgument(0,
+ ignoringCleanups(cxxNewExpr(isArray(),
+ hasType(hasCanonicalType(pointerType(
+ pointee(equalsBoundNode(PointerTypeN))))))
+ .bind(NewExprN))))
+ )).bind(ConstructExprN);
Finder->addMatcher(FindConstructExpr, this);
}
void SmartPtrArrayMismatchCheck::check(const MatchFinder::MatchResult &Result) {
const auto *FoundNewExpr = Result.Nodes.getNodeAs<CXXNewExpr>(NewExprN);
const auto *FoundConstructExpr =
- Result.Nodes.getNodeAs<CXXConstructExpr>(ConstructExprN);
+ Result.Nodes.getNodeAs<Expr>(ConstructExprN);
const auto *FoundConstructorDecl =
Result.Nodes.getNodeAs<CXXConstructorDecl>(ConstructorN);
diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp
index f4e89470a80da..acf2672eed166 100644
--- a/clang-tools-extra/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp
+++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "OwningMemoryCheck.h"
+#include "../utils/Matchers.h"
#include "../utils/OptionsUtils.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
@@ -14,6 +15,7 @@
using namespace clang::ast_matchers;
using namespace clang::ast_matchers::internal;
+using namespace clang::tidy::matchers;
namespace clang::tidy::cppcoreguidelines {
@@ -58,12 +60,12 @@ void OwningMemoryCheck::registerMatchers(MatchFinder *Finder) {
const auto LegacyOwnerConsumers = functionDecl(LegacyConsumerFunctions);
const auto CreatesOwner =
- anyOf(cxxNewExpr(),
- callExpr(callee(
- functionDecl(returns(qualType(hasDeclaration(OwnerDecl)))))),
+ anyOf(ignoringCleanups(cxxNewExpr()),
+ ignoringCleanups(callExpr(callee(
+ functionDecl(returns(qualType(hasDeclaration(OwnerDecl))))))),
CreatesLegacyOwner, LegacyOwnerCast);
- const auto ConsideredOwner = eachOf(IsOwnerType, CreatesOwner);
+ const auto ConsideredOwner = ignoringCleanups(eachOf(IsOwnerType, CreatesOwner));
const auto ScopeDeclaration = anyOf(translationUnitDecl(), namespaceDecl(),
recordDecl(), functionDecl());
diff --git a/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp b/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp
index 42a60bb897028..3e12f55be0444 100644
--- a/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp
@@ -7,12 +7,14 @@
//===----------------------------------------------------------------------===//
#include "MakeSmartPtrCheck.h"
+#include "../utils/Matchers.h"
#include "../utils/TypeTraits.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Lex/Lexer.h"
#include "clang/Lex/Preprocessor.h"
using namespace clang::ast_matchers;
+using namespace clang::tidy::matchers;
namespace clang::tidy::modernize {
@@ -79,13 +81,16 @@ void MakeSmartPtrCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {
traverse(TK_AsIs,
cxxConstructExpr(
anyOf(hasParent(cxxBindTemporaryExpr()),
- hasParent(varDecl().bind(DirectVar))),
+ hasParent(varDecl().bind(DirectVar)),
+ hasParent(exprWithCleanups(hasParent(varDecl().bind(DirectVar))))),
hasType(getSmartPointerTypeMatcher()), argumentCountIs(1),
- hasArgument(
- 0, cxxNewExpr(hasType(pointsTo(qualType(hasCanonicalType(
- equalsBoundNode(PointerType))))),
- CanCallCtor, unless(IsPlacement))
- .bind(NewExpression)),
+ hasArgument(0, ignoringCleanups(
+ cxxNewExpr(hasType(pointsTo(qualType(
+ hasCanonicalType(
+ equalsBoundNode(
+ PointerType))))),
+ CanCallCtor, unless(IsPlacement))
+ .bind(NewExpression))),
unless(isInTemplateInstantiation()))
.bind(ConstructorCall)),
this);
@@ -95,8 +100,9 @@ void MakeSmartPtrCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {
TK_AsIs,
cxxMemberCallExpr(
unless(isInTemplateInstantiation()),
- hasArgument(0, cxxNewExpr(CanCallCtor, unless(IsPlacement))
- .bind(NewExpression)),
+ hasArgument(0, ignoringCleanups(
+ cxxNewExpr(CanCallCtor, unless(IsPlacement))
+ .bind(NewExpression))),
callee(cxxMethodDecl(hasName("reset"))),
anyOf(thisPointerType(getSmartPointerTypeMatcher()),
on(ignoringImplicit(anyOf(
diff --git a/clang-tools-extra/clang-tidy/utils/Matchers.h b/clang-tools-extra/clang-tidy/utils/Matchers.h
index fea3ef041df1c..d3f9ba77baa46 100644
--- a/clang-tools-extra/clang-tidy/utils/Matchers.h
+++ b/clang-tools-extra/clang-tidy/utils/Matchers.h
@@ -74,6 +74,22 @@ AST_MATCHER(Expr, hasUnevaluatedContext) {
return false;
}
+AST_MATCHER_P(Expr, ignoringCleanups, ast_matchers::internal::Matcher<Expr>,
+ InnerMatcher) {
+ const Expr *E = &Node;
+ const Expr *LastE = nullptr;
+ while (E != LastE) {
+ LastE = E;
+ if (const auto *EWC = dyn_cast<ExprWithCleanups>(E))
+ E = EWC->getSubExpr();
+ else if (const auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E))
+ E = BTE->getSubExpr();
+ }
+ if (!E)
+ return false;
+ return InnerMatcher.matches(*E, Finder, Builder);
+}
+
// A matcher implementation that matches a list of type name regular expressions
// against a NamedDecl. If a regular expression contains the substring "::"
// matching will occur against the qualified name, otherwise only the typename.
>From c62eb76aa7053ff2a558b745b66857396d0e5b4e Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at google.com>
Date: Thu, 2 Apr 2026 18:19:42 -0700
Subject: [PATCH 8/8] clang-format
---
.../bugprone/SmartPtrArrayMismatchCheck.cpp | 22 ++++++-------
.../cppcoreguidelines/OwningMemoryCheck.cpp | 3 +-
.../modernize/MakeSmartPtrCheck.cpp | 31 ++++++++++---------
3 files changed, 29 insertions(+), 27 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/bugprone/SmartPtrArrayMismatchCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/SmartPtrArrayMismatchCheck.cpp
index 11028ded61239..740453c7ffadb 100644
--- a/clang-tools-extra/clang-tidy/bugprone/SmartPtrArrayMismatchCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/SmartPtrArrayMismatchCheck.cpp
@@ -61,22 +61,22 @@ void SmartPtrArrayMismatchCheck::registerMatchers(MatchFinder *Finder) {
parameterCountIs(1), isExplicit())
.bind(ConstructorN);
auto FindConstructExpr =
- expr(ignoringCleanups(
- cxxConstructExpr(
- hasDeclaration(FindConstructor), argumentCountIs(1),
- hasArgument(0,
- ignoringCleanups(cxxNewExpr(isArray(),
- hasType(hasCanonicalType(pointerType(
- pointee(equalsBoundNode(PointerTypeN))))))
- .bind(NewExprN))))
- )).bind(ConstructExprN);
+ expr(ignoringCleanups(cxxConstructExpr(
+ hasDeclaration(FindConstructor), argumentCountIs(1),
+ hasArgument(
+ 0,
+ ignoringCleanups(
+ cxxNewExpr(isArray(),
+ hasType(hasCanonicalType(pointerType(
+ pointee(equalsBoundNode(PointerTypeN))))))
+ .bind(NewExprN))))))
+ .bind(ConstructExprN);
Finder->addMatcher(FindConstructExpr, this);
}
void SmartPtrArrayMismatchCheck::check(const MatchFinder::MatchResult &Result) {
const auto *FoundNewExpr = Result.Nodes.getNodeAs<CXXNewExpr>(NewExprN);
- const auto *FoundConstructExpr =
- Result.Nodes.getNodeAs<Expr>(ConstructExprN);
+ const auto *FoundConstructExpr = Result.Nodes.getNodeAs<Expr>(ConstructExprN);
const auto *FoundConstructorDecl =
Result.Nodes.getNodeAs<CXXConstructorDecl>(ConstructorN);
diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp
index acf2672eed166..6516d6c220bfc 100644
--- a/clang-tools-extra/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp
+++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/OwningMemoryCheck.cpp
@@ -65,7 +65,8 @@ void OwningMemoryCheck::registerMatchers(MatchFinder *Finder) {
functionDecl(returns(qualType(hasDeclaration(OwnerDecl))))))),
CreatesLegacyOwner, LegacyOwnerCast);
- const auto ConsideredOwner = ignoringCleanups(eachOf(IsOwnerType, CreatesOwner));
+ const auto ConsideredOwner =
+ ignoringCleanups(eachOf(IsOwnerType, CreatesOwner));
const auto ScopeDeclaration = anyOf(translationUnitDecl(), namespaceDecl(),
recordDecl(), functionDecl());
diff --git a/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp b/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp
index 3e12f55be0444..8ca536ccc2fd9 100644
--- a/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/MakeSmartPtrCheck.cpp
@@ -78,21 +78,22 @@ void MakeSmartPtrCheck::registerMatchers(ast_matchers::MatchFinder *Finder) {
auto IsPlacement = hasAnyPlacementArg(anything());
Finder->addMatcher(
- traverse(TK_AsIs,
- cxxConstructExpr(
- anyOf(hasParent(cxxBindTemporaryExpr()),
- hasParent(varDecl().bind(DirectVar)),
- hasParent(exprWithCleanups(hasParent(varDecl().bind(DirectVar))))),
- hasType(getSmartPointerTypeMatcher()), argumentCountIs(1),
- hasArgument(0, ignoringCleanups(
- cxxNewExpr(hasType(pointsTo(qualType(
- hasCanonicalType(
- equalsBoundNode(
- PointerType))))),
- CanCallCtor, unless(IsPlacement))
- .bind(NewExpression))),
- unless(isInTemplateInstantiation()))
- .bind(ConstructorCall)),
+ traverse(
+ TK_AsIs,
+ cxxConstructExpr(
+ anyOf(hasParent(cxxBindTemporaryExpr()),
+ hasParent(varDecl().bind(DirectVar)),
+ hasParent(exprWithCleanups(
+ hasParent(varDecl().bind(DirectVar))))),
+ hasType(getSmartPointerTypeMatcher()), argumentCountIs(1),
+ hasArgument(
+ 0, ignoringCleanups(
+ cxxNewExpr(hasType(pointsTo(qualType(hasCanonicalType(
+ equalsBoundNode(PointerType))))),
+ CanCallCtor, unless(IsPlacement))
+ .bind(NewExpression))),
+ unless(isInTemplateInstantiation()))
+ .bind(ConstructorCall)),
this);
Finder->addMatcher(
More information about the cfe-commits
mailing list