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 dscanner.analysis.objectconst; 7 8 import std.stdio; 9 import std.regex; 10 import dparse.ast; 11 import dparse.lexer; 12 import dscanner.analysis.base; 13 import dscanner.analysis.helpers; 14 import dsymbol.scope_ : Scope; 15 16 /** 17 * Checks that opEquals, opCmp, toHash, 'opCast', and toString are either const, 18 * immutable, or inout. 19 */ 20 final class ObjectConstCheck : BaseAnalyzer 21 { 22 alias visit = BaseAnalyzer.visit; 23 24 mixin AnalyzerInfo!"object_const_check"; 25 26 /// 27 this(string fileName, const(Scope)* sc, bool skipTests = false) 28 { 29 super(fileName, sc, skipTests); 30 } 31 32 mixin visitTemplate!ClassDeclaration; 33 mixin visitTemplate!InterfaceDeclaration; 34 mixin visitTemplate!UnionDeclaration; 35 mixin visitTemplate!StructDeclaration; 36 37 override void visit(const AttributeDeclaration d) 38 { 39 if (d.attribute.attribute == tok!"const" && inAggregate) 40 { 41 constColon = true; 42 } 43 d.accept(this); 44 } 45 46 override void visit(const Declaration d) 47 { 48 import std.algorithm : any; 49 bool setConstBlock; 50 if (inAggregate && d.attributes && d.attributes.any!(a => a.attribute == tok!"const")) 51 { 52 setConstBlock = true; 53 constBlock = true; 54 } 55 56 bool containsDisable(A)(const A[] attribs) 57 { 58 import std.algorithm.searching : canFind; 59 return attribs.canFind!(a => a.atAttribute !is null && 60 a.atAttribute.identifier.text == "disable"); 61 } 62 63 if (const FunctionDeclaration fd = d.functionDeclaration) 64 { 65 const isDeclationDisabled = containsDisable(d.attributes) || 66 containsDisable(fd.memberFunctionAttributes); 67 68 if (inAggregate && !constColon && !constBlock && !isDeclationDisabled 69 && isInteresting(fd.name.text) && !hasConst(fd.memberFunctionAttributes)) 70 { 71 addErrorMessage(d.functionDeclaration.name.line, 72 d.functionDeclaration.name.column, "dscanner.suspicious.object_const", 73 "Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const."); 74 } 75 } 76 77 d.accept(this); 78 79 if (!inAggregate) 80 constColon = false; 81 if (setConstBlock) 82 constBlock = false; 83 } 84 85 private static bool hasConst(const MemberFunctionAttribute[] attributes) 86 { 87 import std.algorithm : any; 88 89 return attributes.any!(a => a.tokenType == tok!"const" 90 || a.tokenType == tok!"immutable" || a.tokenType == tok!"inout"); 91 } 92 93 private static bool isInteresting(string name) 94 { 95 return name == "opCmp" || name == "toHash" || name == "opEquals" 96 || name == "toString" || name == "opCast"; 97 } 98 99 private bool constBlock; 100 private bool constColon; 101 102 } 103 104 unittest 105 { 106 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 107 108 StaticAnalysisConfig sac = disabledConfig(); 109 sac.object_const_check = Check.enabled; 110 assertAnalyzerWarnings(q{ 111 void testConsts() 112 { 113 // Will be ok because all are declared const/immutable 114 class Cat 115 { 116 const bool opEquals(Object a, Object b) // ok 117 { 118 return true; 119 } 120 121 const int opCmp(Object o) // ok 122 { 123 return 1; 124 } 125 126 const hash_t toHash() // ok 127 { 128 return 0; 129 } 130 131 const string toString() // ok 132 { 133 return "Cat"; 134 } 135 } 136 137 class Bat 138 { 139 const: override string toString() { return "foo"; } // ok 140 } 141 142 class Fox 143 { 144 const{ override string toString() { return "foo"; }} // ok 145 } 146 147 class Rat 148 { 149 bool opEquals(Object a, Object b) @disable; // ok 150 } 151 152 class Ant 153 { 154 @disable bool opEquals(Object a, Object b); // ok 155 } 156 157 // Will warn, because none are const 158 class Dog 159 { 160 bool opEquals(Object a, Object b) // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const. 161 { 162 return true; 163 } 164 165 int opCmp(Object o) // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const. 166 { 167 return 1; 168 } 169 170 hash_t toHash() // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const. 171 { 172 return 0; 173 } 174 175 string toString() // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const. 176 { 177 return "Dog"; 178 } 179 } 180 } 181 }}, sac); 182 183 stderr.writeln("Unittest for ObjectConstCheck passed."); 184 }