1 /*
  2  * Copyright (c) 1997, 2024, 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.nio.ByteOrder;
 35 import java.util.Objects;
 36 
 37 /**
 38  * An array of values, that is, an array of ints, boolean, floats or the like.
 39  *
 40  * @author      Bill Foote
 41  */
 42 public class JavaValueArray extends JavaLazyReadObject
 43                 /*imports*/ implements ArrayTypeCodes {
 44 
 45     private static String arrayTypeName(byte sig) {
 46         switch (sig) {
 47             case 'B':
 48                 return "byte[]";
 49             case 'Z':
 50                 return "boolean[]";
 51             case 'C':
 52                 return "char[]";
 53             case 'S':
 54                 return "short[]";
 55             case 'I':
 56                 return "int[]";
 57             case 'F':
 58                 return "float[]";
 59             case 'J':
 60                 return "long[]";
 61             case 'D':
 62                 return "double[]";
 63             default:
 64                 throw new RuntimeException("invalid array element sig: " + sig);
 65         }
 66     }
 67 
 68     private static int elementSize(byte type) {
 69         switch (type) {
 70             case 'B':
 71             case 'Z':
 72                 return 1;
 73             case 'C':
 74             case 'S':
 75                 return 2;
 76             case 'I':
 77             case 'F':
 78                 return 4;
 79             case 'J':
 80             case 'D':
 81                 return 8;
 82             default:
 83                 throw new RuntimeException("invalid array element type: " + type);
 84         }
 85     }
 86 
 87     /*
 88      * Java primitive array record (HPROF_GC_PRIM_ARRAY_DUMP) looks
 89      * as below:
 90      *
 91      *    object ID
 92      *    stack trace serial number (int)
 93      *    number of elements (int)
 94      *    element type (byte)
 95      *    array data
 96      */
 97     @Override
 98     protected final long readValueLength() throws IOException {
 99         long offset = getOffset() + idSize() + 4;
100         // length of the array in elements
101         long len = buf().getInt(offset);
102         // byte length of array
103         return len * elementSize(getElementType());
104     }
105 
106     private long dataStartOffset() {
107         return getOffset() + idSize() + 4 + 4 + 1;
108     }
109 
110 
111     @Override
112     protected final JavaThing[] readValue() throws IOException {
113         int len = getLength();
114         long offset = dataStartOffset();
115 
116         JavaThing[] res = new JavaThing[len];
117         synchronized (buf()) {
118             switch (getElementType()) {
119                 case 'Z': {
120                               for (int i = 0; i < len; i++) {
121                                   res[i] = new JavaBoolean(booleanAt(offset));
122                                   offset += 1;
123                               }
124                               return res;
125                 }
126                 case 'B': {
127                               for (int i = 0; i < len; i++) {
128                                   res[i] = new JavaByte(byteAt(offset));
129                                   offset += 1;
130                               }
131                               return res;
132                 }
133                 case 'C': {
134                               for (int i = 0; i < len; i++) {
135                                   res[i] = new JavaChar(charAt(offset));
136                                   offset += 2;
137                               }
138                               return res;
139                 }
140                 case 'S': {
141                               for (int i = 0; i < len; i++) {
142                                   res[i] = new JavaShort(shortAt(offset));
143                                   offset += 2;
144                               }
145                               return res;
146                 }
147                 case 'I': {
148                               for (int i = 0; i < len; i++) {
149                                   res[i] = new JavaInt(intAt(offset));
150                                   offset += 4;
151                               }
152                               return res;
153                 }
154                 case 'J': {
155                               for (int i = 0; i < len; i++) {
156                                   res[i] = new JavaLong(longAt(offset));
157                                   offset += 8;
158                               }
159                               return res;
160                 }
161                 case 'F': {
162                               for (int i = 0; i < len; i++) {
163                                   res[i] = new JavaFloat(floatAt(offset));
164                                   offset += 4;
165                               }
166                               return res;
167                 }
168                 case 'D': {
169                               for (int i = 0; i < len; i++) {
170                                   res[i] = new JavaDouble(doubleAt(offset));
171                                   offset += 8;
172                               }
173                               return res;
174                 }
175                 default: {
176                              throw new RuntimeException("unknown primitive type?");
177                 }
178             }
179         }
180     }
181 
182     // JavaClass set only after resolve.
183     private JavaClass clazz;
184 
185     // This field contains elementSignature byte and
186     // divider to be used to calculate length. Note that
187     // length of content byte[] is not same as array length.
188     // Actual array length is (byte[].length / divider)
189     private int data;
190 
191     // First 8 bits of data is used for element signature
192     private static final int SIGNATURE_MASK = 0x0FF;
193 
194     // Next 8 bits of data is used for length divider
195     private static final int LENGTH_DIVIDER_MASK = 0x0FF00;
196 
197     // Number of bits to shift to get length divider
198     private static final int LENGTH_DIVIDER_SHIFT = 8;
199 
200     public JavaValueArray(byte elementSignature, long offset) {
201         super(offset);
202         this.data = (elementSignature & SIGNATURE_MASK);
203     }
204 
205     public JavaClass getClazz() {
206         return clazz;
207     }
208 
209     public void visitReferencedObjects(JavaHeapObjectVisitor v) {
210         super.visitReferencedObjects(v);
211     }
212 
213     public void resolve(Snapshot snapshot) {
214         if (clazz instanceof JavaClass) {
215             return;
216         }
217         byte elementSig = getElementType();
218         clazz = snapshot.findClass(arrayTypeName(elementSig));
219         if (clazz == null) {
220             clazz = snapshot.getArrayClass("" + ((char) elementSig));
221         }
222         getClazz().addInstance(this);
223         super.resolve(snapshot);
224     }
225 
226     public int getLength() {
227         int divider = (data & LENGTH_DIVIDER_MASK) >>> LENGTH_DIVIDER_SHIFT;
228         if (divider == 0) {
229             byte elementSignature = getElementType();
230             switch (elementSignature) {
231             case 'B':
232             case 'Z':
233                 divider = 1;
234                 break;
235             case 'C':
236             case 'S':
237                 divider = 2;
238                 break;
239             case 'I':
240             case 'F':
241                 divider = 4;
242                 break;
243             case 'J':
244             case 'D':
245                 divider = 8;
246                 break;
247             default:
248                 throw new RuntimeException("unknown primitive type: " +
249                                 elementSignature);
250             }
251             data |= (divider << LENGTH_DIVIDER_SHIFT);
252         }
253         return (int)(getValueLength() / divider);
254     }
255 
256     public JavaThing[] getElements() {
257         return getValue();
258     }
259 
260     public byte getElementType() {
261         return (byte) (data & SIGNATURE_MASK);
262     }
263 
264     private void checkIndex(int index) {
265         Objects.checkIndex(index, getLength());
266     }
267 
268     private void requireType(char type) {
269         if (getElementType() != type) {
270             throw new RuntimeException("not of type : " + type);
271         }
272     }
273 
274     public String valueString() {
275         return valueString(true);
276     }
277 
278     public String valueString(boolean bigLimit) {
279         // Char arrays deserve special treatment
280         StringBuilder result;
281         JavaThing[] things = getValue();
282         byte elementSignature = getElementType();
283         if (elementSignature == 'C')  {
284             result = new StringBuilder();
285             for (int i = 0; i < things.length; i++) {
286                 result.append(things[i]);
287             }
288         } else {
289             int limit = 8;
290             if (bigLimit) {
291                 limit = 1000;
292             }
293             result = new StringBuilder("{");
294             for (int i = 0; i < things.length; i++) {
295                 if (i > 0) {
296                     result.append(", ");
297                 }
298                 if (i >= limit) {
299                     result.append("... ");
300                     break;
301                 }
302                 switch (elementSignature) {
303                     case 'Z': {
304                         boolean val = ((JavaBoolean)things[i]).value;
305                         if (val) {
306                             result.append("true");
307                         } else {
308                             result.append("false");
309                         }
310                         break;
311                     }
312                     case 'B': {
313                         byte val = ((JavaByte)things[i]).value;
314                         result.append("0x").append(Integer.toString(val, 16));
315                         break;
316                     }
317                     case 'S': {
318                         short val = ((JavaShort)things[i]).value;
319                         result.append(val);
320                         break;
321                     }
322                     case 'I': {
323                         int val = ((JavaInt)things[i]).value;
324                         result.append(val);
325                         break;
326                     }
327                     case 'J': {         // long
328                         long val = ((JavaLong)things[i]).value;
329                         result.append(val);
330                         break;
331                     }
332                     case 'F': {
333                         float val = ((JavaFloat)things[i]).value;
334                         result.append(val);
335                         break;
336                     }
337                     case 'D': {         // double
338                         double val = ((JavaDouble)things[i]).value;
339                         result.append(val);
340                         break;
341                     }
342                     default: {
343                         throw new RuntimeException("unknown primitive type?");
344                     }
345                 }
346             }
347             result.append('}');
348         }
349         return result.toString();
350     }
351 
352     private static final int STRING_HI_BYTE_SHIFT;
353     private static final int STRING_LO_BYTE_SHIFT;
354     static {
355         if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) {
356             STRING_HI_BYTE_SHIFT = 8;
357             STRING_LO_BYTE_SHIFT = 0;
358         } else {
359             STRING_HI_BYTE_SHIFT = 0;
360             STRING_LO_BYTE_SHIFT = 8;
361         }
362     }
363 
364     // Tries to represent the value as string (used by JavaObject.toString).
365     public String valueAsString(boolean compact) {
366         if (getElementType() == 'B')  {
367             JavaThing[] things = getValue();
368             if (compact) {
369                 byte[] bytes = new byte[things.length];
370                 for (int i = 0; i < things.length; i++) {
371                     bytes[i] = ((JavaByte)things[i]).value;
372                 }
373                 return new String(bytes);
374             } else {
375                 char[] chars = new char[things.length / 2];
376                 for (int i = 0; i < things.length; i += 2) {
377                     int b1 = ((JavaByte)things[i]).value     << STRING_HI_BYTE_SHIFT;
378                     int b2 = ((JavaByte)things[i + 1]).value << STRING_LO_BYTE_SHIFT;
379                     chars[i / 2] = (char)(b1 | b2);
380                 }
381                 return new String(chars);
382             }
383         }
384         // fallback
385         return valueString();
386     }
387 }