@@ -1643,14 +1643,14 @@ private void checkUninitializedVars(FunctionLike f) {
16431643 && !f .getSource ().getFile ().endsWith ("blizzard.j" )
16441644 && !f .getSource ().getFile ().endsWith ("war3map.j" )) {
16451645 new DataflowAnomalyAnalysis (Utils .isJassCode (f )).execute (f );
1646- checkJassImplicitNullLocalsReadWithoutExplicitWrite (f );
16471646 }
1647+ checkJassImplicitNullLocalsReadWithoutExplicitWrite (f );
16481648 }
16491649
16501650 /**
16511651 * JASS compatibility shim: we currently synthesize "= null" for uninitialized non-primitive
16521652 * locals to avoid invalid emitted JASS. Still report likely user bugs early when such a local
1653- * is read but never explicitly assigned in the input.
1653+ * is read before its first explicit assignment in the input.
16541654 */
16551655 private void checkJassImplicitNullLocalsReadWithoutExplicitWrite (FunctionLike f ) {
16561656 if (!Utils .isJassCode (f )) {
@@ -1671,27 +1671,84 @@ public void visit(LocalVarDef localVarDef) {
16711671 return ;
16721672 }
16731673
1674- Set <LocalVarDef > explicitWrites = new HashSet <>();
1674+ Set <LocalVarDef > implicitNullLocalSet = new HashSet <>(implicitNullLocals );
1675+ Map <LocalVarDef , StmtSet > firstExplicitWrite = new HashMap <>();
16751676 f .accept (new Element .DefaultVisitor () {
16761677 @ Override
16771678 public void visit (StmtSet stmtSet ) {
16781679 super .visit (stmtSet );
16791680 NameLink link = stmtSet .getUpdatedExpr ().attrNameLink ();
16801681 if (link != null && link .getDef () instanceof LocalVarDef ) {
1681- explicitWrites .add ((LocalVarDef ) link .getDef ());
1682+ LocalVarDef local = (LocalVarDef ) link .getDef ();
1683+ if (!implicitNullLocalSet .contains (local )) {
1684+ return ;
1685+ }
1686+ StmtSet previous = firstExplicitWrite .get (local );
1687+ if (previous == null
1688+ || stmtSet .attrSource ().getLeftPos () < previous .attrSource ().getLeftPos ()) {
1689+ firstExplicitWrite .put (local , stmtSet );
1690+ }
1691+ }
1692+ }
1693+ });
1694+
1695+ Set <LocalVarDef > readBeforeExplicitWrite = new HashSet <>();
1696+ f .accept (new Element .DefaultVisitor () {
1697+ @ Override
1698+ public void visit (ExprVarAccess varAccess ) {
1699+ super .visit (varAccess );
1700+ NameLink link = varAccess .attrNameLink ();
1701+ if (link == null || !(link .getDef () instanceof LocalVarDef )) {
1702+ return ;
1703+ }
1704+ LocalVarDef local = (LocalVarDef ) link .getDef ();
1705+ if (!implicitNullLocalSet .contains (local )) {
1706+ return ;
1707+ }
1708+ if (isWriteTarget (varAccess )) {
1709+ return ;
1710+ }
1711+ StmtSet firstWrite = firstExplicitWrite .get (local );
1712+ if (firstWrite == null ) {
1713+ readBeforeExplicitWrite .add (local );
1714+ return ;
1715+ }
1716+ StmtSet enclosingSet = nearestEnclosingStmtSet (varAccess );
1717+ if (enclosingSet == firstWrite ) {
1718+ readBeforeExplicitWrite .add (local );
1719+ return ;
1720+ }
1721+ if (varAccess .attrSource ().getLeftPos () < firstWrite .attrSource ().getLeftPos ()) {
1722+ readBeforeExplicitWrite .add (local );
16821723 }
16831724 }
16841725 });
16851726
16861727 for (LocalVarDef local : implicitNullLocals ) {
1687- if (explicitWrites .contains (local )) {
1688- continue ;
1728+ if (readBeforeExplicitWrite .contains (local )) {
1729+ local .addWarning ("Variable " + local .getName ()
1730+ + " is read before explicit initialization in input JASS; defaulting to null." );
16891731 }
1690- if (f .attrReadVariables ().contains (local )) {
1691- local .addError ("Variable " + local .getName ()
1692- + " is read before explicit initialization in input JASS." );
1732+ }
1733+ }
1734+
1735+ private boolean isWriteTarget (ExprVarAccess varAccess ) {
1736+ if (!(varAccess .getParent () instanceof StmtSet )) {
1737+ return false ;
1738+ }
1739+ StmtSet set = (StmtSet ) varAccess .getParent ();
1740+ return set .getUpdatedExpr () == varAccess ;
1741+ }
1742+
1743+ private @ Nullable StmtSet nearestEnclosingStmtSet (Element e ) {
1744+ Element current = e .getParent ();
1745+ while (current != null ) {
1746+ if (current instanceof StmtSet ) {
1747+ return (StmtSet ) current ;
16931748 }
1749+ current = current .getParent ();
16941750 }
1751+ return null ;
16951752 }
16961753
16971754 private boolean isImplicitNullInit (LocalVarDef localVarDef ) {
0 commit comments