diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitStatement.java b/src/main/java/org/perlonjava/backend/jvm/EmitStatement.java index 7d2eba823..824cd0d45 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitStatement.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitStatement.java @@ -41,7 +41,7 @@ public class EmitStatement { static void emitScopeExitNullStores(EmitterContext ctx, int scopeIndex, boolean closeIO) { if (closeIO) { // For scalar variables in loop bodies, call cleanup to close IO - // on anonymous globs (deterministic DESTROY for lexical file handles). + // on anonymous globs and fire DESTROY on blessed objects. java.util.List scalarIndices = ctx.symbolTable.getMyScalarIndicesInScope(scopeIndex); for (int idx : scalarIndices) { ctx.mv.visitVarInsn(Opcodes.ALOAD, idx); @@ -60,7 +60,7 @@ static void emitScopeExitNullStores(EmitterContext ctx, int scopeIndex, boolean } } - /** Convenience overload: null stores only, no IO cleanup (safe for all contexts). */ + /** Convenience overload: null stores only, no IO/DESTROY cleanup (safe for all contexts). */ static void emitScopeExitNullStores(EmitterContext ctx, int scopeIndex) { emitScopeExitNullStores(ctx, scopeIndex, false); } diff --git a/src/main/java/org/perlonjava/core/Configuration.java b/src/main/java/org/perlonjava/core/Configuration.java index 83e109aea..e2971cf28 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 = "92020251e"; + public static final String gitCommitId = "865da3648"; /** * Git commit date of the build (ISO format: YYYY-MM-DD). diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeBase.java b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeBase.java index 5f31511d9..998a9ea39 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeBase.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeBase.java @@ -13,6 +13,10 @@ public abstract class RuntimeBase implements DynamicState, Iterable + * Perl semantics: DESTROY exceptions are caught and warned "(in cleanup)". + * DESTROY is called with the blessed reference as $_[0]. + *

+ * Note: Without reference counting, this may call DESTROY while other + * references to the object still exist. The destroyCalled flag on + * RuntimeBase prevents double-DESTROY. + */ + public static void callDestroyIfNeeded(RuntimeScalar scalar) { + if (scalar == null) return; + int blessId = RuntimeScalarType.blessedId(scalar); + if (blessId == 0) return; + RuntimeBase base = (RuntimeBase) scalar.value; + if (base.destroyCalled) return; + base.destroyCalled = true; + + String className = NameNormalizer.getBlessStr(blessId); + RuntimeScalar destroyMethod = InheritanceResolver.findMethodInHierarchy( + "DESTROY", className, null, 0); + if (destroyMethod == null || destroyMethod.type != CODE) { + base.destroyCalled = false; // No DESTROY found, allow future attempts + return; + } + + try { + // Call DESTROY($self) in void context + RuntimeArray args = new RuntimeArray(); + args.push(scalar); + RuntimeCode.apply(destroyMethod, args, RuntimeContextType.VOID); + } catch (Exception e) { + // Perl: DESTROY exceptions are turned into warnings + String msg = e.getMessage(); + if (msg == null) msg = e.getClass().getName(); + Warnings.warn(new RuntimeArray(new RuntimeScalar("(in cleanup) " + msg + "\n")), RuntimeContextType.VOID); + } } public RuntimeScalar defined() {