1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ahat.heapdump;
18 
19 import com.android.ahat.proguard.ProguardMap;
20 import java.io.File;
21 import java.io.IOException;
22 import java.nio.BufferUnderflowException;
23 import java.nio.ByteBuffer;
24 import java.nio.channels.FileChannel;
25 import java.nio.charset.StandardCharsets;
26 import java.nio.file.StandardOpenOption;
27 import java.util.ArrayList;
28 import java.util.Comparator;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 
34 /**
35  * Provides methods for parsing heap dumps.
36  */
37 public class Parser {
38   private static final int ID_SIZE = 4;
39 
Parser()40   private Parser() {
41   }
42 
43   /**
44    * Parses a heap dump from a File.
45    * <p>
46    * The heap dump should be a heap dump in the J2SE HPROF format optionally
47    * with Android extensions and satisfying the following additional
48    * constraints:
49    * <ul>
50    * <li>
51    * Class serial numbers, stack frames, and stack traces individually satisfy
52    * the following:
53    * <ul>
54    *   <li> All elements are defined before they are referenced.
55    *   <li> Ids are densely packed in some range [a, b] where a is not necessarily 0.
56    *   <li> There are not more than 2^31 elements defined.
57    * </ul>
58    * <li> All classes are defined via a LOAD CLASS record before the first
59    * heap dump segment.
60    * <li> The ID size used in the heap dump is 4 bytes.
61    * </ul>
62    * <p>
63    * The given proguard map will be used to deobfuscate class names, field
64    * names, and stack traces in the heap dump.
65    *
66    * @param hprof the hprof file to parse
67    * @param map the proguard map for deobfuscation
68    * @return the parsed heap dump
69    * @throws IOException if the heap dump could not be read
70    * @throws HprofFormatException if the heap dump is not properly formatted
71    */
parseHeapDump(File hprof, ProguardMap map)72   public static AhatSnapshot parseHeapDump(File hprof, ProguardMap map)
73     throws IOException, HprofFormatException {
74     try {
75       return parseHeapDump(new HprofBuffer(hprof), map);
76     } catch (BufferUnderflowException e) {
77       throw new HprofFormatException("Unexpected end of file", e);
78     }
79   }
80 
81   /**
82    * Parses a heap dump from a byte buffer.
83    * <p>
84    * The heap dump should be a heap dump in the J2SE HPROF format optionally
85    * with Android extensions and satisfying the following additional
86    * constraints:
87    * <ul>
88    * <li>
89    * Class serial numbers, stack frames, and stack traces individually satisfy
90    * the following:
91    * <ul>
92    *   <li> All elements are defined before they are referenced.
93    *   <li> Ids are densely packed in some range [a, b] where a is not necessarily 0.
94    *   <li> There are not more than 2^31 elements defined.
95    * </ul>
96    * <li> All classes are defined via a LOAD CLASS record before the first
97    * heap dump segment.
98    * <li> The ID size used in the heap dump is 4 bytes.
99    * </ul>
100    * <p>
101    * The given proguard map will be used to deobfuscate class names, field
102    * names, and stack traces in the heap dump.
103    *
104    * @param hprof the bytes of the hprof file to parse
105    * @param map the proguard map for deobfuscation
106    * @return the parsed heap dump
107    * @throws IOException if the heap dump could not be read
108    * @throws HprofFormatException if the heap dump is not properly formatted
109    */
parseHeapDump(ByteBuffer hprof, ProguardMap map)110   public static AhatSnapshot parseHeapDump(ByteBuffer hprof, ProguardMap map)
111     throws IOException, HprofFormatException {
112     try {
113       return parseHeapDump(new HprofBuffer(hprof), map);
114     } catch (BufferUnderflowException e) {
115       throw new HprofFormatException("Unexpected end of file", e);
116     }
117   }
118 
parseHeapDump(HprofBuffer hprof, ProguardMap map)119   private static AhatSnapshot parseHeapDump(HprofBuffer hprof, ProguardMap map)
120     throws IOException, HprofFormatException, BufferUnderflowException {
121     // Read, and mostly ignore, the hprof header info.
122     {
123       StringBuilder format = new StringBuilder();
124       int b;
125       while ((b = hprof.getU1()) != 0) {
126         format.append((char)b);
127       }
128 
129       int idSize = hprof.getU4();
130       if (idSize != ID_SIZE) {
131         throw new HprofFormatException("Id size " + idSize + " not supported.");
132       }
133       int hightime = hprof.getU4();
134       int lowtime = hprof.getU4();
135     }
136 
137     // First pass: Read through all the heap dump records. Construct the
138     // AhatInstances, initialize them as much as possible and save any
139     // additional temporary data we need to complete their initialization in
140     // the fixup pass.
141     Site rootSite = new Site("ROOT");
142     List<AhatInstance> instances = new ArrayList<AhatInstance>();
143     List<RootData> roots = new ArrayList<RootData>();
144     HeapList heaps = new HeapList();
145     {
146       // Note: Strings do not satisfy the DenseMap requirements on heap dumps
147       // from Android K.
148       UnDenseMap<String> strings = new UnDenseMap<String>("String");
149       DenseMap<ProguardMap.Frame> frames = new DenseMap<ProguardMap.Frame>("Stack Frame");
150       DenseMap<Site> sites = new DenseMap<Site>("Stack Trace");
151       DenseMap<String> classNamesBySerial = new DenseMap<String>("Class Serial Number");
152       AhatClassObj javaLangClass = null;
153       AhatClassObj[] primArrayClasses = new AhatClassObj[Type.values().length];
154       ArrayList<AhatClassObj> classes = new ArrayList<AhatClassObj>();
155       Instances<AhatClassObj> classById = null;
156 
157       while (hprof.hasRemaining()) {
158         int tag = hprof.getU1();
159         int time = hprof.getU4();
160         int recordLength = hprof.getU4();
161         switch (tag) {
162           case 0x01: { // STRING
163             long id = hprof.getId();
164             byte[] bytes = new byte[recordLength - ID_SIZE];
165             hprof.getBytes(bytes);
166             String str = new String(bytes, StandardCharsets.UTF_8);
167             strings.put(id, str);
168             break;
169           }
170 
171           case 0x02: { // LOAD CLASS
172             int classSerialNumber = hprof.getU4();
173             long objectId = hprof.getId();
174             int stackSerialNumber = hprof.getU4();
175             long classNameStringId = hprof.getId();
176             String obfClassName = strings.get(classNameStringId);
177             String clrClassName = map.getClassName(obfClassName);
178             AhatClassObj classObj = new AhatClassObj(objectId, clrClassName);
179             classNamesBySerial.put(classSerialNumber, clrClassName);
180             classes.add(classObj);
181 
182             // Check whether this class is one of the special classes we are
183             // interested in, and if so, save it for later use.
184             if ("java.lang.Class".equals(clrClassName)) {
185               javaLangClass = classObj;
186             }
187 
188             for (Type type : Type.values()) {
189               if (clrClassName.equals(type.name + "[]")) {
190                 primArrayClasses[type.ordinal()] = classObj;
191               }
192             }
193             break;
194           }
195 
196           case 0x04: { // STACK FRAME
197             long frameId = hprof.getId();
198             long methodNameStringId = hprof.getId();
199             long methodSignatureStringId = hprof.getId();
200             long methodFileNameStringId = hprof.getId();
201             int classSerialNumber = hprof.getU4();
202             int lineNumber = hprof.getU4();
203 
204             ProguardMap.Frame frame = map.getFrame(
205                 classNamesBySerial.get(classSerialNumber),
206                 strings.get(methodNameStringId),
207                 strings.get(methodSignatureStringId),
208                 strings.get(methodFileNameStringId),
209                 lineNumber);
210             frames.put(frameId, frame);
211             break;
212           }
213 
214           case 0x05: { // STACK TRACE
215             int stackSerialNumber = hprof.getU4();
216             int threadSerialNumber = hprof.getU4();
217             int numFrames = hprof.getU4();
218             ProguardMap.Frame[] trace = new ProguardMap.Frame[numFrames];
219             for (int i = 0; i < numFrames; i++) {
220               long frameId = hprof.getId();
221               trace[i] = frames.get(frameId);
222             }
223             sites.put(stackSerialNumber, rootSite.getSite(trace));
224             break;
225           }
226 
227           case 0x1C: { // HEAP DUMP SEGMENT
228             if (classById == null) {
229               classById = new Instances<AhatClassObj>(classes);
230             }
231             int subtag;
232             while (!isEndOfHeapDumpSegment(subtag = hprof.getU1())) {
233               switch (subtag) {
234                 case 0x01: { // ROOT JNI GLOBAL
235                   long objectId = hprof.getId();
236                   long refId = hprof.getId();
237                   roots.add(new RootData(objectId, RootType.JNI_GLOBAL));
238                   break;
239                 }
240 
241                 case 0x02: { // ROOT JNI LOCAL
242                   long objectId = hprof.getId();
243                   int threadSerialNumber = hprof.getU4();
244                   int frameNumber = hprof.getU4();
245                   roots.add(new RootData(objectId, RootType.JNI_LOCAL));
246                   break;
247                 }
248 
249                 case 0x03: { // ROOT JAVA FRAME
250                   long objectId = hprof.getId();
251                   int threadSerialNumber = hprof.getU4();
252                   int frameNumber = hprof.getU4();
253                   roots.add(new RootData(objectId, RootType.JAVA_FRAME));
254                   break;
255                 }
256 
257                 case 0x04: { // ROOT NATIVE STACK
258                   long objectId = hprof.getId();
259                   int threadSerialNumber = hprof.getU4();
260                   roots.add(new RootData(objectId, RootType.NATIVE_STACK));
261                   break;
262                 }
263 
264                 case 0x05: { // ROOT STICKY CLASS
265                   long objectId = hprof.getId();
266                   roots.add(new RootData(objectId, RootType.STICKY_CLASS));
267                   break;
268                 }
269 
270                 case 0x06: { // ROOT THREAD BLOCK
271                   long objectId = hprof.getId();
272                   int threadSerialNumber = hprof.getU4();
273                   roots.add(new RootData(objectId, RootType.THREAD_BLOCK));
274                   break;
275                 }
276 
277                 case 0x07: { // ROOT MONITOR USED
278                   long objectId = hprof.getId();
279                   roots.add(new RootData(objectId, RootType.MONITOR));
280                   break;
281                 }
282 
283                 case 0x08: { // ROOT THREAD OBJECT
284                   long objectId = hprof.getId();
285                   int threadSerialNumber = hprof.getU4();
286                   int stackSerialNumber = hprof.getU4();
287                   roots.add(new RootData(objectId, RootType.THREAD));
288                   break;
289                 }
290 
291                 case 0x20: { // CLASS DUMP
292                   ClassObjData data = new ClassObjData();
293                   long objectId = hprof.getId();
294                   int stackSerialNumber = hprof.getU4();
295                   long superClassId = hprof.getId();
296                   data.classLoaderId = hprof.getId();
297                   long signersId = hprof.getId();
298                   long protectionId = hprof.getId();
299                   long reserved1 = hprof.getId();
300                   long reserved2 = hprof.getId();
301                   int instanceSize = hprof.getU4();
302                   int constantPoolSize = hprof.getU2();
303                   for (int i = 0; i < constantPoolSize; ++i) {
304                     int index = hprof.getU2();
305                     Type type = hprof.getType();
306                     hprof.skip(type.size);
307                   }
308                   int numStaticFields = hprof.getU2();
309                   data.staticFields = new FieldValue[numStaticFields];
310                   AhatClassObj obj = classById.get(objectId);
311                   String clrClassName = obj.getName();
312                   long staticFieldsSize = 0;
313                   for (int i = 0; i < numStaticFields; ++i) {
314                     String obfName = strings.get(hprof.getId());
315                     String clrName = map.getFieldName(clrClassName, obfName);
316                     Type type = hprof.getType();
317                     Value value = hprof.getDeferredValue(type);
318                     staticFieldsSize += type.size;
319                     data.staticFields[i] = new FieldValue(clrName, type, value);
320                   }
321                   AhatClassObj superClass = classById.get(superClassId);
322                   int numInstanceFields = hprof.getU2();
323                   Field[] ifields = new Field[numInstanceFields];
324                   for (int i = 0; i < numInstanceFields; ++i) {
325                     String name = map.getFieldName(obj.getName(), strings.get(hprof.getId()));
326                     ifields[i] = new Field(name, hprof.getType());
327                   }
328                   Site site = sites.get(stackSerialNumber);
329 
330                   if (javaLangClass == null) {
331                     throw new HprofFormatException("No class definition found for java.lang.Class");
332                   }
333                   obj.initialize(heaps.getCurrentHeap(), site, javaLangClass);
334                   obj.initialize(superClass, instanceSize, ifields, staticFieldsSize);
335                   obj.setTemporaryUserData(data);
336                   break;
337                 }
338 
339                 case 0x21: { // INSTANCE DUMP
340                   long objectId = hprof.getId();
341                   int stackSerialNumber = hprof.getU4();
342                   long classId = hprof.getId();
343                   int numBytes = hprof.getU4();
344                   ClassInstData data = new ClassInstData(hprof.tell());
345                   hprof.skip(numBytes);
346 
347                   Site site = sites.get(stackSerialNumber);
348                   AhatClassObj classObj = classById.get(classId);
349                   AhatClassInstance obj = new AhatClassInstance(objectId);
350                   obj.initialize(heaps.getCurrentHeap(), site, classObj);
351                   obj.setTemporaryUserData(data);
352                   instances.add(obj);
353                   break;
354                 }
355 
356                 case 0x22: { // OBJECT ARRAY DUMP
357                   long objectId = hprof.getId();
358                   int stackSerialNumber = hprof.getU4();
359                   int length = hprof.getU4();
360                   long classId = hprof.getId();
361                   ObjArrayData data = new ObjArrayData(length, hprof.tell());
362                   hprof.skip(length * ID_SIZE);
363 
364                   Site site = sites.get(stackSerialNumber);
365                   AhatClassObj classObj = classById.get(classId);
366                   AhatArrayInstance obj = new AhatArrayInstance(objectId);
367                   obj.initialize(heaps.getCurrentHeap(), site, classObj);
368                   obj.setTemporaryUserData(data);
369                   instances.add(obj);
370                   break;
371                 }
372 
373                 case 0x23: { // PRIMITIVE ARRAY DUMP
374                   long objectId = hprof.getId();
375                   int stackSerialNumber = hprof.getU4();
376                   int length = hprof.getU4();
377                   Type type = hprof.getPrimitiveType();
378                   Site site = sites.get(stackSerialNumber);
379 
380                   AhatClassObj classObj = primArrayClasses[type.ordinal()];
381                   if (classObj == null) {
382                     throw new HprofFormatException(
383                         "No class definition found for " + type.name + "[]");
384                   }
385 
386                   AhatArrayInstance obj = new AhatArrayInstance(objectId);
387                   obj.initialize(heaps.getCurrentHeap(), site, classObj);
388                   instances.add(obj);
389                   switch (type) {
390                     case BOOLEAN: {
391                       boolean[] data = new boolean[length];
392                       for (int i = 0; i < length; ++i) {
393                         data[i] = hprof.getBool();
394                       }
395                       obj.initialize(data);
396                       break;
397                     }
398 
399                     case CHAR: {
400                       char[] data = new char[length];
401                       for (int i = 0; i < length; ++i) {
402                         data[i] = hprof.getChar();
403                       }
404                       obj.initialize(data);
405                       break;
406                     }
407 
408                     case FLOAT: {
409                       float[] data = new float[length];
410                       for (int i = 0; i < length; ++i) {
411                         data[i] = hprof.getFloat();
412                       }
413                       obj.initialize(data);
414                       break;
415                     }
416 
417                     case DOUBLE: {
418                       double[] data = new double[length];
419                       for (int i = 0; i < length; ++i) {
420                         data[i] = hprof.getDouble();
421                       }
422                       obj.initialize(data);
423                       break;
424                     }
425 
426                     case BYTE: {
427                       byte[] data = new byte[length];
428                       hprof.getBytes(data);
429                       obj.initialize(data);
430                       break;
431                     }
432 
433                     case SHORT: {
434                       short[] data = new short[length];
435                       for (int i = 0; i < length; ++i) {
436                         data[i] = hprof.getShort();
437                       }
438                       obj.initialize(data);
439                       break;
440                     }
441 
442                     case INT: {
443                       int[] data = new int[length];
444                       for (int i = 0; i < length; ++i) {
445                         data[i] = hprof.getInt();
446                       }
447                       obj.initialize(data);
448                       break;
449                     }
450 
451                     case LONG: {
452                       long[] data = new long[length];
453                       for (int i = 0; i < length; ++i) {
454                         data[i] = hprof.getLong();
455                       }
456                       obj.initialize(data);
457                       break;
458                     }
459                   }
460                   break;
461                 }
462 
463                 case 0x89: { // ROOT INTERNED STRING (ANDROID)
464                   long objectId = hprof.getId();
465                   roots.add(new RootData(objectId, RootType.INTERNED_STRING));
466                   break;
467                 }
468 
469                 case 0x8a: { // ROOT FINALIZING (ANDROID)
470                   long objectId = hprof.getId();
471                   roots.add(new RootData(objectId, RootType.FINALIZING));
472                   break;
473                 }
474 
475                 case 0x8b: { // ROOT DEBUGGER (ANDROID)
476                   long objectId = hprof.getId();
477                   roots.add(new RootData(objectId, RootType.DEBUGGER));
478                   break;
479                 }
480 
481                 case 0x8d: { // ROOT VM INTERNAL (ANDROID)
482                   long objectId = hprof.getId();
483                   roots.add(new RootData(objectId, RootType.VM_INTERNAL));
484                   break;
485                 }
486 
487                 case 0x8e: { // ROOT JNI MONITOR (ANDROID)
488                   long objectId = hprof.getId();
489                   int threadSerialNumber = hprof.getU4();
490                   int frameNumber = hprof.getU4();
491                   roots.add(new RootData(objectId, RootType.JNI_MONITOR));
492                   break;
493                 }
494 
495                 case 0xfe: { // HEAP DUMP INFO (ANDROID)
496                   int type = hprof.getU4();
497                   long stringId = hprof.getId();
498                   heaps.setCurrentHeap(strings.get(stringId));
499                   break;
500                 }
501 
502                 case 0xff: { // ROOT UNKNOWN
503                   long objectId = hprof.getId();
504                   roots.add(new RootData(objectId, RootType.UNKNOWN));
505                   break;
506                 }
507 
508                 default:
509                   throw new HprofFormatException(
510                       String.format("Unsupported heap dump sub tag 0x%02x", subtag));
511               }
512             }
513 
514             // Reset the file pointer back because we read the first byte into
515             // the next record.
516             hprof.skip(-1);
517             break;
518           }
519 
520           default:
521             // Ignore any other tags that we either don't know about or don't
522             // care about.
523             hprof.skip(recordLength);
524             break;
525         }
526       }
527 
528       instances.addAll(classes);
529     }
530 
531     // Sort roots and instances by id in preparation for the fixup pass.
532     Instances<AhatInstance> mInstances = new Instances<AhatInstance>(instances);
533     roots.sort(new Comparator<RootData>() {
534       @Override
535       public int compare(RootData a, RootData b) {
536         return Long.compare(a.id, b.id);
537       }
538     });
539     roots.add(null);
540 
541     // Fixup pass: Label the root instances and fix up references to instances
542     // that we couldn't previously resolve.
543     SuperRoot superRoot = new SuperRoot();
544     {
545       Iterator<RootData> ri = roots.iterator();
546       RootData root = ri.next();
547       for (AhatInstance inst : mInstances) {
548         long id = inst.getId();
549 
550         // Skip past any roots that don't have associated instances.
551         // It's not clear why there would be a root without an associated
552         // instance dump, but it does happen in practice, for example when
553         // taking heap dumps using the RI.
554         while (root != null && root.id < id) {
555           root = ri.next();
556         }
557 
558         // Check if this instance is a root, and if so, update its root types.
559         if (root != null && root.id == id) {
560           superRoot.addRoot(inst);
561           while (root != null && root.id == id) {
562             inst.addRootType(root.type);
563             root = ri.next();
564           }
565         }
566 
567         // Fixup the instance based on its type using the temporary data we
568         // saved during the first pass over the heap dump.
569         if (inst instanceof AhatClassInstance) {
570           ClassInstData data = (ClassInstData)inst.getTemporaryUserData();
571           inst.setTemporaryUserData(null);
572 
573           // Compute the size of the fields array in advance to avoid
574           // extra allocations and copies that would come from using an array
575           // list to collect the field values.
576           int numFields = 0;
577           for (AhatClassObj cls = inst.getClassObj(); cls != null; cls = cls.getSuperClassObj()) {
578             numFields += cls.getInstanceFields().length;
579           }
580 
581           Value[] fields = new Value[numFields];
582           int i = 0;
583           hprof.seek(data.position);
584           for (AhatClassObj cls = inst.getClassObj(); cls != null; cls = cls.getSuperClassObj()) {
585             for (Field field : cls.getInstanceFields()) {
586               fields[i++] = hprof.getValue(field.type, mInstances);
587             }
588           }
589           ((AhatClassInstance)inst).initialize(fields);
590         } else if (inst instanceof AhatClassObj) {
591           ClassObjData data = (ClassObjData)inst.getTemporaryUserData();
592           inst.setTemporaryUserData(null);
593           AhatInstance loader = mInstances.get(data.classLoaderId);
594           for (int i = 0; i < data.staticFields.length; ++i) {
595             FieldValue field = data.staticFields[i];
596             if (field.value instanceof DeferredInstanceValue) {
597               DeferredInstanceValue deferred = (DeferredInstanceValue)field.value;
598               data.staticFields[i] = new FieldValue(
599                   field.name, field.type, Value.pack(mInstances.get(deferred.getId())));
600             }
601           }
602           ((AhatClassObj)inst).initialize(loader, data.staticFields);
603         } else if (inst instanceof AhatArrayInstance && inst.getTemporaryUserData() != null) {
604           // TODO: Have specialized object array instance and check for that
605           // rather than checking for the presence of user data?
606           ObjArrayData data = (ObjArrayData)inst.getTemporaryUserData();
607           inst.setTemporaryUserData(null);
608           AhatInstance[] array = new AhatInstance[data.length];
609           hprof.seek(data.position);
610           for (int i = 0; i < data.length; i++) {
611             array[i] = mInstances.get(hprof.getId());
612           }
613           ((AhatArrayInstance)inst).initialize(array);
614         }
615       }
616     }
617 
618     hprof = null;
619     roots = null;
620     return new AhatSnapshot(superRoot, mInstances, heaps.heaps, rootSite);
621   }
622 
isEndOfHeapDumpSegment(int subtag)623   private static boolean isEndOfHeapDumpSegment(int subtag) {
624     return subtag == 0x1C || subtag == 0x2C;
625   }
626 
627   private static class RootData {
628     public long id;
629     public RootType type;
630 
RootData(long id, RootType type)631     public RootData(long id, RootType type) {
632       this.id = id;
633       this.type = type;
634     }
635   }
636 
637   private static class ClassInstData {
638     // The byte position in the hprof file where instance field data starts.
639     public int position;
640 
ClassInstData(int position)641     public ClassInstData(int position) {
642       this.position = position;
643     }
644   }
645 
646   private static class ObjArrayData {
647     public int length;          // Number of array elements.
648     public int position;        // Position in hprof file containing element data.
649 
ObjArrayData(int length, int position)650     public ObjArrayData(int length, int position) {
651       this.length = length;
652       this.position = position;
653     }
654   }
655 
656   private static class ClassObjData {
657     public long classLoaderId;
658     public FieldValue[] staticFields; // Contains DeferredInstanceValues.
659   }
660 
661   /**
662    * Dummy value representing a reference to an instance that has not yet been
663    * resolved.
664    * When first initializing class static fields, we don't yet know what kinds
665    * of objects Object references refer to. We use DeferredInstanceValue as
666    * a dummy kind of value to store the id of an object. In the fixup pass we
667    * resolve all the DeferredInstanceValues into their proper InstanceValues.
668    */
669   private static class DeferredInstanceValue extends Value {
670     private long mId;
671 
DeferredInstanceValue(long id)672     public DeferredInstanceValue(long id) {
673       mId = id;
674     }
675 
getId()676     public long getId() {
677       return mId;
678     }
679 
680     @Override
getType()681     Type getType() {
682       return Type.OBJECT;
683     }
684 
685     @Override
toString()686     public String toString() {
687       return String.format("0x%08x", mId);
688     }
689 
equals(Object other)690     @Override public boolean equals(Object other) {
691       if (other instanceof DeferredInstanceValue) {
692         DeferredInstanceValue value = (DeferredInstanceValue)other;
693         return mId == value.mId;
694       }
695       return false;
696     }
697   }
698 
699   /**
700    * A convenient abstraction for lazily building up the list of heaps seen in
701    * the heap dump.
702    */
703   private static class HeapList {
704     public List<AhatHeap> heaps = new ArrayList<AhatHeap>();
705     private AhatHeap current;
706 
getCurrentHeap()707     public AhatHeap getCurrentHeap() {
708       if (current == null) {
709         setCurrentHeap("default");
710       }
711       return current;
712     }
713 
setCurrentHeap(String name)714     public void setCurrentHeap(String name) {
715       for (AhatHeap heap : heaps) {
716         if (name.equals(heap.getName())) {
717           current = heap;
718           return;
719         }
720       }
721 
722       current = new AhatHeap(name, heaps.size());
723       heaps.add(current);
724     }
725   }
726 
727   /**
728    * A mapping from id to elements, where certain conditions are
729    * satisfied. The conditions are:
730    *  - all elements are defined before they are referenced.
731    *  - ids are densely packed in some range [a, b] where a is not
732    *    necessarily 0.
733    *  - there are not more than 2^31 elements defined.
734    */
735   private static class DenseMap<T> {
736     private String mElementType;
737 
738     // mValues behaves like a circular buffer.
739     // mKeyAt0 is the key corresponding to index 0 of mValues. Values with
740     // smaller keys will wrap around to the end of the mValues buffer. The
741     // buffer is expanded when it is no longer big enough to hold all the keys
742     // from mMinKey to mMaxKey.
743     private Object[] mValues;
744     private long mKeyAt0;
745     private long mMaxKey;
746     private long mMinKey;
747 
748     /**
749      * Constructs a DenseMap.
750      * @param elementType Human readable name describing the type of
751      *                    elements for error message if the required
752      *                    conditions are found not to hold.
753      */
DenseMap(String elementType)754     public DenseMap(String elementType) {
755       mElementType = elementType;
756     }
757 
put(long key, T value)758     public void put(long key, T value) {
759       if (mValues == null) {
760         mValues = new Object[8];
761         mValues[0] = value;
762         mKeyAt0 = key;
763         mMaxKey = key;
764         mMinKey = key;
765         return;
766       }
767 
768       long max = Math.max(mMaxKey, key);
769       long min = Math.min(mMinKey, key);
770       int count = (int)(max + 1 - min);
771       if (count > mValues.length) {
772         Object[] values = new Object[2 * count];
773 
774         // Copy over the values into the newly allocated larger buffer. It is
775         // convenient to move the value with mMinKey to index 0 when we make
776         // the copy.
777         for (int i = 0; i < mValues.length; ++i) {
778           values[i] = mValues[indexOf(i + mMinKey)];
779         }
780         mValues = values;
781         mKeyAt0 = mMinKey;
782       }
783       mMinKey = min;
784       mMaxKey = max;
785       mValues[indexOf(key)] = value;
786     }
787 
788     /**
789      * Returns the value for the given key.
790      * @throws HprofFormatException if there is no value with the key in the
791      *         given map.
792      */
get(long key)793     public T get(long key) throws HprofFormatException {
794       T value = null;
795       if (mValues != null && key >= mMinKey && key <= mMaxKey) {
796         value = (T)mValues[indexOf(key)];
797       }
798 
799       if (value == null) {
800         throw new HprofFormatException(String.format(
801               "%s with id 0x%x referenced before definition", mElementType, key));
802       }
803       return value;
804     }
805 
indexOf(long key)806     private int indexOf(long key) {
807       return ((int)(key - mKeyAt0) + mValues.length) % mValues.length;
808     }
809   }
810 
811   /**
812    * A mapping from id to elements, where we don't have nice conditions to
813    * work with.
814    */
815   private static class UnDenseMap<T> {
816     private String mElementType;
817     private Map<Long, T> mValues = new HashMap<Long, T>();
818 
819     /**
820      * Constructs an UnDenseMap.
821      * @param elementType Human readable name describing the type of
822      *                    elements for error message if the required
823      *                    conditions are found not to hold.
824      */
UnDenseMap(String elementType)825     public UnDenseMap(String elementType) {
826       mElementType = elementType;
827     }
828 
put(long key, T value)829     public void put(long key, T value) {
830       mValues.put(key, value);
831     }
832 
833     /**
834      * Returns the value for the given key.
835      * @throws HprofFormatException if there is no value with the key in the
836      *         given map.
837      */
get(long key)838     public T get(long key) throws HprofFormatException {
839       T value = mValues.get(key);
840       if (value == null) {
841         throw new HprofFormatException(String.format(
842               "%s with id 0x%x referenced before definition", mElementType, key));
843       }
844       return value;
845     }
846   }
847 
848   /**
849    * Wrapper around a ByteBuffer that presents a uniform interface for
850    * accessing data from an hprof file.
851    */
852   private static class HprofBuffer {
853     private ByteBuffer mBuffer;
854 
HprofBuffer(File path)855     public HprofBuffer(File path) throws IOException {
856       FileChannel channel = FileChannel.open(path.toPath(), StandardOpenOption.READ);
857       mBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
858       channel.close();
859     }
860 
HprofBuffer(ByteBuffer buffer)861     public HprofBuffer(ByteBuffer buffer) {
862       mBuffer = buffer;
863     }
864 
hasRemaining()865     public boolean hasRemaining() {
866       return mBuffer.hasRemaining();
867     }
868 
869     /**
870      * Return the current absolution position in the file.
871      */
tell()872     public int tell() {
873       return mBuffer.position();
874     }
875 
876     /**
877      * Seek to the given absolution position in the file.
878      */
seek(int position)879     public void seek(int position) {
880       mBuffer.position(position);
881     }
882 
883     /**
884      * Skip ahead in the file by the given delta bytes. Delta may be negative
885      * to skip backwards in the file.
886      */
skip(int delta)887     public void skip(int delta) {
888       seek(tell() + delta);
889     }
890 
getU1()891     public int getU1() {
892       return mBuffer.get() & 0xFF;
893     }
894 
getU2()895     public int getU2() {
896       return mBuffer.getShort() & 0xFFFF;
897     }
898 
getU4()899     public int getU4() {
900       return mBuffer.getInt();
901     }
902 
getId()903     public long getId() {
904       return mBuffer.getInt() & 0xFFFFFFFFL;
905     }
906 
getBool()907     public boolean getBool() {
908       return mBuffer.get() != 0;
909     }
910 
getChar()911     public char getChar() {
912       return mBuffer.getChar();
913     }
914 
getFloat()915     public float getFloat() {
916       return mBuffer.getFloat();
917     }
918 
getDouble()919     public double getDouble() {
920       return mBuffer.getDouble();
921     }
922 
getByte()923     public byte getByte() {
924       return mBuffer.get();
925     }
926 
getBytes(byte[] bytes)927     public void getBytes(byte[] bytes) {
928       mBuffer.get(bytes);
929     }
930 
getShort()931     public short getShort() {
932       return mBuffer.getShort();
933     }
934 
getInt()935     public int getInt() {
936       return mBuffer.getInt();
937     }
938 
getLong()939     public long getLong() {
940       return mBuffer.getLong();
941     }
942 
943     private static Type[] TYPES = new Type[] {
944       null, null, Type.OBJECT, null,
945         Type.BOOLEAN, Type.CHAR, Type.FLOAT, Type.DOUBLE,
946         Type.BYTE, Type.SHORT, Type.INT, Type.LONG
947     };
948 
getType()949     public Type getType() throws HprofFormatException {
950       int id = getU1();
951       Type type = id < TYPES.length ? TYPES[id] : null;
952       if (type == null) {
953         throw new HprofFormatException("Invalid basic type id: " + id);
954       }
955       return type;
956     }
957 
958     public Type getPrimitiveType() throws HprofFormatException {
959       Type type = getType();
960       if (type == Type.OBJECT) {
961         throw new HprofFormatException("Expected primitive type, but found type 'Object'");
962       }
963       return type;
964     }
965 
966     /**
967      * Get a value from the hprof file, using the given instances map to
968      * convert instance ids to their corresponding AhatInstance objects.
969      */
970     public Value getValue(Type type, Instances instances) {
971       switch (type) {
972         case OBJECT:  return Value.pack(instances.get(getId()));
973         case BOOLEAN: return Value.pack(getBool());
974         case CHAR: return Value.pack(getChar());
975         case FLOAT: return Value.pack(getFloat());
976         case DOUBLE: return Value.pack(getDouble());
977         case BYTE: return Value.pack(getByte());
978         case SHORT: return Value.pack(getShort());
979         case INT: return Value.pack(getInt());
980         case LONG: return Value.pack(getLong());
981         default: throw new AssertionError("unsupported enum member");
982       }
983     }
984 
985     /**
986      * Get a value from the hprof file. AhatInstance values are returned as
987      * DefferredInstanceValues rather than their corresponding AhatInstance
988      * objects.
989      */
990     public Value getDeferredValue(Type type) {
991       switch (type) {
992         case OBJECT: return new DeferredInstanceValue(getId());
993         case BOOLEAN: return Value.pack(getBool());
994         case CHAR: return Value.pack(getChar());
995         case FLOAT: return Value.pack(getFloat());
996         case DOUBLE: return Value.pack(getDouble());
997         case BYTE: return Value.pack(getByte());
998         case SHORT: return Value.pack(getShort());
999         case INT: return Value.pack(getInt());
1000         case LONG: return Value.pack(getLong());
1001         default: throw new AssertionError("unsupported enum member");
1002       }
1003     }
1004   }
1005 }
1006