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) && (!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', 'opCast', and/or '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 
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 
58 		return attributes.any!(a => a.tokenType == tok!"const"
59 				|| a.tokenType == tok!"immutable" || 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 
76 	StaticAnalysisConfig sac;
77 	sac.object_const_check = true;
78 	assertAnalyzerWarnings(q{
79 		void testConsts()
80 		{
81 			// Will be ok because all are declared const/immutable
82 			class Cat
83 			{
84 				const bool opEquals(Object a, Object b) // ok
85 				{
86 					return true;
87 				}
88 
89 				const int opCmp(Object o) // ok
90 				{
91 					return 1;
92 				}
93 
94 				const hash_t toHash() // ok
95 				{
96 					return 0;
97 				}
98 
99 				const string toString() // ok
100 				{
101 					return "Cat";
102 				}
103 			}
104 
105 			// Will warn, because none are const
106 			class Dog
107 			{
108 				bool opEquals(Object a, Object b) // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.
109 				{
110 					return true;
111 				}
112 
113 				int opCmp(Object o) // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.
114 				{
115 					return 1;
116 				}
117 
118 				hash_t toHash() // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.
119 				{
120 					return 0;
121 				}
122 
123 				string toString() // [warn]: Methods 'opCmp', 'toHash', 'opEquals', 'opCast', and/or 'toString' are non-const.
124 				{
125 					return "Dog";
126 				}
127 			}
128 		}
129 	}}, sac);
130 
131 	stderr.writeln("Unittest for ObjectConstCheck passed.");
132 }