Fixes cases of false or non positive with the useless init check (#475)
* fix #474 fix #473 fix #476 - Cases of false and non positive with the useless init check * do not warn on documented variables * fix #477 - Custom type initialized to init should not trigger a warn * allow struct.init when know struct has `@disable` ctor * fix last false detection in phobos * prevent check in the "compiles" trait * - use canFind when filter.empty was negated - FQN the struct names - prevent a double query in the canBeInit AA - import the whole also package - there was not test on non-initilized variables * fix, self-linting missed a case that was not yet fixed * fix more undetected warns during self linting * use a flag instead of a stack + apply skipTests * convert spaces to tabs
This commit is contained in:
parent
3aeee9a108
commit
f89d356601
|
@ -56,7 +56,7 @@ public:
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
bool inAggregate = false;
|
bool inAggregate;
|
||||||
bool skipTests;
|
bool skipTests;
|
||||||
|
|
||||||
template visitTemplate(T)
|
template visitTemplate(T)
|
||||||
|
|
|
@ -34,12 +34,12 @@ class DuplicateAttributeCheck : BaseAnalyzer
|
||||||
|
|
||||||
void checkAttributes(const Declaration node)
|
void checkAttributes(const Declaration node)
|
||||||
{
|
{
|
||||||
bool hasProperty = false;
|
bool hasProperty;
|
||||||
bool hasSafe = false;
|
bool hasSafe;
|
||||||
bool hasTrusted = false;
|
bool hasTrusted;
|
||||||
bool hasSystem = false;
|
bool hasSystem;
|
||||||
bool hasPure = false;
|
bool hasPure;
|
||||||
bool hasNoThrow = false;
|
bool hasNoThrow;
|
||||||
|
|
||||||
// Check the attributes
|
// Check the attributes
|
||||||
foreach (attribute; node.attributes)
|
foreach (attribute; node.attributes)
|
||||||
|
|
|
@ -24,7 +24,7 @@ class EnumArrayLiteralCheck : BaseAnalyzer
|
||||||
super(fileName, sc, skipTests);
|
super(fileName, sc, skipTests);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool looking = false;
|
bool looking;
|
||||||
|
|
||||||
mixin visitTemplate!ClassDeclaration;
|
mixin visitTemplate!ClassDeclaration;
|
||||||
mixin visitTemplate!InterfaceDeclaration;
|
mixin visitTemplate!InterfaceDeclaration;
|
||||||
|
|
|
@ -59,8 +59,8 @@ class FunctionAttributeCheck : BaseAnalyzer
|
||||||
{
|
{
|
||||||
if (dec.parameters.parameters.length == 0)
|
if (dec.parameters.parameters.length == 0)
|
||||||
{
|
{
|
||||||
bool foundConst = false;
|
bool foundConst;
|
||||||
bool foundProperty = false;
|
bool foundProperty;
|
||||||
foreach (attribute; dec.attributes)
|
foreach (attribute; dec.attributes)
|
||||||
foundConst = foundConst || attribute.attribute.type == tok!"const"
|
foundConst = foundConst || attribute.attribute.type == tok!"const"
|
||||||
|| attribute.attribute.type == tok!"immutable"
|
|| attribute.attribute.type == tok!"immutable"
|
||||||
|
|
|
@ -125,7 +125,6 @@ private:
|
||||||
size_t length;
|
size_t length;
|
||||||
const newLine = tok.line > prevLine;
|
const newLine = tok.line > prevLine;
|
||||||
bool multiLine;
|
bool multiLine;
|
||||||
|
|
||||||
if (tok.text is null)
|
if (tok.text is null)
|
||||||
length += str(tok.type).length;
|
length += str(tok.type).length;
|
||||||
else
|
else
|
||||||
|
|
|
@ -65,7 +65,7 @@ class ObjectConstCheck : BaseAnalyzer
|
||||||
|| name == "toString" || name == "opCast";
|
|| name == "toString" || name == "opCast";
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool looking = false;
|
private bool looking;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,9 +39,9 @@ class OpEqualsWithoutToHashCheck : BaseAnalyzer
|
||||||
|
|
||||||
private void actualCheck(const Token name, const StructBody structBody)
|
private void actualCheck(const Token name, const StructBody structBody)
|
||||||
{
|
{
|
||||||
bool hasOpEquals = false;
|
bool hasOpEquals;
|
||||||
bool hasToHash = false;
|
bool hasToHash;
|
||||||
bool hasOpCmp = false;
|
bool hasOpCmp;
|
||||||
|
|
||||||
// Just return if missing children
|
// Just return if missing children
|
||||||
if (!structBody || !structBody.declarations || name is Token.init)
|
if (!structBody || !structBody.declarations || name is Token.init)
|
||||||
|
|
|
@ -161,7 +161,7 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config,
|
||||||
bool analyze(string[] fileNames, const StaticAnalysisConfig config,
|
bool analyze(string[] fileNames, const StaticAnalysisConfig config,
|
||||||
ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true)
|
ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true)
|
||||||
{
|
{
|
||||||
bool hasErrors = false;
|
bool hasErrors;
|
||||||
foreach (fileName; fileNames)
|
foreach (fileName; fileNames)
|
||||||
{
|
{
|
||||||
auto code = readFile(fileName);
|
auto code = readFile(fileName);
|
||||||
|
|
|
@ -51,10 +51,10 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
|
||||||
immutable bool prevOverride = getOverride();
|
immutable bool prevOverride = getOverride();
|
||||||
immutable bool prevDisabled = getDisabled();
|
immutable bool prevDisabled = getDisabled();
|
||||||
immutable bool prevDeprecated = getDeprecated();
|
immutable bool prevDeprecated = getDeprecated();
|
||||||
bool dis = false;
|
bool dis;
|
||||||
bool dep = false;
|
bool dep;
|
||||||
bool ovr = false;
|
bool ovr;
|
||||||
bool pushed = false;
|
bool pushed;
|
||||||
foreach (attribute; dec.attributes)
|
foreach (attribute; dec.attributes)
|
||||||
{
|
{
|
||||||
if (isProtection(attribute.attribute.type))
|
if (isProtection(attribute.attribute.type))
|
||||||
|
|
|
@ -223,7 +223,7 @@ private:
|
||||||
castExpression.accept(this);
|
castExpression.accept(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool foundCast = false;
|
bool foundCast;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initializer is null)
|
if (initializer is null)
|
||||||
|
|
|
@ -5,13 +5,19 @@
|
||||||
module analysis.useless_initializer;
|
module analysis.useless_initializer;
|
||||||
|
|
||||||
import analysis.base;
|
import analysis.base;
|
||||||
|
import containers.dynamicarray;
|
||||||
|
import containers.hashmap;
|
||||||
import dparse.ast;
|
import dparse.ast;
|
||||||
import dparse.lexer;
|
import dparse.lexer;
|
||||||
|
import std.algorithm;
|
||||||
|
import std.range : empty;
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Limitations:
|
Limitations:
|
||||||
- Stuff = Stuff.init doesnot work with type with * []
|
- 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).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,49 +31,143 @@ final class UselessInitializerChecker : BaseAnalyzer
|
||||||
private:
|
private:
|
||||||
|
|
||||||
enum key = "dscanner.useless-initializer";
|
enum key = "dscanner.useless-initializer";
|
||||||
|
|
||||||
version(unittest)
|
version(unittest)
|
||||||
|
{
|
||||||
enum msg = "X";
|
enum msg = "X";
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
enum msg = `Variable %s initializer is useless because it does not differ from the default value`;
|
enum msg = `Variable %s initializer is useless because it does not differ from the default value`;
|
||||||
|
}
|
||||||
|
|
||||||
static immutable strDefs = [`""`, `""c`, `""w`, `""d`, "``", "``c", "``w", "``d", "q{}"];
|
static immutable strDefs = [`""`, `""c`, `""w`, `""d`, "``", "``c", "``w", "``d", "q{}"];
|
||||||
static immutable intDefs = ["0", "0L", "0UL", "0uL", "0U", "0x0", "0b0"];
|
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:
|
public:
|
||||||
|
|
||||||
///
|
///
|
||||||
this(string fileName, bool skipTests = false)
|
this(string fileName, bool skipTests = false)
|
||||||
{
|
{
|
||||||
super(fileName, null, skipTests);
|
super(fileName, null, skipTests);
|
||||||
|
_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);
|
||||||
|
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)
|
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)
|
foreach (declarator; decl.declarators)
|
||||||
{
|
{
|
||||||
if (!decl.type || !decl.type.type2)
|
if (!declarator.initializer ||
|
||||||
|
!declarator.initializer.nonVoidInitializer ||
|
||||||
|
declarator.comment !is null)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
if (!declarator.initializer || !declarator.initializer.nonVoidInitializer)
|
}
|
||||||
continue;
|
|
||||||
|
|
||||||
import std.format : format;
|
|
||||||
|
|
||||||
version(unittest)
|
version(unittest)
|
||||||
enum warn = q{addErrorMessage(declarator.name.line, declarator.name.column,
|
{
|
||||||
key, msg);};
|
enum warn = q{addErrorMessage(declarator.name.line,
|
||||||
|
declarator.name.column, key, msg);};
|
||||||
|
}
|
||||||
else
|
else
|
||||||
enum warn = q{addErrorMessage(declarator.name.line, declarator.name.column,
|
{
|
||||||
key, msg.format(declarator.name.text));};
|
import std.format : format;
|
||||||
|
enum warn = q{addErrorMessage(declarator.name.line,
|
||||||
|
declarator.name.column, key, msg.format(declarator.name.text));};
|
||||||
|
}
|
||||||
|
|
||||||
// --- Info about the declaration type --- //
|
// --- Info about the declaration type --- //
|
||||||
import std.algorithm : among, canFind;
|
const bool isPtr = decl.type.typeSuffixes && decl.type.typeSuffixes
|
||||||
import std.algorithm.iteration : filter;
|
.canFind!(a => a.star != tok!"");
|
||||||
import std.range : empty;
|
const bool isArr = decl.type.typeSuffixes && decl.type.typeSuffixes
|
||||||
|
.canFind!(a => a.array);
|
||||||
const bool isPtr = decl.type.typeSuffixes && !decl.type.typeSuffixes
|
|
||||||
.filter!(a => a.star != tok!"").empty;
|
|
||||||
const bool isArr = decl.type.typeSuffixes && !decl.type.typeSuffixes
|
|
||||||
.filter!(a => a.array).empty;
|
|
||||||
|
|
||||||
bool isStr, isSzInt;
|
bool isStr, isSzInt;
|
||||||
Token customType;
|
Token customType;
|
||||||
|
@ -100,7 +200,8 @@ public:
|
||||||
case tok!"int", tok!"uint":
|
case tok!"int", tok!"uint":
|
||||||
case tok!"long", tok!"ulong":
|
case tok!"long", tok!"ulong":
|
||||||
case tok!"cent", tok!"ucent":
|
case tok!"cent", tok!"ucent":
|
||||||
if (intDefs.canFind(value.text))
|
case tok!"bool":
|
||||||
|
if (intDefs.canFind(value.text) || value == tok!"false")
|
||||||
mixin(warn);
|
mixin(warn);
|
||||||
goto default;
|
goto default;
|
||||||
default:
|
default:
|
||||||
|
@ -154,8 +255,12 @@ public:
|
||||||
ue.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier == customType &&
|
ue.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier == customType &&
|
||||||
ue.identifierOrTemplateInstance && ue.identifierOrTemplateInstance.identifier.text == "init")
|
ue.identifierOrTemplateInstance && ue.identifierOrTemplateInstance.identifier.text == "init")
|
||||||
{
|
{
|
||||||
|
if (customType.text in _structCanBeInit)
|
||||||
|
{
|
||||||
|
if (!_structCanBeInit[customType.text])
|
||||||
mixin(warn);
|
mixin(warn);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 'Symbol ArrayInitializer' : assumes Symbol is an array b/c of the Init
|
// 'Symbol ArrayInitializer' : assumes Symbol is an array b/c of the Init
|
||||||
else if (nvi.arrayInitializer && (isArr || isStr))
|
else if (nvi.arrayInitializer && (isArr || isStr))
|
||||||
|
@ -180,6 +285,7 @@ public:
|
||||||
|
|
||||||
// fails
|
// fails
|
||||||
assertAnalyzerWarnings(q{
|
assertAnalyzerWarnings(q{
|
||||||
|
struct S {}
|
||||||
ubyte a = 0x0; // [warn]: X
|
ubyte a = 0x0; // [warn]: X
|
||||||
int a = 0; // [warn]: X
|
int a = 0; // [warn]: X
|
||||||
ulong a = 0; // [warn]: X
|
ulong a = 0; // [warn]: X
|
||||||
|
@ -199,10 +305,13 @@ public:
|
||||||
int a = int.init; // [warn]: X
|
int a = int.init; // [warn]: X
|
||||||
char a = char.init; // [warn]: X
|
char a = char.init; // [warn]: X
|
||||||
S s = S.init; // [warn]: X
|
S s = S.init; // [warn]: X
|
||||||
|
bool a = false; // [warn]: X
|
||||||
}, sac);
|
}, sac);
|
||||||
|
|
||||||
// passes
|
// passes
|
||||||
assertAnalyzerWarnings(q{
|
assertAnalyzerWarnings(q{
|
||||||
|
struct D {@disable this();}
|
||||||
|
struct E {this() @disable;}
|
||||||
ubyte a = 0xFE;
|
ubyte a = 0xFE;
|
||||||
int a = 1;
|
int a = 1;
|
||||||
ulong a = 1;
|
ulong a = 1;
|
||||||
|
@ -216,11 +325,28 @@ public:
|
||||||
dstring a = "fgh"d;
|
dstring a = "fgh"d;
|
||||||
string a = q{int a;};
|
string a = q{int a;};
|
||||||
size_t a = 1;
|
size_t a = 1;
|
||||||
ptrdiff_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 = ['a'];
|
||||||
char[] a = "ze";
|
char[] a = "ze";
|
||||||
S s = S(0,1);
|
S s = S(0,1);
|
||||||
S s = s.call();
|
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);
|
}, sac);
|
||||||
|
|
||||||
stderr.writeln("Unittest for UselessInitializerChecker passed.");
|
stderr.writeln("Unittest for UselessInitializerChecker passed.");
|
||||||
|
|
Loading…
Reference in New Issue