|
6 | 6 | import com.jetbrains.qodana.sarif.model.*; |
7 | 7 | import com.jfrog.ide.common.nodes.FileIssueNode; |
8 | 8 | import com.jfrog.ide.common.nodes.FileTreeNode; |
| 9 | +import com.jfrog.ide.common.nodes.SastIssueNode; |
9 | 10 | import com.jfrog.ide.common.nodes.ScaIssueNode; |
| 11 | +import com.jfrog.ide.common.nodes.subentities.FindingInfo; |
10 | 12 | import com.jfrog.ide.common.nodes.subentities.ImpactPath; |
11 | 13 | import com.jfrog.ide.common.nodes.subentities.Severity; |
12 | 14 | import com.jfrog.ide.common.nodes.subentities.SourceCodeScanType; |
|
17 | 19 | import java.io.*; |
18 | 20 | import java.util.*; |
19 | 21 |
|
| 22 | +/** |
| 23 | + * SarifParser is responsible for parsing SARIF reports and converting them into a list of FileTreeNode objects. |
| 24 | + */ |
20 | 25 | public class SarifParser { |
21 | 26 | private final Log log; |
22 | 27 |
|
| 28 | + /** |
| 29 | + * Constructor for SarifParser. |
| 30 | + * |
| 31 | + * @param log the logger to be used for logging errors and information. |
| 32 | + */ |
23 | 33 | SarifParser(Log log) { |
24 | 34 | this.log = log; |
25 | 35 | } |
26 | 36 |
|
| 37 | + /** |
| 38 | + * Parses the given SARIF report output and returns a list of FileTreeNode objects. |
| 39 | + * |
| 40 | + * @param output the SARIF report as a string. |
| 41 | + * @return a list of FileTreeNode objects representing the parsed findings. |
| 42 | + * @throws NoSuchElementException if no runs are found in the SARIF report. |
| 43 | + */ |
27 | 44 | List<FileTreeNode> parse (String output) { |
28 | 45 | Reader reader = new StringReader(output); |
29 | | - |
30 | 46 | SarifReport report = SarifUtil.readReport(reader); |
31 | | - // extract SCA run object from SARIF |
32 | | - |
33 | 47 | List<Run> runs = report.getRuns(); |
34 | 48 | if (runs.isEmpty()) { |
35 | 49 | log.error("No runs found in SARIF report"); |
36 | 50 | throw new NoSuchElementException("No runs found in the scan SARIF report"); |
37 | 51 | } |
38 | | - |
39 | 52 | return new ArrayList<>(parseScanFindings(runs)); |
40 | 53 | } |
41 | 54 |
|
42 | | - |
| 55 | + /** |
| 56 | + * Parses the scan findings from the given list of runs. |
| 57 | + * |
| 58 | + * @param runs the list of runs from the SARIF report. |
| 59 | + * @return a list of FileTreeNode objects representing the parsed findings. |
| 60 | + */ |
43 | 61 | private List<FileTreeNode> parseScanFindings(List<Run> runs){ |
44 | | - // a method for parsing SCA findings and build FileTreeNodes and IssueNodes |
45 | 62 | List<FileTreeNode> fileTreeNodes = new ArrayList<>(); |
46 | 63 |
|
47 | 64 | for (Run run : runs) { |
@@ -81,30 +98,79 @@ private List<FileTreeNode> parseScanFindings(List<Run> runs){ |
81 | 98 | return fileTreeNodes; |
82 | 99 | } |
83 | 100 |
|
84 | | - |
| 101 | + /** |
| 102 | + * Generates a ScaIssueNode from the given rule and result. |
| 103 | + * |
| 104 | + * @param rule the reporting descriptor rule. |
| 105 | + * @param result the result from the SARIF report. |
| 106 | + * @return a ScaIssueNode representing the issue. |
| 107 | + */ |
85 | 108 | private FileIssueNode generateScaFileIssueNode(ReportingDescriptor rule, Result result){ |
86 | 109 | Applicability applicability = Applicability.fromSarif(Objects.requireNonNull(result.getProperties()).get("applicability").toString()); |
87 | 110 | String fixedVersions = Objects.requireNonNull(result.getProperties()).get("fixedVersion").toString(); |
88 | 111 | List<List<ImpactPath>> impactPaths = new ObjectMapper().convertValue(Objects.requireNonNull(rule.getProperties()).get("impactPaths"), new TypeReference<>() { |
89 | 112 | }); |
90 | 113 | Severity severity = Severity.fromSarif(result.getLevel().toString()); |
91 | 114 | String fullDescription = rule.getFullDescription().getText(); |
| 115 | + String reason = result.getMessage().getText(); |
92 | 116 |
|
93 | | - return new ScaIssueNode(SourceCodeScanType.SCA.getScannerIssueTitle(), fullDescription, severity, rule.getId(), applicability, impactPaths, fixedVersions); |
| 117 | + return new ScaIssueNode(SourceCodeScanType.SCA.getScannerIssueTitle(), reason, severity, rule.getId(), applicability, impactPaths, fixedVersions, fullDescription); |
94 | 118 | } |
95 | 119 |
|
| 120 | + /** |
| 121 | + * Generates a FileIssueNode for JAS from the given rule, result, reporter, and file path. |
| 122 | + * |
| 123 | + * @param rule the reporting descriptor rule. |
| 124 | + * @param result the result from the SARIF report. |
| 125 | + * @param reporter the source code scan type. |
| 126 | + * @param filePath the file path where the issue was found. |
| 127 | + * @return a FileIssueNode representing the issue. |
| 128 | + */ |
96 | 129 | private FileIssueNode generateJasFileIssueNode(ReportingDescriptor rule, Result result, SourceCodeScanType reporter, String filePath){ |
97 | 130 | Severity severity = Severity.fromSarif(result.getLevel().toString()); |
98 | | - String fullDescription = rule.getFullDescription().getText(); // TODO: is needed? |
| 131 | + String fullDescription = rule.getFullDescription().getText(); |
99 | 132 | int rowStart = result.getLocations().get(0).getPhysicalLocation().getRegion().getStartLine(); |
100 | 133 | int colStart = result.getLocations().get(0).getPhysicalLocation().getRegion().getStartColumn(); |
101 | 134 | int rowEnd = result.getLocations().get(0).getPhysicalLocation().getRegion().getEndLine(); |
102 | 135 | int colEnd = result.getLocations().get(0).getPhysicalLocation().getRegion().getEndColumn(); |
103 | 136 | String lineSnippet = result.getLocations().get(0).getPhysicalLocation().getRegion().getSnippet().getText(); |
104 | 137 | String reason = result.getMessage().getText(); |
| 138 | + if (reporter.equals(SourceCodeScanType.SAST)) { |
| 139 | + FindingInfo[][] codeFlows = convertCodeFlowsToFindingInfo(result.getCodeFlows()); |
| 140 | + return new SastIssueNode(reporter.getScannerIssueTitle(), filePath, rowStart, colStart, rowEnd, colEnd, reason, |
| 141 | + lineSnippet, codeFlows, severity, rule.getId(), fullDescription); |
| 142 | + } |
105 | 143 |
|
106 | 144 | return new FileIssueNode(reporter.getScannerIssueTitle(), filePath, rowStart, colStart, rowEnd, colEnd, reason, |
107 | | - lineSnippet, reporter, severity, rule.getId()); |
| 145 | + lineSnippet, reporter, severity, rule.getId(), fullDescription); |
| 146 | + } |
| 147 | + |
| 148 | + private static FindingInfo[][] convertCodeFlowsToFindingInfo(List<CodeFlow> codeFlows) { |
| 149 | + if (codeFlows == null || codeFlows.isEmpty()) { |
| 150 | + return null; |
| 151 | + } |
| 152 | + List<ThreadFlow> flows = codeFlows.get(0).getThreadFlows(); |
| 153 | + if (flows == null || flows.isEmpty()) { |
| 154 | + return null; |
| 155 | + } |
| 156 | + FindingInfo[][] results = new FindingInfo[flows.size()][]; |
| 157 | + for (int i = 0; i < flows.size(); i++) { |
| 158 | + ThreadFlow flow = flows.get(i); |
| 159 | + List<ThreadFlowLocation> locations = flow.getLocations(); |
| 160 | + results[i] = new FindingInfo[locations.size()]; |
| 161 | + for (int j = 0; j < locations.size(); j++) { |
| 162 | + PhysicalLocation location = locations.get(j).getLocation().getPhysicalLocation(); |
| 163 | + results[i][j] = new FindingInfo( |
| 164 | + location.getArtifactLocation().getUri(), |
| 165 | + location.getRegion().getStartLine(), |
| 166 | + location.getRegion().getStartColumn(), |
| 167 | + location.getRegion().getEndLine(), |
| 168 | + location.getRegion().getEndColumn(), |
| 169 | + location.getRegion().getSnippet().getText() |
| 170 | + ); |
| 171 | + } |
| 172 | + } |
| 173 | + return results; |
108 | 174 | } |
109 | 175 |
|
110 | 176 |
|
|
0 commit comments