Skip to content

Commit 3b7b495

Browse files
committed
Fix more misery
1 parent b29b092 commit 3b7b495

3 files changed

Lines changed: 137 additions & 8 deletions

File tree

‎Zend/Optimizer/zend_inference.c‎

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2589,6 +2589,39 @@ static uint32_t prop_assign_op_result_type(uint32_t tmp, const zend_property_inf
25892589
return (tmp & prop_type) | scalar_coercion_type(tmp & ~prop_type & MAY_BE_ANY, prop_type, strict);
25902590
}
25912591

2592+
/* Whether ASSIGN_OBJ_OP may return the uncoerced result by having overloaded handler. */
2593+
static bool assign_obj_op_may_be_overloaded(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op *ssa_op, const zend_property_info *prop_info)
2594+
{
2595+
const zend_class_entry *ce;
2596+
bool is_instanceof;
2597+
2598+
if (!prop_info) {
2599+
return true;
2600+
}
2601+
2602+
if (opline->op1_type == IS_UNUSED) {
2603+
ce = op_array->scope;
2604+
is_instanceof = true;
2605+
} else if (ssa_op->op1_use >= 0) {
2606+
ce = ssa->var_info[ssa_op->op1_use].ce;
2607+
is_instanceof = ssa->var_info[ssa_op->op1_use].is_instanceof;
2608+
} else {
2609+
return true;
2610+
}
2611+
2612+
/* A child class may add __get() or hooks. */
2613+
if (!ce
2614+
|| (is_instanceof && !(ce->ce_flags & ZEND_ACC_FINAL))
2615+
|| !(ce->ce_flags & ZEND_ACC_LINKED)) {
2616+
return true;
2617+
}
2618+
2619+
return ce->__get /* Unset property */
2620+
|| ce->create_object /* Custom handlers */
2621+
|| prop_info->hooks
2622+
|| (prop_info->flags & ZEND_ACC_READONLY); /* Reinitialized in __clone() */
2623+
}
2624+
25922625
static bool result_may_be_separated(zend_ssa *ssa, zend_ssa_op *ssa_op)
25932626
{
25942627
int tmp_var = ssa_op->result_def;
@@ -2908,9 +2941,15 @@ static zend_always_inline zend_result _zend_update_type_info(
29082941
}
29092942
} else if (opline->opcode == ZEND_ASSIGN_OBJ_OP
29102943
|| opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP) {
2911-
/* The return value must also satisfy the property type */
2944+
/* The return value must also satisfy the property type,
2945+
* unless the overloaded handlers return the uncoerced result. */
29122946
t1 = zend_fetch_prop_type(script, prop_info, &ce);
2913-
tmp = prop_assign_op_result_type(tmp, prop_info, t1, (op_array->fn_flags & ZEND_ACC_STRICT_TYPES) != 0);
2947+
if (opline->opcode == ZEND_ASSIGN_OBJ_OP
2948+
&& assign_obj_op_may_be_overloaded(op_array, ssa, opline, ssa_op, prop_info)) {
2949+
tmp |= prop_assign_op_result_type(tmp, prop_info, t1, (op_array->fn_flags & ZEND_ACC_STRICT_TYPES) != 0);
2950+
} else {
2951+
tmp = prop_assign_op_result_type(tmp, prop_info, t1, (op_array->fn_flags & ZEND_ACC_STRICT_TYPES) != 0);
2952+
}
29142953
} else {
29152954
if (tmp & MAY_BE_REF) {
29162955
/* Typed reference may cause auto conversion */
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
--TEST--
2+
Compound assignment to a typed property through read_property/write_property returns the uncoerced result
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=-1
7+
--EXTENSIONS--
8+
opcache
9+
--FILE--
10+
<?php
11+
final class B {
12+
public bool $b = false;
13+
function __construct() { unset($this->b); }
14+
function __get($n) { return 100; }
15+
}
16+
17+
class H {
18+
public int $x {
19+
set => $value;
20+
}
21+
}
22+
23+
class P {
24+
public bool $b = false;
25+
}
26+
27+
class Q extends P {
28+
function __construct() { unset($this->b); }
29+
function __get($n) { return 100; }
30+
}
31+
32+
final class R {
33+
public readonly int $p;
34+
function __construct() { $this->p = 1; }
35+
function __clone() {
36+
$r = ($this->p += 1.5);
37+
var_dump([is_int($r), $r]);
38+
}
39+
}
40+
41+
function f(B $o) {
42+
$r = ($o->b += 1);
43+
return [is_bool($r), $r];
44+
}
45+
46+
function g(H $o) {
47+
$o->x = 1;
48+
$r = ($o->x += 1.5);
49+
return [is_int($r), $r];
50+
}
51+
52+
function h(P $o) {
53+
$r = ($o->b += 1);
54+
return [is_bool($r), $r];
55+
}
56+
57+
var_dump(f(new B));
58+
var_dump(g(new H));
59+
var_dump(h(new Q));
60+
clone new R;
61+
?>
62+
--EXPECTF--
63+
array(2) {
64+
[0]=>
65+
bool(false)
66+
[1]=>
67+
int(2)
68+
}
69+
70+
Deprecated: Implicit conversion from float 2.5 to int loses precision in %s on line %d
71+
array(2) {
72+
[0]=>
73+
bool(false)
74+
[1]=>
75+
float(2.5)
76+
}
77+
array(2) {
78+
[0]=>
79+
bool(false)
80+
[1]=>
81+
int(2)
82+
}
83+
84+
Deprecated: Implicit conversion from float 2.5 to int loses precision in %s on line %d
85+
array(2) {
86+
[0]=>
87+
bool(false)
88+
[1]=>
89+
float(2.5)
90+
}

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ known:
3838
; (lines=10, args=3, vars=3, tmps=%d, ssa_vars=12, no_loops)
3939
; (after dfa pass)
4040
; %s
41-
; return []
41+
; return [[packed, hash] array [long] of [long, double]]
4242
; #0.CV0($o) NOVAL [undef]
4343
; #1.CV1($x) NOVAL [undef]
4444
; #2.CV2($y) NOVAL [undef]
@@ -48,13 +48,13 @@ BB0:
4848
0000 #3.CV0($o) [object (instanceof C)] = RECV 1
4949
0001 #4.CV1($x) [double] = RECV 2
5050
0002 #5.CV2($y) [long] RANGE[MIN..MAX] = RECV 3
51-
0003 #7.T3 [] = ASSIGN_OBJ_OP (ADD) #3.CV0($o) [object (instanceof C)] -> #6.CV0($o) [object (instanceof C)] string("is")
51+
0003 #7.T3 [double] = ASSIGN_OBJ_OP (ADD) #3.CV0($o) [object (instanceof C)] -> #6.CV0($o) [object (instanceof C)] string("is")
5252
0004 OP_DATA #4.CV1($x) [double]
53-
0005 #8.T4 [] = INIT_ARRAY 2 (packed) #7.T3 [] NEXT
54-
0006 #10.T5 [double] = ASSIGN_OBJ_OP (BW_OR) #6.CV0($o) [object (instanceof C)] -> #9.CV0($o) NOVAL [object (instanceof C)] string("f")
53+
0005 #8.T4 [[packed, hash] array [long] of [double]] = INIT_ARRAY 2 (packed) #7.T3 [double] NEXT
54+
0006 #10.T5 [long, double] = ASSIGN_OBJ_OP (BW_OR) #6.CV0($o) [object (instanceof C)] -> #9.CV0($o) NOVAL [object (instanceof C)] string("f")
5555
0007 OP_DATA #5.CV2($y) [long] RANGE[MIN..MAX]
56-
0008 ADD_ARRAY_ELEMENT #10.T5 [double] NEXT #8.T4 [] -> #11.T4 []
57-
0009 RETURN #11.T4 []
56+
0008 ADD_ARRAY_ELEMENT #10.T5 [long, double] NEXT #8.T4 [[packed, hash] array [long] of [double]] -> #11.T4 [[packed, hash] array [long] of [long, double]]
57+
0009 RETURN #11.T4 [[packed, hash] array [long] of [long, double]]
5858

5959
unknown:
6060
; (lines=8, args=2, vars=3, tmps=%d, ssa_vars=11, no_loops)

0 commit comments

Comments
 (0)