IndirectRefTable.cpp revision ddbd6f44af283415162ea7bb1b4e7ef77c8de492
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(const char* what, IndirectRef iref, int idx) const
74{
75    Object* obj = table[idx];
76    IndirectRef checkRef = toIndirectRef(obj, idx);
77    if (checkRef != iref) {
78        if (indirectRefKind(iref) != kIndirectKindWeakGlobal) {
79            LOGE("JNI ERROR (app bug): attempt to %s stale %s reference (req=%p vs cur=%p; table=%p)",
80                    what, indirectRefKindToString(kind), iref, checkRef, this);
81            abortMaybe();
82        }
83        return false;
84    }
85    return true;
86}
87
88IndirectRef IndirectRefTable::add(u4 cookie, Object* obj)
89{
90    IRTSegmentState prevState;
91    prevState.all = cookie;
92    size_t topIndex = segmentState.parts.topIndex;
93
94    assert(obj != NULL);
95    assert(dvmIsValidObject(obj));
96    assert(table != NULL);
97    assert(allocEntries <= maxEntries);
98    assert(segmentState.parts.numHoles >= prevState.parts.numHoles);
99
100    if (topIndex == allocEntries) {
101        /* reached end of allocated space; did we hit buffer max? */
102        if (topIndex == maxEntries) {
103            LOGE("JNI ERROR (app bug): %s reference table overflow (max=%d)",
104                    indirectRefKindToString(kind), maxEntries);
105            dump(indirectRefKindToString(kind));
106            dvmAbort();
107        }
108
109        size_t newSize = allocEntries * 2;
110        if (newSize > maxEntries) {
111            newSize = maxEntries;
112        }
113        assert(newSize > allocEntries);
114
115        Object** newTable = (Object**) realloc(table, newSize * sizeof(Object*));
116        if (newTable == NULL) {
117            LOGE("JNI ERROR (app bug): unable to expand %s reference table (from %d to %d, max=%d)",
118                    indirectRefKindToString(kind),
119                    allocEntries, newSize, maxEntries);
120            dump(indirectRefKindToString(kind));
121            dvmAbort();
122        }
123
124        /* update entries; adjust "nextEntry" in case memory moved */
125        table = newTable;
126        allocEntries = newSize;
127    }
128
129    IndirectRef result;
130
131    /*
132     * We know there's enough room in the table.  Now we just need to find
133     * the right spot.  If there's a hole, find it and fill it; otherwise,
134     * add to the end of the list.
135     */
136    int numHoles = segmentState.parts.numHoles - prevState.parts.numHoles;
137    if (numHoles > 0) {
138        assert(topIndex > 1);
139        /* find the first hole; likely to be near the end of the list */
140        Object** pScan = &table[topIndex - 1];
141        assert(*pScan != NULL);
142        while (*--pScan != NULL) {
143            assert(pScan >= table + prevState.parts.topIndex);
144        }
145        updateSlotAdd(obj, pScan - table);
146        result = toIndirectRef(obj, pScan - table);
147        *pScan = obj;
148        segmentState.parts.numHoles--;
149    } else {
150        /* add to the end */
151        updateSlotAdd(obj, topIndex);
152        result = toIndirectRef(obj, topIndex);
153        table[topIndex++] = obj;
154        segmentState.parts.topIndex = topIndex;
155    }
156
157    assert(result != NULL);
158    return result;
159}
160
161/*
162 * Verify that the indirect table lookup is valid.
163 *
164 * Returns "false" if something looks bad.
165 */
166bool IndirectRefTable::getChecked(IndirectRef iref) const
167{
168    if (iref == NULL) {
169        LOGW("Attempt to look up NULL %s reference",
170                indirectRefKindToString(kind));
171        return false;
172    }
173    if (indirectRefKind(iref) == kIndirectKindInvalid) {
174        LOGE("JNI ERROR (app bug): invalid %s reference (%p)",
175                indirectRefKindToString(kind), iref);
176        abortMaybe();
177        return false;
178    }
179
180    int topIndex = segmentState.parts.topIndex;
181    int idx = extractIndex(iref);
182    if (idx >= topIndex) {
183        /* bad -- stale reference? */
184        LOGE("JNI ERROR (app bug): accessed stale %s reference at index %d (top=%d)",
185                indirectRefKindToString(kind), idx, topIndex);
186        abortMaybe();
187        return false;
188    }
189
190    if (!checkEntry("use", iref, idx)) {
191        return false;
192    }
193
194    return true;
195}
196
197/*
198 * Remove "obj" from "pRef".  We extract the table offset bits from "iref"
199 * and zap the corresponding entry, leaving a hole if it's not at the top.
200 *
201 * If the entry is not between the current top index and the bottom index
202 * specified by the cookie, we don't remove anything.  This is the behavior
203 * required by JNI's DeleteLocalRef function.
204 *
205 * Note this is NOT called when a local frame is popped.  This is only used
206 * for explicit single removals.
207 *
208 * Returns "false" if nothing was removed.
209 */
210bool IndirectRefTable::remove(u4 cookie, IndirectRef iref)
211{
212    IRTSegmentState prevState;
213    prevState.all = cookie;
214    int topIndex = segmentState.parts.topIndex;
215    int bottomIndex = prevState.parts.topIndex;
216
217    assert(table != NULL);
218    assert(allocEntries <= maxEntries);
219    assert(segmentState.parts.numHoles >= prevState.parts.numHoles);
220
221    int idx = extractIndex(iref);
222    if (idx < bottomIndex) {
223        /* wrong segment */
224        LOGV("Attempt to remove index outside index area (%d vs %d-%d)",
225            idx, bottomIndex, topIndex);
226        return false;
227    }
228    if (idx >= topIndex) {
229        /* bad -- stale reference? */
230        LOGD("Attempt to remove invalid index %d (bottom=%d top=%d)",
231            idx, bottomIndex, topIndex);
232        return false;
233    }
234
235    if (idx == topIndex-1) {
236        /*
237         * Top-most entry.  Scan up and consume holes.  No need to NULL
238         * out the entry, since the test vs. topIndex will catch it.
239         */
240        if (!checkEntry("remove", iref, idx)) {
241            return false;
242        }
243        updateSlotRemove(idx);
244
245#ifndef NDEBUG
246        table[idx] = (Object*)0xd3d3d3d3;
247#endif
248
249        int numHoles =
250            segmentState.parts.numHoles - prevState.parts.numHoles;
251        if (numHoles != 0) {
252            while (--topIndex > bottomIndex && numHoles != 0) {
253                LOGV("+++ checking for hole at %d (cookie=0x%08x) val=%p",
254                    topIndex-1, cookie, table[topIndex-1]);
255                if (table[topIndex-1] != NULL)
256                    break;
257                LOGV("+++ ate hole at %d", topIndex-1);
258                numHoles--;
259            }
260            segmentState.parts.numHoles =
261                numHoles + prevState.parts.numHoles;
262            segmentState.parts.topIndex = topIndex;
263        } else {
264            segmentState.parts.topIndex = topIndex-1;
265            LOGV("+++ ate last entry %d", topIndex-1);
266        }
267    } else {
268        /*
269         * Not the top-most entry.  This creates a hole.  We NULL out the
270         * entry to prevent somebody from deleting it twice and screwing up
271         * the hole count.
272         */
273        if (table[idx] == NULL) {
274            LOGV("--- WEIRD: removing null entry %d", idx);
275            return false;
276        }
277        if (!checkEntry("remove", iref, idx)) {
278            return false;
279        }
280        updateSlotRemove(idx);
281
282        table[idx] = NULL;
283        segmentState.parts.numHoles++;
284        LOGV("+++ left hole at %d, holes=%d",
285            idx, segmentState.parts.numHoles);
286    }
287
288    return true;
289}
290
291const char* indirectRefKindToString(IndirectRefKind kind)
292{
293    switch (kind) {
294    case kIndirectKindInvalid:      return "invalid";
295    case kIndirectKindLocal:        return "local";
296    case kIndirectKindGlobal:       return "global";
297    case kIndirectKindWeakGlobal:   return "weak global";
298    default:                        return "UNKNOWN";
299    }
300}
301
302void IndirectRefTable::dump(const char* descr) const
303{
304    dvmDumpReferenceTableContents(table, capacity(), descr);
305}
306