`IndexOutOfBoundsException` when parsing Anonymous Classes using `this` in a method
What version of OpenRewrite are you using?
I am using
- rewrite-recipe-bom 3.18.0
How are you running OpenRewrite?
Writing my own recipes, reproducing occurring errors in JUnit Tests.
What is the smallest, simplest way to reproduce the problem?
@Language("java")
private static final String EXCEPTION_THROWN =
"""
package some.testpackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Comparator;
public class MyTestClass {
/** LOGGER */
private static final Logger logger = LoggerFactory.getLogger(MyTestClass.class);
public void logSomething() {
Comparator<String> comp = new Comparator<>() {
@Override
public int compare(String o1, String o2) {
// No issues
logger.info("Created: " + Integer.valueOf(3));
logger.info("Created: {}, this);
System.out.println("Created: " + this);
// Here is the issue!
logger.info("Created: " + this);
return o1.compareTo(o2);
}
};
}
}
""";
@Test
void exceptionDemonstration() {
// This test should succeed or fail, but throws an Exception instead
rewriteRun(
// The specific recipe does not matter as much, it just has to be one that actually looks into the specific line
spec -> spec.recipeFromResources("org.openrewrite.java.logging.slf4j.Slf4jBestPractices"),
java(EXCEPTION_THROWN));
}
What did you expect to see?
The test should fail because the recipe in question should change parts of the code.
What did you see instead?
The test threw an exception because the line logger.info("Created: " + this") triggers an IndexOutOfBoundsException
What is the full stack trace of any errors you encountered?
java.lang.AssertionError: Failed to run recipe at Cursor{MethodInvocation->JRightPadded(element=logger.info("Created: " + this), after=Space(comments=<0 comments>, whitespace=<empty>))->Block->MethodDeclaration->JRightPadded(element=MethodDeclaration{some.testpackage.MyTestClass$1{name=compare,return=int,parameters=[java.lang.String,java.lang.String]}}, after=Space(comments=<0 comments>, whitespace=<empty>))->Block->NewClass->JLeftPadded(before=Space(comments=<0 comments>, whitespace='·₁'), element=new Comparator<>() {
@Override
public int compare(String o1, String o2) {
logger.info("Created: " + this);
return o1.compareTo(o2);
}
})->NamedVariable->JRightPadded(element=comp = new Comparator<>() {
@Override
public int compare(String o1, String o2) {
logger.info("Created: " + this);
return o1.compareTo(o2);
}
}, after=Space(comments=<0 comments>, whitespace=<empty>))->VariableDeclarations->JRightPadded(element=Comparator<String> comp = new Comparator<>() {
@Override
public int compare(String o1, String o2) {
logger.info("Created: " + this);
return o1.compareTo(o2);
}
}, after=Space(comments=<0 comments>, whitespace=<empty>))->Block->MethodDeclaration->JRightPadded(element=MethodDeclaration{some.testpackage.MyTestClass{name=logSomething,return=void,parameters=[]}}, after=Space(comments=<0 comments>, whitespace=<empty>))->Block->ClassDeclaration->CompilationUnit->root}
(Full stack has been attached) Full_Stacktrace.txt
Additional Information
As far as I can tell, the issue does not occur:
- With anything other than
this - When using a lambda expression instead of an Anonymous Class
- When the recipe does not try to change the method call (because
logger.info("Created: {}", this)does not trigger the exception)
Honestly, my use-case seems like an absolute corner case barely anyone will stumble across, but just in case this is a deeper issue with the way the Cursor handles Anonymous Classes I'll leave this here.
Thanks for reporting.
Hints to whoever tries to solve it
It needs to be debugged, but by briefly looking at it I think it may turn out that the problem is with one of the slf4j subrecipes, not with the OpenRewrite framework. Especially that the recipes there use dynamic construction of Java templates: https://github.com/openrewrite/rewrite-logging-frameworks/blob/3d26bf9dfcb07408cba08fa401e4a0a38e84be53/src/main/java/org/openrewrite/java/logging/slf4j/JulParameterizedArguments.java#L116