From 7f46445a38ad080713c198ea81f50b2c8dbd6f34 Mon Sep 17 00:00:00 2001 From: NathanFreeman <1056159381@qq.com> Date: Sat, 8 Aug 2026 16:01:07 +0800 Subject: [PATCH 1/3] support strict_types --- src/Translator.php | 40 ++++++++++++++++++++++++ tests/compiler/declare/strict_types.phpt | 17 ++++++++++ 2 files changed, 57 insertions(+) create mode 100644 tests/compiler/declare/strict_types.phpt diff --git a/src/Translator.php b/src/Translator.php index d3426cc3..8b0f1087 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -94,6 +94,7 @@ class Translator extends Preprocessor */ protected array $classCeList = []; protected array $classCeInfo = []; + private int $strictTypes = 0; protected function isConstructorNativeFunction(FunctionDef $func): bool { @@ -2416,6 +2417,12 @@ CODE; $this->resetMethod(); $this->resetFunction(); + /** + * Here, the value of strict_types needs to be reset, + * as not every file has declare(strict_types=1); set. + */ + $this->setStrictTypes(0); + $cppCode = ''; foreach ($stmts as $v) { $type = $v->getType(); @@ -2569,6 +2576,8 @@ CODE; } elseif ($key === 'strict_types') { 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'); + } else { + $this->setStrictTypes(1); } } else { $this->fatalError($v, 'declare(' . $key . '=' . $value . ') is not supported'); @@ -3246,6 +3255,18 @@ CODE; if ($functionDef->argCountRequired > 0) { $cppCode .= $this->genWrapperRequiredArgCountCheck($functionDef, $displayName); } + + /** + * If the current file declares `declare(strict_types=1);`, the current function must be marked with the `ZEND_ACC_STRICT_TYPES` flag. + * This flag is used to ensure that when the function calls other functions through the ZendVM, it strictly validates argument types + * according to the declared mode. + * + * It is important to note that this mechanism only applies to functions dynamically invoked by the ZendVM. + * If the target function is compiled native code, type checking will not be triggered—this is by design in the ZendVM, + * whose philosophy is to trust the C code implementation of compiled internal functions. + */ + $cppCode .= $this->genStrictTypesCode(); + foreach ($functionDef->argInfoList as $k => $argInfo) { $var = 'arg_' . $argInfo->name; if ($argInfo->variadic) { @@ -4981,4 +5002,23 @@ CODE; return $code; } + + private function genStrictTypesCode(): string + { + /** + * Type checking is enabled only in binary mode. Since binary mode runs as a standalone executable, + * it must preserve behavioral semantics consistent with directly executed PHP code, + * so type checking must be retained to ensure safety. + * Conversely, when compiled as an extension (e.g., ext/curl), the compilation side does not need to + * enforce built-in type checking; whether it is enabled should be left to the calling PHP code to decide. + */ + return $this->strictTypes == 1 && $this->isBuildModeBin() && !$this->isWasiTarget() + ? "execute_data->func->common.fn_flags |= ZEND_ACC_STRICT_TYPES;" . PHP_EOL + : ''; + } + + private function setStrictTypes(int $value): void + { + $this->strictTypes = $value; + } } diff --git a/tests/compiler/declare/strict_types.phpt b/tests/compiler/declare/strict_types.phpt new file mode 100644 index 00000000..b2cfc285 --- /dev/null +++ b/tests/compiler/declare/strict_types.phpt @@ -0,0 +1,17 @@ +--TEST-- +declare: strict types 1 +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: parse_url(): Argument #1 ($url) must be of type string, int given in %s:%d +Stack trace: +#0 %s +#1 %s +#2 {main} + thrown in %s on line %d + From b69582011a143a1047a9e11f660f07ad81f8f12f Mon Sep 17 00:00:00 2001 From: NathanFreeman <1056159381@qq.com> Date: Sat, 8 Aug 2026 16:42:38 +0800 Subject: [PATCH 2/3] Force enable type checking --- src/Translator.php | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/src/Translator.php b/src/Translator.php index 8b0f1087..ffc160d9 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -94,7 +94,6 @@ class Translator extends Preprocessor */ protected array $classCeList = []; protected array $classCeInfo = []; - private int $strictTypes = 0; protected function isConstructorNativeFunction(FunctionDef $func): bool { @@ -2417,12 +2416,6 @@ CODE; $this->resetMethod(); $this->resetFunction(); - /** - * Here, the value of strict_types needs to be reset, - * as not every file has declare(strict_types=1); set. - */ - $this->setStrictTypes(0); - $cppCode = ''; foreach ($stmts as $v) { $type = $v->getType(); @@ -3257,9 +3250,9 @@ CODE; } /** - * If the current file declares `declare(strict_types=1);`, the current function must be marked with the `ZEND_ACC_STRICT_TYPES` flag. - * This flag is used to ensure that when the function calls other functions through the ZendVM, it strictly validates argument types - * according to the declared mode. + * Tthe current function must be marked with the `ZEND_ACC_STRICT_TYPES` flag. + * This flag is used to ensure that when the function calls other functions through the ZendVM, + * it strictly validates argument types according to the declared mode. * * It is important to note that this mechanism only applies to functions dynamically invoked by the ZendVM. * If the target function is compiled native code, type checking will not be triggered—this is by design in the ZendVM, @@ -5005,20 +4998,6 @@ CODE; private function genStrictTypesCode(): string { - /** - * Type checking is enabled only in binary mode. Since binary mode runs as a standalone executable, - * it must preserve behavioral semantics consistent with directly executed PHP code, - * so type checking must be retained to ensure safety. - * Conversely, when compiled as an extension (e.g., ext/curl), the compilation side does not need to - * enforce built-in type checking; whether it is enabled should be left to the calling PHP code to decide. - */ - return $this->strictTypes == 1 && $this->isBuildModeBin() && !$this->isWasiTarget() - ? "execute_data->func->common.fn_flags |= ZEND_ACC_STRICT_TYPES;" . PHP_EOL - : ''; - } - - private function setStrictTypes(int $value): void - { - $this->strictTypes = $value; + return "execute_data->func->common.fn_flags |= ZEND_ACC_STRICT_TYPES;" . PHP_EOL; } } From 525a4e4412496315fbcd17185d56f6be059ee5d4 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Sat, 8 Aug 2026 20:39:08 +0800 Subject: [PATCH 3/3] fix: register TypePHP functions with strict types --- src/Translator.php | 27 +++---------- src/gen_stub.php | 4 +- .../declare/strict_types-default.phpt | 38 +++++++++++++++++++ tests/compiler/declare/strict_types.phpt | 18 ++++----- 4 files changed, 55 insertions(+), 32 deletions(-) create mode 100644 tests/compiler/declare/strict_types-default.phpt diff --git a/src/Translator.php b/src/Translator.php index ffc160d9..177cd755 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -898,11 +898,11 @@ CODE; } $fullName = $functionDef->getNamespacedName(); $zifName = $this->escapeZendFnName($fullName); - if ($functionDef->namespace) { - $code .= $this->getIndent() . 'ZEND_NAMED_FE("' . $this->escapeString($fullName) . '", ZEND_FN(' . $zifName . '), arginfo_' . $zifName . ')' . PHP_EOL; - } else { - $code .= $this->getIndent() . 'ZEND_FE(' . $zifName . ', arginfo_' . $zifName . ')' . PHP_EOL; - } + // TypePHP is always strict. Store the flag in the registered + // zend_function instead of rewriting shared metadata on every call. + $code .= $this->getIndent() . 'ZEND_RAW_FENTRY("' . $this->escapeString($fullName) + . '", ZEND_FN(' . $zifName . '), arginfo_' . $zifName + . ', ZEND_ACC_STRICT_TYPES, NULL, NULL)' . PHP_EOL; } $code .= $this->getIndent() . "ZEND_FE_END\n};\n// clang-format on" . PHP_EOL . PHP_EOL; @@ -2569,8 +2569,6 @@ CODE; } elseif ($key === 'strict_types') { 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'); - } else { - $this->setStrictTypes(1); } } else { $this->fatalError($v, 'declare(' . $key . '=' . $value . ') is not supported'); @@ -3249,17 +3247,6 @@ CODE; $cppCode .= $this->genWrapperRequiredArgCountCheck($functionDef, $displayName); } - /** - * Tthe current function must be marked with the `ZEND_ACC_STRICT_TYPES` flag. - * This flag is used to ensure that when the function calls other functions through the ZendVM, - * it strictly validates argument types according to the declared mode. - * - * It is important to note that this mechanism only applies to functions dynamically invoked by the ZendVM. - * If the target function is compiled native code, type checking will not be triggered—this is by design in the ZendVM, - * whose philosophy is to trust the C code implementation of compiled internal functions. - */ - $cppCode .= $this->genStrictTypesCode(); - foreach ($functionDef->argInfoList as $k => $argInfo) { $var = 'arg_' . $argInfo->name; if ($argInfo->variadic) { @@ -4996,8 +4983,4 @@ CODE; return $code; } - private function genStrictTypesCode(): string - { - return "execute_data->func->common.fn_flags |= ZEND_ACC_STRICT_TYPES;" . PHP_EOL; - } } diff --git a/src/gen_stub.php b/src/gen_stub.php index 779d39c8..51d9a8b2 100755 --- a/src/gen_stub.php +++ b/src/gen_stub.php @@ -1688,7 +1688,9 @@ class FuncInfo { private function getArginfoFlagsByPhpVersions(): VersionFlags { - $flags = []; + // TypePHP is always strict. Register functions and methods with the + // flag once so dynamic calls made by native bodies inherit that mode. + $flags = ["ZEND_ACC_STRICT_TYPES"]; if ($this->isMethod()) { if ($this->flags & Modifiers::PROTECTED) { diff --git a/tests/compiler/declare/strict_types-default.phpt b/tests/compiler/declare/strict_types-default.phpt new file mode 100644 index 00000000..a14b9410 --- /dev/null +++ b/tests/compiler/declare/strict_types-default.phpt @@ -0,0 +1,38 @@ +--TEST-- +TypePHP enables strict types without a declare directive +--FILE-- + +--EXPECT-- +main=TypeError +function=TypeError +method=TypeError diff --git a/tests/compiler/declare/strict_types.phpt b/tests/compiler/declare/strict_types.phpt index b2cfc285..120d14cc 100644 --- a/tests/compiler/declare/strict_types.phpt +++ b/tests/compiler/declare/strict_types.phpt @@ -4,14 +4,14 @@ declare: strict types 1 ---EXPECTF-- -Fatal error: Uncaught TypeError: parse_url(): Argument #1 ($url) must be of type string, int given in %s:%d -Stack trace: -#0 %s -#1 %s -#2 {main} - thrown in %s on line %d - +--EXPECT-- +TypeError