Problems with an ArrayIndexOutOfBoundsException

Context I have source code (as a String) that I compile in memory with the Java compiler API. I then transform the bytecode to add instrumentation: mainly tracking line coverage and obtaining variable changes. What I'm trying to do Now I want to add logging. For this I want to replace calls to System.out.println with my own logging function. (This is necessary, because (1) my custom class loader blocks loading of the System class and (2) because I want the log information stored along with the other data and contain class, method and line number of the call.) What already works Simply calling System.out.println("Hello, World!"); works already. Concatenating a string with a variable works now as well, e.g. System.out.println("x = " + x); The transformation is done with the MethodVisitor from ASM:
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
if (owner.equals("java/io/PrintStream") && name.equals("println") && descriptor.equals("(Ljava/lang/String;)V")) {
visitLdcInsn(classId);
visitLdcInsn(pMethodName);
super.visitMethodInsn(
Opcodes.INVOKESTATIC,
Type.getInternalName(Debug.class),
"log",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
false);
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}

@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
if (opcode == Opcodes.GETSTATIC
&& owner.equals("java/lang/System")
&& name.equals("out")
&& desc.equals("Ljava/io/PrintStream;")) {
// needs to be loaded to keep stack size valid
super.visitFieldInsn(opcode, Type.getInternalName(Debug.class), "dummyStream", desc);
} else {
super.visitFieldInsn(opcode, owner, name, desc);
}
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
if (owner.equals("java/io/PrintStream") && name.equals("println") && descriptor.equals("(Ljava/lang/String;)V")) {
visitLdcInsn(classId);
visitLdcInsn(pMethodName);
super.visitMethodInsn(
Opcodes.INVOKESTATIC,
Type.getInternalName(Debug.class),
"log",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
false);
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}

@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
if (opcode == Opcodes.GETSTATIC
&& owner.equals("java/lang/System")
&& name.equals("out")
&& desc.equals("Ljava/io/PrintStream;")) {
// needs to be loaded to keep stack size valid
super.visitFieldInsn(opcode, Type.getInternalName(Debug.class), "dummyStream", desc);
} else {
super.visitFieldInsn(opcode, owner, name, desc);
}
}
So I mainly switch out the call to a printStreams println with the call to my logging function and I switch out accessing System.out with fetching a dummy PrintStream (that does nothing) from my Debug class. In bytecode this means that an access to System.out.println is replaced with:
7: getstatic #50 // Field de/tim_greller/susserver/model/execution/instrumentation/Debug.dummyStream:Ljava/io/PrintStream;
10: ldc #13 // String Hello, World!
12: ldc #38 // String Demo#[email protected]
14: ldc #51 // String add
16: invokestatic #55 // Method de/tim_greller/susserver/model/execution/instrumentation/Debug.log:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
7: getstatic #50 // Field de/tim_greller/susserver/model/execution/instrumentation/Debug.dummyStream:Ljava/io/PrintStream;
10: ldc #13 // String Hello, World!
12: ldc #38 // String Demo#[email protected]
14: ldc #51 // String add
16: invokestatic #55 // Method de/tim_greller/susserver/model/execution/instrumentation/Debug.log:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
which in Java means:
PrintStream var10000 = Debug.dummyStream;
Debug.log("Hello, World!", "Demo#[email protected]", "add");
PrintStream var10000 = Debug.dummyStream;
Debug.log("Hello, World!", "Demo#[email protected]", "add");
4 Replies
JavaBot
JavaBot10mo ago
This post has been reserved for your question.
Hey @<Tim>! Please use /close or the Close Post button above when your problem is solved. Please remember to follow the help guidelines. This post will be automatically closed after 300 minutes of inactivity.
TIP: Narrow down your issue to simple and precise questions to maximize the chance that others will reply in here.
<Tim>
<Tim>OP10mo ago
What doesn't work As soon as I want to do all of that inside a loop, I get this error:
java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
at org.springframework.asm.Frame.merge(Frame.java:1269)
at org.springframework.asm.Frame.merge(Frame.java:1245)
at org.springframework.asm.MethodWriter.computeAllFrames(MethodWriter.java:1611)
at org.springframework.asm.MethodWriter.visitMaxs(MethodWriter.java:1547)
at org.springframework.asm.MethodVisitor.visitMaxs(MethodVisitor.java:784)
at org.springframework.asm.ClassReader.readCode(ClassReader.java:2667)
at org.springframework.asm.ClassReader.readMethod(ClassReader.java:1516)
at org.springframework.asm.ClassReader.accept(ClassReader.java:746)
at org.springframework.asm.ClassReader.accept(ClassReader.java:426)
at de.tim_greller.susserver.model.execution.instrumentation.CoverageClassTransformer.transform(CoverageClassTransformer.java:14)
at de.tim_greller.susserver.model.execution.compilation.InMemoryCompiler$2.findClass(InMemoryCompiler.java:121)
at de.tim_greller.susserver.model.execution.compilation.InMemoryCompiler$2.loadClass(InMemoryCompiler.java:140)
at FooTest.test(FooTest.java:8)
java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
at org.springframework.asm.Frame.merge(Frame.java:1269)
at org.springframework.asm.Frame.merge(Frame.java:1245)
at org.springframework.asm.MethodWriter.computeAllFrames(MethodWriter.java:1611)
at org.springframework.asm.MethodWriter.visitMaxs(MethodWriter.java:1547)
at org.springframework.asm.MethodVisitor.visitMaxs(MethodVisitor.java:784)
at org.springframework.asm.ClassReader.readCode(ClassReader.java:2667)
at org.springframework.asm.ClassReader.readMethod(ClassReader.java:1516)
at org.springframework.asm.ClassReader.accept(ClassReader.java:746)
at org.springframework.asm.ClassReader.accept(ClassReader.java:426)
at de.tim_greller.susserver.model.execution.instrumentation.CoverageClassTransformer.transform(CoverageClassTransformer.java:14)
at de.tim_greller.susserver.model.execution.compilation.InMemoryCompiler$2.findClass(InMemoryCompiler.java:121)
at de.tim_greller.susserver.model.execution.compilation.InMemoryCompiler$2.loadClass(InMemoryCompiler.java:140)
at FooTest.test(FooTest.java:8)
The example class is this:
public class Foo {
public static int add(int a, int b) {
int s = a, x = b;
while (x > 0) {
System.out.println("x: " + x);
x = x - 1;
s = s + 1;
}
return s;
}
}
public class Foo {
public static int add(int a, int b) {
int s = a, x = b;
while (x > 0) {
System.out.println("x: " + x);
x = x - 1;
s = s + 1;
}
return s;
}
}
The System.out.println is not transformed but omited from the bytecode after transformation completely Does anyone have an idea of how I can implement the logging? It doesn't need to be based on System.out.println, but if I don't want to mess around with the source code and regexes or something, it needs to be something that is available at compile time of the code. Could it be that the printStream on the stack causes problems? Can I remove it maybe? 😬 It was in fact the dummy printstream. If I don't load it, it works in loops as well. Hope it doesn't cause any side effects, as I thought I left it there for a reason. But looks good so far :) Thanks for the help, you were good rubber duckies :)
JavaBot
JavaBot10mo ago
If you are finished with your post, please close it. If you are not, please ignore this message. Note that you will not be able to send further messages here after this post have been closed but you will be able to create new posts.
JavaBot
JavaBot10mo ago
Post Closed
This post has been closed by <@330307656105328640>.
Want results from more Discord servers?
Add your server