Skip to content

fix: preserve string portion of $! for unknown error messages#630

Merged
fglock merged 1 commit intomasterfrom
fix/errno-dualvar-string-preservation
Apr 29, 2026
Merged

fix: preserve string portion of $! for unknown error messages#630
fglock merged 1 commit intomasterfrom
fix/errno-dualvar-string-preservation

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 29, 2026

Summary

Fixes a $! dualvar bug discovered while running ./jcpan -t File::Digest.

ErrnoVariable.set(String) was discarding the string and storing type=INTEGER, value=0 whenever the input was neither a number nor a known strerror() message. This is the path SystemOperator takes when it sets $! from a Java IOException message (e.g. after system of a nonexistent program). As a result, my $str = $! saw 0 instead of the message, breaking dualvar semantics.

Reproducer (before fix)

$ ./jperl -e 'system "/tmp/nonexistent"; my $str = $!; print "[$str]\n"; print $str ? "TRUE\n" : "FALSE\n"'
[0]
FALSE

System Perl correctly returns [No such file or directory] / TRUE.

Fix

Store the unknown-string branch as DUALVAR (numeric side RuntimeScalar(0), string side the message), matching the other set(...) paths in the same file. Numeric ops still take the fast path through DualVar.numericValue(), so they don't go through NumberParser.parseNumber() and don't emit "isn't numeric" warnings — preserving the intent of 7124842.

Impact

Unblocks PERLANCAR/Proc-ChildError t/01-basics.t, which does:

system "/tmp/nonexistent...";
like(explain_child_error(), qr/^failed to execute: \S.+ \(-1\)/);

This in turn unblocks much of the File::Digest dependency chain under ./jcpan -t File::Digest:

  • Proc::ChildError — PASS (was FAIL)
  • IPC::System::Options — PASS
  • Perinci::Object — PASS
  • File::Digest — PASS

Remaining failures in the chain (JavaScript::QuickJS, parts of Data::Sah::Coerce, Time::Moment) are unrelated XS/native-binding issues.

Test plan

  • make passes (all unit tests green)
  • ./jperl -e 'system "/tmp/nonexistent"; my $str = $!; ...' now matches system Perl's behaviour
  • ./jcpan -t Proc::ChildError — Result: PASS
  • ./jcpan -t File::Digest — File::Digest itself: Result: PASS

Generated with Devin

ErrnoVariable.set(String) was discarding the string and storing
type=INTEGER, value=0 whenever the input was neither a number nor a
known strerror() message. This is the path taken when SystemOperator
sets $! from a Java IOException message (e.g. after `system` of a
nonexistent program). Result: `my $str = $!` saw 0, not the message,
breaking dualvar semantics.

Fix: store the unknown-string branch as DUALVAR with a RuntimeScalar(0)
numeric side and the message as the string side. Numeric ops still take
the fast path through DualVar.numericValue() so they don't go through
NumberParser.parseNumber() and won't emit "isn't numeric" warnings,
preserving the intent of 7124842.

This unblocks PERLANCAR/Proc-ChildError t/01-basics.t, which does

    system "/tmp/nonexistent";
    like(explain_child_error(), qr/^failed to execute: \S.+ \(-1\)/);

and in turn unblocks the dependency chain for File::Digest under
`./jcpan -t File::Digest` (Proc::ChildError, IPC::System::Options,
Perinci::Object, File::Digest itself all pass now).

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 d5085fd into master Apr 29, 2026
2 checks passed
@fglock fglock deleted the fix/errno-dualvar-string-preservation branch April 29, 2026 20:28
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