| author | |
| committer | |
| log | 51c6bb92b1c0c02b214ae21986dce3f2e9960099 |
| tree | 58d7865a9829233fa789fc1a9a4946c66d38d89e |
| parent | 83f6f730cdd5bb9c2a12b30c0aac33d858a1eaa8 |
| parent | 2f1052a313cb09f87f04cef56805c33be62eb169 |
| signature |
Stricter shift left/right safety checks8 files changed, 172 insertions(+), 32 deletions(-)
lib/std/io.zig+8-2| ... | ... | @@ -350,12 +350,18 @@ pub fn BitInStream(endian: builtin.Endian, comptime Error: type) type { |
| 350 | 350 | switch (endian) { |
| 351 | 351 | .Big => { |
| 352 | 352 | out_buffer = @as(Buf, self.bit_buffer >> shift); |
| 353 | self.bit_buffer <<= n; | |
| 353 | if (n >= u7_bit_count) | |
| 354 | self.bit_buffer = 0 | |
| 355 | else | |
| 356 | self.bit_buffer <<= n; | |
| 354 | 357 | }, |
| 355 | 358 | .Little => { |
| 356 | 359 | const value = (self.bit_buffer << shift) >> shift; |
| 357 | 360 | out_buffer = @as(Buf, value); |
| 358 | self.bit_buffer >>= n; | |
| 361 | if (n >= u7_bit_count) | |
| 362 | self.bit_buffer = 0 | |
| 363 | else | |
| 364 | self.bit_buffer >>= n; | |
| 359 | 365 | }, |
| 360 | 366 | } |
| 361 | 367 | self.bit_count -= n; |
lib/std/mem.zig+7-1| ... | ... | @@ -935,6 +935,9 @@ pub fn writeInt(comptime T: type, buffer: *[@divExact(T.bit_count, 8)]u8, value: |
| 935 | 935 | pub fn writeIntSliceLittle(comptime T: type, buffer: []u8, value: T) void { |
| 936 | 936 | assert(buffer.len >= @divExact(T.bit_count, 8)); |
| 937 | 937 | |
| 938 | if (T.bit_count == 0) | |
| 939 | return set(u8, buffer, 0); | |
| 940 | ||
| 938 | 941 | // TODO I want to call writeIntLittle here but comptime eval facilities aren't good enough |
| 939 | 942 | const uint = std.meta.IntType(false, T.bit_count); |
| 940 | 943 | var bits = @truncate(uint, value); |
| ... | ... | @@ -952,6 +955,9 @@ pub fn writeIntSliceLittle(comptime T: type, buffer: []u8, value: T) void { |
| 952 | 955 | pub fn writeIntSliceBig(comptime T: type, buffer: []u8, value: T) void { |
| 953 | 956 | assert(buffer.len >= @divExact(T.bit_count, 8)); |
| 954 | 957 | |
| 958 | if (T.bit_count == 0) | |
| 959 | return set(u8, buffer, 0); | |
| 960 | ||
| 955 | 961 | // TODO I want to call writeIntBig here but comptime eval facilities aren't good enough |
| 956 | 962 | const uint = std.meta.IntType(false, T.bit_count); |
| 957 | 963 | var bits = @truncate(uint, value); |
| ... | ... | @@ -1821,7 +1827,7 @@ test "sliceAsBytes" { |
| 1821 | 1827 | } |
| 1822 | 1828 | |
| 1823 | 1829 | test "sliceAsBytes with sentinel slice" { |
| 1824 | const empty_string:[:0]const u8 = ""; | |
| 1830 | const empty_string: [:0]const u8 = ""; | |
| 1825 | 1831 | const bytes = sliceAsBytes(empty_string); |
| 1826 | 1832 | testing.expect(bytes.len == 0); |
| 1827 | 1833 | } |
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+41-27| ... | ... | @@ -16635,49 +16635,69 @@ static IrInstGen *ir_analyze_bit_shift(IrAnalyze *ira, IrInstSrcBinOp *bin_op_in |
| 16635 | 16635 | IrInstGen *casted_op2; |
| 16636 | 16636 | IrBinOp op_id = bin_op_instruction->op_id; |
| 16637 | 16637 | if (op1->value->type->id == ZigTypeIdComptimeInt) { |
| 16638 | // comptime_int has no finite bit width | |
| 16638 | 16639 | casted_op2 = op2; |
| 16639 | 16640 | |
| 16640 | 16641 | if (op_id == IrBinOpBitShiftLeftLossy) { |
| 16641 | 16642 | op_id = IrBinOpBitShiftLeftExact; |
| 16642 | 16643 | } |
| 16643 | 16644 | |
| 16644 | if (casted_op2->value->data.x_bigint.is_negative) { | |
| 16645 | if (!instr_is_comptime(op2)) { | |
| 16646 | ir_add_error(ira, &bin_op_instruction->base.base, | |
| 16647 | buf_sprintf("LHS of shift must be an integer type, or RHS must be compile-time known")); | |
| 16648 | return ira->codegen->invalid_inst_gen; | |
| 16649 | } | |
| 16650 | ||
| 16651 | ZigValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad); | |
| 16652 | if (op2_val == nullptr) | |
| 16653 | return ira->codegen->invalid_inst_gen; | |
| 16654 | ||
| 16655 | if (op2_val->data.x_bigint.is_negative) { | |
| 16645 | 16656 | Buf *val_buf = buf_alloc(); |
| 16646 | bigint_append_buf(val_buf, &casted_op2->value->data.x_bigint, 10); | |
| 16647 | ir_add_error(ira, &casted_op2->base, buf_sprintf("shift by negative value %s", buf_ptr(val_buf))); | |
| 16657 | bigint_append_buf(val_buf, &op2_val->data.x_bigint, 10); | |
| 16658 | ir_add_error(ira, &casted_op2->base, | |
| 16659 | buf_sprintf("shift by negative value %s", buf_ptr(val_buf))); | |
| 16648 | 16660 | return ira->codegen->invalid_inst_gen; |
| 16649 | 16661 | } |
| 16650 | 16662 | } else { |
| 16663 | const unsigned bit_count = op1->value->type->data.integral.bit_count; | |
| 16651 | 16664 | 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) { | |
| 16665 | bit_count > 0 ? bit_count - 1 : 0); | |
| 16655 | 16666 | |
| 16656 | ZigValue *op2_val = ir_resolve_const(ira, op2, UndefBad); | |
| 16667 | casted_op2 = ir_implicit_cast(ira, op2, shift_amt_type); | |
| 16668 | if (type_is_invalid(casted_op2->value->type)) | |
| 16669 | return ira->codegen->invalid_inst_gen; | |
| 16670 | ||
| 16671 | // This check is only valid iff op1 has at least one bit | |
| 16672 | if (bit_count > 0 && instr_is_comptime(casted_op2)) { | |
| 16673 | ZigValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad); | |
| 16657 | 16674 | if (op2_val == nullptr) |
| 16658 | 16675 | 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); | |
| 16676 | ||
| 16677 | BigInt bit_count_value = {0}; | |
| 16678 | bigint_init_unsigned(&bit_count_value, bit_count); | |
| 16679 | ||
| 16680 | if (bigint_cmp(&op2_val->data.x_bigint, &bit_count_value) != CmpLT) { | |
| 16664 | 16681 | ErrorMsg* msg = ir_add_error(ira, |
| 16665 | 16682 | &bin_op_instruction->base.base, |
| 16666 | 16683 | 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))); | |
| 16684 | add_error_note(ira->codegen, msg, op1->base.source_node, | |
| 16685 | buf_sprintf("type %s has only %u bits", | |
| 16686 | buf_ptr(&op1->value->type->name), bit_count)); | |
| 16687 | ||
| 16674 | 16688 | return ira->codegen->invalid_inst_gen; |
| 16675 | 16689 | } |
| 16676 | 16690 | } |
| 16691 | } | |
| 16677 | 16692 | |
| 16678 | casted_op2 = ir_implicit_cast(ira, op2, shift_amt_type); | |
| 16679 | if (type_is_invalid(casted_op2->value->type)) | |
| 16693 | // Fast path for zero RHS | |
| 16694 | if (instr_is_comptime(casted_op2)) { | |
| 16695 | ZigValue *op2_val = ir_resolve_const(ira, casted_op2, UndefBad); | |
| 16696 | if (op2_val == nullptr) | |
| 16680 | 16697 | return ira->codegen->invalid_inst_gen; |
| 16698 | ||
| 16699 | if (bigint_cmp_zero(&op2_val->data.x_bigint) == CmpEQ) | |
| 16700 | return ir_analyze_cast(ira, &bin_op_instruction->base.base, op1->value->type, op1); | |
| 16681 | 16701 | } |
| 16682 | 16702 | |
| 16683 | 16703 | if (instr_is_comptime(op1) && instr_is_comptime(casted_op2)) { |
| ... | ... | @@ -16690,12 +16710,6 @@ static IrInstGen *ir_analyze_bit_shift(IrAnalyze *ira, IrInstSrcBinOp *bin_op_in |
| 16690 | 16710 | return ira->codegen->invalid_inst_gen; |
| 16691 | 16711 | |
| 16692 | 16712 | return ir_analyze_math_op(ira, &bin_op_instruction->base.base, op1->value->type, op1_val, op_id, op2_val); |
| 16693 | } else if (op1->value->type->id == ZigTypeIdComptimeInt) { | |
| 16694 | ir_add_error(ira, &bin_op_instruction->base.base, | |
| 16695 | buf_sprintf("LHS of shift must be an integer type, or RHS must be compile-time known")); | |
| 16696 | return ira->codegen->invalid_inst_gen; | |
| 16697 | } else if (instr_is_comptime(casted_op2) && bigint_cmp_zero(&casted_op2->value->data.x_bigint) == CmpEQ) { | |
| 16698 | return ir_build_cast(ira, &bin_op_instruction->base.base, op1->value->type, op1, CastOpNoop); | |
| 16699 | 16713 | } |
| 16700 | 16714 | |
| 16701 | 16715 | return ir_build_bin_op_gen(ira, &bin_op_instruction->base.base, op1->value->type, |
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 { |
test/stage1/behavior/math.zig+19| ... | ... | @@ -453,6 +453,25 @@ fn testShrExact(x: u8) void { |
| 453 | 453 | expect(shifted == 0b00101101); |
| 454 | 454 | } |
| 455 | 455 | |
| 456 | test "shift left/right on u0 operand" { | |
| 457 | const S = struct { | |
| 458 | fn doTheTest() void { | |
| 459 | var x: u0 = 0; | |
| 460 | var y: u0 = 0; | |
| 461 | expectEqual(@as(u0, 0), x << 0); | |
| 462 | expectEqual(@as(u0, 0), x >> 0); | |
| 463 | expectEqual(@as(u0, 0), x << y); | |
| 464 | expectEqual(@as(u0, 0), x >> y); | |
| 465 | expectEqual(@as(u0, 0), @shlExact(x, 0)); | |
| 466 | expectEqual(@as(u0, 0), @shrExact(x, 0)); | |
| 467 | expectEqual(@as(u0, 0), @shlExact(x, y)); | |
| 468 | expectEqual(@as(u0, 0), @shrExact(x, y)); | |
| 469 | } | |
| 470 | }; | |
| 471 | S.doTheTest(); | |
| 472 | comptime S.doTheTest(); | |
| 473 | } | |
| 474 | ||
| 456 | 475 | test "comptime_int addition" { |
| 457 | 476 | comptime { |
| 458 | 477 | expect(35361831660712422535336160538497375248 + 101752735581729509668353361206450473702 == 137114567242441932203689521744947848950); |