From a2ec77041bc4a58d922cd3f8db923b7272b1f8f7 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Mon, 8 Feb 2021 11:43:57 -0800 Subject: [PATCH] translate-c: call @boolToInt on return value when necessary In C, if a function has return type `int` and the return expression is a boolean expression, there is no implicit cast. Therefore the translated Zig code needs to call @boolToInt() on the result. Written with feedback from @Vexu Fixes #6215 --- src/translate_c.zig | 44 +++++++++++++++++++++++++++++++++++---- test/run_translated_c.zig | 35 +++++++++++++++++++++++++++++++ test/translate_c.zig | 8 +++---- 3 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/translate_c.zig b/src/translate_c.zig index e3652ddbfb..82a7695c13 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -78,6 +78,10 @@ const Scope = struct { mangle_count: u32 = 0, lbrace: ast.TokenIndex, + /// When the block corresponds to a function, keep track of the return type + /// so that the return expression can be cast, if necessary + return_type: ?clang.QualType = null, + fn init(c: *Context, parent: *Scope, labeled: bool) !Block { var blk = Block{ .base = .{ @@ -209,6 +213,21 @@ const Scope = struct { } } + fn findBlockReturnType(inner: *Scope, c: *Context) ?clang.QualType { + var scope = inner; + while (true) { + switch (scope.id) { + .Root => return null, + .Block => { + const block = @fieldParentPtr(Block, "base", scope); + if (block.return_type) |qt| return qt; + scope = scope.parent.?; + }, + else => scope = scope.parent.?, + } + } + } + fn getAlias(scope: *Scope, name: []const u8) []const u8 { return switch (scope.id) { .Root => return name, @@ -580,6 +599,8 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void { else => break fn_type, } } else unreachable; + const fn_ty = @ptrCast(*const clang.FunctionType, fn_type); + const return_qt = fn_ty.getReturnType(); const proto_node = switch (fn_type.getTypeClass()) { .FunctionProto => blk: { @@ -617,7 +638,9 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void { // actual function definition with body const body_stmt = fn_decl.getBody(); var block_scope = try Scope.Block.init(rp.c, &c.global_scope.base, false); + block_scope.return_type = return_qt; defer block_scope.deinit(); + var scope = &block_scope.base; var param_id: c_uint = 0; @@ -667,10 +690,7 @@ fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void { }; // add return statement if the function didn't have one blk: { - const fn_ty = @ptrCast(*const clang.FunctionType, fn_type); - if (fn_ty.getNoReturnAttr()) break :blk; - const return_qt = fn_ty.getReturnType(); if (isCVoid(return_qt)) break :blk; if (block_scope.statements.items.len > 0) { @@ -2018,16 +2038,32 @@ fn transIntegerLiteral( return maybeSuppressResult(rp, scope, result_used, &as_node.base); } +/// In C if a function has return type `int` and the return value is a boolean +/// expression, there is no implicit cast. So the translated Zig will need to +/// call @boolToInt +fn zigShouldCastBooleanReturnToInt(node: ?*ast.Node, qt: ?clang.QualType) bool { + if (node == null or qt == null) return false; + return isBoolRes(node.?) and cIsNativeInt(qt.?); +} + fn transReturnStmt( rp: RestorePoint, scope: *Scope, expr: *const clang.ReturnStmt, ) TransError!*ast.Node { const return_kw = try appendToken(rp.c, .Keyword_return, "return"); - const rhs: ?*ast.Node = if (expr.getRetValue()) |val_expr| + var rhs: ?*ast.Node = if (expr.getRetValue()) |val_expr| try transExprCoercing(rp, scope, val_expr, .used, .r_value) else null; + const return_qt = scope.findBlockReturnType(rp.c); + if (zigShouldCastBooleanReturnToInt(rhs, return_qt)) { + const bool_to_int_node = try rp.c.createBuiltinCall("@boolToInt", 1); + bool_to_int_node.params()[0] = rhs.?; + bool_to_int_node.rparen_token = try appendToken(rp.c, .RParen, ")"); + + rhs = &bool_to_int_node.base; + } const return_expr = try ast.Node.ControlFlowExpression.create(rp.c.arena, .{ .ltoken = return_kw, .tag = .Return, diff --git a/test/run_translated_c.zig b/test/run_translated_c.zig index a8a3a0e21b..e28bdc96f0 100644 --- a/test/run_translated_c.zig +++ b/test/run_translated_c.zig @@ -874,4 +874,39 @@ pub fn addCases(cases: *tests.RunTranslatedCContext) void { \\ return 0; \\} , ""); + + cases.add("Return boolean expression as int; issue #6215", + \\#include + \\#include + \\bool actual_bool(void) { return 4 - 1 < 4;} + \\char char_bool_ret(void) { return 0 || 1; } + \\short short_bool_ret(void) { return 0 < 1; } + \\int int_bool_ret(void) { return 1 && 1; } + \\long long_bool_ret(void) { return !(0 > 1); } + \\static int GLOBAL = 1; + \\int nested_scopes(int a, int b) { + \\ if (a == 1) { + \\ int target = 1; + \\ return b == target; + \\ } else { + \\ int target = 2; + \\ if (b == target) { + \\ return GLOBAL == 1; + \\ } + \\ return target == 2; + \\ } + \\} + \\int main(void) { + \\ if (!actual_bool()) abort(); + \\ if (!char_bool_ret()) abort(); + \\ if (!short_bool_ret()) abort(); + \\ if (!int_bool_ret()) abort(); + \\ if (!long_bool_ret()) abort(); + \\ if (!nested_scopes(1, 1)) abort(); + \\ if (nested_scopes(1, 2)) abort(); + \\ if (!nested_scopes(0, 2)) abort(); + \\ if (!nested_scopes(0, 3)) abort(); + \\ return 1 != 1; + \\} + , ""); } diff --git a/test/translate_c.zig b/test/translate_c.zig index 75d00d12f4..03ca87d5f6 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -1305,10 +1305,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void { \\ var a: c_int = undefined; \\ var b: f32 = undefined; \\ var c: ?*c_void = undefined; - \\ return !(a == @as(c_int, 0)); - \\ return !(a != 0); - \\ return !(b != 0); - \\ return !(c != null); + \\ return @boolToInt(!(a == @as(c_int, 0))); + \\ return @boolToInt(!(a != 0)); + \\ return @boolToInt(!(b != 0)); + \\ return @boolToInt(!(c != null)); \\} });