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 std.typecons : scoped;
18 
19 import std.experimental.allocator : CAllocatorImpl;
20 import std.experimental.allocator.mallocator : Mallocator;
21 import std.experimental.allocator.building_blocks.region : Region;
22 import std.experimental.allocator.building_blocks.allocator_list : AllocatorList;
23 
24 import analysis.config;
25 import analysis.base;
26 import analysis.style;
27 import analysis.enumarrayliteral;
28 import analysis.pokemon;
29 import analysis.del;
30 import analysis.fish;
31 import analysis.numbers;
32 import analysis.objectconst;
33 import analysis.range;
34 import analysis.ifelsesame;
35 import analysis.constructors;
36 import analysis.unused;
37 import analysis.unused_label;
38 import analysis.duplicate_attribute;
39 import analysis.opequals_without_tohash;
40 import analysis.length_subtraction;
41 import analysis.builtin_property_names;
42 import analysis.asm_style;
43 import analysis.logic_precedence;
44 import analysis.stats_collector;
45 import analysis.undocumented;
46 import analysis.comma_expression;
47 import analysis.function_attributes;
48 import analysis.local_imports;
49 import analysis.unmodified;
50 import analysis.if_statements;
51 import analysis.redundant_parens;
52 import analysis.mismatched_args;
53 import analysis.label_var_same_name_check;
54 import analysis.line_length;
55 import analysis.auto_ref_assignment;
56 import analysis.incorrect_infinite_range;
57 import analysis.useless_assert;
58 
59 import dsymbol.string_interning : internString;
60 import dsymbol.scope_;
61 import dsymbol.semantic;
62 import dsymbol.conversion;
63 import dsymbol.conversion.first;
64 import dsymbol.conversion.second;
65 import dsymbol.modulecache : ModuleCache;
66 
67 bool first = true;
68 
69 private alias ASTAllocator = CAllocatorImpl!(
70 	AllocatorList!(n => Region!Mallocator(1024 * 128), Mallocator));
71 
72 void messageFunction(string fileName, size_t line, size_t column, string message, bool isError)
73 {
74 	writefln("%s(%d:%d)[%s]: %s", fileName, line, column, isError ? "error" : "warn",
75 		message);
76 }
77 
78 void messageFunctionJSON(string fileName, size_t line, size_t column, string message,
79 	bool)
80 {
81 	writeJSON("dscanner.syntax", fileName, line, column, message);
82 }
83 
84 void writeJSON(string key, string fileName, size_t line, size_t column, string message)
85 {
86 	if (!first)
87 		writeln(",");
88 	else
89 		first = false;
90 	writeln("    {");
91 	writeln(`      "key": "`, key, `",`);
92 	writeln(`      "fileName": "`, fileName, `",`);
93 	writeln(`      "line": `, line, `,`);
94 	writeln(`      "column": `, column, `,`);
95 	writeln(`      "message": "`, message.replace(`"`, `\"`), `"`);
96 	write("    }");
97 }
98 
99 bool syntaxCheck(string[] fileNames, ref StringCache stringCache, ref ModuleCache moduleCache)
100 {
101 	StaticAnalysisConfig config = defaultStaticAnalysisConfig();
102 	return analyze(fileNames, config, stringCache, moduleCache, false);
103 }
104 
105 void generateReport(string[] fileNames, const StaticAnalysisConfig config,
106 	ref StringCache cache, ref ModuleCache moduleCache)
107 {
108 	writeln("{");
109 	writeln(`  "issues": [`);
110 	first = true;
111 	StatsCollector stats = new StatsCollector("");
112 	ulong lineOfCodeCount;
113 	foreach (fileName; fileNames)
114 	{
115 		File f = File(fileName);
116 		if (f.size == 0)
117 			continue;
118 		auto code = uninitializedArray!(ubyte[])(to!size_t(f.size));
119 		f.rawRead(code);
120 		ParseAllocator p = new ParseAllocator;
121 		const(Token)[] tokens;
122 		const Module m = parseModule(fileName, code, p, cache, true, tokens, &lineOfCodeCount);
123 		stats.visit(m);
124 		MessageSet results = analyze(fileName, m, config, moduleCache, tokens, true);
125 		foreach (result; results[])
126 		{
127 			writeJSON(result.key, result.fileName, result.line, result.column, result.message);
128 		}
129 	}
130 	writeln();
131 	writeln("  ],");
132 	writefln(`  "interfaceCount": %d,`, stats.interfaceCount);
133 	writefln(`  "classCount": %d,`, stats.classCount);
134 	writefln(`  "functionCount": %d,`, stats.functionCount);
135 	writefln(`  "templateCount": %d,`, stats.templateCount);
136 	writefln(`  "structCount": %d,`, stats.structCount);
137 	writefln(`  "statementCount": %d,`, stats.statementCount);
138 	writefln(`  "lineOfCodeCount": %d,`, lineOfCodeCount);
139 	writefln(`  "undocumentedPublicSymbols": %d`, stats.undocumentedPublicSymbols);
140 	writeln("}");
141 }
142 
143 /**
144  * For multiple files
145  *
146  * Returns: true if there were errors or if there were warnings and `staticAnalyze` was true.
147  */
148 bool analyze(string[] fileNames, const StaticAnalysisConfig config,
149 	ref StringCache cache, ref ModuleCache moduleCache, bool staticAnalyze = true)
150 {
151 	bool hasErrors = false;
152 	foreach (fileName; fileNames)
153 	{
154 		File f = File(fileName);
155 		if (f.size == 0)
156 			continue;
157 		auto code = uninitializedArray!(ubyte[])(to!size_t(f.size));
158 		f.rawRead(code);
159 		ParseAllocator p = new ParseAllocator;
160 		uint errorCount = 0;
161 		uint warningCount = 0;
162 		const(Token)[] tokens;
163 		const Module m = parseModule(fileName, code, p, cache, false, tokens, null,
164 			&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, ParseAllocator 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,
191 		report ? &messageFunctionJSON : &messageFunction, errorCount, warningCount);
192 }
193 
194 MessageSet analyze(string fileName, const Module m,
195 	const StaticAnalysisConfig analysisConfig, ref ModuleCache moduleCache,
196 	const(Token)[] tokens, bool staticAnalyze = true)
197 {
198 	if (!staticAnalyze)
199 		return null;
200 
201 	auto symbolAllocator = new ASTAllocator;
202 	auto first = scoped!FirstPass(m, internString(fileName), symbolAllocator,
203 		symbolAllocator, true, &moduleCache, null);
204 	first.run();
205 
206 	secondPass(first.rootSymbol, first.moduleScope, moduleCache);
207 	typeid(SemanticSymbol).destroy(first.rootSymbol);
208 	const(Scope)* moduleScope = first.moduleScope;
209 
210 	BaseAnalyzer[] checks;
211 
212 	if (analysisConfig.asm_style_check)
213 		checks ~= new AsmStyleCheck(fileName, moduleScope);
214 	if (analysisConfig.backwards_range_check)
215 		checks ~= new BackwardsRangeCheck(fileName, moduleScope);
216 	if (analysisConfig.builtin_property_names_check)
217 		checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope);
218 	if (analysisConfig.comma_expression_check)
219 		checks ~= new CommaExpressionCheck(fileName, moduleScope);
220 	if (analysisConfig.constructor_check)
221 		checks ~= new ConstructorCheck(fileName, moduleScope);
222 	if (analysisConfig.could_be_immutable_check)
223 		checks ~= new UnmodifiedFinder(fileName, moduleScope);
224 	if (analysisConfig.delete_check)
225 		checks ~= new DeleteCheck(fileName, moduleScope);
226 	if (analysisConfig.duplicate_attribute)
227 		checks ~= new DuplicateAttributeCheck(fileName, moduleScope);
228 	if (analysisConfig.enum_array_literal_check)
229 		checks ~= new EnumArrayLiteralCheck(fileName, moduleScope);
230 	if (analysisConfig.exception_check)
231 		checks ~= new PokemonExceptionCheck(fileName, moduleScope);
232 	if (analysisConfig.float_operator_check)
233 		checks ~= new FloatOperatorCheck(fileName, moduleScope);
234 	if (analysisConfig.function_attribute_check)
235 		checks ~= new FunctionAttributeCheck(fileName, moduleScope);
236 	if (analysisConfig.if_else_same_check)
237 		checks ~= new IfElseSameCheck(fileName, moduleScope);
238 	if (analysisConfig.label_var_same_name_check)
239 		checks ~= new LabelVarNameCheck(fileName, moduleScope);
240 	if (analysisConfig.length_subtraction_check)
241 		checks ~= new LengthSubtractionCheck(fileName, moduleScope);
242 	if (analysisConfig.local_import_check)
243 		checks ~= new LocalImportCheck(fileName, moduleScope);
244 	if (analysisConfig.logical_precedence_check)
245 		checks ~= new LogicPrecedenceCheck(fileName, moduleScope);
246 	if (analysisConfig.mismatched_args_check)
247 		checks ~= new MismatchedArgumentCheck(fileName, moduleScope);
248 	if (analysisConfig.number_style_check)
249 		checks ~= new NumberStyleCheck(fileName, moduleScope);
250 	if (analysisConfig.object_const_check)
251 		checks ~= new ObjectConstCheck(fileName, moduleScope);
252 	if (analysisConfig.opequals_tohash_check)
253 		checks ~= new OpEqualsWithoutToHashCheck(fileName, moduleScope);
254 	if (analysisConfig.redundant_parens_check)
255 		checks ~= new RedundantParenCheck(fileName, moduleScope);
256 	if (analysisConfig.style_check)
257 		checks ~= new StyleChecker(fileName, moduleScope);
258 	if (analysisConfig.undocumented_declaration_check)
259 		checks ~= new UndocumentedDeclarationCheck(fileName, moduleScope);
260 	if (analysisConfig.unused_label_check)
261 		checks ~= new UnusedLabelCheck(fileName, moduleScope);
262 	if (analysisConfig.unused_variable_check)
263 		checks ~= new UnusedVariableCheck(fileName, moduleScope);
264 	if (analysisConfig.long_line_check)
265 		checks ~= new LineLengthCheck(fileName, tokens);
266 	if (analysisConfig.auto_ref_assignment_check)
267 		checks ~= new AutoRefAssignmentCheck(fileName);
268 	if (analysisConfig.incorrect_infinite_range_check)
269 		checks ~= new IncorrectInfiniteRangeCheck(fileName);
270 	if (analysisConfig.useless_assert_check)
271 		checks ~= new UselessAssertCheck(fileName);
272 	version (none)
273 		if (analysisConfig.redundant_if_check)
274 			checks ~= new IfStatementCheck(fileName, moduleScope);
275 
276 	foreach (check; checks)
277 	{
278 		check.visit(m);
279 	}
280 
281 	MessageSet set = new MessageSet;
282 	foreach (check; checks)
283 		foreach (message; check.messages)
284 			set.insert(message);
285 	return set;
286 }