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 	///
22 	this(string fileName, const(Scope)* sc, bool skipTests = false)
23 	{
24 		super(fileName, sc, skipTests);
25 	}
26 
27 	override void visit(const Module mod)
28 	{
29 		pushScope();
30 		mod.accept(this);
31 		popScope();
32 	}
33 
34 	override void visit(const FunctionLiteralExpression flit)
35 	{
36 		if (flit.specifiedFunctionBody)
37 		{
38 			pushScope();
39 			flit.specifiedFunctionBody.accept(this);
40 			popScope();
41 		}
42 	}
43 
44 	override void visit(const FunctionBody functionBody)
45 	{
46 		if (functionBody.specifiedFunctionBody !is null)
47 		{
48 			pushScope();
49 			functionBody.specifiedFunctionBody.accept(this);
50 			popScope();
51 		}
52         if (functionBody.missingFunctionBody && functionBody.missingFunctionBody.functionContracts)
53 			functionBody.missingFunctionBody.functionContracts.each!((a){pushScope(); a.accept(this); popScope();});
54         if (functionBody.specifiedFunctionBody && functionBody.specifiedFunctionBody.functionContracts)
55 			functionBody.specifiedFunctionBody.functionContracts.each!((a){pushScope(); a.accept(this); popScope();});
56 	}
57 
58 	override void visit(const LabeledStatement labeledStatement)
59 	{
60 		auto token = &labeledStatement.identifier;
61 		Label* label = token.text in current;
62 		if (label is null)
63 		{
64 			current[token.text] = Label(token.text, token.line, token.column, false);
65 		}
66 		else
67 		{
68 			label.line = token.line;
69 			label.column = token.column;
70 		}
71 		if (labeledStatement.declarationOrStatement !is null)
72 			labeledStatement.declarationOrStatement.accept(this);
73 	}
74 
75 	override void visit(const ContinueStatement contStatement)
76 	{
77 		if (contStatement.label.text.length)
78 			labelUsed(contStatement.label.text);
79 	}
80 
81 	override void visit(const BreakStatement breakStatement)
82 	{
83 		if (breakStatement.label.text.length)
84 			labelUsed(breakStatement.label.text);
85 	}
86 
87 	override void visit(const GotoStatement gotoStatement)
88 	{
89 		if (gotoStatement.label.text.length)
90 			labelUsed(gotoStatement.label.text);
91 	}
92 
93 	override void visit(const AsmInstruction instr)
94 	{
95 		instr.accept(this);
96 
97 		bool jmp;
98 		if (instr.identifierOrIntegerOrOpcode.text.length)
99 			jmp = instr.identifierOrIntegerOrOpcode.text[0] == 'j';
100 
101 		if (!jmp || !instr.operands || instr.operands.operands.length != 1)
102 			return;
103 
104 		const AsmExp e = cast(AsmExp) instr.operands.operands[0];
105 		if (e.left && cast(AsmBrExp) e.left)
106 		{
107 			const AsmBrExp b = cast(AsmBrExp) e.left;
108 			if (b && b.asmUnaExp && b.asmUnaExp.asmPrimaryExp)
109 			{
110 				const AsmPrimaryExp p = b.asmUnaExp.asmPrimaryExp;
111 				if (p && p.identifierChain && p.identifierChain.identifiers.length == 1)
112 					labelUsed(p.identifierChain.identifiers[0].text);
113 			}
114 		}
115 	}
116 
117 private:
118 
119 	static struct Label
120 	{
121 		string name;
122 		size_t line;
123 		size_t column;
124 		bool used;
125 	}
126 
127 	Label[string][] stack;
128 
129 	auto ref current()
130 	{
131 		return stack[$ - 1];
132 	}
133 
134 	void pushScope()
135 	{
136 		stack.length++;
137 	}
138 
139 	void popScope()
140 	{
141 		foreach (label; current.byValue())
142 		{
143 			if (label.line == size_t.max || label.column == size_t.max)
144 			{
145 				// TODO: handle unknown labels
146 			}
147 			else 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 dscanner.analysis.config : Check, StaticAnalysisConfig, disabledConfig;
169 	import std.stdio : stderr;
170 
171 	StaticAnalysisConfig sac = disabledConfig();
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 	// from std.math
229 	assertAnalyzerWarnings(q{
230 		real polyImpl() {
231 			asm {
232 				jecxz return_ST;
233 		    }
234 		}
235 	}}, sac);
236 
237 	// a label might be hard to find, e.g. in a mixin
238 	assertAnalyzerWarnings(q{
239 		real polyImpl() {
240 			mixin("return_ST: return 1;");
241 			asm {
242 				jecxz return_ST;
243 		    }
244 		}
245 	}}, sac);
246 
247 	stderr.writeln("Unittest for UnusedLabelCheck passed.");
248 }