/*
* Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.vm.hosted;
import java.io.*;
import java.lang.ref.*;
import java.lang.reflect.*;
import java.nio.*;
import java.util.*;
import sun.misc.*;
import com.sun.max.program.*;
import com.sun.max.vm.*;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.jdk.*;
import com.sun.max.vm.jdk.JDK.ClassRef;
import com.sun.max.vm.jdk.JDK.LazyClassRef;
import com.sun.max.vm.layout.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.type.*;
import com.sun.max.vm.value.*;
/**
* This class encapsulates a number of mechanisms for intercepting certain parts of JDK
* state for the purpose of modifying how that state is copied into the boot image.
*/
public final class JDKInterceptor {
private JDKInterceptor() {
}
/**
* These are the properties that are to be remembered (inherited) from the host VM on which the image was built.
* Strictly speaking java vendor.* should not be inherited. However, from JDK 7 onwards all the platforms on which
* Maxine is built have Oracle as the vendor so it is ok. [On Mac OS/JDK 6 Maxine will incorrectly have java.vendor
* set to Apple.]
*
* Evidently several of these properties are sensitive to the environment the VM is running in.
* This isn't important for the typical current uses of Maxine, but should be fixed in due course.
* The problem is that finding all the JDK code that might be baked into the image and depends
* on such a property. For now, it is better to inherit the host VM value, than simply guess it
* on startup, because most users run the VM in the same environment where they build it.
*/
public static final String[] REMEMBERED_PROPERTY_NAMES = {
"awt.toolkit",
"java.specification.version",
"java.specification.name",
"java.class.version",
"java.vendor",
"java.vendor.url",
"java.vendor.url.bug",
"java.runtime.name",
"java.runtime.version",
"java.awt.graphicsenv",
"java.awt.printerjob",
"file.encoding",
"file.encoding.pkg",
"file.separator",
"line.separator",
"path.separator",
"sun.jnu.encoding",
"sun.io.unicode.encoding",
"sun.os.patch.level",
"os.version",
"user.language",
"user.country",
"user.variant"
};
public static final Properties initialSystemProperties = buildInitialSystemProperties();
/**
* Overrides the default VM library path with the specified new path.
* We use this to avoid having to specify command line arguments
* over and over again for each and every JUnit test and other misc. program runs.
*
* @param paths a string representing the new paths to use in looking up native libraries
*/
public static void setLibraryPath(String paths) {
final String[] pathArray = paths.split(File.pathSeparator);
WithoutAccessCheck.setStaticField(ClassLoader.class, "usr_paths", pathArray);
}
private static final Vector<Object> systemNativeLibraries = new Vector<Object>();
static {
Object lib = null;
try {
Class<?> c = JDK.java_lang_ClassLoader$NativeLibrary.javaClass();
Constructor cons = c.getConstructor(Class.class, String.class);
cons.setAccessible(true);
lib = cons.newInstance(MaxineVM.class, "maxvm");
} catch (Exception e) {
throw FatalError.unexpected("Could not construct VM native library", e);
}
systemNativeLibraries.add(lib);
}
/**
* This array contains all the intercepted fields that are either ignored (i.e. set to zero) or
* specially handled when building the prototype.
*/
// Checkstyle: stop
private static final Object[] interceptedFieldArray = {
JDK.java_lang_ApplicationShutdownHooks,
new ValueField("hooks", ReferenceValue.from(new IdentityHashMap<Thread, Thread>())).makeNonFinal(),
JDK.java_lang_Class,
"cachedConstructor",
"newInstanceCallerCache",
"name",
"declaredFields",
"publicFields",
"declaredMethods",
"publicMethods",
"declaredConstructors",
"publicConstructors",
"declaredPublicFields",
"declaredPublicMethods",
"genericInfo",
"enumConstants",
"enumConstantDirectory",
"annotations",
"declaredAnnotations",
"classRedefinedCount",
"lastRedefinedCount",
JDK.java_lang_ClassLoader,
new ZeroField("bootstrapClassPath").makeOptional(),
"scl",
"sclSet",
"usr_paths",
"sys_paths",
new ValueField("loadedLibraryNames", ReferenceValue.from(new Vector())).makeNonFinal(),
new ValueField("systemNativeLibraries", ReferenceValue.from(systemNativeLibraries)).makeNonFinal(),
JDK.java_util_EnumMap,
"entrySet",
JDK.java_lang_reflect_Field,
"genericInfo",
"declaredAnnotations",
"fieldAccessor",
"overrideFieldAccessor",
JDK.java_lang_reflect_Constructor,
"genericInfo",
"declaredAnnotations",
"constructorAccessor",
JDK.java_lang_reflect_Method,
"genericInfo",
"declaredAnnotations",
"methodAccessor",
JDK.java_lang_Package,
"loader",
"packageInfo",
JDK.java_lang_Shutdown,
new NewShutdownHookList("hooks").makeNonFinal(),
JDK.java_lang_System,
new ZeroField("security").makeOptional(),
new ValueField("props", ReferenceValue.from(initialSystemProperties)).makeNonFinal(),
JDK.java_lang_ref_Reference,
"discovered",
"pending",
JDK.java_lang_ref_Finalizer,
"unfinalized",
new ValueField("queue", ReferenceValue.from(new ReferenceQueue())).makeNonFinal(),
JDK.java_lang_Throwable,
new ZeroField("backtrace").makeOptional(),
JDK.java_lang_Thread,
"parkBlocker",
"blocker",
"threadLocals",
"inheritableThreadLocals",
JDK.java_lang_ProcessEnvironment,
new ZeroField("theEnvironment").makeNonFinal(),
new ZeroField("theUnmodifiableEnvironment").makeNonFinal(),
JDK.java_lang_Terminator,
"handler",
JDK.sun_misc_VM,
"booted",
"finalRefCount",
"peakFinalRefCount",
JDK.sun_net_www_protocol_jar_JarFileFactory,
new ValueField("fileCache", ReferenceValue.from(new HashMap())).makeNonFinal(),
new ValueField("urlCache", ReferenceValue.from(new HashMap())).makeNonFinal(),
JDK.sun_reflect_ConstantPool,
new ZeroField("constantPoolOop").makeOptional(),
JDK.sun_reflect_Reflection,
new ValueField("fieldFilterMap", ReferenceValue.from(new HashMap<Class, String[]>())).makeOptional(),
new ValueField("methodFilterMap", ReferenceValue.from(new HashMap<Class, String[]>())).makeOptional(),
JDK.sun_util_calendar_ZoneInfo,
new ZeroField("aliasTable").makeNonFinal(),
JDK.sun_security_jca_ProviderConfig,
new ValueField("LOCK", ReferenceValue.from(new Object())).makeOptional(),
JDK.java_util_Random,
new FieldOffsetRecomputation("seedOffset", "seed"),
JDK.java_util_concurrent_ConcurrentSkipListSet,
new FieldOffsetRecomputation("mapOffset", "m"),
JDK.java_util_concurrent_ConcurrentLinkedQueue,
new FieldOffsetRecomputation("headOffset", "head"),
new FieldOffsetRecomputation("tailOffset", "tail"),
JDK.java_util_concurrent_CopyOnWriteArrayList,
new FieldOffsetRecomputation("lockOffset", "lock"),
JDK.java_nio_DirectByteBuffer,
new ArrayBaseOffsetRecomputation("arrayBaseOffset", byte[].class),
JDK.java_nio_DirectCharBufferS,
new ArrayBaseOffsetRecomputation("arrayBaseOffset", char[].class),
JDK.java_nio_DirectCharBufferU,
new ArrayBaseOffsetRecomputation("arrayBaseOffset", char[].class),
JDK.java_nio_DirectDoubleBufferS,
new ArrayBaseOffsetRecomputation("arrayBaseOffset", double[].class),
JDK.java_nio_DirectDoubleBufferU,
new ArrayBaseOffsetRecomputation("arrayBaseOffset", double[].class),
JDK.java_nio_DirectFloatBufferS,
new ArrayBaseOffsetRecomputation("arrayBaseOffset", float[].class),
JDK.java_nio_DirectFloatBufferU,
new ArrayBaseOffsetRecomputation("arrayBaseOffset", float[].class),
JDK.java_nio_DirectIntBufferS,
new ArrayBaseOffsetRecomputation("arrayBaseOffset", int[].class),
JDK.java_nio_DirectIntBufferU,
new ArrayBaseOffsetRecomputation("arrayBaseOffset", int[].class),
JDK.java_nio_DirectLongBufferS,
new ArrayBaseOffsetRecomputation("arrayBaseOffset", long[].class),
JDK.java_nio_DirectLongBufferU,
new ArrayBaseOffsetRecomputation("arrayBaseOffset", long[].class),
JDK.java_nio_DirectShortBufferS,
new ArrayBaseOffsetRecomputation("arrayBaseOffset", short[].class),
JDK.java_nio_DirectShortBufferU,
new ArrayBaseOffsetRecomputation("arrayBaseOffset", short[].class),
JDK.java_nio_charset_CharsetEncoder,
"cachedDecoder",
JDK.java_util_concurrent_atomic_AtomicBoolean,
new FieldOffsetRecomputation("valueOffset", "value"),
JDK.java_util_concurrent_atomic_AtomicInteger,
new FieldOffsetRecomputation("valueOffset", "value"),
JDK.java_util_concurrent_atomic_AtomicLong,
new FieldOffsetRecomputation("valueOffset", "value"),
JDK.java_util_concurrent_atomic_AtomicReference,
new FieldOffsetRecomputation("valueOffset", "value"),
JDK.java_util_concurrent_atomic_AtomicIntegerArray,
new ArrayBaseOffsetRecomputation("base", int[].class),
JDK.java_util_concurrent_atomic_AtomicLongArray,
new ArrayBaseOffsetRecomputation("base", long[].class),
JDK.java_util_concurrent_atomic_AtomicReferenceArray,
new ArrayBaseOffsetRecomputation("base", Object[].class),
JDK.java_util_concurrent_atomic_AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl,
new AtomicFieldUpdaterOffsetRecomputation("offset"),
JDK.java_util_concurrent_atomic_AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl,
new AtomicFieldUpdaterOffsetRecomputation("offset"),
JDK.java_util_concurrent_atomic_AtomicLongFieldUpdater$CASUpdater,
new AtomicFieldUpdaterOffsetRecomputation("offset"),
JDK.java_util_concurrent_atomic_AtomicLongFieldUpdater$LockedUpdater,
new AtomicFieldUpdaterOffsetRecomputation("offset"),
JDK.java_io_UnixFileSystem,
new ExpiringCacheField("cache"),
new ExpiringCacheField("javaHomePrefixCache"),
JDK.java_util_concurrent_locks_AbstractQueuedSynchronizer,
new FieldOffsetRecomputation("stateOffset", "state"),
new FieldOffsetRecomputation("headOffset", "head"),
new FieldOffsetRecomputation("tailOffset", "tail"),
new FieldOffsetRecomputation("waitStatusOffset", JDK.java_util_concurrent_locks_AbstractQueuedSynchronizer$Node, "waitStatus"),
new FieldOffsetRecomputation("nextOffset", JDK.java_util_concurrent_locks_AbstractQueuedSynchronizer$Node, "next"),
JDK.java_util_concurrent_locks_AbstractQueuedLongSynchronizer,
new FieldOffsetRecomputation("stateOffset", "state"),
new FieldOffsetRecomputation("headOffset", "head"),
new FieldOffsetRecomputation("tailOffset", "tail"),
new FieldOffsetRecomputation("waitStatusOffset", JDK.java_util_concurrent_locks_AbstractQueuedLongSynchronizer$Node, "waitStatus"),
new FieldOffsetRecomputation("nextOffset", JDK.java_util_concurrent_locks_AbstractQueuedLongSynchronizer$Node, "next"),
JDK.java_util_concurrent_locks_LockSupport,
new FieldOffsetRecomputation("parkBlockerOffset", JDK.java_lang_Thread, "parkBlocker"),
JDK.java_math_BigInteger,
new FieldOffsetRecomputation("signumOffset", "signum"),
new FieldOffsetRecomputation("magOffset", "mag"),
};
private static final Object[] interceptedFieldArrayJDK6 = {
JDK.sun_misc_Launcher,
"bootstrapClassPath",
JDK.java_util_concurrent_atomic_AtomicIntegerArray,
new ArrayIndexScaleRecomputation("scale", int[].class),
JDK.java_util_concurrent_atomic_AtomicLongArray,
new ArrayIndexScaleRecomputation("scale", long[].class),
JDK.java_util_concurrent_atomic_AtomicReferenceArray,
new ArrayIndexScaleRecomputation("scale", Object[].class).makeOptional(),
};
private static final Object[] interceptedFieldArrayJDK7 = {
JDK.java_util_concurrent_atomic_AtomicIntegerArray,
new ArrayIndexScaleShiftRecomputation("shift", int[].class),
JDK.java_util_concurrent_atomic_AtomicLongArray,
new ArrayIndexScaleShiftRecomputation("shift", long[].class),
JDK.java_util_concurrent_atomic_AtomicReferenceArray,
new ArrayIndexScaleShiftRecomputation("shift", Object[].class),
JDK.sun_misc_Unsafe,
new ArrayBaseOffsetRecomputation("ARRAY_BOOLEAN_BASE_OFFSET", boolean[].class),
new ArrayBaseOffsetRecomputation("ARRAY_BYTE_BASE_OFFSET", byte[].class),
new ArrayBaseOffsetRecomputation("ARRAY_SHORT_BASE_OFFSET", short[].class),
new ArrayBaseOffsetRecomputation("ARRAY_CHAR_BASE_OFFSET", char[].class),
new ArrayBaseOffsetRecomputation("ARRAY_INT_BASE_OFFSET", int[].class),
new ArrayBaseOffsetRecomputation("ARRAY_LONG_BASE_OFFSET", long[].class),
new ArrayBaseOffsetRecomputation("ARRAY_FLOAT_BASE_OFFSET", float[].class),
new ArrayBaseOffsetRecomputation("ARRAY_DOUBLE_BASE_OFFSET", double[].class),
new ArrayBaseOffsetRecomputation("ARRAY_OBJECT_BASE_OFFSET", Object[].class),
new ArrayIndexScaleRecomputation("ARRAY_BOOLEAN_INDEX_SCALE", boolean[].class),
new ArrayIndexScaleRecomputation("ARRAY_BYTE_INDEX_SCALE", byte[].class),
new ArrayIndexScaleRecomputation("ARRAY_SHORT_INDEX_SCALE", short[].class),
new ArrayIndexScaleRecomputation("ARRAY_CHAR_INDEX_SCALE", char[].class),
new ArrayIndexScaleRecomputation("ARRAY_INT_INDEX_SCALE", int[].class),
new ArrayIndexScaleRecomputation("ARRAY_LONG_INDEX_SCALE", long[].class),
new ArrayIndexScaleRecomputation("ARRAY_FLOAT_INDEX_SCALE", float[].class),
new ArrayIndexScaleRecomputation("ARRAY_DOUBLE_INDEX_SCALE", double[].class),
new ArrayIndexScaleRecomputation("ARRAY_OBJECT_INDEX_SCALE", Object[].class),
JDK.java_util_concurrent_ConcurrentHashMap,
new ArrayBaseOffsetRecomputation("TBASE", Object[].class),
new ArrayIndexScaleShiftRecomputation("TSHIFT", Object[].class),
new ArrayBaseOffsetRecomputation("SBASE", Object[].class),
new ArrayIndexScaleShiftRecomputation("SSHIFT", Object[].class),
JDK.java_util_concurrent_ConcurrentHashMap$HashEntry,
new FieldOffsetRecomputation("nextOffset", "next"),
JDK.java_util_concurrent_ForkJoinPool,
new ArrayBaseOffsetRecomputation("ABASE", Object[].class),
new ArrayIndexScaleShiftRecomputation("ASHIFT", Object[].class),
new FieldOffsetRecomputation("ctlOffset", "ctl"),
new FieldOffsetRecomputation("stealCountOffset", "stealCount"),
new FieldOffsetRecomputation("blockedCountOffset", "blockedCount"),
new FieldOffsetRecomputation("quiescerCountOffset", "quiescerCount"),
new FieldOffsetRecomputation("scanGuardOffset", "scanGuard"),
new FieldOffsetRecomputation("nextWorkerNumberOffset", "nextWorkerNumber"),
JDK.java_util_concurrent_ForkJoinWorkerThread,
new ArrayBaseOffsetRecomputation("ABASE", Object[].class),
new ArrayIndexScaleShiftRecomputation("ASHIFT", Object[].class),
JDK.java_util_concurrent_ForkJoinTask,
new FieldOffsetRecomputation("statusOffset", "status"),
JDK.java_util_concurrent_SynchronousQueue$TransferStack,
new FieldOffsetRecomputation("headOffset", "head"),
JDK.java_util_concurrent_SynchronousQueue$TransferStack$SNode,
new FieldOffsetRecomputation("matchOffset", "match"),
new FieldOffsetRecomputation("nextOffset", "next"),
JDK.java_util_concurrent_SynchronousQueue$TransferQueue,
new FieldOffsetRecomputation("headOffset", "head"),
new FieldOffsetRecomputation("tailOffset", "tail"),
new FieldOffsetRecomputation("cleanMeOffset", "cleanMe"),
JDK.java_util_concurrent_SynchronousQueue$TransferQueue$QNode,
new FieldOffsetRecomputation("itemOffset", "item"),
new FieldOffsetRecomputation("nextOffset", "next"),
JDK.java_util_concurrent_atomic_AtomicStampedReference,
new FieldOffsetRecomputation("pairOffset", "pair"),
JDK.java_util_concurrent_atomic_AtomicMarkableReference,
new FieldOffsetRecomputation("pairOffset", "pair"),
JDK.sun_misc_PerfCounter,
new ValueField("lb", ReferenceValue.from(LongBuffer.allocate(1))).makeNonFinal(),
JDK.sun_misc_ProxyGenerator,
new ValueField("saveGeneratedFiles", BooleanValue.from(false)).makeNonFinal(),
JDK.java_lang_invoke_MethodType,
new FieldOffsetRecomputation("ptypesOffset", "ptypes"),
new FieldOffsetRecomputation("rtypeOffset", "rtype"),
// The following fields have been added in JDK 7 update 6
JDK.java_util_Hashtable,
new FieldOffsetRecomputation("HASHSEED_OFFSET", "hashSeed").makeOptional(),
JDK.java_util_HashMap_Holder,
new FieldOffsetRecomputation("HASHSEED_OFFSET", JDK.java_util_HashMap, "hashSeed").makeOptional(),
};
// Checkstyle: resume
private static final Map<String, Map<String, InterceptedField>> interceptedFieldMap = buildInterceptedFieldMap();
/**
* Checks whether the specified field should be omitted.
*
* @param field the field to check
* @return {@code true} if the field should be omitted
*/
public static boolean isOmittedField(Field field) {
return field.getName().startsWith("$SWITCH_TABLE") || getInterceptedField(field) instanceof ZeroField;
}
public static InterceptedField getInterceptedField(FieldActor fieldActor) {
return getInterceptedField(fieldActor.holder().name.toString(), fieldActor.name.toString());
}
public static InterceptedField getInterceptedField(Field field) {
return getInterceptedField(field.getDeclaringClass().getName(), field.getName());
}
private static InterceptedField getInterceptedField(String className, String fieldName) {
final Map<String, InterceptedField> map = interceptedFieldMap.get(className);
if (map != null) {
return map.get(fieldName);
}
if (UnsafeUsageChecker.isClassUsingUnsafe(className)) {
throw ProgramError.unexpected("class is using Unsafe operations to get field or array offsets, but no field interceptor present: " + className);
}
return null;
}
public static boolean hasMutabilityOverride(FieldActor fieldActor) {
InterceptedField f = getInterceptedField(fieldActor);
return f != null && f.mutabilityOverride;
}
private static Map<String, Map<String, InterceptedField>> buildInterceptedFieldMap() {
Map<String, Map<String, InterceptedField>> map = new HashMap<String, Map<String, InterceptedField>>();
fillInterceptedFieldMap(map, interceptedFieldArray);
if (JDK.JDK_VERSION == JDK.JDK_6) {
fillInterceptedFieldMap(map, interceptedFieldArrayJDK6);
}
if (JDK.JDK_VERSION == JDK.JDK_7) {
fillInterceptedFieldMap(map, interceptedFieldArrayJDK7);
}
return map;
}
/**
* Builds a map that stores the intercepted fields for each class.
* @param specification an array of objects consisting of a ClassRef followed by a non-empty sequence of either
* String objects or InterceptedField objects.
*/
private static void fillInterceptedFieldMap(Map<String, Map<String, InterceptedField>> map, Object[] specification) {
int i = 0;
for (; i < specification.length; i++) {
final Object object = specification[i];
if (object instanceof ClassRef) {
// we found a classref, add it and its intercepted fields to the map
ClassRef holder = (ClassRef) object;
if (holder.javaClass() == null) {
// Optional/platform dependent class: skips its field specs
while (i + 1 < specification.length && !(specification[i + 1] instanceof ClassRef)) {
i++;
}
continue;
}
Map<String, InterceptedField> fieldMap = map.get(holder.className());
if (fieldMap == null) {
fieldMap = new HashMap<String, InterceptedField>();
map.put(holder.className(), fieldMap);
}
// add all the subsequent field entries to the map
for (++i; i < specification.length; i++) {
Object field = specification[i];
InterceptedField interceptedField;
if (field instanceof ClassRef) {
i--;
break;
} else if (field instanceof String) {
interceptedField = new ZeroField((String) field);
} else {
interceptedField = (InterceptedField) field;
}
if (interceptedField.classRef == null) {
interceptedField.classRef = holder;
}
interceptedField.verify(holder);
fieldMap.put(interceptedField.getName(), interceptedField);
}
} else {
throw ProgramError.unexpected("format of intercepted field array is wrong");
}
}
}
/**
* Register a field of a class to be reset while bootstrapping.
*
* @param fieldName the name of the field as a string
*/
public static void resetField(String className, String fieldName) {
Map<String, InterceptedField> fieldMap = interceptedFieldMap.get(className);
if (fieldMap == null) {
fieldMap = new HashMap<String, InterceptedField>();
interceptedFieldMap.put(className, fieldMap);
}
ZeroField zeroField = new ZeroField(fieldName);
zeroField.makeNonFinal();
zeroField.verify(new LazyClassRef(className));
fieldMap.put(fieldName, zeroField);
}
private static Properties buildInitialSystemProperties() {
final Properties properties = new Properties();
for (String p : REMEMBERED_PROPERTY_NAMES) {
final String value = System.getProperty(p);
if (value != null) {
properties.setProperty(p, value);
}
}
return properties;
}
/**
* A mechanism for special-casing the value of a field from a JDK class that is written into
* the boot image. All other fields of JDK classes simply have their current value (read via
* reflection) written to the boot image.
*/
public abstract static class InterceptedField {
private final String name;
protected ClassRef classRef;
protected boolean verifyFieldExists = true;
public FieldActor fieldActor;
private boolean mutabilityOverride = false;
InterceptedField(String name) {
this.name = name;
}
public InterceptedField makeNonFinal() {
mutabilityOverride = true;
return this;
}
public InterceptedField makeOptional() {
verifyFieldExists = false;
return this;
}
public String getName() {
return name;
}
/**
* Gets the value of the field represented by this object that is to be written to the boot image.
*
* @param object an object of the class in which this field is defined
* @param field the field for which a value is being requested.
*/
public abstract Value getValue(Object object, FieldActor field);
/**
* Determines if this field is mutable.
*
* @return whether the value of this field can be modified in the VM
*/
boolean isMutable() {
return !fieldActor.isConstant() || mutabilityOverride;
}
protected void verify(ClassRef holder) {
if (verifyFieldExists) {
ensureFieldExists(holder, name);
}
}
protected static void ensureFieldExists(ClassRef holder, String fieldName) {
try {
holder.javaClass().getDeclaredField(fieldName);
} catch (NoSuchFieldException ex) {
// Some fields are hidden from reflection. In order to avoid false positives, these fields
// have to set the flag verifyFieldExists to false.
throw ProgramError.unexpected("Class " + holder.className() + " does not declare field " + fieldName);
}
}
}
/**
* An intercepted field whose boot image value is fixed to a value given in the
* {@linkplain ValueField#ValueField(String, Value) constructor}.
*/
private static class ValueField extends InterceptedField {
private final Value value;
ValueField(String name, Value value) {
super(name);
this.value = value;
}
@Override
public Value getValue(Object object, FieldActor field) {
return value;
}
}
/**
* An intercepted field whose boot image value is the {@linkplain Kind#zeroValue() zero value}
* corresponding to the field's kind.
*/
public static class ZeroField extends InterceptedField {
ZeroField(String name) {
super(name);
}
@Override
public Value getValue(Object object, FieldActor field) {
return field.kind.zeroValue();
}
}
private static class AtomicFieldUpdaterOffsetRecomputation extends InterceptedField {
AtomicFieldUpdaterOffsetRecomputation(String name) {
super(name);
}
@Override
public Value getValue(Object object, FieldActor fieldActor) {
final Field field = fieldActor.toJava();
assert !Modifier.isStatic(field.getModifiers());
try {
/*
* Explanation: java.util.concurrent is full of objects and classes that cache the offset
* of particular fields in the JDK. Here, Atomic<X>FieldUpdater implementation objects cache
* the offset of some specified field. Which field? Oh...only Doug Lea knows that. We have to
* search the declaring class for a field that has the same "unsafe" offset as the cached
* offset in this atomic updater object.
*/
final Field tclassField = field.getDeclaringClass().getDeclaredField("tclass");
tclassField.setAccessible(true);
final Class tclass = (Class) tclassField.get(object);
final Field offsetField = fieldActor.toJava();
offsetField.setAccessible(true);
final long offset = offsetField.getLong(object);
// search the declared fields for a field with a matching offset
for (Field f : tclass.getDeclaredFields()) {
if ((f.getModifiers() & Modifier.STATIC) == 0) {
final long fieldOffset = WithoutAccessCheck.unsafe.objectFieldOffset(f);
if (fieldOffset == offset) {
return LongValue.from(FieldActor.fromJava(f).offset());
}
}
}
throw ProgramError.unexpected("unknown atomic field updater of class: " + tclass + ", offset = " + offset);
} catch (Exception e) {
throw ProgramError.unexpected(e);
}
}
}
private static class ExpiringCacheField extends InterceptedField {
private final Map<Object, Object> newValues = new IdentityHashMap<Object, Object>();
ExpiringCacheField(String name) {
super(name);
}
@Override
public Value getValue(Object object, FieldActor fieldActor) {
Object result = newValues.get(object);
if (result == null) {
result = WithoutAccessCheck.newInstance(JDK.java_io_ExpiringCache.javaClass());
newValues.put(object, result);
}
return ReferenceValue.from(result);
}
}
/**
* An intercepted field whose boot image value is the {@linkplain FieldActor#offset() offset} of another field.
* This facility is required to fix up field values obtained via {@link Unsafe#fieldOffset(Field)}.
*/
private static class FieldOffsetRecomputation extends InterceptedField {
private final String fieldName;
FieldOffsetRecomputation(String offsetFieldName, String fieldName) {
this(offsetFieldName, null, fieldName);
}
FieldOffsetRecomputation(String offsetFieldName, ClassRef classRef, String fieldName) {
super(offsetFieldName);
this.fieldName = fieldName;
this.classRef = classRef;
}
@Override
public Value getValue(Object object, FieldActor fieldActor) {
try {
final Field field = classRef.javaClass().getDeclaredField(fieldName);
return LongValue.from(FieldActor.fromJava(field).offset());
} catch (SecurityException e) {
throw ProgramError.unexpected(e);
} catch (NoSuchFieldException e) {
throw ProgramError.unexpected(e);
}
}
@Override
protected void verify(ClassRef holder) {
super.verify(holder);
if (verifyFieldExists) {
ensureFieldExists(classRef, fieldName);
}
}
}
/**
* An intercepted field whose boot image value is the offset of
* the first element in an array from the array's origin.
* This facility is required to fix up field values obtained via {@link Unsafe#arrayBaseOffset(Class)}.
*/
private static class ArrayBaseOffsetRecomputation extends InterceptedField {
private final Class arrayClass;
ArrayBaseOffsetRecomputation(String arrayBaseOffsetFieldName, Class arrayClass) {
super(arrayBaseOffsetFieldName);
this.arrayClass = arrayClass;
}
@Override
public Value getValue(Object object, FieldActor fieldActor) {
ArrayLayout arrayLayout = (ArrayLayout) ClassActor.fromJava(arrayClass).dynamicHub().specificLayout;
return fieldActor.kind.convert(IntValue.from(arrayLayout.getElementOffsetFromOrigin(0).toInt()));
}
}
/**
* An intercepted field whose boot image value is the scale factor for addressing elements in an array.
* This facility is required to fix up field values obtained via {@link Unsafe#arrayIndexScale(Class)}.
*/
private static class ArrayIndexScaleRecomputation extends InterceptedField {
private final Class arrayClass;
ArrayIndexScaleRecomputation(String arrayIndexScaleFieldName, Class arrayClass) {
super(arrayIndexScaleFieldName);
this.arrayClass = arrayClass;
}
@Override
public Value getValue(Object object, FieldActor fieldActor) {
return IntValue.from(ClassActor.fromJava(arrayClass).componentClassActor().kind.width.numberOfBytes);
}
}
/**
* An intercepted field whose boot image value is the scale factor for addressing elements in an array,
* converted to be useful in a shift operation using the log2.
* This facility is required to fix up field values obtained via {@link Unsafe#arrayIndexScale(Class)}.
*/
private static class ArrayIndexScaleShiftRecomputation extends ArrayIndexScaleRecomputation {
ArrayIndexScaleShiftRecomputation(String arrayIndexScaleFieldName, Class arrayClass) {
super(arrayIndexScaleFieldName, arrayClass);
}
@Override
public Value getValue(Object object, FieldActor fieldActor) {
int scale = super.getValue(object, fieldActor).asInt();
// The following code is taken from the static initializer of ConcurrentHashMap in JDK 7
if ((scale & (scale - 1)) != 0) {
throw new Error("data type scale not a power of two");
}
return IntValue.from(31 - Integer.numberOfLeadingZeros(scale));
}
}
/**
* At some point the JDK changed java.lang.Shutdown:hooks from type ArrayList to a Runnable[].
* Detect which is the case and reallocate as necessary.
*/
private static class NewShutdownHookList extends InterceptedField {
private Object result;
NewShutdownHookList(String fieldName) {
super(fieldName);
}
@Override
public Value getValue(Object object, FieldActor fieldActor) {
try {
if (result == null) {
Field field = classRef.javaClass().getDeclaredField(getName());
if (field.getType() == Runnable[].class) {
// allocate a new array
// the size of the array is controlled by a private static final field
Field sizeField = classRef.javaClass().getDeclaredField("MAX_SYSTEM_HOOKS");
sizeField.setAccessible(true);
int size = sizeField.getInt(null);
result = new Runnable[size];
} else {
// allocate a new array list
result = new ArrayList<Runnable>();
}
}
} catch (SecurityException e) {
throw ProgramError.unexpected(e);
} catch (NoSuchFieldException e) {
throw ProgramError.unexpected(e);
} catch (IllegalAccessException e) {
throw ProgramError.unexpected(e);
}
return ReferenceValue.from(result);
}
}
}