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