Skip to content

Commit f067232

Browse files
authored
Added remediators and improved method searching flexibility (#437)
1 parent d2676e7 commit f067232

File tree

40 files changed

+1024
-151
lines changed

40 files changed

+1024
-151
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public CodemodFileScanningResult visit(
5959
detectorRule(),
6060
findingsForThisPath,
6161
finding -> String.valueOf(finding.getId()),
62-
Finding::getLine);
62+
Finding::getLine,
63+
f -> null);
6364
}
6465
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public CodemodFileScanningResult visit(
5050
detectorRule(),
5151
hotspotsForFile,
5252
SonarFinding::getKey,
53-
SonarFinding::getLine);
53+
i -> i.getTextRange() != null ? i.getTextRange().getStartLine() : i.getLine(),
54+
i -> i.getTextRange() != null ? i.getTextRange().getEndLine() : null);
5455
}
5556
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ public CodemodFileScanningResult visit(
4949
detectorRule(),
5050
issues.getResultsByPath(context.path()),
5151
Issue::getKey,
52-
Issue::getLine,
52+
i -> i.getTextRange() != null ? i.getTextRange().getStartLine() : i.getLine(),
53+
i -> i.getTextRange() != null ? i.getTextRange().getEndLine() : null,
5354
i -> i.getTextRange().getStartOffset());
5455
}
5556
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public CodemodFileScanningResult visit(
4949
detectorRule(),
5050
issuesForFile,
5151
SonarFinding::getKey,
52-
SonarFinding::getLine,
52+
f -> f.getTextRange() != null ? f.getTextRange().getStartLine() : f.getLine(),
5353
f -> f.getTextRange().getStartOffset());
5454
}
5555
}

framework/codemodder-base/src/main/java/io/codemodder/javaparser/ChangesResult.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
/** Represents the result of changes made during parsing. */
77
public interface ChangesResult {
8+
9+
/** Returns true if changes were applied. */
810
boolean areChangesApplied();
911

1012
List<DependencyGAV> getDependenciesRequired();

framework/codemodder-base/src/main/java/io/codemodder/remediation/DefaultFixCandidateSearcher.java

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,23 @@
22

33
import com.github.javaparser.Position;
44
import com.github.javaparser.ast.CompilationUnit;
5+
import com.github.javaparser.ast.Node;
56
import com.github.javaparser.ast.expr.MethodCallExpr;
7+
import com.github.javaparser.ast.expr.ObjectCreationExpr;
68
import io.codemodder.codetf.DetectorRule;
79
import io.codemodder.codetf.UnfixedFinding;
8-
import java.util.ArrayList;
9-
import java.util.HashMap;
10-
import java.util.List;
11-
import java.util.Map;
10+
import java.util.*;
1211
import java.util.function.Function;
1312
import java.util.function.Predicate;
13+
import org.jetbrains.annotations.VisibleForTesting;
1414

1515
final class DefaultFixCandidateSearcher<T> implements FixCandidateSearcher<T> {
1616

17-
private final List<Predicate<MethodCallExpr>> matchers;
17+
private final List<Predicate<MethodOrConstructor>> matchers;
1818
private final String methodName;
1919

2020
DefaultFixCandidateSearcher(
21-
final String methodName, final List<Predicate<MethodCallExpr>> matchers) {
21+
final String methodName, final List<Predicate<MethodOrConstructor>> matchers) {
2222
this.methodName = methodName;
2323
this.matchers = matchers;
2424
}
@@ -30,50 +30,61 @@ public FixCandidateSearchResults<T> search(
3030
final DetectorRule rule,
3131
final List<T> issuesForFile,
3232
final Function<T, String> getKey,
33-
final Function<T, Integer> getLine,
33+
final Function<T, Integer> getStartLine,
34+
final Function<T, Integer> getEndLine,
3435
final Function<T, Integer> getColumn) {
3536

3637
List<UnfixedFinding> unfixedFindings = new ArrayList<>();
37-
List<MethodCallExpr> calls =
38-
cu.findAll(MethodCallExpr.class).stream()
39-
.filter(
40-
mce ->
41-
mce.getRange()
42-
.isPresent()) // don't find calls we may have added -- you can pick these
43-
// out by them not having a range yet
44-
.filter(mce -> methodName == null || methodName.equals(mce.getNameAsString()))
38+
39+
List<MethodOrConstructor> calls =
40+
cu.findAll(Node.class).stream()
41+
// limit to just the interesting nodes for us
42+
.filter(n -> n instanceof MethodCallExpr || n instanceof ObjectCreationExpr)
43+
// turn them into our convenience type
44+
.map(MethodOrConstructor::new)
45+
// don't find calls we may have added -- you can pick these out by them not having a
46+
// range yet
47+
.filter(MethodOrConstructor::hasRange)
48+
// filter by method name if one is provided in the search
49+
.filter(mce -> methodName == null || mce.isMethodCallWithName(methodName))
50+
// filter by matchers
4551
.filter(mce -> matchers.stream().allMatch(m -> m.test(mce)))
4652
.toList();
4753

48-
Map<MethodCallExpr, List<T>> fixCandidateToIssueMapping = new HashMap<>();
54+
Map<MethodOrConstructor, List<T>> fixCandidateToIssueMapping = new HashMap<>();
4955

5056
for (T issue : issuesForFile) {
5157
String findingId = getKey.apply(issue);
52-
int line = getLine.apply(issue);
58+
int issueStartLine = getStartLine.apply(issue);
59+
Integer issueEndLine = getEndLine.apply(issue);
5360
Integer column = getColumn.apply(issue);
54-
List<MethodCallExpr> callsForIssue =
61+
List<MethodOrConstructor> callsForIssue =
5562
calls.stream()
56-
.filter(mce -> mce.getRange().isPresent())
57-
.filter(mce -> mce.getRange().get().begin.line == line)
63+
.filter(MethodOrConstructor::hasRange)
64+
.filter(
65+
mce -> {
66+
int callStartLine = mce.getRange().begin.line;
67+
return matches(issueStartLine, issueEndLine, callStartLine);
68+
})
5869
.toList();
5970
if (callsForIssue.size() > 1 && column != null) {
6071
callsForIssue =
6172
callsForIssue.stream()
62-
.filter(mce -> mce.getRange().get().contains(new Position(line, column)))
73+
.filter(mce -> mce.getRange().contains(new Position(issueStartLine, column)))
6374
.toList();
6475
}
6576
if (callsForIssue.isEmpty()) {
6677
unfixedFindings.add(
6778
new UnfixedFinding(
68-
findingId, rule, path, line, RemediationMessages.noCallsAtThatLocation));
79+
findingId, rule, path, issueStartLine, RemediationMessages.noCallsAtThatLocation));
6980
continue;
7081
} else if (callsForIssue.size() > 1) {
7182
unfixedFindings.add(
7283
new UnfixedFinding(
73-
findingId, rule, path, line, RemediationMessages.multipleCallsFound));
84+
findingId, rule, path, issueStartLine, RemediationMessages.multipleCallsFound));
7485
continue;
7586
}
76-
MethodCallExpr call = callsForIssue.get(0);
87+
MethodOrConstructor call = callsForIssue.get(0);
7788
fixCandidateToIssueMapping.computeIfAbsent(call, k -> new ArrayList<>()).add(issue);
7889
}
7990

