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 @@ -15,9 +15,11 @@
import org.eclipse.jdt.annotation.Nullable;

import java.util.List;
import java.util.Set;


public class AntlrJassParseTreeTransformer {
private static final Set<String> JASS_PRIMITIVE_TYPES = Set.of("integer", "real", "boolean");
private final String file;
private final ErrorHandler cuErrorHandler;
private final LineOffsets lineOffsets;
Expand Down Expand Up @@ -123,12 +125,23 @@ private WStatements transformJassLocals(List<JassParser.JassLocalContext> jassLo
OptTypeExpr optTyp = transformOptionalType(l.jassTypeExpr());
Identifier name = Ast.Identifier(source(l.name), l.name.getText());
OptExpr initialExpr = transformOptionalExpr(l.initial);
if (l.initial == null && shouldDefaultLocalToNull(optTyp)) {
initialExpr = Ast.ExprNull(source(l));
}
result.add(Ast.LocalVarDef(source(l), modifiers, optTyp, name,
initialExpr));
}
return result;
}

private boolean shouldDefaultLocalToNull(OptTypeExpr optTyp) {
if (!(optTyp instanceof TypeExprSimple)) {
return false;
}
String typeName = ((TypeExprSimple) optTyp).getTypeName();
return !JASS_PRIMITIVE_TYPES.contains(typeName);
}

