From ad5807d997449ac64f10fece95161b6ea0ae3161 Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Fri, 20 Feb 2026 10:26:12 +0100 Subject: [PATCH 1/3] fix: Remove "Interpreter error" prefix from eval'd code exceptions When interpreter executes code from eval STRING (sourceName starts with "(eval N)"), exceptions are now passed through without wrapping them in formatInterpreterError(). This preserves clean error messages in $@ that match the compiler's behavior. Impact on re/regexp.t with JPERL_EVAL_USE_INTERPRETER=1: - Before: 1738/2210 passing (78.6%) - After: 1785/2210 passing (80.8%) - Improvement: +47 tests (+2.1 percentage points) This closes the gap between interpreter and compiler to just 1 test (1786 compiler vs 1785 interpreter). Example error message improvement: Before: Interpreter error in (eval 6) line 1 (pc=7): Invalid [] range... After: Invalid [] range "b-a" in regex; marked by <-- HERE... Co-Authored-By: Claude Opus 4.6 --- .../org/perlonjava/interpreter/BytecodeInterpreter.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java index c3e7ddffb..9aef6cbf2 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java @@ -1929,6 +1929,15 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c throw (PerlDieException) e; } + // Check if we're running inside an eval STRING context + // (sourceName starts with "(eval " when code is from eval STRING) + // In this case, don't wrap the exception - let the outer eval handler catch it + boolean insideEvalString = code.sourceName != null && code.sourceName.startsWith("(eval "); + if (insideEvalString) { + // Re-throw as-is - will be caught by EvalStringHandler.evalString() + throw e; + } + // Wrap other exceptions with interpreter context including bytecode context String errorMessage = formatInterpreterError(code, pc, e); throw new RuntimeException(errorMessage, e); From ebc92fab44190994c81a8456ea5cf256babc185c Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Fri, 20 Feb 2026 10:46:28 +0100 Subject: [PATCH 2/3] fix: Implement scalar reference dereference in interpreter Fixed two issues with scalar reference dereference in the interpreter: 1. DEREF opcode was just copying the reference instead of dereferencing it. Now properly calls scalarDeref() on the reference to get the target value. 2. LOAD_SYMBOLIC_SCALAR was treating all block results as variable names for symbolic references (like ${"varname"}). Now checks if the block returns a REFERENCE type and dereferences it (like ${\(0)}), otherwise treats it as a symbolic variable name. This fixes the inline scalar reference dereference syntax ${\...} which is used in Perl for patterns like: - ${\(0)} - create anonymous scalar ref and dereference it - ${\(defined($1)?1:0)} - conditional expression in scalar ref Impact on re/regexp.t with JPERL_EVAL_USE_INTERPRETER=1: - Before: 1785/2210 passing (80.8%) - After: 1786/2210 passing (80.8%) - Gap closed: Interpreter now matches compiler exactly (0 test difference) Test case that now works: eval q{my $x = ${\(0)}; say $x} # Now prints "0" instead of "" Co-Authored-By: Claude Opus 4.6 --- .../interpreter/BytecodeInterpreter.java | 43 +++++++++++-------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java index 9aef6cbf2..3887564a9 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java @@ -1326,12 +1326,11 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c } case Opcodes.DEREF: { - // Dereference: rd = rs (dereferencing depends on context) - // For now, just copy the reference - proper dereferencing - // is context-dependent and handled by specific operators + // Dereference: rd = $$rs (scalar reference dereference) int rd = bytecode[pc++]; int rs = bytecode[pc++]; - registers[rd] = registers[rs]; + RuntimeScalar ref = (RuntimeScalar) registers[rs]; + registers[rd] = ref.scalarDeref(); break; } @@ -1806,24 +1805,33 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c case Opcodes.LOAD_SYMBOLIC_SCALAR: { // Load via symbolic reference: rd = GlobalVariable.getGlobalVariable(nameReg.toString()).get() + // OR dereference if nameReg contains a scalar reference // Format: LOAD_SYMBOLIC_SCALAR rd nameReg int rd = bytecode[pc++]; int nameReg = bytecode[pc++]; - // Get the variable name from the name register + // Get the value from the name register RuntimeScalar nameScalar = (RuntimeScalar) registers[nameReg]; - String varName = nameScalar.toString(); - - // Normalize the variable name to include package prefix if needed - // This is important for ${label:var} cases where "colon" becomes "main::colon" - String normalizedName = org.perlonjava.runtime.NameNormalizer.normalizeVariableName( - varName, - "main" // Use main package as default for symbolic references - ); - // Get the global variable and load its value - RuntimeScalar globalVar = GlobalVariable.getGlobalVariable(normalizedName); - registers[rd] = globalVar; + // Check if it's a scalar reference - if so, dereference it + if (nameScalar.type == RuntimeScalarType.REFERENCE) { + // This is ${\ref} - dereference the reference + registers[rd] = nameScalar.scalarDeref(); + } else { + // This is ${"varname"} - symbolic reference to variable + String varName = nameScalar.toString(); + + // Normalize the variable name to include package prefix if needed + // This is important for ${label:var} cases where "colon" becomes "main::colon" + String normalizedName = org.perlonjava.runtime.NameNormalizer.normalizeVariableName( + varName, + "main" // Use main package as default for symbolic references + ); + + // Get the global variable and load its value + RuntimeScalar globalVar = GlobalVariable.getGlobalVariable(normalizedName); + registers[rd] = globalVar; + } break; } @@ -2036,7 +2044,8 @@ private static int executeTypeOps(short opcode, short[] bytecode, int pc, case Opcodes.DEREF: { int rd = bytecode[pc++]; int rs = bytecode[pc++]; - registers[rd] = registers[rs]; + RuntimeScalar ref = (RuntimeScalar) registers[rs]; + registers[rd] = ref.scalarDeref(); return pc; } From 7f14e668b9387aa60043d6ede2f051b69751140f Mon Sep 17 00:00:00 2001 From: Flavio Soibelmann Glock Date: Fri, 20 Feb 2026 11:18:58 +0100 Subject: [PATCH 3/3] fix: Handle RuntimeList in DEREF opcode to prevent ClassCastException The DEREF opcode was unconditionally casting to RuntimeScalar, which caused ClassCastException when processing declared reference lists like `my (\$f, $g)`. Now checks the value type before attempting dereference: - If RuntimeScalar with REFERENCE type: calls scalarDeref() - If RuntimeScalar without REFERENCE type: passes through unchanged - If RuntimeList or other type: passes through unchanged This maintains the fix for ${\(0)} while handling list contexts correctly. Impact on perl5_t/t/op/decl-refs.t with JPERL_EVAL_USE_INTERPRETER=1: - Before regression: 270/408 passing (66.2%) - After regression: 157/408 passing (38.5%) - ClassCastException crash - After this fix: 272/408 passing (66.7%) - actually 2 tests better! Impact on perl5_t/t/re/regexp.t with JPERL_EVAL_USE_INTERPRETER=1: - Still passing: 1786/2210 (80.8%) - no regression Co-Authored-By: Claude Opus 4.6 --- .../interpreter/BytecodeInterpreter.java | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java index 3887564a9..265ff201d 100644 --- a/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java +++ b/src/main/java/org/perlonjava/interpreter/BytecodeInterpreter.java @@ -1327,10 +1327,24 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c case Opcodes.DEREF: { // Dereference: rd = $$rs (scalar reference dereference) + // Can receive RuntimeScalar or RuntimeList int rd = bytecode[pc++]; int rs = bytecode[pc++]; - RuntimeScalar ref = (RuntimeScalar) registers[rs]; - registers[rd] = ref.scalarDeref(); + RuntimeBase value = registers[rs]; + + // Only dereference if it's a RuntimeScalar with REFERENCE type + if (value instanceof RuntimeScalar) { + RuntimeScalar scalar = (RuntimeScalar) value; + if (scalar.type == RuntimeScalarType.REFERENCE) { + registers[rd] = scalar.scalarDeref(); + } else { + // Non-reference scalar, just copy + registers[rd] = value; + } + } else { + // RuntimeList or other types, pass through + registers[rd] = value; + } break; } @@ -2044,8 +2058,21 @@ private static int executeTypeOps(short opcode, short[] bytecode, int pc, case Opcodes.DEREF: { int rd = bytecode[pc++]; int rs = bytecode[pc++]; - RuntimeScalar ref = (RuntimeScalar) registers[rs]; - registers[rd] = ref.scalarDeref(); + RuntimeBase value = registers[rs]; + + // Only dereference if it's a RuntimeScalar with REFERENCE type + if (value instanceof RuntimeScalar) { + RuntimeScalar scalar = (RuntimeScalar) value; + if (scalar.type == RuntimeScalarType.REFERENCE) { + registers[rd] = scalar.scalarDeref(); + } else { + // Non-reference scalar, just copy + registers[rd] = value; + } + } else { + // RuntimeList or other types, pass through + registers[rd] = value; + } return pc; }