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 */
70 static jint
hashBucket(jlong key)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
newSeqNum(void)80 newSeqNum(void)
81 {
82     return gdata->nextSeqNum++;
83 }
84 
85 /* Create a fresh RefNode structure, create a weak ref and tag the object */
86 static RefNode *
createNode(JNIEnv * env,jobject ref)87 createNode(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 */
129 static void
deleteNode(JNIEnv * env,RefNode * node)130 deleteNode(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 */
149 static jobject
strengthenNode(JNIEnv * env,RefNode * node)150 strengthenNode(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 */
177 static jweak
weakenNode(JNIEnv * env,RefNode * node)178 weakenNode(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  */
206 static RefNode *
findNodeByRef(JNIEnv * env,jobject ref)207 findNodeByRef(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 */
224 static void
deleteNodeByID(JNIEnv * env,jlong id,jint refCount)225 deleteNodeByID(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  */
271 static RefNode *
findNodeByID(JNIEnv * env,jlong id)272 findNodeByID(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 */
298 static void
initializeObjectsByID(int size)299 initializeObjectsByID(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 */
310 static void
hashIn(RefNode * node)311 hashIn(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 */
322 static RefNode *
newCommonRef(JNIEnv * env,jobject ref)323 newCommonRef(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 */
371 void
commonRef_initialize(void)372 commonRef_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 */
380 void
commonRef_reset(JNIEnv * env)381 commonRef_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  */
413 jlong
commonRef_refToID(JNIEnv * env,jobject ref)414 commonRef_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  */
446 jobject
commonRef_idToRef(JNIEnv * env,jlong id)447 commonRef_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 */
478 void
commonRef_idToRef_delete(JNIEnv * env,jobject ref)479 commonRef_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 */
489 jvmtiError
commonRef_pin(jlong id)490 commonRef_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 */
523 jvmtiError
commonRef_unpin(jlong id)524 commonRef_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 */
548 void
commonRef_release(JNIEnv * env,jlong id)549 commonRef_release(JNIEnv *env, jlong id)
550 {
551     debugMonitorEnter(gdata->refLock); {
552         deleteNodeByID(env, id, 1);
553     } debugMonitorExit(gdata->refLock);
554 }
555 
556 void
commonRef_releaseMultiple(JNIEnv * env,jlong id,jint refCount)557 commonRef_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 */
565 void
commonRef_compact(void)566 commonRef_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 */
609 void
commonRef_lock(void)610 commonRef_lock(void)
611 {
612     debugMonitorEnter(gdata->refLock);
613 }
614 
615 /* Unlock the commonRef tables */
616 void
commonRef_unlock(void)617 commonRef_unlock(void)
618 {
619     debugMonitorExit(gdata->refLock);
620 }
621