1 /*
2  * Copyright (C) 2016 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 java.awt.image.BufferedImage;
20 import java.util.Iterator;
21 import java.util.NoSuchElementException;
22 
23 /**
24  * A typical Java object from a parsed heap dump.
25  * Note that this is used for Java objects that are instances of classes (as
26  * opposed to arrays), not for class objects themselves.
27  * See {@link AhatClassObj } for the representation of class objects.
28  * <p>
29  * This class provides a method for iterating over the instance fields of the
30  * object in addition to those methods inherited from {@link AhatInstance}.
31  */
32 public class AhatClassInstance extends AhatInstance {
33   // Instance fields of the object. These are stored in order of the instance
34   // field descriptors from the class object, starting with this class first,
35   // followed by the super class, and so on. We store the values separate from
36   // the field types and names to save memory.
37   private Value[] mFields;
38 
AhatClassInstance(long id)39   AhatClassInstance(long id) {
40     super(id);
41   }
42 
initialize(Value[] fields)43   void initialize(Value[] fields) {
44     mFields = fields;
45   }
46 
47   @Override
getExtraJavaSize()48   long getExtraJavaSize() {
49     return 0;
50   }
51 
getField(String fieldName)52   @Override public Value getField(String fieldName) {
53     for (FieldValue field : getInstanceFields()) {
54       if (fieldName.equals(field.name)) {
55         return field.value;
56       }
57     }
58     return null;
59   }
60 
getRefField(String fieldName)61   @Override public AhatInstance getRefField(String fieldName) {
62     Value value = getField(fieldName);
63     return value == null ? null : value.asAhatInstance();
64   }
65 
66   /**
67    * Read an int field of an instance.
68    * The field is assumed to be an int type.
69    * Returns <code>def</code> if the field value is not an int or could not be
70    * read.
71    */
getIntField(String fieldName, Integer def)72   private Integer getIntField(String fieldName, Integer def) {
73     Value value = getField(fieldName);
74     if (value == null || !value.isInteger()) {
75       return def;
76     }
77     return value.asInteger();
78   }
79 
80   /**
81    * Read a long field of this instance.
82    * The field is assumed to be a long type.
83    * Returns <code>def</code> if the field value is not an long or could not
84    * be read.
85    */
getLongField(String fieldName, Long def)86   private Long getLongField(String fieldName, Long def) {
87     Value value = getField(fieldName);
88     if (value == null || !value.isLong()) {
89       return def;
90     }
91     return value.asLong();
92   }
93 
94   /**
95    * Returns the list of class instance fields for this instance.
96    * Includes values of field inherited from the superclass of this instance.
97    * The fields are returned in no particular order.
98    *
99    * @return Iterable over the instance field values.
100    */
getInstanceFields()101   public Iterable<FieldValue> getInstanceFields() {
102     return new InstanceFieldIterator(mFields, getClassObj());
103   }
104 
105   @Override
getReferences()106   Iterable<Reference> getReferences() {
107     if (isInstanceOfClass("java.lang.ref.Reference")) {
108       return new WeakReferentReferenceIterator();
109     }
110     return new StrongReferenceIterator();
111   }
112 
113   /**
114    * Returns true if this is an instance of a (subclass of a) class with the
115    * given name.
116    */
isInstanceOfClass(String className)117   private boolean isInstanceOfClass(String className) {
118     AhatClassObj cls = getClassObj();
119     while (cls != null) {
120       if (className.equals(cls.getName())) {
121         return true;
122       }
123       cls = cls.getSuperClassObj();
124     }
125     return false;
126   }
127 
asString(int maxChars)128   @Override public String asString(int maxChars) {
129     if (!isInstanceOfClass("java.lang.String")) {
130       return null;
131     }
132 
133     Value value = getField("value");
134     if (value == null || !value.isAhatInstance()) {
135       return null;
136     }
137 
138     AhatInstance inst = value.asAhatInstance();
139     if (inst.isArrayInstance()) {
140       AhatArrayInstance chars = inst.asArrayInstance();
141       int numChars = chars.getLength();
142       int count = getIntField("count", numChars);
143       int offset = getIntField("offset", 0);
144       return chars.asMaybeCompressedString(offset, count, maxChars);
145     }
146     return null;
147   }
148 
getReferent()149   @Override public AhatInstance getReferent() {
150     if (isInstanceOfClass("java.lang.ref.Reference")) {
151       return getRefField("referent");
152     }
153     return null;
154   }
155 
getDexCacheLocation(int maxChars)156   @Override public String getDexCacheLocation(int maxChars) {
157     if (isInstanceOfClass("java.lang.DexCache")) {
158       AhatInstance location = getRefField("location");
159       if (location != null) {
160         return location.asString(maxChars);
161       }
162     }
163     return null;
164   }
165 
getAssociatedBitmapInstance()166   @Override public AhatInstance getAssociatedBitmapInstance() {
167     return getBitmapInfo() == null ? null : this;
168   }
169 
isClassInstance()170   @Override public boolean isClassInstance() {
171     return true;
172   }
173 
asClassInstance()174   @Override public AhatClassInstance asClassInstance() {
175     return this;
176   }
177 
toString()178   @Override public String toString() {
179     return String.format("%s@%08x", getClassName(), getId());
180   }
181 
182   /**
183    * Read the given field from the given instance.
184    * The field is assumed to be a byte[] field.
185    * Returns null if the field value is null, not a byte[] or could not be read.
186    */
getByteArrayField(String fieldName)187   private byte[] getByteArrayField(String fieldName) {
188     AhatInstance field = getRefField(fieldName);
189     return field == null ? null : field.asByteArray();
190   }
191 
192   private static class BitmapInfo {
193     public final int width;
194     public final int height;
195     public final byte[] buffer;
196 
BitmapInfo(int width, int height, byte[] buffer)197     public BitmapInfo(int width, int height, byte[] buffer) {
198       this.width = width;
199       this.height = height;
200       this.buffer = buffer;
201     }
202   }
203 
204   /**
205    * Return bitmap info for this object, or null if no appropriate bitmap
206    * info is available.
207    */
getBitmapInfo()208   private BitmapInfo getBitmapInfo() {
209     if (!isInstanceOfClass("android.graphics.Bitmap")) {
210       return null;
211     }
212 
213     Integer width = getIntField("mWidth", null);
214     if (width == null) {
215       return null;
216     }
217 
218     Integer height = getIntField("mHeight", null);
219     if (height == null) {
220       return null;
221     }
222 
223     byte[] buffer = getByteArrayField("mBuffer");
224     if (buffer == null) {
225       return null;
226     }
227 
228     if (buffer.length < 4 * height * width) {
229       return null;
230     }
231 
232     return new BitmapInfo(width, height, buffer);
233 
234   }
235 
asBitmap()236   @Override public BufferedImage asBitmap() {
237     BitmapInfo info = getBitmapInfo();
238     if (info == null) {
239       return null;
240     }
241 
242     // Convert the raw data to an image
243     // Convert BGRA to ABGR
244     int[] abgr = new int[info.height * info.width];
245     for (int i = 0; i < abgr.length; i++) {
246       abgr[i] = (
247           (((int) info.buffer[i * 4 + 3] & 0xFF) << 24)
248           + (((int) info.buffer[i * 4 + 0] & 0xFF) << 16)
249           + (((int) info.buffer[i * 4 + 1] & 0xFF) << 8)
250           + ((int) info.buffer[i * 4 + 2] & 0xFF));
251     }
252 
253     BufferedImage bitmap = new BufferedImage(
254         info.width, info.height, BufferedImage.TYPE_4BYTE_ABGR);
255     bitmap.setRGB(0, 0, info.width, info.height, abgr, 0, info.width);
256     return bitmap;
257   }
258 
259   @Override
asRegisteredNativeAllocation()260   RegisteredNativeAllocation asRegisteredNativeAllocation() {
261     if (!isInstanceOfClass("sun.misc.Cleaner")) {
262       return null;
263     }
264 
265     Value vthunk = getField("thunk");
266     if (vthunk == null || !vthunk.isAhatInstance()) {
267       return null;
268     }
269 
270     AhatClassInstance thunk = vthunk.asAhatInstance().asClassInstance();
271     if (thunk == null
272         || !thunk.isInstanceOfClass("libcore.util.NativeAllocationRegistry$CleanerThunk")) {
273       return null;
274     }
275 
276     Value vregistry = thunk.getField("this$0");
277     if (vregistry == null || !vregistry.isAhatInstance()) {
278       return null;
279     }
280 
281     AhatClassInstance registry = vregistry.asAhatInstance().asClassInstance();
282     if (registry == null || !registry.isInstanceOfClass("libcore.util.NativeAllocationRegistry")) {
283       return null;
284     }
285 
286     Value size = registry.getField("size");
287     if (!size.isLong()) {
288       return null;
289     }
290 
291     Value referent = getField("referent");
292     if (referent == null || !referent.isAhatInstance()) {
293       return null;
294     }
295 
296     RegisteredNativeAllocation rna = new RegisteredNativeAllocation();
297     rna.referent = referent.asAhatInstance();
298     rna.size = size.asLong();
299     return rna;
300   }
301 
302   private static class InstanceFieldIterator implements Iterable<FieldValue>,
303                                                         Iterator<FieldValue> {
304     // The complete list of instance field values to iterate over, including
305     // superclass field values.
306     private Value[] mValues;
307     private int mValueIndex;
308 
309     // The list of field descriptors specific to the current class in the
310     // class hierarchy, not including superclass field descriptors.
311     // mFields and mFieldIndex are reset each time we walk up to the next
312     // superclass in the call hierarchy.
313     private Field[] mFields;
314     private int mFieldIndex;
315     private AhatClassObj mNextClassObj;
316 
InstanceFieldIterator(Value[] values, AhatClassObj classObj)317     public InstanceFieldIterator(Value[] values, AhatClassObj classObj) {
318       mValues = values;
319       mFields = classObj.getInstanceFields();
320       mValueIndex = 0;
321       mFieldIndex = 0;
322       mNextClassObj = classObj.getSuperClassObj();
323     }
324 
325     @Override
hasNext()326     public boolean hasNext() {
327       // If we have reached the end of the fields in the current class,
328       // continue walking up the class hierarchy to get superclass fields as
329       // well.
330       while (mFieldIndex == mFields.length && mNextClassObj != null) {
331         mFields = mNextClassObj.getInstanceFields();
332         mFieldIndex = 0;
333         mNextClassObj = mNextClassObj.getSuperClassObj();
334       }
335       return mFieldIndex < mFields.length;
336     }
337 
338     @Override
next()339     public FieldValue next() {
340       if (!hasNext()) {
341         throw new NoSuchElementException();
342       }
343       Field field = mFields[mFieldIndex++];
344       Value value = mValues[mValueIndex++];
345       return new FieldValue(field.name, field.type, value);
346     }
347 
348     @Override
iterator()349     public Iterator<FieldValue> iterator() {
350       return this;
351     }
352   }
353 
354   /**
355    * A Reference iterator that iterates over the fields of this instance
356    * assuming all field references are strong references.
357    */
358   private class StrongReferenceIterator implements Iterable<Reference>,
359                                                    Iterator<Reference> {
360     private Iterator<FieldValue> mIter = getInstanceFields().iterator();
361     private Reference mNext = null;
362 
363     @Override
hasNext()364     public boolean hasNext() {
365       while (mNext == null && mIter.hasNext()) {
366         FieldValue field = mIter.next();
367         if (field.value != null && field.value.isAhatInstance()) {
368           AhatInstance ref = field.value.asAhatInstance();
369           mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, true);
370         }
371       }
372       return mNext != null;
373     }
374 
375     @Override
next()376     public Reference next() {
377       if (!hasNext()) {
378         throw new NoSuchElementException();
379       }
380       Reference next = mNext;
381       mNext = null;
382       return next;
383     }
384 
385     @Override
iterator()386     public Iterator<Reference> iterator() {
387       return this;
388     }
389   }
390 
391   /**
392    * A Reference iterator that iterates over the fields of a subclass of
393    * java.lang.ref.Reference, where the 'referent' field is considered weak.
394    */
395   private class WeakReferentReferenceIterator implements Iterable<Reference>,
396                                                          Iterator<Reference> {
397     private Iterator<FieldValue> mIter = getInstanceFields().iterator();
398     private Reference mNext = null;
399 
400     @Override
hasNext()401     public boolean hasNext() {
402       while (mNext == null && mIter.hasNext()) {
403         FieldValue field = mIter.next();
404         if (field.value != null && field.value.isAhatInstance()) {
405           boolean strong = !field.name.equals("referent");
406           AhatInstance ref = field.value.asAhatInstance();
407           mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, strong);
408         }
409       }
410       return mNext != null;
411     }
412 
413     @Override
next()414     public Reference next() {
415       if (!hasNext()) {
416         throw new NoSuchElementException();
417       }
418       Reference next = mNext;
419       mNext = null;
420       return next;
421     }
422 
423     @Override
iterator()424     public Iterator<Reference> iterator() {
425       return this;
426     }
427   }
428 }
429