@@ -94,4 +105,19 @@ public List<FixCandidate<T>> fixCandidates() {
94105
}
95106
};
96107
}
108+
109+
@VisibleForTesting
110+
static boolean matches(
111+
final int issueStartLine, final Integer issueEndLine, final int startCallLine) {
112+
// if the issue spans multiple lines, the call must be within that range
113+
if (issueEndLine != null) {
114+
return isInBetween(startCallLine, issueStartLine, issueEndLine);
115+
}
116+
// if the issue is on a single line, the call must be on that line
117+
return startCallLine == issueStartLine;
118+
}
119+
120+
private static boolean isInBetween(int number, int lowerBound, int upperBound) {
121+
return number >= lowerBound && number <= upperBound;
122+
}
97123
}

framework/codemodder-base/src/main/java/io/codemodder/remediation/FixCandidate.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package io.codemodder.remediation;
22

3-
import com.github.javaparser.ast.expr.MethodCallExpr;
43
import java.util.List;
54
import java.util.Objects;
65

76
/** The potential fix location. */
8-
public record FixCandidate<T>(MethodCallExpr methodCall, List<T> issues) {
7+
public record FixCandidate<T>(MethodOrConstructor call, List<T> issues) {
98

109
public FixCandidate {
11-
Objects.requireNonNull(methodCall);
10+
Objects.requireNonNull(call);
1211
Objects.requireNonNull(issues);
1312
if (issues.isEmpty()) {
1413
throw new IllegalArgumentException("issues cannot be empty");

framework/codemodder-base/src/main/java/io/codemodder/remediation/FixCandidateSearcher.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package io.codemodder.remediation;
22

33
import com.github.javaparser.ast.CompilationUnit;
4-
import com.github.javaparser.ast.expr.MethodCallExpr;
54
import io.codemodder.codetf.DetectorRule;
65
import java.util.ArrayList;
76
import java.util.List;
@@ -19,13 +18,14 @@ FixCandidateSearchResults<T> search(
1918
DetectorRule rule,
2019
List<T> issuesForFile,
2120
Function<T, String> getKey,
22-
Function<T, Integer> getLine,
21+
Function<T, Integer> getStartLine,
22+
Function<T, Integer> getEndLine,
2323
Function<T, Integer> getColumn);
2424

2525
/** Builder for {@link FixCandidateSearcher}. */
2626
final class Builder<T> {
2727
private String methodName;
28-
private final List<Predicate<MethodCallExpr>> methodMatchers;
28+
private final List<Predicate<MethodOrConstructor>> methodMatchers;
2929

3030
public Builder() {
3131
this.methodMatchers = new ArrayList<>();
@@ -36,7 +36,7 @@ public Builder<T> withMethodName(final String methodName) {
3636
return this;
3737
}
3838

39-
public Builder<T> withMatcher(final Predicate<MethodCallExpr> methodMatcher) {
39+
public Builder<T> withMatcher(final Predicate<MethodOrConstructor> methodMatcher) {
4040
this.methodMatchers.add(Objects.requireNonNull(methodMatcher));
4141
return this;
4242
}

framework/codemodder-base/src/main/java/io/codemodder/remediation/GenericRemediationMetadata.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public enum GenericRemediationMetadata {
1111
HEADER_INJECTION("header-injection"),
1212
REFLECTION_INJECTION("reflection-injection"),
1313
DESERIALIZATION("java-deserialization"),
14+
MISSING_SECURE_FLAG("missing-secure-flag"),
1415
SQL_INJECTION("sql-injection");
1516

1617
private final CodemodReporterStrategy reporter;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package io.codemodder.remediation;
2+
3+
import com.github.javaparser.Range;
4+
import com.github.javaparser.ast.Node;
5+
import com.github.javaparser.ast.NodeList;
6+
import com.github.javaparser.ast.expr.MethodCallExpr;
7+
import com.github.javaparser.ast.expr.ObjectCreationExpr;
8+
import com.github.javaparser.ast.nodeTypes.NodeWithArguments;
9+
import java.util.Collection;
10+
import java.util.Objects;
11+
12+
/** Convenience type to represent a method or constructor call in our APIs that work with both. */
13+
public record MethodOrConstructor(Node node) {
14+
15+
public MethodOrConstructor {
16+
Objects.requireNonNull(node);
17+
}
18+
19+
/** Return the wrapped node as a {@link Node}. */
20+
public Node asNode() {
21+
return this.node;
22+
}
23+
24+
/** This assumes a range is present, and explodes if it doesn't. */
25+
public Range getRange() {
26+
return this.asNode().getRange().orElseThrow();
27+
}
28+
29+
/** Return the wrapped node as a {@link com.github.javaparser.ast.nodeTypes.NodeWithArguments}. */
30+
public NodeWithArguments<?> asNodeWithArguments() {
31+
return (NodeWithArguments<?>) this.node;
32+
}
33+
34+
/** Get the arguments for the call. */
35+
public NodeList<?> getArguments() {
36+
return this.asNodeWithArguments().getArguments();
37+
}
38+
39+
/** Return true if this is a constructor call. */
40+
public boolean isConstructor() {
41+
return this.node instanceof ObjectCreationExpr;
42+
}
43+
44+
/** Return true if this is a method call. */
45+
public boolean isMethodCall() {
46+
return this.node instanceof MethodCallExpr;
47+
}
48+
49+
/** Return true if this is a method call with a scope. */
50+
public boolean isMethodCallWithScope() {
51+
return this.node instanceof MethodCallExpr mce && mce.getScope().isPresent();
52+
}
53+
54+
/** Return true if this is a constructor call for the given type. */
55+
public boolean isConstructorForType(final String type) {
56+
return this.node instanceof ObjectCreationExpr oce && oce.getTypeAsString().equals(type);
57+
}
58+
59+
/** Return true if the node has a range, meaning it was not added by us. */
60+
public boolean hasRange() {
61+
return this.asNode().getRange().isPresent();
62+
}
63+
64+
/** Return true if this is a method call and it has the given name. */
65+
public boolean isMethodCallWithName(final String name) {
66+
return this.isMethodCall() && ((MethodCallExpr) this.node).getNameAsString().equals(name);
67+
}
68+
69+
/** Return true if this is a method call and it has one of the given names. */
70+
public boolean isMethodCallWithNameIn(final Collection<String> names) {
71+
return this.isMethodCall() && names.contains(((MethodCallExpr) this.node).getNameAsString());
72+
}
73+
74+
/** Return the wrapped node as a {@link MethodCallExpr} or blow up. */
75+
public MethodCallExpr asMethodCall() {
76+
if (this.isMethodCall()) {
77+
return (MethodCallExpr) this.node;
78+
}
79+
throw new IllegalStateException("Not a method call");
80+
}
81+
}

0 commit comments

Comments
 (0)