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 jdk.test.lib.hprof.parser.ReadBuffer; 35 36 /** 37 * Represents Java instance 38 * 39 * @author Bill Foote 40 */ 41 public class JavaObject extends JavaLazyReadObject { 42 43 private Object clazz; // Number before resolve 44 // JavaClass after resolve 45 46 public JavaObject(Object clazz, long offset) { 47 super(offset); 48 this.clazz = clazz; 49 } 50 51 /** 52 * Construct a new JavaObject. 53 * 54 * @param classID id of the class object 55 * @param offset The offset of field data 56 */ 57 public JavaObject(long classID, long offset) { 58 this(makeId(classID), offset); 59 } 60 61 public void resolve(Snapshot snapshot) { 62 if (clazz instanceof JavaClass) { 63 return; 64 } 65 if (clazz instanceof Number) { 66 long classID = getIdValue((Number)clazz); 67 clazz = snapshot.findThing(classID); 68 if (! (clazz instanceof JavaClass)) { 69 warn("Class " + Long.toHexString(classID) + " not found, " + 70 "adding fake class!"); 71 int length; 72 ReadBuffer buf = snapshot.getReadBuffer(); 73 int idSize = snapshot.getIdentifierSize(); 74 long lenOffset = getOffset() + 2*idSize + 4; 75 try { 76 length = buf.getInt(lenOffset); 77 } catch (IOException exp) { 78 throw new RuntimeException(exp); 79 } 80 clazz = snapshot.addFakeInstanceClass(classID, length); 81 } 82 } else { 83 throw new InternalError("should not reach here"); 84 } 85 86 JavaClass cl = (JavaClass) clazz; 87 cl.resolve(snapshot); 88 89 // while resolving, parse fields in verbose mode. 90 // but, getFields calls parseFields in non-verbose mode 91 // to avoid printing warnings repeatedly. 92 parseFields(true); 93 94 cl.addInstance(this); 95 super.resolve(snapshot); 96 } 97 98 /** 99 * Are we the same type as other? We are iff our clazz is the 100 * same type as other's. 101 */ 102 public boolean isSameTypeAs(JavaThing other) { 103 if (!(other instanceof JavaObject)) { 104 return false; 105 } 106 JavaObject oo = (JavaObject) other; 107 return getClazz().equals(oo.getClazz()); 108 } 109 110 /** 111 * Return our JavaClass object. This may only be called after resolve. 112 */ 113 public JavaClass getClazz() { 114 return (JavaClass) clazz; 115 } 116 117 public JavaThing[] getFields() { 118 // pass false to verbose mode so that dereference 119 // warnings are not printed. 120 return parseFields(false); 121 } 122 123 // returns the value of field of given name 124 public JavaThing getField(String name) { 125 JavaThing[] flds = getFields(); 126 JavaField[] instFields = getClazz().getFieldsForInstance(); 127 for (int i = 0; i < instFields.length; i++) { 128 if (instFields[i].getName().equals(name)) { 129 return flds[i]; 130 } 131 } 132 return null; 133 } 134 135 public int compareTo(JavaThing other) { 136 if (other instanceof JavaObject) { 137 JavaObject oo = (JavaObject) other; 138 return getClazz().getName().compareTo(oo.getClazz().getName()); 139 } 140 return super.compareTo(other); 141 } 142 143 public void visitReferencedObjects(JavaHeapObjectVisitor v) { 144 super.visitReferencedObjects(v); 145 JavaThing[] flds = getFields(); 146 for (int i = 0; i < flds.length; i++) { 147 if (flds[i] != null) { 148 if (v.mightExclude() 149 && v.exclude(getClazz().getClassForField(i), 150 getClazz().getFieldForInstance(i))) 151 { 152 // skip it 153 } else if (flds[i] instanceof JavaHeapObject) { 154 v.visit((JavaHeapObject) flds[i]); 155 } 156 } 157 } 158 } 159 160 public boolean refersOnlyWeaklyTo(Snapshot ss, JavaThing other) { 161 if (ss.getWeakReferenceClass() != null) { 162 final int referentFieldIndex = ss.getReferentFieldIndex(); 163 if (ss.getWeakReferenceClass().isAssignableFrom(getClazz())) { 164 // 165 // REMIND: This introduces a dependency on the JDK 166 // implementation that is undesirable. 167 JavaThing[] flds = getFields(); 168 for (int i = 0; i < flds.length; i++) { 169 if (i != referentFieldIndex && flds[i] == other) { 170 return false; 171 } 172 } 173 return true; 174 } 175 } 176 return false; 177 } 178 179 /** 180 * Describe the reference that this thing has to target. This will only 181 * be called if target is in the array returned by getChildrenForRootset. 182 */ 183 public String describeReferenceTo(JavaThing target, Snapshot ss) { 184 JavaThing[] flds = getFields(); 185 for (int i = 0; i < flds.length; i++) { 186 if (flds[i] == target) { 187 JavaField f = getClazz().getFieldForInstance(i); 188 return "field " + f.getName(); 189 } 190 } 191 return super.describeReferenceTo(target, ss); 192 } 193 194 public String toString() { 195 if (getClazz().isString()) { 196 JavaThing value = getField("value"); 197 if (value instanceof JavaValueArray) { 198 return ((JavaValueArray)value).valueAsString(); 199 } else { 200 return "null"; 201 } 202 } else { 203 return super.toString(); 204 } 205 } 206 207 // Internals only below this point 208 209 /* 210 * Java instance record (HPROF_GC_INSTANCE_DUMP) looks as below: 211 * 212 * object ID 213 * stack trace serial number (int) 214 * class ID 215 * data length (int) 216 * byte[length] 217 */ 218 @Override 219 protected long readValueLength() throws IOException { 220 long lengthOffset = getOffset() + 2 * idSize() + 4; 221 return buf().getInt(lengthOffset); 222 } 223 224 @Override 225 protected final JavaThing[] readValue() throws IOException { 226 return parseFields(false); 227 } 228 229 protected long dataStartOffset() { 230 return getOffset() + idSize() + 4 + idSize() + 4; 231 } 232 233 private JavaThing[] parseFields(boolean verbose) { 234 JavaClass cl = getClazz(); 235 int target = cl.getNumFieldsForInstance(); 236 JavaField[] fields = cl.getFields(); 237 JavaThing[] fieldValues = new JavaThing[target]; 238 Snapshot snapshot = cl.getSnapshot(); 239 int fieldNo = 0; 240 // In the dump file, the fields are stored in this order: 241 // fields of most derived class (immediate class) are stored 242 // first and then the super class and so on. In this object, 243 // fields are stored in the reverse ("natural") order. i.e., 244 // fields of most super class are stored first. 245 246 // target variable is used to compensate for the fact that 247 // the dump file starts field values from the leaf working 248 // upwards in the inheritance hierarchy, whereas JavaObject 249 // starts with the top of the inheritance hierarchy and works down. 250 target -= fields.length; 251 JavaClass currClass = cl; 252 long offset = dataStartOffset(); 253 for (int i = 0; i < fieldValues.length; i++, fieldNo++) { 254 while (fieldNo >= fields.length) { 255 currClass = currClass.getSuperclass(); 256 fields = currClass.getFields(); 257 fieldNo = 0; 258 target -= fields.length; 259 } 260 JavaField f = fields[fieldNo]; 261 char sig = f.getSignature().charAt(0); 262 try { 263 if (f instanceof InlinedJavaField inlinedField) { 264 JavaClass fieldClass = inlinedField.getInlinedFieldClass(); 265 fieldValues[target+fieldNo] = new InlinedJavaObject(fieldClass, offset); 266 offset += fieldClass.getInlinedInstanceSize(); 267 } else { 268 switch (sig) { 269 case 'Q': { 270 warn("(parseFields) field " + getClazz().getName() + "." + f.getName() 271 + " is not inlined, but has Q-signature: " + f.getSignature()); 272 } // continue as 'L' object 273 case 'L': 274 case '[': { 275 long id = objectIdAt(offset); 276 offset += idSize(); 277 JavaObjectRef ref = new JavaObjectRef(id); 278 fieldValues[target + fieldNo] = ref.dereference(snapshot, f, verbose); 279 break; 280 } 281 case 'Z': { 282 byte value = byteAt(offset); 283 offset++; 284 fieldValues[target + fieldNo] = new JavaBoolean(value != 0); 285 break; 286 } 287 case 'B': { 288 byte value = byteAt(offset); 289 offset++; 290 fieldValues[target + fieldNo] = new JavaByte(value); 291 break; 292 } 293 case 'S': { 294 short value = shortAt(offset); 295 offset += 2; 296 fieldValues[target + fieldNo] = new JavaShort(value); 297 break; 298 } 299 case 'C': { 300 char value = charAt(offset); 301 offset += 2; 302 fieldValues[target + fieldNo] = new JavaChar(value); 303 break; 304 } 305 case 'I': { 306 int value = intAt(offset); 307 offset += 4; 308 fieldValues[target + fieldNo] = new JavaInt(value); 309 break; 310 } 311 case 'J': { 312 long value = longAt(offset); 313 offset += 8; 314 fieldValues[target + fieldNo] = new JavaLong(value); 315 break; 316 } 317 case 'F': { 318 float value = floatAt(offset); 319 offset += 4; 320 fieldValues[target + fieldNo] = new JavaFloat(value); 321 break; 322 } 323 case 'D': { 324 double value = doubleAt(offset); 325 offset += 8; 326 fieldValues[target + fieldNo] = new JavaDouble(value); 327 break; 328 } 329 default: 330 throw new RuntimeException("invalid signature: " + sig); 331 332 } 333 } 334 } catch (IOException exp) { 335 System.err.println("lazy read failed at offset " + offset); 336 exp.printStackTrace(); 337 return Snapshot.EMPTY_JAVATHING_ARRAY; 338 } 339 } 340 return fieldValues; 341 } 342 343 private void warn(String msg) { 344 System.out.println("WARNING: " + msg); 345 } 346 }