1 // Copyright (c) 2014, Matthew Brennan Jones <matthew.brennan.jones@gmail.com>
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.duplicate_attribute;
7 
8 import std.stdio;
9 import std..string;
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 for duplicate attributes such as @property, @safe,
18  * @trusted, @system, pure, and nothrow
19  */
20 class DuplicateAttributeCheck : BaseAnalyzer
21 {
22 	alias visit = BaseAnalyzer.visit;
23 
24 	this(string fileName, const(Scope)* sc, bool skipTests = false)
25 	{
26 		super(fileName, sc, skipTests);
27 	}
28 
29 	override void visit(const Declaration node)
30 	{
31 		checkAttributes(node);
32 		node.accept(this);
33 	}
34 
35 	void checkAttributes(const Declaration node)
36 	{
37 		bool hasProperty;
38 		bool hasSafe;
39 		bool hasTrusted;
40 		bool hasSystem;
41 		bool hasPure;
42 		bool hasNoThrow;
43 
44 		// Check the attributes
45 		foreach (attribute; node.attributes)
46 		{
47 			size_t line, column;
48 			string attributeName = getAttributeName(attribute, line, column);
49 			if (!attributeName || line == 0 || column == 0)
50 				return;
51 
52 			// Check for the attributes
53 			checkDuplicateAttribute(attributeName, "property", line, column, hasProperty);
54 			checkDuplicateAttribute(attributeName, "safe", line, column, hasSafe);
55 			checkDuplicateAttribute(attributeName, "trusted", line, column, hasTrusted);
56 			checkDuplicateAttribute(attributeName, "system", line, column, hasSystem);
57 			checkDuplicateAttribute(attributeName, "pure", line, column, hasPure);
58 			checkDuplicateAttribute(attributeName, "nothrow", line, column, hasNoThrow);
59 		}
60 
61 		// Just return if missing function nodes
62 		if (!node.functionDeclaration || !node.functionDeclaration.memberFunctionAttributes)
63 			return;
64 
65 		// Check the functions
66 		foreach (memberFunctionAttribute; node.functionDeclaration.memberFunctionAttributes)
67 		{
68 			size_t line, column;
69 			string attributeName = getAttributeName(memberFunctionAttribute, line, column);
70 			if (!attributeName || line == 0 || column == 0)
71 				return;
72 
73 			// Check for the attributes
74 			checkDuplicateAttribute(attributeName, "property", line, column, hasProperty);
75 			checkDuplicateAttribute(attributeName, "safe", line, column, hasSafe);
76 			checkDuplicateAttribute(attributeName, "trusted", line, column, hasTrusted);
77 			checkDuplicateAttribute(attributeName, "system", line, column, hasSystem);
78 			checkDuplicateAttribute(attributeName, "pure", line, column, hasPure);
79 			checkDuplicateAttribute(attributeName, "nothrow", line, column, hasNoThrow);
80 		}
81 	}
82 
83 	void checkDuplicateAttribute(const string attributeName,
84 			const string attributeDesired, size_t line, size_t column, ref bool hasAttribute)
85 	{
86 		// Just return if not an attribute
87 		if (attributeName != attributeDesired)
88 			return;
89 
90 		// Already has that attribute
91 		if (hasAttribute)
92 		{
93 			string message = "Attribute '%s' is duplicated.".format(attributeName);
94 			addErrorMessage(line, column, "dscanner.unnecessary.duplicate_attribute", message);
95 		}
96 
97 		// Mark it as having that attribute
98 		hasAttribute = true;
99 	}
100 
101 	string getAttributeName(const Attribute attribute, ref size_t line, ref size_t column)
102 	{
103 		// Get the name from the attribute identifier
104 		if (attribute && attribute.atAttribute && attribute.atAttribute.identifier !is Token.init
105 				&& attribute.atAttribute.identifier.text
106 				&& attribute.atAttribute.identifier.text.length)
107 		{
108 			auto token = attribute.atAttribute.identifier;
109 			line = token.line;
110 			column = token.column;
111 			return token.text;
112 		}
113 
114 		// Get the attribute from the storage class token
115 		if (attribute && attribute.attribute.type != tok!"")
116 		{
117 			line = attribute.attribute.line;
118 			column = attribute.attribute.column;
119 			return attribute.attribute.type.str;
120 		}
121 
122 		return null;
123 	}
124 
125 	string getAttributeName(const MemberFunctionAttribute memberFunctionAttribute,
126 			ref size_t line, ref size_t column)
127 	{
128 		// Get the name from the tokenType
129 		if (memberFunctionAttribute && memberFunctionAttribute.tokenType !is IdType.init
130 				&& memberFunctionAttribute.tokenType.str
131 				&& memberFunctionAttribute.tokenType.str.length)
132 		{
133 			// FIXME: How do we get the line/column number?
134 			return memberFunctionAttribute.tokenType.str;
135 		}
136 
137 		// Get the name from the attribute identifier
138 		if (memberFunctionAttribute && memberFunctionAttribute.atAttribute
139 				&& memberFunctionAttribute.atAttribute.identifier !is Token.init
140 				&& memberFunctionAttribute.atAttribute.identifier.type == tok!"identifier"
141 				&& memberFunctionAttribute.atAttribute.identifier.text
142 				&& memberFunctionAttribute.atAttribute.identifier.text.length)
143 		{
144 			auto iden = memberFunctionAttribute.atAttribute.identifier;
145 			line = iden.line;
146 			column = iden.column;
147 			return iden.text;
148 		}
149 
150 		return null;
151 	}
152 }
153 
154 unittest
155 {
156 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
157 
158 	StaticAnalysisConfig sac = disabledConfig();
159 	sac.duplicate_attribute = Check.enabled;
160 	assertAnalyzerWarnings(q{
161 		class ExampleAttributes
162 		{
163 			@property @safe bool xxx() // ok
164 			{
165 				return false;
166 			}
167 
168 			// Duplicate before
169 			@property @property bool aaa() // [warn]: Attribute 'property' is duplicated.
170 			{
171 				return false;
172 			}
173 
174 			// Duplicate after
175 			bool bbb() @safe @safe // [warn]: Attribute 'safe' is duplicated.
176 			{
177 				return false;
178 			}
179 
180 			// Duplicate before and after
181 			@system bool ccc() @system // [warn]: Attribute 'system' is duplicated.
182 			{
183 				return false;
184 			}
185 
186 			// Duplicate before and after
187 			@trusted bool ddd() @trusted // [warn]: Attribute 'trusted' is duplicated.
188 			{
189 				return false;
190 			}
191 		}
192 
193 		class ExamplePureNoThrow
194 		{
195 			pure nothrow bool aaa() // ok
196 			{
197 				return false;
198 			}
199 
200 			pure pure bool bbb() // [warn]: Attribute 'pure' is duplicated.
201 			{
202 				return false;
203 			}
204 
205 			// FIXME: There is no way to get the line/column number of the attribute like this
206 			bool ccc() pure pure // FIXME: [warn]: Attribute 'pure' is duplicated.
207 			{
208 				return false;
209 			}
210 
211 			nothrow nothrow bool ddd() // [warn]: Attribute 'nothrow' is duplicated.
212 			{
213 				return false;
214 			}
215 
216 			// FIXME: There is no way to get the line/column number of the attribute like this
217 			bool eee() nothrow nothrow // FIXME: [warn]: Attribute 'nothrow' is duplicated.
218 			{
219 				return false;
220 			}
221 		}
222 	}c, sac);
223 
224 	stderr.writeln("Unittest for DuplicateAttributeCheck passed.");
225 }