1 //          Copyright Basile Burg 2017.
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.useless_initializer;
6 
7 import analysis.base;
8 import containers.dynamicarray;
9 import containers.hashmap;
10 import dparse.ast;
11 import dparse.lexer;
12 import std.algorithm;
13 import std.range : empty;
14 import std.stdio;
15 
16 /*
17 Limitations:
18 	- Stuff s = Stuff.init does not work with type with postfixes`*` `[]`.
19 	- Stuff s = Stuff.init is only detected for struct within the module.
20 	- BasicType b = BasicType(v), default init used in BasicType ctor, e.g int(8).
21 */
22 
23 /**
24  * Check that detects the initializers that are
25  * not different from the implcit initializer.
26  */
27 final class UselessInitializerChecker : BaseAnalyzer
28 {
29 	alias visit = BaseAnalyzer.visit;
30 
31 private:
32 
33 	enum key = "dscanner.useless-initializer";
34 
35 	version(unittest)
36 	{
37 		enum msg = "X";
38 	}
39 	else
40 	{
41 		enum msg = `Variable %s initializer is useless because it does not differ from the default value`;
42 	}
43 
44 	static immutable intDefs = ["0", "0L", "0UL", "0uL", "0U", "0x0", "0b0"];
45 
46 	HashMap!(string, bool) _structCanBeInit;
47 	DynamicArray!(string) _structStack;
48 	DynamicArray!(bool) _inStruct;
49 	DynamicArray!(bool) _atDisabled;
50 	bool _inTest;
51 
52 public:
53 
54 	///
55 	this(string fileName, bool skipTests = false)
56 	{
57 		super(fileName, null, skipTests);
58 		_inStruct.insert(false);
59 	}
60 
61 	override void visit(const(Unittest) test)
62 	{
63 		if (skipTests)
64 			return;
65 		_inTest = true;
66 		test.accept(this);
67 		_inTest = false;
68 	}
69 
70 	override void visit(const(StructDeclaration) decl)
71 	{
72 		if (_inTest)
73 			return;
74 
75 		assert(_inStruct.length > 1);
76 
77 		const string structName = _inStruct[$-2] ?
78 			_structStack.back() ~ "." ~ decl.name.text :
79 		  decl.name.text;
80 
81 		_structStack.insert(structName);
82 		_structCanBeInit[structName] = false;
83 		_atDisabled.insert(false);
84 		decl.accept(this);
85 		_structStack.removeBack();
86 		_atDisabled.removeBack();
87 	}
88 
89 	override void visit(const(Declaration) decl)
90 	{
91 		_inStruct.insert(decl.structDeclaration !is null);
92 		decl.accept(this);
93 		if (_inStruct.length > 1 && _inStruct[$-2] && decl.constructor &&
94 			((decl.constructor.parameters && decl.constructor.parameters.parameters.length == 0) ||
95 			!decl.constructor.parameters))
96 		{
97 			_atDisabled[$-1] = decl.attributes
98 				.canFind!(a => a.atAttribute !is null && a.atAttribute.identifier.text == "disable");
99 		}
100 		_inStruct.removeBack();
101 	}
102 
103 	override void visit(const(Constructor) decl)
104 	{
105 		if (_inStruct.length > 1 && _inStruct[$-2] &&
106 			((decl.parameters && decl.parameters.parameters.length == 0) || !decl.parameters))
107 		{
108 			const bool canBeInit = !_atDisabled[$-1];
109 			_structCanBeInit[_structStack.back()] = canBeInit;
110 			if (!canBeInit)
111 				_structCanBeInit[_structStack.back()] = !decl.memberFunctionAttributes
112 					.canFind!(a => a.atAttribute !is null && a.atAttribute.identifier.text == "disable");
113 		}
114 		decl.accept(this);
115 	}
116 
117 	// issue 473, prevent to visit delegates that contain duck type checkers.
118 	override void visit(const(TypeofExpression)) {}
119 
120 	// issue 473, prevent to check expressions in __traits(compiles, ...)
121 	override void visit(const(TraitsExpression) e)
122 	{
123 		if (e.identifier.text == "compiles")
124 		{
125 			return;
126 		}
127 		else
128 		{
129 			e.accept(this);
130 		}
131 	}
132 
133 	override void visit(const(VariableDeclaration) decl)
134 	{
135 		if (!decl.type || !decl.type.type2 ||
136 			// initializer has to appear clearly in generated ddoc
137 			decl.comment !is null ||
138 			// issue 474, manifest constants HAVE to be initialized.
139 			decl.storageClasses.canFind!(a => a.token == tok!"enum"))
140 		{
141 			return;
142 		}
143 
144 		foreach (declarator; decl.declarators)
145 		{
146 			if (!declarator.initializer ||
147 				!declarator.initializer.nonVoidInitializer ||
148 				declarator.comment !is null)
149 			{
150 					continue;
151 			}
152 
153 			version(unittest)
154 			{
155 				enum warn = q{addErrorMessage(declarator.name.line,
156 					declarator.name.column, key, msg);};
157 			}
158 			else
159 			{
160 				import std.format : format;
161 				enum warn = q{addErrorMessage(declarator.name.line,
162 				declarator.name.column, key, msg.format(declarator.name.text));};
163 			}
164 
165 			// ---  Info about the declaration type --- //
166 			const bool isPtr = decl.type.typeSuffixes && decl.type.typeSuffixes
167 				.canFind!(a => a.star != tok!"");
168 			const bool isArr = decl.type.typeSuffixes && decl.type.typeSuffixes
169 				.canFind!(a => a.array);
170 
171 			bool isStr, isSzInt;
172 			Token customType;
173 
174 			if (decl.type.type2.symbol && decl.type.type2.symbol.identifierOrTemplateChain &&
175 				decl.type.type2.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances.length == 1)
176 			{
177 				const IdentifierOrTemplateInstance idt =
178 					decl.type.type2.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances[0];
179 
180 				customType = idt.identifier;
181 				isStr = customType.text.among("string", "wstring", "dstring") != 0;
182 				isSzInt = customType.text.among("size_t", "ptrdiff_t") != 0;
183 			}
184 
185 			// --- 'BasicType/Symbol AssignExpression' ---//
186 			const NonVoidInitializer nvi = declarator.initializer.nonVoidInitializer;
187 			const UnaryExpression ue = cast(UnaryExpression) nvi.assignExpression;
188 			if (ue && ue.primaryExpression)
189 			{
190 				const Token value = ue.primaryExpression.primary;
191 
192 				if (!isPtr && !isArr && !isStr && decl.type.type2.builtinType != tok!"")
193 				{
194 					switch(decl.type.type2.builtinType)
195 					{
196 					// check for common cases of default values
197 					case tok!"byte",    tok!"ubyte":
198 					case tok!"short",   tok!"ushort":
199 					case tok!"int",     tok!"uint":
200 					case tok!"long",    tok!"ulong":
201 					case tok!"cent",    tok!"ucent":
202 					case tok!"bool":
203 						if (intDefs.canFind(value.text) || value == tok!"false")
204 							mixin(warn);
205 						goto default;
206 					default:
207 					// check for BasicType.init
208 						if (ue.primaryExpression.basicType.type == decl.type.type2.builtinType &&
209 							ue.primaryExpression.primary.text == "init" &&
210 							!ue.primaryExpression.expression)
211 							mixin(warn);
212 					}
213 				}
214 				else if (isSzInt)
215 				{
216 					if (intDefs.canFind(value.text))
217 						mixin(warn);
218 				}
219 				else if (isPtr || isStr)
220 				{
221 					if (str(value.type) == "null")
222 						mixin(warn);
223 				}
224 				else if (isArr)
225 				{
226 					if (str(value.type) == "null")
227 						mixin(warn);
228 					else if (nvi.arrayInitializer && nvi.arrayInitializer.arrayMemberInitializations.length == 0)
229 						mixin(warn);
230 				}
231 			}
232 
233 			// Symbol s = Symbol.init
234 			else if (ue && customType != tok!"" && ue.unaryExpression && ue.unaryExpression.primaryExpression &&
235 				ue.unaryExpression.primaryExpression.identifierOrTemplateInstance &&
236 				ue.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier == customType &&
237 				ue.identifierOrTemplateInstance && ue.identifierOrTemplateInstance.identifier.text == "init")
238 			{
239 				if (customType.text in _structCanBeInit)
240 				{
241 					if  (!_structCanBeInit[customType.text])
242 						mixin(warn);
243 				}
244 			}
245 
246 			// 'Symbol ArrayInitializer' : assumes Symbol is an array b/c of the Init
247 			else if (nvi.arrayInitializer && (isArr || isStr))
248 			{
249 				if (nvi.arrayInitializer.arrayMemberInitializations.length == 0)
250 					mixin(warn);
251 			}
252 		}
253 
254 		decl.accept(this);
255 	}
256 }
257 
258 @system unittest
259 {
260 	import analysis.config : Check, disabledConfig, StaticAnalysisConfig;
261 	import analysis.helpers: assertAnalyzerWarnings;
262 	import std.stdio : stderr;
263 
264 	StaticAnalysisConfig sac = disabledConfig;
265 	sac.useless_initializer = Check.enabled;
266 
267 	// fails
268 	assertAnalyzerWarnings(q{
269 		struct S {}
270 		ubyte a = 0x0;      // [warn]: X
271 		int a = 0;          // [warn]: X
272 		ulong a = 0;        // [warn]: X
273 		int* a = null;      // [warn]: X
274 		Foo* a = null;      // [warn]: X
275 		int[] a = null;     // [warn]: X
276 		int[] a = [];       // [warn]: X
277 		string a = null;    // [warn]: X
278 		string a = null;    // [warn]: X
279 		wstring a = null;   // [warn]: X
280 		dstring a = null;   // [warn]: X
281 		size_t a = 0;       // [warn]: X
282 		ptrdiff_t a = 0;    // [warn]: X
283 		string a = [];      // [warn]: X
284 		char[] a = null;    // [warn]: X
285 		int a = int.init;   // [warn]: X
286 		char a = char.init; // [warn]: X
287 		S s = S.init;       // [warn]: X
288 		bool a = false;     // [warn]: X
289 	}, sac);
290 
291 	// passes
292 	assertAnalyzerWarnings(q{
293 		struct D {@disable this();}
294 		struct E {this() @disable;}
295 		ubyte a = 0xFE;
296 		int a = 1;
297 		ulong a = 1;
298 		int* a = &a;
299 		Foo* a = &a;
300 		int[] a = &a;
301 		int[] a = [0];
302 		string a = "sdf";
303 		string a = "sdg"c;
304 		wstring a = "sdg"w;
305 		dstring a = "fgh"d;
306 		string a = q{int a;};
307 		size_t a = 1;
308 		ptrdiff_t a;
309 		ubyte a;
310 		int a;
311 		ulong a;
312 		int* a;
313 		Foo* a;
314 		int[] a;
315 		string a;
316 		wstring a;
317 		dstring a;
318 		string a = ['a'];
319 		string a = "";
320 		string a = ""c;
321 		wstring a = ""w;
322 		dstring a = ""d;
323 		string a = q{};
324 		char[] a = "ze";
325 		S s = S(0,1);
326 		S s = s.call();
327 		enum {a}
328 		enum ubyte a = 0;
329 		static assert(is(typeof((){T t = T.init;})));
330 		void foo(){__traits(compiles, (){int a = 0;}).writeln;}
331 		bool a;
332 		D d = D.init;
333 		E e = E.init;
334 		NotKnown nk = NotKnown.init;
335 	}, sac);
336 
337 	stderr.writeln("Unittest for UselessInitializerChecker passed.");
338 }
339