From 3b8685c48eea965cb0eeb0963275a2aa1bdaf8c8 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Tue, 21 Apr 2026 20:22:36 +0200 Subject: [PATCH] fix(tie,MakeMaker): AUTOLOAD fallback + INST_ARCHLIBDIR expansion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes surfaced while running `jcpan -t AI::Prolog`: 1. tie: Do not fall through to AUTOLOAD for UNTIE/DESTROY. Retying an already-tied hash (as Regexp::Common does when its import loads sub-modules that themselves `use Regexp::Common`) triggers an implicit untie. The untie path calls tiedUntie() which, via tieCallIfExists("UNTIE"), was using InheritanceResolver.findMethodInHierarchy — which transparently falls back to AUTOLOAD. That invoked Regexp::Common's AUTOLOAD with an unset $AUTOLOAD, producing the cryptic error `Can't at .../balanced.pm line 9.` and breaking loading of Regexp::Common entirely. tie special methods (UNTIE, DESTROY) are "if exists" only: they should not trigger AUTOLOAD dispatch. Reject AUTOLOAD-resolved matches (detected via code.autoloadVariableName != null) in all four tieCallIfExists helpers: TiedVariableBase, TieHash, TieArray, TieHandle. 2. MakeMaker: Expand $(INST_ARCHLIBDIR)/$(INST_AUTODIR) and tolerate missing generated sources. Term::ReadKey's Makefile.PL sets `PM => { 'ReadKey.pm' => '$(INST_ARCHLIBDIR)/ReadKey.pm' }`. PerlOnJava's MakeMaker only expanded INST_LIB/INST_ARCHLIB/ INST_LIBDIR, leaving INST_ARCHLIBDIR literal which produced `mkdir: : No such file or directory`. - Derive and paths from NAME and expand INST_LIBDIR, INST_ARCHLIBDIR, INST_AUTODIR, INST_ARCHAUTODIR (both $(...) and ${...} forms). - Make `_shell_cp` tolerant of missing source files. Some distributions (Term::ReadKey) generate .pm via a .pm.PL that requires XS bootstrap; we cannot run that, but the install should still succeed so dependent modules can proceed. Result: `jcpan -t AI::Prolog`: 460/460 tests pass (previously 14/19 test files failed to load at all). Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../org/perlonjava/core/Configuration.java | 4 +- .../runtime/runtimetypes/TieArray.java | 5 +++ .../runtime/runtimetypes/TieHandle.java | 5 +++ .../runtime/runtimetypes/TieHash.java | 5 +++ .../runtimetypes/TiedVariableBase.java | 5 +++ src/main/perl/lib/ExtUtils/MakeMaker.pm | 39 ++++++++++++++++--- 6 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index d890f8464..bb54e5ea5 100644 --- a/src/main/java/org/perlonjava/core/Configuration.java +++ b/src/main/java/org/perlonjava/core/Configuration.java @@ -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 = "5ab2ed282"; + public static final String gitCommitId = "9a275c7a1"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). @@ -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 21 2026 16:09:03"; + public static final String buildTimestamp = "Apr 21 2026 20:21:33"; // Prevent instantiation private Configuration() { diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/TieArray.java b/src/main/java/org/perlonjava/runtime/runtimetypes/TieArray.java index 22fc770ee..c5398e370 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/TieArray.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/TieArray.java @@ -107,6 +107,11 @@ private static RuntimeScalar tieCallIfExists(RuntimeArray array, String methodNa // Method doesn't exist, return undef return RuntimeScalarCache.scalarUndef; } + // Ignore AUTOLOAD fallback — tie special methods (UNTIE, DESTROY) are + // "if exists" only; they should not trigger AUTOLOAD dispatch. + if (method.value instanceof RuntimeCode rc && rc.autoloadVariableName != null) { + return RuntimeScalarCache.scalarUndef; + } // Method exists, call it return RuntimeCode.apply(method, new RuntimeArray(self), RuntimeContextType.SCALAR).getFirst(); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/TieHandle.java b/src/main/java/org/perlonjava/runtime/runtimetypes/TieHandle.java index ea66b6c2f..7c690855c 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/TieHandle.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/TieHandle.java @@ -201,6 +201,11 @@ private RuntimeScalar tieCallIfExists(String methodName) { // Method doesn't exist, return undef return RuntimeScalarCache.scalarUndef; } + // Ignore AUTOLOAD fallback — tie special methods (UNTIE, DESTROY) are + // "if exists" only; they should not trigger AUTOLOAD dispatch. + if (method.value instanceof RuntimeCode rc && rc.autoloadVariableName != null) { + return RuntimeScalarCache.scalarUndef; + } // Method exists, call it return RuntimeCode.apply(method, new RuntimeArray(self), RuntimeContextType.SCALAR).getFirst(); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/TieHash.java b/src/main/java/org/perlonjava/runtime/runtimetypes/TieHash.java index 5d9a80bc9..abdca40ed 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/TieHash.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/TieHash.java @@ -94,6 +94,11 @@ private static RuntimeScalar tieCallIfExists(RuntimeHash hash, String methodName // Method doesn't exist, return undef return RuntimeScalarCache.scalarUndef; } + // Ignore AUTOLOAD fallback — tie special methods (UNTIE, DESTROY) are + // "if exists" only; they should not trigger AUTOLOAD dispatch. + if (method.value instanceof RuntimeCode rc && rc.autoloadVariableName != null) { + return RuntimeScalarCache.scalarUndef; + } // Method exists, call it return RuntimeCode.apply(method, new RuntimeArray(self), RuntimeContextType.SCALAR).getFirst(); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/TiedVariableBase.java b/src/main/java/org/perlonjava/runtime/runtimetypes/TiedVariableBase.java index c4e694101..561b8c176 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/TiedVariableBase.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/TiedVariableBase.java @@ -80,6 +80,11 @@ protected RuntimeScalar tieCallIfExists(String methodName) { // Method doesn't exist, return undef return RuntimeScalarCache.scalarUndef; } + // Ignore AUTOLOAD fallback — tie special methods (UNTIE, DESTROY) are + // "if exists" only; they should not trigger AUTOLOAD dispatch. + if (method.value instanceof RuntimeCode rc && rc.autoloadVariableName != null) { + return RuntimeScalarCache.scalarUndef; + } // Method exists, call it return RuntimeCode.apply(method, new RuntimeArray(self), RuntimeContextType.SCALAR).getFirst(); diff --git a/src/main/perl/lib/ExtUtils/MakeMaker.pm b/src/main/perl/lib/ExtUtils/MakeMaker.pm index 7e0ecc52c..ca14aab6f 100644 --- a/src/main/perl/lib/ExtUtils/MakeMaker.pm +++ b/src/main/perl/lib/ExtUtils/MakeMaker.pm @@ -193,13 +193,38 @@ sub _install_pure_perl { # Use explicit PM hash if provided if ($args->{PM}) { %pm = %{$args->{PM}}; - # Expand Make-style variables like $(INST_LIB) to actual paths + # Expand Make-style variables like $(INST_LIB) to actual paths. + # Standard MakeMaker derives directory-bearing vars from NAME: + # INST_LIBDIR = INST_LIB/ (e.g. blib/lib/Term) + # INST_ARCHLIBDIR = INST_ARCHLIB/ + # INST_AUTODIR = INST_LIB/auto/ + # INST_ARCHAUTODIR = INST_ARCHLIB/auto/ + # where is NAME with the last :: component removed and + # is the full NAME with :: replaced by /. + my @parts = split /::/, ($name || ''); + pop @parts; # drop BASEEXT (last component) + my $parent_dir = @parts ? join('/', @parts) : ''; + (my $full_path = ($name || '')) =~ s{::}{/}g; + my $libdir = $parent_dir + ? File::Spec->catdir($INSTALL_BASE, $parent_dir) + : $INSTALL_BASE; + my $autodir = $full_path + ? File::Spec->catdir($INSTALL_BASE, 'auto', $full_path) + : $INSTALL_BASE; for my $key (keys %pm) { my $val = $pm{$key}; + # Directory-bearing variables (must come before the bare LIB/ARCHLIB forms) + $val =~ s/\$\(INST_LIBDIR\)/$libdir/g; + $val =~ s/\$\(INST_ARCHLIBDIR\)/$libdir/g; # treat ARCHLIBDIR same as LIBDIR + $val =~ s/\$\(INST_AUTODIR\)/$autodir/g; + $val =~ s/\$\(INST_ARCHAUTODIR\)/$autodir/g; + $val =~ s/\$\{INST_LIBDIR\}/$libdir/g; + $val =~ s/\$\{INST_ARCHLIBDIR\}/$libdir/g; + # Bare library roots $val =~ s/\$\(INST_LIB\)/$INSTALL_BASE/g; $val =~ s/\$\(INST_ARCHLIB\)/$INSTALL_BASE/g; # treat ARCHLIB same as LIB - $val =~ s/\$\(INST_LIBDIR\)/$INSTALL_BASE/g; - $val =~ s/\$\{INST_LIB\}/$INSTALL_BASE/g; # also handle ${VAR} form + $val =~ s/\$\{INST_LIB\}/$INSTALL_BASE/g; + $val =~ s/\$\{INST_ARCHLIB\}/$INSTALL_BASE/g; $pm{$key} = $val; } } else { @@ -637,12 +662,16 @@ sub _shell_mkdir { return "\t\@mkdir -p '$dir'"; } -# Helper: generate a shell cp command for Makefile +# Helper: generate a shell cp command for Makefile. +# Tolerant of missing source files: some distributions generate .pm files +# from .pm.PL scripts that require XS bootstrap (e.g. Term::ReadKey) and +# PerlOnJava cannot run them. We skip missing sources with a warning +# rather than failing the whole install. sub _shell_cp { my ($src, $dest) = @_; $src =~ s/'/'\\''/g; $dest =~ s/'/'\\''/g; - return "\t\@rm -f '$dest' && cp '$src' '$dest'"; + return "\t\@if [ -f '$src' ]; then rm -f '$dest' && cp '$src' '$dest'; else echo 'PerlOnJava: skipping missing source: $src'; fi"; } sub _create_mymeta {