From e761e0ac18aa1eab22b9547d702067910ed82c4a Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Thu, 8 Apr 2021 09:25:52 -0700 Subject: [PATCH] translate-c: wrap switch statements in a while (true) loop This allows `break` statements to be directly translated from the original C. Add a break statement as the last statement of the while loop to ensure we don't have an infinite loop if no breaks / returns are hit in the switch. Fixes #8387 --- src/translate_c.zig | 33 +++++++++++++----- test/run_translated_c.zig | 43 +++++++++++++++++++++++ test/translate_c.zig | 73 ++++++++++++++++++++++----------------- 3 files changed, 109 insertions(+), 40 deletions(-) diff --git a/src/translate_c.zig b/src/translate_c.zig index 5962b85a6b..bd6fd190a8 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -2357,7 +2357,6 @@ fn transInitListExprVector( expr: *const clang.InitListExpr, ty: *const clang.Type, ) TransError!Node { - const qt = getExprQualType(c, @ptrCast(*const clang.Expr, expr)); const vector_type = try transQualType(c, scope, qt, loc); const init_count = expr.getNumInits(); @@ -2700,9 +2699,19 @@ fn transSwitch( scope: *Scope, stmt: *const clang.SwitchStmt, ) TransError!Node { + var loop_scope = Scope{ + .parent = scope, + .id = .loop, + }; + + var block_scope = try Scope.Block.init(c, &loop_scope, false); + defer block_scope.deinit(); + + const base_scope = &block_scope.base; + var cond_scope = Scope.Condition{ .base = .{ - .parent = scope, + .parent = base_scope, .id = .condition, }, }; @@ -2725,8 +2734,8 @@ fn transSwitch( .CaseStmtClass => { var items = std.ArrayList(Node).init(c.gpa); defer items.deinit(); - const sub = try transCaseStmt(c, scope, it[0], &items); - const res = try transSwitchProngStmt(c, scope, sub, it, end_it); + const sub = try transCaseStmt(c, base_scope, it[0], &items); + const res = try transSwitchProngStmt(c, base_scope, sub, it, end_it); if (items.items.len == 0) { has_default = true; @@ -2751,7 +2760,7 @@ fn transSwitch( else => break, }; - const res = try transSwitchProngStmt(c, scope, sub, it, end_it); + const res = try transSwitchProngStmt(c, base_scope, sub, it, end_it); const switch_else = try Tag.switch_else.create(c.arena, res); try cases.append(switch_else); @@ -2765,10 +2774,15 @@ fn transSwitch( try cases.append(else_prong); } - return Tag.@"switch".create(c.arena, .{ + const switch_node = try Tag.@"switch".create(c.arena, .{ .cond = switch_expr, .cases = try c.arena.dupe(Node, cases.items), }); + try block_scope.statements.append(switch_node); + try block_scope.statements.append(Tag.@"break".init()); + const while_body = try block_scope.complete(c); + + return Tag.while_true.create(c.arena, while_body); } /// Collects all items for this case, returns the first statement after the labels. @@ -2818,7 +2832,7 @@ fn transSwitchProngStmt( parent_end_it: clang.CompoundStmt.ConstBodyIterator, ) TransError!Node { switch (stmt.getStmtClass()) { - .BreakStmtClass => return Tag.empty_block.init(), + .BreakStmtClass => return Tag.@"break".init(), .ReturnStmtClass => return transStmt(c, scope, stmt, .unused), .CaseStmtClass, .DefaultStmtClass => unreachable, else => { @@ -2847,7 +2861,10 @@ fn transSwitchProngStmtInline( try block.statements.append(result); return; }, - .BreakStmtClass => return, + .BreakStmtClass => { + try block.statements.append(Tag.@"break".init()); + return; + }, .CaseStmtClass => { var sub = @ptrCast(*const clang.CaseStmt, it[0]).getSubStmt(); while (true) switch (sub.getStmtClass()) { diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index 8f4c568aa2..7e5d0da367 100644 --- a/test/run_translated_c.zig +++ b/test/run_translated_c.zig @@ -1410,4 +1410,47 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { \\} , ""); + cases.add("break from switch statement. Issue #8387", + \\#include + \\int switcher(int x) { + \\ switch (x) { + \\ case 0: // no braces + \\ x += 1; + \\ break; + \\ case 1: // conditional break + \\ if (x == 1) { + \\ x += 1; + \\ break; + \\ } + \\ x += 100; + \\ case 2: { // braces with fallthrough + \\ x += 1; + \\ } + \\ case 3: // fallthrough to return statement + \\ x += 1; + \\ case 42: { // random out of order case + \\ x += 1; + \\ return x; + \\ } + \\ case 4: { // break within braces + \\ x += 1; + \\ break; + \\ } + \\ case 5: + \\ x += 1; // fallthrough to default + \\ default: + \\ x += 1; + \\ } + \\ return x; + \\} + \\int main(void) { + \\ int expected[] = {1, 2, 5, 5, 5, 7, 7}; + \\ for (int i = 0; i < sizeof(expected) / sizeof(int); i++) { + \\ int res = switcher(i); + \\ if (res != expected[i]) abort(); + \\ } + \\ if (switcher(42) != 43) abort(); + \\ return 0; + \\} + , ""); } diff --git a/test/translate_c.zig b/test/translate_c.zig index e2f974ff89..142579c92c 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -2072,40 +2072,49 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\pub export fn switch_fn(arg_i: c_int) void { \\ var i = arg_i; \\ var res: c_int = 0; - \\ switch (i) { - \\ @as(c_int, 0) => { - \\ res = 1; - \\ res = 2; - \\ res = @as(c_int, 3) * i; - \\ }, - \\ @as(c_int, 1)...@as(c_int, 3) => { - \\ res = 2; - \\ res = @as(c_int, 3) * i; - \\ }, - \\ else => { - \\ res = @as(c_int, 3) * i; - \\ }, - \\ @as(c_int, 7) => { - \\ { - \\ res = 7; + \\ while (true) { + \\ switch (i) { + \\ @as(c_int, 0) => { + \\ res = 1; + \\ res = 2; + \\ res = @as(c_int, 3) * i; \\ break; - \\ } - \\ }, - \\ @as(c_int, 4), @as(c_int, 5) => { - \\ res = 69; - \\ { - \\ res = 5; + \\ }, + \\ @as(c_int, 1)...@as(c_int, 3) => { + \\ res = 2; + \\ res = @as(c_int, 3) * i; + \\ break; + \\ }, + \\ else => { + \\ res = @as(c_int, 3) * i; + \\ break; + \\ }, + \\ @as(c_int, 7) => { + \\ { + \\ res = 7; + \\ break; + \\ } + \\ }, + \\ @as(c_int, 4), @as(c_int, 5) => { + \\ res = 69; + \\ { + \\ res = 5; + \\ return; + \\ } + \\ }, + \\ @as(c_int, 6) => { + \\ while (true) { + \\ switch (res) { + \\ @as(c_int, 9) => break, + \\ else => {}, + \\ } + \\ break; + \\ } + \\ res = 1; \\ return; - \\ } - \\ }, - \\ @as(c_int, 6) => { - \\ switch (res) { - \\ @as(c_int, 9) => {}, - \\ else => {}, - \\ } - \\ res = 1; - \\ return; - \\ }, + \\ }, + \\ } + \\ break; \\ } \\} });