1 /*
  2  * Copyright (c) 1998, 2022, 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.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 #if defined(_ALLBSD_SOURCE)
 27 #include <stdint.h>                     /* for uintptr_t */
 28 #endif
 29 
 30 #include "util.h"
 31 #include "commonRef.h"
 32 
 33 #define ALL_REFS -1
 34 
 35 /*
 36  * Each object sent to the front end is tracked with the RefNode struct
 37  * (see util.h).
 38  * External to this module, objects are identified by a jlong id which is
 39  * simply the sequence number. A weak reference is usually used so that
 40  * the presence of a debugger-tracked object will not prevent
 41  * its collection. Once an object is collected, its RefNode may be
 42  * deleted and the weak ref inside may be reused (these may happen in
 43  * either order). Using the sequence number
 44  * as the object id prevents ambiguity in the object id when the weak ref
 45  * is reused. The RefNode* is stored with the object as it's JVMTI Tag.
 46  *
 47  * The ref member is changed from weak to strong when
 48  * gc of the object is to be prevented.
 49  * Whether or not it is strong, it is never exported from this module.
 50  *
 51  * A reference count of each jobject is also maintained here. It tracks
 52  * the number times an object has been referenced through
 53  * commonRef_refToID. A RefNode is freed once the reference
 54  * count is decremented to 0 (with commonRef_release*), even if the
 55  * corresponding object has not been collected.
 56  *
 57  * One hash table is maintained. The mapping of ID to jobject (or RefNode*)
 58  * is handled with one hash table that will re-size itself as the number
 59  * of RefNode's grow.
 60  */
 61 
 62 /* Initial hash table size (must be power of 2) */
 63 #define HASH_INIT_SIZE 512
 64 /* If element count exceeds HASH_EXPAND_SCALE*hash_size we expand & re-hash */
 65 #define HASH_EXPAND_SCALE 8
 66 /* Maximum hash table size (must be power of 2) */
 67 #define HASH_MAX_SIZE  (1024*HASH_INIT_SIZE)
 68 
 69 /* Map a key (ID) to a hash bucket */
 70 static jint
 71 hashBucket(jlong key)
 72 {
 73     /* Size should always be a power of 2, use mask instead of mod operator */
 74     /*LINTED*/
 75     return ((jint)key) & (gdata->objectsByIDsize-1);
 76 }
 77 
 78 /* Generate a new ID */
 79 static jlong
 80 newSeqNum(void)
 81 {
 82     return gdata->nextSeqNum++;
 83 }
 84 
 85 /* Returns true if this is a strong reference, meaning that either or both of
 86    isPinAll and isCommonPin are true. */
 87 static jboolean
 88 isStrong(RefNode* node)
 89 {
 90     return node->isPinAll || node->isCommonPin;
 91 }
 92 
 93 /* Create a fresh RefNode structure, create a weak ref and tag the object */
 94 static RefNode *
 95 createNode(JNIEnv *env, jobject ref)
 96 {
 97     RefNode   *node;
 98     jobject    strongOrWeakRef;
 99     jvmtiError error;
100     jboolean   pinAll = gdata->pinAllCount != 0;
101 
102     /* Could allocate RefNode's in blocks, not sure it would help much */
103     node = (RefNode*)jvmtiAllocate((int)sizeof(RefNode));
104     if (node == NULL) {
105         return NULL;
106     }
107 
108     if (pinAll) {
109         /* Create strong reference to make sure we have a reference */
110         strongOrWeakRef = JNI_FUNC_PTR(env,NewGlobalRef)(env, ref);
111     } else {
112         /* Create weak reference to make sure we have a reference */
113         strongOrWeakRef = JNI_FUNC_PTR(env,NewWeakGlobalRef)(env, ref);
114 
115         // NewWeakGlobalRef can throw OOM, clear exception here.
116         if ((*env)->ExceptionCheck(env)) {
117             (*env)->ExceptionClear(env);
118             jvmtiDeallocate(node);
119             return NULL;
120         }
121     }
122 
123     /* Set tag on strongOrWeakRef */
124     error = JVMTI_FUNC_PTR(gdata->jvmti, SetTag)
125                           (gdata->jvmti, strongOrWeakRef, ptr_to_jlong(node));
126     if ( error != JVMTI_ERROR_NONE ) {
127         if (pinAll) {
128             JNI_FUNC_PTR(env,DeleteGlobalRef)(env, strongOrWeakRef);
129         } else {
130             JNI_FUNC_PTR(env,DeleteWeakGlobalRef)(env, strongOrWeakRef);
131         }
132         jvmtiDeallocate(node);
133         return NULL;
134     }
135 
136     /* Fill in RefNode */
137     node->ref         = strongOrWeakRef;
138     node->count       = 1;
139     node->isPinAll    = pinAll;
140     node->isCommonPin = JNI_FALSE;
141     node->seqNum      = newSeqNum();
142 
143     /* Count RefNode's created */
144     gdata->objectsByIDcount++;
145     return node;
146 }
147 
148 /* Delete a RefNode allocation, delete weak/global ref and clear tag */
149 static void
150 deleteNode(JNIEnv *env, RefNode *node)
151 {
152     LOG_MISC(("Freeing %d (%x)\n", (int)node->seqNum, node->ref));
153 
154     if ( node->ref != NULL ) {
155         /* Clear tag */
156         (void)JVMTI_FUNC_PTR(gdata->jvmti,SetTag)
157                             (gdata->jvmti, node->ref, NULL_OBJECT_ID);
158         if (isStrong(node)) {
159             JNI_FUNC_PTR(env,DeleteGlobalRef)(env, node->ref);
160         } else {
161             JNI_FUNC_PTR(env,DeleteWeakGlobalRef)(env, node->ref);
162         }
163     }
164     gdata->objectsByIDcount--;
165     jvmtiDeallocate(node);
166 }
167 
168 /* Change a RefNode to have a strong reference */
169 static jobject
170 strengthenNode(JNIEnv *env, RefNode *node, jboolean isPinAll)
171 {
172     if (!isStrong(node)) {
173         jobject strongRef;
174 
175         strongRef = JNI_FUNC_PTR(env,NewGlobalRef)(env, node->ref);
176         /*
177          * NewGlobalRef on a weak ref will return NULL if the weak
178          * reference has been collected or if out of memory.
179          * It never throws OOM.
180          * We need to distinguish those two occurrences.
181          */
182         if ((strongRef == NULL) && !isSameObject(env, node->ref, NULL)) {
183             EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"NewGlobalRef");
184         }
185         if (strongRef != NULL) {
186             JNI_FUNC_PTR(env,DeleteWeakGlobalRef)(env, node->ref);
187             node->ref         = strongRef;
188         } else {
189           return NULL;
190         }
191     }
192     if (isPinAll) {
193         node->isPinAll = JNI_TRUE;
194     } else {
195         node->isCommonPin = JNI_TRUE;
196     }
197     return node->ref;
198 }
199 
200 /* Change a RefNode to have a weak reference */
201 static jweak
202 weakenNode(JNIEnv *env, RefNode *node, jboolean isUnpinAll)
203 {
204     jboolean willStillBeStrong = (node->isPinAll && !isUnpinAll) || (node->isCommonPin && isUnpinAll);
205 
206     // If the node is strong, but the reason(s) for it being strong
207     // will no longer exist, then weaken it.
208     if (isStrong(node) && !willStillBeStrong) {
209         jweak weakRef;
210 
211         weakRef = JNI_FUNC_PTR(env,NewWeakGlobalRef)(env, node->ref);
212         // NewWeakGlobalRef can throw OOM, clear exception here.
213         if ((*env)->ExceptionCheck(env)) {
214             (*env)->ExceptionClear(env);
215         }
216 
217         if (weakRef != NULL) {
218             JNI_FUNC_PTR(env,DeleteGlobalRef)(env, node->ref);
219             node->ref      = weakRef;
220         } else {
221           return NULL;
222         }
223     }
224 
225     if (isUnpinAll) {
226         node->isPinAll = JNI_FALSE;
227     } else {
228         node->isCommonPin = JNI_FALSE;
229     }
230     return node->ref;
231 }
232 
233 /*
234  * Returns the node which contains the common reference for the
235  * given object. The passed reference should not be a weak reference
236  * managed in the object hash table (i.e. returned by commonRef_idToRef)
237  * because no sequence number checking is done.
238  */
239 static RefNode *
240 findNodeByRef(JNIEnv *env, jobject ref)
241 {
242     jvmtiError error;
243     jlong      tag;
244 
245     tag   = NULL_OBJECT_ID;
246     error = JVMTI_FUNC_PTR(gdata->jvmti,GetTag)(gdata->jvmti, ref, &tag);
247     if ( error == JVMTI_ERROR_NONE ) {
248         RefNode   *node;
249 
250         node = (RefNode*)jlong_to_ptr(tag);
251         return node;
252     }
253     return NULL;
254 }
255 
256 /* Locate and delete a node based on ID */
257 static void
258 deleteNodeByID(JNIEnv *env, jlong id, jint refCount)
259 {
260     jint     slot;
261     RefNode *node;
262     RefNode *prev;
263 
264     slot = hashBucket(id);
265     node = gdata->objectsByID[slot];
266     prev = NULL;
267 
268     while (node != NULL) {
269         if (id == node->seqNum) {
270             if (refCount != ALL_REFS) {
271                 node->count -= refCount;
272             } else {
273                 node->count = 0;
274             }
275             if (node->count <= 0) {
276                 if ( node->count < 0 ) {
277                     EXIT_ERROR(AGENT_ERROR_INTERNAL,"RefNode count < 0");
278                 }
279                 /* Detach from id hash table */
280                 if (prev == NULL) {
281                     gdata->objectsByID[slot] = node->next;
282                 } else {
283                     prev->next = node->next;
284                 }
285                 deleteNode(env, node);
286             }
287             break;
288         }
289         prev = node;
290         node = node->next;
291     }
292 }
293 
294 /*
295  * Returns the node stored in the object hash table for the given object
296  * id. The id should be a value previously returned by
297  * commonRef_refToID.
298  *
299  *  NOTE: It is possible that a match is found here, but that the object
300  *        is garbage collected by the time the caller inspects node->ref.
301  *        Callers should take care using the node->ref object returned here.
302  *
303  */
304 static RefNode *
305 findNodeByID(JNIEnv *env, jlong id)
306 {
307     jint     slot;
308     RefNode *node;
309     RefNode *prev;
310 
311     slot = hashBucket(id);
312     node = gdata->objectsByID[slot];
313     prev = NULL;
314 
315     while (node != NULL) {
316         if ( id == node->seqNum ) {
317             if ( prev != NULL ) {
318                 /* Re-order hash list so this one is up front */
319                 prev->next = node->next;
320                 node->next = gdata->objectsByID[slot];
321                 gdata->objectsByID[slot] = node;
322             }
323             break;
324         }
325         node = node->next;
326     }
327     return node;
328 }
329 
330 /* Initialize the hash table stored in gdata area */
331 static void
332 initializeObjectsByID(int size)
333 {
334     /* Size should always be a power of 2 */
335     if ( size > HASH_MAX_SIZE ) size = HASH_MAX_SIZE;
336     gdata->objectsByIDsize  = size;
337     gdata->objectsByIDcount = 0;
338     gdata->objectsByID      = (RefNode**)jvmtiAllocate((int)sizeof(RefNode*)*size);
339     (void)memset(gdata->objectsByID, 0, (int)sizeof(RefNode*)*size);
340 }
341 
342 /* hash in a RefNode */
343 static void
344 hashIn(RefNode *node)
345 {
346     jint     slot;
347 
348     /* Add to id hashtable */
349     slot                     = hashBucket(node->seqNum);
350     node->next               = gdata->objectsByID[slot];
351     gdata->objectsByID[slot] = node;
352 }
353 
354 /* Allocate and add RefNode to hash table */
355 static RefNode *
356 newCommonRef(JNIEnv *env, jobject ref)
357 {
358     RefNode *node;
359 
360     /* Allocate the node and set it up */
361     node = createNode(env, ref);
362     if ( node == NULL ) {
363         return NULL;
364     }
365 
366     /* See if hash table needs expansion */
367     if ( gdata->objectsByIDcount > gdata->objectsByIDsize*HASH_EXPAND_SCALE &&
368          gdata->objectsByIDsize < HASH_MAX_SIZE ) {
369         RefNode **old;
370         int       oldsize;
371         int       newsize;
372         int       i;
373 
374         /* Save old information */
375         old     = gdata->objectsByID;
376         oldsize = gdata->objectsByIDsize;
377         /* Allocate new hash table */
378         gdata->objectsByID = NULL;
379         newsize = oldsize*HASH_EXPAND_SCALE;
380         if ( newsize > HASH_MAX_SIZE ) newsize = HASH_MAX_SIZE;
381         initializeObjectsByID(newsize);
382         /* Walk over old one and hash in all the RefNodes */
383         for ( i = 0 ; i < oldsize ; i++ ) {
384             RefNode *onode;
385 
386             onode = old[i];
387             while (onode != NULL) {
388                 RefNode *next;
389 
390                 next = onode->next;
391                 hashIn(onode);
392                 onode = next;
393             }
394         }
395         jvmtiDeallocate(old);
396     }
397 
398     /* Add to id hashtable */
399     hashIn(node);
400     return node;
401 }
402 
403 /* Initialize the commonRefs usage */
404 void
405 commonRef_initialize(void)
406 {
407     gdata->refLock = debugMonitorCreate("JDWP Reference Table Monitor");
408     gdata->nextSeqNum = 1; /* 0 used for error indication */
409     gdata->pinAllCount = 0;
410     initializeObjectsByID(HASH_INIT_SIZE);
411 }
412 
413 /* Reset the commonRefs usage */
414 void
415 commonRef_reset(JNIEnv *env)
416 {
417     debugMonitorEnter(gdata->refLock); {
418         int i;
419 
420         for (i = 0; i < gdata->objectsByIDsize; i++) {
421             RefNode *node;
422 
423             node = gdata->objectsByID[i];
424             while (node != NULL) {
425                 RefNode *next;
426 
427                 next = node->next;
428                 deleteNode(env, node);
429                 node = next;
430             }
431             gdata->objectsByID[i] = NULL;
432         }
433 
434         /* Toss entire hash table and re-create a new one */
435         jvmtiDeallocate(gdata->objectsByID);
436         gdata->objectsByID      = NULL;
437         gdata->nextSeqNum       = 1; /* 0 used for error indication */
438         initializeObjectsByID(HASH_INIT_SIZE);
439 
440     } debugMonitorExit(gdata->refLock);
441 }
442 
443 /*
444  * Given a reference obtained from JNI or JVMTI, return an object
445  * id suitable for sending to the debugger front end.
446  */
447 jlong
448 commonRef_refToID(JNIEnv *env, jobject ref)
449 {
450     jlong id;
451 
452     if (ref == NULL) {
453         return NULL_OBJECT_ID;
454     }
455 
456     id = NULL_OBJECT_ID;
457     debugMonitorEnter(gdata->refLock); {
458         RefNode *node;
459 
460         node = findNodeByRef(env, ref);
461         if (node == NULL) {
462             node = newCommonRef(env, ref);
463             if ( node != NULL ) {
464                 id = node->seqNum;
465             }
466         } else {
467             id = node->seqNum;
468             node->count++;
469         }
470     } debugMonitorExit(gdata->refLock);
471     return id;
472 }
473 
474 /*
475  * Given an object ID obtained from the debugger front end, return a
476  * strong, global reference to that object (or NULL if the object
477  * has been collected). The reference can then be used for JNI and
478  * JVMTI calls. Caller is responsible for deleting the returned reference.
479  */
480 jobject
481 commonRef_idToRef(JNIEnv *env, jlong id)
482 {
483     jobject ref;
484 
485     ref = NULL;
486     debugMonitorEnter(gdata->refLock); {
487         RefNode *node;
488 
489         node = findNodeByID(env, id);
490         if (node != NULL) {
491           if (isStrong(node)) {
492                 saveGlobalRef(env, node->ref, &ref);
493             } else {
494                 jobject lref;
495 
496                 lref = JNI_FUNC_PTR(env,NewLocalRef)(env, node->ref);
497                 // NewLocalRef never throws OOM.
498                 if ( lref == NULL ) {
499                     /* Object was GC'd shortly after we found the node */
500                     deleteNodeByID(env, node->seqNum, ALL_REFS);
501                 } else {
502                     saveGlobalRef(env, node->ref, &ref);
503                     JNI_FUNC_PTR(env,DeleteLocalRef)(env, lref);
504                 }
505             }
506         }
507     } debugMonitorExit(gdata->refLock);
508     return ref;
509 }
510 
511 /* Deletes the global reference that commonRef_idToRef() created */
512 void
513 commonRef_idToRef_delete(JNIEnv *env, jobject ref)
514 {
515     if ( ref==NULL ) {
516         return;
517     }
518     tossGlobalRef(env, &ref);
519 }
520 
521 
522 /* Prevent garbage collection of an object */
523 jvmtiError
524 commonRef_pin(jlong id)
525 {
526     jvmtiError error;
527 
528     error = JVMTI_ERROR_NONE;
529     if (id == NULL_OBJECT_ID) {
530         return error;
531     }
532     debugMonitorEnter(gdata->refLock); {
533         JNIEnv  *env;
534         RefNode *node;
535 
536         env  = getEnv();
537         node = findNodeByID(env, id);
538         if (node == NULL) {
539             error = AGENT_ERROR_INVALID_OBJECT;
540         } else {
541             jobject strongRef;
542 
543             strongRef = strengthenNode(env, node, JNI_FALSE /* isPinAll */);
544             if (strongRef == NULL) {
545                 /*
546                  * Referent has been collected, clean up now.
547                  */
548                 error = AGENT_ERROR_INVALID_OBJECT;
549                 deleteNodeByID(env, id, ALL_REFS);
550             }
551         }
552     } debugMonitorExit(gdata->refLock);
553     return error;
554 }
555 
556 /* Permit garbage collection of an object */
557 jvmtiError
558 commonRef_unpin(jlong id)
559 {
560     jvmtiError error;
561 
562     error = JVMTI_ERROR_NONE;
563     debugMonitorEnter(gdata->refLock); {
564         JNIEnv  *env;
565         RefNode *node;
566 
567         env  = getEnv();
568         node = findNodeByID(env, id);
569         if (node != NULL) {
570             jweak weakRef;
571 
572             weakRef = weakenNode(env, node, JNI_FALSE /* isUnpinAll */);
573             if (weakRef == NULL) {
574                 error = AGENT_ERROR_OUT_OF_MEMORY;
575             }
576         }
577     } debugMonitorExit(gdata->refLock);
578     return error;
579 }
580 
581 /* Prevent garbage collection of object */
582 void
583 commonRef_pinAll()
584 {
585     debugMonitorEnter(gdata->refLock); {
586         gdata->pinAllCount++;
587 
588         if (gdata->pinAllCount == 1) {
589             JNIEnv  *env;
590             RefNode *node;
591             RefNode *prev;
592             int     i;
593 
594             env = getEnv();
595 
596             /*
597              * Walk through the id-based hash table. Detach any nodes
598              * for which the ref has been collected.
599              */
600             for (i = 0; i < gdata->objectsByIDsize; i++) {
601                 node = gdata->objectsByID[i];
602                 prev = NULL;
603                 while (node != NULL) {
604                     jobject strongRef;
605 
606                     strongRef = strengthenNode(env, node, JNI_TRUE /* isPinAll */);
607 
608                     /* Has the object been collected? */
609                     if (strongRef == NULL) {
610                         RefNode *freed;
611 
612                         /* Detach from the ID list */
613                         if (prev == NULL) {
614                             gdata->objectsByID[i] = node->next;
615                         } else {
616                             prev->next = node->next;
617                         }
618                         freed = node;
619                         node = node->next;
620                         deleteNode(env, freed);
621                     } else {
622                         prev = node;
623                         node = node->next;
624                     }
625                 }
626             }
627         }
628     } debugMonitorExit(gdata->refLock);
629 }
630 
631 /* Permit garbage collection of objects */
632 void
633 commonRef_unpinAll()
634 {
635     debugMonitorEnter(gdata->refLock); {
636         gdata->pinAllCount--;
637 
638         if (gdata->pinAllCount == 0) {
639             JNIEnv  *env;
640             RefNode *node;
641             int     i;
642 
643             env = getEnv();
644 
645             for (i = 0; i < gdata->objectsByIDsize; i++) {
646                 for (node = gdata->objectsByID[i]; node != NULL; node = node->next) {
647                     jweak weakRef;
648 
649                     weakRef = weakenNode(env, node, JNI_TRUE /* isUnpinAll */);
650                     if (weakRef == NULL) {
651                         EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"NewWeakGlobalRef");
652                     }
653                 }
654             }
655         }
656     } debugMonitorExit(gdata->refLock);
657 }
658 
659 /* Release tracking of an object by ID */
660 void
661 commonRef_release(JNIEnv *env, jlong id)
662 {
663     debugMonitorEnter(gdata->refLock); {
664         deleteNodeByID(env, id, 1);
665     } debugMonitorExit(gdata->refLock);
666 }
667 
668 void
669 commonRef_releaseMultiple(JNIEnv *env, jlong id, jint refCount)
670 {
671     debugMonitorEnter(gdata->refLock); {
672         deleteNodeByID(env, id, refCount);
673     } debugMonitorExit(gdata->refLock);
674 }
675 
676 /* Get rid of RefNodes for objects that no longer exist */
677 void
678 commonRef_compact(void)
679 {
680     JNIEnv  *env;
681     RefNode *node;
682     RefNode *prev;
683     int      i;
684 
685     env = getEnv();
686     debugMonitorEnter(gdata->refLock); {
687         if ( gdata->objectsByIDsize > 0 ) {
688             /*
689              * Walk through the id-based hash table. Detach any nodes
690              * for which the ref has been collected.
691              */
692             for (i = 0; i < gdata->objectsByIDsize; i++) {
693                 node = gdata->objectsByID[i];
694                 prev = NULL;
695                 while (node != NULL) {
696                     /* Has the object been collected? */
697                   if (!isStrong(node) && isSameObject(env, node->ref, NULL)) {
698                         RefNode *freed;
699 
700                         /* Detach from the ID list */
701                         if (prev == NULL) {
702                             gdata->objectsByID[i] = node->next;
703                         } else {
704                             prev->next = node->next;
705                         }
706                         freed = node;
707                         node = node->next;
708                         deleteNode(env, freed);
709                     } else {
710                         prev = node;
711                         node = node->next;
712                     }
713                 }
714             }
715         }
716     } debugMonitorExit(gdata->refLock);
717 }
718 
719 /* Lock the commonRef tables */
720 void
721 commonRef_lock(void)
722 {
723     debugMonitorEnter(gdata->refLock);
724 }
725 
726 /* Unlock the commonRef tables */
727 void
728 commonRef_unlock(void)
729 {
730     debugMonitorExit(gdata->refLock);
731 }