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 }