Skip to content

fix: Geo::IP support — DynaLoader stubs, IPv6 Socket, nested eval STRING lexicals#511

Merged
fglock merged 2 commits intomasterfrom
fix/geo-ip-dynaloader-socket-v6
Apr 20, 2026
Merged

fix: Geo::IP support — DynaLoader stubs, IPv6 Socket, nested eval STRING lexicals#511
fglock merged 2 commits intomasterfrom
fix/geo-ip-dynaloader-socket-v6

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 20, 2026

Summary

Two fixes that together take ./jcpan -t Geo::IP from "configure fails immediately" to 8 of 8 test files passing.


Fix 1 — DynaLoader dl_* probe stubs + Socket IPv6 (commit 1)

Geo::IP's Makefile.PL calls DynaLoader::dl_findfile('GeoIP') to choose between XS and pure-Perl. We previously stubbed only bootstrap/boot_DynaLoader. Added no-op stubs for dl_findfile, dl_load_file, dl_find_symbol, dl_find_symbol_anywhere, dl_install_xsub, dl_undef_symbols, dl_error.

Also implemented Socket::pack_sockaddr_in6 / unpack_sockaddr_in6 (28-byte sockaddr_in6 layout: family, port, flowinfo, 16-byte addr, scope_id) and added them to @EXPORT, unblocking the PP-path IPv6 use Socket qw/ ... unpack_sockaddr_in6 /.

Fix 2 — Nested eval STRING visibility of outer my lexicals in named subs (commit 2)

Standard Perl lets a string-eval's compile-time scope include every my visible at the call site, including variables declared in an enclosing eval STRING. PerlOnJava's interpreter backend broke this for named subs defined inside a nested eval:

eval q{
    my $y = 99;
    eval q{ sub bar { return $y } 1 };
    bar();      # before: dies at parse-time or returns undef
                # after:  returns 99
};

Root cause: EvalStringHandler (interpreter backend) created a fresh, empty ScopedSymbolTable and received the caller's lexical environment only as a Map<name, register> used to build runtime capture arrays. The variable names never made it into the symbol table, so Variable.checkStrictVarsAtParseTime failed for named sub bodies, and SubroutineParser.handleNamedSub's JVM-path closure capture found nothing to capture.

Fix: Seed the parser's symbol table with one our entry per caller lexical, using the same "BEGIN package alias" trick that the JVM backend's RuntimeCode.evalStringHelper already uses:

  1. Allocate a unique PerlOnJava::_BEGIN_<id> namespace per eval invocation.
  2. Alias each captured my/state value into GlobalVariable.globalXxx under that namespace.
  3. Seed addVariable(name, "our", seedPkg, null) so SubroutineParser.handleNamedSub takes its decl == "our" branch and looks values up via the alias.

Direct references inside the eval body are unaffected — they still go through BytecodeCompiler's parentRegistry-populated symbol table (register capture).

This mirrors exactly what the JVM backend has done correctly all along; the interpreter path was missing the alias step.

Geo::IP's remaining 2 test failures were entirely due to this: its v6 subs live in an inner eval <<'__IPV6__' that references outer-eval my @countries / @code3s / @names.

Design doc: dev/design/nested-eval-string-lexicals.md.


Verification

Check Result
make (all unit tests) green
make test-bundled-modules green
New test src/test/resources/unit/eval_nested_lexicals.t (8 subtests) 8/8 pass, matches standard Perl
./jcpan -t Geo::IP 8/8 test files pass (was 0/8, then 6/8)
perl5_t/t/op/eval.t 159/173 pass (91.9%)
Reproducer from plan doc, direct/anon/named subs + 3-deep nesting all match standard Perl

Files

File Change
src/main/java/org/perlonjava/runtime/perlmodule/DynaLoader.java Register dl_* probe no-op stubs.
src/main/java/org/perlonjava/runtime/perlmodule/Socket.java Implement pack_sockaddr_in6 / unpack_sockaddr_in6.
src/main/perl/lib/Socket.pm Export the two new functions.
src/main/perl/lib/DynaLoader.pm Defensive .pm-level stubs (currently unreached — %INC is preset by Java — but useful if that changes).
src/main/java/org/perlonjava/backend/bytecode/EvalStringHandler.java Seed symbol table with alias-package our entries; move capture computation before parse so seeding sees the final filtered set.
src/test/resources/unit/eval_nested_lexicals.t New regression test.
dev/design/nested-eval-string-lexicals.md Design doc.

Test plan

  • make passes
  • make test-bundled-modules passes
  • ./jcpan -t Geo::IP passes all 8 test files
  • New eval_nested_lexicals.t matches standard Perl
  • No regression in perl5_t/t/op/eval.t

Generated with Devin

