authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-07-02 15:49:49-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-07-02 15:50:28-04:00
log35463526cceb91243410bdab4d74e2d5b3c60f66
tree98824c6dcd3f1fba0a45b84666815c8a9ee41207
parent2759c7951da050d825cf765c4b660f5562fb01a4

add runtime safety for `@intToEnum`; add docs for runtime safety

See #367

3 files changed, 233 insertions(+), 29 deletions(-)

doc/langref.html.in+197-28
......@@ -6144,7 +6144,14 @@ fn assert(ok: bool) void {
61446144 if (!ok) unreachable; // assertion failure
61456145}
61466146 {#code_end#}
6147 <p>At runtime crashes with the message <code>reached unreachable code</code> and a stack trace.</p>
6147 <p>At runtime:</p>
6148 {#code_begin|exe_err#}
6149const std = @import("std");
6150
6151pub fn main() void {
6152 std.debug.assert(false);
6153}
6154 {#code_end#}
61486155 {#header_close#}
61496156 {#header_open|Index out of Bounds#}
61506157 <p>At compile-time:</p>
......@@ -6154,7 +6161,16 @@ comptime {
61546161 const garbage = array[5];
61556162}
61566163 {#code_end#}
6157 <p>At runtime crashes with the message <code>index out of bounds</code> and a stack trace.</p>
6164 <p>At runtime:</p>
6165 {#code_begin|exe_err#}
6166pub fn main() void {
6167 var x = foo("hello");
6168}
6169
6170fn foo(x: []const u8) u8 {
6171 return x[5];
6172}
6173 {#code_end#}
61586174 {#header_close#}
61596175 {#header_open|Cast Negative Number to Unsigned Integer#}
61606176 <p>At compile-time:</p>
......@@ -6164,10 +6180,18 @@ comptime {
61646180 const unsigned = @intCast(u32, value);
61656181}
61666182 {#code_end#}
6167 <p>At runtime crashes with the message <code>attempt to cast negative value to unsigned integer</code> and a stack trace.</p>
6183 <p>At runtime:</p>
6184 {#code_begin|exe_err#}
6185const std = @import("std");
6186
6187pub fn main() void {
6188 var value: i32 = -1;
6189 var unsigned = @intCast(u32, value);
6190 std.debug.warn("value: {}\n", unsigned);
6191}
6192 {#code_end#}
61686193 <p>
6169 If you are trying to obtain the maximum value of an unsigned integer, use <code>@maxValue(T)</code>,
6170 where <code>T</code> is the integer type, such as <code>u32</code>.
6194 To obtain the maximum value of an unsigned integer, use {#link|@maxValue#}.
61716195 </p>
61726196 {#header_close#}
61736197 {#header_open|Cast Truncates Data#}
......@@ -6178,11 +6202,18 @@ comptime {
61786202 const byte = @intCast(u8, spartan_count);
61796203}
61806204 {#code_end#}
6181 <p>At runtime crashes with the message <code>integer cast truncated bits</code> and a stack trace.</p>
6205 <p>At runtime:</p>
6206 {#code_begin|exe_err#}
6207const std = @import("std");
6208
6209pub fn main() void {
6210 var spartan_count: u16 = 300;
6211 const byte = @intCast(u8, spartan_count);
6212 std.debug.warn("value: {}\n", byte);
6213}
6214 {#code_end#}
61826215 <p>
6183 If you are trying to truncate bits, use <code>@truncate(T, value)</code>,
6184 where <code>T</code> is the integer type, such as <code>u32</code>, and <code>value</code>
6185 is the value you want to truncate.
6216 To truncate bits, use {#link|@truncate#}.
61866217 </p>
61876218 {#header_close#}
61886219 {#header_open|Integer Overflow#}
......@@ -6194,9 +6225,9 @@ comptime {
61946225 <li><code>-</code> (negation)</li>
61956226 <li><code>*</code> (multiplication)</li>
61966227 <li><code>/</code> (division)</li>
6197 <li><code>@divTrunc</code> (division)</li>
6198 <li><code>@divFloor</code> (division)</li>
6199 <li><code>@divExact</code> (division)</li>
6228 <li>{#link|@divTrunc#} (division)</li>
6229 <li>{#link|@divFloor#} (division)</li>
6230 <li>{#link|@divExact#} (division)</li>
62006231 </ul>
62016232 <p>Example with addition at compile-time:</p>
62026233 {#code_begin|test_err|operation caused overflow#}
......@@ -6205,7 +6236,16 @@ comptime {
62056236 byte += 1;
62066237}
62076238 {#code_end#}
6208 <p>At runtime crashes with the message <code>integer overflow</code> and a stack trace.</p>
6239 <p>At runtime:</p>
6240 {#code_begin|exe_err#}
6241const std = @import("std");
6242
6243pub fn main() void {
6244 var byte: u8 = 255;
6245 byte += 1;
6246 std.debug.warn("value: {}\n", byte);
6247}
6248 {#code_end#}
62096249 {#header_close#}
62106250 {#header_open|Standard Library Math Functions#}
62116251 <p>These functions provided by the standard library return possible errors.</p>
......@@ -6240,13 +6280,13 @@ pub fn main() !void {
62406280 occurred, as well as returning the overflowed bits:
62416281 </p>
62426282 <ul>
6243 <li><code>@addWithOverflow</code></li>
6244 <li><code>@subWithOverflow</code></li>
6245 <li><code>@mulWithOverflow</code></li>
6246 <li><code>@shlWithOverflow</code></li>
6283 <li>{#link|@addWithOverflow#}</li>
6284 <li>{#link|@subWithOverflow#}</li>
6285 <li>{#link|@mulWithOverflow#}</li>
6286 <li>{#link|@shlWithOverflow#}</li>
62476287 </ul>
62486288 <p>
6249 Example of <code>@addWithOverflow</code>:
6289 Example of {#link|@addWithOverflow#}:
62506290 </p>
62516291 {#code_begin|exe#}
62526292const warn = @import("std").debug.warn;
......@@ -6292,7 +6332,16 @@ comptime {
62926332 const x = @shlExact(u8(0b01010101), 2);
62936333}
62946334 {#code_end#}
6295 <p>At runtime crashes with the message <code>left shift overflowed bits</code> and a stack trace.</p>
6335 <p>At runtime:</p>
6336 {#code_begin|exe_err#}
6337const std = @import("std");
6338
6339pub fn main() void {
6340 var x: u8 = 0b01010101;
6341 var y = @shlExact(x, 2);
6342 std.debug.warn("value: {}\n", y);
6343}
6344 {#code_end#}
62966345 {#header_close#}
62976346 {#header_open|Exact Right Shift Overflow#}
62986347 <p>At compile-time:</p>
......@@ -6301,7 +6350,16 @@ comptime {
63016350 const x = @shrExact(u8(0b10101010), 2);
63026351}
63036352 {#code_end#}
6304 <p>At runtime crashes with the message <code>right shift overflowed bits</code> and a stack trace.</p>
6353 <p>At runtime:</p>
6354 {#code_begin|exe_err#}
6355const std = @import("std");
6356
6357pub fn main() void {
6358 var x: u8 = 0b10101010;
6359 var y = @shrExact(x, 2);
6360 std.debug.warn("value: {}\n", y);
6361}
6362 {#code_end#}
63056363 {#header_close#}
63066364 {#header_open|Division by Zero#}
63076365 <p>At compile-time:</p>
......@@ -6312,8 +6370,17 @@ comptime {
63126370 const c = a / b;
63136371}
63146372 {#code_end#}
6315 <p>At runtime crashes with the message <code>division by zero</code> and a stack trace.</p>
6373 <p>At runtime:</p>
6374 {#code_begin|exe_err#}
6375const std = @import("std");
63166376
6377pub fn main() void {
6378 var a: u32 = 1;
6379 var b: u32 = 0;
6380 var c = a / b;
6381 std.debug.warn("value: {}\n", c);
6382}
6383 {#code_end#}
63176384 {#header_close#}
63186385 {#header_open|Remainder Division by Zero#}
63196386 <p>At compile-time:</p>
......@@ -6324,14 +6391,57 @@ comptime {
63246391 const c = a % b;
63256392}
63266393 {#code_end#}
6327 <p>At runtime crashes with the message <code>remainder division by zero</code> and a stack trace.</p>
6394 <p>At runtime:</p>
6395 {#code_begin|exe_err#}
6396const std = @import("std");
63286397
6398pub fn main() void {
6399 var a: u32 = 10;
6400 var b: u32 = 0;
6401 var c = a % b;
6402 std.debug.warn("value: {}\n", c);
6403}
6404 {#code_end#}
63296405 {#header_close#}
63306406 {#header_open|Exact Division Remainder#}
6331 <p>TODO</p>
6407 <p>At compile-time:</p>
6408 {#code_begin|test_err|exact division had a remainder#}
6409comptime {
6410 const a: u32 = 10;
6411 const b: u32 = 3;
6412 const c = @divExact(a, b);
6413}
6414 {#code_end#}
6415 <p>At runtime:</p>
6416 {#code_begin|exe_err#}
6417const std = @import("std");
6418
6419pub fn main() void {
6420 var a: u32 = 10;
6421 var b: u32 = 3;
6422 var c = @divExact(a, b);
6423 std.debug.warn("value: {}\n", c);
6424}
6425 {#code_end#}
63326426 {#header_close#}
63336427 {#header_open|Slice Widen Remainder#}
6334 <p>TODO</p>
6428 <p>At compile-time:</p>
6429 {#code_begin|test_err|unable to convert#}
6430comptime {
6431 var bytes = [5]u8{ 1, 2, 3, 4, 5 };
6432 var slice = @bytesToSlice(u32, bytes);
6433}
6434 {#code_end#}
6435 <p>At runtime:</p>
6436 {#code_begin|exe_err#}
6437const std = @import("std");
6438
6439pub fn main() void {
6440 var bytes = [5]u8{ 1, 2, 3, 4, 5 };
6441 var slice = @bytesToSlice(u32, bytes[0..]);
6442 std.debug.warn("value: {}\n", slice[0]);
6443}
6444 {#code_end#}
63356445 {#header_close#}
63366446 {#header_open|Attempt to Unwrap Null#}
63376447 <p>At compile-time:</p>
......@@ -6341,7 +6451,16 @@ comptime {
63416451 const number = optional_number.?;
63426452}
63436453 {#code_end#}
6344 <p>At runtime crashes with the message <code>attempt to unwrap null</code> and a stack trace.</p>
6454 <p>At runtime:</p>
6455 {#code_begin|exe_err#}
6456const std = @import("std");
6457
6458pub fn main() void {
6459 var optional_number: ?i32 = null;
6460 var number = optional_number.?;
6461 std.debug.warn("value: {}\n", number);
6462}
6463 {#code_end#}
63456464 <p>One way to avoid this crash is to test for null instead of assuming non-null, with
63466465 the <code>if</code> expression:</p>
63476466 {#code_begin|exe|test#}
......@@ -6356,6 +6475,7 @@ pub fn main() void {
63566475 }
63576476}
63586477 {#code_end#}
6478 {#see_also|Optionals#}
63596479 {#header_close#}
63606480 {#header_open|Attempt to Unwrap Error#}
63616481 <p>At compile-time:</p>
......@@ -6368,7 +6488,19 @@ fn getNumberOrFail() !i32 {
63686488 return error.UnableToReturnNumber;
63696489}
63706490 {#code_end#}
6371 <p>At runtime crashes with the message <code>attempt to unwrap error: ErrorCode</code> and a stack trace.</p>
6491 <p>At runtime:</p>
6492 {#code_begin|exe_err#}
6493const std = @import("std");
6494
6495pub fn main() void {
6496 const number = getNumberOrFail() catch unreachable;
6497 std.debug.warn("value: {}\n", number);
6498}
6499
6500fn getNumberOrFail() !i32 {
6501 return error.UnableToReturnNumber;
6502}
6503 {#code_end#}
63726504 <p>One way to avoid this crash is to test for an error instead of assuming a successful result, with
63736505 the <code>if</code> expression:</p>
63746506 {#code_begin|exe#}
......@@ -6388,6 +6520,7 @@ fn getNumberOrFail() !i32 {
63886520 return error.UnableToReturnNumber;
63896521}
63906522 {#code_end#}
6523 {#see_also|Errors#}
63916524 {#header_close#}
63926525 {#header_open|Invalid Error Code#}
63936526 <p>At compile-time:</p>
......@@ -6398,11 +6531,47 @@ comptime {
63986531 const invalid_err = @intToError(number);
63996532}
64006533 {#code_end#}
6401 <p>At runtime crashes with the message <code>invalid error code</code> and a stack trace.</p>
6534 <p>At runtime:</p>
6535 {#code_begin|exe_err#}
6536const std = @import("std");
6537
6538pub fn main() void {
6539 var err = error.AnError;
6540 var number = @errorToInt(err) + 500;
6541 var invalid_err = @intToError(number);
6542 std.debug.warn("value: {}\n", number);
6543}
6544 {#code_end#}
64026545 {#header_close#}
64036546 {#header_open|Invalid Enum Cast#}
6404 <p>TODO</p>
6547 <p>At compile-time:</p>
6548 {#code_begin|test_err|has no tag matching integer value 3#}
6549const Foo = enum {
6550 A,
6551 B,
6552 C,
6553};
6554comptime {
6555 const a: u2 = 3;
6556 const b = @intToEnum(Foo, a);
6557}
6558 {#code_end#}
6559 <p>At runtime:</p>
6560 {#code_begin|exe_err#}
6561const std = @import("std");
6562
6563const Foo = enum {
6564 A,
6565 B,
6566 C,
6567};
64056568
6569pub fn main() void {
6570 var a: u2 = 3;
6571 var b = @intToEnum(Foo, a);
6572 std.debug.warn("value: {}\n", @tagName(b));
6573}
6574 {#code_end#}
64066575 {#header_close#}
64076576
64086577 {#header_open|Invalid Error Set Cast#}
src/codegen.cpp+18-1
......@@ -2673,8 +2673,25 @@ static LLVMValueRef ir_render_int_to_enum(CodeGen *g, IrExecutable *executable,
26732673 TypeTableEntry *tag_int_type = wanted_type->data.enumeration.tag_int_type;
26742674
26752675 LLVMValueRef target_val = ir_llvm_value(g, instruction->target);
2676 return gen_widen_or_shorten(g, ir_want_runtime_safety(g, &instruction->base),
2676 LLVMValueRef tag_int_value = gen_widen_or_shorten(g, ir_want_runtime_safety(g, &instruction->base),
26772677 instruction->target->value.type, tag_int_type, target_val);
2678
2679 if (ir_want_runtime_safety(g, &instruction->base)) {
2680 LLVMBasicBlockRef bad_value_block = LLVMAppendBasicBlock(g->cur_fn_val, "BadValue");
2681 LLVMBasicBlockRef ok_value_block = LLVMAppendBasicBlock(g->cur_fn_val, "OkValue");
2682 size_t field_count = wanted_type->data.enumeration.src_field_count;
2683 LLVMValueRef switch_instr = LLVMBuildSwitch(g->builder, tag_int_value, bad_value_block, field_count);
2684 for (size_t field_i = 0; field_i < field_count; field_i += 1) {
2685 LLVMValueRef this_tag_int_value = bigint_to_llvm_const(tag_int_type->type_ref,
2686 &wanted_type->data.enumeration.fields[field_i].value);
2687 LLVMAddCase(switch_instr, this_tag_int_value, ok_value_block);
2688 }
2689 LLVMPositionBuilderAtEnd(g->builder, bad_value_block);
2690 gen_safety_crash(g, PanicMsgIdBadEnumValue);
2691
2692 LLVMPositionBuilderAtEnd(g->builder, ok_value_block);
2693 }
2694 return tag_int_value;
26782695}
26792696
26802697static LLVMValueRef ir_render_int_to_err(CodeGen *g, IrExecutable *executable, IrInstructionIntToErr *instruction) {
test/runtime_safety.zig+18
......@@ -1,6 +1,24 @@
11const tests = @import("tests.zig");
22
33pub fn addCases(cases: *tests.CompareOutputContext) void {
4 cases.addRuntimeSafety("@intToEnum - no matching tag value",
5 \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {
6 \\ @import("std").os.exit(126);
7 \\}
8 \\const Foo = enum {
9 \\ A,
10 \\ B,
11 \\ C,
12 \\};
13 \\pub fn main() void {
14 \\ baz(bar(3));
15 \\}
16 \\fn bar(a: u2) Foo {
17 \\ return @intToEnum(Foo, a);
18 \\}
19 \\fn baz(a: Foo) void {}
20 );
21
422 cases.addRuntimeSafety("@floatToInt cannot fit - negative to unsigned",
523 \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {
624 \\ @import("std").os.exit(126);