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
17 changes: 7 additions & 10 deletions src/main/java/org/perlonjava/backend/jvm/EmitControlFlow.java
Original file line number Diff line number Diff line change
Expand Up @@ -317,13 +317,14 @@ static void handleGotoSubroutine(EmitterVisitor emitterVisitor, OperatorNode sub
ctx.mv.visitVarInsn(Opcodes.ASTORE, codeRefSlot);

argsNode.accept(emitterVisitor.with(RuntimeContextType.LIST));
// Call getArrayOfAlias() directly on the RuntimeBase value: going through
// getList() would force RuntimeArray.getList() to copy each element with
// `new RuntimeScalar(element)`, which destroys aliasing of @_ across the
// tail call. `goto &SUB` is required to pass the caller's @_ aliased to
// the target sub so that `$_[N] = ...` mutations propagate. (See
// JSON::Validator::Schema::_validate_type_boolean which relies on this.)
ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"org/perlonjava/runtime/runtimetypes/RuntimeBase",
"getList",
"()Lorg/perlonjava/runtime/runtimetypes/RuntimeList;",
false);
ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"org/perlonjava/runtime/runtimetypes/RuntimeList",
"getArrayOfAlias",
"()Lorg/perlonjava/runtime/runtimetypes/RuntimeArray;",
false);
Expand Down Expand Up @@ -406,13 +407,9 @@ static void handleGotoSubroutineBlock(EmitterVisitor emitterVisitor, BlockNode b

// Build the args array
argsNode.accept(emitterVisitor.with(RuntimeContextType.LIST));
// See note in handleGotoSubroutine: skip getList() to preserve @_ aliasing.
ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"org/perlonjava/runtime/runtimetypes/RuntimeBase",
"getList",
"()Lorg/perlonjava/runtime/runtimetypes/RuntimeList;",
false);
ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"org/perlonjava/runtime/runtimetypes/RuntimeList",
"getArrayOfAlias",
"()Lorg/perlonjava/runtime/runtimetypes/RuntimeArray;",
false);
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/perlonjava/core/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "0416ffb3b";
public static final String gitCommitId = "eddceb611";

/**
* Git commit date of the build (ISO format: YYYY-MM-DD).
Expand All @@ -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 30 2026 16:17:32";
public static final String buildTimestamp = "Apr 30 2026 16:52:57";

// Prevent instantiation
private Configuration() {
Expand Down
8 changes: 7 additions & 1 deletion src/main/perl/lib/CPAN/Distribution.pm
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ use POSIX ":sys_wait_h";
use vars qw($VERSION);
$VERSION = "2.34";

my $run_allow_installing_within_test = 1; # boolean; either in test or in install, there is no third option
my $run_allow_installing_within_test = 0; # boolean; either in test or in install, there is no third option
# PerlOnJava: defer the "may downgrade / may install outdated dist" checks to
# the install phase so that `jcpan -t Module::Name` can test older releases
# that contain modules no longer indexed under the same distribution (e.g.
# JSON::Validator::Ref only exists in JSON-Validator-4.25 even though the
# current indexed JSON::Validator dist is 5.15). For full installs the check
# still fires at install time.

# no prepare, because prepare is not a command on the shell command line
# TODO: clear instance cache on reload
Expand Down
46 changes: 46 additions & 0 deletions src/test/resources/unit/tail_calls.t
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,51 @@ SKIP: {
}
}

###################
# @_ aliasing across goto &SUB
# Regression test: goto &SUB must pass the caller's @_ aliased to the
# target sub's @_, so that `$_[N] = ...` in the target sub mutates the
# caller's variable. Previously the bytecode emitted
# `argsValue.getList().getArrayOfAlias()`, and RuntimeArray.getList()
# deep-copied each element via `new RuntimeScalar(element)` — which
# silently broke aliasing across the tail call. JSON::Validator's
# coerce/boolean validators (and many other CPAN modules) depend on it.

# Test 5: goto &NAME preserves $_[0] aliasing to caller's variable
{
sub mutator_target { $_[0] = "MUTATED" }
sub mutator_via_goto { goto &mutator_target }
my $x = "orig";
mutator_via_goto($x);
is($x, "MUTATED", 'goto &NAME preserves @_ aliasing (single arg)');
}

# Test 6: aliasing survives nested goto &NAME chains and method dispatch
{
package GotoAliasObj;
sub new { bless {}, shift }
sub mutate { $_[1] = "M" }
sub via_goto1 { goto &GotoAliasObj::via_goto2 }
sub via_goto2 { my $self = shift; $self->mutate($_[0]) }
sub entry { my $self = shift; $self->via_goto1($_[0]) }
package main;
my $y = "orig";
GotoAliasObj->new->entry($y);
is($y, "M", 'goto &NAME preserves @_ aliasing across method dispatch chain');
}

# Test 7: goto __SUB__ also preserves aliasing
{
my $hits = 0;
sub recursive_mutator {
$hits++;
if ($hits < 3) { goto __SUB__ }
$_[0] = "DONE";
}
my $z = "orig";
recursive_mutator($z);
is($z, "DONE", 'goto __SUB__ preserves @_ aliasing');
}

done_testing();

Loading