Skip to content

Commit 092bd1b

Browse files
Remove DESTROY support (moved to separate PR)
DESTROY for blessed objects will be handled in a dedicated branch (feature/destroy-blessed-objects) where it can be developed with proper reference counting before merging. Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 5dc1e64 commit 092bd1b

5 files changed

Lines changed: 4 additions & 66 deletions

File tree

src/main/java/org/perlonjava/backend/jvm/EmitStatement.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public class EmitStatement {
4141
static void emitScopeExitNullStores(EmitterContext ctx, int scopeIndex, boolean closeIO) {
4242
if (closeIO) {
4343
// For scalar variables in loop bodies, call cleanup to close IO
44-
// on anonymous globs and fire DESTROY on blessed objects.
44+
// on anonymous globs (deterministic DESTROY for lexical file handles).
4545
java.util.List<Integer> scalarIndices = ctx.symbolTable.getMyScalarIndicesInScope(scopeIndex);
4646
for (int idx : scalarIndices) {
4747
ctx.mv.visitVarInsn(Opcodes.ALOAD, idx);
@@ -60,7 +60,7 @@ static void emitScopeExitNullStores(EmitterContext ctx, int scopeIndex, boolean
6060
}
6161
}
6262

63-
/** Convenience overload: null stores only, no IO/DESTROY cleanup (safe for all contexts). */
63+
/** Convenience overload: null stores only, no IO cleanup (safe for all contexts). */
6464
static void emitScopeExitNullStores(EmitterContext ctx, int scopeIndex) {
6565
emitScopeExitNullStores(ctx, scopeIndex, false);
6666
}

src/main/java/org/perlonjava/core/Configuration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ public final class Configuration {
3333
* Automatically populated by Gradle/Maven during build.
3434
* DO NOT EDIT MANUALLY - this value is replaced at build time.
3535
*/
36-
public static final String gitCommitId = "4c7ab0412";
36+
public static final String gitCommitId = "5dc1e64a3";
3737

3838
/**
3939
* Git commit date of the build (ISO format: YYYY-MM-DD).
4040
* Automatically populated by Gradle/Maven during build.
4141
* DO NOT EDIT MANUALLY - this value is replaced at build time.
4242
*/
43-
public static final String gitCommitDate = "2026-04-06";
43+
public static final String gitCommitDate = "2026-04-07";
4444

4545
// Prevent instantiation
4646
private Configuration() {

src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeBase.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ public abstract class RuntimeBase implements DynamicState, Iterable<RuntimeScala
1313
// Index to the class that this reference belongs
1414
public int blessId;
1515

16-
// Guard flag to prevent calling DESTROY more than once on the same object.
17-
// Set to true after DESTROY has been called (or is being called).
18-
public boolean destroyCalled;
19-
2016
/**
2117
* Adds this entity to the specified RuntimeList.
2218
*

src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeHash.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -413,11 +413,6 @@ public RuntimeScalar delete(RuntimeScalar key) {
413413
var value = elements.remove(k);
414414
if (byteKeys != null) byteKeys.remove(k);
415415
if (value != null) {
416-
// Call DESTROY on blessed references being removed from a hash.
417-
// In Perl, delete() on the last reference triggers DESTROY.
418-
// Without ref counting, we call it eagerly; the destroyCalled
419-
// flag on RuntimeBase prevents double-DESTROY.
420-
RuntimeScalar.callDestroyIfNeeded(value);
421416
yield new RuntimeScalar(value);
422417
}
423418
yield new RuntimeScalar();
@@ -437,7 +432,6 @@ public RuntimeScalar delete(String key) {
437432
var value = elements.remove(key);
438433
if (byteKeys != null) byteKeys.remove(key);
439434
if (value != null) {
440-
RuntimeScalar.callDestroyIfNeeded(value);
441435
yield new RuntimeScalar(value);
442436
}
443437
yield new RuntimeScalar();

src/main/java/org/perlonjava/runtime/runtimetypes/RuntimeScalar.java

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,9 +1746,6 @@ public RuntimeScalar undefine() {
17461746
InheritanceResolver.invalidateCache();
17471747
return this;
17481748
}
1749-
// Call DESTROY on blessed references before clearing.
1750-
// undef $obj explicitly discards the reference, so DESTROY should fire.
1751-
callDestroyIfNeeded(this);
17521749
// Close IO handles when dropping a glob reference.
17531750
// This mimics Perl's internal sv_clear behavior where IO handles are closed
17541751
// when the glob's reference count drops to zero (independent of DESTROY).
@@ -1843,55 +1840,6 @@ public static void scopeExitCleanup(RuntimeScalar scalar) {
18431840
}
18441841
}
18451842
}
1846-
// Also handle blessed references with DESTROY methods.
1847-
// When a lexical variable holding a blessed reference goes out of scope,
1848-
// call DESTROY on the object. This is an approximation of Perl's
1849-
// reference-counted DESTROY: without ref counting, we may call DESTROY
1850-
// early if other references exist. The destroyCalled flag prevents
1851-
// double-DESTROY.
1852-
callDestroyIfNeeded(scalar);
1853-
}
1854-
1855-
/**
1856-
* Calls DESTROY on a blessed object if:
1857-
* 1. The scalar holds a blessed reference (blessId != 0)
1858-
* 2. DESTROY hasn't been called yet on this object (destroyCalled flag)
1859-
* 3. The class hierarchy defines a DESTROY method
1860-
* <p>
1861-
* Perl semantics: DESTROY exceptions are caught and warned "(in cleanup)".
1862-
* DESTROY is called with the blessed reference as $_[0].
1863-
* <p>
1864-
* Note: Without reference counting, this may call DESTROY while other
1865-
* references to the object still exist. The destroyCalled flag on
1866-
* RuntimeBase prevents double-DESTROY.
1867-
*/
1868-
public static void callDestroyIfNeeded(RuntimeScalar scalar) {
1869-
if (scalar == null) return;
1870-
int blessId = RuntimeScalarType.blessedId(scalar);
1871-
if (blessId == 0) return;
1872-
RuntimeBase base = (RuntimeBase) scalar.value;
1873-
if (base.destroyCalled) return;
1874-
base.destroyCalled = true;
1875-
1876-
String className = NameNormalizer.getBlessStr(blessId);
1877-
RuntimeScalar destroyMethod = InheritanceResolver.findMethodInHierarchy(
1878-
"DESTROY", className, null, 0);
1879-
if (destroyMethod == null || destroyMethod.type != CODE) {
1880-
base.destroyCalled = false; // No DESTROY found, allow future attempts
1881-
return;
1882-
}
1883-
1884-
try {
1885-
// Call DESTROY($self) in void context
1886-
RuntimeArray args = new RuntimeArray();
1887-
args.push(scalar);
1888-
RuntimeCode.apply(destroyMethod, args, RuntimeContextType.VOID);
1889-
} catch (Exception e) {
1890-
// Perl: DESTROY exceptions are turned into warnings
1891-
String msg = e.getMessage();
1892-
if (msg == null) msg = e.getClass().getName();
1893-
Warnings.warn(new RuntimeArray(new RuntimeScalar("(in cleanup) " + msg + "\n")), RuntimeContextType.VOID);
1894-
}
18951843
}
18961844

18971845
public RuntimeScalar defined() {

0 commit comments

Comments
 (0)