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 std.container;
8 import std.d.ast;
9 import std.d.lexer;
10 import analysis.base;
11 
12 /**
13  * Checks for variables that could have been declared const or immutable
14  */
15 class UnmodifiedFinder:BaseAnalyzer
16 {
17 	alias visit = BaseAnalyzer.visit;
18 
19 	///
20 	this(string fileName)
21 	{
22 		super(fileName);
23 	}
24 
25 	override void visit(const Module mod)
26 	{
27 		pushScope();
28 		mod.accept(this);
29 		popScope();
30 	}
31 
32 	override void visit(const BlockStatement blockStatement)
33 	{
34 		pushScope();
35 		blockStatementDepth++;
36 		blockStatement.accept(this);
37 		blockStatementDepth--;
38 		popScope();
39 	}
40 
41 	override void visit(const StructBody structBody)
42 	{
43 		pushScope();
44 		auto oldBlockStatementDepth = blockStatementDepth;
45 		blockStatementDepth = 0;
46 		structBody.accept(this);
47 		blockStatementDepth = oldBlockStatementDepth;
48 		popScope();
49 	}
50 
51 	override void visit(const VariableDeclaration dec)
52 	{
53 		if (dec.autoDeclaration is null && blockStatementDepth > 0
54 			&& isImmutable <= 0 && !canFindImmutable(dec))
55 		{
56 			foreach (d; dec.declarators)
57 			{
58 				if (initializedFromCast(d.initializer))
59 					continue;
60 				tree[$ - 1].insert(new VariableInfo(d.name.text, d.name.line,
61 					d.name.column));
62 			}
63 		}
64 		dec.accept(this);
65 	}
66 
67 	override void visit(const AutoDeclaration autoDeclaration)
68 	{
69 		import std.algorithm : canFind;
70 
71 		if (blockStatementDepth > 0 && isImmutable <= 0
72 			&& (!autoDeclaration.storageClasses.canFind!(a => a.token == tok!"const"
73 			|| a.token == tok!"enum" || a.token == tok!"immutable")))
74 		{
75 			foreach (size_t i, id; autoDeclaration.identifiers)
76 			{
77 				if (initializedFromCast(autoDeclaration.initializers[i]))
78 					continue;
79 				tree[$ - 1].insert(new VariableInfo(id.text, id.line,
80 					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 			assignExpression.ternaryExpression.accept(this);
92 			interest--;
93 			assignExpression.assignExpression.accept(this);
94 		}
95 		else
96 			assignExpression.accept(this);
97 	}
98 
99 	override void visit(const Declaration dec)
100 	{
101 		if (canFindImmutableOrConst(dec))
102 		{
103 			isImmutable++;
104 			dec.accept(this);
105 			isImmutable--;
106 		}
107 		else
108 			dec.accept(this);
109 	}
110 
111 	override void visit(const IdentifierChain ic)
112 	{
113 		if (ic.identifiers.length && interest > 0)
114 			variableMightBeModified(ic.identifiers[0].text);
115 		ic.accept(this);
116 	}
117 
118 	override void visit(const IdentifierOrTemplateInstance ioti)
119 	{
120 		if (ioti.identifier != tok!"" && interest > 0)
121 			variableMightBeModified(ioti.identifier.text);
122 		ioti.accept(this);
123 	}
124 
125 	mixin PartsMightModify!AsmPrimaryExp;
126 	mixin PartsMightModify!IndexExpression;
127 	mixin PartsMightModify!SliceExpression;
128 	mixin PartsMightModify!FunctionCallExpression;
129 	mixin PartsMightModify!IdentifierOrTemplateChain;
130 	mixin PartsMightModify!ReturnStatement;
131 
132 	override void visit(const UnaryExpression unary)
133 	{
134 		if (unary.prefix == tok!"++" || unary.prefix == tok!"--"
135 			|| unary.suffix == tok!"++" || unary.suffix == tok!"--")
136 		{
137 			interest++;
138 			unary.accept(this);
139 			interest--;
140 		}
141 		else
142 			unary.accept(this);
143 	}
144 
145 	override void visit(const ForeachStatement foreachStatement)
146 	{
147 		if (foreachStatement.low !is null)
148 		{
149 			interest++;
150 			foreachStatement.low.accept(this);
151 			interest--;
152 		}
153 		foreachStatement.declarationOrStatement.accept(this);
154 	}
155 
156 	override void visit(const TraitsExpression)
157 	{
158 		// issue #266: Ignore unmodified variables inside of `__traits` expressions
159 	}
160 
161 	override void visit(const TypeofExpression)
162 	{
163 		// issue #270: Ignore unmodified variables inside of `typeof` expressions
164 	}
165 
166 private:
167 
168 	template PartsMightModify(T)
169 	{
170 		override void visit(const T t)
171 		{
172 			interest++;
173 			t.accept(this);
174 			interest--;
175 		}
176 	}
177 
178 	void variableMightBeModified(string name)
179 	{
180 //		import std.stdio : stderr;
181 //		stderr.writeln("Marking ", name, " as possibly modified");
182 		size_t index = tree.length - 1;
183 		auto vi = VariableInfo(name);
184 		while (true)
185 		{
186 			if (tree[index].removeKey(&vi) != 0 || index == 0)
187 				break;
188 			index--;
189 		}
190 	}
191 
192 	bool initializedFromCast(const Initializer initializer)
193 	{
194 		import std.typecons : scoped;
195 
196 		static class CastFinder : ASTVisitor
197 		{
198 			alias visit = ASTVisitor.visit;
199 			override void visit(const CastExpression castExpression)
200 			{
201 				foundCast = true;
202 				castExpression.accept(this);
203 			}
204 			bool foundCast = false;
205 		}
206 
207 		if (initializer is null)
208 			return false;
209 		auto finder = scoped!CastFinder();
210 		finder.visit(initializer);
211 		return finder.foundCast;
212 	}
213 
214 	bool canFindImmutableOrConst(const Declaration dec)
215 	{
216 		import std.algorithm : canFind, map, filter;
217 		return !dec.attributes.map!(a => a.attribute).filter!(
218 			a => a == cast(IdType) tok!"immutable" || a == cast(IdType) tok!"const")
219 			.empty;
220 	}
221 
222 	bool canFindImmutable(const VariableDeclaration dec)
223 	{
224 		import std.algorithm : canFind;
225 		foreach (storageClass; dec.storageClasses)
226 		{
227 			if (storageClass.token == tok!"enum")
228 				return true;
229 		}
230 		foreach (attr; dec.attributes)
231 		{
232 			if (attr.attribute.type == tok!"immutable" || attr.attribute.type == tok!"const")
233 				return true;
234 		}
235 		if (dec.type !is null)
236 		{
237 			if (dec.type.typeConstructors.canFind(cast(IdType) tok!"immutable"))
238 				return true;
239 		}
240 		return false;
241 	}
242 
243 	static struct VariableInfo
244 	{
245 		string name;
246 		size_t line;
247 		size_t column;
248 	}
249 
250 	void popScope()
251 	{
252 		foreach (vi; tree[$ - 1])
253 		{
254 			immutable string errorMessage = "Variable " ~ vi.name
255 				~ " is never modified and could have been declared const"
256 				~ " or immutable.";
257 			addErrorMessage(vi.line, vi.column, "dscanner.suspicious.unmodified",
258 				errorMessage);
259 		}
260 		tree = tree[0 .. $ - 1];
261 	}
262 
263 	void pushScope()
264 	{
265 		tree ~= new RedBlackTree!(VariableInfo*, "a.name < b.name");
266 	}
267 
268 	int blockStatementDepth;
269 
270 	int interest;
271 
272 	int isImmutable;
273 
274 	RedBlackTree!(VariableInfo*, "a.name < b.name")[] tree;
275 }
276