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, id.column));
81 			}
82 		}
83 		autoDeclaration.accept(this);
84 	}
85 
86 	override void visit(const AssignExpression assignExpression)
87 	{
88 		if (assignExpression.operator != tok!"")
89 		{
90 			interest++;
91 			guaranteeUse++;
92 			assignExpression.ternaryExpression.accept(this);
93 			guaranteeUse--;
94 			interest--;
95 
96 			if (assignExpression.operator == tok!"~=")
97 				interest++;
98 			assignExpression.expression.accept(this);
99 			if (assignExpression.operator == tok!"~=")
100 				interest--;
101 		}
102 		else
103 			assignExpression.accept(this);
104 	}
105 
106 	override void visit(const Declaration dec)
107 	{
108 		if (canFindImmutableOrConst(dec))
109 		{
110 			isImmutable++;
111 			dec.accept(this);
112 			isImmutable--;
113 		}
114 		else
115 			dec.accept(this);
116 	}
117 
118 	override void visit(const IdentifierChain ic)
119 	{
120 		if (ic.identifiers.length && interest > 0)
121 			variableMightBeModified(ic.identifiers[0].text);
122 		ic.accept(this);
123 	}
124 
125 	override void visit(const IdentifierOrTemplateInstance ioti)
126 	{
127 		if (ioti.identifier != tok!"" && interest > 0)
128 			variableMightBeModified(ioti.identifier.text);
129 		ioti.accept(this);
130 	}
131 
132 	mixin PartsMightModify!AsmPrimaryExp;
133 	mixin PartsMightModify!IndexExpression;
134 	mixin PartsMightModify!FunctionCallExpression;
135 	mixin PartsMightModify!IdentifierOrTemplateChain;
136 	mixin PartsMightModify!ReturnStatement;
137 
138 	override void visit(const UnaryExpression unary)
139 	{
140 		if (unary.prefix == tok!"++" || unary.prefix == tok!"--"
141 				|| unary.suffix == tok!"++" || unary.suffix == tok!"--"
142 				|| unary.prefix == tok!"*" || unary.prefix == tok!"&")
143 		{
144 			interest++;
145 			guaranteeUse++;
146 			unary.accept(this);
147 			guaranteeUse--;
148 			interest--;
149 		}
150 		else
151 			unary.accept(this);
152 	}
153 
154 	override void visit(const ForeachStatement foreachStatement)
155 	{
156 		if (foreachStatement.low !is null)
157 		{
158 			interest++;
159 			foreachStatement.low.accept(this);
160 			interest--;
161 		}
162 		foreachStatement.declarationOrStatement.accept(this);
163 	}
164 
165 	override void visit(const TraitsExpression)
166 	{
167 		// issue #266: Ignore unmodified variables inside of `__traits` expressions
168 	}
169 
170 	override void visit(const TypeofExpression)
171 	{
172 		// issue #270: Ignore unmodified variables inside of `typeof` expressions
173 	}
174 
175 	override void visit(const AsmStatement a)
176 	{
177 		inAsm = true;
178 		a.accept(this);
179 		inAsm = false;
180 	}
181 
182 private:
183 
184 	template PartsMightModify(T)
185 	{
186 		override void visit(const T t)
187 		{
188 			interest++;
189 			t.accept(this);
190 			interest--;
191 		}
192 	}
193 
194 	void variableMightBeModified(string name)
195 	{
196 		size_t index = tree.length - 1;
197 		auto vi = VariableInfo(name);
198 		if (guaranteeUse == 0)
199 		{
200 			auto r = tree[index].equalRange(&vi);
201 			if (!r.empty && r.front.isValueType && !inAsm)
202 				return;
203 		}
204 		while (true)
205 		{
206 			if (tree[index].removeKey(&vi) != 0 || index == 0)
207 				break;
208 			index--;
209 		}
210 	}
211 
212 	bool initializedFromCast(const Initializer initializer)
213 	{
214 		import std.typecons : scoped;
215 
216 		static class CastFinder : ASTVisitor
217 		{
218 			alias visit = ASTVisitor.visit;
219 			override void visit(const CastExpression castExpression)
220 			{
221 				foundCast = true;
222 				castExpression.accept(this);
223 			}
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 
239 		return !dec.attributes.map!(a => a.attribute)
240 			.filter!(a => a == cast(IdType) tok!"immutable" || a == cast(IdType) tok!"const").empty;
241 	}
242 
243 	bool canFindImmutable(const VariableDeclaration dec)
244 	{
245 		import std.algorithm : canFind;
246 
247 		foreach (storageClass; dec.storageClasses)
248 		{
249 			if (storageClass.token == tok!"enum")
250 				return true;
251 		}
252 		foreach (sc; dec.storageClasses)
253 		{
254 			if (sc.token == tok!"immutable" || sc.token == tok!"const")
255 				return true;
256 		}
257 		if (dec.type !is null)
258 		{
259 			if (dec.type.typeConstructors.canFind(cast(IdType) tok!"immutable"))
260 				return true;
261 		}
262 		return false;
263 	}
264 
265 	static struct VariableInfo
266 	{
267 		string name;
268 		size_t line;
269 		size_t column;
270 		bool isValueType;
271 	}
272 
273 	void popScope()
274 	{
275 		foreach (vi; tree[$ - 1])
276 		{
277 			immutable string errorMessage = "Variable " ~ vi.name
278 				~ " is never modified and could have been declared const" ~ " or immutable.";
279 			addErrorMessage(vi.line, vi.column, "dscanner.suspicious.unmodified", errorMessage);
280 		}
281 		tree = tree[0 .. $ - 1];
282 	}
283 
284 	void pushScope()
285 	{
286 		tree ~= new RedBlackTree!(VariableInfo*, "a.name < b.name");
287 	}
288 
289 	int blockStatementDepth;
290 
291 	int interest;
292 
293 	int guaranteeUse;
294 
295 	int isImmutable;
296 
297 	bool inAsm;
298 
299 	RedBlackTree!(VariableInfo*, "a.name < b.name")[] tree;
300 }
301 
302 bool isValueTypeSimple(const Type type) pure nothrow @nogc
303 {
304 	if (type.type2 is null)
305 		return false;
306 	return type.type2.builtinType != tok!"" && type.typeSuffixes.length == 0;
307 }