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