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