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