Skip to content

Commit 3546352

Browse files
committed
add runtime safety for @intToEnum; add docs for runtime safety
See #367
1 parent 2759c79 commit 3546352

File tree

3 files changed

+233
-29
lines changed

3 files changed

+233
-29
lines changed

doc/langref.html.in

Lines changed: 197 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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#}
6149+
const std = @import("std");
6150+
6151+
pub 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#}
6166+
pub fn main() void {
6167+
var x = foo("hello");
6168+
}
6169+
6170+
fn 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#}
6185+
const std = @import("std");
6186+
6187+
pub 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#}
6207+
const std = @import("std");
6208+
6209+
pub 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#}
6241+
const std = @import("std");
6242+
6243+
pub 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#}
62526292
const 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#}
6337+
const std = @import("std");
6338+
6339+
pub 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#}
6355+
const std = @import("std");
6356+
6357+
pub 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#}
6375+
const std = @import("std");
63166376

6377+
pub 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#}
6396+
const std = @import("std");
63286397

6398+
pub 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#}
6409+
comptime {
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#}
6417+
const std = @import("std");
6418+
6419+
pub 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#}
6430+
comptime {
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#}
6437+
const std = @import("std");
6438+
6439+
pub 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#}
6456+
const std = @import("std");
6457+
6458+
pub 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#}
6493+
const std = @import("std");
6494+
6495+
pub fn main() void {
6496+
const number = getNumberOrFail() catch unreachable;
6497+
std.debug.warn("value: {}\n", number);
6498+
}
6499+
6500+
fn 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#}
6536+
const std = @import("std");
6537+
6538+
pub 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#}
6549+
const Foo = enum {
6550+
A,
6551+
B,
6552+
C,
6553+
};
6554+
comptime {
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#}
6561+
const std = @import("std");
6562+
6563+
const Foo = enum {
6564+
A,
6565+
B,
6566+
C,
6567+
};
64056568

6569+
pub 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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

26802697
static LLVMValueRef ir_render_int_to_err(CodeGen *g, IrExecutable *executable, IrInstructionIntToErr *instruction) {

0 commit comments

Comments
 (0)