IndirectRefTable.cpp revision 24c57f1d575fccc11239cf71089c7d04270a21c3
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 * Indirect reference table management.
19 */
20#include "Dalvik.h"
21
22static void abortMaybe() {
23    // If CheckJNI is on, it'll give a more detailed error before aborting.
24    // Otherwise, we want to abort rather than hand back a bad reference.
25    if (!gDvmJni.useCheckJni) {
26        dvmAbort();
27    }
28}
29
30bool IndirectRefTable::init(size_t initialCount,
31        size_t maxCount, IndirectRefKind desiredKind)
32{
33    assert(initialCount > 0);
34    assert(initialCount <= maxCount);
35    assert(kind != kIndirectKindInvalid);
36
37    table = (Object**) malloc(initialCount * sizeof(Object*));
38    if (table == NULL) {
39        return false;
40    }
41#ifndef NDEBUG
42    memset(table, 0xd1, initialCount * sizeof(Object*));
43#endif
44
45    slotData =
46        (IndirectRefSlot*) calloc(maxCount, sizeof(IndirectRefSlot));
47    if (slotData == NULL) {
48        return false;
49    }
50
51    segmentState.all = IRT_FIRST_SEGMENT;
52    allocEntries = initialCount;
53    maxEntries = maxCount;
54    kind = desiredKind;
55
56    return true;
57}
58
59/*
60 * Clears out the contents of a IndirectRefTable, freeing allocated storage.
61 */
62void IndirectRefTable::destroy()
63{
64    free(table);
65    free(slotData);
66    table = NULL;
67    allocEntries = maxEntries = -1;
68}
69
70/*
71 * Make sure that the entry at "idx" is correctly paired with "iref".
72 */
73bool IndirectRefTable::checkEntry(IndirectRef iref, int idx) const
74{
75    Object* obj = table[idx];
76    IndirectRef checkRef = toIndirectRef(obj, idx);
77    if (checkRef != iref) {
78        LOGE("JNI ERROR (app bug): use of stale %s reference (req=%p vs cur=%p; table=%p)",
79                indirectRefKindToString(kind), iref, checkRef, this);
80        abortMaybe();
81        return false;
82    }
83    return true;
84}
85
86IndirectRef IndirectRefTable::add(u4 cookie, Object* obj)
87{
88    IRTSegmentState prevState;
89    prevState.all = cookie;
90    size_t topIndex = segmentState.parts.topIndex;
91
92    assert(obj != NULL);
93    assert(dvmIsValidObject(obj));
94    assert(table != NULL);
95    assert(allocEntries <= maxEntries);
96    assert(segmentState.parts.numHoles >= prevState.parts.numHoles);
97
98    if (topIndex == allocEntries) {
99        /* reached end of allocated space; did we hit buffer max? */
100        if (topIndex == maxEntries) {
101            LOGE("JNI ERROR (app bug): %s reference table overflow (max=%d)",
102                    indirectRefKindToString(kind), maxEntries);
103            dump(indirectRefKindToString(kind));
104            dvmAbort();
105        }
106
107        size_t newSize = allocEntries * 2;
108        if (newSize > maxEntries) {
109            newSize = maxEntries;
110        }
111        assert(newSize > allocEntries);
112
113        Object** newTable = (Object**) realloc(table, newSize * sizeof(Object*));
114        if (newTable == NULL) {
115            LOGE("JNI ERROR (app bug): unable to expand %s reference table (from %d to %d, max=%d)",
116                    indirectRefKindToString(kind),
117                    allocEntries, newSize, maxEntries);
118            dump(indirectRefKindToString(kind));
119            dvmAbort();
120        }
121
122        /* update entries; adjust "nextEntry" in case memory moved */
123        table = newTable;
124        allocEntries = newSize;
125    }
126
127    IndirectRef result;
128
129    /*
130     * We know there's enough room in the table.  Now we just need to find
131     * the right spot.  If there's a hole, find it and fill it; otherwise,
132     * add to the end of the list.
133     */
134    int numHoles = segmentState.parts.numHoles - prevState.parts.numHoles;
135    if (numHoles > 0) {
136        assert(topIndex > 1);
137        /* find the first hole; likely to be near the end of the list */
138        Object** pScan = &table[topIndex - 1];
139        assert(*pScan != NULL);
140        while (*--pScan != NULL) {
141            assert(pScan >= table + prevState.parts.topIndex);
142        }
143        updateSlotAdd(obj, pScan - table);
144        result = toIndirectRef(obj, pScan - table);
145        *pScan = obj;
146        segmentState.parts.numHoles--;
147    } else {
148        /* add to the end */
149        updateSlotAdd(obj, topIndex);
150        result = toIndirectRef(obj, topIndex);
151        table[topIndex++] = obj;
152        segmentState.parts.topIndex = topIndex;
153    }
154
155    assert(result != NULL);
156    return result;
157}
158
159/*
160 * Verify that the indirect table lookup is valid.
161 *
162 * Returns "false" if something looks bad.
163 */
164bool IndirectRefTable::getChecked(IndirectRef iref) const
165{
166    if (iref == NULL) {
167        LOGW("Attempt to look up NULL %s reference",
168                indirectRefKindToString(kind));
169        return false;
170    }
171    if (indirectRefKind(iref) == kIndirectKindInvalid) {
172        LOGE("JNI ERROR (app bug): invalid %s reference (%p)",
173                indirectRefKindToString(kind), iref);
174        abortMaybe();
175        return false;
176    }
177
178    int topIndex = segmentState.parts.topIndex;
179    int idx = extractIndex(iref);
180    if (idx >= topIndex) {
181        /* bad -- stale reference? */
182        LOGE("JNI ERROR (app bug): accessed stale %s reference at index %d (top=%d)",
183                indirectRefKindToString(kind), idx, topIndex);
184        abortMaybe();
185        return false;
186    }
187
188    Object* obj = table[idx];
189    if (obj == NULL) {
190        LOGE("JNI ERROR (app bug): use of deleted %s reference (%p)",
191                indirectRefKindToString(kind), iref);
192        abortMaybe();
193        return false;
194    }
195    if (!checkEntry(iref, idx)) {
196        return false;
197    }
198
199    return true;
200}
201
202/*
203 * Remove "obj" from "pRef".  We extract the table offset bits from "iref"
204 * and zap the corresponding entry, leaving a hole if it's not at the top.
205 *
206 * If the entry is not between the current top index and the bottom index
207 * specified by the cookie, we don't remove anything.  This is the behavior
208 * required by JNI's DeleteLocalRef function.
209 *
210 * Note this is NOT called when a local frame is popped.  This is only used
211 * for explict single removals.
212 *
213 * Returns "false" if nothing was removed.
214 */
215bool IndirectRefTable::remove(u4 cookie, IndirectRef iref)
216{
217    IRTSegmentState prevState;
218    prevState.all = cookie;
219    int topIndex = segmentState.parts.topIndex;
220    int bottomIndex = prevState.parts.topIndex;
221
222    assert(table != NULL);
223    assert(allocEntries <= maxEntries);
224    assert(segmentState.parts.numHoles >= prevState.parts.numHoles);
225
226    int idx = extractIndex(iref);
227    if (idx < bottomIndex) {
228        /* wrong segment */
229        LOGV("Attempt to remove index outside index area (%d vs %d-%d)",
230            idx, bottomIndex, topIndex);
231        return false;
232    }
233    if (idx >= topIndex) {
234        /* bad -- stale reference? */
235        LOGD("Attempt to remove invalid index %d (bottom=%d top=%d)",
236            idx, bottomIndex, topIndex);
237        return false;
238    }
239
240    if (idx == topIndex-1) {
241        /*
242         * Top-most entry.  Scan up and consume holes.  No need to NULL
243         * out the entry, since the test vs. topIndex will catch it.
244         */
245        if (!checkEntry(iref, idx)) {
246            return false;
247        }
248        updateSlotRemove(idx);
249
250#ifndef NDEBUG
251        table[idx] = (Object*)0xd3d3d3d3;
252#endif
253
254        int numHoles =
255            segmentState.parts.numHoles - prevState.parts.numHoles;
256        if (numHoles != 0) {
257            while (--topIndex > bottomIndex && numHoles != 0) {
258                LOGV("+++ checking for hole at %d (cookie=0x%08x) val=%p",
259                    topIndex-1, cookie, table[topIndex-1]);
260                if (table[topIndex-1] != NULL)
261                    break;
262                LOGV("+++ ate hole at %d", topIndex-1);
263                numHoles--;
264            }
265            segmentState.parts.numHoles =
266                numHoles + prevState.parts.numHoles;
267            segmentState.parts.topIndex = topIndex;
268        } else {
269            segmentState.parts.topIndex = topIndex-1;
270            LOGV("+++ ate last entry %d", topIndex-1);
271        }
272    } else {
273        /*
274         * Not the top-most entry.  This creates a hole.  We NULL out the
275         * entry to prevent somebody from deleting it twice and screwing up
276         * the hole count.
277         */
278        if (table[idx] == NULL) {
279            LOGV("--- WEIRD: removing null entry %d", idx);
280            return false;
281        }
282        if (!checkEntry(iref, idx)) {
283            return false;
284        }
285        updateSlotRemove(idx);
286
287        table[idx] = NULL;
288        segmentState.parts.numHoles++;
289        LOGV("+++ left hole at %d, holes=%d",
290            idx, segmentState.parts.numHoles);
291    }
292
293    return true;
294}
295
296const char* indirectRefKindToString(IndirectRefKind kind)
297{
298    switch (kind) {
299    case kIndirectKindInvalid:      return "invalid";
300    case kIndirectKindLocal:        return "local";
301    case kIndirectKindGlobal:       return "global";
302    case kIndirectKindWeakGlobal:   return "weak global";
303    default:                        return "UNKNOWN";
304    }
305}
306
307void IndirectRefTable::dump(const char* descr) const
308{
309    dvmDumpReferenceTableContents(table, capacity(), descr);
310}
311