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

183 lines
4.2 KiB
D

// Copyright Brian Schott (Hackerpilot) 2014.
// 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.objectconst;
import std.stdio;
import std.regex;
import dparse.ast;
import dparse.lexer;
import dscanner.analysis.base;
import dscanner.analysis.helpers;
import dsymbol.scope_ : Scope;
/**
* Checks that opEquals, opCmp, toHash, 'opCast', and toString are either const,
* immutable, or inout.
*/
final class ObjectConstCheck : BaseAnalyzer
{
alias visit = BaseAnalyzer.visit;
///
this(string fileName, const(Scope)* sc, bool skipTests = false)
{
super(fileName, sc, skipTests);
}
mixin visitTemplate!ClassDeclaration;
mixin visitTemplate!InterfaceDeclaration;
mixin visitTemplate!UnionDeclaration;
mixin visitTemplate!StructDeclaration;
override void visit(const AttributeDeclaration d)
{
if (d.attribute.attribute == tok!"const" && inAggregate)
{
constColon = true;
}
d.accept(this);
}
override void visit(const Declaration d)
{
import std.algorithm : any;
bool setConstBlock;
if (inAggregate && d.attributes && d.attributes.any!(a => a.attribute == tok!"const"))
{
setConstBlock = true;
constBlock = true;
}
bool containsDisable(A)(const A[] attribs)
{
import std.algorithm.searching : canFind;
return attribs.canFind!(a => a.atAttribute !is null &&
a.atAttribute.identifier.text == "disable");
}
if (const FunctionDeclaration fd = d.functionDeclaration)
{
const isDeclationDisabled = containsDisable(d.attributes) ||
containsDisable(fd.memberFunctionAttributes);
if (inAggregate && !constColon && !constBlock && !isDeclationDisabled
&& isInteresting(fd.name.text) && !hasConst(fd.memberFunctionAttributes))
{
addErrorMessage(d.functionDeclaration.name.line,
d.functionDeclaration.name.column, "dscanner.suspicious.object_const",
"Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.");
}
}
d.accept(this);
if (!inAggregate)
constColon = false;
if (setConstBlock)
constBlock = false;
}
private static bool hasConst(const MemberFunctionAttribute[] attributes)
{
import std.algorithm : any;
return attributes.any!(a => a.tokenType == tok!"const"
|| a.tokenType == tok!"immutable" || a.tokenType == tok!"inout");
}
private static bool isInteresting(string name)
{
return name == "opCmp" || name == "toHash" || name == "opEquals"
|| name == "toString" || name == "opCast";
}
private bool constBlock;
private bool constColon;
}
unittest
{
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
StaticAnalysisConfig sac = disabledConfig();
sac.object_const_check = Check.enabled;
assertAnalyzerWarnings(q{
void testConsts()
{
// Will be ok because all are declared const/immutable
class Cat
{
const bool opEquals(Object a, Object b) // ok
{
return true;
}
const int opCmp(Object o) // ok
{
return 1;
}
const hash_t toHash() // ok
{
return 0;
}
const string toString() // ok
{
return "Cat";
}
}
class Bat
{
const: override string toString() { return "foo"; } // ok
}
class Fox
{
const{ override string toString() { return "foo"; }} // ok
}
class Rat
{
bool opEquals(Object a, Object b) @disable; // ok
}
class Ant
{
@disable bool opEquals(Object a, Object b); // ok
}
// Will warn, because none are const
class Dog
{
bool opEquals(Object a, Object b) // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.
{
return true;
}
int opCmp(Object o) // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.
{
return 1;
}
hash_t toHash() // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.
{
return 0;
}
string toString() // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.
{
return "Dog";
}
}
}
}c, sac);
stderr.writeln("Unittest for ObjectConstCheck passed.");
}