authorgravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2020-03-10 20:54:05+01:00
committergravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2020-03-10 20:54:05+01:00
log300fceac6eca99fd858678b03a47357e72856e10
treee0a165d8ce2bdb1b394acf16e8b1a465ab84f9a8
parent9c4dc7b1bb79b2e1cf5a88d99b9e187640426769

ir: Implement more safety checks for shl/shr

The checks are now valid on types whose size is not a power of two. Closes #2096

5 files changed, 115 insertions(+), 22 deletions(-)

src/all_types.hpp+1
......@@ -1834,6 +1834,7 @@ enum PanicMsgId {
18341834 PanicMsgIdBadNoAsyncCall,
18351835 PanicMsgIdResumeNotSuspendedFn,
18361836 PanicMsgIdBadSentinel,
1837 PanicMsgIdShxTooBigRhs,
18371838
18381839 PanicMsgIdCount,
18391840};
src/codegen.cpp+32
......@@ -974,6 +974,8 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) {
974974 return buf_create_from_str("resumed a non-suspended function");
975975 case PanicMsgIdBadSentinel:
976976 return buf_create_from_str("sentinel mismatch");
977 case PanicMsgIdShxTooBigRhs:
978 return buf_create_from_str("shift amount is greater than the type size");
977979 }
978980 zig_unreachable();
979981}
......@@ -2841,6 +2843,26 @@ static LLVMValueRef gen_rem(CodeGen *g, bool want_runtime_safety, bool want_fast
28412843
28422844}
28432845
2846static void gen_shift_rhs_check(CodeGen *g, ZigType *lhs_type, ZigType *rhs_type, LLVMValueRef value) {
2847 // We only check if the rhs value of the shift expression is greater or
2848 // equal to the number of bits of the lhs if it's not a power of two,
2849 // otherwise the check is useful as the allowed values are limited by the
2850 // operand type itself
2851 if (!is_power_of_2(lhs_type->data.integral.bit_count)) {
2852 LLVMValueRef bit_count_value = LLVMConstInt(get_llvm_type(g, rhs_type),
2853 lhs_type->data.integral.bit_count, false);
2854 LLVMValueRef less_than_bit = LLVMBuildICmp(g->builder, LLVMIntULT, value, bit_count_value, "");
2855 LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "CheckFail");
2856 LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "CheckOk");
2857 LLVMBuildCondBr(g->builder, less_than_bit, ok_block, fail_block);
2858
2859 LLVMPositionBuilderAtEnd(g->builder, fail_block);
2860 gen_safety_crash(g, PanicMsgIdShxTooBigRhs);
2861
2862 LLVMPositionBuilderAtEnd(g->builder, ok_block);
2863 }
2864}
2865
28442866static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable,
28452867 IrInstGenBinOp *bin_op_instruction)
28462868{
......@@ -2949,6 +2971,11 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable,
29492971 {
29502972 assert(scalar_type->id == ZigTypeIdInt);
29512973 LLVMValueRef op2_casted = gen_widen_or_shorten(g, false, op2->value->type, scalar_type, op2_value);
2974
2975 if (want_runtime_safety) {
2976 gen_shift_rhs_check(g, scalar_type, op2->value->type, op2_value);
2977 }
2978
29522979 bool is_sloppy = (op_id == IrBinOpBitShiftLeftLossy);
29532980 if (is_sloppy) {
29542981 return LLVMBuildShl(g->builder, op1_value, op2_casted, "");
......@@ -2965,6 +2992,11 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable,
29652992 {
29662993 assert(scalar_type->id == ZigTypeIdInt);
29672994 LLVMValueRef op2_casted = gen_widen_or_shorten(g, false, op2->value->type, scalar_type, op2_value);
2995
2996 if (want_runtime_safety) {
2997 gen_shift_rhs_check(g, scalar_type, op2->value->type, op2_value);
2998 }
2999
29683000 bool is_sloppy = (op_id == IrBinOpBitShiftRightLossy);
29693001 if (is_sloppy) {
29703002 if (scalar_type->data.integral.is_signed) {
src/ir.cpp+18-20
......@@ -16648,36 +16648,34 @@ static IrInstGen *ir_analyze_bit_shift(IrAnalyze *ira, IrInstSrcBinOp *bin_op_in
1664816648 return ira->codegen->invalid_inst_gen;
1664916649 }
1665016650 } else {
16651 assert(op1->value->type->data.integral.bit_count > 0);
1665116652 ZigType *shift_amt_type = get_smallest_unsigned_int_type(ira->codegen,
16652 op1->value->type->data.integral.bit_count - 1);
16653 if (bin_op_instruction->op_id == IrBinOpBitShiftLeftLossy &&
16654 op2->value->type->id == ZigTypeIdComptimeInt) {
16653 op1->value->type->data.integral.bit_count - 1);
1665516654
16656 ZigValue *op2_val = ir_resolve_const(ira, op2, UndefBad);
16655 casted_op2 = ir_implicit_cast(ira, op2, shift_amt_type);
16656 if (type_is_invalid(casted_op2->value->type))
16657 return ira->codegen->invalid_inst_gen;
16658
16659 if (instr_is_comptime(casted_op2)) {
16660 ZigValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad);
1665716661 if (op2_val == nullptr)
1665816662 return ira->codegen->invalid_inst_gen;
16659 if (!bigint_fits_in_bits(&op2_val->data.x_bigint,
16660 shift_amt_type->data.integral.bit_count,
16661 op2_val->data.x_bigint.is_negative)) {
16662 Buf *val_buf = buf_alloc();
16663 bigint_append_buf(val_buf, &op2_val->data.x_bigint, 10);
16663
16664 BigInt bit_count_value = {0};
16665 bigint_init_unsigned(&bit_count_value, op1->value->type->data.integral.bit_count);
16666
16667 if (bigint_cmp(&op2_val->data.x_bigint, &bit_count_value) != CmpLT) {
1666416668 ErrorMsg* msg = ir_add_error(ira,
1666516669 &bin_op_instruction->base.base,
1666616670 buf_sprintf("RHS of shift is too large for LHS type"));
16667 add_error_note(
16668 ira->codegen,
16669 msg,
16670 op2->base.source_node,
16671 buf_sprintf("value %s cannot fit into type %s",
16672 buf_ptr(val_buf),
16673 buf_ptr(&shift_amt_type->name)));
16671 add_error_note(ira->codegen, msg, op1->base.source_node,
16672 buf_sprintf("type %s has only %u bits",
16673 buf_ptr(&op1->value->type->name),
16674 op1->value->type->data.integral.bit_count));
16675
1667416676 return ira->codegen->invalid_inst_gen;
1667516677 }
1667616678 }
16677
16678 casted_op2 = ir_implicit_cast(ira, op2, shift_amt_type);
16679 if (type_is_invalid(casted_op2->value->type))
16680 return ira->codegen->invalid_inst_gen;
1668116679 }
1668216680
1668316681 if (instr_is_comptime(op1) && instr_is_comptime(casted_op2)) {
test/compile_errors.zig+33-2
......@@ -2,6 +2,38 @@ const tests = @import("tests.zig");
22const std = @import("std");
33
44pub fn addCases(cases: *tests.CompileErrorContext) void {
5 cases.addTest("shift on type with non-power-of-two size",
6 \\export fn entry() void {
7 \\ const S = struct {
8 \\ fn a() void {
9 \\ var x: u24 = 42;
10 \\ _ = x >> 24;
11 \\ }
12 \\ fn b() void {
13 \\ var x: u24 = 42;
14 \\ _ = x << 24;
15 \\ }
16 \\ fn c() void {
17 \\ var x: u24 = 42;
18 \\ _ = @shlExact(x, 24);
19 \\ }
20 \\ fn d() void {
21 \\ var x: u24 = 42;
22 \\ _ = @shrExact(x, 24);
23 \\ }
24 \\ };
25 \\ S.a();
26 \\ S.b();
27 \\ S.c();
28 \\ S.d();
29 \\}
30 , &[_][]const u8{
31 "tmp.zig:5:19: error: RHS of shift is too large for LHS type",
32 "tmp.zig:9:19: error: RHS of shift is too large for LHS type",
33 "tmp.zig:13:17: error: RHS of shift is too large for LHS type",
34 "tmp.zig:17:17: error: RHS of shift is too large for LHS type",
35 });
36
537 cases.addTest("combination of noasync and async",
638 \\export fn entry() void {
739 \\ noasync {
......@@ -4029,8 +4061,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
40294061 \\}
40304062 \\export fn entry() u16 { return f(); }
40314063 , &[_][]const u8{
4032 "tmp.zig:3:14: error: RHS of shift is too large for LHS type",
4033 "tmp.zig:3:17: note: value 8 cannot fit into type u3",
4064 "tmp.zig:3:17: error: integer value 8 cannot be coerced to type 'u3'",
40344065 });
40354066
40364067 cases.add("missing function call param",
test/runtime_safety.zig+31
......@@ -1,6 +1,37 @@
11const tests = @import("tests.zig");
22
33pub fn addCases(cases: *tests.CompareOutputContext) void {
4 cases.addRuntimeSafety("shift left by huge amount",
5 \\const std = @import("std");
6 \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {
7 \\ std.debug.warn("{}\n", .{message});
8 \\ if (std.mem.eql(u8, message, "shift amount is greater than the type size")) {
9 \\ std.process.exit(126); // good
10 \\ }
11 \\ std.process.exit(0); // test failed
12 \\}
13 \\pub fn main() void {
14 \\ var x: u24 = 42;
15 \\ var y: u5 = 24;
16 \\ var z = x >> y;
17 \\}
18 );
19
20 cases.addRuntimeSafety("shift right by huge amount",
21 \\const std = @import("std");
22 \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {
23 \\ if (std.mem.eql(u8, message, "shift amount is greater than the type size")) {
24 \\ std.process.exit(126); // good
25 \\ }
26 \\ std.process.exit(0); // test failed
27 \\}
28 \\pub fn main() void {
29 \\ var x: u24 = 42;
30 \\ var y: u5 = 24;
31 \\ var z = x << y;
32 \\}
33 );
34
435 cases.addRuntimeSafety("slice sentinel mismatch - optional pointers",
536 \\const std = @import("std");
637 \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn {