replace libdparse in unused label check (#65)

This commit is contained in:
lucica28 2023-05-26 14:29:56 +03:00 committed by Albert24GG
parent 47dc93d6e1
commit a7545fc9d2
2 changed files with 114 additions and 102 deletions

View File

@ -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);

View File

@ -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.");
} }