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