Replace libdparse with DMD in LabelVarNameCheck (#101)

* Replace libdparse with DMD in LabelVarNameCheck

* Disable check for local functions
This commit is contained in:
Vladiwostok 2024-04-08 18:32:03 +03:00 committed by Albert24GG
parent de6c0a3c98
commit 0e34c831ff
2 changed files with 177 additions and 71 deletions

View File

@ -4,174 +4,282 @@
module dscanner.analysis.label_var_same_name_check; module dscanner.analysis.label_var_same_name_check;
import dparse.ast;
import dparse.lexer;
import dsymbol.scope_ : Scope;
import dscanner.analysis.base; import dscanner.analysis.base;
import dscanner.analysis.helpers; import dmd.cond : Include;
import std.conv : to;
/** /**
* Checks for labels and variables that have the same name. * Checks for labels and variables that have the same name.
*/ */
final class LabelVarNameCheck : ScopedBaseAnalyzer extern (C++) class LabelVarNameCheck(AST) : BaseAnalyzerDmd
{ {
mixin AnalyzerInfo!"label_var_same_name_check"; mixin AnalyzerInfo!"label_var_same_name_check";
alias visit = BaseAnalyzerDmd.visit;
this(BaseAnalyzerArguments args) mixin ScopedVisit!(AST.Module);
mixin ScopedVisit!(AST.IfStatement);
mixin ScopedVisit!(AST.WithStatement);
mixin ScopedVisit!(AST.WhileStatement);
mixin ScopedVisit!(AST.DoStatement);
mixin ScopedVisit!(AST.ForStatement);
mixin ScopedVisit!(AST.CaseStatement);
mixin ScopedVisit!(AST.ForeachStatement);
mixin ScopedVisit!(AST.ForeachRangeStatement);
mixin ScopedVisit!(AST.ScopeStatement);
mixin ScopedVisit!(AST.FuncAliasDeclaration);
mixin ScopedVisit!(AST.CtorDeclaration);
mixin ScopedVisit!(AST.DtorDeclaration);
mixin ScopedVisit!(AST.InvariantDeclaration);
mixin FunctionVisit!(AST.FuncDeclaration);
mixin FunctionVisit!(AST.TemplateDeclaration);
mixin FunctionVisit!(AST.UnitTestDeclaration);
mixin FunctionVisit!(AST.FuncLiteralDeclaration);
mixin AggregateVisit!(AST.ClassDeclaration);
mixin AggregateVisit!(AST.StructDeclaration);
mixin AggregateVisit!(AST.InterfaceDeclaration);
mixin AggregateVisit!(AST.UnionDeclaration);
extern (D) this(string fileName, bool skipTests = false)
{ {
super(args); super(fileName);
} }
mixin AggregateVisit!ClassDeclaration; override void visit(AST.VarDeclaration vd)
mixin AggregateVisit!StructDeclaration;
mixin AggregateVisit!InterfaceDeclaration;
mixin AggregateVisit!UnionDeclaration;
override void visit(const VariableDeclaration var)
{ {
foreach (dec; var.declarators) import dmd.astenums : STC;
duplicateCheck(dec.name, false, conditionalDepth > 0);
if (!(vd.storage_class & STC.scope_) && !isInLocalFunction)
{
auto thing = Thing(to!string(vd.ident.toChars()), vd.loc.linnum, vd.loc.charnum);
duplicateCheck(thing, false, conditionalDepth > 0);
} }
override void visit(const LabeledStatement labeledStatement) super.visit(vd);
{
duplicateCheck(labeledStatement.identifier, true, conditionalDepth > 0);
if (labeledStatement.declarationOrStatement !is null)
labeledStatement.declarationOrStatement.accept(this);
} }
override void visit(const ConditionalDeclaration condition) override void visit(AST.LabelStatement ls)
{ {
if (condition.falseDeclarations.length > 0) auto thing = Thing(to!string(ls.ident.toChars()), ls.loc.linnum, ls.loc.charnum);
duplicateCheck(thing, true, conditionalDepth > 0);
super.visit(ls);
}
override void visit(AST.ConditionalDeclaration conditionalDeclaration)
{
import dmd.root.array : peekSlice;
conditionalDeclaration.condition.accept(this);
if (conditionalDeclaration.condition.isVersionCondition())
++conditionalDepth; ++conditionalDepth;
condition.accept(this);
if (condition.falseDeclarations.length > 0) if (conditionalDeclaration.elsedecl || conditionalDeclaration.condition.inc != Include.yes)
pushScope();
foreach (decl; conditionalDeclaration.decl.peekSlice())
decl.accept(this);
if (conditionalDeclaration.elsedecl || conditionalDeclaration.condition.inc != Include.yes)
popScope();
if (conditionalDeclaration.elsedecl)
foreach (decl; conditionalDeclaration.elsedecl.peekSlice())
decl.accept(this);
if (conditionalDeclaration.condition.isVersionCondition())
--conditionalDepth; --conditionalDepth;
} }
override void visit(const VersionCondition condition) override void visit(AST.ConditionalStatement conditionalStatement)
{ {
conditionalStatement.condition.accept(this);
if (conditionalStatement.condition.isVersionCondition)
++conditionalDepth; ++conditionalDepth;
condition.accept(this);
if (conditionalStatement.ifbody)
{
if (conditionalStatement.elsebody)
pushScope();
conditionalStatement.ifbody.accept(this);
if (conditionalStatement.elsebody)
popScope();
}
if (conditionalStatement.elsebody)
{
if (conditionalStatement.condition.inc == Include.no)
pushScope();
conditionalStatement.elsebody.accept(this);
if (conditionalStatement.condition.inc == Include.no)
popScope();
}
if (conditionalStatement.condition.isVersionCondition)
--conditionalDepth; --conditionalDepth;
} }
alias visit = ScopedBaseAnalyzer.visit; override void visit(AST.AnonDeclaration ad)
{
pushScope();
pushAggregateName("", ad.loc.linnum, ad.loc.charnum);
super.visit(ad);
popScope();
popAggregateName();
}
private: private:
extern (D) Thing[string][] stack;
int conditionalDepth;
extern (D) Thing[] parentAggregates;
extern (D) string parentAggregateText;
bool isInFunction;
bool isInLocalFunction;
enum string KEY = "dscanner.suspicious.label_var_same_name"; enum string KEY = "dscanner.suspicious.label_var_same_name";
Thing[string][] stack; template FunctionVisit(NodeType)
{
override void visit(NodeType n)
{
auto oldIsInFunction = isInFunction;
auto oldIsInLocalFunction = isInLocalFunction;
pushScope();
if (isInFunction)
isInLocalFunction = true;
else
isInFunction = true;
super.visit(n);
popScope();
isInFunction = oldIsInFunction;
isInLocalFunction = oldIsInLocalFunction;
}
}
template AggregateVisit(NodeType) template AggregateVisit(NodeType)
{ {
override void visit(const NodeType n) override void visit(NodeType n)
{ {
pushAggregateName(n.name); pushScope();
n.accept(this); pushAggregateName(to!string(n.ident.toString()), n.loc.linnum, n.loc.charnum);
super.visit(n);
popScope();
popAggregateName(); popAggregateName();
} }
} }
void duplicateCheck(const Token name, bool fromLabel, bool isConditional) template ScopedVisit(NodeType)
{
override void visit(NodeType n)
{
pushScope();
super.visit(n);
popScope();
}
}
extern (D) void duplicateCheck(const Thing id, bool fromLabel, bool isConditional)
{ {
import std.conv : to;
import std.range : retro; import std.range : retro;
size_t i; size_t i;
foreach (s; retro(stack)) foreach (s; retro(stack))
{ {
string fqn = parentAggregateText ~ name.text; string fqn = parentAggregateText ~ id.name;
const(Thing)* thing = fqn in s; const(Thing)* thing = fqn in s;
if (thing is null) if (thing is null)
currentScope[fqn] = Thing(fqn, name.line, name.column, !fromLabel /+, isConditional+/ ); {
currentScope[fqn] = Thing(fqn, id.line, id.column, !fromLabel);
}
else if (i != 0 || !isConditional) else if (i != 0 || !isConditional)
{ {
immutable thisKind = fromLabel ? "Label" : "Variable"; immutable thisKind = fromLabel ? "Label" : "Variable";
immutable otherKind = thing.isVar ? "variable" : "label"; immutable otherKind = thing.isVar ? "variable" : "label";
addErrorMessage(name, KEY, auto msg = thisKind ~ " \"" ~ fqn ~ "\" has the same name as a "
thisKind ~ " \"" ~ fqn ~ "\" has the same name as a " ~ otherKind ~ " defined on line " ~ to!string(thing.line) ~ ".";
~ otherKind ~ " defined on line " ~ to!string(thing.line) ~ "."); addErrorMessage(id.line, id.column, KEY, msg);
} }
++i; ++i;
} }
} }
static struct Thing extern (D) static struct Thing
{ {
string name; string name;
size_t line; size_t line;
size_t column; size_t column;
bool isVar; bool isVar;
//bool isConditional;
} }
ref currentScope() @property extern (D) ref currentScope() @property
{ {
return stack[$ - 1]; return stack[$ - 1];
} }
protected override void pushScope() extern (D) void pushScope()
{ {
stack.length++; stack.length++;
} }
protected override void popScope() extern (D) void popScope()
{ {
stack.length--; stack.length--;
} }
int conditionalDepth; extern (D) void pushAggregateName(string name, size_t line, size_t column)
void pushAggregateName(Token name)
{ {
parentAggregates ~= name; parentAggregates ~= Thing(name, line, column);
updateAggregateText(); updateAggregateText();
} }
void popAggregateName() extern (D) void popAggregateName()
{ {
parentAggregates.length -= 1; parentAggregates.length -= 1;
updateAggregateText(); updateAggregateText();
} }
void updateAggregateText() extern (D) void updateAggregateText()
{ {
import std.algorithm : map; import std.algorithm : map;
import std.array : join; import std.array : join;
if (parentAggregates.length) if (parentAggregates.length)
parentAggregateText = parentAggregates.map!(a => a.text).join(".") ~ "."; parentAggregateText = parentAggregates.map!(a => a.name).join(".") ~ ".";
else else
parentAggregateText = ""; parentAggregateText = "";
} }
Token[] parentAggregates;
string parentAggregateText;
} }
unittest unittest
{ {
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
import dscanner.analysis.helpers : assertAnalyzerWarningsDMD;
import std.stdio : stderr; import std.stdio : stderr;
StaticAnalysisConfig sac = disabledConfig(); StaticAnalysisConfig sac = disabledConfig();
sac.label_var_same_name_check = Check.enabled; sac.label_var_same_name_check = Check.enabled;
assertAnalyzerWarnings(q{
assertAnalyzerWarningsDMD(q{
unittest unittest
{ {
blah: blah:
int blah; /+ int blah; // [warn]: Variable "blah" has the same name as a label defined on line 4.
^^^^ [warn]: Variable "blah" has the same name as a label defined on line 4. +/
} }
int blah; int blah;
unittest unittest
{ {
static if (stuff) static if (stuff)
int a; int a;
int a; /+ int a; // [warn]: Variable "a" has the same name as a variable defined on line 11.
^ [warn]: Variable "a" has the same name as a variable defined on line 12. +/
} }
unittest unittest
@ -188,8 +296,7 @@ unittest
int a = 10; int a = 10;
else else
int a = 20; int a = 20;
int a; /+ int a; // [warn]: Variable "a" has the same name as a variable defined on line 28.
^ [warn]: Variable "a" has the same name as a variable defined on line 30. +/
} }
template T(stuff) template T(stuff)
{ {
@ -212,8 +319,7 @@ unittest
int c = 10; int c = 10;
else else
int c = 20; int c = 20;
int c; /+ int c; // [warn]: Variable "c" has the same name as a variable defined on line 51.
^ [warn]: Variable "c" has the same name as a variable defined on line 54. +/
} }
unittest unittest
@ -251,8 +357,7 @@ unittest
interface A interface A
{ {
int a; int a;
int a; /+ int a; // [warn]: Variable "A.a" has the same name as a variable defined on line 89.
^ [warn]: Variable "A.a" has the same name as a variable defined on line 93. +/
} }
} }
@ -276,7 +381,6 @@ unittest
break; break;
} }
} }
}c, sac); }c, sac);
stderr.writeln("Unittest for LabelVarNameCheck passed."); stderr.writeln("Unittest for LabelVarNameCheck passed.");
} }

