D-Scanner/src/dscanner/analysis/unmodified.d

352 lines
7.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 dscanner.analysis.unmodified;
import dscanner.analysis.base;
/**
* Checks for variables that could have been declared const or immutable
*/
// TODO: many similarities to unused_param.d, maybe refactor into a common base class
extern (C++) class UnmodifiedFinder(AST) : BaseAnalyzerDmd
{
alias visit = BaseAnalyzerDmd.visit;
mixin AnalyzerInfo!"could_be_immutable_check";
private enum KEY = "dscanner.suspicious.unmodified";
private enum MSG = "Variable %s is never modified and could have been declared const or immutable.";
private static struct VarInfo
{
string name;
ulong lineNum;
ulong charNum;
bool isUsed = false;
}
private alias VarSet = VarInfo[string];
private VarSet[] usedVars;
private bool inAggregate;
extern (D) this(string fileName, bool skipTests = false)
{
super(fileName, skipTests);
pushScope();
}
override void visit(AST.UserAttributeDeclaration userAttribute)
{
if (shouldIgnoreDecl(userAttribute, KEY))
return;
super.visit(userAttribute);
}
override void visit(AST.Module mod)
{
if (shouldIgnoreDecl(mod.userAttribDecl(), KEY))
return;
super.visit(mod);
}
override void visit(AST.CompoundStatement compoundStatement)
{
pushScope();
super.visit(compoundStatement);
popScope();
}
override void visit(AST.TemplateDeclaration templateDeclaration)
{
auto oldInTemplate = inAggregate;
inAggregate = true;
super.visit(templateDeclaration);
inAggregate = oldInTemplate;
}
override void visit(AST.StructDeclaration structDecl)
{
auto oldInAggregate = inAggregate;
inAggregate = true;
super.visit(structDecl);
inAggregate = oldInAggregate;
}
override void visit(AST.VarDeclaration varDeclaration)
{
import dmd.astenums : STC;
super.visit(varDeclaration);
if (varDeclaration.ident is null)
return;
string varName = cast(string) varDeclaration.ident.toString();
bool isConst = varDeclaration.storage_class & STC.const_ || varDeclaration.storage_class & STC.immutable_
|| varDeclaration.storage_class & STC.manifest || isConstType(varDeclaration.type);
bool markAsUsed = isConst || isFromCastOrNew(varDeclaration._init) || inAggregate;
currentScope[varName] = VarInfo(varName, varDeclaration.loc.linnum, varDeclaration.loc.charnum, markAsUsed);
}
private bool isConstType(AST.Type type)
{
import dmd.astenums : MODFlags;
if (type is null)
return false;
bool isConst = type.mod & MODFlags.const_ || type.mod & MODFlags.immutable_;
if (auto typePtr = type.isTypePointer())
isConst = isConst || typePtr.next.mod & MODFlags.const_ || typePtr.next.mod & MODFlags.immutable_;
return isConst;
}
private bool isFromCastOrNew(AST.Initializer initializer)
{
if (initializer is null)
return false;
auto initExpr = initializer.isExpInitializer();
if (initExpr is null)
return false;
return initExpr.exp.isNewExp() !is null || initExpr.exp.isCastExp() !is null;
}
override void visit(AST.IntervalExp intervalExp)
{
super.visit(intervalExp);
auto identifier1 = intervalExp.lwr.isIdentifierExp();
if (identifier1 && identifier1.ident)
markAsUsed(cast(string) identifier1.ident.toString());
auto identifier2 = intervalExp.upr.isIdentifierExp();
if (identifier2 && identifier2.ident)
markAsUsed(cast(string) identifier2.ident.toString());
}
override void visit(AST.IndexExp indexExpression)
{
super.visit(indexExpression);
auto identifier1 = indexExpression.e1.isIdentifierExp();
if (identifier1 && identifier1.ident)
markAsUsed(cast(string) identifier1.ident.toString());
auto identifier2 = indexExpression.e2.isIdentifierExp();
if (identifier2 && identifier2.ident)
markAsUsed(cast(string) identifier2.ident.toString());
}
mixin VisitAssignNode!(AST.AssignExp);
mixin VisitAssignNode!(AST.BinAssignExp);
mixin VisitAssignNode!(AST.PtrExp);
mixin VisitAssignNode!(AST.AddrExp);
mixin VisitAssignNode!(AST.PreExp);
mixin VisitAssignNode!(AST.PostExp);
private template VisitAssignNode(NodeType)
{
override void visit(NodeType node)
{
super.visit(node);
if (node.e1 is null)
return;
auto identifier = node.e1.isIdentifierExp();
if (identifier && identifier.ident)
markAsUsed(cast(string) identifier.ident.toString());
}
}
mixin VisitFunctionNode!(AST.CallExp);
mixin VisitFunctionNode!(AST.NewExp);
private template VisitFunctionNode(NodeType)
{
override void visit(NodeType node)
{
super.visit(node);
if (node.arguments is null)
return;
foreach (arg; *node.arguments)
{
auto identifier = arg.isIdentifierExp();
if (identifier && identifier.ident)
markAsUsed(cast(string) arg.isIdentifierExp().ident.toString());
}
}
}
mixin VisitDotExpressionNode!(AST.DotIdExp);
mixin VisitDotExpressionNode!(AST.DotTemplateInstanceExp);
private template VisitDotExpressionNode(NodeType)
{
override void visit(NodeType node)
{
super.visit(node);
auto identifierExp = node.e1.isIdentifierExp();
if (identifierExp && identifierExp.ident)
markAsUsed(cast(string) identifierExp.ident.toString());
}
}
private extern (D) void markAsUsed(string varName)
{
import std.range : retro;
foreach (funcScope; usedVars.retro())
{
if (varName in funcScope)
{
funcScope[varName].isUsed = true;
break;
}
}
}
@property private extern (D) VarSet currentScope()
{
return usedVars[$ - 1];
}
private void pushScope()
{
// Error with gdc-12
//usedVars ~= new VarSet;
// Workaround for gdc-12
VarSet newScope;
newScope["test"] = VarInfo("test", 0, 0);
usedVars ~= newScope;
currentScope.remove("test");
}
private void popScope()
{
import std.algorithm : each, filter;
import std.format : format;
currentScope.byValue
.filter!(var => !var.isUsed)
.each!(var => addErrorMessage(var.lineNum, var.charNum, KEY, MSG.format(var.name)));
usedVars.length--;
}
}
unittest
{
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
import dscanner.analysis.helpers : assertAnalyzerWarningsDMD;
import std.stdio : stderr;
StaticAnalysisConfig sac = disabledConfig();
sac.could_be_immutable_check = Check.enabled;
// fails
assertAnalyzerWarningsDMD(q{
void foo()
{
int i = 1; // [warn]: Variable i is never modified and could have been declared const or immutable.
}
}, sac);
assertAnalyzerWarningsDMD(q{
void foo()
{
int i = 5; // [warn]: Variable i is never modified and could have been declared const or immutable.
int j = 6;
j = i + 5;
}
}c, sac);
// pass
assertAnalyzerWarningsDMD(q{
void foo()
{
const(int) i;
const int j;
const(int)* a;
const int* b;
}
}, sac);
assertAnalyzerWarningsDMD(q{
void foo()
{
immutable(int) i;
immutable int j;
immutable(int)* b;
immutable int* a;
}
}, sac);
assertAnalyzerWarningsDMD(q{
void foo()
{
enum i = 1;
enum string j = "test";
}
}, sac);
assertAnalyzerWarningsDMD(q{
void foo()
{
E e = new E;
auto f = new F;
}
}, sac);
assertAnalyzerWarningsDMD(q{
void issue640()
{
size_t i1;
new Foo(i1);
size_t i2;
foo(i2);
}
}, sac);
assertAnalyzerWarningsDMD(q{
void foo()
{
int i = 5; // [warn]: Variable i is never modified and could have been declared const or immutable.
int j = 6;
j = i + 5;
}
}c, sac);
assertAnalyzerWarningsDMD(q{
void foo()
{
int i = 5;
if (true)
--i;
else
i++;
}
}c, sac);
assertAnalyzerWarningsDMD(q{
@("nolint(dscanner.suspicious.unmodified)")
void foo(){
int i = 1;
}
}, sac);
stderr.writeln("Unittest for UnmodifiedFinder passed.");
}