Skip to content

Commit 0a14902

Browse files
committed
[Proposal] Warn about the loss of precision in hex literals
Emit an E_COMPILE_WARNING if these are seen. (See #4758) due to concerns about being unsafe to handle in user-space.) - E_COMPILE_WARNING is also emitted for "Octal escape sequence overflow" but it's been long enough to consider changing that. See GH-4758. - Making this proposal suddenly a ParseError in php 8.1 seems too soon. TODO: Warn for octal and binary literals as well. I expect this to behave the same on 32-bit and 64-bit builds (floats are 64 bits on both) For example, `0xffff_ffff_ffff_f400` overflows and becomes the **float** `0xffff_ffff_ffff_f000`. This will warn about that. Instead of using `0xffff_ffff_ffff_f400` with binary bitwise operands, most code should use the signed 64-bit int `~0xbff`.
1 parent 97f10fc commit 0a14902

File tree

3 files changed

+75
-2
lines changed

3 files changed

+75
-2
lines changed

Zend/tests/hex_overflow_number.phpt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Hex overflow in numeric literal warning
3+
--FILE--
4+
<?php
5+
var_dump(eval('return 0xffff_ffff_ffff_f800;'));
6+
var_dump(eval('return 0xffff_ffff_ffff_fa00;'));
7+
var_dump(eval('return 0xffff_ffff_ffff_fb00;'));
8+
var_dump(eval('return 0xffff_ffff_ffff_ffff;'));
9+
var_dump(eval('return 0x1_ffff_ffff_ffff_ffff;'));
10+
--EXPECTF--
11+
float(1.844674407370955E+19)
12+
13+
Warning: Saw imprecise float hex literal - the last 2 non-zero bits were truncated in %shex_overflow_number.php(3) : eval()'d code on line 1
14+
float(1.844674407370955E+19)
15+
16+
Warning: Saw imprecise float hex literal - the last 3 non-zero bits were truncated in %shex_overflow_number.php(4) : eval()'d code on line 1
17+
float(1.844674407370955E+19)
18+
19+
Warning: Saw imprecise float hex literal - the last 11 non-zero bits were truncated in %shex_overflow_number.php(5) : eval()'d code on line 1
20+
float(1.8446744073709552E+19)
21+
22+
Warning: Saw imprecise float hex literal - the last 12 non-zero bits were truncated in %shex_overflow_number.php(6) : eval()'d code on line 1
23+
float(3.6893488147419103E+19)

Zend/zend_language_scanner.l

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,45 @@ static void strip_underscores(char *str, size_t *len)
135135
*dest = '\0';
136136
}
137137

138+
/* Get the number of bits in the representation of a hex literal. Precondition: *str represents a non-zero number that overflowed an int. */
139+
static int bits_in_hex_representation(const char *str, size_t len)
140+
{
141+
size_t bits = len * 4;
142+
const char *end = str + len - 1;
143+
int last_digit;
144+
while (*end == '0') {
145+
bits -= 4;
146+
end--;
147+
ZEND_ASSERT(end >= str);
148+
}
149+
if ('0' <= *end && *end <= '9') {
150+
last_digit = *end - '0';
151+
} else if ('a' <= *end && *end <= 'f') {
152+
last_digit = *end - 'a' + 10;
153+
} else {
154+
ZEND_ASSERT('A' <= *end && *end <= 'F');
155+
last_digit = *end - 'A' + 10;
156+
}
157+
if ((last_digit & 1) == 0) {
158+
bits--;
159+
if ((last_digit & 2) == 0) {
160+
bits--;
161+
if ((last_digit & 4) == 0) {
162+
bits--;
163+
}
164+
}
165+
}
166+
if (*str <= '1') {
167+
bits -= 3;
168+
} else if (*str <= '2') {
169+
bits -= 2;
170+
} else if (*str <= '4') {
171+
bits -= 1;
172+
}
173+
return bits;
174+
}
175+
176+
138177
static size_t encoding_filter_script_to_internal(unsigned char **to, size_t *to_length, const unsigned char *from, size_t from_length)
139178
{
140179
const zend_encoding *internal_encoding = zend_multibyte_get_internal_encoding();
@@ -2066,9 +2105,13 @@ NEWLINE ("\r"|"\n"|"\r\n")
20662105
}
20672106
RETURN_TOKEN_WITH_VAL(T_LNUMBER);
20682107
} else {
2108+
size_t bits_in_representation = bits_in_hex_representation(hex, len);
20692109
ZVAL_DOUBLE(zendlval, zend_hex_strtod(hex, (const char **)&end));
20702110
/* errno isn't checked since we allow HUGE_VAL/INF overflow */
20712111
ZEND_ASSERT(end == hex + len);
2112+
if (bits_in_representation > 53) {
2113+
zend_error(E_COMPILE_WARNING, "Saw imprecise float hex literal - the last %zu non-zero bits were truncated", bits_in_representation - 53);
2114+
}
20722115
if (contains_underscores) {
20732116
efree(hex);
20742117
}

ext/standard/tests/strings/pack64.phpt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,14 @@ print_r(unpack("q", pack("q", 0x8000000000000002)));
3232
print_r(unpack("q", pack("q", -1)));
3333
print_r(unpack("q", pack("q", 0x8000000000000000)));
3434
?>
35-
--EXPECT--
35+
--EXPECTF--
36+
Warning: Saw imprecise float hex literal - the last 10 non-zero bits were truncated in %spack64.php on line 7
37+
38+
Warning: Saw imprecise float hex literal - the last 10 non-zero bits were truncated in %spack64.php on line 13
39+
40+
Warning: Saw imprecise float hex literal - the last 10 non-zero bits were truncated in %spack64.php on line 19
41+
42+
Warning: Saw imprecise float hex literal - the last 10 non-zero bits were truncated in %spack64.php on line 25
3643
Array
3744
(
3845
[1] => 281474976710654
@@ -112,4 +119,4 @@ Array
112119
Array
113120
(
114121
[1] => -9223372036854775808
115-
)
122+
)

0 commit comments

Comments
 (0)