From 01698528d1dff627b7e057651b137c20df7c7231 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 25 Mar 2022 09:09:32 -0700 Subject: [PATCH 1/4] stage2: safety checks for slicing a null C pointer --- src/Sema.zig | 14 ++++++++++++++ test/behavior/slice.zig | 1 + .../stage2/slice_of_null_pointer.zig | 10 ++++++++++ 3 files changed, 25 insertions(+) create mode 100644 test/compile_errors/stage2/slice_of_null_pointer.zig diff --git a/src/Sema.zig b/src/Sema.zig index 4e0b57fbc6..86e330a759 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -19964,6 +19964,14 @@ fn analyzeSlice( slice_ty = ptr_ptr_child_ty; array_ty = ptr_ptr_child_ty; elem_ty = ptr_ptr_child_ty.childType(); + + if (ptr_ptr_child_ty.ptrSize() == .C) { + if (try sema.resolveDefinedValue(block, ptr_src, ptr_or_slice)) |ptr_val| { + if (ptr_val.isNull()) { + return sema.fail(block, ptr_src, "slice of null pointer", .{}); + } + } + } }, .Slice => { ptr_sentinel = ptr_ptr_child_ty.sentinel(); @@ -20162,6 +20170,12 @@ fn analyzeSlice( try sema.requireRuntimeBlock(block, src); if (block.wantSafety()) { + // requirement: slicing C ptr is non-null + if (ptr_ptr_child_ty.isCPtr()) { + const is_non_null = try sema.analyzeIsNull(block, ptr_src, ptr, true); + try sema.addSafetyCheck(block, is_non_null, .unwrap_null); + } + // requirement: end <= len const opt_len_inst = if (array_ty.zigTypeTag() == .Array) try sema.addIntUnsigned(Type.usize, array_ty.arrayLenIncludingSentinel()) diff --git a/test/behavior/slice.zig b/test/behavior/slice.zig index 9f3ba001cf..a9f89130a1 100644 --- a/test/behavior/slice.zig +++ b/test/behavior/slice.zig @@ -233,6 +233,7 @@ fn sliceFromLenToLen(a_slice: []u8, start: usize, end: usize) []u8 { test "C pointer" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; var buf: [*c]const u8 = "kjdhfkjdhfdkjhfkfjhdfkjdhfkdjhfdkjhf"; var len: u32 = 10; diff --git a/test/compile_errors/stage2/slice_of_null_pointer.zig b/test/compile_errors/stage2/slice_of_null_pointer.zig new file mode 100644 index 0000000000..1e3f0d6aee --- /dev/null +++ b/test/compile_errors/stage2/slice_of_null_pointer.zig @@ -0,0 +1,10 @@ +comptime { + var x: [*c]u8 = null; + var runtime_len: usize = 0; + var y = x[0..runtime_len]; + _ = y; +} + +// slice of null C pointer +// +// :4:14: error: slice of null pointer From 8fbac2e86d35bf363b67aba0f1915b7c9d32dcd0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 25 Mar 2022 09:40:11 -0700 Subject: [PATCH 2/4] stage2: runtime safety check integer cast truncating bits --- src/Sema.zig | 21 ++++++++++++++++++--- test/behavior/eval.zig | 1 + test/behavior/fn.zig | 1 + test/behavior/for.zig | 1 + test/behavior/int128.zig | 1 + 5 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 86e330a759..473c9eab66 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6781,14 +6781,27 @@ fn intCast( return sema.fail(block, operand_src, "unable to cast runtime value to 'comptime_int'", .{}); } - // TODO insert safety check to make sure the value fits in the dest type - _ = runtime_safety; - if ((try sema.typeHasOnePossibleValue(block, dest_ty_src, dest_ty))) |opv| { return sema.addConstant(dest_ty, opv); } try sema.requireRuntimeBlock(block, operand_src); + if (runtime_safety) { + const target = sema.mod.getTarget(); + const operand_ty = sema.typeOf(operand); + const actual_info = operand_ty.intInfo(target); + const wanted_info = dest_ty.intInfo(target); + const actual_bits = actual_info.bits; + const wanted_bits = wanted_info.bits; + + // requirement: operand can fit into bit size of destination type + if (actual_bits > wanted_bits) { + const max_int = try dest_ty.maxInt(sema.arena, target); + const max_int_inst = try sema.addConstant(operand_ty, max_int); + const is_in_range = try block.addBinOp(.cmp_lte, operand, max_int_inst); + try sema.addSafetyCheck(block, is_in_range, .cast_truncated_data); + } + } return block.addTyOp(.intcast, dest_ty, operand); } @@ -16166,6 +16179,7 @@ pub const PanicId = enum { incorrect_alignment, invalid_error_code, index_out_of_bounds, + cast_truncated_data, }; fn addSafetyCheck( @@ -16288,6 +16302,7 @@ fn safetyPanic( .incorrect_alignment => "incorrect alignment", .invalid_error_code => "invalid error code", .index_out_of_bounds => "attempt to index out of bounds", + .cast_truncated_data => "integer cast truncated bits", }; const msg_inst = msg_inst: { diff --git a/test/behavior/eval.zig b/test/behavior/eval.zig index e3024a3895..9cad1c6106 100644 --- a/test/behavior/eval.zig +++ b/test/behavior/eval.zig @@ -443,6 +443,7 @@ fn copyWithPartialInline(s: []u32, b: []u8) void { test "binary math operator in partially inlined function" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO var s: [4]u32 = undefined; var b: [16]u8 = undefined; diff --git a/test/behavior/fn.zig b/test/behavior/fn.zig index ed71bf3d59..68eb730b57 100644 --- a/test/behavior/fn.zig +++ b/test/behavior/fn.zig @@ -315,6 +315,7 @@ test "function pointers" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO const fns = [_]*const @TypeOf(fn1){ &fn1, diff --git a/test/behavior/for.zig b/test/behavior/for.zig index 5188f02381..db3288a4d1 100644 --- a/test/behavior/for.zig +++ b/test/behavior/for.zig @@ -69,6 +69,7 @@ test "basic for loop" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; const expected_result = [_]u8{ 9, 8, 7, 6, 0, 1, 2, 3 } ** 3; diff --git a/test/behavior/int128.zig b/test/behavior/int128.zig index f57999511c..08c6dd0e4d 100644 --- a/test/behavior/int128.zig +++ b/test/behavior/int128.zig @@ -46,6 +46,7 @@ test "int128" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO var buff: i128 = -1; try expect(buff < 0 and (buff + 1) == 0); From 4ad98d07141cfba9ec9eb94c5daa13cc70c9d9cd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 25 Mar 2022 09:53:07 -0700 Subject: [PATCH 3/4] stage2: runtime safety check intCast to u0 must fit --- src/Sema.zig | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Sema.zig b/src/Sema.zig index 473c9eab66..4a87145700 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6782,6 +6782,20 @@ fn intCast( } if ((try sema.typeHasOnePossibleValue(block, dest_ty_src, dest_ty))) |opv| { + // requirement: intCast(u0, input) iff input == 0 + if (runtime_safety and block.wantSafety()) { + try sema.requireRuntimeBlock(block, operand_src); + const target = sema.mod.getTarget(); + const wanted_info = dest_ty.intInfo(target); + const wanted_bits = wanted_info.bits; + + if (wanted_bits == 0) { + const zero_inst = try sema.addConstant(sema.typeOf(operand), Value.zero); + const is_in_range = try block.addBinOp(.cmp_eq, operand, zero_inst); + try sema.addSafetyCheck(block, is_in_range, .cast_truncated_data); + } + } + return sema.addConstant(dest_ty, opv); } @@ -6794,7 +6808,7 @@ fn intCast( const actual_bits = actual_info.bits; const wanted_bits = wanted_info.bits; - // requirement: operand can fit into bit size of destination type + // requirement: bitSizeOf(operand) <= bitSizeOf(destination type) if (actual_bits > wanted_bits) { const max_int = try dest_ty.maxInt(sema.arena, target); const max_int_inst = try sema.addConstant(operand_ty, max_int); From 3c918184385f9ffc80693fb2683b4a9b574f9b66 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 25 Mar 2022 16:45:29 -0700 Subject: [PATCH 4/4] stage2: runtime safety check intCast signedness --- src/Sema.zig | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index 4a87145700..beab957529 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6800,7 +6800,7 @@ fn intCast( } try sema.requireRuntimeBlock(block, operand_src); - if (runtime_safety) { + if (runtime_safety and block.wantSafety()) { const target = sema.mod.getTarget(); const operand_ty = sema.typeOf(operand); const actual_info = operand_ty.intInfo(target); @@ -6808,8 +6808,21 @@ fn intCast( const actual_bits = actual_info.bits; const wanted_bits = wanted_info.bits; - // requirement: bitSizeOf(operand) <= bitSizeOf(destination type) - if (actual_bits > wanted_bits) { + // requirement: signed to unsigned >= 0 + if (actual_info.signedness == .signed and + wanted_info.signedness == .unsigned) + { + const zero_inst = try sema.addConstant(sema.typeOf(operand), Value.zero); + const is_in_range = try block.addBinOp(.cmp_gte, operand, zero_inst); + try sema.addSafetyCheck(block, is_in_range, .cast_truncated_data); + } + + // requirement: unsigned int value fits into target type + if (actual_bits > wanted_bits or + (actual_bits == wanted_bits and + actual_info.signedness == .unsigned and + wanted_info.signedness == .signed)) + { const max_int = try dest_ty.maxInt(sema.arena, target); const max_int_inst = try sema.addConstant(operand_ty, max_int); const is_in_range = try block.addBinOp(.cmp_lte, operand, max_int_inst);