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
This commit is contained in:
Evan Haas 2021-04-08 09:25:52 -07:00 committed by Andrew Kelley
parent 36a33c99e3
commit e761e0ac18
3 changed files with 109 additions and 40 deletions

View File

@ -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()) {

View File

@ -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;
\\}
, "");
}

View File

@ -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;
\\ }
\\}
});