D-Scanner/src/dscanner/analysis/body_on_disabled_funcs.d

216 lines
5.9 KiB
D

module dscanner.analysis.body_on_disabled_funcs;
import dscanner.analysis.base;
import dparse.ast;
import dparse.lexer;
import dsymbol.scope_;
import std.meta : AliasSeq;
final class BodyOnDisabledFuncsCheck : BaseAnalyzer
{
alias visit = BaseAnalyzer.visit;
mixin AnalyzerInfo!"body_on_disabled_func_check";
this(BaseAnalyzerArguments args)
{
super(args);
}
static foreach (AggregateType; AliasSeq!(InterfaceDeclaration, ClassDeclaration,
StructDeclaration, UnionDeclaration, FunctionDeclaration))
override void visit(const AggregateType t)
{
scope wasDisabled = isDisabled;
isDisabled = false;
t.accept(this);
isDisabled = wasDisabled;
}
override void visit(const Declaration dec)
{
foreach (attr; dec.attributes)
{
if (attr.atAttribute !is null && attr.atAttribute.identifier.text == "disable") {
// found attr block w. disable: dec.constructor
scope wasDisabled = isDisabled;
isDisabled = true;
visitDeclarationInner(dec);
dec.accept(this);
isDisabled = wasDisabled;
return;
}
}
visitDeclarationInner(dec);
scope wasDisabled = isDisabled;
dec.accept(this);
isDisabled = wasDisabled;
}
private:
bool isDisabled = false;
bool isDeclDisabled(T)(const T dec)
{
// `@disable { ... }`
if (isDisabled)
return true;
static if (__traits(hasMember, T, "storageClasses"))
{
// `@disable doThing() {}`
if (hasDisabledStorageclass(dec.storageClasses))
return true;
}
// `void doThing() @disable {}`
return hasDisabledMemberFunctionAttribute(dec.memberFunctionAttributes);
}
void visitDeclarationInner(const Declaration dec)
{
if (dec.attributeDeclaration !is null
&& dec.attributeDeclaration.attribute
&& dec.attributeDeclaration.attribute.atAttribute
&& dec.attributeDeclaration.attribute.atAttribute.identifier.text == "disable")
{
// found `@disable:`, so all code in this block is now disabled
isDisabled = true;
}
else if (dec.functionDeclaration !is null
&& isDeclDisabled(dec.functionDeclaration)
&& dec.functionDeclaration.functionBody !is null
&& dec.functionDeclaration.functionBody.missingFunctionBody is null)
{
addErrorMessage(dec.functionDeclaration.functionBody,
KEY, "Function marked with '@disabled' should not have a body");
}
else if (dec.constructor !is null
&& isDeclDisabled(dec.constructor)
&& dec.constructor.functionBody !is null
&& dec.constructor.functionBody.missingFunctionBody is null)
{
addErrorMessage(dec.constructor.functionBody,
KEY, "Constructor marked with '@disabled' should not have a body");
}
else if (dec.destructor !is null
&& isDeclDisabled(dec.destructor)
&& dec.destructor.functionBody !is null
&& dec.destructor.functionBody.missingFunctionBody is null)
{
addErrorMessage(dec.destructor.functionBody,
KEY, "Destructor marked with '@disabled' should not have a body");
}
}
bool hasDisabledStorageclass(const(StorageClass[]) storageClasses)
{
foreach (sc; storageClasses)
{
if (sc.atAttribute !is null && sc.atAttribute.identifier.text == "disable")
return true;
}
return false;
}
bool hasDisabledMemberFunctionAttribute(const(MemberFunctionAttribute[]) memberFunctionAttributes)
{
foreach (attr; memberFunctionAttributes)
{
if (attr.atAttribute !is null && attr.atAttribute.identifier.text == "disable")
return true;
}
return false;
}
enum string KEY = "dscanner.confusing.disabled_function_with_body";
}
unittest
{
import std.stdio : stderr;
import std.format : format;
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
import dscanner.analysis.helpers : assertAnalyzerWarnings;
StaticAnalysisConfig sac = disabledConfig();
sac.body_on_disabled_func_check = Check.enabled;
assertAnalyzerWarnings(q{
class C1
{
this() {}
void doThing() {}
~this() {}
this();
void doThing();
~this();
@disable:
@disable
{
class UnaffectedSubClass
{
this() {}
void doThing() {}
~this() {}
}
}
this() {} /+
^^ [warn]: Constructor marked with '@disabled' should not have a body +/
void doThing() {} /+
^^ [warn]: Function marked with '@disabled' should not have a body +/
~this() {} /+
^^ [warn]: Destructor marked with '@disabled' should not have a body +/
this();
void doThing();
~this();
}
class C2
{
@disable this() {} /+
^^ [warn]: Constructor marked with '@disabled' should not have a body +/
@disable { this() {} } /+
^^ [warn]: Constructor marked with '@disabled' should not have a body +/
this() @disable {} /+
^^ [warn]: Constructor marked with '@disabled' should not have a body +/
@disable void doThing() {} /+
^^ [warn]: Function marked with '@disabled' should not have a body +/
@disable doThing() {} /+
^^ [warn]: Function marked with '@disabled' should not have a body +/
@disable { void doThing() {} } /+
^^ [warn]: Function marked with '@disabled' should not have a body +/
void doThing() @disable {} /+
^^ [warn]: Function marked with '@disabled' should not have a body +/
@disable ~this() {} /+
^^ [warn]: Destructor marked with '@disabled' should not have a body +/
@disable { ~this() {} } /+
^^ [warn]: Destructor marked with '@disabled' should not have a body +/
~this() @disable {} /+
^^ [warn]: Destructor marked with '@disabled' should not have a body +/
@disable this();
@disable { this(); }
this() @disable;
@disable void doThing();
// @disable doThing(); // this is invalid grammar!
@disable { void doThing(); }
void doThing() @disable;
@disable ~this();
@disable { ~this(); }
~this() @disable;
}
}c, sac);
stderr.writeln("Unittest for BodyOnDisabledFuncsCheck passed.");
}