Merge master and update dependencies

This commit is contained in:
Hackerpilot 2015-09-18 02:56:30 -07:00
commit 380064de6a
18 changed files with 604 additions and 221 deletions

1
.gitignore vendored
View File

@ -25,6 +25,7 @@ dscanner-report.json
*.o
*.obj
*.exe
obj
#debug build
dsc

View File

@ -79,12 +79,13 @@ you do not want to use the one created by the "--defaultConfig" option.
* Confusing asm syntax.
* Placement of const, immutable, or inout before a function return type instead of after the parameters.
* Functions in interface declarations redundantly marked 'abstract'.
* Declaring a variable with the same name as a label.
* Variables that could have been declared const or immutable (experimental)
* Redundant parenthesis.
* Unused labels.
#### Wishlish
* Assigning to foreach variables that are not "ref".
* Unused imports.
* Variables that are never modified and not declared immutable.
* Assignment in conditionals
https://github.com/Hackerpilot/Dscanner/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement
### Reports
The "--report" option writes a JSON report on the static analysis checks

@ -1 +1 @@
Subproject commit d732a67e76f60fd037547c3ffe8776c6deda6bab
Subproject commit 05b1f9f5906c4ac1f38964c7456482b3f11daa32

@ -1 +1 @@
Subproject commit 393da9696f81d0692f8022e2d5d40dc662d984f9
Subproject commit 1c5579db6c1d88a097d46f5bee5ae1e2cf8fff02

@ -1 +1 @@
Subproject commit 0dccfca0e2a132b3c862a62da1c323ccd24e622d
Subproject commit 071e0cee083a0392d9226fdfba593dd86c21d412

View File

