authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-09-11 09:01:05+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-09-12 22:52:23+01:00
log03c363300fd829f3a656c6a9854a9c9720c9b3f1
tree9cf60f26f2d44e9527fac310102fdb80e085d100
parent0001f91e4e1e51cd64cdd5c0a21451c8bad67233

AstGen: do not allow unlabeled `break` to exit a labeled switch

`break`ing from something which isn't a loop should always be opt-in. This was a bug in #21257.

2 files changed, 19 insertions(+), 2 deletions(-)

lib/std/zig/AstGen.zig+2-2
...@@ -7811,9 +7811,7 @@ fn switchExpr(...@@ -7811,9 +7811,7 @@ fn switchExpr(
7811 const switch_block = try parent_gz.makeBlockInst(switch_tag, node);7811 const switch_block = try parent_gz.makeBlockInst(switch_tag, node);
78127812
7813 if (switch_full.label_token) |label_token| {7813 if (switch_full.label_token) |label_token| {
7814 block_scope.break_block = switch_block.toOptional();
7815 block_scope.continue_block = switch_block.toOptional();7814 block_scope.continue_block = switch_block.toOptional();
7816 // `break_result_info` already set above
7817 block_scope.continue_result_info = .{7815 block_scope.continue_result_info = .{
7818 .rl = if (any_payload_is_ref)7816 .rl = if (any_payload_is_ref)
7819 .{ .ref_coerced_ty = raw_operand_ty_ref }7817 .{ .ref_coerced_ty = raw_operand_ty_ref }
...@@ -7825,6 +7823,8 @@ fn switchExpr(...@@ -7825,6 +7823,8 @@ fn switchExpr(
7825 .token = label_token,7823 .token = label_token,
7826 .block_inst = switch_block,7824 .block_inst = switch_block,
7827 };7825 };
7826 // `break` can target this via `label.block_inst`
7827 // `break_result_info` already set by `setBreakResultInfo`
7828 }7828 }
78297829
7830 // We re-use this same scope for all cases, including the special prong, if any.7830 // We re-use this same scope for all cases, including the special prong, if any.
test/behavior/switch.zig+17
...@@ -985,3 +985,20 @@ test "labeled switch with break" {...@@ -985,3 +985,20 @@ test "labeled switch with break" {
985985
986 comptime assert(comptime_val);986 comptime assert(comptime_val);
987}987}
988
989test "unlabeled break ignores switch" {
990 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
991 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
992 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
993 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
994 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest; // TODO
995
996 const result = while (true) {
997 _ = s: switch (@as(u32, 1)) {
998 1 => continue :s 123,
999 else => |x| break x,
1000 };
1001 comptime unreachable; // control flow never breaks from the switch
1002 };
1003 try expect(result == 123);
1004}