• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 = AhatClassInstance.create(classObj, 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     // Ensure that no invalid classes are kept. In hprof dumps of RI it has been seen that two class
586     // objects would be stored for the same class name, but with different IDs. Only one of the IDs
587     // is present in the class dump data.
588     mInstances.removeIf(x -> x instanceof AhatClassObj && x.getTemporaryUserData() == null);
589 
590     roots.sort(new Comparator<RootData>() {
591       @Override
592       public int compare(RootData a, RootData b) {
593         return Long.compare(a.id, b.id);
594       }
595     });
596     roots.add(null);
597 
598     // Fixup pass: Label the root instances and fix up references to instances
599     // that we couldn't previously resolve.
600     SuperRoot superRoot = new SuperRoot();
601     {
602       progress.start("Resolving references", mInstances.size());
603       Iterator<RootData> ri = roots.iterator();
604       RootData root = ri.next();
605       for (AhatInstance inst : mInstances) {
606         progress.advance();
607         long id = inst.getId();
608 
609         // Skip past any roots that don't have associated instances.
610         // It's not clear why there would be a root without an associated
611         // instance dump, but it does happen in practice, for example when
612         // taking heap dumps using the RI.
613         while (root != null && root.id < id) {
614           root = ri.next();
615         }
616 
617         // Check if this instance is a root, and if so, update its root types.
618         if (root != null && root.id == id) {
619           superRoot.addRoot(inst);
620           while (root != null && root.id == id) {
621             inst.addRootType(root.type);
622             root = ri.next();
623           }
624         }
625 
626         // Fixup the instance based on its type using the temporary data we
627         // saved during the first pass over the heap dump.
628         if (inst instanceof AhatClassInstance) {
629           ClassInstData data = (ClassInstData)inst.getTemporaryUserData();
630           inst.setTemporaryUserData(null);
631 
632           // Compute the size of the fields array in advance to avoid
633           // extra allocations and copies that would come from using an array
634           // list to collect the field values.
635           int numFields = 0;
636           for (AhatClassObj cls = inst.getClassObj(); cls != null; cls = cls.getSuperClassObj()) {
637             numFields += cls.getInstanceFields().length;
638           }
639 
640           Value[] fields = new Value[numFields];
641           int i = 0;
642           hprof.seek(data.position);
643           for (AhatClassObj cls = inst.getClassObj(); cls != null; cls = cls.getSuperClassObj()) {
644             for (Field field : cls.getInstanceFields()) {
645               fields[i++] = hprof.getValue(field.type, mInstances);
646             }
647           }
648           ((AhatClassInstance)inst).initialize(fields);
649         } else if (inst instanceof AhatClassObj) {
650           ClassObjData data = (ClassObjData)inst.getTemporaryUserData();
651           inst.setTemporaryUserData(null);
652           AhatInstance loader = mInstances.get(data.classLoaderId);
653           for (int i = 0; i < data.staticFields.length; ++i) {
654             FieldValue field = data.staticFields[i];
655             if (field.value instanceof DeferredInstanceValue) {
656               DeferredInstanceValue deferred = (DeferredInstanceValue)field.value;
657               data.staticFields[i] = new FieldValue(
658                   field.name, field.type, Value.pack(mInstances.get(deferred.getId())));
659             }
660           }
661           ((AhatClassObj)inst).initialize(loader, data.staticFields);
662         } else if (inst instanceof AhatArrayInstance && inst.getTemporaryUserData() != null) {
663           // TODO: Have specialized object array instance and check for that
664           // rather than checking for the presence of user data?
665           ObjArrayData data = (ObjArrayData)inst.getTemporaryUserData();
666           inst.setTemporaryUserData(null);
667           AhatInstance[] array = new AhatInstance[data.length];
668           hprof.seek(data.position);
669           for (int i = 0; i < data.length; i++) {
670             array[i] = mInstances.get(hprof.getId());
671           }
672           ((AhatArrayInstance)inst).initialize(array);
673         }
674       }
675       progress.done();
676     }
677 
678     hprof = null;
679     roots = null;
680     return new AhatSnapshot(superRoot, mInstances, heaps.heaps, rootSite, progress, retained);
681   }
682 
683   private static class RootData {
684     public long id;
685     public RootType type;
686 
RootData(long id, RootType type)687     public RootData(long id, RootType type) {
688       this.id = id;
689       this.type = type;
690     }
691   }
692 
693   private static class ClassInstData {
694     // The byte position in the hprof file where instance field data starts.
695     public int position;
696 
ClassInstData(int position)697     public ClassInstData(int position) {
698       this.position = position;
699     }
700   }
701 
702   private static class ObjArrayData {
703     public int length;          // Number of array elements.
704     public int position;        // Position in hprof file containing element data.
705 
ObjArrayData(int length, int position)706     public ObjArrayData(int length, int position) {
707       this.length = length;
708       this.position = position;
709     }
710   }
711 
712   private static class ClassObjData {
713     public long classLoaderId;
714     public FieldValue[] staticFields; // Contains DeferredInstanceValues.
715   }
716 
717   /**
718    * Placeholder value representing a reference to an instance that has not yet been
719    * resolved.
720    * When first initializing class static fields, we don't yet know what kinds
721    * of objects Object references refer to. We use DeferredInstanceValue as
722    * a placeholder kind of value to store the id of an object. In the fixup pass we
723    * resolve all the DeferredInstanceValues into their proper InstanceValues.
724    */
725   private static class DeferredInstanceValue extends Value {
726     private long mId;
727 
DeferredInstanceValue(long id)728     public DeferredInstanceValue(long id) {
729       mId = id;
730     }
731 
getId()732     public long getId() {
733       return mId;
734     }
735 
736     @Override
getType()737     Type getType() {
738       return Type.OBJECT;
739     }
740 
741     @Override
toString()742     public String toString() {
743       return String.format("0x%08x", mId);
744     }
745 
hashCode()746     @Override public int hashCode() {
747       return Objects.hash(mId);
748     }
749 
equals(Object other)750     @Override public boolean equals(Object other) {
751       if (other instanceof DeferredInstanceValue) {
752         DeferredInstanceValue value = (DeferredInstanceValue)other;
753         return mId == value.mId;
754       }
755       return false;
756     }
757   }
758 
759   /**
760    * A convenient abstraction for lazily building up the list of heaps seen in
761    * the heap dump.
762    */
763   private static class HeapList {
764     public List<AhatHeap> heaps = new ArrayList<AhatHeap>();
765     private AhatHeap current;
766 
getCurrentHeap()767     public AhatHeap getCurrentHeap() {
768       if (current == null) {
769         setCurrentHeap("default");
770       }
771       return current;
772     }
773 
setCurrentHeap(String name)774     public void setCurrentHeap(String name) {
775       for (AhatHeap heap : heaps) {
776         if (name.equals(heap.getName())) {
777           current = heap;
778           return;
779         }
780       }
781 
782       current = new AhatHeap(name, heaps.size());
783       heaps.add(current);
784     }
785   }
786 
787   /**
788    * A mapping from id to elements, where certain conditions are
789    * satisfied. The conditions are:
790    *  - all elements are defined before they are referenced.
791    *  - ids are densely packed in some range [a, b] where a is not
792    *    necessarily 0.
793    *  - there are not more than 2^31 elements defined.
794    */
795   private static class DenseMap<T> {
796     private String mElementType;
797 
798     // mValues behaves like a circular buffer.
799     // mKeyAt0 is the key corresponding to index 0 of mValues. Values with
800     // smaller keys will wrap around to the end of the mValues buffer. The
801     // buffer is expanded when it is no longer big enough to hold all the keys
802     // from mMinKey to mMaxKey.
803     private Object[] mValues;
804     private long mKeyAt0;
805     private long mMaxKey;
806     private long mMinKey;
807 
808     /**
809      * Constructs a DenseMap.
810      * @param elementType Human readable name describing the type of
811      *                    elements for error message if the required
812      *                    conditions are found not to hold.
813      */
DenseMap(String elementType)814     public DenseMap(String elementType) {
815       mElementType = elementType;
816     }
817 
put(long key, T value)818     public void put(long key, T value) {
819       if (mValues == null) {
820         mValues = new Object[8];
821         mValues[0] = value;
822         mKeyAt0 = key;
823         mMaxKey = key;
824         mMinKey = key;
825         return;
826       }
827 
828       long max = Math.max(mMaxKey, key);
829       long min = Math.min(mMinKey, key);
830       int count = (int)(max + 1 - min);
831       if (count > mValues.length) {
832         Object[] values = new Object[2 * count];
833 
834         // Copy over the values into the newly allocated larger buffer. It is
835         // convenient to move the value with mMinKey to index 0 when we make
836         // the copy.
837         for (int i = 0; i < mValues.length; ++i) {
838           values[i] = mValues[indexOf(i + mMinKey)];
839         }
840         mValues = values;
841         mKeyAt0 = mMinKey;
842       }
843       mMinKey = min;
844       mMaxKey = max;
845       mValues[indexOf(key)] = value;
846     }
847 
848     /**
849      * Returns the value for the given key.
850      * @throws HprofFormatException if there is no value with the key in the
851      *         given map.
852      */
get(long key)853     public T get(long key) throws HprofFormatException {
854       T value = null;
855       if (mValues != null && key >= mMinKey && key <= mMaxKey) {
856         value = (T)mValues[indexOf(key)];
857       }
858 
859       if (value == null) {
860         throw new HprofFormatException(String.format(
861               "%s with id 0x%x referenced before definition", mElementType, key));
862       }
863       return value;
864     }
865 
indexOf(long key)866     private int indexOf(long key) {
867       return ((int)(key - mKeyAt0) + mValues.length) % mValues.length;
868     }
869   }
870 
871   /**
872    * A mapping from id to elements, where we don't have nice conditions to
873    * work with.
874    */
875   private static class UnDenseMap<T> {
876     private String mElementType;
877     private Map<Long, T> mValues = new HashMap<Long, T>();
878 
879     /**
880      * Constructs an UnDenseMap.
881      * @param elementType Human readable name describing the type of
882      *                    elements for error message if the required
883      *                    conditions are found not to hold.
884      */
UnDenseMap(String elementType)885     public UnDenseMap(String elementType) {
886       mElementType = elementType;
887     }
888 
put(long key, T value)889     public void put(long key, T value) {
890       mValues.put(key, value);
891     }
892 
893     /**
894      * Returns the value for the given key.
895      * @throws HprofFormatException if there is no value with the key in the
896      *         given map.
897      */
get(long key)898     public T get(long key) throws HprofFormatException {
899       T value = mValues.get(key);
900       if (value == null) {
901         throw new HprofFormatException(String.format(
902               "%s with id 0x%x referenced before definition", mElementType, key));
903       }
904       return value;
905     }
906   }
907 
908   /**
909    * Wrapper around a ByteBuffer that presents a uniform interface for
910    * accessing data from an hprof file.
911    */
912   private static class HprofBuffer {
913     private boolean mIdSize8;
914     private final ByteBuffer mBuffer;
915 
HprofBuffer(File path)916     public HprofBuffer(File path) throws IOException {
917       FileChannel channel = FileChannel.open(path.toPath(), StandardOpenOption.READ);
918       mBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
919       channel.close();
920     }
921 
HprofBuffer(ByteBuffer buffer)922     public HprofBuffer(ByteBuffer buffer) {
923       mBuffer = buffer;
924     }
925 
setIdSize8()926     public void setIdSize8() {
927       mIdSize8 = true;
928     }
929 
hasRemaining()930     public boolean hasRemaining() {
931       return mBuffer.hasRemaining();
932     }
933 
934     /**
935      * Returns the size of the file in bytes.
936      */
size()937     public int size() {
938       return mBuffer.capacity();
939     }
940 
941     /**
942      * Return the current absolution position in the file.
943      */
tell()944     public int tell() {
945       return mBuffer.position();
946     }
947 
948     /**
949      * Seek to the given absolution position in the file.
950      */
seek(int position)951     public void seek(int position) {
952       mBuffer.position(position);
953     }
954 
955     /**
956      * Skip ahead in the file by the given delta bytes. Delta may be negative
957      * to skip backwards in the file.
958      */
skip(int delta)959     public void skip(int delta) {
960       seek(tell() + delta);
961     }
962 
getU1()963     public int getU1() {
964       return mBuffer.get() & 0xFF;
965     }
966 
getU2()967     public int getU2() {
968       return mBuffer.getShort() & 0xFFFF;
969     }
970 
getU4()971     public int getU4() {
972       return mBuffer.getInt();
973     }
974 
getId()975     public long getId() {
976       if (mIdSize8) {
977         return mBuffer.getLong();
978       } else {
979         return mBuffer.getInt() & 0xFFFFFFFFL;
980       }
981     }
982 
getBool()983     public boolean getBool() {
984       return mBuffer.get() != 0;
985     }
986 
getChar()987     public char getChar() {
988       return mBuffer.getChar();
989     }
990 
getFloat()991     public float getFloat() {
992       return mBuffer.getFloat();
993     }
994 
getDouble()995     public double getDouble() {
996       return mBuffer.getDouble();
997     }
998 
getByte()999     public byte getByte() {
1000       return mBuffer.get();
1001     }
1002 
getBytes(byte[] bytes)1003     public void getBytes(byte[] bytes) {
1004       mBuffer.get(bytes);
1005     }
1006 
getShort()1007     public short getShort() {
1008       return mBuffer.getShort();
1009     }
1010 
getInt()1011     public int getInt() {
1012       return mBuffer.getInt();
1013     }
1014 
getLong()1015     public long getLong() {
1016       return mBuffer.getLong();
1017     }
1018 
1019     private static Type[] TYPES = new Type[] {
1020       null, null, Type.OBJECT, null,
1021         Type.BOOLEAN, Type.CHAR, Type.FLOAT, Type.DOUBLE,
1022         Type.BYTE, Type.SHORT, Type.INT, Type.LONG
1023     };
1024 
getType()1025     public Type getType() throws HprofFormatException {
1026       int id = getU1();
1027       Type type = id < TYPES.length ? TYPES[id] : null;
1028       if (type == null) {
1029         throw new HprofFormatException("Invalid basic type id: " + id);
1030       }
1031       return type;
1032     }
1033 
1034     public Type getPrimitiveType() throws HprofFormatException {
1035       Type type = getType();
1036       if (type == Type.OBJECT) {
1037         throw new HprofFormatException("Expected primitive type, but found type 'Object'");
1038       }
1039       return type;
1040     }
1041 
1042     /**
1043      * Get a value from the hprof file, using the given instances map to
1044      * convert instance ids to their corresponding AhatInstance objects.
1045      */
1046     public Value getValue(Type type, Instances instances) {
1047       switch (type) {
1048         case OBJECT:  return Value.pack(instances.get(getId()));
1049         case BOOLEAN: return Value.pack(getBool());
1050         case CHAR: return Value.pack(getChar());
1051         case FLOAT: return Value.pack(getFloat());
1052         case DOUBLE: return Value.pack(getDouble());
1053         case BYTE: return Value.pack(getByte());
1054         case SHORT: return Value.pack(getShort());
1055         case INT: return Value.pack(getInt());
1056         case LONG: return Value.pack(getLong());
1057         default: throw new AssertionError("unsupported enum member");
1058       }
1059     }
1060 
1061     /**
1062      * Get a value from the hprof file. AhatInstance values are returned as
1063      * DefferredInstanceValues rather than their corresponding AhatInstance
1064      * objects.
1065      */
1066     public Value getDeferredValue(Type type) {
1067       switch (type) {
1068         case OBJECT: return new DeferredInstanceValue(getId());
1069         case BOOLEAN: return Value.pack(getBool());
1070         case CHAR: return Value.pack(getChar());
1071         case FLOAT: return Value.pack(getFloat());
1072         case DOUBLE: return Value.pack(getDouble());
1073         case BYTE: return Value.pack(getByte());
1074         case SHORT: return Value.pack(getShort());
1075         case INT: return Value.pack(getInt());
1076         case LONG: return Value.pack(getLong());
1077         default: throw new AssertionError("unsupported enum member");
1078       }
1079     }
1080   }
1081 
1082   // ART outputs class names such as:
1083   //   "java.lang.Class", "java.lang.Class[]", "byte", "byte[]"
1084   // RI outputs class names such as:
1085   //   "java/lang/Class", '[Ljava/lang/Class;", N/A, "[B"
1086   //
1087   // This function converts all class names to match the ART format, which is
1088   // assumed elsewhere in ahat.
1089   private static String normalizeClassName(String name) throws HprofFormatException {
1090     int numDimensions = 0;
1091     while (name.startsWith("[")) {
1092       numDimensions++;
1093       name = name.substring(1);
1094     }
1095 
1096     if (numDimensions > 0) {
1097       // If there was an array type signature to start, then interpret the
1098       // class name as a type signature.
1099       switch (name.charAt(0)) {
1100         case 'Z': name = "boolean"; break;
1101         case 'B': name = "byte"; break;
1102         case 'C': name = "char"; break;
1103         case 'S': name = "short"; break;
1104         case 'I': name = "int"; break;
1105         case 'J': name = "long"; break;
1106         case 'F': name = "float"; break;
1107         case 'D': name = "double"; break;
1108         case 'L': name = name.substring(1, name.length() - 1); break;
1109         default: throw new HprofFormatException("Invalid type signature in class name: " + name);
1110       }
1111     }
1112 
1113     name = name.replace('/', '.');
1114 
1115     for (int i = 0; i < numDimensions; ++i) {
1116       name += "[]";
1117     }
1118 
1119     return name;
1120   }
1121 }
1122