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 }