1 //          Copyright Brian Schott (Hackerpilot) 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.unmodified;
6 
7 import analysis.base;
8 import dsymbol.scope_ : Scope;
9 import std.container;
10 import dparse.ast;
11 import dparse.lexer;
12 
13 /**
14  * Checks for variables that could have been declared const or immutable
15  */
16 class UnmodifiedFinder : BaseAnalyzer
17 {
18 	alias visit = BaseAnalyzer.visit;
19 
20 	///
21 	this(string fileName, const(Scope)* sc, bool skipTests = false)
22 	{
23 		super(fileName, sc, skipTests);
24 	}
25 
26 	override void visit(const Module mod)
27 	{
28 		pushScope();
29 		mod.accept(this);
30 		popScope();
31 	}
32 
33 	override void visit(const BlockStatement blockStatement)
34 	{
35 		pushScope();
36 		blockStatementDepth++;
37 		blockStatement.accept(this);
38 		blockStatementDepth--;
39 		popScope();
40 	}
41 
42 	override void visit(const StructBody structBody)
43 	{
44 		pushScope();
45 		immutable oldBlockStatementDepth = blockStatementDepth;
46 		blockStatementDepth = 0;
47 		structBody.accept(this);
48 		blockStatementDepth = oldBlockStatementDepth;
49 		popScope();
50 	}
51 
52 	override void visit(const VariableDeclaration dec)
53 	{
54 		if (dec.autoDeclaration is null && blockStatementDepth > 0
55 				&& isImmutable <= 0 && !canFindImmutable(dec))
56 		{
57 			foreach (d; dec.declarators)
58 			{
59 				if (initializedFromCast(d.initializer))
60 					continue;
61 				tree[$ - 1].insert(new VariableInfo(d.name.text, d.name.line,
62 						d.name.column, isValueTypeSimple(dec.type)));
63 			}
64 		}
65 		dec.accept(this);
66 	}
67 
68 	override void visit(const AutoDeclaration autoDeclaration)
69 	{
70 		import std.algorithm : canFind;
71 
72 		if (blockStatementDepth > 0 && isImmutable <= 0
73 				&& (!autoDeclaration.storageClasses.canFind!(a => a.token == tok!"const"
74 					|| a.token == tok!"enum" || a.token == tok!"immutable")))
75 		{
76 			foreach (part; autoDeclaration.parts)
77 			{
78 				if (initializedFromCast(part.initializer))
79 					continue;
80 				tree[$ - 1].insert(new VariableInfo(part.identifier.text,
81 						part.identifier.line, part.identifier.column));
82 			}
83 		}
84 		autoDeclaration.accept(this);
85 	}
86 
87 	override void visit(const AssignExpression assignExpression)
88 	{
89 		if (assignExpression.operator != tok!"")
90 		{
91 			interest++;
92 			guaranteeUse++;
93 			assignExpression.ternaryExpression.accept(this);
94 			guaranteeUse--;
95 			interest--;
96 
97 			if (assignExpression.operator == tok!"~=")
98 				interest++;
99 			assignExpression.expression.accept(this);
100 			if (assignExpression.operator == tok!"~=")
101 				interest--;
102 		}
103 		else
104 			assignExpression.accept(this);
105 	}
106 
107 	override void visit(const Declaration dec)
108 	{
109 		if (canFindImmutableOrConst(dec))
110 		{
111 			isImmutable++;
112 			dec.accept(this);
113 			isImmutable--;
114 		}
115 		else
116 			dec.accept(this);
117 	}
118 
119 	override void visit(const IdentifierChain ic)
120 	{
121 		if (ic.identifiers.length && interest > 0)
122 			variableMightBeModified(ic.identifiers[0].text);
123 		ic.accept(this);
124 	}
125 
126 	override void visit(const IdentifierOrTemplateInstance ioti)
127 	{
128 		if (ioti.identifier != tok!"" && interest > 0)
129 			variableMightBeModified(ioti.identifier.text);
130 		ioti.accept(this);
131 	}
132 
133 	mixin PartsMightModify!AsmPrimaryExp;
134 	mixin PartsMightModify!IndexExpression;
135 	mixin PartsMightModify!FunctionCallExpression;
136 	mixin PartsMightModify!IdentifierOrTemplateChain;
137 	mixin PartsMightModify!ReturnStatement;
138 
139 	override void visit(const UnaryExpression unary)
140 	{
141 		if (unary.prefix == tok!"++" || unary.prefix == tok!"--"
142 				|| unary.suffix == tok!"++" || unary.suffix == tok!"--"
143 				|| unary.prefix == tok!"*" || unary.prefix == tok!"&")
144 		{
145 			interest++;
146 			guaranteeUse++;
147 			unary.accept(this);
148 			guaranteeUse--;
149 			interest--;
150 		}
151 		else
152 			unary.accept(this);
153 	}
154 
155 	override void visit(const ForeachStatement foreachStatement)
156 	{
157 		if (foreachStatement.low !is null)
158 		{
159 			interest++;
160 			foreachStatement.low.accept(this);
161 			interest--;
162 		}
163 		if (foreachStatement.declarationOrStatement !is null)
164 			foreachStatement.declarationOrStatement.accept(this);
165 	}
166 
167 	override void visit(const TraitsExpression)
168 	{
169 		// issue #266: Ignore unmodified variables inside of `__traits` expressions
170 	}
171 
172 	override void visit(const TypeofExpression)
173 	{
174 		// issue #270: Ignore unmodified variables inside of `typeof` expressions
175 	}
176 
177 	override void visit(const AsmStatement a)
178 	{
179 		inAsm = true;
180 		a.accept(this);
181 		inAsm = false;
182 	}
183 
184 private:
185 
186 	template PartsMightModify(T)
187 	{
188 		override void visit(const T t)
189 		{
190 			interest++;
191 			t.accept(this);
192 			interest--;
193 		}
194 	}
195 
196 	void variableMightBeModified(string name)
197 	{
198 		size_t index = tree.length - 1;
199 		auto vi = VariableInfo(name);
200 		if (guaranteeUse == 0)
201 		{
202 			auto r = tree[index].equalRange(&vi);
203 			if (!r.empty && r.front.isValueType && !inAsm)
204 				return;
205 		}
206 		while (true)
207 		{
208 			if (tree[index].removeKey(&vi) != 0 || index == 0)
209 				break;
210 			index--;
211 		}
212 	}
213 
214 	bool initializedFromCast(const Initializer initializer)
215 	{
216 		import std.typecons : scoped;
217 
218 		static class CastFinder : ASTVisitor
219 		{
220 			alias visit = ASTVisitor.visit;
221 			override void visit(const CastExpression castExpression)
222 			{
223 				foundCast = true;
224 				castExpression.accept(this);
225 			}
226 
227 			bool foundCast;
228 		}
229 
230 		if (initializer is null)
231 			return false;
232 		auto finder = scoped!CastFinder();
233 		finder.visit(initializer);
234 		return finder.foundCast;
235 	}
236 
237 	bool canFindImmutableOrConst(const Declaration dec)
238 	{
239 		import std.algorithm : canFind, map, filter;
240 
241 		return !dec.attributes.map!(a => a.attribute)
242 			.filter!(a => a == tok!"immutable" || a == tok!"const").empty;
243 	}
244 
245 	bool canFindImmutable(const VariableDeclaration dec)
246 	{
247 		import std.algorithm : canFind;
248 
249 		foreach (storageClass; dec.storageClasses)
250 		{
251 			if (storageClass.token == tok!"enum")
252 				return true;
253 		}
254 		foreach (sc; dec.storageClasses)
255 		{
256 			if (sc.token == tok!"immutable" || sc.token == tok!"const")
257 				return true;
258 		}
259 		if (dec.type !is null)
260 		{
261 			foreach (tk; dec.type.typeConstructors)
262 				if (tk == tok!"immutable" || tk == tok!"const")
263 					return true;
264 			if (dec.type.type2)
265 			{
266 				const tk = dec.type.type2.typeConstructor;
267 				if (tk == tok!"immutable" || tk == tok!"const")
268 					return true;
269 			}
270 		}
271 		return false;
272 	}
273 
274 	static struct VariableInfo
275 	{
276 		string name;
277 		size_t line;
278 		size_t column;
279 		bool isValueType;
280 	}
281 
282 	void popScope()
283 	{
284 		foreach (vi; tree[$ - 1])
285 		{
286 			immutable string errorMessage = "Variable " ~ vi.name
287 				~ " is never modified and could have been declared const or immutable.";
288 			addErrorMessage(vi.line, vi.column, "dscanner.suspicious.unmodified", errorMessage);
289 		}
290 		tree = tree[0 .. $ - 1];
291 	}
292 
293 	void pushScope()
294 	{
295 		tree ~= new RedBlackTree!(VariableInfo*, "a.name < b.name");
296 	}
297 
298 	int blockStatementDepth;
299 
300 	int interest;
301 
302 	int guaranteeUse;
303 
304 	int isImmutable;
305 
306 	bool inAsm;
307 
308 	RedBlackTree!(VariableInfo*, "a.name < b.name")[] tree;
309 }
310 
311 bool isValueTypeSimple(const Type type) pure nothrow @nogc
312 {
313 	if (type.type2 is null)
314 		return false;
315 	return type.type2.builtinType != tok!"" && type.typeSuffixes.length == 0;
316 }
317 
318 @system unittest
319 {
320     import analysis.config : StaticAnalysisConfig, Check, disabledConfig;
321     import analysis.helpers : assertAnalyzerWarnings;
322     import std.stdio : stderr;
323     import std.format : format;
324 
325     StaticAnalysisConfig sac = disabledConfig();
326     sac.could_be_immutable_check = Check.enabled;
327 
328     // pass
329 
330     assertAnalyzerWarnings(q{
331         void foo(){const(int) i;}
332     }, sac);
333 
334 	assertAnalyzerWarnings(q{
335         void foo(){immutable(int)* i;}
336     }, sac);
337 
338 	assertAnalyzerWarnings(q{
339         void foo(){enum i = 1;}
340     }, sac);
341 
342 }