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