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:
Basile Burg 2017-06-28 08:08:33 +02:00 committed by GitHub
parent 3aeee9a108
commit f89d356601
11 changed files with 323 additions and 198 deletions

View File

@ -56,7 +56,7 @@ public:
protected: protected:
bool inAggregate = false; bool inAggregate;
bool skipTests; bool skipTests;
template visitTemplate(T) template visitTemplate(T)

View File

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

View File

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

View File

@ -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"

View File

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

View File

@ -65,7 +65,7 @@ class ObjectConstCheck : BaseAnalyzer
|| name == "toString" || name == "opCast"; || name == "toString" || name == "opCast";
} }
private bool looking = false; private bool looking;
} }

View File

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

View File

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

View File

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

View File

@ -223,7 +223,7 @@ private:
castExpression.accept(this); castExpression.accept(this);
} }
bool foundCast = false; bool foundCast;
} }
if (initializer is null) if (initializer is null)

View File

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