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 		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 
226 			bool foundCast = false;
227 		}
228 
229 		if (initializer is null)
230 			return false;
231 		auto finder = scoped!CastFinder();
232 		finder.visit(initializer);
233 		return finder.foundCast;
234 	}
235 
236 	bool canFindImmutableOrConst(const Declaration dec)
237 	{
238 		import std.algorithm : canFind, map, filter;
239 
240 		return !dec.attributes.map!(a => a.attribute)
241 			.filter!(a => a == cast(IdType) tok!"immutable" || a == cast(IdType) tok!"const").empty;
242 	}
243 
244 	bool canFindImmutable(const VariableDeclaration dec)
245 	{
246 		import std.algorithm : canFind;
247 
248 		foreach (storageClass; dec.storageClasses)
249 		{
250 			if (storageClass.token == tok!"enum")
251 				return true;
252 		}
253 		foreach (sc; dec.storageClasses)
254 		{
255 			if (sc.token == tok!"immutable" || sc.token == tok!"const")
256 				return true;
257 		}
258 		if (dec.type !is null)
259 		{
260 			if (dec.type.typeConstructors.canFind(cast(IdType) tok!"immutable"))
261 				return true;
262 		}
263 		return false;
264 	}
265 
266 	static struct VariableInfo
267 	{
268 		string name;
269 		size_t line;
270 		size_t column;
271 		bool isValueType;
272 	}
273 
274 	void popScope()
275 	{
276 		foreach (vi; tree[$ - 1])
277 		{
278 			immutable string errorMessage = "Variable " ~ vi.name
279 				~ " is never modified and could have been declared const" ~ " or immutable.";
280 			addErrorMessage(vi.line, vi.column, "dscanner.suspicious.unmodified", 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 }