diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-04 03:03:10 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-04 05:43:20 +0900 |
| commit | 6cb1849473792bd73dbfb6265d363f149f687572 (patch) | |
| tree | 78f02030ac91929f449072d504673e9e6a21e2a2 /crates/shirabe-php-rpc | |
| parent | a02fc7d728a9973a3275a0f47604081c4439b424 (diff) | |
| download | php-shirabe-6cb1849473792bd73dbfb6265d363f149f687572.tar.gz php-shirabe-6cb1849473792bd73dbfb6265d363f149f687572.tar.zst php-shirabe-6cb1849473792bd73dbfb6265d363f149f687572.zip | |
fix(plugin): resolve review findings in the plugin activation flow
InstallationManager::execute now takes &self (the mock recorder moved
into a RefCell) and its callers hold only shared borrows: plugin
registration inside a batch re-enters the same manager handle through
Composer::getInstallationManager()->getInstallPath(), which panicked
on the RefCell re-borrow under the &mut shape — the same re-entrancy
the repository side already fixed, unreachable from the ported tests
because they call PluginInstaller::install directly like PHPUnit does.
The worker-side InstalledVersions mirror now matches the full tail of
FilesystemRepository::write: unconditional reload plus the
reflection-based selfDir/installedIsLocalDir restore. The previous
class_exists(false) guard rested on a lazy-load assumption that does
not hold in the worker (its real ClassLoader only knows the Composer
checkout's vendor dir, so a later lazy load would read the checkout's
installed.php, not the project's); the mirror is now skipped only when
the class is not autoloadable at all, i.e. no plugin runtime and hence
no observer code. Boot-time seeding stays TODO(plugin).
Also from the review: registered_plugins entries are removed only
after the deactivate/uninstall loop (PHP unsets last, and a throw must
leave the entry observable); extra.class keeps associative-array
values and fails loudly on non-strings instead of silently dropping
them; the two discarded write() results now propagate (they carry the
reload-push failure); register_package's allow-plugins skip message is
DEBUG like the addPlugin side; the loader-eviction divergence of
REGISTERED_LOADERS and the lossy UTF-8 spots carry searchable
markers; the test-only proxy downcast follows the __ naming rule; the
R-table dispatch clones the entity out instead of holding the table
borrow across the handler; the IO/PartialComposer stubs turn a
plugin-side `new NullIO()` into an explicit error instead of an
ArgumentCountError; and the empty() emulation covers float 0.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-rpc')
| -rw-r--r-- | crates/shirabe-php-rpc/php/stubs/Composer/IO/BaseIO.php | 10 | ||||
| -rw-r--r-- | crates/shirabe-php-rpc/php/stubs/Composer/PartialComposer.php | 10 | ||||
| -rw-r--r-- | crates/shirabe-php-rpc/php/worker.php | 42 | ||||
| -rw-r--r-- | crates/shirabe-php-rpc/src/lib.rs | 6 |
4 files changed, 52 insertions, 16 deletions
diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/IO/BaseIO.php b/crates/shirabe-php-rpc/php/stubs/Composer/IO/BaseIO.php index 3a268c22..51cdbe08 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/IO/BaseIO.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/IO/BaseIO.php @@ -16,8 +16,16 @@ abstract class BaseIO implements IOInterface, \ShirabeRustStub /** @var int */ protected $__epoch; - public function __construct(int $rhandle, int $epoch) + public function __construct(int $rhandle = 0, int $epoch = 0) { + if (func_num_args() < 2) { + // Constructing the class from plugin code (a common idiom for e.g. `new BufferIO()`) + // is an open question of the plugin design; only proxy instantiation passes a + // Rust handle. Fail with a diagnosable message instead of an ArgumentCountError. + throw new \RuntimeException( + 'Shirabe does not support constructing ' . static::class . ' inside the plugin process yet' + ); + } $this->__rhandle = $rhandle; $this->__epoch = $epoch; } diff --git a/crates/shirabe-php-rpc/php/stubs/Composer/PartialComposer.php b/crates/shirabe-php-rpc/php/stubs/Composer/PartialComposer.php index 7f085178..2641b267 100644 --- a/crates/shirabe-php-rpc/php/stubs/Composer/PartialComposer.php +++ b/crates/shirabe-php-rpc/php/stubs/Composer/PartialComposer.php @@ -21,8 +21,16 @@ class PartialComposer implements \ShirabeRustStub /** @var int */ protected $__epoch; - public function __construct(int $rhandle, int $epoch) + public function __construct(int $rhandle = 0, int $epoch = 0) { + if (func_num_args() < 2) { + // Constructing the class from plugin code (a common idiom for e.g. `new BufferIO()`) + // is an open question of the plugin design; only proxy instantiation passes a + // Rust handle. Fail with a diagnosable message instead of an ArgumentCountError. + throw new \RuntimeException( + 'Shirabe does not support constructing ' . static::class . ' inside the plugin process yet' + ); + } $this->__rhandle = $rhandle; $this->__epoch = $epoch; } diff --git a/crates/shirabe-php-rpc/php/worker.php b/crates/shirabe-php-rpc/php/worker.php index d8f10e93..50e9ef76 100644 --- a/crates/shirabe-php-rpc/php/worker.php +++ b/crates/shirabe-php-rpc/php/worker.php @@ -365,11 +365,6 @@ final class ShirabeRpcRuntime } } - /** - * Registers the script-class autoloader: classes referenced by composer.json scripts are - * resolved by asking the Rust-side ClassLoader (built by EventDispatcher::makeAutoloader) - * where the class file lives. Handle 0 is the runtime service endpoint on the Rust side. - */ /** Re-prepends the stub autoloader so it precedes any autoloader registered since. */ public static function ensureStubAutoloaderPriority(): void { @@ -380,6 +375,11 @@ final class ShirabeRpcRuntime spl_autoload_register(self::$stubAutoloader, true, true); } + /** + * Registers the script-class autoloader: classes referenced by composer.json scripts are + * resolved by asking the Rust-side ClassLoader (built by EventDispatcher::makeAutoloader) + * where the class file lives. Handle 0 is the runtime service endpoint on the Rust side. + */ public static function enableScriptAutoloader(): void { if (self::$scriptAutoloaderRegistered) { @@ -577,13 +577,33 @@ ShirabeRpcRuntime::$dispatch = [ } return true; }, - // Mirrors FilesystemRepository::write's in-process `InstalledVersions::reload($versions)` - // into this child. The class_exists guard (no autoload) matches the upstream observable - // behavior: when the class was never loaded here, a later lazy load reads the - // freshly-written installed.php anyway. + // Mirrors the tail of FilesystemRepository::write into this child: the unconditional + // `InstalledVersions::reload($versions)` plus the reflection-based selfDir / + // installedIsLocalDir restore. Skipped only when the class is not even autoloadable here + // (the Composer PHP runtime was never loaded): without it no code in this process can + // observe InstalledVersions at all. + // TODO(plugin): seeding the initial state when the plugin runtime boots (the Factory-time + // safelyLoadInstalledVersions of the project's installed.php) is not wired yet; until the + // first write of a run, a plugin observes an unseeded InstalledVersions. '__shirabe_installed_versions_reload' => static function ($args) { - if (class_exists('Composer\\InstalledVersions', false)) { - \Composer\InstalledVersions::reload($args[0]); + [$versions, $repoDir] = $args; + if (!class_exists('Composer\\InstalledVersions')) { + return true; + } + \Composer\InstalledVersions::reload($versions); + try { + $reflProp = new ReflectionProperty(\Composer\InstalledVersions::class, 'selfDir'); + (\PHP_VERSION_ID < 80100) and $reflProp->setAccessible(true); + $reflProp->setValue(null, strtr($repoDir, '\\', '/')); + + $reflProp = new ReflectionProperty(\Composer\InstalledVersions::class, 'installedIsLocalDir'); + (\PHP_VERSION_ID < 80100) and $reflProp->setAccessible(true); + $reflProp->setValue(null, true); + } catch (ReflectionException $e) { + if (preg_match('{Property .*? does not exist}i', $e->getMessage()) !== 1) { + throw $e; + } + // noop, an outdated InstalledVersions class simply lacks the properties } return true; }, diff --git a/crates/shirabe-php-rpc/src/lib.rs b/crates/shirabe-php-rpc/src/lib.rs index cfee5e4c..9c5dae55 100644 --- a/crates/shirabe-php-rpc/src/lib.rs +++ b/crates/shirabe-php-rpc/src/lib.rs @@ -508,9 +508,9 @@ fn rpc_call( send_frame(&reply)?; } Frame::ReleaseRustHandle { .. } => { - // TODO(plugin): there is no persistent R table yet (script-event handles are - // scoped to a single dispatched call), so stub destructor notifications carry no - // state to clean up. + // TODO(plugin): R-table garbage collection is deferred — the shirabe crate + // keeps its entries alive for the worker's lifetime, and per-call script-event + // handles carry no state either, so the notification is dropped here. continue; } Frame::EpochBump { .. } => { |
