Skip to content

Commit

Permalink
add runtime safety for @intToEnum; add docs for runtime safety
Browse files Browse the repository at this point in the history
See #367
  • Loading branch information
andrewrk committed Jul 2, 2018
1 parent 2759c79 commit 3546352
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 29 deletions.
225 changes: 197 additions & 28 deletions doc/langref.html.in
Expand Up @@ -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>
Expand All @@ -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>
Expand All @@ -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#}
Expand All @@ -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#}
Expand All @@ -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#}
Expand All @@ -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>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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>
Expand All @@ -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>
Expand All @@ -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>
Expand All @@ -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>
Expand All @@ -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#}
Expand All @@ -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>
Expand All @@ -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#}
Expand All @@ -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>
Expand All @@ -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#}
Expand Down
19 changes: 18 additions & 1 deletion src/codegen.cpp
Expand Up @@ -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) {
Expand Down

0 comments on commit 3546352

Please sign in to comment.