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     return new ReferenceIterator();
108   }
109 
asString(int maxChars)110   @Override public String asString(int maxChars) {
111     if (!isInstanceOfClass("java.lang.String")) {
112       return null;
113     }
114 
115     Value value = getField("value");
116     if (value == null || !value.isAhatInstance()) {
117       return null;
118     }
119 
120     AhatInstance inst = value.asAhatInstance();
121     if (inst.isArrayInstance()) {
122       AhatArrayInstance chars = inst.asArrayInstance();
123       int numChars = chars.getLength();
124       int count = getIntField("count", numChars);
125       int offset = getIntField("offset", 0);
126       return chars.asMaybeCompressedString(offset, count, maxChars);
127     }
128     return null;
129   }
130 
getReferent()131   @Override public AhatInstance getReferent() {
132     if (isInstanceOfClass("java.lang.ref.Reference")) {
133       return getRefField("referent");
134     }
135     return null;
136   }
137 
getDexCacheLocation(int maxChars)138   @Override public String getDexCacheLocation(int maxChars) {
139     if (isInstanceOfClass("java.lang.DexCache")) {
140       AhatInstance location = getRefField("location");
141       if (location != null) {
142         return location.asString(maxChars);
143       }
144     }
145     return null;
146   }
147 
getBinderProxyInterfaceName()148   @Override public String getBinderProxyInterfaceName() {
149     if (isInstanceOfClass("android.os.BinderProxy")) {
150       for (AhatInstance inst : getReverseReferences()) {
151         String className = inst.getClassName();
152         if (className.endsWith("$Stub$Proxy")) {
153           Value value = inst.getField("mRemote");
154           if (value != null && value.asAhatInstance() == this) {
155             return className.substring(0, className.lastIndexOf("$Stub$Proxy"));
156           }
157         }
158       }
159     }
160     return null;
161   }
162 
getBinderTokenDescriptor()163   @Override public String getBinderTokenDescriptor() {
164     String descriptor = getBinderDescriptor();
165     if (descriptor == null) {
166       return null;
167     }
168 
169     if (isInstanceOfClass(descriptor + "$Stub")) {
170       // This is an instance of an auto-generated interface class, and
171       // therefore not a binder token.
172       return null;
173     }
174 
175     return descriptor;
176   }
177 
getBinderStubInterfaceName()178   @Override public String getBinderStubInterfaceName() {
179     String descriptor = getBinderDescriptor();
180     if (descriptor == null || descriptor.isEmpty()) {
181       // Binder interface stubs always have a non-empty descriptor
182       return null;
183     }
184 
185     // We only consider something a binder service if it's an instance of the
186     // auto-generated descriptor$Stub class.
187     if (isInstanceOfClass(descriptor + "$Stub")) {
188       return descriptor;
189     }
190 
191     return null;
192   }
193 
getAssociatedBitmapInstance()194   @Override public AhatInstance getAssociatedBitmapInstance() {
195     return getBitmapInfo() == null ? null : this;
196   }
197 
isClassInstance()198   @Override public boolean isClassInstance() {
199     return true;
200   }
201 
asClassInstance()202   @Override public AhatClassInstance asClassInstance() {
203     return this;
204   }
205 
toString()206   @Override public String toString() {
207     return String.format("%s@%08x", getClassName(), getId());
208   }
209 
210   /**
211    * Returns the descriptor of an android.os.Binder object.
212    * If no descriptor is set, returns an empty string.
213    * If the object is not an android.os.Binder object, returns null.
214    */
getBinderDescriptor()215   private String getBinderDescriptor() {
216     if (isInstanceOfClass("android.os.Binder")) {
217       Value value = getField("mDescriptor");;
218 
219       if (value == null) {
220         return "";
221       } else {
222         return value.asAhatInstance().asString();
223       }
224     } else {
225       return null;
226     }
227   }
228 
229   /**
230    * Read the given field from the given instance.
231    * The field is assumed to be a byte[] field.
232    * Returns null if the field value is null, not a byte[] or could not be read.
233    */
getByteArrayField(String fieldName)234   private byte[] getByteArrayField(String fieldName) {
235     AhatInstance field = getRefField(fieldName);
236     return field == null ? null : field.asByteArray();
237   }
238 
239   private static class BitmapInfo {
240     public final int width;
241     public final int height;
242     public final byte[] buffer;
243 
BitmapInfo(int width, int height, byte[] buffer)244     public BitmapInfo(int width, int height, byte[] buffer) {
245       this.width = width;
246       this.height = height;
247       this.buffer = buffer;
248     }
249   }
250 
251   /**
252    * Return bitmap info for this object, or null if no appropriate bitmap
253    * info is available.
254    */
getBitmapInfo()255   private BitmapInfo getBitmapInfo() {
256     if (!isInstanceOfClass("android.graphics.Bitmap")) {
257       return null;
258     }
259 
260     Integer width = getIntField("mWidth", null);
261     if (width == null) {
262       return null;
263     }
264 
265     Integer height = getIntField("mHeight", null);
266     if (height == null) {
267       return null;
268     }
269 
270     byte[] buffer = getByteArrayField("mBuffer");
271     if (buffer == null) {
272       return null;
273     }
274 
275     if (buffer.length < 4 * height * width) {
276       return null;
277     }
278 
279     return new BitmapInfo(width, height, buffer);
280 
281   }
282 
asBitmap()283   @Override public BufferedImage asBitmap() {
284     BitmapInfo info = getBitmapInfo();
285     if (info == null) {
286       return null;
287     }
288 
289     // Convert the raw data to an image
290     // Convert BGRA to ABGR
291     int[] abgr = new int[info.height * info.width];
292     for (int i = 0; i < abgr.length; i++) {
293       abgr[i] = (
294           (((int) info.buffer[i * 4 + 3] & 0xFF) << 24)
295           + (((int) info.buffer[i * 4 + 0] & 0xFF) << 16)
296           + (((int) info.buffer[i * 4 + 1] & 0xFF) << 8)
297           + ((int) info.buffer[i * 4 + 2] & 0xFF));
298     }
299 
300     BufferedImage bitmap = new BufferedImage(
301         info.width, info.height, BufferedImage.TYPE_4BYTE_ABGR);
302     bitmap.setRGB(0, 0, info.width, info.height, abgr, 0, info.width);
303     return bitmap;
304   }
305 
306   @Override
asRegisteredNativeAllocation()307   RegisteredNativeAllocation asRegisteredNativeAllocation() {
308     if (!isInstanceOfClass("sun.misc.Cleaner")) {
309       return null;
310     }
311 
312     Value vthunk = getField("thunk");
313     if (vthunk == null || !vthunk.isAhatInstance()) {
314       return null;
315     }
316 
317     AhatClassInstance thunk = vthunk.asAhatInstance().asClassInstance();
318     if (thunk == null
319         || !thunk.isInstanceOfClass("libcore.util.NativeAllocationRegistry$CleanerThunk")) {
320       return null;
321     }
322 
323     Value vregistry = thunk.getField("this$0");
324     if (vregistry == null || !vregistry.isAhatInstance()) {
325       return null;
326     }
327 
328     AhatClassInstance registry = vregistry.asAhatInstance().asClassInstance();
329     if (registry == null || !registry.isInstanceOfClass("libcore.util.NativeAllocationRegistry")) {
330       return null;
331     }
332 
333     Value size = registry.getField("size");
334     if (!size.isLong()) {
335       return null;
336     }
337 
338     Value referent = getField("referent");
339     if (referent == null || !referent.isAhatInstance()) {
340       return null;
341     }
342 
343     RegisteredNativeAllocation rna = new RegisteredNativeAllocation();
344     rna.referent = referent.asAhatInstance();
345     rna.size = size.asLong();
346     return rna;
347   }
348 
349   private static class InstanceFieldIterator implements Iterable<FieldValue>,
350                                                         Iterator<FieldValue> {
351     // The complete list of instance field values to iterate over, including
352     // superclass field values.
353     private Value[] mValues;
354     private int mValueIndex;
355 
356     // The list of field descriptors specific to the current class in the
357     // class hierarchy, not including superclass field descriptors.
358     // mFields and mFieldIndex are reset each time we walk up to the next
359     // superclass in the call hierarchy.
360     private Field[] mFields;
361     private int mFieldIndex;
362     private AhatClassObj mNextClassObj;
363 
InstanceFieldIterator(Value[] values, AhatClassObj classObj)364     public InstanceFieldIterator(Value[] values, AhatClassObj classObj) {
365       mValues = values;
366       mFields = classObj.getInstanceFields();
367       mValueIndex = 0;
368       mFieldIndex = 0;
369       mNextClassObj = classObj.getSuperClassObj();
370     }
371 
372     @Override
hasNext()373     public boolean hasNext() {
374       // If we have reached the end of the fields in the current class,
375       // continue walking up the class hierarchy to get superclass fields as
376       // well.
377       while (mFieldIndex == mFields.length && mNextClassObj != null) {
378         mFields = mNextClassObj.getInstanceFields();
379         mFieldIndex = 0;
380         mNextClassObj = mNextClassObj.getSuperClassObj();
381       }
382       return mFieldIndex < mFields.length;
383     }
384 
385     @Override
next()386     public FieldValue next() {
387       if (!hasNext()) {
388         throw new NoSuchElementException();
389       }
390       Field field = mFields[mFieldIndex++];
391       Value value = mValues[mValueIndex++];
392       return new FieldValue(field.name, field.type, value);
393     }
394 
395     @Override
iterator()396     public Iterator<FieldValue> iterator() {
397       return this;
398     }
399   }
400 
401   /**
402    * Returns the reachability type associated with this instance.
403    * For example, returns Reachability.WEAK for an instance of
404    * java.lang.ref.WeakReference.
405    */
getJavaLangRefType()406   private Reachability getJavaLangRefType() {
407     AhatClassObj cls = getClassObj();
408     while (cls != null) {
409       switch (cls.getName()) {
410         case "java.lang.ref.PhantomReference": return Reachability.PHANTOM;
411         case "java.lang.ref.WeakReference": return Reachability.WEAK;
412         case "java.lang.ref.FinalizerReference": return Reachability.FINALIZER;
413         case "java.lang.ref.Finalizer": return Reachability.FINALIZER;
414         case "java.lang.ref.SoftReference": return Reachability.SOFT;
415       }
416       cls = cls.getSuperClassObj();
417     }
418     return Reachability.STRONG;
419   }
420 
421   /**
422    * A Reference iterator that iterates over the fields of this instance.
423    */
424   private class ReferenceIterator implements Iterable<Reference>,
425                                              Iterator<Reference> {
426     private final Iterator<FieldValue> mIter = getInstanceFields().iterator();
427     private Reference mNext = null;
428 
429     // If we are iterating over a subclass of java.lang.ref.Reference, the
430     // 'referent' field doesn't have strong reachability. mJavaLangRefType
431     // describes what type of java.lang.ref.Reference subinstance this is.
432     private final Reachability mJavaLangRefType = getJavaLangRefType();
433 
434     @Override
hasNext()435     public boolean hasNext() {
436       while (mNext == null && mIter.hasNext()) {
437         FieldValue field = mIter.next();
438         if (field.value != null && field.value.isAhatInstance()) {
439           Reachability reachability = Reachability.STRONG;
440           if (mJavaLangRefType != Reachability.STRONG && "referent".equals(field.name)) {
441             reachability = mJavaLangRefType;
442           }
443           AhatInstance ref = field.value.asAhatInstance();
444           mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, reachability);
445         }
446       }
447       return mNext != null;
448     }
449 
450     @Override
next()451     public Reference next() {
452       if (!hasNext()) {
453         throw new NoSuchElementException();
454       }
455       Reference next = mNext;
456       mNext = null;
457       return next;
458     }
459 
460     @Override
iterator()461     public Iterator<Reference> iterator() {
462       return this;
463     }
464   }
465 }
466