replace libdparse in unused label check (#65)
This commit is contained in:
parent
47dc93d6e1
commit
a7545fc9d2
|
|
@ -885,10 +885,6 @@ private BaseAnalyzer[] getAnalyzersForModuleAndConfig(string fileName,
|
||||||
checks ~= new UndocumentedDeclarationCheck(args.setSkipTests(
|
checks ~= new UndocumentedDeclarationCheck(args.setSkipTests(
|
||||||
analysisConfig.undocumented_declaration_check == Check.skipTests && !ut));
|
analysisConfig.undocumented_declaration_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!UnusedLabelCheck(analysisConfig))
|
|
||||||
checks ~= new UnusedLabelCheck(args.setSkipTests(
|
|
||||||
analysisConfig.unused_label_check == Check.skipTests && !ut));
|
|
||||||
|
|
||||||
if (moduleName.shouldRun!UnusedVariableCheck(analysisConfig))
|
if (moduleName.shouldRun!UnusedVariableCheck(analysisConfig))
|
||||||
checks ~= new UnusedVariableCheck(args.setSkipTests(
|
checks ~= new UnusedVariableCheck(args.setSkipTests(
|
||||||
analysisConfig.unused_variable_check == Check.skipTests && !ut));
|
analysisConfig.unused_variable_check == Check.skipTests && !ut));
|
||||||
|
|
@ -1312,6 +1308,12 @@ MessageSet analyzeDmd(string fileName, ASTCodegen.Module m, const char[] moduleN
|
||||||
config.logical_precedence_check == Check.skipTests && !ut
|
config.logical_precedence_check == Check.skipTests && !ut
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!(UnusedLabelCheck!ASTCodegen)(config))
|
||||||
|
visitors ~= new UnusedLabelCheck!ASTCodegen(
|
||||||
|
fileName,
|
||||||
|
config.unused_label_check == Check.skipTests && !ut
|
||||||
|
);
|
||||||
|
|
||||||
if (moduleName.shouldRunDmd!(BuiltinPropertyNameCheck!ASTCodegen)(config))
|
if (moduleName.shouldRunDmd!(BuiltinPropertyNameCheck!ASTCodegen)(config))
|
||||||
visitors ~= new BuiltinPropertyNameCheck!ASTCodegen(fileName);
|
visitors ~= new BuiltinPropertyNameCheck!ASTCodegen(fileName);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,128 +5,131 @@
|
||||||
module dscanner.analysis.unused_label;
|
module dscanner.analysis.unused_label;
|
||||||
|
|
||||||
import dscanner.analysis.base;
|
import dscanner.analysis.base;
|
||||||
import dscanner.analysis.helpers;
|
import dmd.tokens;
|
||||||
import dparse.ast;
|
|
||||||
import dparse.lexer;
|
|
||||||
import dsymbol.scope_ : Scope;
|
|
||||||
import std.algorithm.iteration : each;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for labels that are never used.
|
* Checks for labels that are never used.
|
||||||
*/
|
*/
|
||||||
final class UnusedLabelCheck : BaseAnalyzer
|
extern (C++) class UnusedLabelCheck(AST) : BaseAnalyzerDmd
|
||||||
{
|
{
|
||||||
alias visit = BaseAnalyzer.visit;
|
alias visit = BaseAnalyzerDmd.visit;
|
||||||
|
|
||||||
mixin AnalyzerInfo!"unused_label_check";
|
mixin AnalyzerInfo!"unused_label_check";
|
||||||
|
|
||||||
///
|
extern (D) this(string fileName, bool skipTests = false)
|
||||||
this(BaseAnalyzerArguments args)
|
|
||||||
{
|
{
|
||||||
super(args);
|
super(fileName, skipTests);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const Module mod)
|
override void visit(AST.Module m)
|
||||||
{
|
{
|
||||||
pushScope();
|
pushScope();
|
||||||
mod.accept(this);
|
super.visit(m);
|
||||||
popScope();
|
popScope();
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const FunctionLiteralExpression flit)
|
override void visit(AST.LabelStatement ls)
|
||||||
{
|
{
|
||||||
if (flit.specifiedFunctionBody)
|
Label* label = ls.ident.toString() in current;
|
||||||
{
|
|
||||||
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)
|
if (label is null)
|
||||||
{
|
{
|
||||||
current[token.text] = Label(token.text, token, false);
|
current[ls.ident.toString()] = Label(ls.ident.toString(),
|
||||||
|
ls.loc.linnum, ls.loc.charnum, false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
label.token = token;
|
label.line = ls.loc.linnum;
|
||||||
|
label.column = ls.loc.charnum;
|
||||||
}
|
}
|
||||||
if (labeledStatement.declarationOrStatement !is null)
|
|
||||||
labeledStatement.declarationOrStatement.accept(this);
|
super.visit(ls);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const ContinueStatement contStatement)
|
override void visit(AST.GotoStatement gs)
|
||||||
{
|
{
|
||||||
if (contStatement.label.text.length)
|
if (gs.ident)
|
||||||
labelUsed(contStatement.label.text);
|
labelUsed(gs.ident.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const BreakStatement breakStatement)
|
override void visit(AST.BreakStatement bs)
|
||||||
{
|
{
|
||||||
if (breakStatement.label.text.length)
|
if (bs.ident)
|
||||||
labelUsed(breakStatement.label.text);
|
labelUsed(bs.ident.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const GotoStatement gotoStatement)
|
override void visit(AST.StaticForeachStatement s)
|
||||||
{
|
{
|
||||||
if (gotoStatement.label.text.length)
|
if (s.sfe.aggrfe)
|
||||||
labelUsed(gotoStatement.label.text);
|
super.visit(s.sfe.aggrfe);
|
||||||
|
|
||||||
|
if (s.sfe.rangefe)
|
||||||
|
super.visit(s.sfe.rangefe);
|
||||||
}
|
}
|
||||||
|
|
||||||
override void visit(const AsmInstruction instr)
|
override void visit(AST.ContinueStatement cs)
|
||||||
{
|
{
|
||||||
instr.accept(this);
|
if (cs.ident)
|
||||||
|
labelUsed(cs.ident.toString());
|
||||||
|
}
|
||||||
|
|
||||||
bool jmp;
|
override void visit(AST.FuncDeclaration fd)
|
||||||
if (instr.identifierOrIntegerOrOpcode.text.length)
|
{
|
||||||
jmp = instr.identifierOrIntegerOrOpcode.text[0] == 'j';
|
pushScope();
|
||||||
|
super.visit(fd);
|
||||||
|
popScope();
|
||||||
|
}
|
||||||
|
|
||||||
if (!jmp || !instr.operands || instr.operands.operands.length != 1)
|
override void visit(AST.FuncLiteralDeclaration fd)
|
||||||
|
{
|
||||||
|
pushScope();
|
||||||
|
super.visit(fd);
|
||||||
|
popScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
override void visit(AST.AsmStatement as)
|
||||||
|
{
|
||||||
|
if (!as.tokens)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const AsmExp e = cast(AsmExp) instr.operands.operands[0];
|
// Look for jump instructions
|
||||||
if (e.left && cast(AsmBrExp) e.left)
|
bool jmp;
|
||||||
{
|
if (getFirstLetterOf(cast(char*) as.tokens[0].ptr) == 'j')
|
||||||
const AsmBrExp b = cast(AsmBrExp) e.left;
|
jmp = true;
|
||||||
if (b && b.asmUnaExp && b.asmUnaExp.asmPrimaryExp)
|
|
||||||
{
|
// Last argument of the jmp instruction will be the label
|
||||||
const AsmPrimaryExp p = b.asmUnaExp.asmPrimaryExp;
|
Token* label;
|
||||||
if (p && p.identifierChain && p.identifierChain.identifiers.length == 1)
|
for (label = as.tokens; label.next; label = label.next) {}
|
||||||
labelUsed(p.identifierChain.identifiers[0].text);
|
|
||||||
}
|
if (jmp && label.ident)
|
||||||
}
|
labelUsed(label.ident.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private char getFirstLetterOf(char* str)
|
||||||
|
{
|
||||||
|
import std.ascii : isAlpha;
|
||||||
|
|
||||||
|
if (str is null)
|
||||||
|
return '\0';
|
||||||
|
|
||||||
|
while (str && !isAlpha(*str))
|
||||||
|
str++;
|
||||||
|
|
||||||
|
return *str;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
enum string KEY = "dscanner.suspicious.unused_label";
|
|
||||||
|
|
||||||
static struct Label
|
static struct Label
|
||||||
{
|
{
|
||||||
string name;
|
const(char)[] name;
|
||||||
Token token;
|
size_t line;
|
||||||
|
size_t column;
|
||||||
bool used;
|
bool used;
|
||||||
}
|
}
|
||||||
|
|
||||||
Label[string][] stack;
|
extern (D) Label[const(char)[]][] stack;
|
||||||
|
|
||||||
auto ref current()
|
extern (D) auto ref current()
|
||||||
{
|
{
|
||||||
return stack[$ - 1];
|
return stack[$ - 1];
|
||||||
}
|
}
|
||||||
|
|
@ -138,26 +141,28 @@ private:
|
||||||
|
|
||||||
void popScope()
|
void popScope()
|
||||||
{
|
{
|
||||||
|
import std.conv : to;
|
||||||
|
|
||||||
foreach (label; current.byValue())
|
foreach (label; current.byValue())
|
||||||
{
|
{
|
||||||
if (label.token is Token.init)
|
if (label.line == size_t.max || label.column == size_t.max)
|
||||||
{
|
{
|
||||||
// TODO: handle unknown labels
|
// TODO: handle unknown labels
|
||||||
}
|
}
|
||||||
else if (!label.used)
|
else if (!label.used)
|
||||||
{
|
{
|
||||||
addErrorMessage(label.token, KEY,
|
addErrorMessage(label.line, label.column, "dscanner.suspicious.unused_label",
|
||||||
"Label \"" ~ label.name ~ "\" is not used.");
|
"Label \"" ~ to!string(label.name) ~ "\" is not used.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stack.length--;
|
stack.length--;
|
||||||
}
|
}
|
||||||
|
|
||||||
void labelUsed(string name)
|
extern (D) void labelUsed(const(char)[] name)
|
||||||
{
|
{
|
||||||
Label* entry = name in current;
|
Label* entry = name in current;
|
||||||
if (entry is null)
|
if (entry is null)
|
||||||
current[name] = Label(name, Token.init, true);
|
current[name] = Label(name, size_t.max, size_t.max, true);
|
||||||
else
|
else
|
||||||
entry.used = true;
|
entry.used = true;
|
||||||
}
|
}
|
||||||
|
|
@ -165,25 +170,24 @@ private:
|
||||||
|
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
import dscanner.analysis.config : Check, StaticAnalysisConfig, disabledConfig;
|
import dscanner.analysis.helpers : assertAnalyzerWarningsDMD;
|
||||||
|
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
|
||||||
import std.stdio : stderr;
|
import std.stdio : stderr;
|
||||||
|
|
||||||
StaticAnalysisConfig sac = disabledConfig();
|
StaticAnalysisConfig sac = disabledConfig();
|
||||||
sac.unused_label_check = Check.enabled;
|
sac.unused_label_check = Check.enabled;
|
||||||
assertAnalyzerWarnings(q{
|
assertAnalyzerWarningsDMD(q{
|
||||||
int testUnusedLabel()
|
int testUnusedLabel()
|
||||||
{
|
{
|
||||||
int x = 0;
|
int x = 0;
|
||||||
A: /+
|
A: // [warn]: Label "A" is not used.
|
||||||
^ [warn]: Label "A" is not used. +/
|
|
||||||
if (x) goto B;
|
if (x) goto B;
|
||||||
x++;
|
x++;
|
||||||
B:
|
B:
|
||||||
goto C;
|
goto C;
|
||||||
void foo()
|
void foo()
|
||||||
{
|
{
|
||||||
C: /+
|
C: // [warn]: Label "C" is not used.
|
||||||
^ [warn]: Label "C" is not used. +/
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
C:
|
C:
|
||||||
|
|
@ -193,12 +197,10 @@ unittest
|
||||||
D:
|
D:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
D: /+
|
D: // [warn]: Label "D" is not used.
|
||||||
^ [warn]: Label "D" is not used. +/
|
|
||||||
goto E;
|
goto E;
|
||||||
() {
|
() {
|
||||||
E: /+
|
E: // [warn]: Label "E" is not used.
|
||||||
^ [warn]: Label "E" is not used. +/
|
|
||||||
return;
|
return;
|
||||||
}();
|
}();
|
||||||
E:
|
E:
|
||||||
|
|
@ -207,15 +209,13 @@ unittest
|
||||||
F:
|
F:
|
||||||
return;
|
return;
|
||||||
}();
|
}();
|
||||||
F: /+
|
F: // [warn]: Label "F" is not used.
|
||||||
^ [warn]: Label "F" is not used. +/
|
|
||||||
return x;
|
return x;
|
||||||
G: /+
|
G: // [warn]: Label "G" is not used.
|
||||||
^ [warn]: Label "G" is not used. +/
|
|
||||||
}
|
}
|
||||||
}c, sac);
|
}c, sac);
|
||||||
|
|
||||||
assertAnalyzerWarnings(q{
|
assertAnalyzerWarningsDMD(q{
|
||||||
void testAsm()
|
void testAsm()
|
||||||
{
|
{
|
||||||
asm { jmp lbl;}
|
asm { jmp lbl;}
|
||||||
|
|
@ -223,17 +223,16 @@ unittest
|
||||||
}
|
}
|
||||||
}c, sac);
|
}c, sac);
|
||||||
|
|
||||||
assertAnalyzerWarnings(q{
|
assertAnalyzerWarningsDMD(q{
|
||||||
void testAsm()
|
void testAsm()
|
||||||
{
|
{
|
||||||
asm { mov RAX,1;}
|
asm { mov RAX,1;}
|
||||||
lbl: /+
|
lbl: // [warn]: Label "lbl" is not used.
|
||||||
^^^ [warn]: Label "lbl" is not used. +/
|
|
||||||
}
|
}
|
||||||
}c, sac);
|
}c, sac);
|
||||||
|
|
||||||
// from std.math
|
// from std.math
|
||||||
assertAnalyzerWarnings(q{
|
assertAnalyzerWarningsDMD(q{
|
||||||
real polyImpl() {
|
real polyImpl() {
|
||||||
asm {
|
asm {
|
||||||
jecxz return_ST;
|
jecxz return_ST;
|
||||||
|
|
@ -242,7 +241,7 @@ unittest
|
||||||
}c, sac);
|
}c, sac);
|
||||||
|
|
||||||
// a label might be hard to find, e.g. in a mixin
|
// a label might be hard to find, e.g. in a mixin
|
||||||
assertAnalyzerWarnings(q{
|
assertAnalyzerWarningsDMD(q{
|
||||||
real polyImpl() {
|
real polyImpl() {
|
||||||
mixin("return_ST: return 1;");
|
mixin("return_ST: return 1;");
|
||||||
asm {
|
asm {
|
||||||
|
|
@ -251,5 +250,16 @@ unittest
|
||||||
}
|
}
|
||||||
}c, sac);
|
}c, sac);
|
||||||
|
|
||||||
|
assertAnalyzerWarningsDMD(q{
|
||||||
|
void testAsm()
|
||||||
|
{
|
||||||
|
asm nothrow @nogc
|
||||||
|
{
|
||||||
|
"movgr2fcsr $r0,%0" :
|
||||||
|
: "r" (newState & (roundingMask | allExceptions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}c, sac);
|
||||||
|
|
||||||
stderr.writeln("Unittest for UnusedLabelCheck passed.");
|
stderr.writeln("Unittest for UnusedLabelCheck passed.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue