Skip to content

fix: glob slot introspection so jcpan -t RPG::Traveller::Person passes#637

Merged
fglock merged 1 commit intomasterfrom
feature/glob-introspection-rpg-traveller-20260430-130726
Apr 30, 2026
Merged

fix: glob slot introspection so jcpan -t RPG::Traveller::Person passes#637
fglock merged 1 commit intomasterfrom
feature/glob-introspection-rpg-traveller-20260430-130726

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 30, 2026

Summary

Makes ./jcpan -t RPG::Traveller::Person pass end-to-end. The test chain depends on Devel::Symdump (via Test::Pod::Coverage) for stash introspection, and several PerlOnJava-side glob bugs were making that introspection lie or refuse to load.

Bugs fixed

  • *{"main::"} returned an empty hash on dereferenceRuntimeGlob.getGlobHash() stripped a leading "main::" even when that was the entire glob name.
  • local(*X) = $glob made *X{ARRAY|HASH} falsely defineddynamicSaveState eagerly auto-vivified empty array/hash slots, leaking back as a defined slot. Devel::Symdump uses exactly that test to walk the symbol table. Save/restore now tracks pre-existence and only repopulates slots that really existed; glob-to-glob aliasing also skips non-existent source slots (with the stash-glob HASH treated as intrinsic).
  • open(Pkg::FH, ...) IO didn't surface through stash entriesopen only sets IO on the canonical glob in globalIORefs, while *{$Pkg::{FH}}{IO} and local(*X) = $Pkg::{FH} operate on a RuntimeStashEntry clone whose IO field was an empty placeholder. getGlobSlot("IO") now falls back to the canonical glob; set(RuntimeGlob) only prefers value.IO when it actually carries a RuntimeIO (or a tied handle).
  • B::GV::GvFLAGS / B::GVf_IMPORTED_CV were missing — Pod::Coverage requires them; without them t/PodCoverage.t produced no plan. Stubs added; a new Internals::jperl_is_imported_sub($fqn) probe exposes the existing GlobalVariable.isSubs map so imports are skipped. Constants (subs with empty prototype) are also treated as imported, mirroring real Perl's PCS handling.
  • $Config{d_telldir} (and friends) were undef — PerlOnJava implements telldir/seekdir/rewinddir/readdir via java.nio, but Config.pm didn't advertise them. Devel::Symdump's _is_dirhandle branches on d_telldir; with the flag defined, dirhandles now reports opened directory handles.

Result

  • ./jcpan -t RPG::Traveller::Person exits 0, all 17 subtests across 6 test files pass (including t/PodCoverage.t).
  • Devel::Symdump's own t/symdump.t improved from 9/22 failed to 3/13 failed. The remaining three are sensitive to specific Perl-internal symbol layout (filehandles count, special-variable hash list, ordering of main::_) and are out of scope here.

Test plan

  • make (all unit tests pass)
  • ./jcpan -t RPG::Traveller::Person passes
  • Targeted reproducers for each bug (stash deref, local(*X) = $glob, *{$stash{FH}}{IO}, dirhandle introspection)

Generated with Devin

@fglock fglock force-pushed the feature/glob-introspection-rpg-traveller-20260430-130726 branch 3 times, most recently from a885a41 to 969a05a Compare April 30, 2026 12:51
`./jcpan -t RPG::Traveller::Person` failed because the test chain
required Devel::Symdump (used by Test::Pod::Coverage) to introspect
the symbol table, and several PerlOnJava-side glob-handling bugs
made that introspection report wrong answers — or refuse to run.

Root causes addressed:

1. `*{"main::"}` returned an empty hash on dereference.
   `RuntimeGlob.getGlobHash()` stripped a leading `"main::"` even when
   that was the *entire* glob name, ending up looking up `""` in the
   global hashes table. Now we only strip the prefix when there's
   something after it, mirroring `GlobalVariable.getGlobalHash()`.

