1 // Distributed under the Boost Software License, Version 1.0.
2 //    (See accompanying file LICENSE_1_0.txt or copy at
3 //          http://www.boost.org/LICENSE_1_0.txt)
4 
5 module dscanner.analysis.redundant_attributes;
6 
7 import dparse.ast;
8 import dparse.lexer;
9 import dsymbol.scope_ : Scope;
10 import dscanner.analysis.base;
11 import dscanner.analysis.helpers;
12 
13 import std.algorithm;
14 import std.conv : to, text;
15 import std.range : empty, front, walkLength;
16 
17 /**
18  * Checks for redundant attributes. At the moment only visibility attributes.
19  */
20 final class RedundantAttributesCheck : BaseAnalyzer
21 {
22 	mixin AnalyzerInfo!"redundant_attributes_check";
23 
24 	this(string fileName, const(Scope)* sc, bool skipTests = false)
25 	{
26 		super(fileName, sc, skipTests);
27 		stack.length = 0;
28 	}
29 
30 	override void visit(const Declaration decl)
31 	{
32 
33 		// labels, e.g. private:
34 		if (auto attr = decl.attributeDeclaration)
35 		{
36 			if (filterAttributes(attr.attribute))
37 			{
38 				addAttribute(attr.attribute);
39 			}
40 		}
41 
42 		auto attributes = decl.attributes.filter!(a => filterAttributes(a));
43 		if (attributes.walkLength > 0) {
44 
45 			// new scope: private { }
46 			if (decl.declarations.length > 0)
47 			{
48 				const prev = currentAttributes[];
49 				// append to current scope and reset once block is left
50 				foreach (attr; attributes)
51 					addAttribute(attr);
52 
53 				scope(exit) currentAttributes = prev;
54 				decl.accept(this);
55 			} // declarations, e.g. private int ...
56 			else
57 			{
58 				foreach (attr; attributes)
59 					checkAttribute(attr);
60 
61 				decl.accept(this);
62 			}
63 		}
64 		else
65 		{
66 			decl.accept(this);
67 		}
68 	}
69 
70 	alias visit = BaseAnalyzer.visit;
71 
72 	mixin ScopedVisit!Module;
73 	mixin ScopedVisit!BlockStatement;
74 	mixin ScopedVisit!StructBody;
75 	mixin ScopedVisit!CaseStatement;
76 	mixin ScopedVisit!ForStatement;
77 	mixin ScopedVisit!IfStatement;
78 	mixin ScopedVisit!TemplateDeclaration;
79 	mixin ScopedVisit!ConditionalDeclaration;
80 
81 private:
82 
83 	alias ConstAttribute = const Attribute;
84 	alias CurrentScope = ConstAttribute[];
85 	ref CurrentScope currentAttributes()
86 	{
87 		return stack[$ - 1];
88 	}
89 
90 	CurrentScope[] stack;
91 
92 	void addAttribute(const Attribute attr)
93 	{
94 		removeOverwrite(attr);
95 		if (checkAttribute(attr))
96 		{
97 			currentAttributes ~= attr;
98 		}
99 	}
100 
101 	bool checkAttribute(const Attribute attr)
102 	{
103 		auto match = currentAttributes.find!(a => a.attribute.type == attr.attribute.type);
104 		if (!match.empty)
105 		{
106 			auto token = attr.attribute;
107 			addErrorMessage(token.line, token.column, KEY,
108 					text("same visibility attribute used as defined on line ",
109 						match.front.attribute.line.to!string, "."));
110 			return false;
111 		}
112 		return true;
113 	}
114 
115 	void removeOverwrite(const Attribute attr)
116 	{
117 		import std.array : array;
118 		const group = getAttributeGroup(attr);
119 		if (currentAttributes.filter!(a => getAttributeGroup(a) == group
120 					&& !isIdenticalAttribute(a, attr)).walkLength > 0)
121 		{
122 			currentAttributes = currentAttributes.filter!(a => getAttributeGroup(a) != group
123 					|| isIdenticalAttribute(a, attr)).array;
124 		}
125 	}
126 
127 	bool filterAttributes(const Attribute attr)
128 	{
129 		return isAccessSpecifier(attr);
130 	}
131 
132 	static int getAttributeGroup(const Attribute attr)
133 	{
134 		if (isAccessSpecifier(attr))
135 			return 1;
136 
137 		// TODO: not implemented
138 		return attr.attribute.type;
139 	}
140 
141 	static bool isAccessSpecifier(const Attribute attr)
142 	{
143 		auto type = attr.attribute.type;
144 		return type.among(tok!"private", tok!"protected", tok!"public", tok!"package", tok!"export") > 0;
145 	}
146 
147 	static bool isIdenticalAttribute(const Attribute a, const Attribute b)
148 	{
149 		return a.attribute.type == b.attribute.type;
150 	}
151 
152 	auto attributesString()
153 	{
154 		return currentAttributes.map!(a => a.attribute.type.str).joiner(",").to!string;
155 	}
156 
157 	template ScopedVisit(NodeType)
158 	{
159 		override void visit(const NodeType n)
160 		{
161 			pushScope();
162 			n.accept(this);
163 			popScope();
164 		}
165 	}
166 
167 	void pushScope()
168 	{
169 		stack.length++;
170 	}
171 
172 	void popScope()
173 	{
174 		stack.length--;
175 	}
176 
177 	enum string KEY = "dscanner.suspicious.redundant_attributes";
178 }
179 
180 
181 version(unittest)
182 {
183 	import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
184 	import std.stdio : stderr;
185 }
186 
187 unittest
188 {
189 	StaticAnalysisConfig sac = disabledConfig();
190 	sac.redundant_attributes_check = Check.enabled;
191 
192 	// test labels vs. block attributes
193 	assertAnalyzerWarnings(q{
194 unittest
195 {
196 private:
197 	private int blah; // [warn]: same visibility attribute used as defined on line 4.
198 protected
199 {
200 	protected int blah; // [warn]: same visibility attribute used as defined on line 6.
201 }
202 	private int blah; // [warn]: same visibility attribute used as defined on line 4.
203 }}}, sac);
204 
205 	// test labels vs. block attributes
206 	assertAnalyzerWarnings(q{
207 unittest
208 {
209 	private:
210 	private: // [warn]: same visibility attribute used as defined on line 4.
211 	public:
212 		private int a;
213 		public int b; // [warn]: same visibility attribute used as defined on line 6.
214 		public // [warn]: same visibility attribute used as defined on line 6.
215 		{
216 			int c;
217 		}
218 }}}, sac);
219 
220 	// test scopes
221 	assertAnalyzerWarnings(q{
222 unittest
223 {
224 private:
225 	private int foo2; // [warn]: same visibility attribute used as defined on line 4.
226 	private void foo() // [warn]: same visibility attribute used as defined on line 4.
227 	{
228 		private int blah;
229 	}
230 }}}, sac);
231 
232 	// check duplicated visibility attributes
233 	assertAnalyzerWarnings(q{
234 unittest
235 {
236 private:
237 	public int a;
238 private: // [warn]: same visibility attribute used as defined on line 4.
239 }}}, sac);
240 
241 	// test conditional compilation
242 	assertAnalyzerWarnings(q{
243 unittest
244 {
245 version(unittest)
246 {
247 	private:
248 	private int foo; // [warn]: same visibility attribute used as defined on line 6.
249 }
250 private int foo2;
251 }}}, sac);
252 
253 // test scopes
254 	assertAnalyzerWarnings(q{
255 unittest
256 {
257 public:
258 	if (1 == 1)
259 	{
260 		private int b;
261 	}
262 	else
263 	{
264 		public int b;
265 	}
266 	public int b; // [warn]: same visibility attribute used as defined on line 4.
267 }}}, sac);
268 }
269 
270 // test other attribute (not yet implemented, thus shouldn't trigger warnings)
271 unittest
272 {
273 	StaticAnalysisConfig sac = disabledConfig();
274 	sac.redundant_attributes_check = Check.enabled;
275 
276 	// test labels vs. block attributes
277 	assertAnalyzerWarnings(q{
278 unittest
279 {
280 @safe:
281 	@safe void foo();
282 @system
283 {
284 	@system void foo();
285 }
286 	@safe void foo();
287 }}}, sac);
288 
289 
290 	stderr.writeln("Unittest for RedundantAttributesCheck passed.");
291 }