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      * 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 value = getField("value");
192             if (value instanceof JavaValueArray) {
193                 return ((JavaValueArray)value).valueAsString();
194             } else {
195                 return "null";
196             }
197         } else {
198             return super.toString();
199         }
200     }
201 
202     // Internals only below this point
203 
204     /*
205      * Java instance record (HPROF_GC_INSTANCE_DUMP) looks as below:
206      *
207      *     object ID
208      *     stack trace serial number (int)
209      *     class ID
210      *     data length (int)
211      *     byte[length]
212      */
213     @Override
214     protected final long readValueLength() throws IOException {
215         long lengthOffset = getOffset() + 2 * idSize() + 4;
216         return buf().getInt(lengthOffset);
217     }
218 
219     @Override
220     protected final JavaThing[] readValue() throws IOException {
221         return parseFields(false);
222     }
223 
224     private long dataStartOffset() {
225         return getOffset() + idSize() + 4 + idSize() + 4;
226     }
227 
228     private JavaThing[] parseFields(boolean verbose) {
229         JavaClass cl = getClazz();
230         int target = cl.getNumFieldsForInstance();
231         JavaField[] fields = cl.getFields();
232         JavaThing[] fieldValues = new JavaThing[target];
233         Snapshot snapshot = cl.getSnapshot();
234         int fieldNo = 0;
235         // In the dump file, the fields are stored in this order:
236         // fields of most derived class (immediate class) are stored
237         // first and then the super class and so on. In this object,
238         // fields are stored in the reverse ("natural") order. i.e.,
239         // fields of most super class are stored first.
240 
241         // target variable is used to compensate for the fact that
242         // the dump file starts field values from the leaf working
243         // upwards in the inheritance hierarchy, whereas JavaObject
244         // starts with the top of the inheritance hierarchy and works down.
245         target -= fields.length;
246         JavaClass currClass = cl;
247         long offset = dataStartOffset();
248         for (int i = 0; i < fieldValues.length; i++, fieldNo++) {
249             while (fieldNo >= fields.length) {
250                 currClass = currClass.getSuperclass();
251                 fields = currClass.getFields();
252                 fieldNo = 0;
253                 target -= fields.length;
254             }
255             JavaField f = fields[fieldNo];
256             char sig = f.getSignature().charAt(0);
257             try {
258                 switch (sig) {
259                     case 'L':
260                     case '[': {
261                         long id = objectIdAt(offset);
262                         offset += idSize();
263                         JavaObjectRef ref = new JavaObjectRef(id);
264                         fieldValues[target+fieldNo] = ref.dereference(snapshot, f, verbose);
265                         break;
266                     }
267                     case 'Z': {
268                         byte value = byteAt(offset);
269                         offset++;
270                         fieldValues[target+fieldNo] = new JavaBoolean(value != 0);
271                         break;
272                     }
273                     case 'B': {
274                         byte value = byteAt(offset);
275                         offset++;
276                         fieldValues[target+fieldNo] = new JavaByte(value);
277                         break;
278                     }
279                     case 'S': {
280                         short value = shortAt(offset);
281                         offset += 2;
282                         fieldValues[target+fieldNo] = new JavaShort(value);
283                         break;
284                     }
285                     case 'C': {
286                         char value = charAt(offset);
287                         offset += 2;
288                         fieldValues[target+fieldNo] = new JavaChar(value);
289                         break;
290                     }
291                     case 'I': {
292                         int value = intAt(offset);
293                         offset += 4;
294                         fieldValues[target+fieldNo] = new JavaInt(value);
295                         break;
296                     }
297                     case 'J': {
298                         long value = longAt(offset);
299                         offset += 8;
300                         fieldValues[target+fieldNo] = new JavaLong(value);
301                         break;
302                     }
303                     case 'F': {
304                         float value = floatAt(offset);
305                         offset += 4;
306                         fieldValues[target+fieldNo] = new JavaFloat(value);
307                         break;
308                     }
309                     case 'D': {
310                         double value = doubleAt(offset);
311                         offset += 8;
312                         fieldValues[target+fieldNo] = new JavaDouble(value);
313                         break;
314                     }
315                     default:
316                         throw new RuntimeException("invalid signature: " + sig);
317 
318                 }
319         } catch (IOException exp) {
320             System.err.println("lazy read failed at offset " + offset);
321             exp.printStackTrace();
322             return Snapshot.EMPTY_JAVATHING_ARRAY;
323             }
324         }
325         return fieldValues;
326     }
327 
328     private void warn(String msg) {
329         System.out.println("WARNING: " + msg);
330     }
331 }