From 55a7bad53a7c4dc2d0f49105ffbe44a94f39057b Mon Sep 17 00:00:00 2001 From: "Flavio S. Glock" Date: Wed, 25 Mar 2026 14:43:58 +0100 Subject: [PATCH] Fix stash deletion for ${BLOCK}{key} patterns This fixes a bug where delete ${\042Foo::\042}{bar} would not properly remove the stash entry when using the ${BLOCK}{key} syntax. Changes: - JVM backend (Dereference.java): Fix handleHashElementOperator to use the hashOperation parameter for ${BLOCK}{key} patterns. Previously it always called hashDerefGet regardless of delete/exists operations. - Interpreter (CompileExistsDelete.java): Fix resolveHashFromBinaryOp to compile operands in SCALAR context to avoid -1 register errors. Note: Stash deletion removes the stash entry but compiled subroutine references continue to work, matching Perl CV caching behavior. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../backend/bytecode/CompileExistsDelete.java | 6 ++- .../perlonjava/backend/jvm/Dereference.java | 40 +++++++++++++++++-- .../org/perlonjava/core/Configuration.java | 2 +- .../runtimetypes/HashSpecialVariable.java | 2 + .../runtime/runtimetypes/RuntimeStash.java | 2 + 5 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/perlonjava/backend/bytecode/CompileExistsDelete.java b/src/main/java/org/perlonjava/backend/bytecode/CompileExistsDelete.java index eff3e5b42..b977cb69a 100644 --- a/src/main/java/org/perlonjava/backend/bytecode/CompileExistsDelete.java +++ b/src/main/java/org/perlonjava/backend/bytecode/CompileExistsDelete.java @@ -398,12 +398,14 @@ private static int resolveHashFromBinaryOp(BytecodeCompiler bc, BinaryOperatorNo bc.emit(nameIdx); return hashReg; } else { - leftOp.operand.accept(bc); + // Compile operand in SCALAR context to ensure we get a result register + bc.compileNode(leftOp.operand, -1, RuntimeContextType.SCALAR); int scalarReg = bc.lastResultReg; return derefHash(bc, scalarReg, tokenIndex); } } else if (hashAccess.left instanceof BinaryOperatorNode) { - hashAccess.left.accept(bc); + // Compile in SCALAR context to ensure we get a result register + bc.compileNode(hashAccess.left, -1, RuntimeContextType.SCALAR); int scalarReg = bc.lastResultReg; return derefHash(bc, scalarReg, tokenIndex); } diff --git a/src/main/java/org/perlonjava/backend/jvm/Dereference.java b/src/main/java/org/perlonjava/backend/jvm/Dereference.java index 1c8c26c38..1d4806c46 100644 --- a/src/main/java/org/perlonjava/backend/jvm/Dereference.java +++ b/src/main/java/org/perlonjava/backend/jvm/Dereference.java @@ -584,12 +584,28 @@ public static void handleHashElementOperator(EmitterVisitor emitterVisitor, Bina Node elem = nodeRight.elements.getFirst(); elem.accept(scalarVisitor); if (emitterVisitor.ctx.symbolTable.isStrictOptionEnabled(HINT_STRICT_REFS)) { + // Use strict version (throws error on symbolic references) + String methodName = switch (hashOperation) { + case "get" -> "hashDerefGet"; + case "delete" -> "hashDerefDelete"; + case "exists" -> "hashDerefExists"; + default -> + throw new PerlCompilerException(node.tokenIndex, "Not implemented: hash operation: " + hashOperation, emitterVisitor.ctx.errorUtil); + }; emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeScalar", - "hashDerefGet", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); + methodName, "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); } else { + // Use non-strict version (allows symbolic references) + String methodName = switch (hashOperation) { + case "get" -> "hashDerefGetNonStrict"; + case "delete" -> "hashDerefDeleteNonStrict"; + case "exists" -> "hashDerefExistsNonStrict"; + default -> + throw new PerlCompilerException(node.tokenIndex, "Not implemented: hash operation: " + hashOperation, emitterVisitor.ctx.errorUtil); + }; emitterVisitor.pushCurrentPackage(); emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeScalar", - "hashDerefGetNonStrict", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); + methodName, "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); } } else { // Multiple elements - this is a hash slice, but that's not commonly used with ${} @@ -601,12 +617,28 @@ public static void handleHashElementOperator(EmitterVisitor emitterVisitor, Bina emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/perlonjava/runtime/operators/StringOperators", "join", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Lorg/perlonjava/runtime/runtimetypes/RuntimeBase;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); if (emitterVisitor.ctx.symbolTable.isStrictOptionEnabled(HINT_STRICT_REFS)) { + // Use strict version (throws error on symbolic references) + String methodName = switch (hashOperation) { + case "get" -> "hashDerefGet"; + case "delete" -> "hashDerefDelete"; + case "exists" -> "hashDerefExists"; + default -> + throw new PerlCompilerException(node.tokenIndex, "Not implemented: hash operation: " + hashOperation, emitterVisitor.ctx.errorUtil); + }; emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeScalar", - "hashDerefGet", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); + methodName, "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); } else { + // Use non-strict version (allows symbolic references) + String methodName = switch (hashOperation) { + case "get" -> "hashDerefGetNonStrict"; + case "delete" -> "hashDerefDeleteNonStrict"; + case "exists" -> "hashDerefExistsNonStrict"; + default -> + throw new PerlCompilerException(node.tokenIndex, "Not implemented: hash operation: " + hashOperation, emitterVisitor.ctx.errorUtil); + }; emitterVisitor.pushCurrentPackage(); emitterVisitor.ctx.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/perlonjava/runtime/runtimetypes/RuntimeScalar", - "hashDerefGetNonStrict", "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); + methodName, "(Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;Ljava/lang/String;)Lorg/perlonjava/runtime/runtimetypes/RuntimeScalar;", false); } } diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index 95889cc4e..db45d8027 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 = "bef8bcb54"; + public static final String gitCommitId = "0f1682f37"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/HashSpecialVariable.java b/src/main/java/org/perlonjava/runtime/runtimetypes/HashSpecialVariable.java index d927a5e57..448a0dcfc 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/HashSpecialVariable.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/HashSpecialVariable.java @@ -232,6 +232,8 @@ public RuntimeScalar remove(Object key) { } // Get references to all the slots before deleting + // Only remove from globalCodeRefs, NOT pinnedCodeRefs, to allow compiled code + // to continue calling the subroutine (Perl caches CVs at compile time) RuntimeScalar code = GlobalVariable.globalCodeRefs.remove(fullKey); RuntimeScalar scalar = GlobalVariable.globalVariables.remove(fullKey); RuntimeArray array = GlobalVariable.globalArrays.remove(fullKey); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeStash.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeStash.java index 2135f09f5..f72dde2a2 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeStash.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeStash.java @@ -168,6 +168,8 @@ private RuntimeScalar deleteGlob(String k) { } // Get the CODE slot before deleting (most common case) + // Only remove from globalCodeRefs, NOT pinnedCodeRefs, to allow compiled code + // to continue calling the subroutine (Perl caches CVs at compile time) RuntimeScalar code = GlobalVariable.globalCodeRefs.remove(fullKey); // Delete all other slots