通过ZendVM调用函数或者方法的时候,如果开启了类型检查,需要检查参数类型

pull/43/head
NathanFreeman 3 weeks ago
parent 6cfab3bf9a
commit 4c21a682a5
  1. 32
      src/Translator.php
  2. 10
      tests/compiler/declare/Test.php
  3. 16
      tests/compiler/declare/strict_types_1.phpt
  4. 18
      tests/compiler/declare/strict_types_2.phpt

@ -92,6 +92,7 @@ class Translator extends Preprocessor
*/
protected array $classCeList = [];
protected array $classCeInfo = [];
private bool $declareStrictTypes = false;
protected function isConstructorNativeFunction(FunctionDef $func): bool
{
@ -2339,6 +2340,8 @@ CODE;
$this->resetClass();
$this->resetMethod();
$this->resetFunction();
// 重置$this->declareStrictTypes,并不是每份文件都设置了declare(strict_types=1),防止污染下一份文件
$this->setDeclareStrictTypes(false);
$cppCode = '';
foreach ($stmts as $v) {
@ -2494,6 +2497,9 @@ CODE;
if (!($declare->value instanceof Node\Scalar\Int_) or $declare->value->value !== 1) {
$this->fatalError($v, 'declare(strict_types=0) is not allowed, only strict_types=1 is supported');
}
// 当前文件设置了declare(strict_types=1),我们需要保存这个状态,然后生成对应的代码。
$this->setDeclareStrictTypes(true);
} else {
$this->fatalError($v, 'declare(' . $key . '=' . $value . ') is not supported');
}
@ -3170,6 +3176,15 @@ CODE;
if ($functionDef->argCountRequired > 0) {
$cppCode .= $this->genWrapperRequiredArgCountCheck($functionDef, $displayName);
}
/**
* 若当前文件声明了 declare(strict_types=1);,则需为当前函数标记 ZEND_ACC_STRICT_TYPES 标志。
* 该标志用于确保函数在通过 ZendVM 调用其他函数时,能够严格按照声明模式校验参数类型。
* 需要注意的是,此机制仅作用于 ZendVM 动态调用的函数,
* 若目标函数为编译后的本地代码,则不会触发类型检查——这是 ZendVM 的设计使然,其哲学是信任已编译内部函数的 C 代码实现。
*/
$cppCode .= $this->genDeclareStrictTypesCode();
foreach ($functionDef->argInfoList as $k => $argInfo) {
$var = 'arg_' . $argInfo->name;
if ($argInfo->variadic) {
@ -4882,4 +4897,21 @@ CODE;
return $code;
}
private function genDeclareStrictTypesCode(): string
{
/**
* 类型检查仅在二进制模式下启用。由于二进制模式作为独立可执行程序运行,需保持和直接运行的PHP代码一致的行为语义,因此必须保留类型检查以确保安全性。
* 相反,当编译为扩展(如 ext/curl)时,编译端无需强制内置类型检查,其开启与否应由调用方PHP代码自行决定。
*/
return $this->declareStrictTypes && $this->isBuildModeBin()
? "execute_data->func->common.fn_flags |= ZEND_ACC_STRICT_TYPES;" . PHP_EOL
: '';
}
private function setDeclareStrictTypes(bool $type): void
{
$this->declareStrictTypes = $type;
}
}

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
class Test
{
public function handle(string $value)
{
}
}

@ -0,0 +1,16 @@
--TEST--
declare(strict_types=1): Type checking of function calls 1
--FILE--
<?php
declare(strict_types=1);
function main() {
ini_set(1, 1);
}
?>
--EXPECTF--
Fatal error: Uncaught TypeError: ini_set(): Argument #1 ($option) must be of type string, int given in %s:%d
Stack trace:
#0 %s
#1 %s
#2 {main}
thrown in %s on line %d

@ -0,0 +1,18 @@
--TEST--
declare(strict_types=1): Type checking of function calls 2
--FILE--
<?php
declare(strict_types=1);
function main() {
require __DIR__ . "/Test.php";
(new Test())->handle(true);
}
?>
--EXPECTF--
Fatal error: Uncaught TypeError: Test::handle(): Argument #1 ($value) must be of type string, true given in %s:%d
Stack trace:
#0 %s
#1 %s
#2 {main}
thrown in %s on line %d
Loading…
Cancel
Save