Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 58 additions & 4 deletions dev/modules/anyevent_fixes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,53 @@ This document tracks the work needed to make `./jcpan -t AnyEvent` pass
all 83 test programs. The project policy requires all tests to pass,
including low-priority ones.

## Discussion — jcpan noise & signals (2026-05-09, PR #700)

PR [#700](https://github.com/fglock/PerlOnJava/pull/700) addresses **spurious CPAN-time warnings** when installing/testing AnyEvent (and similar dists), not the functional gaps below.

### What that PR fixes

| Topic | Problem | Approach |
|-------|---------|------------|
| `Subroutine … redefined` / `Constant subroutine … redefined` | `warnings`-helper logic treated `globalWarningsEnabled` like “almost every category on”, unlike Perl’s `$^W` + `${^WARNING_BITS}` | `ckWarnForScope('redefine')` for non-constant redefine; constant redefine warns unless `no warnings 'redefine'` (even when `$^W` is 0); detect prior constant via prototype **`()`** |
| `"my" … masks earlier declaration` | Wrong category (`redefine`) + unconditional warn in one parser path | Gate with `ckWarnForScope(..., "shadow")` |
| `Warning: AutoSplit had to create top-level blib/lib/auto unexpectedly` | PerlOnJava MakeMaker only `mkdir -p blib/arch` before `pm_to_blib`; AutoSplit then creates `blib/lib/auto` | Emit `mkdir -p blib/lib/auto` before staging (matches typical EU::MM ordering) |

### `t/02_signals.t` — `Bail out! No signal caught.`

**What the test does** (`t/02_signals.t`): registers an **`INT`** watcher (`AnyEvent->signal`), arms a **5s bailout** timer, prints **`ok 2`**, then **`kill 'INT', $$`**. The handler should **`syswrite`** one byte on an internal pipe; **`AnyEvent::Loop`** **`select`** sees the fd; the **`io`** callback runs **`_signal_exec`**, which invokes AE callbacks → **`ok 3`**. If the pipe never wakes the loop, the timer prints bailout.

**Failure modes on PerlOnJava**

1. **Forward subroutine reference / GV stub (primary, reproducible)**
`AnyEvent::Base::signal` is built inside `eval q{ ... }` and registers **`AE::io(..., \&_signal_exec)` before** **`*_signal_exec = sub { ... }`**. On Perl 5, **` \&_signal_exec`** taken early still calls the body installed later (same GV). On **`jperl`**, invoking that early coderef **does not** run the assigned sub:

```perl
package AnyEvent::Base;
eval q{
my $cb = \&_signal_exec;
*_signal_exec = sub { print "ok\n" };
$cb->();
};
```

Stock **`perl`** prints **`ok`**; **`jperl`** prints nothing. The event loop’s stored **`io`** callback can therefore be a no-op / wrong resolution, matching runtime errors such as **`Undefined subroutine &AnyEvent::Base::_signal_exec`** when **`AnyEvent::Loop`** invokes **`$_->[2]()`** on the watcher.

**Fix direction:** **`RuntimeCode` / GV / coderef identity** — ensure **` \&pkg::name`** created before **`*pkg::name = sub { ... }`** shares the callable cell with Perl 5 (stub upgrade when the glob is filled).

2. **POSIX signals on the JVM (secondary)**
Even with (1) fixed, **`kill 'INT', $$`** must run Perl’s **`%SIG`** handler in a process-compatible way. Expect further gaps vs Unix Perl.

**Workarounds today**

- Treat **`t/02_signals.t`** / **`t/03_child.t`** (fork) as **expected gaps** until **`fork`** and signal/coderef semantics match Perl.
- Run signal-heavy upstream suites with **`perl`**, not **`jperl`**, where policy allows (same theme as **`AGENTS.md`** fork note).
- Optional: **distroprefs** patch to early-exit **`t/02_signals.t`** on **`jperl`** (maintenance cost).

**Earlier note on `weaken`:** an older hypothesis tied **`t/02_signals`** to **`weaken`** + **`select`** watchers. The **` \&_signal_exec`** stub issue is a better match for **`Undefined … _signal_exec`** and should be verified first after any GV fix.

---

## Status

| Date | Failed | Passed | Subtests running | Subtests failed |
Expand Down Expand Up @@ -165,10 +212,13 @@ minimal reproduction in `AnyEvent::Socket::parse_address` and fix.

### H — `t/09_multi.t` and `t/02_signals.t`: signal delivery / Ctrl+C

Exit 130 is SIGINT. The test's timer/signal infrastructure is probably
reaching a deadlock and the outer harness sends SIGINT. Likely related
to AnyEvent::Base using `pipe` + signals, which now compiles but may
not actually wake up the select loop.
**Update (2026-05-09):** `t/02_signals.t` fails with **`Bail out! No signal caught.`**
(the 5s timer). Root cause analysis: **`AnyEvent::Base`** installs **` \&_signal_exec`**
before **`*_signal_exec = sub { ... }`**; **`jperl`** does not upgrade the early coderef
when the glob is filled — see **Discussion — jcpan noise & signals** above. Fix **`GV`/stub
coderef parity** first; then re-evaluate JVM **`kill`** / **`%SIG`**.

`Exit 130` / harness SIGINT may still appear when tests hang once the stub bug is fixed.

### I — `t/13_weaken.t`: 3 subtests — weaken semantics

Expand Down Expand Up @@ -209,6 +259,10 @@ Low priority, narrow after A–I land.

### Current status

- **2026-05-09:** Document updated with PR [#700](https://github.com/fglock/PerlOnJava/pull/700)
discussion (warnings/`${^WARNING_BITS}`, AutoSplit **`blib/lib/auto`**, constant redefine)
and clarified **`t/02_signals.t`** analysis (forward **` \&_signal_exec`** stub vs **`weaken`**).

Tier 1 fixes landed. Remaining work:

- **Quick wins likely**: B (lvalue vec), G (inet_pton), J (single
Expand Down
5 changes: 2 additions & 3 deletions src/main/java/org/perlonjava/backend/jvm/EmitVariable.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import org.perlonjava.frontend.astnode.*;
import org.perlonjava.frontend.semantic.SymbolTable;
import org.perlonjava.runtime.perlmodule.Strict;
import org.perlonjava.runtime.perlmodule.Warnings;
import org.perlonjava.runtime.operators.WarnDie;
import org.perlonjava.runtime.runtimetypes.*;

Expand Down Expand Up @@ -1423,8 +1422,8 @@ static void handleMyOperator(EmitterVisitor emitterVisitor, OperatorNode node) {
String var = sigil + name;
if (CompilerOptions.DEBUG_ENABLED) emitterVisitor.ctx.logDebug("MY " + operator + " " + sigil + name);

// Check for redeclaration warnings
if (Warnings.warningManager.isWarningEnabled("redefine")) {
// Check for redeclaration warnings (Perl category "shadow", not "redefine")
if (WarningFlags.ckWarnForScope(emitterVisitor.ctx.symbolTable, "shadow")) {
// Skip warning for state variables that were already hoisted by EmitBlock.
// The hoisted declaration pre-allocates the JVM local slot, so the original
// declaration sees it as a duplicate. This is not a real user-level redeclaration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,8 +357,9 @@ && isGlobalOnlyVariable(name)) {
"\"our\" variable " + var + " redeclared"));
}
} else {
// For 'my'/'local', warn if redeclared in the same scope
if (ctx.symbolTable.getVariableIndexInCurrentScope(var) != -1) {
// For 'my'/'local', warn if redeclared in the same scope (warnings 'shadow')
if (WarningFlags.ckWarnForScope(ctx.symbolTable, "shadow")
&& ctx.symbolTable.getVariableIndexInCurrentScope(var) != -1) {
System.err.println(
ctx.errorUtil.errorMessage(node.getIndex(),
"\"" + operator + "\" variable " + var + " masks earlier declaration in same scope"));
Expand Down
21 changes: 11 additions & 10 deletions src/main/java/org/perlonjava/frontend/parser/SubroutineParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -1140,8 +1140,8 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S
|| existingCode.compilerSupplier != null;
if (isRedefinition) {
oldPrototype = existingCode.prototype;
// A constant sub has empty prototype "()" - detect for "Constant subroutine" warning
isConstantSub = "".equals(oldPrototype);
// Previous sub was compile-time constant iff prototype is "()". (Perl stores "()", not "")
isConstantSub = "()".equals(oldPrototype) || "".equals(oldPrototype);
// Java-registered methods (via registerMethod) have isStatic=true and methodHandle set
isBuiltinSub = existingCode.isStatic && existingCode.methodHandle != null;
}
Expand All @@ -1157,7 +1157,6 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S
}

// Prototype mismatch is a default warning (always on unless explicitly disabled)
boolean dollarW = GlobalVariable.getGlobalVariable("main::" + Character.toString('W' - 'A' + 1)).getBoolean();
{
// Perl format: "sub NAME: none vs (new)" or "sub NAME (old) vs none"
// When prototype is null, display as ": none"; when defined, display as " (proto)"
Expand All @@ -1171,14 +1170,16 @@ public static ListNode handleNamedSubWithFilter(Parser parser, String subName, S
}
}

// "Constant subroutine X redefined" is a default warning (always on)
// "Subroutine X redefined" requires -w or use warnings 'redefine'
// "Subroutine X redefined": ckWARN('redefine') — $^W or lexical 'redefine' in ${^WARNING_BITS}.
// "Constant subroutine X redefined": still emitted when $^W is 0 (e.g. eval under local $^W=0);
// only suppressed by lexical no warnings 'redefine' / no warnings. See perl5_t/t/comp/redef.t.
if (isConstantSub) {
String msg = "Constant subroutine " + subName + " redefined" + location;
org.perlonjava.runtime.operators.WarnDie.warn(
new RuntimeScalar(msg), new RuntimeScalar(""));
} else if (!Warnings.warningManager.isWarningDisabled("redefine")
&& (dollarW || Warnings.warningManager.isWarningEnabled("redefine"))) {
if (!Warnings.warningManager.isWarningDisabled("redefine")) {
String msg = "Constant subroutine " + subName + " redefined" + location;
org.perlonjava.runtime.operators.WarnDie.warn(
new RuntimeScalar(msg), new RuntimeScalar(""));
}
} else if (WarningFlags.ckWarnForScope(parser.ctx.symbolTable, "redefine")) {
String msg = "Subroutine " + subName + " redefined" + location;
org.perlonjava.runtime.operators.WarnDie.warn(
new RuntimeScalar(msg), new RuntimeScalar(""));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,31 @@ public static String toWarningBitsString(BitSet enabled, BitSet fatal,
return new String(bytes, StandardCharsets.ISO_8859_1);
}

/**
* Perl compile-time {@code ckWARN}-style check: honor {@code no warnings 'category'},
* then {@code $^W (-w)}, then the category's enabled bit in the scope's
* {@code ${^WARNING_BITS}} string.
*
* <p>Do not use {@link #warningManager}{@code .isWarningEnabled()} for compile-time
* diagnostics: when {@link #globalWarningsEnabled} is true (after a bare {@code use warnings}
* anywhere in the process), that helper incorrectly treats almost every category as on unless
* explicitly disabled, which breaks modules that set warning state only via
* {@code ${^WARNING_BITS}} (e.g. AnyEvent's generated {@code AnyEvent::common_sense}).
*/
public static boolean ckWarnForScope(ScopedSymbolTable scope, String category) {
if (scope != null && scope.isWarningCategoryDisabled(category)) {
return false;
}
if (GlobalVariable.getGlobalVariable("main::" + Character.toString('W' - 'A' + 1))
.getBoolean()) {
return true;
}
if (scope == null) {
return false;
}
return isEnabledInBits(scope.getWarningBitsString(), category);
}

/**
* Checks if a category is enabled in a warning bits string.
*
Expand Down
4 changes: 4 additions & 0 deletions src/main/perl/lib/ExtUtils/MakeMaker.pm
Original file line number Diff line number Diff line change
Expand Up @@ -743,8 +743,12 @@ $depend_rules_str
# ExtUtils::MakeMaker). Files are NOT copied to INSTALLSITELIB here;
# that happens in the 'install' target.
# Also create blib/arch so that "use blib" / "-Mblib" works (blib.pm requires both).
# Pre-create blib/lib/auto before AutoSplit runs (each staged .pm may call autosplit into it).
# Without this, AutoSplit::autosplit_file creates the dir and prints a warning — stock
# EU::MakeMaker typically creates blib dirs earlier via blibdirs / pm_to_blib ordering.
pm_to_blib::$pm_deps_str
\t\@mkdir -p blib/arch
\t\@mkdir -p blib/lib/auto
$blib_cmds_str

# pure_all is an alias target some postambles (File::ShareDir::Install,
Expand Down
Loading