Conversation
The bytecode-interpreter opcodes SCOPE_EXIT_CLEANUP_HASH and SCOPE_EXIT_CLEANUP_ARRAY blindly cast their register slot to RuntimeHash / RuntimeArray. If a control-flow path skipped the my-hash / my-array initialisation (early `return`, `last`, `goto`, or a short-circuit guarding the declaration), the register could still hold a transient RuntimeScalar produced by an unrelated CREATE_LIST that recycled the same slot. The unconditional cast then threw class RuntimeScalar cannot be cast to class RuntimeHash class RuntimeScalar cannot be cast to class RuntimeArray at sub/scope exit, even when the user's logic completed normally. This only surfaces in the interpreter-fallback path (the JIT/JVM backend uses a different code generator), so it stayed hidden until something forced fallback. `use Moose;` is the canonical trigger: Sub::Exporter::Progressive::import contains `goto \&Exporter::import`, which forces JIT->interpreter fallback for that whole sub, and the sub also has lexical hashes/arrays. The scalar variant SCOPE_EXIT_CLEANUP already had the same defensive `instanceof` check (with a long explanatory comment); this commit mirrors that pattern for the hash and array variants and adds an extensive comment explaining the invariants, the trigger, and a minimal repro so future maintainers don't "clean up" the check. Adds src/test/resources/unit/interpreter_myhash_myarray_scope_exit.t which exercises every variant on the interpreter-fallback path (verified via JPERL_SHOW_FALLBACK=1). Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a
ClassCastExceptionthrown by the bytecode interpreter at sub/scopeexit when a
my %hormy @ais declared on a control-flow path that maybe skipped (early
return,last,goto, or short-circuit&&/||guarding the declaration), inside a sub that fell back from the JIT to the
interpreter.
Root cause
SCOPE_EXIT_CLEANUP_HASH/SCOPE_EXIT_CLEANUP_ARRAYblindly castregisters[reg]toRuntimeHash/RuntimeArray. The compiler reusesregister slots across statements, so a slot later assigned to
my %hcan transiently hold a
RuntimeScalarleft over from a priorCREATE_LISTetc. If control flow skips theMY_HASH/MY_ARRAYinitialisation, the cleanup runs anyway and the cast fails.
The scalar variant
SCOPE_EXIT_CLEANUPalready had the matchingdefensive
instanceofcheck; this PR mirrors it for the hash andarray variants.
Trigger
Real-world repro:
use Moose;->Sub::Exporter::Progressive::importuses
goto \&Exporter::importwhich forces the wholeimportsub ontothe interpreter-fallback path. The sub also has lexical hashes/arrays,
so every Moose-based test died at
use Moose;with the cast above.Minimal repro:
Before this PR:
class RuntimeScalar cannot be cast to class RuntimeHash.After this PR: prints
okand exits cleanly.What's in the diff
BytecodeInterpreter.java-- guard the two cleanup opcodes withinstanceofand add an extensive multi-paragraph comment explainingthe invariants, the trigger and a copy-pasteable minimal repro, so
the check is not "cleaned up" by a future refactor.
src/test/resources/unit/interpreter_myhash_myarray_scope_exit.twith 6 sub-tests covering my-hash, my-array, mixed, the
Sub::Exporter::Progressive-stylefor-loop pattern, a stressloop, and a short-circuit-skipped
my %h. All verified to actuallyexercise interpreter fallback via
JPERL_SHOW_FALLBACK=1.Test plan
interpreter_myhash_myarray_scope_exit.tpasses (6/6).
JIT->interpreter fallback path (confirmed with
JPERL_SHOW_FALLBACK=1).make(full unit-test suite, both backends) is green.use Moose;(after extracting Moose-2.4000) now loadsClass::MOPand its dependency tree without the cast failure(next blocker is the unrelated
XSLoader::loadinMoose.pm,tracked in
dev/modules/moose_support.mdPhase 3).Generated with Devin