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
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,12 @@ private boolean isNonAsciiLengthOneScalarAllowedUnderNoUtf8(String sigil, String
* @param varName The variable name with sigil (e.g., "$A", "@array")
* @return true if access should be blocked under strict vars
*/
/** Returns true if strict refs is currently enabled in the symbol table. */
boolean isStrictRefsEnabled() {
return emitterContext != null && emitterContext.symbolTable != null
&& emitterContext.symbolTable.isStrictOptionEnabled(Strict.HINT_STRICT_REFS);
}

boolean shouldBlockGlobalUnderStrictVars(String varName) {
// Only check if strict vars is enabled
if (emitterContext == null || emitterContext.symbolTable == null) {
Expand Down Expand Up @@ -2484,7 +2490,11 @@ void compileVariableReference(OperatorNode node, String op) {
// Lexical variable - use existing register
lastResultReg = getVariableRegister(varName);
} else {
// Global variable - load it
// Global variable - check strict vars then load
if (shouldBlockGlobalUnderStrictVars(varName)) {
throwCompilerException("Global symbol \"" + varName + "\" requires explicit package name");
}

// Use NameNormalizer to properly handle special variables (like $&)
// which must always be in the "main" package
String globalVarName = varName.substring(1); // Remove $ sigil first
Expand All @@ -2507,16 +2517,23 @@ void compileVariableReference(OperatorNode node, String op) {
// Execute the block to get a variable name string, then load that variable
BlockNode block = (BlockNode) node.operand;

// Compile the block
// Check strict refs at compile time — mirrors JVM path in EmitVariable.java
block.accept(this);
int blockResultReg = lastResultReg;

// Load via symbolic reference
int rd = allocateRegister();
emitWithToken(Opcodes.LOAD_SYMBOLIC_SCALAR, node.getIndex());
emitReg(rd);
emitReg(blockResultReg);

if (isStrictRefsEnabled()) {
// strict refs: scalarDeref() — throws for non-refs
emitWithToken(Opcodes.DEREF_SCALAR_STRICT, node.getIndex());
emitReg(rd);
emitReg(blockResultReg);
} else {
// no strict refs: scalarDerefNonStrict(pkg) — allows symrefs
int pkgIdx = addToStringPool(getCurrentPackage());
emitWithToken(Opcodes.DEREF_SCALAR_NONSTRICT, node.getIndex());
emitReg(rd);
emitReg(blockResultReg);
emit(pkgIdx);
}
lastResultReg = rd;
} else if (node.operand instanceof OperatorNode) {
// Operator dereference: $$x, $${expr}, etc.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1769,12 +1769,15 @@ public static RuntimeList execute(InterpretedCode code, RuntimeArray args, int c
pc = executeSystemOps(opcode, bytecode, pc, registers);
break;

// Group 9: Special I/O (151-154) and DEREF_GLOB
// Group 9: Special I/O (151-154), glob ops, strict deref
case Opcodes.EVAL_STRING:
case Opcodes.SELECT_OP:
case Opcodes.LOAD_GLOB:
case Opcodes.SLEEP_OP:
case Opcodes.DEREF_GLOB:
case Opcodes.LOAD_GLOB_DYNAMIC:
case Opcodes.DEREF_SCALAR_STRICT:
case Opcodes.DEREF_SCALAR_NONSTRICT:
pc = executeSpecialIO(opcode, bytecode, pc, registers, code);
break;

Expand Down Expand Up @@ -3087,7 +3090,8 @@ private static int executeSystemOps(int opcode, int[] bytecode, int pc,

/**
* Execute special I/O operations (opcodes 151-154).
* Handles: EVAL_STRING, SELECT_OP, LOAD_GLOB, SLEEP_OP, DEREF_GLOB
* Handles: EVAL_STRING, SELECT_OP, LOAD_GLOB, SLEEP_OP, DEREF_GLOB, LOAD_GLOB_DYNAMIC,
* DEREF_SCALAR_STRICT, DEREF_SCALAR_NONSTRICT
*/
private static int executeSpecialIO(int opcode, int[] bytecode, int pc,
RuntimeBase[] registers, InterpretedCode code) {
Expand All @@ -3102,6 +3106,12 @@ private static int executeSpecialIO(int opcode, int[] bytecode, int pc,
return SlowOpcodeHandler.executeSleep(bytecode, pc, registers);
case Opcodes.DEREF_GLOB:
return SlowOpcodeHandler.executeDerefGlob(bytecode, pc, registers, code);
case Opcodes.LOAD_GLOB_DYNAMIC:
return SlowOpcodeHandler.executeLoadGlobDynamic(bytecode, pc, registers, code);
case Opcodes.DEREF_SCALAR_STRICT:
return SlowOpcodeHandler.executeDerefScalarStrict(bytecode, pc, registers);
case Opcodes.DEREF_SCALAR_NONSTRICT:
return SlowOpcodeHandler.executeDerefScalarNonStrict(bytecode, pc, registers, code);
default:
throw new RuntimeException("Unknown special I/O opcode: " + opcode);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -641,36 +641,59 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
// We need to evaluate the LHS FIRST to get the variable name,
// then evaluate the RHS, to ensure the RHS doesn't clobber the LHS registers
if (node.left instanceof OperatorNode leftOp && leftOp.operator.equals("$")) {
boolean strictRefsEnabled = bytecodeCompiler.isStrictRefsEnabled();

if (leftOp.operand instanceof BlockNode) {
// ${block} = value
// ${block} = value — mirrors JVM EmitVariable.java case "$"
BlockNode block = (BlockNode) leftOp.operand;
block.accept(bytecodeCompiler);
int nameReg = bytecodeCompiler.lastResultReg;

// Now compile the RHS
// Deref to get lvalue target (strict or non-strict)
int derefReg = bytecodeCompiler.allocateRegister();
if (strictRefsEnabled) {
bytecodeCompiler.emitWithToken(Opcodes.DEREF_SCALAR_STRICT, node.getIndex());
bytecodeCompiler.emitReg(derefReg);
bytecodeCompiler.emitReg(nameReg);
} else {
int pkgIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage());
bytecodeCompiler.emitWithToken(Opcodes.DEREF_SCALAR_NONSTRICT, node.getIndex());
bytecodeCompiler.emitReg(derefReg);
bytecodeCompiler.emitReg(nameReg);
bytecodeCompiler.emit(pkgIdx);
}

// Now compile the RHS and assign
node.right.accept(bytecodeCompiler);
int valueReg = bytecodeCompiler.lastResultReg;

// Use STORE_SYMBOLIC_SCALAR to store via symbolic reference
bytecodeCompiler.emit(Opcodes.STORE_SYMBOLIC_SCALAR);
bytecodeCompiler.emitReg(nameReg);
bytecodeCompiler.emit(Opcodes.SET_SCALAR);
bytecodeCompiler.emitReg(derefReg);
bytecodeCompiler.emitReg(valueReg);

bytecodeCompiler.lastResultReg = valueReg;
return;
} else if (leftOp.operand instanceof OperatorNode) {
// $$var = value (scalar dereference assignment)
// Evaluate the inner expression to get the variable name
// $$var = value — mirrors JVM EmitVariable.java case "$"
leftOp.operand.accept(bytecodeCompiler);
int nameReg = bytecodeCompiler.lastResultReg;

// Now compile the RHS
int derefReg = bytecodeCompiler.allocateRegister();
if (strictRefsEnabled) {
bytecodeCompiler.emitWithToken(Opcodes.DEREF_SCALAR_STRICT, node.getIndex());
bytecodeCompiler.emitReg(derefReg);
bytecodeCompiler.emitReg(nameReg);
} else {
int pkgIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage());
bytecodeCompiler.emitWithToken(Opcodes.DEREF_SCALAR_NONSTRICT, node.getIndex());
bytecodeCompiler.emitReg(derefReg);
bytecodeCompiler.emitReg(nameReg);
bytecodeCompiler.emit(pkgIdx);
}

node.right.accept(bytecodeCompiler);
int valueReg = bytecodeCompiler.lastResultReg;

// Use STORE_SYMBOLIC_SCALAR to store via symbolic reference
bytecodeCompiler.emit(Opcodes.STORE_SYMBOLIC_SCALAR);
bytecodeCompiler.emitReg(nameReg);
bytecodeCompiler.emit(Opcodes.SET_SCALAR);
bytecodeCompiler.emitReg(derefReg);
bytecodeCompiler.emitReg(valueReg);

bytecodeCompiler.lastResultReg = valueReg;
Expand Down Expand Up @@ -896,6 +919,25 @@ public static void compileAssignmentOperator(BytecodeCompiler bytecodeCompiler,
bytecodeCompiler.emitReg(globReg);
bytecodeCompiler.emitReg(valueReg);

bytecodeCompiler.lastResultReg = globReg;
} else if (leftOp.operator.equals("*") && leftOp.operand instanceof BlockNode) {
// Symbolic typeglob assignment: *{"name"} = value (no strict refs)
// Evaluate the block to get the glob name as a scalar, then load glob by name
leftOp.operand.accept(bytecodeCompiler);
int nameScalarReg = bytecodeCompiler.lastResultReg;

int globReg = bytecodeCompiler.allocateRegister();
int pkgIdx = bytecodeCompiler.addToStringPool(bytecodeCompiler.getCurrentPackage());
bytecodeCompiler.emitWithToken(Opcodes.LOAD_GLOB_DYNAMIC, node.getIndex());
bytecodeCompiler.emitReg(globReg);
bytecodeCompiler.emitReg(nameScalarReg);
bytecodeCompiler.emit(pkgIdx);

// Store value to glob
bytecodeCompiler.emit(Opcodes.STORE_GLOB);
bytecodeCompiler.emitReg(globReg);
bytecodeCompiler.emitReg(valueReg);

bytecodeCompiler.lastResultReg = globReg;
} else if (leftOp.operator.equals("pos")) {
// pos($var) = value - lvalue assignment to regex position
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1669,15 +1669,25 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
bytecodeCompiler.throwCompilerException("open requires arguments");
}

// Compile all arguments into a list
// Compile all arguments into a list.
// Track the first-arg (filehandle) register so we can write the GLOB back
// after OPEN — IOOperator.open() does fileHandle.set() on a copy in the array,
// so we must propagate the result back to the original lexical register.
int argsReg = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emit(Opcodes.NEW_ARRAY);
bytecodeCompiler.emitReg(argsReg);

int fhReg = -1;
boolean first = true;
for (Node arg : argsList.elements) {
arg.accept(bytecodeCompiler);
int elemReg = bytecodeCompiler.lastResultReg;

if (first) {
fhReg = elemReg; // remember the filehandle lvalue register
first = false;
}

bytecodeCompiler.emit(Opcodes.ARRAY_PUSH);
bytecodeCompiler.emitReg(argsReg);
bytecodeCompiler.emitReg(elemReg);
Expand All @@ -1690,6 +1700,24 @@ public static void visitOperator(BytecodeCompiler bytecodeCompiler, OperatorNode
bytecodeCompiler.emit(bytecodeCompiler.currentCallContext);
bytecodeCompiler.emitReg(argsReg);

// Write the (now-modified) first element of args back to the fh register.
// IOOperator.open() calls fileHandle.set(glob) on a copy inside the array,
// so we must retrieve element 0 and store it back into the lexical $fh.
if (fhReg >= 0) {
int idx0Reg = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emit(Opcodes.LOAD_INT);
bytecodeCompiler.emitReg(idx0Reg);
bytecodeCompiler.emit(0); // index 0
int gotReg = bytecodeCompiler.allocateRegister();
bytecodeCompiler.emit(Opcodes.ARRAY_GET);
bytecodeCompiler.emitReg(gotReg);
bytecodeCompiler.emitReg(argsReg);
bytecodeCompiler.emitReg(idx0Reg);
bytecodeCompiler.emit(Opcodes.SET_SCALAR);
bytecodeCompiler.emitReg(fhReg);
bytecodeCompiler.emitReg(gotReg);
}

bytecodeCompiler.lastResultReg = rd;
} else if (op.equals("matchRegex")) {
// m/pattern/flags - create a regex and optionally match against a string
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/org/perlonjava/backend/bytecode/Opcodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -1076,5 +1076,22 @@ public class Opcodes {
* Format: DEREF_GLOB rd rs nameIdx(currentPackage) */
public static final short DEREF_GLOB = 333;

/** Load glob by runtime name (symbolic ref): rd = GlobalVariable.getGlobalIO(normalize(nameReg, pkg))
* Used for *{"name"} = value typeglob assignment with dynamic name
* Format: LOAD_GLOB_DYNAMIC rd nameReg pkgIdx */
public static final short LOAD_GLOB_DYNAMIC = 334;

/** Scalar dereference (strict refs): rd = rs.scalarDeref()
* Throws "Can't use string as a SCALAR ref while strict refs in use" for non-refs.
* Matches JVM path: scalarDeref() — used when strict refs is enabled.
* Format: DEREF_SCALAR_STRICT rd rs */
public static final short DEREF_SCALAR_STRICT = 335;

/** Scalar dereference (no strict refs): rd = rs.scalarDerefNonStrict(pkg)
* Allows symbolic references (string name -> global variable lookup).
* Matches JVM path: scalarDerefNonStrict(pkg) — used when strict refs is disabled.
* Format: DEREF_SCALAR_NONSTRICT rd rs pkgIdx */
public static final short DEREF_SCALAR_NONSTRICT = 336;

private Opcodes() {} // Utility class - no instantiation
}
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,64 @@ public static int executeLoadGlob(
return pc;
}

/**
* LOAD_GLOB_DYNAMIC: rd = GlobalVariable.getGlobalIO(normalize(nameReg, pkg))
* Format: LOAD_GLOB_DYNAMIC rd nameReg pkgIdx
* Effect: Loads a glob by runtime name — used for *{"name"} = value symbolic assignment
*/
public static int executeLoadGlobDynamic(
int[] bytecode,
int pc,
RuntimeBase[] registers,
InterpretedCode code) {

int rd = bytecode[pc++];
int nameReg = bytecode[pc++];
int pkgIdx = bytecode[pc++];

String pkg = code.stringPool[pkgIdx];
String name = ((RuntimeScalar) registers[nameReg]).toString();
String globalName = NameNormalizer.normalizeVariableName(name, pkg);

registers[rd] = GlobalVariable.getGlobalIO(globalName);
return pc;
}

/**
* DEREF_SCALAR_STRICT: rd = rs.scalarDeref()
* Format: DEREF_SCALAR_STRICT rd rs
* Matches JVM path: scalarDeref() — throws for non-refs under strict refs.
*/
public static int executeDerefScalarStrict(
int[] bytecode,
int pc,
RuntimeBase[] registers) {

int rd = bytecode[pc++];
int rs = bytecode[pc++];
registers[rd] = ((RuntimeScalar) registers[rs]).scalarDeref();
return pc;
}

/**
* DEREF_SCALAR_NONSTRICT: rd = rs.scalarDerefNonStrict(pkg)
* Format: DEREF_SCALAR_NONSTRICT rd rs pkgIdx
* Matches JVM path: scalarDerefNonStrict(pkg) — allows symbolic refs.
*/
public static int executeDerefScalarNonStrict(
int[] bytecode,
int pc,
RuntimeBase[] registers,
InterpretedCode code) {

int rd = bytecode[pc++];
int rs = bytecode[pc++];
int pkgIdx = bytecode[pc++];
String pkg = code.stringPool[pkgIdx];
registers[rd] = ((RuntimeScalar) registers[rs]).scalarDerefNonStrict(pkg);
return pc;
}

/**
* DEREF_GLOB: rd = rs.globDerefNonStrict(currentPackage)
* Format: DEREF_GLOB rd rs nameIdx(currentPackage)
Expand Down