diff --git a/ext/spl/ArrayIteratorBuiltin.php b/ext/spl/ArrayIteratorBuiltin.php index a51503a7a61..414e0ee4e30 100644 --- a/ext/spl/ArrayIteratorBuiltin.php +++ b/ext/spl/ArrayIteratorBuiltin.php @@ -257,9 +257,15 @@ public function execute(Frame $frame): void ArrayIteratorBuiltin::CLASS_LC, 'ArrayIterator::seek()' ); - // php-src zim_ArrayIterator_seek — exactly 1 user arg (#30963) + // php-src zim_ArrayIterator_seek — exactly 1 user arg (#30963); Z_PARAM_LONG soft-null (#31730). $this->requireExactUserArgCount($frame, 'ArrayIterator::seek', 1); - $offset = $frame->calledArgs[1]->resolveIndirect()->toInt(); + $offset = VmMath::parseZParamLongBuiltinArgForFrame( + $frame, + 1, + 'ArrayIterator::seek', + 1, + 'offset' + ); ArrayIteratorBuiltin::seek($object, $offset); } } diff --git a/test/compliance/cases/stdlib/arrayiterator_seek_null_soft.phpt b/test/compliance/cases/stdlib/arrayiterator_seek_null_soft.phpt new file mode 100644 index 00000000000..239fc8eff82 --- /dev/null +++ b/test/compliance/cases/stdlib/arrayiterator_seek_null_soft.phpt @@ -0,0 +1,24 @@ +--TEST-- +ArrayIterator::seek(null) — soft-null DEP then offset 0 (#31730) +--FILE-- +seek(null); + echo 'key=' . $ai->key() . ' current=' . $ai->current() . "\n"; +} catch (Throwable $e) { + echo get_class($e) . ': ' . $e->getMessage() . "\n"; +} +?> +--EXPECT-- +DEP:ArrayIterator::seek(): Passing null to parameter #1 ($offset) of type int is deprecated +key=0 current=10 diff --git a/test/unit/Issue31730ArrayIteratorSeekNullTest.php b/test/unit/Issue31730ArrayIteratorSeekNullTest.php new file mode 100644 index 00000000000..ff435e530e1 --- /dev/null +++ b/test/unit/Issue31730ArrayIteratorSeekNullTest.php @@ -0,0 +1,118 @@ +runBin('bin/vm.php', $this->softProbeCode()); + $this->assertSame($this->expectedSoftOutput(), $out); + } + + public function testJitSeekNullDeprecationThenOffsetZero(): void + { + $out = $this->runBin('bin/jit.php', $this->softProbeCode()); + $this->assertSame($this->expectedSoftOutput(), $out); + } + + public function testVmStrictTypesSeekNullTypeError(): void + { + $out = $this->runBin('bin/vm.php', $this->strictProbeCode()); + $this->assertSame( + "TypeError: ArrayIterator::seek(): Argument #1 (\$offset) must be of type int, null given\n", + $out + ); + } + + public function testJitStrictTypesSeekNullTypeError(): void + { + $out = $this->runBin('bin/jit.php', $this->strictProbeCode()); + $this->assertSame( + "TypeError: ArrayIterator::seek(): Argument #1 (\$offset) must be of type int, null given\n", + $out + ); + } + + private function expectedSoftOutput(): string + { + return "DEP:ArrayIterator::seek(): Passing null to parameter #1 (\$offset) of type int is deprecated\n" + ."key=0 current=10\n"; + } + + private function softProbeCode(): string + { + return <<<'PHP' +seek(null); + echo 'key=' . $ai->key() . ' current=' . $ai->current() . "\n"; +} catch (Throwable $e) { + echo get_class($e) . ': ' . $e->getMessage() . "\n"; +} +PHP; + } + + private function strictProbeCode(): string + { + return <<<'PHP' +seek(null); + echo "ok\n"; +} catch (Throwable $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +} +PHP; + } + + private function runBin(string $bin, string $code): string + { + $repo = dirname(__DIR__, 2); + $tmp = tempnam(sys_get_temp_dir(), 'phpc_31730_'); + $this->assertNotFalse($tmp); + file_put_contents($tmp, $code); + $env = $_ENV; + LlvmToolchain::applyProcessEnv($env, $repo); + $proc = proc_open( + ['php', $repo.'/'.$bin, $tmp], + [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], + $pipes, + $repo, + $env + ); + $this->assertIsResource($proc); + fclose($pipes[0]); + $stdout = stream_get_contents($pipes[1]); + $stderr = stream_get_contents($pipes[2]); + fclose($pipes[1]); + fclose($pipes[2]); + $exit = proc_close($proc); + @unlink($tmp); + $this->assertSame(0, $exit, $stdout.$stderr); + + return $stdout; + } +}