Skip to content

Commit f4c1393

Browse files
authored
Merge pull request #29 from blackducksoftware/task/romeara/28-failed-file-handling
GH-28 Update handling of files which failed to parse
2 parents ffb611a + ebe9826 commit f4c1393

File tree

7 files changed

+369
-5
lines changed

7 files changed

+369
-5
lines changed

CHANGE_LOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
66

77
## [Unreleased]
88
### Changed
9+
- (GH-28) Record files which failed to parse in a report, instead of failing the entire analysis
910
- Updated `com.google.guava:guava` from `31.0-jre` to `31.1-jre`
1011
- Updated `org.ow2.asm:asm` from `9.2` to `9.3`
1112
- Updated `org.slf4j:slf4j-api` from `1.7.32` to `1.7.36`

docs/REPORT_FORMAT.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ The "method uses" report(s) are within the `referenced-method-uses` directory of
5454
- The qualified class and method name the reference is located in
5555
- methodUses[].uses[].lineNumber
5656
- Optional - the line number the reference is made on within the source file
57+
58+
## Broken Files Report
59+
60+
The "broken files" report(s) are within the "broken-files" directory of the expanded report file. Each of these files is a JSON file, containing several elements:
61+
62+
- brokenFiles[]
63+
- Array of all broken files described by this report
64+
- brokenFiles[].path
65+
- Path of the file which failed to parse
66+
- brokenFiles[].error
67+
- Optional. Short text string describing the error which prevented parsing
5768

5869
## ID Generation
5970

method-analyzer-core/src/main/java/com/synopsys/method/analyzer/core/MethodUseAnalyzer.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
import java.net.InetAddress;
2828
import java.nio.file.Files;
2929
import java.nio.file.Path;
30+
import java.util.HashMap;
3031
import java.util.List;
32+
import java.util.Map;
3133
import java.util.Objects;
3234
import java.util.stream.Collectors;
3335
import java.util.stream.Stream;
@@ -39,6 +41,7 @@
3941
import org.slf4j.LoggerFactory;
4042

