When entering a method, I want to record all the arguments of that method. Since ASM can be hard to debug, I write some demo code(use @apangin's collector class) and test its validity. However, it reports java.lang.VerifyError: (class: com/D, method: add signature: (II)I) Accessing value from uninitialized register 2
when I use the following code.
I think when a frame is created, the local variables are initialized by this
and the arguments of the method. But register 2
(I guess it means the third element in local variables) should be initialized by the second int argument, why it still throws the error?
The code of com/D:
public class D extends C {
public D() { }
public int a = 4;
@Override
public int p() { return a; }
public static int add(int a, int b){
return a+b;
}
}
The visitCode
method in my methodVisitor adapter class:
public void visitCode() {
mv.visitCode();
if (needAnalysePurity){
pushArgArrToStackInMeth();
mv.visitInsn(POP);
}
}
public void pushArgArrToStackInMeth(){
Type[] args = Type.getArgumentTypes(this.selfDesc);
// new ArgumentCollector(N)
String collector = Type.getInternalName(ArgCollectorLocals.class);
mv.visitTypeInsn(NEW, collector);
mv.visitInsn(DUP);
mv.visitIntInsn(SIPUSH, args.length);
mv.visitMethodInsn(INVOKESPECIAL, collector, "<init>", "(I)V", false);
// for each argument call the corresponding collector.add(arg)
int index = 1;
for (Type arg : args) {
String argDesc = arg.getDescriptor();
if (argDesc.length() > 1) {
argDesc = "Ljava/lang/Object;";
}
mv.visitVarInsn(arg.getOpcode(ILOAD), index);
mv.visitMethodInsn(INVOKEVIRTUAL, collector, "add", "(" + argDesc + ")L" + collector + ";", false);
index += arg.getSize();
}
// collector.toArray()
mv.visitMethodInsn(INVOKEVIRTUAL, collector, "toArray", "()[Ljava/lang/Object;", false);
}
The collector class used to collect the arguments:
public class ArgCollectorLocals {
private final Object[] args;
private int index;
public ArgCollectorLocals(int length) {
this.args = new Object[length];
}
public ArgCollectorLocals add(boolean a) {
args[index++] = a;
return this;
}
public ArgCollectorLocals add(byte a) {
args[index++] = a;
return this;
}
public ArgCollectorLocals add(char a) {
args[index++] = a;
return this;
}
public ArgCollectorLocals add(short a) {
args[index++] = a;
return this;
}
public ArgCollectorLocals add(int a) {
args[index++] = a;
return this;
}
public ArgCollectorLocals add(long a) {
args[index++] = a;
return this;
}
public ArgCollectorLocals add(float a) {
args[index++] = a;
return this;
}
public ArgCollectorLocals add(double a) {
args[index++] = a;
return this;
}
public ArgCollectorLocals add(Object a) {
args[index++] = a;
return this;
}
public Object[] toArray() {
return args;
}
}
Thanks to @apangin, I finally solved this problem. Besides the method arguments, I also want to record the instance that the method belongs to (if the method is not static), so I have the following code:
public void pushArgArrToStackInMeth(){
Type[] args = Type.getArgumentTypes(this.selfDesc);
// new ArgumentCollector(N)
String collector = Type.getInternalName(ArgCollectorLocals.class);
mv.visitTypeInsn(NEW, collector);
mv.visitInsn(DUP);
if (isStatic){
mv.visitIntInsn(SIPUSH, args.length);
}else{
mv.visitIntInsn(SIPUSH, args.length + 1); // for this
}
mv.visitMethodInsn(INVOKESPECIAL, collector, "<init>", "(I)V", false);
// record "this" if not static
if (!isStatic){
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, collector, "add", "(Ljava/lang/Object;)L" + collector + ";", false);
}
// for each argument call the corresponding collector.add(arg)
int index = 1;
if (isStatic){
index = 0;
}
for (Type arg : args) {
String argDesc = arg.getDescriptor();
if (argDesc.length() > 1) {
argDesc = "Ljava/lang/Object;";
}
mv.visitVarInsn(arg.getOpcode(ILOAD), index);
mv.visitMethodInsn(INVOKEVIRTUAL, collector, "add", "(" + argDesc + ")L" + collector + ";", false);
index += arg.getSize();
}
// collector.toArray()
mv.visitMethodInsn(INVOKEVIRTUAL, collector, "toArray", "()[Ljava/lang/Object;", false);
}