Perf: use CFG dataflow for traceLocalSetter and guard hot-path logging.
Replace per-call method-body AST scans with reaching-definitions lookup, guard remaining debug/trace calls in call-graph hot paths, and add branch edge-case tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
package click.kamil.springstatemachineexporter.ast.common;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.service.VariableTracer;
|
||||
import org.eclipse.jdt.core.dom.Expression;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class FindLocalSetterArgumentTest {
|
||||
|
||||
private CodebaseContext context;
|
||||
private VariableTracer variableTracer;
|
||||
|
||||
@BeforeEach
|
||||
void setUp(@TempDir Path tempDir) throws IOException {
|
||||
writeJava(tempDir, "com/example/Payload.java", """
|
||||
package com.example;
|
||||
public class Payload {
|
||||
private String event = "DEFAULT";
|
||||
public String getEvent() { return event; }
|
||||
public void setEvent(String event) { this.event = event; }
|
||||
}
|
||||
""");
|
||||
context = new CodebaseContext();
|
||||
context.setResolveBindings(true);
|
||||
context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
|
||||
variableTracer = new VariableTracer(context, context.getConstantResolver());
|
||||
}
|
||||
|
||||
@Test
|
||||
void lastSetterBeforeGetterWins(@TempDir Path tempDir) throws IOException {
|
||||
writeJava(tempDir, "com/example/Runner.java", """
|
||||
package com.example;
|
||||
public class Runner {
|
||||
public void run() {
|
||||
Payload payload = new Payload();
|
||||
payload.setEvent("FIRST");
|
||||
payload.setEvent("SECOND");
|
||||
String value = payload.getEvent();
|
||||
}
|
||||
}
|
||||
""");
|
||||
context.scan(tempDir);
|
||||
|
||||
Expression result = variableTracer.traceLocalSetter(
|
||||
"com.example.Runner.run", "payload", "getEvent");
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.toString()).contains("SECOND");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setterAfterGetterIsIgnored(@TempDir Path tempDir) throws IOException {
|
||||
writeJava(tempDir, "com/example/Runner.java", """
|
||||
package com.example;
|
||||
public class Runner {
|
||||
public void run() {
|
||||
Payload payload = new Payload();
|
||||
payload.setEvent("BEFORE");
|
||||
String value = payload.getEvent();
|
||||
payload.setEvent("AFTER");
|
||||
}
|
||||
}
|
||||
""");
|
||||
context.scan(tempDir);
|
||||
|
||||
Expression result = variableTracer.traceLocalSetter(
|
||||
"com.example.Runner.run", "payload", "getEvent");
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.toString()).contains("BEFORE");
|
||||
assertThat(result.toString()).doesNotContain("AFTER");
|
||||
}
|
||||
|
||||
@Test
|
||||
void ifElseBranchesBothReachGetter_lastOnCfgPathWins(@TempDir Path tempDir) throws IOException {
|
||||
writeJava(tempDir, "com/example/Runner.java", """
|
||||
package com.example;
|
||||
public class Runner {
|
||||
public void run(boolean flag) {
|
||||
Payload payload = new Payload();
|
||||
if (flag) {
|
||||
payload.setEvent("IF_BRANCH");
|
||||
} else {
|
||||
payload.setEvent("ELSE_BRANCH");
|
||||
}
|
||||
String value = payload.getEvent();
|
||||
}
|
||||
}
|
||||
""");
|
||||
context.scan(tempDir);
|
||||
|
||||
Expression result = variableTracer.traceLocalSetter(
|
||||
"com.example.Runner.run", "payload", "getEvent");
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
// Both branches are on the CFG path to the getter; last in source order wins.
|
||||
assertThat(result.toString()).contains("ELSE_BRANCH");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setterOnSiblingBranchIsExcluded(@TempDir Path tempDir) throws IOException {
|
||||
writeJava(tempDir, "com/example/Runner.java", """
|
||||
package com.example;
|
||||
public class Runner {
|
||||
public void run(boolean flag) {
|
||||
Payload payload = new Payload();
|
||||
if (flag) {
|
||||
payload.setEvent("TAKEN");
|
||||
String value = payload.getEvent();
|
||||
return;
|
||||
}
|
||||
payload.setEvent("OTHER_BRANCH");
|
||||
}
|
||||
}
|
||||
""");
|
||||
context.scan(tempDir);
|
||||
|
||||
Expression result = variableTracer.traceLocalSetter(
|
||||
"com.example.Runner.run", "payload", "getEvent");
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.toString()).contains("TAKEN");
|
||||
assertThat(result.toString()).doesNotContain("OTHER_BRANCH");
|
||||
}
|
||||
|
||||
@Test
|
||||
void noGetterCallFallsBackToOrderedScan(@TempDir Path tempDir) throws IOException {
|
||||
writeJava(tempDir, "com/example/Runner.java", """
|
||||
package com.example;
|
||||
public class Runner {
|
||||
public void run() {
|
||||
Payload payload = new Payload();
|
||||
payload.setEvent("ONLY");
|
||||
}
|
||||
}
|
||||
""");
|
||||
context.scan(tempDir);
|
||||
|
||||
Expression result = variableTracer.traceLocalSetter(
|
||||
"com.example.Runner.run", "payload", "getEvent");
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.toString()).contains("ONLY");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldAssignmentSetterIsFound(@TempDir Path tempDir) throws IOException {
|
||||
writeJava(tempDir, "com/example/MutablePayload.java", """
|
||||
package com.example;
|
||||
public class MutablePayload {
|
||||
public String event;
|
||||
public String getEvent() { return event; }
|
||||
}
|
||||
""");
|
||||
writeJava(tempDir, "com/example/Runner.java", """
|
||||
package com.example;
|
||||
public class Runner {
|
||||
public void run() {
|
||||
MutablePayload payload = new MutablePayload();
|
||||
payload.event = "ASSIGNED";
|
||||
String value = payload.getEvent();
|
||||
}
|
||||
}
|
||||
""");
|
||||
context.scan(tempDir);
|
||||
|
||||
Expression result = variableTracer.traceLocalSetter(
|
||||
"com.example.Runner.run", "payload", "getEvent");
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.toString()).contains("ASSIGNED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setterInsideNestedExpressionIsFound(@TempDir Path tempDir) throws IOException {
|
||||
writeJava(tempDir, "com/example/Runner.java", """
|
||||
package com.example;
|
||||
public class Runner {
|
||||
public void run() {
|
||||
Payload payload = new Payload();
|
||||
consume(payload.setEvent("NESTED"));
|
||||
String value = payload.getEvent();
|
||||
}
|
||||
private void consume(String ignored) {}
|
||||
}
|
||||
""");
|
||||
context.scan(tempDir);
|
||||
|
||||
Expression result = variableTracer.traceLocalSetter(
|
||||
"com.example.Runner.run", "payload", "getEvent");
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.toString()).contains("NESTED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void returnsNullWhenNoMatchingSetter(@TempDir Path tempDir) throws IOException {
|
||||
writeJava(tempDir, "com/example/Runner.java", """
|
||||
package com.example;
|
||||
public class Runner {
|
||||
public void run() {
|
||||
Payload payload = new Payload();
|
||||
String value = payload.getEvent();
|
||||
}
|
||||
}
|
||||
""");
|
||||
context.scan(tempDir);
|
||||
|
||||
Expression result = variableTracer.traceLocalSetter(
|
||||
"com.example.Runner.run", "payload", "getEvent");
|
||||
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
private static void writeJava(Path tempDir, String relativePath, String source) throws IOException {
|
||||
Path file = tempDir.resolve(relativePath);
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.writeString(file, source);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user