Commit 35463526cc

Andrew Kelley <superjoe30@gmail.com>
2018-07-02 21:49:49
add runtime safety for `@intToEnum`; add docs for runtime safety
See #367
1 parent 2759c79
Changed files (3)
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#}
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) {
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);