@fglock fglock changed the title fix: enable Geo::IP pure-Perl mode and IPv6 socket support fix: Geo::IP support — DynaLoader stubs, IPv6 Socket, nested eval STRING lexicals Apr 20, 2026
fglock and others added 2 commits April 20, 2026 15:59
Two fixes to make `./jcpan -t Geo::IP` progress from configure-time
failure to 6/8 test files passing (20/21 subtests).

1. DynaLoader dl_* probe stubs
   Geo::IP's Makefile.PL calls DynaLoader::dl_findfile('GeoIP') to
   decide between XS and pure-Perl code paths. PerlOnJava only stubbed
   bootstrap/boot_DynaLoader, so this died with "Undefined subroutine".
   Register no-op stubs for dl_findfile, dl_load_file, dl_find_symbol,
   dl_find_symbol_anywhere, dl_install_xsub, dl_undef_symbols, dl_error
   in DynaLoader.java. All return empty/undef so modules fall through
   to their pure-Perl implementations.

   (DynaLoader.pm gets the same stubs behind `unless defined` guards,
   but they are currently unreached because Java's initialize() presets
   %INC{DynaLoader.pm}.)

2. Socket::pack_sockaddr_in6 / unpack_sockaddr_in6
   Geo::IP's PP path does:
       use Socket qw/ ... unpack_sockaddr_in6 /;
   which failed because Socket.pm did not export it and Socket.java
   did not implement it. Added both functions (28-byte sockaddr_in6
   layout: family, port, flowinfo, 16-byte addr, scope_id) and listed
   them in @export.

The two remaining Geo::IP test failures (country_v6.t, part of org.t)
are caused by a separate, unrelated limitation: PerlOnJava's inner
`eval STRING` does not inherit `my` lexicals from an outer
`eval STRING`, so the IPv6 subs that reference Geo::IP's `my @countries`
fail to compile. Not addressed here.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Standard Perl lets a string-eval's compile-time scope include every `my`
visible at the call site, including variables declared in an enclosing
eval STRING. PerlOnJava's interpreter backend broke this for named subs
defined inside a nested eval: strict-vars fired at parse time, or (once
parsing was fixed) the sub's closure captured nothing and `$y` returned
undef at runtime.

Minimal reproducer (fixed by this commit):
    eval q{
        my $y = 99;
        eval q{ sub bar { return $y } 1 };
        bar();                          # now returns 99
    };

Root cause
----------
`EvalStringHandler` (interpreter-backend eval STRING) created a fresh,
empty `ScopedSymbolTable` and only received the caller's lexical
environment as a `Map<name, register>` used purely to build runtime
capture arrays.  The variable NAMES never made it into the symbol
table, so `Variable.checkStrictVarsAtParseTime` failed for named sub
bodies, and `SubroutineParser.handleNamedSub`'s JVM-path closure
capture had nothing to capture.

Fix
---
Seed the parser's symbol table with one `our` entry per caller lexical,
using the same "BEGIN package alias" trick that the JVM backend's
`RuntimeCode.evalStringHelper` already uses:

  1. Allocate a unique `PerlOnJava::_BEGIN_<id>` namespace per eval
     invocation.
  2. Alias each captured `my`/`state` value into
     `GlobalVariable.globalXxx` under that namespace.
  3. Seed `ScopedSymbolTable.addVariable(name, "our", seedPkg, null)`
     so `SubroutineParser.handleNamedSub` takes its `decl == "our"`
     branch (line 1153) and looks the value up via the alias.

Direct references inside the eval body are unaffected: they go through
`BytecodeCompiler`'s own parentRegistry-populated symbol table (register
capture), not the parser's.

Move the capturedVars / adjustedRegistry computation up so it runs
before parse — the seeding step needs the final filtered set of
captured variables.

Impact
------
- `./jcpan -t Geo::IP` now passes 8/8 test files (was 6/8). The
  remaining 2 failures on the parent branch were entirely due to this
  bug: Geo::IP wraps its v6 subs in an inner `eval <<'__IPV6__'` that
  references outer-eval `my @countries` / `@code3s` / `@names`.
- New test: `src/test/resources/unit/eval_nested_lexicals.t`
  (8 subtests covering direct refs, anon subs, named subs, 3-deep
  nesting, hash lookups). Matches standard Perl exactly.
- `make` (all unit tests): green.
- `make test-bundled-modules`: green.
- `perl5_t/t/op/eval.t`: 159/173 passing (91.9%).

Design doc: `dev/design/nested-eval-string-lexicals.md`.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock force-pushed the fix/geo-ip-dynaloader-socket-v6 branch from 4d42ae7 to 393bae9 Compare April 20, 2026 14:00
@fglock fglock merged commit 4b2bcf8 into master Apr 20, 2026
2 checks passed
@fglock fglock deleted the fix/geo-ip-dynaloader-socket-v6 branch April 20, 2026 14:07
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