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 //
5 module dscanner.analysis.vcall_in_ctor;
6 
7 import dscanner.analysis.base;
8 import dscanner.utils;
9 import dparse.ast, dparse.lexer;
10 import std.algorithm: among;
11 import std.algorithm.iteration : filter;
12 import std.algorithm.searching : find;
13 import std.range.primitives : empty;
14 import std.range: retro;
15 
16 /**
17  * Checks virtual calls from the constructor to methods defined in the same class.
18  *
19  * When not used carefully, virtual calls from constructors can lead to a call
20  * in a derived instance that's not yet constructed.
21  */
22 class VcallCtorChecker : BaseAnalyzer
23 {
24     alias visit = BaseAnalyzer.visit;
25 
26 private:
27 
28     enum string KEY = "dscanner.vcall_ctor";
29     enum string MSG = "a virtual call inside a constructor may lead to"
30         ~ " unexpected results in the derived classes";
31 
32     // what's called in the ctor
33     Token[][] _ctorCalls;
34     // the virtual method in the classes
35     Token[][] _virtualMethods;
36 
37 
38     // The problem only happens in classes
39     bool[] _inClass = [false];
40     // The problem only happens in __ctor
41     bool[] _inCtor = [false];
42     // The problem only happens with call to virtual methods
43     bool[] _isVirtual = [true];
44     // The problem only happens with call to virtual methods
45     bool[] _isNestedFun = [false];
46     // The problem only happens in derived classes that override
47     bool[] _isFinal = [false];
48 
49     void pushVirtual(bool value)
50     {
51         _isVirtual ~= value;
52     }
53 
54     void pushInClass(bool value)
55     {
56         _inClass ~= value;
57         _ctorCalls.length += 1;
58         _virtualMethods.length += 1;
59     }
60 
61     void pushInCtor(bool value)
62     {
63         _inCtor ~= value;
64     }
65 
66     void pushNestedFunc(bool value)
67     {
68         _isNestedFun ~= value;
69     }
70 
71     void pushIsFinal(bool value)
72     {
73         _isFinal ~= value;
74     }
75 
76     void popVirtual()
77     {
78         _isVirtual.length -= 1;
79     }
80 
81     void popInClass()
82     {
83         _inClass.length -= 1;
84         _ctorCalls.length -= 1;
85         _virtualMethods.length -= 1;
86     }
87 
88     void popInCtor()
89     {
90         _inCtor.length -= 1;
91     }
92 
93     void popNestedFunc()
94     {
95         _isNestedFun.length -= 1;
96     }
97 
98     void popIsFinal()
99     {
100         _isFinal.length -= 1;
101     }
102 
103     void overwriteVirtual(bool value)
104     {
105         _isVirtual[$-1] = value;
106     }
107 
108     bool isVirtual()
109     {
110         return _isVirtual[$-1];
111     }
112 
113     bool isInClass()
114     {
115         return _inClass[$-1];
116     }
117 
118     bool isInCtor()
119     {
120         return _inCtor[$-1];
121     }
122 
123     bool isFinal()
124     {
125         return _isFinal[$-1];
126     }
127 
128     bool isInNestedFunc()
129     {
130         return _isNestedFun[$-1];
131     }
132 
133     void check()
134     {
135         foreach (call; _ctorCalls[$-1])
136             foreach (vm; _virtualMethods[$-1])
137         {
138             if (call == vm)
139             {
140                 addErrorMessage(call.line, call.column, KEY, MSG);
141                 break;
142             }
143         }
144     }
145 
146 public:
147 
148     ///
149     this(string fileName, bool skipTests = false)
150     {
151         super(fileName, null, skipTests);
152     }
153 
154     override void visit(const(ClassDeclaration) decl)
155     {
156         pushVirtual(true);
157         pushInClass(true);
158         pushNestedFunc(false);
159         decl.accept(this);
160         check();
161         popVirtual();
162         popInClass();
163         popNestedFunc();
164     }
165 
166     override void visit(const(Constructor) ctor)
167     {
168         pushInCtor(isInClass);
169         ctor.accept(this);
170         popInCtor();
171     }
172 
173     override void visit(const(Declaration) d)
174     {
175         // "<protection>:"
176         if (d.attributeDeclaration && d.attributeDeclaration.attribute)
177         {
178             const tp = d.attributeDeclaration.attribute.attribute.type;
179             overwriteVirtual(isProtection(tp) & (tp != tok!"private"));
180         }
181 
182         // "protection {}"
183         bool pop;
184         scope(exit) if (pop)
185             popVirtual;
186         if (d.attributes) foreach (attr; d.attributes.retro)
187         {
188             if (attr.attribute == tok!"public" || attr.attribute == tok!"protected" ||
189                 attr.attribute == tok!"package")
190             {
191                 pushVirtual(true);
192                 pop = true;
193                 break;
194             }
195             else if (attr.attribute == tok!"private")
196             {
197                 pushVirtual(false);
198                 pop = true;
199                 break;
200             }
201         }
202 
203         // final class... final function
204         const bool pf = !d.attributes.find!(a => a.attribute.type == tok!"final").empty;
205         if ((d.classDeclaration || d.functionDeclaration) && pf)
206             pushIsFinal(true);
207 
208         d.accept(this);
209 
210         if ((d.classDeclaration || d.functionDeclaration) && pf)
211             popIsFinal;
212     }
213 
214     override void visit(const(FunctionCallExpression) exp)
215     {
216         // nested function are not virtual
217         pushNestedFunc(true);
218         exp.accept(this);
219         popNestedFunc();
220     }
221 
222     override void visit(const(UnaryExpression) exp)
223     {
224         if (isInCtor)
225         // get function identifier for a call, only for this member (so no ident chain)
226         if (const IdentifierOrTemplateInstance iot = safeAccess(exp)
227             .functionCallExpression.unaryExpression.primaryExpression.identifierOrTemplateInstance)
228         {
229             const Token t = iot.identifier;
230             if (t != tok!"")
231             {
232                 _ctorCalls[$-1] ~= t;
233             }
234         }
235         exp.accept(this);
236     }
237 
238     override void visit(const(FunctionDeclaration) d)
239     {
240         if (isInClass() && !isInNestedFunc() && !isFinal() && !d.templateParameters)
241         {
242             bool virtualOnce;
243             bool notVirtualOnce;
244 
245             // handle "private", "public"... for this declaration
246             if (d.attributes) foreach (attr; d.attributes.retro)
247             {
248                 if (attr.attribute == tok!"public" || attr.attribute ==  tok!"protected" ||
249                     attr.attribute ==  tok!"package")
250                 {
251                     if (!isVirtual)
252                     {
253                         virtualOnce = true;
254                         break;
255                     }
256                 }
257                 else if (attr.attribute == tok!"private")
258                 {
259                     if (isVirtual)
260                     {
261                         notVirtualOnce = true;
262                         break;
263                     }
264                 }
265             }
266 
267             if (!isVirtual && virtualOnce)
268                 _virtualMethods[$-1] ~= d.name;
269             else if (isVirtual && !virtualOnce)
270                 _virtualMethods[$-1] ~= d.name;
271 
272         }
273         d.accept(this);
274     }
275 }
276 
277 unittest
278 {
279     import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
280     import dscanner.analysis.helpers : assertAnalyzerWarnings;
281     import std.stdio : stderr;
282     import std.format : format;
283 
284     StaticAnalysisConfig sac = disabledConfig();
285     sac.vcall_in_ctor = Check.enabled;
286 
287     // fails
288     assertAnalyzerWarnings(q{
289         class Bar
290         {
291             this(){foo();} // [warn]: %s
292             private:
293             public
294             void foo(){}
295 
296         }
297     }c.format(VcallCtorChecker.MSG), sac);
298 
299     assertAnalyzerWarnings(q{
300         class Bar
301         {
302             this()
303             {
304                 foo(); // [warn]: %s
305                 foo(); // [warn]: %s
306                 bar();
307             }
308             private: void bar();
309             public{void foo(){}}
310         }
311     }c.format(VcallCtorChecker.MSG, VcallCtorChecker.MSG), sac);
312 
313     assertAnalyzerWarnings(q{
314         class Bar
315         {
316             this()
317             {
318                 foo();
319                 bar(); // [warn]: %s
320             }
321             private: public void bar();
322             public private {void foo(){}}
323         }
324     }c.format(VcallCtorChecker.MSG), sac);
325 
326     // passes
327     assertAnalyzerWarnings(q{
328         class Bar
329         {
330             this(){foo();}
331             private void foo(){}
332         }
333     }, sac);
334 
335     assertAnalyzerWarnings(q{
336         class Bar
337         {
338             this(){foo();}
339             private {void foo(){}}
340         }
341     }, sac);
342 
343     assertAnalyzerWarnings(q{
344         class Bar
345         {
346             this(){foo();}
347             private public protected private void foo(){}
348         }
349     }, sac);
350 
351     assertAnalyzerWarnings(q{
352         class Bar
353         {
354             this(){foo();}
355             final private public protected void foo(){}
356         }
357     }, sac);
358 
359     assertAnalyzerWarnings(q{
360         class Bar
361         {
362             this(){foo();}
363             final void foo(){}
364         }
365     }, sac);
366 
367     assertAnalyzerWarnings(q{
368         final class Bar
369         {
370             public:
371             this(){foo();}
372             void foo(){}
373         }
374     }, sac);
375 
376     assertAnalyzerWarnings(q{
377         class Bar
378         {
379             public:
380             this(){foo();}
381             void foo(T)(){}
382         }
383     }, sac);
384 
385     import std.stdio: writeln;
386     writeln("Unittest for VcallCtorChecker passed");
387 }
388