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 }