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 {...@@ -6144,7 +6144,14 @@ fn assert(ok: bool) void {
6144 if (!ok) unreachable; // assertion failure6144 if (!ok) unreachable; // assertion failure
6145}6145}
6146 {#code_end#}6146 {#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#}
6148 {#header_close#}6155 {#header_close#}
6149 {#header_open|Index out of Bounds#}6156 {#header_open|Index out of Bounds#}
6150 <p>At compile-time:</p>6157 <p>At compile-time:</p>
...@@ -6154,7 +6161,16 @@ comptime {...@@ -6154,7 +6161,16 @@ comptime {
6154 const garbage = array[5];6161 const garbage = array[5];
6155}6162}
6156 {#code_end#}6163 {#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#}
6158 {#header_close#}6174 {#header_close#}
6159 {#header_open|Cast Negative Number to Unsigned Integer#}6175 {#header_open|Cast Negative Number to Unsigned Integer#}
6160 <p>At compile-time:</p>6176 <p>At compile-time:</p>
...@@ -6164,10 +6180,18 @@ comptime {...@@ -6164,10 +6180,18 @@ comptime {
6164 const unsigned = @intCast(u32, value);6180 const unsigned = @intCast(u32, value);
6165}6181}
6166 {#code_end#}6182 {#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#}
6168 <p>6193 <p>
6169 If you are trying to obtain the maximum value of an unsigned integer, use <code>@maxValue(T)</code>,6194 To obtain the maximum value of an unsigned integer, use {#link|@maxValue#}.
6170 where <code>T</code> is the integer type, such as <code>u32</code>.
6171 </p>6195 </p>
6172 {#header_close#}6196 {#header_close#}
6173 {#header_open|Cast Truncates Data#}6197 {#header_open|Cast Truncates Data#}
...@@ -6178,11 +6202,18 @@ comptime {...@@ -6178,11 +6202,18 @@ comptime {
6178 const byte = @intCast(u8, spartan_count);6202 const byte = @intCast(u8, spartan_count);
6179}6203}
6180 {#code_end#}6204 {#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#}
6182 <p>6215 <p>
6183 If you are trying to truncate bits, use <code>@truncate(T, value)</code>,6216 To truncate bits, use {#link|@truncate#}.
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.
6186 </p>6217 </p>
6187 {#header_close#}6218 {#header_close#}
6188 {#header_open|Integer Overflow#}6219 {#header_open|Integer Overflow#}
...@@ -6194,9 +6225,9 @@ comptime {...@@ -6194,9 +6225,9 @@ comptime {
6194 <li><code>-</code> (negation)</li>6225 <li><code>-</code> (negation)</li>
6195 <li><code>*</code> (multiplication)</li>6226 <li><code>*</code> (multiplication)</li>
6196 <li><code>/</code> (division)</li>6227 <li><code>/</code> (division)</li>
6197 <li><code>@divTrunc</code> (division)</li>6228 <li>{#link|@divTrunc#} (division)</li>
6198 <li><code>@divFloor</code> (division)</li>6229 <li>{#link|@divFloor#} (division)</li>
6199 <li><code>@divExact</code> (division)</li>6230 <li>{#link|@divExact#} (division)</li>
6200 </ul>6231 </ul>
6201 <p>Example with addition at compile-time:</p>6232 <p>Example with addition at compile-time:</p>
6202 {#code_begin|test_err|operation caused overflow#}6233 {#code_begin|test_err|operation caused overflow#}
...@@ -6205,7 +6236,16 @@ comptime {...@@ -6205,7 +6236,16 @@ comptime {
6205 byte += 1;6236 byte += 1;
6206}6237}
6207 {#code_end#}6238 {#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#}
6209 {#header_close#}6249 {#header_close#}
6210 {#header_open|Standard Library Math Functions#}6250 {#header_open|Standard Library Math Functions#}
6211 <p>These functions provided by the standard library return possible errors.</p>6251 <p>These functions provided by the standard library return possible errors.</p>
...@@ -6240,13 +6280,13 @@ pub fn main() !void {...@@ -6240,13 +6280,13 @@ pub fn main() !void {
6240 occurred, as well as returning the overflowed bits:6280 occurred, as well as returning the overflowed bits:
6241 </p>6281 </p>
6242 <ul>6282 <ul>
6243 <li><code>@addWithOverflow</code></li>6283 <li>{#link|@addWithOverflow#}</li>
6244 <li><code>@subWithOverflow</code></li>6284 <li>{#link|@subWithOverflow#}</li>
6245 <li><code>@mulWithOverflow</code></li>6285 <li>{#link|@mulWithOverflow#}</li>
6246 <li><code>@shlWithOverflow</code></li>6286 <li>{#link|@shlWithOverflow#}</li>
6247 </ul>6287 </ul>
6248 <p>6288 <p>
6249 Example of <code>@addWithOverflow</code>:6289 Example of {#link|@addWithOverflow#}:
6250 </p>6290 </p>
6251 {#code_begin|exe#}6291 {#code_begin|exe#}
6252const warn = @import("std").debug.warn;6292const warn = @import("std").debug.warn;
...@@ -6292,7 +6332,16 @@ comptime {...@@ -6292,7 +6332,16 @@ comptime {
6292 const x = @shlExact(u8(0b01010101), 2);6332 const x = @shlExact(u8(0b01010101), 2);
6293}6333}
6294 {#code_end#}6334 {#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#}
6296 {#header_close#}6345 {#header_close#}
6297 {#header_open|Exact Right Shift Overflow#}6346 {#header_open|Exact Right Shift Overflow#}
6298 <p>At compile-time:</p>6347 <p>At compile-time:</p>
...@@ -6301,7 +6350,16 @@ comptime {...@@ -6301,7 +6350,16 @@ comptime {
6301 const x = @shrExact(u8(0b10101010), 2);6350 const x = @shrExact(u8(0b10101010), 2);
6302}6351}
6303 {#code_end#}6352 {#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#}
6305 {#header_close#}6363 {#header_close#}
6306 {#header_open|Division by Zero#}6364 {#header_open|Division by Zero#}
6307 <p>At compile-time:</p>6365 <p>At compile-time:</p>
...@@ -6312,8 +6370,17 @@ comptime {...@@ -6312,8 +6370,17 @@ comptime {
6312 const c = a / b;6370 const c = a / b;
6313}6371}
6314 {#code_end#}6372 {#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#}
6317 {#header_close#}6384 {#header_close#}
6318 {#header_open|Remainder Division by Zero#}6385 {#header_open|Remainder Division by Zero#}
6319 <p>At compile-time:</p>6386 <p>At compile-time:</p>
...@@ -6324,14 +6391,57 @@ comptime {...@@ -6324,14 +6391,57 @@ comptime {
6324 const c = a % b;6391 const c = a % b;
6325}6392}
6326 {#code_end#}6393 {#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#}
6329 {#header_close#}6405 {#header_close#}
6330 {#header_open|Exact Division Remainder#}6406 {#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#}
6332 {#header_close#}6426 {#header_close#}
6333 {#header_open|Slice Widen Remainder#}6427 {#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#}
6335 {#header_close#}6445 {#header_close#}
6336 {#header_open|Attempt to Unwrap Null#}6446 {#header_open|Attempt to Unwrap Null#}
6337 <p>At compile-time:</p>6447 <p>At compile-time:</p>
...@@ -6341,7 +6451,16 @@ comptime {...@@ -6341,7 +6451,16 @@ comptime {
6341 const number = optional_number.?;6451 const number = optional_number.?;
6342}6452}
6343 {#code_end#}6453 {#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#}
6345 <p>One way to avoid this crash is to test for null instead of assuming non-null, with6464 <p>One way to avoid this crash is to test for null instead of assuming non-null, with
6346 the <code>if</code> expression:</p>6465 the <code>if</code> expression:</p>
6347 {#code_begin|exe|test#}6466 {#code_begin|exe|test#}
...@@ -6356,6 +6475,7 @@ pub fn main() void {...@@ -6356,6 +6475,7 @@ pub fn main() void {
6356 }6475 }
6357}6476}
6358 {#code_end#}6477 {#code_end#}
6478 {#see_also|Optionals#}
6359 {#header_close#}6479 {#header_close#}
6360 {#header_open|Attempt to Unwrap Error#}6480 {#header_open|Attempt to Unwrap Error#}
6361 <p>At compile-time:</p>6481 <p>At compile-time:</p>
...@@ -6368,7 +6488,19 @@ fn getNumberOrFail() !i32 {...@@ -6368,7 +6488,19 @@ fn getNumberOrFail() !i32 {
6368 return error.UnableToReturnNumber;6488 return error.UnableToReturnNumber;
6369}6489}
6370 {#code_end#}6490 {#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#}
6372 <p>One way to avoid this crash is to test for an error instead of assuming a successful result, with6504 <p>One way to avoid this crash is to test for an error instead of assuming a successful result, with
6373 the <code>if</code> expression:</p>6505 the <code>if</code> expression:</p>
6374 {#code_begin|exe#}6506 {#code_begin|exe#}
...@@ -6388,6 +6520,7 @@ fn getNumberOrFail() !i32 {...@@ -6388,6 +6520,7 @@ fn getNumberOrFail() !i32 {
6388 return error.UnableToReturnNumber;6520 return error.UnableToReturnNumber;
6389}6521}
6390 {#code_end#}6522 {#code_end#}
6523 {#see_also|Errors#}
6391 {#header_close#}6524 {#header_close#}
6392 {#header_open|Invalid Error Code#}6525 {#header_open|Invalid Error Code#}
6393 <p>At compile-time:</p>6526 <p>At compile-time:</p>
...@@ -6398,11 +6531,47 @@ comptime {...@@ -6398,11 +6531,47 @@ comptime {
6398 const invalid_err = @intToError(number);6531 const invalid_err = @intToError(number);
6399}6532}
6400 {#code_end#}6533 {#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#}
6402 {#header_close#}6545 {#header_close#}
6403 {#header_open|Invalid Enum Cast#}6546 {#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#}
6406 {#header_close#}6575 {#header_close#}
64076576
6408 {#header_open|Invalid Error Set Cast#}6577 {#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,...@@ -2673,8 +2673,25 @@ static LLVMValueRef ir_render_int_to_enum(CodeGen *g, IrExecutable *executable,
2673 TypeTableEntry *tag_int_type = wanted_type->data.enumeration.tag_int_type;2673 TypeTableEntry *tag_int_type = wanted_type->data.enumeration.tag_int_type;
26742674
2675 LLVMValueRef target_val = ir_llvm_value(g, instruction->target);2675 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),
2677 instruction->target->value.type, tag_int_type, target_val);2677 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;
2678}2695}
26792696
2680static LLVMValueRef ir_render_int_to_err(CodeGen *g, IrExecutable *executable, IrInstructionIntToErr *instruction) {2697static LLVMValueRef ir_render_int_to_err(CodeGen *g, IrExecutable *executable, IrInstructionIntToErr *instruction) {
test/runtime_safety.zig+18
...@@ -1,6 +1,24 @@...@@ -1,6 +1,24 @@
1const tests = @import("tests.zig");1const tests = @import("tests.zig");
22
3pub fn addCases(cases: *tests.CompareOutputContext) void {3pub 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
4 cases.addRuntimeSafety("@floatToInt cannot fit - negative to unsigned",22 cases.addRuntimeSafety("@floatToInt cannot fit - negative to unsigned",
5 \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {23 \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {
6 \\ @import("std").os.exit(126);24 \\ @import("std").os.exit(126);