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