1 /*
  2  * Copyright (c) 1997, 2025, 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.lang.ref.SoftReference;
 35 import java.util.*;
 36 
 37 import jdk.test.lib.hprof.parser.ReadBuffer;
 38 import jdk.test.lib.hprof.util.Misc;
 39 
 40 /**
 41  * Represents a snapshot of the Java objects in the VM at one instant.
 42  * This is the top-level "model" object read out of a single .hprof or .bod
 43  * file.
 44  */
 45 
 46 public class Snapshot implements AutoCloseable {
 47 
 48     public static final long SMALL_ID_MASK = 0x0FFFFFFFFL;
 49     public static final JavaThing[] EMPTY_JAVATHING_ARRAY = new JavaThing[0];
 50 
 51     private static final JavaField[] EMPTY_FIELD_ARRAY = new JavaField[0];
 52     private static final JavaStatic[] EMPTY_STATIC_ARRAY = new JavaStatic[0];
 53 
 54     // all heap objects
 55     private Hashtable<Number, JavaHeapObject> heapObjects =
 56                  new Hashtable<Number, JavaHeapObject>();
 57 
 58     private Hashtable<Number, JavaClass> fakeClasses =
 59                  new Hashtable<Number, JavaClass>();
 60 
 61     // all Roots in this Snapshot
 62     private Vector<Root> roots = new Vector<Root>();
 63 
 64     // name-to-class map
 65     private Map<String, JavaClass> classes =
 66                  new TreeMap<String, JavaClass>();
 67 
 68     private Map<Number, Number> flatArrays = new HashMap<>();
 69 
 70     private Map<Number, ClassInlinedFields[]> inlinedFields = new HashMap<>();
 71 
 72     // new objects relative to a baseline - lazily initialized
 73     private volatile Map<JavaHeapObject, Boolean> newObjects;
 74 
 75     // allocation site traces for all objects - lazily initialized
 76     private volatile Map<JavaHeapObject, StackTrace> siteTraces;
 77 
 78     // object-to-Root map for all objects
 79     private Map<JavaHeapObject, Root> rootsMap =
 80                  new HashMap<JavaHeapObject, Root>();
 81 
 82     // soft cache of finalizeable objects - lazily initialized
 83     private SoftReference<Vector<?>> finalizablesCache;
 84 
 85     // threads
 86     private ArrayList<ThreadObject> threads = new ArrayList<>();
 87 
 88     // represents null reference
 89     private JavaThing nullThing;
 90 
 91     // java.lang.ref.Reference class
 92     private JavaClass weakReferenceClass;
 93     // index of 'referent' field in java.lang.ref.Reference class
 94     private int referentFieldIndex;
 95 
 96     // java.lang.Class class
 97     private JavaClass javaLangClass;
 98     // java.lang.String class
 99     private JavaClass javaLangString;
100     // java.lang.ClassLoader class
101     private JavaClass javaLangClassLoader;
102 
103     // unknown "other" array class
104     private volatile JavaClass otherArrayType;
105     // Stuff to exclude from reachable query
106     private ReachableExcludes reachableExcludes;
107     // the underlying heap dump buffer
108     private ReadBuffer readBuf;
109 
110     // True iff some heap objects have isNew set
111     private boolean hasNewSet;
112     private boolean unresolvedObjectsOK;
113 
114     // whether object array instances have new style class or
115     // old style (element) class.
116     private boolean newStyleArrayClass;
117 
118     // object id size in the heap dump
119     private int identifierSize = 4;
120 
121     // minimum object size - accounts for object header in
122     // most Java virtual machines - we assume 2 identifierSize
123     // (which is true for Sun's hotspot JVM).
124     private int minimumObjectSize;
125 
126     public Snapshot(ReadBuffer buf) {
127         nullThing = new HackJavaValue("<null>", 0);
128         readBuf = buf;
129     }
130 
131     public void setSiteTrace(JavaHeapObject obj, StackTrace trace) {
132         if (trace != null && trace.getFrames().length != 0) {
133             initSiteTraces();
134             siteTraces.put(obj, trace);
135         }
136     }
137 
138     public StackTrace getSiteTrace(JavaHeapObject obj) {
139         if (siteTraces != null) {
140             return siteTraces.get(obj);
141         } else {
142             return null;
143         }
144     }
145 
146     public void setNewStyleArrayClass(boolean value) {
147         newStyleArrayClass = value;
148     }
149 
150     public boolean isNewStyleArrayClass() {
151         return newStyleArrayClass;
152     }
153 
154     public void setIdentifierSize(int size) {
155         identifierSize = size;
156         minimumObjectSize = 2 * size;
157     }
158 
159     public int getIdentifierSize() {
160         return identifierSize;
161     }
162 
163     public int getMinimumObjectSize() {
164         return minimumObjectSize;
165     }
166 
167     public void addHeapObject(long id, JavaHeapObject ho) {
168         heapObjects.put(makeId(id), ho);
169     }
170 
171     public void addRoot(Root r) {
172         r.setIndex(roots.size());
173         roots.addElement(r);
174     }
175 
176     public void addClass(long id, JavaClass c) {
177         addHeapObject(id, c);
178         putInClassesMap(c);
179     }
180 
181     public void addThreadObject(ThreadObject thread) {
182         threads.add(thread);
183     }
184 
185     JavaClass addFakeInstanceClass(long classID, int instSize) {
186         // Create a fake class name based on ID.
187         String name = "unknown-class<@" + Misc.toHex(classID) + ">";
188 
189         // Create fake fields convering the given instance size.
190         // Create as many as int type fields and for the left over
191         // size create byte type fields.
192         int numInts = instSize / 4;
193         int numBytes = instSize % 4;
194         JavaField[] fields = new JavaField[numInts + numBytes];
195         int i;
196         for (i = 0; i < numInts; i++) {
197             fields[i] = new JavaField("unknown-field-" + i, "I");
198         }
199         for (i = 0; i < numBytes; i++) {
200             fields[i + numInts] = new JavaField("unknown-field-" +
201                                                 i + numInts, "B");
202         }
203 
204         // Create fake instance class
205         JavaClass c = new JavaClass(name, 0, 0, 0, 0, fields,
206                                  EMPTY_STATIC_ARRAY, instSize);
207         // Add the class
208         addFakeClass(makeId(classID), c);
209         return c;
210     }
211 
212     public void addFlatArray(long objID, long elementClassID) {
213         flatArrays.put(makeId(objID), makeId(elementClassID));
214     }
215 
216     /**
217      * @return null if the array is not flat array
218      */
219     Number findFlatArrayElementType(long arrayObjectID) {
220         return flatArrays.get(makeId(arrayObjectID));
221     }
222 
223     public static class ClassInlinedFields {
224         final int fieldIndex;
225         final int synthFieldCount;
226         final String fieldName;
227         final long fieldClassID;
228 
229         public ClassInlinedFields(int fieldIndex, int synthFieldCount, String fieldName, long fieldClassID) {
230             this.fieldIndex = fieldIndex;
231             this.synthFieldCount = synthFieldCount;
232             this.fieldName = fieldName;
233             this.fieldClassID =  fieldClassID;
234         }
235     }
236 
237     public void addClassInlinedFields(long classID, ClassInlinedFields[] fields) {
238         inlinedFields.put(makeId(classID), fields);
239     }
240 
241     /**
242      * @return null if the class has no inlined fields
243      */
244     ClassInlinedFields[] findClassInlinedFields(long classID) {
245         return inlinedFields.get(makeId(classID));
246     }
247 
248     /**
249      * @return true iff it's possible that some JavaThing instances might
250      *          isNew set
251      *
252      * @see JavaThing.isNew()
253      */
254     public boolean getHasNewSet() {
255         return hasNewSet;
256     }
257 
258     //
259     // Used in the body of resolve()
260     //
261     private static class MyVisitor extends AbstractJavaHeapObjectVisitor {
262         JavaHeapObject t;
263         public void visit(JavaHeapObject other) {
264             other.addReferenceFrom(t);
265         }
266     }
267 
268     // To show heap parsing progress, we print a '.' after this limit
269     private static final int DOT_LIMIT = 5000;
270 
271     /**
272      * Called after reading complete, to initialize the structure
273      */
274     public void resolve(boolean calculateRefs) {
275         System.out.println("Resolving " + heapObjects.size() + " objects...");
276 
277         // First, resolve the classes.  All classes must be resolved before
278         // we try any objects, because the objects use classes in their
279         // resolution.
280         javaLangClass = findClass("java.lang.Class");
281         if (javaLangClass == null) {
282             System.out.println("WARNING:  hprof file does not include java.lang.Class!");
283             javaLangClass = new JavaClass("java.lang.Class", 0, 0, 0, 0,
284                                  EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0);
285             addFakeClass(javaLangClass);
286         }
287         javaLangString = findClass("java.lang.String");
288         if (javaLangString == null) {
289             System.out.println("WARNING:  hprof file does not include java.lang.String!");
290             javaLangString = new JavaClass("java.lang.String", 0, 0, 0, 0,
291                                  EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0);
292             addFakeClass(javaLangString);
293         }
294         javaLangClassLoader = findClass("java.lang.ClassLoader");
295         if (javaLangClassLoader == null) {
296             System.out.println("WARNING:  hprof file does not include java.lang.ClassLoader!");
297             javaLangClassLoader = new JavaClass("java.lang.ClassLoader", 0, 0, 0, 0,
298                                  EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0);
299             addFakeClass(javaLangClassLoader);
300         }
301 
302         for (JavaHeapObject t : heapObjects.values()) {
303             if (t instanceof JavaClass) {
304                 t.resolve(this);
305             }
306         }
307 
308         // Now, resolve everything else.
309         for (JavaHeapObject t : heapObjects.values()) {
310             if (!(t instanceof JavaClass)) {
311                 t.resolve(this);
312             }
313         }
314 
315         heapObjects.putAll(fakeClasses);
316         fakeClasses.clear();
317 
318         weakReferenceClass = findClass("java.lang.ref.Reference");
319         referentFieldIndex = 0;
320         if (weakReferenceClass != null)  {
321             JavaField[] fields = weakReferenceClass.getFieldsForInstance();
322             for (int i = 0; i < fields.length; i++) {
323                 if ("referent".equals(fields[i].getName())) {
324                     referentFieldIndex = i;
325                     break;
326                 }
327             }
328         }
329 
330         if (calculateRefs) {
331             calculateReferencesToObjects();
332             System.out.print("Eliminating duplicate references");
333             System.out.flush();
334             // This println refers to the *next* step
335         }
336         int count = 0;
337         for (JavaHeapObject t : heapObjects.values()) {
338             t.setupReferrers();
339             ++count;
340             if (calculateRefs && count % DOT_LIMIT == 0) {
341                 System.out.print(".");
342                 System.out.flush();
343             }
344         }
345         if (calculateRefs) {
346             System.out.println("");
347         }
348 
349         // to ensure that Iterator.remove() on getClasses()
350         // result will throw exception..
351         classes = Collections.unmodifiableMap(classes);
352     }
353 
354     private void calculateReferencesToObjects() {
355         System.out.print("Chasing references, expect "
356                          + (heapObjects.size() / DOT_LIMIT) + " dots");
357         System.out.flush();
358         int count = 0;
359         MyVisitor visitor = new MyVisitor();
360         for (JavaHeapObject t : heapObjects.values()) {
361             visitor.t = t;
362             // call addReferenceFrom(t) on all objects t references:
363             t.visitReferencedObjects(visitor);
364             ++count;
365             if (count % DOT_LIMIT == 0) {
366                 System.out.print(".");
367                 System.out.flush();
368             }
369         }
370         System.out.println();
371         for (Root r : roots) {
372             r.resolve(this);
373             JavaHeapObject t = findThing(r.getId());
374             if (t != null) {
375                 t.addReferenceFromRoot(r);
376             }
377         }
378     }
379 
380     public void markNewRelativeTo(Snapshot baseline) {
381         hasNewSet = true;
382         for (JavaHeapObject t : heapObjects.values()) {
383             boolean isNew;
384             long thingID = t.getId();
385             if (thingID == 0L || thingID == -1L) {
386                 isNew = false;
387             } else {
388                 JavaThing other = baseline.findThing(t.getId());
389                 if (other == null) {
390                     isNew = true;
391                 } else {
392                     isNew = !t.isSameTypeAs(other);
393                 }
394             }
395             t.setNew(isNew);
396         }
397     }
398 
399     public Enumeration<JavaHeapObject> getThings() {
400         return heapObjects.elements();
401     }
402 
403 
404     public JavaHeapObject findThing(long id) {
405         Number idObj = makeId(id);
406         JavaHeapObject jho = heapObjects.get(idObj);
407         return jho != null? jho : fakeClasses.get(idObj);
408     }
409 
410     public JavaHeapObject findThing(String id) {
411         return findThing(Misc.parseHex(id));
412     }
413 
414     public JavaClass findClass(String name) {
415         if (name.startsWith("0x")) {
416             return (JavaClass) findThing(name);
417         } else {
418             return classes.get(name);
419         }
420     }
421 
422     /**
423      * Return an Iterator of all of the classes in this snapshot.
424      **/
425     public Iterator<JavaClass> getClasses() {
426         // note that because classes is a TreeMap
427         // classes are already sorted by name
428         return classes.values().iterator();
429     }
430 
431     public JavaClass[] getClassesArray() {
432         JavaClass[] res = new JavaClass[classes.size()];
433         classes.values().toArray(res);
434         return res;
435     }
436 
437     public synchronized Enumeration<?> getFinalizerObjects() {
438         Vector<?> obj;
439         if (finalizablesCache != null &&
440             (obj = finalizablesCache.get()) != null) {
441             return obj.elements();
442         }
443 
444         JavaClass clazz = findClass("java.lang.ref.Finalizer");
445         JavaObject queue = (JavaObject) clazz.getStaticField("queue");
446         JavaThing tmp = queue.getField("head");
447         Vector<JavaHeapObject> finalizables = new Vector<JavaHeapObject>();
448         if (tmp != getNullThing()) {
449             JavaObject head = (JavaObject) tmp;
450             while (true) {
451                 JavaHeapObject referent = (JavaHeapObject) head.getField("referent");
452                 JavaThing next = head.getField("next");
453                 if (next == getNullThing() || next.equals(head)) {
454                     break;
455                 }
456                 head = (JavaObject) next;
457                 finalizables.add(referent);
458             }
459         }
460         finalizablesCache = new SoftReference<Vector<?>>(finalizables);
461         return finalizables.elements();
462     }
463 
464     public Enumeration<Root> getRoots() {
465         return roots.elements();
466     }
467 
468     public Root[] getRootsArray() {
469         Root[] res = new Root[roots.size()];
470         roots.toArray(res);
471         return res;
472     }
473 
474     public Root getRootAt(int i) {
475         return roots.elementAt(i);
476     }
477 
478     public List<ThreadObject> getThreads() {
479         return Collections.unmodifiableList(threads);
480     }
481 
482     public ReferenceChain[]
483     rootsetReferencesTo(JavaHeapObject target, boolean includeWeak) {
484         Vector<ReferenceChain> fifo = new Vector<ReferenceChain>();  // This is slow... A real fifo would help
485             // Must be a fifo to go breadth-first
486         Hashtable<JavaHeapObject, JavaHeapObject> visited = new Hashtable<JavaHeapObject, JavaHeapObject>();
487         // Objects are added here right after being added to fifo.
488         Vector<ReferenceChain> result = new Vector<ReferenceChain>();
489         visited.put(target, target);
490         fifo.addElement(new ReferenceChain(target, null));
491 
492         while (fifo.size() > 0) {
493             ReferenceChain chain = fifo.elementAt(0);
494             fifo.removeElementAt(0);
495             JavaHeapObject curr = chain.getObj();
496             if (curr.getRoot() != null) {
497                 result.addElement(chain);
498                 // Even though curr is in the rootset, we want to explore its
499                 // referrers, because they might be more interesting.
500             }
501             Enumeration<JavaThing> referrers = curr.getReferrers();
502             while (referrers.hasMoreElements()) {
503                 JavaHeapObject t = (JavaHeapObject)referrers.nextElement();
504                 if (t != null && !visited.containsKey(t)) {
505                     if (includeWeak || !t.refersOnlyWeaklyTo(this, curr)) {
506                         visited.put(t, t);
507                         fifo.addElement(new ReferenceChain(t, chain));
508                     }
509                 }
510             }
511         }
512 
513         ReferenceChain[] realResult = new ReferenceChain[result.size()];
514         for (int i = 0; i < result.size(); i++) {
515             realResult[i] =  result.elementAt(i);
516         }
517         return realResult;
518     }
519 
520     public boolean getUnresolvedObjectsOK() {
521         return unresolvedObjectsOK;
522     }
523 
524     public void setUnresolvedObjectsOK(boolean v) {
525         unresolvedObjectsOK = v;
526     }
527 
528     public JavaClass getWeakReferenceClass() {
529         return weakReferenceClass;
530     }
531 
532     public int getReferentFieldIndex() {
533         return referentFieldIndex;
534     }
535 
536     public JavaThing getNullThing() {
537         return nullThing;
538     }
539 
540     public void setReachableExcludes(ReachableExcludes e) {
541         reachableExcludes = e;
542     }
543 
544     public ReachableExcludes getReachableExcludes() {
545         return reachableExcludes;
546     }
547 
548     // package privates
549     void addReferenceFromRoot(Root r, JavaHeapObject obj) {
550         Root root = rootsMap.get(obj);
551         if (root == null) {
552             rootsMap.put(obj, r);
553         } else {
554             rootsMap.put(obj, root.mostInteresting(r));
555         }
556     }
557 
558     Root getRoot(JavaHeapObject obj) {
559         return rootsMap.get(obj);
560     }
561 
562     JavaClass getJavaLangClass() {
563         return javaLangClass;
564     }
565 
566     JavaClass getJavaLangString() {
567         return javaLangString;
568     }
569 
570     JavaClass getJavaLangClassLoader() {
571         return javaLangClassLoader;
572     }
573 
574     JavaClass getOtherArrayType() {
575         if (otherArrayType == null) {
576             synchronized(this) {
577                 if (otherArrayType == null) {
578                     addFakeClass(new JavaClass("[<other>", 0, 0, 0, 0,
579                                      EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY,
580                                      0));
581                     otherArrayType = findClass("[<other>");
582                 }
583             }
584         }
585         return otherArrayType;
586     }
587 
588     JavaClass getArrayClass(String elementSignature) {
589         JavaClass clazz;
590         synchronized(classes) {
591             clazz = findClass("[" + elementSignature);
592             if (clazz == null) {
593                 clazz = new JavaClass("[" + elementSignature, 0, 0, 0, 0,
594                                    EMPTY_FIELD_ARRAY, EMPTY_STATIC_ARRAY, 0);
595                 addFakeClass(clazz);
596                 // This is needed because the JDK only creates Class structures
597                 // for array element types, not the arrays themselves.  For
598                 // analysis, though, we need to pretend that there's a
599                 // JavaClass for the array type, too.
600             }
601         }
602         return clazz;
603     }
604 
605     ReadBuffer getReadBuffer() {
606         return readBuf;
607     }
608 
609     void setNew(JavaHeapObject obj, boolean isNew) {
610         initNewObjects();
611         if (isNew) {
612             newObjects.put(obj, Boolean.TRUE);
613         }
614     }
615 
616     boolean isNew(JavaHeapObject obj) {
617         if (newObjects != null) {
618             return newObjects.get(obj) != null;
619         } else {
620             return false;
621         }
622     }
623 
624     // Internals only below this point
625     private Number makeId(long id) {
626         if (identifierSize == 4) {
627             return (int)id;
628         } else {
629             return id;
630         }
631     }
632 
633     private void putInClassesMap(JavaClass c) {
634         String name = c.getName();
635         if (classes.containsKey(name)) {
636             // more than one class can have the same name
637             // if so, create a unique name by appending
638             // - and id string to it.
639             name += "-" + c.getIdString();
640         }
641         classes.put(c.getName(), c);
642     }
643 
644     private void addFakeClass(JavaClass c) {
645         putInClassesMap(c);
646         c.resolve(this);
647     }
648 
649     private void addFakeClass(Number id, JavaClass c) {
650         fakeClasses.put(id, c);
651         addFakeClass(c);
652     }
653 
654     private synchronized void initNewObjects() {
655         if (newObjects == null) {
656             synchronized (this) {
657                 if (newObjects == null) {
658                     newObjects = new HashMap<JavaHeapObject, Boolean>();
659                 }
660             }
661         }
662     }
663 
664     private synchronized void initSiteTraces() {
665         if (siteTraces == null) {
666             synchronized (this) {
667                 if (siteTraces == null) {
668                     siteTraces = new HashMap<JavaHeapObject, StackTrace>();
669                 }
670             }
671         }
672     }
673 
674     @Override
675     public void close() throws IOException {
676         readBuf.close();
677     }
678 
679 }