diff options
| author | Andrew Kelley <superjoe30@gmail.com> | 2018-07-02 15:49:49 -0400 |
|---|---|---|
| committer | Andrew Kelley <superjoe30@gmail.com> | 2018-07-02 15:50:28 -0400 |
| commit | 35463526cceb91243410bdab4d74e2d5b3c60f66 (patch) | |
| tree | 98824c6dcd3f1fba0a45b84666815c8a9ee41207 | |
| parent | 2759c7951da050d825cf765c4b660f5562fb01a4 (diff) | |
| download | zig-35463526cceb91243410bdab4d74e2d5b3c60f66.tar.gz zig-35463526cceb91243410bdab4d74e2d5b3c60f66.zip | |
add runtime safety for `@intToEnum`; add docs for runtime safety
See #367
| -rw-r--r-- | doc/langref.html.in | 225 | ||||
| -rw-r--r-- | src/codegen.cpp | 19 | ||||
| -rw-r--r-- | test/runtime_safety.zig | 18 |
3 files changed, 233 insertions, 29 deletions
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#} - <p>At runtime crashes with the message <code>reached unreachable code</code> and a stack trace.</p> + <p>At runtime:</p> + {#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#} <p>At compile-time:</p> @@ -6154,7 +6161,16 @@ comptime { const garbage = array[5]; } {#code_end#} - <p>At runtime crashes with the message <code>index out of bounds</code> and a stack trace.</p> + <p>At runtime:</p> + {#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#} <p>At compile-time:</p> @@ -6164,10 +6180,18 @@ comptime { const unsigned = @intCast(u32, value); } {#code_end#} - <p>At runtime crashes with the message <code>attempt to cast negative value to unsigned integer</code> and a stack trace.</p> + <p>At runtime:</p> + {#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#} <p> - If you are trying to obtain the maximum value of an unsigned integer, use <code>@maxValue(T)</code>, - where <code>T</code> is the integer type, such as <code>u32</code>. + To obtain the maximum value of an unsigned integer, use {#link|@maxValue#}. </p> {#header_close#} {#header_open|Cast Truncates Data#} @@ -6178,11 +6202,18 @@ comptime { const byte = @intCast(u8, spartan_count); } {#code_end#} - <p>At runtime crashes with the message <code>integer cast truncated bits</code> and a stack trace.</p> + <p>At runtime:</p> + {#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#} <p> - If you are trying to truncate bits, use <code>@truncate(T, value)</code>, - where <code>T</code> is the integer type, such as <code>u32</code>, and <code>value</code> - is the value you want to truncate. + To truncate bits, use {#link|@truncate#}. </p> {#header_close#} {#header_open|Integer Overflow#} @@ -6194,9 +6225,9 @@ comptime { <li><code>-</code> (negation)</li> <li><code>*</code> (multiplication)</li> <li><code>/</code> (division)</li> - <li><code>@divTrunc</code> (division)</li> - <li><code>@divFloor</code> (division)</li> - <li><code>@divExact</code> (division)</li> + <li>{#link|@divTrunc#} (division)</li> + <li>{#link|@divFloor#} (division)</li> + <li>{#link|@divExact#} (division)</li> </ul> <p>Example with addition at compile-time:</p> {#code_begin|test_err|operation caused overflow#} @@ -6205,7 +6236,16 @@ comptime { byte += 1; } {#code_end#} - <p>At runtime crashes with the message <code>integer overflow</code> and a stack trace.</p> + <p>At runtime:</p> + {#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#} <p>These functions provided by the standard library return possible errors.</p> @@ -6240,13 +6280,13 @@ pub fn main() !void { occurred, as well as returning the overflowed bits: </p> <ul> - <li><code>@addWithOverflow</code></li> - <li><code>@subWithOverflow</code></li> - <li><code>@mulWithOverflow</code></li> - <li><code>@shlWithOverflow</code></li> + <li>{#link|@addWithOverflow#}</li> + <li>{#link|@subWithOverflow#}</li> + <li>{#link|@mulWithOverflow#}</li> + <li>{#link|@shlWithOverflow#}</li> </ul> <p> - Example of <code>@addWithOverflow</code>: + Example of {#link|@addWithOverflow#}: </p> {#code_begin|exe#} const warn = @import("std").debug.warn; @@ -6292,7 +6332,16 @@ comptime { const x = @shlExact(u8(0b01010101), 2); } {#code_end#} - <p>At runtime crashes with the message <code>left shift overflowed bits</code> and a stack trace.</p> + <p>At runtime:</p> + {#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#} <p>At compile-time:</p> @@ -6301,7 +6350,16 @@ comptime { const x = @shrExact(u8(0b10101010), 2); } {#code_end#} - <p>At runtime crashes with the message <code>right shift overflowed bits</code> and a stack trace.</p> + <p>At runtime:</p> + {#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#} <p>At compile-time:</p> @@ -6312,8 +6370,17 @@ comptime { const c = a / b; } {#code_end#} - <p>At runtime crashes with the message <code>division by zero</code> and a stack trace.</p> + <p>At runtime:</p> + {#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#} <p>At compile-time:</p> @@ -6324,14 +6391,57 @@ comptime { const c = a % b; } {#code_end#} - <p>At runtime crashes with the message <code>remainder division by zero</code> and a stack trace.</p> + <p>At runtime:</p> + {#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#} - <p>TODO</p> + <p>At compile-time:</p> + {#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#} + <p>At runtime:</p> + {#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#} - <p>TODO</p> + <p>At compile-time:</p> + {#code_begin|test_err|unable to convert#} +comptime { + var bytes = [5]u8{ 1, 2, 3, 4, 5 }; + var slice = @bytesToSlice(u32, bytes); +} + {#code_end#} + <p>At runtime:</p> + {#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#} <p>At compile-time:</p> @@ -6341,7 +6451,16 @@ comptime { const number = optional_number.?; } {#code_end#} - <p>At runtime crashes with the message <code>attempt to unwrap null</code> and a stack trace.</p> + <p>At runtime:</p> + {#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#} <p>One way to avoid this crash is to test for null instead of assuming non-null, with the <code>if</code> expression:</p> {#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#} <p>At compile-time:</p> @@ -6368,7 +6488,19 @@ fn getNumberOrFail() !i32 { return error.UnableToReturnNumber; } {#code_end#} - <p>At runtime crashes with the message <code>attempt to unwrap error: ErrorCode</code> and a stack trace.</p> + <p>At runtime:</p> + {#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#} <p>One way to avoid this crash is to test for an error instead of assuming a successful result, with the <code>if</code> expression:</p> {#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#} <p>At compile-time:</p> @@ -6398,11 +6531,47 @@ comptime { const invalid_err = @intToError(number); } {#code_end#} - <p>At runtime crashes with the message <code>invalid error code</code> and a stack trace.</p> + <p>At runtime:</p> + {#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#} - <p>TODO</p> + <p>At compile-time:</p> + {#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#} + <p>At runtime:</p> + {#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); |