@ -1,27 +1,26 @@
.PHONY: all test
DMD = dmd
GDC = gdc
LDC = ldc2
SRC = src/*.d\
src/analysis/*.d\
libdparse/src/std/*.d\
libdparse/src/std/d/*.d\
inifiled/source/*.d\
DC ?= dmd
DMD := $(DC)
GDC := gdc
LDC := ldc2
OBJ_DIR := obj
SRC := \
$(shell find containers/experimental_allocator/src -name "*.d")\
$(shell find containers/src -name "*.d")\
$(shell find dsymbol/src -name "*.d")\
containers/src/containers/ttree.d\
containers/src/containers/unrolledlist.d\
containers/src/containers/hashset.d\
containers/src/containers/internal/hash.d\
containers/src/containers/internal/node.d\
containers/src/containers/internal/storage_type.d\
containers/src/memory/allocators.d\
containers/src/memory/appender.d
INCLUDE_PATHS = -Ilibdparse/src -Idsymbol/src -Icontainers/src
$(shell find inifiled/source/ -name "*.d")\
$(shell find libdparse/src/std/ -name "*.d")\
$(shell find src/ -name "*.d")
INCLUDE_PATHS = \
-Iinifiled/source -Isrc\
-Ilibdparse/src\
-Ilibdparse/experimental_allocator/src\
-Idsymbol/src -Icontainers/src
VERSIONS =
DEBUG_VERSIONS = -version=std_parser_verbose
#DMD_FLAGS = -w -O -release -inline
DMD_FLAGS = -w
DMD_FLAGS = -w -O -inline -J. -od${OBJ_DIR}
DMD_TEST_FLAGS = -w -g -unittest -J.
all: dmdbuild
ldc: ldcbuild
@ -31,11 +30,11 @@ githash:
git log -1 --format="%H" > githash.txt
debug:
${DMD} -w -g -ofdsc ${VERSIONS} ${DEBUG_VERSIONS} ${INCLUDE_PATHS} ${SRC} -J.
${DC} -w -g -ofdsc ${VERSIONS} ${DEBUG_VERSIONS} ${INCLUDE_PATHS} ${SRC}
dmdbuild: githash
dmdbuild: githash $(SRC)
mkdir -p bin
${DMD} ${DMD_FLAGS} -ofbin/dscanner ${VERSIONS} ${INCLUDE_PATHS} ${SRC} -J.
${DC} ${DMD_FLAGS} -ofbin/dscanner ${VERSIONS} ${INCLUDE_PATHS} ${SRC}
rm -f bin/dscanner.o
gdcbuild: githash
@ -50,8 +49,9 @@ test:
@./test.sh
clean:
rm -rf dsc *.o
rm -rf dsc
rm -rf bin
rm -rf ${OBJ_DIR}
rm -f dscanner-report.json
report: all

View File

@ -95,4 +95,7 @@ struct StaticAnalysisConfig
@INI("Checks for mismatched argument and parameter names")
bool mismatched_args_check;
@INI("Checks for labels with the same name as variables")
bool label_var_same_name_check;
}

View File

@ -59,8 +59,10 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, stri
ParseAllocator p = new ParseAllocator;
const(Module) m = parseModule(file, cast(ubyte[]) code, p, cache, false);
ModuleCache moduleCache;
// Run the code and get any warnings
MessageSet rawWarnings = analyze("test", m, config);
MessageSet rawWarnings = analyze("test", m, config, moduleCache);
string[] codeLines = code.split("\n");
// Get the warnings ordered by line

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, const(Scope)* sc)
{
super(fileName, sc);
}
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

@ -30,8 +30,8 @@ final class MismatchedArgumentCheck : BaseAnalyzer
auto identVisitor = scoped!IdentVisitor;
identVisitor.visit(fce.unaryExpression);
DSymbol* sym = resolveSymbol(sc, identVisitor.names);
const istring[] params = sym is null ? [] : sym.parts[].map!(a => a.name).array();
const(DSymbol)* sym = resolveSymbol(sc, identVisitor.names);
const istring[] params = sym is null ? [] : sym.opSlice().map!(a => cast() a.name).array();
const ArgMismatch[] mismatches = compareArgsToParams(params, args);
foreach(size_t i, ref const mm; mismatches)
addErrorMessage(argVisitor.lines[i], argVisitor.columns[i], KEY,
@ -113,15 +113,15 @@ final class ArgVisitor : ASTVisitor
istring[] args;
}
DSymbol* resolveSymbol(const Scope* sc, const istring[] symbolChain)
const(DSymbol)* resolveSymbol(const Scope* sc, const istring[] symbolChain)
{
import std.array:empty;
DSymbol*[] s = sc.getSymbolsByName(symbolChain[0]);
const(DSymbol)*[] s = sc.getSymbolsByName(symbolChain[0]);
if (s.empty)
return null;
DSymbol* sym = s[0];
const(DSymbol)* sym = s[0];
foreach (i; 1 .. symbolChain.length)
{
if (sym.kind == CompletionKind.variableName || sym.kind == CompletionKind.memberVariableName

View File

@ -14,8 +14,12 @@ import std.array;
import std.d.lexer;
import std.d.parser;
import std.d.ast;
import std.allocator : CAllocatorImpl;
import std.typecons:scoped;
import std.typecons : scoped;
import std.experimental.allocator : CAllocatorImpl;
import std.experimental.allocator.mallocator : Mallocator;
import std.experimental.allocator.building_blocks.region : Region;
import std.experimental.allocator.building_blocks.allocator_list : AllocatorList;
import analysis.config;
import analysis.base;
@ -46,22 +50,29 @@ import analysis.unmodified;
import analysis.if_statements;
import analysis.redundant_parens;
import analysis.mismatched_args;
import analysis.label_var_same_name_check;
import memory.allocators:BlockAllocator;
import dsymbol.string_interning : internString;
import dsymbol.scope_;
import dsymbol.semantic;
import dsymbol.conversion;
import dsymbol.conversion.first;
import dsymbol.conversion.second;
import dsymbol.modulecache : ModuleCache;
bool first = true;
void messageFunction(string fileName, size_t line, size_t column, string message,
bool isError)
private alias ASTAllocator = CAllocatorImpl!(
AllocatorList!(n => Region!Mallocator(1024 * 128), Mallocator));
void messageFunction(string fileName, size_t line, size_t column, string message, bool isError)
{
writefln("%s(%d:%d)[%s]: %s", fileName, line, column,
isError ? "error" : "warn", message);
writefln("%s(%d:%d)[%s]: %s", fileName, line, column, isError ? "error" : "warn",
message);
}
void messageFunctionJSON(string fileName, size_t line, size_t column, string message, bool)
void messageFunctionJSON(string fileName, size_t line, size_t column, string message,
bool)
{
writeJSON("dscanner.syntax", fileName, line, column, message);
}
@ -78,16 +89,17 @@ void writeJSON(string key, string fileName, size_t line, size_t column, string m
writeln(` "line": `, line, `,`);
writeln(` "column": `, column, `,`);
writeln(` "message": "`, message.replace(`"`, `\"`), `"`);
write( " }");
write(" }");
}
bool syntaxCheck(string[] fileNames)
bool syntaxCheck(string[] fileNames, ref StringCache stringCache, ref ModuleCache moduleCache)
{
StaticAnalysisConfig config = defaultStaticAnalysisConfig();
return analyze(fileNames, config, false);
return analyze(fileNames, config, stringCache, moduleCache, false);
}
void generateReport(string[] fileNames, const StaticAnalysisConfig config)
void generateReport(string[] fileNames, const StaticAnalysisConfig config,
ref StringCache cache, ref ModuleCache moduleCache)
{
writeln("{");
writeln(` "issues": [`);
@ -97,14 +109,14 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config)
foreach (fileName; fileNames)
{
File f = File(fileName);
if (f.size == 0) continue;
if (f.size == 0)
continue;
auto code = uninitializedArray!(ubyte[])(to!size_t(f.size));
f.rawRead(code);
ParseAllocator p = new ParseAllocator;
StringCache cache = StringCache(StringCache.defaultBucketCount);
const Module m = parseModule(fileName, code, p, cache, true, &lineOfCodeCount);
stats.visit(m);
MessageSet results = analyze(fileName, m, config, true);
MessageSet results = analyze(fileName, m, config, moduleCache, true);
foreach (result; results[])
{
writeJSON(result.key, result.fileName, result.line, result.column, result.message);
@ -123,26 +135,31 @@ void generateReport(string[] fileNames, const StaticAnalysisConfig config)
writeln("}");
}
/// For multiple files
/// Returns: true if there were errors
bool analyze(string[] fileNames, const StaticAnalysisConfig config, bool staticAnalyze = true)
/**
* 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,
ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true)
{
bool hasErrors = false;
foreach (fileName; fileNames)
{
File f = File(fileName);
if (f.size == 0) continue;
if (f.size == 0)
continue;
auto code = uninitializedArray!(ubyte[])(to!size_t(f.size));
f.rawRead(code);
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);
assert (m);
if (errorCount > 0)
&errorCount, &warningCount);
assert(m);
if (errorCount > 0 || (staticAnalyze && warningCount > 0))
hasErrors = true;
MessageSet results = analyze(fileName, m, config, staticAnalyze);
MessageSet results = analyze(fileName, m, config, moduleCache, staticAnalyze);
if (results is null)
continue;
foreach (result; results[])
@ -157,6 +174,7 @@ const(Module) parseModule(string fileName, ubyte[] code, ParseAllocator p,
uint* errorCount = null, uint* warningCount = null)
{
import stats : isLineOfCode;
LexerConfig config;
config.fileName = fileName;
config.stringBehavior = StringBehavior.source;
@ -164,47 +182,81 @@ const(Module) parseModule(string fileName, ubyte[] code, ParseAllocator p,
if (linesOfCode !is null)
(*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens);
return std.d.parser.parseModule(tokens, fileName, p,
report ? &messageFunctionJSON : &messageFunction,
errorCount, warningCount);
report ? &messageFunctionJSON : &messageFunction, errorCount, warningCount);
}
MessageSet analyze(string fileName, const Module m,
const StaticAnalysisConfig analysisConfig, bool staticAnalyze = true)
const StaticAnalysisConfig analysisConfig, ref ModuleCache moduleCache, bool staticAnalyze = true)
{
if (!staticAnalyze)
return null;
auto allocator = scoped!(CAllocatorImpl!(BlockAllocator!(1024 * 16)))();
const(Scope)* moduleScope = generateAutocompleteTrees(m, allocator);
auto symbolAllocator = new ASTAllocator;
auto first = scoped!FirstPass(m, internString(fileName), symbolAllocator,
symbolAllocator, true, &moduleCache, null);
first.run();
secondPass(first.rootSymbol, first.moduleScope, moduleCache);
typeid(SemanticSymbol).destroy(first.rootSymbol);
const(Scope)* moduleScope = first.moduleScope;
BaseAnalyzer[] checks;
if (analysisConfig.style_check) checks ~= new StyleChecker(fileName, moduleScope);
if (analysisConfig.enum_array_literal_check) checks ~= new EnumArrayLiteralCheck(fileName, moduleScope);
if (analysisConfig.exception_check) checks ~= new PokemonExceptionCheck(fileName, moduleScope);
if (analysisConfig.delete_check) checks ~= new DeleteCheck(fileName, moduleScope);
if (analysisConfig.float_operator_check) checks ~= new FloatOperatorCheck(fileName, moduleScope);
if (analysisConfig.number_style_check) checks ~= new NumberStyleCheck(fileName, moduleScope);
if (analysisConfig.object_const_check) checks ~= new ObjectConstCheck(fileName, moduleScope);
if (analysisConfig.backwards_range_check) checks ~= new BackwardsRangeCheck(fileName, moduleScope);
if (analysisConfig.if_else_same_check) checks ~= new IfElseSameCheck(fileName, moduleScope);
if (analysisConfig.constructor_check) checks ~= new ConstructorCheck(fileName, moduleScope);
if (analysisConfig.unused_label_check) checks ~= new UnusedLabelCheck(fileName, moduleScope);
if (analysisConfig.unused_variable_check) checks ~= new UnusedVariableCheck(fileName, moduleScope);
if (analysisConfig.duplicate_attribute) checks ~= new DuplicateAttributeCheck(fileName, moduleScope);
if (analysisConfig.opequals_tohash_check) checks ~= new OpEqualsWithoutToHashCheck(fileName, moduleScope);
if (analysisConfig.length_subtraction_check) checks ~= new LengthSubtractionCheck(fileName, moduleScope);
if (analysisConfig.builtin_property_names_check) checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope);
if (analysisConfig.asm_style_check) checks ~= new AsmStyleCheck(fileName, moduleScope);
if (analysisConfig.logical_precedence_check) checks ~= new LogicPrecedenceCheck(fileName, moduleScope);
if (analysisConfig.undocumented_declaration_check) checks ~= new UndocumentedDeclarationCheck(fileName, moduleScope);
if (analysisConfig.function_attribute_check) checks ~= new FunctionAttributeCheck(fileName, moduleScope);
if (analysisConfig.comma_expression_check) checks ~= new CommaExpressionCheck(fileName, moduleScope);
if (analysisConfig.local_import_check) checks ~= new LocalImportCheck(fileName, moduleScope);
if (analysisConfig.could_be_immutable_check) checks ~= new UnmodifiedFinder(fileName, moduleScope);
if (analysisConfig.redundant_parens_check) checks ~= new RedundantParenCheck(fileName, moduleScope);
if (analysisConfig.mismatched_args_check) checks ~= new MismatchedArgumentCheck(fileName, moduleScope);
version(none) if (analysisConfig.redundant_if_check) checks ~= new IfStatementCheck(fileName, moduleScope);
if (analysisConfig.asm_style_check)
checks ~= new AsmStyleCheck(fileName, moduleScope);
if (analysisConfig.backwards_range_check)
checks ~= new BackwardsRangeCheck(fileName, moduleScope);
if (analysisConfig.builtin_property_names_check)
checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope);
if (analysisConfig.comma_expression_check)
checks ~= new CommaExpressionCheck(fileName, moduleScope);
if (analysisConfig.constructor_check)
checks ~= new ConstructorCheck(fileName, moduleScope);
if (analysisConfig.could_be_immutable_check)
checks ~= new UnmodifiedFinder(fileName, moduleScope);
if (analysisConfig.delete_check)
checks ~= new DeleteCheck(fileName, moduleScope);
if (analysisConfig.duplicate_attribute)
checks ~= new DuplicateAttributeCheck(fileName, moduleScope);
if (analysisConfig.enum_array_literal_check)
checks ~= new EnumArrayLiteralCheck(fileName, moduleScope);
if (analysisConfig.exception_check)
checks ~= new PokemonExceptionCheck(fileName, moduleScope);
if (analysisConfig.float_operator_check)
checks ~= new FloatOperatorCheck(fileName, moduleScope);
if (analysisConfig.function_attribute_check)
checks ~= new FunctionAttributeCheck(fileName, moduleScope);
if (analysisConfig.if_else_same_check)
checks ~= new IfElseSameCheck(fileName, moduleScope);
if (analysisConfig.label_var_same_name_check)
checks ~= new LabelVarNameCheck(fileName, moduleScope);
if (analysisConfig.length_subtraction_check)
checks ~= new LengthSubtractionCheck(fileName, moduleScope);
if (analysisConfig.local_import_check)
checks ~= new LocalImportCheck(fileName, moduleScope);
if (analysisConfig.logical_precedence_check)
checks ~= new LogicPrecedenceCheck(fileName, moduleScope);
if (analysisConfig.mismatched_args_check)
checks ~= new MismatchedArgumentCheck(fileName, moduleScope);
if (analysisConfig.number_style_check)
checks ~= new NumberStyleCheck(fileName, moduleScope);
if (analysisConfig.object_const_check)
checks ~= new ObjectConstCheck(fileName, moduleScope);
if (analysisConfig.opequals_tohash_check)
checks ~= new OpEqualsWithoutToHashCheck(fileName, moduleScope);
if (analysisConfig.redundant_parens_check)
checks ~= new RedundantParenCheck(fileName, moduleScope);
if (analysisConfig.style_check)
checks ~= new StyleChecker(fileName, moduleScope);
if (analysisConfig.undocumented_declaration_check)
checks ~= new UndocumentedDeclarationCheck(fileName, moduleScope);
if (analysisConfig.unused_label_check)
checks ~= new UnusedLabelCheck(fileName, moduleScope);
if (analysisConfig.unused_variable_check)
checks ~= new UnusedVariableCheck(fileName, moduleScope);
version (none)
if (analysisConfig.redundant_if_check)
checks ~= new IfStatementCheck(fileName, moduleScope);
foreach (check; checks)
{
@ -217,4 +269,3 @@ MessageSet analyze(string fileName, const Module m,
set.insert(message);
return set;
}

View File

@ -9,6 +9,8 @@ import analysis.base;
import dsymbol.scope_ : Scope;
import std.d.ast;
import std.d.lexer;
import std.regex : ctRegex, matchAll;
import std.stdio;
/**
@ -38,13 +40,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 +69,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 +112,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 +120,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 +177,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 +210,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 +260,8 @@ private:
{
IdType protection;
bool isOverride;
bool isDeprecated;
bool isDisabled;
}
ProtectionInfo[] stack;
@ -232,3 +275,5 @@ private immutable string[] ignoredFunctionNames = [
"toHash",
"main"
];
private enum getSetRe = ctRegex!`^(?:get|set)(?:\p{Lu}|_).*`;

View File

@ -159,6 +159,16 @@ class UnmodifiedFinder:BaseAnalyzer
foreachStatement.declarationOrStatement.accept(this);
}
override void visit(const TraitsExpression)
{
// issue #266: Ignore unmodified variables inside of `__traits` expressions
}
override void visit(const TypeofExpression)
{
// issue #270: Ignore unmodified variables inside of `typeof` expressions
}
private:
template PartsMightModify(T)

View File

@ -316,6 +316,16 @@ class UnusedVariableCheck : BaseAnalyzer
variableUsed(primary.identifierChain.identifiers[0].text);
}
override void visit(const TraitsExpression)
{
// issue #266: Ignore unused variables inside of `__traits` expressions
}
override void visit(const TypeofExpression)
{
// issue #270: Ignore unused variables inside of `typeof` expressions
}
private:
mixin template PartsUseVariables(NodeType)
@ -333,13 +343,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;
@ -20,39 +19,11 @@ class UnusedLabelCheck : BaseAnalyzer
super(fileName, sc);
}
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)
@ -100,15 +71,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)
@ -124,11 +86,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

@ -13,11 +13,13 @@ import std.range;
import std.stdio;
import std.array;
import std.conv;
import std.typecons;
/**
* Prints CTAGS information to the given file.
* Includes metadata according to exuberant format used by Vim.
* Params:
* outpt = the file that CTAGS info is written to
* output = the file that Exuberant TAGS info is written to
* fileNames = tags will be generated from these files
*/
void printCtags(File output, string[] fileNames)
@ -28,101 +30,141 @@ void printCtags(File output, string[] fileNames)
foreach (fileName; fileNames)
{
File f = File(fileName);
if (f.size == 0) continue;
if (f.size == 0)
continue;
auto bytes = uninitializedArray!(ubyte[])(to!size_t(f.size));
f.rawRead(bytes);
auto tokens = getTokensForParser(bytes, config, &cache);
Module m = parseModule(tokens.array, fileName, null, &doNothing);
auto printer = new CTagsPrinter;
printer.fileName = fileName;
printer.visit(m);
tags ~= printer.tagLines;
}
output.write("!_TAG_FILE_FORMAT\t2\n"
~ "!_TAG_FILE_SORTED\t1\n"
~ "!_TAG_FILE_AUTHOR\tBrian Schott\n"
~ "!_TAG_PROGRAM_URL\thttps://github.com/Hackerpilot/Dscanner/\n");
output.write(
"!_TAG_FILE_FORMAT\t2\n" ~ "!_TAG_FILE_SORTED\t1\n" ~ "!_TAG_FILE_AUTHOR\tBrian Schott\n" ~ "!_TAG_PROGRAM_URL\thttps://github.com/Hackerpilot/Dscanner/\n");
tags.sort().copy(output.lockingTextWriter);
}
private:
void doNothing(string, size_t, size_t, string, bool) {}
/// States determining how an access modifier affects tags when traversing the
/// AST.
/// The assumption is that there are fewer AST nodes and patterns that affects
/// the whole scope.
/// Therefor the default was chosen to be Reset.
enum AccessState
{
Reset, /// when ascending the AST reset back to the previous access.
Keep /// when ascending the AST keep the new access.
}
class CTagsPrinter : ASTVisitor
alias ContextType = Tuple!(string, "c", string, "access");
void doNothing(string, size_t, size_t, string, bool)
{
}
string paramsToString(Dec)(const Dec dec)
{
import std.d.formatter : Formatter;
auto app = appender!string();
auto formatter = new Formatter!(typeof(app))(app);
static if (is(Dec == FunctionDeclaration) || is(Dec == Constructor))
{
formatter.format(dec.parameters);
}
else static if (is(Dec == TemplateDeclaration))
{
formatter.format(dec.templateParameters);
}
return app.data;
}
final 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);
tagLines ~= "%s\t%s\t%d;\"\tc\tline:%d%s%s\n".format(dec.name.text,
fileName, dec.name.line, dec.name.line, context.c, context.access);
auto c = context;
context = "\tclass:" ~ dec.name.text;
context = ContextType("\tclass:" ~ dec.name.text, "\taccess:public");
dec.accept(this);
context = c;
}
override void visit(const StructDeclaration dec)
{
tagLines ~= "%s\t%s\t%d;\"\ts%s\n".format(dec.name.text, fileName, dec.name.line, context);
if (dec.name == tok!"")
{
dec.accept(this);
return;
}
tagLines ~= "%s\t%s\t%d;\"\ts\tline:%d%s%s\n".format(dec.name.text,
fileName, dec.name.line, dec.name.line, context.c, context.access);
auto c = context;
context = "\tstruct:" ~ dec.name.text;
context = ContextType("\tstruct:" ~ dec.name.text, "\taccess:public");
dec.accept(this);
context = c;
}
override void visit(const InterfaceDeclaration dec)
{
tagLines ~= "%s\t%s\t%d;\"\ti%s\n".format(dec.name.text, fileName, dec.name.line, context);
tagLines ~= "%s\t%s\t%d;\"\ti\tline:%d%s%s\n".format(dec.name.text,
fileName, dec.name.line, dec.name.line, context.c, context.access);
auto c = context;
context = "\tclass:" ~ dec.name.text;
context = ContextType("\tclass:" ~ dec.name.text, context.access);
dec.accept(this);
context = c;
}
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 params = paramsToString(dec);
tagLines ~= "%s\t%s\t%d;\"\tT\tline:%d%s%s\tsignature:%s\n".format(
dec.name.text, fileName, dec.name.line, dec.name.line, context.c,
context.access, params);
auto c = context;
context = "\ttemplate:" ~ dec.name.text;
context = ContextType("\ttemplate:" ~ dec.name.text, context.access);
dec.accept(this);
context = c;
}
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;
context = "\tfunction:" ~ dec.name.text;
dec.accept(this);
context = c;
auto params = paramsToString(dec);
tagLines ~= "%s\t%s\t%d;\"\tf\tline:%d%s%s\tsignature:%s\n".format(
dec.name.text, fileName, dec.name.line, dec.name.line, context.c,
context.access, params);
}
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;
context = "\tfunction: this";
dec.accept(this);
context = c;
auto params = paramsToString(dec);
tagLines ~= "this\t%s\t%d;\"\tf\tline:%d%s\tsignature:%s%s\n".format(fileName,
dec.line, dec.line, context.c, params, context.access);
}
override void visit(const Destructor dec)
{
tagLines ~= "~this\t%s\t%d;\"\tf%s\n".format(fileName, dec.line,
context);
auto c = context;
context = "\tfunction: this";
dec.accept(this);
context = c;
tagLines ~= "~this\t%s\t%d;\"\tf\tline:%d%s\n".format(fileName,
dec.line, dec.line, context.c);
}
override void visit(const EnumDeclaration dec)
{
tagLines ~= "%s\t%s\t%d;\"\tg%s\n".format(dec.name.text, fileName,
dec.name.line, context);
tagLines ~= "%s\t%s\t%d;\"\tg\tline:%d%s%s\n".format(dec.name.text,
fileName, dec.name.line, dec.name.line, context.c, context.access);
auto c = context;
context = "\tenum:" ~ dec.name.text;
context = ContextType("\tenum:" ~ dec.name.text, context.access);
dec.accept(this);
context = c;
}
@ -134,32 +176,32 @@ 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);
tagLines ~= "%s\t%s\t%d;\"\tu\tline:%d%s\n".format(dec.name.text,
fileName, dec.name.line, dec.name.line, context.c);
auto c = context;
context = "\tunion:" ~ dec.name.text;
context = ContextType("\tunion:" ~ dec.name.text, context.access);
dec.accept(this);
context = c;
}
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.c);
}
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.c);
}
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%s\n".format(d.name.text,
fileName, d.name.line, d.name.line, context.c, context.access);
}
dec.accept(this);
}
@ -168,22 +210,117 @@ 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%s\n".format(i.text,
fileName, i.line, i.line, context.c, context.access);
}
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%s\n".format(fileName,
dec.line, dec.line, context.c, context.access);
}
override void visit(const ModuleDeclaration dec)
{
context = ContextType("", "\taccess:public");
dec.accept(this);
}
override void visit(const Attribute attribute)
{
if (attribute.attribute != tok!"")
{
switch (attribute.attribute.type)
{
case tok!"export":
context.access = "\taccess:public";
break;
case tok!"public":
context.access = "\taccess:public";
break;
case tok!"package":
context.access = "\taccess:protected";
break;
case tok!"protected":
context.access = "\taccess:protected";
break;
case tok!"private":
context.access = "\taccess:private";
break;
default:
}
}
attribute.accept(this);
}
override void visit(const AttributeDeclaration dec)
{
accessSt = AccessState.Keep;
dec.accept(this);
}
override void visit(const Declaration dec)
{
auto c = context;
dec.accept(this);
final switch (accessSt) with (AccessState)
{
case Reset:
context = c;
break;
case Keep:
break;
}
accessSt = AccessState.Reset;
}
override void visit(const Unittest dec)
{
// skipping symbols inside a unit test to not clutter the ctags file
// with "temporary" symbols.
// TODO when phobos have a unittest library investigate how that could
// be used to describe the tests.
// Maybe with UDA's to give the unittest a "name".
}
override void visit(const AliasDeclaration dec)
{
// Old style alias
if (dec.identifierList)
{
foreach (i; dec.identifierList.identifiers)
{
tagLines ~= "%s\t%s\t%d;\"\ta\tline:%d%s%s\n".format(i.text,
fileName, i.line, i.line, context.c, context.access);
}
}
dec.accept(this);
}
override void visit(const AliasInitializer dec)
{
tagLines ~= "%s\t%s\t%d;\"\ta\tline:%d%s%s\n".format(dec.name.text,
fileName, dec.name.line, dec.name.line, context.c, context.access);
dec.accept(this);
}
override void visit(const AliasThisDeclaration dec)
{
auto name = dec.identifier;
tagLines ~= "%s\t%s\t%d;\"\ta\tline:%d%s%s\n".format(name.text,
fileName, name.line, name.line, context.c, context.access);
dec.accept(this);
}
alias visit = ASTVisitor.visit;
string fileName;
string[] tagLines;
int suppressDepth;
string context;
ContextType context;
AccessState accessSt;
}

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

