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 	override void visit(const BreakStatement breakStatement)
80 	{
81 		if (breakStatement.label.text.length)
82 			labelUsed(breakStatement.label.text);
83 	}
84 	override void visit(const GotoStatement gotoStatement)
85 	{
86 		if (gotoStatement.label.text.length)
87 			labelUsed(gotoStatement.label.text);
88 	}
89 
90 private:
91 
92 	static struct Label
93 	{
94 		string name;
95 		size_t line;
96 		size_t column;
97 		bool used;
98 	}
99 
100 	Label[string][] stack;
101 
102 	auto ref current() @property
103 	{
104 		return stack[$-1];
105 	}
106 
107 	void pushScope()
108 	{
109 		stack.length++;
110 	}
111 
112 	void popScope()
113 	{
114 		foreach (label; current.byValue())
115 		{
116 			assert(label.line != size_t.max && label.column != size_t.max);
117 			if (!label.used)
118 			{
119 				addErrorMessage(label.line, label.column,
120 					"dscanner.suspicious.unused_label",
121 					"Label \"" ~ label.name ~ "\" is not used.");
122 			}
123 		}
124 		stack.length--;
125 	}
126 
127 	void labelUsed(string name)
128 	{
129 		Label* entry = name in current;
130 		if (entry is null)
131 			current[name] = Label(name, size_t.max, size_t.max, true);
132 		else
133 			entry.used = true;
134 	}
135 }
136 
137 unittest
138 {
139 	import analysis.config : StaticAnalysisConfig;
140 	import std.stdio : stderr;
141 
142 	StaticAnalysisConfig sac;
143 	sac.unused_label_check = true;
144 	assertAnalyzerWarnings(q{
145 		int testUnusedLabel()
146 		{
147 		    int x = 0;
148 		A: // [warn]: Label "A" is not used.
149 			if (x) goto B;
150 			x++;
151 		B:
152 			goto C;
153 			void foo()
154 			{
155 			C: // [warn]: Label "C" is not used.
156 				return;
157 			}
158 		C:
159 			void bar()
160 			{
161 				goto D;
162 			D:
163 				return;
164 			}
165 		D: // [warn]: Label "D" is not used.
166 			goto E;
167 			() {
168 			E: // [warn]: Label "E" is not used.
169 				return;
170 			}();
171 		E:
172 			() {
173 				goto F;
174 			F:
175 				return;
176 			}();
177 		F: // [warn]: Label "F" is not used.
178 			return x;
179 		G: // [warn]: Label "G" is not used.
180 		}
181 	}}, sac);
182 
183 	stderr.writeln("Unittest for UnusedLabelCheck passed.");
184 }