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 }