Skip to content

fix(jcpan): unblock ActiveResource dependency chain (Encode tags, SAX undef parity)#568

Merged
fglock merged 5 commits intomasterfrom
feature/active-resource-deps
Apr 27, 2026
Merged

fix(jcpan): unblock ActiveResource dependency chain (Encode tags, SAX undef parity)#568
fglock merged 5 commits intomasterfrom
feature/active-resource-deps

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 27, 2026

Summary

Investigates and fixes two of the three failures uncovered by jcpan -t ActiveResource.

ActiveResource itself never reaches its own test suite — it fails because of a
chain of dependencies, each broken for a different reason:

ActiveResource
├── Class::Accessor::Lvalue   (XS dep "Want" — no Java port; deferred)
└── XML::Hash
    └── Test::XML
        └── XML::SemanticDiff (2 fails fixed here)

See dev/modules/active_resource.md (added in
this PR) for the full breakdown.

What this PR does

  1. dev/modules/active_resource.md — full diagnosis of all three blockers.
  2. dev/modules/want.md — detailed port plan for the deferred Want
    blocker (see "Out of scope" below).
  3. Fix return inside a literal can break JVM stack #1: Encode %EXPORT_TAGS — was missing :all and :default, so
    use Encode qw(:all) died with "all" is not defined in %Encode::EXPORT_TAGS.
    Adds a reusable defineDefaultAndAllTags() helper in PerlModuleBase and
    wires it up in Encode.java. Now matches core perl exactly:
    keys %Encode::EXPORT_TAGS = (all, default, fallback_all, fallbacks).
  4. Fix -i switch wip #2: XML::Parser::Expat current_element push/pop timing — real
    libexpat updates Context AFTER the user Start handler returns and BEFORE
    the user End handler runs. PerlOnJava had the opposite timing, so
    current_element() returned the wrong element from inside Start/End. This
    broke XML::SemanticDiff (its Text accumulator misattributed text to the
    just-started child, turning empty-element CData from undef into '').
    Fixed by reordering the Context push/pop in XMLParserExpat.java.

Out of scope (follow-up)

  • Implementing the Want XS module (large, separate PR). Without it,
    Class::Accessor::Lvalue and therefore ActiveResource itself remain
    unreachable. Detailed port plan in
    dev/modules/want.md — recommends starting with
    Option A1: a runtime lvalue-context flag in PerlOnJava plus a Pure-Perl
    Want shim covering LVALUE/RVALUE/ASSIGN/LIST/SCALAR/VOID and
    rreturn/lnoreturn. That subset is enough to unblock
    Class::Accessor::Lvalue and therefore ActiveResource; the more exotic
    OBJECT('Pkg') / CHAIN(N) introspection is left for later.

