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 final 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 && functionDec.functionBody.specifiedFunctionBody)
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 		interestDepth++;
170 		assignExp.accept(this);
171 		interestDepth--;
172 	}
173 
174 	override void visit(const TemplateDeclaration templateDeclaration)
175 	{
176 		immutable inAgg = inAggregateScope;
177 		inAggregateScope = true;
178 		templateDeclaration.accept(this);
179 		inAggregateScope = inAgg;
180 	}
181 
182 	override void visit(const IdentifierOrTemplateChain chain)
183 	{
184 		if (interestDepth > 0 && chain.identifiersOrTemplateInstances[0].identifier != tok!"")
185 			variableUsed(chain.identifiersOrTemplateInstances[0].identifier.text);
186 		chain.accept(this);
187 	}
188 
189 	override void visit(const TemplateSingleArgument single)
190 	{
191 		if (single.token != tok!"")
192 			variableUsed(single.token.text);
193 	}
194 
195 	override void visit(const UnaryExpression unary)
196 	{
197 		const bool interesting = unary.prefix == tok!"*" || unary.unaryExpression !is null;
198 		interestDepth += interesting;
199 		unary.accept(this);
200 		interestDepth -= interesting;
201 	}
202 
203 	override void visit(const MixinExpression mix)
204 	{
205 		interestDepth++;
206 		mixinDepth++;
207 		mix.accept(this);
208 		mixinDepth--;
209 		interestDepth--;
210 	}
211 
212 	override void visit(const PrimaryExpression primary)
213 	{
214 		if (interestDepth > 0)
215 		{
216 			const IdentifierOrTemplateInstance idt = primary.identifierOrTemplateInstance;
217 
218 			if (idt !is null)
219 			{
220 				if (idt.identifier != tok!"")
221 					variableUsed(idt.identifier.text);
222 				else if (idt.templateInstance && idt.templateInstance.identifier != tok!"")
223 					variableUsed(idt.templateInstance.identifier.text);
224 			}
225 			if (mixinDepth > 0 && primary.primary == tok!"stringLiteral"
226 					|| primary.primary == tok!"wstringLiteral"
227 					|| primary.primary == tok!"dstringLiteral")
228 			{
229 				foreach (part; matchAll(primary.primary.text, re))
230 				{
231 					void checkTree(in size_t treeIndex)
232 					{
233 						auto uu = UnUsed(part.hit);
234 						auto r = tree[treeIndex].equalRange(&uu);
235 						if (!r.empty)
236 							r.front.uncertain = true;
237 					}
238 					checkTree(tree.length - 1);
239 					if (tree.length >= 2)
240 						checkTree(tree.length - 2);
241 				}
242 			}
243 		}
244 		primary.accept(this);
245 	}
246 
247 	override void visit(const ReturnStatement retStatement)
248 	{
249 		if (retStatement.expression !is null)
250 		{
251 			interestDepth++;
252 			visit(retStatement.expression);
253 			interestDepth--;
254 		}
255 	}
256 
257 	override void visit(const BlockStatement blockStatement)
258 	{
259 		immutable bool sb = inAggregateScope;
260 		inAggregateScope = false;
261 		if (blockStatementIntroducesScope)
262 			pushScope();
263 		blockStatement.accept(this);
264 		if (blockStatementIntroducesScope)
265 			popScope();
266 		inAggregateScope = sb;
267 	}
268 
269 	override void visit(const VariableDeclaration variableDeclaration)
270 	{
271 		foreach (d; variableDeclaration.declarators)
272 			this.variableDeclared(d.name.text, d.name.line, d.name.column, false, false);
273 		variableDeclaration.accept(this);
274 	}
275 
276 	override void visit(const Type2 tp)
277 	{
278 		if (tp.typeIdentifierPart &&
279 			tp.typeIdentifierPart.identifierOrTemplateInstance)
280 		{
281 			const IdentifierOrTemplateInstance idt = tp.typeIdentifierPart.identifierOrTemplateInstance;
282 			if (idt.identifier != tok!"")
283 				variableUsed(idt.identifier.text);
284 			else if (idt.templateInstance)
285 			{
286 				const TemplateInstance ti = idt.templateInstance;
287 				if (ti.identifier != tok!"")
288 					variableUsed(idt.templateInstance.identifier.text);
289 				if (ti.templateArguments && ti.templateArguments.templateSingleArgument)
290 					variableUsed(ti.templateArguments.templateSingleArgument.token.text);
291 			}
292 		}
293 		tp.accept(this);
294 	}
295 
296 	override void visit(const AutoDeclaration autoDeclaration)
297 	{
298 		foreach (t; autoDeclaration.parts.map!(a => a.identifier))
299 			this.variableDeclared(t.text, t.line, t.column, false, false);
300 		autoDeclaration.accept(this);
301 	}
302 
303 	override void visit(const WithStatement withStatetement)
304 	{
305 		interestDepth++;
306 		if (withStatetement.expression)
307 			withStatetement.expression.accept(this);
308 		interestDepth--;
309 		if (withStatetement.declarationOrStatement)
310 			withStatetement.declarationOrStatement.accept(this);
311 	}
312 
313 	override void visit(const Parameter parameter)
314 	{
315 		import std.algorithm : among;
316 		import std.algorithm.iteration : filter;
317 		import std.range : empty;
318 		import std.array : array;
319 
320 		if (parameter.name != tok!"")
321 		{
322 			immutable bool isRef = !parameter.parameterAttributes
323 				.filter!(a => a.idType.among(tok!"ref", tok!"out")).empty;
324 			immutable bool isPtr = parameter.type && !parameter.type
325 				.typeSuffixes.filter!(a => a.star != tok!"").empty;
326 
327 			variableDeclared(parameter.name.text, parameter.name.line,
328 					parameter.name.column, true, isRef | isPtr);
329 
330 			if (parameter.default_ !is null)
331 			{
332 				interestDepth++;
333 				parameter.default_.accept(this);
334 				interestDepth--;
335 			}
336 		}
337 	}
338 
339 	override void visit(const StructBody structBody)
340 	{
341 		immutable bool sb = inAggregateScope;
342 		inAggregateScope = true;
343 		foreach (dec; structBody.declarations)
344 			visit(dec);
345 		inAggregateScope = sb;
346 	}
347 
348 	override void visit(const ConditionalStatement conditionalStatement)
349 	{
350 		immutable bool cs = blockStatementIntroducesScope;
351 		blockStatementIntroducesScope = false;
352 		conditionalStatement.accept(this);
353 		blockStatementIntroducesScope = cs;
354 	}
355 
356 	override void visit(const AsmPrimaryExp primary)
357 	{
358 		if (primary.token != tok!"")
359 			variableUsed(primary.token.text);
360 		if (primary.identifierChain !is null)
361 			variableUsed(primary.identifierChain.identifiers[0].text);
362 	}
363 
364 	override void visit(const TraitsExpression)
365 	{
366 		// issue #266: Ignore unused variables inside of `__traits` expressions
367 	}
368 
369 	override void visit(const TypeofExpression)
370 	{
371 		// issue #270: Ignore unused variables inside of `typeof` expressions
372 	}
373 
374 private:
375 
376 	mixin template PartsUseVariables(NodeType)
377 	{
378 		override void visit(const NodeType node)
379 		{
380 			interestDepth++;
381 			node.accept(this);
382 			interestDepth--;
383 		}
384 	}
385 
386 	void variableDeclared(string name, size_t line, size_t column, bool isParameter, bool isRef)
387 	{
388 		if (inAggregateScope || name.all!(a => a == '_'))
389 			return;
390 		tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef));
391 	}
392 
393 	void variableUsed(string name)
394 	{
395 		size_t treeIndex = tree.length - 1;
396 		auto uu = UnUsed(name);
397 		while (true)
398 		{
399 			if (tree[treeIndex].removeKey(&uu) != 0 || treeIndex == 0)
400 				break;
401 			treeIndex--;
402 		}
403 	}
404 
405 	void popScope()
406 	{
407 		foreach (uu; tree[$ - 1])
408 		{
409 			if (!uu.isRef && tree.length > 1)
410 			{
411 			    if (uu.uncertain)
412 			        continue;
413 				immutable string certainty = uu.uncertain ? " might not be used."
414 					: " is never used.";
415 				immutable string errorMessage = (uu.isParameter ? "Parameter " : "Variable ")
416 					~ uu.name ~ certainty;
417 				addErrorMessage(uu.line, uu.column, uu.isParameter ? "dscanner.suspicious.unused_parameter"
418 						: "dscanner.suspicious.unused_variable", errorMessage);
419 			}
420 		}
421 		tree = tree[0 .. $ - 1];
422 	}
423 
424 	void pushScope()
425 	{
426 		tree ~= new RedBlackTree!(UnUsed*, "a.name < b.name");
427 	}
428 
429 	struct UnUsed
430 	{
431 		string name;
432 		size_t line;
433 		size_t column;
434 		bool isParameter;
435 		bool isRef;
436 		bool uncertain;
437 	}
438 
439 	RedBlackTree!(UnUsed*, "a.name < b.name")[] tree;
440 
441 	uint interestDepth;
442 
443 	uint mixinDepth;
444 
445 	bool isOverride;
446 
447 	bool inAggregateScope;
448 
449 	bool blockStatementIntroducesScope = true;
450 
451 	Regex!char re;
452 }
453 
454 @system unittest
455 {
456 	import std.stdio : stderr;
457 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
458 	import dscanner.analysis.helpers : assertAnalyzerWarnings;
459 
460 	StaticAnalysisConfig sac = disabledConfig();
461 	sac.unused_variable_check = Check.enabled;
462 	assertAnalyzerWarnings(q{
463 
464 	// Issue 274
465 	unittest
466 	{
467 		size_t byteIndex = 0;
468 		*(cast(FieldType*)(retVal.ptr + byteIndex)) = item;
469 	}
470 
471 	// bug encountered after correct DIP 1009 impl in dparse
472 	version (StdDdoc)
473 	{
474 	    bool isAbsolute(R)(R path) pure nothrow @safe
475 	    if (isRandomAccessRange!R && isSomeChar!(ElementType!R) ||
476 	        is(StringTypeOf!R));
477 	}
478 
479 	unittest
480 	{
481 		int a; // [warn]: Variable a is never used.
482 	}
483 
484 	void inPSC(in int a){} // [warn]: Parameter a is never used.
485 
486 	// Issue 380
487 	int templatedEnum()
488 	{
489 		enum a(T) = T.init;
490 		return a!int;
491 	}
492 
493 	// Issue 380
494 	int otherTemplatedEnum()
495 	{
496 		auto a(T) = T.init; // [warn]: Variable a is never used.
497 		return 0;
498 	}
499 
500 	void doStuff(int a, int b) // [warn]: Parameter b is never used.
501 	{
502 		return a;
503 	}
504 
505 	// Issue 364
506 	void test364_1()
507 	{
508 		enum s = 8;
509 		immutable t = 2;
510 		int[s][t] a;
511 		a[0][0] = 1;
512 	}
513 
514 	void test364_2()
515 	{
516 		enum s = 8;
517 		alias a = e!s;
518 		a = 1;
519 	}
520 
521 	// Issue 352
522 	void test352_1()
523 	{
524 		void f(int *x) {*x = 1;}
525 	}
526 
527 	void test352_2()
528 	{
529 		void f(Bat** bat) {*bat = bats.ptr + 8;}
530 	}
531 
532 	// Issue 490
533 	void test490()
534 	{
535 		auto cb1 = delegate(size_t _) {};
536 		cb1(3);
537 		auto cb2 = delegate(size_t a) {}; // [warn]: Parameter a is never used.
538 		cb2(3);
539 	}
540 
541 	void oops ()
542 	{
543 	    class Identity { int val; }
544 	    Identity v;
545 	    v.val = 0;
546 	}
547 	
548 	bool hasDittos(int decl)
549 	{
550 		mixin("decl++;");
551 	}
552 
553 	void main()
554 	{
555 	    const int testValue;
556 	    testValue.writeln;
557 	}
558 
559 	}}, sac);
560 	stderr.writeln("Unittest for UnusedVariableCheck passed.");
561 }
562