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