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 
6 module dscanner.analysis.final_attribute;
7 
8 import dscanner.analysis.base;
9 import dscanner.analysis.helpers;
10 import dparse.ast;
11 import dparse.lexer;
12 
13 /**
14  * Checks for useless usage of the final attribute.
15  *
16  * There are several cases where the compiler allows them even if it's a noop.
17  */
18 final class FinalAttributeChecker : BaseAnalyzer
19 {
20 
21 private:
22 
23 	enum string KEY = "dscanner.useless.final";
24 	enum string MSGB = "Useless final attribute, %s";
25 
26 	static struct MESSAGE
27 	{
28 		static immutable struct_i    = "structs cannot be subclassed";
29 		static immutable union_i     = "unions cannot be subclassed";
30 		static immutable class_t     = "templated functions declared within a class are never virtual";
31 		static immutable class_p     = "private functions declared within a class are never virtual";
32 		static immutable class_f     = "functions declared within a final class are never virtual";
33 		static immutable class_s     = "static functions are never virtual";
34 		static immutable interface_t = "templated functions declared within an interface are never virtual";
35 		static immutable struct_f    = "functions declared within a struct are never virtual";
36 		static immutable union_f     = "functions declared within an union are never virtual";
37 		static immutable func_n      = "nested functions are never virtual";
38 		static immutable func_g      = "global functions are never virtual";
39 	}
40 
41 	enum Parent
42 	{
43 		module_,
44 		struct_,
45 		union_,
46 		class_,
47 		function_,
48 		interface_
49 	}
50 
51 	bool[] _private;
52 	bool _finalAggregate;
53 	bool _alwaysStatic;
54 	bool _blockStatic;
55 	Parent _parent = Parent.module_;
56 
57 	void addError(T)(T t, string msg)
58 	{
59 		import std.format : format;
60 		const size_t lne = t.name.line;
61 		const size_t col = t.name.column;
62 		addErrorMessage(lne, col, KEY, MSGB.format(msg));
63 	}
64 
65 public:
66 
67 	alias visit = BaseAnalyzer.visit;
68 
69 	///
70 	this(string fileName, bool skipTests = false)
71 	{
72 		super(fileName, null, skipTests);
73 		_private.length = 1;
74 	}
75 
76 	override void visit(const(StructDeclaration) sd)
77 	{
78 		const Parent saved = _parent;
79 		_parent = Parent.struct_;
80 		_private.length += 1;
81 		_alwaysStatic = false;
82 		sd.accept(this);
83 		_private.length -= 1;
84 		_parent = saved;
85 	}
86 
87 	override void visit(const(InterfaceDeclaration) id)
88 	{
89 		const Parent saved = _parent;
90 		_parent = Parent.interface_;
91 		_private.length += 1;
92 		_alwaysStatic = false;
93 		id.accept(this);
94 		_private.length -= 1;
95 		_parent = saved;
96 	}
97 
98 	override void visit(const(UnionDeclaration) ud)
99 	{
100 		const Parent saved = _parent;
101 		_parent = Parent.union_;
102 		_private.length += 1;
103 		_alwaysStatic = false;
104 		ud.accept(this);
105 		_private.length -= 1;
106 		_parent = saved;
107 	}
108 
109 	override void visit(const(ClassDeclaration) cd)
110 	{
111 		const Parent saved = _parent;
112 		_parent = Parent.class_;
113 		_private.length += 1;
114 		_alwaysStatic = false;
115 		cd.accept(this);
116 		_private.length -= 1;
117 		_parent = saved;
118 	}
119 
120 	override void visit(const(MixinTemplateDeclaration) mtd)
121 	{
122 		// can't really know where it'll be mixed (class |final class | struct ?)
123 	}
124 
125 	override void visit(const(TemplateDeclaration) mtd)
126 	{
127 		// regular template are also mixable
128 	}
129 
130 	override void visit(const(AttributeDeclaration) decl)
131 	{
132 		if (_parent == Parent.class_ && decl.attribute &&
133 			decl.attribute.attribute == tok!"static")
134 				_alwaysStatic = true;
135 	}
136 
137 	override void visit(const(Declaration) d)
138 	{
139 		import std.algorithm.iteration : filter;
140 		import std.algorithm.searching : canFind;
141 
142 		const Parent savedParent = _parent;
143 
144 		bool undoBlockStatic;
145 		if (_parent == Parent.class_ && d.attributes &&
146 			d.attributes.canFind!(a => a.attribute == tok!"static"))
147 		{
148 			_blockStatic = true;
149 			undoBlockStatic = true;
150 		}
151 
152 		scope(exit)
153 		{
154 			d.accept(this);
155 			_parent = savedParent;
156 			if (undoBlockStatic)
157 				_blockStatic = false;
158 		}
159 
160 		if (!d.attributeDeclaration &&
161 			!d.classDeclaration &&
162 			!d.structDeclaration &&
163 			!d.unionDeclaration &&
164 			!d.interfaceDeclaration &&
165 			!d.functionDeclaration)
166 				return;
167 
168 		if (d.attributeDeclaration && d.attributeDeclaration.attribute)
169 		{
170 			const tp = d.attributeDeclaration.attribute.attribute.type;
171 			_private[$-1] = isProtection(tp) & (tp == tok!"private");
172 		}
173 
174 		const bool isFinal = d.attributes
175 			.canFind!(a => a.attribute.type == tok!"final");
176 
177 		const bool isStaticOnce = d.attributes
178 			.canFind!(a => a.attribute.type == tok!"static");
179 
180 		// determine if private
181 		const bool changeProtectionOnce = d.attributes
182 			.canFind!(a => a.attribute.type.isProtection);
183 
184 		const bool isPrivateOnce = d.attributes
185 			.canFind!(a => a.attribute.type == tok!"private");
186 
187 		bool isPrivate;
188 
189 		if (isPrivateOnce)
190 			isPrivate = true;
191 		else if (_private[$-1] && !changeProtectionOnce)
192 			isPrivate = true;
193 
194 		// check final aggregate type
195 		if (d.classDeclaration || d.structDeclaration || d.unionDeclaration)
196 		{
197 			_finalAggregate = isFinal;
198 			if (_finalAggregate && savedParent == Parent.module_)
199 			{
200 				if (d.structDeclaration)
201 					addError(d.structDeclaration, MESSAGE.struct_i);
202 				else if (d.unionDeclaration)
203 					addError(d.unionDeclaration, MESSAGE.union_i);
204 			}
205 		}
206 
207 		if (!d.functionDeclaration)
208 			return;
209 
210 		// check final functions
211 		_parent = Parent.function_;
212 		const(FunctionDeclaration) fd = d.functionDeclaration;
213 
214 		if (isFinal) final switch(savedParent)
215 		{
216 		case Parent.class_:
217 			if (fd.templateParameters)
218 				addError(fd, MESSAGE.class_t);
219 			if (isPrivate)
220 				addError(fd, MESSAGE.class_p);
221 			else if (isStaticOnce || _alwaysStatic || _blockStatic)
222 				addError(fd, MESSAGE.class_s);
223 			else if (_finalAggregate)
224 				addError(fd, MESSAGE.class_f);
225 			break;
226 		case Parent.interface_:
227 			if (fd.templateParameters)
228 				addError(fd, MESSAGE.interface_t);
229 			break;
230 		case Parent.struct_:
231 			addError(fd, MESSAGE.struct_f);
232 			break;
233 		case Parent.union_:
234 			addError(fd, MESSAGE.union_f);
235 			break;
236 		case Parent.function_:
237 			addError(fd, MESSAGE.func_n);
238 			break;
239 		case Parent.module_:
240 			addError(fd, MESSAGE.func_g);
241 			break;
242 		}
243 	}
244 }
245 
246 @system unittest
247 {
248 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
249 	import dscanner.analysis.helpers : assertAnalyzerWarnings;
250 	import std.stdio : stderr;
251 	import std.format : format;
252 
253 	StaticAnalysisConfig sac = disabledConfig();
254 	sac.final_attribute_check = Check.enabled;
255 
256 	// pass
257 
258 	assertAnalyzerWarnings(q{
259 		void foo(){}
260 	}, sac);
261 
262 	assertAnalyzerWarnings(q{
263 		void foo(){void foo(){}}
264 	}, sac);
265 
266 	assertAnalyzerWarnings(q{
267 		struct S{}
268 	}, sac);
269 
270 	assertAnalyzerWarnings(q{
271 		union U{}
272 	}, sac);
273 
274 	assertAnalyzerWarnings(q{
275 		class Foo{public final void foo(){}}
276 	}, sac);
277 
278 	assertAnalyzerWarnings(q{
279 		final class Foo{static struct Bar{}}
280 	}, sac);
281 
282 	assertAnalyzerWarnings(q{
283 		class Foo{private: public final void foo(){}}
284 	}, sac);
285 
286 	assertAnalyzerWarnings(q{
287 		class Foo{private: public: final void foo(){}}
288 	}, sac);
289 
290 	assertAnalyzerWarnings(q{
291 		class Foo{private: public: final void foo(){}}
292 	}, sac);
293 
294 	assertAnalyzerWarnings(q{
295 		class Impl
296 		{
297 			private:
298 			static if (true)
299 			{
300 				protected final void _wrap_getSource() {}
301 			}
302 		}
303 	}, sac);
304 
305 	assertAnalyzerWarnings(q{
306 		mixin template Impl()
307 		{
308 			protected final void mixin_template_can() {}
309 		}
310 	}, sac);
311 
312 	// fail
313 
314 	assertAnalyzerWarnings(q{
315 		final void foo(){} // [warn]: %s
316 	}c.format(
317 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.func_g)
318 	), sac);
319 
320 	assertAnalyzerWarnings(q{
321 		void foo(){final void foo(){}} // [warn]: %s
322 	}c.format(
323 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.func_n)
324 	), sac);
325 
326 	assertAnalyzerWarnings(q{
327 		void foo()
328 		{
329 			static if (true)
330 			final class A{ private: final protected void foo(){}} // [warn]: %s
331 		}
332 	}c.format(
333 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_f)
334 	), sac);
335 
336 	assertAnalyzerWarnings(q{
337 		final struct Foo{} // [warn]: %s
338 	}c.format(
339 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.struct_i)
340 	), sac);
341 
342 	assertAnalyzerWarnings(q{
343 		final union Foo{} // [warn]: %s
344 	}c.format(
345 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.union_i)
346 	), sac);
347 
348 	assertAnalyzerWarnings(q{
349 		class Foo{private final void foo(){}} // [warn]: %s
350 	}c.format(
351 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p)
352 	), sac);
353 
354 	assertAnalyzerWarnings(q{
355 		class Foo{private: final void foo(){}} // [warn]: %s
356 	}c.format(
357 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p)
358 	), sac);
359 
360 	assertAnalyzerWarnings(q{
361 		interface Foo{final void foo(T)(){}} // [warn]: %s
362 	}c.format(
363 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.interface_t)
364 	), sac);
365 
366 	assertAnalyzerWarnings(q{
367 		final class Foo{final void foo(){}} // [warn]: %s
368 	}c.format(
369 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_f)
370 	), sac);
371 
372 	assertAnalyzerWarnings(q{
373 		private: final class Foo {public: private final void foo(){}} // [warn]: %s
374 	}c.format(
375 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p)
376 	), sac);
377 
378 	assertAnalyzerWarnings(q{
379 		class Foo {final static void foo(){}} // [warn]: %s
380 	}c.format(
381 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
382 	), sac);
383 
384 	assertAnalyzerWarnings(q{
385 		class Foo
386 		{
387 			void foo(){}
388 			static: final void foo(){} // [warn]: %s
389 		}
390 	}c.format(
391 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
392 	), sac);
393 
394 	assertAnalyzerWarnings(q{
395 		class Foo
396 		{
397 			void foo(){}
398 			static{ final void foo(){}} // [warn]: %s
399 			void foo(){}
400 		}
401 	}c.format(
402 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
403 	), sac);
404 
405 	stderr.writeln("Unittest for FinalAttributeChecker passed.");
406 }