Skip to content

Commit 47ae17d

Browse files
committed
Optimizer: array keys whose conversion may throw
Float keys emit a deprecation and resource keys a warning, which an error handler can turn into an exception. Share one key mask in zend_may_throw_ex() so ASSIGN_DIM, INIT_ARRAY, ADD_ARRAY_ELEMENT, FETCH_DIM_W/FETCH_LIST_W, ARRAY_KEY_EXISTS, FETCH_DIM_IS and ISSET_ISEMPTY_DIM_OBJ agree. Fixes GH-16396 Closes GH-24653
1 parent a2011db commit 47ae17d

13 files changed

Lines changed: 328 additions & 7 deletions

‎NEWS‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ PHP NEWS
2222
. Fixed bug GH-25164 (Function JIT loop calling a user function runs once
2323
or never terminates). (Ilia Alshanetsky)
2424
. Fixed memory leak in JIT ASSIGN_DIM when the assignment fails. (Jorg Sowa)
25+
. Fixed bug GH-16396 (Failed to invoke user error handler during index
26+
conversion in JIT). (Jorg Sowa)
2527

2628
- Streams:
2729
. Fixed bug GH-22063 (Stream filter chain UAF on self-removal during

‎Zend/Optimizer/zend_inference.c‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4934,6 +4934,9 @@ ZEND_API zend_result zend_ssa_inference(zend_arena **arena, const zend_op_array
49344934
}
49354935
/* }}} */
49364936

4937+
/* Key types whose conversion to an array offset may emit a diagnostic */
4938+
#define MAY_BE_THROWING_KEY (MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)
4939+
49374940
ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, const zend_ssa *ssa, uint32_t t1, uint32_t t2)
49384941
{
49394942
if (opline->op1_type == IS_CV) {
@@ -5213,7 +5216,7 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
52135216
return 1;
52145217
}
52155218
return (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_TRUE|MAY_BE_FALSE|MAY_BE_STRING|MAY_BE_LONG|MAY_BE_DOUBLE)) || opline->op2_type == IS_UNUSED ||
5216-
(t2 & (MAY_BE_UNDEF|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
5219+
(t2 & (MAY_BE_UNDEF|MAY_BE_THROWING_KEY));
52175220
case ZEND_ASSIGN_OBJ:
52185221
if (t1 & (MAY_BE_ANY-MAY_BE_OBJECT)) {
52195222
return 1;
@@ -5265,9 +5268,9 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
52655268
case ZEND_ROPE_END:
52665269
return t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT);
52675270
case ZEND_INIT_ARRAY:
5268-
return (opline->op2_type != IS_UNUSED) && (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
5271+
return (opline->op2_type != IS_UNUSED) && (t2 & MAY_BE_THROWING_KEY);
52695272
case ZEND_ADD_ARRAY_ELEMENT:
5270-
return (opline->op2_type == IS_UNUSED) || (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
5273+
return (opline->op2_type == IS_UNUSED) || (t2 & MAY_BE_THROWING_KEY);
52715274
case ZEND_STRLEN:
52725275
return (t1 & MAY_BE_ANY) != MAY_BE_STRING;
52735276
case ZEND_COUNT:
@@ -5294,9 +5297,9 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
52945297
case ZEND_FETCH_IS:
52955298
return (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
52965299
case ZEND_ISSET_ISEMPTY_DIM_OBJ:
5297-
return (t1 & MAY_BE_OBJECT) || (t2 & (MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT));
5300+
return (t1 & MAY_BE_OBJECT) || (t2 & MAY_BE_THROWING_KEY);
52985301
case ZEND_FETCH_DIM_IS:
5299-
return (t1 & MAY_BE_OBJECT) || (t2 & (MAY_BE_DOUBLE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
5302+
return (t1 & MAY_BE_OBJECT) || (t2 & MAY_BE_THROWING_KEY);
53005303
case ZEND_CAST:
53015304
switch (opline->extended_value) {
53025305
case IS_LONG:
@@ -5317,7 +5320,7 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
53175320
if ((t2 & MAY_BE_ANY) != MAY_BE_ARRAY) {
53185321
return 1;
53195322
}
5320-
if ((t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
5323+
if (t1 & MAY_BE_THROWING_KEY) {
53215324
return 1;
53225325
}
53235326
return 0;
@@ -5342,7 +5345,7 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
53425345
if (t1 & (MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
53435346
return 1;
53445347
}
5345-
if (t2 & (MAY_BE_RESOURCE|MAY_BE_ARRAY|MAY_BE_OBJECT)) {
5348+
if (t2 & MAY_BE_THROWING_KEY) {
53465349
return 1;
53475350
}
53485351
if (opline->op2_type == IS_UNUSED) {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
JIT ADD_ARRAY_ELEMENT: float key conversion may throw
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.jit=function
7+
opcache.file_update_protection=0
8+
--EXTENSIONS--
9+
opcache
10+
--FILE--
11+
<?php
12+
set_error_handler(function ($errno, $errstr) {
13+
throw new Exception($errstr);
14+
});
15+
16+
function test(float $k) {
17+
var_dump([0, $k => 1]);
18+
}
19+
20+
try {
21+
test(1.5);
22+
} catch (Exception $e) {
23+
echo $e::class, ": ", $e->getMessage(), "\n";
24+
}
25+
?>
26+
--EXPECT--
27+
Exception: Implicit conversion from float 1.5 to int loses precision
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
JIT ARRAY_KEY_EXISTS: float key conversion may throw
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.jit=function
7+
opcache.file_update_protection=0
8+
--EXTENSIONS--
9+
opcache
10+
--FILE--
11+
<?php
12+
set_error_handler(function ($errno, $errstr) {
13+
throw new Exception($errstr);
14+
});
15+
16+
function test(float $k) {
17+
var_dump(array_key_exists($k, []));
18+
}
19+
20+
try {
21+
test(1.5);
22+
} catch (Exception $e) {
23+
echo $e::class, ": ", $e->getMessage(), "\n";
24+
}
25+
?>
26+
--EXPECT--
27+
Exception: Implicit conversion from float 1.5 to int loses precision
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
--TEST--
2+
JIT ASSIGN_DIM: float key conversion may throw
3+
--CREDITS--
4+
YuanchengJiang
5+
--INI--
6+
opcache.enable=1
7+
opcache.enable_cli=1
8+
opcache.jit=function
9+
opcache.file_update_protection=0
10+
--EXTENSIONS--
11+
opcache
12+
--FILE--
13+
<?php
14+
set_error_handler(function ($errno, $errstr) {
15+
throw new Exception($errstr);
16+
});
17+
18+
function test(float $k) {
19+
$a = [];
20+
var_dump($a[$k] = 1);
21+
}
22+
23+
try {
24+
test(1.5);
25+
} catch (Exception $e) {
26+
echo $e::class, ": ", $e->getMessage(), "\n";
27+
}
28+
?>
29+
--EXPECT--
30+
Exception: Implicit conversion from float 1.5 to int loses precision
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
--TEST--
2+
JIT FETCH_DIM_W: float key conversion may throw
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.jit=function
7+
opcache.file_update_protection=0
8+
--EXTENSIONS--
9+
opcache
10+
--FILE--
11+
<?php
12+
set_error_handler(function ($errno, $errstr) {
13+
throw new Exception($errstr);
14+
});
15+
16+
class C {
17+
function __destruct() {
18+
echo "destruct\n";
19+
}
20+
}
21+
22+
function g($x, &$y) {}
23+
24+
function test(float $k) {
25+
$c = new C;
26+
g($c, $a[$k]);
27+
}
28+
29+
try {
30+
test(1.5);
31+
} catch (Exception $e) {
32+
echo $e::class, ": ", $e->getMessage(), "\n";
33+
}
34+
?>
35+
--EXPECT--
36+
destruct
37+
Exception: Implicit conversion from float 1.5 to int loses precision
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
JIT INIT_ARRAY: float key conversion may throw
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.jit=function
7+
opcache.file_update_protection=0
8+
--EXTENSIONS--
9+
opcache
10+
--FILE--
11+
<?php
12+
set_error_handler(function ($errno, $errstr) {
13+
throw new Exception($errstr);
14+
});
15+
16+
function test(float $k) {
17+
var_dump([$k => 1]);
18+
}
19+
20+
try {
21+
test(1.5);
22+
} catch (Exception $e) {
23+
echo $e::class, ": ", $e->getMessage(), "\n";
24+
}
25+
?>
26+
--EXPECT--
27+
Exception: Implicit conversion from float 1.5 to int loses precision
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
JIT ISSET_ISEMPTY_DIM_OBJ: resource key conversion may throw
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.jit=function
7+
opcache.file_update_protection=0
8+
--EXTENSIONS--
9+
opcache
10+
--FILE--
11+
<?php
12+
set_error_handler(function ($errno, $errstr) {
13+
throw new Exception($errstr);
14+
});
15+
16+
function test() {
17+
$k = fopen("php://memory", "r");
18+
$a = [];
19+
var_dump(isset($a[$k]));
20+
}
21+
22+
try {
23+
test();
24+
} catch (Exception $e) {
25+
echo $e::class, ": ", $e->getMessage(), "\n";
26+
}
27+
?>
28+
--EXPECTF--
29+
Exception: Resource ID#%d used as offset, casting to integer (%d)

‎ext/opcache/tests/opt/dce_018.phpt‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
--TEST--
2+
DCE must not remove ASSIGN_DIM with a float key
3+
--CREDITS--
4+
YuanchengJiang
5+
--INI--
6+
opcache.enable=1
7+
opcache.enable_cli=1
8+
opcache.optimization_level=-1
9+
opcache.file_update_protection=0
10+
--EXTENSIONS--
11+
opcache
12+
--FILE--
13+
<?php
14+
set_error_handler(function ($errno, $errstr) {
15+
throw new Exception($errstr);
16+
});
17+
18+
function test(float $k) {
19+
$a[$k] = 1;
20+
}
21+
22+
try {
23+
test(1.5);
24+
} catch (Exception $e) {
25+
echo $e::class, ": ", $e->getMessage(), "\n";
26+
}
27+
?>
28+
--EXPECT--
29+
Exception: Implicit conversion from float 1.5 to int loses precision

‎ext/opcache/tests/opt/dce_019.phpt‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
DCE must not remove INIT_ARRAY with a float key
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
opcache.file_update_protection=0
8+
--EXTENSIONS--
9+
opcache
10+
--FILE--
11+
<?php
12+
set_error_handler(function ($errno, $errstr) {
13+
throw new Exception($errstr);
14+
});
15+
16+
function test(float $k) {
17+
$a = [$k => 1];
18+
}
19+
20+
try {
21+
test(1.5);
22+
} catch (Exception $e) {
23+
echo $e::class, ": ", $e->getMessage(), "\n";
24+
}
25+
?>
26+
--EXPECT--
27+
Exception: Implicit conversion from float 1.5 to int loses precision

0 commit comments

Comments
 (0)