Test plan

  • make passes locally (all unit tests).
  • New regression tests:
    • src/test/resources/unit/encode_export_tags.t — 8 subtests
    • src/test/resources/unit/xml_parser_current_element.t — 12 subtests
  • Bundled XML::Parser suite (src/test/resources/module/XML-Parser/t/*.t):
    45 files / 434 tests, all pass (no regression from the Context timing change).
  • XML::SemanticDiff standalone: 18 files / 47 tests, all pass; the 2
    previously-failing subtests in t/16zero_to_empty_str_cmp.t now pass.
  • Encode parity: keys %Encode::EXPORT_TAGS now matches system perl.

Generated with Devin

@fglock fglock marked this pull request as ready for review April 27, 2026 08:33
fglock and others added 5 commits April 27, 2026 10:42
Document the dependency-chain failures uncovered while running
`jcpan -t ActiveResource`:

  ActiveResource
  ├── Class::Accessor::Lvalue   (XS dep "Want" — no Java port)
  └── XML::Hash
      └── Test::XML
          └── XML::SemanticDiff (2 fails in t/16zero_to_empty_str_cmp.t)

Identifies three independent issues with priority order:
  1. Encode %EXPORT_TAGS missing :all / :default  (cheap, broad impact)
  2. SAX empty-element text reported as '' instead of undef  (medium)
  3. Want XS module needed by Class::Accessor::Lvalue          (deferred)

This PR will land #1 and (if scoped small) #2; #3 is tracked as a
follow-up because it requires either a Pure-Perl Want shim or a full
Java port.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Core Encode.pm exposes four export tags:
    all, default, fallbacks, fallback_all

PerlOnJava only registered :fallbacks and :fallback_all, so any module
doing `use Encode qw(:all)` or `qw(:default)` died at import with:

    "all" is not defined in %Encode::EXPORT_TAGS at (eval N) line 1.

This bit at least Test::XML's t/sax.t and t/basic.t (and is the kind of
thing many CPAN modules trip over).

Add a small reusable helper `defineDefaultAndAllTags()` in
PerlModuleBase that builds :default = @export and :all = @export +
@EXPORT_OK from the EXPORT/EXPORT_OK arrays already populated. Call it
from Encode.initialize() after all defineExport(...) calls so the tag
arrays capture the final lists.

Adds a regression test that asserts both the tag presence and that
`use Encode qw(:all)` / qw(:default) succeed.

Verified parity with system perl:
    keys %Encode::EXPORT_TAGS = (all, default, fallback_all, fallbacks)

Refs dev/modules/active_resource.md (issue #1).

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
XML::Parser::Expat's current_element() returns $self->{Context}->[-1].
Real libexpat updates Context AFTER the user Start handler returns and
BEFORE the user End handler runs, so:

  - inside StartTag: current_element returns the *parent* element
    (or undef at the root)
  - inside EndTag:   current_element returns the parent element
    (or undef at the root)

PerlOnJava had the opposite timing — pushed before Start and popped
after End — so current_element returned the just-started/closing
element instead. This broke XML::SemanticDiff: Style::Stream's doText
fires Text from inside Start/End, and XML::SemanticDiff::Text uses
current_element to attribute accumulated text. With the wrong parent,
empty-element text was attributed to the new child element, turning
its CData from undef into ''.

Fix: in XMLParserExpat.java, move the Context push to the end of
startElement() (after the user start handler) and the pop to before
the user end handler. Also factor the push/pop into small helpers
(pushContext / popContext) for the skip-path balancing.

Verified empirically against system perl with Style => 'Stream':

    [Start root] cur=undef depth=0
    [Text]       cur=root
    [Start el2]  cur=root  depth=1
    [End el2]    cur=root  depth=1
    [Text]       cur=root
    [End root]   cur=undef depth=0

Test results:
  - XML::Parser bundled suite: 45 files / 434 tests still pass
  - XML::SemanticDiff: 2 previously-failing subtests in
    t/16zero_to_empty_str_cmp.t now pass; full suite green (47/47)
  - Adds src/test/resources/unit/xml_parser_current_element.t with
    12 subtests covering Start/Text/End attribution at root and nested

Refs dev/modules/active_resource.md (issue #2).

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Scopes a port of CPAN's Want module (the XS dep that blocks
Class::Accessor::Lvalue and therefore ActiveResource).

Covers:
- What Want's API actually does and why wantarray isn't enough.
- Why it's hard on PerlOnJava (no op tree at runtime).
- Three options (Pure-Perl shim, Java port of subset, full parity)
  and a recommendation to start with Option A1: a runtime
  lvalue-context flag plus a Perl-side shim covering
  LVALUE/RVALUE/ASSIGN/LIST/SCALAR/VOID + rreturn/lnoreturn.
- Acceptance tests, implementation checklist, and risks
  (re-entrancy, interpreter parity, perf).

Refs dev/modules/active_resource.md (issue #3).
@fglock fglock force-pushed the feature/active-resource-deps branch from 395a26e to 218a431 Compare April 27, 2026 08:43
@fglock fglock merged commit f323d80 into master Apr 27, 2026
2 checks passed
@fglock fglock deleted the feature/active-resource-deps branch April 27, 2026 12:26
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.

return inside a literal can break JVM stack

1 participant