added missing opcodes to VM
[gaemu.git] / gaem / anal / uninit.d
blob88577b98bff933bf6a24f07190d0ab51fd1cb404
1 /* GML analyzer
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module gaem.anal.uninit is aliced;
20 import gaem.parser;
21 import gaem.anal.utils;
24 // ////////////////////////////////////////////////////////////////////////// //
25 void analUninited (NodeFunc fn) {
26 bool[string] locals;
27 bool[string] globals;
28 Loc[string] vdecls; // for error messages
30 int argvar (string s) {
31 switch (s) {
32 case "argument0": return 0;
33 case "argument1": return 1;
34 case "argument2": return 2;
35 case "argument3": return 3;
36 case "argument4": return 4;
37 case "argument5": return 5;
38 case "argument6": return 6;
39 case "argument7": return 7;
40 case "argument8": return 8;
41 case "argument9": return 9;
42 case "argument10": return 10;
43 case "argument11": return 11;
44 case "argument12": return 12;
45 case "argument13": return 13;
46 case "argument14": return 14;
47 case "argument15": return 15;
48 case "self": return 16;
49 case "other": return 17;
50 default:
52 return -1;
55 void warning(A...) (Loc loc, A args) {
56 import std.stdio;
57 writeln(loc, ": ", args);
58 if (fn.pp !is null) fn.pp.printCaret(loc);
61 // collect var declarations (gml is not properly scoped)
62 visitNodes(fn.ebody, (Node n) {
63 if (auto vd = cast(NodeVarDecl)n) {
64 foreach (immutable idx, string name; vd.names) {
65 if (name in locals) {
66 if (vd.asGlobal) message(fn, vd.locs[idx], "conflicting variable '", name, "' declaration (previous at ", vdecls[name].toStringNoFile, ")");
67 } else if (name in globals) {
68 if (!vd.asGlobal) message(fn, vd.locs[idx], "conflicting variable '", name, "' declaration (previous at ", vdecls[name].toStringNoFile, ")");
70 vdecls[name] = vd.locs[idx];
71 if (vd.asGlobal) {
72 globals[name] = true;
73 } else {
74 locals[name] = true;
78 return VisitRes.Continue;
79 });
81 void findUninitialized () {
82 bool[string] inited;
83 bool[string] used;
85 void processExpr (Node n, bool asAss=false) {
86 if (n is null) return;
87 visitNodes(n, (Node nn) {
88 if (auto id = cast(NodeId)nn) {
89 if (argvar(id.name) < 0) {
90 if (!asAss && id.name in locals && id.name !in inited) {
91 message(fn, nn.loc, "using uninitialized variable; declared at ", vdecls[id.name].toStringNoFile);
94 inited[id.name] = true;
95 used[id.name] = true;
96 return VisitRes.SkipChildren;
98 if (auto n = cast(NodeFCall)nn) {
99 if (cast(NodeId)n.fe is null) message(fn, n.loc, "invalid function call");
100 if (n.args.length > 16) message(fn, n.loc, "too many arguments in function call");
101 foreach (immutable idx, Node a; n.args) {
102 // no assignments allowed there
103 processExpr(a);
105 return VisitRes.SkipChildren;
107 return VisitRes.Continue;
111 void processStatement (Node nn) {
112 if (nn is null) return;
113 return selectNode!void(nn,
114 (NodeVarDecl n) {},
115 (NodeBlock n) {
116 foreach (Node st; n.stats) {
117 if (cast(NodeStatementBreakCont)st !is null) break;
118 processStatement(st);
119 if (cast(NodeReturn)st !is null) break;
122 (NodeStatementEmpty n) {},
123 (NodeStatementAss n) {
124 if (cast(NodeId)n.el is null && cast(NodeDot)n.el is null && cast(NodeIndex)n.el is null) {
125 message(fn, nn.loc, "assignment to rvalue");
126 return;
128 processExpr(n.er); // it is calculated first
129 if (auto did = cast(NodeId)n.el) {
130 inited[did.name] = true;
131 used[did.name] = true;
132 } else {
133 processExpr(n.el, asAss:true);
136 (NodeStatementExpr n) { processExpr(n.e); },
137 (NodeReturn n) { processExpr(n.e); },
138 (NodeWith n) {
139 processExpr(n.e); // can't contain assignments
140 // body can be executed zero times, so...
141 auto before = inited.dup;
142 processStatement(n.ebody);
143 inited = before;
145 (NodeIf n) {
146 processExpr(n.ec);
147 auto before = inited.dup;
148 processStatement(n.et);
149 auto tset = inited.dup;
150 inited = before.dup;
151 processStatement(n.ef);
152 // now copy to `before` all items that are set both in `tset` and in `inited`
153 foreach (string name; inited.byKey) {
154 if (name in tset) before[name] = true;
156 inited = before;
158 (NodeStatementBreakCont n) {},
159 (NodeFor n) {
160 processStatement(n.einit);
161 // "next" and "cond" can't contain assignments, so it's safe here
162 processExpr(n.econd);
163 processStatement(n.enext);
164 // yet body can be executed zero times, so...
165 auto before = inited.dup;
166 processStatement(n.ebody);
167 inited = before;
169 (NodeWhile n) {
170 // "cond" can't contain assignments, so it's safe here
171 processExpr(n.econd);
172 // yet body can be executed zero times, so...
173 auto before = inited.dup;
174 processStatement(n.ebody);
175 inited = before;
177 (NodeDoUntil n) {
178 // "cond" can't contain assignments, so it's safe here
179 processExpr(n.econd);
180 // body is guaranteed to execute at least one time
181 processStatement(n.ebody);
183 (NodeRepeat n) {
184 // "count" can't contain assignments, so it's safe here
185 processExpr(n.ecount);
186 // yet body can be executed zero times, so...
187 auto before = inited.dup;
188 processStatement(n.ebody);
189 inited = before;
191 (NodeSwitch n) {
192 // "expr" can't contain assignments, so it's safe here
193 processExpr(n.e);
194 auto before = inited.dup;
195 foreach (ref ci; n.cases) {
196 processExpr(ci.e); // can't contain assignments
197 // and this one can
198 if (ci.st !is null) {
199 inited = before.dup;
200 processStatement(ci.st);
203 inited = before;
205 () { assert(0, "unimplemented node: "~typeid(nn).name); },
209 processStatement(fn.ebody);
211 // now show unused locals
212 static struct Info { Loc loc; string name; }
213 Info[] unusedLocs;
214 foreach (string name; locals.keys) {
215 if (name !in used) {
216 //message(fn, vdecls[name], "unused local '", name, "'");
217 unusedLocs ~= Info(vdecls[name], name);
218 locals.remove(name);
221 import std.algorithm : sort;
222 unusedLocs.sort!((ref a, ref b) { if (a.loc.line < b.loc.line) return true; if (a.loc.line > b.loc.line) return false; return (a.loc.col < b.loc.col); });
223 foreach (ref nfo; unusedLocs) message(fn, nfo.loc, "unused local '", nfo.name, "'");
226 findUninitialized();