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 }