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 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 					immutable size_t treeIndex = tree.length - 1;
238 					auto uu = UnUsed(part.hit);
239 					auto r = tree[treeIndex].equalRange(&uu);
240 					if (!r.empty)
241 						r.front.uncertain = true;
242 				}
243 			}
244 		}
245 		primary.accept(this);
246 	}
247 
248 	override void visit(const ReturnStatement retStatement)
249 	{
250 		if (retStatement.expression !is null)
251 		{
252 			interestDepth++;
253 			visit(retStatement.expression);
254 			interestDepth--;
255 		}
256 	}
257 
258 	override void visit(const BlockStatement blockStatement)
259 	{
260 		immutable bool sb = inAggregateScope;
261 		inAggregateScope = false;
262 		if (blockStatementIntroducesScope)
263 			pushScope();
264 		blockStatement.accept(this);
265 		if (blockStatementIntroducesScope)
266 			popScope();
267 		inAggregateScope = sb;
268 	}
269 
270 	override void visit(const VariableDeclaration variableDeclaration)
271 	{
272 		foreach (d; variableDeclaration.declarators)
273 			this.variableDeclared(d.name.text, d.name.line, d.name.column, false, false);
274 		variableDeclaration.accept(this);
275 	}
276 
277 	override void visit(const Type2 tp)
278 	{
279 		if (tp.symbol && tp.symbol.identifierOrTemplateChain &&
280 			tp.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances)
281 		{
282 			const IdentifierOrTemplateInstance idt = tp.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances[0];
283 			if (idt.identifier != tok!"")
284 				variableUsed(idt.identifier.text);
285 			else if (idt.templateInstance)
286 			{
287 				const TemplateInstance ti = idt.templateInstance;
288 				if (ti.identifier != tok!"")
289 					variableUsed(idt.templateInstance.identifier.text);
290 				if (ti.templateArguments && ti.templateArguments.templateSingleArgument)
291 					variableUsed(ti.templateArguments.templateSingleArgument.token.text);
292 			}
293 		}
294 		tp.accept(this);
295 	}
296 
297 	override void visit(const AutoDeclaration autoDeclaration)
298 	{
299 		foreach (t; autoDeclaration.parts.map!(a => a.identifier))
300 			this.variableDeclared(t.text, t.line, t.column, false, false);
301 		autoDeclaration.accept(this);
302 	}
303 
304 	override void visit(const WithStatement withStatetement)
305 	{
306 		interestDepth++;
307 		withStatetement.expression.accept(this);
308 		interestDepth--;
309 		withStatetement.statementNoCaseNoDefault.accept(this);
310 	}
311 
312 	override void visit(const Parameter parameter)
313 	{
314 		import std.algorithm : among;
315 		import std.algorithm.iteration : filter;
316 		import std.range : empty;
317 		import std.array : array;
318 
319 		if (parameter.name != tok!"")
320 		{
321 			immutable bool isRef = !parameter.parameterAttributes
322 				.filter!(a => a.among(tok!"ref", tok!"out")).empty;
323 			immutable bool isPtr = parameter.type && !parameter.type
324 				.typeSuffixes.filter!(a => a.star != tok!"").empty;
325 
326 			variableDeclared(parameter.name.text, parameter.name.line,
327 					parameter.name.column, true, isRef | isPtr);
328 
329 			if (parameter.default_ !is null)
330 			{
331 				interestDepth++;
332 				parameter.default_.accept(this);
333 				interestDepth--;
334 			}
335 		}
336 	}
337 
338 	override void visit(const StructBody structBody)
339 	{
340 		immutable bool sb = inAggregateScope;
341 		inAggregateScope = true;
342 		foreach (dec; structBody.declarations)
343 			visit(dec);
344 		inAggregateScope = sb;
345 	}
346 
347 	override void visit(const ConditionalStatement conditionalStatement)
348 	{
349 		immutable bool cs = blockStatementIntroducesScope;
350 		blockStatementIntroducesScope = false;
351 		conditionalStatement.accept(this);
352 		blockStatementIntroducesScope = cs;
353 	}
354 
355 	override void visit(const AsmPrimaryExp primary)
356 	{
357 		if (primary.token != tok!"")
358 			variableUsed(primary.token.text);
359 		if (primary.identifierChain !is null)
360 			variableUsed(primary.identifierChain.identifiers[0].text);
361 	}
362 
363 	override void visit(const TraitsExpression)
364 	{
365 		// issue #266: Ignore unused variables inside of `__traits` expressions
366 	}
367 
368 	override void visit(const TypeofExpression)
369 	{
370 		// issue #270: Ignore unused variables inside of `typeof` expressions
371 	}
372 
373 private:
374 
375 	mixin template PartsUseVariables(NodeType)
376 	{
377 		override void visit(const NodeType node)
378 		{
379 			interestDepth++;
380 			node.accept(this);
381 			interestDepth--;
382 		}
383 	}
384 
385 	void variableDeclared(string name, size_t line, size_t column, bool isParameter, bool isRef)
386 	{
387 		if (inAggregateScope || name.all!(a => a == '_'))
388 			return;
389 		tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef));
390 	}
391 
392 	void variableUsed(string name)
393 	{
394 		size_t treeIndex = tree.length - 1;
395 		auto uu = UnUsed(name);
396 		while (true)
397 		{
398 			if (tree[treeIndex].removeKey(&uu) != 0 || treeIndex == 0)
399 				break;
400 			treeIndex--;
401 		}
402 	}
403 
404 	void popScope()
405 	{
406 		foreach (uu; tree[$ - 1])
407 		{
408 			if (!uu.isRef && tree.length > 1)
409 			{
410 				immutable string certainty = uu.uncertain ? " might not be used."
411 					: " is never used.";
412 				immutable string errorMessage = (uu.isParameter ? "Parameter " : "Variable ")
413 					~ uu.name ~ certainty;
414 				addErrorMessage(uu.line, uu.column, uu.isParameter ? "dscanner.suspicious.unused_parameter"
415 						: "dscanner.suspicious.unused_variable", errorMessage);
416 			}
417 		}
418 		tree = tree[0 .. $ - 1];
419 	}
420 
421 	void pushScope()
422 	{
423 		tree ~= new RedBlackTree!(UnUsed*, "a.name < b.name");
424 	}
425 
426 	struct UnUsed
427 	{
428 		string name;
429 		size_t line;
430 		size_t column;
431 		bool isParameter;
432 		bool isRef;
433 		bool uncertain;
434 	}
435 
436 	RedBlackTree!(UnUsed*, "a.name < b.name")[] tree;
437 
438 	uint interestDepth;
439 
440 	uint mixinDepth;
441 
442 	bool isOverride;
443 
444 	bool inAggregateScope;
445 
446 	bool blockStatementIntroducesScope = true;
447 
448 	Regex!char re;
449 }
450 
451 @system unittest
452 {
453 	import std.stdio : stderr;
454 	import analysis.config : StaticAnalysisConfig, Check, disabledConfig;
455 	import analysis.helpers : assertAnalyzerWarnings;
456 
457 	StaticAnalysisConfig sac = disabledConfig();
458 	sac.unused_variable_check = Check.enabled;
459 	assertAnalyzerWarnings(q{
460 
461 	// Issue 274
462 	unittest
463 	{
464 		size_t byteIndex = 0;
465 		*(cast(FieldType*)(retVal.ptr + byteIndex)) = item;
466 	}
467 
468 	unittest
469 	{
470 		int a; // [warn]: Variable a is never used.
471 	}
472 
473 	void inPSC(in int a){} // [warn]: Parameter a is never used.
474 
475 	// Issue 380
476 	int templatedEnum()
477 	{
478 		enum a(T) = T.init;
479 		return a!int;
480 	}
481 
482 	// Issue 380
483 	int otherTemplatedEnum()
484 	{
485 		auto a(T) = T.init; // [warn]: Variable a is never used.
486 		return 0;
487 	}
488 
489 	void doStuff(int a, int b) // [warn]: Parameter b is never used.
490 	{
491 		return a;
492 	}
493 
494 	// Issue 364
495 	void test364_1()
496 	{
497 		enum s = 8;
498 		immutable t = 2;
499 		int[s][t] a;
500 		a[0][0] = 1;
501 	}
502 
503 	void test364_2()
504 	{
505 		enum s = 8;
506 		alias a = e!s;
507 		a = 1;
508 	}
509 
510 	// Issue 352
511 	void test352_1()
512 	{
513 		void f(int *x) {*x = 1;}
514 	}
515 
516 	void test352_2()
517 	{
518 		void f(Bat** bat) {*bat = bats.ptr + 8;}
519 	}
520 
521 	// Issue 490
522 	void test490()
523 	{
524 		auto cb1 = delegate(size_t _) {};
525 		cb1(3);
526 		auto cb2 = delegate(size_t a) {}; // [warn]: Parameter a is never used.
527 		cb2(3);
528 	}
529 
530 	}}, sac);
531 	stderr.writeln("Unittest for UnusedVariableCheck passed.");
532 }
533