Merge upstream/master

This commit is contained in:
Sebastian Wilzbach 2017-09-14 01:55:08 +02:00
commit 35bd4f22af
28 changed files with 629 additions and 309 deletions

View File

@ -12,6 +12,11 @@ makefile has "ldc" and "gdc" targets if you'd prefer to compile with one of thes
compilers instead of DMD. To install, simply place the generated binary (in the
"bin" folder) somewhere on your $PATH.
### Testing
Testing does not work with DUB.
Under linux or OSX run the tests with `make test`.
Under Windows run the tests with `build.bat test`.
### Installing with DUB
```sh
@ -313,7 +318,7 @@ It is possible to create a new section `analysis.config.ModuleFilters` in the `.
In this optional section a comma-separated list of inclusion and exclusion selectors can
be specified for every check on which selective filtering should be applied.
These given selectors match on the module name and partial matches (`std.` or `.foo.`) are possible.
Morover, every selectors must begin with either `+` (inclusion) or `-` (exclusion).
Moreover, every selectors must begin with either `+` (inclusion) or `-` (exclusion).
Exclusion selectors take precedence over all inclusion operators.
Of course, for every check a different selector set can given:
@ -321,8 +326,6 @@ Of course, for every check a different selector set can given:
[analysis.config.ModuleFilters]
final_attribute_check = "+std.foo,+std.bar"
useless_initializer = "-std."
;pseudo variable (workaround against an inifiled bug)
foo=""
```
A few examples:
@ -333,3 +336,5 @@ A few examples:
- `+.bar`: Includes all modules matching `.bar` (e.g. `foo.bar`, `a.b.c.barros`)
- `-etc.`: Excludes all modules from `.etc`
- `+std,-std.internal`: Includes entire `std`, except for the internal modules
- `-etc.`: Excludes all modules from `.etc`
- `+std,-std.internal`: Includes entire `std`, except for the internal modules

View File

@ -1,7 +1,8 @@
@echo off
setlocal enabledelayedexpansion
set DFLAGS=-O -release -inline
set DFLAGS=-O -release -inline -version=StdLoggerDisableWarning
set TESTFLAGS=-g -w -version=StdLoggerDisableWarning
set CORE=
set LIBDPARSE=
set STD=
@ -13,8 +14,6 @@ set LIBDDOC=
for %%x in (src\*.d) do set CORE=!CORE! %%x
for %%x in (src\analysis\*.d) do set ANALYSIS=!ANALYSIS! %%x
for %%x in (libdparse\experimental_allocator\src\std\experimental\allocator\*.d) do set STD=!STD! %%x
for %%x in (libdparse\experimental_allocator\src\std\experimental\allocator\building_blocks\*.d) do set STD=!STD! %%x
for %%x in (libdparse\src\dparse\*.d) do set LIBDPARSE=!LIBDPARSE! %%x
for %%x in (libdparse\src\std\experimental\*.d) do set LIBDPARSE=!LIBDPARSE! %%x
for %%x in (libddoc\src\ddoc\*.d) do set LIBDDOC=!LIBDDOC! %%x
@ -25,6 +24,21 @@ for %%x in (dsymbol\src\dsymbol\conversion\*.d) do set DSYMBOL=!DSYMBOL! %%x
for %%x in (containers\src\containers\*.d) do set CONTAINERS=!CONTAINERS! %%x
for %%x in (containers\src\containers\internal\*.d) do set CONTAINERS=!CONTAINERS! %%x
@echo on
dmd %CORE% %STD% %LIBDPARSE% %LIBDDOC% %ANALYSIS% %INIFILED% %DSYMBOL% %CONTAINERS% %DFLAGS% -I"libdparse\src" -I"dsymbol\src" -I"containers\src" -I"libddoc\src" -ofdscanner.exe
if "%1" == "test" goto test_cmd
@echo on
dmd %CORE% %STD% %LIBDPARSE% %LIBDDOC% %ANALYSIS% %INIFILED% %DSYMBOL% %CONTAINERS% %DFLAGS% -I"libdparse\src" -I"dsymbol\src" -I"containers\src" -I"libddoc\src" -ofbin\dscanner.exe
goto eof
:test_cmd
@echo on
set TESTNAME="bin\dscanner-unittest"
dmd %STD% %LIBDPARSE% %LIBDDOC% %INIFILED% %DSYMBOL% %CONTAINERS% -I"libdparse\src" -I"dsymbol\src" -I"containers\src" -I"libddoc\src" -lib %TESTFLAGS% -of%TESTNAME%.lib
if exist %TESTNAME%.lib dmd %CORE% %ANALYSIS% %TESTNAME%.lib -I"src" -I"inifiled\source" -I"libdparse\src" -I"dsymbol\src" -I"containers\src" -I"libddoc\src" -unittest %TESTFLAGS% -of%TESTNAME%.exe
if exist %TESTNAME%.exe %TESTNAME%.exe
if exist %TESTNAME%.obj del %TESTNAME%.obj
if exist %TESTNAME%.lib del %TESTNAME%.lib
if exist %TESTNAME%.exe del %TESTNAME%.exe
:eof

@ -1 +1 @@
Subproject commit d22c9714a60ac05cb32db938e81a396cffb5ffa6
Subproject commit 6920a0489fbef44f105cdfb76d426a03ae14259a

View File

@ -1,19 +1,22 @@
{
"name": "dscanner",
"description": "Swiss-army knife for D source code",
"copyright": "© Brian Schott",
"authors": ["Brian Schott"],
"license" : "Boost Software License - Version 1.0",
"targetType": "autodetect",
"versions": [
"built_with_dub",
"StdLoggerDisableWarning"
],
"dependencies": {
"libdparse": "~>0.7.2-alpha.1",
"dsymbol": "~>0.2.0",
"inifiled": ">=0.0.6",
"emsi_containers": "~>0.5.3",
"libddoc": "~>0.2.0"
},
"name" : "dscanner",
"description" : "Swiss-army knife for D source code",
"copyright" : "© Brian Schott",
"authors" : [
"Brian Schott"
],
"license" : "Boost Software License - Version 1.0",
"targetType" : "autodetect",
"versions" : [
"built_with_dub",
"StdLoggerDisableWarning"
],
"dependencies" : {
"libdparse" : "~>0.7.2-alpha.1",
"dsymbol" : "~>0.2.6",
"inifiled" : ">=1.0.2",
"emsi_containers" : "~>0.5.3",
"libddoc" : "~>0.2.0"
},
"targetPath" : "bin"
}

View File

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

View File

@ -34,12 +34,12 @@ class DuplicateAttributeCheck : BaseAnalyzer
void checkAttributes(const Declaration node)
{
bool hasProperty = false;
bool hasSafe = false;
bool hasTrusted = false;
bool hasSystem = false;
bool hasPure = false;
bool hasNoThrow = false;
bool hasProperty;
bool hasSafe;
bool hasTrusted;
bool hasSystem;
bool hasPure;
bool hasNoThrow;
// Check the attributes
foreach (attribute; node.attributes)

View File

@ -24,7 +24,7 @@ class EnumArrayLiteralCheck : BaseAnalyzer
super(fileName, sc, skipTests);
}
bool looking = false;
bool looking;
mixin visitTemplate!ClassDeclaration;
mixin visitTemplate!InterfaceDeclaration;

View File

@ -30,6 +30,7 @@ private:
static immutable class_t = "templated functions declared within a class are never virtual";
static immutable class_p = "private functions declared within a class are never virtual";
static immutable class_f = "functions declared within a final class are never virtual";
static immutable class_s = "static functions are never virtual";
static immutable interface_t = "templated functions declared within an interface are never virtual";
static immutable struct_f = "functions declared within a struct are never virtual";
static immutable union_f = "functions declared within an union are never virtual";
@ -49,6 +50,8 @@ private:
bool[] _private;
bool _finalAggregate;
bool _alwaysStatic;
bool _blockStatic;
Parent _parent = Parent.module_;
void addError(T)(T t, string msg)
@ -75,6 +78,7 @@ public:
const Parent saved = _parent;
_parent = Parent.struct_;
_private.length += 1;
_alwaysStatic = false;
sd.accept(this);
_private.length -= 1;
_parent = saved;
@ -85,6 +89,7 @@ public:
const Parent saved = _parent;
_parent = Parent.interface_;
_private.length += 1;
_alwaysStatic = false;
id.accept(this);
_private.length -= 1;
_parent = saved;
@ -95,6 +100,7 @@ public:
const Parent saved = _parent;
_parent = Parent.union_;
_private.length += 1;
_alwaysStatic = false;
ud.accept(this);
_private.length -= 1;
_parent = saved;
@ -105,6 +111,7 @@ public:
const Parent saved = _parent;
_parent = Parent.class_;
_private.length += 1;
_alwaysStatic = false;
cd.accept(this);
_private.length -= 1;
_parent = saved;
@ -120,14 +127,34 @@ public:
// regular template are also mixable
}
override void visit(const(AttributeDeclaration) decl)
{
if (_parent == Parent.class_ && decl.attribute &&
decl.attribute.attribute == tok!"static")
_alwaysStatic = true;
}
override void visit(const(Declaration) d)
{
import std.algorithm.iteration : filter;
import std.algorithm.searching : canFind;
const Parent savedParent = _parent;
bool undoBlockStatic;
if (_parent == Parent.class_ && d.attributes &&
d.attributes.canFind!(a => a.attribute == tok!"static"))
{
_blockStatic = true;
undoBlockStatic = true;
}
scope(exit)
{
d.accept(this);
_parent = savedParent;
if (undoBlockStatic)
_blockStatic = false;
}
if (!d.attributeDeclaration &&
@ -138,30 +165,27 @@ public:
!d.functionDeclaration)
return;
import std.algorithm.iteration : filter;
import std.algorithm.searching : find;
import std.range.primitives : empty;
if (d.attributeDeclaration && d.attributeDeclaration.attribute)
{
const tp = d.attributeDeclaration.attribute.attribute.type;
_private[$-1] = isProtection(tp) & (tp == tok!"private");
}
const bool isFinal = !d.attributes
.find!(a => a.attribute.type == tok!"final")
.empty;
const bool isFinal = d.attributes
.canFind!(a => a.attribute.type == tok!"final");
const bool isStaticOnce = d.attributes
.canFind!(a => a.attribute.type == tok!"static");
// determine if private
const bool changeProtectionOnce = !d.attributes
.filter!(a => a.attribute.type.isProtection)
.empty;
const bool changeProtectionOnce = d.attributes
.canFind!(a => a.attribute.type.isProtection);
const bool isPrivateOnce = !d.attributes
.find!(a => a.attribute.type == tok!"private")
.empty;
const bool isPrivateOnce = d.attributes
.canFind!(a => a.attribute.type == tok!"private");
bool isPrivate;
if (isPrivateOnce)
isPrivate = true;
else if (_private[$-1] && !changeProtectionOnce)
@ -194,6 +218,8 @@ public:
addError(fd, MESSAGE.class_t);
if (isPrivate)
addError(fd, MESSAGE.class_p);
else if (isStaticOnce || _alwaysStatic || _blockStatic)
addError(fd, MESSAGE.class_s);
else if (_finalAggregate)
addError(fd, MESSAGE.class_f);
break;
@ -349,5 +375,32 @@ public:
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_p)
), sac);
assertAnalyzerWarnings(q{
class Foo {final static void foo(){}} // [warn]: %s
}c.format(
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
), sac);
assertAnalyzerWarnings(q{
class Foo
{
void foo(){}
static: final void foo(){} // [warn]: %s
}
}c.format(
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
), sac);
assertAnalyzerWarnings(q{
class Foo
{
void foo(){}
static{ final void foo(){}} // [warn]: %s
void foo(){}
}
}c.format(
FinalAttributeChecker.MSGB.format(FinalAttributeChecker.MESSAGE.class_s)
), sac);
stderr.writeln("Unittest for FinalAttributeChecker passed.");
}

View File

@ -59,8 +59,8 @@ class FunctionAttributeCheck : BaseAnalyzer
{
if (dec.parameters.parameters.length == 0)
{
bool foundConst = false;
bool foundProperty = false;
bool foundConst;
bool foundProperty;
foreach (attribute; dec.attributes)
foundConst = foundConst || attribute.attribute.type == tok!"const"
|| attribute.attribute.type == tok!"immutable"

View File

@ -50,6 +50,7 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config,
{
import analysis.run : parseModule;
import dparse.lexer : StringCache, Token;
import std.ascii : newline;
StringCache cache = StringCache(StringCache.defaultBucketCount);
RollbackAllocator r;
@ -60,7 +61,7 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config,
// Run the code and get any warnings
MessageSet rawWarnings = analyze("test", m, config, moduleCache, tokens);
string[] codeLines = code.split("\n");
string[] codeLines = code.split(newline);
// Get the warnings ordered by line
string[size_t] warnings;

View File

@ -28,6 +28,11 @@ class LabelVarNameCheck : BaseAnalyzer
mixin ScopedVisit!IfStatement;
mixin ScopedVisit!TemplateDeclaration;
mixin AggregateVisit!ClassDeclaration;
mixin AggregateVisit!StructDeclaration;
mixin AggregateVisit!InterfaceDeclaration;
mixin AggregateVisit!UnionDeclaration;
override void visit(const VariableDeclaration var)
{
foreach (dec; var.declarators)
@ -63,6 +68,16 @@ private:
Thing[string][] stack;
template AggregateVisit(NodeType)
{
override void visit(const NodeType n)
{
pushAggregateName(n.name);
n.accept(this);
popAggregateName();
}
}
template ScopedVisit(NodeType)
{
override void visit(const NodeType n)
@ -81,15 +96,16 @@ private:
size_t i;
foreach (s; retro(stack))
{
const(Thing)* thing = name.text in s;
string fqn = parentAggregateText ~ name.text;
const(Thing)* thing = fqn in s;
if (thing is null)
currentScope[name.text] = Thing(name.text, name.line, name.column, !fromLabel /+, isConditional+/ );
currentScope[fqn] = Thing(fqn, name.line, name.column, !fromLabel /+, isConditional+/ );
else if (i != 0 || !isConditional)
{
immutable thisKind = fromLabel ? "Label" : "Variable";
immutable otherKind = thing.isVar ? "variable" : "label";
addErrorMessage(name.line, name.column, "dscanner.suspicious.label_var_same_name",
thisKind ~ " \"" ~ name.text ~ "\" has the same name as a "
thisKind ~ " \"" ~ fqn ~ "\" has the same name as a "
~ otherKind ~ " defined on line " ~ to!string(thing.line) ~ ".");
}
++i;
@ -121,6 +137,32 @@ private:
}
int conditionalDepth;
void pushAggregateName(Token name)
{
parentAggregates ~= name;
updateAggregateText();
}
void popAggregateName()
{
parentAggregates.length -= 1;
updateAggregateText();
}
void updateAggregateText()
{
import std.algorithm : map;
import std.array : join;
if (parentAggregates.length)
parentAggregateText = parentAggregates.map!(a => a.text).join(".") ~ ".";
else
parentAggregateText = "";
}
Token[] parentAggregates;
string parentAggregateText;
}
unittest
@ -190,6 +232,45 @@ unittest
else version(BigEndian) { enum string NAME = "UTF-16BE"; }
}
unittest
{
int a;
struct A {int a;}
}
unittest
{
int a;
struct A { struct A {int a;}}
}
unittest
{
int a;
class A { class A {int a;}}
}
unittest
{
int a;
interface A { interface A {int a;}}
}
unittest
{
interface A
{
int a;
int a; // [warn]: Variable "A.a" has the same name as a variable defined on line 89.
}
}
unittest
{
int aa;
struct a { int a; }
}
}c, sac);
stderr.writeln("Unittest for LabelVarNameCheck passed.");
}

View File

@ -44,6 +44,7 @@ private:
enum KEY = "dscanner.confusing.lambda_returns_lambda";
}
version(Windows) {/*because of newline in code*/} else
unittest
{
import analysis.helpers : assertAnalyzerWarnings;

View File

@ -125,7 +125,6 @@ private:
size_t length;
const newLine = tok.line > prevLine;
bool multiLine;
if (tok.text is null)
length += str(tok.type).length;
else

View File

@ -21,6 +21,7 @@ class ObjectConstCheck : BaseAnalyzer
{
alias visit = BaseAnalyzer.visit;
///
this(string fileName, const(Scope)* sc, bool skipTests = false)
{
super(fileName, sc, skipTests);
@ -31,24 +32,40 @@ class ObjectConstCheck : BaseAnalyzer
mixin visitTemplate!UnionDeclaration;
mixin visitTemplate!StructDeclaration;
override void visit(const AttributeDeclaration d)
{
if (d.attribute.attribute == tok!"const" && inAggregate)
{
constColon = true;
}
d.accept(this);
}
override void visit(const Declaration d)
{
if (inAggregate && d.functionDeclaration !is null
&& isInteresting(d.functionDeclaration.name.text) && (!hasConst(d.attributes)
&& !hasConst(d.functionDeclaration.memberFunctionAttributes)))
import std.algorithm : any;
bool setConstBlock;
if (inAggregate && d.attributes && d.attributes.any!(a => a.attribute == tok!"const"))
{
setConstBlock = true;
constBlock = true;
}
if (inAggregate && d.functionDeclaration !is null && !constColon && !constBlock
&& isInteresting(d.functionDeclaration.name.text)
&& !hasConst(d.functionDeclaration.memberFunctionAttributes))
{
addErrorMessage(d.functionDeclaration.name.line,
d.functionDeclaration.name.column, "dscanner.suspicious.object_const",
"Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.");
}
d.accept(this);
}
private static bool hasConst(const Attribute[] attributes)
{
import std.algorithm : any;
return attributes.any!(a => a.attribute == tok!"const");
if (!inAggregate)
constColon = false;
if (setConstBlock)
constBlock = false;
}
private static bool hasConst(const MemberFunctionAttribute[] attributes)
@ -65,7 +82,8 @@ class ObjectConstCheck : BaseAnalyzer
|| name == "toString" || name == "opCast";
}
private bool looking = false;
private bool constBlock;
private bool constColon;
}
@ -102,6 +120,16 @@ unittest
}
}
class Bat
{
const: override string toString() { return "foo"; } // ok
}
class Fox
{
const{ override string toString() { return "foo"; }} // ok
}
// Will warn, because none are const
class Dog
{

View File

@ -39,9 +39,9 @@ class OpEqualsWithoutToHashCheck : BaseAnalyzer
private void actualCheck(const Token name, const StructBody structBody)
{
bool hasOpEquals = false;
bool hasToHash = false;
bool hasOpCmp = false;
bool hasOpEquals;
bool hasToHash;
bool hasOpCmp;
// Just return if missing children
if (!structBody || !structBody.declarations || name is Token.init)

View File

@ -141,9 +141,10 @@ private:
long parseNumber(string te)
{
import std.conv : to;
import std.string : removechars;
import std.regex : ctRegex, replaceAll;
string t = te.removechars("_uUlL");
enum re = ctRegex!("[_uUlL]", "");
string t = te.replaceAll(re, "");
if (t.length > 2)
{
if (t[1] == 'x' || t[1] == 'X')

View File

@ -11,6 +11,7 @@ import std.conv;
import std.algorithm;
import std.range;
import std.array;
import std.functional : toDelegate;
import dparse.lexer;
import dparse.parser;
import dparse.ast;
@ -162,7 +163,7 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config,
bool analyze(string[] fileNames, const StaticAnalysisConfig config,
ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true)
{
bool hasErrors = false;
bool hasErrors;
foreach (fileName; fileNames)
{
auto code = readFile(fileName);
@ -203,8 +204,9 @@ const(Module) parseModule(string fileName, ubyte[] code, RollbackAllocator* p,
tokens = getTokensForParser(code, config, &cache);
if (linesOfCode !is null)
(*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens);
return dparse.parser.parseModule(tokens, fileName, p, report
? &messageFunctionJSON : &messageFunction, errorCount, warningCount);
return dparse.parser.parseModule(tokens, fileName, p,
report ? toDelegate(&messageFunctionJSON) : toDelegate(&messageFunction),
errorCount, warningCount);
}
/**

View File

@ -70,18 +70,12 @@ final class StyleChecker : BaseAnalyzer
override void visit(const VariableDeclaration vd)
{
import std.algorithm.iteration : filter;
varIsEnum = !vd.storageClasses.filter!(a => a.token == tok!"enum").empty;
vd.accept(this);
}
override void visit(const Declarator dec)
{
if (varIsEnum)
checkAggregateName("Variable", dec.name);
else
checkLowercaseName("Variable", dec.name);
checkLowercaseName("Variable", dec.name);
}
override void visit(const FunctionDeclaration dec)
@ -143,8 +137,6 @@ final class StyleChecker : BaseAnalyzer
aggregateType ~ " name '" ~ name.text ~ "' does not match style guidelines.");
}
bool varIsEnum;
bool[] _winStyles = [false];
bool winStyle()
@ -183,7 +175,10 @@ unittest
interface puma {} // [warn]: Interface name 'puma' does not match style guidelines.
struct dog {} // [warn]: Struct name 'dog' does not match style guidelines.
enum racoon { a } // [warn]: Enum name 'racoon' does not match style guidelines.
enum bool Something = false;
enum bool something = false;
enum bool someThing = false;
enum Cat { fritz, }
enum Cat = Cat.fritz;
}c, sac);
assertAnalyzerWarnings(q{

View File

@ -51,10 +51,10 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
immutable bool prevOverride = getOverride();
immutable bool prevDisabled = getDisabled();
immutable bool prevDeprecated = getDeprecated();
bool dis = false;
bool dep = false;
bool ovr = false;
bool pushed = false;
bool dis;
bool dep;
bool ovr;
bool pushed;
foreach (attribute; dec.attributes)
{
if (isProtection(attribute.attribute.type))

View File

@ -160,7 +160,8 @@ class UnmodifiedFinder : BaseAnalyzer
foreachStatement.low.accept(this);
interest--;
}
foreachStatement.declarationOrStatement.accept(this);
if (foreachStatement.declarationOrStatement !is null)
foreachStatement.declarationOrStatement.accept(this);
}
override void visit(const TraitsExpression)
@ -223,7 +224,7 @@ private:
castExpression.accept(this);
}
bool foundCast = false;
bool foundCast;
}
if (initializer is null)

View File

@ -11,6 +11,7 @@ import std.container;
import std.regex : Regex, regex, matchAll;
import dsymbol.scope_ : Scope;
import std.algorithm.iteration : map;
import std.algorithm : all;
/**
* Checks for unused variables.
@ -90,18 +91,26 @@ class UnusedVariableCheck : BaseAnalyzer
override void visit(const WhileStatement whileStatement)
{
interestDepth++;
whileStatement.expression.accept(this);
interestDepth--;
whileStatement.declarationOrStatement.accept(this);
if (whileStatement.expression !is null)
{
interestDepth++;
whileStatement.expression.accept(this);
interestDepth--;
}
if (whileStatement.declarationOrStatement !is null)
whileStatement.declarationOrStatement.accept(this);
}
override void visit(const DoStatement doStatement)
{
interestDepth++;
doStatement.expression.accept(this);
interestDepth--;
doStatement.statementNoCaseNoDefault.accept(this);
if (doStatement.expression !is null)
{
interestDepth++;
doStatement.expression.accept(this);
interestDepth--;
}
if (doStatement.statementNoCaseNoDefault !is null)
doStatement.statementNoCaseNoDefault.accept(this);
}
override void visit(const ForStatement forStatement)
@ -120,7 +129,8 @@ class UnusedVariableCheck : BaseAnalyzer
forStatement.increment.accept(this);
interestDepth--;
}
forStatement.declarationOrStatement.accept(this);
if (forStatement.declarationOrStatement !is null)
forStatement.declarationOrStatement.accept(this);
}
override void visit(const IfStatement ifStatement)
@ -131,7 +141,8 @@ class UnusedVariableCheck : BaseAnalyzer
ifStatement.expression.accept(this);
interestDepth--;
}
ifStatement.thenStatement.accept(this);
if (ifStatement.thenStatement !is null)
ifStatement.thenStatement.accept(this);
if (ifStatement.elseStatement !is null)
ifStatement.elseStatement.accept(this);
}
@ -155,7 +166,8 @@ class UnusedVariableCheck : BaseAnalyzer
override void visit(const AssignExpression assignExp)
{
assignExp.ternaryExpression.accept(this);
if (assignExp.ternaryExpression !is null)
assignExp.ternaryExpression.accept(this);
if (assignExp.expression !is null)
{
interestDepth++;
@ -372,7 +384,7 @@ private:
void variableDeclared(string name, size_t line, size_t column, bool isParameter, bool isRef)
{
if (inAggregateScope)
if (inAggregateScope || name.all!(a => a == '_'))
return;
tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef));
}
@ -506,6 +518,15 @@ private:
void f(Bat** bat) {*bat = bats.ptr + 8;}
}
// Issue 490
void test490()
{
auto cb1 = delegate(size_t _) {};
cb1(3);
auto cb2 = delegate(size_t a) {}; // [warn]: Parameter a is never used.
cb2(3);
}
}c, sac);
stderr.writeln("Unittest for UnusedVariableCheck passed.");
}

View File

@ -5,13 +5,19 @@
module analysis.useless_initializer;
import analysis.base;
import containers.dynamicarray;
import containers.hashmap;
import dparse.ast;
import dparse.lexer;
import std.algorithm;
import std.range : empty;
import std.stdio;
/*
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).
*/
/**
@ -20,209 +26,314 @@ Limitations:
*/
final class UselessInitializerChecker : BaseAnalyzer
{
alias visit = BaseAnalyzer.visit;
alias visit = BaseAnalyzer.visit;
private:
enum 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`;
enum key = "dscanner.useless-initializer";
static immutable strDefs = [`""`, `""c`, `""w`, `""d`, "``", "``c", "``w", "``d", "q{}"];
static immutable intDefs = ["0", "0L", "0UL", "0uL", "0U", "0x0", "0b0"];
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(string fileName, bool skipTests = false)
{
super(fileName, null, skipTests);
}
///
this(string fileName, bool skipTests = false)
{
super(fileName, null, skipTests);
_inStruct.insert(false);
}
override void visit(const(VariableDeclaration) decl)
{
foreach (declarator; decl.declarators)
{
if (!decl.type || !decl.type.type2)
continue;
if (!declarator.initializer || !declarator.initializer.nonVoidInitializer)
continue;
override void visit(const(Unittest) test)
{
if (skipTests)
return;
_inTest = true;
test.accept(this);
_inTest = false;
}
import std.format : format;
override void visit(const(StructDeclaration) decl)
{
if (_inTest)
return;
version(unittest)
enum warn = q{addErrorMessage(declarator.name.line, declarator.name.column,
key, msg);};
else
enum warn = q{addErrorMessage(declarator.name.line, declarator.name.column,
key, msg.format(declarator.name.text));};
assert(_inStruct.length > 1);
// --- Info about the declaration type --- //
import std.algorithm : among, canFind;
import std.algorithm.iteration : filter;
import std.range : empty;
const string structName = _inStruct[$-2] ?
_structStack.back() ~ "." ~ decl.name.text :
decl.name.text;
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;
_structStack.insert(structName);
_structCanBeInit[structName] = false;
_atDisabled.insert(false);
decl.accept(this);
_structStack.removeBack();
_atDisabled.removeBack();
}
bool isStr, isSzInt;
Token customType;
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();
}
if (decl.type.type2.symbol && decl.type.type2.symbol.identifierOrTemplateChain &&
decl.type.type2.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances.length == 1)
{
const IdentifierOrTemplateInstance idt =
decl.type.type2.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances[0];
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);
}
customType = idt.identifier;
isStr = customType.text.among("string", "wstring", "dstring") != 0;
isSzInt = customType.text.among("size_t", "ptrdiff_t") != 0;
}
// issue 473, prevent to visit delegates that contain duck type checkers.
override void visit(const(TypeofExpression)) {}
// --- '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;
// 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);
}
}
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":
if (intDefs.canFind(value.text))
mixin(warn);
goto default;
default:
// check for BasicType.init
if (ue.primaryExpression.basicType.type == decl.type.type2.builtinType &&
ue.primaryExpression.primary.text == "init" &&
!ue.primaryExpression.expression)
mixin(warn);
}
}
else if (isSzInt)
{
if (intDefs.canFind(value.text))
mixin(warn);
}
else if (isPtr)
{
if (str(value.type) == "null")
mixin(warn);
}
else if (isArr)
{
if (str(value.type) == "null")
mixin(warn);
else if (nvi.arrayInitializer && nvi.arrayInitializer.arrayMemberInitializations.length == 0)
mixin(warn);
else if (decl.type.type2.builtinType != tok!"")
{
switch(decl.type.type2.builtinType)
{
case tok!"char", tok!"wchar", tok!"dchar":
if (strDefs.canFind(value.text))
mixin(warn);
break;
default:
}
}
}
else if (isStr)
{
if (strDefs.canFind(value.text))
mixin(warn);
else if (nvi.arrayInitializer && nvi.arrayInitializer.arrayMemberInitializations.length == 0)
mixin(warn);
}
}
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;
}
// Symbol s = Symbol.init
else if (ue && customType != tok!"" && ue.unaryExpression && ue.unaryExpression.primaryExpression &&
ue.unaryExpression.primaryExpression.identifierOrTemplateInstance &&
ue.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier == customType &&
ue.identifierOrTemplateInstance && ue.identifierOrTemplateInstance.identifier.text == "init")
{
mixin(warn);
}
foreach (declarator; decl.declarators)
{
if (!declarator.initializer ||
!declarator.initializer.nonVoidInitializer ||
declarator.comment !is null)
{
continue;
}
// 'Symbol ArrayInitializer' : assumes Symbol is an array b/c of the Init
else if (nvi.arrayInitializer && (isArr || isStr))
{
if (nvi.arrayInitializer.arrayMemberInitializations.length == 0)
mixin(warn);
}
}
version(unittest)
{
enum warn = q{addErrorMessage(declarator.name.line,
declarator.name.column, key, msg);};
}
else
{
import std.format : format;
enum warn = q{addErrorMessage(declarator.name.line,
declarator.name.column, key, msg.format(declarator.name.text));};
}
decl.accept(this);
}
// --- 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 (decl.type.type2.symbol && decl.type.type2.symbol.identifierOrTemplateChain &&
decl.type.type2.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances.length == 1)
{
const IdentifierOrTemplateInstance idt =
decl.type.type2.symbol.identifierOrTemplateChain.identifiersOrTemplateInstances[0];
customType = idt.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")
mixin(warn);
goto default;
default:
// check for BasicType.init
if (ue.primaryExpression.basicType.type == decl.type.type2.builtinType &&
ue.primaryExpression.primary.text == "init" &&
!ue.primaryExpression.expression)
mixin(warn);
}
}
else if (isSzInt)
{
if (intDefs.canFind(value.text))
mixin(warn);
}
else if (isPtr || isStr)
{
if (str(value.type) == "null")
mixin(warn);
}
else if (isArr)
{
if (str(value.type) == "null")
mixin(warn);
else if (nvi.arrayInitializer && nvi.arrayInitializer.arrayMemberInitializations.length == 0)
mixin(warn);
}
}
// Symbol s = Symbol.init
else if (ue && customType != tok!"" && ue.unaryExpression && ue.unaryExpression.primaryExpression &&
ue.unaryExpression.primaryExpression.identifierOrTemplateInstance &&
ue.unaryExpression.primaryExpression.identifierOrTemplateInstance.identifier == customType &&
ue.identifierOrTemplateInstance && ue.identifierOrTemplateInstance.identifier.text == "init")
{
if (customType.text in _structCanBeInit)
{
if (!_structCanBeInit[customType.text])
mixin(warn);
}
}
// 'Symbol ArrayInitializer' : assumes Symbol is an array b/c of the Init
else if (nvi.arrayInitializer && (isArr || isStr))
{
if (nvi.arrayInitializer.arrayMemberInitializations.length == 0)
mixin(warn);
}
}
decl.accept(this);
}
}
@system unittest
{
import analysis.config : Check, disabledConfig, StaticAnalysisConfig;
import analysis.helpers: assertAnalyzerWarnings;
import std.stdio : stderr;
import analysis.config : Check, disabledConfig, StaticAnalysisConfig;
import analysis.helpers: assertAnalyzerWarnings;
import std.stdio : stderr;
StaticAnalysisConfig sac = disabledConfig;
sac.useless_initializer = Check.enabled;
StaticAnalysisConfig sac = disabledConfig;
sac.useless_initializer = Check.enabled;
// fails
assertAnalyzerWarnings(q{
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 = ""; // [warn]: X
string a = ""c; // [warn]: X
wstring a = ""w; // [warn]: X
dstring a = ""d; // [warn]: X
string a = q{}; // [warn]: X
size_t a = 0; // [warn]: X
ptrdiff_t a = 0; // [warn]: X
string a = []; // [warn]: X
char[] a = ""; // [warn]: X
int a = int.init; // [warn]: X
char a = char.init; // [warn]: X
S s = S.init; // [warn]: X
}, sac);
// 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{
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 = 1;
string a = ['a'];
char[] a = "ze";
S s = S(0,1);
S s = s.call();
}, 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);
stderr.writeln("Unittest for UselessInitializerChecker passed.");
stderr.writeln("Unittest for UselessInitializerChecker passed.");
}

