checker: disallow confusing smaller unsigned type == signed

Otherwise C will evaluate `byte(-1) == -1` as false.
Also disallow unsigned == negative literal.

Fixes #13955.
pull/13967/head
Nick Treleaven 2022-04-07 18:15:48 +01:00
parent 6425000ce4
commit 3be4e19bf9
5 changed files with 62 additions and 2 deletions

View File

@ -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 == `^` {

View File

@ -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,

View File

@ -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 {

View File

@ -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 | }

View File

@ -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)
}