/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ahat.heapdump; import com.android.ahat.progress.NullProgress; import com.android.ahat.progress.Progress; import com.android.ahat.proguard.ProguardMap; import java.io.File; import java.io.IOException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.StandardCharsets; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; /** * Provides methods for parsing heap dumps. *

* The heap dump should be a heap dump in the J2SE HPROF format optionally * with Android extensions and satisfying the following additional * constraints: *

*/ public class Parser { private HprofBuffer hprof = null; private ProguardMap map = new ProguardMap(); private Progress progress = new NullProgress(); private Reachability retained = Reachability.SOFT; /** * Creates an hprof Parser that parses a heap dump from a byte buffer. * * @param hprof byte buffer to parse the heap dump from. */ public Parser(ByteBuffer hprof) { this.hprof = new HprofBuffer(hprof); } /** * Creates an hprof Parser that parses a heap dump from a file. * * @param hprof file to parse the heap dump from. * @throws IOException if the file cannot be accessed. */ public Parser(File hprof) throws IOException { this.hprof = new HprofBuffer(hprof); } /** * Sets the proguard map to use for deobfuscating the heap. * * @param map proguard map to use to deobfuscate the heap. * @return this Parser instance. */ public Parser map(ProguardMap map) { if (map == null) { throw new NullPointerException("map == null"); } this.map = map; return this; } /** * Sets the progress indicator to use when parsing the heap. * * @param progress progress indicator to use when parsing the heap. * @return this Parser instance. */ public Parser progress(Progress progress) { if (progress == null) { throw new NullPointerException("progress == null"); } this.progress = progress; return this; } /** * Specify the weakest reachability of instances to treat as retained. * * @param retained the weakest reachability of instances to treat as retained. * @return this Parser instance. */ public Parser retained(Reachability retained) { this.retained = retained; return this; } /** * Parse the heap dump. * * @throws IOException if the heap dump could not be read * @throws HprofFormatException if the heap dump is not properly formatted * @return the parsed heap dump */ public AhatSnapshot parse() throws IOException, HprofFormatException { try { return parseInternal(); } catch (BufferUnderflowException e) { throw new HprofFormatException("Unexpected end of file", e); } } /** * Parses a heap dump from a File with given proguard map. * * @param hprof the hprof file to parse * @param map the proguard map for deobfuscation * @return the parsed heap dump * @throws IOException if the heap dump could not be read * @throws HprofFormatException if the heap dump is not properly formatted */ public static AhatSnapshot parseHeapDump(File hprof, ProguardMap map) throws IOException, HprofFormatException { return new Parser(hprof).map(map).parse(); } /** * Parses a heap dump from a byte buffer with given proguard map. * * @param hprof the bytes of the hprof file to parse * @param map the proguard map for deobfuscation * @return the parsed heap dump * @throws IOException if the heap dump could not be read * @throws HprofFormatException if the heap dump is not properly formatted */ public static AhatSnapshot parseHeapDump(ByteBuffer hprof, ProguardMap map) throws IOException, HprofFormatException { return new Parser(hprof).map(map).parse(); } private AhatSnapshot parseInternal() throws IOException, HprofFormatException { // Read, and mostly ignore, the hprof header info. int idSize; { StringBuilder format = new StringBuilder(); int b; while ((b = hprof.getU1()) != 0) { format.append((char)b); } idSize = hprof.getU4(); if (idSize == 8) { hprof.setIdSize8(); } else if (idSize != 4) { throw new HprofFormatException("Id size " + idSize + " not supported."); } int hightime = hprof.getU4(); int lowtime = hprof.getU4(); } // First pass: Read through all the heap dump records. Construct the // AhatInstances, initialize them as much as possible and save any // additional temporary data we need to complete their initialization in // the fixup pass. Site rootSite = new Site("ROOT"); List instances = new ArrayList(); List roots = new ArrayList(); HeapList heaps = new HeapList(); { // Note: Strings do not satisfy the DenseMap requirements on heap dumps // from Android K. And the RI seems to use string id 0 to refer to a // null string? UnDenseMap strings = new UnDenseMap("String"); strings.put(0, "???"); DenseMap frames = new DenseMap("Stack Frame"); DenseMap sites = new DenseMap("Stack Trace"); DenseMap classNamesBySerial = new DenseMap("Class Serial Number"); AhatClassObj javaLangClass = null; AhatClassObj[] primArrayClasses = new AhatClassObj[Type.values().length]; ArrayList classes = new ArrayList(); Instances classById = null; progress.start("Reading hprof", hprof.size()); while (hprof.hasRemaining()) { progress.update(hprof.tell()); int tag = hprof.getU1(); int time = hprof.getU4(); int recordLength = hprof.getU4(); switch (tag) { case 0x01: { // STRING long id = hprof.getId(); byte[] bytes = new byte[recordLength - idSize]; hprof.getBytes(bytes); String str = new String(bytes, StandardCharsets.UTF_8); strings.put(id, str); break; } case 0x02: { // LOAD CLASS int classSerialNumber = hprof.getU4(); long objectId = hprof.getId(); int stackSerialNumber = hprof.getU4(); long classNameStringId = hprof.getId(); String rawClassName = strings.get(classNameStringId); String obfClassName = normalizeClassName(rawClassName); String clrClassName = map.getClassName(obfClassName); AhatClassObj classObj = new AhatClassObj(objectId, clrClassName); classNamesBySerial.put(classSerialNumber, clrClassName); classes.add(classObj); // Check whether this class is one of the special classes we are // interested in, and if so, save it for later use. if ("java.lang.Class".equals(clrClassName)) { javaLangClass = classObj; } for (Type type : Type.values()) { if (clrClassName.equals(type.name + "[]")) { primArrayClasses[type.ordinal()] = classObj; } } break; } case 0x04: { // STACK FRAME long frameId = hprof.getId(); long methodNameStringId = hprof.getId(); long methodSignatureStringId = hprof.getId(); long methodFileNameStringId = hprof.getId(); int classSerialNumber = hprof.getU4(); int lineNumber = hprof.getU4(); ProguardMap.Frame frame = map.getFrame( classNamesBySerial.get(classSerialNumber), strings.get(methodNameStringId), strings.get(methodSignatureStringId), strings.get(methodFileNameStringId), lineNumber); frames.put(frameId, frame); break; } case 0x05: { // STACK TRACE int stackSerialNumber = hprof.getU4(); int threadSerialNumber = hprof.getU4(); int numFrames = hprof.getU4(); ProguardMap.Frame[] trace = new ProguardMap.Frame[numFrames]; for (int i = 0; i < numFrames; i++) { long frameId = hprof.getId(); trace[i] = frames.get(frameId); } sites.put(stackSerialNumber, rootSite.getSite(trace)); break; } case 0x0C: // HEAP DUMP case 0x1C: { // HEAP DUMP SEGMENT int endOfRecord = hprof.tell() + recordLength; if (classById == null) { classById = new Instances(classes); } while (hprof.tell() < endOfRecord) { progress.update(hprof.tell()); int subtag = hprof.getU1(); switch (subtag) { case 0x01: { // ROOT JNI GLOBAL long objectId = hprof.getId(); long refId = hprof.getId(); roots.add(new RootData(objectId, RootType.JNI_GLOBAL)); break; } case 0x02: { // ROOT JNI LOCAL long objectId = hprof.getId(); int threadSerialNumber = hprof.getU4(); int frameNumber = hprof.getU4(); roots.add(new RootData(objectId, RootType.JNI_LOCAL)); break; } case 0x03: { // ROOT JAVA FRAME long objectId = hprof.getId(); int threadSerialNumber = hprof.getU4(); int frameNumber = hprof.getU4(); roots.add(new RootData(objectId, RootType.JAVA_FRAME)); break; } case 0x04: { // ROOT NATIVE STACK long objectId = hprof.getId(); int threadSerialNumber = hprof.getU4(); roots.add(new RootData(objectId, RootType.NATIVE_STACK)); break; } case 0x05: { // ROOT STICKY CLASS long objectId = hprof.getId(); roots.add(new RootData(objectId, RootType.STICKY_CLASS)); break; } case 0x06: { // ROOT THREAD BLOCK long objectId = hprof.getId(); int threadSerialNumber = hprof.getU4(); roots.add(new RootData(objectId, RootType.THREAD_BLOCK)); break; } case 0x07: { // ROOT MONITOR USED long objectId = hprof.getId(); roots.add(new RootData(objectId, RootType.MONITOR)); break; } case 0x08: { // ROOT THREAD OBJECT long objectId = hprof.getId(); int threadSerialNumber = hprof.getU4(); int stackSerialNumber = hprof.getU4(); roots.add(new RootData(objectId, RootType.THREAD)); break; } case 0x20: { // CLASS DUMP ClassObjData data = new ClassObjData(); long objectId = hprof.getId(); int stackSerialNumber = hprof.getU4(); long superClassId = hprof.getId(); data.classLoaderId = hprof.getId(); long signersId = hprof.getId(); long protectionId = hprof.getId(); long reserved1 = hprof.getId(); long reserved2 = hprof.getId(); int instanceSize = hprof.getU4(); int constantPoolSize = hprof.getU2(); for (int i = 0; i < constantPoolSize; ++i) { int index = hprof.getU2(); Type type = hprof.getType(); hprof.skip(type.size(idSize)); } int numStaticFields = hprof.getU2(); data.staticFields = new FieldValue[numStaticFields]; AhatClassObj obj = classById.get(objectId); String clrClassName = obj.getName(); long staticFieldsSize = 0; for (int i = 0; i < numStaticFields; ++i) { String obfName = strings.get(hprof.getId()); String clrName = map.getFieldName(clrClassName, obfName); Type type = hprof.getType(); Value value = hprof.getDeferredValue(type); staticFieldsSize += type.size(idSize); data.staticFields[i] = new FieldValue(clrName, type, value); } AhatClassObj superClass = classById.get(superClassId); int numInstanceFields = hprof.getU2(); Field[] ifields = new Field[numInstanceFields]; for (int i = 0; i < numInstanceFields; ++i) { String name = map.getFieldName(obj.getName(), strings.get(hprof.getId())); ifields[i] = new Field(name, hprof.getType()); } Site site = sites.get(stackSerialNumber); if (javaLangClass == null) { throw new HprofFormatException("No class definition found for java.lang.Class"); } obj.initialize(heaps.getCurrentHeap(), site, javaLangClass); obj.initialize(superClass, instanceSize, ifields, staticFieldsSize); obj.setTemporaryUserData(data); break; } case 0x21: { // INSTANCE DUMP long objectId = hprof.getId(); int stackSerialNumber = hprof.getU4(); long classId = hprof.getId(); int numBytes = hprof.getU4(); ClassInstData data = new ClassInstData(hprof.tell()); hprof.skip(numBytes); Site site = sites.get(stackSerialNumber); AhatClassObj classObj = classById.get(classId); AhatClassInstance obj = AhatClassInstance.create(classObj, objectId); obj.initialize(heaps.getCurrentHeap(), site, classObj); obj.setTemporaryUserData(data); instances.add(obj); break; } case 0x22: { // OBJECT ARRAY DUMP long objectId = hprof.getId(); int stackSerialNumber = hprof.getU4(); int length = hprof.getU4(); long classId = hprof.getId(); ObjArrayData data = new ObjArrayData(length, hprof.tell()); hprof.skip(length * idSize); Site site = sites.get(stackSerialNumber); AhatClassObj classObj = classById.get(classId); AhatArrayInstance obj = new AhatArrayInstance(objectId, idSize); obj.initialize(heaps.getCurrentHeap(), site, classObj); obj.setTemporaryUserData(data); instances.add(obj); break; } case 0x23: { // PRIMITIVE ARRAY DUMP long objectId = hprof.getId(); int stackSerialNumber = hprof.getU4(); int length = hprof.getU4(); Type type = hprof.getPrimitiveType(); Site site = sites.get(stackSerialNumber); AhatClassObj classObj = primArrayClasses[type.ordinal()]; if (classObj == null) { throw new HprofFormatException( "No class definition found for " + type.name + "[]"); } AhatArrayInstance obj = new AhatArrayInstance(objectId, idSize); obj.initialize(heaps.getCurrentHeap(), site, classObj); instances.add(obj); switch (type) { case BOOLEAN: { boolean[] data = new boolean[length]; for (int i = 0; i < length; ++i) { data[i] = hprof.getBool(); } obj.initialize(data); break; } case CHAR: { char[] data = new char[length]; for (int i = 0; i < length; ++i) { data[i] = hprof.getChar(); } obj.initialize(data); break; } case FLOAT: { float[] data = new float[length]; for (int i = 0; i < length; ++i) { data[i] = hprof.getFloat(); } obj.initialize(data); break; } case DOUBLE: { double[] data = new double[length]; for (int i = 0; i < length; ++i) { data[i] = hprof.getDouble(); } obj.initialize(data); break; } case BYTE: { byte[] data = new byte[length]; hprof.getBytes(data); obj.initialize(data); break; } case SHORT: { short[] data = new short[length]; for (int i = 0; i < length; ++i) { data[i] = hprof.getShort(); } obj.initialize(data); break; } case INT: { int[] data = new int[length]; for (int i = 0; i < length; ++i) { data[i] = hprof.getInt(); } obj.initialize(data); break; } case LONG: { long[] data = new long[length]; for (int i = 0; i < length; ++i) { data[i] = hprof.getLong(); } obj.initialize(data); break; } default: throw new AssertionError("unsupported enum member"); } break; } case 0x89: { // ROOT INTERNED STRING (ANDROID) long objectId = hprof.getId(); roots.add(new RootData(objectId, RootType.INTERNED_STRING)); break; } case 0x8a: { // ROOT FINALIZING (ANDROID) long objectId = hprof.getId(); roots.add(new RootData(objectId, RootType.FINALIZING)); break; } case 0x8b: { // ROOT DEBUGGER (ANDROID) long objectId = hprof.getId(); roots.add(new RootData(objectId, RootType.DEBUGGER)); break; } case 0x8d: { // ROOT VM INTERNAL (ANDROID) long objectId = hprof.getId(); roots.add(new RootData(objectId, RootType.VM_INTERNAL)); break; } case 0x8e: { // ROOT JNI MONITOR (ANDROID) long objectId = hprof.getId(); int threadSerialNumber = hprof.getU4(); int frameNumber = hprof.getU4(); roots.add(new RootData(objectId, RootType.JNI_MONITOR)); break; } case 0xfe: { // HEAP DUMP INFO (ANDROID) int type = hprof.getU4(); long stringId = hprof.getId(); heaps.setCurrentHeap(strings.get(stringId)); break; } case 0xff: { // ROOT UNKNOWN long objectId = hprof.getId(); roots.add(new RootData(objectId, RootType.UNKNOWN)); break; } default: throw new HprofFormatException( String.format("Unsupported heap dump sub tag 0x%02x", subtag)); } } break; } default: // Ignore any other tags that we either don't know about or don't // care about. hprof.skip(recordLength); break; } } progress.done(); instances.addAll(classes); } // Sort roots and instances by id in preparation for the fixup pass. Instances mInstances = new Instances(instances); roots.sort(new Comparator() { @Override public int compare(RootData a, RootData b) { return Long.compare(a.id, b.id); } }); roots.add(null); // Fixup pass: Label the root instances and fix up references to instances // that we couldn't previously resolve. SuperRoot superRoot = new SuperRoot(); { progress.start("Resolving references", mInstances.size()); Iterator ri = roots.iterator(); RootData root = ri.next(); for (AhatInstance inst : mInstances) { progress.advance(); long id = inst.getId(); // Skip past any roots that don't have associated instances. // It's not clear why there would be a root without an associated // instance dump, but it does happen in practice, for example when // taking heap dumps using the RI. while (root != null && root.id < id) { root = ri.next(); } // Check if this instance is a root, and if so, update its root types. if (root != null && root.id == id) { superRoot.addRoot(inst); while (root != null && root.id == id) { inst.addRootType(root.type); root = ri.next(); } } // Fixup the instance based on its type using the temporary data we // saved during the first pass over the heap dump. if (inst instanceof AhatClassInstance) { ClassInstData data = (ClassInstData)inst.getTemporaryUserData(); inst.setTemporaryUserData(null); // Compute the size of the fields array in advance to avoid // extra allocations and copies that would come from using an array // list to collect the field values. int numFields = 0; for (AhatClassObj cls = inst.getClassObj(); cls != null; cls = cls.getSuperClassObj()) { numFields += cls.getInstanceFields().length; } Value[] fields = new Value[numFields]; int i = 0; hprof.seek(data.position); for (AhatClassObj cls = inst.getClassObj(); cls != null; cls = cls.getSuperClassObj()) { for (Field field : cls.getInstanceFields()) { fields[i++] = hprof.getValue(field.type, mInstances); } } ((AhatClassInstance)inst).initialize(fields); } else if (inst instanceof AhatClassObj) { ClassObjData data = (ClassObjData)inst.getTemporaryUserData(); inst.setTemporaryUserData(null); AhatInstance loader = mInstances.get(data.classLoaderId); for (int i = 0; i < data.staticFields.length; ++i) { FieldValue field = data.staticFields[i]; if (field.value instanceof DeferredInstanceValue) { DeferredInstanceValue deferred = (DeferredInstanceValue)field.value; data.staticFields[i] = new FieldValue( field.name, field.type, Value.pack(mInstances.get(deferred.getId()))); } } ((AhatClassObj)inst).initialize(loader, data.staticFields); } else if (inst instanceof AhatArrayInstance && inst.getTemporaryUserData() != null) { // TODO: Have specialized object array instance and check for that // rather than checking for the presence of user data? ObjArrayData data = (ObjArrayData)inst.getTemporaryUserData(); inst.setTemporaryUserData(null); AhatInstance[] array = new AhatInstance[data.length]; hprof.seek(data.position); for (int i = 0; i < data.length; i++) { array[i] = mInstances.get(hprof.getId()); } ((AhatArrayInstance)inst).initialize(array); } } progress.done(); } hprof = null; roots = null; return new AhatSnapshot(superRoot, mInstances, heaps.heaps, rootSite, progress, retained); } private static class RootData { public long id; public RootType type; public RootData(long id, RootType type) { this.id = id; this.type = type; } } private static class ClassInstData { // The byte position in the hprof file where instance field data starts. public int position; public ClassInstData(int position) { this.position = position; } } private static class ObjArrayData { public int length; // Number of array elements. public int position; // Position in hprof file containing element data. public ObjArrayData(int length, int position) { this.length = length; this.position = position; } } private static class ClassObjData { public long classLoaderId; public FieldValue[] staticFields; // Contains DeferredInstanceValues. } /** * Placeholder value representing a reference to an instance that has not yet been * resolved. * When first initializing class static fields, we don't yet know what kinds * of objects Object references refer to. We use DeferredInstanceValue as * a placeholder kind of value to store the id of an object. In the fixup pass we * resolve all the DeferredInstanceValues into their proper InstanceValues. */ private static class DeferredInstanceValue extends Value { private long mId; public DeferredInstanceValue(long id) { mId = id; } public long getId() { return mId; } @Override Type getType() { return Type.OBJECT; } @Override public String toString() { return String.format("0x%08x", mId); } @Override public int hashCode() { return Objects.hash(mId); } @Override public boolean equals(Object other) { if (other instanceof DeferredInstanceValue) { DeferredInstanceValue value = (DeferredInstanceValue)other; return mId == value.mId; } return false; } } /** * A convenient abstraction for lazily building up the list of heaps seen in * the heap dump. */ private static class HeapList { public List heaps = new ArrayList(); private AhatHeap current; public AhatHeap getCurrentHeap() { if (current == null) { setCurrentHeap("default"); } return current; } public void setCurrentHeap(String name) { for (AhatHeap heap : heaps) { if (name.equals(heap.getName())) { current = heap; return; } } current = new AhatHeap(name, heaps.size()); heaps.add(current); } } /** * A mapping from id to elements, where certain conditions are * satisfied. The conditions are: * - all elements are defined before they are referenced. * - ids are densely packed in some range [a, b] where a is not * necessarily 0. * - there are not more than 2^31 elements defined. */ private static class DenseMap { private String mElementType; // mValues behaves like a circular buffer. // mKeyAt0 is the key corresponding to index 0 of mValues. Values with // smaller keys will wrap around to the end of the mValues buffer. The // buffer is expanded when it is no longer big enough to hold all the keys // from mMinKey to mMaxKey. private Object[] mValues; private long mKeyAt0; private long mMaxKey; private long mMinKey; /** * Constructs a DenseMap. * @param elementType Human readable name describing the type of * elements for error message if the required * conditions are found not to hold. */ public DenseMap(String elementType) { mElementType = elementType; } public void put(long key, T value) { if (mValues == null) { mValues = new Object[8]; mValues[0] = value; mKeyAt0 = key; mMaxKey = key; mMinKey = key; return; } long max = Math.max(mMaxKey, key); long min = Math.min(mMinKey, key); int count = (int)(max + 1 - min); if (count > mValues.length) { Object[] values = new Object[2 * count]; // Copy over the values into the newly allocated larger buffer. It is // convenient to move the value with mMinKey to index 0 when we make // the copy. for (int i = 0; i < mValues.length; ++i) { values[i] = mValues[indexOf(i + mMinKey)]; } mValues = values; mKeyAt0 = mMinKey; } mMinKey = min; mMaxKey = max; mValues[indexOf(key)] = value; } /** * Returns the value for the given key. * @throws HprofFormatException if there is no value with the key in the * given map. */ public T get(long key) throws HprofFormatException { T value = null; if (mValues != null && key >= mMinKey && key <= mMaxKey) { value = (T)mValues[indexOf(key)]; } if (value == null) { throw new HprofFormatException(String.format( "%s with id 0x%x referenced before definition", mElementType, key)); } return value; } private int indexOf(long key) { return ((int)(key - mKeyAt0) + mValues.length) % mValues.length; } } /** * A mapping from id to elements, where we don't have nice conditions to * work with. */ private static class UnDenseMap { private String mElementType; private Map mValues = new HashMap(); /** * Constructs an UnDenseMap. * @param elementType Human readable name describing the type of * elements for error message if the required * conditions are found not to hold. */ public UnDenseMap(String elementType) { mElementType = elementType; } public void put(long key, T value) { mValues.put(key, value); } /** * Returns the value for the given key. * @throws HprofFormatException if there is no value with the key in the * given map. */ public T get(long key) throws HprofFormatException { T value = mValues.get(key); if (value == null) { throw new HprofFormatException(String.format( "%s with id 0x%x referenced before definition", mElementType, key)); } return value; } } /** * Wrapper around a ByteBuffer that presents a uniform interface for * accessing data from an hprof file. */ private static class HprofBuffer { private boolean mIdSize8; private final ByteBuffer mBuffer; public HprofBuffer(File path) throws IOException { FileChannel channel = FileChannel.open(path.toPath(), StandardOpenOption.READ); mBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); channel.close(); } public HprofBuffer(ByteBuffer buffer) { mBuffer = buffer; } public void setIdSize8() { mIdSize8 = true; } public boolean hasRemaining() { return mBuffer.hasRemaining(); } /** * Returns the size of the file in bytes. */ public int size() { return mBuffer.capacity(); } /** * Return the current absolution position in the file. */ public int tell() { return mBuffer.position(); } /** * Seek to the given absolution position in the file. */ public void seek(int position) { mBuffer.position(position); } /** * Skip ahead in the file by the given delta bytes. Delta may be negative * to skip backwards in the file. */ public void skip(int delta) { seek(tell() + delta); } public int getU1() { return mBuffer.get() & 0xFF; } public int getU2() { return mBuffer.getShort() & 0xFFFF; } public int getU4() { return mBuffer.getInt(); } public long getId() { if (mIdSize8) { return mBuffer.getLong(); } else { return mBuffer.getInt() & 0xFFFFFFFFL; } } public boolean getBool() { return mBuffer.get() != 0; } public char getChar() { return mBuffer.getChar(); } public float getFloat() { return mBuffer.getFloat(); } public double getDouble() { return mBuffer.getDouble(); } public byte getByte() { return mBuffer.get(); } public void getBytes(byte[] bytes) { mBuffer.get(bytes); } public short getShort() { return mBuffer.getShort(); } public int getInt() { return mBuffer.getInt(); } public long getLong() { return mBuffer.getLong(); } private static Type[] TYPES = new Type[] { null, null, Type.OBJECT, null, Type.BOOLEAN, Type.CHAR, Type.FLOAT, Type.DOUBLE, Type.BYTE, Type.SHORT, Type.INT, Type.LONG }; public Type getType() throws HprofFormatException { int id = getU1(); Type type = id < TYPES.length ? TYPES[id] : null; if (type == null) { throw new HprofFormatException("Invalid basic type id: " + id); } return type; } public Type getPrimitiveType() throws HprofFormatException { Type type = getType(); if (type == Type.OBJECT) { throw new HprofFormatException("Expected primitive type, but found type 'Object'"); } return type; } /** * Get a value from the hprof file, using the given instances map to * convert instance ids to their corresponding AhatInstance objects. */ public Value getValue(Type type, Instances instances) { switch (type) { case OBJECT: return Value.pack(instances.get(getId())); case BOOLEAN: return Value.pack(getBool()); case CHAR: return Value.pack(getChar()); case FLOAT: return Value.pack(getFloat()); case DOUBLE: return Value.pack(getDouble()); case BYTE: return Value.pack(getByte()); case SHORT: return Value.pack(getShort()); case INT: return Value.pack(getInt()); case LONG: return Value.pack(getLong()); default: throw new AssertionError("unsupported enum member"); } } /** * Get a value from the hprof file. AhatInstance values are returned as * DefferredInstanceValues rather than their corresponding AhatInstance * objects. */ public Value getDeferredValue(Type type) { switch (type) { case OBJECT: return new DeferredInstanceValue(getId()); case BOOLEAN: return Value.pack(getBool()); case CHAR: return Value.pack(getChar()); case FLOAT: return Value.pack(getFloat()); case DOUBLE: return Value.pack(getDouble()); case BYTE: return Value.pack(getByte()); case SHORT: return Value.pack(getShort()); case INT: return Value.pack(getInt()); case LONG: return Value.pack(getLong()); default: throw new AssertionError("unsupported enum member"); } } } // ART outputs class names such as: // "java.lang.Class", "java.lang.Class[]", "byte", "byte[]" // RI outputs class names such as: // "java/lang/Class", '[Ljava/lang/Class;", N/A, "[B" // // This function converts all class names to match the ART format, which is // assumed elsewhere in ahat. private static String normalizeClassName(String name) throws HprofFormatException { int numDimensions = 0; while (name.startsWith("[")) { numDimensions++; name = name.substring(1); } if (numDimensions > 0) { // If there was an array type signature to start, then interpret the // class name as a type signature. switch (name.charAt(0)) { case 'Z': name = "boolean"; break; case 'B': name = "byte"; break; case 'C': name = "char"; break; case 'S': name = "short"; break; case 'I': name = "int"; break; case 'J': name = "long"; break; case 'F': name = "float"; break; case 'D': name = "double"; break; case 'L': name = name.substring(1, name.length() - 1); break; default: throw new HprofFormatException("Invalid type signature in class name: " + name); } } name = name.replace('/', '.'); for (int i = 0; i < numDimensions; ++i) { name += "[]"; } return name; } }