diff --git a/src/Translator.php b/src/Translator.php index 52b80885..103c62e5 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -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,9 @@ CODE; if ($functionDef->argCountRequired > 0) { $cppCode .= $this->genWrapperRequiredArgCountCheck($functionDef, $displayName); } + + $cppCode .= $this->genDeclareStrictTypesCode(); + foreach ($functionDef->argInfoList as $k => $argInfo) { $var = 'arg_' . $argInfo->name; if ($argInfo->variadic) { @@ -4882,4 +4891,26 @@ CODE; return $code; } + + /** + * 若当前文件声明了 declare(strict_types=1);,则需为当前函数标记 ZEND_ACC_STRICT_TYPES 标志。 + * 该标志用于确保函数在通过 ZendVM 调用其他函数时,能够严格按照声明模式校验参数类型。 + * 需要注意的是,此机制仅作用于 ZendVM 动态调用的函数, + * 若目标函数为编译后的本地代码,则不会触发类型检查——这是 ZendVM 的设计使然,其哲学是信任已编译内部函数的 C 代码实现。 + * + * 类型检查仅在二进制模式下启用。由于二进制模式作为独立可执行程序运行,需保持和直接运行的PHP代码一致的行为语义,因此必须保留类型检查以确保安全性。 + * 相反,当编译为扩展(如 ext/curl)时,编译端无需强制内置类型检查,其开启与否应由调用方PHP代码自行决定。 + */ + private function genDeclareStrictTypesCode(): string + { + 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; + } + } diff --git a/tests/compiler/declare/Test.php b/tests/compiler/declare/Test.php new file mode 100644 index 00000000..ba8ff95c --- /dev/null +++ b/tests/compiler/declare/Test.php @@ -0,0 +1,10 @@ + +--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 diff --git a/tests/compiler/declare/strict_types_2.phpt b/tests/compiler/declare/strict_types_2.phpt new file mode 100644 index 00000000..fc538722 --- /dev/null +++ b/tests/compiler/declare/strict_types_2.phpt @@ -0,0 +1,18 @@ +--TEST-- +declare(strict_types=1): Type checking of function calls 2 +--FILE-- +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