Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions run_exiftool_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash
cd /Users/fglock/projects/PerlOnJava2/Image-ExifTool-13.44
PASS=0
FAIL=0
PASS_LIST=""
FAIL_LIST=""
for t in t/*.t; do
name=$(basename "$t" .t)
output=$(timeout 60 java -jar ../target/perlonjava-3.0.0.jar -Ilib "$t" 2>&1)
exit_code=$?
# Check for "not ok" or non-zero exit
if echo "$output" | grep -q "^not ok"; then
FAIL=$((FAIL + 1))
FAIL_LIST="$FAIL_LIST $name"
elif [ $exit_code -ne 0 ]; then
FAIL=$((FAIL + 1))
FAIL_LIST="$FAIL_LIST $name"
else
PASS=$((PASS + 1))
PASS_LIST="$PASS_LIST $name"
fi
echo "$name: exit=$exit_code"
done
echo ""
echo "PASS: $PASS"
echo "FAIL: $FAIL"
echo "Failing:$FAIL_LIST"
49 changes: 31 additions & 18 deletions src/main/java/org/perlonjava/backend/bytecode/BytecodeCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -3002,25 +3002,34 @@ void compileVariableDeclaration(OperatorNode node, String op) {
continue;
}

// Regular scalar variable in list
if (sigil.equals("$") && sigilOp.operand instanceof IdentifierNode idNode) {
String varName = "$" + idNode.name;

// Check if it's a lexical variable
if (hasVariable(varName)) {
throwCompilerException("Can't localize lexical variable " + varName);
if (sigilOp.operand instanceof IdentifierNode idNode) {
if (sigil.equals("*")) {
String globalName = NameNormalizer.normalizeVariableName(idNode.name, getCurrentPackage());
int nameIdx = addToStringPool(globalName);
int rd = allocateRegister();
emit(Opcodes.LOCAL_GLOB);
emitReg(rd);
emit(nameIdx);
varRegs.add(rd);
} else {
String varName = sigil + idNode.name;
if (hasVariable(varName)) {
throwCompilerException("Can't localize lexical variable " + varName);
}
String globalVarName = NameNormalizer.normalizeVariableName(idNode.name, getCurrentPackage());
int nameIdx = addToStringPool(globalVarName);
int rd = allocateRegister();
if (sigil.equals("$")) {
emitWithToken(Opcodes.LOCAL_SCALAR, node.getIndex());
} else if (sigil.equals("@")) {
emitWithToken(Opcodes.LOCAL_ARRAY, node.getIndex());
} else if (sigil.equals("%")) {
emitWithToken(Opcodes.LOCAL_HASH, node.getIndex());
}
emitReg(rd);
emit(nameIdx);
varRegs.add(rd);
}

// Localize global variable
String globalVarName = NameNormalizer.normalizeVariableName(idNode.name, getCurrentPackage());
int nameIdx = addToStringPool(globalVarName);

int rd = allocateRegister();
emitWithToken(Opcodes.LOCAL_SCALAR, node.getIndex());
emitReg(rd);
emit(nameIdx);

varRegs.add(rd);
}
}
}
Expand Down Expand Up @@ -3854,6 +3863,8 @@ private void visitNamedSubroutine(SubroutineNode node) {
this.errorUtil,
packedRegistry
);
subCompiler.symbolTable.setCurrentPackage(getCurrentPackage(),
symbolTable.currentPackageIsClass());

// Set the BEGIN ID in the sub-compiler so it knows to use RETRIEVE_BEGIN opcodes
subCompiler.currentSubroutineBeginId = beginId;
Expand Down Expand Up @@ -3960,6 +3971,8 @@ private void visitAnonymousSubroutine(SubroutineNode node) {
this.errorUtil,
parentRegistry // Pass parent variable registry for nested closure support
);
subCompiler.symbolTable.setCurrentPackage(getCurrentPackage(),
symbolTable.currentPackageIsClass());

// Step 4: Compile the subroutine body
// Sub-compiler will use parentRegistry to resolve captured variables
Expand Down
108 changes: 92 additions & 16 deletions src/main/java/org/perlonjava/backend/bytecode/BytecodeInterpreter.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,27 @@ public class BytecodeInterpreter {
// Debug flag for regex compilation (set at class load time)
private static final boolean DEBUG_REGEX = System.getenv("DEBUG_REGEX") != null;

static RuntimeScalar ensureMutableScalar(RuntimeBase val) {
if (val instanceof RuntimeScalarReadOnly ro) {
RuntimeScalar copy = new RuntimeScalar();
copy.type = ro.type;
copy.value = ro.value;
return copy;
}
if (val instanceof ScalarSpecialVariable sv) {
RuntimeScalar src = sv.getValueAsScalar();
RuntimeScalar copy = new RuntimeScalar();
copy.type = src.type;
copy.value = src.value;
return copy;
}
return (RuntimeScalar) val;
}

static boolean isImmutableProxy(RuntimeBase val) {
return val instanceof RuntimeScalarReadOnly || val instanceof ScalarSpecialVariable;
}

/**
* Execute interpreted bytecode.
*
Expand Down Expand Up @@ -115,10 +136,6 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
return new RuntimeList();
}
RuntimeList retList = retVal.getList();
// Materialize $1, $&, etc. into concrete scalars BEFORE returning.
// The finally block will call savedRegexState.restore(), which overwrites
// global regex state. Any lazy ScalarSpecialVariable references in the
// return list must be resolved while this sub's regex state is still active.
RuntimeCode.materializeSpecialVarsInResult(retList);
return retList;
}
Expand Down Expand Up @@ -182,9 +199,11 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c

case Opcodes.MOVE: {
// Register copy: rd = rs
// Must unwrap RuntimeScalarReadOnly to prevent read-only values in variable registers
int dest = bytecode[pc++];
int src = bytecode[pc++];
registers[dest] = registers[src];
RuntimeBase srcVal = registers[src];
registers[dest] = isImmutableProxy(srcVal) ? ensureMutableScalar(srcVal) : srcVal;
break;
}

Expand Down Expand Up @@ -244,6 +263,16 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
break;
}

case Opcodes.UNDEFINE_SCALAR: {
// Undefine variable in-place: rd.undefine()
int rd = bytecode[pc++];
if (isImmutableProxy(registers[rd])) {
registers[rd] = ensureMutableScalar(registers[rd]);
}
registers[rd].undefine();
break;
}

// =================================================================
// VARIABLE ACCESS - GLOBAL
// =================================================================
Expand Down Expand Up @@ -315,6 +344,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c

if (iterator.hasNext()) {
RuntimeScalar element = iterator.next();
if (isImmutableProxy(element)) {
element = ensureMutableScalar(element);
}
registers[rd] = element;
GlobalVariable.aliasGlobalVariable(name, element);
pc = bodyTarget; // ABSOLUTE jump back to body start
Expand Down Expand Up @@ -422,7 +454,17 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
// addToScalar calls getValueAsScalar() for ScalarSpecialVariable
int rd = bytecode[pc++];
int rs = bytecode[pc++];
registers[rs].addToScalar((RuntimeScalar) registers[rd]);
RuntimeBase rdVal = registers[rd];
RuntimeScalar rdScalar;
if (isImmutableProxy(rdVal)) {
rdScalar = new RuntimeScalar();
registers[rd] = rdScalar;
} else if (rdVal instanceof RuntimeScalar) {
rdScalar = (RuntimeScalar) rdVal;
} else {
rdScalar = rdVal.scalar();
}
registers[rs].addToScalar(rdScalar);
break;
}

Expand Down Expand Up @@ -662,7 +704,8 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c

if (iterator.hasNext()) {
// Get next element and jump back to body
registers[rd] = iterator.next();
RuntimeScalar elem = iterator.next();
registers[rd] = (isImmutableProxy(elem)) ? ensureMutableScalar(elem) : elem;
pc = bodyTarget; // ABSOLUTE jump back to body start
}
// else: fall through to exit
Expand Down Expand Up @@ -916,8 +959,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
int valueReg = bytecode[pc++];
RuntimeHash hash = (RuntimeHash) registers[hashReg];
RuntimeScalar key = (RuntimeScalar) registers[keyReg];
RuntimeScalar val = (RuntimeScalar) registers[valueReg];
hash.put(key.toString(), val); // Convert key to String
RuntimeBase valBase = registers[valueReg];
RuntimeScalar val = (valBase instanceof RuntimeScalar) ? (RuntimeScalar) valBase : valBase.scalar();
hash.put(key.toString(), ensureMutableScalar(val));
break;
}

Expand Down Expand Up @@ -999,7 +1043,8 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c

// Convert to scalar if called in scalar context
if (context == RuntimeContextType.SCALAR) {
registers[rd] = result.scalar();
RuntimeBase scalarResult = result.scalar();
registers[rd] = (isImmutableProxy(scalarResult)) ? ensureMutableScalar(scalarResult) : scalarResult;
} else {
registers[rd] = result;
}
Expand Down Expand Up @@ -1061,7 +1106,8 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c

// Convert to scalar if called in scalar context
if (context == RuntimeContextType.SCALAR) {
registers[rd] = result.scalar();
RuntimeBase scalarResult = result.scalar();
registers[rd] = (isImmutableProxy(scalarResult)) ? ensureMutableScalar(scalarResult) : scalarResult;
} else {
registers[rd] = result;
}
Expand Down Expand Up @@ -1173,21 +1219,26 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
case Opcodes.INC_REG: {
// Increment register in-place: r++
int rd = bytecode[pc++];
registers[rd] = MathOperators.add((RuntimeScalar) registers[rd], 1);
RuntimeBase incResult = MathOperators.add((RuntimeScalar) registers[rd], 1);
registers[rd] = (isImmutableProxy(incResult)) ? ensureMutableScalar(incResult) : incResult;
break;
}

case Opcodes.DEC_REG: {
// Decrement register in-place: r--
int rd = bytecode[pc++];
registers[rd] = MathOperators.subtract((RuntimeScalar) registers[rd], 1);
RuntimeBase decResult = MathOperators.subtract((RuntimeScalar) registers[rd], 1);
registers[rd] = (isImmutableProxy(decResult)) ? ensureMutableScalar(decResult) : decResult;
break;
}

case Opcodes.ADD_ASSIGN: {
// Add and assign: rd += rs (modifies rd in place)
int rd = bytecode[pc++];
int rs = bytecode[pc++];
if (isImmutableProxy(registers[rd])) {
registers[rd] = ensureMutableScalar(registers[rd]);
}
MathOperators.addAssign(
(RuntimeScalar) registers[rd],
(RuntimeScalar) registers[rs]
Expand All @@ -1200,6 +1251,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
int rd = bytecode[pc++];
int immediate = readInt(bytecode, pc);
pc += 1;
if (isImmutableProxy(registers[rd])) {
registers[rd] = ensureMutableScalar(registers[rd]);
}
RuntimeScalar result = MathOperators.add((RuntimeScalar) registers[rd], immediate);
((RuntimeScalar) registers[rd]).set(result);
break;
Expand Down Expand Up @@ -2311,7 +2365,7 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
}

// Not in eval - show detailed error with bytecode context
int errorPc = Math.max(0, pc - 1); // Go back one instruction
int errorPc = Math.max(0, pc - 1);

// Show bytecode context (10 bytes before errorPc)
StringBuilder bcContext = new StringBuilder();
Expand All @@ -2327,7 +2381,9 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
}
bcContext.append(" ]");

String errorMessage = "ClassCastException" + bcContext + ": " + e.getMessage();
StackTraceElement[] st = e.getStackTrace();
String javaLine = (st.length > 0) ? " [java:" + st[0].getFileName() + ":" + st[0].getLineNumber() + "]" : "";
String errorMessage = "ClassCastException" + bcContext + ": " + e.getMessage() + javaLine;
throw new RuntimeException(formatInterpreterError(code, errorPc, new Exception(errorMessage)), e);
} catch (Throwable e) {
// Check if we're inside an eval block
Expand Down Expand Up @@ -2358,7 +2414,15 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
}

// Wrap other exceptions with interpreter context including bytecode context
String errorMessage = formatInterpreterError(code, pc, e);
int debugPc = Math.max(0, pc - 3);
String opcodeInfo = " [opcodes at pc-3..pc: ";
for (int di = debugPc; di <= Math.min(pc + 2, bytecode.length - 1); di++) {
if (di == pc) opcodeInfo += ">>>";
opcodeInfo += bytecode[di] + " ";
if (di == pc) opcodeInfo += "<<< ";
}
opcodeInfo += "]";
String errorMessage = formatInterpreterError(code, pc, e) + opcodeInfo;
throw new RuntimeException(errorMessage, e);
}
} // end outer while
Expand Down Expand Up @@ -2478,6 +2542,9 @@ private static int executeArithmetic(int opcode, int[] bytecode, int pc,
case Opcodes.SUBTRACT_ASSIGN: {
int rd = bytecode[pc++];
int rs = bytecode[pc++];
if (isImmutableProxy(registers[rd])) {
registers[rd] = ensureMutableScalar(registers[rd]);
}
RuntimeBase val1 = registers[rd];
RuntimeBase val2 = registers[rs];
RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar();
Expand All @@ -2489,6 +2556,9 @@ private static int executeArithmetic(int opcode, int[] bytecode, int pc,
case Opcodes.MULTIPLY_ASSIGN: {
int rd = bytecode[pc++];
int rs = bytecode[pc++];
if (isImmutableProxy(registers[rd])) {
registers[rd] = ensureMutableScalar(registers[rd]);
}
RuntimeBase val1 = registers[rd];
RuntimeBase val2 = registers[rs];
RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar();
Expand All @@ -2500,6 +2570,9 @@ private static int executeArithmetic(int opcode, int[] bytecode, int pc,
case Opcodes.DIVIDE_ASSIGN: {
int rd = bytecode[pc++];
int rs = bytecode[pc++];
if (isImmutableProxy(registers[rd])) {
registers[rd] = ensureMutableScalar(registers[rd]);
}
RuntimeBase val1 = registers[rd];
RuntimeBase val2 = registers[rs];
RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar();
Expand All @@ -2511,6 +2584,9 @@ private static int executeArithmetic(int opcode, int[] bytecode, int pc,
case Opcodes.MODULUS_ASSIGN: {
int rd = bytecode[pc++];
int rs = bytecode[pc++];
if (isImmutableProxy(registers[rd])) {
registers[rd] = ensureMutableScalar(registers[rd]);
}
RuntimeBase val1 = registers[rd];
RuntimeBase val2 = registers[rs];
RuntimeScalar s1 = (val1 instanceof RuntimeScalar) ? (RuntimeScalar) val1 : val1.scalar();
Expand Down
Loading