From e83d4a23369e2349aa5c339b19e7d553c285cf96 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Thu, 18 Dec 2025 17:22:57 +0100 Subject: [PATCH] Support named filehandles in filehandle duplication Added support for duplicating named filehandles like STDERR, STDOUT, and STDIN using the >&NAME syntax (e.g., open SAVEERR, '>&STDERR'). Previously, only numeric file descriptors were supported (e.g., >&2). This caused 'Unsupported filehandle duplication: STDERR' errors when running code that uses the common pattern of saving and restoring standard handles. The fix extends openFileHandleDup() to: - Handle STDIN, STDOUT, STDERR by name (case-insensitive) - Look up other named filehandles in the global IO table Added regression test filehandle_dup.t with 4 subtests covering: - Duplicating STDERR by name - Duplicating STDOUT by name - Duplicating by file descriptor number - Redirect and restore STDERR pattern --- .../org/perlonjava/operators/IOOperator.java | 41 ++++++++--- src/test/resources/unit/filehandle_dup.t | 69 +++++++++++++++++++ 2 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 src/test/resources/unit/filehandle_dup.t diff --git a/src/main/java/org/perlonjava/operators/IOOperator.java b/src/main/java/org/perlonjava/operators/IOOperator.java index 47feac2ca..f35f0668d 100644 --- a/src/main/java/org/perlonjava/operators/IOOperator.java +++ b/src/main/java/org/perlonjava/operators/IOOperator.java @@ -1852,21 +1852,44 @@ private static RuntimeIO findFileHandleByDescriptor(int fd) { public static RuntimeIO openFileHandleDup(String fileName, String mode) { boolean isParsimonious = mode.endsWith("="); // &= modes reuse file descriptor + RuntimeIO sourceHandle = null; + // Check if it's a numeric file descriptor if (fileName.matches("^\\d+$")) { int fd = Integer.parseInt(fileName); - RuntimeIO sourceHandle = findFileHandleByDescriptor(fd); - if (sourceHandle != null && sourceHandle.ioHandle != null) { - if (isParsimonious) { - return sourceHandle; - } else { - return duplicateFileHandle(sourceHandle); - } - } else { + sourceHandle = findFileHandleByDescriptor(fd); + if (sourceHandle == null || sourceHandle.ioHandle == null) { throw new PerlCompilerException("Bad file descriptor: " + fd); } } else { - throw new PerlCompilerException("Unsupported filehandle duplication: " + fileName); + // Handle named filehandles like STDERR, STDOUT, STDIN + switch (fileName.toUpperCase()) { + case "STDIN": + sourceHandle = RuntimeIO.stdin; + break; + case "STDOUT": + sourceHandle = RuntimeIO.stdout; + break; + case "STDERR": + sourceHandle = RuntimeIO.stderr; + break; + default: + // Try to look up as a global filehandle + String normalizedName = fileName.contains("::") ? fileName : "main::" + fileName; + RuntimeGlob glob = GlobalVariable.getGlobalIO(normalizedName); + if (glob != null) { + sourceHandle = glob.getRuntimeIO(); + } + if (sourceHandle == null || sourceHandle.ioHandle == null) { + throw new PerlCompilerException("Unsupported filehandle duplication: " + fileName); + } + } + } + + if (isParsimonious) { + return sourceHandle; + } else { + return duplicateFileHandle(sourceHandle); } } diff --git a/src/test/resources/unit/filehandle_dup.t b/src/test/resources/unit/filehandle_dup.t new file mode 100644 index 000000000..2b4ad2e95 --- /dev/null +++ b/src/test/resources/unit/filehandle_dup.t @@ -0,0 +1,69 @@ +use strict; +use warnings; +use Test::More tests => 4; + +# Test filehandle duplication with named filehandles (STDERR, STDOUT, STDIN) +# This is a regression test for the "Unsupported filehandle duplication: STDERR" error + +subtest 'duplicate STDERR by name' => sub { + plan tests => 2; + + # Save STDERR to a new filehandle + open my $saveerr, ">&STDERR" or die "Cannot dup STDERR: $!"; + ok(defined $saveerr, 'Duplicated STDERR to lexical filehandle'); + + # Write to the duplicated handle + print $saveerr ""; # Just test that we can write without error + ok(1, 'Can write to duplicated STDERR handle'); + + close $saveerr; +}; + +subtest 'duplicate STDOUT by name' => sub { + plan tests => 2; + + # Save STDOUT to a new filehandle + open my $saveout, ">&STDOUT" or die "Cannot dup STDOUT: $!"; + ok(defined $saveout, 'Duplicated STDOUT to lexical filehandle'); + + # Write to the duplicated handle + print $saveout ""; # Just test that we can write without error + ok(1, 'Can write to duplicated STDOUT handle'); + + close $saveout; +}; + +subtest 'duplicate by file descriptor number' => sub { + plan tests => 2; + + # Duplicate STDERR using file descriptor 2 + open my $saveerr, ">&2" or die "Cannot dup fd 2: $!"; + ok(defined $saveerr, 'Duplicated fd 2 (STDERR) to lexical filehandle'); + + # Duplicate STDOUT using file descriptor 1 + open my $saveout, ">&1" or die "Cannot dup fd 1: $!"; + ok(defined $saveout, 'Duplicated fd 1 (STDOUT) to lexical filehandle'); + + close $saveerr; + close $saveout; +}; + +subtest 'redirect and restore STDERR' => sub { + plan tests => 2; + + # This pattern is commonly used to temporarily suppress STDERR + my $dummy = \*SAVEERR; # avoid "used only once" warning + + # Save STDERR + open SAVEERR, ">&STDERR" or die "Cannot save STDERR: $!"; + ok(1, 'Saved STDERR to bareword filehandle'); + + # Restore STDERR + close STDERR; + open STDERR, ">&SAVEERR" or die "Cannot restore STDERR: $!"; + ok(1, 'Restored STDERR from saved handle'); + + close SAVEERR; +}; + +1;