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 dscanner.analysis.unused_label;
6 
7 import dscanner.analysis.base;
8 import dscanner.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 			if (label.line == size_t.max || label.column == size_t.max)
147 			{
148 				// TODO: handle unknown labels
149 			}
150 			else if (!label.used)
151 			{
152 				addErrorMessage(label.line, label.column, "dscanner.suspicious.unused_label",
153 						"Label \"" ~ label.name ~ "\" is not used.");
154 			}
155 		}
156 		stack.length--;
157 	}
158 
159 	void labelUsed(string name)
160 	{
161 		Label* entry = name in current;
162 		if (entry is null)
163 			current[name] = Label(name, size_t.max, size_t.max, true);
164 		else
165 			entry.used = true;
166 	}
167 }
168 
169 unittest
170 {
171 	import dscanner.analysis.config : Check, StaticAnalysisConfig, disabledConfig;
172 	import std.stdio : stderr;
173 
174 	StaticAnalysisConfig sac = disabledConfig();
175 	sac.unused_label_check = Check.enabled;
176 	assertAnalyzerWarnings(q{
177 		int testUnusedLabel()
178 		{
179 		    int x = 0;
180 		A: // [warn]: Label "A" is not used.
181 			if (x) goto B;
182 			x++;
183 		B:
184 			goto C;
185 			void foo()
186 			{
187 			C: // [warn]: Label "C" is not used.
188 				return;
189 			}
190 		C:
191 			void bar()
192 			{
193 				goto D;
194 			D:
195 				return;
196 			}
197 		D: // [warn]: Label "D" is not used.
198 			goto E;
199 			() {
200 			E: // [warn]: Label "E" is not used.
201 				return;
202 			}();
203 		E:
204 			() {
205 				goto F;
206 			F:
207 				return;
208 			}();
209 		F: // [warn]: Label "F" is not used.
210 			return x;
211 		G: // [warn]: Label "G" is not used.
212 		}
213 	}}, sac);
214 
215 	assertAnalyzerWarnings(q{
216 		void testAsm()
217 		{
218 			asm { jmp lbl;}
219 			lbl:
220 		}
221 	}}, sac);
222 
223 	assertAnalyzerWarnings(q{
224 		void testAsm()
225 		{
226 			asm { mov RAX,1;}
227 			lbl: // [warn]: Label "lbl" is not used.
228 		}
229 	}}, sac);
230 
231 	// from std.math
232 	assertAnalyzerWarnings(q{
233 		real polyImpl() {
234 			asm {
235 				jecxz return_ST;
236 		    }
237 		}
238 	}}, sac);
239 
240 	// a label might be hard to find, e.g. in a mixin
241 	assertAnalyzerWarnings(q{
242 		real polyImpl() {
243 			mixin("return_ST: return 1;");
244 			asm {
245 				jecxz return_ST;
246 		    }
247 		}
248 	}}, sac);
249 
250 	stderr.writeln("Unittest for UnusedLabelCheck passed.");
251 }