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