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