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 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      * Construct a new JavaObject.
 47      *
 48      * @param classID id of the class object
 49      * @param offset The offset of field data
 50      */
 51     public JavaObject(long classID, long offset) {
 52         super(offset);
 53         this.clazz = makeId(classID);
 54     }
 55 
 56     public void resolve(Snapshot snapshot) {
 57         if (clazz instanceof JavaClass) {
 58             return;
 59         }
 60         if (clazz instanceof Number) {
 61             long classID = getIdValue((Number)clazz);
 62             clazz = snapshot.findThing(classID);
 63             if (! (clazz instanceof JavaClass)) {
 64                 warn("Class " + Long.toHexString(classID) + " not found, " +
 65                      "adding fake class!");
 66                 int length;
 67                 ReadBuffer buf = snapshot.getReadBuffer();
 68                 int idSize = snapshot.getIdentifierSize();
 69                 long lenOffset = getOffset() + 2*idSize + 4;
 70                 try {
 71                     length = buf.getInt(lenOffset);
 72                 } catch (IOException exp) {
 73                     throw new RuntimeException(exp);
 74                 }
 75                 clazz = snapshot.addFakeInstanceClass(classID, length);
 76             }
 77         } else {
 78             throw new InternalError("should not reach here");
 79         }
 80 
 81         JavaClass cl = (JavaClass) clazz;
 82         cl.resolve(snapshot);
 83 
 84         // while resolving, parse fields in verbose mode.
 85         // but, getFields calls parseFields in non-verbose mode
 86         // to avoid printing warnings repeatedly.
 87         parseFields(true);
 88 
 89         cl.addInstance(this);
 90         super.resolve(snapshot);
 91     }
 92 
 93     /**
 94      * Are we the same type as other?  We are iff our clazz is the
 95      * same type as other's.
 96      */
 97     public boolean isSameTypeAs(JavaThing other) {
 98         if (!(other instanceof JavaObject)) {
 99             return false;
100         }
101         JavaObject oo = (JavaObject) other;
102         return getClazz().equals(oo.getClazz());
103     }
104 
105     /**
106      * Return our JavaClass object.  This may only be called after resolve.
107      */
108     public JavaClass getClazz() {
109         return (JavaClass) clazz;
110     }
111 
112     public JavaThing[] getFields() {
113         // pass false to verbose mode so that dereference
114         // warnings are not printed.
115         return parseFields(false);
116     }
117 
118     // returns the value of field of given name
119     public JavaThing getField(String name) {
120         JavaThing[] flds = getFields();
121         JavaField[] instFields = getClazz().getFieldsForInstance();
122         for (int i = 0; i < instFields.length; i++) {
123             if (instFields[i].getName().equals(name)) {
124                 return flds[i];
125             }
126         }
127         return null;
128     }
129 
130     public int compareTo(JavaThing other) {
131         if (other instanceof JavaObject) {
132             JavaObject oo = (JavaObject) other;
133             return getClazz().getName().compareTo(oo.getClazz().getName());
134         }
135         return super.compareTo(other);
136     }
137 
138     public void visitReferencedObjects(JavaHeapObjectVisitor v) {
139         super.visitReferencedObjects(v);
140         JavaThing[] flds = getFields();
141         for (int i = 0; i < flds.length; i++) {
142             if (flds[i] != null) {
143                 if (v.mightExclude()
144                     && v.exclude(getClazz().getClassForField(i),
145                                  getClazz().getFieldForInstance(i)))
146                 {
147                     // skip it
148                 } else if (flds[i] instanceof JavaHeapObject) {
149                     v.visit((JavaHeapObject) flds[i]);
150                 }
151             }
152         }
153     }
154 
155     public boolean refersOnlyWeaklyTo(Snapshot ss, JavaThing other) {
156         if (ss.getWeakReferenceClass() != null) {
157             final int referentFieldIndex = ss.getReferentFieldIndex();
158             if (ss.getWeakReferenceClass().isAssignableFrom(getClazz())) {
159                 //
160                 // REMIND:  This introduces a dependency on the JDK
161                 //      implementation that is undesirable.
162                 JavaThing[] flds = getFields();
163                 for (int i = 0; i < flds.length; i++) {
164                     if (i != referentFieldIndex && flds[i] == other) {
165                         return false;
166                     }
167                 }
168                 return true;
169             }
170         }
171         return false;
172     }
173 
174     /**
175      * Describe the reference that this thing has to target.  This will only
176      * be called if target is in the array returned by getChildrenForRootset.
177      */
178     public String describeReferenceTo(JavaThing target, Snapshot ss) {
179         JavaThing[] flds = getFields();
180         for (int i = 0; i < flds.length; i++) {
181             if (flds[i] == target) {
182                 JavaField f = getClazz().getFieldForInstance(i);
183                 return "field " + f.getName();
184             }
185         }
186         return super.describeReferenceTo(target, ss);
187     }
188 
189     public String toString() {
190         if (getClazz().isString()) {
191             JavaThing coder = getField("coder");
192             boolean compact = false;
193             if (coder instanceof JavaByte) {
194                 compact = ((JavaByte)coder).value == 0;
195             }
196             JavaThing value = getField("value");
197             if (value instanceof JavaValueArray) {
198                 return ((JavaValueArray)value).valueAsString(compact);
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 final 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     private 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                 switch (sig) {
264                     case 'L':
265                     case '[': {
266                         long id = objectIdAt(offset);
267                         offset += idSize();
268                         JavaObjectRef ref = new JavaObjectRef(id);
269                         fieldValues[target+fieldNo] = ref.dereference(snapshot, f, verbose);
270                         break;
271                     }
272                     case 'Z': {
273                         byte value = byteAt(offset);
274                         offset++;
275                         fieldValues[target+fieldNo] = new JavaBoolean(value != 0);
276                         break;
277                     }
278                     case 'B': {
279                         byte value = byteAt(offset);
280                         offset++;
281                         fieldValues[target+fieldNo] = new JavaByte(value);
282                         break;
283                     }
284                     case 'S': {
285                         short value = shortAt(offset);
286                         offset += 2;
287                         fieldValues[target+fieldNo] = new JavaShort(value);
288                         break;
289                     }
290                     case 'C': {
291                         char value = charAt(offset);
292                         offset += 2;
293                         fieldValues[target+fieldNo] = new JavaChar(value);
294                         break;
295                     }
296                     case 'I': {
297                         int value = intAt(offset);
298                         offset += 4;
299                         fieldValues[target+fieldNo] = new JavaInt(value);
300                         break;
301                     }
302                     case 'J': {
303                         long value = longAt(offset);
304                         offset += 8;
305                         fieldValues[target+fieldNo] = new JavaLong(value);
306                         break;
307                     }
308                     case 'F': {
309                         float value = floatAt(offset);
310                         offset += 4;
311                         fieldValues[target+fieldNo] = new JavaFloat(value);
312                         break;
313                     }
314                     case 'D': {
315                         double value = doubleAt(offset);
316                         offset += 8;
317                         fieldValues[target+fieldNo] = new JavaDouble(value);
318                         break;
319                     }
320                     default:
321                         throw new RuntimeException("invalid signature: " + sig);
322 
323                 }
324         } catch (IOException exp) {
325             System.err.println("lazy read failed at offset " + offset);
326             exp.printStackTrace();
327             return Snapshot.EMPTY_JAVATHING_ARRAY;
328             }
329         }
330         return fieldValues;
331     }
332 
333     private void warn(String msg) {
334         System.out.println("WARNING: " + msg);
335     }
336 }