|
| 1 | +/** |
| 2 | + * OWASP Benchmark Project |
| 3 | + * |
| 4 | + * <p>This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For |
| 5 | + * details, please see <a |
| 6 | + * href="https://owasp.org/www-project-benchmark/">https://owasp.org/www-project-benchmark/</a>. |
| 7 | + * |
| 8 | + * <p>The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms |
| 9 | + * of the GNU General Public License as published by the Free Software Foundation, version 2. |
| 10 | + * |
| 11 | + * <p>The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY |
| 12 | + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
| 13 | + * PURPOSE. See the GNU General Public License for more details. |
| 14 | + * |
| 15 | + * @author Dave Wichers |
| 16 | + * @created 2025 |
| 17 | + */ |
| 18 | +package org.owasp.benchmarkutils.score.parsers; |
| 19 | + |
| 20 | +import org.json.JSONArray; |
| 21 | +import org.json.JSONObject; |
| 22 | +import org.owasp.benchmarkutils.score.CweNumber; |
| 23 | +import org.owasp.benchmarkutils.score.ResultFile; |
| 24 | +import org.owasp.benchmarkutils.score.TestCaseResult; |
| 25 | +import org.owasp.benchmarkutils.score.TestSuiteResults; |
| 26 | + |
| 27 | +public class ReSharperReader extends Reader { |
| 28 | + |
| 29 | + @Override |
| 30 | + public boolean canRead(ResultFile resultFile) { |
| 31 | + return resultFile.isJson() |
| 32 | + && resultFile.line(1).contains("schemastore.azurewebsites.net") |
| 33 | + && resultFile.json().has("runs"); |
| 34 | + } |
| 35 | + |
| 36 | + @Override |
| 37 | + public TestSuiteResults parse(ResultFile resultFile) throws Exception { |
| 38 | + TestSuiteResults tr = |
| 39 | + new TestSuiteResults("ReSharper", false, TestSuiteResults.ToolType.SAST); |
| 40 | + |
| 41 | + JSONArray runs = resultFile.json().getJSONArray("runs"); |
| 42 | + JSONArray results = runs.getJSONObject(0).getJSONArray("results"); |
| 43 | + // tr.setToolVersion(resultFile.json().getString("version")); |
| 44 | + |
| 45 | + // results |
| 46 | + for (int i = 0; i < results.length(); i++) { |
| 47 | + TestCaseResult tcr = parseReSharperFindings(results.getJSONObject(i)); |
| 48 | + if (tcr != null) { |
| 49 | + tr.put(tcr); |
| 50 | + } |
| 51 | + } |
| 52 | + return tr; |
| 53 | + } |
| 54 | + |
| 55 | + private TestCaseResult parseReSharperFindings(JSONObject result) { |
| 56 | + try { |
| 57 | + String ruleId = result.getString("ruleId"); // Name of rule |
| 58 | + String level = |
| 59 | + result.getString("level"); // Severity level of finding (note, warning, ...) |
| 60 | + String ruleExplanation = result.getJSONObject("message").getString("text"); |
| 61 | + |
| 62 | + String uri = |
| 63 | + result.getJSONArray("locations") |
| 64 | + .getJSONObject(0) |
| 65 | + .getJSONObject("physicalLocation") |
| 66 | + .getJSONObject("artifactLocation") |
| 67 | + .getString("uri"); |
| 68 | + |
| 69 | + // String className = result.getString("filename"); |
| 70 | + // className = (className.substring(className.lastIndexOf('/') + |
| 71 | + // 1)).split("\\.")[0]; |
| 72 | + if (isTestCaseFile(uri)) { |
| 73 | + TestCaseResult tcr = new TestCaseResult(); |
| 74 | + tcr.setActualResultTestID(TestSuiteResults.getFileNameNoPath(uri)); |
| 75 | + |
| 76 | + // Figure out CWE |
| 77 | + int cweNum = cweLookup(ruleId, level, ruleExplanation, uri); |
| 78 | + if (cweNum != CweNumber.DONTCARE) { |
| 79 | + tcr.setCWE(cweNum); |
| 80 | + return tcr; |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + } catch (Exception ex) { |
| 85 | + ex.printStackTrace(); |
| 86 | + } |
| 87 | + |
| 88 | + return null; |
| 89 | + } |
| 90 | + |
| 91 | + /** |
| 92 | + * This method maps ReSharper rules to their corresponding CWE, or CweNumber.DONTCARE, if we |
| 93 | + * don't care about it. |
| 94 | + */ |
| 95 | + private int cweLookup( |
| 96 | + String ruleid, String severityLevel, String ruleExplanation, String filename) { |
| 97 | + switch (ruleid) { |
| 98 | + case "ArrangeModifiersOrder": // Inconsistent modifiers declaration order |
| 99 | + case "CheckNamespace": // Namespace does not correspond to file location |
| 100 | + case "ClassNeverInstantiated.Global": // Class 'FOO' is never instantiated |
| 101 | + case "CollectionNeverQueried.Local": // Content of collection 'FOO' is only updated but |
| 102 | + // never used |
| 103 | + case "ConvertIfStatementToConditionalTernaryExpression": // Convert into '?:' expression |
| 104 | + case "ConvertIfStatementToNullCoalescingExpression": // Convert into '??' expression |
| 105 | + case "FieldCanBeMadeReadOnly.Local": // Field can be made readonly |
| 106 | + case "ForCanBeConvertedToForeach": // For-loop can be converted into foreach-loop |
| 107 | + case "InconsistentNaming": // Name 'FOO' does not match rule 'Static readonly fields |
| 108 | + // (private)' |
| 109 | + case "JoinDeclarationAndInitializer": // Join declaration and assignment |
| 110 | + case "PossibleIntendedRethrow": // Exception rethrow possibly intended |
| 111 | + case "RedundantNameQualifier": // Qualifier is redundant |
| 112 | + case "RedundantUsingDirective": // Using directive not required and can be safely |
| 113 | + case "RedundantVerbatimStringPrefix": // Redundant verbatim string prefix |
| 114 | + // removed |
| 115 | + case "TooWideLocalVariableScope": // Local variable 'FOO' can be declared in inner scope |
| 116 | + case "UnassignedReadonlyField.Compiler": // Readonly field 'FOO' is never assigned |
| 117 | + case "UnusedMemberInSuper.Global": // Only overrides of method 'FOO' are used |
| 118 | + case "UnusedType.Global": // Class is never used |
| 119 | + case "UseObjectOrCollectionInitializer": // Use object or collection initializer (to |
| 120 | + // improve readability) |
| 121 | + case "UseStringInterpolation": // Use string interpolation expression |
| 122 | + return CweNumber.DONTCARE; |
| 123 | + |
| 124 | + case "AssignNullToNotNullAttribute": // Possible 'null' assignment to non-nullable |
| 125 | + case "ExpressionIsAlwaysNull": // Expression is always null |
| 126 | + case "PossibleNullReferenceException": // Possible 'System.NullReferenceException' |
| 127 | + // entity |
| 128 | + case "ReplaceWithStringIsNullOrEmpty": // Replace with '!String.IsNullOrEmpty' |
| 129 | + return 476; // CWE-476: NULL Pointer Dereference |
| 130 | + case "ConditionIsAlwaysTrueOrFalse": |
| 131 | + { |
| 132 | + if ("Expression is always false".equals(ruleExplanation)) |
| 133 | + return 570; // CWE-570 Expression is Always False |
| 134 | + else if ("Expression is always true".equals(ruleExplanation)) |
| 135 | + return 571; // CWE-571 Expression is Always True |
| 136 | + else { |
| 137 | + System.err.println( |
| 138 | + "WARNING: Unmapped rule explanation of '" |
| 139 | + + ruleExplanation |
| 140 | + + "' for ruleid id: ConditionIsAlwaysTrueOrFalse"); |
| 141 | + } |
| 142 | + } // Intentionally fall thru to EqualExpressionComparison |
| 143 | + case "EqualExpressionComparison": // Similar expressions comparison |
| 144 | + return 571; // // CWE-571 Expression is Always True |
| 145 | + |
| 146 | + case "BadChildStatementIndent": // Line indent is not restored to the previous level |
| 147 | + // around child statement |
| 148 | + case "CSharpWarnings::CS0642": // Possible mistaken empty statement |
| 149 | + case "MisleadingBodyLikeStatement": // Statement can be confused with previous |
| 150 | + // statement's body |
| 151 | + return 483; // CWE-483 Incorrect Block Delimitation |
| 152 | + |
| 153 | + case "CSharpWarnings::CS0162": // Code is unreachable |
| 154 | + case "EmptyForStatement": // Empty 'for' loop is redundant |
| 155 | + case "EmptyStatement": // Empty statement is redundant |
| 156 | + case "HeuristicUnreachableCode": // Case/Code is heuristically unreachable |
| 157 | + case "MathAbsMethodIsRedundant": // Math.Abs() argument is always non-negative |
| 158 | + case "RedundantBoolCompare": // Comparison with true is redundant |
| 159 | + case "RedundantCast": // Type cast is redundant |
| 160 | + case "RedundantDefaultMemberInitializer": // Initializing field by default value is |
| 161 | + case "RedundantJumpStatement": // Redundant control flow jump statement |
| 162 | + case "RedundantStringFormatCall": // Redundant 'String.Format()' call |
| 163 | + // redundant |
| 164 | + case "UnusedField.Compiler": // Field 'FOO' is never used |
| 165 | + case "UnusedMember.Global": // Constant/Field/Method 'FOO' is never used |
| 166 | + case "UnusedMember.Local": // Constant/Field/Method 'FOO' is never used |
| 167 | + case "UselessBinaryOperation": // Addition or subtraction of 0 in every execution path, |
| 168 | + // which is useless |
| 169 | + return 561; // CWE-561 Dead Code |
| 170 | + |
| 171 | + case "CSharpWarnings::CS0618": // 'CS0618: Constructor |
| 172 | + // 'System.Net.Sockets.TcpListener.TcpListener(int)' is |
| 173 | + // obsolete: 'This method has been deprecated. Please use |
| 174 | + // TcpListener(IPAddress localaddr, int port) instead. |
| 175 | + // http://go.microsoft.com/fwlink/?linkid=14202' |
| 176 | + return 477; // CWE-477 Use of Obsolete Function |
| 177 | + case "CSharpWarnings::CS0665": // Assignment in conditional expression; did you mean to |
| 178 | + // use '==' instead of '='? |
| 179 | + return 481; // CWE-481 Assigning instead of Comparing |
| 180 | + |
| 181 | + case "CSharpWarnings::CS1717": // Assignment made to same variable; did you mean to |
| 182 | + // assign something else? |
| 183 | + return 481; // CWE-481 Assigning instead of Comparing |
| 184 | + |
| 185 | + case "FormatStringProblem": // Formatting is specified, but the argument is not |
| 186 | + // 'IFormattable' |
| 187 | + return 440; // CWE-440 Expected Behavior Violation |
| 188 | + case "FunctionNeverReturns": // Function never returns |
| 189 | + return 770; // CWE-770 Allocation of Resources Without Limits or Throttling |
| 190 | + case "FunctionRecursiveOnAllPaths": // Method is recursive on all execution paths |
| 191 | + return 674; // CWE-674 Uncontrolled Recursion |
| 192 | + case "InconsistentOrderOfLocks": // The expression is used in several lock statements |
| 193 | + // with inconsistent execution order, forming a cycle |
| 194 | + return 833; // CWE-833 Deadlock |
| 195 | + case "IntDivisionByZero": // Division by zero in at least one execution path |
| 196 | + return 369; // CWE-369 Divide by Zero |
| 197 | + case "IntVariableOverflowInUncheckedContext": // Possible overflow in unchecked context |
| 198 | + return 190; // CWE-190 Integer Overflow or Wraparound |
| 199 | + |
| 200 | + case "MemberCanBePrivate.Global": // Method 'FOO' can be made private |
| 201 | + case "MemberCanBeProtected.Global": // Method 'FOO' can be made protected |
| 202 | + return 668; // CWE-668 Exposure of Resource to Wrong Sphere |
| 203 | + |
| 204 | + case "NotAccessedField.Compiler": // Field 'FOO' is assigned but its value is never used |
| 205 | + case "NotAccessedVariable": // Local variable 'data' is only assigned but its value is |
| 206 | + // never used |
| 207 | + case "NotAccessedVariable.Compiler": // Local variable 'FOO' is assigned but its value |
| 208 | + // is never used |
| 209 | + case "RedundantAssignment": // Value assigned is not used in any execution path |
| 210 | + case "RedundantToStringCall": // Redundant 'Object.ToString()' call |
| 211 | + case "StructuredMessageTemplateProblem": // Argument is not used in message template |
| 212 | + case "UnusedParameter.Global": // Parameter 'FOO' is never used |
| 213 | + case "UnusedParameter.Local": // Parameter 'FOO' is never used |
| 214 | + case "UnusedVariable": // Local variable 'FOO' is never used |
| 215 | + case "UnusedVariable.Compiler": // Local variable 'FOO' is never used |
| 216 | + return 563; // CWE-563 Assignment to Variable without Use |
| 217 | + |
| 218 | + case "ReturnValueOfPureMethodIsNotUsed": // Return value of pure method is not used |
| 219 | + return 252; // CWE-252 Unchecked Return Value |
| 220 | + case "SuspiciousTypeConversion.Global": // Suspicious comparison: there is no type in |
| 221 | + // the solution which is inherited from both |
| 222 | + // 'System.Net.IPHostEntry' and 'string' |
| 223 | + return 510; // CWE-510 Trapdoor |
| 224 | + default: |
| 225 | + System.err.println( |
| 226 | + "WARNING: no CWE value provided for ruleid id: '" |
| 227 | + + ruleid |
| 228 | + + "' with rule explanation: '" |
| 229 | + + ruleExplanation |
| 230 | + + "' and severity: '" |
| 231 | + + severityLevel |
| 232 | + + "' for file: " |
| 233 | + + TestSuiteResults.getFileNameNoPath(filename)); |
| 234 | + } |
| 235 | + return CweNumber.UNMAPPED; |
| 236 | + } |
| 237 | +} |
0 commit comments