/*******************************************************************************
* 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.runtime.state.migrator;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import rubah.Rubah;
import rubah.bytecode.transformers.AddForwardField;
import rubah.bytecode.transformers.ProxyGenerator;
import rubah.org.apache.commons.collections.map.HashedMap;
import rubah.runtime.Version;
import sun.misc.Unsafe;
public class UnsafeUtils {
private static final String UNSAFE_FIELD_NAME = "theUnsafe";
private static final long KLASS_OFFSET = 8L;
private static final long HASHCODE_OFFSET = 1L;
private static final UnsafeUtils instance = new UnsafeUtils();
private HashedMap classOffsetsMap = new HashedMap();
private Map<Class<?>, ProxyOffsets> proxyOffsetsMap = new HashMap<>();
private static final Unsafe unsafe;
static {
Class<?> unsafeClass = Unsafe.class;
Field unsafeField;
try {
unsafeField = unsafeClass.getDeclaredField(UNSAFE_FIELD_NAME);
boolean accessible = unsafeField.isAccessible();
unsafeField.setAccessible(true);
unsafe = (Unsafe) unsafeField.get(null);
unsafeField.setAccessible(accessible);
} catch (NoSuchFieldException e) {
throw new Error(e);
} catch (SecurityException e) {
throw new Error(e);
} catch (IllegalArgumentException e) {
throw new Error(e);
} catch (IllegalAccessException e) {
throw new Error(e);
}
}
public static UnsafeUtils getInstance() {
return instance;
}
public static Unsafe getUnsafe() {
return unsafe;
}
private UnsafeUtils() {
// Empty
}
public int getHashCode(Object obj) {
return unsafe.getInt(obj, HASHCODE_OFFSET);
}
public void setHashCode(Object obj, int hashCode) {
unsafe.putInt(obj, HASHCODE_OFFSET, hashCode);
}
public void setHashCode(Object src, Object dest) {
// HashCodes are lazily initalized by the JVM
// This line ensures the object has an hashCode
System.identityHashCode(dest);
// Otherwise, the JVM overwrites whatever the following line writes
unsafe.putInt(dest, HASHCODE_OFFSET, System.identityHashCode(src));
}
public void setOffsets(Collection<Class<?>> classes, Version v1) {
for (Class<?> c : classes) {
setOffsets(c);
String originalName = v1.getOriginalName(c.getName());
if (originalName == null)
continue;
String nextVersionName = v1.getUpdatableName(originalName);
if (nextVersionName.equals(c.getName()))
continue;
try {
setOffsets(Class.forName(nextVersionName, false, Rubah.getLoader()));
} catch (ClassNotFoundException e) {
throw new Error("Should never happen");
}
}
}
public synchronized ClassOffsets setOffsets(Class<?> c) {
if (c.isInterface())
return new ClassOffsets();
ClassOffsets classOffsets = (ClassOffsets) classOffsetsMap.get(c);
if (classOffsets != null)
return classOffsets;
else
classOffsets = new ClassOffsets();
Class<?> parent = c.getSuperclass();
if (parent != null)
classOffsets.offsets.addAll(setOffsets(parent).offsets);
boolean foundStaticBase = false;
for (Field f : c.getDeclaredFields()) {
if (Modifier.isStatic(f.getModifiers()) && !foundStaticBase) {
classOffsets.staticBase = unsafe.staticFieldBase(f);
foundStaticBase = true;
}
if (f.getType().isPrimitive() || (f.getType().isArray() && f.getType().getComponentType().isPrimitive()))
continue;
if (f.getName().equals(AddForwardField.FIELD_NAME))
continue;
if (Modifier.isStatic(f.getModifiers())) {
classOffsets.staticOffsets.add(unsafe.staticFieldOffset(f));
} else {
classOffsets.offsets.add(unsafe.objectFieldOffset(f));
}
}
classOffsetsMap.put(c, classOffsets);
return classOffsets;
}
public ClassOffsets getOffsets(Class<?> c) {
ClassOffsets ret = (ClassOffsets) this.classOffsetsMap.get(c);
if (ret == null)
return setOffsets(c);
return ret;
}
public static class ClassOffsets {
private Object staticBase;
private LinkedList<Long> offsets = new LinkedList<Long>();
private LinkedList<Long> staticOffsets = new LinkedList<Long>();
public Object getStaticBase() {
return staticBase;
}
public LinkedList<Long> getOffsets() {
return offsets;
}
public LinkedList<Long> getStaticOffsets() {
return staticOffsets;
}
}
private HashMap<Class<?>, Object> classToKlass = new HashMap<>();
public void registerClassToKlass(Class<?> c) {
if (classToKlass.containsKey(c))
return;
Object obj;
try {
obj = unsafe.allocateInstance(c);
} catch (InstantiationException e) {
throw new Error(e);
}
int klass = unsafe.getInt(obj, KLASS_OFFSET);
this.classToKlass.put(c, klass);
}
public int getKlass(Object target) {
return unsafe.getInt(target, KLASS_OFFSET);
}
public Object getClassToken (Class<?> c) {
if (c.isArray())
return Array.newInstance(c.getComponentType(), 0);
else
try {
return unsafe.allocateInstance(c);
} catch (InstantiationException e) {
throw new Error(e);
}
}
public void changeClass(Object target, Object fromClassToken, Object toClassToken) {
int oldKlass = unsafe.getInt(fromClassToken, KLASS_OFFSET);
int newKlass = unsafe.getInt(toClassToken, KLASS_OFFSET);
unsafe.compareAndSwapInt(target, KLASS_OFFSET, oldKlass, newKlass);
// unsafe.putInt(target, KLASS_OFFSET, newKlass);
}
public void changeClass(Object target, Object classToken) {
int newKlass = unsafe.getInt(classToken, KLASS_OFFSET);
unsafe.putInt(target, KLASS_OFFSET, newKlass);
}
public void changeClass(Object target, Class<?> newClass) {
try {
Object obj = this.classToKlass.get(newClass);
if (obj == null) {
if (newClass.isArray())
obj = Array.newInstance(newClass.getComponentType(), 0);
else
obj = unsafe.allocateInstance(newClass);
this.classToKlass.put(newClass, obj);
}
int newKlass = unsafe.getInt(obj, KLASS_OFFSET);
unsafe.putInt(target, KLASS_OFFSET, newKlass);
} catch (InstantiationException e) {
throw new Error(e);
}
}
public ProxyOffsets getProxyOffsets(Class<?> proxyClass) {
ProxyOffsets ret = this.proxyOffsetsMap.get(proxyClass);
if (ret == null) {
try {
Field baseField = proxyClass.getDeclaredField(ProxyGenerator.BASE_OBJ_NAME);
Field scaleField = proxyClass.getDeclaredField(ProxyGenerator.SCALE_NAME);
Field proxiedField = proxyClass.getDeclaredField(ProxyGenerator.PROXIED_OBJ_NAME);
Field offsetField = proxyClass.getDeclaredField(ProxyGenerator.OFFSET_NAME);
ret = new ProxyOffsets();
ret.baseOffset = (short) unsafe.objectFieldOffset(baseField);
ret.scaleOffset = (short) unsafe.objectFieldOffset(scaleField);
ret.proxiedOffset = (short) unsafe.objectFieldOffset(proxiedField);
ret.offsetOffset = (short) unsafe.objectFieldOffset(offsetField);
this.proxyOffsetsMap.put(proxyClass, ret);
} catch (NoSuchFieldException e) {
throw new Error(e);
} catch (SecurityException e) {
throw new Error(e);
}
}
return ret;
}
public static class ProxyOffsets {
public short baseOffset;
public short scaleOffset;
public short proxiedOffset;
public short offsetOffset;
}
public static void write(Object owner, long val, long offset) {
unsafe.putLong(owner, offset, val);
}
public static void write(Object owner, double val, long offset) {
unsafe.putDouble(owner, offset, val);
}
}