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 }