/*
* Minha.pt: middleware testing platform.
* Copyright (c) 2011-2014, Universidade do Minho.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package pt.minha.kernel.instrument;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Method;
public class SyncToMonitorClassVisitor extends ClassVisitor {
public static final String PREFIX = "$sync_";
private String clz;
private boolean hasClinit;
private int access;
private Translation trans;
public SyncToMonitorClassVisitor(ClassVisitor visitor, Translation trans) {
super(Opcodes.ASM5, visitor);
this.trans = trans;
}
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
if (trans.isSynchronized()) {
clz = name;
this.access = access;
if ((access&Opcodes.ACC_INTERFACE)==0) {
trans.getLogger().debug("adding explicit class monitor object");
FieldVisitor fv = visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "_fake_class", "L"+ClassConfig.fake_prefix+"java/lang/Object;", null, null);
fv.visitEnd();
}
}
super.visit(version, access, name, signature, superName, interfaces);
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (trans.isSynchronized()) {
if (name.equals("<clinit>")) {
trans.getLogger().debug("redirecting existing static initializer");
hasClinit = true;
return new ClinitVisitor(super.visitMethod(access, name, desc, signature, exceptions));
}
if ((access & Opcodes.ACC_SYNCHRONIZED) != 0) {
trans.getLogger().debug("wrapping {}{} with {}{}", name, desc, PREFIX, name);
makeStub(access & ~Opcodes.ACC_SYNCHRONIZED, name, desc, signature, exceptions);
return super.visitMethod((access & ~(Opcodes.ACC_SYNCHRONIZED | Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED) | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_PRIVATE), PREFIX+name, desc, signature, exceptions);
}
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
public void visitEnd() {
if (trans.isSynchronized()) {
if (!hasClinit && (access&Opcodes.ACC_INTERFACE)==0) {
mkClinit();
trans.getLogger().debug("adding fake static initializer");
}
}
super.visitEnd();
}
private void mkClinit() {
MethodVisitor mv = visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
mv.visitCode();
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private class ClinitVisitor extends MethodVisitor {
public ClinitVisitor(MethodVisitor arg0) {
super(Opcodes.ASM5, arg0);
}
public void visitCode() {
super.visitCode();
if ((access&Opcodes.ACC_INTERFACE)!=0)
return;
mv.visitTypeInsn(Opcodes.NEW, ClassConfig.fake_prefix+"java/lang/Object");
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, ClassConfig.fake_prefix+"java/lang/Object", "<init>", "()V", false);
mv.visitFieldInsn(Opcodes.PUTSTATIC, clz, "_fake_class", "L"+ClassConfig.fake_prefix+"java/lang/Object;");
}
}
public void makeStub(int access, String name, String desc, String signature, String[] exceptions) {
Method m = new Method(name, desc);
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
mv.visitCode();
Label begin = new Label();
Label pre_invoke = new Label();
Label pos_leave = new Label();
Label in_catch = new Label();
Label pre_rethrow = new Label();
Label end = new Label();
mv.visitTryCatchBlock(pre_invoke, pos_leave, in_catch, null);
mv.visitTryCatchBlock(in_catch, pre_rethrow, in_catch, null);
mv.visitLabel(begin);
int offset;
if ((access&Opcodes.ACC_STATIC)==0) {
mv.visitVarInsn(Opcodes.ALOAD, 0);
offset = 1;
} else {
mv.visitFieldInsn(Opcodes.GETSTATIC, clz, "_fake_class", "L"+ClassConfig.fake_prefix+"java/lang/Object;");
offset = 0;
}
int length = 0;
for(Type t: m.getArgumentTypes())
length += t.getSize();
mv.visitInsn(Opcodes.DUP);
mv.visitVarInsn(Opcodes.ASTORE, offset + length);
mv.visitInsn(Opcodes.MONITORENTER);
mv.visitLabel(pre_invoke);
if ((access&Opcodes.ACC_STATIC)==0)
mv.visitVarInsn(Opcodes.ALOAD, 0);
int i=offset;
for(Type t: m.getArgumentTypes()) {
// t.getOpcode() should work for long and double too... :-(
if (t.getClassName().equals("long"))
mv.visitVarInsn(Opcodes.LLOAD, i);
else if (t.getClassName().equals("double"))
mv.visitVarInsn(Opcodes.DLOAD, i);
else
mv.visitVarInsn(t.getOpcode(Opcodes.ILOAD), i);
i += t.getSize();
}
boolean itf = (access&Opcodes.ACC_INTERFACE)!=0;
if ((access&Opcodes.ACC_STATIC)==0)
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, clz, PREFIX+name, desc, itf);
else
mv.visitMethodInsn(Opcodes.INVOKESTATIC, clz, PREFIX+name, desc, itf);
mv.visitVarInsn(Opcodes.ALOAD, offset + length);
mv.visitInsn(Opcodes.MONITOREXIT);
mv.visitLabel(pos_leave);
if (m.getReturnType().equals(Type.VOID_TYPE))
mv.visitInsn(Opcodes.RETURN);
else
mv.visitInsn(m.getReturnType().getOpcode(Opcodes.IRETURN));
mv.visitLabel(in_catch);
mv.visitVarInsn(Opcodes.ALOAD, offset+length);
mv.visitInsn(Opcodes.MONITOREXIT);
mv.visitLabel(pre_rethrow);
mv.visitInsn(Opcodes.ATHROW);
mv.visitLabel(end);
i=0;
if ((access&Opcodes.ACC_STATIC)==0)
mv.visitLocalVariable("this", "L"+clz+";", null, begin, end, i++);
for(Type t: m.getArgumentTypes())
mv.visitLocalVariable("arg"+i, t.getDescriptor(), null, begin, end, i++);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
}