https://github.com/kumarak updated 
https://github.com/llvm/llvm-project/pull/208850

>From a94f7b2287eb04da44ecea4fa48f18733c9c4779 Mon Sep 17 00:00:00 2001
From: AkshayK <[email protected]>
Date: Fri, 10 Jul 2026 17:39:00 -0400
Subject: [PATCH 1/2] [CIR] Fix use-after-free when emitting ternary with a
 throw-expression arm

VisitAbstractConditionalOperator saved a raw insertion point when an arm
yielded no value, intending to patch in a cir.yield later. For a noreturn
arm (e.g. throw), that insertion point refers to the empty dead-code block
created after the throw's terminator - a block LexicalScope::cleanup()
erases when emitBranch's scope exits. The deferred patch loop then
restored the insertion point into the freed block, producing invalid IR
("cir.yield must be the last operation in the parent block") or a
segfault, depending on heap reuse.

Skip saving an insertion point in that case: the region is already
terminated by cir.unreachable and needs no yield. The verifier and
cir-flatten-cfg both accept unreachable-terminated ternary regions.

The existing ternary-throw.cpp only covered glvalue conditionals, which
take the LValue path; add the scalar-rvalue cases that reach this code,
including both-arms-throw.
---
 clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp |  6 ++
 clang/test/CIR/CodeGen/ternary-throw.cpp   | 89 ++++++++++++++++++++++
 2 files changed, 95 insertions(+)

diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
index 645cca9fe16eb..8e1e955be9051 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
@@ -2929,6 +2929,12 @@ mlir::Value 
ScalarExprEmitter::VisitAbstractConditionalOperator(
       yieldTy = branch.getType();
       cir::YieldOp::create(b, loc, branch);
     } else {
+      // A noreturn branch (e.g. throw) leaves the insertion point in a dead
+      // block that LexicalScope cleanup erases; no yield is needed and saving
+      // an insertion point into it would dangle.
+      mlir::Block *curBlock = b.getInsertionBlock();
+      if (curBlock->empty() && !curBlock->isEntryBlock())
+        return;
       // If LHS or RHS is a throw or void expression we need to patch
       // arms as to properly match yield types.
       insertPoints.push_back(b.saveInsertionPoint());
diff --git a/clang/test/CIR/CodeGen/ternary-throw.cpp 
b/clang/test/CIR/CodeGen/ternary-throw.cpp
index fbafa9811d76e..8a342ee51308d 100644
--- a/clang/test/CIR/CodeGen/ternary-throw.cpp
+++ b/clang/test/CIR/CodeGen/ternary-throw.cpp
@@ -650,3 +650,92 @@ void test_agg_throw_false(bool flag) {
 // OGCG:   unreachable
 // OGCG: [[END]]:
 // OGCG:   ret void
+
+int test_scalar_throw_true(bool flag, int x) {
+  return flag ? throw 0 : x;
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z22test_scalar_throw_truebi(
+// CIR:   %[[COND:.*]] = cir.load{{.*}} : !cir.ptr<!cir.bool>, !cir.bool
+// CIR:   %{{.*}} = cir.ternary(%[[COND]], true {
+// CIR:     %[[EXC:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
+// CIR:     cir.throw %[[EXC]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:     cir.unreachable
+// CIR-NEXT:   }, false {
+// CIR:     %[[X:.*]] = cir.load{{.*}} : !cir.ptr<!s32i>, !s32i
+// CIR:     cir.yield %[[X]] : !s32i
+// CIR:   }) : (!cir.bool) -> !s32i
+
+// LLVM-LABEL: define{{.*}} i32 @_Z22test_scalar_throw_truebi(
+// LLVM:   br i1 %{{.*}}, label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]]
+// LLVM: [[TRUE_BB]]:
+// LLVM:   call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIi
+// LLVM:   unreachable
+// LLVM: [[FALSE_BB]]:
+// LLVM:   %{{.*}} = load i32
+// LLVM:   ret i32
+
+// OGCG-LABEL: define{{.*}} i32 @_Z22test_scalar_throw_truebi(
+// OGCG:   br i1 %{{.*}}, label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]]
+// OGCG: [[TRUE_BB]]:
+// OGCG:   call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIi
+// OGCG:   unreachable
+// OGCG: [[FALSE_BB]]:
+// OGCG:   ret i32
+
+int test_scalar_throw_false(bool flag, int x) {
+  return flag ? x : throw 0;
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z23test_scalar_throw_falsebi(
+// CIR:   %{{.*}} = cir.ternary(%{{.*}}, true {
+// CIR:     %[[X:.*]] = cir.load{{.*}} : !cir.ptr<!s32i>, !s32i
+// CIR:     cir.yield %[[X]] : !s32i
+// CIR:   }, false {
+// CIR:     %[[EXC:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
+// CIR:     cir.throw %[[EXC]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:     cir.unreachable
+// CIR-NEXT:   }) : (!cir.bool) -> !s32i
+
+// LLVM-LABEL: define{{.*}} i32 @_Z23test_scalar_throw_falsebi(
+// LLVM:   br i1 %{{.*}}, label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]]
+// LLVM: [[FALSE_BB]]:
+// LLVM:   call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIi
+// LLVM:   unreachable
+
+// OGCG-LABEL: define{{.*}} i32 @_Z23test_scalar_throw_falsebi(
+// OGCG:   br i1 %{{.*}}, label %{{.*}}, label %[[FALSE_BB:.*]]
+// OGCG: [[FALSE_BB]]:
+// OGCG:   call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIi
+// OGCG:   unreachable
+
+void test_both_throw(bool flag) {
+  flag ? throw 1 : throw 2;
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z15test_both_throwb(
+// CIR:   cir.ternary(%{{.*}}, true {
+// CIR:     cir.throw %{{.*}} : !cir.ptr<!s32i>, @_ZTIi
+// CIR:     cir.unreachable
+// CIR-NEXT:   }, false {
+// CIR:     cir.throw %{{.*}} : !cir.ptr<!s32i>, @_ZTIi
+// CIR:     cir.unreachable
+// CIR-NEXT:   })
+
+// LLVM-LABEL: define{{.*}} void @_Z15test_both_throwb(
+// LLVM:   br i1 %{{.*}}, label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]]
+// LLVM: [[TRUE_BB]]:
+// LLVM:   call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIi
+// LLVM:   unreachable
+// LLVM: [[FALSE_BB]]:
+// LLVM:   call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIi
+// LLVM:   unreachable
+
+// OGCG-LABEL: define{{.*}} void @_Z15test_both_throwb(
+// OGCG:   br i1 %{{.*}}, label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]]
+// OGCG: [[TRUE_BB]]:
+// OGCG:   call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIi
+// OGCG:   unreachable
+// OGCG: [[FALSE_BB]]:
+// OGCG:   call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIi
+// OGCG:   unreachable

>From 21334628c3b11d34bd9879902068c1d3f10627f9 Mon Sep 17 00:00:00 2001
From: AkshayK <[email protected]>
Date: Mon, 13 Jul 2026 18:44:18 -0400
Subject: [PATCH 2/2] [CIR] Address review: terminate ternary regions after
 creation instead of saving insertion points

---
 clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 66 ++++++++--------------
 1 file changed, 22 insertions(+), 44 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
index 8e1e955be9051..2d76d40e25dda 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
@@ -2906,8 +2906,6 @@ mlir::Value 
ScalarExprEmitter::VisitAbstractConditionalOperator(
 
   mlir::Value condV = cgf.emitOpOnBoolExpr(loc, condExpr);
   CIRGenFunction::ConditionalEvaluation eval(cgf);
-  SmallVector<mlir::OpBuilder::InsertPoint, 2> insertPoints{};
-  mlir::Type yieldTy{};
 
   auto emitBranch = [&](mlir::OpBuilder &b, mlir::Location loc, Expr *expr) {
     CIRGenFunction::LexicalScope lexScope{cgf, loc, b.getInsertionBlock()};
@@ -2925,55 +2923,35 @@ mlir::Value 
ScalarExprEmitter::VisitAbstractConditionalOperator(
       branchCleanups.forceCleanup({&branch});
     }
 
-    if (branch) {
-      yieldTy = branch.getType();
+    if (branch)
       cir::YieldOp::create(b, loc, branch);
-    } else {
-      // A noreturn branch (e.g. throw) leaves the insertion point in a dead
-      // block that LexicalScope cleanup erases; no yield is needed and saving
-      // an insertion point into it would dangle.
-      mlir::Block *curBlock = b.getInsertionBlock();
-      if (curBlock->empty() && !curBlock->isEntryBlock())
-        return;
-      // If LHS or RHS is a throw or void expression we need to patch
-      // arms as to properly match yield types.
-      insertPoints.push_back(b.saveInsertionPoint());
-    }
   };
 
-  mlir::Value result = cir::TernaryOp::create(
-                           builder, loc, condV,
-                           /*trueBuilder=*/
-                           [&](mlir::OpBuilder &b, mlir::Location loc) {
-                             emitBranch(b, loc, lhsExpr);
-                           },
-                           /*falseBuilder=*/
-                           [&](mlir::OpBuilder &b, mlir::Location loc) {
-                             emitBranch(b, loc, rhsExpr);
-                           })
-                           .getResult();
-
-  if (!insertPoints.empty()) {
-    // If both arms are void, so be it.
-    if (!yieldTy)
-      yieldTy = cgf.voidTy;
-
-    // Insert required yields.
-    for (mlir::OpBuilder::InsertPoint &toInsert : insertPoints) {
+  cir::TernaryOp ternary = cir::TernaryOp::create(
+      builder, loc, condV,
+      /*trueBuilder=*/
+      [&](mlir::OpBuilder &b, mlir::Location loc) {
+        emitBranch(b, loc, lhsExpr);
+      },
+      /*falseBuilder=*/
+      [&](mlir::OpBuilder &b, mlir::Location loc) {
+        emitBranch(b, loc, rhsExpr);
+      });
+
+  // Only a void arm can be left unterminated (a noreturn arm already ends
+  // in cir.unreachable); close it with an empty cir.yield.
+  for (mlir::Region *region :
+       {&ternary.getTrueRegion(), &ternary.getFalseRegion()}) {
+    mlir::Block &lastBlock = region->back();
+    if (lastBlock.empty() ||
+        !lastBlock.back().hasTrait<mlir::OpTrait::IsTerminator>()) {
       mlir::OpBuilder::InsertionGuard guard(builder);
-      builder.restoreInsertionPoint(toInsert);
-
-      // Block does not return: build empty yield.
-      if (mlir::isa<cir::VoidType>(yieldTy)) {
-        cir::YieldOp::create(builder, loc);
-      } else { // Block returns: set null yield value.
-        mlir::Value op0 = builder.getNullValue(yieldTy, loc);
-        cir::YieldOp::create(builder, loc, op0);
-      }
+      builder.setInsertionPointToEnd(&lastBlock);
+      cir::YieldOp::create(builder, loc);
     }
   }
 
-  return result;
+  return ternary.getResult();
 }
 
 mlir::Value CIRGenFunction::emitScalarPrePostIncDec(const UnaryOperator *e,

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to