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 }