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 }