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