diff --git a/doc/langref.html.in b/doc/langref.html.in index 15e04459bd..1da4205b89 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -6144,7 +6144,14 @@ fn assert(ok: bool) void { if (!ok) unreachable; // assertion failure } {#code_end#} -

At runtime crashes with the message reached unreachable code and a stack trace.

+

At runtime:

+ {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + std.debug.assert(false); +} + {#code_end#} {#header_close#} {#header_open|Index out of Bounds#}

At compile-time:

@@ -6154,7 +6161,16 @@ comptime { const garbage = array[5]; } {#code_end#} -

At runtime crashes with the message index out of bounds and a stack trace.

+

At runtime:

+ {#code_begin|exe_err#} +pub fn main() void { + var x = foo("hello"); +} + +fn foo(x: []const u8) u8 { + return x[5]; +} + {#code_end#} {#header_close#} {#header_open|Cast Negative Number to Unsigned Integer#}

At compile-time:

@@ -6164,10 +6180,18 @@ comptime { const unsigned = @intCast(u32, value); } {#code_end#} -

At runtime crashes with the message attempt to cast negative value to unsigned integer and a stack trace.

+

At runtime:

+ {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var value: i32 = -1; + var unsigned = @intCast(u32, value); + std.debug.warn("value: {}\n", unsigned); +} + {#code_end#}

- If you are trying to obtain the maximum value of an unsigned integer, use @maxValue(T), - where T is the integer type, such as u32. + To obtain the maximum value of an unsigned integer, use {#link|@maxValue#}.

{#header_close#} {#header_open|Cast Truncates Data#} @@ -6178,11 +6202,18 @@ comptime { const byte = @intCast(u8, spartan_count); } {#code_end#} -

At runtime crashes with the message integer cast truncated bits and a stack trace.

+

At runtime:

+ {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var spartan_count: u16 = 300; + const byte = @intCast(u8, spartan_count); + std.debug.warn("value: {}\n", byte); +} + {#code_end#}

- If you are trying to truncate bits, use @truncate(T, value), - where T is the integer type, such as u32, and value - is the value you want to truncate. + To truncate bits, use {#link|@truncate#}.

{#header_close#} {#header_open|Integer Overflow#} @@ -6194,9 +6225,9 @@ comptime {
  • - (negation)
  • * (multiplication)
  • / (division)
  • -
  • @divTrunc (division)
  • -
  • @divFloor (division)
  • -
  • @divExact (division)
  • +
  • {#link|@divTrunc#} (division)
  • +
  • {#link|@divFloor#} (division)
  • +
  • {#link|@divExact#} (division)
  • Example with addition at compile-time:

    {#code_begin|test_err|operation caused overflow#} @@ -6205,7 +6236,16 @@ comptime { byte += 1; } {#code_end#} -

    At runtime crashes with the message integer overflow and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var byte: u8 = 255; + byte += 1; + std.debug.warn("value: {}\n", byte); +} + {#code_end#} {#header_close#} {#header_open|Standard Library Math Functions#}

    These functions provided by the standard library return possible errors.

    @@ -6240,13 +6280,13 @@ pub fn main() !void { occurred, as well as returning the overflowed bits:

    - Example of @addWithOverflow: + Example of {#link|@addWithOverflow#}:

    {#code_begin|exe#} const warn = @import("std").debug.warn; @@ -6292,7 +6332,16 @@ comptime { const x = @shlExact(u8(0b01010101), 2); } {#code_end#} -

    At runtime crashes with the message left shift overflowed bits and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var x: u8 = 0b01010101; + var y = @shlExact(x, 2); + std.debug.warn("value: {}\n", y); +} + {#code_end#} {#header_close#} {#header_open|Exact Right Shift Overflow#}

    At compile-time:

    @@ -6301,7 +6350,16 @@ comptime { const x = @shrExact(u8(0b10101010), 2); } {#code_end#} -

    At runtime crashes with the message right shift overflowed bits and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var x: u8 = 0b10101010; + var y = @shrExact(x, 2); + std.debug.warn("value: {}\n", y); +} + {#code_end#} {#header_close#} {#header_open|Division by Zero#}

    At compile-time:

    @@ -6312,8 +6370,17 @@ comptime { const c = a / b; } {#code_end#} -

    At runtime crashes with the message division by zero and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); +pub fn main() void { + var a: u32 = 1; + var b: u32 = 0; + var c = a / b; + std.debug.warn("value: {}\n", c); +} + {#code_end#} {#header_close#} {#header_open|Remainder Division by Zero#}

    At compile-time:

    @@ -6324,14 +6391,57 @@ comptime { const c = a % b; } {#code_end#} -

    At runtime crashes with the message remainder division by zero and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); +pub fn main() void { + var a: u32 = 10; + var b: u32 = 0; + var c = a % b; + std.debug.warn("value: {}\n", c); +} + {#code_end#} {#header_close#} {#header_open|Exact Division Remainder#} -

    TODO

    +

    At compile-time:

    + {#code_begin|test_err|exact division had a remainder#} +comptime { + const a: u32 = 10; + const b: u32 = 3; + const c = @divExact(a, b); +} + {#code_end#} +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var a: u32 = 10; + var b: u32 = 3; + var c = @divExact(a, b); + std.debug.warn("value: {}\n", c); +} + {#code_end#} {#header_close#} {#header_open|Slice Widen Remainder#} -

    TODO

    +

    At compile-time:

    + {#code_begin|test_err|unable to convert#} +comptime { + var bytes = [5]u8{ 1, 2, 3, 4, 5 }; + var slice = @bytesToSlice(u32, bytes); +} + {#code_end#} +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var bytes = [5]u8{ 1, 2, 3, 4, 5 }; + var slice = @bytesToSlice(u32, bytes[0..]); + std.debug.warn("value: {}\n", slice[0]); +} + {#code_end#} {#header_close#} {#header_open|Attempt to Unwrap Null#}

    At compile-time:

    @@ -6341,7 +6451,16 @@ comptime { const number = optional_number.?; } {#code_end#} -

    At runtime crashes with the message attempt to unwrap null and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var optional_number: ?i32 = null; + var number = optional_number.?; + std.debug.warn("value: {}\n", number); +} + {#code_end#}

    One way to avoid this crash is to test for null instead of assuming non-null, with the if expression:

    {#code_begin|exe|test#} @@ -6356,6 +6475,7 @@ pub fn main() void { } } {#code_end#} + {#see_also|Optionals#} {#header_close#} {#header_open|Attempt to Unwrap Error#}

    At compile-time:

    @@ -6368,7 +6488,19 @@ fn getNumberOrFail() !i32 { return error.UnableToReturnNumber; } {#code_end#} -

    At runtime crashes with the message attempt to unwrap error: ErrorCode and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + const number = getNumberOrFail() catch unreachable; + std.debug.warn("value: {}\n", number); +} + +fn getNumberOrFail() !i32 { + return error.UnableToReturnNumber; +} + {#code_end#}

    One way to avoid this crash is to test for an error instead of assuming a successful result, with the if expression:

    {#code_begin|exe#} @@ -6388,6 +6520,7 @@ fn getNumberOrFail() !i32 { return error.UnableToReturnNumber; } {#code_end#} + {#see_also|Errors#} {#header_close#} {#header_open|Invalid Error Code#}

    At compile-time:

    @@ -6398,11 +6531,47 @@ comptime { const invalid_err = @intToError(number); } {#code_end#} -

    At runtime crashes with the message invalid error code and a stack trace.

    +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); + +pub fn main() void { + var err = error.AnError; + var number = @errorToInt(err) + 500; + var invalid_err = @intToError(number); + std.debug.warn("value: {}\n", number); +} + {#code_end#} {#header_close#} {#header_open|Invalid Enum Cast#} -

    TODO

    +

    At compile-time:

    + {#code_begin|test_err|has no tag matching integer value 3#} +const Foo = enum { + A, + B, + C, +}; +comptime { + const a: u2 = 3; + const b = @intToEnum(Foo, a); +} + {#code_end#} +

    At runtime:

    + {#code_begin|exe_err#} +const std = @import("std"); +const Foo = enum { + A, + B, + C, +}; + +pub fn main() void { + var a: u2 = 3; + var b = @intToEnum(Foo, a); + std.debug.warn("value: {}\n", @tagName(b)); +} + {#code_end#} {#header_close#} {#header_open|Invalid Error Set Cast#} diff --git a/src/codegen.cpp b/src/codegen.cpp index 4419f4fc84..9c37c174d6 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2673,8 +2673,25 @@ static LLVMValueRef ir_render_int_to_enum(CodeGen *g, IrExecutable *executable, TypeTableEntry *tag_int_type = wanted_type->data.enumeration.tag_int_type; LLVMValueRef target_val = ir_llvm_value(g, instruction->target); - return gen_widen_or_shorten(g, ir_want_runtime_safety(g, &instruction->base), + LLVMValueRef tag_int_value = gen_widen_or_shorten(g, ir_want_runtime_safety(g, &instruction->base), instruction->target->value.type, tag_int_type, target_val); + + if (ir_want_runtime_safety(g, &instruction->base)) { + LLVMBasicBlockRef bad_value_block = LLVMAppendBasicBlock(g->cur_fn_val, "BadValue"); + LLVMBasicBlockRef ok_value_block = LLVMAppendBasicBlock(g->cur_fn_val, "OkValue"); + size_t field_count = wanted_type->data.enumeration.src_field_count; + LLVMValueRef switch_instr = LLVMBuildSwitch(g->builder, tag_int_value, bad_value_block, field_count); + for (size_t field_i = 0; field_i < field_count; field_i += 1) { + LLVMValueRef this_tag_int_value = bigint_to_llvm_const(tag_int_type->type_ref, + &wanted_type->data.enumeration.fields[field_i].value); + LLVMAddCase(switch_instr, this_tag_int_value, ok_value_block); + } + LLVMPositionBuilderAtEnd(g->builder, bad_value_block); + gen_safety_crash(g, PanicMsgIdBadEnumValue); + + LLVMPositionBuilderAtEnd(g->builder, ok_value_block); + } + return tag_int_value; } static LLVMValueRef ir_render_int_to_err(CodeGen *g, IrExecutable *executable, IrInstructionIntToErr *instruction) { diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index a7e8d6dc0e..3d58dfe748 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -1,6 +1,24 @@ const tests = @import("tests.zig"); pub fn addCases(cases: *tests.CompareOutputContext) void { + cases.addRuntimeSafety("@intToEnum - no matching tag value", + \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { + \\ @import("std").os.exit(126); + \\} + \\const Foo = enum { + \\ A, + \\ B, + \\ C, + \\}; + \\pub fn main() void { + \\ baz(bar(3)); + \\} + \\fn bar(a: u2) Foo { + \\ return @intToEnum(Foo, a); + \\} + \\fn baz(a: Foo) void {} + ); + cases.addRuntimeSafety("@floatToInt cannot fit - negative to unsigned", \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { \\ @import("std").os.exit(126);