From 3be4e19bf91e2a6c48554021e4dac2489ab19693 Mon Sep 17 00:00:00 2001 From: Nick Treleaven Date: Thu, 7 Apr 2022 18:15:48 +0100 Subject: [PATCH] checker: disallow confusing smaller unsigned type == signed Otherwise C will evaluate `byte(-1) == -1` as false. Also disallow unsigned == negative literal. Fixes #13955. --- vlib/builtin/string.v | 2 +- vlib/v/ast/types.v | 2 ++ vlib/v/checker/checker.v | 24 +++++++++++++++ .../infix_unsigned_and_signed_int_err.out | 29 ++++++++++++++++++- .../infix_unsigned_and_signed_int_err.vv | 7 +++++ 5 files changed, 62 insertions(+), 2 deletions(-) diff --git a/vlib/builtin/string.v b/vlib/builtin/string.v index f1252149f3..7f38bc878a 100644 --- a/vlib/builtin/string.v +++ b/vlib/builtin/string.v @@ -1937,7 +1937,7 @@ pub fn (name string) match_glob(pattern string) bool { mut is_inverted := false mut inner_match := false mut inner_idx := bstart + 1 - mut inner_c := 0 + mut inner_c := byte(0) if inner_idx < plen { inner_c = pattern[inner_idx] if inner_c == `^` { diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index 842c304629..a5422c7ef3 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -442,6 +442,8 @@ pub const ( i64_type_idx, isize_type_idx] unsigned_integer_type_idxs = [byte_type_idx, u8_type_idx, u16_type_idx, u32_type_idx, u64_type_idx, usize_type_idx] + // C will promote any type smaller than int to int in an expression + int_promoted_type_idxs = [char_type_idx, i8_type_idx, i16_type_idx, byte_type_idx, u8_type_idx, u16_type_idx] float_type_idxs = [f32_type_idx, f64_type_idx, float_literal_type_idx] number_type_idxs = [i8_type_idx, i16_type_idx, int_type_idx, i64_type_idx, byte_type_idx, char_type_idx, u16_type_idx, u32_type_idx, u64_type_idx, isize_type_idx, diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 0fc6104eab..06b58cea95 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -623,6 +623,30 @@ pub fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { if is_mismatch { c.error('possible type mismatch of compared values of `$node.op` operation', left_right_pos) + } else if left_type in ast.integer_type_idxs && right_type in ast.integer_type_idxs { + is_left_type_signed := left_type in ast.signed_integer_type_idxs + is_right_type_signed := right_type in ast.signed_integer_type_idxs + if !is_left_type_signed && mut node.right is ast.IntegerLiteral { + if node.right.val.int() < 0 { + lt := c.table.sym(left_type).name + c.error('`$lt` cannot be compared with negative value', + node.right.pos) + } + } else if !is_right_type_signed && mut node.left is ast.IntegerLiteral { + if node.left.val.int() < 0 { + rt := c.table.sym(right_type).name + c.error('negative value cannot be compared with `$rt`', + node.left.pos) + } + } else if is_left_type_signed != is_right_type_signed { + // prevent e.g. `u16(-1) == int(-1)` which is false in C + if (is_right_type_signed && left_type in ast.int_promoted_type_idxs) || + (is_left_type_signed && right_type in ast.int_promoted_type_idxs) { + lt := c.table.sym(left_type).name + rt := c.table.sym(right_type).name + c.error('`$lt` cannot be compared with `$rt`', node.pos) + } + } } } .key_in, .not_in { diff --git a/vlib/v/checker/tests/infix_unsigned_and_signed_int_err.out b/vlib/v/checker/tests/infix_unsigned_and_signed_int_err.out index 1895d686ff..f75fea3617 100644 --- a/vlib/v/checker/tests/infix_unsigned_and_signed_int_err.out +++ b/vlib/v/checker/tests/infix_unsigned_and_signed_int_err.out @@ -6,8 +6,35 @@ vlib/v/checker/tests/infix_unsigned_and_signed_int_err.vv:2:14: error: unsigned 4 | } vlib/v/checker/tests/infix_unsigned_and_signed_int_err.vv:6:5: error: unsigned integer cannot be compared with negative value 4 | } - 5 | + 5 | 6 | if -1 > u32(1) { | ~~ 7 | println('unexpected') 8 | } +vlib/v/checker/tests/infix_unsigned_and_signed_int_err.vv:10:18: error: `byte` cannot be compared with negative value + 8 | } + 9 | // unsigned == literal + 10 | _ = byte(-1) == -1 // false! + | ~~ + 11 | _ = -1 == u16(-1) // false! + 12 | +vlib/v/checker/tests/infix_unsigned_and_signed_int_err.vv:11:6: error: negative value cannot be compared with `u16` + 9 | // unsigned == literal + 10 | _ = byte(-1) == -1 // false! + 11 | _ = -1 == u16(-1) // false! + | ~~ + 12 | + 13 | // unsigned == signed +vlib/v/checker/tests/infix_unsigned_and_signed_int_err.vv:14:14: error: `u16` cannot be compared with `int` + 12 | + 13 | // unsigned == signed + 14 | _ = u16(-1) == int(-1) + | ~~ + 15 | _ = int(-1) != byte(-1) + 16 | } +vlib/v/checker/tests/infix_unsigned_and_signed_int_err.vv:15:14: error: `int` cannot be compared with `byte` + 13 | // unsigned == signed + 14 | _ = u16(-1) == int(-1) + 15 | _ = int(-1) != byte(-1) + | ~~ + 16 | } diff --git a/vlib/v/checker/tests/infix_unsigned_and_signed_int_err.vv b/vlib/v/checker/tests/infix_unsigned_and_signed_int_err.vv index e3404eb33a..ed6fc040ed 100644 --- a/vlib/v/checker/tests/infix_unsigned_and_signed_int_err.vv +++ b/vlib/v/checker/tests/infix_unsigned_and_signed_int_err.vv @@ -6,4 +6,11 @@ fn main() { if -1 > u32(1) { println('unexpected') } + // unsigned == literal + _ = byte(-1) == -1 // false! + _ = -1 == u16(-1) // false! + + // unsigned == signed + _ = u16(-1) == int(-1) + _ = int(-1) != byte(-1) }