switched to GPLv3 ONLY, because i don't trust FSF anymore
[gaemu.git] / gaem / anal / uninit.d
blob72162839af5ac67380d35e17d265212a419fad95
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, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module gaem.anal.uninit is aliced;
19 import gaem.parser;
20 import gaem.anal.utils;
23 // ////////////////////////////////////////////////////////////////////////// //
24 void analUninited (NodeFunc fn) {
25 bool[string] locals;
26 bool[string] globals;
27 Loc[string] vdecls; // for error messages
29 int argvar (string s) {
30 switch (s) {
31 case "argument0": return 0;
32 case "argument1": return 1;
33 case "argument2": return 2;
34 case "argument3": return 3;
35 case "argument4": return 4;
36 case "argument5": return 5;
37 case "argument6": return 6;
38 case "argument7": return 7;
39 case "argument8": return 8;
40 case "argument9": return 9;
41 case "argument10": return 10;
42 case "argument11": return 11;
43 case "argument12": return 12;
44 case "argument13": return 13;
45 case "argument14": return 14;
46 case "argument15": return 15;
47 case "self": return 16;
48 case "other": return 17;
49 default:
51 return -1;
54 void warning(A...) (Loc loc, A args) {
55 import std.stdio;
56 writeln(loc, ": ", args);
57 if (fn.pp !is null) fn.pp.printCaret(loc);
60 // collect var declarations (gml is not properly scoped)
61 visitNodes(fn.ebody, (Node n) {
62 if (auto vd = cast(NodeVarDecl)n) {
63 foreach (immutable idx, string name; vd.names) {
64 if (name in locals) {
65 if (vd.asGlobal) message(fn, vd.locs[idx], "conflicting variable '", name, "' declaration (previous at ", vdecls[name].toStringNoFile, ")");
66 } else if (name in globals) {
67 if (!vd.asGlobal) message(fn, vd.locs[idx], "conflicting variable '", name, "' declaration (previous at ", vdecls[name].toStringNoFile, ")");
69 vdecls[name] = vd.locs[idx];
70 if (vd.asGlobal) {
71 globals[name] = true;
72 } else {
73 locals[name] = true;
77 return VisitRes.Continue;
78 });
80 void findUninitialized () {
81 bool[string] inited;
82 bool[string] used;
84 void processExpr (Node n, bool asAss=false) {
85 if (n is null) return;
86 visitNodes(n, (Node nn) {
87 if (auto id = cast(NodeId)nn) {
88 if (argvar(id.name) < 0) {
89 if (!asAss && id.name in locals && id.name !in inited) {
90 message(fn, nn.loc, "using uninitialized variable; declared at ", vdecls[id.name].toStringNoFile);
93 inited[id.name] = true;
94 used[id.name] = true;
95 return VisitRes.SkipChildren;
97 if (auto n = cast(NodeFCall)nn) {
98 if (cast(NodeId)n.fe is null) message(fn, n.loc, "invalid function call");
99 if (n.args.length > 16) message(fn, n.loc, "too many arguments in function call");
100 foreach (immutable idx, Node a; n.args) {
101 // no assignments allowed there
102 processExpr(a);
104 return VisitRes.SkipChildren;
106 return VisitRes.Continue;
110 void processStatement (Node nn) {
111 if (nn is null) return;
112 return selectNode!void(nn,
113 (NodeVarDecl n) {},
114 (NodeBlock n) {
115 foreach (Node st; n.stats) {
116 if (cast(NodeStatementBreakCont)st !is null) break;
117 processStatement(st);
118 if (cast(NodeReturn)st !is null) break;
121 (NodeStatementEmpty n) {},
122 (NodeStatementAss n) {
123 if (cast(NodeId)n.el is null && cast(NodeDot)n.el is null && cast(NodeIndex)n.el is null) {
124 message(fn, nn.loc, "assignment to rvalue");
125 return;
127 processExpr(n.er); // it is calculated first
128 if (auto did = cast(NodeId)n.el) {
129 inited[did.name] = true;
130 used[did.name] = true;
131 } else {
132 processExpr(n.el, asAss:true);
135 (NodeStatementExpr n) { processExpr(n.e); },
136 (NodeReturn n) { processExpr(n.e); },
137 (NodeWith n) {
138 processExpr(n.e); // can't contain assignments
139 // body can be executed zero times, so...
140 auto before = inited.dup;
141 processStatement(n.ebody);
142 inited = before;
144 (NodeIf n) {
145 processExpr(n.ec);
146 auto before = inited.dup;
147 processStatement(n.et);
148 auto tset = inited.dup;
149 inited = before.dup;
150 processStatement(n.ef);
151 // now copy to `before` all items that are set both in `tset` and in `inited`
152 foreach (string name; inited.byKey) {
153 if (name in tset) before[name] = true;
155 inited = before;
157 (NodeStatementBreakCont n) {},
158 (NodeFor n) {
159 processStatement(n.einit);
160 // "next" and "cond" can't contain assignments, so it's safe here
161 processExpr(n.econd);
162 processStatement(n.enext);
163 // yet body can be executed zero times, so...
164 auto before = inited.dup;
165 processStatement(n.ebody);
166 inited = before;
168 (NodeWhile n) {
169 // "cond" can't contain assignments, so it's safe here
170 processExpr(n.econd);
171 // yet body can be executed zero times, so...
172 auto before = inited.dup;
173 processStatement(n.ebody);
174 inited = before;
176 (NodeDoUntil n) {
177 // "cond" can't contain assignments, so it's safe here
178 processExpr(n.econd);
179 // body is guaranteed to execute at least one time
180 processStatement(n.ebody);
182 (NodeRepeat n) {
183 // "count" can't contain assignments, so it's safe here
184 processExpr(n.ecount);
185 // yet body can be executed zero times, so...
186 auto before = inited.dup;
187 processStatement(n.ebody);
188 inited = before;
190 (NodeSwitch n) {
191 // "expr" can't contain assignments, so it's safe here
192 processExpr(n.e);
193 auto before = inited.dup;
194 foreach (ref ci; n.cases) {
195 processExpr(ci.e); // can't contain assignments
196 // and this one can
197 if (ci.st !is null) {
198 inited = before.dup;
199 processStatement(ci.st);
202 inited = before;
204 () { assert(0, "unimplemented node: "~typeid(nn).name); },
208 processStatement(fn.ebody);
210 // now show unused locals
211 static struct Info { Loc loc; string name; }
212 Info[] unusedLocs;
213 foreach (string name; locals.keys) {
214 if (name !in used) {
215 //message(fn, vdecls[name], "unused local '", name, "'");
216 unusedLocs ~= Info(vdecls[name], name);
217 locals.remove(name);
220 import std.algorithm : sort;
221 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); });
222 foreach (ref nfo; unusedLocs) message(fn, nfo.loc, "unused local '", nfo.name, "'");
225 findUninitialized();