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 39 AhatClassInstance(long id) { 40 super(id); 41 } 42 43 void initialize(Value[] fields) { 44 mFields = fields; 45 } 46 47 @Override 48 long getExtraJavaSize() { 49 return 0; 50 } 51 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 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 */ 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 */ 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 */ 101 public Iterable<FieldValue> getInstanceFields() { 102 return new InstanceFieldIterator(mFields, getClassObj()); 103 } 104 105 @Override 106 Iterable<Reference> getReferences() { 107 return new ReferenceIterator(); 108 } 109 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 131 @Override public AhatInstance getReferent() { 132 if (isInstanceOfClass("java.lang.ref.Reference")) { 133 return getRefField("referent"); 134 } 135 return null; 136 } 137 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 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 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 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 194 @Override public AhatInstance getAssociatedBitmapInstance() { 195 return getBitmapInfo() == null ? null : this; 196 } 197 198 @Override public boolean isClassInstance() { 199 return true; 200 } 201 202 @Override public AhatClassInstance asClassInstance() { 203 return this; 204 } 205 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 */ 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 */ 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 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 */ 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 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 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 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 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 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 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 */ 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 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 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 461 public Iterator<Reference> iterator() { 462 return this; 463 } 464 } 465 } 466