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 }