1 // Copyright Brian Schott (Hackerpilot) 2014. 2 // Distributed under the Boost Software License, Version 1.0. 3 // (See accompanying file LICENSE_1_0.txt or copy at 4 // http://www.boost.org/LICENSE_1_0.txt) 5 6 module analysis.objectconst; 7 8 import std.stdio; 9 import std.regex; 10 import std.d.ast; 11 import std.d.lexer; 12 import analysis.base; 13 import analysis.helpers; 14 15 /** 16 * Checks that opEquals, opCmp, toHash, and toString are either const, 17 * immutable, or inout. 18 */ 19 class ObjectConstCheck : BaseAnalyzer 20 { 21 alias visit = BaseAnalyzer.visit; 22 23 this(string fileName) 24 { 25 super(fileName); 26 } 27 28 mixin visitTemplate!ClassDeclaration; 29 mixin visitTemplate!InterfaceDeclaration; 30 mixin visitTemplate!UnionDeclaration; 31 mixin visitTemplate!StructDeclaration; 32 33 override void visit(const Declaration d) 34 { 35 if (inAggregate && d.functionDeclaration !is null 36 && isInteresting(d.functionDeclaration.name.text) 37 && (!hasConst(d.attributes) 38 && !hasConst(d.functionDeclaration.memberFunctionAttributes))) 39 { 40 addErrorMessage(d.functionDeclaration.name.line, 41 d.functionDeclaration.name.column, "dscanner.suspicious.object_const", 42 "Methods 'opCmp', 'toHash', 'opEquals', and 'toString' are non-const."); 43 } 44 d.accept(this); 45 } 46 47 private static bool hasConst(const Attribute[] attributes) 48 { 49 import std.algorithm : any; 50 return attributes.any!(a => a.attribute == tok!"const"); 51 } 52 53 private static bool hasConst(const MemberFunctionAttribute[] attributes) 54 { 55 import std.algorithm : any; 56 return attributes.any!(a => a.tokenType == tok!"const" 57 || a.tokenType == tok!"immutable" 58 || a.tokenType == tok!"inout"); 59 } 60 61 private static bool isInteresting(string name) 62 { 63 return name == "opCmp" || name == "toHash" || name == "opEquals" 64 || name == "toString"; 65 } 66 67 private bool looking = false; 68 69 } 70 71 unittest 72 { 73 import analysis.config : StaticAnalysisConfig; 74 StaticAnalysisConfig sac; 75 sac.object_const_check = true; 76 assertAnalyzerWarnings(q{ 77 void testConsts() 78 { 79 // Will be ok because all are declared const/immutable 80 class Cat 81 { 82 const bool opEquals(Object a, Object b) // ok 83 { 84 return true; 85 } 86 87 const int opCmp(Object o) // ok 88 { 89 return 1; 90 } 91 92 const hash_t toHash() // ok 93 { 94 return 0; 95 } 96 97 const string toString() // ok 98 { 99 return "Cat"; 100 } 101 } 102 103 // Will warn, because none are const 104 class Dog 105 { 106 bool opEquals(Object a, Object b) // [warn]: Methods 'opCmp', 'toHash', 'opEquals', and 'toString' are non-const. 107 { 108 return true; 109 } 110 111 int opCmp(Object o) // [warn]: Methods 'opCmp', 'toHash', 'opEquals', and 'toString' are non-const. 112 { 113 return 1; 114 } 115 116 hash_t toHash() // [warn]: Methods 'opCmp', 'toHash', 'opEquals', and 'toString' are non-const. 117 { 118 return 0; 119 } 120 121 string toString() // [warn]: Methods 'opCmp', 'toHash', 'opEquals', and 'toString' are non-const. 122 { 123 return "Dog"; 124 } 125 } 126 } 127 }}, sac); 128 129 stderr.writeln("Unittest for ObjectConstCheck passed."); 130 } 131