310 lines
6.7 KiB
D
310 lines
6.7 KiB
D
// Copyright Brian Schott (Hackerpilot) 2015.
|
|
// 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.unmodified;
|
|
|
|
import analysis.base;
|
|
import dsymbol.scope_ : Scope;
|
|
import std.container;
|
|
import std.d.ast;
|
|
import std.d.lexer;
|
|
|
|
/**
|
|
* Checks for variables that could have been declared const or immutable
|
|
*/
|
|
class UnmodifiedFinder:BaseAnalyzer
|
|
{
|
|
alias visit = BaseAnalyzer.visit;
|
|
|
|
///
|
|
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 blockStatement)
|
|
{
|
|
pushScope();
|
|
blockStatementDepth++;
|
|
blockStatement.accept(this);
|
|
blockStatementDepth--;
|
|
popScope();
|
|
}
|
|
|
|
override void visit(const StructBody structBody)
|
|
{
|
|
pushScope();
|
|
immutable oldBlockStatementDepth = blockStatementDepth;
|
|
blockStatementDepth = 0;
|
|
structBody.accept(this);
|
|
blockStatementDepth = oldBlockStatementDepth;
|
|
popScope();
|
|
}
|
|
|
|
override void visit(const VariableDeclaration dec)
|
|
{
|
|
if (dec.autoDeclaration is null && blockStatementDepth > 0
|
|
&& isImmutable <= 0 && !canFindImmutable(dec))
|
|
{
|
|
foreach (d; dec.declarators)
|
|
{
|
|
if (initializedFromCast(d.initializer))
|
|
continue;
|
|
tree[$ - 1].insert(new VariableInfo(d.name.text, d.name.line,
|
|
d.name.column, isValueTypeSimple(dec.type)));
|
|
}
|
|
}
|
|
dec.accept(this);
|
|
}
|
|
|
|
override void visit(const AutoDeclaration autoDeclaration)
|
|
{
|
|
import std.algorithm : canFind;
|
|
|
|
if (blockStatementDepth > 0 && isImmutable <= 0
|
|
&& (!autoDeclaration.storageClasses.canFind!(a => a.token == tok!"const"
|
|
|| a.token == tok!"enum" || a.token == tok!"immutable")))
|
|
{
|
|
foreach (size_t i, id; autoDeclaration.identifiers)
|
|
{
|
|
if (initializedFromCast(autoDeclaration.initializers[i]))
|
|
continue;
|
|
tree[$ - 1].insert(new VariableInfo(id.text, id.line,
|
|
id.column));
|
|
}
|
|
}
|
|
autoDeclaration.accept(this);
|
|
}
|
|
|
|
override void visit(const AssignExpression assignExpression)
|
|
{
|
|
if (assignExpression.operator != tok!"")
|
|
{
|
|
interest++;
|
|
guaranteeUse++;
|
|
assignExpression.ternaryExpression.accept(this);
|
|
guaranteeUse--;
|
|
interest--;
|
|
|
|
if (assignExpression.operator == tok!"~=")
|
|
interest++;
|
|
assignExpression.expression.accept(this);
|
|
if (assignExpression.operator == tok!"~=")
|
|
interest--;
|
|
}
|
|
else
|
|
assignExpression.accept(this);
|
|
}
|
|
|
|
override void visit(const Declaration dec)
|
|
{
|
|
if (canFindImmutableOrConst(dec))
|
|
{
|
|
isImmutable++;
|
|
dec.accept(this);
|
|
isImmutable--;
|
|
}
|
|
else
|
|
dec.accept(this);
|
|
}
|
|
|
|
override void visit(const IdentifierChain ic)
|
|
{
|
|
if (ic.identifiers.length && interest > 0)
|
|
variableMightBeModified(ic.identifiers[0].text);
|
|
ic.accept(this);
|
|
}
|
|
|
|
override void visit(const IdentifierOrTemplateInstance ioti)
|
|
{
|
|
if (ioti.identifier != tok!"" && interest > 0)
|
|
variableMightBeModified(ioti.identifier.text);
|
|
ioti.accept(this);
|
|
}
|
|
|
|
mixin PartsMightModify!AsmPrimaryExp;
|
|
mixin PartsMightModify!IndexExpression;
|
|
mixin PartsMightModify!SliceExpression;
|
|
mixin PartsMightModify!FunctionCallExpression;
|
|
mixin PartsMightModify!IdentifierOrTemplateChain;
|
|
mixin PartsMightModify!ReturnStatement;
|
|
|
|
override void visit(const UnaryExpression unary)
|
|
{
|
|
if (unary.prefix == tok!"++" || unary.prefix == tok!"--"
|
|
|| unary.suffix == tok!"++" || unary.suffix == tok!"--"
|
|
|| unary.prefix == tok!"*" || unary.prefix == tok!"&")
|
|
{
|
|
interest++;
|
|
guaranteeUse++;
|
|
unary.accept(this);
|
|
guaranteeUse--;
|
|
interest--;
|
|
}
|
|
else
|
|
unary.accept(this);
|
|
}
|
|
|
|
override void visit(const ForeachStatement foreachStatement)
|
|
{
|
|
if (foreachStatement.low !is null)
|
|
{
|
|
interest++;
|
|
foreachStatement.low.accept(this);
|
|
interest--;
|
|
}
|
|
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
|
|
}
|
|
|
|
override void visit(const AsmStatement a)
|
|
{
|
|
inAsm = true;
|
|
a.accept(this);
|
|
inAsm = false;
|
|
}
|
|
|
|
private:
|
|
|
|
template PartsMightModify(T)
|
|
{
|
|
override void visit(const T t)
|
|
{
|
|
interest++;
|
|
t.accept(this);
|
|
interest--;
|
|
}
|
|
}
|
|
|
|
void variableMightBeModified(string name)
|
|
{
|
|
size_t index = tree.length - 1;
|
|
auto vi = VariableInfo(name);
|
|
if (guaranteeUse == 0)
|
|
{
|
|
auto r = tree[index].equalRange(&vi);
|
|
if (!r.empty && r.front.isValueType && !inAsm)
|
|
return;
|
|
}
|
|
while (true)
|
|
{
|
|
if (tree[index].removeKey(&vi) != 0 || index == 0)
|
|
break;
|
|
index--;
|
|
}
|
|
}
|
|
|
|
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 canFindImmutableOrConst(const Declaration dec)
|
|
{
|
|
import std.algorithm : canFind, map, filter;
|
|
return !dec.attributes.map!(a => a.attribute).filter!(
|
|
a => a == cast(IdType) tok!"immutable" || a == cast(IdType) tok!"const")
|
|
.empty;
|
|
}
|
|
|
|
bool canFindImmutable(const VariableDeclaration dec)
|
|
{
|
|
import std.algorithm : canFind;
|
|
foreach (storageClass; dec.storageClasses)
|
|
{
|
|
if (storageClass.token == tok!"enum")
|
|
return true;
|
|
}
|
|
foreach (attr; dec.attributes)
|
|
{
|
|
if (attr.attribute.type == tok!"immutable" || attr.attribute.type == tok!"const")
|
|
return true;
|
|
}
|
|
if (dec.type !is null)
|
|
{
|
|
if (dec.type.typeConstructors.canFind(cast(IdType) tok!"immutable"))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static struct VariableInfo
|
|
{
|
|
string name;
|
|
size_t line;
|
|
size_t column;
|
|
bool isValueType;
|
|
}
|
|
|
|
void popScope()
|
|
{
|
|
foreach (vi; tree[$ - 1])
|
|
{
|
|
immutable string errorMessage = "Variable " ~ vi.name
|
|
~ " is never modified and could have been declared const"
|
|
~ " or immutable.";
|
|
addErrorMessage(vi.line, vi.column, "dscanner.suspicious.unmodified",
|
|
errorMessage);
|
|
}
|
|
tree = tree[0 .. $ - 1];
|
|
}
|
|
|
|
void pushScope()
|
|
{
|
|
tree ~= new RedBlackTree!(VariableInfo*, "a.name < b.name");
|
|
}
|
|
|
|
int blockStatementDepth;
|
|
|
|
int interest;
|
|
|
|
int guaranteeUse;
|
|
|
|
int isImmutable;
|
|
|
|
bool inAsm;
|
|
|
|
RedBlackTree!(VariableInfo*, "a.name < b.name")[] tree;
|
|
}
|
|
|
|
bool isValueTypeSimple(const Type type) pure nothrow @nogc
|
|
{
|
|
if (type.type2 is null)
|
|
return false;
|
|
return type.type2.builtinType != tok!"" && type.typeSuffixes.length == 0;
|
|
}
|