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 analysis.unused_label; 6 7 import analysis.base; 8 import 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 assert(label.line != size_t.max && label.column != size_t.max); 147 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 analysis.config : Check, StaticAnalysisConfig; 169 import std.stdio : stderr; 170 171 StaticAnalysisConfig sac; 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 stderr.writeln("Unittest for UnusedLabelCheck passed."); 229 }