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