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 analysis.objectconst;
7 
8 import std.stdio;
9 import std.regex;
10 import dparse.ast;
11 import dparse.lexer;
12 import analysis.base;
13 import 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 	this(string fileName, const(Scope)* sc)
25 	{
26 		super(fileName, sc);
27 	}
28 
29 	mixin visitTemplate!ClassDeclaration;
30 	mixin visitTemplate!InterfaceDeclaration;
31 	mixin visitTemplate!UnionDeclaration;
32 	mixin visitTemplate!StructDeclaration;
33 
34 	override void visit(const Declaration d)
35 	{
36 		if (inAggregate && d.functionDeclaration !is null
37 			&& isInteresting(d.functionDeclaration.name.text)
38 			&& (!hasConst(d.attributes)
39 			&& !hasConst(d.functionDeclaration.memberFunctionAttributes)))
40 		{
41 			addErrorMessage(d.functionDeclaration.name.line,
42 				d.functionDeclaration.name.column, "dscanner.suspicious.object_const",
43 				"Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.");
44 		}
45 		d.accept(this);
46 	}
47 
48 	private static bool hasConst(const Attribute[] attributes)
49 	{
50 		import std.algorithm : any;
51 		return attributes.any!(a => a.attribute == tok!"const");
52 	}
53 
54 	private static bool hasConst(const MemberFunctionAttribute[] attributes)
55 	{
56 		import std.algorithm : any;
57 		return attributes.any!(a => a.tokenType == tok!"const"
58 			|| a.tokenType == tok!"immutable"
59 			|| a.tokenType == tok!"inout");
60 	}
61 
62 	private static bool isInteresting(string name)
63 	{
64 		return name == "opCmp" || name == "toHash" || name == "opEquals"
65 			|| name == "toString" || name == "opCast";
66 	}
67 
68 	private bool looking = false;
69 
70 }
71 
72 unittest
73 {
74 	import analysis.config : StaticAnalysisConfig;
75 	StaticAnalysisConfig sac;
76 	sac.object_const_check = true;
77 	assertAnalyzerWarnings(q{
78 		void testConsts()
79 		{
80 			// Will be ok because all are declared const/immutable
81 			class Cat
82 			{
83 				const bool opEquals(Object a, Object b) // ok
84 				{
85 					return true;
86 				}
87 
88 				const int opCmp(Object o) // ok
89 				{
90 					return 1;
91 				}
92 
93 				const hash_t toHash() // ok
94 				{
95 					return 0;
96 				}
97 
98 				const string toString() // ok
99 				{
100 					return "Cat";
101 				}
102 			}
103 
104 			// Will warn, because none are const
105 			class Dog
106 			{
107 				bool opEquals(Object a, Object b) // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.
108 				{
109 					return true;
110 				}
111 
112 				int opCmp(Object o) // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.
113 				{
114 					return 1;
115 				}
116 
117 				hash_t toHash() // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.
118 				{
119 					return 0;
120 				}
121 
122 				string toString() // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.
123 				{
124 					return "Dog";
125 				}
126 			}
127 		}
128 	}}, sac);
129 
130 	stderr.writeln("Unittest for ObjectConstCheck passed.");
131 }
132