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