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)
22 	{
23 		super(fileName, sc);
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 (size_t i, id; autoDeclaration.identifiers)
77 			{
78 				if (initializedFromCast(autoDeclaration.initializers[i]))
79 					continue;
80 				tree[$ - 1].insert(new VariableInfo(id.text, id.line,
81 					id.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 		foreachStatement.declarationOrStatement.accept(this);
164 	}
165 
166 	override void visit(const TraitsExpression)
167 	{
168 		// issue #266: Ignore unmodified variables inside of `__traits` expressions
169 	}
170 
171 	override void visit(const TypeofExpression)
172 	{
173 		// issue #270: Ignore unmodified variables inside of `typeof` expressions
174 	}
175 
176 	override void visit(const AsmStatement a)
177 	{
178 		inAsm = true;
179 		a.accept(this);
180 		inAsm = false;
181 	}
182 
183 private:
184 
185 	template PartsMightModify(T)
186 	{
187 		override void visit(const T t)
188 		{
189 			interest++;
190 			t.accept(this);
191 			interest--;
192 		}
193 	}
194 
195 	void variableMightBeModified(string name)
196 	{
197 		size_t index = tree.length - 1;
198 		auto vi = VariableInfo(name);
199 		if (guaranteeUse == 0)
200 		{
201 			auto r = tree[index].equalRange(&vi);
202 			if (!r.empty && r.front.isValueType && !inAsm)
203 				return;
204 		}
205 		while (true)
206 		{
207 			if (tree[index].removeKey(&vi) != 0 || index == 0)
208 				break;
209 			index--;
210 		}
211 	}
212 
213 	bool initializedFromCast(const Initializer initializer)
214 	{
215 		import std.typecons : scoped;
216 
217 		static class CastFinder : ASTVisitor
218 		{
219 			alias visit = ASTVisitor.visit;
220 			override void visit(const CastExpression castExpression)
221 			{
222 				foundCast = true;
223 				castExpression.accept(this);
224 			}
225 			bool foundCast = false;
226 		}
227 
228 		if (initializer is null)
229 			return false;
230 		auto finder = scoped!CastFinder();
231 		finder.visit(initializer);
232 		return finder.foundCast;
233 	}
234 
235 	bool canFindImmutableOrConst(const Declaration dec)
236 	{
237 		import std.algorithm : canFind, map, filter;
238 		return !dec.attributes.map!(a => a.attribute).filter!(
239 			a => a == cast(IdType) tok!"immutable" || a == cast(IdType) tok!"const")
240 			.empty;
241 	}
242 
243 	bool canFindImmutable(const VariableDeclaration dec)
244 	{
245 		import std.algorithm : canFind;
246 		foreach (storageClass; dec.storageClasses)
247 		{
248 			if (storageClass.token == tok!"enum")
249 				return true;
250 		}
251 		foreach (sc; dec.storageClasses)
252 		{
253 			if (sc.token == tok!"immutable" || sc.token == tok!"const")
254 				return true;
255 		}
256 		if (dec.type !is null)
257 		{
258 			if (dec.type.typeConstructors.canFind(cast(IdType) tok!"immutable"))
259 				return true;
260 		}
261 		return false;
262 	}
263 
264 	static struct VariableInfo
265 	{
266 		string name;
267 		size_t line;
268 		size_t column;
269 		bool isValueType;
270 	}
271 
272 	void popScope()
273 	{
274 		foreach (vi; tree[$ - 1])
275 		{
276 			immutable string errorMessage = "Variable " ~ vi.name
277 				~ " is never modified and could have been declared const"
278 				~ " or immutable.";
279 			addErrorMessage(vi.line, vi.column, "dscanner.suspicious.unmodified",
280 				errorMessage);
281 		}
282 		tree = tree[0 .. $ - 1];
283 	}
284 
285 	void pushScope()
286 	{
287 		tree ~= new RedBlackTree!(VariableInfo*, "a.name < b.name");
288 	}
289 
290 	int blockStatementDepth;
291 
292 	int interest;
293 
294 	int guaranteeUse;
295 
296 	int isImmutable;
297 
298 	bool inAsm;
299 
300 	RedBlackTree!(VariableInfo*, "a.name < b.name")[] tree;
301 }
302 
303 bool isValueTypeSimple(const Type type) pure nothrow @nogc
304 {
305 	if (type.type2 is null)
306 		return false;
307 	return type.type2.builtinType != tok!"" && type.typeSuffixes.length == 0;
308 }