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
6 changes: 4 additions & 2 deletions src/main/java/org/perlonjava/app/cli/ArgumentParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/perlonjava/app/cli/CompilerOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 19 additions & 5 deletions src/main/java/org/perlonjava/backend/jvm/EmitForeach.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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));
Expand All @@ -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
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/org/perlonjava/backend/jvm/EmitOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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(
Expand Down
11 changes: 2 additions & 9 deletions src/main/java/org/perlonjava/backend/jvm/EmitVariable.java
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
16 changes: 11 additions & 5 deletions src/main/java/org/perlonjava/frontend/parser/OperatorParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading