1 //          Copyright Brian Schott (Hackerpilot) 2014-2015.
2 // Distributed under the Boost Software License, Version 1.0.
3 //    (See accompanying file LICENSE_1_0.txt or copy at
4 //          http://www.boost.org/LICENSE_1_0.txt)
5 module analysis.unused;
6 
7 import dparse.ast;
8 import dparse.lexer;
9 import analysis.base;
10 import std.container;
11 import std.regex : Regex, regex, matchAll;
12 import dsymbol.scope_ : Scope;
13 
14 /**
15  * Checks for unused variables.
16  */
17 class UnusedVariableCheck : BaseAnalyzer
18 {
19 	alias visit = BaseAnalyzer.visit;
20 
21 	/**
22 	 * Params:
23 	 *     fileName = the name of the file being analyzed
24 	 */
25 	this(string fileName, const(Scope)* sc)
26 	{
27 		super(fileName, sc);
28 		re = regex("[\\p{Alphabetic}_][\\w_]*");
29 	}
30 
31 	override void visit(const Module mod)
32 	{
33 		pushScope();
34 		mod.accept(this);
35 		popScope();
36 	}
37 
38 	override void visit(const Declaration declaration)
39 	{
40 		if (!isOverride)
41 			foreach (attribute; declaration.attributes)
42 				isOverride = isOverride || (attribute.attribute == tok!"override");
43 		declaration.accept(this);
44 		isOverride = false;
45 	}
46 
47 	override void visit(const FunctionDeclaration functionDec)
48 	{
49 		pushScope();
50 		if (functionDec.functionBody !is null)
51 		{
52 			immutable bool ias = inAggregateScope;
53 			inAggregateScope = false;
54 			if (!isOverride)
55 				functionDec.parameters.accept(this);
56 			functionDec.functionBody.accept(this);
57 			inAggregateScope = ias;
58 		}
59 		popScope();
60 	}
61 
62 	mixin PartsUseVariables!AliasInitializer;
63 	mixin PartsUseVariables!ArgumentList;
64 	mixin PartsUseVariables!AssertExpression;
65 	mixin PartsUseVariables!ClassDeclaration;
66 	mixin PartsUseVariables!FunctionBody;
67 	mixin PartsUseVariables!FunctionCallExpression;
68 	mixin PartsUseVariables!FunctionDeclaration;
69 	mixin PartsUseVariables!IndexExpression;
70 	mixin PartsUseVariables!Initializer;
71 	mixin PartsUseVariables!InterfaceDeclaration;
72 	mixin PartsUseVariables!NewExpression;
73 	mixin PartsUseVariables!StaticIfCondition;
74 	mixin PartsUseVariables!StructDeclaration;
75 	mixin PartsUseVariables!TemplateArgumentList;
76 	mixin PartsUseVariables!ThrowStatement;
77 	mixin PartsUseVariables!CastExpression;
78 
79 	override void visit(const SwitchStatement switchStatement)
80 	{
81 		if (switchStatement.expression !is null)
82 		{
83 			interestDepth++;
84 			switchStatement.expression.accept(this);
85 			interestDepth--;
86 		}
87 		switchStatement.accept(this);
88 	}
89 
90 	override void visit(const WhileStatement whileStatement)
91 	{
92 		interestDepth++;
93 		whileStatement.expression.accept(this);
94 		interestDepth--;
95 		whileStatement.declarationOrStatement.accept(this);
96 	}
97 
98 	override void visit(const DoStatement doStatement)
99 	{
100 		interestDepth++;
101 		doStatement.expression.accept(this);
102 		interestDepth--;
103 		doStatement.statementNoCaseNoDefault.accept(this);
104 	}
105 
106 	override void visit(const ForStatement forStatement)
107 	{
108 		if (forStatement.initialization !is null)
109 			forStatement.initialization.accept(this);
110 		if (forStatement.test !is null)
111 		{
112 			interestDepth++;
113 			forStatement.test.accept(this);
114 			interestDepth--;
115 		}
116 		if (forStatement.increment !is null)
117 		{
118 			interestDepth++;
119 			forStatement.increment.accept(this);
120 			interestDepth--;
121 		}
122 		forStatement.declarationOrStatement.accept(this);
123 	}
124 
125 	override void visit(const IfStatement ifStatement)
126 	{
127 		if (ifStatement.expression !is null)
128 		{
129 			interestDepth++;
130 			ifStatement.expression.accept(this);
131 			interestDepth--;
132 		}
133 		ifStatement.thenStatement.accept(this);
134 		if (ifStatement.elseStatement !is null)
135 			ifStatement.elseStatement.accept(this);
136 	}
137 
138 	override void visit(const ForeachStatement foreachStatement)
139 	{
140 		if (foreachStatement.low !is null)
141 		{
142 			interestDepth++;
143 			foreachStatement.low.accept(this);
144 			interestDepth--;
145 		}
146 		if (foreachStatement.high !is null)
147 		{
148 			interestDepth++;
149 			foreachStatement.high.accept(this);
150 			interestDepth--;
151 		}
152 		foreachStatement.accept(this);
153 	}
154 
155 	override void visit(const AssignExpression assignExp)
156 	{
157 		assignExp.ternaryExpression.accept(this);
158 		if (assignExp.expression !is null)
159 		{
160 			interestDepth++;
161 			assignExp.expression.accept(this);
162 			interestDepth--;
163 		}
164 	}
165 
166 	override void visit(const TemplateDeclaration templateDeclaration)
167 	{
168 		immutable inAgg = inAggregateScope;
169 		inAggregateScope = true;
170 		templateDeclaration.accept(this);
171 		inAggregateScope = inAgg;
172 	}
173 
174 	override void visit(const IdentifierOrTemplateChain chain)
175 	{
176 		if (interestDepth > 0 && chain.identifiersOrTemplateInstances[0].identifier != tok!"")
177 			variableUsed(chain.identifiersOrTemplateInstances[0].identifier.text);
178 		chain.accept(this);
179 	}
180 
181 	override void visit(const TemplateSingleArgument single)
182 	{
183 		if (single.token != tok!"")
184 			variableUsed(single.token.text);
185 	}
186 
187 	override void visit(const UnaryExpression unary)
188 	{
189 		if (unary.prefix == tok!"*")
190 			interestDepth++;
191 		unary.accept(this);
192 		if (unary.prefix == tok!"*")
193 			interestDepth--;
194 	}
195 
196 	override void visit(const MixinExpression mix)
197 	{
198 		interestDepth++;
199 		mixinDepth++;
200 		mix.accept(this);
201 		mixinDepth--;
202 		interestDepth--;
203 	}
204 
205 	override void visit(const PrimaryExpression primary)
206 	{
207 		if (interestDepth > 0)
208 		{
209 			if (primary.identifierOrTemplateInstance !is null
210 					&& primary.identifierOrTemplateInstance.identifier != tok!"")
211 			{
212 				variableUsed(primary.identifierOrTemplateInstance.identifier.text);
213 			}
214 			if (mixinDepth > 0 && primary.primary == tok!"stringLiteral"
215 					|| primary.primary == tok!"wstringLiteral"
216 					|| primary.primary == tok!"dstringLiteral")
217 			{
218 				foreach (part; matchAll(primary.primary.text, re))
219 				{
220 					immutable size_t treeIndex = tree.length - 1;
221 					auto uu = UnUsed(part.hit);
222 					auto r = tree[treeIndex].equalRange(&uu);
223 					if (!r.empty)
224 						r.front.uncertain = true;
225 				}
226 			}
227 		}
228 		primary.accept(this);
229 	}
230 
231 	override void visit(const ReturnStatement retStatement)
232 	{
233 		if (retStatement.expression !is null)
234 		{
235 			interestDepth++;
236 			visit(retStatement.expression);
237 			interestDepth--;
238 		}
239 	}
240 
241 	override void visit(const BlockStatement blockStatement)
242 	{
243 		immutable bool sb = inAggregateScope;
244 		inAggregateScope = false;
245 		if (blockStatementIntroducesScope)
246 			pushScope();
247 		blockStatement.accept(this);
248 		if (blockStatementIntroducesScope)
249 			popScope();
250 		inAggregateScope = sb;
251 	}
252 
253 	override void visit(const VariableDeclaration variableDeclaration)
254 	{
255 		foreach (d; variableDeclaration.declarators)
256 			this.variableDeclared(d.name.text, d.name.line, d.name.column, false, false);
257 		variableDeclaration.accept(this);
258 	}
259 
260 	override void visit(const AutoDeclaration autoDeclaration)
261 	{
262 		foreach (t; autoDeclaration.identifiers)
263 			this.variableDeclared(t.text, t.line, t.column, false, false);
264 		autoDeclaration.accept(this);
265 	}
266 
267 	override void visit(const WithStatement withStatetement)
268 	{
269 		interestDepth++;
270 		withStatetement.expression.accept(this);
271 		interestDepth--;
272 		withStatetement.statementNoCaseNoDefault.accept(this);
273 	}
274 
275 	override void visit(const Parameter parameter)
276 	{
277 		import std.algorithm : canFind;
278 		import std.array : array;
279 
280 		if (parameter.name != tok!"")
281 		{
282 			immutable bool isRef = canFind(parameter.parameterAttributes, cast(IdType) tok!"ref")
283 				|| canFind(parameter.parameterAttributes,
284 						cast(IdType) tok!"in") || canFind(parameter.parameterAttributes,
285 						cast(IdType) tok!"out");
286 			variableDeclared(parameter.name.text, parameter.name.line,
287 					parameter.name.column, true, isRef);
288 			if (parameter.default_ !is null)
289 			{
290 				interestDepth++;
291 				parameter.default_.accept(this);
292 				interestDepth--;
293 			}
294 		}
295 	}
296 
297 	override void visit(const StructBody structBody)
298 	{
299 		immutable bool sb = inAggregateScope;
300 		inAggregateScope = true;
301 		foreach (dec; structBody.declarations)
302 			visit(dec);
303 		inAggregateScope = sb;
304 	}
305 
306 	override void visit(const ConditionalStatement conditionalStatement)
307 	{
308 		immutable bool cs = blockStatementIntroducesScope;
309 		blockStatementIntroducesScope = false;
310 		conditionalStatement.accept(this);
311 		blockStatementIntroducesScope = cs;
312 	}
313 
314 	override void visit(const AsmPrimaryExp primary)
315 	{
316 		if (primary.token != tok!"")
317 			variableUsed(primary.token.text);
318 		if (primary.identifierChain !is null)
319 			variableUsed(primary.identifierChain.identifiers[0].text);
320 	}
321 
322 	override void visit(const TraitsExpression)
323 	{
324 		// issue #266: Ignore unused variables inside of `__traits` expressions
325 	}
326 
327 	override void visit(const TypeofExpression)
328 	{
329 		// issue #270: Ignore unused variables inside of `typeof` expressions
330 	}
331 
332 private:
333 
334 	mixin template PartsUseVariables(NodeType)
335 	{
336 		override void visit(const NodeType node)
337 		{
338 			interestDepth++;
339 			node.accept(this);
340 			interestDepth--;
341 		}
342 	}
343 
344 	void variableDeclared(string name, size_t line, size_t column, bool isParameter, bool isRef)
345 	{
346 		if (inAggregateScope)
347 			return;
348 		tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef));
349 	}
350 
351 	void variableUsed(string name)
352 	{
353 		size_t treeIndex = tree.length - 1;
354 		auto uu = UnUsed(name);
355 		while (true)
356 		{
357 			if (tree[treeIndex].removeKey(&uu) != 0 || treeIndex == 0)
358 				break;
359 			treeIndex--;
360 		}
361 	}
362 
363 	void popScope()
364 	{
365 		foreach (uu; tree[$ - 1])
366 		{
367 			if (!uu.isRef && tree.length > 1)
368 			{
369 				immutable string certainty = uu.uncertain ? " might not be used."
370 					: " is never used.";
371 				immutable string errorMessage = (uu.isParameter ? "Parameter " : "Variable ")
372 					~ uu.name ~ certainty;
373 				addErrorMessage(uu.line, uu.column, uu.isParameter ? "dscanner.suspicious.unused_parameter"
374 						: "dscanner.suspicious.unused_variable", errorMessage);
375 			}
376 		}
377 		tree = tree[0 .. $ - 1];
378 	}
379 
380 	void pushScope()
381 	{
382 		tree ~= new RedBlackTree!(UnUsed*, "a.name < b.name");
383 	}
384 
385 	struct UnUsed
386 	{
387 		string name;
388 		size_t line;
389 		size_t column;
390 		bool isParameter;
391 		bool isRef;
392 		bool uncertain;
393 	}
394 
395 	RedBlackTree!(UnUsed*, "a.name < b.name")[] tree;
396 
397 	uint interestDepth;
398 
399 	uint mixinDepth;
400 
401 	bool isOverride;
402 
403 	bool inAggregateScope;
404 
405 	bool blockStatementIntroducesScope = true;
406 
407 	Regex!char re;
408 }
409 
410 unittest
411 {
412 	import std.stdio : stderr;
413 	import analysis.config : StaticAnalysisConfig;
414 	import analysis.helpers : assertAnalyzerWarnings;
415 
416 	StaticAnalysisConfig sac;
417 	sac.unused_variable_check = true;
418 	assertAnalyzerWarnings(q{
419 
420 	// Issue 274
421 	unittest
422 	{
423 		size_t byteIndex = 0;
424 		*(cast(FieldType*)(retVal.ptr + byteIndex)) = item;
425 	}
426 
427 	unittest
428 	{
429 		int a; // [warn]: Variable a is never used.
430 	}
431 
432 	void doStuff(int a, int b) // [warn]: Parameter b is never used.
433 	{
434 		return a;
435 	}
436 
437 	}}, sac);
438 	stderr.writeln("Unittest for UnusedVariableCheck passed.");
439 }