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