Commit e761e0ac18

Evan Haas <evan@lagerdata.com>
2021-04-08 18:25:52
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
1 parent 36a33c9
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()) {
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 <stdlib.h>
+        \\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;
+        \\}
+    , "");
 }
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;
         \\    }
         \\}
     });