diff --git a/src/main/java/org/perlonjava/app/cli/ArgumentParser.java b/src/main/java/org/perlonjava/app/cli/ArgumentParser.java index 68a107051..9694839ba 100644 --- a/src/main/java/org/perlonjava/app/cli/ArgumentParser.java +++ b/src/main/java/org/perlonjava/app/cli/ArgumentParser.java @@ -318,8 +318,10 @@ private static int processClusteredSwitches(String[] args, CompilerOptions parse return index; case 'w': - // enable many useful warnings - parsedArgs.moduleUseStatements.add(new ModuleUseStatement(switchChar, "warnings", null, false)); + // enable many useful warnings via $^W (old-style global warnings) + // Note: This intentionally does NOT add "use warnings" - $^W=1 at initialization + // is sufficient and avoids line number offset issues + parsedArgs.warnFlag = true; break; case 'W': // enable all warnings diff --git a/src/main/java/org/perlonjava/app/cli/CompilerOptions.java b/src/main/java/org/perlonjava/app/cli/CompilerOptions.java index 447096908..960f56311 100644 --- a/src/main/java/org/perlonjava/app/cli/CompilerOptions.java +++ b/src/main/java/org/perlonjava/app/cli/CompilerOptions.java @@ -67,6 +67,7 @@ public class CompilerOptions implements Cloneable { public boolean allowUnsafeOperations = false; // For -U public boolean runUnderDebugger = false; // For -d public boolean taintWarnings = false; // For -t + public boolean warnFlag = false; // For -w (sets $^W = 1) public String debugFlags = ""; // For -D // Unicode/encoding flags for -C switches public boolean unicodeStdin = false; // -CS or -CI diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitForeach.java b/src/main/java/org/perlonjava/backend/jvm/EmitForeach.java index 0da0c6502..55f21ee95 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitForeach.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitForeach.java @@ -7,6 +7,8 @@ import org.perlonjava.frontend.analysis.RegexUsageDetector; import org.perlonjava.frontend.astnode.*; import org.perlonjava.runtime.perlmodule.Warnings; +import org.perlonjava.runtime.runtimetypes.GlobalContext; +import org.perlonjava.runtime.runtimetypes.GlobalVariable; import org.perlonjava.runtime.runtimetypes.RuntimeContextType; public class EmitForeach { @@ -132,10 +134,18 @@ public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) { if (variableNode instanceof OperatorNode opNode && (opNode.operator.equals("my") || opNode.operator.equals("our"))) { isDeclaredInFor = true; - boolean isWarningEnabled = Warnings.warningManager.isWarningEnabled("redefine"); + // Shadow warning is emitted during parsing in OperatorParser.addVariableToScope() + // For loop variables, we need to temporarily disable it to avoid spurious warnings + // Check both lexical 'shadow' category and $^W + boolean isWarningEnabled = Warnings.warningManager.isWarningEnabled("shadow"); + boolean isWarnVarEnabled = GlobalVariable.getGlobalVariable(GlobalContext.encodeSpecialVar("W")).getBoolean(); if (isWarningEnabled) { - // turn off "masks earlier declaration" warning - Warnings.warningManager.setWarningState("redefine", false); + // turn off lexical "shadow" warning for loop variables + Warnings.warningManager.setWarningState("shadow", false); + } + if (isWarnVarEnabled) { + // temporarily turn off $^W for loop variables + GlobalVariable.getGlobalVariable(GlobalContext.encodeSpecialVar("W")).set(0); } // emit the variable declarations variableNode.accept(emitterVisitor.with(RuntimeContextType.VOID)); @@ -160,8 +170,12 @@ public static void emitFor1(EmitterVisitor emitterVisitor, For1Node node) { } if (isWarningEnabled) { - // restore warnings - Warnings.warningManager.setWarningState("redefine", true); + // restore lexical warnings + Warnings.warningManager.setWarningState("shadow", true); + } + if (isWarnVarEnabled) { + // restore $^W + GlobalVariable.getGlobalVariable(GlobalContext.encodeSpecialVar("W")).set(1); } // Reset global variable check after rewriting diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java b/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java index 7cecf6518..0cde29b47 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitOperator.java @@ -262,8 +262,26 @@ static void handleIndexBuiltin(EmitterVisitor emitterVisitor, OperatorNode node) static void handleAtan2(EmitterVisitor emitterVisitor, OperatorNode node) { EmitterVisitor scalarVisitor = emitterVisitor.with(RuntimeContextType.SCALAR); if (node.operand instanceof ListNode operand) { + // Spill the first operand before evaluating the second so non-local control flow + // propagation can't jump to returnLabel with an extra value on the JVM operand stack. + MethodVisitor mv = emitterVisitor.ctx.mv; operand.elements.get(0).accept(scalarVisitor); + + int leftSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot(); + boolean pooled = leftSlot >= 0; + if (!pooled) { + leftSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable(); + } + mv.visitVarInsn(Opcodes.ASTORE, leftSlot); + operand.elements.get(1).accept(scalarVisitor); + + mv.visitVarInsn(Opcodes.ALOAD, leftSlot); + mv.visitInsn(Opcodes.SWAP); + + if (pooled) { + emitterVisitor.ctx.javaClassInfo.releaseSpillSlot(); + } emitOperator(node, emitterVisitor); } } diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitOperatorDeleteExists.java b/src/main/java/org/perlonjava/backend/jvm/EmitOperatorDeleteExists.java index a4781e17a..15a0c3b68 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitOperatorDeleteExists.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitOperatorDeleteExists.java @@ -103,9 +103,18 @@ private static void handleDeleteExistsInner(OperatorNode node, EmitterVisitor em // Check if this is a compound expression like $hash->{key}[index] if (binop.left instanceof BinaryOperatorNode leftBinop && leftBinop.operator.equals("->")) { // Handle compound hash->array dereference for exists/delete - // First evaluate the hash dereference to get the array + // Spill the left operand before evaluating the index so non-local control flow + // propagation can't jump to returnLabel with an extra value on the JVM operand stack. + MethodVisitor mv = emitterVisitor.ctx.mv; leftBinop.accept(emitterVisitor.with(RuntimeContextType.SCALAR)); + int leftSlot = emitterVisitor.ctx.javaClassInfo.acquireSpillSlot(); + boolean pooled = leftSlot >= 0; + if (!pooled) { + leftSlot = emitterVisitor.ctx.symbolTable.allocateLocalVariable(); + } + mv.visitVarInsn(Opcodes.ASTORE, leftSlot); + // Now emit the index if (binop.right instanceof ArrayLiteralNode arrayLiteral && arrayLiteral.elements.size() == 1) { @@ -116,6 +125,13 @@ private static void handleDeleteExistsInner(OperatorNode node, EmitterVisitor em emitterVisitor.ctx.errorUtil); } + mv.visitVarInsn(Opcodes.ALOAD, leftSlot); + mv.visitInsn(Opcodes.SWAP); + + if (pooled) { + emitterVisitor.ctx.javaClassInfo.releaseSpillSlot(); + } + // Call the appropriate method if (operator.equals("exists")) { emitterVisitor.ctx.mv.visitMethodInsn( diff --git a/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java b/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java index e58d6b937..279fddad9 100644 --- a/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java +++ b/src/main/java/org/perlonjava/backend/jvm/EmitVariable.java @@ -1092,15 +1092,8 @@ static void handleMyOperator(EmitterVisitor emitterVisitor, OperatorNode node) { String name = ((IdentifierNode) identifierNode).name; String var = sigil + name; emitterVisitor.ctx.logDebug("MY " + operator + " " + sigil + name); - if (emitterVisitor.ctx.symbolTable.getVariableIndexInCurrentScope(var) != -1) { - if (Warnings.warningManager.isWarningEnabled("redefine")) { - System.err.println( - emitterVisitor.ctx.errorUtil.errorMessage(node.getIndex(), - "Warning: \"" + operator + "\" variable " - + var - + " masks earlier declaration in same ctx.symbolTable")); - } - } + // Note: shadow warning is emitted during parsing in OperatorParser.addVariableToScope() + // We don't emit it again here to avoid duplicates int varIndex = emitterVisitor.ctx.symbolTable.addVariable(var, operator, sigilNode); // TODO optimization - SETVAR+MY can be combined diff --git a/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java b/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java index 3bf28c7a6..a85b142f0 100644 --- a/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java +++ b/src/main/java/org/perlonjava/frontend/parser/OperatorParser.java @@ -6,6 +6,7 @@ import org.perlonjava.frontend.lexer.LexerToken; import org.perlonjava.runtime.operators.WarnDie; import org.perlonjava.runtime.perlmodule.Strict; +import org.perlonjava.runtime.runtimetypes.GlobalContext; import org.perlonjava.runtime.runtimetypes.GlobalVariable; import org.perlonjava.runtime.runtimetypes.NameNormalizer; import org.perlonjava.runtime.runtimetypes.PerlCompilerException; @@ -232,11 +233,16 @@ private static void addVariableToScope(EmitterContext ctx, String operator, Oper String name = ((IdentifierNode) identifierNode).name; String var = sigil + name; if (ctx.symbolTable.getVariableIndexInCurrentScope(var) != -1) { - System.err.println( - ctx.errorUtil.errorMessage(node.getIndex(), - "Warning: \"" + operator + "\" variable " - + var - + " masks earlier declaration in same ctx.symbolTable")); + // Check if shadow warnings are enabled via 'use warnings "shadow"' or via $^W (-w flag) + boolean shadowEnabled = ctx.symbolTable.isWarningCategoryEnabled("shadow") + || GlobalVariable.getGlobalVariable(GlobalContext.encodeSpecialVar("W")).getBoolean(); + if (shadowEnabled) { + // "our" uses "redeclared", "my"/"state" use "masks earlier declaration in same scope" + String message = operator.equals("our") + ? "\"" + operator + "\" variable " + var + " redeclared" + : "\"" + operator + "\" variable " + var + " masks earlier declaration in same scope"; + System.err.print(ctx.errorUtil.warningMessage(node.getIndex(), message)); + } } int varIndex = ctx.symbolTable.addVariable(var, operator, node); // Note: the isDeclaredReference flag is stored in node.annotations diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java index 77efd771d..4d792d376 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/ErrorMessageUtil.java @@ -209,6 +209,18 @@ public String errorMessage(int index, String message) { return message + " at " + loc.fileName() + " line " + loc.lineNumber() + ", near " + errorMessageQuote(nearString) + "\n"; } + /** + * Constructs a warning message without "near" context, matching Perl's warning format. + * + * @param index the index of the token where the warning occurred + * @param message the warning message + * @return the formatted warning message + */ + public String warningMessage(int index, String message) { + SourceLocation loc = getSourceLocationAccurate(index); + return message + " at " + loc.fileName() + " line " + loc.lineNumber() + ".\n"; + } + private String buildNearString(int index) { int end = Math.min(tokens.size() - 1, index + 5); StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java b/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java index fa695fcd2..646b2e12a 100644 --- a/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java +++ b/src/main/java/org/perlonjava/runtime/runtimetypes/GlobalContext.java @@ -60,6 +60,11 @@ public static void initializeGlobals(CompilerOptions compilerOptions) { GlobalVariable.getGlobalVariable("main::" + Character.toString('X' - 'A' + 1)).set("jperl"); } + // Initialize $^W based on -w flag + if (compilerOptions.warnFlag) { + GlobalVariable.getGlobalVariable(encodeSpecialVar("W")).set(1); + } + GlobalVariable.getGlobalVariable("main::]").set(Configuration.getPerlVersionOld()); // initialize $] to Perl version GlobalVariable.getGlobalVariable("main::@").set(""); // initialize $@ to "" GlobalVariable.getGlobalVariable("main::_"); // initialize $_ to "undef"