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 : 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 			if (primary.identifierOrTemplateInstance !is null
211 					&& primary.identifierOrTemplateInstance.identifier != tok!"")
212 			{
213 				variableUsed(primary.identifierOrTemplateInstance.identifier.text);
214 			}
215 			if (mixinDepth > 0 && primary.primary == tok!"stringLiteral"
216 					|| primary.primary == tok!"wstringLiteral"
217 					|| primary.primary == tok!"dstringLiteral")
218 			{
219 				foreach (part; matchAll(primary.primary.text, re))
220 				{
221 					immutable size_t treeIndex = tree.length - 1;
222 					auto uu = UnUsed(part.hit);
223 					auto r = tree[treeIndex].equalRange(&uu);
224 					if (!r.empty)
225 						r.front.uncertain = true;
226 				}
227 			}
228 		}
229 		primary.accept(this);
230 	}
231 
232 	override void visit(const ReturnStatement retStatement)
233 	{
234 		if (retStatement.expression !is null)
235 		{
236 			interestDepth++;
237 			visit(retStatement.expression);
238 			interestDepth--;
239 		}
240 	}
241 
242 	override void visit(const BlockStatement blockStatement)
243 	{
244 		immutable bool sb = inAggregateScope;
245 		inAggregateScope = false;
246 		if (blockStatementIntroducesScope)
247 			pushScope();
248 		blockStatement.accept(this);
249 		if (blockStatementIntroducesScope)
250 			popScope();
251 		inAggregateScope = sb;
252 	}
253 
254 	override void visit(const VariableDeclaration variableDeclaration)
255 	{
256 		foreach (d; variableDeclaration.declarators)
257 			this.variableDeclared(d.name.text, d.name.line, d.name.column, false, false);
258 		variableDeclaration.accept(this);
259 	}
260 
261 	override void visit(const AutoDeclaration autoDeclaration)
262 	{
263 		foreach (t; autoDeclaration.parts.map!(a => a.identifier))
264 			this.variableDeclared(t.text, t.line, t.column, false, false);
265 		autoDeclaration.accept(this);
266 	}
267 
268 	override void visit(const WithStatement withStatetement)
269 	{
270 		interestDepth++;
271 		withStatetement.expression.accept(this);
272 		interestDepth--;
273 		withStatetement.statementNoCaseNoDefault.accept(this);
274 	}
275 
276 	override void visit(const Parameter parameter)
277 	{
278 		import std.algorithm : canFind;
279 		import std.array : array;
280 
281 		if (parameter.name != tok!"")
282 		{
283 			immutable bool isRef = canFind(parameter.parameterAttributes, cast(IdType) tok!"ref")
284 				|| canFind(parameter.parameterAttributes,
285 						cast(IdType) tok!"in") || canFind(parameter.parameterAttributes,
286 						cast(IdType) tok!"out");
287 			variableDeclared(parameter.name.text, parameter.name.line,
288 					parameter.name.column, true, isRef);
289 			if (parameter.default_ !is null)
290 			{
291 				interestDepth++;
292 				parameter.default_.accept(this);
293 				interestDepth--;
294 			}
295 		}
296 	}
297 
298 	override void visit(const StructBody structBody)
299 	{
300 		immutable bool sb = inAggregateScope;
301 		inAggregateScope = true;
302 		foreach (dec; structBody.declarations)
303 			visit(dec);
304 		inAggregateScope = sb;
305 	}
306 
307 	override void visit(const ConditionalStatement conditionalStatement)
308 	{
309 		immutable bool cs = blockStatementIntroducesScope;
310 		blockStatementIntroducesScope = false;
311 		conditionalStatement.accept(this);
312 		blockStatementIntroducesScope = cs;
313 	}
314 
315 	override void visit(const AsmPrimaryExp primary)
316 	{
317 		if (primary.token != tok!"")
318 			variableUsed(primary.token.text);
319 		if (primary.identifierChain !is null)
320 			variableUsed(primary.identifierChain.identifiers[0].text);
321 	}
322 
323 	override void visit(const TraitsExpression)
324 	{
325 		// issue #266: Ignore unused variables inside of `__traits` expressions
326 	}
327 
328 	override void visit(const TypeofExpression)
329 	{
330 		// issue #270: Ignore unused variables inside of `typeof` expressions
331 	}
332 
333 private:
334 
335 	mixin template PartsUseVariables(NodeType)
336 	{
337 		override void visit(const NodeType node)
338 		{
339 			interestDepth++;
340 			node.accept(this);
341 			interestDepth--;
342 		}
343 	}
344 
345 	void variableDeclared(string name, size_t line, size_t column, bool isParameter, bool isRef)
346 	{
347 		if (inAggregateScope)
348 			return;
349 		tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef));
350 	}
351 
352 	void variableUsed(string name)
353 	{
354 		size_t treeIndex = tree.length - 1;
355 		auto uu = UnUsed(name);
356 		while (true)
357 		{
358 			if (tree[treeIndex].removeKey(&uu) != 0 || treeIndex == 0)
359 				break;
360 			treeIndex--;
361 		}
362 	}
363 
364 	void popScope()
365 	{
366 		foreach (uu; tree[$ - 1])
367 		{
368 			if (!uu.isRef && tree.length > 1)
369 			{
370 				immutable string certainty = uu.uncertain ? " might not be used."
371 					: " is never used.";
372 				immutable string errorMessage = (uu.isParameter ? "Parameter " : "Variable ")
373 					~ uu.name ~ certainty;
374 				addErrorMessage(uu.line, uu.column, uu.isParameter ? "dscanner.suspicious.unused_parameter"
375 						: "dscanner.suspicious.unused_variable", errorMessage);
376 			}
377 		}
378 		tree = tree[0 .. $ - 1];
379 	}
380 
381 	void pushScope()
382 	{
383 		tree ~= new RedBlackTree!(UnUsed*, "a.name < b.name");
384 	}
385 
386 	struct UnUsed
387 	{
388 		string name;
389 		size_t line;
390 		size_t column;
391 		bool isParameter;
392 		bool isRef;
393 		bool uncertain;
394 	}
395 
396 	RedBlackTree!(UnUsed*, "a.name < b.name")[] tree;
397 
398 	uint interestDepth;
399 
400 	uint mixinDepth;
401 
402 	bool isOverride;
403 
404 	bool inAggregateScope;
405 
406 	bool blockStatementIntroducesScope = true;
407 
408 	Regex!char re;
409 }
410 
411 unittest
412 {
413 	import std.stdio : stderr;
414 	import analysis.config : StaticAnalysisConfig, Check;
415 	import analysis.helpers : assertAnalyzerWarnings;
416 
417 	StaticAnalysisConfig sac;
418 	sac.unused_variable_check = Check.enabled;
419 	assertAnalyzerWarnings(q{
420 
421 	// Issue 274
422 	unittest
423 	{
424 		size_t byteIndex = 0;
425 		*(cast(FieldType*)(retVal.ptr + byteIndex)) = item;
426 	}
427 
428 	unittest
429 	{
430 		int a; // [warn]: Variable a is never used.
431 	}
432 
433 	void doStuff(int a, int b) // [warn]: Parameter b is never used.
434 	{
435 		return a;
436 	}
437 
438 	}}, sac);
439 	stderr.writeln("Unittest for UnusedVariableCheck passed.");
440 }