Skip to content

Commit 49e4d79

Browse files
authored
Add file searching ability to CodeDirectory (#363)
Analyzing binary result from multiple tools shows we'll need this functionality in multiple places, so we're adding it to the generally available `CodeDirectory` type.
1 parent 86438ca commit 49e4d79

File tree

18 files changed

+161
-56
lines changed

18 files changed

+161
-56
lines changed

framework/codemodder-base/src/main/java/io/codemodder/CLI.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,10 +377,11 @@ public Integer call() throws IOException {
377377
}
378378

379379
// create the loader
380+
CodeDirectory codeDirectory = new DefaultCodeDirectory(projectPath);
380381
List<Path> sarifFiles =
381382
sarifs != null ? sarifs.stream().map(Path::of).collect(Collectors.toList()) : List.of();
382383
Map<String, List<RuleSarif>> pathSarifMap =
383-
SarifParser.create().parseIntoMap(sarifFiles, projectPath);
384+
SarifParser.create().parseIntoMap(sarifFiles, codeDirectory);
384385
List<ParameterArgument> codemodParameters =
385386
createFromParameterStrings(this.codemodParameters);
386387
CodemodLoader loader =
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
package io.codemodder;
22

3+
import java.io.IOException;
34
import java.nio.file.Path;
5+
import java.util.Optional;
46

57
/** Holds a code directory (e.g., a repository root). */
68
public interface CodeDirectory {
79

810
/** The filesystem directory path we are running against. */
911
Path asPath();
12+
13+
/**
14+
* Find a file with the given trailing path. This is useful for situations in which you only know
15+
* the last part of the path for a file within the project.
16+
*/
17+
Optional<Path> findFilesWithTrailingPath(final String path) throws IOException;
18+
19+
static CodeDirectory from(final Path projectDir) {
20+
return new DefaultCodeDirectory(projectDir);
21+
}
1022
}

framework/codemodder-base/src/main/java/io/codemodder/DefaultCodeDirectory.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
package io.codemodder;
22

3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.nio.file.FileVisitResult;
36
import java.nio.file.Files;
47
import java.nio.file.Path;
8+
import java.nio.file.SimpleFileVisitor;
9+
import java.nio.file.attribute.BasicFileAttributes;
510
import java.util.Objects;
11+
import java.util.Optional;
12+
import java.util.concurrent.atomic.AtomicReference;
613

714
final class DefaultCodeDirectory implements CodeDirectory {
815

@@ -26,4 +33,31 @@ final class DefaultCodeDirectory implements CodeDirectory {
2633
public Path asPath() {
2734
return repositoryDir;
2835
}
36+
37+
@Override
38+
public Optional<Path> findFilesWithTrailingPath(final String path) throws IOException {
39+
// find the files with the trailing path
40+
AtomicReference<Path> found = new AtomicReference<>();
41+
42+
final String cleanPath =
43+
path.trim()
44+
.replace("\\\\", "\\")
45+
.replace("//", "/")
46+
.replace('\\', File.separatorChar)
47+
.replace('/', File.separatorChar);
48+
49+
Files.walkFileTree(
50+
repositoryDir,
51+
new SimpleFileVisitor<>() {
52+
@Override
53+
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) {
54+
if (file.toString().endsWith(cleanPath)) {
55+
found.set(file);
56+
return FileVisitResult.TERMINATE;
57+
}
58+
return FileVisitResult.CONTINUE;
59+
}
60+
});
61+
return Optional.ofNullable(found.get());
62+
}
2963
}

framework/codemodder-base/src/main/java/io/codemodder/DefaultSarifParser.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ private Optional<Map.Entry<String, RuleSarif>> tryToBuild(
3030
final String toolName,
3131
final String rule,
3232
final SarifSchema210 sarif,
33-
final Path repositoryRoot,
33+
final CodeDirectory codeDirectory,
3434
final List<RuleSarifFactory> factories) {
3535
for (final var factory : factories) {
36-
final var maybeRuleSarif = factory.build(toolName, rule, sarif, repositoryRoot);
36+
final var maybeRuleSarif = factory.build(toolName, rule, sarif, codeDirectory);
3737
if (maybeRuleSarif.isPresent()) {
3838
return Optional.of(Map.entry(toolName, maybeRuleSarif.get()));
3939
}
@@ -64,7 +64,7 @@ private String extractRuleId(final Result result, final Run run) {
6464
}
6565

6666
private Stream<Map.Entry<String, RuleSarif>> fromSarif(
67-
final Run run, final SarifSchema210 sarif, final Path repositoryRoot) {
67+
final Run run, final SarifSchema210 sarif, final CodeDirectory codeDirectory) {
6868
// driver name
6969
final var toolName = run.getTool().getDriver().getName();
7070
final List<RuleSarifFactory> factories =
@@ -81,20 +81,21 @@ private Stream<Map.Entry<String, RuleSarif>> fromSarif(
8181
: Stream.<String>empty();
8282

8383
return allResults.flatMap(
84-
rule -> tryToBuild(toolName, rule, sarif, repositoryRoot, factories).stream());
84+
rule -> tryToBuild(toolName, rule, sarif, codeDirectory, factories).stream());
8585
}
8686

8787
/**
8888
* Parse a list of SARIF files and organize the obtained {@link RuleSarif}s by tool name with a
8989
* map .
9090
*/
91+
@Override
9192
public Map<String, List<RuleSarif>> parseIntoMap(
92-
final List<Path> sarifFiles, final Path repositoryRoot) {
93+
final List<Path> sarifFiles, final CodeDirectory codeDirectory) {
9394
final var map = new HashMap<String, List<RuleSarif>>();
9495
sarifFiles.stream()
9596
.flatMap(f -> readSarifFile(f).stream())
9697
.flatMap(
97-
sarif -> sarif.getRuns().stream().flatMap(run -> fromSarif(run, sarif, repositoryRoot)))
98+
sarif -> sarif.getRuns().stream().flatMap(run -> fromSarif(run, sarif, codeDirectory)))
9899
.forEach(
99100
p ->
100101
map.merge(
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package io.codemodder;
22

33
import com.contrastsecurity.sarif.SarifSchema210;
4-
import java.nio.file.Path;
54
import java.util.Optional;
65

76
/** Builds {@link RuleSarif}s. */
87
public interface RuleSarifFactory {
98

109
/** Builds {@link RuleSarif}s if it supports {@code toolName}. */
1110
Optional<RuleSarif> build(
12-
String toolName, String rule, SarifSchema210 sarif, Path repositoryRoot);
11+
String toolName, String rule, SarifSchema210 sarif, CodeDirectory codeDirectory);
1312
}

framework/codemodder-base/src/main/java/io/codemodder/SarifParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public interface SarifParser {
1414
* Given a list of sarif {@link Path}s, organize them into a {@link Map} containing {@link
1515
* RuleSarif}s organized by tool name.
1616
*/
17-
Map<String, List<RuleSarif>> parseIntoMap(List<Path> sarifFiles, Path repositoryRoot);
17+
Map<String, List<RuleSarif>> parseIntoMap(List<Path> sarifFiles, CodeDirectory codeDirectory);
1818

1919
static SarifParser create() {
2020
return new DefaultSarifParser();
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package io.codemodder;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.io.IOException;
6+
import java.nio.file.Files;
7+
import java.nio.file.Path;
8+
import java.util.Optional;
9+
import java.util.stream.Stream;
10+
import org.junit.jupiter.api.BeforeEach;
11+
import org.junit.jupiter.api.io.TempDir;
12+
import org.junit.jupiter.params.ParameterizedTest;
13+
import org.junit.jupiter.params.provider.Arguments;
14+
import org.junit.jupiter.params.provider.MethodSource;
15+
16+
final class DefaultCodeDirectoryTest {
17+
18+
private DefaultCodeDirectory codeDirectory;
19+
private Path repoDir;
20+
21+
@BeforeEach
22+
void setup(@TempDir final Path repoDir) throws IOException {
23+
Path testFile1 = repoDir.resolve("my/other/test/file1.java");
24+
Path srcFile1 = repoDir.resolve("src/main/file1.java");
25+
Path srcFile2 = repoDir.resolve("src/main/file2.java");
26+
27+
Files.createDirectories(testFile1.getParent());
28+
Files.createFile(testFile1);
29+
30+
Files.createDirectories(srcFile1.getParent());
31+
Files.createFile(srcFile1);
32+
33+
Files.createDirectories(srcFile2.getParent());
34+
Files.createFile(srcFile2);
35+
36+
Files.writeString(testFile1, "test file 1");
37+
Files.writeString(srcFile1, "src file 1");
38+
Files.writeString(srcFile2, "src file 2");
39+
40+
this.repoDir = repoDir;
41+
codeDirectory = new DefaultCodeDirectory(repoDir);
42+
}
43+
44+
@ParameterizedTest
45+
@MethodSource("fileTests")
46+
void it_finds_files(final String givenPath, final String expectedPath) throws IOException {
47+
Optional<Path> filesWithTrailingPath = codeDirectory.findFilesWithTrailingPath(givenPath);
48+
49+
if (expectedPath == null) {
50+
assertThat(filesWithTrailingPath).isEmpty();
51+
} else {
52+
assertThat(filesWithTrailingPath).isPresent();
53+
Path expected = repoDir.resolve(expectedPath);
54+
assertThat(filesWithTrailingPath.get()).isEqualTo(expected);
55+
}
56+
}
57+
58+
private static Stream<Arguments> fileTests() {
59+
return Stream.of(
60+
Arguments.of("file1.java", "my/other/test/file1.java"),
61+
Arguments.of("main/file1.java", "src/main/file1.java"),
62+
Arguments.of("main//file1.java", "src/main/file1.java"),
63+
Arguments.of("main\\file1.java", "src/main/file1.java"),
64+
Arguments.of("src\\\\main\\file1.java", "src/main/file1.java"),
65+
Arguments.of("file2.java", "src/main/file2.java"),
66+
Arguments.of("file3.java", null));
67+
}
68+
}

framework/codemodder-testutils/src/main/java/io/codemodder/testutils/CodemodTestMixin.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,12 @@ private void verifyCodemod(
115115
Path sonarJson = testResourceDir.resolve("sonar-issues.json");
116116

117117
// Check for any sarif files and build the RuleSarif map
118+
CodeDirectory codeDir = CodeDirectory.from(tmpDir);
118119
List<Path> allSarifs = new ArrayList<>();
119120
Files.newDirectoryStream(testResourceDir, "*.sarif")
120121
.iterator()
121122
.forEachRemaining(allSarifs::add);
122-
Map<String, List<RuleSarif>> map = SarifParser.create().parseIntoMap(allSarifs, tmpDir);
123+
Map<String, List<RuleSarif>> map = SarifParser.create().parseIntoMap(allSarifs, codeDir);
123124

124125
// Check for any a defectdojo
125126
Path defectDojo = testResourceDir.resolve("defectdojo.json");

framework/codemodder-testutils/src/main/java/io/codemodder/testutils/RawFileCodemodTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,9 @@ private void verifyCodemod(
127127
files.filter(file -> file.getFileName().toString().endsWith(".sarif")).toList();
128128
}
129129

130+
final CodeDirectory codeDirectory = CodeDirectory.from(tmpDir);
130131
final Map<String, List<RuleSarif>> map =
131-
SarifParser.create().parseIntoMap(allSarifFiles, tmpDir);
132+
SarifParser.create().parseIntoMap(allSarifFiles, codeDirectory);
132133

133134
// grab all the .before and .after files in the dir
134135
final List<Path> allBeforeFiles;

plugins/codemodder-plugin-appscan/src/main/java/io/codemodder/providers/sarif/appscan/AppScanRuleSarif.java

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,19 @@
11
package io.codemodder.providers.sarif.appscan;
22

33
import com.contrastsecurity.sarif.*;
4+
import io.codemodder.CodeDirectory;
45
import io.codemodder.RuleSarif;
56
import java.io.IOException;
67
import java.io.UncheckedIOException;
7-
import java.nio.file.FileVisitResult;
8-
import java.nio.file.Files;
98
import java.nio.file.Path;
10-
import java.nio.file.SimpleFileVisitor;
11-
import java.nio.file.attribute.BasicFileAttributes;
129
import java.util.*;
13-
import java.util.concurrent.atomic.AtomicReference;
1410

1511
/** A {@link RuleSarif} for AppScan results. */
1612
final class AppScanRuleSarif implements RuleSarif {
1713

1814
private final SarifSchema210 sarif;
1915
private final String ruleId;
2016
private final Map<Path, List<Result>> resultsCache;
21-
private final Path repositoryRoot;
2217
private final List<String> locations;
2318

2419
/** A map of a AppScan SARIF "location" URIs mapped to their respective file paths. */
@@ -28,11 +23,10 @@ final class AppScanRuleSarif implements RuleSarif {
2823
* Creates an {@link AppScanRuleSarif} that has already done the work of mapping AppScan SARIF
2924
* locations, which are strange combinations of class name and file path, into predictable paths.
3025
*/
31-
public AppScanRuleSarif(
32-
final String ruleId, final SarifSchema210 sarif, final Path repositoryRoot) {
26+
AppScanRuleSarif(
27+
final String ruleId, final SarifSchema210 sarif, final CodeDirectory codeDirectory) {
3328
this.sarif = Objects.requireNonNull(sarif);
3429
this.ruleId = Objects.requireNonNull(ruleId);
35-
this.repositoryRoot = repositoryRoot;
3630
this.resultsCache = new HashMap<>();
3731
this.locations =
3832
sarif.getRuns().get(0).getArtifacts().stream()
@@ -49,7 +43,7 @@ public AppScanRuleSarif(
4943
// we have a real but partial path, now we have to find it in the repository
5044
Optional<Path> existingRealPath;
5145
try {
52-
existingRealPath = findFileWithTrailingPath(path);
46+
existingRealPath = codeDirectory.findFilesWithTrailingPath(path);
5347
} catch (IOException e) {
5448
throw new UncheckedIOException(e);
5549
}
@@ -61,24 +55,6 @@ public AppScanRuleSarif(
6155
this.artifactLocationIndices = Map.copyOf(artifactLocationIndicesMap);
6256
}
6357

64-
private Optional<Path> findFileWithTrailingPath(final String path) throws IOException {
65-
// find the files with the trailing path
66-
AtomicReference<Path> found = new AtomicReference<>();
67-
Files.walkFileTree(
68-
repositoryRoot,
69-
new SimpleFileVisitor<>() {
70-
@Override
71-
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) {
72-
if (file.toString().endsWith(path)) {
73-
found.set(file);
74-
return FileVisitResult.TERMINATE;
75-
}
76-
return FileVisitResult.CONTINUE;
77-
}
78-
});
79-
return Optional.ofNullable(found.get());
80-
}
81-
8258
@Override
8359
public List<Region> getRegionsFromResultsByRule(final Path path) {
8460
List<Result> resultsByLocationPath = getResultsByLocationPath(path);

0 commit comments

Comments
 (0)