From 7e1b4a8ee32ea5d7370dec4e66e6c98dab5b6cf5 Mon Sep 17 00:00:00 2001 From: PurHur Date: Tue, 11 Aug 2026 20:11:07 +0000 Subject: [PATCH] Stdlib: memory_get_usage/peak(null) TypeError under strict_types (#30346) Honor caller strict_types for Z_PARAM_BOOL $real_usage via frame-aware VmMath bool parse (and JIT lowerCoerceZParamBool), matching php-src. Co-authored-by: Cursor --- ext/standard/VmMemory.php | 30 ++++----- ext/standard/memory_get_peak_usage.php | 20 +++++- ext/standard/memory_get_usage.php | 20 +++++- lib/JIT/JitMemoryUsageArg.php | 5 ++ phpunit.xml.dist | 2 + .../MemoryGetUsageNullStrictJITTest.php | 26 ++++++++ .../MemoryGetUsageNullStrictVMTest.php | 26 ++++++++ .../stdlib/memory_get_usage_null_strict.phpt | 25 ++++++++ .../memory_get_usage_null_strict_jit.phpt | 17 +++++ ...sue_30346_memory_get_usage_null_strict.php | 14 +++++ .../MemoryGetUsageNullStrict30346Test.php | 62 +++++++++++++++++++ 11 files changed, 228 insertions(+), 19 deletions(-) create mode 100644 test/compliance/MemoryGetUsageNullStrictJITTest.php create mode 100644 test/compliance/MemoryGetUsageNullStrictVMTest.php create mode 100644 test/compliance/cases/stdlib/memory_get_usage_null_strict.phpt create mode 100644 test/compliance/cases/stdlib/memory_get_usage_null_strict_jit.phpt create mode 100644 test/repro/issue_30346_memory_get_usage_null_strict.php create mode 100644 test/unit/MemoryGetUsageNullStrict30346Test.php diff --git a/ext/standard/VmMemory.php b/ext/standard/VmMemory.php index c8a5541922..408dadbd5b 100644 --- a/ext/standard/VmMemory.php +++ b/ext/standard/VmMemory.php @@ -4,6 +4,7 @@ namespace PHPCompiler\ext\standard; +use PHPCompiler\Frame; use PHPCompiler\VM\EnumCaseSupport; use PHPCompiler\VM\MemoryAccounting; use PHPCompiler\VM\Variable; @@ -36,14 +37,13 @@ public static function beginRequest(): void self::$peakReal = 0; } - public static function resolveUsageArg(Variable $var, string $fn): bool + /** + * Z_PARAM_BOOL $real_usage — MemoryUsage enum first, then frame-aware bool + * (strict_types → TypeError on null; soft → DEP+false) (#30346 / #21615). + */ + public static function resolveUsageArg(Frame $frame, int $argIndex, string $fn): bool { - $var = $var->resolveIndirect(); - if (Variable::TYPE_NULL === $var->type) { - VmNullNumberParamDeprecation::emit(null, $fn, 1, 'real_usage', 'bool'); - - return false; - } + $var = $frame->calledArgs[$argIndex]->resolveIndirect(); $fromEnum = self::tryMemoryUsageBool($var); if (null !== $fromEnum) { return $fromEnum; @@ -55,18 +55,14 @@ public static function resolveUsageArg(Variable $var, string $fn): bool EnumCaseSupport::typeNameForVariable($var) )); } - if (Variable::TYPE_BOOLEAN === $var->type) { - return $var->toBool(); - } - if (Variable::TYPE_INTEGER === $var->type) { - return 0 !== $var->toInt(); - } - throw new \TypeError(sprintf( - '%s(): Argument #1 ($real_usage) must be of type bool, %s given', + return VmMath::parseBoolBuiltinArgForFrame( + $frame, + $argIndex, $fn, - EnumCaseSupport::typeNameForVariable($var) - )); + 1, + 'real_usage' + ); } public static function tryMemoryUsageBool(Variable $var): ?bool diff --git a/ext/standard/memory_get_peak_usage.php b/ext/standard/memory_get_peak_usage.php index cb4b2f322d..e77cde839e 100644 --- a/ext/standard/memory_get_peak_usage.php +++ b/ext/standard/memory_get_peak_usage.php @@ -7,6 +7,9 @@ use PHPCompiler\Frame; use PHPCompiler\Func\Internal; use PHPCompiler\JIT\Context; +use PHPCompiler\JIT\ExceptionBridge; +use PHPCompiler\JIT\JitNativeString; +use PHPCompiler\JIT\JitValueBox; use PHPCompiler\JIT\Variable as JITVariable; use PHPCompiler\VM\MemoryAccounting; use PHPLLVM\Value; @@ -45,6 +48,20 @@ public function call(Context $context, JITVariable ...$args): Value 'memory_get_peak_usage() expects at most 1 argument, '.\count($args).' given' ); } + // Compile-time null under strict: catchable TypeError then stop IR (#30346 peer). + if (isset($args[0]) && $context->callerStrictTypes && ( + JITVariable::TYPE_NULL === $args[0]->type || ($args[0]->isNullConstant ?? false) + )) { + JitNativeString::ensureInsertBlock($context); + ExceptionBridge::emitTypeErrorAndAbort( + $context, + 'memory_get_peak_usage(): Argument #1 ($real_usage) must be of type bool, null given' + ); + JitNativeString::ensureInsertBlock($context); + $slot = JitValueBox::alloc($context); + + return JitValueBox::pointer($context, $slot); + } return JitMemory::getPeakUsage($context, $args[0] ?? null); } @@ -60,6 +77,7 @@ private static function resolveRealUsage(Frame $frame): bool if (0 === $argc) { return false; } - return VmMemory::resolveUsageArg($frame->calledArgs[0], 'memory_get_peak_usage'); + // Z_PARAM_BOOL — caller strict_types → TypeError on null (#30346 peer). + return VmMemory::resolveUsageArg($frame, 0, 'memory_get_peak_usage'); } } diff --git a/ext/standard/memory_get_usage.php b/ext/standard/memory_get_usage.php index 96aa21de8c..69b96f189f 100644 --- a/ext/standard/memory_get_usage.php +++ b/ext/standard/memory_get_usage.php @@ -7,6 +7,9 @@ use PHPCompiler\Frame; use PHPCompiler\Func\Internal; use PHPCompiler\JIT\Context; +use PHPCompiler\JIT\ExceptionBridge; +use PHPCompiler\JIT\JitNativeString; +use PHPCompiler\JIT\JitValueBox; use PHPCompiler\JIT\Variable as JITVariable; use PHPCompiler\VM\MemoryAccounting; use PHPLLVM\Value; @@ -38,6 +41,20 @@ public function call(Context $context, JITVariable ...$args): Value 'memory_get_usage() expects at most 1 argument, '.\count($args).' given' ); } + // Compile-time null under strict: catchable TypeError then stop IR (#30346 / peer #30169). + if (isset($args[0]) && $context->callerStrictTypes && ( + JITVariable::TYPE_NULL === $args[0]->type || ($args[0]->isNullConstant ?? false) + )) { + JitNativeString::ensureInsertBlock($context); + ExceptionBridge::emitTypeErrorAndAbort( + $context, + 'memory_get_usage(): Argument #1 ($real_usage) must be of type bool, null given' + ); + JitNativeString::ensureInsertBlock($context); + $slot = JitValueBox::alloc($context); + + return JitValueBox::pointer($context, $slot); + } return JitMemory::getUsage($context, $args[0] ?? null); } @@ -53,6 +70,7 @@ private static function resolveRealUsage(Frame $frame): bool if (0 === $argc) { return false; } - return VmMemory::resolveUsageArg($frame->calledArgs[0], 'memory_get_usage'); + // Z_PARAM_BOOL — caller strict_types → TypeError on null (#30346). + return VmMemory::resolveUsageArg($frame, 0, 'memory_get_usage'); } } diff --git a/lib/JIT/JitMemoryUsageArg.php b/lib/JIT/JitMemoryUsageArg.php index 837e8a5ebb..3f0d375508 100644 --- a/lib/JIT/JitMemoryUsageArg.php +++ b/lib/JIT/JitMemoryUsageArg.php @@ -26,6 +26,11 @@ public static function lower(Context $context, ?Variable $arg, string $fn): Valu return $context->constantFromBool($compileTime); } + // Z_PARAM_BOOL — honor caller strict_types; soft-null DEP+coerce (#30346). + if (Variable::TYPE_NULL === $arg->type || ($arg->isNullConstant ?? false)) { + return JitBoolArg::lowerCoerceZParamBool($context, $arg, $fn, 'real_usage', 1); + } + if (Variable::TYPE_NATIVE_BOOL === $arg->type) { return $context->helper->loadValue($arg); } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4a23f24e78..17946bdf99 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -365,6 +365,8 @@ ./test/compliance/CompactBoolWarningActual30119JITTest.php ./test/compliance/ObjectBoolTypeErrorActual30100VMTest.php ./test/compliance/ObjectBoolTypeErrorActual30100JITTest.php + ./test/compliance/MemoryGetUsageNullStrictVMTest.php + ./test/compliance/MemoryGetUsageNullStrictJITTest.php ./test/unit diff --git a/test/compliance/MemoryGetUsageNullStrictJITTest.php b/test/compliance/MemoryGetUsageNullStrictJITTest.php new file mode 100644 index 0000000000..145fb7dcd8 --- /dev/null +++ b/test/compliance/MemoryGetUsageNullStrictJITTest.php @@ -0,0 +1,26 @@ + self::parsePHPT( + __DIR__.'/cases/stdlib/memory_get_usage_null_strict_jit.phpt', + 'memory_get_usage_null_strict_jit.phpt' + ); + } + + public function setUp(): void + { + $this->BIN = realpath(__DIR__.'/../../bin/jit.php'); + } +} diff --git a/test/compliance/MemoryGetUsageNullStrictVMTest.php b/test/compliance/MemoryGetUsageNullStrictVMTest.php new file mode 100644 index 0000000000..56557c6df2 --- /dev/null +++ b/test/compliance/MemoryGetUsageNullStrictVMTest.php @@ -0,0 +1,26 @@ + self::parsePHPT( + __DIR__.'/cases/stdlib/memory_get_usage_null_strict.phpt', + 'memory_get_usage_null_strict.phpt' + ); + } + + public function setUp(): void + { + $this->BIN = realpath(__DIR__.'/../../bin/vm.php'); + } +} diff --git a/test/compliance/cases/stdlib/memory_get_usage_null_strict.phpt b/test/compliance/cases/stdlib/memory_get_usage_null_strict.phpt new file mode 100644 index 0000000000..6319c8417b --- /dev/null +++ b/test/compliance/cases/stdlib/memory_get_usage_null_strict.phpt @@ -0,0 +1,25 @@ +--TEST-- +memory_get_usage/peak_usage(null) under strict_types TypeError (#30346, ext/standard/basic_functions.c Z_PARAM_BOOL) +--FILE-- +getMessage(), "\n"; + } +} +$n = null; +try { + var_export(memory_get_usage($n)); + echo " uncaught-var\n"; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +--EXPECT-- +memory_get_usage(): Argument #1 ($real_usage) must be of type bool, null given +memory_get_peak_usage(): Argument #1 ($real_usage) must be of type bool, null given +memory_get_usage(): Argument #1 ($real_usage) must be of type bool, null given diff --git a/test/compliance/cases/stdlib/memory_get_usage_null_strict_jit.phpt b/test/compliance/cases/stdlib/memory_get_usage_null_strict_jit.phpt new file mode 100644 index 0000000000..04c2e12cc3 --- /dev/null +++ b/test/compliance/cases/stdlib/memory_get_usage_null_strict_jit.phpt @@ -0,0 +1,17 @@ +--TEST-- +JIT: memory_get_usage/peak_usage(null) under strict_types TypeError (#30346) +--FILE-- +getMessage(), "\n"; + } +} +--EXPECT-- +memory_get_usage(): Argument #1 ($real_usage) must be of type bool, null given +memory_get_peak_usage(): Argument #1 ($real_usage) must be of type bool, null given diff --git a/test/repro/issue_30346_memory_get_usage_null_strict.php b/test/repro/issue_30346_memory_get_usage_null_strict.php new file mode 100644 index 0000000000..423c9fa677 --- /dev/null +++ b/test/repro/issue_30346_memory_get_usage_null_strict.php @@ -0,0 +1,14 @@ +getMessage(), "\n"; +} +try { + memory_get_peak_usage(null); + echo "uncaught\n"; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} diff --git a/test/unit/MemoryGetUsageNullStrict30346Test.php b/test/unit/MemoryGetUsageNullStrict30346Test.php new file mode 100644 index 0000000000..6d59e1c30e --- /dev/null +++ b/test/unit/MemoryGetUsageNullStrict30346Test.php @@ -0,0 +1,62 @@ +runPhpScript(realpath(dirname(__DIR__, 2).'/bin/vm.php')); + $this->assertSame( + "memory_get_usage(): Argument #1 (\$real_usage) must be of type bool, null given\n" + ."memory_get_peak_usage(): Argument #1 (\$real_usage) must be of type bool, null given\n", + $out + ); + } + + public function testJitTypeErrorUnderStrictTypes(): void + { + $out = $this->runPhpScript(realpath(dirname(__DIR__, 2).'/bin/jit.php')); + $this->assertStringContainsString( + 'memory_get_usage(): Argument #1 ($real_usage) must be of type bool, null given', + $out + ); + $this->assertStringContainsString( + 'memory_get_peak_usage(): Argument #1 ($real_usage) must be of type bool, null given', + $out + ); + } + + private function runPhpScript(string $bin): string + { + $root = dirname(__DIR__, 2); + $repro = realpath($root.'/test/repro/issue_30346_memory_get_usage_null_strict.php'); + $this->assertNotFalse($repro); + $cmd = [PHP_BINARY, '-d', 'error_reporting=E_ALL', '-d', 'display_errors=1', $bin, $repro]; + $descriptorSpec = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $env = []; + foreach (array_merge($_ENV, $_SERVER) as $key => $value) { + if (is_string($value) || is_int($value) || is_float($value)) { + $env[(string) $key] = (string) $value; + } + } + $proc = proc_open($cmd, $descriptorSpec, $pipes, $root, $env); + $this->assertIsResource($proc); + fclose($pipes[0]); + $stdout = stream_get_contents($pipes[1]); + fclose($pipes[1]); + $stderr = stream_get_contents($pipes[2]); + fclose($pipes[2]); + $exit = proc_close($proc); + $this->assertSame(0, $exit, (string) $stdout.(string) $stderr); + + return (string) $stdout; + } +}