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