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 }