256 lines
4.8 KiB
D
256 lines
4.8 KiB
D
// 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.");
|
|
}
|