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.run;
7 
8 import std.stdio;
9 import std.array;
10 import std.conv;
11 import std.algorithm;
12 import std.range;
13 import std.array;
14 import dparse.lexer;
15 import dparse.parser;
16 import dparse.ast;
17 import dparse.rollback_allocator;
18 import std.typecons : scoped;
19 
20 import std.experimental.allocator : CAllocatorImpl;
21 import std.experimental.allocator.mallocator : Mallocator;
22 import std.experimental.allocator.building_blocks.region : Region;
23 import std.experimental.allocator.building_blocks.allocator_list : AllocatorList;
24 
25 import analysis.config;
26 import analysis.base;
27 import analysis.style;
28 import analysis.enumarrayliteral;
29 import analysis.pokemon;
30 import analysis.del;
31 import analysis.fish;
32 import analysis.numbers;
33 import analysis.objectconst;
34 import analysis.range;
35 import analysis.ifelsesame;
36 import analysis.constructors;
37 import analysis.unused;
38 import analysis.unused_label;
39 import analysis.duplicate_attribute;
40 import analysis.opequals_without_tohash;
41 import analysis.length_subtraction;
42 import analysis.builtin_property_names;
43 import analysis.asm_style;
44 import analysis.logic_precedence;
45 import analysis.stats_collector;
46 import analysis.undocumented;
47 import analysis.comma_expression;
48 import analysis.function_attributes;
49 import analysis.local_imports;
50 import analysis.unmodified;
51 import analysis.if_statements;
52 import analysis.redundant_parens;
53 import analysis.mismatched_args;
54 import analysis.label_var_same_name_check;
55 import analysis.line_length;
56 import analysis.auto_ref_assignment;
57 import analysis.incorrect_infinite_range;
58 import analysis.useless_assert;
59 import analysis.alias_syntax_check;
60 import analysis.static_if_else;
61 import analysis.lambda_return_check;
62 import analysis.auto_function;
63 import analysis.imports_sortedness;
64 import analysis.explicitly_annotated_unittests;
65 import analysis.final_attribute;
66 
67 import dsymbol.string_interning : internString;
68 import dsymbol.scope_;
69 import dsymbol.semantic;
70 import dsymbol.conversion;
71 import dsymbol.conversion.first;
72 import dsymbol.conversion.second;
73 import dsymbol.modulecache : ModuleCache;
74 
75 import readers;
76 
77 bool first = true;
78 
79 private alias ASTAllocator = CAllocatorImpl!(
80 		AllocatorList!(n => Region!Mallocator(1024 * 128), Mallocator));
81 
82 void messageFunction(string fileName, size_t line, size_t column, string message, bool isError)
83 {
84 	writefln("%s(%d:%d)[%s]: %s", fileName, line, column, isError ? "error" : "warn", message);
85 }
86 
87 void messageFunctionJSON(string fileName, size_t line, size_t column, string message, bool)
88 {
89 	writeJSON("dscanner.syntax", fileName, line, column, message);
90 }
91 
92 void writeJSON(string key, string fileName, size_t line, size_t column, string message)
93 {
94 	if (!first)
95 		writeln(",");
96 	else
97 		first = false;
98 	writeln("    {");
99 	writeln(`      "key": "`, key, `",`);
100 	writeln(`      "fileName": "`, fileName.replace(`"`, `\"`).replace("\\", "\\\\"), `",`);
101 	writeln(`      "line": `, line, `,`);
102 	writeln(`      "column": `, column, `,`);
103 	writeln(`      "message": "`, message.replace(`"`, `\"`).replace("\\", "\\\\"), `"`);
104 	write("    }");
105 }
106 
107 bool syntaxCheck(string[] fileNames, ref StringCache stringCache, ref ModuleCache moduleCache)
108 {
109 	StaticAnalysisConfig config = defaultStaticAnalysisConfig();
110 	return analyze(fileNames, config, stringCache, moduleCache, false);
111 }
112 
113 void generateReport(string[] fileNames, const StaticAnalysisConfig config,
114 		ref StringCache cache, ref ModuleCache moduleCache)
115 {
116 	writeln("{");
117 	writeln(`  "issues": [`);
118 	first = true;
119 	StatsCollector stats = new StatsCollector("");
120 	ulong lineOfCodeCount;
121 	foreach (fileName; fileNames)
122 	{
123 		auto code = readFile(fileName);
124 		// Skip files that could not be read and continue with the rest
125 		if (code.length == 0)
126 			continue;
127 		RollbackAllocator r;
128 		const(Token)[] tokens;
129 		const Module m = parseModule(fileName, code, &r, cache, true, tokens, &lineOfCodeCount);
130 		stats.visit(m);
131 		MessageSet results = analyze(fileName, m, config, moduleCache, tokens, true);
132 		foreach (result; results[])
133 		{
134 			writeJSON(result.key, result.fileName, result.line, result.column, result.message);
135 		}
136 	}
137 	writeln();
138 	writeln("  ],");
139 	writefln(`  "interfaceCount": %d,`, stats.interfaceCount);
140 	writefln(`  "classCount": %d,`, stats.classCount);
141 	writefln(`  "functionCount": %d,`, stats.functionCount);
142 	writefln(`  "templateCount": %d,`, stats.templateCount);
143 	writefln(`  "structCount": %d,`, stats.structCount);
144 	writefln(`  "statementCount": %d,`, stats.statementCount);
145 	writefln(`  "lineOfCodeCount": %d,`, lineOfCodeCount);
146 	writefln(`  "undocumentedPublicSymbols": %d`, stats.undocumentedPublicSymbols);
147 	writeln("}");
148 }
149 
150 /**
151  * For multiple files
152  *
153  * Returns: true if there were errors or if there were warnings and `staticAnalyze` was true.
154  */
155 bool analyze(string[] fileNames, const StaticAnalysisConfig config,
156 		ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true)
157 {
158 	bool hasErrors = false;
159 	foreach (fileName; fileNames)
160 	{
161 		auto code = readFile(fileName);
162 		// Skip files that could not be read and continue with the rest
163 		if (code.length == 0)
164 			continue;
165 		RollbackAllocator r;
166 		uint errorCount = 0;
167 		uint warningCount = 0;
168 		const(Token)[] tokens;
169 		const Module m = parseModule(fileName, code, &r, cache, false, tokens,
170 				null, &errorCount, &warningCount);
171 		assert(m);
172 		if (errorCount > 0 || (staticAnalyze && warningCount > 0))
173 			hasErrors = true;
174 		MessageSet results = analyze(fileName, m, config, moduleCache, tokens, staticAnalyze);
175 		if (results is null)
176 			continue;
177 		foreach (result; results[])
178 		{
179 			hasErrors = true;
180 			writefln("%s(%d:%d)[warn]: %s", result.fileName, result.line,
181 					result.column, result.message);
182 		}
183 	}
184 	return hasErrors;
185 }
186 
187 const(Module) parseModule(string fileName, ubyte[] code, RollbackAllocator* p,
188 		ref StringCache cache, bool report, ref const(Token)[] tokens,
189 		ulong* linesOfCode = null, uint* errorCount = null, uint* warningCount = null)
190 {
191 	import stats : isLineOfCode;
192 
193 	LexerConfig config;
194 	config.fileName = fileName;
195 	config.stringBehavior = StringBehavior.source;
196 	tokens = getTokensForParser(code, config, &cache);
197 	if (linesOfCode !is null)
198 		(*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens);
199 	return dparse.parser.parseModule(tokens, fileName, p, report
200 			? &messageFunctionJSON : &messageFunction, errorCount, warningCount);
201 }
202 
203 MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig analysisConfig,
204 		ref ModuleCache moduleCache, const(Token)[] tokens, bool staticAnalyze = true)
205 {
206 	import dsymbol.symbol : DSymbol;
207 
208 	if (!staticAnalyze)
209 		return null;
210 
211 	auto symbolAllocator = scoped!ASTAllocator();
212 	version (unittest)
213 		enum ut = true;
214 	else
215 		enum ut = false;
216 
217 
218 	auto first = scoped!FirstPass(m, internString(fileName), symbolAllocator,
219 			symbolAllocator, true, &moduleCache, null);
220 	first.run();
221 
222 	secondPass(first.rootSymbol, first.moduleScope, moduleCache);
223 	auto moduleScope = first.moduleScope;
224 	scope(exit) typeid(DSymbol).destroy(first.rootSymbol.acSymbol);
225 	scope(exit) typeid(SemanticSymbol).destroy(first.rootSymbol);
226 	scope(exit) typeid(Scope).destroy(first.moduleScope);
227 	BaseAnalyzer[] checks;
228 
229 	if (analysisConfig.asm_style_check != Check.disabled)
230 		checks ~= new AsmStyleCheck(fileName, moduleScope,
231 		analysisConfig.asm_style_check == Check.skipTests && !ut);
232 
233 	if (analysisConfig.backwards_range_check != Check.disabled)
234 		checks ~= new BackwardsRangeCheck(fileName, moduleScope,
235 		analysisConfig.backwards_range_check == Check.skipTests && !ut);
236 
237 	if (analysisConfig.builtin_property_names_check != Check.disabled)
238 		checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope,
239 		analysisConfig.builtin_property_names_check == Check.skipTests && !ut);
240 
241 	if (analysisConfig.comma_expression_check != Check.disabled)
242 		checks ~= new CommaExpressionCheck(fileName, moduleScope,
243 		analysisConfig.comma_expression_check == Check.skipTests && !ut);
244 
245 	if (analysisConfig.constructor_check != Check.disabled)
246 		checks ~= new ConstructorCheck(fileName, moduleScope,
247 		analysisConfig.constructor_check == Check.skipTests && !ut);
248 
249 	if (analysisConfig.could_be_immutable_check != Check.disabled)
250 		checks ~= new UnmodifiedFinder(fileName, moduleScope,
251 		analysisConfig.could_be_immutable_check == Check.skipTests && !ut);
252 
253 	if (analysisConfig.delete_check != Check.disabled)
254 		checks ~= new DeleteCheck(fileName, moduleScope,
255 		analysisConfig.delete_check == Check.skipTests && !ut);
256 
257 	if (analysisConfig.duplicate_attribute != Check.disabled)
258 		checks ~= new DuplicateAttributeCheck(fileName, moduleScope,
259 		analysisConfig.duplicate_attribute == Check.skipTests && !ut);
260 
261 	if (analysisConfig.enum_array_literal_check != Check.disabled)
262 		checks ~= new EnumArrayLiteralCheck(fileName, moduleScope,
263 		analysisConfig.enum_array_literal_check == Check.skipTests && !ut);
264 
265 	if (analysisConfig.exception_check != Check.disabled)
266 		checks ~= new PokemonExceptionCheck(fileName, moduleScope,
267 		analysisConfig.exception_check == Check.skipTests && !ut);
268 
269 	if (analysisConfig.float_operator_check != Check.disabled)
270 		checks ~= new FloatOperatorCheck(fileName, moduleScope,
271 		analysisConfig.float_operator_check == Check.skipTests && !ut);
272 
273 	if (analysisConfig.function_attribute_check != Check.disabled)
274 		checks ~= new FunctionAttributeCheck(fileName, moduleScope,
275 		analysisConfig.function_attribute_check == Check.skipTests && !ut);
276 
277 	if (analysisConfig.if_else_same_check != Check.disabled)
278 		checks ~= new IfElseSameCheck(fileName, moduleScope,
279 		analysisConfig.if_else_same_check == Check.skipTests&& !ut);
280 
281 	if (analysisConfig.label_var_same_name_check != Check.disabled)
282 		checks ~= new LabelVarNameCheck(fileName, moduleScope,
283 		analysisConfig.label_var_same_name_check == Check.skipTests && !ut);
284 
285 	if (analysisConfig.length_subtraction_check != Check.disabled)
286 		checks ~= new LengthSubtractionCheck(fileName, moduleScope,
287 		analysisConfig.length_subtraction_check == Check.skipTests && !ut);
288 
289 	if (analysisConfig.local_import_check != Check.disabled)
290 		checks ~= new LocalImportCheck(fileName, moduleScope,
291 		analysisConfig.local_import_check == Check.skipTests && !ut);
292 
293 	if (analysisConfig.logical_precedence_check != Check.disabled)
294 		checks ~= new LogicPrecedenceCheck(fileName, moduleScope,
295 		analysisConfig.logical_precedence_check == Check.skipTests && !ut);
296 
297 	if (analysisConfig.mismatched_args_check != Check.disabled)
298 		checks ~= new MismatchedArgumentCheck(fileName, moduleScope,
299 		analysisConfig.mismatched_args_check == Check.skipTests && !ut);
300 
301 	if (analysisConfig.number_style_check != Check.disabled)
302 		checks ~= new NumberStyleCheck(fileName, moduleScope,
303 		analysisConfig.number_style_check == Check.skipTests && !ut);
304 
305 	if (analysisConfig.object_const_check != Check.disabled)
306 		checks ~= new ObjectConstCheck(fileName, moduleScope,
307 		analysisConfig.object_const_check == Check.skipTests && !ut);
308 
309 	if (analysisConfig.opequals_tohash_check != Check.disabled)
310 		checks ~= new OpEqualsWithoutToHashCheck(fileName, moduleScope,
311 		analysisConfig.opequals_tohash_check == Check.skipTests && !ut);
312 
313 	if (analysisConfig.redundant_parens_check != Check.disabled)
314 		checks ~= new RedundantParenCheck(fileName, moduleScope,
315 		analysisConfig.redundant_parens_check == Check.skipTests && !ut);
316 
317 	if (analysisConfig.style_check != Check.disabled)
318 		checks ~= new StyleChecker(fileName, moduleScope,
319 		analysisConfig.style_check == Check.skipTests && !ut);
320 
321 	if (analysisConfig.undocumented_declaration_check != Check.disabled)
322 		checks ~= new UndocumentedDeclarationCheck(fileName, moduleScope,
323 		analysisConfig.undocumented_declaration_check == Check.skipTests && !ut);
324 
325 	if (analysisConfig.unused_label_check != Check.disabled)
326 		checks ~= new UnusedLabelCheck(fileName, moduleScope,
327 		analysisConfig.unused_label_check == Check.skipTests && !ut);
328 
329 	if (analysisConfig.unused_variable_check != Check.disabled)
330 		checks ~= new UnusedVariableCheck(fileName, moduleScope,
331 		analysisConfig.unused_variable_check == Check.skipTests && !ut);
332 
333 	if (analysisConfig.long_line_check != Check.disabled)
334 		checks ~= new LineLengthCheck(fileName, tokens,
335 		analysisConfig.long_line_check == Check.skipTests && !ut);
336 
337 	if (analysisConfig.auto_ref_assignment_check != Check.disabled)
338 		checks ~= new AutoRefAssignmentCheck(fileName,
339 		analysisConfig.auto_ref_assignment_check == Check.skipTests && !ut);
340 
341 	if (analysisConfig.incorrect_infinite_range_check != Check.disabled)
342 		checks ~= new IncorrectInfiniteRangeCheck(fileName,
343 		analysisConfig.incorrect_infinite_range_check == Check.skipTests && !ut);
344 
345 	if (analysisConfig.useless_assert_check != Check.disabled)
346 		checks ~= new UselessAssertCheck(fileName,
347 		analysisConfig.useless_assert_check == Check.skipTests && !ut);
348 
349 	if (analysisConfig.alias_syntax_check != Check.disabled)
350 		checks ~= new AliasSyntaxCheck(fileName,
351 		analysisConfig.alias_syntax_check == Check.skipTests && !ut);
352 
353 	if (analysisConfig.static_if_else_check != Check.disabled)
354 		checks ~= new StaticIfElse(fileName,
355 		analysisConfig.static_if_else_check == Check.skipTests && !ut);
356 
357 	if (analysisConfig.lambda_return_check != Check.disabled)
358 		checks ~= new LambdaReturnCheck(fileName,
359 		analysisConfig.lambda_return_check == Check.skipTests && !ut);
360 
361 	if (analysisConfig.auto_function_check != Check.disabled)
362 		checks ~= new AutoFunctionChecker(fileName,
363 		analysisConfig.auto_function_check == Check.skipTests && !ut);
364 
365 	if (analysisConfig.imports_sortedness != Check.disabled)
366 		checks ~= new ImportSortednessCheck(fileName,
367 		analysisConfig.imports_sortedness == Check.skipTests && !ut);
368 
369 	if (analysisConfig.explicitly_annotated_unittests != Check.disabled)
370 		checks ~= new ExplicitlyAnnotatedUnittestCheck(fileName,
371 		analysisConfig.explicitly_annotated_unittests == Check.skipTests && !ut);
372 
373 	if (analysisConfig.final_attribute_check != Check.disabled)
374 		checks ~= new FinalAttributeChecker(fileName,
375 		analysisConfig.final_attribute_check == Check.skipTests && !ut);
376 
377 	version (none)
378 		if (analysisConfig.redundant_if_check != Check.disabled)
379 			checks ~= new IfStatementCheck(fileName, moduleScope,
380 			analysisConfig.redundant_if_check == Check.skipTests && !ut);
381 
382 	foreach (check; checks)
383 	{
384 		check.visit(m);
385 	}
386 
387 	MessageSet set = new MessageSet;
388 	foreach (check; checks)
389 		foreach (message; check.messages)
390 			set.insert(message);
391 	return set;
392 }
393