This commit is contained in:
Hackerpilot 2015-06-04 17:47:16 -07:00
commit ba70f0e9dd
11 changed files with 288 additions and 104 deletions

@ -1 +1 @@
Subproject commit 32f6d638e38888e1bb11cf43e93fe2d11132a98f
Subproject commit bd7c1c2dbb08bf160c4b646e0aede2af1ef6e0e4

View File

@ -92,4 +92,7 @@ struct StaticAnalysisConfig
@INI("Checks for redundant parenthesis")
bool redundant_parens_check;
@INI("Checks for labels with the same name as variables")
bool label_var_same_name_check;
}

View File

@ -0,0 +1,119 @@
// 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 analysis.label_var_same_name_check;
import std.d.ast;
import std.d.lexer;
import analysis.base;
import analysis.helpers;
/**
* Checks for labels and variables that have the same name.
*/
class LabelVarNameCheck : BaseAnalyzer
{
this(string fileName)
{
super(fileName);
}
override void visit(const Module mod)
{
pushScope();
mod.accept(this);
popScope();
}
override void visit(const BlockStatement block)
{
pushScope();
block.accept(this);
popScope();
}
override void visit(const StructBody structBody)
{
pushScope();
structBody.accept(this);
popScope();
}
override void visit(const VariableDeclaration var)
{
foreach (dec; var.declarators)
duplicateCheck(dec.name, false);
}
override void visit(const LabeledStatement labeledStatement)
{
duplicateCheck(labeledStatement.identifier, true);
if (labeledStatement.declarationOrStatement !is null)
labeledStatement.declarationOrStatement.accept(this);
}
alias visit = BaseAnalyzer.visit;
private:
Thing[string][] stack;
void duplicateCheck(const Token name, bool fromLabel)
{
import std.conv : to;
const(Thing)* thing = name.text in currentScope;
if (thing is null)
currentScope[name.text] = Thing(name.text, name.line, name.column, false);
else
{
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 "
~ otherKind ~ " defined on line " ~ to!string(thing.line) ~ ".");
}
}
static struct Thing
{
string name;
size_t line;
size_t column;
bool isVar;
}
ref currentScope() @property
{
return stack[$-1];
}
void pushScope()
{
stack.length++;
}
void popScope()
{
stack.length--;
}
}
unittest
{
import analysis.config : StaticAnalysisConfig;
import std.stdio : stderr;
StaticAnalysisConfig sac;
sac.label_var_same_name_check = true;
assertAnalyzerWarnings(q{
unittest
{
blah:
int blah; // [warn]: Variable "blah" has the same name as a label defined on line 4.
}
int blah;
}c, sac);
stderr.writeln("Unittest for LabelVarNameCheck passed.");
}

View File

