1 // Distributed under the Boost Software License, Version 1.0. 2 // (See accompanying file LICENSE_1_0.txt or copy at 3 // http://www.boost.org/LICENSE_1_0.txt) 4 5 module dscanner.analysis.redundant_attributes; 6 7 import dparse.ast; 8 import dparse.lexer; 9 import dsymbol.scope_ : Scope; 10 import dscanner.analysis.base; 11 import dscanner.analysis.helpers; 12 13 import std.algorithm; 14 import std.conv : to, text; 15 import std.range : empty, front, walkLength; 16 17 /** 18 * Checks for redundant attributes. At the moment only visibility attributes. 19 */ 20 class RedundantAttributesCheck : BaseAnalyzer 21 { 22 this(string fileName, const(Scope)* sc, bool skipTests = false) 23 { 24 super(fileName, sc, skipTests); 25 stack.length = 0; 26 } 27 28 override void visit(const Declaration decl) 29 { 30 31 // labels, e.g. private: 32 if (auto attr = decl.attributeDeclaration) 33 { 34 if (filterAttributes(attr.attribute)) 35 { 36 addAttribute(attr.attribute); 37 } 38 } 39 40 auto attributes = decl.attributes.filter!(a => filterAttributes(a)); 41 if (attributes.walkLength > 0) { 42 43 // new scope: private { } 44 if (decl.declarations.length > 0) 45 { 46 const prev = currentAttributes[]; 47 // append to current scope and reset once block is left 48 foreach (attr; attributes) 49 addAttribute(attr); 50 51 scope(exit) currentAttributes = prev; 52 decl.accept(this); 53 } // declarations, e.g. private int ... 54 else 55 { 56 foreach (attr; attributes) 57 checkAttribute(attr); 58 59 decl.accept(this); 60 } 61 } 62 else 63 { 64 decl.accept(this); 65 } 66 } 67 68 alias visit = BaseAnalyzer.visit; 69 70 mixin ScopedVisit!Module; 71 mixin ScopedVisit!BlockStatement; 72 mixin ScopedVisit!StructBody; 73 mixin ScopedVisit!CaseStatement; 74 mixin ScopedVisit!ForStatement; 75 mixin ScopedVisit!IfStatement; 76 mixin ScopedVisit!TemplateDeclaration; 77 mixin ScopedVisit!ConditionalDeclaration; 78 79 private: 80 81 alias ConstAttribute = const Attribute; 82 alias CurrentScope = ConstAttribute[]; 83 ref CurrentScope currentAttributes() 84 { 85 return stack[$ - 1]; 86 } 87 88 CurrentScope[] stack; 89 90 void addAttribute(const Attribute attr) 91 { 92 removeOverwrite(attr); 93 if (checkAttribute(attr)) 94 { 95 currentAttributes ~= attr; 96 } 97 } 98 99 bool checkAttribute(const Attribute attr) 100 { 101 auto match = currentAttributes.find!(a => a.attribute.type == attr.attribute.type); 102 if (!match.empty) 103 { 104 auto token = attr.attribute; 105 addErrorMessage(token.line, token.column, KEY, 106 text("same visibility attribute used as defined on line ", 107 match.front.attribute.line.to!string, ".")); 108 return false; 109 } 110 return true; 111 } 112 113 void removeOverwrite(const Attribute attr) 114 { 115 import std.array : array; 116 const group = getAttributeGroup(attr); 117 if (currentAttributes.filter!(a => getAttributeGroup(a) == group 118 && !isIdenticalAttribute(a, attr)).walkLength > 0) 119 { 120 currentAttributes = currentAttributes.filter!(a => getAttributeGroup(a) != group 121 || isIdenticalAttribute(a, attr)).array; 122 } 123 } 124 125 bool filterAttributes(const Attribute attr) 126 { 127 return isAccessSpecifier(attr); 128 } 129 130 static int getAttributeGroup(const Attribute attr) 131 { 132 if (isAccessSpecifier(attr)) 133 return 1; 134 135 // TODO: not implemented 136 return attr.attribute.type; 137 } 138 139 static bool isAccessSpecifier(const Attribute attr) 140 { 141 auto type = attr.attribute.type; 142 return type.among(tok!"private", tok!"protected", tok!"public", tok!"package", tok!"export") > 0; 143 } 144 145 static bool isIdenticalAttribute(const Attribute a, const Attribute b) 146 { 147 return a.attribute.type == b.attribute.type; 148 } 149 150 auto attributesString() 151 { 152 return currentAttributes.map!(a => a.attribute.type.str).joiner(",").to!string; 153 } 154 155 template ScopedVisit(NodeType) 156 { 157 override void visit(const NodeType n) 158 { 159 pushScope(); 160 n.accept(this); 161 popScope(); 162 } 163 } 164 165 void pushScope() 166 { 167 stack.length++; 168 } 169 170 void popScope() 171 { 172 stack.length--; 173 } 174 175 enum string KEY = "dscanner.suspicious.redundant_attributes"; 176 } 177 178 179 version(unittest) 180 { 181 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 182 import std.stdio : stderr; 183 } 184 185 unittest 186 { 187 StaticAnalysisConfig sac = disabledConfig(); 188 sac.redundant_attributes_check = Check.enabled; 189 190 // test labels vs. block attributes 191 assertAnalyzerWarnings(q{ 192 unittest 193 { 194 private: 195 private int blah; // [warn]: same visibility attribute used as defined on line 4. 196 protected 197 { 198 protected int blah; // [warn]: same visibility attribute used as defined on line 6. 199 } 200 private int blah; // [warn]: same visibility attribute used as defined on line 4. 201 }}c, sac); 202 203 // test labels vs. block attributes 204 assertAnalyzerWarnings(q{ 205 unittest 206 { 207 private: 208 private: // [warn]: same visibility attribute used as defined on line 4. 209 public: 210 private int a; 211 public int b; // [warn]: same visibility attribute used as defined on line 6. 212 public // [warn]: same visibility attribute used as defined on line 6. 213 { 214 int c; 215 } 216 }}c, sac); 217 218 // test scopes 219 assertAnalyzerWarnings(q{ 220 unittest 221 { 222 private: 223 private int foo2; // [warn]: same visibility attribute used as defined on line 4. 224 private void foo() // [warn]: same visibility attribute used as defined on line 4. 225 { 226 private int blah; 227 } 228 }}c, sac); 229 230 // check duplicated visibility attributes 231 assertAnalyzerWarnings(q{ 232 unittest 233 { 234 private: 235 public int a; 236 private: // [warn]: same visibility attribute used as defined on line 4. 237 }}c, sac); 238 239 // test conditional compilation 240 assertAnalyzerWarnings(q{ 241 unittest 242 { 243 version(unittest) 244 { 245 private: 246 private int foo; // [warn]: same visibility attribute used as defined on line 6. 247 } 248 private int foo2; 249 }}c, sac); 250 251 // test scopes 252 assertAnalyzerWarnings(q{ 253 unittest 254 { 255 public: 256 if (1 == 1) 257 { 258 private int b; 259 } 260 else 261 { 262 public int b; 263 } 264 public int b; // [warn]: same visibility attribute used as defined on line 4. 265 }}c, sac); 266 } 267 268 // test other attribute (not yet implemented, thus shouldn't trigger warnings) 269 unittest 270 { 271 StaticAnalysisConfig sac = disabledConfig(); 272 sac.redundant_attributes_check = Check.enabled; 273 274 // test labels vs. block attributes 275 assertAnalyzerWarnings(q{ 276 unittest 277 { 278 @safe: 279 @safe void foo(); 280 @system 281 { 282 @system void foo(); 283 } 284 @safe void foo(); 285 }}c, sac); 286 287 288 stderr.writeln("Unittest for RedundantAttributesCheck passed."); 289 }