Skip to content

Commit e93537d

Browse files
authored
Added support for changing Statement object in assignments. (#404)
The SQL parameterizer codemod will now be able to apply the fix when the statement object originates from an assignment, e.g.: ```java Statement stmt = null try{ stmt = conn.createStatement(); stmt.executeQuery(...); } ... ``` This unfortunately comes at the cost of breaking code whenever the assignment doesn't shadow the initiation. Since this particular case requires more sophisticated tools (mainly flow analysis), it is restricted for single assignments and the other cases stays as future work for now.
1 parent 928d321 commit e93537d

File tree

5 files changed

+222
-64
lines changed

5 files changed

+222
-64
lines changed

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

Lines changed: 171 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import io.codemodder.Either;
1212
import io.codemodder.ast.ASTTransforms;
1313
import io.codemodder.ast.ASTs;
14+
import io.codemodder.ast.ExpressionStmtVariableDeclaration;
15+
import io.codemodder.ast.LocalScope;
1416
import io.codemodder.ast.LocalVariableDeclaration;
1517
import io.codemodder.ast.TryResourceDeclaration;
1618
import java.util.*;
@@ -174,44 +176,85 @@ private Optional<MethodCallExpr> validateExecuteCall(final MethodCallExpr execut
174176
* the call itself, if immediate, or the {@link LocalVariableDeclaration} that has it as an
175177
* initializer.
176178
*/
177-
private Optional<Either<MethodCallExpr, LocalVariableDeclaration>> findStatementCreationExpr(
178-
final MethodCallExpr executeCall) {
179-
// Is itself a MethodCallExpr like <conn>.createStatement
179+
private Optional<Either<MethodCallExpr, Either<AssignExpr, LocalVariableDeclaration>>>
180+
findStatementCreationExpr(final MethodCallExpr executeCall) {
181+
// Has the form: <conn>.createStatement().executeQuery(...)
180182
// We require <conn> to be a nameExpr in this case.
181-
final Optional<Either<MethodCallExpr, LocalVariableDeclaration>> maybeImmediate =
182-
executeCall
183-
.getScope()
184-
.flatMap(this::isConnectionCreateStatement)
185-
// .filter(scope -> scope.getScope().filter(Expression::isNameExpr).isPresent())
186-
.map(Either::left);
183+
final Optional<Either<MethodCallExpr, Either<AssignExpr, LocalVariableDeclaration>>>
184+
maybeImmediate =
185+
executeCall
186+
.getScope()
187+
.flatMap(this::isConnectionCreateStatement)
188+
// .filter(scope -> scope.getScope().filter(Expression::isNameExpr).isPresent())
189+
.map(Either::left);
190+
191+
if (maybeImmediate.isPresent()) {
192+
return maybeImmediate;
193+
}
187194

188-
// Is a NameExpr that is initialized with <conn>.createStatement
189-
final Optional<Either<MethodCallExpr, LocalVariableDeclaration>> maybeLVD =
195+
// Has the form: <stmt>.executeQuery()
196+
// Find the <stmt> declaration
197+
final Optional<LocalVariableDeclaration> maybeLVD =
190198
executeCall
191199
.getScope()
192200
.map(expr -> expr instanceof NameExpr ? expr.asNameExpr() : null)
193-
.flatMap(ne -> ASTs.findEarliestLocalVariableDeclarationOf(ne, ne.getNameAsString()))
194-
.filter(
195-
lvd ->
196-
lvd.getVariableDeclarator()
197-
.getInitializer()
198-
.map(this::isConnectionCreateStatement)
199-
.isPresent())
200-
.map(Either::right);
201-
202-
return maybeImmediate.or(() -> maybeLVD);
201+
.flatMap(ne -> ASTs.findEarliestLocalVariableDeclarationOf(ne, ne.getNameAsString()));
202+
203+
// Has a single assignment
204+
// We erroniously assume that it always shadows the init expression
205+
// Needs some flow analysis to correctly address this case
206+
final Optional<AssignExpr> maybeSingleAssigned =
207+
maybeLVD
208+
.map(lvd -> ASTs.findAllAssignments(lvd).limit(2).toList())
209+
.filter(allAssignments -> allAssignments.size() == 1)
210+
.map(allAssignments -> allAssignments.get(0))
211+
.filter(assign -> assign.getTarget().isNameExpr())
212+
.filter(assign -> isConnectionCreateStatement(assign.getValue()).isPresent());
213+
214+
if (maybeSingleAssigned.isPresent()) {
215+
return maybeSingleAssigned.map(a -> Either.right(Either.left(a)));
216+
}
217+
218+
// Is <stmt> initialized with <conn>.createStatement()?
219+
final Optional<LocalVariableDeclaration> maybeInitExpr =
220+
maybeLVD.filter(
221+
lvd ->
222+
lvd.getVariableDeclarator()
223+
.getInitializer()
224+
.map(this::isConnectionCreateStatement)
225+
.isPresent());
226+
227+
return maybeInitExpr.map(init -> Either.right(Either.right(init)));
203228
}
204229

205-
private Optional<Either<MethodCallExpr, LocalVariableDeclaration>> validateStatementCreationExpr(
206-
final Either<MethodCallExpr, LocalVariableDeclaration> stmtObject) {
207-
if (stmtObject.isRight() && !canChangeTypes(stmtObject.getRight())) {
208-
return Optional.empty();
209-
}
230+
private Optional<Either<MethodCallExpr, Either<AssignExpr, LocalVariableDeclaration>>>
231+
validateStatementCreationExpr(
232+
final Either<MethodCallExpr, Either<AssignExpr, LocalVariableDeclaration>> stmtObject) {
210233
if (stmtObject.isRight()
211-
&& stmtObject.getRight() instanceof TryResourceDeclaration
212-
&& !validateTryResource((TryResourceDeclaration) stmtObject.getRight(), executeCall)) {
234+
&& stmtObject.getRight().isRight()
235+
&& !canChangeTypes(stmtObject.getRight().getRight())) {
213236
return Optional.empty();
214237
}
238+
if (stmtObject.isRight()) {
239+
// For the assignment case, the declaration must be from an ExpressionStmt
240+
if (stmtObject.getRight().isLeft()) {
241+
final var maybelvd =
242+
ASTs.findEarliestLocalVariableDeclarationOf(
243+
stmtObject.getRight().getLeft(),
244+
stmtObject.getRight().getLeft().getTarget().asNameExpr().getNameAsString())
245+
.filter(lvd -> lvd instanceof ExpressionStmtVariableDeclaration);
246+
if (maybelvd.isEmpty()) {
247+
return Optional.empty();
248+
}
249+
250+
} else {
251+
if (stmtObject.getRight().getRight() instanceof TryResourceDeclaration
252+
&& !validateTryResource(
253+
(TryResourceDeclaration) stmtObject.getRight().getRight(), executeCall)) {
254+
return Optional.empty();
255+
}
256+
}
257+
}
215258
return Optional.of(stmtObject);
216259
}
217260

@@ -388,31 +431,37 @@ private Expression buildParameter(
388431
* <p>(3) Change <stmtCreation>.execute*() to pstmt.execute().
389432
*/
390433
private MethodCallExpr fix(
391-
final Either<MethodCallExpr, LocalVariableDeclaration> stmtCreation,
434+
final Either<MethodCallExpr, Either<AssignExpr, LocalVariableDeclaration>> stmtCreation,
392435
final QueryParameterizer queryParameterizer,
393436
final MethodCallExpr executeCall) {
394437

395438
var executeStmt = ASTs.findParentStatementFrom(executeCall).get();
396439
// (0)
397-
if (stmtCreation.isRight() && executeStmt == stmtCreation.getRight().getStatement()) {
440+
if (stmtCreation.isRight()
441+
&& stmtCreation.getRight().isRight()
442+
&& executeStmt == stmtCreation.getRight().getRight().getStatement()) {
398443
final int stmtObjectIndex =
399444
stmtCreation
445+
.getRight()
400446
.getRight()
401447
.getStatement()
402448
.asTryStmt()
403449
.getResources()
404-
.indexOf(stmtCreation.getRight().getVariableDeclarationExpr());
450+
.indexOf(stmtCreation.getRight().getRight().getVariableDeclarationExpr());
405451

406452
executeStmt =
407453
ASTTransforms.splitResources(
408-
stmtCreation.getRight().getStatement().asTryStmt(), stmtObjectIndex)
454+
stmtCreation.getRight().getRight().getStatement().asTryStmt(), stmtObjectIndex)
409455
.getTryBlock()
410456
.getStatement(0);
411457
}
412458

413459
final String stmtName =
414460
stmtCreation.ifLeftOrElseGet(
415-
mce -> generateNameWithSuffix(preparedStatementNamePrefix, mce), lvd -> lvd.getName());
461+
mce -> generateNameWithSuffix(preparedStatementNamePrefix, mce),
462+
assignOrLVD ->
463+
assignOrLVD.ifLeftOrElseGet(
464+
a -> a.getTarget().asNameExpr().getNameAsString(), lvd -> lvd.getName()));
416465

417466
// (1)
418467
final var combinedExpressions =
@@ -423,7 +472,8 @@ private MethodCallExpr fix(
423472
var topStatement = executeStmt;
424473
for (int i = combinedExpressions.size() - 1; i >= 0; i--) {
425474
final var expr = combinedExpressions.get(i);
426-
final var setStmt =
475+
ExpressionStmt setStmt = null;
476+
setStmt =
427477
new ExpressionStmt(
428478
new MethodCallExpr(
429479
new NameExpr(stmtName),
@@ -441,12 +491,15 @@ private MethodCallExpr fix(
441491
args.addAll(
442492
stmtCreation.ifLeftOrElseGet(
443493
mce -> mce.getArguments(),
444-
lvd ->
445-
lvd.getVariableDeclarator()
446-
.getInitializer()
447-
.get()
448-
.asMethodCallExpr()
449-
.getArguments()));
494+
assignOrLVD ->
495+
assignOrLVD.ifLeftOrElseGet(
496+
a -> a.getValue().asMethodCallExpr().getArguments(),
497+
lvd ->
498+
lvd.getVariableDeclarator()
499+
.getInitializer()
500+
.get()
501+
.asMethodCallExpr()
502+
.getArguments())));
450503

451504
// (3)
452505
executeCall.setName("execute");
@@ -467,29 +520,82 @@ private MethodCallExpr fix(
467520

468521
// (2.b)
469522
} else {
470-
stmtCreation
471-
.getRight()
472-
.getVariableDeclarator()
473-
.setType(StaticJavaParser.parseType("PreparedStatement"));
474-
stmtCreation
475-
.getRight()
476-
.getVariableDeclarator()
477-
.getInitializer()
478-
.ifPresent(expr -> expr.asMethodCallExpr().setName("prepareStatement"));
479-
stmtCreation
480-
.getRight()
481-
.getVariableDeclarator()
482-
.getInitializer()
483-
.ifPresent(expr -> expr.asMethodCallExpr().setArguments(args));
484-
pstmtCreation =
485-
stmtCreation.getRight().getVariableDeclarator().getInitializer().get().asMethodCallExpr();
523+
final var assignOrLVD = stmtCreation.getRight();
524+
if (assignOrLVD.isLeft()) {
525+
pstmtCreation = assignOrLVD.getLeft().getValue().asMethodCallExpr();
526+
pstmtCreation.setArguments(args);
527+
pstmtCreation.setName("prepareStatement");
528+
529+
// change the assignment
530+
assignOrLVD.getLeft().setValue(StaticJavaParser.parseExpression("a"));
531+
assignOrLVD.getLeft().setValue(pstmtCreation);
532+
533+
// change the initialization to be null and its type to PreparedStatement
534+
// This will only work assuming a single shadowing assignment, may require changes here in
535+
// the future
536+
var maybeLVD =
537+
ASTs.findEarliestLocalVariableDeclarationOf(
538+
assignOrLVD.getLeft().getTarget(),
539+
assignOrLVD.getLeft().getTarget().asNameExpr().getNameAsString());
540+
if (maybeLVD.isPresent()) {
541+
var vd = maybeLVD.get().getVariableDeclarator();
542+
vd.setInitializer(new NullLiteralExpr());
543+
vd.setType(StaticJavaParser.parseType("PreparedStatement"));
544+
}
545+
546+
} else {
547+
assignOrLVD
548+
.getRight()
549+
.getVariableDeclarator()
550+
.setType(StaticJavaParser.parseType("PreparedStatement"));
551+
assignOrLVD
552+
.getRight()
553+
.getVariableDeclarator()
554+
.getInitializer()
555+
.ifPresent(expr -> expr.asMethodCallExpr().setName("prepareStatement"));
556+
assignOrLVD
557+
.getRight()
558+
.getVariableDeclarator()
559+
.getInitializer()
560+
.ifPresent(expr -> expr.asMethodCallExpr().setArguments(args));
561+
pstmtCreation =
562+
assignOrLVD
563+
.getRight()
564+
.getVariableDeclarator()
565+
.getInitializer()
566+
.get()
567+
.asMethodCallExpr();
568+
}
486569
}
487570
return pstmtCreation;
488571
}
489572

490-
private boolean assignedOrDefinedInScope(NameExpr name, LocalVariableDeclaration lvd) {
573+
private boolean resolvedInScope(
574+
final Either<AssignExpr, LocalVariableDeclaration> assignOrLVD, Expression expr) {
575+
if (assignOrLVD.isLeft()) {
576+
final var scope =
577+
LocalScope.fromAssignExpression(
578+
assignOrLVD
579+
.getLeft()); // Unsupported case for scope calculation, fail here until we add
580+
// some
581+
if (scope.stream().findAny().isEmpty()) {
582+
return true;
583+
}
584+
return scope.inScope(expr);
585+
}
586+
return assignOrLVD.getRight().getScope().inScope(expr);
587+
}
588+
589+
private boolean assignedOrDefinedInScope(
590+
final NameExpr name, final Either<AssignExpr, LocalVariableDeclaration> assignOrLVD) {
591+
final var scope =
592+
assignOrLVD.ifLeftOrElseGet(a -> LocalScope.fromAssignExpression(a), lvd -> lvd.getScope());
593+
// Unsupported case for scope calculation, fail here until we add some
594+
if (scope.stream().findAny().isEmpty()) {
595+
return true;
596+
}
491597
final Stream<AssignExpr> assignmentsInScope =
492-
lvd.getScope().stream()
598+
scope.stream()
493599
.flatMap(
494600
node -> node instanceof AssignExpr ? Stream.of((AssignExpr) node) : Stream.empty());
495601

@@ -500,7 +606,7 @@ private boolean assignedOrDefinedInScope(NameExpr name, LocalVariableDeclaration
500606

501607
final boolean definedInScope =
502608
ASTs.findNonCallableSimpleNameSource(name.getName())
503-
.filter(source -> lvd.getScope().inScope(source))
609+
.filter(source -> scope.inScope(source))
504610
.isPresent();
505611

506612
return assignedInScope || definedInScope;
@@ -521,6 +627,7 @@ public Optional<MethodCallExpr> checkAndFix() {
521627
// Now find the stmt creation expression, if any and validate it
522628
final var stmtObject =
523629
findStatementCreationExpr(executeCall).flatMap(this::validateStatementCreationExpr);
630+
524631
if (stmtObject.isPresent()) {
525632
// Now look for injections
526633
final QueryParameterizer queryp;
@@ -536,22 +643,22 @@ public Optional<MethodCallExpr> checkAndFix() {
536643
.get()
537644
.ifLeftOrElseGet(
538645
mcd -> false,
539-
stmtLVD ->
646+
assignOrLVD ->
540647
queryp.getLinearizedQuery().getResolvedExpressionsMap().keySet().stream()
541-
.anyMatch(expr -> stmtLVD.getScope().inScope(expr)));
648+
.anyMatch(expr -> resolvedInScope(assignOrLVD, expr)));
542649

543-
// Is any name in the linearized expression defined/assigned inside the scope of the
650+
//// Is any name in the linearized expression defined/assigned inside the scope of the
544651
// Statement Object?
545652
final boolean nameInScope =
546653
stmtObject
547654
.get()
548655
.ifLeftOrElseGet(
549656
mcd -> false,
550-
stmtLVD ->
657+
assignOrLVD ->
551658
queryp.getLinearizedQuery().getLinearized().stream()
552659
.filter(expr -> expr.isNameExpr())
553660
.map(expr -> expr.asNameExpr())
554-
.anyMatch(name -> assignedOrDefinedInScope(name, stmtLVD)));
661+
.anyMatch(name -> assignedOrDefinedInScope(name, assignOrLVD)));
555662

556663
if (queryp.getInjections().isEmpty() || resolvedInScope || nameInScope) {
557664
return Optional.empty();

core-codemods/src/test/resources/sql-parameterizer/Test.java.after

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,17 @@ public final class Test {
8181
var rs = stmt.execute();
8282
return rs;
8383
}
84+
85+
public ResultSet singleAssigned(String input) throws SQLException {
86+
String sql = "SELECT * FROM USERS WHERE USER = ?";
87+
PreparedStatement stmt = null;
88+
try {
89+
stmt = conn.prepareStatement(sql);
90+
stmt.setString(1, input);
91+
ResultSet rs = stmt.execute();
92+
return rs;
93+
} catch (Exception e) {
94+
}
95+
}
96+
8497
}

core-codemods/src/test/resources/sql-parameterizer/Test.java.before

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,16 @@ public final class Test {
6666
var rs = stmt.executeQuery(sql);
6767
return rs;
6868
}
69+
70+
public ResultSet singleAssigned(String input) throws SQLException {
71+
String sql = "SELECT * FROM USERS WHERE USER = '" + input + "'";
72+
Statement stmt = conn.createStatement();
73+
try {
74+
stmt = conn.createStatement();
75+
ResultSet rs = stmt.executeQuery(sql);
76+
return rs;
77+
} catch (Exception e) {
78+
}
79+
}
80+
6981
}

framework/codemodder-base/src/main/java/io/codemodder/ast/ASTs.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ public final class ASTs {
3333

3434
private ASTs() {}
3535

36+
/** Test for this pattern: {@link ExpressionStmt} -&gt; {@link Expression} ({@code expr}). */
37+
public static Optional<ExpressionStmt> isExpressionStmtExpr(final Expression expr) {
38+
return expr.getParentNode()
39+
.map(p -> p instanceof ExpressionStmt ? (ExpressionStmt) p : null)
40+
.filter(stmt -> stmt.getExpression() == expr);
41+
}
42+
3643
/**
3744
* Test for this pattern: {@link AssignExpr} -&gt; {@link Expression} ({@code expr}), where
3845
* ({@code expr}) is the right hand side expression of the assignment.

0 commit comments

Comments
 (0)