@ -43,6 +43,7 @@ import analysis.local_imports;
import analysis.unmodified;
import analysis.if_statements;
import analysis.redundant_parens;
import analysis.label_var_same_name_check;
bool first = true;
@ -115,8 +116,11 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config)
writeln("}");
}
/// For multiple files
/// Returns: true if there were errors
/**
* For multiple files
*
* Returns: true if there were errors or if there were warnings and `staticAnalyze` was true.
*/
bool analyze(string[] fileNames, const StaticAnalysisConfig config, bool staticAnalyze = true)
{
bool hasErrors = false;
@ -129,10 +133,11 @@ bool analyze(string[] fileNames, const StaticAnalysisConfig config, bool staticA
ParseAllocator p = new ParseAllocator;
StringCache cache = StringCache(StringCache.defaultBucketCount);
uint errorCount = 0;
uint warningCount = 0;
const Module m = parseModule(fileName, code, p, cache, false, null,
&errorCount, null);
&errorCount, &warningCount);
assert (m);
if (errorCount > 0)
if (errorCount > 0 || (staticAnalyze && warningCount > 0))
hasErrors = true;
MessageSet results = analyze(fileName, m, config, staticAnalyze);
if (results is null)
@ -192,6 +197,7 @@ MessageSet analyze(string fileName, const Module m,
if (analysisConfig.local_import_check) checks ~= new LocalImportCheck(fileName);
if (analysisConfig.could_be_immutable_check) checks ~= new UnmodifiedFinder(fileName);
if (analysisConfig.redundant_parens_check) checks ~= new RedundantParenCheck(fileName);
if (analysisConfig.label_var_same_name_check) checks ~= new LabelVarNameCheck(fileName);
version(none) if (analysisConfig.redundant_if_check) checks ~= new IfStatementCheck(fileName);
foreach (check; checks)

View File

@ -9,6 +9,7 @@ import std.d.ast;
import std.d.lexer;
import analysis.base;
import std.regex : ctRegex, matchAll;
import std.stdio;
/**
@ -38,13 +39,19 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
if (isProtection(attr.attribute.type))
set(attr.attribute.type);
else if (attr.attribute == tok!"override")
{
setOverride(true);
}
else if (attr.deprecated_ !is null)
setDeprecated(true);
else if (attr.atAttribute !is null && attr.atAttribute.identifier.text == "disable")
setDisabled(true);
}
immutable bool shouldPop = dec.attributeDeclaration is null;
immutable bool prevOverride = getOverride();
immutable bool prevDisabled = getDisabled();
immutable bool prevDeprecated = getDeprecated();
bool dis = false;
bool dep = false;
bool ovr = false;
bool pushed = false;
foreach (attribute; dec.attributes)
@ -61,14 +68,26 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
}
else if (attribute.attribute == tok!"override")
ovr = true;
else if (attribute.deprecated_ !is null)
dep = true;
else if (attribute.atAttribute !is null && attribute.atAttribute.identifier.text == "disable")
dis = true;
}
if (ovr)
setOverride(true);
if (dis)
setDisabled(true);
if (dep)
setDeprecated(true);
dec.accept(this);
if (shouldPop && pushed)
pop();
if (ovr)
setOverride(prevOverride);
if (dis)
setDisabled(prevDisabled);
if (dep)
setDeprecated(prevDeprecated);
}
override void visit(const VariableDeclaration variable)
@ -92,7 +111,7 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
override void visit(const ConditionalDeclaration cond)
{
const VersionCondition ver = cond.compileCondition.versionCondition;
if (ver is null || ver.token != tok!"unittest" && ver.token.text != "none")
if ((ver is null || ver.token != tok!"unittest") && ver.token.text != "none")
cond.accept(this);
else if (cond.falseDeclaration !is null)
visit(cond.falseDeclaration);
@ -100,6 +119,7 @@ class UndocumentedDeclarationCheck : BaseAnalyzer
override void visit(const FunctionBody fb) {}
override void visit(const Unittest u) {}
override void visit(const TraitsExpression t) {}
mixin V!ClassDeclaration;
mixin V!InterfaceDeclaration;
@ -156,8 +176,7 @@ private:
static bool isGetterOrSetter(string name)
{
import std.algorithm:startsWith;
return name.startsWith("get") || name.startsWith("set");
return !matchAll(name, getSetRe).empty;
}
static bool isProperty(const FunctionDeclaration dec)
@ -190,9 +209,30 @@ private:
stack[$ - 1].isOverride = o;
}
bool getDisabled()
{
return stack[$ - 1].isDisabled;
}
void setDisabled(bool d = true)
{
stack[$ - 1].isDisabled = d;
}
bool getDeprecated()
{
return stack[$ - 1].isDeprecated;
}
void setDeprecated(bool d = true)
{
stack[$ - 1].isDeprecated = d;
}
bool currentIsInteresting()
{
return stack[$ - 1].protection == tok!"public" && !(stack[$ - 1].isOverride);
return stack[$ - 1].protection == tok!"public" && !stack[$ - 1].isOverride
&& !stack[$ - 1].isDisabled && !stack[$ - 1].isDeprecated;
}
void set(IdType p)
@ -219,6 +259,8 @@ private:
{
IdType protection;
bool isOverride;
bool isDeprecated;
bool isDisabled;
}
ProtectionInfo[] stack;
@ -232,3 +274,5 @@ private immutable string[] ignoredFunctionNames = [
"toHash",
"main"
];
private enum getSetRe = ctRegex!`^(?:get|set)(?:\p{Lu}|_).*`;

View File

@ -153,6 +153,11 @@ class UnmodifiedFinder:BaseAnalyzer
foreachStatement.declarationOrStatement.accept(this);
}
override void visit(const TraitsExpression)
{
// Issue #266. Ignore everything inside of __traits expressions.
}
private:
template PartsMightModify(T)

View File

@ -277,7 +277,6 @@ class UnusedVariableCheck : BaseAnalyzer
import std.array : array;
if (parameter.name != tok!"")
{
// stderr.writeln("Adding parameter ", parameter.name.text);
immutable bool isRef = canFind(parameter.parameterAttributes, cast(IdType) tok!"ref")
|| canFind(parameter.parameterAttributes, cast(IdType) tok!"in")
|| canFind(parameter.parameterAttributes, cast(IdType) tok!"out");
@ -317,6 +316,11 @@ class UnusedVariableCheck : BaseAnalyzer
variableUsed(primary.identifierChain.identifiers[0].text);
}
override void visit(const TraitsExpression)
{
// Issue #266. Ignore everything inside of __traits expressions.
}
private:
mixin template PartsUseVariables(NodeType)
@ -334,13 +338,11 @@ private:
{
if (inAggregateScope)
return;
// stderr.writeln("Adding ", name, " ", isParameter, " ", isRef);
tree[$ - 1].insert(new UnUsed(name, line, column, isParameter, isRef));
}
void variableUsed(string name)
{
// writeln("Marking ", name, " used");
size_t treeIndex = tree.length - 1;
auto uu = UnUsed(name);
while (true)

View File

@ -4,7 +4,6 @@
module analysis.unused_label;
import std.stdio;
import std.d.ast;
import std.d.lexer;
import analysis.base;
@ -19,39 +18,11 @@ class UnusedLabelCheck : BaseAnalyzer
super(fileName);
}
static struct Label
override void visit(const Module mod)
{
string name;
size_t line;
size_t column;
bool used;
}
Label[string][] stack;
auto ref current() @property
{
return stack[$-1];
}
void pushScope()
{
stack.length++;
}
void popScope()
{
foreach (label; current.byValue())
{
assert(label.line != size_t.max && label.column != size_t.max);
if (!label.used)
{
addErrorMessage(label.line, label.column,
"dscanner.suspicious.unused_label",
"Label \"" ~ label.name ~ "\" is not used.");
}
}
stack.length--;
pushScope();
mod.accept(this);
popScope();
}
override void visit(const FunctionBody functionBody)
@ -99,15 +70,6 @@ class UnusedLabelCheck : BaseAnalyzer
labeledStatement.declarationOrStatement.accept(this);
}
void labelUsed(string name)
{
Label* entry = name in current;
if (entry is null)
current[name] = Label(name, size_t.max, size_t.max, true);
else
entry.used = true;
}
override void visit(const ContinueStatement contStatement)
{
if (contStatement.label.text.length)
@ -123,11 +85,58 @@ class UnusedLabelCheck : BaseAnalyzer
if (gotoStatement.label.text.length)
labelUsed(gotoStatement.label.text);
}
private:
static struct Label
{
string name;
size_t line;
size_t column;
bool used;
}
Label[string][] stack;
auto ref current() @property
{
return stack[$-1];
}
void pushScope()
{
stack.length++;
}
void popScope()
{
foreach (label; current.byValue())
{
assert(label.line != size_t.max && label.column != size_t.max);
if (!label.used)
{
addErrorMessage(label.line, label.column,
"dscanner.suspicious.unused_label",
"Label \"" ~ label.name ~ "\" is not used.");
}
}
stack.length--;
}
void labelUsed(string name)
{
Label* entry = name in current;
if (entry is null)
current[name] = Label(name, size_t.max, size_t.max, true);
else
entry.used = true;
}
}
unittest
{
import analysis.config : StaticAnalysisConfig;
import std.stdio : stderr;
StaticAnalysisConfig sac;
sac.unused_label_check = true;

View File

@ -53,8 +53,8 @@ class CTagsPrinter : ASTVisitor
{
override void visit(const ClassDeclaration dec)
{
tagLines ~= "%s\t%s\t%d;\"\tc%s\n".format(dec.name.text, fileName, dec.name.line, context);
auto c = context;
tagLines ~= "%s\t%s\t%d;\"\tc\tline:%d%s\n".format(dec.name.text, fileName, dec.name.line, dec.name.line, context);
const c = context;
context = "\tclass:" ~ dec.name.text;
dec.accept(this);
context = c;
@ -62,8 +62,8 @@ class CTagsPrinter : ASTVisitor
override void visit(const StructDeclaration dec)
{
tagLines ~= "%s\t%s\t%d;\"\ts%s\n".format(dec.name.text, fileName, dec.name.line, context);
auto c = context;
tagLines ~= "%s\t%s\t%d;\"\ts\tline:%d%s\n".format(dec.name.text, fileName, dec.name.line, dec.name.line, context);
const c = context;
context = "\tstruct:" ~ dec.name.text;
dec.accept(this);
context = c;
@ -71,8 +71,8 @@ class CTagsPrinter : ASTVisitor
override void visit(const InterfaceDeclaration dec)
{
tagLines ~= "%s\t%s\t%d;\"\ti%s\n".format(dec.name.text, fileName, dec.name.line, context);
auto c = context;
tagLines ~= "%s\t%s\t%d;\"\ti\tline:%d%s\n".format(dec.name.text, fileName, dec.name.line, dec.name.line, context);
const c = context;
context = "\tclass:" ~ dec.name.text;
dec.accept(this);
context = c;
@ -80,8 +80,8 @@ class CTagsPrinter : ASTVisitor
override void visit(const TemplateDeclaration dec)
{
tagLines ~= "%s\t%s\t%d;\"\tT%s\n".format(dec.name.text, fileName, dec.name.line, context);
auto c = context;
tagLines ~= "%s\t%s\t%d;\"\tT\tline:%d%s\n".format(dec.name.text, fileName, dec.name.line, dec.name.line, context);
const c = context;
context = "\ttemplate:" ~ dec.name.text;
dec.accept(this);
context = c;
@ -89,9 +89,9 @@ class CTagsPrinter : ASTVisitor
override void visit(const FunctionDeclaration dec)
{
tagLines ~= "%s\t%s\t%d;\"\tf\tarity:%d%s\n".format(dec.name.text, fileName,
dec.name.line, dec.parameters.parameters.length, context);
auto c = context;
tagLines ~= "%s\t%s\t%d;\"\tf\tarity:%d\tline:%d%s\n".format(dec.name.text, fileName,
dec.name.line, dec.parameters.parameters.length, dec.name.line, context);
const c = context;
context = "\tfunction:" ~ dec.name.text;
dec.accept(this);
context = c;
@ -99,9 +99,9 @@ class CTagsPrinter : ASTVisitor
override void visit(const Constructor dec)
{
tagLines ~= "this\t%s\t%d;\"\tf\tarity:%d%s\n".format(fileName,
dec.line, dec.parameters.parameters.length, context);
auto c = context;
tagLines ~= "this\t%s\t%d;\"\tf\tarity:%d\tline:%d%s\n".format(fileName,
dec.line, dec.parameters.parameters.length, dec.line, context);
const c = context;
context = "\tfunction: this";
dec.accept(this);
context = c;
@ -109,9 +109,9 @@ class CTagsPrinter : ASTVisitor
override void visit(const Destructor dec)
{
tagLines ~= "~this\t%s\t%d;\"\tf%s\n".format(fileName, dec.line,
context);
auto c = context;
tagLines ~= "~this\t%s\t%d;\"\tf\tline:%d%s\n".format(fileName, dec.line,
dec.line, context);
const c = context;
context = "\tfunction: this";
dec.accept(this);
context = c;
@ -119,9 +119,9 @@ class CTagsPrinter : ASTVisitor
override void visit(const EnumDeclaration dec)
{
tagLines ~= "%s\t%s\t%d;\"\tg%s\n".format(dec.name.text, fileName,
dec.name.line, context);
auto c = context;
tagLines ~= "%s\t%s\t%d;\"\tg\tline:%d%s\n".format(dec.name.text, fileName,
dec.name.line, dec.name.line, context);
const c = context;
context = "\tenum:" ~ dec.name.text;
dec.accept(this);
context = c;
@ -134,9 +134,9 @@ class CTagsPrinter : ASTVisitor
dec.accept(this);
return;
}
tagLines ~= "%s\t%s\t%d;\"\tu%s\n".format(dec.name.text, fileName,
dec.name.line, context);
auto c = context;
tagLines ~= "%s\t%s\t%d;\"\tu\tline:%d%s\n".format(dec.name.text, fileName,
dec.name.line, dec.name.line, context);
const c = context;
context = "\tunion:" ~ dec.name.text;
dec.accept(this);
context = c;
@ -144,22 +144,22 @@ class CTagsPrinter : ASTVisitor
override void visit(const AnonymousEnumMember mem)
{
tagLines ~= "%s\t%s\t%d;\"\te%s\n".format(mem.name.text, fileName,
mem.name.line, context);
tagLines ~= "%s\t%s\t%d;\"\te\tline:%d%s\n".format(mem.name.text, fileName,
mem.name.line, mem.name.line, context);
}
override void visit(const EnumMember mem)
{
tagLines ~= "%s\t%s\t%d;\"\te%s\n".format(mem.name.text, fileName,
mem.name.line, context);
tagLines ~= "%s\t%s\t%d;\"\te\tline:%d%s\n".format(mem.name.text, fileName,
mem.name.line, mem.name.line, context);
}
override void visit(const VariableDeclaration dec)
{
foreach (d; dec.declarators)
{
tagLines ~= "%s\t%s\t%d;\"\tv%s\n".format(d.name.text, fileName,
d.name.line, context);
tagLines ~= "%s\t%s\t%d;\"\tv\tline:%d%s\n".format(d.name.text, fileName,
d.name.line, d.name.line, context);
}
dec.accept(this);
}
@ -168,15 +168,15 @@ class CTagsPrinter : ASTVisitor
{
foreach (i; dec.identifiers)
{
tagLines ~= "%s\t%s\t%d;\"\tv%s\n".format(i.text, fileName,
i.line, context);
tagLines ~= "%s\t%s\t%d;\"\tv\tline:%d%s\n".format(i.text, fileName,
i.line, i.line, context);
}
dec.accept(this);
}
override void visit(const Invariant dec)
{
tagLines ~= "invariant\t%s\t%d;\"\tv%s\n".format(fileName, dec.line, context);
tagLines ~= "invariant\t%s\t%d;\"\tv\tline:%d%s\n".format(fileName, dec.line, dec.line, context);
}
alias visit = ASTVisitor.visit;

View File

@ -8,7 +8,7 @@ module dscanner_version;
/**
* Human-readable version number
*/
enum DSCANNER_VERSION = "v0.2.0-dev";
enum DSCANNER_VERSION = "v0.2.0-beta1";
version (Windows) {}
else

View File

@ -34,17 +34,8 @@ import inifiled;
int main(string[] args)
{
version (unittest)
{
return 0;
}
else
{
return run(args);
}
}
int run(string[] args)
{
bool sloc;
bool highlight;
bool ctags;
@ -191,11 +182,11 @@ int run(string[] args)
if (report)
generateReport(expandArgs(args), config);
else
analyze(expandArgs(args), config, true);
return analyze(expandArgs(args), config, true) ? 1 : 0;
}
else if (syntaxCheck)
{
return .syntaxCheck(expandArgs(args));
return .syntaxCheck(expandArgs(args)) ? 1 : 0;
}
else
{
@ -274,6 +265,7 @@ int run(string[] args)
return 0;
}
private:
string[] expandArgs(string[] args)
{
@ -289,7 +281,7 @@ string[] expandArgs(string[] args)
return false;
}
}
string[] rVal;
if (args.length == 1)
args ~= ".";
@ -368,11 +360,13 @@ options:
--syntaxCheck | -s [sourceFile]
Lexes and parses sourceFile, printing the line and column number of any
syntax errors to stdout. One error or warning is printed per line.
If no files are specified, input is read from stdin.
If no files are specified, input is read from stdin. %1$s will exit with
a status code of zero if no errors are found, 1 otherwise.
--styleCheck | -S [sourceFiles]
Lexes and parses sourceFiles, printing the line and column number of any
static analysis check failures stdout.
static analysis check failures stdout. %1$s will exit with a status code
of zero if no warnings or errors are found, 1 otherwise.
--ctags | -c sourceFile
Generates ctags information from the given source code file. Note that
@ -397,7 +391,9 @@ options:
current working directory if none are specified.
--report [sourceFiles sourceDirectories]
Generate a static analysis report in JSON format. Implies --styleCheck.
Generate a static analysis report in JSON format. Implies --styleCheck,
however the exit code will still be zero if errors or warnings are
found.
--config configFile
Use the given configuration file instead of the default located in