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