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
41 changes: 32 additions & 9 deletions src/main/java/org/perlonjava/operators/IOOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
69 changes: 69 additions & 0 deletions src/test/resources/unit/filehandle_dup.t
Original file line number Diff line number Diff line change
@@ -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;