[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