private WStatements transformJassStatements(JassParser.JassStatementsContext stmts) {
WStatements result = Ast.WStatements();
for (JassParser.JassStatementContext s : stmts.jassStatement()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
import org.eclipse.jdt.annotation.Nullable;

import java.util.List;
import java.util.Set;

public class AntlrJurstParseTreeTransformer {
private static final Set<String> JASS_PRIMITIVE_TYPES = Set.of("integer", "real", "boolean");

private final String file;
private final ErrorHandler cuErrorHandler;
Expand Down Expand Up @@ -134,12 +136,23 @@ private WStatements transformJassLocals(List<JassLocalContext> jassLocals) {
OptTypeExpr optTyp = transformOptionalType(l.typeExpr());
Identifier name = text(l.name);
OptExpr initialExpr = transformOptionalExpr(l.initial);
if (l.initial == null && shouldDefaultJassLocalToNull(optTyp)) {
initialExpr = Ast.ExprNull(source(l));
}
result.add(Ast.LocalVarDef(source(l), modifiers, optTyp, name,
initialExpr));
}
return result;
}

private boolean shouldDefaultJassLocalToNull(OptTypeExpr optTyp) {
if (!(optTyp instanceof TypeExprSimple)) {
return false;
}
String typeName = ((TypeExprSimple) optTyp).getTypeName();
return !JASS_PRIMITIVE_TYPES.contains(typeName);
}

private WStatements transformJassStatements(JassStatementsContext stmts) {
WStatements result = Ast.WStatements();
for (JassStatementContext s : stmts.jassStatement()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@
import java.util.Comparator;
import java.util.Deque;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@SuppressWarnings("Duplicates")
public class AntlrWurstParseTreeTransformer {
private static final Set<String> JASS_PRIMITIVE_TYPES = Set.of("integer", "real", "boolean");

private final String file;
private final ErrorHandler cuErrorHandler;
Expand Down Expand Up @@ -225,12 +227,23 @@ private WStatements transformJassLocals(List<JassLocalContext> jassLocals) {
OptTypeExpr optTyp = transformOptionalType(l.typeExpr());
Identifier name = text(l.name);
OptExpr initialExpr = transformOptionalExpr(l.initial);
if (l.initial == null && shouldDefaultJassLocalToNull(optTyp)) {
initialExpr = Ast.ExprNull(source(l));
}
result.add(Ast.LocalVarDef(source(l), modifiers, optTyp, name,
initialExpr));
}
return result;
}

private boolean shouldDefaultJassLocalToNull(OptTypeExpr optTyp) {
if (!(optTyp instanceof TypeExprSimple)) {
return false;
}
String typeName = ((TypeExprSimple) optTyp).getTypeName();
return !JASS_PRIMITIVE_TYPES.contains(typeName);
}

private WStatements transformJassStatements(JassStatementsContext stmts) {
WStatements result = Ast.WStatements();
if (stmts != null && stmts.jassStatement() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1644,6 +1644,125 @@ private void checkUninitializedVars(FunctionLike f) {
&& !f.getSource().getFile().endsWith("war3map.j")) {
new DataflowAnomalyAnalysis(Utils.isJassCode(f)).execute(f);
}
checkJassImplicitNullLocalsReadWithoutExplicitWrite(f);
}

/**
* JASS compatibility shim: we currently synthesize "= null" for uninitialized non-primitive
* locals to avoid invalid emitted JASS. Still report likely user bugs early when such a local
* is read before its first explicit assignment in the input.
*/
private void checkJassImplicitNullLocalsReadWithoutExplicitWrite(FunctionLike f) {
if (!Utils.isJassCode(f)) {
return;
}

List<LocalVarDef> implicitNullLocals = new ArrayList<>();
f.accept(new Element.DefaultVisitor() {
@Override
public void visit(LocalVarDef localVarDef) {
super.visit(localVarDef);
if (isImplicitNullInit(localVarDef)) {
implicitNullLocals.add(localVarDef);
}
}
});
if (implicitNullLocals.isEmpty()) {
return;
}

Set<LocalVarDef> implicitNullLocalSet = new HashSet<>(implicitNullLocals);
Map<LocalVarDef, StmtSet> firstExplicitWrite = new HashMap<>();
f.accept(new Element.DefaultVisitor() {
@Override
public void visit(StmtSet stmtSet) {
super.visit(stmtSet);
NameLink link = stmtSet.getUpdatedExpr().attrNameLink();
if (link != null && link.getDef() instanceof LocalVarDef) {
LocalVarDef local = (LocalVarDef) link.getDef();
if (!implicitNullLocalSet.contains(local)) {
return;
}
StmtSet previous = firstExplicitWrite.get(local);
if (previous == null
|| stmtSet.attrSource().getLeftPos() < previous.attrSource().getLeftPos()) {
firstExplicitWrite.put(local, stmtSet);
}
}
}
});

Set<LocalVarDef> readBeforeExplicitWrite = new HashSet<>();
f.accept(new Element.DefaultVisitor() {
@Override
public void visit(ExprVarAccess varAccess) {
super.visit(varAccess);
NameLink link = varAccess.attrNameLink();
if (link == null || !(link.getDef() instanceof LocalVarDef)) {
return;
}
LocalVarDef local = (LocalVarDef) link.getDef();
if (!implicitNullLocalSet.contains(local)) {
return;
}
if (isWriteTarget(varAccess)) {
return;
}
StmtSet firstWrite = firstExplicitWrite.get(local);
if (firstWrite == null) {
readBeforeExplicitWrite.add(local);
return;
}
StmtSet enclosingSet = nearestEnclosingStmtSet(varAccess);
if (enclosingSet == firstWrite) {
readBeforeExplicitWrite.add(local);
return;
}
if (varAccess.attrSource().getLeftPos() < firstWrite.attrSource().getLeftPos()) {
readBeforeExplicitWrite.add(local);
}
}
});

for (LocalVarDef local : implicitNullLocals) {
if (readBeforeExplicitWrite.contains(local)) {
local.addWarning("Variable " + local.getName()
+ " is read before explicit initialization in input JASS; defaulting to null.");
}
}
}

private boolean isWriteTarget(ExprVarAccess varAccess) {
if (!(varAccess.getParent() instanceof StmtSet)) {
return false;
}
StmtSet set = (StmtSet) varAccess.getParent();
return set.getUpdatedExpr() == varAccess;
}

private @Nullable StmtSet nearestEnclosingStmtSet(Element e) {
Element current = e.getParent();
while (current != null) {
if (current instanceof StmtSet) {
return (StmtSet) current;
}
current = current.getParent();
}
return null;
}

private boolean isImplicitNullInit(LocalVarDef localVarDef) {
if (!(localVarDef.getInitialExpr() instanceof ExprNull)) {
return false;
}
// Synthetic null-inits created by the JASS parser use the exact local declaration source span.
return sameSourceSpan(localVarDef.getInitialExpr().attrSource(), localVarDef.attrSource());
}

private boolean sameSourceSpan(de.peeeq.wurstscript.parser.WPos a, de.peeeq.wurstscript.parser.WPos b) {
return a.getFile().equals(b.getFile())
&& a.getLeftPos() == b.getLeftPos()
&& a.getRightPos() == b.getRightPos();
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,66 @@ public void jass() {
));
}

@Test
public void jassLocalHandleDefaultsToNull() {
testAssertOkLinesWithStdLib(false,
"function tiw takes nothing returns nothing",
"local location uiw",
"set uiw = null",
"endfunction",
"package B",
" init",
" tiw()",
"endpackage"
);
}

@Test
public void jassLocalHandleReadWithoutExplicitInitReportsEarly() {
testAssertWarningsLinesWithStdLib(false, "read before explicit initialization in input JASS",
"function tiw takes nothing returns nothing",
"local location uiw",
"call RemoveLocation(uiw)",
"endfunction",
"package B",
" init",
" tiw()",
"endpackage"
);
}

@Test
public void war3mapJassLocalHandleReadWithoutExplicitInitReportsEarly() {
test()
.withStdLib()
.setStopOnFirstError(false)
.executeProg(false)
.expectWarning("read before explicit initialization in input JASS")
.compilationUnits(
compilationUnit("war3map.j",
"function showUnitTextAlliesWithZ takes nothing returns nothing",
"local location p2",
"call RemoveLocation(p2)",
"endfunction"
)
);
}

@Test
public void jassLocalHandleReadBeforeLaterWriteStillWarns() {
testAssertWarningsLinesWithStdLib(false, "read before explicit initialization in input JASS",
"function tiw takes nothing returns nothing",
"local location uiw",
"call RemoveLocation(uiw)",
"set uiw = GetRectCenter(GetPlayableMapRect())",
"call RemoveLocation(uiw)",
"set uiw = null",
"endfunction",
"package B",
" init",
" tiw()",
"endpackage"
);
}

}
Loading