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 std.d.lexer;
15 import std.d.parser;
16 import std.d.ast;
17 
18 import analysis.config;
19 import analysis.base;
20 import analysis.style;
21 import analysis.enumarrayliteral;
22 import analysis.pokemon;
23 import analysis.del;
24 import analysis.fish;
25 import analysis.numbers;
26 import analysis.objectconst;
27 import analysis.range;
28 import analysis.ifelsesame;
29 import analysis.constructors;
30 import analysis.unused;
31 import analysis.unused_label;
32 import analysis.duplicate_attribute;
33 import analysis.opequals_without_tohash;
34 import analysis.length_subtraction;
35 import analysis.builtin_property_names;
36 import analysis.asm_style;
37 import analysis.logic_precedence;
38 import analysis.stats_collector;
39 import analysis.undocumented;
40 import analysis.comma_expression;
41 import analysis.function_attributes;
42 import analysis.local_imports;
43 import analysis.unmodified;
44 import analysis.if_statements;
45 import analysis.redundant_parens;
46 import analysis.label_var_same_name_check;
47 
48 bool first = true;
49 
50 void messageFunction(string fileName, size_t line, size_t column, string message,
51 	bool isError)
52 {
53 	writefln("%s(%d:%d)[%s]: %s", fileName, line, column,
54 		isError ? "error" : "warn", message);
55 }
56 
57 void messageFunctionJSON(string fileName, size_t line, size_t column, string message, bool)
58 {
59 	writeJSON("dscanner.syntax", fileName, line, column, message);
60 }
61 
62 void writeJSON(string key, string fileName, size_t line, size_t column, string message)
63 {
64 	if (!first)
65 		writeln(",");
66 	else
67 		first = false;
68 	writeln("    {");
69 	writeln(`      "key": "`, key, `",`);
70 	writeln(`      "fileName": "`, fileName, `",`);
71 	writeln(`      "line": `, line, `,`);
72 	writeln(`      "column": `, column, `,`);
73 	writeln(`      "message": "`, message.replace(`"`, `\"`), `"`);
74 	write(  "    }");
75 }
76 
77 bool syntaxCheck(string[] fileNames)
78 {
79 	StaticAnalysisConfig config = defaultStaticAnalysisConfig();
80 	return analyze(fileNames, config, false);
81 }
82 
83 void generateReport(string[] fileNames, const StaticAnalysisConfig config)
84 {
85 	writeln("{");
86 	writeln(`  "issues": [`);
87 	first = true;
88 	StatsCollector stats = new StatsCollector("");
89 	ulong lineOfCodeCount;
90 	foreach (fileName; fileNames)
91 	{
92 		File f = File(fileName);
93 		if (f.size == 0) continue;
94 		auto code = uninitializedArray!(ubyte[])(to!size_t(f.size));
95 		f.rawRead(code);
96 		ParseAllocator p = new ParseAllocator;
97 		StringCache cache = StringCache(StringCache.defaultBucketCount);
98 		const Module m = parseModule(fileName, code, p, cache, true, &lineOfCodeCount);
99 		stats.visit(m);
100 		MessageSet results = analyze(fileName, m, config, true);
101 		foreach (result; results[])
102 		{
103 			writeJSON(result.key, result.fileName, result.line, result.column, result.message);
104 		}
105 	}
106 	writeln();
107 	writeln("  ],");
108 	writefln(`  "interfaceCount": %d,`, stats.interfaceCount);
109 	writefln(`  "classCount": %d,`, stats.classCount);
110 	writefln(`  "functionCount": %d,`, stats.functionCount);
111 	writefln(`  "templateCount": %d,`, stats.templateCount);
112 	writefln(`  "structCount": %d,`, stats.structCount);
113 	writefln(`  "statementCount": %d,`, stats.statementCount);
114 	writefln(`  "lineOfCodeCount": %d,`, lineOfCodeCount);
115 	writefln(`  "undocumentedPublicSymbols": %d`, stats.undocumentedPublicSymbols);
116 	writeln("}");
117 }
118 
119 /**
120  * For multiple files
121  *
122  * Returns: true if there were errors or if there were warnings and `staticAnalyze` was true.
123  */
124 bool analyze(string[] fileNames, const StaticAnalysisConfig config, bool staticAnalyze = true)
125 {
126 	bool hasErrors = false;
127 	foreach (fileName; fileNames)
128 	{
129 		File f = File(fileName);
130 		if (f.size == 0) continue;
131 		auto code = uninitializedArray!(ubyte[])(to!size_t(f.size));
132 		f.rawRead(code);
133 		ParseAllocator p = new ParseAllocator;
134 		StringCache cache = StringCache(StringCache.defaultBucketCount);
135 		uint errorCount = 0;
136 		uint warningCount = 0;
137 		const Module m = parseModule(fileName, code, p, cache, false, null,
138 			&errorCount, &warningCount);
139 		assert (m);
140 		if (errorCount > 0 || (staticAnalyze && warningCount > 0))
141 			hasErrors = true;
142 		MessageSet results = analyze(fileName, m, config, staticAnalyze);
143 		if (results is null)
144 			continue;
145 		foreach (result; results[])
146 			writefln("%s(%d:%d)[warn]: %s", result.fileName, result.line,
147 				result.column, result.message);
148 	}
149 	return hasErrors;
150 }
151 
152 const(Module) parseModule(string fileName, ubyte[] code, ParseAllocator p,
153 	ref StringCache cache, bool report, ulong* linesOfCode = null,
154 	uint* errorCount = null, uint* warningCount = null)
155 {
156 	import stats : isLineOfCode;
157 	LexerConfig config;
158 	config.fileName = fileName;
159 	config.stringBehavior = StringBehavior.source;
160 	const(Token)[] tokens = getTokensForParser(code, config, &cache);
161 	if (linesOfCode !is null)
162 		(*linesOfCode) += count!(a => isLineOfCode(a.type))(tokens);
163 	return std.d.parser.parseModule(tokens, fileName, p,
164 		report ? &messageFunctionJSON : &messageFunction,
165 		errorCount, warningCount);
166 }
167 
168 MessageSet analyze(string fileName, const Module m,
169 	const StaticAnalysisConfig analysisConfig, bool staticAnalyze = true)
170 {
171 	if (!staticAnalyze)
172 		return null;
173 
174 	BaseAnalyzer[] checks;
175 
176 	if (analysisConfig.style_check) checks ~= new StyleChecker(fileName);
177 	if (analysisConfig.enum_array_literal_check) checks ~= new EnumArrayLiteralCheck(fileName);
178 	if (analysisConfig.exception_check) checks ~= new PokemonExceptionCheck(fileName);
179 	if (analysisConfig.delete_check) checks ~= new DeleteCheck(fileName);
180 	if (analysisConfig.float_operator_check) checks ~= new FloatOperatorCheck(fileName);
181 	if (analysisConfig.number_style_check) checks ~= new NumberStyleCheck(fileName);
182 	if (analysisConfig.object_const_check) checks ~= new ObjectConstCheck(fileName);
183 	if (analysisConfig.backwards_range_check) checks ~= new BackwardsRangeCheck(fileName);
184 	if (analysisConfig.if_else_same_check) checks ~= new IfElseSameCheck(fileName);
185 	if (analysisConfig.constructor_check) checks ~= new ConstructorCheck(fileName);
186 	if (analysisConfig.unused_label_check) checks ~= new UnusedLabelCheck(fileName);
187 	if (analysisConfig.unused_variable_check) checks ~= new UnusedVariableCheck(fileName);
188 	if (analysisConfig.duplicate_attribute) checks ~= new DuplicateAttributeCheck(fileName);
189 	if (analysisConfig.opequals_tohash_check) checks ~= new OpEqualsWithoutToHashCheck(fileName);
190 	if (analysisConfig.length_subtraction_check) checks ~= new LengthSubtractionCheck(fileName);
191 	if (analysisConfig.builtin_property_names_check) checks ~= new BuiltinPropertyNameCheck(fileName);
192 	if (analysisConfig.asm_style_check) checks ~= new AsmStyleCheck(fileName);
193 	if (analysisConfig.logical_precedence_check) checks ~= new LogicPrecedenceCheck(fileName);
194 	if (analysisConfig.undocumented_declaration_check) checks ~= new UndocumentedDeclarationCheck(fileName);
195 	if (analysisConfig.function_attribute_check) checks ~= new FunctionAttributeCheck(fileName);
196 	if (analysisConfig.comma_expression_check) checks ~= new CommaExpressionCheck(fileName);
197 	if (analysisConfig.local_import_check) checks ~= new LocalImportCheck(fileName);
198 	if (analysisConfig.could_be_immutable_check) checks ~= new UnmodifiedFinder(fileName);
199 	if (analysisConfig.redundant_parens_check) checks ~= new RedundantParenCheck(fileName);
200 	if (analysisConfig.label_var_same_name_check) checks ~= new LabelVarNameCheck(fileName);
201 	version(none) if (analysisConfig.redundant_if_check) checks ~= new IfStatementCheck(fileName);
202 
203 	foreach (check; checks)
204 	{
205 		check.visit(m);
206 	}
207 
208 	MessageSet set = new MessageSet;
209 	foreach (check; checks)
210 		foreach (message; check.messages)
211 			set.insert(message);
212 	return set;
213 }
214