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 dscanner.analysis.run;
7 
8 import core.memory : GC;
9 
10 import std.stdio;
11 import std.array;
12 import std.conv;
13 import std.algorithm;
14 import std.range;
15 import std.array;
16 import std.functional : toDelegate;
17 import dparse.lexer;
18 import dparse.parser;
19 import dparse.ast;
20 import dparse.rollback_allocator;
21 import std.typecons : scoped;
22 
23 import stdx.allocator : CAllocatorImpl;
24 import stdx.allocator.mallocator : Mallocator;
25 import stdx.allocator.building_blocks.region : Region;
26 import stdx.allocator.building_blocks.allocator_list : AllocatorList;
27 
28 import dscanner.analysis.config;
29 import dscanner.analysis.base;
30 import dscanner.analysis.style;
31 import dscanner.analysis.enumarrayliteral;
32 import dscanner.analysis.pokemon;
33 import dscanner.analysis.del;
34 import dscanner.analysis.fish;
35 import dscanner.analysis.numbers;
36 import dscanner.analysis.objectconst;
37 import dscanner.analysis.range;
38 import dscanner.analysis.ifelsesame;
39 import dscanner.analysis.constructors;
40 import dscanner.analysis.unused;
41 import dscanner.analysis.unused_label;
42 import dscanner.analysis.duplicate_attribute;
43 import dscanner.analysis.opequals_without_tohash;
44 import dscanner.analysis.length_subtraction;
45 import dscanner.analysis.builtin_property_names;
46 import dscanner.analysis.asm_style;
47 import dscanner.analysis.logic_precedence;
48 import dscanner.analysis.stats_collector;
49 import dscanner.analysis.undocumented;
50 import dscanner.analysis.comma_expression;
51 import dscanner.analysis.function_attributes;
52 import dscanner.analysis.local_imports;
53 import dscanner.analysis.unmodified;
54 import dscanner.analysis.if_statements;
55 import dscanner.analysis.redundant_parens;
56 import dscanner.analysis.mismatched_args;
57 import dscanner.analysis.label_var_same_name_check;
58 import dscanner.analysis.line_length;
59 import dscanner.analysis.auto_ref_assignment;
60 import dscanner.analysis.incorrect_infinite_range;
61 import dscanner.analysis.useless_assert;
62 import dscanner.analysis.alias_syntax_check;
63 import dscanner.analysis.static_if_else;
64 import dscanner.analysis.lambda_return_check;
65 import dscanner.analysis.auto_function;
66 import dscanner.analysis.imports_sortedness;
67 import dscanner.analysis.explicitly_annotated_unittests;
68 import dscanner.analysis.properly_documented_public_functions;
69 import dscanner.analysis.final_attribute;
70 import dscanner.analysis.vcall_in_ctor;
71 import dscanner.analysis.useless_initializer;
72 import dscanner.analysis.allman;
73 import dscanner.analysis.redundant_attributes;
74 import dscanner.analysis.has_public_example;
75 import dscanner.analysis.assert_without_msg;
76 import dscanner.analysis.if_constraints_indent;
77 import dscanner.analysis.trust_too_much;
78 import dscanner.analysis.redundant_storage_class;
79 
80 import dsymbol.string_interning : internString;
81 import dsymbol.scope_;
82 import dsymbol.semantic;
83 import dsymbol.conversion;
84 import dsymbol.conversion.first;
85 import dsymbol.conversion.second;
86 import dsymbol.modulecache : ModuleCache;
87 
88 import dscanner.utils;
89 
90 bool first = true;
91 
92 private alias ASTAllocator = CAllocatorImpl!(
93 		AllocatorList!(n => Region!Mallocator(1024 * 128), Mallocator));
94 
95 immutable string defaultErrorFormat = "{filepath}({line}:{column})[{type}]: {message}";
96 
97 void messageFunctionFormat(string format, string fileName, size_t line, size_t column, string message, bool isError)
98 {
99 	auto s = format;
100 
101 	s = s.replace("{filepath}", fileName);
102 	s = s.replace("{line}", to!string(line));
103 	s = s.replace("{column}", to!string(column));
104 	s = s.replace("{type}", isError ? "error" : "warn");
105 	s = s.replace("{message}", message);
106 
107 	writefln("%s", s);
108 }
109 
110 void messageFunction(string fileName, size_t line, size_t column, string message, bool isError)
111 {
112 	messageFunctionFormat(defaultErrorFormat, fileName, line, column, message, isError);
113 }
114 
115 void messageFunctionJSON(string fileName, size_t line, size_t column, string message, bool)
116 {
117 	writeJSON("dscanner.syntax", fileName, line, column, message);
118 }
119 
120 void writeJSON(string key, string fileName, size_t line, size_t column, string message)
121 {
122 	if (!first)
123 		writeln(",");
124 	else
125 		first = false;
126 	writeln("    {");
127 	writeln(`      "key": "`, key, `",`);
128 	writeln(`      "fileName": "`, fileName.replace("\\", "\\\\").replace(`"`, `\"`), `",`);
129 	writeln(`      "line": `, line, `,`);
130 	writeln(`      "column": `, column, `,`);
131 	writeln(`      "message": "`, message.replace("\\", "\\\\").replace(`"`, `\"`), `"`);
132 	write("    }");
133 }
134 
135 bool syntaxCheck(string[] fileNames, string errorFormat, ref StringCache stringCache, ref ModuleCache moduleCache)
136 {
137 	StaticAnalysisConfig config = defaultStaticAnalysisConfig();
138 	return analyze(fileNames, config, errorFormat, stringCache, moduleCache, false);
139 }
140 
141 void generateReport(string[] fileNames, const StaticAnalysisConfig config,
142 		ref StringCache cache, ref ModuleCache moduleCache)
143 {
144 	writeln("{");
145 	writeln(`  "issues": [`);
146 	first = true;
147 	StatsCollector stats = new StatsCollector("");
148 	ulong lineOfCodeCount;
149 	foreach (fileName; fileNames)
150 	{
151 		auto code = readFile(fileName);
152 		// Skip files that could not be read and continue with the rest
153 		if (code.length == 0)
154 			continue;
155 		RollbackAllocator r;
156 		const(Token)[] tokens;
157 		const Module m = parseModule(fileName, code, &r, defaultErrorFormat, cache, true, tokens, &lineOfCodeCount);
158 		stats.visit(m);
159 		MessageSet results = analyze(fileName, m, config, moduleCache, tokens, true);
160 		foreach (result; results[])
161 		{
162 			writeJSON(result.key, result.fileName, result.line, result.column, result.message);
163 		}
164 	}
165 	writeln();
166 	writeln("  ],");
167 	writefln(`  "interfaceCount": %d,`, stats.interfaceCount);
168 	writefln(`  "classCount": %d,`, stats.classCount);
169 	writefln(`  "functionCount": %d,`, stats.functionCount);
170 	writefln(`  "templateCount": %d,`, stats.templateCount);
171 	writefln(`  "structCount": %d,`, stats.structCount);
172 	writefln(`  "statementCount": %d,`, stats.statementCount);
173 	writefln(`  "lineOfCodeCount": %d,`, lineOfCodeCount);
174 	writefln(`  "undocumentedPublicSymbols": %d`, stats.undocumentedPublicSymbols);
175 	writeln("}");
176 }
177 
178 /**
179  * For multiple files
180  *
181  * Returns: true if there were errors or if there were warnings and `staticAnalyze` was true.
182  */
183 bool analyze(string[] fileNames, const StaticAnalysisConfig config, string errorFormat,
184 		ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true)
185 {
186 	bool hasErrors;
187 	foreach (fileName; fileNames)
188 	{
189 		auto code = readFile(fileName);
190 		// Skip files that could not be read and continue with the rest
191 		if (code.length == 0)
192 			continue;
193 		RollbackAllocator r;
194 		uint errorCount;
195 		uint warningCount;
196 		const(Token)[] tokens;
197 		const Module m = parseModule(fileName, code, &r, errorFormat, cache, false, tokens,
198 				null, &errorCount, &warningCount);
199 		assert(m);
200 		if (errorCount > 0 || (staticAnalyze && warningCount > 0))
201 			hasErrors = true;
202 		MessageSet results = analyze(fileName, m, config, moduleCache, tokens, staticAnalyze);
203 		if (results is null)
204 			continue;
205 		foreach (result; results[])
206 		{
207 			hasErrors = true;
208 			messageFunctionFormat(errorFormat, result.fileName, result.line, result.column, result.message, false);
209 		}
210 	}
211 	return hasErrors;
212 }
213 
214 const(Module) parseModule(string fileName, ubyte[] code, RollbackAllocator* p,
215 		string errorFormat, ref StringCache cache, bool report, ref const(Token)[] tokens,
216 		ulong* linesOfCode = null, uint* errorCount = null, uint* warningCount = null)
217 {
218 	import dscanner.stats : isLineOfCode;
219 
220 	auto writeMessages = delegate(string fileName, size_t line, size_t column, string message, bool isError){
221 		return messageFunctionFormat(errorFormat, fileName, line, column, message, isError);
222 	};
223 
224 	LexerConfig config;
225 	config.fileName = fileName;
226 	config.stringBehavior = StringBehavior.source;
227 	tokens = getTokensForParser(code, config, &cache);
228 	if (linesOfCode !is null)
229 		(*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens);
230 	return dparse.parser.parseModule(tokens, fileName, p,
231 		report ? toDelegate(&messageFunctionJSON) : writeMessages,
232 		errorCount, warningCount);
233 }
234 
235 /**
236 Checks whether a module is part of a user-specified include/exclude list.
237 The user can specify a comma-separated list of filters, everyone needs to start with
238 either a '+' (inclusion) or '-' (exclusion).
239 If no includes are specified, all modules are included.
240 */
241 bool shouldRun(string a)(string moduleName, const ref StaticAnalysisConfig config)
242 {
243 	if (mixin("config." ~ a) == Check.disabled)
244 		return false;
245 
246 	// By default, run the check
247 	if (!moduleName.length)
248 		return true;
249 
250 	auto filters = mixin("config.filters." ~ a);
251 
252 	// Check if there are filters are defined
253 	// filters starting with a comma are invalid
254 	if (filters.length == 0 || filters[0].length == 0)
255 		return true;
256 
257 	auto includers = filters.filter!(f => f[0] == '+').map!(f => f[1..$]);
258 	auto excluders = filters.filter!(f => f[0] == '-').map!(f => f[1..$]);
259 
260 	// exclusion has preference over inclusion
261 	if (!excluders.empty && excluders.any!(s => moduleName.canFind(s)))
262 		return false;
263 
264 	if (!includers.empty)
265 		return includers.any!(s => moduleName.canFind(s));
266 
267 	// by default: include all modules
268 	return true;
269 }
270 
271 ///
272 unittest
273 {
274 	bool test(string moduleName, string filters)
275 	{
276 		StaticAnalysisConfig config;
277 		// it doesn't matter which check we test here
278 		config.asm_style_check = Check.enabled;
279 		// this is done automatically by inifiled
280 		config.filters.asm_style_check = filters.split(",");
281 		return shouldRun!"asm_style_check"(moduleName, config);
282 	}
283 
284 	// test inclusion
285 	assert(test("std.foo", "+std."));
286 	// partial matches are ok
287 	assert(test("std.foo", "+bar,+foo"));
288 	// full as well
289 	assert(test("std.foo", "+bar,+std.foo,+foo"));
290 	// mismatch
291 	assert(!test("std.foo", "+bar,+banana"));
292 
293 	// test exclusion
294 	assert(!test("std.foo", "-std."));
295 	assert(!test("std.foo", "-bar,-std.foo"));
296 	assert(!test("std.foo", "-bar,-foo"));
297 	// mismatch
298 	assert(test("std.foo", "-bar,-banana"));
299 
300 	// test combination (exclusion has precedence)
301 	assert(!test("std.foo", "+foo,-foo"));
302 	assert(test("std.foo", "+foo,-bar"));
303 	assert(test("std.bar.foo", "-barr,+bar"));
304 }
305 
306 MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig analysisConfig,
307 		ref ModuleCache moduleCache, const(Token)[] tokens, bool staticAnalyze = true)
308 {
309 	import dsymbol.symbol : DSymbol;
310 
311 	if (!staticAnalyze)
312 		return null;
313 
314 	auto symbolAllocator = scoped!ASTAllocator();
315 	version (unittest)
316 		enum ut = true;
317 	else
318 		enum ut = false;
319 
320 	string moduleName;
321 	if (m !is null && m.moduleDeclaration !is null &&
322 		  m.moduleDeclaration.moduleName !is null &&
323 		  m.moduleDeclaration.moduleName.identifiers !is null)
324 		moduleName = m.moduleDeclaration.moduleName.identifiers.map!(e => e.text).join(".");
325 
326 	auto first = scoped!FirstPass(m, internString(fileName), symbolAllocator,
327 			symbolAllocator, true, &moduleCache, null);
328 	first.run();
329 
330 	secondPass(first.rootSymbol, first.moduleScope, moduleCache);
331 	auto moduleScope = first.moduleScope;
332 	scope(exit) typeid(DSymbol).destroy(first.rootSymbol.acSymbol);
333 	scope(exit) typeid(SemanticSymbol).destroy(first.rootSymbol);
334 	scope(exit) typeid(Scope).destroy(first.moduleScope);
335 	BaseAnalyzer[] checks;
336 
337 	GC.disable;
338 
339 	with(analysisConfig)
340 	if (moduleName.shouldRun!"asm_style_check"(analysisConfig))
341 		checks ~= new AsmStyleCheck(fileName, moduleScope,
342 		asm_style_check == Check.skipTests && !ut);
343 
344 	if (moduleName.shouldRun!"backwards_range_check"(analysisConfig))
345 		checks ~= new BackwardsRangeCheck(fileName, moduleScope,
346 		analysisConfig.backwards_range_check == Check.skipTests && !ut);
347 
348 	if (moduleName.shouldRun!"builtin_property_names_check"(analysisConfig))
349 		checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope,
350 		analysisConfig.builtin_property_names_check == Check.skipTests && !ut);
351 
352 	if (moduleName.shouldRun!"comma_expression_check"(analysisConfig))
353 		checks ~= new CommaExpressionCheck(fileName, moduleScope,
354 		analysisConfig.comma_expression_check == Check.skipTests && !ut);
355 
356 	if (moduleName.shouldRun!"constructor_check"(analysisConfig))
357 		checks ~= new ConstructorCheck(fileName, moduleScope,
358 		analysisConfig.constructor_check == Check.skipTests && !ut);
359 
360 	if (moduleName.shouldRun!"could_be_immutable_check"(analysisConfig))
361 		checks ~= new UnmodifiedFinder(fileName, moduleScope,
362 		analysisConfig.could_be_immutable_check == Check.skipTests && !ut);
363 
364 	if (moduleName.shouldRun!"delete_check"(analysisConfig))
365 		checks ~= new DeleteCheck(fileName, moduleScope,
366 		analysisConfig.delete_check == Check.skipTests && !ut);
367 
368 	if (moduleName.shouldRun!"duplicate_attribute"(analysisConfig))
369 		checks ~= new DuplicateAttributeCheck(fileName, moduleScope,
370 		analysisConfig.duplicate_attribute == Check.skipTests && !ut);
371 
372 	if (moduleName.shouldRun!"enum_array_literal_check"(analysisConfig))
373 		checks ~= new EnumArrayLiteralCheck(fileName, moduleScope,
374 		analysisConfig.enum_array_literal_check == Check.skipTests && !ut);
375 
376 	if (moduleName.shouldRun!"exception_check"(analysisConfig))
377 		checks ~= new PokemonExceptionCheck(fileName, moduleScope,
378 		analysisConfig.exception_check == Check.skipTests && !ut);
379 
380 	if (moduleName.shouldRun!"float_operator_check"(analysisConfig))
381 		checks ~= new FloatOperatorCheck(fileName, moduleScope,
382 		analysisConfig.float_operator_check == Check.skipTests && !ut);
383 
384 	if (moduleName.shouldRun!"function_attribute_check"(analysisConfig))
385 		checks ~= new FunctionAttributeCheck(fileName, moduleScope,
386 		analysisConfig.function_attribute_check == Check.skipTests && !ut);
387 
388 	if (moduleName.shouldRun!"if_else_same_check"(analysisConfig))
389 		checks ~= new IfElseSameCheck(fileName, moduleScope,
390 		analysisConfig.if_else_same_check == Check.skipTests&& !ut);
391 
392 	if (moduleName.shouldRun!"label_var_same_name_check"(analysisConfig))
393 		checks ~= new LabelVarNameCheck(fileName, moduleScope,
394 		analysisConfig.label_var_same_name_check == Check.skipTests && !ut);
395 
396 	if (moduleName.shouldRun!"length_subtraction_check"(analysisConfig))
397 		checks ~= new LengthSubtractionCheck(fileName, moduleScope,
398 		analysisConfig.length_subtraction_check == Check.skipTests && !ut);
399 
400 	if (moduleName.shouldRun!"local_import_check"(analysisConfig))
401 		checks ~= new LocalImportCheck(fileName, moduleScope,
402 		analysisConfig.local_import_check == Check.skipTests && !ut);
403 
404 	if (moduleName.shouldRun!"logical_precedence_check"(analysisConfig))
405 		checks ~= new LogicPrecedenceCheck(fileName, moduleScope,
406 		analysisConfig.logical_precedence_check == Check.skipTests && !ut);
407 
408 	if (moduleName.shouldRun!"mismatched_args_check"(analysisConfig))
409 		checks ~= new MismatchedArgumentCheck(fileName, moduleScope,
410 		analysisConfig.mismatched_args_check == Check.skipTests && !ut);
411 
412 	if (moduleName.shouldRun!"number_style_check"(analysisConfig))
413 		checks ~= new NumberStyleCheck(fileName, moduleScope,
414 		analysisConfig.number_style_check == Check.skipTests && !ut);
415 
416 	if (moduleName.shouldRun!"object_const_check"(analysisConfig))
417 		checks ~= new ObjectConstCheck(fileName, moduleScope,
418 		analysisConfig.object_const_check == Check.skipTests && !ut);
419 
420 	if (moduleName.shouldRun!"opequals_tohash_check"(analysisConfig))
421 		checks ~= new OpEqualsWithoutToHashCheck(fileName, moduleScope,
422 		analysisConfig.opequals_tohash_check == Check.skipTests && !ut);
423 
424 	if (moduleName.shouldRun!"redundant_parens_check"(analysisConfig))
425 		checks ~= new RedundantParenCheck(fileName, moduleScope,
426 		analysisConfig.redundant_parens_check == Check.skipTests && !ut);
427 
428 	if (moduleName.shouldRun!"style_check"(analysisConfig))
429 		checks ~= new StyleChecker(fileName, moduleScope,
430 		analysisConfig.style_check == Check.skipTests && !ut);
431 
432 	if (moduleName.shouldRun!"undocumented_declaration_check"(analysisConfig))
433 		checks ~= new UndocumentedDeclarationCheck(fileName, moduleScope,
434 		analysisConfig.undocumented_declaration_check == Check.skipTests && !ut);
435 
436 	if (moduleName.shouldRun!"unused_label_check"(analysisConfig))
437 		checks ~= new UnusedLabelCheck(fileName, moduleScope,
438 		analysisConfig.unused_label_check == Check.skipTests && !ut);
439 
440 	if (moduleName.shouldRun!"unused_variable_check"(analysisConfig))
441 		checks ~= new UnusedVariableCheck(fileName, moduleScope,
442 		analysisConfig.unused_variable_check == Check.skipTests && !ut);
443 
444 	if (moduleName.shouldRun!"long_line_check"(analysisConfig))
445 		checks ~= new LineLengthCheck(fileName, tokens,
446 		analysisConfig.long_line_check == Check.skipTests && !ut);
447 
448 	if (moduleName.shouldRun!"auto_ref_assignment_check"(analysisConfig))
449 		checks ~= new AutoRefAssignmentCheck(fileName,
450 		analysisConfig.auto_ref_assignment_check == Check.skipTests && !ut);
451 
452 	if (moduleName.shouldRun!"incorrect_infinite_range_check"(analysisConfig))
453 		checks ~= new IncorrectInfiniteRangeCheck(fileName,
454 		analysisConfig.incorrect_infinite_range_check == Check.skipTests && !ut);
455 
456 	if (moduleName.shouldRun!"useless_assert_check"(analysisConfig))
457 		checks ~= new UselessAssertCheck(fileName,
458 		analysisConfig.useless_assert_check == Check.skipTests && !ut);
459 
460 	if (moduleName.shouldRun!"alias_syntax_check"(analysisConfig))
461 		checks ~= new AliasSyntaxCheck(fileName,
462 		analysisConfig.alias_syntax_check == Check.skipTests && !ut);
463 
464 	if (moduleName.shouldRun!"static_if_else_check"(analysisConfig))
465 		checks ~= new StaticIfElse(fileName,
466 		analysisConfig.static_if_else_check == Check.skipTests && !ut);
467 
468 	if (moduleName.shouldRun!"lambda_return_check"(analysisConfig))
469 		checks ~= new LambdaReturnCheck(fileName,
470 		analysisConfig.lambda_return_check == Check.skipTests && !ut);
471 
472 	if (moduleName.shouldRun!"auto_function_check"(analysisConfig))
473 		checks ~= new AutoFunctionChecker(fileName,
474 		analysisConfig.auto_function_check == Check.skipTests && !ut);
475 
476 	if (moduleName.shouldRun!"imports_sortedness"(analysisConfig))
477 		checks ~= new ImportSortednessCheck(fileName,
478 		analysisConfig.imports_sortedness == Check.skipTests && !ut);
479 
480 	if (moduleName.shouldRun!"explicitly_annotated_unittests"(analysisConfig))
481 		checks ~= new ExplicitlyAnnotatedUnittestCheck(fileName,
482 		analysisConfig.explicitly_annotated_unittests == Check.skipTests && !ut);
483 
484 	if (moduleName.shouldRun!"properly_documented_public_functions"(analysisConfig))
485 		checks ~= new ProperlyDocumentedPublicFunctions(fileName,
486 		analysisConfig.properly_documented_public_functions == Check.skipTests && !ut);
487 
488 	if (moduleName.shouldRun!"final_attribute_check"(analysisConfig))
489 		checks ~= new FinalAttributeChecker(fileName,
490 		analysisConfig.final_attribute_check == Check.skipTests && !ut);
491 
492 	if (moduleName.shouldRun!"vcall_in_ctor"(analysisConfig))
493 		checks ~= new VcallCtorChecker(fileName,
494 		analysisConfig.vcall_in_ctor == Check.skipTests && !ut);
495 
496 	if (moduleName.shouldRun!"useless_initializer"(analysisConfig))
497 		checks ~= new UselessInitializerChecker(fileName,
498 		analysisConfig.useless_initializer == Check.skipTests && !ut);
499 
500 	if (moduleName.shouldRun!"allman_braces_check"(analysisConfig))
501 		checks ~= new AllManCheck(fileName, tokens,
502 		analysisConfig.allman_braces_check == Check.skipTests && !ut);
503 
504 	if (moduleName.shouldRun!"redundant_attributes_check"(analysisConfig))
505 		checks ~= new RedundantAttributesCheck(fileName, moduleScope,
506 		analysisConfig.redundant_attributes_check == Check.skipTests && !ut);
507 
508 	if (moduleName.shouldRun!"has_public_example"(analysisConfig))
509 		checks ~= new HasPublicExampleCheck(fileName, moduleScope,
510 		analysisConfig.has_public_example == Check.skipTests && !ut);
511 
512 	if (moduleName.shouldRun!"assert_without_msg"(analysisConfig))
513 		checks ~= new AssertWithoutMessageCheck(fileName, moduleScope,
514 		analysisConfig.assert_without_msg == Check.skipTests && !ut);
515 
516 	if (moduleName.shouldRun!"if_constraints_indent"(analysisConfig))
517 		checks ~= new IfConstraintsIndentCheck(fileName, tokens,
518 		analysisConfig.if_constraints_indent == Check.skipTests && !ut);
519 
520 	if (moduleName.shouldRun!"trust_too_much"(analysisConfig))
521 		checks ~= new TrustTooMuchCheck(fileName,
522 		analysisConfig.trust_too_much == Check.skipTests && !ut);
523 
524 	if (moduleName.shouldRun!"redundant_storage_classes"(analysisConfig))
525 		checks ~= new RedundantStorageClassCheck(fileName,
526 		analysisConfig.redundant_storage_classes == Check.skipTests && !ut);
527 
528 	version (none)
529 		if (moduleName.shouldRun!"redundant_if_check"(analysisConfig))
530 			checks ~= new IfStatementCheck(fileName, moduleScope,
531 			analysisConfig.redundant_if_check == Check.skipTests && !ut);
532 
533 	foreach (check; checks)
534 	{
535 		check.visit(m);
536 	}
537 
538 	MessageSet set = new MessageSet;
539 	foreach (check; checks)
540 		foreach (message; check.messages)
541 			set.insert(message);
542 
543 	GC.enable;
544 
545 	return set;
546 }
547