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.label_var_same_name_check; 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 /** 14 * Checks for labels and variables that have the same name. 15 */ 16 class LabelVarNameCheck : BaseAnalyzer 17 { 18 this(string fileName, const(Scope)* sc, bool skipTests = false) 19 { 20 super(fileName, sc, skipTests); 21 } 22 23 mixin ScopedVisit!Module; 24 mixin ScopedVisit!BlockStatement; 25 mixin ScopedVisit!StructBody; 26 mixin ScopedVisit!CaseStatement; 27 mixin ScopedVisit!ForStatement; 28 mixin ScopedVisit!IfStatement; 29 mixin ScopedVisit!TemplateDeclaration; 30 31 mixin AggregateVisit!ClassDeclaration; 32 mixin AggregateVisit!StructDeclaration; 33 mixin AggregateVisit!InterfaceDeclaration; 34 mixin AggregateVisit!UnionDeclaration; 35 36 override void visit(const VariableDeclaration var) 37 { 38 foreach (dec; var.declarators) 39 duplicateCheck(dec.name, false, conditionalDepth > 0); 40 } 41 42 override void visit(const LabeledStatement labeledStatement) 43 { 44 duplicateCheck(labeledStatement.identifier, true, conditionalDepth > 0); 45 if (labeledStatement.declarationOrStatement !is null) 46 labeledStatement.declarationOrStatement.accept(this); 47 } 48 49 override void visit(const ConditionalDeclaration condition) 50 { 51 if (condition.falseDeclarations.length > 0) 52 ++conditionalDepth; 53 condition.accept(this); 54 if (condition.falseDeclarations.length > 0) 55 --conditionalDepth; 56 } 57 58 override void visit(const VersionCondition condition) 59 { 60 ++conditionalDepth; 61 condition.accept(this); 62 --conditionalDepth; 63 } 64 65 alias visit = BaseAnalyzer.visit; 66 67 private: 68 69 Thing[string][] stack; 70 71 template AggregateVisit(NodeType) 72 { 73 override void visit(const NodeType n) 74 { 75 pushAggregateName(n.name); 76 n.accept(this); 77 popAggregateName(); 78 } 79 } 80 81 template ScopedVisit(NodeType) 82 { 83 override void visit(const NodeType n) 84 { 85 pushScope(); 86 n.accept(this); 87 popScope(); 88 } 89 } 90 91 void duplicateCheck(const Token name, bool fromLabel, bool isConditional) 92 { 93 import std.conv : to; 94 import std.range : retro; 95 96 size_t i; 97 foreach (s; retro(stack)) 98 { 99 string fqn = parentAggregateText ~ name.text; 100 const(Thing)* thing = fqn in s; 101 if (thing is null) 102 currentScope[fqn] = Thing(fqn, name.line, name.column, !fromLabel /+, isConditional+/ ); 103 else if (i != 0 || !isConditional) 104 { 105 immutable thisKind = fromLabel ? "Label" : "Variable"; 106 immutable otherKind = thing.isVar ? "variable" : "label"; 107 addErrorMessage(name.line, name.column, "dscanner.suspicious.label_var_same_name", 108 thisKind ~ " \"" ~ fqn ~ "\" has the same name as a " 109 ~ otherKind ~ " defined on line " ~ to!string(thing.line) ~ "."); 110 } 111 ++i; 112 } 113 } 114 115 static struct Thing 116 { 117 string name; 118 size_t line; 119 size_t column; 120 bool isVar; 121 //bool isConditional; 122 } 123 124 ref currentScope() @property 125 { 126 return stack[$ - 1]; 127 } 128 129 void pushScope() 130 { 131 stack.length++; 132 } 133 134 void popScope() 135 { 136 stack.length--; 137 } 138 139 int conditionalDepth; 140 141 void pushAggregateName(Token name) 142 { 143 parentAggregates ~= name; 144 updateAggregateText(); 145 } 146 147 void popAggregateName() 148 { 149 parentAggregates.length -= 1; 150 updateAggregateText(); 151 } 152 153 void updateAggregateText() 154 { 155 import std.algorithm : map; 156 import std.array : join; 157 158 if (parentAggregates.length) 159 parentAggregateText = parentAggregates.map!(a => a.text).join(".") ~ "."; 160 else 161 parentAggregateText = ""; 162 } 163 164 Token[] parentAggregates; 165 string parentAggregateText; 166 } 167 168 unittest 169 { 170 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 171 import std.stdio : stderr; 172 173 StaticAnalysisConfig sac = disabledConfig(); 174 sac.label_var_same_name_check = Check.enabled; 175 assertAnalyzerWarnings(q{ 176 unittest 177 { 178 blah: 179 int blah; // [warn]: Variable "blah" has the same name as a label defined on line 4. 180 } 181 int blah; 182 unittest 183 { 184 static if (stuff) 185 int a; 186 int a; // [warn]: Variable "a" has the same name as a variable defined on line 11. 187 } 188 189 unittest 190 { 191 static if (stuff) 192 int a = 10; 193 else 194 int a = 20; 195 } 196 197 unittest 198 { 199 static if (stuff) 200 int a = 10; 201 else 202 int a = 20; 203 int a; // [warn]: Variable "a" has the same name as a variable defined on line 28. 204 } 205 template T(stuff) 206 { 207 int b; 208 } 209 210 void main(string[] args) 211 { 212 for (int a = 0; a < 10; a++) 213 things(a); 214 215 for (int a = 0; a < 10; a++) 216 things(a); 217 int b; 218 } 219 220 unittest 221 { 222 version (Windows) 223 int c = 10; 224 else 225 int c = 20; 226 int c; // [warn]: Variable "c" has the same name as a variable defined on line 51. 227 } 228 229 unittest 230 { 231 version(LittleEndian) { enum string NAME = "UTF-16LE"; } 232 else version(BigEndian) { enum string NAME = "UTF-16BE"; } 233 } 234 235 unittest 236 { 237 int a; 238 struct A {int a;} 239 } 240 241 unittest 242 { 243 int a; 244 struct A { struct A {int a;}} 245 } 246 247 unittest 248 { 249 int a; 250 class A { class A {int a;}} 251 } 252 253 unittest 254 { 255 int a; 256 interface A { interface A {int a;}} 257 } 258 259 unittest 260 { 261 interface A 262 { 263 int a; 264 int a; // [warn]: Variable "A.a" has the same name as a variable defined on line 89. 265 } 266 } 267 268 unittest 269 { 270 int aa; 271 struct a { int a; } 272 } 273 274 }c, sac); 275 stderr.writeln("Unittest for LabelVarNameCheck passed."); 276 }