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
2 changes: 2 additions & 0 deletions docs/about/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ priorities and future plans.

- Preserve the input line number when a borrowed anonymous filehandle alias leaves a JVM subroutine scope (#1933).

- Preserve filehandle line-counter assignments made through localized `$.` scopes (#1965).

- Resolve glob-backed filehandles in `POSIX::isatty` without assuming the glob value is a `RuntimeIO` (#1932).

- Add an initial CryptX Java XS bridge for binary/base64url helpers, functional digests, and HMAC using the existing JCA and Bouncy Castle providers (#1220).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ static boolean isImmutableProxy(RuntimeBase val) {
// interpreter's mutating opcodes must NOT silently strip it --
// we want `for (3) { ++$_ }` to throw, not silently succeed.
if (val instanceof ReadOnlyAlias) return false;
if (val instanceof ScalarSpecialVariable specialVariable
&& specialVariable.hasSpecialAssignmentBehavior()) {
return false;
}
return val instanceof RuntimeScalarReadOnly || val instanceof ScalarSpecialVariable;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ public boolean isNumberedCapture() {
return variableId == Id.CAPTURE;
}

/** Whether assignments must reach this special scalar's setter. */
public boolean hasSpecialAssignmentBehavior() {
return switch (variableId) {
case INPUT_LINE_NUMBER, HINTS, WARNING_BITS, REMOVED_ENCODING -> true;
default -> false;
};
}

/**
* Throws an exception as this variable represents a constant item
* and cannot be modified.
Expand Down Expand Up @@ -530,9 +538,10 @@ public void dynamicRestoreState() {
if (!inputLineStateStack.isEmpty()) {
InputLineState previous = inputLineStateStack.pop();
RuntimeIO.setLastAccessedHandle(previous.lastHandle);
if (previous.lastHandle != null) {
previous.lastHandle.currentLineNumber = previous.lastLineNumber;
}
// $. localization restores which handle is current, but the
// line counter belongs to that handle. Assignments to $. (and
// reads from the handle) change its counter permanently, even
// when they happen inside a dynamically localized scope.
lvalue = previous.localValue;
// Do not sync this.type/value from lvalue here; $. reads always
// delegate to currentLineNumber via getValueAsScalar().
Expand Down
32 changes: 32 additions & 0 deletions src/test/resources/unit/localized_input_line_number.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use strict;
use warnings;
use Test::More;

my $contents = "one\ntwo\nthree\n";
open my $fh, '<', \$contents or die "open scalar handle: $!";

<$fh>;
<$fh>;
is($., 2, 'reading updates the current handle line number');

seek($fh, 0, 0) or die "seek scalar handle: $!";
if (1) {
local $. = 0;
}
is($., 0, 'localized assignment to $. remains on the filehandle after scope exit');
is(scalar <$fh>, "one\n", 'the handle was rewound');
is($., 1, 'reading after the rewind starts at line one');

my $other_contents = "other one\nother two\n";
open my $other, '<', \$other_contents or die "open second scalar handle: $!";
<$fh>;
<$fh>;
is($., 3, 'the original handle has advanced to line three');
{
local $.;
<$other>;
is($., 1, 'reading another handle updates $. inside the localized scope');
}
is($., 3, 'scope exit restores the previously current handle');

done_testing;
Loading