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 AutoDeclaration autoDeclaration)
266 	{
267 		foreach (t; autoDeclaration.parts.map!(a => a.identifier))
268 			this.variableDeclared(t.text, t.line, t.column, false, false);
269 		autoDeclaration.accept(this);
270 	}
271 
272 	override void visit(const WithStatement withStatetement)
273 	{
274 		interestDepth++;
275 		withStatetement.expression.accept(this);
276 		interestDepth--;
277 		withStatetement.statementNoCaseNoDefault.accept(this);
278 	}
279 
280 	override void visit(const Parameter parameter)
281 	{
282 		import std.algorithm : canFind;
283 		import std.array : array;
284 
285 		if (parameter.name != tok!"")
286 		{
287 			immutable bool isRef = canFind(parameter.parameterAttributes, cast(IdType) tok!"ref")
288 				|| canFind(parameter.parameterAttributes,
289 						cast(IdType) tok!"in") || canFind(parameter.parameterAttributes,
290 						cast(IdType) tok!"out");
291 			variableDeclared(parameter.name.text, parameter.name.line,
292 					parameter.name.column, true, isRef);
293 			if (parameter.default_ !is null)
294 			{
295 				interestDepth++;
296 				parameter.default_.accept(this);
297 				interestDepth--;
298 			}
299 		}
300 	}
301 
302 	override void visit(const StructBody structBody)
303 	{
304 		immutable bool sb = inAggregateScope;
305 		inAggregateScope = true;
306 		foreach (dec; structBody.declarations)
307 			visit(dec);
308 		inAggregateScope = sb;
309 	}
310 
311 	override void visit(const ConditionalStatement conditionalStatement)
312 	{
313 		immutable bool cs = blockStatementIntroducesScope;
314 		blockStatementIntroducesScope = false;
315 		conditionalStatement.accept(this);
316 		blockStatementIntroducesScope = cs;
317 	}
318 
319 	override void visit(const AsmPrimaryExp primary)
320 	{
321 		if (primary.token != tok!"")
322 			variableUsed(primary.token.text);
323 		if (primary.identifierChain !is null)
324 			variableUsed(primary.identifierChain.identifiers[0].text);
325 	}
326 
327 	override void visit(const TraitsExpression)
328 	{
329 		// issue #266: Ignore unused variables inside of `__traits` expressions
330 	}
331 
332 	override void visit(const TypeofExpression)
333 	{
334 		// issue #270: Ignore unused variables inside of `typeof` expressions
335 	}
336 
337 private:
338 
339 	mixin template PartsUseVariables(NodeType)
340 	{
341 		override void visit(const NodeType node)
342 		{
343 			interestDepth++;
344 			node.accept(this);
345 			interestDepth--;
346 		}
347 	}
348 
349 	void variableDeclared(string name, size_t line, size_t column, bool isParameter, bool isRef)
350 	{
351 		if (inAggregateScope)
352 			return;
353 		tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef));
354 	}
355 
356 	void variableUsed(string name)
357 	{
358 		size_t treeIndex = tree.length - 1;
359 		auto uu = UnUsed(name);
360 		while (true)
361 		{
362 			if (tree[treeIndex].removeKey(&uu) != 0 || treeIndex == 0)
363 				break;
364 			treeIndex--;
365 		}
366 	}
367 
368 	void popScope()
369 	{
370 		foreach (uu; tree[$ - 1])
371 		{
372 			if (!uu.isRef && tree.length > 1)
373 			{
374 				immutable string certainty = uu.uncertain ? " might not be used."
375 					: " is never used.";
376 				immutable string errorMessage = (uu.isParameter ? "Parameter " : "Variable ")
377 					~ uu.name ~ certainty;
378 				addErrorMessage(uu.line, uu.column, uu.isParameter ? "dscanner.suspicious.unused_parameter"
379 						: "dscanner.suspicious.unused_variable", errorMessage);
380 			}
381 		}
382 		tree = tree[0 .. $ - 1];
383 	}
384 
385 	void pushScope()
386 	{
387 		tree ~= new RedBlackTree!(UnUsed*, "a.name < b.name");
388 	}
389 
390 	struct UnUsed
391 	{
392 		string name;
393 		size_t line;
394 		size_t column;
395 		bool isParameter;
396 		bool isRef;
397 		bool uncertain;
398 	}
399 
400 	RedBlackTree!(UnUsed*, "a.name < b.name")[] tree;
401 
402 	uint interestDepth;
403 
404 	uint mixinDepth;
405 
406 	bool isOverride;
407 
408 	bool inAggregateScope;
409 
410 	bool blockStatementIntroducesScope = true;
411 
412 	Regex!char re;
413 }
414 
415 unittest
416 {
417 	import std.stdio : stderr;
418 	import analysis.config : StaticAnalysisConfig, Check;
419 	import analysis.helpers : assertAnalyzerWarnings;
420 
421 	StaticAnalysisConfig sac;
422 	sac.unused_variable_check = Check.enabled;
423 	assertAnalyzerWarnings(q{
424 
425 	// Issue 274
426 	unittest
427 	{
428 		size_t byteIndex = 0;
429 		*(cast(FieldType*)(retVal.ptr + byteIndex)) = item;
430 	}
431 
432 	unittest
433 	{
434 		int a; // [warn]: Variable a is never used.
435 	}
436 
437 	// Issue 380
438 	int templatedEnum()
439 	{
440 		enum a(T) = T.init;
441 		return a!int;
442 	}
443 
444 	// Issue 380
445 	int otherTemplatedEnum()
446 	{
447 		auto a(T) = T.init; // [warn]: Variable a is never used.
448 		return 0;
449 	}
450 
451 	void doStuff(int a, int b) // [warn]: Parameter b is never used.
452 	{
453 		return a;
454 	}
455 
456 	}}, sac);
457 	stderr.writeln("Unittest for UnusedVariableCheck passed.");
458 }