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 }