1 /*
  2  * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 
 25 /*
 26  * The Original Code is HAT. The Initial Developer of the
 27  * Original Code is Bill Foote, with contributions from others
 28  * at JavaSoft/Sun.
 29  */
 30 
 31 package jdk.test.lib.hprof.model;
 32 
 33 import java.io.IOException;
 34 import java.util.Objects;
 35 
 36 /**
 37  * An array of values, that is, an array of ints, boolean, floats, etc.
 38  * or flat array of primitive objects.
 39  *
 40  * @author      Bill Foote
 41  */
 42 public class JavaValueArray extends JavaLazyReadObject
 43                 /*imports*/ implements ArrayTypeCodes {
 44 
 45     private static int elementSize(byte type) {
 46         switch (type) {
 47             case 'B':
 48             case 'Z':
 49                 return 1;
 50             case 'C':
 51             case 'S':
 52                 return 2;
 53             case 'I':
 54             case 'F':
 55                 return 4;
 56             case 'J':
 57             case 'D':
 58                 return 8;
 59             default:
 60                 throw new RuntimeException("invalid array element type: " + type);
 61         }
 62     }
 63 
 64     /*
 65      * Java primitive array record (HPROF_GC_PRIM_ARRAY_DUMP) looks
 66      * as below:
 67      *
 68      *    object ID
 69      *    stack trace serial number (int)
 70      *    number of elements (int)
 71      *    element type (byte)
 72      *    array data
 73      */
 74     @Override
 75     protected final long readValueLength() throws IOException {
 76         long offset = getOffset() + idSize() + 4;
 77         // length of the array in elements
 78         long len = buf().getInt(offset);
 79         // byte length of array
 80         return len * elementSize(getRealElementType());
 81     }
 82 
 83     private long dataStartOffset() {
 84         return getOffset() + idSize() + 4 + 4 + 1;
 85     }
 86 
 87 
 88     @Override
 89     protected final JavaThing[] readValue() throws IOException {
 90         int len = getLength();
 91         long offset = dataStartOffset();
 92 
 93         JavaThing[] res = new JavaThing[len];
 94         synchronized (buf()) {
 95             switch (getElementType()) {
 96                 case 'Z': {
 97                               for (int i = 0; i < len; i++) {
 98                                   res[i] = new JavaBoolean(booleanAt(offset));
 99                                   offset += 1;
100                               }
101                               return res;
102                 }
103                 case 'B': {
104                               for (int i = 0; i < len; i++) {
105                                   res[i] = new JavaByte(byteAt(offset));
106                                   offset += 1;
107                               }
108                               return res;
109                 }
110                 case 'C': {
111                               for (int i = 0; i < len; i++) {
112                                   res[i] = new JavaChar(charAt(offset));
113                                   offset += 2;
114                               }
115                               return res;
116                 }
117                 case 'S': {
118                               for (int i = 0; i < len; i++) {
119                                   res[i] = new JavaShort(shortAt(offset));
120                                   offset += 2;
121                               }
122                               return res;
123                 }
124                 case 'I': {
125                               for (int i = 0; i < len; i++) {
126                                   res[i] = new JavaInt(intAt(offset));
127                                   offset += 4;
128                               }
129                               return res;
130                 }
131                 case 'J': {
132                               for (int i = 0; i < len; i++) {
133                                   res[i] = new JavaLong(longAt(offset));
134                                   offset += 8;
135                               }
136                               return res;
137                 }
138                 case 'F': {
139                               for (int i = 0; i < len; i++) {
140                                   res[i] = new JavaFloat(floatAt(offset));
141                                   offset += 4;
142                               }
143                               return res;
144                 }
145                 case 'D': {
146                               for (int i = 0; i < len; i++) {
147                                   res[i] = new JavaDouble(doubleAt(offset));
148                                   offset += 8;
149                               }
150                               return res;
151                 }
152                 case 'Q': {
153                     for (int i = 0; i < len; i++) {
154                         res[i] = new InlinedJavaObject(flatArrayElementClass, offset);
155                         offset += flatArrayElementClass.getInlinedInstanceSize();
156                     }
157                     return res;
158                 }
159                 default: {
160                              throw new RuntimeException("unknown primitive type?");
161                 }
162             }
163         }
164     }
165 
166     // JavaClass set only after resolve.
167     private JavaClass clazz;
168 
169     private long objID;
170 
171     // This field contains elementSignature byte and
172     // divider to be used to calculate length. Note that
173     // length of content byte[] is not same as array length.
174     // Actual array length is (byte[].length / divider)
175     private int data;
176 
177     // First 8 bits of data is used for element signature
178     private static final int SIGNATURE_MASK = 0x0FF;
179 
180     // Next 8 bits of data is used for length divider
181     private static final int LENGTH_DIVIDER_MASK = 0x0FF00;
182 
183     // Number of bits to shift to get length divider
184     private static final int LENGTH_DIVIDER_SHIFT = 8;
185 
186     // Flat array support.
187     private JavaClass flatArrayElementClass;
188 
189     public JavaValueArray(long id, byte elementSignature, long offset) {
190         super(offset);
191         this.objID = id;
192         this.data = (elementSignature & SIGNATURE_MASK);
193     }
194 
195     public JavaClass getClazz() {
196         return clazz;
197     }
198 
199     public boolean isFlatArray() {
200         return flatArrayElementClass != null;
201     }
202 
203     public JavaClass getFlatElementClazz() {
204         return flatArrayElementClass;
205     }
206 
207     public void visitReferencedObjects(JavaHeapObjectVisitor v) {
208         super.visitReferencedObjects(v);
209     }
210 
211     public void resolve(Snapshot snapshot) {
212         if (clazz instanceof JavaClass) {
213             return;
214         }
215 
216         byte elementType = getElementType();
217         String elementSig = "" + (char)elementType;
218         // Check if this is a flat array of primitive objects.
219         Number elementClassID = snapshot.findFlatArrayElementType(objID);
220         if (elementClassID != null) {
221             // This is flat array.
222             JavaHeapObject elementClazz = snapshot.findThing(getIdValue(elementClassID));
223             if (elementClazz instanceof JavaClass elementJavaClazz) {
224                 flatArrayElementClass = elementJavaClazz;
225                 // need to resolve the element class
226                 flatArrayElementClass.resolve(snapshot);
227                 elementSig = "Q" + flatArrayElementClass.getName() + ";";
228             } else {
229                 // The class not found.
230                 System.out.println("WARNING: flat array element class not found");
231             }
232         }
233         clazz = snapshot.getArrayClass(elementSig);
234         getClazz().addInstance(this);
235         super.resolve(snapshot);
236     }
237 
238     public int getLength() {
239         int divider = (data & LENGTH_DIVIDER_MASK) >>> LENGTH_DIVIDER_SHIFT;
240         if (divider == 0) {
241             byte elementSignature = getElementType();
242             switch (elementSignature) {
243             case 'B':
244             case 'Z':
245                 divider = 1;
246                 break;
247             case 'C':
248             case 'S':
249                 divider = 2;
250                 break;
251             case 'I':
252             case 'F':
253                 divider = 4;
254                 break;
255             case 'J':
256             case 'D':
257                 divider = 8;
258                 break;
259             case 'Q':
260                 divider = flatArrayElementClass.getInlinedInstanceSize();
261                 break;
262             default:
263                 throw new RuntimeException("unknown primitive type: " +
264                                 elementSignature);
265             }
266             data |= (divider << LENGTH_DIVIDER_SHIFT);
267         }
268         return (int)(getValueLength() / divider);
269     }
270 
271     public JavaThing[] getElements() {
272         return getValue();
273     }
274 
275     public byte getElementType() {
276         return isFlatArray() ? (byte)'Q' : getRealElementType();
277     }
278 
279     private byte getRealElementType() {
280         return (byte) (data & SIGNATURE_MASK);
281     }
282 
283     private void checkIndex(int index) {
284         Objects.checkIndex(index, getLength());
285     }
286 
287     private void requireType(char type) {
288         if (getElementType() != type) {
289             throw new RuntimeException("not of type : " + type);
290         }
291     }
292 
293     public String valueString() {
294         return valueString(true);
295     }
296 
297     public String valueString(boolean bigLimit) {
298         // Char arrays deserve special treatment
299         StringBuilder result;
300         JavaThing[] things = getValue();
301         byte elementSignature = getElementType();
302         if (elementSignature == 'C' && !isFlatArray())  {
303             result = new StringBuilder();
304             for (int i = 0; i < things.length; i++) {
305                 result.append(things[i]);
306             }
307         } else {
308             int limit = 8;
309             if (bigLimit) {
310                 limit = 1000;
311             }
312             result = new StringBuilder("{");
313             for (int i = 0; i < things.length; i++) {
314                 if (i > 0) {
315                     result.append(", ");
316                 }
317                 if (i >= limit) {
318                     result.append("... ");
319                     break;
320                 }
321                 switch (elementSignature) {
322                     case 'Z': {
323                         boolean val = ((JavaBoolean)things[i]).value;
324                         if (val) {
325                             result.append("true");
326                         } else {
327                             result.append("false");
328                         }
329                         break;
330                     }
331                     case 'B': {
332                         byte val = ((JavaByte)things[i]).value;
333                         result.append("0x").append(Integer.toString(val, 16));
334                         break;
335                     }
336                     case 'S': {
337                         short val = ((JavaShort)things[i]).value;
338                         result.append(val);
339                         break;
340                     }
341                     case 'I': {
342                         int val = ((JavaInt)things[i]).value;
343                         result.append(val);
344                         break;
345                     }
346                     case 'J': {         // long
347                         long val = ((JavaLong)things[i]).value;
348                         result.append(val);
349                         break;
350                     }
351                     case 'F': {
352                         float val = ((JavaFloat)things[i]).value;
353                         result.append(val);
354                         break;
355                     }
356                     case 'D': {         // double
357                         double val = ((JavaDouble)things[i]).value;
358                         result.append(val);
359                         break;
360                     }
361                     case 'Q': {
362                         InlinedJavaObject obj = (InlinedJavaObject)things[i];
363                         result.append(obj);
364                     }
365                     default: {
366                         throw new RuntimeException("unknown primitive type?");
367                     }
368                 }
369             }
370             result.append('}');
371         }
372         return result.toString();
373     }
374 
375     // Tries to represent the value as string (used by JavaObject.toString).
376     public String valueAsString() {
377         if (getElementType() == 'B')  {
378             JavaThing[] things = getValue();
379             byte[] bytes = new byte[things.length];
380             for (int i = 0; i < things.length; i++) {
381                 bytes[i] = ((JavaByte)things[i]).value;
382             }
383             return new String(bytes);
384         }
385         // fallback
386         return valueString();
387     }
388 }