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     }}.format(
317         FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.func_g)
318     ), sac);
319 
320     assertAnalyzerWarnings(q{
321         void foo(){final void foo(){}} // [warn]: %s
322     }}.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     }}.format(
333         FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_f)
334     ), sac);
335 
336     assertAnalyzerWarnings(q{
337         final struct Foo{} // [warn]: %s
338     }}.format(
339         FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.struct_i)
340     ), sac);
341 
342     assertAnalyzerWarnings(q{
343         final union Foo{} // [warn]: %s
344     }}.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     }}.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     }}.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     }}.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     }}.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     }}.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     }}.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     }}.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     }}.format(
402         FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
403     ), sac);
404 
405     stderr.writeln("Unittest for FinalAttributeChecker passed.");
406 }