From dbb31159649ad6f19ac097dd27c1c9693ea2ea04 Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Wed, 29 Apr 2026 22:26:06 +0200 Subject: [PATCH] fix: preserve string portion of $! for unknown error messages 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 712484220. 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> --- src/main/java/org/perlonjava/core/Configuration.java | 4 ++-- .../runtime/runtimetypes/ErrnoVariable.java | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index 493011b61..02a71a6c1 100644 --- a/src/main/java/org/perlonjava/core/Configuration.java +++ b/src/main/java/org/perlonjava/core/Configuration.java @@ -33,7 +33,7 @@ public final class Configuration { * Automatically populated by Gradle/Maven during build. * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String gitCommitId = "121ffb20f"; + public static final String gitCommitId = "c6eb7bdc7"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). @@ -48,7 +48,7 @@ public final class Configuration { * Parsed by App::perlbrew and other tools via: perl -V | grep "Compiled at" * DO NOT EDIT MANUALLY - this value is replaced at build time. */ - public static final String buildTimestamp = "Apr 29 2026 20:58:13"; + public static final String buildTimestamp = "Apr 29 2026 22:10:32"; // Prevent instantiation private Configuration() { diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrnoVariable.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrnoVariable.java index 82e5aef01..c0dd7be7b 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrnoVariable.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrnoVariable.java @@ -228,13 +228,15 @@ public RuntimeScalar set(String value) { int num = Integer.parseInt(value.trim()); return set(num); } catch (NumberFormatException e) { - // Not a number and not a known message - store as message with errno 0 - // Always maintain INTEGER type so numeric operations use the fast path - // and never trigger "isn't numeric" warnings via NumberParser.parseNumber() + // Not a number and not a known message - store as message with errno 0. + // Use DUALVAR so the string portion is preserved when $! is read or + // copied (e.g. `my $str = $!`). The numeric side of the dualvar is 0 + // (a plain INTEGER scalar) so numeric ops don't go through + // NumberParser.parseNumber() and won't emit "isn't numeric" warnings. this.errno = 0; this.message = value; - this.type = RuntimeScalarType.INTEGER; - this.value = 0; + this.type = RuntimeScalarType.DUALVAR; + this.value = new DualVar(new RuntimeScalar(0), new RuntimeScalar(value)); return this; } }