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