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 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 if (inAggregate && d.functionDeclaration !is null && !constColon && !constBlock 55 && isInteresting(d.functionDeclaration.name.text) 56 && !hasConst(d.functionDeclaration.memberFunctionAttributes)) 57 { 58 addErrorMessage(d.functionDeclaration.name.line, 59 d.functionDeclaration.name.column, "dscanner.suspicious.object_const", 60 "Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const."); 61 } 62 63 d.accept(this); 64 65 if (!inAggregate) 66 constColon = false; 67 if (setConstBlock) 68 constBlock = false; 69 } 70 71 private static bool hasConst(const MemberFunctionAttribute[] attributes) 72 { 73 import std.algorithm : any; 74 75 return attributes.any!(a => a.tokenType == tok!"const" 76 || a.tokenType == tok!"immutable" || a.tokenType == tok!"inout"); 77 } 78 79 private static bool isInteresting(string name) 80 { 81 return name == "opCmp" || name == "toHash" || name == "opEquals" 82 || name == "toString" || name == "opCast"; 83 } 84 85 private bool constBlock; 86 private bool constColon; 87 88 } 89 90 unittest 91 { 92 import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; 93 94 StaticAnalysisConfig sac = disabledConfig(); 95 sac.object_const_check = Check.enabled; 96 assertAnalyzerWarnings(q{ 97 void testConsts() 98 { 99 // Will be ok because all are declared const/immutable 100 class Cat 101 { 102 const bool opEquals(Object a, Object b) // ok 103 { 104 return true; 105 } 106 107 const int opCmp(Object o) // ok 108 { 109 return 1; 110 } 111 112 const hash_t toHash() // ok 113 { 114 return 0; 115 } 116 117 const string toString() // ok 118 { 119 return "Cat"; 120 } 121 } 122 123 class Bat 124 { 125 const: override string toString() { return "foo"; } // ok 126 } 127 128 class Fox 129 { 130 const{ override string toString() { return "foo"; }} // ok 131 } 132 133 // Will warn, because none are const 134 class Dog 135 { 136 bool opEquals(Object a, Object b) // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const. 137 { 138 return true; 139 } 140 141 int opCmp(Object o) // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const. 142 { 143 return 1; 144 } 145 146 hash_t toHash() // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const. 147 { 148 return 0; 149 } 150 151 string toString() // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const. 152 { 153 return "Dog"; 154 } 155 } 156 } 157 }}, sac); 158 159 stderr.writeln("Unittest for ObjectConstCheck passed."); 160 }