View File

@ -843,10 +843,6 @@ private BaseAnalyzer[] getAnalyzersForModuleAndConfig(string fileName,
checks ~= new FunctionAttributeCheck(args.setSkipTests( checks ~= new FunctionAttributeCheck(args.setSkipTests(
analysisConfig.function_attribute_check == Check.skipTests && !ut)); analysisConfig.function_attribute_check == Check.skipTests && !ut));
if (moduleName.shouldRun!LabelVarNameCheck(analysisConfig))
checks ~= new LabelVarNameCheck(args.setSkipTests(
analysisConfig.label_var_same_name_check == Check.skipTests && !ut));
if (moduleName.shouldRun!MismatchedArgumentCheck(analysisConfig)) if (moduleName.shouldRun!MismatchedArgumentCheck(analysisConfig))
checks ~= new MismatchedArgumentCheck(args.setSkipTests( checks ~= new MismatchedArgumentCheck(args.setSkipTests(
analysisConfig.mismatched_args_check == Check.skipTests && !ut)); analysisConfig.mismatched_args_check == Check.skipTests && !ut));
@ -1351,6 +1347,12 @@ MessageSet analyzeDmd(string fileName, ASTCodegen.Module m, const char[] moduleN
config.max_cyclomatic_complexity.to!int config.max_cyclomatic_complexity.to!int
); );
if (moduleName.shouldRunDmd!(LabelVarNameCheck!ASTCodegen)(config))
visitors ~= new LabelVarNameCheck!ASTCodegen(
fileName,
config.label_var_same_name_check == Check.skipTests && !ut
);
foreach (visitor; visitors) foreach (visitor; visitors)
{ {
m.accept(visitor); m.accept(visitor);