Merge pull request '使用PHP内置的宏来检查参数数量' (#48) from args-count-check into master

Reviewed-on: #48
master
韩天峰 2 weeks ago
commit 343084c4ba
  1. 2
      src/CompilerBase.php
  2. 16
      src/Generator/ClosureGenerator.php
  3. 31
      src/Generator/ParameterCountCheckGenerator.php
  4. 30
      src/Translator.php
  5. 2
      tests/compiler/arrow_fn/003.phpt
  6. 2
      tests/compiler/callable/dynamic-method-lexical-scope.phpt
  7. 4
      tests/compiler/closure/closure-param-defaults.phpt
  8. 122
      tests/compiler/functions/args-count.phpt
  9. 4
      tests/compiler/type_decl/nullable-required-param-check.phpt

@ -35,6 +35,7 @@ use TypePhp\Generator\CallArgumentGenerator;
use TypePhp\Generator\ClosureGenerator; use TypePhp\Generator\ClosureGenerator;
use TypePhp\Generator\FiberGenerator; use TypePhp\Generator\FiberGenerator;
use TypePhp\Generator\PlaceHolderGenerator; use TypePhp\Generator\PlaceHolderGenerator;
use TypePhp\Generator\ParameterCountCheckGenerator;
use TypePhp\Generator\PropertyPromotion; use TypePhp\Generator\PropertyPromotion;
use TypePhp\Generator\Symbol; use TypePhp\Generator\Symbol;
use TypePhp\Generator\Utils; use TypePhp\Generator\Utils;
@ -112,6 +113,7 @@ class CompilerBase implements PropertyAccessContext
use ClosureGenerator; use ClosureGenerator;
use FiberGenerator; use FiberGenerator;
use PlaceHolderGenerator; use PlaceHolderGenerator;
use ParameterCountCheckGenerator;
use PropertyPromotion; use PropertyPromotion;
use MagicMethodDetector; use MagicMethodDetector;
use StdContainerTrait; use StdContainerTrait;

@ -150,19 +150,9 @@ trait ClosureGenerator
} }
$requiredArgCount++; $requiredArgCount++;
} }
if ($requiredArgCount > 0) {
$expected = $requiredArgCount === count($params) ? 'exactly' : 'at least'; $hasVariadic = $params !== [] && $params[array_key_last($params)]->variadic;
$message = $this->genCharPtr( $code .= $this->genParameterCountCheck($requiredArgCount, count($params), $hasVariadic);
'Too few arguments to function {closure}(), %u passed and ' . $expected . ' ' . $requiredArgCount . ' expected',
true
);
$code .= $this->getIndent() . 'if (UNEXPECTED(php::getCallArgNum() < ' . $requiredArgCount . ')) {' . PHP_EOL;
$this->indentLevel++;
$code .= $this->getIndent() . 'php::throwExceptionEx(zend_ce_argument_count_error, 0, ' . $message . ', php::getCallArgNum());' . PHP_EOL;
$code .= $this->getIndent() . 'return php::null;' . PHP_EOL;
$this->indentLevel--;
$code .= $this->getIndent() . '}' . PHP_EOL;
}
foreach ($params as $i => $param) { foreach ($params as $i => $param) {
if ($param->byRef) { if ($param->byRef) {

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace TypePhp\Generator;
trait ParameterCountCheckGenerator
{
/**
* Generate the runtime check used at a Zend-to-TypePHP call boundary.
*
* TypePHP requires dynamic calls to obey the same argument-count rules as
* statically resolved calls. This also keeps the generated internal-function
* arginfo consistent with what its wrapper accepts.
*
* PHPX owns the Zend boundary details. In particular, a bare
* ZEND_PARSE_PARAMETERS_START/END pair is invalid because ZPP expects every
* declared parameter to be consumed by a Z_PARAM_* macro.
*/
protected function genParameterCountCheck(int $requiredArgCount, int $declaredArgCount, bool $variadic): string
{
if ($requiredArgCount === 0 && $variadic) {
return '';
}
return 'php::checkCallArgCount('
. $requiredArgCount . ', '
. $declaredArgCount . ', '
. $this->escapeBool($variadic)
. ');' . PHP_EOL;
}
}

@ -3366,11 +3366,14 @@ CODE;
// returns with EG(exception) set. Convert back to normal Zend exception // returns with EG(exception) set. Convert back to normal Zend exception
// propagation at the outermost wrapper. // propagation at the outermost wrapper.
$cppCode = 'try {' . PHP_EOL; $cppCode = 'try {' . PHP_EOL;
$callParams = '';
if ($functionDef->argCountRequired > 0) {
$cppCode .= $this->genWrapperRequiredArgCountCheck($functionDef, $displayName);
}
$cppCode .= $this->genParameterCountCheck(
$functionDef->argCountRequired,
count($functionDef->argInfoList),
$functionDef->hasVariadicArg(),
);
$callParams = '';
foreach ($functionDef->argInfoList as $k => $argInfo) { foreach ($functionDef->argInfoList as $k => $argInfo) {
$var = 'arg_' . $argInfo->name; $var = 'arg_' . $argInfo->name;
if ($argInfo->variadic) { if ($argInfo->variadic) {
@ -3498,25 +3501,6 @@ CODE;
], true); ], true);
} }
private function genWrapperRequiredArgCountCheck(FunctionDef $functionDef, string $displayName): string
{
$required = $functionDef->argCountRequired;
$expected = $required === count($functionDef->argInfoList) ? 'exactly' : 'at least';
$message = $this->genCharPtr(
'Too few arguments to function ' . $displayName . '(), %u passed and ' . $expected . ' ' . $required . ' expected',
true
);
$code = $this->getIndent() . 'if (UNEXPECTED(php::getCallArgNum() < ' . $required . ')) {' . PHP_EOL;
$this->indentLevel++;
$code .= $this->getIndent() . 'php::throwExceptionEx(zend_ce_argument_count_error, 0, ' . $message . ', php::getCallArgNum());' . PHP_EOL;
$code .= $this->getIndent() . 'return;' . PHP_EOL;
$this->indentLevel--;
$code .= $this->getIndent() . '}' . PHP_EOL;
return $code;
}
protected function genMethodWrapper(ClassDef $classDef, MethodDef $methodDef): string protected function genMethodWrapper(ClassDef $classDef, MethodDef $methodDef): string
{ {
$name = $classDef->getNamespacedName(); $name = $classDef->getNamespacedName();

@ -12,7 +12,7 @@ function test_basic_arrow() {
// Test arrow function with multiple parameters // Test arrow function with multiple parameters
function test_multi_param_arrow() { function test_multi_param_arrow() {
$pairs = [[1, 2], [3, 4], [5, 6]]; $pairs = [[1, 2], [3, 4], [5, 6]];
$sums = array_map(fn($a, $b) => $a + $b, ...$pairs); $sums = array_map(fn($a, $b, $_unused) => $a + $b, ...$pairs);
return $sums; return $sums;
} }

@ -5,7 +5,7 @@ Dynamic method calls use the declaring class scope
class DynamicScopeBase class DynamicScopeBase
{ {
private function privateValue(): string private function privateValue(mixed $_unused = null): string
{ {
return 'base-private'; return 'base-private';
} }

@ -48,6 +48,6 @@ array(3) {
NULL NULL
} }
string(18) "ArgumentCountError" string(18) "ArgumentCountError"
string(74) "Too few arguments to function {closure}(), 0 passed and exactly 1 expected" string(57) "stdClass::{closure}() expects exactly 1 argument, 0 given"
string(18) "ArgumentCountError" string(18) "ArgumentCountError"
string(75) "Too few arguments to function {closure}(), 0 passed and at least 1 expected" string(58) "stdClass::{closure}() expects at least 1 argument, 0 given"

@ -0,0 +1,122 @@
--TEST--
Zend wrappers validate required, optional, variadic and excessive arguments
--FILE--
<?php
declare(strict_types=1);
function noArgs() {
echo "noArgs\n";
}
function hello(string $value) {
echo "hello:$value\n";
}
class Greeter {
public function hello(string $value, string $suffix = '!'): void {
echo "method:$value$suffix\n";
}
public static function staticHello(string $value): void {}
}
function world(callable $callback) {
$callback('a');
}
function variadic(string $value, ...$results) {
var_dump($value, $results);
}
function main()
{
try {
$noArgs = 'noArgs';
$noArgs('extra');
} catch (ArgumentCountError $e) {
var_dump($e->getMessage());
}
try {
$hello = 'hello';
$hello('value', 'extra');
} catch (ArgumentCountError $e) {
var_dump($e->getMessage());
}
$noArgsClosure = static function (): void {
echo "closure\n";
};
try {
$noArgsClosure('closure-extra');
} catch (ArgumentCountError $e) {
var_dump($e->getMessage());
}
try {
$callback = 'hello';
$callback();
} catch (ArgumentCountError $e) {
var_dump($e->getMessage());
}
try {
$method = [new Greeter(), 'hello'];
$method();
} catch (ArgumentCountError $e) {
var_dump($e->getMessage());
}
try {
$method('value', '!', 'extra');
} catch (ArgumentCountError $e) {
var_dump($e->getMessage());
}
try {
$staticMethod = [Greeter::class, 'staticHello'];
$staticMethod();
} catch (ArgumentCountError $e) {
var_dump($e->getMessage());
}
try {
world(function(string $value, string $value1) {
});
} catch (ArgumentCountError $e) {
var_dump($e->getMessage());
}
try {
$callable = "variadic";
$callable();
} catch (ArgumentCountError $e) {
var_dump($e->getMessage());
}
$callable('1', 1, 2, 3, 4, 5);
}
?>
--EXPECT--
string(45) "noArgs() expects exactly 0 arguments, 1 given"
string(43) "hello() expects exactly 1 argument, 2 given"
string(58) "stdClass::{closure}() expects exactly 0 arguments, 1 given"
string(43) "hello() expects exactly 1 argument, 0 given"
string(53) "Greeter::hello() expects at least 1 argument, 0 given"
string(53) "Greeter::hello() expects at most 2 arguments, 3 given"
string(58) "Greeter::staticHello() expects exactly 1 argument, 0 given"
string(58) "stdClass::{closure}() expects exactly 2 arguments, 1 given"
string(47) "variadic() expects at least 1 argument, 0 given"
string(1) "1"
array(5) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
[3]=>
int(4)
[4]=>
int(5)
}

@ -33,6 +33,6 @@ function main(): void
?> ?>
--EXPECT-- --EXPECT--
string(18) "ArgumentCountError" string(18) "ArgumentCountError"
string(84) "Too few arguments to function expect_nullable_int(), 0 passed and exactly 1 expected" string(57) "expect_nullable_int() expects exactly 1 argument, 0 given"
string(18) "ArgumentCountError" string(18) "ArgumentCountError"
string(94) "Too few arguments to function expect_nullable_with_default(), 0 passed and at least 1 expected" string(67) "expect_nullable_with_default() expects at least 1 argument, 0 given"

Loading…
Cancel
Save