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 com.android.tools.perflib.heap.ClassInstance;
20 import com.android.tools.perflib.heap.Instance;
21 import java.awt.image.BufferedImage;
22 import java.util.Arrays;
23 import java.util.List;
24 
25 public class AhatClassInstance extends AhatInstance {
26   private FieldValue[] mFieldValues;
27 
AhatClassInstance(long id)28   public AhatClassInstance(long id) {
29     super(id);
30   }
31 
initialize(AhatSnapshot snapshot, Instance inst)32   @Override void initialize(AhatSnapshot snapshot, Instance inst) {
33     super.initialize(snapshot, inst);
34 
35     ClassInstance classInst = (ClassInstance)inst;
36     List<ClassInstance.FieldValue> fieldValues = classInst.getValues();
37     mFieldValues = new FieldValue[fieldValues.size()];
38     for (int i = 0; i < mFieldValues.length; i++) {
39       ClassInstance.FieldValue field = fieldValues.get(i);
40       String name = field.getField().getName();
41       String type = field.getField().getType().toString();
42       Value value = snapshot.getValue(field.getValue());
43 
44       mFieldValues[i] = new FieldValue(name, type, value);
45 
46       if (field.getValue() instanceof Instance) {
47         Instance ref = (Instance)field.getValue();
48         if (ref.getNextInstanceToGcRoot() == inst) {
49           value.asAhatInstance().setNextInstanceToGcRoot(this, "." + name);
50         }
51       }
52     }
53   }
54 
getField(String fieldName)55   @Override public Value getField(String fieldName) {
56     for (FieldValue field : mFieldValues) {
57       if (fieldName.equals(field.getName())) {
58         return field.getValue();
59       }
60     }
61     return null;
62   }
63 
getRefField(String fieldName)64   @Override public AhatInstance getRefField(String fieldName) {
65     Value value = getField(fieldName);
66     return value == null ? null : value.asAhatInstance();
67   }
68 
69   /**
70    * Read an int field of an instance.
71    * The field is assumed to be an int type.
72    * Returns <code>def</code> if the field value is not an int or could not be
73    * read.
74    */
getIntField(String fieldName, Integer def)75   private Integer getIntField(String fieldName, Integer def) {
76     Value value = getField(fieldName);
77     if (value == null || !value.isInteger()) {
78       return def;
79     }
80     return value.asInteger();
81   }
82 
83   /**
84    * Read a long field of this instance.
85    * The field is assumed to be a long type.
86    * Returns <code>def</code> if the field value is not an long or could not
87    * be read.
88    */
getLongField(String fieldName, Long def)89   private Long getLongField(String fieldName, Long def) {
90     Value value = getField(fieldName);
91     if (value == null || !value.isLong()) {
92       return def;
93     }
94     return value.asLong();
95   }
96 
97   /**
98    * Returns the list of class instance fields for this instance.
99    */
getInstanceFields()100   public List<FieldValue> getInstanceFields() {
101     return Arrays.asList(mFieldValues);
102   }
103 
104   /**
105    * Returns true if this is an instance of a class with the given name.
106    */
isInstanceOfClass(String className)107   private boolean isInstanceOfClass(String className) {
108     AhatClassObj cls = getClassObj();
109     while (cls != null) {
110       if (className.equals(cls.getName())) {
111         return true;
112       }
113       cls = cls.getSuperClassObj();
114     }
115     return false;
116   }
117 
asString(int maxChars)118   @Override public String asString(int maxChars) {
119     if (!isInstanceOfClass("java.lang.String")) {
120       return null;
121     }
122 
123     Value value = getField("value");
124     if (!value.isAhatInstance()) {
125       return null;
126     }
127 
128     AhatInstance inst = value.asAhatInstance();
129     if (inst.isArrayInstance()) {
130       AhatArrayInstance chars = inst.asArrayInstance();
131       int numChars = chars.getLength();
132       int count = getIntField("count", numChars);
133       int offset = getIntField("offset", 0);
134       return chars.asMaybeCompressedString(offset, count, maxChars);
135     }
136     return null;
137   }
138 
getReferent()139   @Override public AhatInstance getReferent() {
140     if (isInstanceOfClass("java.lang.ref.Reference")) {
141       return getRefField("referent");
142     }
143     return null;
144   }
145 
getDexCacheLocation(int maxChars)146   @Override public String getDexCacheLocation(int maxChars) {
147     if (isInstanceOfClass("java.lang.DexCache")) {
148       AhatInstance location = getRefField("location");
149       if (location != null) {
150         return location.asString(maxChars);
151       }
152     }
153     return null;
154   }
155 
getAssociatedBitmapInstance()156   @Override public AhatInstance getAssociatedBitmapInstance() {
157     if (isInstanceOfClass("android.graphics.Bitmap")) {
158       return this;
159     }
160     return null;
161   }
162 
isClassInstance()163   @Override public boolean isClassInstance() {
164     return true;
165   }
166 
asClassInstance()167   @Override public AhatClassInstance asClassInstance() {
168     return this;
169   }
170 
toString()171   @Override public String toString() {
172     return String.format("%s@%08x", getClassName(), getId());
173   }
174 
175   /**
176    * Read the given field from the given instance.
177    * The field is assumed to be a byte[] field.
178    * Returns null if the field value is null, not a byte[] or could not be read.
179    */
getByteArrayField(String fieldName)180   private byte[] getByteArrayField(String fieldName) {
181     Value value = getField(fieldName);
182     if (!value.isAhatInstance()) {
183       return null;
184     }
185     return value.asAhatInstance().asByteArray();
186   }
187 
asBitmap()188   public BufferedImage asBitmap() {
189     if (!isInstanceOfClass("android.graphics.Bitmap")) {
190       return null;
191     }
192 
193     Integer width = getIntField("mWidth", null);
194     if (width == null) {
195       return null;
196     }
197 
198     Integer height = getIntField("mHeight", null);
199     if (height == null) {
200       return null;
201     }
202 
203     byte[] buffer = getByteArrayField("mBuffer");
204     if (buffer == null) {
205       return null;
206     }
207 
208     // Convert the raw data to an image
209     // Convert BGRA to ABGR
210     int[] abgr = new int[height * width];
211     for (int i = 0; i < abgr.length; i++) {
212       abgr[i] = (
213           (((int) buffer[i * 4 + 3] & 0xFF) << 24)
214           + (((int) buffer[i * 4 + 0] & 0xFF) << 16)
215           + (((int) buffer[i * 4 + 1] & 0xFF) << 8)
216           + ((int) buffer[i * 4 + 2] & 0xFF));
217     }
218 
219     BufferedImage bitmap = new BufferedImage(
220         width, height, BufferedImage.TYPE_4BYTE_ABGR);
221     bitmap.setRGB(0, 0, width, height, abgr, 0, width);
222     return bitmap;
223   }
224 }
225