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

410 lines
9.9 KiB
D

// Copyright Basile Burg 2017.
// 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.useless_initializer;
import dscanner.analysis.base;
import dscanner.analysis.nolint;
import dscanner.utils : safeAccess;
import containers.dynamicarray;
import containers.hashmap;
import dparse.ast;
import dparse.lexer;
import std.algorithm;
import std.range : empty;
import std.stdio;
/*
Limitations:
- Stuff s = Stuff.init does not work with type with postfixes`*` `[]`.
- Stuff s = Stuff.init is only detected for struct within the module.
- BasicType b = BasicType(v), default init used in BasicType ctor, e.g int(8).
*/
/**
* Check that detects the initializers that are
* not different from the implcit initializer.
*/
final class UselessInitializerChecker : BaseAnalyzer
{
alias visit = BaseAnalyzer.visit;
mixin AnalyzerInfo!"useless_initializer";
private:
enum string KEY = "dscanner.useless-initializer";
version(unittest)
{
enum msg = "X";
}
else
{
enum msg = "Variable `%s` initializer is useless because it does not differ from the default value";
}
static immutable intDefs = ["0", "0L", "0UL", "0uL", "0U", "0x0", "0b0"];
HashMap!(string, bool) _structCanBeInit;
DynamicArray!(string) _structStack;
DynamicArray!(bool) _inStruct;
DynamicArray!(bool) _atDisabled;
bool _inTest;
public:
///
this(BaseAnalyzerArguments args)
{
super(args);
_inStruct.insert(false);
}
override void visit(const(Unittest) test)
{
if (skipTests)
return;
_inTest = true;
test.accept(this);
_inTest = false;
}
override void visit(const(StructDeclaration) decl)
{
if (_inTest)
return;
assert(_inStruct.length > 1);
const string structName = _inStruct[$-2] ?
_structStack.back() ~ "." ~ decl.name.text :
decl.name.text;
_structStack.insert(structName);
_structCanBeInit[structName] = false;
_atDisabled.insert(false);
decl.accept(this);
_structStack.removeBack();
_atDisabled.removeBack();
}
override void visit(const(Declaration) decl)
{
_inStruct.insert(decl.structDeclaration !is null);
with (noLint.push(NoLintFactory.fromDeclaration(decl)))
decl.accept(this);
if (_inStruct.length > 1 && _inStruct[$-2] && decl.constructor &&
((decl.constructor.parameters && decl.constructor.parameters.parameters.length == 0) ||
!decl.constructor.parameters))
{
_atDisabled[$-1] = decl.attributes
.canFind!(a => a.atAttribute !is null && a.atAttribute.identifier.text == "disable");
}
_inStruct.removeBack();
}
override void visit(const(Constructor) decl)
{
if (_inStruct.length > 1 && _inStruct[$-2] &&
((decl.parameters && decl.parameters.parameters.length == 0) || !decl.parameters))
{
const bool canBeInit = !_atDisabled[$-1];
_structCanBeInit[_structStack.back()] = canBeInit;
if (!canBeInit)
_structCanBeInit[_structStack.back()] = !decl.memberFunctionAttributes
.canFind!(a => a.atAttribute !is null && a.atAttribute.identifier.text == "disable");
}
decl.accept(this);
}
// issue 473, prevent to visit delegates that contain duck type checkers.
override void visit(const(TypeofExpression)) {}
// issue 473, prevent to check expressions in __traits(compiles, ...)
override void visit(const(TraitsExpression) e)
{
if (e.identifier.text == "compiles")
{
return;
}
else
{
e.accept(this);
}
}
override void visit(const(VariableDeclaration) decl)
{
if (!decl.type || !decl.type.type2 ||
// initializer has to appear clearly in generated ddoc
decl.comment !is null ||
// issue 474, manifest constants HAVE to be initialized.
decl.storageClasses.canFind!(a => a.token == tok!"enum"))
{
return;
}
foreach (declarator; decl.declarators)
{
if (!declarator.initializer ||
!declarator.initializer.nonVoidInitializer ||
declarator.comment !is null)
{
continue;
}
version(unittest)
{
void warn(const BaseNode range)
{
addErrorMessage(range, KEY, msg);
}
}
else
{
import std.format : format;
void warn(const BaseNode range)
{
addErrorMessage(range, KEY, msg.format(declarator.name.text));
}
}
// --- Info about the declaration type --- //
const bool isPtr = decl.type.typeSuffixes && decl.type.typeSuffixes
.canFind!(a => a.star != tok!"");
const bool isArr = decl.type.typeSuffixes && decl.type.typeSuffixes
.canFind!(a => a.array);
bool isStr, isSzInt;
Token customType;
if (const TypeIdentifierPart tip = safeAccess(decl).type.type2.typeIdentifierPart)
{
if (!tip.typeIdentifierPart)
{
customType = tip.identifierOrTemplateInstance.identifier;
isStr = customType.text.among("string", "wstring", "dstring") != 0;
isSzInt = customType.text.among("size_t", "ptrdiff_t") != 0;
}
}
// --- 'BasicType/Symbol AssignExpression' ---//
const NonVoidInitializer nvi = declarator.initializer.nonVoidInitializer;
const UnaryExpression ue = cast(UnaryExpression) nvi.assignExpression;
if (ue && ue.primaryExpression)
{
const Token value = ue.primaryExpression.primary;
if (!isPtr && !isArr && !isStr && decl.type.type2.builtinType != tok!"")
{
switch(decl.type.type2.builtinType)
{
// check for common cases of default values
case tok!"byte", tok!"ubyte":
case tok!"short", tok!"ushort":
case tok!"int", tok!"uint":
case tok!"long", tok!"ulong":
case tok!"cent", tok!"ucent":
case tok!"bool":
if (intDefs.canFind(value.text) || value == tok!"false")
warn(nvi);
goto default;
default:
// check for BasicType.init
if (ue.primaryExpression.basicType.type == decl.type.type2.builtinType &&
ue.primaryExpression.primary.text == "init" &&
!ue.primaryExpression.expression)
warn(nvi);
}
}
else if (isSzInt)
{
if (intDefs.canFind(value.text))
warn(nvi);
}
else if (isPtr || isStr)
{
if (str(value.type) == "null")
warn(nvi);
}
else if (isArr)
{
if (str(value.type) == "null")
warn(nvi);
else if (nvi.arrayInitializer && nvi.arrayInitializer.arrayMemberInitializations.length == 0)
warn(nvi);
}
}
else if (const IdentifierOrTemplateInstance iot = safeAccess(ue)
.unaryExpression.primaryExpression.identifierOrTemplateInstance)
{
// Symbol s = Symbol.init
if (ue && customType != tok!"" && iot.identifier == customType &&
ue.identifierOrTemplateInstance && ue.identifierOrTemplateInstance.identifier.text == "init")
{
if (customType.text in _structCanBeInit)
{
if (!_structCanBeInit[customType.text])
warn(nvi);
}
}
}
// 'Symbol ArrayInitializer' : assumes Symbol is an array b/c of the Init
else if (nvi.arrayInitializer && (isArr || isStr))
{
if (nvi.arrayInitializer.arrayMemberInitializations.length == 0)
warn(nvi);
}
}
decl.accept(this);
}
}
@system unittest
{
import dscanner.analysis.config : Check, disabledConfig, StaticAnalysisConfig;
import dscanner.analysis.helpers: assertAnalyzerWarnings;
import std.stdio : stderr;
StaticAnalysisConfig sac = disabledConfig;
sac.useless_initializer = Check.enabled;
// fails
assertAnalyzerWarnings(q{
struct S {}
ubyte a = 0x0; /+
^^^ [warn]: X +/
int a = 0; /+
^ [warn]: X +/
ulong a = 0; /+
^ [warn]: X +/
int* a = null; /+
^^^^ [warn]: X +/
Foo* a = null; /+
^^^^ [warn]: X +/
int[] a = null; /+
^^^^ [warn]: X +/
int[] a = []; /+
^^ [warn]: X +/
string a = null; /+
^^^^ [warn]: X +/
string a = null; /+
^^^^ [warn]: X +/
wstring a = null; /+
^^^^ [warn]: X +/
dstring a = null; /+
^^^^ [warn]: X +/
size_t a = 0; /+
^ [warn]: X +/
ptrdiff_t a = 0; /+
^ [warn]: X +/
string a = []; /+
^^ [warn]: X +/
char[] a = null; /+
^^^^ [warn]: X +/
int a = int.init; /+
^^^^^^^^ [warn]: X +/
char a = char.init; /+
^^^^^^^^^ [warn]: X +/
S s = S.init; /+
^^^^^^ [warn]: X +/
bool a = false; /+
^^^^^ [warn]: X +/
}, sac);
// passes
assertAnalyzerWarnings(q{
struct D {@disable this();}
struct E {this() @disable;}
ubyte a = 0xFE;
int a = 1;
ulong a = 1;
int* a = &a;
Foo* a = &a;
int[] a = &a;
int[] a = [0];
string a = "sdf";
string a = "sdg"c;
wstring a = "sdg"w;
dstring a = "fgh"d;
string a = q{int a;};
size_t a = 1;
ptrdiff_t a;
ubyte a;
int a;
ulong a;
int* a;
Foo* a;
int[] a;
string a;
wstring a;
dstring a;
string a = ['a'];
string a = "";
string a = ""c;
wstring a = ""w;
dstring a = ""d;
string a = q{};
char[] a = "ze";
S s = S(0,1);
S s = s.call();
enum {a}
enum ubyte a = 0;
static assert(is(typeof((){T t = T.init;})));
void foo(){__traits(compiles, (){int a = 0;}).writeln;}
bool a;
D d = D.init;
E e = E.init;
NotKnown nk = NotKnown.init;
}, sac);
// passes
assertAnalyzerWarnings(q{
@("nolint(dscanner.useless-initializer)")
int a = 0;
int a = 0; /+
^ [warn]: X +/
@("nolint(dscanner.useless-initializer)")
int f() {
int a = 0;
}
struct nolint { string s; }
@nolint("dscanner.useless-initializer")
int a = 0;
int a = 0; /+
^ [warn]: X +/
@("nolint(other_check, dscanner.useless-initializer, another_one)")
int a = 0;
@nolint("other_check", "another_one", "dscanner.useless-initializer")
int a = 0;
}, sac);
// passes (disable check at module level)
assertAnalyzerWarnings(q{
@("nolint(dscanner.useless-initializer)")
module my_module;
int a = 0;
int f() {
int a = 0;
}
}, sac);
stderr.writeln("Unittest for UselessInitializerChecker passed.");
}