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

See #367
This commit is contained in:
Andrew Kelley 2018-07-02 15:49:49 -04:00
parent 2759c7951d
commit 35463526cc
3 changed files with 233 additions and 29 deletions

View File

@ -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#}

View File

@ -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) {

View File

@ -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);