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 }