Piotr said:
Boris said:
Piotr Kobzda wrote:
[snip]
I have another one, too. Namely, using your code I can't transform
methods
1. which don't have any arguments (verifier says
java.lang.VerifyError: (class: path/to/TimingTest, method: test
signature: ()V) Illegal local variable number) )
and
2. methods with $ in their names (like access$0) -> inner classes?
(verifier says java.lang.VerifyError: (class: path/to/TimingTest,
method: access$0 signature: (Lpath/to/TimingTest
V) Illegal local
variable number)
Could you look into that, please?
It seems to me you have incorrectly computed max locals value for your
methods. Have you set computeMaxs option in your ClassWriter?
Erm... I did. Just now. ;-)
According to tests so far, it works fine. Thanks.
My code (with ASM 2.1) transforms perfect all kind of methods in my
tests (synthetic, parameterized etc.).
There is thousand possible reasons of your code malfunction, but first
of all ensure you are not fighting with some bugs of ASM beta 3, so if
you haven't done it yet, switch to the stable ASM version.
Tried that, too. Tests up to now show no visible differences. I haven't compared
bytecode, yet.
Very strange... And I have no concept what's the reason of that, show a
little more code.
My bad. As I renamed arguments of visitMethodInsn(...) I left "desc" of
super.super.visitMethodInsn() unchanged. Should have been _aDesc_. Plus a couple
of missing double-quotes which disappeared when I prepared the code for posting
(I refactored quite a bit since the beginning and I try to keep some level of
consistency for the sake of this conversation).
But now I have another problem. Throwing a RuntimeException effectively aborts
transformation: Transformer catches the exception and returns unmodified bytes.
I'd like to be able to do this on a mehod level: skip the methods already
containing timing calls (leaving them unmodified), and be able to transform
others. I do something similar first thing in visitMethod(...) (see attached
code) but, unlike detecting calls within the body of a method, I know which
methods are registered in advance.
(I know I keep repeating myself, but...) Any suggestions?
After reading this and your previous post I think, that you don't have
to care about existing start() and end() calls within your code.
Similar problem exist for example in a case of recursive calls, your
start() method will be called many times before end(). And it should be
YourTiming start() and end() methods implementation responsibility to
treat such a scenarios well.
Currently it only detects them. There are some possibilities:
1. keep only the first call and (silently) ignore other consecutive ones
2. keep the last one and (silently) dispose of the ones before
3. throw an error
4. ?
You said before that there is some kind of stack-trace computed in your
implementation, so having it in a calls to end() you can find out which
start() this call is related to (simply ignoring all unrecognized end()
calls).
That's what I'm doing. But I don't seem to know how to get hold of method
signature, so there can be problems if consecutive threads call timed methods
with the same name, but different signatures. I can't rely on line numbers, because:
1. MyTiming.start and MyTiming.end are called in different lines (so I can't
pair them up)
2. There may even not be line number info if the bytecode is doctored that way
(am I right?).
This needs some kind of method calls stack reflected in your
implementation but will give your users a freedom of choices.
Moreover, you will no longer need end() taking a String parameter in
this scenario, because you can assume that each end() call is always
related to the last start() call (parameterized or not) performed in the
same level of method calls stack.
Hope that's clear.
Well, not quite, but I think I'll keep those parametrized calls to be able to
time blocks of code within the method.
package path.to.instrumentation.asm;
import java.util.Map;
import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import path.to.MyTiming;
public class TimingTransformerAsmClassAdapter extends ClassAdapter {
private static final boolean DEBUG = false;
private static final boolean VERBOSE = true || DEBUG;
private Map methodNames;
public TimingTransformerAsmClassAdapter(
final ClassVisitor classVisitor,
final Map methodNames
) {
super(classVisitor);
this.methodNames = methodNames;
}
public MethodVisitor visitMethod(
final int access,
final String name,
final String desc,
final String signature,
final String[] exceptions
) {
final String nameDesc = name + desc;
if(!methodNames.isEmpty()) {
if(
!methodNames.containsKey(name)
&& !methodNames.containsKey(nameDesc)
) {
verboseln("skipping unregistered method " + nameDesc);
return cv.visitMethod(access, name, desc, signature, exceptions);
}
}
//If ClassWriter in TimingTransformerAsm.transform(...) is not
//initialized properly (meaning: if it does not compute the maximum
//stack size and the maximum number of local variables automatically:
//-> new ClassWriter(ClassWriter.COMPUTE_MAXS); for 3.0 beta and
//-> new ClassWriter(true); for 2.2.1),
//the following block must be uncommented. Otherwise transfomed classes
//won't necessarily pass verification!
// if(desc.startsWith("()")) // Can't transform methods with no args!?!
// {
// verboseln("skipping special method (no args!?!) " + nameDesc);
// return cv.visitMethod(access, name, desc, signature, exceptions);
// }
//
// if(name.indexOf("$") >= 0) //Don't transform inner class methods!?!
// {
// verboseln("skipping special method (inner class?!?) " + nameDesc);
// return cv.visitMethod(access, name, desc, signature, exceptions);
// }
verboseln("transforming method " + nameDesc);
return
new MethodAdapter(
super.visitMethod(access, name, desc, signature, exceptions)
) {
final Label startLabel = new Label();
final Label finallyLabel = new Label();
int retOpcode = Opcodes.RETURN;
private void genStartCode() {
super.visitMethodInsn(
Opcodes.INVOKESTATIC,
MyTiming.INTERNAL_CLASS_NAME,
MyTiming.MARK_START_METHOD_NAME,
"()V"
);
}
private void genEndCode() {
super.visitMethodInsn(
Opcodes.INVOKESTATIC,
MyTiming.INTERNAL_CLASS_NAME,
MyTiming.MARK_STOP_METHOD_NAME,
"()V"
);
}
public void visitCode() {
super.visitCode();
super.visitLabel(startLabel);
genStartCode();
}
public void visitInsn(final int opcode) {
if(isRetInsn(opcode)) {
retOpcode = opcode;
super.visitJumpInsn(Opcodes.GOTO, finallyLabel);
}
else {
super.visitInsn(opcode);
}
}
public void visitMethodInsn(
final int opcode,
final String owner,
final String aName,
final String aDesc
) {
super.visitMethodInsn(opcode, owner, aName, aDesc);
if(
owner.equals("path/to/MyTiming)
&& (
aName.equals("start")
|| aName.equals("end")
)
) {
throw new RuntimeException(nameDesc
+ " already contains timing calls. Aborting.");
}
}
public void visitMaxs(final int maxStack, final int maxLocals) { // public void visitEnd() {
final Label endLabel = new Label();
super.visitLabel(endLabel);
super.visitVarInsn(Opcodes.ASTORE, 1);
genEndCode();
super.visitVarInsn(Opcodes.ALOAD, 1);
super.visitInsn(Opcodes.ATHROW);
super.visitLabel(finallyLabel);
genEndCode();
super.visitInsn(retOpcode);
super.visitTryCatchBlock(
startLabel,
endLabel,
endLabel,
null
);
super.visitMaxs(maxStack, maxLocals); //super.visitEnd();
}
};
}
private static boolean isRetInsn(final int opcode) {
switch(opcode) {
case Opcodes.IRETURN:
case Opcodes.LRETURN:
case Opcodes.FRETURN:
case Opcodes.DRETURN:
case Opcodes.ARETURN:
case Opcodes.RETURN:
return true;
default:
return false;
}
}
private static final void out(String s) {
System.out.print(
TimingTransformerAsmClassAdapter.class.getSimpleName()
+ "> " + s
);
}
private static final void outln(String s) {
System.out.println(
TimingTransformerAsmClassAdapter.class.getSimpleName()
+ "> " + s
);
}
private static final void verbose(String s) {
if(VERBOSE)
out(s);
}
private static final void verboseln(String s) {
if(VERBOSE)
outln(s);
}
private static final void debug(String s) {
if(DEBUG)
out(s);
}
private static final void debugln(String s) {
if(DEBUG)
outln(s);
}
}