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