/*******************************************************************************
* 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.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.javatuples.Pair;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import rubah.Rubah;
import rubah.framework.Clazz;
import rubah.framework.Field;
import rubah.framework.Method;
import rubah.framework.Namespace;
import rubah.framework.Type;
import rubah.runtime.Version;
import sun.misc.Unsafe;
public class AddGettersAndSetters extends RubahTransformer {
public static final String GETTER_PREFFIX = "$GET$";
public static final String SETTER_PREFFIX = "$SET$";
private static final int ACCESS = ACC_PUBLIC | ACC_SYNTHETIC;
private static final Type UNSAFE_TYPE = Type.getType(Unsafe.class);
private static final Type STRING_TYPE = Type.getType(String.class);
private static final Type CLASS_TYPE = Type.getType(Class.class);
private static final Type FIELD_TYPE = Type.getType(java.lang.reflect.Field.class);
private static final Type OBJECT_TYPE = Type.getType(Object.class);
private static final Map<Integer, Pair<String, Type>> putInvocationMap;
static {
putInvocationMap = new HashMap<Integer, Pair<String,Type>>();
putInvocationMap.put(org.objectweb.asm.Type.BOOLEAN,
new Pair<String, Type>("putBoolean", Type.BOOLEAN_TYPE));
putInvocationMap.put(org.objectweb.asm.Type.BYTE,
new Pair<String, Type>("putByte", Type.BYTE_TYPE));
putInvocationMap.put(org.objectweb.asm.Type.CHAR,
new Pair<String, Type>("putChar", Type.CHAR_TYPE));
putInvocationMap.put(org.objectweb.asm.Type.DOUBLE,
new Pair<String, Type>("putDouble", Type.DOUBLE_TYPE));
putInvocationMap.put(org.objectweb.asm.Type.FLOAT,
new Pair<String, Type>("putFloat", Type.FLOAT_TYPE));
putInvocationMap.put(org.objectweb.asm.Type.INT,
new Pair<String, Type>("putInt", Type.INT_TYPE));
putInvocationMap.put(org.objectweb.asm.Type.LONG,
new Pair<String, Type>("putLong", Type.LONG_TYPE));
putInvocationMap.put(org.objectweb.asm.Type.SHORT,
new Pair<String, Type>("putShort", Type.SHORT_TYPE));
putInvocationMap.put(org.objectweb.asm.Type.BOOLEAN,
new Pair<String, Type>("putBoolean", Type.BOOLEAN_TYPE));
}
private HashSet<Field> fields = new HashSet<Field>();
private HashSet<String> foundGettersSetters = new HashSet<String>();
private boolean generateInheritedFields = false;
public static String generateGetterName(Type owner, String fieldName) {
return generateGetterName(null, owner, fieldName);
}
public static String generateGetterName(Version version, Type owner, String fieldName) {
if (version != null) {
String name = version.getOriginalName(owner.getClassName());
if (name != null) {
owner = Type.getObjectType(name.replace('.', '/'));
Clazz ownerClass = version.getNamespace().getClass(owner);
out: while (ownerClass != null) {
for (Field f : ownerClass.getFields()) {
if (f.getName().equals(fieldName)) {
owner = ownerClass.getASMType();
break out;
}
}
ownerClass = ownerClass.getParent();
}
}
}
return GETTER_PREFFIX + owner.getClassName().replaceAll("[^a-zA-Z0-9]", "\\$") + "$" + fieldName;
}
public static String generateSetterName(Type owner, String fieldName) {
return generateSetterName(null, owner, fieldName);
}
public static String generateSetterName(Version version, Type owner, String fieldName) {
if (version != null) {
String name = version.getOriginalName(owner.getClassName());
if (name != null) {
owner = Type.getObjectType(name.replace('.', '/'));
Clazz ownerClass = version.getNamespace().getClass(owner);
out: while (ownerClass != null) {
for (Field f : ownerClass.getFields()) {
if (f.getName().equals(fieldName)) {
owner = ownerClass.getASMType();
break out;
}
}
ownerClass = ownerClass.getParent();
}
}
}
return SETTER_PREFFIX + owner.getClassName().replaceAll("[^a-zA-Z0-9]", "\\$") + "$" + fieldName;
}
public AddGettersAndSetters(Namespace namespace, ClassVisitor visitor) {
super(null, namespace, visitor);
}
public AddGettersAndSetters(HashMap<String, Object> objectsMap, Namespace namespace,
ClassVisitor visitor) {
super(objectsMap, namespace, visitor);
}
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
// Ensure that version >= 1.5
switch (version) {
case V1_1:
case V1_2:
case V1_3:
case V1_4:
version = V1_5;
break;
}
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.isInterface) {
return ret;
}
Field field = null;
if (this.objectsMap != null) {
field = (Field) this.objectsMap.get(name);
}
if (field == null) {
field = new Field(access, name, this.namespace.getClass(Type.getType(desc)), value != null);
}
this.fields.add(field);
return ret;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
Method m = (this.objectsMap != null ? ((Method)this.objectsMap.get(name)) : null);
String realName = (m != null ? m.getName() : name);
if (realName.startsWith(GETTER_PREFFIX) || realName.startsWith(SETTER_PREFFIX)) {
this.foundGettersSetters.add(realName);
}
return super.visitMethod(access, name, desc, signature, exceptions);
}
@Override
public void visitEnd() {
if (this.thisClass.isInterface())
return;
this.generateFields(this.thisClass.getFields(), this.thisClass, true);
Clazz c = this.thisClass.getParent();
while (c != null) {
this.generateFields(c.getFields(), this.thisClass, false);
if (this.generateInheritedFields)
this.generateFields(c.getFields(), c, false);
c = c.getParent();
}
super.visitEnd();
}
private void generateFields(Set<Field> fields, Clazz owner, boolean generatePrivate) {
for (Field field : fields) {
if (field.getName().equals(AddForwardField.FIELD_NAME))
continue;
if (!generatePrivate && Modifier.isPrivate(field.getAccess()))
continue;
boolean isStatic = Modifier.isStatic(field.getAccess());
boolean isFinal = Modifier.isFinal(field.getAccess());
Clazz type = field.getType();
this.generateGetter(isStatic, field.getName(), owner, type);
if (isFinal)
this.generateFinalSetter(isStatic, field.getName(), owner, type);
else
this.generateSetter(isStatic, field.getName(), owner, type);
}
}
private void generateGetter(boolean isStatic, String name, Clazz owner, Clazz type) {
String getterName = generateGetterName(owner.getASMType(), name);
if (this.foundGettersSetters.contains(getterName))
return;
MethodVisitor mv = this.visitMethod(
(isStatic ? (ACCESS | ACC_STATIC) : ACCESS),
getterName,
Type.getMethodDescriptor(type.getASMType()),
null,
null);
mv.visitCode();
if (isStatic) {
mv.visitFieldInsn(
GETSTATIC,
owner.getASMType().getInternalName(),
name,
type.getASMType().getDescriptor());
} else {
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(
GETFIELD,
owner.getASMType().getInternalName(),
name,
type.getASMType().getDescriptor());
}
mv.visitInsn(type.getASMType().getOpcode(IRETURN));
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void generateSetter(boolean isStatic, String name, Clazz owner, Clazz type) {
String setterName = generateSetterName(owner.getASMType(), name);
if (this.foundGettersSetters.contains(setterName))
return;
MethodVisitor mv = this.visitMethod(
(isStatic ? (ACCESS | ACC_STATIC) : ACCESS),
setterName,
Type.getMethodDescriptor(Type.VOID_TYPE, type.getASMType()),
null,
null);
mv.visitCode();
int i = 0;
if (!isStatic)
mv.visitVarInsn(ALOAD, i++);
mv.visitVarInsn(type.getASMType().getOpcode(ILOAD), i);
mv.visitFieldInsn(
(isStatic ? PUTSTATIC : PUTFIELD),
owner.getASMType().getInternalName(),
name,
type.getASMType().getDescriptor());
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Use unsafe to ensure ability to write to final fields
* @param isStatic
* @param name
* @param type
*/
private void generateFinalSetter(boolean isStatic, String name, Clazz owner, Clazz type) {
String setterName = generateSetterName(owner.getASMType(), name);
if (this.foundGettersSetters.contains(setterName))
return;
int fieldLocal = 1 + type.getASMType().getSize();
int unsafeLocal = fieldLocal + 1;
MethodVisitor mv = this.visitMethod(
(isStatic ? (ACCESS | ACC_STATIC) : ACCESS),
setterName,
Type.getMethodDescriptor(Type.VOID_TYPE, type.getASMType()),
null,
null);
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, "java/lang/SecurityException");
Label l3 = new Label();
mv.visitTryCatchBlock(l0, l1, l3, "java/lang/NoSuchFieldException");
mv.visitLabel(l0);
mv.visitLdcInsn(owner.getASMType().getASMType());
mv.visitLdcInsn(name);
mv.visitMethodInsn(
INVOKEVIRTUAL,
CLASS_TYPE.getInternalName(),
"getDeclaredField",
Type.getMethodDescriptor(FIELD_TYPE, STRING_TYPE),
false);
mv.visitVarInsn(ASTORE, fieldLocal);
mv.visitMethodInsn(
INVOKESTATIC,
Type.getType(Rubah.class).getInternalName(),
"getUnsafe",
Type.getMethodDescriptor(UNSAFE_TYPE),
false);
mv.visitVarInsn(ASTORE, unsafeLocal);
mv.visitVarInsn(ALOAD, unsafeLocal);
if (isStatic) {
mv.visitVarInsn(ALOAD, unsafeLocal);
mv.visitVarInsn(ALOAD, fieldLocal);
mv.visitMethodInsn(
INVOKEVIRTUAL,
UNSAFE_TYPE.getInternalName(),
"staticFieldBase",
"(Ljava/lang/reflect/Field;)Ljava/lang/Object;",
false);
mv.visitVarInsn(ALOAD, unsafeLocal);
mv.visitVarInsn(ALOAD, fieldLocal);
mv.visitMethodInsn(
INVOKEVIRTUAL,
UNSAFE_TYPE.getInternalName(),
"staticFieldOffset",
Type.getMethodDescriptor(Type.LONG_TYPE, FIELD_TYPE),
false);
mv.visitVarInsn(type.getASMType().getOpcode(ILOAD), 0);
} else {
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, unsafeLocal);
mv.visitVarInsn(ALOAD, fieldLocal);
mv.visitMethodInsn(
INVOKEVIRTUAL,
UNSAFE_TYPE.getInternalName(),
"objectFieldOffset",
Type.getMethodDescriptor(Type.LONG_TYPE, FIELD_TYPE),
false);
mv.visitVarInsn(type.getASMType().getOpcode(ILOAD), 1);
}
this.generatePutObjectInvocation(mv, type.getASMType());
mv.visitLabel(l1);
Label l6 = new Label();
mv.visitJumpInsn(GOTO, l6);
mv.visitLabel(l2);
mv.visitVarInsn(ASTORE, 2);
mv.visitTypeInsn(NEW, "java/lang/Error");
mv.visitInsn(DUP);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Error", "<init>", "(Ljava/lang/Throwable;)V",false);
mv.visitInsn(ATHROW);
mv.visitLabel(l3);
mv.visitVarInsn(ASTORE, 2);
mv.visitTypeInsn(NEW, "java/lang/Error");
mv.visitInsn(DUP);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Error", "<init>", "(Ljava/lang/Throwable;)V",false);
mv.visitInsn(ATHROW);
mv.visitLabel(l6);
mv.visitInsn(RETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private void generatePutObjectInvocation(MethodVisitor mv, Type type) {
Pair<String, Type> result = putInvocationMap.get(type.getSort());
if (result == null) {
result = new Pair<String, Type>("putObject", OBJECT_TYPE);
}
mv.visitMethodInsn(
INVOKEVIRTUAL,
UNSAFE_TYPE.getInternalName(),
result.getValue0(),
Type.getMethodDescriptor(
Type.VOID_TYPE,
OBJECT_TYPE,
Type.LONG_TYPE,
result.getValue1()),
false);
}
public AddGettersAndSetters setGenerateInheritedFields(boolean generateInheritedFields) {
this.generateInheritedFields = generateInheritedFields;
return this;
}
}