• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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    * Tests whether a byte looks like an ascii character.
272    */
looksLikeAscii(byte b)273   private static boolean looksLikeAscii(byte b) {
274     return b == '\n' || b == '\t' || (b >= ' ' && b <= '~');
275   }
276 
277   /**
278    * Returns the String value associated with this array.
279    */
asString(int offset, int count, int maxChars)280   String asString(int offset, int count, int maxChars) {
281     if (0 <= maxChars && maxChars < count) {
282       count = maxChars;
283     }
284 
285     if (mCharArray != null) {
286       // Always treat char arrays as strings.
287       if (count == 0) {
288         return "";
289       }
290 
291       int numChars = mCharArray.length;
292       int end = offset + count - 1;
293       if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
294         return new String(mCharArray, offset, count);
295       }
296 
297       return null;
298     }
299 
300     if (mByteArray != null) {
301       // Treat byte arrays as strings if they look like a sequence of ascii
302       // characters.
303       int end = offset + count - 1;
304       if (offset < 0 || offset >= mByteArray.length || end < 0 || end >= mByteArray.length) {
305         return null;
306       }
307 
308       for (int i = offset; i < end; ++i) {
309         if (mByteArray[i] == '\0') {
310           count = i - offset;
311           break;
312         }
313 
314         if (!looksLikeAscii(mByteArray[i])) {
315           return null;
316         }
317       }
318 
319       if (count <= 0) {
320         return null;
321       }
322       return asAsciiString(offset, count, maxChars);
323     }
324 
325     return null;
326   }
327 
328   /**
329    * Returns the ascii String value associated with this array.
330    * Only byte arrays are considered as having an associated ascii String value.
331    */
asAsciiString(int offset, int count, int maxChars)332   String asAsciiString(int offset, int count, int maxChars) {
333     if (mByteArray == null) {
334       return null;
335     }
336 
337     if (count == 0) {
338       return "";
339     }
340     int numChars = mByteArray.length;
341     if (0 <= maxChars && maxChars < count) {
342       count = maxChars;
343     }
344 
345     int end = offset + count - 1;
346     if (offset >= 0 && offset < numChars && end >= 0 && end < numChars) {
347       return new String(mByteArray, offset, count, StandardCharsets.US_ASCII);
348     }
349     return null;
350   }
351 
352   /**
353    * Returns the String value associated with this array. Byte arrays are
354    * considered as ascii encoded strings.
355    */
asMaybeCompressedString(int offset, int count, int maxChars)356   String asMaybeCompressedString(int offset, int count, int maxChars) {
357     String str = asString(offset, count, maxChars);
358     if (str == null) {
359       str = asAsciiString(offset, count, maxChars);
360     }
361     return str;
362   }
363 
getAssociatedBitmapInstance()364   @Override public AhatInstance getAssociatedBitmapInstance() {
365     if (mByteArray != null) {
366       List<AhatInstance> refs = getReverseReferences();
367       if (refs.size() == 1) {
368         AhatInstance ref = refs.get(0);
369         return ref.getAssociatedBitmapInstance();
370       }
371     }
372     return null;
373   }
374 
getAssociatedClassForOverhead()375   @Override public AhatClassObj getAssociatedClassForOverhead() {
376     if (mByteArray != null) {
377       List<AhatInstance> refs = getHardReverseReferences();
378       if (refs.size() == 1) {
379         AhatClassObj ref = refs.get(0).asClassObj();
380         if (ref != null) {
381           for (FieldValue field : ref.getStaticFieldValues()) {
382             if (field.name.equals("$classOverhead")) {
383               if (field.value.asAhatInstance() == this) {
384                 return ref;
385               }
386               return null;
387             }
388           }
389         }
390       }
391     }
392     return null;
393   }
394 
toString()395   @Override public String toString() {
396     String className = getClassName();
397     if (className.endsWith("[]")) {
398       className = className.substring(0, className.length() - 2);
399     }
400     return String.format("%s[%d]@%08x", className, mValues.size(), getId());
401   }
402 
403   @Override
asByteArray()404   public byte[] asByteArray() {
405     return mByteArray;
406   }
407 }
408