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
3 changes: 2 additions & 1 deletion dev/design/issue-queue.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Reviewed issue queues

Snapshot: 2026-10-28, from live open issues, PRs and the #1649 phase contract.
Snapshot: 2026-10-29, from live open issues, PRs and the #1649 phase contract.
This is a bounded selection aid, not the complete backlog or fresh runtime
validation. Follow the [triage and claim protocol](../../docs/guides/issue-triage.md)
and recheck live issue comments/PRs before starting. Unlisted work is unreviewed
Expand All @@ -26,6 +26,7 @@ resolved before implementation. No new pass or failure is asserted here.

| Code/test boundary | Issue | Priority / state | Evidence, next action and blocker |
|---|---|---|---|
| File-handle EOF state | [#1922](https://github.com/fglock/PerlOnJava/issues/1922) `IO::Handle::clearerr` | Normal / Active | `Log::Procmail` 0.14 passes on system Perl but failed on both PerlOnJava backends when reading appended data after EOF. The shared runtime fix and permanent regression are in [draft PR #1924](https://github.com/fglock/PerlOnJava/pull/1924); the full make gate and all 281 CPAN tests pass. Await review and CI. |
| File metadata | [#1912](https://github.com/fglock/PerlOnJava/issues/1912) `Time::HiRes::stat` | Normal / Active | Daiku 1.004 passes on system Perl and exposed the missing runtime function; the implementation and both-backend regression are in [PR #1913](https://github.com/fglock/PerlOnJava/pull/1913). |
| XML provider APIs | [#1892](https://github.com/fglock/PerlOnJava/issues/1892) XSLT output | High / Active | [PR #1894](https://github.com/fglock/PerlOnJava/pull/1894) is open; verify its serialization fix against HTML::ToDocBook. Coordinate through that work rather than start a duplicate. |
| XML provider APIs | [#1323](https://github.com/fglock/PerlOnJava/issues/1323) `indexElements` | Normal / Ready | Missing Document method is identified. Add standard-Perl-validated focused coverage and Cindy acceptance; check XML provider overlap with #1894. No established ownership prerequisite. |
Expand Down
2 changes: 2 additions & 0 deletions docs/about/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ priorities and future plans.
- Run MakeMaker `MY::post_initialize` hooks so generated files join `pm_to_blib`, and parse Perl's `$]` version variable.

- Preserve hash-reference constant calls after typeglob undefinition, dereference glob-backed hash values during Morpheus configuration traversal, parse Error.pm's dynamic `catch $class with { ... }` form on both backends, and retain integer scalar identity through `int()` for decimal stringification; preserve regex referent types through blessed scalar references and cover Morpheus relational overload dispatch and caller filename normalization (#1342, #1376, #1427, #1428).
- Clear EOF state across file-handle wrappers when `IO::Handle::clearerr` is called, allowing reads to resume after a file grows (#1922).
- Preserve exact integer results for wide `unpack` checksums instead of zeroing sums rounded by double conversion.
- Implement `Time::HiRes::stat` with high-resolution file timestamps (#1912).

- Export `fieldhash` from `Hash::Util` for compatibility with core Perl.
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/org/perlonjava/runtime/io/BorrowedIOHandle.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ public RuntimeScalar eof() {
return delegate.eof();
}

@Override
public RuntimeScalar clearerr() {
if (closed) return handleClosed("clearerr");
return delegate.clearerr();
}

@Override
public RuntimeScalar tell() {
if (closed) return handleClosed("tell");
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/org/perlonjava/runtime/io/CustomFileChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,18 @@ public RuntimeScalar eof() {
}
}

/**
* Clear EOF state so a regular file can be read again after it grows.
* Keep any unread bytes in the read-ahead buffer; only discard the physical
* EOF observation so the next read can probe the channel again.
*/
@Override
public RuntimeScalar clearerr() {
isEOF = false;
physicalEof = false;
return new RuntimeScalar(0);
}

/**
* Gets the current position in the file.
*
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/org/perlonjava/runtime/io/DupIOHandle.java
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,12 @@ public RuntimeScalar eof() {
return delegate.eof();
}

@Override
public RuntimeScalar clearerr() {
if (closed) return handleClosed("clearerr");
return delegate.clearerr();
}

@Override
public RuntimeScalar tell() {
if (closed) return handleClosed("tell");
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/org/perlonjava/runtime/io/IOHandle.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,15 @@ default RuntimeScalar eof() {
return RuntimeIO.handleIOError("eof operation is not supported.");
}

/**
* Clears the handle's EOF indicator. The Perl-level IO::Handle method also
* clears the process error variable; handles with buffered EOF state must
* reset that state here so reads can resume after the underlying source grows.
*/
default RuntimeScalar clearerr() {
return new RuntimeScalar(0);
}

default RuntimeScalar tell() {
return RuntimeIO.handleIOError("tell operation is not supported.");
}
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/perlonjava/runtime/io/LayeredIOHandle.java
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,11 @@ public RuntimeScalar eof() {
return delegate.eof();
}

@Override
public RuntimeScalar clearerr() {
return delegate.clearerr();
}

/**
* Returns the current position in the file.
*
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/org/perlonjava/runtime/io/NativeFdIOHandle.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ public RuntimeScalar eof() {
return eofReached ? RuntimeScalarCache.scalarTrue : RuntimeScalarCache.scalarFalse;
}

@Override
public RuntimeScalar clearerr() {
eofReached = false;
return new RuntimeScalar(0);
}

@Override
public boolean isReadReady() {
return !closed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public ThreadInheritancePolicy threadInheritancePolicy() {
@Override public RuntimeScalar fileno() { return open() ? delegate.fileno() : closed("fileno"); }
@Override public boolean isReadReady() { return !open() || delegate.isReadReady(); }
@Override public RuntimeScalar eof() { return open() ? delegate.eof() : scalarTrue; }
@Override public RuntimeScalar clearerr() { return open() ? delegate.clearerr() : closed("clearerr"); }
@Override public RuntimeScalar tell() { return open() ? delegate.tell() : closed("tell"); }
@Override public RuntimeScalar seek(long pos, int whence) { return open() ? delegate.seek(pos, whence) : closed("seek"); }
@Override public RuntimeScalar truncate(long length) { return open() ? delegate.truncate(length) : closed("truncate"); }
Expand Down
20 changes: 4 additions & 16 deletions src/main/java/org/perlonjava/runtime/operators/Unpack.java
Original file line number Diff line number Diff line change
Expand Up @@ -483,22 +483,10 @@ private static RuntimeList unpackInternal(String template, UnpackState state,
BigInteger mask = BigInteger.ONE.shiftLeft(checksumBits).subtract(BigInteger.ONE);
bigChecksum = bigChecksum.and(mask);

// Now check if the masked value would lose precision as a double.
double maskedAsDouble = bigChecksum.doubleValue();
if ((format != 'Q' && format != 'J')
&& maskedAsDouble > 0 && maskedAsDouble == maskedAsDouble - 1.0) {
values.add(new RuntimeScalar(0));
} else {
// Perl's unsigned-native checksum promotion at
// 63 bits retains the carry that crosses the
// signed-long boundary.
if ((format == 'Q' || format == 'J') && checksumBits == 63
&& bigChecksum.equals(BigInteger.ONE.shiftLeft(63).subtract(BigInteger.ONE))) {
values.add(new RuntimeScalar(bigChecksum.add(BigInteger.TWO)));
} else {
values.add(new RuntimeScalar(bigChecksum.longValue()));
}
}
// Keep integer checksums exact. Converting to double
// here incorrectly turns values such as 2**54 - 1
// into zero when the double rounds up to 2**54.
values.add(new RuntimeScalar(bigChecksum.longValue()));
} else if (checksumBits == 64) {
// 64-bit mask: 2^64 - 1 = 0xFFFFFFFFFFFFFFFF
BigInteger mask = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE);
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/org/perlonjava/runtime/perlmodule/IOHandle.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,10 @@ public static RuntimeList _clearerr(RuntimeArray args, int ctx) {
return new RuntimeList(new RuntimeScalar(-1));
}

// Clear $!
RuntimeScalar result = fh.ioHandle.clearerr();
// Clear $! after the underlying handle has reset its EOF/error state.
GlobalVariable.getGlobalVariable("main::!").set("");
return new RuntimeList(new RuntimeScalar(0));
return result.getList();
}

/**
Expand Down
22 changes: 22 additions & 0 deletions src/test/resources/unit/io_clearerr_after_eof.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use strict;
use warnings;
use Test::More tests => 4;
use File::Temp qw(tempfile);
use IO::File;

my ($writer, $path) = tempfile();
print {$writer} "first\n" or die "write initial line: $!";
close $writer or die "close initial writer: $!";

my $reader = IO::File->new($path, '<') or die "open reader: $!";
is(scalar <$reader>, "first\n", 'read initial line');
is(scalar <$reader>, undef, 'read reaches EOF');

open $writer, '>>', $path or die "open append writer: $!";
print {$writer} "second\n" or die "append line: $!";
close $writer or die "close append writer: $!";

is($reader->clearerr, 0, 'clearerr succeeds');
is(scalar <$reader>, "second\n", 'read data appended after EOF');

$reader->close;
8 changes: 8 additions & 0 deletions src/test/resources/unit/pack_core_regressions.t
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ is(length(pack('D', 0)), $Config::Config{longdblsize},
'Config reports the implemented long-double pack width');
is(unpack('%54Q', pack('Q', 18014398509481983)), 18014398509481983,
'wide Q checksum retains integer precision modulo 2**54');
is(unpack('%63Q*', pack('Q*', 0, 1, 9223372036854775807,
9223372036854775808, 18446744073709551615)),
'9223372036854775807', 'wide Q checksum preserves the exact 63-bit masked sum');
for my $bits (54, 63) {
my $expected = $bits == 54 ? '18014398509481983' : '9223372036854775807';
is(unpack("%${bits}c*", pack('c*', -128, -1, 0, 1, 127)), $expected,
"wide c checksum retains integer precision modulo 2**$bits");
}
is(unpack('w', pack('w', '18014398509481982')), '18014398509481982',
'BER preserves a large exact integer string');
{
Expand Down
Loading