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 }