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