From b4ab3df2ec85fbe477d7721344a8cd3630b437a1 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 16 Aug 2026 13:59:28 +0900 Subject: feat(plugin): guard Rust-owned classes the worker has no proxy for The worker's autoloader fell through to the real Composer source for every Rust-owned FQCN without a proxy stub, so plugin code doing `new Filesystem()` or subclassing `LibraryInstaller` silently ran on a second instance the Rust side never sees. An unimplemented part of the plugin API has to fail with an explicit error naming it, not quietly work on a disconnected copy. The stub generator now emits a guard class for each of those FQCNs: the real declaration, hierarchy and constants, with every constructor and method raising an explicit error. References satisfied by the declaration alone (`instanceof`, `X::class`, `Link::TYPE_REQUIRE`) keep working. Two FQCNs stay resolvable to the real class, each listed with the worker-side mechanism that makes a natively constructed instance correct. The error had nowhere to go: `Installer::run` dropped the `Result` of both `dispatch_script` calls, so an exception from a listener ended in exit 0. Both propagate now, the way the exception does upstream. Three real-plugin E2E comparisons stop at a guard and are ignored, each naming the class it needs. Co-Authored-By: Claude Opus 5 (1M context) --- crates/shirabe-php-rpc/build.rs | 20 ++ .../php/guards/Composer/Advisory/AuditConfig.php | 24 ++ .../Composer/Advisory/IgnoredSecurityAdvisory.php | 25 ++ .../Composer/Advisory/PartialSecurityAdvisory.php | 31 +++ .../guards/Composer/Advisory/SecurityAdvisory.php | 30 +++ .../guards/Composer/Autoload/AutoloadGenerator.php | 143 ++++++++++++ .../shirabe-php-rpc/php/guards/Composer/Cache.php | 110 +++++++++ .../php/guards/Composer/Compiler.php | 22 ++ .../guards/Composer/Config/JsonConfigSource.php | 74 ++++++ .../Composer/DependencyResolver/Decisions.php | 145 ++++++++++++ .../Composer/DependencyResolver/DefaultPolicy.php | 55 +++++ .../Composer/DependencyResolver/GenericRule.php | 107 +++++++++ .../DependencyResolver/LocalRepoTransaction.php | 20 ++ .../DependencyResolver/LockTransaction.php | 37 +++ .../DependencyResolver/MultiConflictRule.php | 107 +++++++++ .../guards/Composer/DependencyResolver/Pool.php | 115 ++++++++++ .../Composer/DependencyResolver/PoolBuilder.php | 37 +++ .../Composer/DependencyResolver/PoolOptimizer.php | 22 ++ .../guards/Composer/DependencyResolver/Request.php | 126 ++++++++++ .../Composer/DependencyResolver/Rule2Literals.php | 107 +++++++++ .../guards/Composer/DependencyResolver/RuleSet.php | 78 +++++++ .../DependencyResolver/RuleSetGenerator.php | 65 ++++++ .../DependencyResolver/RuleSetIterator.php | 42 ++++ .../SecurityAdvisoryPoolFilter.php | 25 ++ .../guards/Composer/DependencyResolver/Solver.php | 58 +++++ .../Composer/DependencyResolver/Transaction.php | 39 ++++ .../guards/Composer/Downloader/FileDownloader.php | 105 +++++++++ .../Composer/Downloader/FossilDownloader.php | 100 ++++++++ .../guards/Composer/Downloader/GitDownloader.php | 149 ++++++++++++ .../guards/Composer/Downloader/GzipDownloader.php | 45 ++++ .../guards/Composer/Downloader/HgDownloader.php | 100 ++++++++ .../guards/Composer/Downloader/PathDownloader.php | 48 ++++ .../Composer/Downloader/PerforceDownloader.php | 111 +++++++++ .../guards/Composer/Downloader/PharDownloader.php | 45 ++++ .../guards/Composer/Downloader/RarDownloader.php | 45 ++++ .../guards/Composer/Downloader/SvnDownloader.php | 110 +++++++++ .../guards/Composer/Downloader/TarDownloader.php | 45 ++++ .../guards/Composer/Downloader/XzDownloader.php | 45 ++++ .../guards/Composer/Downloader/ZipDownloader.php | 55 +++++ .../php/guards/Composer/Factory.php | 134 +++++++++++ .../IgnoreListPlatformRequirementFilter.php | 34 +++ .../php/guards/Composer/Installer.php | 253 +++++++++++++++++++++ .../guards/Composer/Installer/BinaryInstaller.php | 66 ++++++ .../guards/Composer/Installer/InstallerEvent.php | 47 ++++ .../guards/Composer/Installer/LibraryInstaller.php | 104 +++++++++ .../Composer/Installer/MetapackageInstaller.php | 66 ++++++ .../guards/Composer/Installer/PluginInstaller.php | 64 ++++++ .../guards/Composer/Installer/ProjectInstaller.php | 68 ++++++ .../php/guards/Composer/Json/JsonFile.php | 81 +++++++ .../Package/Archiver/ArchivableFilesFinder.php | 24 ++ .../Composer/Package/Archiver/ArchiveManager.php | 51 +++++ .../Composer/Package/Archiver/PharArchiver.php | 27 +++ .../Composer/Package/Archiver/ZipArchiver.php | 27 +++ .../guards/Composer/Package/Loader/ArrayLoader.php | 40 ++++ .../Composer/Package/Loader/RootPackageLoader.php | 39 ++++ .../Package/Loader/ValidatingArrayLoader.php | 44 ++++ .../php/guards/Composer/Package/Locker.php | 113 +++++++++ .../Composer/Package/Version/VersionBumper.php | 25 ++ .../Composer/Package/Version/VersionSelector.php | 32 +++ .../php/guards/Composer/Plugin/CommandEvent.php | 36 +++ .../php/guards/Composer/Plugin/PluginManager.php | 118 ++++++++++ .../Composer/Plugin/PostFileDownloadEvent.php | 50 ++++ .../Composer/Plugin/PreFileDownloadEvent.php | 65 ++++++ .../guards/Composer/Plugin/PrePoolCreateEvent.php | 70 ++++++ .../Composer/Repository/ArtifactRepository.php | 34 +++ .../Composer/Repository/ComposerRepository.php | 108 +++++++++ .../Composer/Repository/CompositeRepository.php | 80 +++++++ .../Composer/Repository/FilterRepository.php | 80 +++++++ .../Composer/Repository/InstalledRepository.php | 39 ++++ .../Composer/Repository/LockArrayRepository.php | 27 +++ .../Composer/Repository/PackageRepository.php | 37 +++ .../guards/Composer/Repository/PathRepository.php | 38 ++++ .../guards/Composer/Repository/PearRepository.php | 17 ++ .../Composer/Repository/PlatformRepository.php | 63 +++++ .../Composer/Repository/RepositoryFactory.php | 58 +++++ .../guards/Composer/Repository/RepositorySet.php | 93 ++++++++ .../Composer/Repository/RootPackageRepository.php | 24 ++ .../Composer/Repository/Vcs/ForgejoDriver.php | 122 ++++++++++ .../Composer/Repository/Vcs/FossilDriver.php | 116 ++++++++++ .../Composer/Repository/Vcs/GitBitbucketDriver.php | 137 +++++++++++ .../guards/Composer/Repository/Vcs/GitDriver.php | 106 +++++++++ .../Composer/Repository/Vcs/GitHubDriver.php | 147 ++++++++++++ .../Composer/Repository/Vcs/GitLabDriver.php | 165 ++++++++++++++ .../guards/Composer/Repository/Vcs/HgDriver.php | 106 +++++++++ .../Composer/Repository/Vcs/PerforceDriver.php | 117 ++++++++++ .../guards/Composer/Repository/Vcs/SvnDriver.php | 121 ++++++++++ .../guards/Composer/Repository/VcsRepository.php | 70 ++++++ .../php/guards/Composer/Util/AuthHelper.php | 45 ++++ .../php/guards/Composer/Util/Bitbucket.php | 42 ++++ .../php/guards/Composer/Util/ConfigValidator.php | 27 +++ .../php/guards/Composer/Util/Filesystem.php | 167 ++++++++++++++ .../php/guards/Composer/Util/Git.php | 100 ++++++++ .../php/guards/Composer/Util/GitHub.php | 52 +++++ .../php/guards/Composer/Util/GitLab.php | 40 ++++ .../guards/Composer/Util/Http/CurlDownloader.php | 37 +++ .../php/guards/Composer/Util/Http/CurlResponse.php | 22 ++ .../php/guards/Composer/Util/Http/Response.php | 57 +++++ .../php/guards/Composer/Util/HttpDownloader.php | 86 +++++++ .../php/guards/Composer/Util/Loop.php | 39 ++++ .../php/guards/Composer/Util/Perforce.php | 219 ++++++++++++++++++ .../php/guards/Composer/Util/Platform.php | 92 ++++++++ .../php/guards/Composer/Util/ProcessExecutor.php | 114 ++++++++++ .../php/guards/Composer/Util/RemoteFilesystem.php | 85 +++++++ .../php/guards/Composer/Util/Svn.php | 82 +++++++ crates/shirabe-php-rpc/php/worker.php | 38 +++- crates/shirabe-php-rpc/src/lib.rs | 22 +- crates/shirabe-php-rpc/tests/generated_stubs.rs | 6 +- 107 files changed, 7607 insertions(+), 15 deletions(-) create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Advisory/AuditConfig.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Advisory/IgnoredSecurityAdvisory.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Advisory/PartialSecurityAdvisory.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Advisory/SecurityAdvisory.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Autoload/AutoloadGenerator.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Cache.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Compiler.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Config/JsonConfigSource.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/Decisions.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/DefaultPolicy.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/GenericRule.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/LocalRepoTransaction.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/LockTransaction.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/MultiConflictRule.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/Pool.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/PoolBuilder.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/PoolOptimizer.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/Request.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/Rule2Literals.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/RuleSet.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/RuleSetGenerator.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/RuleSetIterator.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/SecurityAdvisoryPoolFilter.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/Solver.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/Transaction.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Downloader/FileDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Downloader/FossilDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Downloader/GitDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Downloader/GzipDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Downloader/HgDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Downloader/PathDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Downloader/PerforceDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Downloader/PharDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Downloader/RarDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Downloader/SvnDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Downloader/TarDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Downloader/XzDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Downloader/ZipDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Factory.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Installer.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Installer/BinaryInstaller.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Installer/InstallerEvent.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Installer/LibraryInstaller.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Installer/MetapackageInstaller.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Installer/PluginInstaller.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Installer/ProjectInstaller.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Json/JsonFile.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Package/Archiver/ArchivableFilesFinder.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Package/Archiver/ArchiveManager.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Package/Archiver/PharArchiver.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Package/Archiver/ZipArchiver.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Package/Loader/ArrayLoader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Package/Loader/RootPackageLoader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Package/Loader/ValidatingArrayLoader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Package/Locker.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Package/Version/VersionBumper.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Package/Version/VersionSelector.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Plugin/CommandEvent.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Plugin/PluginManager.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Plugin/PostFileDownloadEvent.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Plugin/PreFileDownloadEvent.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Plugin/PrePoolCreateEvent.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/ArtifactRepository.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/ComposerRepository.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/CompositeRepository.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/FilterRepository.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/InstalledRepository.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/LockArrayRepository.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/PackageRepository.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/PathRepository.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/PearRepository.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/PlatformRepository.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/RepositoryFactory.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/RepositorySet.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/RootPackageRepository.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/Vcs/ForgejoDriver.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/Vcs/FossilDriver.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/Vcs/GitBitbucketDriver.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/Vcs/GitDriver.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/Vcs/GitHubDriver.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/Vcs/GitLabDriver.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/Vcs/HgDriver.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/Vcs/PerforceDriver.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/Vcs/SvnDriver.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Repository/VcsRepository.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/AuthHelper.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/Bitbucket.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/ConfigValidator.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/Filesystem.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/Git.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/GitHub.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/GitLab.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/Http/CurlDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/Http/CurlResponse.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/Http/Response.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/HttpDownloader.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/Loop.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/Perforce.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/Platform.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/ProcessExecutor.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/RemoteFilesystem.php create mode 100644 crates/shirabe-php-rpc/php/guards/Composer/Util/Svn.php (limited to 'crates/shirabe-php-rpc') diff --git a/crates/shirabe-php-rpc/build.rs b/crates/shirabe-php-rpc/build.rs index 07a77a07..937d0bb2 100644 --- a/crates/shirabe-php-rpc/build.rs +++ b/crates/shirabe-php-rpc/build.rs @@ -79,6 +79,26 @@ fn main() { ) .unwrap(); println!("cargo::rustc-env=SHIRABE_COMPOSER_RUNTIME_BUNDLE_ID={bundle_id}"); + + write_guard_files(&manifest_dir.join("php/guards"), &out_dir); +} + +/// Writes the initializer of `GUARD_FILES`: one `include_str!` per generated guard class. The +/// list is built here rather than spelled out in the source because there is one guard for every +/// Rust-owned class the worker has no proxy stub for. +fn write_guard_files(guards: &Path, out_dir: &Path) { + println!("cargo::rerun-if-changed={}", guards.display()); + + let mut code = String::from("&[\n"); + for file in find_files(guards) { + code += &format!( + " ({:?}, include_str!({:?})),\n", + relative_path(guards, &file), + file.to_str().unwrap() + ); + } + code += "]\n"; + std::fs::write(out_dir.join("guard-files.rs"), code).unwrap(); } /// Every file the bundle holds, in the order `Compiler::compile` adds them. diff --git a/crates/shirabe-php-rpc/php/guards/Composer/Advisory/AuditConfig.php b/crates/shirabe-php-rpc/php/guards/Composer/Advisory/AuditConfig.php new file mode 100644 index 00000000..d45d56ad --- /dev/null +++ b/crates/shirabe-php-rpc/php/guards/Composer/Advisory/AuditConfig.php @@ -0,0 +1,24 @@ + 'PACKAGE', + self::TYPE_REQUEST => 'REQUEST', + self::TYPE_LEARNED => 'LEARNED', + ]; + + public function __construct() + { + \ShirabeUnsupportedClass::fail(self::class, '__construct'); + } + + public function add(Rule $rule, $type): void + { + \ShirabeUnsupportedClass::fail(self::class, 'add'); + } + + public function count(): int + { + \ShirabeUnsupportedClass::fail(self::class, 'count'); + } + + public function ruleById(int $id): Rule + { + \ShirabeUnsupportedClass::fail(self::class, 'ruleById'); + } + + public function getRules(): array + { + \ShirabeUnsupportedClass::fail(self::class, 'getRules'); + } + + public function getIterator(): RuleSetIterator + { + \ShirabeUnsupportedClass::fail(self::class, 'getIterator'); + } + + public function getIteratorFor($types): RuleSetIterator + { + \ShirabeUnsupportedClass::fail(self::class, 'getIteratorFor'); + } + + public function getIteratorWithout($types): RuleSetIterator + { + \ShirabeUnsupportedClass::fail(self::class, 'getIteratorWithout'); + } + + public function getTypes(): array + { + \ShirabeUnsupportedClass::fail(self::class, 'getTypes'); + } + + public function getPrettyString(?RepositorySet $repositorySet = null, ?Request $request = null, ?Pool $pool = null, bool $isVerbose = false): string + { + \ShirabeUnsupportedClass::fail(self::class, 'getPrettyString'); + } + + public function __toString(): string + { + \ShirabeUnsupportedClass::fail(self::class, '__toString'); + } +} diff --git a/crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/RuleSetGenerator.php b/crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/RuleSetGenerator.php new file mode 100644 index 00000000..4406ddda --- /dev/null +++ b/crates/shirabe-php-rpc/php/guards/Composer/DependencyResolver/RuleSetGenerator.php @@ -0,0 +1,65 @@ +https?)://(?P.+?)(?::(?P[0-9]+))?/|git@(?P[^:]+):)(?P.+)/(?P[^/]+?)(?:\.git|/)?$#'; + + public function __construct() + { + \ShirabeUnsupportedClass::fail(self::class, '__construct'); + } + + public function initialize(): void + { + \ShirabeUnsupportedClass::fail(self::class, 'initialize'); + } + + public function setHttpDownloader(HttpDownloader $httpDownloader): void + { + \ShirabeUnsupportedClass::fail(self::class, 'setHttpDownloader'); + } + + public function getComposerInformation(string $identifier): ?array + { + \ShirabeUnsupportedClass::fail(self::class, 'getComposerInformation'); + } + + public function getFileContent(string $file, string $identifier): ?string + { + \ShirabeUnsupportedClass::fail(self::class, 'getFileContent'); + } + + public function getChangeDate(string $identifier): ?\DateTimeImmutable + { + \ShirabeUnsupportedClass::fail(self::class, 'getChangeDate'); + } + + public function getRepositoryUrl(): string + { + \ShirabeUnsupportedClass::fail(self::class, 'getRepositoryUrl'); + } + + public function getUrl(): string + { + \ShirabeUnsupportedClass::fail(self::class, 'getUrl'); + } + + public function getDist(string $identifier): ?array + { + \ShirabeUnsupportedClass::fail(self::class, 'getDist'); + } + + public function getSource(string $identifier): array + { + \ShirabeUnsupportedClass::fail(self::class, 'getSource'); + } + + public function getRootIdentifier(): string + { + \ShirabeUnsupportedClass::fail(self::class, 'getRootIdentifier'); + } + + public function getBranches(): array + { + \ShirabeUnsupportedClass::fail(self::class, 'getBranches'); + } + + public function getTags(): array + { + \ShirabeUnsupportedClass::fail(self::class, 'getTags'); + } + + public function getApiUrl(): string + { + \ShirabeUnsupportedClass::fail(self::class, 'getApiUrl'); + } + + protected function getReferences(string $type): array + { + \ShirabeUnsupportedClass::fail(self::class, 'getReferences'); + } + + protected function fetchProject(): void + { + \ShirabeUnsupportedClass::fail(self::class, 'fetchProject'); + } + + protected function attemptCloneFallback(): bool + { + \ShirabeUnsupportedClass::fail(self::class, 'attemptCloneFallback'); + } + + protected function generateSshUrl(): string + { + \ShirabeUnsupportedClass::fail(self::class, 'generateSshUrl'); + } + + protected function generatePublicUrl(): string + { + \ShirabeUnsupportedClass::fail(self::class, 'generatePublicUrl'); + } + + protected function setupGitDriver(string $url): void + { + \ShirabeUnsupportedClass::fail(self::class, 'setupGitDriver'); + } + + protected function getContents(string $url, bool $fetchingRepoData = false): Response + { + \ShirabeUnsupportedClass::fail(self::class, 'getContents'); + } + + public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool + { + \ShirabeUnsupportedClass::fail(self::class, 'supports'); + } + + public function getRepoData(): ?array + { + \ShirabeUnsupportedClass::fail(self::class, 'getRepoData'); + } + + protected function getNextPage(Response $response): ?string + { + \ShirabeUnsupportedClass::fail(self::class, 'getNextPage'); + } + + protected function shouldCache(string $identifier): bool + { + \ShirabeUnsupportedClass::fail(self::class, 'shouldCache'); + } + + protected function getBaseComposerInformation(string $identifier): ?array + { + \ShirabeUnsupportedClass::fail(self::class, 'getBaseComposerInformation'); + } + + public function hasComposerFile(string $identifier): bool + { + \ShirabeUnsupportedClass::fail(self::class, 'hasComposerFile'); + } + + protected function getScheme(): string + { + \ShirabeUnsupportedClass::fail(self::class, 'getScheme'); + } + + public function cleanup(): void + { + \ShirabeUnsupportedClass::fail(self::class, 'cleanup'); + } +} diff --git a/crates/shirabe-php-rpc/php/guards/Composer/Repository/Vcs/HgDriver.php b/crates/shirabe-php-rpc/php/guards/Composer/Repository/Vcs/HgDriver.php new file mode 100644 index 00000000..18cf388d --- /dev/null +++ b/crates/shirabe-php-rpc/php/guards/Composer/Repository/Vcs/HgDriver.php @@ -0,0 +1,106 @@ + */ @@ -439,18 +459,20 @@ if ($client === false) { stream_set_write_buffer($client, 0); ShirabeRpcRuntime::$socket = $client; ShirabeRpcRuntime::$stubsDir = $argv[2] ?? null; +ShirabeRpcRuntime::$guardsDir = $argv[3] ?? null; // Proxy stub classes take priority over any other autoloader (including autoloaders that a // script or the composer runtime registers later), so a proxied FQCN can never be shadowed by // the real implementation. `__shirabe_require` re-prepends this closure after loading code -// that registers its own prepending autoloader. +// that registers its own prepending autoloader. The guards come after the stubs: they cover the +// Rust-owned classes no stub proxies, so the two directories never hold the same FQCN. ShirabeRpcRuntime::$stubAutoloader = static function (string $class): void { - if (ShirabeRpcRuntime::$stubsDir === null) { - return; - } - $file = ShirabeRpcRuntime::$stubsDir . '/' . str_replace('\\', '/', $class) . '.php'; - if (is_file($file)) { - require $file; + $relative = '/' . str_replace('\\', '/', $class) . '.php'; + foreach ([ShirabeRpcRuntime::$stubsDir, ShirabeRpcRuntime::$guardsDir] as $dir) { + if ($dir !== null && is_file($dir . $relative)) { + require $dir . $relative; + return; + } } }; spl_autoload_register(ShirabeRpcRuntime::$stubAutoloader, true, true); diff --git a/crates/shirabe-php-rpc/src/lib.rs b/crates/shirabe-php-rpc/src/lib.rs index 34c3961d..d818b364 100644 --- a/crates/shirabe-php-rpc/src/lib.rs +++ b/crates/shirabe-php-rpc/src/lib.rs @@ -1035,6 +1035,13 @@ const RUNTIME_FILES: &[(&str, &str)] = &[ ), ]; +/// Guard classes made autoloadable inside the worker, behind the stubs: they shadow the real +/// Composer class of every Rust-owned FQCN no stub proxies, so the worker cannot fall through to +/// the real implementation and run an instance the Rust side never sees. Generated by +/// `scripts/plugin-stub-generator/generate-stubs`; too many to spell out here, so `build.rs` +/// builds the list from the directory itself. +const GUARD_FILES: &[(&str, &str)] = include!(concat!(env!("OUT_DIR"), "/guard-files.rs")); + struct Worker { stream: UnixStream, // Also queried for its exit status when a socket read/write fails, to tell a dead worker @@ -1106,9 +1113,15 @@ fn spawn_worker() -> anyhow::Result { std::fs::write(&script_path, GLUE_SCRIPT)?; let stubs_dir = tempdir.path().join("stubs"); - for (relative_path, contents) in STUB_FILES.iter().chain(RUNTIME_FILES) { - let path = stubs_dir.join(relative_path); - std::fs::create_dir_all(path.parent().expect("stub paths have a parent"))?; + let guards_dir = tempdir.path().join("guards"); + let files = STUB_FILES + .iter() + .chain(RUNTIME_FILES) + .map(|entry| (&stubs_dir, entry)) + .chain(GUARD_FILES.iter().map(|entry| (&guards_dir, entry))); + for (dir, (relative_path, contents)) in files { + let path = dir.join(relative_path); + std::fs::create_dir_all(path.parent().expect("generated class paths have a parent"))?; std::fs::write(&path, contents)?; } @@ -1141,7 +1154,8 @@ fn spawn_worker() -> anyhow::Result { command .arg(&script_path) .arg(WORKER_SOCKET_FD.to_string()) - .arg(&stubs_dir); + .arg(&stubs_dir) + .arg(&guards_dir); // SAFETY: the closure only calls async-signal-safe syscalls, as required between fork and // exec. It owns the child end, so the descriptor stays alive until the exec happens. unsafe { diff --git a/crates/shirabe-php-rpc/tests/generated_stubs.rs b/crates/shirabe-php-rpc/tests/generated_stubs.rs index 0980745e..5648b44c 100644 --- a/crates/shirabe-php-rpc/tests/generated_stubs.rs +++ b/crates/shirabe-php-rpc/tests/generated_stubs.rs @@ -1,6 +1,6 @@ -//! Freshness check for the generated proxy stubs: `generate-stubs --check` verifies that the -//! committed stub files and the `STUB_FILES` list in `lib.rs` match what the generator emits -//! from the current Composer checkout and classifier report. +//! Freshness check for the generated proxy stubs and guard classes: `generate-stubs --check` +//! verifies that the committed stub and guard files, and the `STUB_FILES` list in `lib.rs`, match +//! what the generator emits from the current Composer checkout and classifier report. //! //! The generator needs a PHP interpreter, its composer vendor directory and the classifier //! report; when any of those is missing the test returns early, following the non-mock test -- cgit v1.3.1-4-g156e