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 
61 import dsymbol.string_interning : internString;
62 import dsymbol.scope_;
63 import dsymbol.semantic;
64 import dsymbol.conversion;
65 import dsymbol.conversion.first;
66 import dsymbol.conversion.second;
67 import dsymbol.modulecache : ModuleCache;
68 
69 import readers;
70 
71 bool first = true;
72 
73 private alias ASTAllocator = CAllocatorImpl!(
74 		AllocatorList!(n => Region!Mallocator(1024 * 128), Mallocator));
75 
76 void messageFunction(string fileName, size_t line, size_t column, string message, bool isError)
77 {
78 	writefln("%s(%d:%d)[%s]: %s", fileName, line, column, isError ? "error" : "warn", message);
79 }
80 
81 void messageFunctionJSON(string fileName, size_t line, size_t column, string message, bool)
82 {
83 	writeJSON("dscanner.syntax", fileName, line, column, message);
84 }
85 
86 void writeJSON(string key, string fileName, size_t line, size_t column, string message)
87 {
88 	if (!first)
89 		writeln(",");
90 	else
91 		first = false;
92 	writeln("    {");
93 	writeln(`      "key": "`, key, `",`);
94 	writeln(`      "fileName": "`, fileName, `",`);
95 	writeln(`      "line": `, line, `,`);
96 	writeln(`      "column": `, column, `,`);
97 	writeln(`      "message": "`, message.replace(`"`, `\"`), `"`);
98 	write("    }");
99 }
100 
101 bool syntaxCheck(string[] fileNames, ref StringCache stringCache, ref ModuleCache moduleCache)
102 {
103 	StaticAnalysisConfig config = defaultStaticAnalysisConfig();
104 	return analyze(fileNames, config, stringCache, moduleCache, false);
105 }
106 
107 void generateReport(string[] fileNames, const StaticAnalysisConfig config,
108 		ref StringCache cache, ref ModuleCache moduleCache)
109 {
110 	writeln("{");
111 	writeln(`  "issues": [`);
112 	first = true;
113 	StatsCollector stats = new StatsCollector("");
114 	ulong lineOfCodeCount;
115 	foreach (fileName; fileNames)
116 	{
117 		auto code = fileName == "stdin" ? readStdin() : readFile(fileName);
118 		// Skip files that could not be read and continue with the rest
119 		if (code.length == 0)
120 			continue;
121 		RollbackAllocator r;
122 		const(Token)[] tokens;
123 		const Module m = parseModule(fileName, code, &r, cache, true, tokens, &lineOfCodeCount);
124 		stats.visit(m);
125 		MessageSet results = analyze(fileName, m, config, moduleCache, tokens, true);
126 		foreach (result; results[])
127 		{
128 			writeJSON(result.key, result.fileName, result.line, result.column, result.message);
129 		}
130 	}
131 	writeln();
132 	writeln("  ],");
133 	writefln(`  "interfaceCount": %d,`, stats.interfaceCount);
134 	writefln(`  "classCount": %d,`, stats.classCount);
135 	writefln(`  "functionCount": %d,`, stats.functionCount);
136 	writefln(`  "templateCount": %d,`, stats.templateCount);
137 	writefln(`  "structCount": %d,`, stats.structCount);
138 	writefln(`  "statementCount": %d,`, stats.statementCount);
139 	writefln(`  "lineOfCodeCount": %d,`, lineOfCodeCount);
140 	writefln(`  "undocumentedPublicSymbols": %d`, stats.undocumentedPublicSymbols);
141 	writeln("}");
142 }
143 
144 /**
145  * For multiple files
146  *
147  * Returns: true if there were errors or if there were warnings and `staticAnalyze` was true.
148  */
149 bool analyze(string[] fileNames, const StaticAnalysisConfig config,
150 		ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true)
151 {
152 	bool hasErrors = false;
153 	foreach (fileName; fileNames)
154 	{
155 		auto code = fileName == "stdin" ? readStdin() : readFile(fileName);
156 		// Skip files that could not be read and continue with the rest
157 		if (code.length == 0)
158 			continue;
159 		RollbackAllocator r;
160 		uint errorCount = 0;
161 		uint warningCount = 0;
162 		const(Token)[] tokens;
163 		const Module m = parseModule(fileName, code, &r, cache, false, tokens,
164 				null, &errorCount, &warningCount);
165 		assert(m);
166 		if (errorCount > 0 || (staticAnalyze && warningCount > 0))
167 			hasErrors = true;
168 		MessageSet results = analyze(fileName, m, config, moduleCache, tokens, staticAnalyze);
169 		if (results is null)
170 			continue;
171 		foreach (result; results[])
172 			writefln("%s(%d:%d)[warn]: %s", result.fileName, result.line,
173 					result.column, result.message);
174 	}
175 	return hasErrors;
176 }
177 
178 const(Module) parseModule(string fileName, ubyte[] code, RollbackAllocator* p,
179 		ref StringCache cache, bool report, ref const(Token)[] tokens,
180 		ulong* linesOfCode = null, uint* errorCount = null, uint* warningCount = null)
181 {
182 	import stats : isLineOfCode;
183 
184 	LexerConfig config;
185 	config.fileName = fileName;
186 	config.stringBehavior = StringBehavior.source;
187 	tokens = getTokensForParser(code, config, &cache);
188 	if (linesOfCode !is null)
189 		(*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens);
190 	return dparse.parser.parseModule(tokens, fileName, p, report
191 			? &messageFunctionJSON : &messageFunction, errorCount, warningCount);
192 }
193 
194 MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig analysisConfig,
195 		ref ModuleCache moduleCache, const(Token)[] tokens, bool staticAnalyze = true)
196 {
197 	if (!staticAnalyze)
198 		return null;
199 
200 	auto symbolAllocator = new ASTAllocator;
201 	auto first = scoped!FirstPass(m, internString(fileName), symbolAllocator,
202 			symbolAllocator, true, &moduleCache, null);
203 	first.run();
204 
205 	secondPass(first.rootSymbol, first.moduleScope, moduleCache);
206 	typeid(SemanticSymbol).destroy(first.rootSymbol);
207 	const(Scope)* moduleScope = first.moduleScope;
208 
209 	BaseAnalyzer[] checks;
210 
211 	if (analysisConfig.asm_style_check)
212 		checks ~= new AsmStyleCheck(fileName, moduleScope);
213 	if (analysisConfig.backwards_range_check)
214 		checks ~= new BackwardsRangeCheck(fileName, moduleScope);
215 	if (analysisConfig.builtin_property_names_check)
216 		checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope);
217 	if (analysisConfig.comma_expression_check)
218 		checks ~= new CommaExpressionCheck(fileName, moduleScope);
219 	if (analysisConfig.constructor_check)
220 		checks ~= new ConstructorCheck(fileName, moduleScope);
221 	if (analysisConfig.could_be_immutable_check)
222 		checks ~= new UnmodifiedFinder(fileName, moduleScope);
223 	if (analysisConfig.delete_check)
224 		checks ~= new DeleteCheck(fileName, moduleScope);
225 	if (analysisConfig.duplicate_attribute)
226 		checks ~= new DuplicateAttributeCheck(fileName, moduleScope);
227 	if (analysisConfig.enum_array_literal_check)
228 		checks ~= new EnumArrayLiteralCheck(fileName, moduleScope);
229 	if (analysisConfig.exception_check)
230 		checks ~= new PokemonExceptionCheck(fileName, moduleScope);
231 	if (analysisConfig.float_operator_check)
232 		checks ~= new FloatOperatorCheck(fileName, moduleScope);
233 	if (analysisConfig.function_attribute_check)
234 		checks ~= new FunctionAttributeCheck(fileName, moduleScope);
235 	if (analysisConfig.if_else_same_check)
236 		checks ~= new IfElseSameCheck(fileName, moduleScope);
237 	if (analysisConfig.label_var_same_name_check)
238 		checks ~= new LabelVarNameCheck(fileName, moduleScope);
239 	if (analysisConfig.length_subtraction_check)
240 		checks ~= new LengthSubtractionCheck(fileName, moduleScope);
241 	if (analysisConfig.local_import_check)
242 		checks ~= new LocalImportCheck(fileName, moduleScope);
243 	if (analysisConfig.logical_precedence_check)
244 		checks ~= new LogicPrecedenceCheck(fileName, moduleScope);
245 	if (analysisConfig.mismatched_args_check)
246 		checks ~= new MismatchedArgumentCheck(fileName, moduleScope);
247 	if (analysisConfig.number_style_check)
248 		checks ~= new NumberStyleCheck(fileName, moduleScope);
249 	if (analysisConfig.object_const_check)
250 		checks ~= new ObjectConstCheck(fileName, moduleScope);
251 	if (analysisConfig.opequals_tohash_check)
252 		checks ~= new OpEqualsWithoutToHashCheck(fileName, moduleScope);
253 	if (analysisConfig.redundant_parens_check)
254 		checks ~= new RedundantParenCheck(fileName, moduleScope);
255 	if (analysisConfig.style_check)
256 		checks ~= new StyleChecker(fileName, moduleScope);
257 	if (analysisConfig.undocumented_declaration_check)
258 		checks ~= new UndocumentedDeclarationCheck(fileName, moduleScope);
259 	if (analysisConfig.unused_label_check)
260 		checks ~= new UnusedLabelCheck(fileName, moduleScope);
261 	if (analysisConfig.unused_variable_check)
262 		checks ~= new UnusedVariableCheck(fileName, moduleScope);
263 	if (analysisConfig.long_line_check)
264 		checks ~= new LineLengthCheck(fileName, tokens);
265 	if (analysisConfig.auto_ref_assignment_check)
266 		checks ~= new AutoRefAssignmentCheck(fileName);
267 	if (analysisConfig.incorrect_infinite_range_check)
268 		checks ~= new IncorrectInfiniteRangeCheck(fileName);
269 	if (analysisConfig.useless_assert_check)
270 		checks ~= new UselessAssertCheck(fileName);
271 	if (analysisConfig.alias_syntax_check)
272 		checks ~= new AliasSyntaxCheck(fileName);
273 	version (none)
274 		if (analysisConfig.redundant_if_check)
275 			checks ~= new IfStatementCheck(fileName, moduleScope);
276 
277 	foreach (check; checks)
278 	{
279 		check.visit(m);
280 	}
281 
282 	MessageSet set = new MessageSet;
283 	foreach (check; checks)
284 		foreach (message; check.messages)
285 			set.insert(message);
286 	return set;
287 }