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