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

0 commit comments

Comments
 (0)