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 }