// Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) module dscanner.analysis.unused_label; import dscanner.analysis.base; import dscanner.analysis.helpers; import dparse.ast; import dparse.lexer; import dsymbol.scope_ : Scope; import std.algorithm.iteration : each; /** * Checks for labels that are never used. */ final class UnusedLabelCheck : BaseAnalyzer { alias visit = BaseAnalyzer.visit; mixin AnalyzerInfo!"unused_label_check"; /// this(BaseAnalyzerArguments args) { super(args); } override void visit(const Module mod) { pushScope(); mod.accept(this); popScope(); } override void visit(const FunctionLiteralExpression flit) { if (flit.specifiedFunctionBody) { pushScope(); flit.specifiedFunctionBody.accept(this); popScope(); } } override void visit(const FunctionBody functionBody) { if (functionBody.specifiedFunctionBody !is null) { pushScope(); functionBody.specifiedFunctionBody.accept(this); popScope(); } if (functionBody.missingFunctionBody && functionBody.missingFunctionBody.functionContracts) functionBody.missingFunctionBody.functionContracts.each!((a){pushScope(); a.accept(this); popScope();}); } override void visit(const LabeledStatement labeledStatement) { auto token = labeledStatement.identifier; Label* label = token.text in current; if (label is null) { current[token.text] = Label(token.text, token, false); } else { label.token = token; } if (labeledStatement.declarationOrStatement !is null) labeledStatement.declarationOrStatement.accept(this); } override void visit(const ContinueStatement contStatement) { if (contStatement.label.text.length) labelUsed(contStatement.label.text); } override void visit(const BreakStatement breakStatement) { if (breakStatement.label.text.length) labelUsed(breakStatement.label.text); } override void visit(const GotoStatement gotoStatement) { if (gotoStatement.label.text.length) labelUsed(gotoStatement.label.text); } override void visit(const AsmInstruction instr) { instr.accept(this); bool jmp; if (instr.identifierOrIntegerOrOpcode.text.length) jmp = instr.identifierOrIntegerOrOpcode.text[0] == 'j'; if (!jmp || !instr.operands || instr.operands.operands.length != 1) return; const AsmExp e = cast(AsmExp) instr.operands.operands[0]; if (e.left && cast(AsmBrExp) e.left) { const AsmBrExp b = cast(AsmBrExp) e.left; if (b && b.asmUnaExp && b.asmUnaExp.asmPrimaryExp) { const AsmPrimaryExp p = b.asmUnaExp.asmPrimaryExp; if (p && p.identifierChain && p.identifierChain.identifiers.length == 1) labelUsed(p.identifierChain.identifiers[0].text); } } } private: enum string KEY = "dscanner.suspicious.unused_label"; static struct Label { string name; Token token; bool used; } Label[string][] stack; auto ref current() { return stack[$ - 1]; } void pushScope() { stack.length++; } void popScope() { foreach (label; current.byValue()) { if (label.token is Token.init) { // TODO: handle unknown labels } else if (!label.used) { addErrorMessage(label.token, KEY, "Label \"" ~ label.name ~ "\" is not used."); } } stack.length--; } void labelUsed(string name) { Label* entry = name in current; if (entry is null) current[name] = Label(name, Token.init, true); else entry.used = true; } } unittest { import dscanner.analysis.config : Check, StaticAnalysisConfig, disabledConfig; import std.stdio : stderr; StaticAnalysisConfig sac = disabledConfig(); sac.unused_label_check = Check.enabled; assertAnalyzerWarnings(q{ int testUnusedLabel() { int x = 0; A: /+ ^ [warn]: Label "A" is not used. +/ if (x) goto B; x++; B: goto C; void foo() { C: /+ ^ [warn]: Label "C" is not used. +/ return; } C: void bar() { goto D; D: return; } D: /+ ^ [warn]: Label "D" is not used. +/ goto E; () { E: /+ ^ [warn]: Label "E" is not used. +/ return; }(); E: () { goto F; F: return; }(); F: /+ ^ [warn]: Label "F" is not used. +/ return x; G: /+ ^ [warn]: Label "G" is not used. +/ } }c, sac); assertAnalyzerWarnings(q{ void testAsm() { asm { jmp lbl;} lbl: } }c, sac); assertAnalyzerWarnings(q{ void testAsm() { asm { mov RAX,1;} lbl: /+ ^^^ [warn]: Label "lbl" is not used. +/ } }c, sac); // from std.math assertAnalyzerWarnings(q{ real polyImpl() { asm { jecxz return_ST; } } }c, sac); // a label might be hard to find, e.g. in a mixin assertAnalyzerWarnings(q{ real polyImpl() { mixin("return_ST: return 1;"); asm { jecxz return_ST; } } }c, sac); stderr.writeln("Unittest for UnusedLabelCheck passed."); }