Skip to content

Commit 1d589b3

Browse files
authored
Adds per codemod Includes/Excludes (#438)
Codemods now have their own includes/excludes rules and a `supports` method. Includes/Excludes rules are specified by `PathMatcher` patterns that are relative to the repository root. Before running on a given file, codemods will now perform two checks: includes/excludes and `supports`. Codemod specific Includes/excludes check can be overridden by global configuration from the command line (i.e. with `--path-include` flag). `supports` checks the bare-minimum conditions that must be satisfied before the codemod attempts to run. For example, javaparser codemods will check if the file is a java file and sarif based codemods will check if there is any results for the file. Refactored some scattered file checks in codemods into the `supports` method. For example, the `VerbTamperingCodemod` will now check if the file is a `web.xml` file with the `supports` method instead of the `visit`. This has the benefit of avoiding the generation of empty codetf reports.
1 parent 83a55ce commit 1d589b3

File tree

31 files changed

+426
-119
lines changed

31 files changed

+426
-119
lines changed

core-codemods/src/intTest/java/io/codemodder/integration/WebGoat822Test.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,29 @@ final class WebGoat822Test extends GitRepositoryTest {
2626
super("https://github.com/WebGoat/WebGoat", "main", "e75cfbeb110e3d3a2ca3c8fee2754992d89c419d");
2727
}
2828

29+
private static final String testPathIncludes =
30+
"**.java,"
31+
+ "**/*.java,"
32+
+ "pom.xml,"
33+
+ "**/pom.xml,"
34+
+ "**.jsp,"
35+
+ "**/*.jsp,"
36+
+ "web.xml,"
37+
+ "**/web.xml,"
38+
+ ".github/workflows/*.yml,"
39+
+ ".github/workflows/*.yaml";
40+
41+
private static final String testPathExcludes =
42+
"**/test/**,"
43+
+ "**/testFixtures/**,"
44+
+ "**/*Test.java,"
45+
+ "**/intTest/**,"
46+
+ "**/tests/**,"
47+
+ "**/target/**,"
48+
+ "**/build/**,"
49+
+ "**/.mvn/**,"
50+
+ ".mvn/**";
51+
2952
@Test
3053
void it_injects_dependency_even_when_no_poms_included() throws Exception {
3154

@@ -83,7 +106,13 @@ void it_injects_dependency_even_when_no_poms_included() throws Exception {
83106
void it_transforms_webgoat_normally() throws Exception {
84107
DefaultCodemods.main(
85108
new String[] {
86-
"--output", outputFile.getPath(), "--verbose", "--dont-exit", repoDir.getPath()
109+
"--output",
110+
outputFile.getPath(),
111+
"--verbose",
112+
"--dont-exit",
113+
repoDir.getPath(),
114+
"--path-include=" + testPathIncludes,
115+
"--path-exclude=" + testPathExcludes,
87116
});
88117

89118
ObjectMapper objectMapper = new ObjectMapper();
@@ -121,6 +150,8 @@ void it_transforms_webgoat_with_codeql() throws Exception {
121150
"--sarif",
122151
"src/test/resources/webgoat_v8.2.2_codeql.sarif",
123152
"--dont-exit",
153+
"--path-include=" + testPathIncludes,
154+
"--path-exclude=" + testPathExcludes,
124155
repoDir.getPath()
125156
});
126157

core-codemods/src/main/java/io/codemodder/codemods/AddMissingI18nCodemod.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,18 @@ private List<Language> putInPreferredOrder(final List<Language> languages) {
5353
return orderedLanguages;
5454
}
5555

56+
@Override
57+
public boolean supports(final Path file) {
58+
return getPropertyFilePrefix(file.getFileName().toString()).isPresent();
59+
}
60+
5661
@Override
5762
public CodemodFileScanningResult visitFile(final CodemodInvocationContext context)
5863
throws IOException {
5964
Path path = context.path();
6065
String fileName = path.getFileName().toString();
66+
// The supports check will guarantee that this won't be empty
6167
Optional<String> prefix = getPropertyFilePrefix(fileName);
62-
if (prefix.isEmpty()) {
63-
// doesn't look like a i18n properties file
64-
return CodemodFileScanningResult.none();
65-
}
6668
return doVisitFile(context, path, prefix.get());
6769
}
6870

core-codemods/src/main/java/io/codemodder/codemods/DefaultCodemods.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public static List<Class<? extends CodeChanger>> asList() {
5757
SimplifyRestControllerAnnotationsCodemod.class,
5858
SubstituteReplaceAllCodemod.class,
5959
SonarXXECodemod.class,
60+
SonarSQLInjectionCodemod.class,
6061
SQLParameterizerCodemod.class,
6162
SSRFCodemod.class,
6263
StackTraceExposureCodemod.class,

core-codemods/src/main/java/io/codemodder/codemods/JSPScriptletXSSCodemod.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package io.codemodder.codemods;
22

33
import io.codemodder.*;
4+
import java.nio.file.Path;
45
import java.util.List;
6+
import java.util.Set;
57
import java.util.regex.Pattern;
68

79
/**
@@ -19,12 +21,12 @@
1921
reviewGuidance = ReviewGuidance.MERGE_WITHOUT_REVIEW)
2022
public final class JSPScriptletXSSCodemod extends RegexFileChanger {
2123

24+
private IncludesExcludesPattern includesExcludesPattern;
25+
2226
public JSPScriptletXSSCodemod() {
23-
super(
24-
path -> path.getFileName().toString().toLowerCase().endsWith(".jsp"),
25-
scriptlet,
26-
true,
27-
List.of(DependencyGAV.OWASP_XSS_JAVA_ENCODER));
27+
super(scriptlet, true, List.of(DependencyGAV.OWASP_XSS_JAVA_ENCODER));
28+
this.includesExcludesPattern =
29+
new IncludesExcludesPattern.Default(Set.of("**.[jJ][sS][pP]"), Set.of());
2830
}
2931

3032
@Override
@@ -34,8 +36,18 @@ public String getReplacementFor(final String matchingSnippet) {
3436
return "<%=org.owasp.encoder.Encode.forHtml(" + codeWithinScriptlet + ")%>";
3537
}
3638

39+
@Override
40+
public boolean supports(final Path file) {
41+
return file.getFileName().toString().toLowerCase().endsWith(".jsp");
42+
}
43+
3744
private static final Pattern scriptlet =
3845
Pattern.compile(
3946
"<%(\\s*)=(\\s*)request(\\s*).(\\s*)get((Header|Parameter)(\\s*)\\((\\s*)\".*\"(\\s*)\\)|QueryString\\((\\s*)\\))(\\s*)%>",
4047
Pattern.MULTILINE);
48+
49+
@Override
50+
public IncludesExcludesPattern getIncludesExcludesPattern() {
51+
return includesExcludesPattern;
52+
}
4153
}

core-codemods/src/main/java/io/codemodder/codemods/MavenSecureURLCodemod.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ private CodemodFileScanningResult processXml(final Path file, final List<Result>
126126
}
127127

128128
/*
129-
* Change contents of the {@code url} tag if it it uses an insecure protocol.
129+
* Change contents of the {@code url} tag if it uses an insecure protocol.
130130
*/
131131
private static void handle(
132132
final XMLEventReader xmlEventReader,

core-codemods/src/main/java/io/codemodder/codemods/SpringAbsoluteCookieTimeoutCodemod.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,16 @@ public SpringAbsoluteCookieTimeoutCodemod(
4646
this.safeDuration = parseExistingValueFromLine(count, units);
4747
}
4848

49+
@Override
50+
public boolean supports(final Path file) {
51+
return "application.properties".equalsIgnoreCase(file.getFileName().toString());
52+
}
53+
4954
@Override
5055
public CodemodFileScanningResult visitFile(final CodemodInvocationContext context)
5156
throws IOException {
5257
Path path = context.path();
53-
if (!"application.properties".equalsIgnoreCase(path.getFileName().toString())) {
54-
return CodemodFileScanningResult.none();
55-
} else if (!inExpectedDir(context.codeDirectory().asPath().relativize(path))) {
58+
if (!inExpectedDir(context.codeDirectory().asPath().relativize(path))) {
5659
return CodemodFileScanningResult.none();
5760
}
5861

core-codemods/src/main/java/io/codemodder/codemods/VerbTamperingCodemod.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ public VerbTamperingCodemod(final XPathStreamProcessor processor) {
3636
public CodemodFileScanningResult visitFile(final CodemodInvocationContext context)
3737
throws IOException {
3838
Path file = context.path();
39-
if (!"web.xml".equalsIgnoreCase(file.getFileName().toString())) {
40-
return CodemodFileScanningResult.none();
41-
}
4239
try {
4340
List<CodemodChange> changes = processWebXml(context, file);
4441
return CodemodFileScanningResult.withOnlyChanges(changes);
@@ -107,4 +104,14 @@ public String getIndividualChangeDescription(final Path filePath, final CodemodC
107104
public List<CodeTFReference> getReferences() {
108105
return reporter.getReferences().stream().map(u -> new CodeTFReference(u, u)).toList();
109106
}
107+
108+
@Override
109+
public IncludesExcludesPattern getIncludesExcludesPattern() {
110+
return new IncludesExcludesPattern.Default(Set.of("{,**/}[wW][eE][bB].[xX][mM][lL]"), Set.of());
111+
}
112+
113+
@Override
114+
public boolean supports(final Path file) {
115+
return "web.xml".equalsIgnoreCase(file.getFileName().toString());
116+
}
110117
}

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

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -344,16 +344,23 @@ public Integer call() throws IOException {
344344

345345
// get path includes/excludes
346346
List<String> pathIncludes = this.pathIncludes;
347+
List<String> pathExcludes = this.pathExcludes;
348+
349+
// If no path includes/excludes were specified, relegate filtering to each codemod
350+
boolean useGlobalIncludesExcludes = pathIncludes != null || pathExcludes != null;
351+
IncludesExcludes includesExcludes = IncludesExcludes.any();
347352
if (pathIncludes == null) {
348-
pathIncludes = defaultPathIncludes;
353+
pathIncludes = List.of("**");
349354
}
350355

351-
List<String> pathExcludes = this.pathExcludes;
352356
if (pathExcludes == null) {
353-
pathExcludes = defaultPathExcludes;
357+
pathExcludes = List.of();
358+
}
359+
360+
if (useGlobalIncludesExcludes) {
361+
includesExcludes =
362+
IncludesExcludes.withSettings(projectDirectory, pathIncludes, pathExcludes);
354363
}
355-
IncludesExcludes includesExcludes =
356-
IncludesExcludes.withSettings(projectDirectory, pathIncludes, pathExcludes);
357364

358365
log.debug("including paths: {}", pathIncludes);
359366
log.debug("excluding paths: {}", pathExcludes);
@@ -431,19 +438,35 @@ public Integer call() throws IOException {
431438
FileCache fileCache = FileCache.createDefault(maxFileCacheSize);
432439

433440
for (CodemodIdPair codemod : codemods) {
434-
CodemodExecutor codemodExecutor =
435-
new DefaultCodemodExecutor(
436-
projectPath,
437-
includesExcludes,
438-
codemod,
439-
projectProviders,
440-
codeTFProviders,
441-
fileCache,
442-
javaParserFacade,
443-
encodingDetector,
444-
maxFileSize,
445-
maxFiles,
446-
maxWorkers);
441+
CodemodExecutor codemodExecutor;
442+
if (useGlobalIncludesExcludes) {
443+
codemodExecutor =
444+
new DefaultCodemodExecutor(
445+
projectPath,
446+
includesExcludes,
447+
codemod,
448+
projectProviders,
449+
codeTFProviders,
450+
fileCache,
451+
javaParserFacade,
452+
encodingDetector,
453+
maxFileSize,
454+
maxFiles,
455+
maxWorkers);
456+
} else {
457+
codemodExecutor =
458+
new DefaultCodemodExecutor(
459+
projectPath,
460+
codemod,
461+
projectProviders,
462+
codeTFProviders,
463+
fileCache,
464+
javaParserFacade,
465+
encodingDetector,
466+
maxFileSize,
467+
maxFiles,
468+
maxWorkers);
469+
}
447470

448471
log.info("running codemod: {}", codemod.getId());
449472
CodeTFResult result = codemodExecutor.execute(filePaths);

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,21 @@ public interface CodeChanger {
2222
/** A description of an individual change made by this codemod. */
2323
String getIndividualChangeDescription(final Path filePath, final CodemodChange change);
2424

25+
/**
26+
* A list of paths patterns requested or rejected by the codemod. Those patterns are treated as
27+
* relative to the repository root. These patterns should follow the {@link
28+
* java.nio.file.PathMatcher} specification. These patterns can be overridden by global patterns.
29+
*/
30+
default IncludesExcludesPattern getIncludesExcludesPattern() {
31+
return IncludesExcludesPattern.getAnyMatcher();
32+
}
33+
34+
/**
35+
* A predicate which dictates if the file should be inspected by the codemod. This cannot be
36+
* overridden and should always pass before executing the codemod.
37+
*/
38+
boolean supports(final Path file);
39+
2540
/**
2641
* A lifecycle event that is called before any files are processed. This is a good place to short
2742
* circuit if you don't have the necessary resources (e.g., SARIF).

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.github.javaparser.ast.CompilationUnit;
44
import io.codemodder.codetf.UnfixedFinding;
55
import io.codemodder.javaparser.JavaParserChanger;
6+
import java.nio.file.Path;
67
import java.util.ArrayList;
78
import java.util.Arrays;
89
import java.util.List;
@@ -29,6 +30,17 @@ protected CompositeJavaParserChanger(final JavaParserChanger... changers) {
2930
this.changers = Arrays.asList(Objects.requireNonNull(changers));
3031
}
3132

33+
@Override
34+
public IncludesExcludesPattern getIncludesExcludesPattern() {
35+
// The first changer will dictate which files this composition accepts
36+
return changers.get(0).getIncludesExcludesPattern();
37+
}
38+
39+
@Override
40+
public boolean supports(final Path file) {
41+
return changers.stream().anyMatch(c -> c.supports(file));
42+
}
43+
3244
@Override
3345
public CodemodFileScanningResult visit(
3446
final CodemodInvocationContext context, final CompilationUnit cu) {
@@ -44,9 +56,4 @@ public CodemodFileScanningResult visit(
4456

4557
return CodemodFileScanningResult.from(changes, unfixedFindings);
4658
}
47-
48-
@Override
49-
public boolean shouldRun() {
50-
return changers.stream().anyMatch(CodeChanger::shouldRun);
51-
}
5259
}

0 commit comments

Comments
 (0)