| 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 | 1834 | PanicMsgIdBadNoAsyncCall, |
| 1835 | 1835 | PanicMsgIdResumeNotSuspendedFn, |
| 1836 | 1836 | PanicMsgIdBadSentinel, |
| 1837 | PanicMsgIdShxTooBigRhs, | |
| 1837 | 1838 | |
| 1838 | 1839 | PanicMsgIdCount, |
| 1839 | 1840 | }; |
src/codegen.cpp+32| ... | ... | @@ -974,6 +974,8 @@ static Buf *panic_msg_buf(PanicMsgId msg_id) { |
| 974 | 974 | return buf_create_from_str("resumed a non-suspended function"); |
| 975 | 975 | case PanicMsgIdBadSentinel: |
| 976 | 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 | 980 | zig_unreachable(); |
| 979 | 981 | } |
| ... | ... | @@ -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 | 2866 | static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, |
| 2845 | 2867 | IrInstGenBinOp *bin_op_instruction) |
| 2846 | 2868 | { |
| ... | ... | @@ -2949,6 +2971,11 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, |
| 2949 | 2971 | { |
| 2950 | 2972 | assert(scalar_type->id == ZigTypeIdInt); |
| 2951 | 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 | 2979 | bool is_sloppy = (op_id == IrBinOpBitShiftLeftLossy); |
| 2953 | 2980 | if (is_sloppy) { |
| 2954 | 2981 | return LLVMBuildShl(g->builder, op1_value, op2_casted, ""); |
| ... | ... | @@ -2965,6 +2992,11 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutableGen *executable, |
| 2965 | 2992 | { |
| 2966 | 2993 | assert(scalar_type->id == ZigTypeIdInt); |
| 2967 | 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 | 3000 | bool is_sloppy = (op_id == IrBinOpBitShiftRightLossy); |
| 2969 | 3001 | if (is_sloppy) { |
| 2970 | 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 | 16648 | return ira->codegen->invalid_inst_gen; |
| 16649 | 16649 | } |
| 16650 | 16650 | } else { |
| 16651 | assert(op1->value->type->data.integral.bit_count > 0); | |
| 16651 | 16652 | 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); | |
| 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 | 16661 | if (op2_val == nullptr) |
| 16658 | 16662 | 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) { | |
| 16664 | 16668 | ErrorMsg* msg = ir_add_error(ira, |
| 16665 | 16669 | &bin_op_instruction->base.base, |
| 16666 | 16670 | 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 | ||
| 16674 | 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 | 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 | 2 | const std = @import("std"); |
| 3 | 3 | |
| 4 | 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 | 37 | cases.addTest("combination of noasync and async", |
| 6 | 38 | \\export fn entry() void { |
| 7 | 39 | \\ noasync { |
| ... | ... | @@ -4029,8 +4061,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 4029 | 4061 | \\} |
| 4030 | 4062 | \\export fn entry() u16 { return f(); } |
| 4031 | 4063 | , &[_][]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'", | |
| 4034 | 4065 | }); |
| 4035 | 4066 | |
| 4036 | 4067 | cases.add("missing function call param", |
test/runtime_safety.zig+31| ... | ... | @@ -1,6 +1,37 @@ |
| 1 | 1 | const tests = @import("tests.zig"); |
| 2 | 2 | |
| 3 | 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 | 35 | cases.addRuntimeSafety("slice sentinel mismatch - optional pointers", |
| 5 | 36 | \\const std = @import("std"); |
| 6 | 37 | \\pub fn panic(message: []const u8, stack_trace: ?*@import("builtin").StackTrace) noreturn { |