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