[clang] [Clang] Fixed double finally block execution (PR #146796)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 7 11:04:30 PDT 2025
https://github.com/yasster updated https://github.com/llvm/llvm-project/pull/146796
>From 2ccf38469c28392597199e98515dd5a21cfc7c84 Mon Sep 17 00:00:00 2001
From: Yassine Missoum <mmissoum at microsoft.com>
Date: Tue, 1 Jul 2025 15:25:21 -0700
Subject: [PATCH 1/2] Fixed double finally block execution
---
clang/lib/CodeGen/CGException.cpp | 45 +++++++++++++++----
.../test/CodeGen/seh-finally-double-execute.c | 34 ++++++++++++++
2 files changed, 71 insertions(+), 8 deletions(-)
create mode 100644 clang/test/CodeGen/seh-finally-double-execute.c
diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp
index ad138b9876e8c..ab4086716cc1c 100644
--- a/clang/lib/CodeGen/CGException.cpp
+++ b/clang/lib/CodeGen/CGException.cpp
@@ -1368,14 +1368,24 @@ namespace {
llvm::FunctionCallee EndCatchFn;
llvm::FunctionCallee RethrowFn;
llvm::Value *SavedExnVar;
+ llvm::Value *FinallyExecutedFlag;
PerformFinally(const Stmt *Body, llvm::Value *ForEHVar,
llvm::FunctionCallee EndCatchFn,
- llvm::FunctionCallee RethrowFn, llvm::Value *SavedExnVar)
+ llvm::FunctionCallee RethrowFn, llvm::Value *SavedExnVar,
+ llvm::Value *FinallyExecutedFlag)
: Body(Body), ForEHVar(ForEHVar), EndCatchFn(EndCatchFn),
- RethrowFn(RethrowFn), SavedExnVar(SavedExnVar) {}
+ RethrowFn(RethrowFn), SavedExnVar(SavedExnVar),
+ FinallyExecutedFlag(FinallyExecutedFlag) {}
void Emit(CodeGenFunction &CGF, Flags flags) override {
+ // Only execute the finally block if it hasn't already run.
+ llvm::BasicBlock *RunFinallyBB = CGF.createBasicBlock("finally.run");
+ llvm::BasicBlock *SkipFinallyBB = CGF.createBasicBlock("finally.skip");
+ llvm::Value *AlreadyExecuted = CGF.Builder.CreateFlagLoad(FinallyExecutedFlag, "finally.executed");
+ CGF.Builder.CreateCondBr(AlreadyExecuted, SkipFinallyBB, RunFinallyBB);
+ CGF.EmitBlock(RunFinallyBB);
+ CGF.Builder.CreateFlagStore(true, FinallyExecutedFlag);
// Enter a cleanup to call the end-catch function if one was provided.
if (EndCatchFn)
CGF.EHStack.pushCleanup<CallEndCatchForFinally>(NormalAndEHCleanup,
@@ -1429,6 +1439,7 @@ namespace {
// Now make sure we actually have an insertion point or the
// cleanup gods will hate us.
CGF.EnsureInsertPoint();
+ CGF.EmitBlock(SkipFinallyBB);
}
};
} // end anonymous namespace
@@ -1478,10 +1489,12 @@ void CodeGenFunction::FinallyInfo::enter(CodeGenFunction &CGF, const Stmt *body,
ForEHVar = CGF.CreateTempAlloca(CGF.Builder.getInt1Ty(), "finally.for-eh");
CGF.Builder.CreateFlagStore(false, ForEHVar);
- // Enter a normal cleanup which will perform the @finally block.
+ // Allocate a flag to ensure the finally block is only executed once.
+ llvm::Value *FinallyExecutedFlag = CGF.CreateTempAlloca(CGF.Builder.getInt1Ty(), "finally.executed");
+ CGF.Builder.CreateFlagStore(false, FinallyExecutedFlag);
CGF.EHStack.pushCleanup<PerformFinally>(NormalCleanup, body,
ForEHVar, endCatchFn,
- rethrowFn, SavedExnVar);
+ rethrowFn, SavedExnVar, FinallyExecutedFlag);
// Enter a catch-all scope.
llvm::BasicBlock *catchBB = CGF.createBasicBlock("finally.catchall");
@@ -1724,10 +1737,18 @@ void CodeGenFunction::VolatilizeTryBlocks(
namespace {
struct PerformSEHFinally final : EHScopeStack::Cleanup {
llvm::Function *OutlinedFinally;
- PerformSEHFinally(llvm::Function *OutlinedFinally)
- : OutlinedFinally(OutlinedFinally) {}
+ llvm::Value *FinallyExecutedFlag;
+ PerformSEHFinally(llvm::Function *OutlinedFinally, llvm::Value *FinallyExecutedFlag)
+ : OutlinedFinally(OutlinedFinally), FinallyExecutedFlag(FinallyExecutedFlag) {}
void Emit(CodeGenFunction &CGF, Flags F) override {
+ // Only execute the finally block if it hasn't already run.
+ llvm::BasicBlock *RunFinallyBB = CGF.createBasicBlock("finally.run");
+ llvm::BasicBlock *SkipFinallyBB = CGF.createBasicBlock("finally.skip");
+ llvm::Value *AlreadyExecuted = CGF.Builder.CreateFlagLoad(FinallyExecutedFlag, "finally.executed");
+ CGF.Builder.CreateCondBr(AlreadyExecuted, SkipFinallyBB, RunFinallyBB);
+ CGF.EmitBlock(RunFinallyBB);
+ CGF.Builder.CreateFlagStore(true, FinallyExecutedFlag);
ASTContext &Context = CGF.getContext();
CodeGenModule &CGM = CGF.CGM;
@@ -1769,6 +1790,8 @@ struct PerformSEHFinally final : EHScopeStack::Cleanup {
auto Callee = CGCallee::forDirect(OutlinedFinally);
CGF.EmitCall(FnInfo, Callee, ReturnValueSlot(), Args);
+
+ CGF.EmitBlock(SkipFinallyBB);
}
};
} // end anonymous namespace
@@ -2164,7 +2187,10 @@ llvm::Value *CodeGenFunction::EmitSEHAbnormalTermination() {
void CodeGenFunction::pushSEHCleanup(CleanupKind Kind,
llvm::Function *FinallyFunc) {
- EHStack.pushCleanup<PerformSEHFinally>(Kind, FinallyFunc);
+ // Allocate a flag to ensure the finally block is only executed once.
+ llvm::Value *FinallyExecutedFlag = CreateTempAlloca(Builder.getInt1Ty(), "finally.executed");
+ Builder.CreateFlagStore(false, FinallyExecutedFlag);
+ EHStack.pushCleanup<PerformSEHFinally>(Kind, FinallyFunc, FinallyExecutedFlag);
}
void CodeGenFunction::EnterSEHTryStmt(const SEHTryStmt &S) {
@@ -2175,8 +2201,11 @@ void CodeGenFunction::EnterSEHTryStmt(const SEHTryStmt &S) {
llvm::Function *FinallyFunc =
HelperCGF.GenerateSEHFinallyFunction(*this, *Finally);
+ // Allocate a flag to ensure the finally block is only executed once.
+ llvm::Value *FinallyExecutedFlag = CreateTempAlloca(Builder.getInt1Ty(), "finally.executed");
+ Builder.CreateFlagStore(false, FinallyExecutedFlag);
// Push a cleanup for __finally blocks.
- EHStack.pushCleanup<PerformSEHFinally>(NormalAndEHCleanup, FinallyFunc);
+ EHStack.pushCleanup<PerformSEHFinally>(NormalAndEHCleanup, FinallyFunc, FinallyExecutedFlag);
return;
}
diff --git a/clang/test/CodeGen/seh-finally-double-execute.c b/clang/test/CodeGen/seh-finally-double-execute.c
new file mode 100644
index 0000000000000..0f2d417e0f4fb
--- /dev/null
+++ b/clang/test/CodeGen/seh-finally-double-execute.c
@@ -0,0 +1,34 @@
+// RUN: %clang_cc1 -triple x86_64-windows-msvc -emit-llvm -O0 -fms-extensions -fexceptions -fcxx-exceptions -o - %s | FileCheck %s
+
+int freed = 0;
+void myfree(int *p) {
+ ++freed;
+}
+
+// CHECK-LABEL: define dso_local i32 @main(
+int main() {
+ int x = 0;
+ int *p = &x;
+ __try {
+ return 0;
+ } __finally {
+ myfree(p);
+ }
+}
+
+// Check that a guard flag is allocated to prevent double execution
+// CHECK: %finally.executed = alloca i1
+// CHECK: store i1 false, ptr %finally.executed
+
+// Check the main function has guard logic to prevent double execution
+// CHECK: %finally.executed{{.*}} = load i1, ptr %finally.executed
+// CHECK: br i1 %finally.executed{{.*}}, label %finally.skip, label %finally.run
+// CHECK: finally.run:
+// CHECK: store i1 true, ptr %finally.executed
+// CHECK: call void @"?fin$0 at 0@main@@"
+// CHECK: finally.skip:
+
+// Check the finally helper function is called only once
+// CHECK-LABEL: define internal void @"?fin$0 at 0@main@@"
+// CHECK: call void @myfree
+// CHECK-NOT: call void @myfree
>From 4c99b3f9dbdfa408169c4167138e64debae40233 Mon Sep 17 00:00:00 2001
From: Yassine Missoum <mmissoum at microsoft.com>
Date: Mon, 7 Jul 2025 10:58:17 -0700
Subject: [PATCH 2/2] Updated the test and the code to fix double finally issue
---
clang/lib/CodeGen/CGException.cpp | 13 +-
.../test/CodeGen/seh-finally-double-execute.c | 130 ++++++++++++++----
2 files changed, 113 insertions(+), 30 deletions(-)
diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp
index ab4086716cc1c..f3cbb77d810a7 100644
--- a/clang/lib/CodeGen/CGException.cpp
+++ b/clang/lib/CodeGen/CGException.cpp
@@ -2201,11 +2201,8 @@ void CodeGenFunction::EnterSEHTryStmt(const SEHTryStmt &S) {
llvm::Function *FinallyFunc =
HelperCGF.GenerateSEHFinallyFunction(*this, *Finally);
- // Allocate a flag to ensure the finally block is only executed once.
- llvm::Value *FinallyExecutedFlag = CreateTempAlloca(Builder.getInt1Ty(), "finally.executed");
- Builder.CreateFlagStore(false, FinallyExecutedFlag);
// Push a cleanup for __finally blocks.
- EHStack.pushCleanup<PerformSEHFinally>(NormalAndEHCleanup, FinallyFunc, FinallyExecutedFlag);
+ pushSEHCleanup(NormalAndEHCleanup, FinallyFunc);
return;
}
@@ -2238,7 +2235,13 @@ void CodeGenFunction::EnterSEHTryStmt(const SEHTryStmt &S) {
void CodeGenFunction::ExitSEHTryStmt(const SEHTryStmt &S) {
// Just pop the cleanup if it's a __finally block.
if (S.getFinallyHandler()) {
- PopCleanupBlock();
+ // In most cases, the cleanup is active, but if the __try contains a
+ // noreturn call, the block will be terminated and the cleanup will be
+ // inactive.
+ if (HaveInsertPoint())
+ PopCleanupBlock();
+ else
+ EHStack.popCleanup();
return;
}
diff --git a/clang/test/CodeGen/seh-finally-double-execute.c b/clang/test/CodeGen/seh-finally-double-execute.c
index 0f2d417e0f4fb..5924993ca78a5 100644
--- a/clang/test/CodeGen/seh-finally-double-execute.c
+++ b/clang/test/CodeGen/seh-finally-double-execute.c
@@ -1,34 +1,114 @@
-// RUN: %clang_cc1 -triple x86_64-windows-msvc -emit-llvm -O0 -fms-extensions -fexceptions -fcxx-exceptions -o - %s | FileCheck %s
+// RUN: %clang_cc1 -x c -triple x86_64-windows-msvc -emit-llvm -O0 -fms-extensions -fexceptions -o - %s | FileCheck %s
+// Global state to track resource cleanup
int freed = 0;
-void myfree(int *p) {
- ++freed;
+void* allocated_buffer = 0;
+
+// External functions that prevent optimization
+extern void external_operation(void);
+extern int external_condition(void);
+extern void external_cleanup(void*);
+
+// Declare SEH exception functions
+void RaiseException(unsigned long code, unsigned long flags, unsigned long argc, void* argv);
+
+// Simulate complex resource allocation/cleanup
+void* allocate_buffer(int size) {
+ // Simulate allocation that could fail
+ if (external_condition()) {
+ allocated_buffer = (void*)0x12345678; // Mock allocation
+ return allocated_buffer;
+ }
+ return 0;
}
-// CHECK-LABEL: define dso_local i32 @main(
-int main() {
- int x = 0;
- int *p = &x;
+void free_buffer(void* buffer) {
+ if (buffer && freed == 0) {
+ freed = 1;
+ allocated_buffer = 0;
+ external_cleanup(buffer); // External cleanup prevents inlining
+ }
+}
+
+
+int complex_operation_with_finally(int operation_type) {
+ void* buffer = 0;
+ int result = 0;
+
__try {
- return 0;
+ // Multiple operations that could throw exceptions
+ buffer = allocate_buffer(1024);
+ if (!buffer) {
+ result = -1;
+ __leave; // Early exit - finally should still run
+ }
+
+ // Simulate complex operations that could throw
+ external_operation(); // Could throw
+
+ if (operation_type == 1) {
+ external_operation(); // Another potential throw point
+ }
+
+ result = 0; // Success
} __finally {
- myfree(p);
+ // Critical cleanup that must run exactly once
+ if (buffer) {
+ free_buffer(buffer);
+ }
+ }
+
+ // Exception raised after finally block has already executed
+ // This is the pattern that causes double execution in resource cleanup
+ if (operation_type == 2) {
+ RaiseException(0xC0000005, 0, 0, 0);
}
+
+ return result;
}
-// Check that a guard flag is allocated to prevent double execution
-// CHECK: %finally.executed = alloca i1
-// CHECK: store i1 false, ptr %finally.executed
-
-// Check the main function has guard logic to prevent double execution
-// CHECK: %finally.executed{{.*}} = load i1, ptr %finally.executed
-// CHECK: br i1 %finally.executed{{.*}}, label %finally.skip, label %finally.run
-// CHECK: finally.run:
-// CHECK: store i1 true, ptr %finally.executed
-// CHECK: call void @"?fin$0 at 0@main@@"
-// CHECK: finally.skip:
-
-// Check the finally helper function is called only once
-// CHECK-LABEL: define internal void @"?fin$0 at 0@main@@"
-// CHECK: call void @myfree
-// CHECK-NOT: call void @myfree
+// CHECK: define dso_local i32 @complex_operation_with_finally(i32 noundef %operation_type)
+// CHECK: %finally.executed = alloca i1, align 1
+// CHECK: store i1 false, ptr %finally.executed, align 1
+
+// Normal path: check if finally already ran.
+// CHECK-LABEL: __try.__leave:
+// CHECK: %[[finally_executed:.+]] = load i1, ptr %finally.executed, align 1
+// CHECK: br i1 %[[finally_executed]], label %finally.skip, label %finally.run
+
+// Normal path: run finally and set flag.
+// CHECK-LABEL: finally.run:
+// CHECK: store i1 true, ptr %finally.executed, align 1
+// CHECK: call void @"?fin$0 at 0@complex_operation_with_finally@@"(i8 noundef
+// CHECK: br label %finally.skip
+
+// Normal path: skip finally.
+// CHECK-LABEL: finally.skip:
+// CHECK: %[[cmp:.+]] = icmp eq i32 {{.+}}, 2
+// CHECK: br i1 %[[cmp]], label %if.then10, label %if.end11
+
+// Exception path: check if finally already ran.
+// CHECK-LABEL: ehcleanup:
+// CHECK: %[[finally_executed_eh:.+]] = load i1, ptr %finally.executed, align 1
+// CHECK: br i1 %[[finally_executed_eh]], label %finally.skip8, label %finally.run7
+
+// Exception path: run finally and set flag.
+// CHECK-LABEL: finally.run7:
+// CHECK: store i1 true, ptr %finally.executed, align 1
+// CHECK: call void @"?fin$0 at 0@complex_operation_with_finally@@"(i8 noundef 1
+// CHECK: br label %finally.skip8
+
+// Exception path: skip finally.
+// CHECK-LABEL: finally.skip8:
+// CHECK: cleanupret from {{.+}} unwind to caller
+
+// CHECK: define internal void @"?fin$0 at 0@complex_operation_with_finally@@"(i8 noundef %abnormal_termination, ptr noundef %frame_pointer)
+// CHECK: call void @free_buffer(ptr noundef
+
+// CHECK-LABEL: @main
+int main() {
+ // This tests that the finally is not executed twice when an exception
+ // is raised after the finally has already run.
+ int result = complex_operation_with_finally(2);
+ return result;
+}
More information about the cfe-commits
mailing list