diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/dev/php-rpc.md | 12 | ||||
| -rw-r--r-- | docs/dev/plugin-class-classification.md | 6 | ||||
| -rw-r--r-- | docs/dev/plugin-stub-generation.md | 52 |
3 files changed, 60 insertions, 10 deletions
diff --git a/docs/dev/php-rpc.md b/docs/dev/php-rpc.md index b1c992d7..f91b8e54 100644 --- a/docs/dev/php-rpc.md +++ b/docs/dev/php-rpc.md @@ -185,7 +185,7 @@ allocates the Rust entity behind a `new SomeProxiedClass(...)` written by plugin answers with `[rhandle, epoch]`. Classes whose entity Rust cannot build are an explicit error naming the class. -## Proxy stubs and runtime classes +## Proxy stubs, runtime classes and guards `php/stubs/` holds the proxy stub classes (`Composer\Script\Event`, `Composer\PartialComposer`, `Composer\Composer`, the `Composer\IO\{BaseIO,ConsoleIO,BufferIO,NullIO}` hierarchy, the @@ -215,8 +215,14 @@ by `scripts/plugin-stub-generator/generate-stubs` and must not be edited by hand here) is a faithful in-process port of the real base class and crosses the wire as a P-table entity (`__shirabeRustHandleDescriptor()` returns null in native mode). -Both sets are written into the same autoload directory at worker spawn and resolved with -highest priority, so these FQCNs can never be shadowed by the real implementation; +`php/guards/` holds the guard classes: one per Composer class whose entity lives on the Rust side +and that neither a stub nor a runtime class shadows. A guard keeps the FQCN, the hierarchy and the +constants of the real class, and raises an explicit error from its constructor and every method, +so code running here can never work on a second instance the Rust side never sees. They are +generated by the same tool as the stubs. + +The three sets are written into two autoload directories at worker spawn (guards behind stubs) and +resolved with highest priority, so these FQCNs can never be shadowed by the real implementation; `__shirabe_require` restores that priority after loading code that prepends its own autoloader. Stubs are interned per rhandle (`WeakReference`-based registry) so identity (`===`) holds, and their destructors send `ReleaseRustHandle`. Reviving a stub for an existing entity bypasses its diff --git a/docs/dev/plugin-class-classification.md b/docs/dev/plugin-class-classification.md index c77eca52..7046ea48 100644 --- a/docs/dev/plugin-class-classification.md +++ b/docs/dev/plugin-class-classification.md @@ -42,6 +42,12 @@ exactly one category. | `php-native` | PHP | the real, unmodified PHP source | none — Rust may or may not have its own port for internal use, and that port is free to diverge in shape | | `unsupported` | n/a | nothing; any reference raises an explicit error | none, until explicitly promoted | +A Rust-owned class the child has no artifact for — every `unsupported` one, and +every `rust-proxy`/`rust-snapshot` one whose stub is not written yet — is +shadowed there by a generated *guard* class instead of the real Composer source: +same FQCN, hierarchy and constants, every constructor and method an explicit +error. See `docs/dev/plugin-stub-generation.md`. + #### rust-proxy The living services: `Composer`, `Config`, `RepositoryManager`, diff --git a/docs/dev/plugin-stub-generation.md b/docs/dev/plugin-stub-generation.md index 68dde156..e2b6d848 100644 --- a/docs/dev/plugin-stub-generation.md +++ b/docs/dev/plugin-stub-generation.md @@ -1,4 +1,4 @@ -# Plugin proxy stub generation +# Plugin proxy stub and guard generation ## Purpose @@ -12,13 +12,21 @@ Composer sources keeps every stub signature mechanically faithful to the real class and turns "the stub is missing something the real class has" into a generation failure instead of silent breakage. +The same tool emits the **guard classes** under +`crates/shirabe-php-rpc/php/guards/`, one for every Rust-owned class no stub and +no `php/runtime/` class shadows. Without them the worker's autoloader falls +through to the real Composer implementation, and code running there works on a +second instance the Rust side never sees — silent breakage in place of the +explicit error an unimplemented plugin API is supposed to raise. `build.rs` +embeds the guards, building the list from the directory itself. + ## Running ``` cd scripts/plugin-stub-generator composer install # once; vendor/ is git-ignored -./generate-stubs # rewrites crates/shirabe-php-rpc/php/stubs/ -./generate-stubs --check # verifies committed stubs are fresh; exit 1 otherwise +./generate-stubs # rewrites the stubs/ and guards/ directories +./generate-stubs --check # verifies committed files are fresh; exit 1 otherwise ``` Inputs: @@ -36,10 +44,19 @@ Inputs: interfaces from vendor packages (e.g. `Psr\Log\LoggerInterface`) resolve too. * the classifier report (`scripts/plugin-class-classifier/report.json`, override with `--report=`). Run `scripts/plugin-class-classifier/classify` first; the - report is git-ignored. Every target must be classified `rust-proxy` or - `contract`, and a report with classification violations is rejected. + report is git-ignored. Every stub target must be classified `rust-proxy` or + `contract`, and a report with classification violations is rejected. The guard + set is read from the report alone: every `rust-proxy`, `rust-snapshot` and + `unsupported` class that is not a stub target, a `php/runtime/` class or an + exemption. +* `guard-exemptions.list` — the Rust-owned FQCNs that stay resolvable to the real + Composer class, each with the worker-side mechanism that makes a native + instance correct (the materialized-value codec for `Composer\Package\Link`, + the dual-mode `Composer\EventDispatcher\Event` for + `Composer\Plugin\PreCommandRunEvent`). An entry that needs no guard anyway + fails the run. -The freshness check runs in `cargo test` as +The freshness check for both sets runs in `cargo test` as `crates/shirabe-php-rpc/tests/generated_stubs.rs`; it returns early when PHP, the generator's vendor directory or the classifier report is unavailable. @@ -93,6 +110,27 @@ the generator's vendor directory or the classifier report is unavailable. order, restricted to names the emitted stub references; signatures declared elsewhere (interface files) are re-spelled through that import table. +## What a guard looks like + +A guard reproduces the real class's declaration — `abstract`/`final`, `extends`, +`implements`, the constants and public static properties verbatim — so that +references satisfied by the declaration alone keep working: `instanceof`, +`catch`, `X::class`, `Link::TYPE_REQUIRE`. Everything executable raises +`ShirabeUnsupportedClass::fail()`, which names the class and the member: + +* the constructor (the class's own, or the one it would inherit; a class whose + hierarchy declares none gets a no-argument one, so the implicit constructor + cannot be reached either), +* every public and protected method the guard would otherwise inherit from real + code: its own, its traits', and those of ancestors up to the first one that is + itself guarded or shadowed by a stub. Private methods are reachable only from + the code the guard replaces, and a destructor that throws would fire during + unwinding, so neither is emitted. + +Abstract methods of an abstract guard stay abstract. A method an ancestor +declares `final` cannot be redeclared, so it keeps running the real +implementation; the guard's header names each one. + ## Coverage assertions Generation fails — instead of emitting something quietly wrong — on: @@ -107,7 +145,7 @@ Generation fails — instead of emitting something quietly wrong — on: * a target whose FQCN is also provided by `php/runtime/`. `generate-stubs` (in both modes) additionally fails when a `.php` file exists -under the stubs directory that no target produces, or when `STUB_FILES` / +under the stubs or guards directory that no target produces, or when `STUB_FILES` / `RUNTIME_FILES` in `crates/shirabe-php-rpc/src/lib.rs` does not embed every generated stub / runtime file. It also cross-checks the handoff property table for `Composer\Console\Application` (declared in `generate-stubs` itself) against |
