/* * 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.tele.object; import java.lang.reflect.*; import java.util.*; import com.sun.max.*; import com.sun.max.annotate.*; import com.sun.max.jdwp.vm.proxy.*; import com.sun.max.program.*; import com.sun.max.tele.*; import com.sun.max.tele.memory.*; import com.sun.max.tele.reference.*; import com.sun.max.tele.util.*; import com.sun.max.unsafe.*; import com.sun.max.vm.actor.*; import com.sun.max.vm.actor.holder.*; import com.sun.max.vm.actor.member.*; import com.sun.max.vm.layout.*; import com.sun.max.vm.layout.Layout.HeaderField; import com.sun.max.vm.type.*; import com.sun.max.vm.value.*; /** * A canonical surrogate for a heap object in the VM. * * This class and its subclasses play the role of typed wrappers for {@link RemoteReference}s that refer to heap objects in * the VM. These wrappers encapsulate implementation details needed for working remotely with VM objects. This is not a * general proxy mechanisms, but rather an encapsulation of the design knowledge about the VM that is needed make sense * of VM state for debugging and visualization purposes. * <p> * The type hierarchy of {@link TeleObject} classes is designed as a <em>projection</em> (in the mathematical sense) * of the representational type hierarchy in the VM. This hierarchy includes standard Java object types, the Maxine * non-Java extended types under {@link TeleHybridObject}, and the special tuple {@link TeleStaticTuple}, which has no * type at all, even in the Maxine extended type system. * <p> * There exist {@link TeleObject} subclasses designed to represent specific object types in the VM for those types that * represent significant information about the runtime state of the VM, for example {@link TeleDynamicHub}. In those * cases there are typically specialized methods for accessing and reasoning about that state information. Other * subclasses are intended to manage usefully a whole subset of the type hierarchy; for example a * {@link TeleArrayObject} is used to represent any kind of array in the VM heap and a {@link TeleTupleObject} is used * to represent ordinary objects that are not called out for special treatment. * <p> * Note that instances of this class are to be created <strong>only</strong> by a reflection-driven * {@linkplain TeleObjectFactory factory}. That declaratively specified factory creates an instance of the most specific * subtype of {@link TeleObject} that applies to a VM object. * <p> * Each subclass implementation is expected to either avoid caching any values read from the VM, or to override * {@link #updateCache(long)} and refresh the cache(s) when that method is called. * <p> * There is an ongoing danger of circularity in the creation of {@link TeleObject} instances for VM objects that have * two-way (or circular) references to other objects; this can lead to infinite regress and stack overflow. The general * strategy for avoiding this is to keep the constructors for concrete subclasses as simple as possible and to avoid * following {@link RemoteReference} fields in constructors if at all possible. A troublesome example of such relationships * involves {@link TeleClassMethodActor}s and {@link TeleTargetMethod}s. * * @see TeleObjectFactory * @see VmObjectAccess */ public abstract class TeleObject extends AbstractVmHolder implements TeleVMCache, ObjectProvider, MaxObject { /** * Identification for the three low-level Maxine heap objects implementations upon which all objects are * implemented. * * @see com.sun.max.vm.object.ObjectAccess */ public enum ObjectKind { /** * A Maxine implementation object that represents a Java array. * * @see com.sun.max.vm.object.ArrayAccess */ ARRAY, /** * A Maxine implementation object that represents a Java object instance: a collection of name/value pairs. * * @see com.sun.max.vm.object.TupleAccess */ TUPLE, /** * A special Maxine implementation object used to implement {@link Hub}s. These represent special objects that * cannot be described in ordinary Java; they have both fields (as in an object instance) and a collection of * specialized arrays. * * @see com.sun.max.vm.object.Hybrid */ HYBRID; } /** * A simple class for aggregating lazily printed statistics, represented as a sequence of objects to be converted to * comma separate strings when actually printed. * */ protected final class StatsPrinter { private final List<Object> statsPrinters = new ArrayList<Object>(10); /** * Adds a statistic to be reported. * * @param obj an object whose string value will be printed * @return a new printer containing nothing to be printed. */ public StatsPrinter addStat(Object obj) { statsPrinters.add(obj == null ? "<unitialized stats>" : obj); return this; } @Override public String toString() { final int size = statsPrinters.size(); if (size == 0) { return ""; } StringBuilder sb = new StringBuilder(50); for (int index = 0; index < size; index++) { sb.append(statsPrinters.get(index).toString()); if (index < size - 1) { sb.append(", "); } } return sb.toString(); } } /** * Default tracing for individual object cache updates, which would generate a tremendous amount of trace output * during each update cycle. Consider overriding for specific subclasses. */ private static final int UPDATE_TRACE_VALUE = 3; /** * Controls tracing for deep object copying. */ protected static final int COPY_TRACE_VALUE = 2; private TimedTrace updateTracer = null; private long lastUpdateEpoch = -1L; private final RemoteReference reference; private final SpecificLayout specificLayout; private final long oid; /** * Cache for Hub. */ private TeleHub teleHub = null; /** * Creates a "surrogate" object that encapsulates information about an object in the VM. * <p> * This is not the same thing as a Proxy, although it can be used that way. Specific subclasses encapsulate design * information about the structure of the VM that enable useful access methods beyond simple field or element * access. Most important are the subclasses for {@link Actor}s, objects in the VM that encapsulate meta-information * about the language and object representation. * <p> * The factory method {@link TeleObjectFactory#make(RemoteReference)} ensures synchronized TeleObjects creation, and * instances should <em>only</em> be created via that factory. * <p> * It is important to avoid in the constructor (and in the constructors of subclasses) the following of * {@link RemoteReference} fields, which leads to the creation of another instance of {@link TeleObject}. This can lead to * infinite regress in the presence of mutually referential objects. * * @param vm the VM in which the object resides * @param reference the location of the object in the VM (whose absolute address can change via GC) * @param specificLayout information about the layout of information in the object */ protected TeleObject(TeleVM vm, RemoteReference reference, SpecificLayout specificLayout) { super(vm); assert reference != null; this.reference = reference; this.specificLayout = specificLayout; oid = this.reference.makeOID(); } private TimedTrace tracer() { if (updateTracer == null) { updateTracer = new TimedTrace(UPDATE_TRACE_VALUE, tracePrefix() + " updating"); } return updateTracer; } /** * {@inheritDoc} * <p> * <strong>Note</strong>: this update gets called automatically as part of {@link TeleObject} instance creation. * * @see TeleObjectFactory#make(RemoteReference) */ public final void updateCache(long epoch) { // TODO (mlvdv) restore thread-lock assertion here for all updates?? final StatsPrinter statsPrinter = new StatsPrinter(); if (status().isNotDead()) { // Do some specialized tracing here, since there are subclasses that we // want to contribute to the tracing statistics, and since we want to // selectively trace certain subclasses. if (epoch > lastUpdateEpoch) { tracer().begin(getObjectUpdateTraceValue(epoch)); if (updateObjectCache(epoch, statsPrinter)) { lastUpdateEpoch = epoch; } tracer().end(getObjectUpdateTraceValue(epoch), statsPrinter); } else { statsPrinter.addStat("Redundant update skipped"); Trace.line(UPDATE_TRACE_VALUE, tracePrefix() + " redundant update epoch=" + epoch + ": " + this); } } } /** * Force an immediate update of any cached data if it has not yet been done during the current refresh cycle. * @see #updateCache(long) */ public final void updateCacheIfNeeded() { final long currentEpoch = vm().teleProcess().epoch(); if (currentEpoch > lastUpdateEpoch && status().isNotDead()) { Trace.line(UPDATE_TRACE_VALUE, tracePrefix() + "out of order update at " + reference.origin().to0xHexString()); updateCache(currentEpoch); } } /** * Gets the level at which to present a trace of individual object updates, specified * here as the default. Subclasses should override to lower the level for specific types, * since producing traces for every object update would generate an unreasonably large amount * of output. * * @param epoch the current process epoch at the time of the update * @return the trace level that should bye used by this object during updates */ protected int getObjectUpdateTraceValue(long epoch) { return UPDATE_TRACE_VALUE; } /** * Internal call to subclasses to update their state, wrapped in order to provide timing and update statistics * reporting and for there to be a uniform bail-out if there is an update failure. * <p> * The intention is for updates to take place top-down, so every override should first call {@code super()} and then * halt the update if there is an updating failure. * * @param epoch the process epoch at the time of this update. * @param statsPrinter list of objects that report statistics for updates performed on this object so far (with no * newlines) * @return whether the object's cache was successfully updated. */ protected boolean updateObjectCache(long epoch, StatsPrinter statsPrinter) { return true; } public final ObjectStatus status() { return reference().status(); } public final RemoteReference reference() { return reference; } public final long getOID() { return oid; } public String maxineRole() { return null; } public String maxineTerseRole() { return maxineRole(); } public boolean hasTextualVisualization() { return false; } public String textualVisualization() { return null; } /** * The class actor for this class is returned when the real class actor * of a {@link TeleObject} cannot be retrieved. */ public static class InvalidObjectClass {} public ClassActor classActorForObjectType() { // TODO: fix class actor lookup try { TeleHub hub = getTeleHub(); if (hub != null) { TeleClassActor teleClassActor = hub.getTeleClassActor(); return teleClassActor.classActor(); } } catch (NullPointerException e) { e.printStackTrace(); } return ClassActor.fromJava(InvalidObjectClass.class); } public TeleClassMethodActor getTeleClassMethodActorForObject() { return null; } public final Pointer origin() { return reference.toOrigin(); } /** * Gets the size of the memory occupied by this object in the VM, * including header. */ protected abstract int objectSize(); public final TeleFixedMemoryRegion objectMemoryRegion() { if (objectSize() > 0) { return new TeleFixedMemoryRegion(vm(), "", specificLayout.originToCell(reference.toOrigin()), objectSize()); } return null; } public final TypeDescriptor headerType(HeaderField headerField) { if (headerField == HeaderField.HUB) { return getTeleHub() == null ? null : JavaTypeDescriptor.forJavaClass(getTeleHub().hub().getClass()); } else if (headerField == HeaderField.LENGTH) { return JavaTypeDescriptor.INT; } return JavaTypeDescriptor.WORD; } /** * The size in bytes of a field in the object's header. * * @param headerField identifies a header field in the object layout * @return the size of the header field */ public final int headerSize(HeaderField headerField) { return headerType(headerField).toKind().width.numberOfBytes; } public final int headerOffset(HeaderField headerField) { if (headerField == HeaderField.LENGTH) { return objects().layoutScheme().arrayLayout.getOffsetFromOrigin(headerField).toInt(); } else { return objects().layoutScheme().generalLayout.getOffsetFromOrigin(headerField).toInt(); } } public Address headerAddress(HeaderField headerField) { return reference().origin().plus(headerOffset(headerField)); } public final TeleFixedMemoryRegion headerMemoryRegion(HeaderField headerField) { final Address address = headerAddress(headerField); final int nBytes = headerSize(headerField); return new TeleFixedMemoryRegion(vm(), "Current memory for header field " + headerField.name, address, nBytes); } public final TeleHub getTeleHub() { if (teleHub == null) { // If this object is a forwarder then get the hub of the new copy teleHub = (TeleHub) objects().makeTeleObject(reference.followIfForwarded().readHubAsRemoteReference()); } return teleHub; } public Word readMiscWord() { return Layout.readMisc(reference); } /** * Gets the fields for either a tuple or hybrid object, returns empty set for arrays. Returns static fields in the * special case of a {@link StaticTuple} object. */ public Set<FieldActor> getFieldActors() { final Set<FieldActor> instanceFieldActors = new HashSet<FieldActor>(); collectInstanceFieldActors(classActorForObjectType(), instanceFieldActors); return instanceFieldActors; } /** * Gathers all instance fields for a class, including inherited fields. * * @param classActor description of a class * @param instanceFieldActors the set to which collected {@link FieldActor}s will be added. */ private void collectInstanceFieldActors(ClassActor classActor, Set<FieldActor> instanceFieldActors) { if (classActor != null) { for (FieldActor fieldActor : classActor.localInstanceFieldActors()) { instanceFieldActors.add(fieldActor); } collectInstanceFieldActors(classActor.superClassActor, instanceFieldActors); } } /** * Gets the current memory address of a field in the object, subject to relocation by GC. * * @param fieldActor descriptor for a field in this class * @return the current location in memory of the field in this object */ public abstract Address fieldAddress(FieldActor fieldActor); public abstract int fieldSize(FieldActor fieldActor); public final TeleFixedMemoryRegion fieldMemoryRegion(FieldActor fieldActor) { final Pointer start = origin().plus(fieldActor.offset()); return new TeleFixedMemoryRegion(vm(), "", start, fieldSize(fieldActor)); } /** * @return a shallow copy of the object in the vm, with any references in it nulled out */ public abstract Object shallowCopy(); /** * A carrier for the context needed to produce a local object that is a deep * copy of a VM object. Copying is truncated at any field annotated with {@link INSPECTED} * and for which {@link INSPECTED#deepCopied()} returns {@code false}. * */ protected static class DeepCopier { int level = 0; final Map<TeleObject, Object> teleObjectToObject; DeepCopier() { teleObjectToObject = new HashMap<TeleObject, Object>(); } DeepCopier(DeepCopier parent) { level = parent.level; teleObjectToObject = parent.teleObjectToObject; } /** * @return the depth of the object graph currently being copied */ protected int level() { return level; } static int totalCopies; static class Count implements Comparable<Count> { Class type; int value; @Override public int compareTo(Count o) { return value - o.value; } } static HashMap<Class, Count> copiesPerType = new HashMap<Class, Count>() { @Override public Count get(Object key) { Count count = super.get(key); if (count == null) { count = new Count(); count.type = (Class) key; put((Class) key, count); } return count; } }; static { if (Trace.hasLevel(1)) { Runtime.getRuntime().addShutdownHook(new Thread("CopiesPerTypePrinter") { @Override public void run() { SortedSet<Count> set = new TreeSet<Count>(copiesPerType.values()); System.out.println("Objects deep copied from VM (by type):"); for (Count c : set) { System.out.println(" " + c.value + "\t" + c.type.getSimpleName()); } } }); } } /** * Registers a newly copied object in the context to avoid duplication. * * @param newInstance specifies {@code object} was just instantiated (as opposed to being a local surrogate) */ protected void register(TeleObject teleObject, Object object, boolean newInstance) { Object oldValue = teleObjectToObject.put(teleObject, object); int numberOfCopies = numberOfCopies(); if (oldValue == null && newInstance && Trace.hasLevel(COPY_TRACE_VALUE)) { copiesPerType.get(object.getClass()).value++; totalCopies++; if ((numberOfCopies % 100) == 0) { Trace.line(COPY_TRACE_VALUE, "Deep copied " + numberOfCopies + " objects [" + totalCopies + " in total]"); } } } /** * Gets the number of unique object copied by this copier. */ public int numberOfCopies() { return teleObjectToObject.size(); } /** * Updates the field of an object or class from the VM. The field will always be set to {@code null} if * the field is annotated with {@link INSPECTED} and {@link INSPECTED#deepCopied()} returns {@code false}. * * @param teleObject surrogate for a tuple in the VM. This will be a static tuple if the field is static. * @param newTuple the local object to be updated in the host VM. This value is ignored if the field is static. * @param fieldActor the field to be copied/updated */ protected void copyField(TeleObject teleObject, Object newTuple, FieldActor fieldActor) { if (!fieldActor.isInjected()) { final Field field = fieldActor.toJava(); field.setAccessible(true); try { final Value value = teleObject.readFieldValue(fieldActor); final Object newJavaValue; if (fieldActor.kind.isReference) { INSPECTED a = fieldActor.getAnnotation(INSPECTED.class); if (a != null && !a.deepCopied()) { return; } final TeleObject teleFieldReferenceObject = teleObject.objects().makeTeleObject((RemoteReference) value.asReference()); if (teleFieldReferenceObject == null) { newJavaValue = null; } else { newJavaValue = makeDeepCopy(fieldActor, teleFieldReferenceObject); } } else if (fieldActor.kind.isWord) { final Class<Class< ? extends Word>> type = null; final Class< ? extends Word> wordType = Utils.cast(type, fieldActor.toJava().getType()); newJavaValue = value.asWord().as(wordType); } else { newJavaValue = value.asBoxedJavaValue(); } field.set(newTuple, newJavaValue); } catch (IllegalAccessException illegalAccessException) { TeleError.unexpected("could not access field: " + field, illegalAccessException); } } } protected Object makeDeepCopy(FieldActor fieldActor, TeleObject teleObject) { return teleObject.makeDeepCopy(this); } } /** * @return produces a deep copy of an object as part of a larger deep copy in which this particular object may have * already been copied. */ protected final Object makeDeepCopy(DeepCopier context) { Object newObject = context.teleObjectToObject.get(this); if (newObject == null) { context.level++; newObject = createDeepCopy(context); context.register(this, newObject, false); context.level--; } return newObject; } /** * @return creates a local deep copy of the object, using Maxine-specific shortcuts when possible to produce a local * equivalent without copying. Implementations that copy recursively must call * {@link TeleObject#makeDeepCopy(DeepCopier)}, and must register newly allocated objects before doing so. * This will result in redundant registrations in those cases. */ protected abstract Object createDeepCopy(DeepCopier context); /** * Checks to see if a deep copy of this object would be potentially problematic, and if so generates a warning message. * * @return a warning message about deep copying, null otherwise; default is no-warning. */ protected String deepCopyWarning() { return null; } /** * Hook for subclasses to refine the extent of {@linkplain #deepCopy() deep copying}. */ protected DeepCopier newDeepCopier() { return new DeepCopier(); } /** * Creates a local copy of the remote VM object. Deep copying is truncated at reference fields * marked with the {@link INSPECTED} annotation specifying the value {@code false} for {@link INSPECTED#deepCopied()}. * * @param copier copier context of {@code null} is none. * @return a best effort deep copy, truncated at reference fields for which {@link INSPECTED#deepCopied()} returns {@code false}. * @see INSPECTED#deepCopied() */ public final Object deepCopy(DeepCopier copier) { Object objectCopy = null; Trace.begin(COPY_TRACE_VALUE, "Deep copying from VM: " + this); long start = System.currentTimeMillis(); final String warningMessage = deepCopyWarning(); if (warningMessage != null) { TeleWarning.message(warningMessage); } if (vm().tryLock()) { try { if (copier == null) { copier = newDeepCopier(); } objectCopy = makeDeepCopy(copier); Trace.end(COPY_TRACE_VALUE, "Deep copying from VM: " + this + " [" + copier.numberOfCopies() + " objects]", start); } finally { vm().unlock(); } } else { TeleWarning.message("Deep copy failed (VM busy) for " + this); } return objectCopy; } public final Object deepCopy() { return deepCopy(null); } /** * Updates the static fields of a specified local class from the VM. */ public static void copyStaticFields(MaxVM vm, Class javaClass) { final ClassActor classActor = ClassActor.fromJava(javaClass); final TeleClassActor teleClassActor = vm.classes().findTeleClassActor(javaClass); final TeleStaticTuple teleStaticTuple = teleClassActor.getTeleStaticTuple(); final String classMessage = "Copying static fields of " + javaClass + " from VM"; Trace.begin(COPY_TRACE_VALUE, classMessage); DeepCopier copier = new DeepCopier(); try { for (FieldActor fieldActor : classActor.localStaticFieldActors()) { final String fieldMessage = fieldActor.format("Copying static field '%n' of type '%t' from VM"); Trace.begin(COPY_TRACE_VALUE, fieldMessage); copier.copyField(teleStaticTuple, null, fieldActor); Trace.end(COPY_TRACE_VALUE, fieldMessage); } } finally { Trace.end(COPY_TRACE_VALUE, classMessage + " [" + copier.numberOfCopies() + " objects]"); } } @Override public String toString() { return getClass().toString() + "<" + oid + ">"; } public ReferenceTypeProvider getReferenceType() { return classes().findTeleClassActor(classActorForObjectType().typeDescriptor); } }