4143
import com.google.common.base.Preconditions;
44+
import com.google.common.base.Strings;
4245
import com.google.common.collect.Multimap;
4346
import com.synopsys.method.analyzer.core.bytecode.ClassMethodReferenceVisitor;
4447
import com.synopsys.method.analyzer.core.model.MethodUse;
@@ -112,6 +115,7 @@ public Path analyze(Path sourceDirectory, Path outputDirectory, String outputFil
112115
Preconditions.checkArgument(Files.isDirectory(sourceDirectory), "The source path provided (%s) is not a directory", sourceDirectory.toString());
113116

114117
Multimap<ReferencedMethod, MethodUse> references = null;
118+
Map<Path, String> brokenFiles = new HashMap<>();
115119
ReportGenerator reportGenerator = new ReportGenerator(InetAddress.getLocalHost().getHostName(), sourceDirectory.toString(), codeLocationName);
116120

117121
try (Stream<Path> files = Files.walk(sourceDirectory)) {
@@ -126,6 +130,14 @@ public Path analyze(Path sourceDirectory, Path outputDirectory, String outputFil
126130
try (InputStream inputStream = Files.newInputStream(classFile)) {
127131
ClassReader reader = new ClassReader(inputStream);
128132
reader.accept(bytecodeAnalyzer, 0);
133+
} catch (IllegalArgumentException e) {
134+
// IDETECT-3275: Instead of killing an entire analysis because of a single broken file, record the
135+
// broken file and move on
136+
if (Strings.nullToEmpty(e.getMessage()).startsWith("Unsupported class file major version")) {
137+
brokenFiles.put(classFile, Strings.nullToEmpty(e.getMessage()));
138+
} else {
139+
throw e;
140+
}
129141
}
130142
}
131143

@@ -136,7 +148,7 @@ public Path analyze(Path sourceDirectory, Path outputDirectory, String outputFil
136148
.forEach(entry -> logger.debug("Found {} references to {}.{}({})",
137149
entry.getValue().size(), entry.getKey().getMethodOwner(), entry.getKey().getMethodName(), entry.getKey().getInputs()));
138150

139-
return reportGenerator.generateReport(references, outputDirectory, outputFileName);
151+
return reportGenerator.generateReport(references, brokenFiles, outputDirectory, outputFileName);
140152
}
141153

142154
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* method-use-analyzer
3+
*
4+
* Copyright (C) 2022 Synopsys Inc.
5+
*
6+
* Licensed to the Apache Software Foundation (ASF) under one
7+
* or more contributor license agreements. See the NOTICE file
8+
* distributed with this work for additional information
9+
* regarding copyright ownership. The ASF licenses this file
10+
* to you under the Apache License, Version 2.0 (the
11+
* "License"); you may not use this file except in compliance
12+
* with the License. You may obtain a copy of the License at
13+
*
14+
* http://www.apache.org/licenses/LICENSE-2.0
15+
*
16+
* Unless required by applicable law or agreed to in writing,
17+
* software distributed under the License is distributed on an
18+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19+
* KIND, either express or implied. See the License for the
20+
* specific language governing permissions and limitations
21+
* under the License.
22+
*/
23+
package com.synopsys.method.analyzer.core.report;
24+
25+
import java.util.Objects;
26+
27+
import javax.annotation.Nullable;
28+
29+
import com.google.common.base.MoreObjects;
30+
31+
/**
32+
* Represents JSON data for a file which failed to parse within an analyzed project
33+
*
34+
* <p>
35+
* Fields within correspond to a defined file format - alterations require evaluation for backwards compatibility and
36+
* required version control
37+
*
38+
* @author romeara
39+
*/
40+
public class BrokenFileJson {
41+
42+
private final String path;
43+
44+
private final String error;
45+
46+
public BrokenFileJson(String path, @Nullable String error) {
47+
this.path = Objects.requireNonNull(path);
48+
this.error = error;
49+
}
50+
51+
public String getPath() {
52+
return path;
53+
}
54+
55+
@Nullable
56+
public String getError() {
57+
return error;
58+
}
59+
60+
@Override
61+
public int hashCode() {
62+
return Objects.hash(getPath(),
63+
getError());
64+
}
65+
66+
@Override
67+
public boolean equals(@Nullable Object obj) {
68+
boolean result = false;
69+
70+
if (obj instanceof BrokenFileJson) {
71+
BrokenFileJson compare = (BrokenFileJson) obj;
72+
73+
result = Objects.equals(compare.getPath(), getPath())
74+
&& Objects.equals(compare.getError(), getError());
75+
}
76+
77+
return result;
78+
}
79+
80+
@Override
81+
public String toString() {
82+
return MoreObjects.toStringHelper(getClass()).omitNullValues()
83+
.add("path", getPath())
84+
.add("error", getError())
85+
.toString();
86+
}
87+
88+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* method-use-analyzer
3+
*
4+
* Copyright (C) 2022 Synopsys Inc.
5+
*
6+
* Licensed to the Apache Software Foundation (ASF) under one
7+
* or more contributor license agreements. See the NOTICE file
8+
* distributed with this work for additional information
9+
* regarding copyright ownership. The ASF licenses this file
10+
* to you under the Apache License, Version 2.0 (the
11+
* "License"); you may not use this file except in compliance
12+
* with the License. You may obtain a copy of the License at
13+
*
14+
* http://www.apache.org/licenses/LICENSE-2.0
15+
*
16+
* Unless required by applicable law or agreed to in writing,
17+
* software distributed under the License is distributed on an
18+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19+
* KIND, either express or implied. See the License for the
20+
* specific language governing permissions and limitations
21+
* under the License.
22+
*/
23+
package com.synopsys.method.analyzer.core.report;
24+
25+
import java.util.List;
26+
import java.util.Objects;
27+
28+
import javax.annotation.Nullable;
29+
30+
import com.google.common.base.MoreObjects;
31+
32+
/**
33+
* Represents full JSON contents for a report on broken files which failed to parse within an analyzed project
34+
*
35+
* <p>
36+
* Fields within correspond to a defined file format - alterations require evaluation for backwards compatibility and
37+
* required version control
38+
*
39+
* @author romeara
40+
*/
41+
public final class BrokenFilesReportJson {
42+
43+
private final List<BrokenFileJson> brokenFiles;
44+
45+
public BrokenFilesReportJson(List<BrokenFileJson> brokenFiles) {
46+
this.brokenFiles = Objects.requireNonNull(brokenFiles);
47+
}
48+
49+
public List<BrokenFileJson> getBrokenFiles() {
50+
return brokenFiles;
51+
}
52+
53+
@Override
54+
public int hashCode() {
55+
return Objects.hash(getBrokenFiles());
56+
}
57+
58+
@Override
59+
public boolean equals(@Nullable Object obj) {
60+
boolean result = false;
61+
62+
if (obj instanceof BrokenFilesReportJson) {
63+
BrokenFilesReportJson compare = (BrokenFilesReportJson) obj;
64+
65+
result = Objects.equals(compare.getBrokenFiles(), getBrokenFiles());
66+
}
67+
68+
return result;
69+
}
70+
71+
@Override
72+
public String toString() {
73+
return MoreObjects.toStringHelper(getClass()).omitNullValues()
74+
.add("brokenFiles", getBrokenFiles())
75+
.toString();
76+
}
77+
78+
}

method-analyzer-core/src/main/java/com/synopsys/method/analyzer/core/report/ReportGenerator.java

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@
3131
import java.nio.file.Path;
3232
import java.nio.file.Paths;
3333
import java.util.Collection;
34+
import java.util.Collections;
3435
import java.util.Comparator;
3536
import java.util.HashMap;
3637
import java.util.LinkedList;
3738
import java.util.List;
3839
import java.util.Map;
3940
import java.util.Map.Entry;
4041
import java.util.Objects;
42+
import java.util.stream.Collectors;
4143
import java.util.stream.Stream;
4244
import java.util.zip.ZipEntry;
4345
import java.util.zip.ZipOutputStream;
@@ -67,12 +69,16 @@ public class ReportGenerator {
6769

6870
private static final String REFERENCE_METHOD_USE_LABEL = "referenced-method-uses";
6971

72+
private static final String BROKEN_FILE_LABEL = "broken-files";
73+
7074
// TODO romeara - In the future, should we investigate dynamic call-through to make this an actual handshake, and
7175
// not a manual constant? Aka ask the service what is allowed?
7276
// This is determined in conjunction with what the KB cloud services will allow - DO NOT adjust this number without
7377
// consultation with the maximum allowed by that service
7478
private static final int REFERENCE_MAX_CHUNK_SIZE = 1000;
7579

80+
private static final int BROKEN_FILE_MAX_CHUNK_SIZE = 1000;
81+
7682
private static final Gson GSON = new Gson();
7783

7884
/** Logger reference to output information to the application log files */
@@ -104,9 +110,33 @@ public ReportGenerator(String hostName, String analyzedDirectory, @Nullable Stri
104110
* @return The path of the generated report on the file system
105111
* @throws IOException
106112
* If there is an error writing the report to the file system
113+
* @deprecated Use {@link #generateReport(Multimap, Map, Path, String)} instead
107114
*/
115+
@Deprecated
108116
public Path generateReport(Multimap<ReferencedMethod, MethodUse> references, Path outputDirectory, String outputFileName) throws IOException {
117+
return generateReport(references, Collections.emptyMap(), outputDirectory, outputFileName);
118+
}
119+
120+
/**
121+
* Generates a report of the provided method references within a project
122+
*
123+
* @param references
124+
* The references to describe in a report
125+
* @param brokenFiles
126+
* Paths of any files for which analysis was attempted, by failed due to parser incompatibly of broken
127+
* file formatting (mapped to a message indicating the specific error)
128+
* @param outputDirectory
129+
* The directory to output the report to
130+
* @param outputFileName
131+
* The file name to use for the report (without extension)
132+
* @return The path of the generated report on the file system
133+
* @throws IOException
134+
* If there is an error writing the report to the file system
135+
*/
136+
public Path generateReport(Multimap<ReferencedMethod, MethodUse> references, Map<Path, String> brokenFiles, Path outputDirectory, String outputFileName)
137+
throws IOException {
109138
Objects.requireNonNull(references);
139+
Objects.requireNonNull(brokenFiles);
110140
Objects.requireNonNull(outputDirectory);
111141
Objects.requireNonNull(outputFileName);
112142

@@ -125,7 +155,11 @@ public Path generateReport(Multimap<ReferencedMethod, MethodUse> references, Pat
125155
methodUses.add(new ReferencedMethodUsesJson(id.getSignature(), entry.getKey(), entry.getValue()));
126156
}
127157

128-
return writeReport(destinationFile, uniqueMethodKeys, methodUses);
158+
List<BrokenFileJson> brokenFileRecords = brokenFiles.entrySet().stream()
159+
.map(entry -> new BrokenFileJson(entry.getKey().toString(), entry.getValue()))
160+
.collect(Collectors.toList());
161+
162+
return writeReport(destinationFile, uniqueMethodKeys, methodUses, brokenFileRecords);
129163
}
130164

131165
/**
@@ -137,14 +171,18 @@ public Path generateReport(Multimap<ReferencedMethod, MethodUse> references, Pat
137171
* List of unique, opaque keys referencing discovered methods
138172
* @param methodUses
139173
* Detailed description(s) of the method uses discovered
174+
* @param brokenFiles
175+
* Description of files which failed to parse
140176
* @return The path the report was written to
141177
* @throws IOException
142178
* If there is an error writing the report
143179
*/
144-
private Path writeReport(Path destinationFile, List<MethodIdJson> uniqueMethodKeys, List<ReferencedMethodUsesJson> methodUses) throws IOException {
180+
private Path writeReport(Path destinationFile, List<MethodIdJson> uniqueMethodKeys, List<ReferencedMethodUsesJson> methodUses,
181+
List<BrokenFileJson> brokenFiles) throws IOException {
145182
Objects.requireNonNull(destinationFile);
146183
Objects.requireNonNull(uniqueMethodKeys);
147184
Objects.requireNonNull(methodUses);
185+
Objects.requireNonNull(brokenFiles);
148186

149187
Map<Path, Path> outputFileMapping = new HashMap<>();
150188

@@ -185,6 +223,18 @@ private Path writeReport(Path destinationFile, List<MethodIdJson> uniqueMethodKe
185223
}
186224
}
187225

226+
List<List<BrokenFileJson>> brokenFilePartitions = Lists.partition(brokenFiles, BROKEN_FILE_MAX_CHUNK_SIZE);
227+
228+
for (int index = 0; index < brokenFilePartitions.size(); index++) {
229+
List<BrokenFileJson> brokenFilePartition = brokenFilePartitions.get(index);
230+
231+
Path brokenFilesFile = workingDirectory.resolve(BROKEN_FILE_LABEL + "-" + index + ".json");
232+
try (BufferedWriter writer = Files.newBufferedWriter(brokenFilesFile)) {
233+
GSON.toJson(new BrokenFilesReportJson(brokenFilePartition), writer);
234+
outputFileMapping.put(brokenFilesFile, Paths.get(BROKEN_FILE_LABEL).resolve(brokenFilesFile.getFileName()));
235+
}
236+
}
237+
188238
writeZipFile(destinationFile, outputFileMapping);
189239

190240
// GH-22: Explicitly clean up temporary files once use of them is complete

0 commit comments

Comments
 (0)