[clang] [clang][AST] Fix crash on labeled break/continue within switch condition statement expression (PR #228655)

via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 2 20:59:56 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Expertcoderz (Expertcoderz)

<details>
<summary>Changes</summary>

[Reopened from #<!-- -->226754 after a Git accident.]

This PR fixes the issue described in #<!-- -->184060: having a labeled `continue` (or `break`) statement in a statement expression within the condition of a `switch` statement would cause Clang to crash:

```c
void foo() {
l1: for (;;) {
    switch (({ continue l1; 1; })) {}
  }
}
```

The issue seems to be caused by a null pointer dereference in `clang/lib/AST/Stmt.cpp`. I've checked the generated IR after applying the fix to ensure that the `break`/`continue` statements are working as expected. Also added regression tests.

---
Full diff: https://github.com/llvm/llvm-project/pull/228655.diff


8 Files Affected:

- (modified) clang/docs/ReleaseNotes.md (+3) 
- (modified) clang/include/clang/AST/Stmt.h (+2-1) 
- (modified) clang/lib/AST/ByteCode/Compiler.cpp (+4-2) 
- (modified) clang/lib/AST/ExprConstant.cpp (+2-1) 
- (modified) clang/lib/AST/Stmt.cpp (+3-3) 
- (modified) clang/lib/CodeGen/CGStmt.cpp (+1-1) 
- (modified) clang/test/CodeGen/labeled-break-continue.c (+50) 
- (modified) clang/test/SemaCXX/labeled-break-continue.cpp (+18) 


``````````diff
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 929892fd34f7f..2c5d6dbfb4b05 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -822,6 +822,9 @@ features cannot lower the translation-unit ABI level;
 - Added missed information to the AST node representing the member function
   when calling a explicit object member function. (#GH218829)
 
+- Fixed a crash when encountering C2y labeled `break`/`continue` statements
+  in a statement expression within a `switch` condition.
+
 #### Miscellaneous Bug Fixes
 
 #### Miscellaneous Clang Crashes Fixed
diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h
index 5d27ded64082d..34bca3706f0a9 100644
--- a/clang/include/clang/AST/Stmt.h
+++ b/clang/include/clang/AST/Stmt.h
@@ -3109,7 +3109,8 @@ class LoopControlStmt : public Stmt {
   void setLabelDecl(LabelDecl *S) { TargetLabel = S; }
 
   /// If this is a named break/continue, get the loop or switch statement
-  /// that this targets.
+  /// that this targets. May return null if the target LabelStmt has not
+  /// yet been created.
   const Stmt *getNamedLoopOrSwitch() const;
 
   // Iterators
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 01daace5b3c83..df37b705d495f 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -7184,7 +7184,8 @@ bool Compiler<Emitter>::visitBreakStmt(const BreakStmt *S) {
     return false;
 
   OptLabelTy TargetLabel = std::nullopt;
-  const Stmt *TargetLoop = S->getNamedLoopOrSwitch();
+  const Stmt *TargetLoop =
+      S->hasLabelTarget() ? S->getNamedLoopOrSwitch() : nullptr;
   const VariableScope<Emitter> *BreakScope = nullptr;
 
   if (!TargetLoop) {
@@ -7224,7 +7225,8 @@ bool Compiler<Emitter>::visitContinueStmt(const ContinueStmt *S) {
     return false;
 
   OptLabelTy TargetLabel = std::nullopt;
-  const Stmt *TargetLoop = S->getNamedLoopOrSwitch();
+  const Stmt *TargetLoop =
+      S->hasLabelTarget() ? S->getNamedLoopOrSwitch() : nullptr;
   const VariableScope<Emitter> *ContinueScope = nullptr;
 
   if (!TargetLoop) {
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 3f295f35d1361..46c408c794f96 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -6385,7 +6385,8 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
   case Stmt::ContinueStmtClass:
   case Stmt::BreakStmtClass: {
     auto *B = cast<LoopControlStmt>(S);
-    Info.BreakContinueStack.push_back(B->getNamedLoopOrSwitch());
+    Info.BreakContinueStack.push_back(
+        B->hasLabelTarget() ? B->getNamedLoopOrSwitch() : nullptr);
     return isa<ContinueStmt>(S) ? ESR_Continue : ESR_Break;
   }
 
diff --git a/clang/lib/AST/Stmt.cpp b/clang/lib/AST/Stmt.cpp
index 15d0e6435aaf3..a8c17676d56f3 100644
--- a/clang/lib/AST/Stmt.cpp
+++ b/clang/lib/AST/Stmt.cpp
@@ -1533,9 +1533,9 @@ const Stmt *LabelStmt::getInnermostLabeledStmt() const {
 }
 
 const Stmt *LoopControlStmt::getNamedLoopOrSwitch() const {
-  if (!hasLabelTarget())
-    return nullptr;
-  return getLabelDecl()->getStmt()->getInnermostLabeledStmt();
+  assert(hasLabelTarget());
+  LabelStmt *Label = getLabelDecl()->getStmt();
+  return Label ? Label->getInnermostLabeledStmt() : nullptr;
 }
 
 DeferStmt::DeferStmt(EmptyShell Empty) : Stmt(DeferStmtClass, Empty) {}
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 03b6e84a1c136..617929e1e613d 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -1703,7 +1703,7 @@ auto CodeGenFunction::GetDestForLoopControlStmt(const LoopControlStmt &S)
     return &BreakContinueStack.back();
 
   const Stmt *LoopOrSwitch = S.getNamedLoopOrSwitch();
-  assert(LoopOrSwitch && "break/continue target not set?");
+  assert(LoopOrSwitch && "break/continue target label not available?");
   for (const BreakContinue &BC : llvm::reverse(BreakContinueStack))
     if (BC.LoopOrSwitch == LoopOrSwitch)
       return &BC;
diff --git a/clang/test/CodeGen/labeled-break-continue.c b/clang/test/CodeGen/labeled-break-continue.c
index f307a1bd79ab8..3d050a5cc6e44 100644
--- a/clang/test/CodeGen/labeled-break-continue.c
+++ b/clang/test/CodeGen/labeled-break-continue.c
@@ -279,3 +279,53 @@ void f7() {
     }
   }
 }
+
+/// https://github.com/llvm/llvm-project/issues/184060
+// CHECK-LABEL: define {{.*}} void @f8()
+// CHECK: entry:
+// CHECK:   %tmp = alloca i32, align 4
+// CHECK:   br label %l1
+// CHECK: l1:
+// CHECK:   br label %for.cond
+// CHECK: for.cond:
+// CHECK:   br label %for.end
+// CHECK: 0:
+// CHECK:   store i32 1, ptr %tmp, align 4
+// CHECK:   %1 = load i32, ptr %tmp, align 4
+// CHECK:   switch i32 %1, label %sw.epilog [
+// CHECK:   ]
+// CHECK: sw.epilog:
+// CHECK:   call {{.*}} i1 @g1()
+// CHECK:   br label %for.cond
+// CHECK: for.end:
+// CHECK:   ret void
+void f8() {
+l1: for (;;) {
+    switch (({ break l1; 1; })) {}
+    g1();
+  }
+}
+
+/// https://github.com/llvm/llvm-project/issues/184060
+// CHECK-LABEL: define {{.*}} void @f9()
+// CHECK: entry:
+// CHECK:   %tmp = alloca i32, align 4
+// CHECK:   br label %l1
+// CHECK: l1:
+// CHECK:   br label %for.cond
+// CHECK: for.cond:
+// CHECK:   br label %for.cond
+// CHECK: 0:
+// CHECK:   store i32 1, ptr %tmp, align 4
+// CHECK:   %1 = load i32, ptr %tmp, align 4
+// CHECK:   switch i32 %1, label %sw.epilog [
+// CHECK:   ]
+// CHECK: sw.epilog:
+// CHECK:   call {{.*}} i1 @g1()
+// CHECK:   br label %for.cond
+void f9() {
+l1: for (;;) {
+    switch (({ continue l1; 1; })) {}
+    g1();
+  }
+}
diff --git a/clang/test/SemaCXX/labeled-break-continue.cpp b/clang/test/SemaCXX/labeled-break-continue.cpp
index 3d34211ed745a..d600ec5ff3caf 100644
--- a/clang/test/SemaCXX/labeled-break-continue.cpp
+++ b/clang/test/SemaCXX/labeled-break-continue.cpp
@@ -49,3 +49,21 @@ void f3() {
     };
   }
 }
+
+void f4() {
+  l1: for (;;) {
+    constexpr int x = ({ // expected-error {{constexpr variable 'x' must be initialized by a constant expression}}
+      break l1; // expected-note {{not supported in a constant expression}}
+      1;
+    });
+  }
+}
+
+void f5() {
+  l1: for (;;) {
+    constexpr int x = ({ // expected-error {{constexpr variable 'x' must be initialized by a constant expression}}
+      continue l1; // expected-note {{not supported in a constant expression}}
+      1;
+    });
+  }
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/228655


More information about the cfe-commits mailing list