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 <[email protected]> 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__ + +@interface X +-m:(aggregate)x; +@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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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( _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
