Skip to content

Commit 67b3b1a

Browse files
nikiciluuu1994
authored andcommitted
Implement explicit send-by-ref
Currently, in order to pass an argument by reference, the reference has to be declared at the definition-site, but not at the call-site: function byRef(&$ref) {} byRef($var); This change adds the ability to specify the by reference pass at *both* the definition-site and the call-site: function byRef(&$ref) {} byRef(&$var); Importantly, specifying a reference during the call will generate an error if the argument is not also declared by-reference at the declaration site: function byVal($val) {} byVal(&$val); // ERROR This makes it different from the call-time-pass-by-reference feature in PHP 4. If this feature is used, the reference has to be declared at both the call- *and* declaration-site. Note that it is still possible to not explicitly specify the use of by-reference passing at the call-site. As such, the following code remain legal: function byRef(&$ref) {} byRef($var);
1 parent 9771302 commit 67b3b1a

28 files changed

+2002
-618
lines changed

Zend/Optimizer/optimize_func_calls.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,24 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
340340
break;
341341
}
342342
break;
343+
case ZEND_SEND_EXPLICIT_REF:
344+
if (call_stack[call - 1].func) {
345+
if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
346+
opline->opcode = ZEND_SEND_REF;
347+
}
348+
}
349+
break;
350+
case ZEND_SEND_EXPLICIT_VAL:
351+
if (call_stack[call - 1].func) {
352+
if (!ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
353+
if (opline->op1_type & (IS_CONST|IS_TMP_VAR)) {
354+
opline->opcode = ZEND_SEND_VAL;
355+
} else {
356+
opline->opcode = ZEND_SEND_VAR;
357+
}
358+
}
359+
}
360+
break;
343361
case ZEND_SEND_UNPACK:
344362
case ZEND_SEND_USER:
345363
case ZEND_SEND_ARRAY:

