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