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 	enum pushPopPrivate = q{
70 		const bool wasPrivate = _private;
71 		_private = false;
72 		scope (exit) _private = wasPrivate;
73 	};
74 
75 	///
76 	this(string fileName, bool skipTests = false)
77 	{
78 		super(fileName, null, skipTests);
79 	}
80 
81 	override void visit(const(StructDeclaration) sd)
82 	{
83 		mixin (pushPopPrivate);
84 		const Parent saved = _parent;
85 		_parent = Parent.struct_;
86 		_alwaysStatic = false;
87 		sd.accept(this);
88 		_parent = saved;
89 	}
90 
91 	override void visit(const(InterfaceDeclaration) id)
92 	{
93 		mixin (pushPopPrivate);
94 		const Parent saved = _parent;
95 		_parent = Parent.interface_;
96 		_alwaysStatic = false;
97 		id.accept(this);
98 		_parent = saved;
99 	}
100 
101 	override void visit(const(UnionDeclaration) ud)
102 	{
103 		mixin (pushPopPrivate);
104 		const Parent saved = _parent;
105 		_parent = Parent.union_;
106 		_alwaysStatic = false;
107 		ud.accept(this);
108 		_parent = saved;
109 	}
110 
111 	override void visit(const(ClassDeclaration) cd)
112 	{
113 		mixin (pushPopPrivate);
114 		const Parent saved = _parent;
115 		_parent = Parent.class_;
116 		_alwaysStatic = false;
117 		cd.accept(this);
118 		_parent = saved;
119 	}
120 
121 	override void visit(const(MixinTemplateDeclaration) mtd)
122 	{
123 		// can't really know where it'll be mixed (class |final class | struct ?)
124 	}
125 
126 	override void visit(const(TemplateDeclaration) mtd)
127 	{
128 		// regular template are also mixable
129 	}
130 
131 	override void visit(const(AttributeDeclaration) decl)
132 	{
133 		if (_parent == Parent.class_ && decl.attribute &&
134 			decl.attribute.attribute == tok!"static")
135 				_alwaysStatic = true;
136 	}
137 
138 	override void visit(const(Declaration) d)
139 	{
140 		import std.algorithm.iteration : filter;
141 		import std.algorithm.searching : canFind;
142 
143 		const Parent savedParent = _parent;
144 
145 		bool undoBlockStatic;
146 		if (_parent == Parent.class_ && d.attributes &&
147 			d.attributes.canFind!(a => a.attribute == tok!"static"))
148 		{
149 			_blockStatic = true;
150 			undoBlockStatic = true;
151 		}
152 
153 		const bool wasFinalAggr = _finalAggregate;
154 		scope(exit)
155 		{
156 			d.accept(this);
157 			_parent = savedParent;
158 			if (undoBlockStatic)
159 				_blockStatic = false;
160 			_finalAggregate = wasFinalAggr;
161 		}
162 
163 		if (!d.attributeDeclaration &&
164 			!d.classDeclaration &&
165 			!d.structDeclaration &&
166 			!d.unionDeclaration &&
167 			!d.interfaceDeclaration &&
168 			!d.functionDeclaration)
169 				return;
170 
171 		if (d.attributeDeclaration && d.attributeDeclaration.attribute)
172 		{
173 			const tp = d.attributeDeclaration.attribute.attribute.type;
174 			_private = isProtection(tp) & (tp == tok!"private");
175 		}
176 
177 		const bool isFinal = d.attributes
178 			.canFind!(a => a.attribute.type == tok!"final");
179 
180 		const bool isStaticOnce = d.attributes
181 			.canFind!(a => a.attribute.type == tok!"static");
182 
183 		// determine if private
184 		const bool changeProtectionOnce = d.attributes
185 			.canFind!(a => a.attribute.type.isProtection);
186 
187 		const bool isPrivateOnce = d.attributes
188 			.canFind!(a => a.attribute.type == tok!"private");
189 
190 		bool isPrivate;
191 
192 		if (isPrivateOnce)
193 			isPrivate = true;
194 		else if (_private && !changeProtectionOnce)
195 			isPrivate = true;
196 
197 		// check final aggregate type
198 		if (d.classDeclaration || d.structDeclaration || d.unionDeclaration)
199 		{
200 			_finalAggregate = isFinal;
201 			if (_finalAggregate && savedParent == Parent.module_)
202 			{
203 				if (d.structDeclaration)
204 					addError(d.structDeclaration, MESSAGE.struct_i);
205 				else if (d.unionDeclaration)
206 					addError(d.unionDeclaration, MESSAGE.union_i);
207 			}
208 		}
209 
210 		if (!d.functionDeclaration)
211 			return;
212 
213 		// check final functions
214 		_parent = Parent.function_;
215 		const(FunctionDeclaration) fd = d.functionDeclaration;
216 
217 		if (isFinal) final switch(savedParent)
218 		{
219 		case Parent.class_:
220 			if (fd.templateParameters)
221 				addError(fd, MESSAGE.class_t);
222 			if (isPrivate)
223 				addError(fd, MESSAGE.class_p);
224 			else if (isStaticOnce || _alwaysStatic || _blockStatic)
225 				addError(fd, MESSAGE.class_s);
226 			else if (_finalAggregate)
227 				addError(fd, MESSAGE.class_f);
228 			break;
229 		case Parent.interface_:
230 			if (fd.templateParameters)
231 				addError(fd, MESSAGE.interface_t);
232 			break;
233 		case Parent.struct_:
234 			addError(fd, MESSAGE.struct_f);
235 			break;
236 		case Parent.union_:
237 			addError(fd, MESSAGE.union_f);
238 			break;
239 		case Parent.function_:
240 			addError(fd, MESSAGE.func_n);
241 			break;
242 		case Parent.module_:
243 			addError(fd, MESSAGE.func_g);
244 			break;
245 		}
246 	}
247 }
248 
249 @system unittest
250 {
251 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
252 	import dscanner.analysis.helpers : assertAnalyzerWarnings;
253 	import std.stdio : stderr;
254 	import std.format : format;
255 
256 	StaticAnalysisConfig sac = disabledConfig();
257 	sac.final_attribute_check = Check.enabled;
258 
259 	// pass
260 
261 	assertAnalyzerWarnings(q{
262 		void foo(){}
263 	}, sac);
264 
265 	assertAnalyzerWarnings(q{
266 		void foo(){void foo(){}}
267 	}, sac);
268 
269 	assertAnalyzerWarnings(q{
270 		struct S{}
271 	}, sac);
272 
273 	assertAnalyzerWarnings(q{
274 		union U{}
275 	}, sac);
276 
277 	assertAnalyzerWarnings(q{
278 		class Foo{public final void foo(){}}
279 	}, sac);
280 
281 	assertAnalyzerWarnings(q{
282 		final class Foo{static struct Bar{}}
283 	}, sac);
284 
285 	assertAnalyzerWarnings(q{
286 		class Foo{private: public final void foo(){}}
287 	}, sac);
288 
289 	assertAnalyzerWarnings(q{
290 		class Foo{private: public: final void foo(){}}
291 	}, sac);
292 
293 	assertAnalyzerWarnings(q{
294 		class Foo{private: public: final void foo(){}}
295 	}, sac);
296 
297 	assertAnalyzerWarnings(q{
298 		class Impl
299 		{
300 			private:
301 			static if (true)
302 			{
303 				protected final void _wrap_getSource() {}
304 			}
305 		}
306 	}, sac);
307 
308 	assertAnalyzerWarnings(q{
309 		mixin template Impl()
310 		{
311 			protected final void mixin_template_can() {}
312 		}
313 	}, sac);
314 
315 	// fail
316 
317 	assertAnalyzerWarnings(q{
318 		final void foo(){} // [warn]: %s
319 	}}.format(
320 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.func_g)
321 	), sac);
322 
323 	assertAnalyzerWarnings(q{
324 		void foo(){final void foo(){}} // [warn]: %s
325 	}}.format(
326 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.func_n)
327 	), sac);
328 
329 	assertAnalyzerWarnings(q{
330 		void foo()
331 		{
332 			static if (true)
333 			final class A{ private: final protected void foo(){}} // [warn]: %s
334 		}
335 	}}.format(
336 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_f)
337 	), sac);
338 
339 	assertAnalyzerWarnings(q{
340 		final struct Foo{} // [warn]: %s
341 	}}.format(
342 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.struct_i)
343 	), sac);
344 
345 	assertAnalyzerWarnings(q{
346 		final union Foo{} // [warn]: %s
347 	}}.format(
348 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.union_i)
349 	), sac);
350 
351 	assertAnalyzerWarnings(q{
352 		class Foo{private final void foo(){}} // [warn]: %s
353 	}}.format(
354 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p)
355 	), sac);
356 
357 	assertAnalyzerWarnings(q{
358 		class Foo{private: final void foo(){}} // [warn]: %s
359 	}}.format(
360 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p)
361 	), sac);
362 
363 	assertAnalyzerWarnings(q{
364 		interface Foo{final void foo(T)(){}} // [warn]: %s
365 	}}.format(
366 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.interface_t)
367 	), sac);
368 
369 	assertAnalyzerWarnings(q{
370 		final class Foo{final void foo(){}} // [warn]: %s
371 	}}.format(
372 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_f)
373 	), sac);
374 
375 	assertAnalyzerWarnings(q{
376 		private: final class Foo {public: private final void foo(){}} // [warn]: %s
377 	}}.format(
378 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p)
379 	), sac);
380 
381 	assertAnalyzerWarnings(q{
382 		class Foo {final static void foo(){}} // [warn]: %s
383 	}}.format(
384 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
385 	), sac);
386 
387 	assertAnalyzerWarnings(q{
388 		class Foo
389 		{
390 			void foo(){}
391 			static: final void foo(){} // [warn]: %s
392 		}
393 	}}.format(
394 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
395 	), sac);
396 
397 	assertAnalyzerWarnings(q{
398 		class Foo
399 		{
400 			void foo(){}
401 			static{ final void foo(){}} // [warn]: %s
402 			void foo(){}
403 		}
404 	}}.format(
405 		FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
406 	), sac);
407 
408 
409 	assertAnalyzerWarnings(q{
410 		class Statement
411 		{
412 			final class UsesEH{}
413 			final void comeFrom(){}
414 		}
415 	}, sac);
416 
417 	stderr.writeln("Unittest for FinalAttributeChecker passed.");
418 }