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 analysis.label_var_same_name_check;
6 
7 import dparse.ast;
8 import dparse.lexer;
9 
10 import analysis.base;
11 import 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)
19 	{
20 		super(fileName, sc);
21 	}
22 
23 	override void visit(const Module mod)
24 	{
25 		pushScope();
26 		mod.accept(this);
27 		popScope();
28 	}
29 
30 	override void visit(const BlockStatement block)
31 	{
32 		pushScope();
33 		block.accept(this);
34 		popScope();
35 	}
36 
37 	override void visit(const StructBody structBody)
38 	{
39 		pushScope();
40 		structBody.accept(this);
41 		popScope();
42 	}
43 
44 	override void visit(const VariableDeclaration var)
45 	{
46 		foreach (dec; var.declarators)
47 			duplicateCheck(dec.name, false, conditionalDepth > 0);
48 	}
49 
50 	override void visit(const LabeledStatement labeledStatement)
51 	{
52 		duplicateCheck(labeledStatement.identifier, true, conditionalDepth > 0);
53 		if (labeledStatement.declarationOrStatement !is null)
54 			labeledStatement.declarationOrStatement.accept(this);
55 	}
56 
57 	override void visit(const ConditionalDeclaration condition)
58 	{
59 		if (condition.falseDeclarations.length > 0)
60 			++conditionalDepth;
61 		condition.accept(this);
62 		if (condition.falseDeclarations.length > 0)
63 			--conditionalDepth;
64 	}
65 
66 	alias visit = BaseAnalyzer.visit;
67 
68 private:
69 
70 	Thing[string][] stack;
71 
72 	void duplicateCheck(const Token name, bool fromLabel, bool isConditional)
73 	{
74 		import std.conv : to;
75 		import std.range : retro;
76 
77 		size_t i = 0;
78 		foreach (s; retro(stack))
79 		{
80 			const(Thing)* thing = name.text in s;
81 			if (thing is null)
82 				currentScope[name.text] = Thing(name.text, name.line, name.column,
83 					!fromLabel/+, isConditional+/);
84 			else if (i != 0 || !isConditional)
85 			{
86 				immutable thisKind = fromLabel ? "Label" : "Variable";
87 				immutable otherKind = thing.isVar ? "variable" : "label";
88 				addErrorMessage(name.line, name.column, "dscanner.suspicious.label_var_same_name",
89 					thisKind ~ " \"" ~ name.text ~ "\" has the same name as a "
90 					~ otherKind ~ " defined on line " ~ to!string(thing.line) ~ ".");
91 			}
92 			++i;
93 		}
94 	}
95 
96 	static struct Thing
97 	{
98 		string name;
99 		size_t line;
100 		size_t column;
101 		bool isVar;
102 		//bool isConditional;
103 	}
104 
105 	ref currentScope() @property
106 	{
107 		return stack[$-1];
108 	}
109 
110 	void pushScope()
111 	{
112 		stack.length++;
113 	}
114 
115 	void popScope()
116 	{
117 		stack.length--;
118 	}
119 
120 	int conditionalDepth;
121 }
122 
123 unittest
124 {
125 	import analysis.config : StaticAnalysisConfig;
126 	import std.stdio : stderr;
127 
128 	StaticAnalysisConfig sac;
129 	sac.label_var_same_name_check = true;
130 	assertAnalyzerWarnings(q{
131 unittest
132 {
133 blah:
134 	int blah; // [warn]: Variable "blah" has the same name as a label defined on line 4.
135 }
136 int blah;
137 unittest
138 {
139 	static if (stuff)
140 		int a;
141 	int a; // [warn]: Variable "a" has the same name as a variable defined on line 11.
142 }
143 
144 unittest
145 {
146 	static if (stuff)
147 		int a = 10;
148 	else
149 		int a = 20;
150 }
151 
152 unittest
153 {
154 	static if (stuff)
155 		int a = 10;
156 	else
157 		int a = 20;
158 	int a; // [warn]: Variable "a" has the same name as a variable defined on line 28.
159 }
160 }}, sac);
161 	stderr.writeln("Unittest for LabelVarNameCheck passed.");
162 }