Skip to content

fix: POSIX :fcntl_h seek constants + cleaner Java-exception error reporting#539

Merged
fglock merged 4 commits intomasterfrom
fix/posix-fcntl-seek
Apr 22, 2026
Merged

fix: POSIX :fcntl_h seek constants + cleaner Java-exception error reporting#539
fglock merged 4 commits intomasterfrom
fix/posix-fcntl-seek

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 22, 2026

Summary

Two small, independent runtime/stdlib fixes surfaced while investigating jcpan -t IO::Any.

POSIX :fcntl_h now exports SEEK_SET / SEEK_CUR / SEEK_END

Real perl's POSIX :fcntl_h exports these; ours did not. Modules that do
use POSIX qw(:fcntl_h) and then use SEEK_SET as a bareword — notably
File::Slurp — die at runtime with Bareword "SEEK_SET" not allowed while "strict subs" in use at File/Slurp.pm line 119, which cascades into
IO::Any, Config::Simple, and anything else pulling in File::Slurp.

CLI reporter no longer surfaces raw Java exception class names

When a Java exception leaked through to the reporter, users would sometimes
see two cryptic Java-looking lines above the real message:

java.lang.NoClassDefFoundError: org/perlonjava/runtime/operators/KillOperator
org.perlonjava.runtime.operators.KillOperator
        TAP::Parser::Iterator::Process at jar:PERL5LIB/TAP/Parser/Iterator/Process.pm line 272

Two causes:

  • A wrapping exception whose message was built from inner.toString()
    ("java.lang.Foo: msg") caused both the wrapper and the inner message to
    print.
  • NoClassDefFoundError (slash-separated) commonly wraps
    ClassNotFoundException (dot-separated) for the same class; the two
    differ only in / vs . and were treated as independent messages.

ErrorMessageUtil.stringifyException gains two helpers:

  • isJavaToStringOf(outer, inner) — suppresses the outer line when it is
    literally Throwable.toString() of any ancestor in the cause chain, or
    differs only in / vs . from an ancestor's message.
  • stripJavaExceptionPrefix(message) — strips a leading fully-qualified
    Java exception class prefix (java.lang.X: …, org.foo.YException: …).
    The pattern requires a dotted prefix with at least two segments plus
    a recognizable Exception / Error / Throwable suffix, so legit Perl
    messages like "DBI error: handle closed" or "Foo: not a java class"
    pass through unchanged.

Impact on jcpan -t IO::Any

Test file Before After
t/01_IO-Any.t 15/34 failed, died at test 20 (SEEK_SET) 1/34 failed (platform flock)
t/02_IO-Any_AnyEvent.t timed out, noisy NoClassDefFoundError output timed out, clean output
t/03_DATA.t ok ok

Remaining failures are unrelated to this PR:

  • t/01_IO-Any.t test 30 is a non-blocking shared-lock assertion that hits
    EDEADLK on macOS — flock emulation quirk.
  • t/02_IO-Any_AnyEvent.t times out because AnyEvent::Loop's I/O watchers
    are destroyed on return (a push @q, $self; weaken $q[-1] refcount issue
    that is being addressed on a separate refcount branch).

Test plan

  • Added ErrorMessageUtilTest with 5 cases (wrapped NCDF, slashed/dotted
    dedup, Perl pass-through, false-positive Foo: bar strings).
  • make — full unit test suite green.
  • Manual re-run of jcpan -t IO::Any before/after; output diff above.

Generated with Devin

fglock and others added 4 commits April 22, 2026 13:10
The POSIX :fcntl_h export tag was missing the seek constants. Real
perl's POSIX exports SEEK_SET/SEEK_CUR/SEEK_END under :fcntl_h, and
modules like File::Slurp rely on this (use POSIX qw(:fcntl_h) then
use SEEK_SET as a bareword).

Without this, File::Slurp fails at runtime with:
  Bareword "SEEK_SET" not allowed while "strict subs" in use

which breaks any module depending on File::Slurp (IO::Any, etc.).

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
When a Java exception leaks through to the CLI reporter, users were
sometimes shown two cryptic lines:

    java.lang.NoClassDefFoundError: org/perlonjava/runtime/operators/KillOperator
    org.perlonjava.runtime.operators.KillOperator
            <perl stack trace>

This happens in two scenarios:
  - A wrapping exception's message is set from Throwable.toString() of the
    inner cause ("java.lang.Foo: message"), so both the wrapper and the
    inner print.
  - NoClassDefFoundError wraps ClassNotFoundException for the same class;
    the two differ only in slash vs dot separators.

ErrorMessageUtil.stringifyException now:
  - Suppresses the outer message when it is literally Throwable.toString()
    of an ancestor in the cause chain, or when it differs only in / vs .
    from that ancestor's message (isJavaToStringOf).
  - Strips a leading fully-qualified Java exception class prefix
    ("java.lang.X: ...") from whatever message remains, so users see the
    actual description instead of JVM class names
    (stripJavaExceptionPrefix).

Perl-style messages (single-word package, trailing newline suppression,
etc.) pass through unchanged. Added ErrorMessageUtilTest covering
happy-path Perl messages, wrapped Java errors, and the slashed/dotted
duplicate case.

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@fglock fglock merged commit ba42e20 into master Apr 22, 2026
2 checks passed
@fglock fglock deleted the fix/posix-fcntl-seek branch April 22, 2026 12:49
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