Implement named parameters

From an engine perspective, named parameters mainly add three
concepts:

 * The SEND_* opcodes now accept a CONST op2, which is the
   argument name. For now, it is looked up by linear scan and
   runtime cached.
 * This may leave UNDEF arguments on the stack. To avoid having
   to deal with them in other places, a CHECK_UNDEF_ARGS opcode
   is used to either replace them with defaults, or error.
 * For variadic functions, EX(extra_named_params) are collected
   and need to be freed based on ZEND_CALL_HAS_EXTRA_NAMED_PARAMS.

RFC: https://wiki.php.net/rfc/named_params

Closes GH-5357.
This commit is contained in:
Nikita Popov 2020-04-06 12:46:52 +02:00
parent 9a71d47d73
commit d92229d8c7
79 changed files with 5974 additions and 2525 deletions

View File

@ -640,6 +640,8 @@ PHP 8.0 UPGRADE NOTES
RFC: https://wiki.php.net/rfc/inheritance_private_methods
. Added support for nullsafe operator (`?->`).
RFC: https://wiki.php.net/rfc/nullsafe_operator
. Added support for named arguments.
RFC: https://wiki.php.net/rfc/named_params
- Date:
. Added DateTime::createFromInterface() and

View File

@ -18,4 +18,4 @@ try {
?>
--EXPECT--
Exception: Cannot unpack Traversable with non-integer keys
Exception: Keys must be of type int|string during argument unpacking

View File

@ -7,11 +7,6 @@ set_error_handler(function($errno, $errstr) {
var_dump($errstr);
});
try {
var_dump(...[1, 2, "foo" => 3, 4]);
} catch (Error $ex) {
var_dump($ex->getMessage());
}
try {
var_dump(...new ArrayIterator([1, 2, "foo" => 3, 4]));
} catch (Error $ex) {
@ -20,5 +15,4 @@ try {
?>
--EXPECT--
string(36) "Cannot unpack array with string keys"
string(42) "Cannot unpack Traversable with string keys"
string(68) "Cannot use positional argument after named argument during unpacking"

View File

@ -60,6 +60,7 @@ assert(0 && ($a = function &(array &$a, ?X $b = null) use ($c,&$d) : ?X {
$x = C::${$z . "_1"};
$x?->y;
$x?->y();
foo(bar: $x);
}
}
}));
@ -202,6 +203,7 @@ Warning: assert(): assert(0 && ($a = function &(array &$a, ?X $b = null) use($c,
$x = C::${$z . '_1'};
$x?->y;
$x?->y();
foo(bar: $x);
}
}

View File

@ -8,4 +8,4 @@ $foo = 'bar';
var_dump(new namespace::$foo);
?>
--EXPECTF--
Parse error: syntax error, unexpected token "namespace" in %s on line %d
Parse error: syntax error, unexpected token "namespace", expecting ":" in %s on line %d

View File

@ -0,0 +1,37 @@
--TEST--
Check that __call() and __callStatic() work with named parameters
--FILE--
<?php
class Test {
public function __call(string $method, array $args) {
$this->{'_'.$method}(...$args);
}
public static function __callStatic(string $method, array $args) {
(new static)->{'_'.$method}(...$args);
}
private function _method($a = 'a', $b = 'b') {
echo "a: $a, b: $b\n";
}
}
$obj = new class { public function __toString() { return "STR"; } };
$test = new Test;
$test->method(a: 'A', b: 'B');
$test->method(b: 'B');
$test->method(b: $obj);
Test::method(a: 'A', b: 'B');
Test::method(b: 'B');
Test::method(b: $obj);
?>
--EXPECT--
a: A, b: B
a: a, b: B
a: a, b: STR
a: A, b: B
a: a, b: B
a: a, b: STR

View File

@ -0,0 +1,64 @@
--TEST--
Check that __invoke() works with named parameters
--FILE--
<?php
class Test {
public function __invoke($a = 'a', $b = 'b') {
echo "a: $a, b: $b\n";
}
}
class Test2 {
public function __invoke($a = 'a', $b = 'b', ...$rest) {
echo "a: $a, b: $b\n";
var_dump($rest);
}
}
$test = new Test;
$test(b: 'B', a: 'A');
$test(b: 'B');
try {
$test(b: 'B', c: 'C');
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
echo "\n";
$test2 = new Test2;
$test2(b: 'B', a: 'A', c: 'C');
$test2(b: 'B', c: 'C');
echo "\n";
$test3 = function($a = 'a', $b = 'b') {
echo "a: $a, b: $b\n";
};
$test3(b: 'B', a: 'A');
$test3(b: 'B');
try {
$test3(b: 'B', c: 'C');
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
a: A, b: B
a: a, b: B
Unknown named parameter $c
a: A, b: B
array(1) {
["c"]=>
string(1) "C"
}
a: a, b: B
array(1) {
["c"]=>
string(1) "C"
}
a: A, b: B
a: a, b: B
Unknown named parameter $c

View File

@ -0,0 +1,50 @@
--TEST--
Named params in attributes
--FILE--
<?php
@@Attribute
class MyAttribute {
public function __construct(
public $a = 'a',
public $b = 'b',
public $c = 'c',
) {}
}
@@MyAttribute('A', c: 'C')
class Test1 {}
@@MyAttribute('A', a: 'C')
class Test2 {}
$attr = (new ReflectionClass(Test1::class))->getAttributes()[0];
var_dump($attr->getName());
var_dump($attr->getArguments());
var_dump($attr->newInstance());
$attr = (new ReflectionClass(Test2::class))->getAttributes()[0];
try {
var_dump($attr->newInstance());
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
string(11) "MyAttribute"
array(2) {
[0]=>
string(1) "A"
["c"]=>
string(1) "C"
}
object(MyAttribute)#1 (3) {
["a"]=>
string(1) "A"
["b"]=>
string(1) "b"
["c"]=>
string(1) "C"
}
Named parameter $a overwrites previous argument

View File

@ -0,0 +1,11 @@
--TEST--
Named params in attributes: Duplicate named parameter error
--FILE--
<?php
@@MyAttribute(a: 'A', a: 'A')
class Test {}
?>
--EXPECTF--
Fatal error: Duplicate named parameter $a in %s on line %d

View File

@ -0,0 +1,21 @@
--TEST--
Named flags parameter for Attribute attribute
--FILE--
<?php
@@Attribute(flags: Attribute::TARGET_CLASS)
class MyAttribute {
}
@@MyAttribute
function test() {}
(new ReflectionFunction('test'))->getAttributes()[0]->newInstance();
?>
--EXPECTF--
Fatal error: Uncaught Error: Attribute "MyAttribute" cannot target function (allowed targets: class) in %s:%d
Stack trace:
#0 %s(%d): ReflectionAttribute->newInstance()
#1 {main}
thrown in %s on line %d

View File

@ -0,0 +1,22 @@
--TEST--
Named flags parameter for Attribute attribute (incorrect parameter name)
--FILE--
<?php
// TODO: This should error at compile-time.
@@Attribute(not_flags: Attribute::TARGET_CLASS)
class MyAttribute {
}
@@MyAttribute
function test() {}
(new ReflectionFunction('test'))->getAttributes()[0]->newInstance();
?>
--EXPECTF--
Fatal error: Uncaught Error: Attribute "MyAttribute" cannot target function (allowed targets: class) in %s:%d
Stack trace:
#0 %s(%d): ReflectionAttribute->newInstance()
#1 {main}
thrown in %s on line %d

View File

@ -0,0 +1,14 @@
--TEST--
Named params in attributes: Positional after named error
--FILE--
<?php
@@Attribute
class MyAttribute { }
@@MyAttribute(a: 'A', 'B')
class Test {}
?>
--EXPECTF--
Fatal error: Cannot use positional argument after named argument in %s on line %d

View File

@ -0,0 +1,69 @@
--TEST--
Extra named params in backtraces
--FILE--
<?php
function test($a, ...$rest) {
var_dump(debug_backtrace());
debug_print_backtrace();
throw new Exception("Test");
}
try {
test(1, 2, x: 3, y: 4);
} catch (Exception $e) {
var_dump($e->getTrace());
echo $e, "\n";
}
?>
--EXPECTF--
array(1) {
[0]=>
array(4) {
["file"]=>
string(%d) "%s"
["line"]=>
int(10)
["function"]=>
string(4) "test"
["args"]=>
array(4) {
[0]=>
int(1)
[1]=>
int(2)
["x"]=>
int(3)
["y"]=>
int(4)
}
}
}
#0 test(1, 2, x: 3, y: 4) called at [%s:10]
array(1) {
[0]=>
array(4) {
["file"]=>
string(%d) "%s"
["line"]=>
int(10)
["function"]=>
string(4) "test"
["args"]=>
array(4) {
[0]=>
int(1)
[1]=>
int(2)
["x"]=>
int(3)
["y"]=>
int(4)
}
}
}
Exception: Test in %s:%d
Stack trace:
#0 %s(%d): test(1, 2, x: 3, y: 4)
#1 {main}

View File

@ -0,0 +1,106 @@
--TEST--
Basic test
--FILE--
<?php
function test($a, $b, $c = "c", $d = "d", $e = "e") {
echo "a=$a, b=$b, c=$c, d=$d, e=$e\n";
}
function test3(&$a, &$b, &$c = "c", &$d = "d", &$e = "e") {
echo "a=$a, b=$b, c=$c, d=$d, e=$e\n";
}
function &id($x) {
return $x;
}
$a = "A"; $b = "B"; $c = "C"; $d = "D"; $e = "E";
echo "SEND_VAL:\n";
test("A", "B", "C", d: "D", e: "E");
test("A", "B", "C", e: "E", d: "D");
test(e: "E", a: "A", d: "D", b: "B", c: "C");
test("A", "B", "C", e: "E");
echo "SEND_VAL_EX:\n";
test2("A", "B", "C", d: "D", e: "E");
test2("A", "B", "C", e: "E", d: "D");
test2(e: "E", a: "A", d: "D", b: "B", c: "C");
test2("A", "B", "C", e: "E");
echo "SEND_VAR:\n";
test($a, $b, $c, d: $d, e: $e);
test($a, $b, $c, e: $e, d: $d);
test(e: $e, a: $a, d: $d, b: $b, c: $c);
test(a: $a, b: $b, c: $c, e: $e);
echo "SEND_VAR_EX:\n";
test2($a, $b, $c, d: $d, e: $e);
test2($a, $b, $c, e: $e, d: $d);
test2(e: $e, a: $a, d: $d, b: $b, c: $c);
test2(a: $a, b: $b, c: $c, e: $e);
echo "SEND_VAR_NO_REF:\n";
test3(id("A"), id("B"), id("C"), d: id("D"), e: id("E"));
test3(id("A"), id("B"), id("C"), e: id("E"), d: id("D"));
test3(e: id("E"), a: id("A"), d: id("D"), b: id("B"), c: id("C"));
test3(id("A"), id("B"), id("C"), e: id("E"));
echo "SEND_VAR_NO_REF_EX:\n";
test4(id("A"), id("B"), id("C"), d: id("D"), e: id("E"));
test4(id("A"), id("B"), id("C"), e: id("E"), d: id("D"));
test4(e: id("E"), a: id("A"), d: id("D"), b: id("B"), c: id("C"));
test4(id("A"), id("B"), id("C"), e: id("E"));
echo "SEND_REF:\n";
test3($a, $b, $c, d: $d, e: $e);
test3($a, $b, $c, e: $e, d: $d);
test3(e: $e, a: $a, d: $d, b: $b, c: $c);
test3(a: $a, b: $b, c: $c, e: $e);
function test2($a, $b, $c = "c", $d = "d", $e = "e") {
echo "a=$a, b=$b, c=$c, d=$d, e=$e\n";
}
function test4(&$a, &$b, &$c = "c", &$d = "d", &$e = "e") {
echo "a=$a, b=$b, c=$c, d=$d, e=$e\n";
}
?>
--EXPECT--
SEND_VAL:
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=d, e=E
SEND_VAL_EX:
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=d, e=E
SEND_VAR:
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=d, e=E
SEND_VAR_EX:
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=d, e=E
SEND_VAR_NO_REF:
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=d, e=E
SEND_VAR_NO_REF_EX:
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=d, e=E
SEND_REF:
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=D, e=E
a=A, b=B, c=C, d=d, e=E

View File

@ -0,0 +1,105 @@
--TEST--
call_user_func() and friends with named parameters
--FILE--
<?php
$test = function($a = 'a', $b = 'b', $c = 'c') {
echo "a = $a, b = $b, c = $c\n";
};
$test_variadic = function(...$args) {
var_dump($args);
};
$test_ref = function(&$ref) {
$ref++;
};
class Test {
public function __construct($a = 'a', $b = 'b', $c = 'c') {
if (func_num_args() != 0) {
echo "a = $a, b = $b, c = $c\n";
}
}
public function method($a = 'a', $b = 'b', $c = 'c') {
echo "a = $a, b = $b, c = $c\n";
}
}
call_user_func($test, 'A', c: 'C');
call_user_func($test, c: 'C', a: 'A');
call_user_func($test, c: 'C');
call_user_func($test_variadic, 'A', c: 'C');
call_user_func($test_ref, ref: null);
var_dump(call_user_func('call_user_func', $test, c: 'D'));
try {
var_dump(call_user_func('array_slice', [1, 2, 3, 4, 5], length: 2));
} catch (ArgumentCountError $e) {
echo $e->getMessage(), "\n";
}
try {
var_dump(call_user_func('array_slice', [1, 2, 3, 4, 'x' => 5], 3, preserve_keys: true));
} catch (ArgumentCountError $e) {
echo $e->getMessage(), "\n";
}
echo "\n";
$test->__invoke('A', c: 'C');
$test_variadic->__invoke('A', c: 'C');
$test->call(new class {}, 'A', c: 'C');
$test_variadic->call(new class {}, 'A', c: 'C');
echo "\n";
$rf = new ReflectionFunction($test);
$rf->invoke('A', c: 'C');
$rf->invokeArgs(['A', 'c' => 'C']);
$rm = new ReflectionMethod(Test::class, 'method');
$rm->invoke(new Test, 'A', c: 'C');
$rm->invokeArgs(new Test, ['A', 'c' => 'C']);
$rc = new ReflectionClass(Test::class);
$rc->newInstance('A', c: 'C');
$rc->newInstanceArgs(['A', 'c' => 'C']);
?>
--EXPECTF--
a = A, b = b, c = C
a = A, b = b, c = C
a = a, b = b, c = C
array(2) {
[0]=>
string(1) "A"
["c"]=>
string(1) "C"
}
Warning: {closure}(): Argument #1 ($ref) must be passed by reference, value given in %s on line %d
a = a, b = b, c = D
NULL
array_slice(): Argument #2 ($offset) not passed
array(2) {
[3]=>
int(4)
["x"]=>
int(5)
}
a = A, b = b, c = C
array(2) {
[0]=>
string(1) "A"
["c"]=>
string(1) "C"
}
a = A, b = b, c = C
array(2) {
[0]=>
string(1) "A"
["c"]=>
string(1) "C"
}
a = A, b = b, c = C
a = A, b = b, c = C
a = A, b = b, c = C
a = A, b = b, c = C
a = A, b = b, c = C
a = A, b = b, c = C

View File

@ -0,0 +1,72 @@
--TEST--
Behavior of call_user_func_array() with named parameters
--FILE--
<?php
namespace {
$test = function($a = 'a', $b = 'b', $c = 'c') {
echo "a = $a, b = $b, c = $c\n";
};
$test_variadic = function(...$args) {
var_dump($args);
};
call_user_func_array($test, ['A', 'B']);
call_user_func_array($test, [1 => 'A', 0 => 'B']);
call_user_func_array($test, ['A', 'c' => 'C']);
call_user_func_array($test_variadic, ['A', 'c' => 'C']);
try {
call_user_func_array($test, ['d' => 'D']);
} catch (\Error $e) {
echo $e->getMessage(), "\n";
}
try {
call_user_func_array($test, ['c' => 'C', 'A']);
} catch (\Error $e) {
echo $e->getMessage(), "\n";
}
echo "\n";
}
namespace Foo {
call_user_func_array($test, ['A', 'B']);
call_user_func_array($test, [1 => 'A', 0 => 'B']);
call_user_func_array($test, ['A', 'c' => 'C']);
call_user_func_array($test_variadic, ['A', 'c' => 'C']);
try {
call_user_func_array($test, ['d' => 'D']);
} catch (\Error $e) {
echo $e->getMessage(), "\n";
}
try {
call_user_func_array($test, ['c' => 'C', 'A']);
} catch (\Error $e) {
echo $e->getMessage(), "\n";
}
}
?>
--EXPECT--
a = A, b = B, c = c
a = A, b = B, c = c
a = A, b = b, c = C
array(2) {
[0]=>
string(1) "A"
["c"]=>
string(1) "C"
}
Unknown named parameter $d
Cannot use positional argument after named argument
a = A, b = B, c = c
a = A, b = B, c = c
a = A, b = b, c = C
array(2) {
[0]=>
string(1) "A"
["c"]=>
string(1) "C"
}
Unknown named parameter $d
Cannot use positional argument after named argument

View File

@ -0,0 +1,35 @@
--TEST--
Skipping over default parameters
--FILE--
<?php
function test1($a = 'a', $b = 'b') {
echo "a: $a, b: $b\n";
}
function test2($a = SOME_CONST, $b = 'b') {
echo "a: $a, b: $b\n";
}
function test3($a = SOME_OTHER_CONST, $b = 'b') {
echo "a: $a, b: $b\n";
}
test1(b: 'B');
define('SOME_CONST', 'X');
test2(b: 'B');
// NB: We also want to test the stack trace.
test3(b: 'B');
?>
--EXPECTF--
a: a, b: B
a: X, b: B
Fatal error: Uncaught Error: Undefined constant "SOME_OTHER_CONST" in %s:%d
Stack trace:
#0 %s(%d): test3(NULL, 'B')
#1 {main}
thrown in %s on line %d

View File

@ -0,0 +1,23 @@
--TEST--
Duplicate param
--FILE--
<?php
function test($a) {}
try {
test(a: 1, a: 2);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
test(1, a: 2);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
Named parameter $a overwrites previous argument
Named parameter $a overwrites previous argument

View File

@ -0,0 +1,29 @@
--TEST--
Behavior of func_get_args() and friends with named parameters
--FILE--
<?php
function test($a, $b = 'b', $c = 'c') {
var_dump(func_num_args());
var_dump(func_get_args());
var_dump(func_get_arg(0));
var_dump(func_get_arg(1));
var_dump(func_get_arg(2));
}
test(c: 'C', a: 'A');
?>
--EXPECT--
int(3)
array(3) {
[0]=>
string(1) "A"
[1]=>
string(1) "b"
[2]=>
string(1) "C"
}
string(1) "A"
string(1) "b"
string(1) "C"

View File

@ -0,0 +1,39 @@
--TEST--
Named params on internal functions
--FILE--
<?php
var_dump(array_slice(arg: [1, 2, 3, 4, 5], offset: 2, length: 2));
var_dump(array_slice(length: 2, offset: 2, arg: [1, 2, 3, 4, 5]));
var_dump(array_slice(arg: ['a' => 0, 'b' => 1], offset: 1, preserve_keys: true));
var_dump(array_slice(['a' => 0, 'b' => 1], preserve_keys: true, offset: 1));
var_dump(str_pad("foo", 6, pad_type: STR_PAD_LEFT));
// Named params work with specialized functions.
var_dump(strlen(string: 'foo'));
?>
--EXPECT--
array(2) {
[0]=>
int(3)
[1]=>
int(4)
}
array(2) {
[0]=>
int(3)
[1]=>
int(4)
}
array(1) {
["b"]=>
int(1)
}
array(1) {
["b"]=>
int(1)
}
string(6) " foo"
int(3)

View File

@ -0,0 +1,21 @@
--TEST--
Named params on internal functions: Variadic functions that don't support extra named args
--FILE--
<?php
try {
array_merge([1, 2], a: [3, 4]);
} catch (ArgumentCountError $e) {
echo $e->getMessage(), "\n";
}
try {
array_diff_key([1, 2], [3, 4], a: [5, 6]);
} catch (ArgumentCountError $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
array_merge() does not accept unknown named parameters
array_diff_key() does not accept unknown named parameters

View File

@ -0,0 +1,38 @@
--TEST--
Required parameter not passed
--FILE--
<?php
function test($a, $b, $c, $d) {
}
try {
test(a: 'a', d: 'd');
} catch (ArgumentCountError $e) {
echo $e->getMessage(), "\n";
}
try {
array_keys(strict: true);
} catch (ArgumentCountError $e) {
echo $e->getMessage(), "\n";
}
try {
array_keys([], strict: true);
} catch (ArgumentCountError $e) {
echo $e->getMessage(), "\n";
}
// This works fine, as search_value is explicitly specified.
var_dump(array_keys([41, 42], search_value: 42, strict: true));
?>
--EXPECT--
test(): Argument #2 ($b) not passed
array_keys(): Argument #1 ($arg) not passed
array_keys(): Argument #2 ($search_value) must be passed explicitly, because the default value is not known
array(1) {
[0]=>
int(1)
}

View File

@ -0,0 +1,10 @@
--TEST--
Cannot used positional parameter after named parameter
--FILE--
<?php
test(a: 1, 2);
?>
--EXPECTF--
Fatal error: Cannot use positional argument after named argument in %s on line %d

View File

@ -0,0 +1,39 @@
--TEST--
Check that reference detection works properly
--FILE--
<?php
$v00 = $v01 = $v32 = $v33 = 0;
test(p32: $v32, p33: $v33, p00: $v00, p01: $v01);
echo "$v00 $v01 $v32 $v33\n";
$v = [0 => 0, 1 => 0, 32 => 0, 33 => 0];
test(p32: $v[32], p33: $v[33], p00: $v[0], p01: $v[1]);
echo "$v[0] $v[1] $v[32] $v[33]\n";
function test(
&$p00 = null, $p01 = null, &$p02 = null, $p03 = null, &$p04 = null, $p05 = null,
&$p06 = null, $p07 = null, &$p08 = null, $p09 = null, &$p10 = null, $p11 = null,
&$p12 = null, $p13 = null, &$p14 = null, $p15 = null, &$p16 = null, $p17 = null,
&$p18 = null, $p19 = null, &$p20 = null, $p21 = null, &$p22 = null, $p23 = null,
&$p24 = null, $p25 = null, &$p26 = null, $p27 = null, &$p28 = null, $p29 = null,
&$p30 = null, $p31 = null, &$p32 = null, $p33 = null, &$p34 = null, $p35 = null
) {
$p00++;
$p32++;
}
$v00 = $v01 = $v32 = $v33 = 0;
test(p32: $v32, p33: $v33, p00: $v00, p01: $v01);
echo "$v00 $v01 $v32 $v33\n";
$v = [0 => 0, 1 => 0, 32 => 0, 33 => 0];
test(p32: $v[32], p33: $v[33], p00: $v[0], p01: $v[1]);
echo "$v[0] $v[1] $v[32] $v[33]\n";
?>
--EXPECT--
1 0 1 0
1 0 1 0
1 0 1 0
1 0 1 0

View File

@ -0,0 +1,15 @@
--TEST--
Reserved keywords can be used with named parameters
--FILE--
<?php
function test($array) {
var_dump($array);
}
test(array: []);
?>
--EXPECT--
array(0) {
}

View File

@ -0,0 +1,26 @@
--TEST--
Unknown named parameter
--FILE--
<?php
function test($a) {
}
function test2(...$a) {
}
try {
test(b: 42);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
test2(a: 42);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
Unknown named parameter $b

View File

@ -0,0 +1,85 @@
--TEST--
Unpacking named parameters
--FILE--
<?php
function test($a, $b, $c) {
echo "a = $a, b = $b, c = $c\n";
}
function test2($a = null, &$b = null) {
$b++;
}
test(...['a' => 'a', 'b' => 'b', 'c' => 'c']);
test(...['c' => 'c', 'b' => 'b', 'a' => 'a']);
test(...['a', 'b' => 'b', 'c' => 'c']);
try {
test(...['a', 'b' => 'b', 'c']);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
test(...['a', 'a' => 'a']);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$ary = ['b' => 0];
$ary2 = $ary;
test2(...$ary);
var_dump($ary, $ary2);
test(...new ArrayIterator(['a' => 'a', 'b' => 'b', 'c' => 'c']));
test(...new ArrayIterator(['c' => 'c', 'b' => 'b', 'a' => 'a']));
test(...new ArrayIterator(['a', 'b' => 'b', 'c' => 'c']));
try {
test(...new ArrayIterator(['a', 'b' => 'b', 'c']));
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
try {
test(...new ArrayIterator(['a', 'a' => 'a']));
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
$ary = ['b' => 0];
$ary2 = $ary;
test2(...new ArrayIterator($ary));
var_dump($ary, $ary2);
?>
--EXPECTF--
a = a, b = b, c = c
a = a, b = b, c = c
a = a, b = b, c = c
Cannot use positional argument after named argument during unpacking
Named parameter $a overwrites previous argument
array(1) {
["b"]=>
int(1)
}
array(1) {
["b"]=>
int(0)
}
a = a, b = b, c = c
a = a, b = b, c = c
a = a, b = b, c = c
Cannot use positional argument after named argument during unpacking
Named parameter $a overwrites previous argument
Warning: Cannot pass by-reference argument 2 of test2() by unpacking a Traversable, passing by-value instead in %s on line %d
array(1) {
["b"]=>
int(0)
}
array(1) {
["b"]=>
int(0)
}

View File

@ -0,0 +1,10 @@
--TEST--
Mixing unpacking and named params (1)
--FILE--
<?php
test(...[], a: 42);
?>
--EXPECTF--
Fatal error: Cannot combine named arguments and argument unpacking in %s on line %d

View File

@ -0,0 +1,10 @@
--TEST--
Mixing unpacking and named params (2)
--FILE--
<?php
test(a: 42, ...[]);
?>
--EXPECTF--
Fatal error: Cannot combine named arguments and argument unpacking in %s on line %d

View File

@ -0,0 +1,66 @@
--TEST--
Additional named params are collect into variadics
--FILE--
<?php
function test($a, string ...$extra) {
var_dump($a);
var_dump($extra);
// Extra named parameters do not contribute toward func_num_args() and func_get_args().
var_dump(func_num_args());
var_dump(func_get_args());
}
function test2(&...$refs) {
foreach ($refs as &$ref) $ref++;
}
test(b: 'b', a: 'a', c: 'c', extra: 'extra');
echo "\n";
test('a', 'b', 'c', d: 'd');
echo "\n";
$x = 0;
$y = 0;
test2(x: $x, y: $y);
var_dump($x, $y);
?>
--EXPECT--
string(1) "a"
array(3) {
["b"]=>
string(1) "b"
["c"]=>
string(1) "c"
["extra"]=>
string(5) "extra"
}
int(1)
array(1) {
[0]=>
string(1) "a"
}
string(1) "a"
array(3) {
[0]=>
string(1) "b"
[1]=>
string(1) "c"
["d"]=>
string(1) "d"
}
int(3)
array(3) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(1) "c"
}
int(1)
int(1)

View File

@ -234,6 +234,9 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_error(int error_code,
case ZPP_ERROR_WRONG_STRING_OR_CLASS_OR_NULL:
zend_wrong_parameter_string_or_class_or_null_error(num, name, arg);
break;
case ZPP_ERROR_UNEXPECTED_EXTRA_NAMED:
zend_unexpected_extra_named_error();
break;
default:
ZEND_ASSERT(error_code != ZPP_ERROR_OK);
}
@ -306,6 +309,14 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_callback_error(uint32_t num, ch
}
/* }}} */
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_unexpected_extra_named_error(void)
{
const char *space;
const char *class_name = get_active_class_name(&space);
zend_argument_count_error("%s%s%s() does not accept unknown named parameters",
class_name, space, get_active_function_name());
}
static ZEND_COLD void ZEND_FASTCALL zend_argument_error_variadic(zend_class_entry *error_ce, uint32_t arg_num, const char *format, va_list va) /* {{{ */
{
const char *space;
@ -950,6 +961,11 @@ static int zend_parse_va_args(uint32_t num_args, const char *type_spec, va_list
}
/* mark the beginning of varargs */
post_varargs = max_num_args;
if (ZEND_CALL_INFO(EG(current_execute_data)) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
zend_unexpected_extra_named_error();
return FAILURE;
}
break;
default:
@ -2298,11 +2314,20 @@ ZEND_API int zend_register_functions(zend_class_entry *scope, const zend_functio
if (reg_function->common.arg_info && reg_function->common.num_args) {
uint32_t i;
for (i = 0; i < reg_function->common.num_args; i++) {
zend_arg_info *arg_info = &reg_function->common.arg_info[i];
zend_internal_arg_info *arg_info = &reg_function->internal_function.arg_info[i];
ZEND_ASSERT(arg_info->name && "Parameter must have a name");
if (ZEND_TYPE_IS_SET(arg_info->type)) {
reg_function->common.fn_flags |= ZEND_ACC_HAS_TYPE_HINTS;
}
#if ZEND_DEBUG
for (uint32_t j = 0; j < i; j++) {
if (!strcmp(arg_info->name, reg_function->internal_function.arg_info[j].name)) {
zend_error_noreturn(E_CORE_ERROR,
"Duplicate parameter name $%s for function %s%s%s()", arg_info->name,
scope ? ZSTR_VAL(scope->name) : "", scope ? "::" : "", ptr->fname);
}
}
#endif
}
}
@ -3305,6 +3330,7 @@ ZEND_API int zend_fcall_info_init(zval *callable, uint32_t check_flags, zend_fca
fci->retval = NULL;
fci->param_count = 0;
fci->params = NULL;
fci->named_params = NULL;
return SUCCESS;
}

View File

@ -47,6 +47,11 @@ typedef struct _zend_fcall_info {
zval *params;
zend_object *object;
uint32_t param_count;
/* This hashtable can also contain positional arguments (with integer keys),
* which will be appended to the normal params[]. This makes it easier to
* integrate APIs like call_user_func_array(). The usual restriction that
* there may not be position arguments after named arguments applies. */
HashTable *named_params;
} zend_fcall_info;
typedef struct _zend_fcall_info_cache {
@ -500,10 +505,13 @@ ZEND_API void add_property_zval_ex(zval *arg, const char *key, size_t key_len, z
#define add_property_zval(__arg, __key, __value) add_property_zval_ex(__arg, __key, strlen(__key), __value)
ZEND_API int _call_user_function_ex(zval *object, zval *function_name, zval *retval_ptr, uint32_t param_count, zval params[]);
ZEND_API int _call_user_function_impl(zval *object, zval *function_name, zval *retval_ptr, uint32_t param_count, zval params[], HashTable *named_params);
#define call_user_function(_unused, object, function_name, retval_ptr, param_count, params) \
_call_user_function_ex(object, function_name, retval_ptr, param_count, params)
#define call_user_function(function_table, object, function_name, retval_ptr, param_count, params) \
_call_user_function_impl(object, function_name, retval_ptr, param_count, params, NULL)
#define call_user_function_named(function_table, object, function_name, retval_ptr, param_count, params, named_params) \
_call_user_function_impl(object, function_name, retval_ptr, param_count, params, named_params)
ZEND_API extern const zend_fcall_info empty_fcall_info;
ZEND_API extern const zend_fcall_info_cache empty_fcall_info_cache;
@ -569,14 +577,14 @@ ZEND_API int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci
* called_scope must be provided for instance and static method calls. */
ZEND_API void zend_call_known_function(
zend_function *fn, zend_object *object, zend_class_entry *called_scope, zval *retval_ptr,
uint32_t param_count, zval *params);
uint32_t param_count, zval *params, HashTable *named_params);
/* Call the provided zend_function instance method on an object. */
static zend_always_inline void zend_call_known_instance_method(
zend_function *fn, zend_object *object, zval *retval_ptr,
uint32_t param_count, zval *params)
{
zend_call_known_function(fn, object, object->ce, retval_ptr, param_count, params);
zend_call_known_function(fn, object, object->ce, retval_ptr, param_count, params, NULL);
}
static zend_always_inline void zend_call_known_instance_method_with_0_params(
@ -1241,6 +1249,7 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_class_or_null_error(u
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_string_or_class_error(uint32_t num, const char *name, zval *arg);
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_parameter_string_or_class_or_null_error(uint32_t num, const char *name, zval *arg);
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_wrong_callback_error(uint32_t num, char *error);
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_unexpected_extra_named_error(void);
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_error(zend_class_entry *error_ce, uint32_t arg_num, const char *format, ...);
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_type_error(uint32_t arg_num, const char *format, ...);
ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_value_error(uint32_t arg_num, const char *format, ...);
@ -1254,6 +1263,7 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_value_error(uint32_t arg_num
#define ZPP_ERROR_WRONG_COUNT 6
#define ZPP_ERROR_WRONG_STRING_OR_CLASS 7
#define ZPP_ERROR_WRONG_STRING_OR_CLASS_OR_NULL 8
#define ZPP_ERROR_UNEXPECTED_EXTRA_NAMED 9
#define ZEND_PARSE_PARAMETERS_START_EX(flags, min_num_args, max_num_args) do { \
const int _flags = (flags); \
@ -1701,11 +1711,31 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_argument_value_error(uint32_t arg_num
dest = NULL; \
dest_num = 0; \
} \
if (UNEXPECTED(ZEND_CALL_INFO(execute_data) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) { \
_error_code = ZPP_ERROR_UNEXPECTED_EXTRA_NAMED; \
break; \
} \
} while (0);
#define Z_PARAM_VARIADIC(spec, dest, dest_num) \
Z_PARAM_VARIADIC_EX(spec, dest, dest_num, 0)
#define Z_PARAM_VARIADIC_WITH_NAMED(dest, dest_num, dest_named) do { \
int _num_varargs = _num_args - _i; \
if (EXPECTED(_num_varargs > 0)) { \
dest = _real_arg + 1; \
dest_num = _num_varargs; \
} else { \
dest = NULL; \
dest_num = 0; \
} \
if (ZEND_CALL_INFO(execute_data) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) { \
dest_named = execute_data->extra_named_params; \
} else { \
dest_named = NULL; \
} \
} while (0);
#define Z_PARAM_STR_OR_ARRAY_HT_EX(dest_str, dest_ht, allow_null) \
Z_PARAM_PROLOGUE(0, 0); \
if (UNEXPECTED(!zend_parse_arg_str_or_array_ht(_arg, &dest_str, &dest_ht, allow_null))) { \

View File

@ -2062,6 +2062,11 @@ simple_list:
zend_ast_export_name(str, ast->child[1], 0, indent);
}
break;
case ZEND_AST_NAMED_ARG:
smart_str_append(str, zend_ast_get_str(ast->child[0]));
smart_str_appends(str, ": ");
ast = ast->child[1];
goto tail_call;
/* 3 child nodes */
case ZEND_AST_METHOD_CALL:

View File

@ -145,6 +145,7 @@ enum _zend_ast_kind {
ZEND_AST_ATTRIBUTE,
ZEND_AST_MATCH,
ZEND_AST_MATCH_ARM,
ZEND_AST_NAMED_ARG,
/* 3 child nodes */
ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT,

View File

@ -29,6 +29,7 @@ static HashTable internal_attributes;
void validate_attribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope)
{
// TODO: More proper signature validation: Too many args, incorrect arg names.
if (attr->argc > 0) {
zval flags;
@ -121,7 +122,7 @@ ZEND_API int zend_get_attribute_value(zval *ret, zend_attribute *attr, uint32_t
return FAILURE;
}
ZVAL_COPY_OR_DUP(ret, &attr->argv[i]);
ZVAL_COPY_OR_DUP(ret, &attr->args[i].value);
if (Z_TYPE_P(ret) == IS_CONSTANT_AST) {
if (SUCCESS != zval_update_constant_ex(ret, scope)) {
@ -182,7 +183,10 @@ static zend_always_inline void free_attribute(zend_attribute *attr, int persiste
zend_string_release(attr->lcname);
for (i = 0; i < attr->argc; i++) {
zval_ptr_dtor(&attr->argv[i]);
if (attr->args[i].name) {
zend_string_release(attr->args[i].name);
}
zval_ptr_dtor(&attr->args[i].value);
}
pefree(attr, persistent);
@ -219,7 +223,8 @@ ZEND_API zend_attribute *zend_add_attribute(HashTable **attributes, zend_bool pe
/* Initialize arguments to avoid partial initialization in case of fatal errors. */
for (uint32_t i = 0; i < argc; i++) {
ZVAL_UNDEF(&attr->argv[i]);
attr->args[i].name = NULL;
ZVAL_UNDEF(&attr->args[i].value);
}
zend_hash_next_index_insert_ptr(*attributes, attr);

View File

@ -30,19 +30,25 @@
#define ZEND_ATTRIBUTE_IS_REPEATABLE (1<<6)
#define ZEND_ATTRIBUTE_FLAGS ((1<<7) - 1)
#define ZEND_ATTRIBUTE_SIZE(argc) (sizeof(zend_attribute) + sizeof(zval) * (argc) - sizeof(zval))
#define ZEND_ATTRIBUTE_SIZE(argc) \
(sizeof(zend_attribute) + sizeof(zend_attribute_arg) * (argc) - sizeof(zend_attribute_arg))
BEGIN_EXTERN_C()
extern ZEND_API zend_class_entry *zend_ce_attribute;
typedef struct {
zend_string *name;
zval value;
} zend_attribute_arg;
typedef struct _zend_attribute {
zend_string *name;
zend_string *lcname;
/* Parameter offsets start at 1, everything else uses 0. */
uint32_t offset;
uint32_t argc;
zval argv[1];
zend_attribute_arg args[1];
} zend_attribute;
typedef struct _zend_internal_attribute {

View File

@ -1640,18 +1640,34 @@ static void debug_backtrace_get_args(zend_execute_data *call, zval *arg_array) /
} else {
ZVAL_EMPTY_ARRAY(arg_array);
}
if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
zend_string *name;
zval *arg;
SEPARATE_ARRAY(arg_array);
ZEND_HASH_FOREACH_STR_KEY_VAL(call->extra_named_params, name, arg) {
ZVAL_DEREF(arg);
Z_TRY_ADDREF_P(arg);
zend_hash_add_new(Z_ARRVAL_P(arg_array), name, arg);
} ZEND_HASH_FOREACH_END();
}
}
/* }}} */
void debug_print_backtrace_args(zval *arg_array) /* {{{ */
{
zend_string *name;
zval *tmp;
int i = 0;
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(arg_array), tmp) {
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(arg_array), name, tmp) {
if (i++) {
ZEND_PUTS(", ");
}
if (name) {
ZEND_PUTS(ZSTR_VAL(name));
ZEND_PUTS(": ");
}
zend_print_flat_zval_r(tmp);
} ZEND_HASH_FOREACH_END();
}

View File

@ -48,9 +48,15 @@ static zend_object_handlers closure_handlers;
ZEND_METHOD(Closure, __invoke) /* {{{ */
{
zend_function *func = EX(func);
zval *arguments = ZEND_CALL_ARG(execute_data, 1);
zval *args;
uint32_t num_args;
HashTable *named_args;
if (call_user_function(CG(function_table), NULL, ZEND_THIS, return_value, ZEND_NUM_ARGS(), arguments) == FAILURE) {
ZEND_PARSE_PARAMETERS_START(0, -1)
Z_PARAM_VARIADIC_WITH_NAMED(args, num_args, named_args)
ZEND_PARSE_PARAMETERS_END();
if (call_user_function_named(CG(function_table), NULL, ZEND_THIS, return_value, num_args, args, named_args) == FAILURE) {
RETVAL_FALSE;
}
@ -122,9 +128,10 @@ ZEND_METHOD(Closure, call)
fci.param_count = 0;
fci.params = NULL;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "o*", &newthis, &fci.params, &fci.param_count) == FAILURE) {
RETURN_THROWS();
}
ZEND_PARSE_PARAMETERS_START(1, -1)
Z_PARAM_OBJECT(newthis)
Z_PARAM_VARIADIC_WITH_NAMED(fci.params, fci.param_count, fci.named_params)
ZEND_PARSE_PARAMETERS_END();
closure = (zend_closure *) Z_OBJ_P(ZEND_THIS);
@ -246,6 +253,7 @@ static ZEND_NAMED_FUNCTION(zend_closure_call_magic) /* {{{ */ {
fcc.function_handler = (EX(func)->internal_function.fn_flags & ZEND_ACC_STATIC) ?
EX(func)->internal_function.scope->__callstatic : EX(func)->internal_function.scope->__call;
fci.named_params = NULL;
fci.params = params;
fci.param_count = 2;
ZVAL_STR(&fci.params[0], EX(func)->common.function_name);

View File

@ -3285,15 +3285,48 @@ void zend_compile_compound_assign(znode *result, zend_ast *ast) /* {{{ */
}
/* }}} */
uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */
static uint32_t zend_get_arg_num(zend_function *fn, zend_string *arg_name) {
// TODO: Caching?
if (fn->type == ZEND_USER_FUNCTION) {
for (uint32_t i = 0; i < fn->common.num_args; i++) {
zend_arg_info *arg_info = &fn->op_array.arg_info[i];
if (zend_string_equals(arg_info->name, arg_name)) {
return i + 1;
}
}
} else {
for (uint32_t i = 0; i < fn->common.num_args; i++) {
zend_internal_arg_info *arg_info = &fn->internal_function.arg_info[i];
size_t len = strlen(arg_info->name);
if (len == ZSTR_LEN(arg_name) && !memcmp(arg_info->name, ZSTR_VAL(arg_name), len)) {
return i + 1;
}
}
}
/* Either an invalid argument name, or collected into a variadic argument. */
return (uint32_t) -1;
}
uint32_t zend_compile_args(
zend_ast *ast, zend_function *fbc, bool *may_have_extra_named_args) /* {{{ */
{
zend_ast_list *args = zend_ast_get_list(ast);
uint32_t i;
zend_bool uses_arg_unpack = 0;
uint32_t arg_count = 0; /* number of arguments not including unpacks */
/* Whether named arguments are used syntactically, to enforce language level limitations.
* May not actually use named argument passing. */
zend_bool uses_named_args = 0;
/* Whether there may be any undef arguments due to the use of named arguments. */
zend_bool may_have_undef = 0;
/* Whether there may be any extra named arguments collected into a variadic. */
*may_have_extra_named_args = 0;
for (i = 0; i < args->children; ++i) {
zend_ast *arg = args->child[i];
zend_string *arg_name = NULL;
uint32_t arg_num = i + 1;
znode arg_node;
@ -3301,6 +3334,11 @@ uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */
zend_uchar opcode;
if (arg->kind == ZEND_AST_UNPACK) {
if (uses_named_args) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot combine named arguments and argument unpacking");
}
uses_arg_unpack = 1;
fbc = NULL;
@ -3308,15 +3346,57 @@ uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */
opline = zend_emit_op(NULL, ZEND_SEND_UNPACK, &arg_node, NULL);
opline->op2.num = arg_count;
opline->result.var = EX_NUM_TO_VAR(arg_count - 1);
/* Unpack may contain named arguments. */
may_have_undef = 1;
if (!fbc || (fbc->common.fn_flags & ZEND_ACC_VARIADIC)) {
*may_have_extra_named_args = 1;
}
continue;
}
if (uses_arg_unpack) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot use positional argument after argument unpacking");
if (arg->kind == ZEND_AST_NAMED_ARG) {
if (uses_arg_unpack) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot combine named arguments and argument unpacking");
}
uses_named_args = 1;
arg_name = zval_make_interned_string(zend_ast_get_zval(arg->child[0]));
arg = arg->child[1];
if (fbc) {
arg_num = zend_get_arg_num(fbc, arg_name);
if (arg_num == arg_count + 1) {
/* Using named arguments, but passing in order. */
arg_name = NULL;
arg_count++;
} else {
// TODO: We could track which arguments were passed, even if out of order.
may_have_undef = 1;
if (arg_num == (uint32_t) -1 && (fbc->common.fn_flags & ZEND_ACC_VARIADIC)) {
*may_have_extra_named_args = 1;
}
}
} else {
arg_num = (uint32_t) -1;
may_have_undef = 1;
*may_have_extra_named_args = 1;
}
} else {
if (uses_arg_unpack) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot use positional argument after argument unpacking");
}
if (uses_named_args) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot use positional argument after named argument");
}
arg_count++;
}
arg_count++;
if (zend_is_call(arg)) {
zend_compile_var(&arg_node, arg, BP_VAR_R, 0);
if (arg_node.op_type & (IS_CONST|IS_TMP_VAR)) {
@ -3327,7 +3407,7 @@ uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */
opcode = ZEND_SEND_VAL;
}
} else {
if (fbc) {
if (fbc && arg_num != (uint32_t) -1) {
if (ARG_MUST_BE_SENT_BY_REF(fbc, arg_num)) {
opcode = ZEND_SEND_VAR_NO_REF;
} else if (ARG_MAY_BE_SENT_BY_REF(fbc, arg_num)) {
@ -3340,7 +3420,7 @@ uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */
}
}
} else if (zend_is_variable(arg) && !zend_ast_is_short_circuited(arg)) {
if (fbc) {
if (fbc && arg_num != (uint32_t) -1) {
if (ARG_SHOULD_BE_SENT_BY_REF(fbc, arg_num)) {
zend_compile_var(&arg_node, arg, BP_VAR_W, 1);
opcode = ZEND_SEND_REF;
@ -3363,7 +3443,14 @@ uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */
}
}
opline = zend_emit_op(NULL, ZEND_CHECK_FUNC_ARG, NULL, NULL);
opline->op2.num = arg_num;
if (arg_name) {
opline->op2_type = IS_CONST;
zend_string_addref(arg_name);
opline->op2.constant = zend_add_literal_string(&arg_name);
opline->result.num = zend_alloc_cache_slots(2);
} else {
opline->op2.num = arg_num;
}
zend_compile_var(&arg_node, arg, BP_VAR_FUNC_ARG, 1);
opcode = ZEND_SEND_FUNC_ARG;
} while (0);
@ -3372,7 +3459,7 @@ uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */
zend_compile_expr(&arg_node, arg);
if (arg_node.op_type == IS_VAR) {
/* pass ++$a or something similar */
if (fbc) {
if (fbc && arg_num != (uint32_t) -1) {
if (ARG_MUST_BE_SENT_BY_REF(fbc, arg_num)) {
opcode = ZEND_SEND_VAR_NO_REF;
} else if (ARG_MAY_BE_SENT_BY_REF(fbc, arg_num)) {
@ -3384,7 +3471,7 @@ uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */
opcode = ZEND_SEND_VAR_NO_REF_EX;
}
} else if (arg_node.op_type == IS_CV) {
if (fbc) {
if (fbc && arg_num != (uint32_t) -1) {
if (ARG_SHOULD_BE_SENT_BY_REF(fbc, arg_num)) {
opcode = ZEND_SEND_REF;
} else {
@ -3395,7 +3482,7 @@ uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */
}
} else {
/* Delay "Only variables can be passed by reference" error to execution */
if (fbc && !ARG_MUST_BE_SENT_BY_REF(fbc, arg_num)) {
if (fbc && arg_num != (uint32_t) -1 && !ARG_MUST_BE_SENT_BY_REF(fbc, arg_num)) {
opcode = ZEND_SEND_VAL;
} else {
opcode = ZEND_SEND_VAL_EX;
@ -3404,8 +3491,19 @@ uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */
}
opline = zend_emit_op(NULL, opcode, &arg_node, NULL);
opline->op2.opline_num = arg_num;
opline->result.var = EX_NUM_TO_VAR(arg_num - 1);
if (arg_name) {
opline->op2_type = IS_CONST;
zend_string_addref(arg_name);
opline->op2.constant = zend_add_literal_string(&arg_name);
opline->result.num = zend_alloc_cache_slots(2);
} else {
opline->op2.opline_num = arg_num;
opline->result.var = EX_NUM_TO_VAR(arg_num - 1);
}
}
if (may_have_undef) {
zend_emit_op(NULL, ZEND_CHECK_UNDEF_ARGS, NULL, NULL);
}
return arg_count;
@ -3443,8 +3541,9 @@ void zend_compile_call_common(znode *result, zend_ast *args_ast, zend_function *
zend_op *opline;
uint32_t opnum_init = get_next_op_number() - 1;
uint32_t arg_count;
bool may_have_extra_named_args;
arg_count = zend_compile_args(args_ast, fbc);
arg_count = zend_compile_args(args_ast, fbc, &may_have_extra_named_args);
zend_do_extended_fcall_begin();
@ -3456,6 +3555,9 @@ void zend_compile_call_common(znode *result, zend_ast *args_ast, zend_function *
}
opline = zend_emit_op(result, zend_get_call_op(opline, fbc), NULL, NULL);
if (may_have_extra_named_args) {
opline->extended_value = ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS;
}
zend_do_extended_fcall_end();
}
/* }}} */
@ -3520,11 +3622,12 @@ void zend_compile_dynamic_call(znode *result, znode *name_node, zend_ast *args_a
}
/* }}} */
static inline zend_bool zend_args_contain_unpack(zend_ast_list *args) /* {{{ */
static inline zend_bool zend_args_contain_unpack_or_named(zend_ast_list *args) /* {{{ */
{
uint32_t i;
for (i = 0; i < args->children; ++i) {
if (args->child[i]->kind == ZEND_AST_UNPACK) {
zend_ast *arg = args->child[i];
if (arg->kind == ZEND_AST_UNPACK || arg->kind == ZEND_AST_NAMED_ARG) {
return 1;
}
}
@ -3774,6 +3877,7 @@ int zend_compile_func_cufa(znode *result, zend_ast_list *args, zend_string *lcna
}
zend_compile_expr(&arg_node, args->child[1]);
zend_emit_op(NULL, ZEND_SEND_ARRAY, &arg_node, NULL);
zend_emit_op(NULL, ZEND_CHECK_UNDEF_ARGS, NULL, NULL);
zend_emit_op(result, ZEND_DO_FCALL, NULL, NULL);
return SUCCESS;
@ -4080,7 +4184,7 @@ int zend_try_compile_special_func(znode *result, zend_string *lcname, zend_ast_l
return FAILURE;
}
if (zend_args_contain_unpack(args)) {
if (zend_args_contain_unpack_or_named(args)) {
return FAILURE;
}
@ -6111,13 +6215,33 @@ static void zend_compile_attributes(HashTable **attributes, zend_ast *ast, uint3
if (args) {
ZEND_ASSERT(args->kind == ZEND_AST_ARG_LIST);
zend_bool uses_named_args = 0;
for (j = 0; j < args->children; j++) {
if (args->child[j]->kind == ZEND_AST_UNPACK) {
zend_ast *arg_ast = args->child[j];
if (arg_ast->kind == ZEND_AST_UNPACK) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot use unpacking in attribute argument list");
}
zend_const_expr_to_zval(&attr->argv[j], args->child[j]);
if (arg_ast->kind == ZEND_AST_NAMED_ARG) {
attr->args[j].name = zend_string_copy(zend_ast_get_str(arg_ast->child[0]));
arg_ast = arg_ast->child[1];
uses_named_args = 1;
for (uint32_t k = 0; k < j; k++) {
if (attr->args[k].name &&
zend_string_equals(attr->args[k].name, attr->args[j].name)) {
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate named parameter $%s",
ZSTR_VAL(attr->args[j].name));
}
}
} else if (uses_named_args) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot use positional argument after named argument");
}
zend_const_expr_to_zval(&attr->args[j].value, arg_ast);
}
}
}

View File

@ -511,6 +511,7 @@ struct _zend_execute_data {
zend_execute_data *prev_execute_data;
zend_array *symbol_table;
void **run_time_cache; /* cache op_array->run_time_cache */
zend_array *extra_named_params;
};
#define ZEND_CALL_HAS_THIS IS_OBJECT_EX
@ -528,6 +529,8 @@ struct _zend_execute_data {
#define ZEND_CALL_FAKE_CLOSURE (1 << 23)
#define ZEND_CALL_GENERATOR (1 << 24)
#define ZEND_CALL_DYNAMIC (1 << 25)
#define ZEND_CALL_MAY_HAVE_UNDEF (1 << 26)
#define ZEND_CALL_HAS_EXTRA_NAMED_PARAMS (1 << 27)
#define ZEND_CALL_SEND_ARG_BY_REF (1u << 31)
#define ZEND_CALL_NESTED_FUNCTION (ZEND_CALL_FUNCTION | ZEND_CALL_NESTED)
@ -951,6 +954,8 @@ ZEND_API zend_string *zend_type_to_string(zend_type type);
#define ZEND_THROW_IS_EXPR 1u
#define ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS 1
/* The send mode and is_variadic flag are stored as part of zend_type */
#define _ZEND_SEND_MODE_SHIFT _ZEND_TYPE_EXTRA_FLAGS_SHIFT
#define _ZEND_IS_VARIADIC_BIT (1 << (_ZEND_TYPE_EXTRA_FLAGS_SHIFT + 2))

View File

@ -566,9 +566,14 @@ static void _build_trace_string(smart_str *str, HashTable *ht, uint32_t num) /*
if (tmp) {
if (Z_TYPE_P(tmp) == IS_ARRAY) {
size_t last_len = ZSTR_LEN(str->s);
zend_string *name;
zval *arg;
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(tmp), arg) {
ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(tmp), name, arg) {
if (name) {
smart_str_append(str, name);
smart_str_appends(str, ": ");
}
_build_trace_args(arg, str);
} ZEND_HASH_FOREACH_END();
@ -662,6 +667,7 @@ ZEND_METHOD(Exception, __toString)
fci.retval = &trace;
fci.param_count = 0;
fci.params = NULL;
fci.named_params = NULL;
zend_call_function(&fci, NULL);

View File

@ -3671,7 +3671,7 @@ ZEND_API void zend_init_execute_data(zend_execute_data *execute_data, zend_op_ar
}
/* }}} */
static zend_execute_data *zend_vm_stack_copy_call_frame(zend_execute_data *call, uint32_t passed_args, uint32_t additional_args) /* {{{ */
zend_execute_data *zend_vm_stack_copy_call_frame(zend_execute_data *call, uint32_t passed_args, uint32_t additional_args) /* {{{ */
{
zend_execute_data *new_call;
int used_stack = (EG(vm_stack_top) - (zval*)call) + additional_args;
@ -3707,16 +3707,6 @@ static zend_execute_data *zend_vm_stack_copy_call_frame(zend_execute_data *call,
}
/* }}} */
static zend_always_inline void zend_vm_stack_extend_call_frame(zend_execute_data **call, uint32_t passed_args, uint32_t additional_args) /* {{{ */
{
if (EXPECTED((uint32_t)(EG(vm_stack_end) - EG(vm_stack_top)) > additional_args)) {
EG(vm_stack_top) += additional_args;
} else {
*call = zend_vm_stack_copy_call_frame(*call, passed_args, additional_args);
}
}
/* }}} */
static zend_always_inline zend_generator *zend_get_running_generator(EXECUTE_DATA_D) /* {{{ */
{
/* The generator object is stored in EX(return_value) */
@ -3786,12 +3776,16 @@ static void cleanup_unfinished_calls(zend_execute_data *execute_data, uint32_t o
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_USER:
if (level == 0) {
ZEND_CALL_NUM_ARGS(call) = opline->op2.num;
/* For named args, the number of arguments is up to date. */
if (opline->op2_type != IS_CONST) {
ZEND_CALL_NUM_ARGS(call) = opline->op2.num;
}
do_exit = 1;
}
break;
case ZEND_SEND_ARRAY:
case ZEND_SEND_UNPACK:
case ZEND_CHECK_UNDEF_ARGS:
if (level == 0) {
do_exit = 1;
}
@ -3836,6 +3830,9 @@ static void cleanup_unfinished_calls(zend_execute_data *execute_data, uint32_t o
if (ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS) {
OBJ_RELEASE(Z_OBJ(call->This));
}
if (ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
zend_free_extra_named_params(call->extra_named_params);
}
if (call->func->common.fn_flags & ZEND_ACC_CLOSURE) {
zend_object_release(ZEND_CLOSURE_OBJECT(call->func));
} else if (call->func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) {
@ -4336,6 +4333,226 @@ static zend_never_inline int ZEND_FASTCALL zend_quick_check_constant(
return _zend_quick_get_constant(key, 0, 1 OPLINE_CC EXECUTE_DATA_CC);
} /* }}} */
static zend_always_inline uint32_t zend_get_arg_offset_by_name(
zend_function *fbc, zend_string *arg_name, void **cache_slot) {
if (EXPECTED(*cache_slot == fbc)) {
return *(uintptr_t *)(cache_slot + 1);
}
// TODO: Use a hash table?
uint32_t num_args = fbc->common.num_args;
if (EXPECTED(fbc->type == ZEND_USER_FUNCTION)
|| EXPECTED(fbc->common.fn_flags & ZEND_ACC_USER_ARG_INFO)) {
for (uint32_t i = 0; i < num_args; i++) {
zend_arg_info *arg_info = &fbc->op_array.arg_info[i];
if (zend_string_equals(arg_name, arg_info->name)) {
*cache_slot = fbc;
*(uintptr_t *)(cache_slot + 1) = i;
return i;
}
}
} else {
for (uint32_t i = 0; i < num_args; i++) {
zend_internal_arg_info *arg_info = &fbc->internal_function.arg_info[i];
size_t len = strlen(arg_info->name);
if (len == ZSTR_LEN(arg_name) && !memcmp(arg_info->name, ZSTR_VAL(arg_name), len)) {
*cache_slot = fbc;
*(uintptr_t *)(cache_slot + 1) = i;
return i;
}
}
}
if (fbc->common.fn_flags & ZEND_ACC_VARIADIC) {
*cache_slot = fbc;
*(uintptr_t *)(cache_slot + 1) = fbc->common.num_args;
return fbc->common.num_args;
}
return (uint32_t) -1;
}
zval * ZEND_FASTCALL zend_handle_named_arg(
zend_execute_data **call_ptr, zend_string *arg_name,
uint32_t *arg_num_ptr, void **cache_slot) {
zend_execute_data *call = *call_ptr;
zend_function *fbc = call->func;
uint32_t arg_offset = zend_get_arg_offset_by_name(fbc, arg_name, cache_slot);
if (UNEXPECTED(arg_offset == (uint32_t) -1)) {
zend_throw_error(NULL, "Unknown named parameter $%s", ZSTR_VAL(arg_name));
return NULL;
}
zval *arg;
if (UNEXPECTED(arg_offset == fbc->common.num_args)) {
/* Unknown named parameter that will be collected into a variadic. */
if (!(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
ZEND_ADD_CALL_FLAG(call, ZEND_CALL_HAS_EXTRA_NAMED_PARAMS);
call->extra_named_params = zend_new_array(0);
}
arg = zend_hash_add_empty_element(call->extra_named_params, arg_name);
if (!arg) {
zend_throw_error(NULL, "Named parameter $%s overwrites previous argument",
ZSTR_VAL(arg_name));
return NULL;
}
*arg_num_ptr = arg_offset + 1;
return arg;
}
uint32_t current_num_args = ZEND_CALL_NUM_ARGS(call);
// TODO: We may wish to optimize the arg_offset == current_num_args case,
// which is probably common (if the named parameters are in order of declaration).
if (arg_offset >= current_num_args) {
uint32_t new_num_args = arg_offset + 1;
ZEND_CALL_NUM_ARGS(call) = new_num_args;
uint32_t num_extra_args = new_num_args - current_num_args;
zend_vm_stack_extend_call_frame(call_ptr, current_num_args, num_extra_args);
call = *call_ptr;
arg = ZEND_CALL_VAR_NUM(call, arg_offset);
if (num_extra_args > 1) {
zval *zv = ZEND_CALL_VAR_NUM(call, current_num_args);
do {
ZVAL_UNDEF(zv);
zv++;
} while (zv != arg);
ZEND_ADD_CALL_FLAG(call, ZEND_CALL_MAY_HAVE_UNDEF);
}
} else {
arg = ZEND_CALL_VAR_NUM(call, arg_offset);
if (UNEXPECTED(!Z_ISUNDEF_P(arg))) {
zend_throw_error(NULL, "Named parameter $%s overwrites previous argument",
ZSTR_VAL(arg_name));
return NULL;
}
}
*arg_num_ptr = arg_offset + 1;
return arg;
}
static void start_fake_frame(zend_execute_data *call, const zend_op *opline) {
zend_execute_data *prev_execute_data = EG(current_execute_data);
call->prev_execute_data = prev_execute_data;
call->opline = opline;
EG(current_execute_data) = call;
}
static void end_fake_frame(zend_execute_data *call) {
zend_execute_data *prev_execute_data = call->prev_execute_data;
EG(current_execute_data) = prev_execute_data;
call->prev_execute_data = NULL;
if (UNEXPECTED(EG(exception)) && ZEND_USER_CODE(prev_execute_data->func->common.type)) {
zend_rethrow_exception(prev_execute_data);
}
}
ZEND_API int ZEND_FASTCALL zend_handle_undef_args(zend_execute_data *call) {
zend_function *fbc = call->func;
if (fbc->type == ZEND_USER_FUNCTION) {
uint32_t num_args = ZEND_CALL_NUM_ARGS(call);
for (uint32_t i = 0; i < num_args; i++) {
zval *arg = ZEND_CALL_VAR_NUM(call, i);
if (!Z_ISUNDEF_P(arg)) {
continue;
}
zend_op_array *op_array = &fbc->op_array;
zend_op *opline = &op_array->opcodes[i];
if (EXPECTED(opline->opcode == ZEND_RECV_INIT)) {
zval *default_value = RT_CONSTANT(opline, opline->op2);
if (Z_OPT_TYPE_P(default_value) == IS_CONSTANT_AST) {
void *run_time_cache = RUN_TIME_CACHE(op_array);
zval *cache_val =
(zval *) ((char *) run_time_cache + Z_CACHE_SLOT_P(default_value));
if (Z_TYPE_P(cache_val) != IS_UNDEF) {
/* We keep in cache only not refcounted values */
ZVAL_COPY_VALUE(arg, cache_val);
} else {
/* Update constant inside a temporary zval, to make sure the CONSTANT_AST
* value is not accessible through back traces. */
zval tmp;
ZVAL_COPY(&tmp, default_value);
start_fake_frame(call, opline);
int ret = zval_update_constant_ex(&tmp, fbc->op_array.scope);
end_fake_frame(call);
if (UNEXPECTED(ret == FAILURE)) {
zval_ptr_dtor_nogc(&tmp);
return FAILURE;
}
ZVAL_COPY_VALUE(arg, &tmp);
if (!Z_REFCOUNTED(tmp)) {
ZVAL_COPY_VALUE(cache_val, &tmp);
}
}
} else {
ZVAL_COPY(arg, default_value);
}
} else {
ZEND_ASSERT(opline->opcode == ZEND_RECV);
start_fake_frame(call, opline);
zend_argument_error(zend_ce_argument_count_error, i + 1, "not passed");
end_fake_frame(call);
}
}
return SUCCESS;
} else {
if (fbc->common.fn_flags & ZEND_ACC_USER_ARG_INFO) {
/* Magic function, let it deal with it. */
return SUCCESS;
}
uint32_t num_args = ZEND_CALL_NUM_ARGS(call);
for (uint32_t i = 0; i < num_args; i++) {
zval *arg = ZEND_CALL_VAR_NUM(call, i);
if (!Z_ISUNDEF_P(arg)) {
continue;
}
zend_internal_arg_info *arg_info = &fbc->internal_function.arg_info[i];
if (i < fbc->common.required_num_args) {
start_fake_frame(call, NULL);
zend_argument_error(zend_ce_argument_count_error, i + 1, "not passed");
end_fake_frame(call);
return FAILURE;
}
zval default_value;
if (zend_get_default_from_internal_arg_info(&default_value, arg_info) == FAILURE) {
start_fake_frame(call, NULL);
zend_argument_error(zend_ce_argument_count_error, i + 1,
"must be passed explicitly, because the default value is not known");
end_fake_frame(call);
return FAILURE;
}
if (Z_TYPE(default_value) == IS_CONSTANT_AST) {
start_fake_frame(call, NULL);
int ret = zval_update_constant_ex(&default_value, fbc->common.scope);
end_fake_frame(call);
if (ret == FAILURE) {
return FAILURE;
}
}
ZVAL_COPY_VALUE(arg, &default_value);
}
}
return SUCCESS;
}
ZEND_API void ZEND_FASTCALL zend_free_extra_named_params(zend_array *extra_named_params)
{
/* Extra named params may be shared. */
zend_array_release(extra_named_params);
}
#if defined(ZEND_VM_IP_GLOBAL_REG) && ((ZEND_VM_KIND == ZEND_VM_KIND_CALL) || (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID))
/* Special versions of functions that sets EX(opline) before calling zend_vm_stack_extend() */
static zend_always_inline zend_execute_data *_zend_vm_stack_push_call_frame_ex(uint32_t used_stack, uint32_t call_info, zend_function *func, uint32_t num_args, void *object_or_called_scope) /* {{{ */

View File

@ -291,6 +291,21 @@ static zend_always_inline void zend_vm_stack_free_call_frame(zend_execute_data *
zend_vm_stack_free_call_frame_ex(ZEND_CALL_INFO(call), call);
}
zend_execute_data *zend_vm_stack_copy_call_frame(
zend_execute_data *call, uint32_t passed_args, uint32_t additional_args);
static zend_always_inline void zend_vm_stack_extend_call_frame(
zend_execute_data **call, uint32_t passed_args, uint32_t additional_args)
{
if (EXPECTED((uint32_t)(EG(vm_stack_end) - EG(vm_stack_top)) > additional_args)) {
EG(vm_stack_top) += additional_args;
} else {
*call = zend_vm_stack_copy_call_frame(*call, passed_args, additional_args);
}
}
ZEND_API void ZEND_FASTCALL zend_free_extra_named_params(zend_array *extra_named_params);
/* services */
ZEND_API const char *get_active_class_name(const char **space);
ZEND_API const char *get_active_function_name(void);
@ -333,6 +348,11 @@ ZEND_API void zend_clean_and_cache_symbol_table(zend_array *symbol_table);
ZEND_API void zend_free_compiled_variables(zend_execute_data *execute_data);
ZEND_API void zend_cleanup_unfinished_execution(zend_execute_data *execute_data, uint32_t op_num, uint32_t catch_op_num);
zval * ZEND_FASTCALL zend_handle_named_arg(
zend_execute_data **call_ptr, zend_string *arg_name,
uint32_t *arg_num_ptr, void **cache_slot);
ZEND_API int ZEND_FASTCALL zend_handle_undef_args(zend_execute_data *call);
#define CACHE_ADDR(num) \
((void**)((char*)EX(run_time_cache) + (num)))

View File

@ -619,7 +619,7 @@ ZEND_API int zval_update_constant(zval *pp) /* {{{ */
}
/* }}} */
int _call_user_function_ex(zval *object, zval *function_name, zval *retval_ptr, uint32_t param_count, zval params[]) /* {{{ */
int _call_user_function_impl(zval *object, zval *function_name, zval *retval_ptr, uint32_t param_count, zval params[], HashTable *named_params) /* {{{ */
{
zend_fcall_info fci;
@ -629,6 +629,7 @@ int _call_user_function_ex(zval *object, zval *function_name, zval *retval_ptr,
fci.retval = retval_ptr;
fci.param_count = param_count;
fci.params = params;
fci.named_params = named_params;
return zend_call_function(&fci, NULL);
}
@ -731,8 +732,14 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
}
for (i=0; i<fci->param_count; i++) {
zval *param;
zval *param = ZEND_CALL_ARG(call, i+1);
zval *arg = &fci->params[i];
if (UNEXPECTED(Z_ISUNDEF_P(arg))) {
/* Allow forwarding undef slots. This is only used by Closure::__invoke(). */
ZVAL_UNDEF(param);
ZEND_ADD_CALL_FLAG(call, ZEND_CALL_MAY_HAVE_UNDEF);
continue;
}
if (ARG_SHOULD_BE_SENT_BY_REF(func, i + 1)) {
if (UNEXPECTED(!Z_ISREF_P(arg))) {
@ -742,6 +749,7 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
zend_param_must_be_ref(func, i + 1);
if (UNEXPECTED(EG(exception))) {
ZEND_CALL_NUM_ARGS(call) = i;
cleanup_args:
zend_vm_stack_free_args(call);
zend_vm_stack_free_call_frame(call);
if (EG(current_execute_data) == &dummy_execute_data) {
@ -759,10 +767,61 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
}
}
param = ZEND_CALL_ARG(call, i+1);
ZVAL_COPY(param, arg);
}
if (fci->named_params) {
zend_string *name;
zval *arg;
uint32_t arg_num = ZEND_CALL_NUM_ARGS(call) + 1;
zend_bool have_named_params = 0;
ZEND_HASH_FOREACH_STR_KEY_VAL(fci->named_params, name, arg) {
zval *target;
if (name) {
void *cache_slot[2] = {NULL, NULL};
have_named_params = 1;
target = zend_handle_named_arg(&call, name, &arg_num, cache_slot);
if (!target) {
goto cleanup_args;
}
} else {
if (have_named_params) {
zend_throw_error(NULL,
"Cannot use positional argument after named argument");
goto cleanup_args;
}
zend_vm_stack_extend_call_frame(&call, arg_num - 1, 1);
target = ZEND_CALL_ARG(call, arg_num);
}
if (ARG_SHOULD_BE_SENT_BY_REF(func, arg_num)) {
if (UNEXPECTED(!Z_ISREF_P(arg))) {
if (!ARG_MAY_BE_SENT_BY_REF(func, arg_num)) {
/* By-value send is not allowed -- emit a warning,
* but still perform the call with a by-value send. */
zend_param_must_be_ref(func, arg_num);
if (UNEXPECTED(EG(exception))) {
goto cleanup_args;
}
}
}
} else {
if (Z_ISREF_P(arg) &&
!(func->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) {
/* don't separate references for __call */
arg = Z_REFVAL_P(arg);
}
}
ZVAL_COPY(target, arg);
if (!name) {
ZEND_CALL_NUM_ARGS(call)++;
arg_num++;
}
} ZEND_HASH_FOREACH_END();
}
if (UNEXPECTED(func->op_array.fn_flags & ZEND_ACC_CLOSURE)) {
uint32_t call_info;
@ -774,6 +833,17 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
ZEND_ADD_CALL_FLAG(call, call_info);
}
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_MAY_HAVE_UNDEF)) {
if (zend_handle_undef_args(call) == FAILURE) {
zend_vm_stack_free_args(call);
zend_vm_stack_free_call_frame(call);
if (EG(current_execute_data) == &dummy_execute_data) {
EG(current_execute_data) = dummy_execute_data.prev_execute_data;
}
return SUCCESS;
}
}
orig_fake_scope = EG(fake_scope);
EG(fake_scope) = NULL;
if (func->type == ZEND_USER_FUNCTION) {
@ -804,6 +874,9 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
}
EG(current_execute_data) = call->prev_execute_data;
zend_vm_stack_free_args(call);
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
zend_array_release(call->extra_named_params);
}
if (EG(exception)) {
zval_ptr_dtor(fci->retval);
@ -849,7 +922,7 @@ int zend_call_function(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache) /
ZEND_API void zend_call_known_function(
zend_function *fn, zend_object *object, zend_class_entry *called_scope, zval *retval_ptr,
uint32_t param_count, zval *params)
uint32_t param_count, zval *params, HashTable *named_params)
{
zval retval;
zend_fcall_info fci;
@ -862,6 +935,7 @@ ZEND_API void zend_call_known_function(
fci.retval = retval_ptr ? retval_ptr : &retval;
fci.param_count = param_count;
fci.params = params;
fci.named_params = named_params;
ZVAL_UNDEF(&fci.function_name); /* Unused */
fcic.function_handler = fn;

View File

@ -127,6 +127,9 @@ ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished
}
/* always free the CV's, in the symtable are only not-free'd IS_INDIRECT's */
zend_free_compiled_variables(execute_data);
if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
zend_free_extra_named_params(execute_data->extra_named_params);
}
if (EX_CALL_INFO() & ZEND_CALL_RELEASE_THIS) {
OBJ_RELEASE(Z_OBJ(execute_data->This));
@ -312,6 +315,11 @@ static HashTable *zend_generator_get_gc(zend_object *object, zval **table, int *
if (EX_CALL_INFO() & ZEND_CALL_CLOSURE) {
zend_get_gc_buffer_add_obj(gc_buffer, ZEND_CLOSURE_OBJECT(EX(func)));
}
if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
zval extra_named_params;
ZVAL_ARR(&extra_named_params, EX(extra_named_params));
zend_get_gc_buffer_add_zval(gc_buffer, &extra_named_params);
}
if (execute_data->opline != op_array->opcodes) {
uint32_t i, op_num = execute_data->opline - op_array->opcodes - 1;

View File

@ -79,7 +79,7 @@ ZEND_API zval* zend_call_method(zend_object *object, zend_class_entry *obj_ce, z
called_scope = obj_ce;
}
zend_call_known_function(fn, object, called_scope, retval_ptr, param_count, params);
zend_call_known_function(fn, object, called_scope, retval_ptr, param_count, params, NULL);
return retval_ptr;
}
/* }}} */

View File

@ -806,7 +806,9 @@ non_empty_argument_list:
;
argument:
expr { $$ = $1; }
expr { $$ = $1; }
| identifier ':' expr
{ $$ = zend_ast_create(ZEND_AST_NAMED_ARG, $1, $3); }
| T_ELLIPSIS expr { $$ = zend_ast_create(ZEND_AST_UNPACK, $2); }
;

View File

@ -1110,6 +1110,7 @@ ZEND_API zend_function *zend_get_call_trampoline_func(zend_class_entry *ce, zend
* The low bit must be zero, to not be interpreted as a MAP_PTR offset.
*/
static const void *dummy = (void*)(intptr_t)2;
static const zend_arg_info arg_info[1] = {{0}};
ZEND_ASSERT(fbc);
@ -1123,7 +1124,7 @@ ZEND_API zend_function *zend_get_call_trampoline_func(zend_class_entry *ce, zend
func->arg_flags[0] = 0;
func->arg_flags[1] = 0;
func->arg_flags[2] = 0;
func->fn_flags = ZEND_ACC_CALL_VIA_TRAMPOLINE | ZEND_ACC_PUBLIC;
func->fn_flags = ZEND_ACC_CALL_VIA_TRAMPOLINE | ZEND_ACC_PUBLIC | ZEND_ACC_VARIADIC;
if (is_static) {
func->fn_flags |= ZEND_ACC_STATIC;
}
@ -1147,7 +1148,7 @@ ZEND_API zend_function *zend_get_call_trampoline_func(zend_class_entry *ce, zend
func->prototype = NULL;
func->num_args = 0;
func->required_num_args = 0;
func->arg_info = 0;
func->arg_info = (zend_arg_info *) arg_info;
return (zend_function*)func;
}

View File

@ -2761,7 +2761,7 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY)
uint32_t call_info = EX_CALL_INFO();
SAVE_OPLINE();
if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED)) == 0)) {
if (EXPECTED((call_info & (ZEND_CALL_CODE|ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) == 0)) {
EG(current_execute_data) = EX(prev_execute_data);
i_free_compiled_variables(execute_data);
@ -2794,6 +2794,10 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY)
zend_clean_and_cache_symbol_table(EX(symbol_table));
}
if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
zend_free_extra_named_params(EX(extra_named_params));
}
/* Free extra args before releasing the closure,
* as that may free the op_array. */
zend_vm_stack_free_extra_args_ex(call_info, execute_data);
@ -2841,11 +2845,14 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY)
#ifdef ZEND_PREFER_RELOAD
call_info = EX_CALL_INFO();
#endif
if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS))) {
if (UNEXPECTED(call_info & (ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS))) {
if (UNEXPECTED(call_info & ZEND_CALL_HAS_SYMBOL_TABLE)) {
zend_clean_and_cache_symbol_table(EX(symbol_table));
}
zend_vm_stack_free_extra_args_ex(call_info, execute_data);
if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
zend_free_extra_named_params(EX(extra_named_params));
}
}
if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) {
OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func)));
@ -3896,7 +3903,16 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL))
EG(current_execute_data) = execute_data;
zend_vm_stack_free_args(call);
zend_vm_stack_free_call_frame(call);
uint32_t call_info = ZEND_CALL_INFO(call);
if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) {
if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
zend_free_extra_named_params(call->extra_named_params);
}
zend_vm_stack_free_call_frame_ex(call_info, call);
} else {
EG(vm_stack_top) = (zval*)call;
}
if (!RETURN_VALUE_USED(opline)) {
i_zval_ptr_dtor(ret);
@ -4000,7 +4016,16 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL))
ZEND_VM_C_LABEL(fcall_by_name_end):
zend_vm_stack_free_args(call);
zend_vm_stack_free_call_frame(call);
uint32_t call_info = ZEND_CALL_INFO(call);
if (UNEXPECTED(call_info & (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_ALLOCATED))) {
if (call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
zend_free_extra_named_params(call->extra_named_params);
}
zend_vm_stack_free_call_frame_ex(call_info, call);
} else {
EG(vm_stack_top) = (zval*)call;
}
if (!RETURN_VALUE_USED(opline)) {
i_zval_ptr_dtor(ret);
@ -4094,6 +4119,10 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL))
ZEND_VM_C_LABEL(fcall_end):
zend_vm_stack_free_args(call);
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
zend_free_extra_named_params(call->extra_named_params);
}
if (!RETURN_VALUE_USED(opline)) {
i_zval_ptr_dtor(ret);
}
@ -4517,13 +4546,24 @@ ZEND_VM_HANDLER(107, ZEND_CATCH, CONST, JMP_ADDR, LAST_CATCH|CACHE_SLOT)
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
ZEND_VM_HOT_HANDLER(65, ZEND_SEND_VAL, CONST|TMPVAR, NUM)
ZEND_VM_HOT_HANDLER(65, ZEND_SEND_VAL, CONST|TMPVAR, CONST|UNUSED|NUM)
{
USE_OPLINE
zval *value, *arg;
if (OP2_TYPE == IS_CONST) {
SAVE_OPLINE();
zend_string *arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op2));
uint32_t arg_num;
arg = zend_handle_named_arg(&EX(call), arg_name, &arg_num, CACHE_ADDR(opline->result.num));
if (UNEXPECTED(!arg)) {
HANDLE_EXCEPTION();
}
} else {
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
}
value = GET_OP1_ZVAL_PTR(BP_VAR_R);
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
ZVAL_COPY_VALUE(arg, value);
if (OP1_TYPE == IS_CONST) {
if (UNEXPECTED(Z_OPT_REFCOUNTED_P(arg))) {
@ -4547,11 +4587,23 @@ ZEND_VM_COLD_HELPER(zend_cannot_pass_by_ref_helper, ANY, ANY)
HANDLE_EXCEPTION();
}
ZEND_VM_HOT_SEND_HANDLER(116, ZEND_SEND_VAL_EX, CONST|TMP, NUM, SPEC(QUICK_ARG))
ZEND_VM_HOT_SEND_HANDLER(116, ZEND_SEND_VAL_EX, CONST|TMP, CONST|UNUSED|NUM, SPEC(QUICK_ARG))
{
USE_OPLINE
zval *value, *arg;
uint32_t arg_num = opline->op2.num;
uint32_t arg_num;
if (OP2_TYPE == IS_CONST) {
SAVE_OPLINE();
zend_string *arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op2));
arg = zend_handle_named_arg(&EX(call), arg_name, &arg_num, CACHE_ADDR(opline->result.num));
if (UNEXPECTED(!arg)) {
HANDLE_EXCEPTION();
}
} else {
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
arg_num = opline->op2.num;
}
if (EXPECTED(arg_num <= MAX_ARG_FLAG_NUM)) {
if (QUICK_ARG_MUST_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
@ -4562,7 +4614,6 @@ ZEND_VM_C_LABEL(send_val_by_ref):
ZEND_VM_DISPATCH_TO_HELPER(zend_cannot_pass_by_ref_helper);
}
value = GET_OP1_ZVAL_PTR(BP_VAR_R);
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
ZVAL_COPY_VALUE(arg, value);
if (OP1_TYPE == IS_CONST) {
if (UNEXPECTED(Z_OPT_REFCOUNTED_P(arg))) {
@ -4572,22 +4623,31 @@ ZEND_VM_C_LABEL(send_val_by_ref):
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_HOT_HANDLER(117, ZEND_SEND_VAR, VAR|CV, NUM)
ZEND_VM_HOT_HANDLER(117, ZEND_SEND_VAR, VAR|CV, CONST|UNUSED|NUM)
{
USE_OPLINE
zval *varptr, *arg;
if (OP2_TYPE == IS_CONST) {
SAVE_OPLINE();
zend_string *arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op2));
uint32_t arg_num;
arg = zend_handle_named_arg(&EX(call), arg_name, &arg_num, CACHE_ADDR(opline->result.num));
if (UNEXPECTED(!arg)) {
HANDLE_EXCEPTION();
}
} else {
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
}
varptr = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
if (OP1_TYPE == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(varptr) == IS_UNDEF)) {
SAVE_OPLINE();
ZVAL_UNDEFINED_OP1();
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
ZVAL_NULL(arg);
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
if (OP1_TYPE == IS_CV) {
ZVAL_COPY_DEREF(arg, varptr);
} else /* if (OP1_TYPE == IS_VAR) */ {
@ -4609,13 +4669,24 @@ ZEND_VM_HOT_HANDLER(117, ZEND_SEND_VAR, VAR|CV, NUM)
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_HANDLER(106, ZEND_SEND_VAR_NO_REF, VAR, NUM)
ZEND_VM_HANDLER(106, ZEND_SEND_VAR_NO_REF, VAR, CONST|UNUSED|NUM)
{
USE_OPLINE
zval *varptr, *arg;
if (OP2_TYPE == IS_CONST) {
SAVE_OPLINE();
zend_string *arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op2));
uint32_t arg_num;
arg = zend_handle_named_arg(&EX(call), arg_name, &arg_num, CACHE_ADDR(opline->result.num));
if (UNEXPECTED(!arg)) {
HANDLE_EXCEPTION();
}
} else {
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
}
varptr = GET_OP1_ZVAL_PTR(BP_VAR_R);
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
ZVAL_COPY_VALUE(arg, varptr);
if (EXPECTED(Z_ISREF_P(varptr))) {
@ -4628,19 +4699,30 @@ ZEND_VM_HANDLER(106, ZEND_SEND_VAR_NO_REF, VAR, NUM)
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
ZEND_VM_HOT_SEND_HANDLER(50, ZEND_SEND_VAR_NO_REF_EX, VAR, NUM, SPEC(QUICK_ARG))
ZEND_VM_HOT_SEND_HANDLER(50, ZEND_SEND_VAR_NO_REF_EX, VAR, CONST|UNUSED|NUM, SPEC(QUICK_ARG))
{
USE_OPLINE
zval *varptr, *arg;
uint32_t arg_num = opline->op2.num;
uint32_t arg_num;
if (OP2_TYPE == IS_CONST) {
SAVE_OPLINE();
zend_string *arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op2));
arg = zend_handle_named_arg(&EX(call), arg_name, &arg_num, CACHE_ADDR(opline->result.num));
if (UNEXPECTED(!arg)) {
HANDLE_EXCEPTION();
}
} else {
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
arg_num = opline->op2.num;
}
if (EXPECTED(arg_num <= MAX_ARG_FLAG_NUM)) {
if (!QUICK_ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
ZEND_VM_DISPATCH_TO_HANDLER(ZEND_SEND_VAR);
ZEND_VM_C_GOTO(send_var);
}
varptr = GET_OP1_ZVAL_PTR(BP_VAR_R);
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
ZVAL_COPY_VALUE(arg, varptr);
if (EXPECTED(Z_ISREF_P(varptr) ||
@ -4649,11 +4731,10 @@ ZEND_VM_HOT_SEND_HANDLER(50, ZEND_SEND_VAR_NO_REF_EX, VAR, NUM, SPEC(QUICK_ARG))
}
} else {
if (!ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
ZEND_VM_DISPATCH_TO_HANDLER(ZEND_SEND_VAR);
ZEND_VM_C_GOTO(send_var);
}
varptr = GET_OP1_ZVAL_PTR(BP_VAR_R);
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
ZVAL_COPY_VALUE(arg, varptr);
if (EXPECTED(Z_ISREF_P(varptr) ||
@ -4666,17 +4747,43 @@ ZEND_VM_HOT_SEND_HANDLER(50, ZEND_SEND_VAR_NO_REF_EX, VAR, NUM, SPEC(QUICK_ARG))
ZVAL_NEW_REF(arg, arg);
zend_error(E_NOTICE, "Only variables should be passed by reference");
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
ZEND_VM_C_LABEL(send_var):
varptr = GET_OP1_ZVAL_PTR(BP_VAR_R);
if (UNEXPECTED(Z_ISREF_P(varptr))) {
zend_refcounted *ref = Z_COUNTED_P(varptr);
varptr = Z_REFVAL_P(varptr);
ZVAL_COPY_VALUE(arg, varptr);
if (UNEXPECTED(GC_DELREF(ref) == 0)) {
efree_size(ref, sizeof(zend_reference));
} else if (Z_OPT_REFCOUNTED_P(arg)) {
Z_ADDREF_P(arg);
}
} else {
ZVAL_COPY_VALUE(arg, varptr);
}
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_HANDLER(67, ZEND_SEND_REF, VAR|CV, NUM)
ZEND_VM_HANDLER(67, ZEND_SEND_REF, VAR|CV, CONST|UNUSED|NUM)
{
USE_OPLINE
zval *varptr, *arg;
SAVE_OPLINE();
varptr = GET_OP1_ZVAL_PTR_PTR(BP_VAR_W);
if (OP2_TYPE == IS_CONST) {
zend_string *arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op2));
uint32_t arg_num;
arg = zend_handle_named_arg(&EX(call), arg_name, &arg_num, CACHE_ADDR(opline->result.num));
if (UNEXPECTED(!arg)) {
HANDLE_EXCEPTION();
}
} else {
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
}
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
varptr = GET_OP1_ZVAL_PTR_PTR(BP_VAR_W);
if (Z_ISREF_P(varptr)) {
Z_ADDREF_P(varptr);
} else {
@ -4688,11 +4795,23 @@ ZEND_VM_HANDLER(67, ZEND_SEND_REF, VAR|CV, NUM)
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_HOT_SEND_HANDLER(66, ZEND_SEND_VAR_EX, VAR|CV, NUM, SPEC(QUICK_ARG))
ZEND_VM_HOT_SEND_HANDLER(66, ZEND_SEND_VAR_EX, VAR|CV, CONST|UNUSED|NUM, SPEC(QUICK_ARG))
{
USE_OPLINE
zval *varptr, *arg;
uint32_t arg_num = opline->op2.num;
uint32_t arg_num;
if (OP2_TYPE == IS_CONST) {
SAVE_OPLINE();
zend_string *arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op2));
arg = zend_handle_named_arg(&EX(call), arg_name, &arg_num, CACHE_ADDR(opline->result.num));
if (UNEXPECTED(!arg)) {
HANDLE_EXCEPTION();
}
} else {
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
arg_num = opline->op2.num;
}
if (EXPECTED(arg_num <= MAX_ARG_FLAG_NUM)) {
if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
@ -4700,7 +4819,16 @@ ZEND_VM_HOT_SEND_HANDLER(66, ZEND_SEND_VAR_EX, VAR|CV, NUM, SPEC(QUICK_ARG))
}
} else if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
ZEND_VM_C_LABEL(send_var_by_ref):
ZEND_VM_DISPATCH_TO_HANDLER(ZEND_SEND_REF);
varptr = GET_OP1_ZVAL_PTR_PTR(BP_VAR_W);
if (Z_ISREF_P(varptr)) {
Z_ADDREF_P(varptr);
} else {
ZVAL_MAKE_REF_EX(varptr, 2);
}
ZVAL_REF(arg, Z_REF_P(varptr));
FREE_OP1_VAR_PTR();
ZEND_VM_NEXT_OPCODE();
}
varptr = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
@ -4712,8 +4840,6 @@ ZEND_VM_C_LABEL(send_var_by_ref):
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
if (OP1_TYPE == IS_CV) {
ZVAL_COPY_DEREF(arg, varptr);
} else /* if (OP1_TYPE == IS_VAR) */ {
@ -4735,10 +4861,23 @@ ZEND_VM_C_LABEL(send_var_by_ref):
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_HOT_SEND_HANDLER(100, ZEND_CHECK_FUNC_ARG, UNUSED, NUM, SPEC(QUICK_ARG))
ZEND_VM_HOT_SEND_HANDLER(100, ZEND_CHECK_FUNC_ARG, UNUSED, CONST|UNUSED|NUM, SPEC(QUICK_ARG))
{
USE_OPLINE
uint32_t arg_num = opline->op2.num;
uint32_t arg_num;
if (OP2_TYPE == IS_CONST) {
zend_string *arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op2));
arg_num = zend_get_arg_offset_by_name(
EX(call)->func, arg_name, CACHE_ADDR(opline->result.num)) + 1;
if (UNEXPECTED(arg_num == 0)) {
/* Treat this as a by-value argument, and throw an error during SEND. */
ZEND_DEL_CALL_FLAG(EX(call), ZEND_CALL_SEND_ARG_BY_REF);
ZEND_VM_NEXT_OPCODE();
}
} else {
arg_num = opline->op2.num;
}
if (EXPECTED(arg_num <= MAX_ARG_FLAG_NUM)) {
if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
@ -4754,17 +4893,38 @@ ZEND_VM_HOT_SEND_HANDLER(100, ZEND_CHECK_FUNC_ARG, UNUSED, NUM, SPEC(QUICK_ARG))
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_HOT_HANDLER(185, ZEND_SEND_FUNC_ARG, VAR, NUM)
ZEND_VM_HOT_HANDLER(185, ZEND_SEND_FUNC_ARG, VAR, CONST|UNUSED|NUM)
{
USE_OPLINE
zval *varptr, *arg;
if (OP2_TYPE == IS_CONST) {
// TODO: Would it make sense to share the cache slot with CHECK_FUNC_ARG?
SAVE_OPLINE();
zend_string *arg_name = Z_STR_P(RT_CONSTANT(opline, opline->op2));
uint32_t arg_num;
arg = zend_handle_named_arg(&EX(call), arg_name, &arg_num, CACHE_ADDR(opline->result.num));
if (UNEXPECTED(!arg)) {
HANDLE_EXCEPTION();
}
} else {
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
}
if (UNEXPECTED(ZEND_CALL_INFO(EX(call)) & ZEND_CALL_SEND_ARG_BY_REF)) {
ZEND_VM_DISPATCH_TO_HANDLER(ZEND_SEND_REF);
varptr = GET_OP1_ZVAL_PTR_PTR(BP_VAR_W);
if (Z_ISREF_P(varptr)) {
Z_ADDREF_P(varptr);
} else {
ZVAL_MAKE_REF_EX(varptr, 2);
}
ZVAL_REF(arg, Z_REF_P(varptr));
FREE_OP1_VAR_PTR();
ZEND_VM_NEXT_OPCODE();
}
varptr = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
arg = ZEND_CALL_VAR(EX(call), opline->result.var);
if (UNEXPECTED(Z_ISREF_P(varptr))) {
zend_refcounted *ref = Z_COUNTED_P(varptr);
@ -4787,7 +4947,7 @@ ZEND_VM_HANDLER(165, ZEND_SEND_UNPACK, ANY, ANY)
{
USE_OPLINE
zval *args;
int arg_num;
uint32_t arg_num;
SAVE_OPLINE();
args = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
@ -4798,20 +4958,28 @@ ZEND_VM_C_LABEL(send_again):
HashTable *ht = Z_ARRVAL_P(args);
zval *arg, *top;
zend_string *name;
zend_bool have_named_params = 0;
zend_vm_stack_extend_call_frame(&EX(call), arg_num - 1, zend_hash_num_elements(ht));
// TODO: Speed this up using a flag that specifies whether there are any ref parameters.
if ((OP1_TYPE & (IS_VAR|IS_CV)) && Z_REFCOUNT_P(args) > 1) {
uint32_t i;
uint32_t tmp_arg_num = arg_num;
int separate = 0;
/* check if any of arguments are going to be passed by reference */
for (i = 0; i < zend_hash_num_elements(ht); i++) {
if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num + i)) {
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, name, arg) {
if (UNEXPECTED(name)) {
void *cache_slot[2] = {NULL, NULL};
tmp_arg_num = zend_get_arg_offset_by_name(
EX(call)->func, name, cache_slot) + 1;
}
if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, tmp_arg_num)) {
separate = 1;
break;
}
}
tmp_arg_num++;
} ZEND_HASH_FOREACH_END();
if (separate) {
SEPARATE_ARRAY(args);
ht = Z_ARRVAL_P(args);
@ -4819,13 +4987,26 @@ ZEND_VM_C_LABEL(send_again):
}
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, name, arg) {
if (name) {
zend_throw_error(NULL, "Cannot unpack array with string keys");
FREE_OP1();
HANDLE_EXCEPTION();
if (UNEXPECTED(name)) {
void *cache_slot[2] = {NULL, NULL};
have_named_params = 1;
top = zend_handle_named_arg(&EX(call), name, &arg_num, cache_slot);
if (UNEXPECTED(!top)) {
FREE_OP1();
HANDLE_EXCEPTION();
}
} else {
if (have_named_params) {
zend_throw_error(NULL,
"Cannot use positional argument after named argument during unpacking");
FREE_OP1();
HANDLE_EXCEPTION();
}
top = ZEND_CALL_ARG(EX(call), arg_num);
ZEND_CALL_NUM_ARGS(EX(call))++;
}
top = ZEND_CALL_ARG(EX(call), arg_num);
if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
if (Z_ISREF_P(arg)) {
Z_ADDREF_P(arg);
@ -4842,13 +5023,13 @@ ZEND_VM_C_LABEL(send_again):
ZVAL_COPY_DEREF(top, arg);
}
ZEND_CALL_NUM_ARGS(EX(call))++;
arg_num++;
} ZEND_HASH_FOREACH_END();
} else if (EXPECTED(Z_TYPE_P(args) == IS_OBJECT)) {
zend_class_entry *ce = Z_OBJCE_P(args);
zend_object_iterator *iter;
zend_bool have_named_params = 0;
if (!ce || !ce->get_iterator) {
zend_type_error("Only arrays and Traversables can be unpacked");
@ -4881,6 +5062,7 @@ ZEND_VM_C_LABEL(send_again):
break;
}
zend_string *name = NULL;
if (iter->funcs->get_current_key) {
zval key;
iter->funcs->get_current_key(iter, &key);
@ -4889,33 +5071,61 @@ ZEND_VM_C_LABEL(send_again):
}
if (UNEXPECTED(Z_TYPE(key) != IS_LONG)) {
zend_throw_error(NULL,
(Z_TYPE(key) == IS_STRING) ?
"Cannot unpack Traversable with string keys" :
"Cannot unpack Traversable with non-integer keys");
zval_ptr_dtor(&key);
break;
if (UNEXPECTED(Z_TYPE(key) != IS_STRING)) {
zend_throw_error(NULL,
"Keys must be of type int|string during argument unpacking");
zval_ptr_dtor(&key);
break;
}
name = Z_STR_P(&key);
}
}
if (ARG_MUST_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
zend_error(
E_WARNING, "Cannot pass by-reference argument %d of %s%s%s()"
" by unpacking a Traversable, passing by-value instead", arg_num,
EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "",
EX(call)->func->common.scope ? "::" : "",
ZSTR_VAL(EX(call)->func->common.function_name)
);
if (UNEXPECTED(name)) {
void *cache_slot[2] = {NULL, NULL};
have_named_params = 1;
top = zend_handle_named_arg(&EX(call), name, &arg_num, cache_slot);
if (UNEXPECTED(!top)) {
break;
}
if (ARG_MUST_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
zend_error(
E_WARNING, "Cannot pass by-reference argument %d of %s%s%s()"
" by unpacking a Traversable, passing by-value instead", arg_num,
EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "",
EX(call)->func->common.scope ? "::" : "",
ZSTR_VAL(EX(call)->func->common.function_name)
);
}
ZVAL_COPY_DEREF(top, arg);
zend_string_release(name);
} else {
if (have_named_params) {
zend_throw_error(NULL,
"Cannot use positional argument after named argument during unpacking");
break;
}
if (ARG_MUST_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
zend_error(
E_WARNING, "Cannot pass by-reference argument %d of %s%s%s()"
" by unpacking a Traversable, passing by-value instead", arg_num,
EX(call)->func->common.scope ? ZSTR_VAL(EX(call)->func->common.scope->name) : "",
EX(call)->func->common.scope ? "::" : "",
ZSTR_VAL(EX(call)->func->common.function_name)
);
}
zend_vm_stack_extend_call_frame(&EX(call), arg_num - 1, 1);
top = ZEND_CALL_ARG(EX(call), arg_num);
ZVAL_COPY_DEREF(top, arg);
ZEND_CALL_NUM_ARGS(EX(call))++;
}
ZVAL_DEREF(arg);
Z_TRY_ADDREF_P(arg);
zend_vm_stack_extend_call_frame(&EX(call), arg_num - 1, 1);
top = ZEND_CALL_ARG(EX(call), arg_num);
ZVAL_COPY_VALUE(top, arg);
ZEND_CALL_NUM_ARGS(EX(call))++;
iter->funcs->move_forward(iter);
}
@ -4959,10 +5169,11 @@ ZEND_VM_HANDLER(119, ZEND_SEND_ARRAY, ANY, ANY, NUM)
HashTable *ht;
zval *arg, *param;
ZEND_VM_C_LABEL(send_array):
ht = Z_ARRVAL_P(args);
if (OP2_TYPE != IS_UNUSED) {
/* We don't need to handle named params in this case,
* because array_slice() is called with $preserve_keys == false. */
zval *op2 = GET_OP2_ZVAL_PTR(BP_VAR_R);
uint32_t skip = opline->extended_value;
uint32_t count = zend_hash_num_elements(ht);
@ -5007,10 +5218,28 @@ ZEND_VM_C_LABEL(send_array):
}
FREE_OP2();
} else {
zend_string *name;
zend_bool have_named_params;
zend_vm_stack_extend_call_frame(&EX(call), 0, zend_hash_num_elements(ht));
arg_num = 1;
param = ZEND_CALL_ARG(EX(call), 1);
ZEND_HASH_FOREACH_VAL(ht, arg) {
have_named_params = 0;
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, name, arg) {
if (name) {
void *cache_slot[2] = {NULL, NULL};
have_named_params = 1;
param = zend_handle_named_arg(&EX(call), name, &arg_num, cache_slot);
if (!param) {
FREE_OP1();
HANDLE_EXCEPTION();
}
} else if (have_named_params) {
zend_throw_error(NULL,
"Cannot use positional argument after named argument");
FREE_OP1();
HANDLE_EXCEPTION();
}
if (ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
if (UNEXPECTED(!Z_ISREF_P(arg))) {
if (!ARG_MAY_BE_SENT_BY_REF(EX(call)->func, arg_num)) {
@ -5026,10 +5255,13 @@ ZEND_VM_C_LABEL(send_array):
arg = Z_REFVAL_P(arg);
}
}
ZVAL_COPY(param, arg);
ZEND_CALL_NUM_ARGS(EX(call))++;
arg_num++;
param++;
if (!name) {
ZEND_CALL_NUM_ARGS(EX(call))++;
arg_num++;
param++;
}
} ZEND_HASH_FOREACH_END();
}
}
@ -5055,6 +5287,20 @@ ZEND_VM_HANDLER(120, ZEND_SEND_USER, CONST|TMP|VAR|CV, NUM)
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
ZEND_VM_HOT_HANDLER(199, ZEND_CHECK_UNDEF_ARGS, UNUSED, UNUSED)
{
USE_OPLINE
zend_execute_data *call = execute_data->call;
if (EXPECTED(!(ZEND_CALL_INFO(call) & ZEND_CALL_MAY_HAVE_UNDEF))) {
ZEND_VM_NEXT_OPCODE();
}
SAVE_OPLINE();
zend_handle_undef_args(call);
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
ZEND_VM_COLD_HELPER(zend_missing_arg_helper, ANY, ANY)
{
#ifdef ZEND_VM_IP_GLOBAL_REG
@ -5201,6 +5447,31 @@ ZEND_VM_HANDLER(164, ZEND_RECV_VARIADIC, NUM, UNUSED, CACHE_SLOT)
ZVAL_EMPTY_ARRAY(params);
}
if (EX_CALL_INFO() & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS) {
zend_string *name;
zval *param;
zend_arg_info *arg_info = &EX(func)->common.arg_info[EX(func)->common.num_args];
if (ZEND_TYPE_IS_SET(arg_info->type)) {
SEPARATE_ARRAY(params);
ZEND_HASH_FOREACH_STR_KEY_VAL(EX(extra_named_params), name, param) {
if (UNEXPECTED(!zend_verify_variadic_arg_type(EX(func), arg_info, arg_num, param, CACHE_ADDR(opline->extended_value)))) {
HANDLE_EXCEPTION();
}
Z_TRY_ADDREF_P(param);
zend_hash_add_new(Z_ARRVAL_P(params), name, param);
} ZEND_HASH_FOREACH_END();
} else if (zend_hash_num_elements(Z_ARRVAL_P(params)) == 0) {
GC_ADDREF(EX(extra_named_params));
ZVAL_ARR(params, EX(extra_named_params));
} else {
SEPARATE_ARRAY(params);
ZEND_HASH_FOREACH_STR_KEY_VAL(EX(extra_named_params), name, param) {
Z_TRY_ADDREF_P(param);
zend_hash_add_new(Z_ARRVAL_P(params), name, param);
} ZEND_HASH_FOREACH_END();
}
}
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
@ -8117,7 +8388,7 @@ ZEND_VM_HANDLER(158, ZEND_CALL_TRAMPOLINE, ANY, ANY)
zend_array *args = NULL;
zend_function *fbc = EX(func);
zval *ret = EX(return_value);
uint32_t call_info = EX_CALL_INFO() & (ZEND_CALL_NESTED | ZEND_CALL_TOP | ZEND_CALL_RELEASE_THIS);
uint32_t call_info = EX_CALL_INFO() & (ZEND_CALL_NESTED | ZEND_CALL_TOP | ZEND_CALL_RELEASE_THIS | ZEND_CALL_HAS_EXTRA_NAMED_PARAMS);
uint32_t num_args = EX_NUM_ARGS();
zend_execute_data *call;
@ -8145,10 +8416,21 @@ ZEND_VM_HANDLER(158, ZEND_CALL_TRAMPOLINE, ANY, ANY)
ZEND_CALL_NUM_ARGS(call) = 2;
ZVAL_STR(ZEND_CALL_ARG(call, 1), fbc->common.function_name);
zval *call_args = ZEND_CALL_ARG(call, 2);
if (args) {
ZVAL_ARR(ZEND_CALL_ARG(call, 2), args);
ZVAL_ARR(call_args, args);
} else {
ZVAL_EMPTY_ARRAY(ZEND_CALL_ARG(call, 2));
ZVAL_EMPTY_ARRAY(call_args);
}
if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
if (zend_hash_num_elements(Z_ARRVAL_P(call_args)) == 0) {
GC_ADDREF(call->extra_named_params);
ZVAL_ARR(call_args, call->extra_named_params);
} else {
SEPARATE_ARRAY(call_args);
zend_hash_copy(Z_ARRVAL_P(call_args), call->extra_named_params, zval_add_ref);
}
}
zend_free_trampoline(fbc);
fbc = call->func;
@ -9187,7 +9469,7 @@ ZEND_VM_C_LABEL(fetch_dim_r_index_undef):
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_SIMPLE, CV|VAR, NUM)
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, op->op2_type == IS_UNUSED && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_SIMPLE, CV|VAR, NUM)
{
USE_OPLINE
zval *varptr, *arg;
@ -9204,7 +9486,7 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, (op1_info & (MAY_BE_UNDEF|MAY_BE_RE
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_EX_SIMPLE, CV|VAR, NUM)
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_EX_SIMPLE, CV|VAR, UNUSED|NUM)
{
USE_OPLINE
zval *varptr, *arg;
@ -9226,7 +9508,7 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, op->op2.num <= MAX_ARG_FLAG_NUM
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAL, op->op1_type == IS_CONST && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1)), ZEND_SEND_VAL_SIMPLE, CONST, NUM)
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAL, op->op1_type == IS_CONST && op->op2_type == IS_UNUSED && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1)), ZEND_SEND_VAL_SIMPLE, CONST, NUM)
{
USE_OPLINE
zval *value, *arg;
@ -9237,7 +9519,7 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAL, op->op1_type == IS_CONST && !Z_REFC
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAL_EX, op->op2.num <= MAX_ARG_FLAG_NUM && op->op1_type == IS_CONST && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1)), ZEND_SEND_VAL_EX_SIMPLE, CONST, NUM)
ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAL_EX, op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && op->op1_type == IS_CONST && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1)), ZEND_SEND_VAL_EX_SIMPLE, CONST, NUM)
{
USE_OPLINE
zval *value, *arg;

File diff suppressed because it is too large Load Diff

View File

@ -1015,6 +1015,17 @@ function gen_handler($f, $spec, $kind, $name, $op1, $op2, $use, $code, $lineno,
}
}
/* Skip QUICK_ARG specialization for named parameters */
if (isset($extra_spec["QUICK_ARG"])) {
if ($op2 === "CONST") {
if ($extra_spec["QUICK_ARG"] == 0) {
unset($extra_spec["QUICK_ARG"]);
} else {
return;
}
}
}
if (ZEND_VM_LINES) {
out($f, "#line $lineno \"$definition_file\"\n");
}
@ -1335,6 +1346,13 @@ function gen_labels($f, $spec, $kind, $prolog, &$specs, $switch_labels = array()
}
}
/* Skip QUICK_ARG specialization for named parameters */
if (isset($extra_spec["QUICK_ARG"])) {
if ($op2 === "CONST") {
unset($extra_spec["QUICK_ARG"]);
}
}
// Emit pointer to specialized handler
$spec_name = $dsc["op"]."_SPEC".$prefix[$op1].$prefix[$op2].extra_spec_name($extra_spec);
switch ($kind) {

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,7 @@
#include <zend.h>
#include <zend_vm_opcodes.h>
static const char *zend_vm_opcodes_names[199] = {
static const char *zend_vm_opcodes_names[200] = {
"ZEND_NOP",
"ZEND_ADD",
"ZEND_SUB",
@ -222,9 +222,10 @@ static const char *zend_vm_opcodes_names[199] = {
"ZEND_CASE_STRICT",
"ZEND_MATCH_ERROR",
"ZEND_JMP_NULL",
"ZEND_CHECK_UNDEF_ARGS",
};
static uint32_t zend_vm_opcodes_flags[199] = {
static uint32_t zend_vm_opcodes_flags[200] = {
0x00000000,
0x00000b0b,
0x00000b0b,
@ -275,7 +276,7 @@ static uint32_t zend_vm_opcodes_flags[199] = {
0x00002007,
0x00000705,
0x00000101,
0x00001001,
0x00001301,
0x07000003,
0x00000007,
0x00000707,
@ -290,9 +291,9 @@ static uint32_t zend_vm_opcodes_flags[199] = {
0x00000003,
0x00040110,
0x00040310,
0x00001007,
0x00001001,
0x00001001,
0x00001307,
0x00001301,
0x00001301,
0x0100a173,
0x01040300,
0x00000005,
@ -325,13 +326,13 @@ static uint32_t zend_vm_opcodes_flags[199] = {
0x00040751,
0x0000070b,
0x00040391,
0x00001001,
0x00001301,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x01000000,
0x00001001,
0x00001301,
0x02042003,
0x00000007,
0x00040771,
@ -341,8 +342,8 @@ static uint32_t zend_vm_opcodes_flags[199] = {
0x01048773,
0x00030107,
0x00020707,
0x00001003,
0x00001001,
0x00001303,
0x00001301,
0x01000703,
0x01000000,
0x00001003,
@ -410,7 +411,7 @@ static uint32_t zend_vm_opcodes_flags[199] = {
0x00100101,
0x00100101,
0x00000101,
0x00001001,
0x00001301,
0x00000101,
0x0300030b,
0x0300030b,
@ -424,6 +425,7 @@ static uint32_t zend_vm_opcodes_flags[199] = {
0x00000301,
0x0000010b,
0x0000200b,
0x00000101,
};
ZEND_API const char* ZEND_FASTCALL zend_get_opcode_name(zend_uchar opcode) {

View File

@ -275,7 +275,8 @@ END_EXTERN_C()
#define ZEND_CASE_STRICT 196
#define ZEND_MATCH_ERROR 197
#define ZEND_JMP_NULL 198
#define ZEND_CHECK_UNDEF_ARGS 199
#define ZEND_VM_LAST_OPCODE 198
#define ZEND_VM_LAST_OPCODE 199
#endif

View File

@ -1385,6 +1385,7 @@ static size_t curl_write(char *data, size_t size, size_t nmemb, void *ctx)
fci.retval = &retval;
fci.param_count = 2;
fci.params = argv;
fci.named_params = NULL;
ch->in_callback = 1;
error = zend_call_function(&fci, &t->fci_cache);
@ -1431,6 +1432,7 @@ static int curl_fnmatch(void *ctx, const char *pattern, const char *string)
fci.retval = &retval;
fci.param_count = 3;
fci.params = argv;
fci.named_params = NULL;
ch->in_callback = 1;
error = zend_call_function(&fci, &t->fci_cache);
@ -1483,6 +1485,7 @@ static size_t curl_progress(void *clientp, double dltotal, double dlnow, double
fci.retval = &retval;
fci.param_count = 5;
fci.params = argv;
fci.named_params = NULL;
ch->in_callback = 1;
error = zend_call_function(&fci, &t->fci_cache);
@ -1538,6 +1541,7 @@ static size_t curl_read(char *data, size_t size, size_t nmemb, void *ctx)
fci.retval = &retval;
fci.param_count = 3;
fci.params = argv;
fci.named_params = NULL;
ch->in_callback = 1;
error = zend_call_function(&fci, &t->fci_cache);
@ -1599,6 +1603,7 @@ static size_t curl_write_header(char *data, size_t size, size_t nmemb, void *ctx
fci.retval = &retval;
fci.param_count = 2;
fci.params = argv;
fci.named_params = NULL;
ch->in_callback = 1;
error = zend_call_function(&fci, &t->fci_cache);

View File

@ -150,6 +150,7 @@ static void dom_xpath_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs,
xmlXPathFreeObject(obj);
fci.object = NULL;
fci.named_params = NULL;
fci.retval = &retval;
if (!zend_make_callable(&fci.function_name, &callable)) {

View File

@ -858,6 +858,7 @@ static void zend_ffi_callback_trampoline(ffi_cif* cif, void* ret, void** args, v
fci.params = do_alloca(sizeof(zval) *callback_data->arg_count, use_heap);
fci.object = NULL;
fci.param_count = callback_data->arg_count;
fci.named_params = NULL;
if (callback_data->type->func.args) {
int n = 0;

View File

@ -1227,6 +1227,7 @@ void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flags
fci.retval = &retval;
fci.params = NULL;
fci.param_count = 0;
fci.named_params = NULL;
if (ctor_params && Z_TYPE_P(ctor_params) != IS_NULL) {
if (zend_fcall_info_args(&fci, ctor_params) == FAILURE) {

View File

@ -782,6 +782,20 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
opline->extended_value = cache_size;
cache_size += sizeof(void *);
break;
case ZEND_SEND_VAL:
case ZEND_SEND_VAL_EX:
case ZEND_SEND_VAR:
case ZEND_SEND_VAR_EX:
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_REF:
case ZEND_SEND_FUNC_ARG:
case ZEND_CHECK_FUNC_ARG:
if (opline->op2_type == IS_CONST) {
opline->result.num = cache_size;
cache_size += 2 * sizeof(void *);
}
break;
}
opline++;
}

View File

@ -230,8 +230,8 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
case ZEND_FETCH_OBJ_FUNC_ARG:
case ZEND_FETCH_DIM_FUNC_ARG:
if (call_stack[call - 1].func) {
ZEND_ASSERT(call_stack[call - 1].func_arg_num != (uint32_t)-1);
if (call_stack[call - 1].func
&& call_stack[call - 1].func_arg_num != (uint32_t)-1) {
if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, call_stack[call - 1].func_arg_num)) {
if (opline->opcode != ZEND_FETCH_STATIC_PROP_FUNC_ARG) {
opline->opcode -= 9;
@ -257,6 +257,11 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
break;
case ZEND_SEND_VAL_EX:
if (call_stack[call - 1].func) {
if (opline->op2_type == IS_CONST) {
call_stack[call - 1].try_inline = 0;
break;
}
if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
/* We won't convert it into_DO_FCALL to emit error at run-time */
call_stack[call - 1].opline = NULL;
@ -267,6 +272,12 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
break;
case ZEND_CHECK_FUNC_ARG:
if (call_stack[call - 1].func) {
if (opline->op2_type == IS_CONST) {
call_stack[call - 1].try_inline = 0;
call_stack[call - 1].func_arg_num = (uint32_t)-1;
break;
}
call_stack[call - 1].func_arg_num = opline->op2.num;
MAKE_NOP(opline);
}
@ -274,6 +285,11 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
case ZEND_SEND_VAR_EX:
case ZEND_SEND_FUNC_ARG:
if (call_stack[call - 1].func) {
if (opline->op2_type == IS_CONST) {
call_stack[call - 1].try_inline = 0;
break;
}
call_stack[call - 1].func_arg_num = (uint32_t)-1;
if (ARG_SHOULD_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
opline->opcode = ZEND_SEND_REF;
@ -284,6 +300,11 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
break;
case ZEND_SEND_VAR_NO_REF_EX:
if (call_stack[call - 1].func) {
if (opline->op2_type == IS_CONST) {
call_stack[call - 1].try_inline = 0;
break;
}
if (ARG_MUST_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
opline->opcode = ZEND_SEND_VAR_NO_REF;
} else if (ARG_MAY_BE_SENT_BY_REF(call_stack[call - 1].func, opline->op2.num)) {
@ -293,6 +314,14 @@ void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx)
}
}
break;
case ZEND_SEND_VAL:
case ZEND_SEND_VAR:
case ZEND_SEND_REF:
if (opline->op2_type == IS_CONST) {
call_stack[call - 1].try_inline = 0;
break;
}
break;
case ZEND_SEND_UNPACK:
case ZEND_SEND_USER:
case ZEND_SEND_ARRAY:

View File

@ -124,8 +124,12 @@ int zend_analyze_calls(zend_arena **arena, zend_script *script, uint32_t build_f
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_USER:
if (call_info) {
uint32_t num = opline->op2.num;
if (opline->op2_type == IS_CONST) {
call_info->named_args = 1;
break;
}
uint32_t num = opline->op2.num;
if (num > 0) {
num--;
}

View File

@ -36,6 +36,7 @@ struct _zend_call_info {
zend_call_info *next_callee;
zend_bool recursive;
zend_bool send_unpack; /* Parameters passed by SEND_UNPACK or SEND_ARRAY */
zend_bool named_args; /* Function has named arguments */
int num_args;
zend_send_arg_info arg_info[1];
};

View File

@ -4354,13 +4354,8 @@ int zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op, const ze
case ZEND_JMP:
case ZEND_CHECK_VAR:
case ZEND_MAKE_REF:
case ZEND_SEND_VAR:
case ZEND_BEGIN_SILENCE:
case ZEND_END_SILENCE:
case ZEND_SEND_VAL:
case ZEND_SEND_REF:
case ZEND_SEND_VAR_EX:
case ZEND_SEND_FUNC_ARG:
case ZEND_FREE:
case ZEND_SEPARATE:
case ZEND_TYPE_CHECK:
@ -4375,10 +4370,17 @@ int zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op, const ze
case ZEND_FUNC_NUM_ARGS:
case ZEND_FUNC_GET_ARGS:
case ZEND_COPY_TMP:
case ZEND_CHECK_FUNC_ARG:
case ZEND_CASE_STRICT:
case ZEND_JMP_NULL:
return 0;
case ZEND_SEND_VAR:
case ZEND_SEND_VAL:
case ZEND_SEND_REF:
case ZEND_SEND_VAR_EX:
case ZEND_SEND_FUNC_ARG:
case ZEND_CHECK_FUNC_ARG:
/* May throw for named params. */
return opline->op2_type == IS_CONST;
case ZEND_INIT_FCALL:
/* can't throw, because call is resolved at compile time */
return 0;

View File

@ -2521,6 +2521,10 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
goto done;
case ZEND_SEND_VAL:
case ZEND_SEND_VAL_EX:
if (opline->op2_type == IS_CONST) {
/* Named parameters not supported in JIT (yet) */
break;
}
if (opline->opcode == ZEND_SEND_VAL_EX
&& opline->op2.num > MAX_ARG_FLAG_NUM) {
break;
@ -2531,6 +2535,10 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
}
goto done;
case ZEND_SEND_REF:
if (opline->op2_type == IS_CONST) {
/* Named parameters not supported in JIT (yet) */
break;
}
if (!zend_jit_send_ref(&dasm_state, opline, op_array,
OP1_INFO(), 0)) {
goto jit_failure;
@ -2541,6 +2549,10 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_FUNC_ARG:
if (opline->op2_type == IS_CONST) {
/* Named parameters not supported in JIT (yet) */
break;
}
if ((opline->opcode == ZEND_SEND_VAR_EX
|| opline->opcode == ZEND_SEND_VAR_NO_REF_EX)
&& opline->op2.num > MAX_ARG_FLAG_NUM) {
@ -2560,6 +2572,10 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
}
goto done;
case ZEND_CHECK_FUNC_ARG:
if (opline->op2_type == IS_CONST) {
/* Named parameters not supported in JIT (yet) */
break;
}
if (opline->op2.num > MAX_ARG_FLAG_NUM) {
break;
}
@ -2567,6 +2583,11 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op
goto jit_failure;
}
goto done;
case ZEND_CHECK_UNDEF_ARGS:
if (!zend_jit_check_undef_args(&dasm_state, opline)) {
goto jit_failure;
}
goto done;
case ZEND_DO_UCALL:
is_terminated = 1;
/* break missing intentionally */

View File

@ -3538,6 +3538,10 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
goto done;
case ZEND_SEND_VAL:
case ZEND_SEND_VAL_EX:
if (opline->op2_type == IS_CONST) {
/* Named parameters not supported in JIT */
break;
}
if (opline->opcode == ZEND_SEND_VAL_EX
&& opline->op2.num > MAX_ARG_FLAG_NUM) {
break;
@ -3562,6 +3566,10 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
}
goto done;
case ZEND_SEND_REF:
if (opline->op2_type == IS_CONST) {
/* Named parameters not supported in JIT */
break;
}
op1_info = OP1_INFO();
if (!zend_jit_send_ref(&dasm_state, opline, op_array,
op1_info, 0)) {
@ -3573,6 +3581,10 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
case ZEND_SEND_VAR_NO_REF:
case ZEND_SEND_VAR_NO_REF_EX:
case ZEND_SEND_FUNC_ARG:
if (opline->op2_type == IS_CONST) {
/* Named parameters not supported in JIT */
break;
}
if ((opline->opcode == ZEND_SEND_VAR_EX
|| opline->opcode == ZEND_SEND_VAR_NO_REF_EX)
&& opline->op2.num > MAX_ARG_FLAG_NUM) {
@ -3609,6 +3621,10 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
}
goto done;
case ZEND_CHECK_FUNC_ARG:
if (opline->op2_type == IS_CONST) {
/* Named parameters not supported in JIT */
break;
}
if (opline->op2.num > MAX_ARG_FLAG_NUM
&& (!JIT_G(current_frame)
|| !JIT_G(current_frame)->call
@ -3619,6 +3635,11 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par
goto jit_failure;
}
goto done;
case ZEND_CHECK_UNDEF_ARGS:
if (!zend_jit_check_undef_args(&dasm_state, opline)) {
goto jit_failure;
}
goto done;
case ZEND_DO_UCALL:
case ZEND_DO_ICALL:
case ZEND_DO_FCALL_BY_NAME:

View File

@ -57,6 +57,9 @@ ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_nested_func_helper(uint32_t
} else if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) {
OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func)));
}
if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
zend_free_extra_named_params(EX(extra_named_params));
}
old_execute_data = execute_data;
execute_data = EX(prev_execute_data);
@ -89,6 +92,9 @@ ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_leave_top_func_helper(uint32_t ca
}
zend_vm_stack_free_extra_args_ex(call_info, execute_data);
}
if (UNEXPECTED(call_info & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {
zend_free_extra_named_params(EX(extra_named_params));
}
if (UNEXPECTED(call_info & ZEND_CALL_CLOSURE)) {
OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func)));
}

View File

@ -8567,7 +8567,8 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
#endif
}
if ((opline-1)->opcode == ZEND_SEND_UNPACK|| (opline-1)->opcode == ZEND_SEND_ARRAY) {
if ((opline-1)->opcode == ZEND_SEND_UNPACK || (opline-1)->opcode == ZEND_SEND_ARRAY ||
(opline-1)->opcode == ZEND_CHECK_UNDEF_ARGS) {
unknown_num_args = 1;
}
@ -8621,6 +8622,10 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
}
}
bool may_have_extra_named_params =
opline->extended_value == ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS &&
(!func || func->common.fn_flags & ZEND_ACC_VARIADIC);
if (!reuse_ip) {
zend_jit_start_reuse_ip();
| // call = EX(call);
@ -9025,6 +9030,17 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
| mov FCARG1a, RX
| EXT_CALL zend_jit_vm_stack_free_args_helper, r0
}
if (may_have_extra_named_params) {
| test byte [RX + offsetof(zend_execute_data, This.u1.type_info) + 3], (ZEND_CALL_HAS_EXTRA_NAMED_PARAMS >> 24)
| jnz >1
|.cold_code
|1:
| mov FCARG1a, aword [RX + offsetof(zend_execute_data, extra_named_params)]
| EXT_CALL zend_free_extra_named_params, r0
| jmp >2
|.code
|2:
}
|8:
if (opline->opcode == ZEND_DO_FCALL) {
@ -9183,6 +9199,24 @@ static int zend_jit_send_val(dasm_State **Dst, const zend_op *opline, const zend
return 1;
}
static int zend_jit_check_undef_args(dasm_State **Dst, const zend_op *opline)
{
| mov FCARG1a, EX->call
| test byte [FCARG1a + offsetof(zend_execute_data, This.u1.type_info) + 3], (ZEND_CALL_MAY_HAVE_UNDEF >> 24)
| jnz >1
|.cold_code
|1:
| SAVE_VALID_OPLINE opline, r0
| EXT_CALL zend_handle_undef_args, r0
| test r0, r0
| jnz ->exception_handler
| jmp >2
|.code
|2:
return 1;
}
static int zend_jit_send_ref(dasm_State **Dst, const zend_op *opline, const zend_op_array *op_array, uint32_t op1_info, int cold)
{
zend_jit_addr op1_addr, arg_addr, ref_addr;
@ -10002,7 +10036,7 @@ static int zend_jit_leave_func(dasm_State **Dst, const zend_op *opline, const ze
{
/* ZEND_CALL_FAKE_CLOSURE handled on slow path to eliminate check for ZEND_CALL_CLOSURE on fast path */
| mov FCARG1d, dword [FP + offsetof(zend_execute_data, This.u1.type_info)]
| test FCARG1d, (ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_FAKE_CLOSURE)
| test FCARG1d, (ZEND_CALL_TOP|ZEND_CALL_HAS_SYMBOL_TABLE|ZEND_CALL_FREE_EXTRA_ARGS|ZEND_CALL_ALLOCATED|ZEND_CALL_HAS_EXTRA_NAMED_PARAMS|ZEND_CALL_FAKE_CLOSURE)
if (trace && trace->op != ZEND_JIT_TRACE_END) {
| jnz >1
|.cold_code

View File

@ -415,7 +415,8 @@ static void zend_file_cache_serialize_attribute(zval *zv,
SERIALIZE_STR(attr->lcname);
for (i = 0; i < attr->argc; i++) {
zend_file_cache_serialize_zval(&attr->argv[i], script, info, buf);
SERIALIZE_STR(attr->args[i].name);
zend_file_cache_serialize_zval(&attr->args[i].value, script, info, buf);
}
}
@ -1180,7 +1181,8 @@ static void zend_file_cache_unserialize_attribute(zval *zv, zend_persistent_scri
UNSERIALIZE_STR(attr->lcname);
for (i = 0; i < attr->argc; i++) {
zend_file_cache_unserialize_zval(&attr->argv[i], script, buf);
UNSERIALIZE_STR(attr->args[i].name);
zend_file_cache_unserialize_zval(&attr->args[i].value, script, buf);
}
}

View File

@ -278,7 +278,10 @@ static HashTable *zend_persist_attributes(HashTable *attributes)
zend_accel_store_interned_string(copy->lcname);
for (i = 0; i < copy->argc; i++) {
zend_persist_zval(&copy->argv[i]);
if (copy->args[i].name) {
zend_accel_store_interned_string(copy->args[i].name);
}
zend_persist_zval(&copy->args[i].value);
}
ZVAL_PTR(v, copy);

View File

@ -165,7 +165,10 @@ static void zend_persist_attributes_calc(HashTable *attributes)
ADD_INTERNED_STRING(attr->lcname);
for (i = 0; i < attr->argc; i++) {
zend_persist_zval_calc(&attr->argv[i]);
if (attr->args[i].name) {
ADD_INTERNED_STRING(attr->args[i].name);
}
zend_persist_zval_calc(&attr->args[i].value);
}
} ZEND_HASH_FOREACH_END();
}

View File

@ -2400,6 +2400,7 @@ PHP_FUNCTION(preg_replace_callback_array)
fci.size = sizeof(fci);
fci.object = NULL;
fci.named_params = NULL;
ZEND_HASH_FOREACH_STR_KEY_VAL(pattern, str_idx_regex, replace) {
if (!str_idx_regex) {

View File

@ -437,6 +437,7 @@ static void pdo_stmt_construct(zend_execute_data *execute_data, pdo_stmt_t *stmt
fci.retval = &retval;
fci.param_count = 0;
fci.params = NULL;
fci.named_params = NULL;
zend_fcall_info_args(&fci, ctor_args);

View File

@ -2589,6 +2589,7 @@ static void php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, zend_long result_
fci.retval = &retval;
fci.params = NULL;
fci.param_count = 0;
fci.named_params = NULL;
if (ctor_params && Z_TYPE_P(ctor_params) != IS_NULL) {
if (zend_fcall_info_args(&fci, ctor_params) == FAILURE) {

View File

@ -1796,18 +1796,19 @@ ZEND_METHOD(ReflectionFunctionAbstract, getStaticVariables)
ZEND_METHOD(ReflectionFunction, invoke)
{
zval retval;
zval *params = NULL;
int result, num_args = 0;
zval *params;
int result, num_args;
HashTable *named_params;
zend_fcall_info fci;
zend_fcall_info_cache fcc;
reflection_object *intern;
zend_function *fptr;
GET_REFLECTION_OBJECT_PTR(fptr);
ZEND_PARSE_PARAMETERS_START(0, -1)
Z_PARAM_VARIADIC_WITH_NAMED(params, num_args, named_params)
ZEND_PARSE_PARAMETERS_END();
if (zend_parse_parameters(ZEND_NUM_ARGS(), "*", &params, &num_args) == FAILURE) {
RETURN_THROWS();
}
GET_REFLECTION_OBJECT_PTR(fptr);
fci.size = sizeof(fci);
ZVAL_UNDEF(&fci.function_name);
@ -1815,6 +1816,7 @@ ZEND_METHOD(ReflectionFunction, invoke)
fci.retval = &retval;
fci.param_count = num_args;
fci.params = params;
fci.named_params = named_params;
fcc.function_handler = fptr;
fcc.called_scope = NULL;
@ -1846,36 +1848,26 @@ ZEND_METHOD(ReflectionFunction, invoke)
ZEND_METHOD(ReflectionFunction, invokeArgs)
{
zval retval;
zval *params, *val;
int result;
int i, argc;
zend_fcall_info fci;
zend_fcall_info_cache fcc;
reflection_object *intern;
zend_function *fptr;
zval *param_array;
HashTable *params;
GET_REFLECTION_OBJECT_PTR(fptr);
if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &param_array) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &params) == FAILURE) {
RETURN_THROWS();
}
argc = zend_hash_num_elements(Z_ARRVAL_P(param_array));
params = safe_emalloc(sizeof(zval), argc, 0);
argc = 0;
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(param_array), val) {
ZVAL_COPY(&params[argc], val);
argc++;
} ZEND_HASH_FOREACH_END();
fci.size = sizeof(fci);
ZVAL_UNDEF(&fci.function_name);
fci.object = NULL;
fci.retval = &retval;
fci.param_count = argc;
fci.params = params;
fci.param_count = 0;
fci.params = NULL;
fci.named_params = params;
fcc.function_handler = fptr;
fcc.called_scope = NULL;
@ -1888,11 +1880,6 @@ ZEND_METHOD(ReflectionFunction, invokeArgs)
result = zend_call_function(&fci, &fcc);
for (i = 0; i < argc; i++) {
zval_ptr_dtor(&params[i]);
}
efree(params);
if (result == FAILURE) {
zend_throw_exception_ex(reflection_exception_ptr, 0,
"Invocation of function %s() failed", ZSTR_VAL(fptr->common.function_name));
@ -3127,14 +3114,14 @@ ZEND_METHOD(ReflectionMethod, getClosure)
static void reflection_method_invoke(INTERNAL_FUNCTION_PARAMETERS, int variadic)
{
zval retval;
zval *params = NULL, *val, *object;
zval *params = NULL, *object;
HashTable *named_params = NULL;
reflection_object *intern;
zend_function *mptr;
int i, argc = 0, result;
int argc = 0, result;
zend_fcall_info fci;
zend_fcall_info_cache fcc;
zend_class_entry *obj_ce;
zval *param_array;
GET_REFLECTION_OBJECT_PTR(mptr);
@ -3155,22 +3142,14 @@ static void reflection_method_invoke(INTERNAL_FUNCTION_PARAMETERS, int variadic)
}
if (variadic) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "o!*", &object, &params, &argc) == FAILURE) {
RETURN_THROWS();
}
ZEND_PARSE_PARAMETERS_START(1, -1)
Z_PARAM_OBJECT_OR_NULL(object)
Z_PARAM_VARIADIC_WITH_NAMED(params, argc, named_params)
ZEND_PARSE_PARAMETERS_END();
} else {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "o!a", &object, &param_array) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "o!h", &object, &named_params) == FAILURE) {
RETURN_THROWS();
}
argc = zend_hash_num_elements(Z_ARRVAL_P(param_array));
params = safe_emalloc(sizeof(zval), argc, 0);
argc = 0;
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(param_array), val) {
ZVAL_COPY(&params[argc], val);
argc++;
} ZEND_HASH_FOREACH_END();
}
/* In case this is a static method, we shouldn't pass an object_ptr
@ -3207,6 +3186,7 @@ static void reflection_method_invoke(INTERNAL_FUNCTION_PARAMETERS, int variadic)
fci.retval = &retval;
fci.param_count = argc;
fci.params = params;
fci.named_params = named_params;
fcc.function_handler = mptr;
fcc.called_scope = intern->ce;
@ -3221,13 +3201,6 @@ static void reflection_method_invoke(INTERNAL_FUNCTION_PARAMETERS, int variadic)
result = zend_call_function(&fci, &fcc);
if (!variadic) {
for (i = 0; i < argc; i++) {
zval_ptr_dtor(&params[i]);
}
efree(params);
}
if (result == FAILURE) {
zend_throw_exception_ex(reflection_exception_ptr, 0,
"Invocation of method %s::%s() failed", ZSTR_VAL(mptr->common.scope->name), ZSTR_VAL(mptr->common.function_name));
@ -4676,8 +4649,9 @@ ZEND_METHOD(ReflectionClass, newInstance)
/* Run the constructor if there is one */
if (constructor) {
zval *params = NULL;
int i, num_args = 0;
zval *params;
int num_args;
HashTable *named_params;
if (!(constructor->common.fn_flags & ZEND_ACC_PUBLIC)) {
zend_throw_exception_ex(reflection_exception_ptr, 0, "Access to non-public constructor of class %s", ZSTR_VAL(ce->name));
@ -4685,20 +4659,13 @@ ZEND_METHOD(ReflectionClass, newInstance)
RETURN_NULL();
}
if (zend_parse_parameters(ZEND_NUM_ARGS(), "*", &params, &num_args) == FAILURE) {
zval_ptr_dtor(return_value);
RETURN_THROWS();
}
ZEND_PARSE_PARAMETERS_START(0, -1)
Z_PARAM_VARIADIC_WITH_NAMED(params, num_args, named_params)
ZEND_PARSE_PARAMETERS_END();
for (i = 0; i < num_args; i++) {
Z_TRY_ADDREF(params[i]);
}
zend_call_known_instance_method(constructor, Z_OBJ_P(return_value), NULL, num_args, params);
for (i = 0; i < num_args; i++) {
zval_ptr_dtor(&params[i]);
}
zend_call_known_function(
constructor, Z_OBJ_P(return_value), Z_OBJCE_P(return_value), NULL,
num_args, params, named_params);
if (EG(exception)) {
zend_object_store_ctor_failed(Z_OBJ_P(return_value));
@ -4734,11 +4701,10 @@ ZEND_METHOD(ReflectionClass, newInstanceWithoutConstructor)
/* {{{ Returns an instance of this class */
ZEND_METHOD(ReflectionClass, newInstanceArgs)
{
zval *val;
reflection_object *intern;
zend_class_entry *ce, *old_scope;
int i, argc = 0;
HashTable *args;
int argc = 0;
HashTable *args = NULL;
zend_function *constructor;
GET_REFLECTION_OBJECT_PTR(ce);
@ -4747,8 +4713,8 @@ ZEND_METHOD(ReflectionClass, newInstanceArgs)
RETURN_THROWS();
}
if (ZEND_NUM_ARGS() > 0) {
argc = args->nNumOfElements;
if (args) {
argc = zend_hash_num_elements(args);
}
if (UNEXPECTED(object_init_ex(return_value, ce) != SUCCESS)) {
@ -4762,31 +4728,14 @@ ZEND_METHOD(ReflectionClass, newInstanceArgs)
/* Run the constructor if there is one */
if (constructor) {
zval *params = NULL;
if (!(constructor->common.fn_flags & ZEND_ACC_PUBLIC)) {
zend_throw_exception_ex(reflection_exception_ptr, 0, "Access to non-public constructor of class %s", ZSTR_VAL(ce->name));
zval_ptr_dtor(return_value);
RETURN_NULL();
}
if (argc) {
params = safe_emalloc(sizeof(zval), argc, 0);
argc = 0;
ZEND_HASH_FOREACH_VAL(args, val) {
ZVAL_COPY(&params[argc], val);
argc++;
} ZEND_HASH_FOREACH_END();
}
zend_call_known_instance_method(constructor, Z_OBJ_P(return_value), NULL, argc, params);
if (params) {
for (i = 0; i < argc; i++) {
zval_ptr_dtor(&params[i]);
}
efree(params);
}
zend_call_known_function(
constructor, Z_OBJ_P(return_value), Z_OBJCE_P(return_value), NULL, 0, NULL, args);
if (EG(exception)) {
zend_object_store_ctor_failed(Z_OBJ_P(return_value));
@ -6273,12 +6222,17 @@ ZEND_METHOD(ReflectionAttribute, getArguments)
RETURN_THROWS();
}
add_next_index_zval(return_value, &tmp);
if (attr->data->args[i].name) {
/* We ensured at compile-time that there are no duplicate parameter names. */
zend_hash_add_new(Z_ARRVAL_P(return_value), attr->data->args[i].name, &tmp);
} else {
add_next_index_zval(return_value, &tmp);
}
}
}
/* }}} */
static int call_attribute_constructor(zend_class_entry *ce, zend_object *obj, zval *args, uint32_t argc) /* {{{ */
static int call_attribute_constructor(zend_class_entry *ce, zend_object *obj, zval *args, uint32_t argc, HashTable *named_params) /* {{{ */
{
zend_function *ctor = ce->constructor;
ZEND_ASSERT(ctor != NULL);
@ -6288,7 +6242,8 @@ static int call_attribute_constructor(zend_class_entry *ce, zend_object *obj, zv
return FAILURE;
}
zend_call_known_instance_method(ctor, obj, NULL, argc, args);
zend_call_known_function(ctor, obj, obj->ce, NULL, argc, args, named_params);
if (EG(exception)) {
zend_object_store_ctor_failed(obj);
return FAILURE;
@ -6298,7 +6253,8 @@ static int call_attribute_constructor(zend_class_entry *ce, zend_object *obj, zv
}
/* }}} */
static void attribute_ctor_cleanup(zval *obj, zval *args, uint32_t argc) /* {{{ */
static void attribute_ctor_cleanup(
zval *obj, zval *args, uint32_t argc, HashTable *named_params) /* {{{ */
{
if (obj) {
zval_ptr_dtor(obj);
@ -6313,6 +6269,10 @@ static void attribute_ctor_cleanup(zval *obj, zval *args, uint32_t argc) /* {{{
efree(args);
}
if (named_params) {
zend_array_destroy(named_params);
}
}
/* }}} */
@ -6327,8 +6287,7 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
zval obj;
zval *args = NULL;
uint32_t count;
uint32_t argc = 0;
HashTable *named_params = NULL;
if (zend_parse_parameters_none() == FAILURE) {
RETURN_THROWS();
@ -6385,31 +6344,40 @@ ZEND_METHOD(ReflectionAttribute, newInstance)
RETURN_THROWS();
}
count = attr->data->argc;
uint32_t argc = 0;
if (attr->data->argc) {
args = emalloc(attr->data->argc * sizeof(zval));
if (count) {
args = emalloc(count * sizeof(zval));
for (argc = 0; argc < attr->data->argc; argc++) {
if (FAILURE == zend_get_attribute_value(&args[argc], attr->data, argc, attr->scope)) {
attribute_ctor_cleanup(&obj, args, argc);
for (uint32_t i = 0; i < attr->data->argc; i++) {
zval val;
if (FAILURE == zend_get_attribute_value(&val, attr->data, i, attr->scope)) {
attribute_ctor_cleanup(&obj, args, i, named_params);
RETURN_THROWS();
}
if (attr->data->args[i].name) {
if (!named_params) {
named_params = zend_new_array(0);
}
zend_hash_add_new(named_params, attr->data->args[i].name, &val);
} else {
ZVAL_COPY_VALUE(&args[i], &val);
argc++;
}
}
}
if (ce->constructor) {
if (FAILURE == call_attribute_constructor(ce, Z_OBJ(obj), args, argc)) {
attribute_ctor_cleanup(&obj, args, argc);
if (FAILURE == call_attribute_constructor(ce, Z_OBJ(obj), args, argc, named_params)) {
attribute_ctor_cleanup(&obj, args, argc, named_params);
RETURN_THROWS();
}
} else if (argc) {
attribute_ctor_cleanup(&obj, args, argc);
} else if (argc || named_params) {
attribute_ctor_cleanup(&obj, args, argc, named_params);
zend_throw_error(NULL, "Attribute class %s does not have a constructor, cannot pass arguments", ZSTR_VAL(ce->name));
RETURN_THROWS();
}
attribute_ctor_cleanup(NULL, args, argc);
attribute_ctor_cleanup(NULL, args, argc, named_params);
RETURN_COPY_VALUE(&obj);
}

View File

@ -437,7 +437,7 @@ static zend_class_entry *spl_perform_autoload(zend_string *class_name, zend_stri
zval param;
ZVAL_STR(&param, class_name);
zend_call_known_function(func, alfi->obj, alfi->ce, NULL, 1, &param);
zend_call_known_function(func, alfi->obj, alfi->ce, NULL, 1, &param, NULL);
if (EG(exception)) {
break;
}

View File

@ -1904,6 +1904,7 @@ static int spl_filesystem_file_call(spl_filesystem_object *intern, zend_function
fci.retval = return_value;
fci.param_count = num_args;
fci.params = params;
fci.named_params = NULL;
ZVAL_STR(&fci.function_name, func_ptr->common.function_name);
fcic.function_handler = func_ptr;

View File

@ -1549,7 +1549,7 @@ PHP_FUNCTION(call_user_func)
ZEND_PARSE_PARAMETERS_START(1, -1)
Z_PARAM_FUNC(fci, fci_cache)
Z_PARAM_VARIADIC('*', fci.params, fci.param_count)
Z_PARAM_VARIADIC_WITH_NAMED(fci.params, fci.param_count, fci.named_params)
ZEND_PARSE_PARAMETERS_END();
fci.retval = &retval;
@ -1567,16 +1567,17 @@ PHP_FUNCTION(call_user_func)
Warning: This function is special-cased by zend_compile.c and so is usually bypassed */
PHP_FUNCTION(call_user_func_array)
{
zval *params, retval;
zval retval;
HashTable *params;
zend_fcall_info fci;
zend_fcall_info_cache fci_cache;
ZEND_PARSE_PARAMETERS_START(2, 2)
Z_PARAM_FUNC(fci, fci_cache)
Z_PARAM_ARRAY(params)
Z_PARAM_ARRAY_HT(params)
ZEND_PARSE_PARAMETERS_END();
zend_fcall_info_args(&fci, params);
fci.named_params = params;
fci.retval = &retval;
if (zend_call_function(&fci, &fci_cache) == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
@ -1585,8 +1586,6 @@ PHP_FUNCTION(call_user_func_array)
}
ZVAL_COPY_VALUE(return_value, &retval);
}
zend_fcall_info_args_clear(&fci, 1);
}
/* }}} */

View File

@ -464,6 +464,7 @@ static void xml_call_handler(xml_parser *parser, zval *handler, zend_function *f
fci.retval = retval;
fci.param_count = argc;
fci.params = argv;
fci.named_params = NULL;
result = zend_call_function(&fci, NULL);
if (result == FAILURE) {

View File

@ -219,6 +219,7 @@ static void xsl_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, int t
}
fci.size = sizeof(fci);
fci.named_params = NULL;
if (fci.param_count > 0) {
fci.params = args;
} else {