| author | |
| committer | |
| log | 300fceac6eca99fd858678b03a47357e72856e10 |
| tree | e0a165d8ce2bdb1b394acf16e8b1a465ab84f9a8 |
| parent | 9c4dc7b1bb79b2e1cf5a88d99b9e187640426769 |
The checks are now valid on types whose size is not a power of two.
Closes #20965 files changed, 115 insertions(+), 22 deletions(-)
src/all_types.hpp+1| ... | @@ -1834,6 +1834,7 @@ enum PanicMsgId { | ... | @@ -1834,6 +1834,7 @@ enum PanicMsgId { |
| 1834 | PanicMsgIdBadNoAsyncCall, | 1834 | PanicMsgIdBadNoAsyncCall, |
| 1835 | PanicMsgIdResumeNotSuspendedFn, | 1835 | PanicMsgIdResumeNotSuspendedFn, |
| 1836 | PanicMsgIdBadSentinel, | 1836 | PanicMsgIdBadSentinel, |
| 1837 | PanicMsgIdShxTooBigRhs, | ||
| 1837 | 1838 | ||
| 1838 | PanicMsgIdCount, | 1839 | PanicMsgIdCount, |
| 1839 | }; | 1840 | }; |
src/codegen.cpp+32| ... | @@ -974,6 +974,8 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) { | ... | @@ -974,6 +974,8 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) { |
| 974 | return buf_create_from_str("resumed a non-suspended function"); | 974 | return buf_create_from_str("resumed a non-suspended function"); |
| 975 | case PanicMsgIdBadSentinel: | 975 | case PanicMsgIdBadSentinel: |
| 976 | return buf_create_from_str("sentinel mismatch"); | 976 | return buf_create_from_str("sentinel mismatch"); |
| 977 | case PanicMsgIdShxTooBigRhs: | ||
| 978 | return buf_create_from_str("shift amount is greater than the type size"); | ||
| 977 | } | 979 | } |
| 978 | zig_unreachable(); | 980 | zig_unreachable(); |
| 979 | } | 981 | } |
| ... | @@ -2841,6 +2843,26 @@ static LLVMValueRef gen_rem(CodeGen *g, bool want_runtime_safety, bool want_fast | ... | @@ -2841,6 +2843,26 @@ static LLVMValueRef gen_rem(CodeGen *g, bool want_runtime_safety, bool want_fast |
| 2841 | 2843 | ||
| 2842 | } | 2844 | } |
| 2843 | 2845 | ||
| 2846 | static 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 | |||
| 2844 | static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, | 2866 | static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, |
| 2845 | IrInstGenBinOp *bin_op_instruction) | 2867 | IrInstGenBinOp *bin_op_instruction) |
| 2846 | { | 2868 | { |
| ... | @@ -2949,6 +2971,11 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, | ... | @@ -2949,6 +2971,11 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, |
| 2949 | { | 2971 | { |
| 2950 | assert(scalar_type->id == ZigTypeIdInt); | 2972 | assert(scalar_type->id == ZigTypeIdInt); |
| 2951 | LLVMValueRef op2_casted = gen_widen_or_shorten(g, false, op2->value->type, scalar_type, op2_value); | 2973 | 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 | |||
| 2952 | bool is_sloppy = (op_id == IrBinOpBitShiftLeftLossy); | 2979 | bool is_sloppy = (op_id == IrBinOpBitShiftLeftLossy); |
| 2953 | if (is_sloppy) { | 2980 | if (is_sloppy) { |
| 2954 | return LLVMBuildShl(g->builder, op1_value, op2_casted, ""); | 2981 | return LLVMBuildShl(g->builder, op1_value, op2_casted, ""); |
| ... | @@ -2965,6 +2992,11 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, | ... | @@ -2965,6 +2992,11 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, |
| 2965 | { | 2992 | { |
| 2966 | assert(scalar_type->id == ZigTypeIdInt); | 2993 | assert(scalar_type->id == ZigTypeIdInt); |
| 2967 | LLVMValueRef op2_casted = gen_widen_or_shorten(g, false, op2->value->type, scalar_type, op2_value); | 2994 | 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 | |||
| 2968 | bool is_sloppy = (op_id == IrBinOpBitShiftRightLossy); | 3000 | bool is_sloppy = (op_id == IrBinOpBitShiftRightLossy); |
| 2969 | if (is_sloppy) { | 3001 | if (is_sloppy) { |
| 2970 | if (scalar_type->data.integral.is_signed) { | 3002 | 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 | ... | @@ -16648,36 +16648,34 @@ static IrInstGen *ir_analyze_bit_shift(IrAnalyze *ira, IrInstSrcBinOp *bin_op_in |
| 16648 | return ira->codegen->invalid_inst_gen; | 16648 | return ira->codegen->invalid_inst_gen; |
| 16649 | } | 16649 | } |
| 16650 | } else { | 16650 | } else { |
| 16651 | assert(op1->value->type->data.integral.bit_count > 0); | ||
| 16651 | ZigType *shift_amt_type = get_smallest_unsigned_int_type(ira->codegen, | 16652 | ZigType *shift_amt_type = get_smallest_unsigned_int_type(ira->codegen, |
| 16652 | op1->value->type->data.integral.bit_count - 1); | 16653 | op1->value->type->data.integral.bit_count - 1); |
| 16653 | if (bin_op_instruction->op_id == IrBinOpBitShiftLeftLossy && | ||
| 16654 | op2->value->type->id == ZigTypeIdComptimeInt) { | ||
| 16655 | 16654 | ||
| 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); | ||
| 16657 | if (op2_val == nullptr) | 16661 | if (op2_val == nullptr) |
| 16658 | return ira->codegen->invalid_inst_gen; | 16662 | return ira->codegen->invalid_inst_gen; |
| 16659 | if (!bigint_fits_in_bits(&op2_val->data.x_bigint, | 16663 | |
| 16660 | shift_amt_type->data.integral.bit_count, | 16664 | BigInt bit_count_value = {0}; |
| 16661 | op2_val->data.x_bigint.is_negative)) { | 16665 | bigint_init_unsigned(&bit_count_value, op1->value->type->data.integral.bit_count); |
| 16662 | Buf *val_buf = buf_alloc(); | 16666 | |
| 16663 | bigint_append_buf(val_buf, &op2_val->data.x_bigint, 10); | 16667 | if (bigint_cmp(&op2_val->data.x_bigint, &bit_count_value) != CmpLT) { |
| 16664 | ErrorMsg* msg = ir_add_error(ira, | 16668 | ErrorMsg* msg = ir_add_error(ira, |
| 16665 | &bin_op_instruction->base.base, | 16669 | &bin_op_instruction->base.base, |
| 16666 | buf_sprintf("RHS of shift is too large for LHS type")); | 16670 | buf_sprintf("RHS of shift is too large for LHS type")); |
| 16667 | add_error_note( | 16671 | add_error_note(ira->codegen, msg, op1->base.source_node, |
| 16668 | ira->codegen, | 16672 | buf_sprintf("type %s has only %u bits", |
| 16669 | msg, | 16673 | buf_ptr(&op1->value->type->name), |
| 16670 | op2->base.source_node, | 16674 | op1->value->type->data.integral.bit_count)); |
| 16671 | buf_sprintf("value %s cannot fit into type %s", | 16675 | |
| 16672 | buf_ptr(val_buf), | ||
| 16673 | buf_ptr(&shift_amt_type->name))); | ||
| 16674 | return ira->codegen->invalid_inst_gen; | 16676 | return ira->codegen->invalid_inst_gen; |
| 16675 | } | 16677 | } |
| 16676 | } | 16678 | } |
| 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; | ||
| 16681 | } | 16679 | } |
| 16682 | 16680 | ||
| 16683 | if (instr_is_comptime(op1) && instr_is_comptime(casted_op2)) { | 16681 | 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"); | ... | @@ -2,6 +2,38 @@ const tests = @import("tests.zig"); |
| 2 | const std = @import("std"); | 2 | const std = @import("std"); |
| 3 | 3 | ||
| 4 | pub fn addCases(cases: *tests.CompileErrorContext) void { | 4 | pub 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 | |||
| 5 | cases.addTest("combination of noasync and async", | 37 | cases.addTest("combination of noasync and async", |
| 6 | \\export fn entry() void { | 38 | \\export fn entry() void { |
| 7 | \\ noasync { | 39 | \\ noasync { |
| ... | @@ -4029,8 +4061,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { | ... | @@ -4029,8 +4061,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 4029 | \\} | 4061 | \\} |
| 4030 | \\export fn entry() u16 { return f(); } | 4062 | \\export fn entry() u16 { return f(); } |
| 4031 | , &[_][]const u8{ | 4063 | , &[_][]const u8{ |
| 4032 | "tmp.zig:3:14: error: RHS of shift is too large for LHS type", | 4064 | "tmp.zig:3:17: error: integer value 8 cannot be coerced to type 'u3'", |
| 4033 | "tmp.zig:3:17: note: value 8 cannot fit into type u3", | ||
| 4034 | }); | 4065 | }); |
| 4035 | 4066 | ||
| 4036 | cases.add("missing function call param", | 4067 | cases.add("missing function call param", |
test/runtime_safety.zig+31| ... | @@ -1,6 +1,37 @@ | ... | @@ -1,6 +1,37 @@ |
| 1 | const tests = @import("tests.zig"); | 1 | const tests = @import("tests.zig"); |
| 2 | 2 | ||
| 3 | pub fn addCases(cases: *tests.CompareOutputContext) void { | 3 | pub 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 | |||
| 4 | cases.addRuntimeSafety("slice sentinel mismatch - optional pointers", | 35 | cases.addRuntimeSafety("slice sentinel mismatch - optional pointers", |
| 5 | \\const std = @import("std"); | 36 | \\const std = @import("std"); |
| 6 | \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { | 37 | \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { |