https://github.com/midhuncodes7 created https://github.com/llvm/llvm-project/pull/212297
Fixes [#198967](https://github.com/llvm/llvm-project/issues/198967). `BasicAliasAnalysis::isNotCapturedBefore` uses `isPotentiallyReachable()` which only walks the forward CFG. In a function containing `setjmp`, a `longjmp` can re-enter at the `setjmp` call site — a back-edge invisible to the CFG. This causes `AA` to incorrectly return `NoAlias` for a local alloca whose address was captured in a branch that only runs before the `longjmp` re-entry. Downstream passes (GVN, DSE) then miscompile the function by eliminating stores that are still live. Fix: Introduce a new function attribute `contains_returns_twice_call`. Clang's EmitCall sets it on the enclosing function whenever it emits a call with the `returns_twice` attribute (`setjmp`, `sigsetjmp`, `_setjmp`, `vfork`, or any user-annotated `__attribute__((returns_twice))` function). `mergeAttributesForInlining` propagates it from callee to caller. In `BasicAliasAnalysis`, after the forward-CFG reachability check, an `O(1)` attribute check conservatively returns `MayAlias` for any identified function-local object in such a function. Trade-off: The check is function-wide — all allocas in a `setjmp`-containing function are treated conservatively by this path. A per-site reachability check would be more precise but `O(n)` per `AA` query. The community agreed the `O(1)` attribute approach is the correct short-term fix. Known limitation: Transparent `setjmp` wrappers that do not carry` __attribute__((returns_twice))` on their declaration are not covered for non-inlined calls. Users should annotate such wrappers explicitly; compiler-side inference is left as follow-on work. >From 30ea53a4b734b9b6b128f4efd93eec2cc88fcbf6 Mon Sep 17 00:00:00 2001 From: Midhunesh <[email protected]> Date: Thu, 16 Jul 2026 11:47:59 +0530 Subject: [PATCH 1/4] Implement ContainsReturnsTwiceCall attribute to handle returns_twice in Alias Analysis --- clang/lib/CodeGen/CGCall.cpp | 4 ++++ llvm/include/llvm/IR/Attributes.td | 3 +++ llvm/lib/Analysis/BasicAliasAnalysis.cpp | 11 ++++++++++- llvm/lib/IR/Attributes.cpp | 5 +++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 08cb9860f2f92..0919fb9171800 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -6321,6 +6321,10 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, CI->setAttributes(Attrs); CI->setCallingConv(static_cast<llvm::CallingConv::ID>(CallingConv)); + // If this call can return twice (e.g. setjmp), mark the enclosing function + if (CI->hasFnAttr(llvm::Attribute::ReturnsTwice)) + CurFn->addFnAttr(llvm::Attribute::ContainsReturnsTwiceCall); + // Apply various metadata. if (!CI->getType()->isVoidTy()) diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td index 4e45100b54d38..6f8ea1d72143f 100644 --- a/llvm/include/llvm/IR/Attributes.td +++ b/llvm/include/llvm/IR/Attributes.td @@ -297,6 +297,9 @@ def ImmArg : EnumAttr<"immarg", IntersectPreserve, [ParamAttr]>; /// Function can return twice. def ReturnsTwice : EnumAttr<"returns_twice", IntersectPreserve, [FnAttr]>; +/// Function contains a call to a function that can return twice (e.g. setjmp). +def ContainsReturnsTwiceCall : EnumAttr<"contains-returns-twice-call", IntersectPreserve, [FnAttr]>; + /// Safe Stack protection. def SafeStack : EnumAttr<"safestack", IntersectPreserve, [FnAttr]>; diff --git a/llvm/lib/Analysis/BasicAliasAnalysis.cpp b/llvm/lib/Analysis/BasicAliasAnalysis.cpp index 7df62577e04db..5b47d1a6782c1 100644 --- a/llvm/lib/Analysis/BasicAliasAnalysis.cpp +++ b/llvm/lib/Analysis/BasicAliasAnalysis.cpp @@ -257,7 +257,16 @@ CaptureComponents EarliestEscapeAnalysis::getCapturesBefore( return isNotInCycle(I, &DT, LI, CI); } - return !isPotentiallyReachable(CaptureInst, I, nullptr, &DT, LI, CI); + if (isPotentiallyReachable(CaptureInst, I, nullptr, &DT, LI, CI)) + return false; + + // A `longjmp` may re-enter the function at any `returns_twice` call + // (e.g. `setjmp`), If the function contains such a call, conservatively + // treat the object as captured. + if (DT.getRoot()->getParent()->hasFnAttr(Attribute::ContainsReturnsTwiceCall)) + return false; + + return true; }; if (IsNotCapturedBefore()) return CaptureComponents::None; diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp index 4087b25951a1c..bf38b21cf573c 100644 --- a/llvm/lib/IR/Attributes.cpp +++ b/llvm/lib/IR/Attributes.cpp @@ -2793,6 +2793,11 @@ bool AttributeFuncs::areOutlineCompatible(const Function &A, void AttributeFuncs::mergeAttributesForInlining(Function &Caller, const Function &Callee) { mergeFnAttrs(Caller, Callee); + + // If the callee contained a returns_twice call (e.g. setjmp), those calls + // are now directly in the caller's body, so propagate the attribute. + if (Callee.hasFnAttribute(Attribute::ContainsReturnsTwiceCall)) + Caller.addFnAttr(Attribute::ContainsReturnsTwiceCall); } void AttributeFuncs::mergeAttributesForOutlining(Function &Base, >From e06312c1e5f93da08be7f9d004e1d799c6f155eb Mon Sep 17 00:00:00 2001 From: Midhunesh <[email protected]> Date: Thu, 16 Jul 2026 12:36:18 +0530 Subject: [PATCH 2/4] Fix attribute --- llvm/include/llvm/IR/Attributes.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td index 6f8ea1d72143f..072244c733412 100644 --- a/llvm/include/llvm/IR/Attributes.td +++ b/llvm/include/llvm/IR/Attributes.td @@ -298,7 +298,7 @@ def ImmArg : EnumAttr<"immarg", IntersectPreserve, [ParamAttr]>; def ReturnsTwice : EnumAttr<"returns_twice", IntersectPreserve, [FnAttr]>; /// Function contains a call to a function that can return twice (e.g. setjmp). -def ContainsReturnsTwiceCall : EnumAttr<"contains-returns-twice-call", IntersectPreserve, [FnAttr]>; +def ContainsReturnsTwiceCall : EnumAttr<"contains_returns_twice_call", IntersectPreserve, [FnAttr]>; /// Safe Stack protection. def SafeStack : EnumAttr<"safestack", IntersectPreserve, [FnAttr]>; >From 99f20a33415a8ac8186982733e93de65fb8801aa Mon Sep 17 00:00:00 2001 From: Midhunesh <[email protected]> Date: Thu, 16 Jul 2026 12:55:28 +0530 Subject: [PATCH 3/4] Fix attribute usage --- llvm/lib/Analysis/BasicAliasAnalysis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/lib/Analysis/BasicAliasAnalysis.cpp b/llvm/lib/Analysis/BasicAliasAnalysis.cpp index 5b47d1a6782c1..95279f10d5b32 100644 --- a/llvm/lib/Analysis/BasicAliasAnalysis.cpp +++ b/llvm/lib/Analysis/BasicAliasAnalysis.cpp @@ -263,7 +263,7 @@ CaptureComponents EarliestEscapeAnalysis::getCapturesBefore( // A `longjmp` may re-enter the function at any `returns_twice` call // (e.g. `setjmp`), If the function contains such a call, conservatively // treat the object as captured. - if (DT.getRoot()->getParent()->hasFnAttr(Attribute::ContainsReturnsTwiceCall)) + if (DT.getRoot()->getParent()->hasFnAttribute(Attribute::ContainsReturnsTwiceCall)) return false; return true; >From 0d43927dd624411e59fd0ab069b5216b0ebec485 Mon Sep 17 00:00:00 2001 From: Midhunesh <[email protected]> Date: Mon, 27 Jul 2026 21:48:09 +0530 Subject: [PATCH 4/4] Add tests to check returns_twice --- .../setjmp-contains-returns-twice-attr.c | 60 ++++++++++ llvm/test/Analysis/BasicAA/setjmp-longjmp.ll | 104 ++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 clang/test/CodeGen/setjmp-contains-returns-twice-attr.c create mode 100644 llvm/test/Analysis/BasicAA/setjmp-longjmp.ll diff --git a/clang/test/CodeGen/setjmp-contains-returns-twice-attr.c b/clang/test/CodeGen/setjmp-contains-returns-twice-attr.c new file mode 100644 index 0000000000000..be3f5dffc75a6 --- /dev/null +++ b/clang/test/CodeGen/setjmp-contains-returns-twice-attr.c @@ -0,0 +1,60 @@ +// Test that functions containing setjmp / sigsetjmp / _setjmp calls, and +// functions containing calls to user-declared __attribute__((returns_twice)) +// functions, are annotated with the contains_returns_twice_call attribute. +// +// The attribute is used by BasicAA to conservatively return MayAlias for +// local allocas in such functions, preventing miscompilation via longjmp +// re-entry paths invisible in the forward CFG. +// +// See: https://github.com/llvm/llvm-project/issues/198967 + +// RUN: %clang_cc1 -x c %s -triple x86_64-linux-gnu -emit-llvm -o - | FileCheck %s + +struct __jmp_buf_tag { int n; }; +typedef struct __jmp_buf_tag jmp_buf[1]; +typedef struct __jmp_buf_tag sigjmp_buf[1]; + +int setjmp(struct __jmp_buf_tag *); +int sigsetjmp(struct __jmp_buf_tag *, int); +int _setjmp(struct __jmp_buf_tag *); + +__attribute__((__returns_twice__)) void checkpoint(void); + +// CHECK: ; Function Attrs:{{.*}}contains_returns_twice_call +// CHECK-NEXT: define{{.*}}@bar_setjmp( +void bar_setjmp(void) { + jmp_buf buf; + setjmp(buf); +} + +// CHECK: ; Function Attrs:{{.*}}contains_returns_twice_call +// CHECK-NEXT: define{{.*}}@bar_sigsetjmp( +void bar_sigsetjmp(void) { + sigjmp_buf buf; + sigsetjmp(buf, 0); +} + +// CHECK: ; Function Attrs:{{.*}}contains_returns_twice_call +// CHECK-NEXT: define{{.*}}@bar_setjmp_underscore( +void bar_setjmp_underscore(void) { + jmp_buf buf; + _setjmp(buf); +} + +// A user-declared returns_twice function must also trigger the attribute. +// CHECK: ; Function Attrs:{{.*}}contains_returns_twice_call +// CHECK-NEXT: define{{.*}}@bar_checkpoint( +void bar_checkpoint(void) { + checkpoint(); +} + +// A function with no returns_twice call must NOT have the attribute. +// The emitted "; Function Attrs:" line for bar_plain will contain only the +// default function attributes (noinline nounwind optnone), not +// contains_returns_twice_call. +// CHECK: ; Function Attrs: noinline nounwind optnone +// CHECK-NEXT: define{{.*}}@bar_plain( +void bar_plain(void) { + int x = 42; + (void)x; +} diff --git a/llvm/test/Analysis/BasicAA/setjmp-longjmp.ll b/llvm/test/Analysis/BasicAA/setjmp-longjmp.ll new file mode 100644 index 0000000000000..63a73f2422d68 --- /dev/null +++ b/llvm/test/Analysis/BasicAA/setjmp-longjmp.ll @@ -0,0 +1,104 @@ +; Test that BasicAA conservatively returns MayAlias for local allocas in +; functions containing returns_twice calls (e.g. setjmp/sigsetjmp), to prevent +; miscompilation via longjmp re-entry paths invisible in the forward CFG. +; +; Reproduces: https://github.com/llvm/llvm-project/issues/198967 +; +; Without the fix, GVN incorrectly concludes that the store through %p_val +; (which may alias %i via a longjmp re-entry) cannot modify %i, then DSE +; eliminates the "store i32 13, ptr %i" as dead. The fix adds an O(1) +; check for the contains_returns_twice_call function attribute at the point +; where isNotCapturedBefore would otherwise return true. + +; RUN: opt < %s -aa-pipeline=basic-aa -passes=gvn,dse -S | FileCheck %s + +target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +@x = external global i32 +@ii = external global i32 +@p = external global ptr + +declare void @redo() +declare void @checkpoint() #0 + +; Control case: function has the same CFG structure but no contains_returns_twice_call +; attribute. GVN/DSE can legally eliminate "store i32 13, ptr %i" because in the +; forward CFG the only path to if.else does not pass through if.then (where &i is +; stored to @p). BasicAA correctly returns NoAlias for (%i, %p_val) here. +; +; Fix case CHECK directives are listed here so that CHECK-NOT is correctly scoped +; between the two CHECK-LABELs: it checks that the store is absent in bar_no_attr +; and present in bar_with_attr. +; +; CHECK-LABEL: define void @bar_no_attr( +; CHECK-NOT: store i32 13, ptr %i +; CHECK-LABEL: define void @bar_with_attr( +; CHECK: store i32 13, ptr %i +define void @bar_no_attr() { +entry: + %i = alloca i32, align 4 + %x_val = load i32, ptr @x + %cond = icmp ne i32 %x_val, 0 + br i1 %cond, label %if.then, label %if.else + +if.then: + store ptr %i, ptr @p + call void @redo() + br label %if.end + +if.else: + ; Bug: without the attribute, GVN sees "store i32 42, ptr %p_val" as + ; NoAlias with %i (the capture in if.then is not visible in the forward + ; CFG), so it forwards 13 through the load and DSE removes this store. + store i32 13, ptr %i + %p_val = load ptr, ptr @p + store i32 42, ptr %p_val + %ii_val = load i32, ptr %i + store i32 %ii_val, ptr @ii + br label %if.end + +if.end: + ret void +} + +; Fix case: same structure with contains_returns_twice_call on the function +; (set automatically by Clang when emitting setjmp / sigsetjmp / any +; returns_twice call). BasicAA conservatively returns MayAlias for (%i, +; %p_val) because a longjmp can re-enter at the checkpoint() site with @p +; already holding &i, making the if.then capture visible to if.else. +; +; The "store i32 13, ptr %i" MUST be preserved; removing it is a +; miscompilation. (CHECKs for this function are listed above to correctly +; scope the CHECK-NOT between the two CHECK-LABELs.) +define void @bar_with_attr() #1 { +entry: + %i = alloca i32, align 4 + call void @checkpoint() #0 + %x_val = load i32, ptr @x + %cond = icmp ne i32 %x_val, 0 + br i1 %cond, label %if.then, label %if.else + +if.then: + ; On the first pass: stores &i into @p, then calls redo(). + ; redo() performs longjmp(buf) which re-enters at checkpoint() above. + ; On re-entry: if.then runs again setting p = &i, then if.else runs + ; with @p == &i, making "store i32 42, ptr %p_val" alias "store i32 13, ptr %i". + store ptr %i, ptr @p + call void @redo() + br label %if.end + +if.else: + store i32 13, ptr %i + %p_val = load ptr, ptr @p + store i32 42, ptr %p_val ; may write to %i via longjmp re-entry path + %ii_val = load i32, ptr %i + store i32 %ii_val, ptr @ii + br label %if.end + +if.end: + ret void +} + +attributes #0 = { returns_twice } +attributes #1 = { contains_returns_twice_call } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
