Improvements to the onmodified variable checker

This commit is contained in:
Hackerpilot 2015-01-15 13:24:42 -08:00
parent f7ffced8ee
commit 8dc46e3992
8 changed files with 64 additions and 30 deletions

View File

@ -29,8 +29,6 @@ class BuiltinPropertyNameCheck : BaseAnalyzer
{ {
alias visit = BaseAnalyzer.visit; alias visit = BaseAnalyzer.visit;
enum string KEY = "dscanner.confusing.builtin_property_names";
this(string fileName) this(string fileName)
{ {
super(fileName); super(fileName);
@ -47,7 +45,7 @@ class BuiltinPropertyNameCheck : BaseAnalyzer
override void visit(const FunctionBody functionBody) override void visit(const FunctionBody functionBody)
{ {
int d = depth; immutable int d = depth;
scope(exit) depth = d; scope(exit) depth = d;
depth = 0; depth = 0;
functionBody.accept(this); functionBody.accept(this);
@ -77,16 +75,18 @@ class BuiltinPropertyNameCheck : BaseAnalyzer
private: private:
enum string KEY = "dscanner.confusing.builtin_property_names";
string generateErrorMessage(string name) string generateErrorMessage(string name)
{ {
import std.string; import std.string : format;
return format("Avoid naming members '%s'. This can" return format("Avoid naming members '%s'. This can"
~ " confuse code that depends on the '.%s' property of a type.", name, name); ~ " confuse code that depends on the '.%s' property of a type.", name, name);
} }
bool isBuiltinProperty(string name) bool isBuiltinProperty(string name)
{ {
import std.algorithm; import std.algorithm : canFind;
return builtinProperties.canFind(name); return builtinProperties.canFind(name);
} }
@ -98,7 +98,7 @@ private:
unittest unittest
{ {
import analysis.config; import analysis.config : StaticAnalysisConfig;
StaticAnalysisConfig sac; StaticAnalysisConfig sac;
sac.builtin_property_names_check = true; sac.builtin_property_names_check = true;
assertAnalyzerWarnings(q{ assertAnalyzerWarnings(q{

View File

@ -18,11 +18,11 @@ class ConstructorCheck : BaseAnalyzer
override void visit(const ClassDeclaration classDeclaration) override void visit(const ClassDeclaration classDeclaration)
{ {
bool oldHasDefault = hasDefaultArgConstructor; immutable bool oldHasDefault = hasDefaultArgConstructor;
bool oldHasNoArg = hasNoArgConstructor; immutable bool oldHasNoArg = hasNoArgConstructor;
hasNoArgConstructor = false; hasNoArgConstructor = false;
hasDefaultArgConstructor = false; hasDefaultArgConstructor = false;
State prev = state; immutable State prev = state;
state = State.inClass; state = State.inClass;
classDeclaration.accept(this); classDeclaration.accept(this);
if (hasNoArgConstructor && hasDefaultArgConstructor) if (hasNoArgConstructor && hasDefaultArgConstructor)
@ -39,7 +39,7 @@ class ConstructorCheck : BaseAnalyzer
override void visit(const StructDeclaration structDeclaration) override void visit(const StructDeclaration structDeclaration)
{ {
State prev = state; immutable State prev = state;
state = State.inStruct; state = State.inStruct;
structDeclaration.accept(this); structDeclaration.accept(this);
state = prev; state = prev;

View File

@ -69,7 +69,7 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, stri
foreach (rawWarning; rawWarnings[]) foreach (rawWarning; rawWarnings[])
{ {
// Skip the warning if it is on line zero // Skip the warning if it is on line zero
size_t rawLine = rawWarning.line; immutable size_t rawLine = rawWarning.line;
if (rawLine == 0) if (rawLine == 0)
{ {
stderr.writefln("!!! Skipping warning because it is on line zero:\n%s", rawWarning.message); stderr.writefln("!!! Skipping warning because it is on line zero:\n%s", rawWarning.message);
@ -89,8 +89,8 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, stri
continue; continue;
// Skip if there is no comment or code // Skip if there is no comment or code
string codePart = codeLine.before("// "); immutable string codePart = codeLine.before("// ");
string commentPart = codeLine.after("// "); immutable string commentPart = codeLine.after("// ");
if (!codePart.length || !commentPart.length) if (!codePart.length || !commentPart.length)
continue; continue;
@ -107,7 +107,7 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, stri
// No warning // No warning
if (lineNo !in warnings) if (lineNo !in warnings)
{ {
string errors = "Expected warning:\n%s\nFrom source code at (%s:?):\n%s".format( immutable string errors = "Expected warning:\n%s\nFrom source code at (%s:?):\n%s".format(
messages[lineNo], messages[lineNo],
lineNo, lineNo,
codeLines[lineNo - line] codeLines[lineNo - line]
@ -117,7 +117,7 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, stri
// Different warning // Different warning
else if (warnings[lineNo] != messages[lineNo]) else if (warnings[lineNo] != messages[lineNo])
{ {
string errors = "Expected warning:\n%s\nBut was:\n%s\nFrom source code at (%s:?):\n%s".format( immutable string errors = "Expected warning:\n%s\nBut was:\n%s\nFrom source code at (%s:?):\n%s".format(
messages[lineNo], messages[lineNo],
warnings[lineNo], warnings[lineNo],
lineNo, lineNo,
@ -143,7 +143,7 @@ void assertAnalyzerWarnings(string code, const StaticAnalysisConfig config, stri
} }
if (unexpectedWarnings.length) if (unexpectedWarnings.length)
{ {
string message = "Unexpected warnings:\n" ~ unexpectedWarnings.join("\n"); immutable string message = "Unexpected warnings:\n" ~ unexpectedWarnings.join("\n");
throw new core.exception.AssertError(message, file, line); throw new core.exception.AssertError(message, file, line);
} }
} }

View File

@ -54,6 +54,8 @@ class ImmutableFinder:BaseAnalyzer
{ {
foreach (d; dec.declarators) foreach (d; dec.declarators)
{ {
if (initializedFromCast(d.initializer))
continue;
tree[$ - 1].insert(new VariableInfo(d.name.text, d.name.line, tree[$ - 1].insert(new VariableInfo(d.name.text, d.name.line,
d.name.column)); d.name.column));
} }
@ -68,9 +70,13 @@ class ImmutableFinder:BaseAnalyzer
&& autoDeclaration.storageClass.token != tok!"enum" && autoDeclaration.storageClass.token != tok!"enum"
&& autoDeclaration.storageClass.token != tok!"immutable")) && autoDeclaration.storageClass.token != tok!"immutable"))
{ {
foreach (id; autoDeclaration.identifiers) foreach (size_t i, id; autoDeclaration.identifiers)
{
if (initializedFromCast(autoDeclaration.initializers[i]))
continue;
tree[$ - 1].insert(new VariableInfo(id.text, id.line, tree[$ - 1].insert(new VariableInfo(id.text, id.line,
id.column)); id.column));
}
} }
autoDeclaration.accept(this); autoDeclaration.accept(this);
} }
@ -130,6 +136,17 @@ class ImmutableFinder:BaseAnalyzer
unary.accept(this); unary.accept(this);
} }
override void visit(const ForeachStatement foreachStatement)
{
if (foreachStatement.low !is null)
{
interest++;
foreachStatement.low.accept(this);
interest--;
}
foreachStatement.declarationOrStatement.accept(this);
}
private: private:
template PartsMightModify(T) template PartsMightModify(T)
@ -156,6 +173,28 @@ private:
} }
} }
bool initializedFromCast(const Initializer initializer)
{
import std.typecons : scoped;
static class CastFinder : ASTVisitor
{
alias visit = ASTVisitor.visit;
override void visit(const CastExpression castExpression)
{
foundCast = true;
castExpression.accept(this);
}
bool foundCast = false;
}
if (initializer is null)
return false;
auto finder = scoped!CastFinder();
finder.visit(initializer);
return finder.foundCast;
}
bool canFindImmutable(const Declaration dec) bool canFindImmutable(const Declaration dec)
{ {
import std.algorithm : canFind, map; import std.algorithm : canFind, map;
@ -195,7 +234,7 @@ private:
foreach (vi; tree[$ - 1]) foreach (vi; tree[$ - 1])
{ {
immutable string errorMessage = "Variable " ~ vi.name immutable string errorMessage = "Variable " ~ vi.name
~ " could have been declared immutable"; ~ " is never modified and could have been declared immutable.";
addErrorMessage(vi.line, vi.column, "dscanner.suspicious.could_be_immutable", addErrorMessage(vi.line, vi.column, "dscanner.suspicious.could_be_immutable",
errorMessage); errorMessage);
} }

View File

@ -29,8 +29,8 @@ class LengthSubtractionCheck : BaseAnalyzer
{ {
if (addExpression.operator == tok!"-") if (addExpression.operator == tok!"-")
{ {
UnaryExpression l = cast(UnaryExpression) addExpression.left; const UnaryExpression l = cast(const UnaryExpression) addExpression.left;
UnaryExpression r = cast(UnaryExpression) addExpression.right; const UnaryExpression r = cast(const UnaryExpression) addExpression.right;
if (l is null || r is null) if (l is null || r is null)
{ {
// stderr.writeln(__FILE__, " ", __LINE__); // stderr.writeln(__FILE__, " ", __LINE__);

View File

@ -57,7 +57,7 @@ class OpEqualsWithoutToHashCheck : BaseAnalyzer
continue; continue;
// Check if opEquals or toHash // Check if opEquals or toHash
string methodName = declaration.functionDeclaration.name.text; immutable string methodName = declaration.functionDeclaration.name.text;
if (methodName == "opEquals") if (methodName == "opEquals")
hasOpEquals = true; hasOpEquals = true;
else if (methodName == "toHash") else if (methodName == "toHash")

View File

@ -44,7 +44,7 @@ class UnusedVariableCheck : BaseAnalyzer
pushScope(); pushScope();
if (functionDec.functionBody !is null) if (functionDec.functionBody !is null)
{ {
bool ias = inAggregateScope; immutable bool ias = inAggregateScope;
inAggregateScope = false; inAggregateScope = false;
if (!isOverride) if (!isOverride)
functionDec.parameters.accept(this); functionDec.parameters.accept(this);

View File

@ -133,7 +133,7 @@ int run(string[] args)
} }
else if (tokenDump || highlight) else if (tokenDump || highlight)
{ {
bool usingStdin = args.length == 1; immutable bool usingStdin = args.length == 1;
ubyte[] bytes = usingStdin ? readStdin() : readFile(args[1]); ubyte[] bytes = usingStdin ? readStdin() : readFile(args[1]);
LexerConfig config; LexerConfig config;
config.stringBehavior = StringBehavior.source; config.stringBehavior = StringBehavior.source;
@ -211,7 +211,7 @@ int run(string[] args)
} }
else if (imports) else if (imports)
{ {
string[] fileNames = usingStdin ? ["stdin"] : args[1 .. $]; const string[] fileNames = usingStdin ? ["stdin"] : args[1 .. $];
LexerConfig config; LexerConfig config;
config.stringBehavior = StringBehavior.source; config.stringBehavior = StringBehavior.source;
auto visitor = new ImportPrinter; auto visitor = new ImportPrinter;
@ -236,12 +236,6 @@ int run(string[] args)
auto tokens = getTokensForParser( auto tokens = getTokensForParser(
usingStdin ? readStdin() : readFile(args[1]), usingStdin ? readStdin() : readFile(args[1]),
config, &cache); config, &cache);
// writeln("text blank\tindex\tline\tcolumn\ttype\tcomment");
// foreach (token; tokens)
// {
// writefln("<<%20s>>%b\t%d\t%d\t%d\t%d\t%s", token.text is null ? str(token.type) : token.text,
// token.text !is null, token.index, token.line, token.column, token.type, token.comment);
// }
auto mod = parseModule(tokens, fileName, null, &doNothing); auto mod = parseModule(tokens, fileName, null, &doNothing);
if (ast) if (ast)
@ -387,6 +381,7 @@ string getConfigurationLocation()
{ {
version (useXDG) version (useXDG)
{ {
int x;
import std.process; import std.process;
string configDir = environment.get("XDG_CONFIG_HOME", null); string configDir = environment.get("XDG_CONFIG_HOME", null);
if (configDir is null) if (configDir is null)