@ -1,4 +1,4 @@
// Copyright Brian Schott (Hackerpilot) 2012-2015.
// Copyright Brian Schott (Hackerpilot) 2012.
// 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)
@ -33,19 +33,10 @@ import inifiled;
import dsymbol.modulecache;
version (unittest)
void main() {}
else
int main(string[] args)
{
version (unittest)
{
return 0;
}
else
{
return run(args);
}
}
int run(string[] args)
{
bool sloc;
bool highlight;
@ -65,9 +56,9 @@ int run(string[] args)
bool report;
string symbolName;
string configLocation;
string[] importPaths;
bool printVersion;
bool explore;
string[] importPaths;
try
{
@ -127,9 +118,10 @@ int run(string[] args)
const(string[]) absImportPaths = importPaths.map!(
a => a.absolutePath().buildNormalizedPath()).array();
ModuleCache.includeParameterSymbols = true;
ModuleCache moduleCache;
if (absImportPaths.length)
ModuleCache.addImportPaths(absImportPaths);
moduleCache.addImportPaths(absImportPaths);
immutable optionCount = count!"a"([sloc, highlight, ctags, tokenCount,
syntaxCheck, ast, imports, outline, tokenDump, styleCheck, defaultConfig,
@ -199,13 +191,13 @@ int run(string[] args)
if (s.exists())
readINIFile(config, s);
if (report)
generateReport(expandArgs(args), config);
generateReport(expandArgs(args), config, cache, moduleCache);
else
analyze(expandArgs(args), config, true);
return analyze(expandArgs(args), config, cache, moduleCache, true) ? 1 : 0;
}
else if (syntaxCheck)
{
return .syntaxCheck(expandArgs(args));
return .syntaxCheck(expandArgs(args), cache, moduleCache) ? 1 : 0;
}
else
{
@ -241,11 +233,11 @@ int run(string[] args)
}
else if (imports)
{
const string[] fileNames = usingStdin ? ["stdin"] : args[1 .. $];
string[] fileNames = usingStdin ? ["stdin"] : args[1 .. $];
LexerConfig config;
config.stringBehavior = StringBehavior.source;
auto visitor = new ImportPrinter;
foreach (name; fileNames)
foreach (name; expandArgs(fileNames))
{
config.fileName = name;
auto tokens = getTokensForParser(
@ -284,6 +276,7 @@ int run(string[] args)
return 0;
}
string[] expandArgs(string[] args)
{
// isFile can throw if it's a broken symlink.
@ -377,11 +370,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
@ -406,7 +401,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