/*******************************************************************************
* Copyright 2014,
* Luis Pina <luis@luispina.me>,
* Michael Hicks <mwh@cs.umd.edu>
*
* This file is part of Rubah.
*
* Rubah 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 3 of the License, or
* (at your option) any later version.
*
* Rubah 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 Rubah. If not, see <http://www.gnu.org/licenses/>.
*******************************************************************************/
package rubah.bytecode.transformers;
import java.lang.ref.Reference;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import org.javatuples.Pair;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.LocalVariablesSorter;
import rubah.Rubah;
import rubah.framework.Field;
import rubah.framework.Namespace;
import rubah.framework.Type;
import rubah.runtime.Version;
import sun.misc.Unsafe;
public class AddTraverseMethod extends RubahTransformer {
private static final String OBJECT_INTERNAL_NAME =
Type.getType(Object.class).getInternalName();
private static final int TRAVERSE_METHOD_ACC_FLAGS = ACC_PUBLIC | ACC_SYNTHETIC;
protected static final String TRAVERSE_METHOD_DESC =
Type.getMethodDescriptor(Type.VOID_TYPE);
public static final String TRAVERSE_METHOD_NAME = "$traverse";
private static final String REGISTER_METHOD_DESC =
Type.getMethodDescriptor(
Type.getType(Object.class),
Type.getType(Object.class),
Type.getType(java.lang.reflect.Field.class));
protected static final String REGISTER_METHOD_SIMPLE_DESC =
Type.getMethodDescriptor(
Type.getType(Object.class),
Type.getType(Object.class));
protected static final String REGISTER_METHOD_NAME = "registerTraversed";
protected static final String REGISTER_METHOD_OWNER_NAME =
Type.getType(Rubah.class).getInternalName();
private static final String[] blackListedPackages = {
// Reflection and unsafe are used in static class initialization
// Must be blacklisted to avoid class loading loops
Method.class.getPackage().getName(),
Unsafe.class.getName(),
// NIO charsets do not hold any interesting reference to updatable state
"java.nio.charset",
// Throwable's backtrace is something handled by native code, don't touch
Throwable.class.getName(),
Rubah.class.getPackage().getName() + ".",
ThreadLocal.class.getName(),
Object.class.getPackage().getName(),
"sun.nio.ch.SocketChannelImpl",
"sun.nio.ch.SocketAdaptor",
"com.sun.proxy",
};
public static boolean isAllowed(String fqn) {
if (fqn.startsWith(Object.class.getName()) || fqn.startsWith(Class.class.getName()) || fqn.startsWith(Reference.class.getPackage().getName()))
return true;
for (String allowedPak : blackListedPackages) {
if (fqn.startsWith(allowedPak)) {
return false;
}
}
return true;
}
private boolean hasParent;
private String parentInternalName;
private List<Pair<String, String>> fields =
new LinkedList<Pair<String,String>>();
protected Version version;
public AddTraverseMethod(HashMap<String, Object> objectsMap, Namespace namespace, ClassVisitor cv) {
super(objectsMap, namespace, cv);
}
public AddTraverseMethod(HashMap<String, Object> objectsMap, Version version, ClassVisitor cv) {
super(objectsMap, version.getNamespace(), cv);
this.version = version;
}
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
this.hasParent = !name.equals(OBJECT_INTERNAL_NAME);
if (this.hasParent) {
this.parentInternalName = superName;
}
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
FieldVisitor ret = super.visitField(access, name, desc, signature, value);
if (this.objectsMap != null) {
Field field = (Field) this.objectsMap.get(name);
if (field == null) {
return ret;
}
name = field.getName();
}
Type fieldType = Type.getType(desc);
if (!this.isInterface && this.isFieldInteresting(access, fieldType)) {
this.fields.add(new Pair<String, String>(name, desc));
}
return ret;
}
protected boolean isFieldInteresting(int access, Type fieldType) {
return !Modifier.isStatic(access) &&
!fieldType.isPrimitive() &&
(!fieldType.isArray() || !fieldType.getElementType().isPrimitive());
}
@Override
public void visitEnd() {
if (!this.isInterface) {
this.generateTraverseMethod();
}
super.visitEnd();
}
protected String getTraverseMethodName() {
return TRAVERSE_METHOD_NAME;
}
protected int getTraverseMethodAccess() {
return TRAVERSE_METHOD_ACC_FLAGS;
}
protected void generateTraverseMethodPreamble(MethodVisitor mv) {
if (this.hasParent) {
if (!isAllowed(this.parentInternalName.replace('/', '.'))) {
return;
}
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(
INVOKESPECIAL,
this.parentInternalName,
TRAVERSE_METHOD_NAME,
TRAVERSE_METHOD_DESC,
false);
}
}
private void generateTraverseMethod() {
LocalVariablesSorter mv = new LocalVariablesSorter(
TRAVERSE_METHOD_ACC_FLAGS,
TRAVERSE_METHOD_DESC,
this.visitMethod(
this.getTraverseMethodAccess(),
this.getTraverseMethodName(),
TRAVERSE_METHOD_DESC,
null,
null));
mv.visitCode();
if (isAllowed(this.thisClass.getFqn())) {
this.generateTraverseMethodPreamble(mv);
if (!this.fields.isEmpty()) {
for (Pair<String, String> field : this.fields) {
this.generateFieldAccess(mv, field);
}
}
}
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
protected void getFieldOwner(MethodVisitor mv) {
mv.visitVarInsn(ALOAD, 0);
}
protected void generateFieldAccess(LocalVariablesSorter mv, Pair<String, String> field) {
Type fieldType = Type.getType(field.getValue1());
if (!isAllowed(fieldType.getClassName())) {
return;
}
this.getFieldOwner(mv);
mv.visitInsn(DUP);
mv.visitFieldInsn(
GETFIELD,
this.thisClass.getASMType().getInternalName(),
field.getValue0(),
field.getValue1());
mv.visitMethodInsn(
INVOKESTATIC,
REGISTER_METHOD_OWNER_NAME,
REGISTER_METHOD_NAME,
REGISTER_METHOD_SIMPLE_DESC,
false);
if (this.version != null) {
mv.visitTypeInsn(CHECKCAST, this.version.eraseUpdatableType(this.namespace.getClass(fieldType)).getASMType().getInternalName());
} else {
mv.visitTypeInsn(CHECKCAST, fieldType.getInternalName());
}
mv.visitFieldInsn(
PUTFIELD,
this.thisClass.getASMType().getInternalName(),
field.getValue0(),
field.getValue1());
}
}