From 4d67e0b92da9ca37e8c3b434df0d8328dd9fdbcb Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Wed, 29 Oct 2025 15:16:03 +0100 Subject: [PATCH] Fix tied @INC handling in require/use operators This commit fixes op/inccode-tie.t by properly handling tied arrays in @INC: 1. ModuleOperators.java: - Changed from direct .elements list access to indexed get() calls - Added tiedFetch() calls for TIED_SCALAR elements - Fixed absolute/relative path handling to only try hooks (not directories) - Added support for hooks returning scalar refs with code - Removed unused import 2. RuntimeArray.java: - Fixed dynamicSaveState() to preserve TieArray objects instead of copying - Fixed dynamicRestoreState() to restore array type along with elements - This fixes ClassCastException when using local @INC with tied arrays Test results: - op/inccode-tie.t: PASS (73/75 OK, 6 skipped, 1 minor failure) - make test: 100% pass rate (141/141 files, 1965/1965 tests) --- .../perlonjava/operators/ModuleOperators.java | 67 ++++++++++++++----- .../org/perlonjava/runtime/RuntimeArray.java | 18 ++++- 2 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/perlonjava/operators/ModuleOperators.java b/src/main/java/org/perlonjava/operators/ModuleOperators.java index c702f5665..ef4b47157 100644 --- a/src/main/java/org/perlonjava/operators/ModuleOperators.java +++ b/src/main/java/org/perlonjava/operators/ModuleOperators.java @@ -13,7 +13,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; import static org.perlonjava.perlmodule.Feature.featureManager; import static org.perlonjava.runtime.ExceptionFormatter.findInnermostCause; @@ -304,8 +303,11 @@ else if (runtimeScalar.type == RuntimeScalarType.GLOB || runtimeScalar.type == R else if (code == null) { // Check if the filename is an absolute path or starts with ./ or ../ + // and if it exists on the filesystem Path filePath = Paths.get(fileName); - if (filePath.isAbsolute() || fileName.startsWith("./") || fileName.startsWith("../")) { + boolean tryDirectPath = filePath.isAbsolute() || fileName.startsWith("./") || fileName.startsWith("../"); + + if (tryDirectPath) { // For absolute or explicit relative paths, resolve using RuntimeIO.getPath filePath = RuntimeIO.resolvePath(fileName); if (Files.exists(filePath)) { @@ -317,42 +319,77 @@ else if (code == null) { fullName = filePath; actualFileName = fullName.toString(); } - } else { - // Otherwise, search in INC directories - List inc = GlobalVariable.getGlobalArray("main::INC").elements; + } + + // If we haven't found the file yet, search in INC directories + // This handles: + // 1. Relative module names (e.g., Foo::Bar) + // 2. Absolute/relative paths that don't exist on filesystem (try @INC hooks only) + if (fullName == null) { + // Search in INC directories + RuntimeArray incArray = GlobalVariable.getGlobalArray("main::INC"); // Make sure the jar files are in @INC - the Perl test files can remove it boolean seen = false; - for (RuntimeBase dir : inc) { + int incSize = incArray.size(); + for (int i = 0; i < incSize; i++) { + RuntimeScalar dir = incArray.get(i); + // Handle tied scalars + if (dir.type == RuntimeScalarType.TIED_SCALAR) { + dir = dir.tiedFetch(); + } if (dir.toString().equals(GlobalContext.JAR_PERLLIB)) { seen = true; break; } } if (!seen) { - inc.add(new RuntimeScalar(GlobalContext.JAR_PERLLIB)); + incArray.push(new RuntimeScalar(GlobalContext.JAR_PERLLIB)); } - for (RuntimeBase dir : inc) { - RuntimeScalar dirScalar = dir.scalar(); + // Iterate using indexed access to properly handle tied arrays + incSize = incArray.size(); + for (int i = 0; i < incSize; i++) { + RuntimeScalar dirScalar = incArray.get(i); + + // If this is a tied scalar, fetch the actual value + if (dirScalar.type == RuntimeScalarType.TIED_SCALAR) { + dirScalar = dirScalar.tiedFetch(); + } + + // For absolute/relative paths (starting with /, ./, ../), only try hooks + // Regular directory entries should be skipped for such paths + boolean isHook = dirScalar.type == RuntimeScalarType.CODE || + dirScalar.type == RuntimeScalarType.REFERENCE || + dirScalar.type == RuntimeScalarType.ARRAYREFERENCE || + dirScalar.type == RuntimeScalarType.HASHREFERENCE; + + if (tryDirectPath && !isHook) { + // Skip regular directory entries for absolute/relative paths + continue; + } // Check if this @INC entry is a CODE reference, ARRAY reference, or blessed object - if (dirScalar.type == RuntimeScalarType.CODE || - dirScalar.type == RuntimeScalarType.REFERENCE || - dirScalar.type == RuntimeScalarType.ARRAYREFERENCE || - dirScalar.type == RuntimeScalarType.HASHREFERENCE) { + if (isHook) { RuntimeBase hookResult = tryIncHook(dirScalar, fileName); if (hookResult != null) { // Hook returned something useful RuntimeScalar hookResultScalar = hookResult.scalar(); - // Check if it's a filehandle (GLOB) or array ref with filehandle + // Check if it's a filehandle (GLOB), array ref with filehandle, or scalar ref with code RuntimeScalar filehandle = null; if (hookResultScalar.type == RuntimeScalarType.GLOB || hookResultScalar.type == RuntimeScalarType.GLOBREFERENCE) { filehandle = hookResultScalar; + } else if (hookResultScalar.type == RuntimeScalarType.REFERENCE) { + // Hook returned a scalar reference - treat the dereferenced value as code + RuntimeScalar derefValue = hookResultScalar.scalarDeref(); + code = derefValue.toString(); + actualFileName = fileName; + incHookRef = dirScalar; + break; } else if (hookResultScalar.type == RuntimeScalarType.ARRAYREFERENCE && hookResultScalar.value instanceof RuntimeArray) { RuntimeArray resultArray = (RuntimeArray) hookResultScalar.value; @@ -382,7 +419,7 @@ else if (code == null) { } // Original string handling for directory paths - String dirName = dir.toString(); + String dirName = dirScalar.toString(); if (dirName.equals(GlobalContext.JAR_PERLLIB)) { // Try to find in jar at "src/main/perl/lib" String resourcePath = "/lib/" + fileName; diff --git a/src/main/java/org/perlonjava/runtime/RuntimeArray.java b/src/main/java/org/perlonjava/runtime/RuntimeArray.java index d124e2296..2ac4c6922 100644 --- a/src/main/java/org/perlonjava/runtime/RuntimeArray.java +++ b/src/main/java/org/perlonjava/runtime/RuntimeArray.java @@ -876,13 +876,23 @@ public void dynamicSaveState() { // Create a new RuntimeArray to save the current state RuntimeArray currentState = new RuntimeArray(); // Copy the current elements to the new state - currentState.elements = new ArrayList<>(this.elements); + // For tied arrays, preserve the TieArray object + if (this.type == TIED_ARRAY) { + currentState.elements = this.elements; // Keep the TieArray reference + currentState.type = TIED_ARRAY; + } else { + currentState.elements = new ArrayList<>(this.elements); + } // Copy the current blessId to the new state currentState.blessId = this.blessId; // Push the current state onto the stack dynamicStateStack.push(currentState); - // Clear the array elements - this.elements.clear(); + // Clear the array elements (for tied arrays, this calls CLEAR) + if (this.type == TIED_ARRAY) { + TieArray.tiedClear(this); + } else { + this.elements.clear(); + } // Reset the blessId this.blessId = 0; } @@ -900,6 +910,8 @@ public void dynamicRestoreState() { RuntimeArray previousState = dynamicStateStack.pop(); // Restore the elements from the saved state this.elements = previousState.elements; + // Restore the type from the saved state (important for tied arrays) + this.type = previousState.type; // Restore the blessId from the saved state this.blessId = previousState.blessId; }