From aa0469a156caa8a175725b927e05daa01ab4a77e Mon Sep 17 00:00:00 2001 From: PurHur Date: Sun, 7 Jun 2026 06:19:18 +0000 Subject: [PATCH] VmFsUnlink/VmFsGlob: bootstrap without ext/ffi (#7314) Route VM unlink/glob through host libc when FFI is disabled or unavailable, matching the JIT/AOT native paths. Adds PHP_COMPILER_DISABLE_FFI gate and compliance/unit coverage for bootstrap temp-file cleanup. Co-authored-by: Cursor --- ext/standard/VmFsGlob.php | 56 +++++++++++++++++-- ext/standard/VmFsUnlink.php | 26 +++++++-- .../cases/stdlib/unlink_bootstrap.phpt | 14 +++++ test/unit/VmFsUnlinkBootstrapTest.php | 36 ++++++++++++ test/unit/VmFsUnlinkTest.php | 4 +- 5 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 test/compliance/cases/stdlib/unlink_bootstrap.phpt create mode 100644 test/unit/VmFsUnlinkBootstrapTest.php diff --git a/ext/standard/VmFsGlob.php b/ext/standard/VmFsGlob.php index a9d9565a5..82b8123e8 100644 --- a/ext/standard/VmFsGlob.php +++ b/ext/standard/VmFsGlob.php @@ -5,9 +5,10 @@ namespace PHPCompiler\ext\standard; /** - * glob() for VM — libc glob(3) via FFI, matching __phpc_glob_vec (issue #4859, #1153). + * glob() for VM — libc glob(3) via FFI, host glob(), or PHP fallback (#4859, #7314). * * php-src: ext/standard/dir.c — PHP_FUNCTION(glob) + * JIT/AOT: __phpc_glob_vec via StringFsGlobVecJit.php */ final class VmFsGlob { @@ -23,20 +24,65 @@ public static function glob(string $pattern, int $flags = 0) $onlyDir = 0 !== ($flags & StdlibConstants::GLOB_ONLYDIR); $libcFlags = $flags & StdlibConstants::GLOB_AVAILABLE_FLAGS & ~StdlibConstants::GLOB_ONLYDIR; - $libc = self::libcGlob($pattern, $libcFlags, $onlyDir); - if (null !== $libc) { - return $libc; + if (self::ffiEnabled()) { + $libc = self::libcGlob($pattern, $libcFlags, $onlyDir); + if (null !== $libc) { + return $libc; + } + } + + $host = self::hostGlob($pattern, $flags, $onlyDir, $libcFlags); + if (null !== $host) { + return $host; } return self::globFallback($pattern, $libcFlags, $onlyDir); } + private static function ffiEnabled(): bool + { + $v = getenv('PHP_COMPILER_DISABLE_FFI'); + if (false !== $v && '' !== $v && '0' !== $v && 'false' !== strtolower($v)) { + return false; + } + + return true; + } + + /** + * Host Zend glob() when VM driver runs under php-src (no ext/ffi required). + * + * @return list|false|null null when host glob unavailable + */ + private static function hostGlob(string $pattern, int $flags, bool $onlyDir, int $libcFlags): array|false|null + { + if (!\function_exists('glob')) { + return null; + } + $hostFlags = $libcFlags | ($onlyDir ? StdlibConstants::GLOB_ONLYDIR : 0); + $matches = @\glob($pattern, $hostFlags); + if (false === $matches) { + return false; + } + if (!$onlyDir) { + return $matches; + } + $filtered = []; + foreach ($matches as $path) { + if (self::pathIsDir($path)) { + $filtered[] = $path; + } + } + + return $filtered; + } + /** * @return list|false|null null when FFI/libc path unavailable (use fallback) */ private static function libcGlob(string $pattern, int $libcFlags, bool $onlyDir): array|false|null { - if (!\extension_loaded('ffi')) { + if (!self::ffiEnabled() || !\extension_loaded('ffi')) { return null; } try { diff --git a/ext/standard/VmFsUnlink.php b/ext/standard/VmFsUnlink.php index 319c7bebc..09f96446e 100644 --- a/ext/standard/VmFsUnlink.php +++ b/ext/standard/VmFsUnlink.php @@ -5,7 +5,10 @@ namespace PHPCompiler\ext\standard; /** - * unlink(2) for VM without calling host PHP unlink() (bootstrap #5063, php-src filestat.c). + * unlink(2) for VM — libc FFI when allowed; host unlink() otherwise (#5063, #7314). + * + * php-src: ext/standard/filestat.c — php_unlink + * JIT/AOT: ext/standard/JitUnlink.php calls libc unlink(2) directly. */ final class VmFsUnlink { @@ -16,12 +19,27 @@ public static function unlink(string $path): bool if (str_contains($path, "\0")) { return false; } - $ffi = self::ffi(); - if (null === $ffi) { + if (self::ffiEnabled()) { + $ffi = self::ffi(); + if (null !== $ffi) { + return 0 === (int) $ffi->unlink($path); + } + } + if (\function_exists('unlink')) { + return @\unlink($path); + } + + return false; + } + + private static function ffiEnabled(): bool + { + $v = getenv('PHP_COMPILER_DISABLE_FFI'); + if (false !== $v && '' !== $v && '0' !== $v && 'false' !== strtolower($v)) { return false; } - return 0 === (int) $ffi->unlink($path); + return true; } private static function ffi(): ?\FFI diff --git a/test/compliance/cases/stdlib/unlink_bootstrap.phpt b/test/compliance/cases/stdlib/unlink_bootstrap.phpt new file mode 100644 index 000000000..c8c1cdacd --- /dev/null +++ b/test/compliance/cases/stdlib/unlink_bootstrap.phpt @@ -0,0 +1,14 @@ +--TEST-- +stdlib unlink() VM bootstrap without ext/ffi (#7314) +--FILE-- +markTestSkipped('host unlink() unavailable'); + } + $path = tempnam(sys_get_temp_dir(), 'phpc_vm_unlink_boot_'); + $this->assertNotFalse($path); + $this->assertFileExists($path); + + $prev = getenv('PHP_COMPILER_DISABLE_FFI'); + putenv('PHP_COMPILER_DISABLE_FFI=1'); + try { + $this->assertTrue(VmFsUnlink::unlink($path)); + $this->assertFileDoesNotExist($path); + $this->assertFalse(VmFsUnlink::unlink($path)); + } finally { + if (false === $prev) { + putenv('PHP_COMPILER_DISABLE_FFI'); + } else { + putenv('PHP_COMPILER_DISABLE_FFI='.$prev); + } + } + } +} diff --git a/test/unit/VmFsUnlinkTest.php b/test/unit/VmFsUnlinkTest.php index b304750b1..3fa91f80e 100644 --- a/test/unit/VmFsUnlinkTest.php +++ b/test/unit/VmFsUnlinkTest.php @@ -12,8 +12,8 @@ final class VmFsUnlinkTest extends TestCase { public function testLibcUnlinkRemovesFile(): void { - if (!\extension_loaded('ffi')) { - $this->markTestSkipped('FFI extension required for VmFsUnlink'); + if (!\extension_loaded('ffi') && !\function_exists('unlink')) { + $this->markTestSkipped('FFI or host unlink() required for VmFsUnlink'); } $path = tempnam(sys_get_temp_dir(), 'phpc_vm_unlink_'); $this->assertNotFalse($path);