From 6e2786e141df0eaa0a4a9aa2dce88ed47c1cce06 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 8 May 2026 15:00:03 +0200 Subject: [PATCH 1/2] docs(modules): add Role::Basic jcpan parity plan Add dev/modules/role_basic.md with symptom, bisection notes, nested-eval EvalRuntimeContext hypothesis, verification commands, and PR checklist. Link from dev/modules/README.md. Runtime change is intentionally out of scope for this commit (follow-up once jcpan Role::Basic passes). Generated with [Cursor](https://cursor.com) Co-Authored-By: Cursor Co-authored-by: Cursor --- dev/modules/README.md | 1 + dev/modules/role_basic.md | 61 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 dev/modules/role_basic.md diff --git a/dev/modules/README.md b/dev/modules/README.md index 049a07300..6693f69c4 100644 --- a/dev/modules/README.md +++ b/dev/modules/README.md @@ -12,6 +12,7 @@ This directory contains design documents and guides related to porting CPAN modu | [xsloader.md](xsloader.md) | XSLoader architecture | | [makemaker_perlonjava.md](makemaker_perlonjava.md) | ExtUtils::MakeMaker implementation | | [cpan_client.md](cpan_client.md) | jcpan - CPAN client for PerlOnJava | +| [role_basic.md](role_basic.md) | Role::Basic jcpan parity — nested eval `EvalRuntimeContext` / `_load_role` | | [dbix_class.md](dbix_class.md) | DBIx::Class support (in progress) | | [dbi_test_parity.md](dbi_test_parity.md) | DBI test-suite parity (~13.5× more passes than master; Phases 1–4 done, incl. a tied-hash method-dispatch fix in the PerlOnJava runtime) | | [math_bigint_bignum.md](math_bigint_bignum.md) | Math::BigInt / BigFloat / BigRat / bignum support (in progress) | diff --git a/dev/modules/role_basic.md b/dev/modules/role_basic.md new file mode 100644 index 000000000..e4a026217 --- /dev/null +++ b/dev/modules/role_basic.md @@ -0,0 +1,61 @@ +# Role::Basic (CPAN) on PerlOnJava + +Documentation-only deliverable: this file is the **plan** (symptom, bisection, hypothesized fix, verification). Landing the **`RuntimeCode` `EvalRuntimeContext` stack** (or another confirmed fix) is a **follow-up** change once `timeout 600 ./jcpan -t Role::Basic` passes. + +## Acceptance + +```bash +timeout 600 ./jcpan -t Role::Basic +make +``` + +Target: all Role-Basic tests pass, especially `t/exceptions.t` (“Trying to load non-roles should fail”). + +## Symptom + +`Role::Basic->_load_role('My::Example')` should end in `Carp::confess` with a message matching `Only roles defined with Role::Basic may be loaded`. On jperl, `$@` was empty and the call wrongly returned success. + +Upstream logic (simplified): after `eval "use $role $version"`, code does `return 1 if $IS_ROLE{$role}` then checks `requires` / `_sub_package`. If the **outer** lexical `$role` reads as the **inner** re-entrant role (`My::Does::Basic`), `%IS_ROLE` makes the early return succeed and `confess` never runs. + +## Investigation notes + +- Instrumenting `_load_role` showed the second debug line (outer frame) still saw the inner package name. +- Bisection on a forked `Role/Basic.pm`: removing the stash preamble (`my $stash = do { no strict 'refs'; \%{"${role}::"} };` and related `%INC` logic) made jperl match stock perl — implicating **interaction** of that block with **nested compilation**, not `"\%{"${role}::"}"` vs `$role . '::'` alone (a minimal two-level reentrancy test with only interpolation can still pass). +- Copying stash keys in `HashSpecialVariable.getStash` / `GlobalVariable.getGlobalHash` did **not** fix jcpan; treat as unrelated unless a future test proves otherwise. + +## Root cause (target fix) + +**Nested `eval STRING` compilation** uses a single `ThreadLocal` for `EvalRuntimeContext` in [`RuntimeCode.java`](../../src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeCode.java): inner `evalStringHelper` / `evalStringWithInterpreter` **overwrites** the context and the outer `finally` block calls **`remove()`**, dropping the outer eval’s context while outer compilation may still run (e.g. `use` during compile → nested `_load_role` → nested `eval`). That matches the BEGIN-alias cleanup comment in the same file about **recursive** eval corrupting globals. + +**Fix:** replace `ThreadLocal` with a **`ThreadLocal>`** (LIFO: `addFirst` on entry, `removeFirst` in outer `finally`). Adjust: + +- `getEvalRuntimeContext()` → `peekFirst()` or null if empty +- `saveAndClearEvalRuntimeContext()` → `removeFirst()` (or null if empty) +- `restoreEvalRuntimeContext(saved)` → `addFirst(saved)` +- `clearCaches()` → `evalRuntimeContextStack.remove()` + +Apply consistently in both `evalStringHelper` and `evalStringWithInterpreter`. + +## Neutral regression tests + +- Prefer a small `.t` under `src/test/resources/unit/` that does **not** bundle Role::Basic: nested `eval` + `do { no strict 'refs'; \%{"${pkg}::"} }` + outer lexical preservation. If no stable minimal repro, rely on jcpan Role::Basic plus existing patterns like `stash_lexical_reentrancy.t` / `sub_reentrant_lexical_register.t` as guards. + +## Constraints + +- Do not patch tarballs under `~/.perlonjava/cpan` for the real fix. +- Do not edit CPAN `t/*.t` files. +- Do not add Role::Basic as a bundled unit-test dependency. + +## PR checklist + +1. Implement the `evalRuntimeContext` stack in `RuntimeCode.java` (and any comment updates, e.g. `BytecodeCompiler`, `dev/design/concurrency.md` if desired). +2. Revert any experimental stash-key-only runtime diffs unless justified. +3. Run `timeout 600 ./jcpan -t Role::Basic` and `make`. +4. Branch from `master`, commit with [AI_POLICY.md](../AI_POLICY.md) attribution (`git commit -F file`). +5. Push and `gh pr create --body-file /tmp/pr_body.md` (never `--body` with inline backticks). + +## Progress + +| Date | Status | +|------------|--------| +| 2026-05-08 | Plan documented in-repo (`role_basic.md` + modules README). Runtime fix pending (jcpan still fails `t/exceptions.t` test 1 until verified). | From cdc5955c6917c143c8f26f6d3f08da14bf4c262d Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Fri, 8 May 2026 15:00:56 +0200 Subject: [PATCH 2/2] docs(modules): drop README link from Role::Basic plan scope Remove dev/modules/README.md quick-reference row; keep plan in role_basic.md only. Update progress table accordingly. Generated with [Cursor](https://cursor.com) Co-Authored-By: Cursor Co-authored-by: Cursor --- dev/modules/README.md | 1 - dev/modules/role_basic.md | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/dev/modules/README.md b/dev/modules/README.md index 6693f69c4..049a07300 100644 --- a/dev/modules/README.md +++ b/dev/modules/README.md @@ -12,7 +12,6 @@ This directory contains design documents and guides related to porting CPAN modu | [xsloader.md](xsloader.md) | XSLoader architecture | | [makemaker_perlonjava.md](makemaker_perlonjava.md) | ExtUtils::MakeMaker implementation | | [cpan_client.md](cpan_client.md) | jcpan - CPAN client for PerlOnJava | -| [role_basic.md](role_basic.md) | Role::Basic jcpan parity — nested eval `EvalRuntimeContext` / `_load_role` | | [dbix_class.md](dbix_class.md) | DBIx::Class support (in progress) | | [dbi_test_parity.md](dbi_test_parity.md) | DBI test-suite parity (~13.5× more passes than master; Phases 1–4 done, incl. a tied-hash method-dispatch fix in the PerlOnJava runtime) | | [math_bigint_bignum.md](math_bigint_bignum.md) | Math::BigInt / BigFloat / BigRat / bignum support (in progress) | diff --git a/dev/modules/role_basic.md b/dev/modules/role_basic.md index e4a026217..f686f86d6 100644 --- a/dev/modules/role_basic.md +++ b/dev/modules/role_basic.md @@ -58,4 +58,4 @@ Apply consistently in both `evalStringHelper` and `evalStringWithInterpreter`. | Date | Status | |------------|--------| -| 2026-05-08 | Plan documented in-repo (`role_basic.md` + modules README). Runtime fix pending (jcpan still fails `t/exceptions.t` test 1 until verified). | +| 2026-05-08 | Plan documented in-repo (`role_basic.md` only). Runtime fix pending (jcpan still fails `t/exceptions.t` test 1 until verified). |