2. `local(*X) = $glob` made `*X{ARRAY|HASH}` falsely report defined.
   `dynamicSaveState` eagerly auto-vivified empty array/hash slots so
   that writes during the local scope wouldn't leak. That auto-vivify
   leaked back out as `defined *X{ARRAY|HASH}` returning true even
   when neither side ever had a slot — Devel::Symdump's `_symdump`
   uses exactly that test to walk the stash and was getting bogus
   hits for every imported sub. Now save/restore tracks pre-existence
   directly via the underlying maps and only repopulates slots that
   really existed before `local`. Glob-to-glob aliasing in
   `set(RuntimeGlob)` also now skips slots that don't exist on the
   source, except for stash globs (`*Pkg::`) where the HASH slot is
   intrinsic.

3. `open(Pkg::FH, ...)` IO didn't surface through stash entries.
   `open` only sets the IO on the canonical glob in `globalIORefs`,
   but `*{$Pkg::{FH}}{IO}` and `local(*X) = $Pkg::{FH}` operate on a
   `RuntimeStashEntry`/glob clone whose `IO` field was an empty
   placeholder. `getGlobSlot("IO")` now falls back to the canonical
   glob when the local slot is empty, and `set(RuntimeGlob)` only
   prefers `value.IO` when it actually carries a `RuntimeIO` (or a
   tied handle) — otherwise it picks up the IO from the source's
   canonical glob. This makes Devel::Symdump's `filehandles` and
   `dirhandles` see opened handles installed under qualified names.

4. `B::GV::GvFLAGS` / `B::GVf_IMPORTED_CV` were missing.
   Pod::Coverage uses these to skip imported helpers; without them
   it could not load and `t/PodCoverage.t` produced no plan. Stubs
   are now provided. To approximate the real-Perl `GVf_IMPORTED_CV`
   bit, a new `Internals::jperl_is_imported_sub($fqn)` probe exposes
   `GlobalVariable.isSubs`, which already records typeglob CODE
   assignments (Exporter-style imports). `use constant` constants
   that take the proxy-constant path are also flagged via the empty
   prototype, mirroring real Perl's PCS handling so packages that
   only declare constants don't fail Pod::Coverage tests. A
   `B::GV::CV` slot accessor was added at the same time.

5. `$Config{d_telldir}` (and friends) were undef.
   PerlOnJava implements `telldir`/`seekdir`/`rewinddir`/`readdir`
   via `java.nio`, but Config.pm didn't advertise them. Devel::Symdump
   branches on `d_telldir` to choose between `telldir()` and B::IO
   introspection for `_is_dirhandle`; with the flag now defined,
   `dirhandles` correctly reports opened directory handles.

End-to-end: `./jcpan -t RPG::Traveller::Person` now exits 0 with all
tests successful (including `t/PodCoverage.t`). Devel::Symdump's own
`t/symdump.t` improved from 9/22 failed to 3/13 failed; the remaining
three tests are sensitive to specific Perl-internal symbol layout
(filehandles count, special-variable hash list, ordering of
`main::_`) and are out of scope for this PR.

Files touched:
- src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeGlob.java
  Stash-name fix in getGlobHash; lazy slot population in
  dynamicSaveState/dynamicRestoreState; conditional aliasing in
  set(RuntimeGlob) for ARRAY/HASH/SCALAR; canonical-glob fallback
  for *X{IO}.
- src/main/java/org/perlonjava/runtime/perlmodule/Internals.java
  Added Internals::jperl_is_imported_sub.
- src/main/perl/lib/B.pm
  Added B::GV::GvFLAGS, B::GV::CV, B::GVf_IMPORTED_CV.
- src/main/perl/lib/Config.pm
  Added d_telldir/d_seekdir/d_rewinddir/d_readdir.
- src/main/java/org/perlonjava/core/Configuration.java
  Auto-updated by `make`.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock force-pushed the feature/glob-introspection-rpg-traveller-20260430-130726 branch from 969a05a to d787d06 Compare April 30, 2026 13:02
@fglock fglock merged commit 13deb9a into master Apr 30, 2026
2 checks passed
@fglock fglock deleted the feature/glob-introspection-rpg-traveller-20260430-130726 branch April 30, 2026 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant