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