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.unused_label;
6 
7 import std.d.ast;
8 import std.d.lexer;
9 import analysis.base;
10 import analysis.helpers;
11 
12 class UnusedLabelCheck : BaseAnalyzer
13 {
14 	alias visit = BaseAnalyzer.visit;
15 
16 	this(string fileName)
17 	{
18 		super(fileName);
19 	}
20 
21 	override void visit(const Module mod)
22 	{
23 		pushScope();
24 		mod.accept(this);
25 		popScope();
26 	}
27 
28 	override void visit(const FunctionBody functionBody)
29 	{
30 		if (functionBody.blockStatement !is null)
31 		{
32 			pushScope();
33 			functionBody.blockStatement.accept(this);
34 			popScope();
35 		}
36 		if (functionBody.bodyStatement !is null)
37 		{
38 			pushScope();
39 			functionBody.bodyStatement.accept(this);
40 			popScope();
41 		}
42 		if (functionBody.outStatement !is null)
43 		{
44 			pushScope();
45 			functionBody.outStatement.accept(this);
46 			popScope();
47 		}
48 		if (functionBody.inStatement !is null)
49 		{
50 			pushScope();
51 			functionBody.inStatement.accept(this);
52 			popScope();
53 		}
54 	}
55 
56 	override void visit(const LabeledStatement labeledStatement)
57 	{
58 		auto token = &labeledStatement.identifier;
59 		Label* label = token.text in current;
60 		if (label is null)
61 		{
62 			current[token.text] = Label(token.text, token.line, token.column, false);
63 		}
64 		else
65 		{
66 			label.line = token.line;
67 			label.column = token.column;
68 		}
69 		if (labeledStatement.declarationOrStatement !is null)
70 			labeledStatement.declarationOrStatement.accept(this);
71 	}
72 
73 	override void visit(const ContinueStatement contStatement)
74 	{
75 		if (contStatement.label.text.length)
76 			labelUsed(contStatement.label.text);
77 	}
78 	override void visit(const BreakStatement breakStatement)
79 	{
80 		if (breakStatement.label.text.length)
81 			labelUsed(breakStatement.label.text);
82 	}
83 	override void visit(const GotoStatement gotoStatement)
84 	{
85 		if (gotoStatement.label.text.length)
86 			labelUsed(gotoStatement.label.text);
87 	}
88 
89 private:
90 
91 	static struct Label
92 	{
93 		string name;
94 		size_t line;
95 		size_t column;
96 		bool used;
97 	}
98 
99 	Label[string][] stack;
100 
101 	auto ref current() @property
102 	{
103 		return stack[$-1];
104 	}
105 
106 	void pushScope()
107 	{
108 		stack.length++;
109 	}
110 
111 	void popScope()
112 	{
113 		foreach (label; current.byValue())
114 		{
115 			assert(label.line != size_t.max && label.column != size_t.max);
116 			if (!label.used)
117 			{
118 				addErrorMessage(label.line, label.column,
119 					"dscanner.suspicious.unused_label",
120 					"Label \"" ~ label.name ~ "\" is not used.");
121 			}
122 		}
123 		stack.length--;
124 	}
125 
126 	void labelUsed(string name)
127 	{
128 		Label* entry = name in current;
129 		if (entry is null)
130 			current[name] = Label(name, size_t.max, size_t.max, true);
131 		else
132 			entry.used = true;
133 	}
134 }
135 
136 unittest
137 {
138 	import analysis.config : StaticAnalysisConfig;
139 	import std.stdio : stderr;
140 
141 	StaticAnalysisConfig sac;
142 	sac.unused_label_check = true;
143 	assertAnalyzerWarnings(q{
144 		int testUnusedLabel()
145 		{
146 		    int x = 0;
147 		A: // [warn]: Label "A" is not used.
148 			if (x) goto B;
149 			x++;
150 		B:
151 			goto C;
152 			void foo()
153 			{
154 			C: // [warn]: Label "C" is not used.
155 				return;
156 			}
157 		C:
158 			void bar()
159 			{
160 				goto D;
161 			D:
162 				return;
163 			}
164 		D: // [warn]: Label "D" is not used.
165 			goto E;
166 			() {
167 			E: // [warn]: Label "E" is not used.
168 				return;
169 			}();
170 		E:
171 			() {
172 				goto F;
173 			F:
174 				return;
175 			}();
176 		F: // [warn]: Label "F" is not used.
177 			return x;
178 		G: // [warn]: Label "G" is not used.
179 		}
180 	}}, sac);
181 
182 	stderr.writeln("Unittest for UnusedLabelCheck passed.");
183 }