Zend/Optimizer/sccp.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ static bool can_replace_op1(
238238
case ZEND_UNSET_DIM:
239239
case ZEND_UNSET_OBJ:
240240
case ZEND_SEND_REF:
241+
case ZEND_SEND_EXPLICIT_REF:
242+
case ZEND_SEND_EXPLICIT_REF_FUNC:
241243
case ZEND_SEND_VAR_EX:
242244
case ZEND_SEND_FUNC_ARG:
243245
case ZEND_SEND_UNPACK:

Zend/Optimizer/zend_call_graph.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ ZEND_API void zend_analyze_calls(zend_arena **arena, zend_script *script, uint32
139139
case ZEND_SEND_VAR_EX:
140140
case ZEND_SEND_FUNC_ARG:
141141
case ZEND_SEND_REF:
142+
case ZEND_SEND_EXPLICIT_VAL:
143+
case ZEND_SEND_EXPLICIT_REF:
144+
case ZEND_SEND_EXPLICIT_REF_FUNC:
142145
case ZEND_SEND_VAR_NO_REF:
143146
case ZEND_SEND_VAR_NO_REF_EX:
144147
case ZEND_SEND_USER:

Zend/Optimizer/zend_dfg.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ static zend_always_inline void _zend_dfg_add_use_def_op(const zend_op_array *op_
154154
case ZEND_BIND_INIT_STATIC_OR_JMP:
155155
case ZEND_SEND_VAR_NO_REF:
156156
case ZEND_SEND_VAR_NO_REF_EX:
157+
case ZEND_SEND_EXPLICIT_REF:
157158
case ZEND_SEND_VAR_EX:
158159
case ZEND_SEND_FUNC_ARG:
159160
case ZEND_SEND_REF:

Zend/Optimizer/zend_inference.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3268,6 +3268,7 @@ static zend_always_inline zend_result _zend_update_type_info(
32683268
}
32693269
break;
32703270
case ZEND_SEND_REF:
3271+
case ZEND_SEND_EXPLICIT_REF:
32713272
if (ssa_op->op1_def >= 0) {
32723273
tmp = MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
32733274
UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
@@ -3658,6 +3659,7 @@ static zend_always_inline zend_result _zend_update_type_info(
36583659
case ZEND_SEND_VAR_NO_REF:
36593660
case ZEND_SEND_VAR_NO_REF_EX:
36603661
case ZEND_SEND_REF:
3662+
case ZEND_SEND_EXPLICIT_REF:
36613663
case ZEND_ASSIGN_REF:
36623664
case ZEND_YIELD:
36633665
case ZEND_INIT_ARRAY:
@@ -4930,6 +4932,7 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
49304932
case ZEND_FETCH_DIM_IS:
49314933
case ZEND_FETCH_OBJ_IS:
49324934
case ZEND_SEND_REF:
4935+
case ZEND_SEND_EXPLICIT_REF:
49334936
case ZEND_UNSET_CV:
49344937
case ZEND_ISSET_ISEMPTY_CV:
49354938
case ZEND_MAKE_REF:
@@ -4958,6 +4961,7 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
49584961
case ZEND_SEND_VAR_NO_REF:
49594962
case ZEND_SEND_VAR_NO_REF_EX:
49604963
case ZEND_SEND_REF:
4964+
case ZEND_SEND_EXPLICIT_REF:
49614965
case ZEND_SEPARATE:
49624966
case ZEND_END_SILENCE:
49634967
case ZEND_MAKE_REF:

Zend/Optimizer/zend_ssa.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,7 @@ static zend_always_inline int _zend_ssa_rename_op(const zend_op_array *op_array,
685685
case ZEND_SEND_VAR_EX:
686686
case ZEND_SEND_FUNC_ARG:
687687
case ZEND_SEND_REF:
688+
case ZEND_SEND_EXPLICIT_REF:
688689
case ZEND_SEND_UNPACK:
689690
case ZEND_FE_RESET_RW:
690691
case ZEND_MAKE_REF:
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
__call() magic with explicit pass by ref
3+
--FILE--
4+
<?php
5+
6+
class Incrementor {
7+
public function inc(&$i) {
8+
$i++;
9+
}
10+
}
11+
12+
class ForwardCalls {
13+
private $object;
14+
public function __construct($object) {
15+
$this->object = $object;
16+
}
17+
public function __call(string $method, array $args) {
18+
return $this->object->$method(...$args);
19+
}
20+
}
21+
22+
$forward = new ForwardCalls(new Incrementor);
23+
24+
$i = 0;
25+
$forward->inc(&$i);
26+
var_dump($i);
27+
28+
$i = 0;
29+
$forward->inc($i);
30+
var_dump($i);
31+
32+
?>
33+
--EXPECT--
34+
int(1)
35+
int(0)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
--TEST--
2+
Basic tests for explicit pass-by-ref
3+
--FILE--
4+
<?php
5+
6+
// Works (by-ref arg)
7+
$a = 42;
8+
incArgRef(&$a);
9+
var_dump($a);
10+
11+
// Works (by-ref arg, deep reference)
12+
writeArgRef(&$b[0][1]);
13+
var_dump($b);
14+
15+
// Works (prefer-ref arg)
16+
$c = 42;
17+
$vars = ['d' => &$c];
18+
extract(&$vars, EXTR_REFS);
19+
$d++;
20+
var_dump($c);
21+
22+
// Works (by-ref arg, by-ref function)
23+
$e = 42;
24+
incArgRef(&returnsRef($e));
25+
var_dump($e);
26+
27+
// Fails (by-val arg)
28+
try {
29+
$f = 1;
30+
var_dump(incArgVal(&$f));
31+
} catch (Error $e) {
32+
echo $e->getMessage(), "\n";
33+
}
34+
35+
// Fails (by-ref arg, by-val function)
36+
try {
37+
$g = 42;
38+
incArgRef(&returnsVal($g));
39+
var_dump($g);
40+
} catch (Error $e) {
41+
echo $e->getMessage(), "\n";
42+
}
43+
44+
// Fails (by-val arg, by-ref function)
45+
try {
46+
$h = 1;
47+
var_dump(incArgVal(&returnsRef($h)));
48+
} catch (Error $e) {
49+
echo $e->getMessage(), "\n";
50+
}
51+
52+
// Functions intentionally declared at the end of the file,
53+
// to avoid the fbc being known during compilation
54+
function incArgVal($a) { return $a + 1; }
55+
function incArgRef(&$a) { $a++; }
56+
function writeArgRef(&$a) { $a = 43; }
57+
58+
function returnsVal($a) { return $a; }
59+
function &returnsRef(&$a) { return $a; }
60+
61+
?>
62+
--EXPECT--
63+
int(43)
64+
array(1) {
65+
[0]=>
66+
array(1) {
67+
[1]=>
68+
int(43)
69+
}
70+
}
71+
int(43)
72+
int(43)
73+
Cannot pass reference to by-value parameter 1
74+
Cannot pass result of by-value function by reference
75+
Cannot pass reference to by-value parameter 1
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
--TEST--
2+
call_user_func() with explicit pass by ref
3+
--FILE--
4+
<?php
5+
6+
// Avoid VM builtin.
7+
namespace Foo;
8+
9+
function inc(&$i) { $i++; }
10+
11+
$i = 0;
12+
call_user_func('Foo\inc', $i);
13+
var_dump($i);
14+
15+
$i = 0;
16+
call_user_func('Foo\inc', &$i);
17+
var_dump($i);
18+
19+
$i = 0;
20+
\call_user_func('Foo\inc', $i);
21+
var_dump($i);
22+
23+
$i = 0;
24+
\call_user_func('Foo\inc', &$i);
25+
var_dump($i);
26+
27+
?>
28+
--EXPECTF--
29+
Warning: Foo\inc(): Argument #1 ($i) must be passed by reference, value given in %s on line %d
30+
int(0)
31+
int(1)
32+
33+
Warning: Foo\inc(): Argument #1 ($i) must be passed by reference, value given in %s on line %d
34+
int(0)
35+
int(1)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
A compilation error is thrown is the ref/val mismatch is detected at compile-time
3+
--FILE--
4+
<?php
5+
6+
function argByVal($a) {}
7+
argByVal(&$b);
8+
9+
?>
10+
--EXPECTF--
11+
Fatal error: Cannot pass reference to by-value parameter 1 in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
A compilation error is thrown when passing result of optimized function by explicit ref
3+
--FILE--
4+
<?php
5+
6+
function argByRef(&$a) {}
7+
8+
argByRef(&strlen("foo"));
9+
10+
?>
11+
--EXPECTF--
12+
Fatal error: Cannot pass result of by-value function by reference in %s on line %d
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
--TEST--
2+
Test behavior with many args (slow-path)
3+
--FILE--
4+
<?php
5+
6+
$a = 42;
7+
argByRef(
8+
100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
9+
110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
10+
120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
11+
130, 131, 132, 133, 134, 135, 136, 137, 138, 139,
12+
&$a
13+
);
14+
var_dump($a);
15+
16+
try {
17+
$b = 42;
18+
argByVal(
19+
100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
20+
110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
21+
120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
22+
130, 131, 132, 133, 134, 135, 136, 137, 138, 139,
23+
&$a
24+
);
25+
var_dump($b);
26+
} catch (Error $e) {
27+
echo $e->getMessage(), "\n";
28+
}
29+
30+
function argByVal(
31+
$a00, $a01, $a02, $a03, $a04, $a05, $a06, $a07, $a08, $a09,
32+
$a10, $a11, $a12, $a13, $a14, $a15, $a16, $a17, $a18, $a19,
33+
$a20, $a21, $a22, $a23, $a24, $a25, $a26, $a27, $a28, $a29,
34+
$a30, $a31, $a32, $a33, $a34, $a35, $a36, $a37, $a38, $a39,
35+
$val
36+
) {
37+
return $val + 1;
38+
}
39+
function argByRef(
40+
$a00, $a01, $a02, $a03, $a04, $a05, $a06, $a07, $a08, $a09,
41+
$a10, $a11, $a12, $a13, $a14, $a15, $a16, $a17, $a18, $a19,
42+
$a20, $a21, $a22, $a23, $a24, $a25, $a26, $a27, $a28, $a29,
43+
$a30, $a31, $a32, $a33, $a34, $a35, $a36, $a37, $a38, $a39,
44+
&$ref
45+
) {
46+
$ref++;
47+
}
48+
49+
?>
50+
--EXPECT--
51+
int(43)
52+
Cannot pass reference to by-value parameter 41
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
Using explicit pass-by-ref with a non-variable results in a parse error
3+
--FILE--
4+
<?php
5+
6+
function byRef(&$a) {}
7+
byRef(&null);
8+
9+
?>
10+
--EXPECTF--
11+
Parse error: syntax error, unexpected token ")", expecting "->" or "?->" or "{" or "[" in %s on line %d

0 commit comments

Comments
 (0)