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