View File

@ -17,6 +17,7 @@ import std.conv;
import std.typecons;
import containers.ttree;
import std.string;
import std.functional : toDelegate;
/**
* Prints CTAGS information to the given file.
@ -65,7 +66,7 @@ void printCtags(File output, string[] fileNames)
}
auto tokens = getTokensForParser(bytes, config, &cache);
Module m = parseModule(tokens.array, fileName, &rba, &doNothing);
Module m = parseModule(tokens.array, fileName, &rba, toDelegate(&doNothing));
auto printer = new CTagsPrinter(&tags);
printer.fileName = fileName;

View File

@ -17,6 +17,7 @@ import std.path;
import std.array;
import std.conv;
import std.string;
import std.functional : toDelegate;
// Prefix tags with their module name. Seems like correct behavior, but just
// in case, make it an option.
@ -48,7 +49,7 @@ void printEtags(File output, bool tagAll, string[] fileNames)
auto bytes = uninitializedArray!(ubyte[])(to!size_t(f.size));
f.rawRead(bytes);
auto tokens = getTokensForParser(bytes, config, &cache);
Module m = parseModule(tokens.array, fileName, &rba, &doNothing);
Module m = parseModule(tokens.array, fileName, &rba, toDelegate(&doNothing));
auto printer = new EtagsPrinter;
printer.moduleName = m.moduleFullName(fileName);

View File

@ -11,6 +11,7 @@ import dparse.parser;
import dparse.rollback_allocator;
import std.stdio;
import std.container.rbtree;
import std.functional : toDelegate;
import readers;
/**
@ -63,7 +64,7 @@ private void visitFile(bool usingStdin, string fileName, RedBlackTree!string imp
config.stringBehavior = StringBehavior.source;
auto visitor = new ImportPrinter;
auto tokens = getTokensForParser(usingStdin ? readStdin() : readFile(fileName), config, cache);
auto mod = parseModule(tokens, fileName, &rba, &doNothing);
auto mod = parseModule(tokens, fileName, &rba, toDelegate(&doNothing));
visitor.visit(mod);
importedModules.insert(visitor.imports[]);
}

View File

@ -15,6 +15,7 @@ import std.stdio;
import std.range;
import std.experimental.lexer;
import std.typecons : scoped;
import std.functional : toDelegate;
import dparse.lexer;
import dparse.parser;
import dparse.rollback_allocator;
@ -283,7 +284,7 @@ else
config.stringBehavior = StringBehavior.source;
auto tokens = getTokensForParser(usingStdin ? readStdin()
: readFile(args[1]), config, &cache);
auto mod = parseModule(tokens, fileName, &rba, &doNothing);
auto mod = parseModule(tokens, fileName, &rba, toDelegate(&doNothing));
if (ast)
{

View File

@ -12,8 +12,25 @@ import dparse.ast;
import dparse.rollback_allocator;
import std.stdio;
import std.file : isFile;
import std.functional : toDelegate;
void findDeclarationOf(File output, string symbolName, string[] fileNames)
{
findDeclarationOf((string fileName, size_t line, size_t column)
{
output.writefln("%s(%d:%d)", fileName, line, column);
}, symbolName, fileNames);
}
/// Delegate that gets called every time a declaration gets found
alias OutputHandler = void delegate(string fileName, size_t line, size_t column);
/// Finds all declarations of a symbol in the given fileNames and calls a handler on every occurence.
/// Params:
/// output = Callback which gets called when a declaration is found
/// symbolName = Symbol name to search for
/// fileNames = An array of file names which might contain stdin to read from stdin
void findDeclarationOf(scope OutputHandler output, string symbolName, string[] fileNames)
{
import std.array : uninitializedArray, array;
import std.conv : to;
@ -31,7 +48,7 @@ void findDeclarationOf(File output, string symbolName, string[] fileNames)
f.rawRead(bytes);
auto tokens = getTokensForParser(bytes, config, &cache);
RollbackAllocator rba;
Module m = parseModule(tokens.array, fileName, &rba, &doNothing);
Module m = parseModule(tokens.array, fileName, &rba, toDelegate(&doNothing));
visitor.fileName = fileName;
visitor.visit(m);
}
@ -45,7 +62,7 @@ void doNothing(string, size_t, size_t, string, bool)
class FinderVisitor : ASTVisitor
{
this(File output, string symbolName)
this(OutputHandler output, string symbolName)
{
this.output = output;
this.symbolName = symbolName;
@ -61,19 +78,19 @@ class FinderVisitor : ASTVisitor
override void visit(const EnumDeclaration dec)
{
if (dec.name.text == symbolName)
output.writefln("%s(%d:%d)", fileName, dec.name.line, dec.name.column);
output(fileName, dec.name.line, dec.name.column);
}
override void visit(const AnonymousEnumMember member)
{
if (member.name.text == symbolName)
output.writefln("%s(%d:%d)", fileName, member.name.line, member.name.column);
output(fileName, member.name.line, member.name.column);
}
override void visit(const EnumMember member)
{
if (member.name.text == symbolName)
output.writefln("%s(%d:%d)", fileName, member.name.line, member.name.column);
output(fileName, member.name.line, member.name.column);
}
override void visit(const AliasDeclaration dec)
@ -83,13 +100,13 @@ class FinderVisitor : ASTVisitor
foreach (ident; dec.identifierList.identifiers)
{
if (ident.text == symbolName)
output.writefln("%s(%d:%d)", fileName, ident.line, ident.column);
output(fileName, ident.line, ident.column);
}
}
foreach (initializer; dec.initializers)
{
if (initializer.name.text == symbolName)
output.writefln("%s(%d:%d)", fileName, initializer.name.line,
output(fileName, initializer.name.line,
initializer.name.column);
}
}
@ -97,7 +114,7 @@ class FinderVisitor : ASTVisitor
override void visit(const Declarator dec)
{
if (dec.name.text == symbolName)
output.writefln("%s(%d:%d)", fileName, dec.name.line, dec.name.column);
output(fileName, dec.name.line, dec.name.column);
}
override void visit(const AutoDeclaration ad)
@ -105,7 +122,7 @@ class FinderVisitor : ASTVisitor
foreach (part; ad.parts)
{
if (part.identifier.text == symbolName)
output.writefln("%s(%d:%d)", fileName, part.identifier.line, part.identifier.column);
output(fileName, part.identifier.line, part.identifier.column);
}
}
@ -118,14 +135,14 @@ class FinderVisitor : ASTVisitor
override void visit(const T t)
{
if (t.name.text == symbolName)
output.writefln("%s(%d:%d)", fileName, t.name.line, t.name.column);
output(fileName, t.name.line, t.name.column);
t.accept(this);
}
}
alias visit = ASTVisitor.visit;
File output;
OutputHandler output;
string symbolName;
string fileName;
}

17
test.sh
View File

@ -1,17 +0,0 @@
rm -f test
rm -f test.o
dmd\
src/*.d\
libdparse/src/std/*.d\
libdparse/src/std/d/*.d\
inifiled/source/*.d\
src/analysis/*.d\
-oftest\
-g -unittest\
-J.
./test
rm -f test test.o