authorgravatar for evan@lagerdata.comEvan Haas <evan@lagerdata.com> 2021-03-18 23:13:59-07:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2021-03-22 10:48:08+02:00
logdce612ac2be5aa8a1aa0ee8dd670d7e875624216
tree5d551dbee5d5cb9f5270f40ea62057961440fc3f
parent187af14599a083f728f79c4a57ceed30fd01f85d

translate-c: Ensure assignments are within a block when necessary

Ensures that if an assignment statement is the sole statement within a C if statement, for loop, do loop, or do while loop, then when translated it resides within a block, even though it does not in the original C. Fixes the following invalid translation: `if (1) if (1) 2;` -> `if (true) if (true) _ = @as(c_int, 2);` To this: ```zig if (true) if (true) { _ = @as(c_int, 2); }; ``` Fixes #8159

3 files changed, 62 insertions(+), 9 deletions(-)

src/translate_c.zig+36-5
...@@ -1063,6 +1063,7 @@ fn transStmt(...@@ -1063,6 +1063,7 @@ fn transStmt(
1063 const gen_sel = @ptrCast(*const clang.GenericSelectionExpr, stmt);1063 const gen_sel = @ptrCast(*const clang.GenericSelectionExpr, stmt);
1064 return transExpr(c, scope, gen_sel.getResultExpr(), result_used);1064 return transExpr(c, scope, gen_sel.getResultExpr(), result_used);
1065 },1065 },
1066 // When adding new cases here, see comment for maybeBlockify()
1066 else => {1067 else => {
1067 return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO implement translation of stmt class {s}", .{@tagName(sc)});1068 return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO implement translation of stmt class {s}", .{@tagName(sc)});
1068 },1069 },
...@@ -2242,6 +2243,35 @@ fn transImplicitValueInitExpr(...@@ -2242,6 +2243,35 @@ fn transImplicitValueInitExpr(
2242 return transZeroInitExpr(c, scope, source_loc, ty);2243 return transZeroInitExpr(c, scope, source_loc, ty);
2243}2244}
22442245
2246/// If a statement can possibly translate to a Zig assignment (either directly because it's
2247/// an assignment in C or indirectly via result assignment to `_`) AND it's the sole statement
2248/// in the body of an if statement or loop, then we need to put the statement into its own block.
2249/// The `else` case here corresponds to statements that could result in an assignment. If a statement
2250/// class never needs a block, add its enum to the top prong.
2251fn maybeBlockify(c: *Context, scope: *Scope, stmt: *const clang.Stmt) TransError!Node {
2252 switch (stmt.getStmtClass()) {
2253 .BreakStmtClass,
2254 .CompoundStmtClass,
2255 .ContinueStmtClass,
2256 .DeclRefExprClass,
2257 .DeclStmtClass,
2258 .DoStmtClass,
2259 .ForStmtClass,
2260 .IfStmtClass,
2261 .ReturnStmtClass,
2262 .NullStmtClass,
2263 .WhileStmtClass,
2264 => return transStmt(c, scope, stmt, .unused),
2265 else => {
2266 var block_scope = try Scope.Block.init(c, scope, false);
2267 defer block_scope.deinit();
2268 const result = try transStmt(c, &block_scope.base, stmt, .unused);
2269 try block_scope.statements.append(result);
2270 return block_scope.complete(c);
2271 },
2272 }
2273}
2274
2245fn transIfStmt(2275fn transIfStmt(
2246 c: *Context,2276 c: *Context,
2247 scope: *Scope,2277 scope: *Scope,
...@@ -2259,9 +2289,10 @@ fn transIfStmt(...@@ -2259,9 +2289,10 @@ fn transIfStmt(
2259 const cond_expr = @ptrCast(*const clang.Expr, stmt.getCond());2289 const cond_expr = @ptrCast(*const clang.Expr, stmt.getCond());
2260 const cond = try transBoolExpr(c, &cond_scope.base, cond_expr, .used);2290 const cond = try transBoolExpr(c, &cond_scope.base, cond_expr, .used);
22612291
2262 const then_body = try transStmt(c, scope, stmt.getThen(), .unused);2292 const then_body = try maybeBlockify(c, scope, stmt.getThen());
2293
2263 const else_body = if (stmt.getElse()) |expr|2294 const else_body = if (stmt.getElse()) |expr|
2264 try transStmt(c, scope, expr, .unused)2295 try maybeBlockify(c, scope, expr)
2265 else2296 else
2266 null;2297 null;
2267 return Tag.@"if".create(c.arena, .{ .cond = cond, .then = then_body, .@"else" = else_body });2298 return Tag.@"if".create(c.arena, .{ .cond = cond, .then = then_body, .@"else" = else_body });
...@@ -2286,7 +2317,7 @@ fn transWhileLoop(...@@ -2286,7 +2317,7 @@ fn transWhileLoop(
2286 .parent = scope,2317 .parent = scope,
2287 .id = .loop,2318 .id = .loop,
2288 };2319 };
2289 const body = try transStmt(c, &loop_scope, stmt.getBody(), .unused);2320 const body = try maybeBlockify(c, &loop_scope, stmt.getBody());
2290 return Tag.@"while".create(c.arena, .{ .cond = cond, .body = body, .cont_expr = null });2321 return Tag.@"while".create(c.arena, .{ .cond = cond, .body = body, .cont_expr = null });
2291}2322}
22922323
...@@ -2312,7 +2343,7 @@ fn transDoWhileLoop(...@@ -2312,7 +2343,7 @@ fn transDoWhileLoop(
2312 const if_not_break = switch (cond.tag()) {2343 const if_not_break = switch (cond.tag()) {
2313 .false_literal => return transStmt(c, scope, stmt.getBody(), .unused),2344 .false_literal => return transStmt(c, scope, stmt.getBody(), .unused),
2314 .true_literal => {2345 .true_literal => {
2315 const body_node = try transStmt(c, scope, stmt.getBody(), .unused);2346 const body_node = try maybeBlockify(c, scope, stmt.getBody());
2316 return Tag.while_true.create(c.arena, body_node);2347 return Tag.while_true.create(c.arena, body_node);
2317 },2348 },
2318 else => try Tag.if_not_break.create(c.arena, cond),2349 else => try Tag.if_not_break.create(c.arena, cond),
...@@ -2388,7 +2419,7 @@ fn transForLoop(...@@ -2388,7 +2419,7 @@ fn transForLoop(
2388 else2419 else
2389 null;2420 null;
23902421
2391 const body = try transStmt(c, &loop_scope, stmt.getBody(), .unused);2422 const body = try maybeBlockify(c, &loop_scope, stmt.getBody());
2392 const while_node = try Tag.@"while".create(c.arena, .{ .cond = cond, .body = body, .cont_expr = cont_expr });2423 const while_node = try Tag.@"while".create(c.arena, .{ .cond = cond, .body = body, .cont_expr = cont_expr });
2393 if (block_scope) |*bs| {2424 if (block_scope) |*bs| {
2394 try bs.statements.append(while_node);2425 try bs.statements.append(while_node);
test/run_translated_c.zig+14
...@@ -1244,4 +1244,18 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {...@@ -1244,4 +1244,18 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void {
1244 \\ return 0;1244 \\ return 0;
1245 \\}1245 \\}
1246 , "");1246 , "");
1247
1248 cases.add("convert single-statement bodies into blocks for if/else/for/while. issue #8159",
1249 \\#include <stdlib.h>
1250 \\int foo() { return 1; }
1251 \\int main(void) {
1252 \\ int i = 0;
1253 \\ if (i == 0) if (i == 0) if (i != 0) i = 1;
1254 \\ if (i != 0) i = 1; else if (i == 0) if (i == 0) i += 1;
1255 \\ for (; i < 10;) for (; i < 10;) i++;
1256 \\ while (i == 100) while (i == 100) foo();
1257 \\ if (0) do do "string"; while(1); while(1);
1258 \\ return 0;
1259 \\}
1260 , "");
1247}1261}
test/translate_c.zig+12-4
...@@ -1934,7 +1934,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {...@@ -1934,7 +1934,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
1934 , &[_][]const u8{1934 , &[_][]const u8{
1935 \\pub export fn foo() c_int {1935 \\pub export fn foo() c_int {
1936 \\ var a: c_int = 5;1936 \\ var a: c_int = 5;
1937 \\ while (true) a = 2;1937 \\ while (true) {
1938 \\ a = 2;
1939 \\ }
1938 \\ while (true) {1940 \\ while (true) {
1939 \\ var a_1: c_int = 4;1941 \\ var a_1: c_int = 4;
1940 \\ a_1 = 9;1942 \\ a_1 = 9;
...@@ -1947,7 +1949,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {...@@ -1947,7 +1949,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
1947 \\ var a_1: c_int = 2;1949 \\ var a_1: c_int = 2;
1948 \\ a_1 = 12;1950 \\ a_1 = 12;
1949 \\ }1951 \\ }
1950 \\ while (true) a = 7;1952 \\ while (true) {
1953 \\ a = 7;
1954 \\ }
1951 \\ return 0;1955 \\ return 0;
1952 \\}1956 \\}
1953 });1957 });
...@@ -2008,7 +2012,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {...@@ -2008,7 +2012,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
2008 \\}2012 \\}
2009 , &[_][]const u8{2013 , &[_][]const u8{
2010 \\pub export fn bar() c_int {2014 \\pub export fn bar() c_int {
2011 \\ if ((if (true) @as(c_int, 5) else if (true) @as(c_int, 4) else @as(c_int, 6)) != 0) _ = @as(c_int, 2);2015 \\ if ((if (true) @as(c_int, 5) else if (true) @as(c_int, 4) else @as(c_int, 6)) != 0) {
2016 \\ _ = @as(c_int, 2);
2017 \\ }
2012 \\ return if (true) @as(c_int, 5) else if (true) @as(c_int, 4) else @as(c_int, 6);2018 \\ return if (true) @as(c_int, 5) else if (true) @as(c_int, 4) else @as(c_int, 6);
2013 \\}2019 \\}
2014 });2020 });
...@@ -2389,7 +2395,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {...@@ -2389,7 +2395,9 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
2389 \\pub const yes = [*c]u8;2395 \\pub const yes = [*c]u8;
2390 \\pub export fn foo() void {2396 \\pub export fn foo() void {
2391 \\ var a: yes = undefined;2397 \\ var a: yes = undefined;
2392 \\ if (a != null) _ = @as(c_int, 2);2398 \\ if (a != null) {
2399 \\ _ = @as(c_int, 2);
2400 \\ }
2393 \\}2401 \\}
2394 });2402 });
23952403