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.nio.charset.StandardCharsets;
20 import java.util.AbstractList;
21 import java.util.Collections;
22 import java.util.List;
23 
24 /**
25  * An array instance from a parsed heap dump.
26  * It is used for both object and primitive arrays. The class provides methods
27  * for accessing the length and elements of the array in addition to those
28  * methods inherited from {@link AhatInstance}.
29  */
30 public class AhatArrayInstance extends AhatInstance {
31   // To save space, we store arrays as primitive arrays or AhatInstance arrays
32   // and provide a wrapper over the arrays to expose a list of Values.
33   // This is especially important for large byte arrays, such as bitmaps.
34   // We keep a separate pointer to the underlying array in the case of byte or
35   // char arrays because they are sometimes useful to have.
36   // TODO: Have different subtypes of AhatArrayInstance to avoid the overhead
37   // of these extra pointers and cost in getReferences when the array type is
38   // not relevant?
39   private List<Value> mValues;
40   private byte[] mByteArray;    // null if not a byte array.
41   private char[] mCharArray;    // null if not a char array.
42   private final int mRefSize;
43 
AhatArrayInstance(long id, int refSize)44   AhatArrayInstance(long id, int refSize) {
45     super(id);
46     mRefSize = refSize;
47   }
48 
49   /**
50    * Initialize the array elements for a primitive boolean array.
51    */
initialize(final boolean[] bools)52   void initialize(final boolean[] bools) {
53     mValues = new AbstractList<Value>() {
54       @Override public int size() {
55         return bools.length;
56       }
57 
58       @Override public Value get(int index) {
59         return Value.pack(bools[index]);
60       }
61     };
62   }
63 
64   /**
65    * Initialize the array elements for a primitive char array.
66    */
initialize(final char[] chars)67   void initialize(final char[] chars) {
68     mCharArray = chars;
69     mValues = new AbstractList<Value>() {
70       @Override public int size() {
71         return chars.length;
72       }
73 
74       @Override public Value get(int index) {
75         return Value.pack(chars[index]);
76       }
77     };
78   }
79 
80   /**
81    * Initialize the array elements for a primitive float array.
82    */
initialize(final float[] floats)83   void initialize(final float[] floats) {
84     mValues = new AbstractList<Value>() {
85       @Override public int size() {
86         return floats.length;
87       }
88 
89       @Override public Value get(int index) {
90         return Value.pack(floats[index]);
91       }
92     };
93   }
94 
95   /**
96    * Initialize the array elements for a primitive double array.
97    */
initialize(final double[] doubles)98   void initialize(final double[] doubles) {
99     mValues = new AbstractList<Value>() {
100       @Override public int size() {
101         return doubles.length;
102       }
103 
104       @Override public Value get(int index) {
105         return Value.pack(doubles[index]);
106       }
107     };
108   }
109 
110   /**
111    * Initialize the array elements for a primitive byte array.
112    */
initialize(final byte[] bytes)113   void initialize(final byte[] bytes) {
114     mByteArray = bytes;
115     mValues = new AbstractList<Value>() {
116       @Override public int size() {
117         return bytes.length;
118       }
119 
120       @Override public Value get(int index) {
121         return Value.pack(bytes[index]);
122       }
123     };
124   }
125 
126   /**
127    * Initialize the array elements for a primitive short array.
128    */
initialize(final short[] shorts)129   void initialize(final short[] shorts) {
130     mValues = new AbstractList<Value>() {
131       @Override public int size() {
132         return shorts.length;
133       }
134 
135       @Override public Value get(int index) {
136         return Value.pack(shorts[index]);
137       }
138     };
139   }
140 
141   /**
142    * Initialize the array elements for a primitive int array.
143    */
initialize(final int[] ints)144   void initialize(final int[] ints) {
145     mValues = new AbstractList<Value>() {
146       @Override public int size() {
147         return ints.length;
148       }
149 
150       @Override public Value get(int index) {
151         return Value.pack(ints[index]);
152       }
153     };
154   }
155 
156   /**
157    * Initialize the array elements for a primitive long array.
158    */
initialize(final long[] longs)159   void initialize(final long[] longs) {
160     mValues = new AbstractList<Value>() {
161       @Override public int size() {
162         return longs.length;
163       }
164 
165       @Override public Value get(int index) {
166         return Value.pack(longs[index]);
167       }
168     };
169   }
170 
171   /**
172    * Initialize the array elements for an instance array.
173    */
initialize(final AhatInstance[] insts)174   void initialize(final AhatInstance[] insts) {
175     mValues = new AbstractList<Value>() {
176       @Override public int size() {
177         return insts.length;
178       }
179 
180       @Override public Value get(int index) {
181         return Value.pack(insts[index]);
182       }
183     };
184   }
185 
186   @Override
getExtraJavaSize()187   long getExtraJavaSize() {
188     int length = getLength();
189     if (length == 0) {
190       return 0;
191     }
192 
193     return Value.getType(mValues.get(0)).size(mRefSize) * getLength();
194   }
195 
196   /**
197    * Returns the number of elements in the array.
198    *
199    * @return number of elements in the array.
200    */
getLength()201   public int getLength() {
202     return mValues.size();
203   }
204 
205   /**
206    * Returns a list of all of the array's elements in order.
207    * The returned list does not support modification.
208    *
209    * @return list of the array's elements.
210    */
getValues()211   public List<Value> getValues() {
212     return mValues;
213   }
214 
215   /**
216    * Returns the value at the given index of this array.
217    *
218    * @param index the index of the value to retrieve
219    * @return the value at the given index
220    * @throws IndexOutOfBoundsException if the index is out of range
221    */
getValue(int index)222   public Value getValue(int index) {
223     return mValues.get(index);
224   }
225 
226   @Override
getReferences()227   Iterable<Reference> getReferences() {
228     // The list of references will be empty if this is a primitive array.
229     List<Reference> refs = Collections.emptyList();
230     if (!mValues.isEmpty()) {
231       Value first = mValues.get(0);
232       if (first == null || first.isAhatInstance()) {
233         refs = new AbstractList<Reference>() {
234           @Override
235           public int size() {
236             return mValues.size();
237           }
238 
239           @Override
240           public Reference get(int index) {
241             Value value = mValues.get(index);
242             if (value != null) {
243               assert value.isAhatInstance();
244               String field = "[" + Integer.toString(index) + "]";
245               return new Reference(AhatArrayInstance.this,
246                                    field,
247                                    value.asAhatInstance(),
248                                    Reachability.STRONG);
249             }
250             return null;
251           }
252         };
253       }
254     }
255     return new SkipNullsIterator(refs);
256   }
257 
isArrayInstance()258   @Override public boolean isArrayInstance() {
259     return true;
260   }
261 
asArrayInstance()262   @Override public AhatArrayInstance asArrayInstance() {
263     return this;
264   }
265 
asString(int maxChars)266   @Override public String asString(int maxChars) {
267     return asString(0, getLength(), maxChars);
268   }
269 
270   /**
271    * Returns the String value associated with this array.
272    * Only char arrays are considered as having an associated String value.
273    */
asString(int offset, int count, int maxChars)274   String asString(int offset, int count, int maxChars) {
275     if (mCharArray == null) {
276       return null;
277     }
278 
279     if (count == 0) {
280       return "";
281     }
282     int numChars = mCharArray.length;
283     if (0 <= maxChars && maxChars < count) {
284       count = maxChars;
285     }
286 
287     int end = offset + count - 1;
288     if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
289       return new String(mCharArray, offset, count);
290     }
291     return null;
292   }
293 
294   /**
295    * Returns the ascii String value associated with this array.
296    * Only byte arrays are considered as having an associated ascii String value.
297    */
asAsciiString(int offset, int count, int maxChars)298   String asAsciiString(int offset, int count, int maxChars) {
299     if (mByteArray == null) {
300       return null;
301     }
302 
303     if (count == 0) {
304       return "";
305     }
306     int numChars = mByteArray.length;
307     if (0 <= maxChars && maxChars < count) {
308       count = maxChars;
309     }
310 
311     int end = offset + count - 1;
312     if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
313       return new String(mByteArray, offset, count, StandardCharsets.US_ASCII);
314     }
315     return null;
316   }
317 
318   /**
319    * Returns the String value associated with this array. Byte arrays are
320    * considered as ascii encoded strings.
321    */
asMaybeCompressedString(int offset, int count, int maxChars)322   String asMaybeCompressedString(int offset, int count, int maxChars) {
323     String str = asString(offset, count, maxChars);
324     if (str == null) {
325       str = asAsciiString(offset, count, maxChars);
326     }
327     return str;
328   }
329 
getAssociatedBitmapInstance()330   @Override public AhatInstance getAssociatedBitmapInstance() {
331     if (mByteArray != null) {
332       List<AhatInstance> refs = getReverseReferences();
333       if (refs.size() == 1) {
334         AhatInstance ref = refs.get(0);
335         return ref.getAssociatedBitmapInstance();
336       }
337     }
338     return null;
339   }
340 
getAssociatedClassForOverhead()341   @Override public AhatClassObj getAssociatedClassForOverhead() {
342     if (mByteArray != null) {
343       List<AhatInstance> refs = getHardReverseReferences();
344       if (refs.size() == 1) {
345         AhatClassObj ref = refs.get(0).asClassObj();
346         if (ref != null) {
347           for (FieldValue field : ref.getStaticFieldValues()) {
348             if (field.name.equals("$classOverhead")) {
349               if (field.value.asAhatInstance() == this) {
350                 return ref;
351               }
352               return null;
353             }
354           }
355         }
356       }
357     }
358     return null;
359   }
360 
toString()361   @Override public String toString() {
362     String className = getClassName();
363     if (className.endsWith("[]")) {
364       className = className.substring(0, className.length() - 2);
365     }
366     return String.format("%s[%d]@%08x", className, mValues.size(), getId());
367   }
368 
asByteArray()369   byte[] asByteArray() {
370     return mByteArray;
371   }
372 }
373