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