IndirectRefTable.cpp revision 92c1f6f1b4249e4e379452ee7b49f027052bf4ce
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(desiredKind != 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    slot_data_ = (IndirectRefSlot*) calloc(initialCount, sizeof(IndirectRefSlot));
46    if (slot_data_ == NULL) {
47        return false;
48    }
49
50    segmentState.all = IRT_FIRST_SEGMENT;
51    alloc_entries_ = initialCount;
52    max_entries_ = maxCount;
53    kind_ = desiredKind;
54
55    return true;
56}
57
58/*
59 * Clears out the contents of a IndirectRefTable, freeing allocated storage.
60 */
61void IndirectRefTable::destroy()
62{
63    free(table_);
64    free(slot_data_);
65    table_ = NULL;
66    slot_data_ = NULL;
67    alloc_entries_ = max_entries_ = -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        LOGE("JNI ERROR (app bug): attempt to %s stale %s reference %p (should be %p)",
79                what, indirectRefKindToString(kind_), iref, checkRef);
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(dvmIsHeapAddress(obj));
94    assert(table_ != NULL);
95    assert(alloc_entries_ <= max_entries_);
96    assert(segmentState.parts.numHoles >= prevState.parts.numHoles);
97
98    if (topIndex == alloc_entries_) {
99        /* reached end of allocated space; did we hit buffer max? */
100        if (topIndex == max_entries_) {
101            LOGE("JNI ERROR (app bug): %s reference table overflow (max=%d)",
102                    indirectRefKindToString(kind_), max_entries_);
103            dump(indirectRefKindToString(kind_));
104            dvmAbort();
105        }
106
107        size_t newSize = alloc_entries_ * 2;
108        if (newSize > max_entries_) {
109            newSize = max_entries_;
110        }
111        assert(newSize > alloc_entries_);
112
113        table_ = (Object**) realloc(table_, newSize * sizeof(Object*));
114        slot_data_ = (IndirectRefSlot*) realloc(slot_data_, newSize * sizeof(IndirectRefSlot));
115        if (table_ == NULL || slot_data_ == NULL) {
116            LOGE("JNI ERROR (app bug): unable to expand %s reference table (from %d to %d, max=%d)",
117                    indirectRefKindToString(kind_),
118                    alloc_entries_, newSize, max_entries_);
119            dump(indirectRefKindToString(kind_));
120            dvmAbort();
121        }
122
123        // Clear the newly-allocated slot_data_ elements.
124        memset(slot_data_ + alloc_entries_, 0, (newSize - alloc_entries_) * sizeof(IndirectRefSlot));
125
126        alloc_entries_ = newSize;
127    }
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    IndirectRef result;
135    int numHoles = segmentState.parts.numHoles - prevState.parts.numHoles;
136    if (numHoles > 0) {
137        assert(topIndex > 1);
138        /* find the first hole; likely to be near the end of the list */
139        Object** pScan = &table_[topIndex - 1];
140        assert(*pScan != NULL);
141        while (*--pScan != NULL) {
142            assert(pScan >= table_ + prevState.parts.topIndex);
143        }
144        updateSlotAdd(obj, pScan - table_);
145        result = toIndirectRef(obj, pScan - table_);
146        *pScan = obj;
147        segmentState.parts.numHoles--;
148    } else {
149        /* add to the end */
150        updateSlotAdd(obj, topIndex);
151        result = toIndirectRef(obj, topIndex);
152        table_[topIndex++] = obj;
153        segmentState.parts.topIndex = topIndex;
154    }
155
156    assert(result != NULL);
157    return result;
158}
159
160/*
161 * Verify that the indirect table lookup is valid.
162 *
163 * Returns "false" if something looks bad.
164 */
165bool IndirectRefTable::getChecked(IndirectRef iref) const
166{
167    if (iref == NULL) {
168        LOGW("Attempt to look up NULL %s reference", 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 %p (index %d in a table of size %d)",
183                indirectRefKindToString(kind_), iref, idx, topIndex);
184        abortMaybe();
185        return false;
186    }
187
188    if (table_[idx] == NULL) {
189        LOGI("JNI ERROR (app bug): accessed deleted %s reference %p",
190                indirectRefKindToString(kind_), iref);
191        abortMaybe();
192        return false;
193    }
194
195    if (!checkEntry("use", iref, idx)) {
196        return false;
197    }
198
199    return true;
200}
201
202static int linearScan(IndirectRef iref, int bottomIndex, int topIndex, Object** table) {
203    for (int i = bottomIndex; i < topIndex; ++i) {
204        if (table[i] == reinterpret_cast<Object*>(iref)) {
205            return i;
206        }
207    }
208    return -1;
209}
210
211bool IndirectRefTable::contains(IndirectRef iref) const {
212    return linearScan(iref, 0, segmentState.parts.topIndex, table_) != -1;
213}
214
215/*
216 * Remove "obj" from "pRef".  We extract the table offset bits from "iref"
217 * and zap the corresponding entry, leaving a hole if it's not at the top.
218 *
219 * If the entry is not between the current top index and the bottom index
220 * specified by the cookie, we don't remove anything.  This is the behavior
221 * required by JNI's DeleteLocalRef function.
222 *
223 * Note this is NOT called when a local frame is popped.  This is only used
224 * for explicit single removals.
225 *
226 * Returns "false" if nothing was removed.
227 */
228bool IndirectRefTable::remove(u4 cookie, IndirectRef iref)
229{
230    IRTSegmentState prevState;
231    prevState.all = cookie;
232    int topIndex = segmentState.parts.topIndex;
233    int bottomIndex = prevState.parts.topIndex;
234
235    assert(table_ != NULL);
236    assert(alloc_entries_ <= max_entries_);
237    assert(segmentState.parts.numHoles >= prevState.parts.numHoles);
238
239    int idx = extractIndex(iref);
240    bool workAroundAppJniBugs = false;
241
242    if (indirectRefKind(iref) == kIndirectKindInvalid && gDvmJni.workAroundAppJniBugs) {
243        idx = linearScan(iref, bottomIndex, topIndex, table_);
244        workAroundAppJniBugs = true;
245        if (idx == -1) {
246            LOGW("trying to work around app JNI bugs, but didn't find %p in table!", iref);
247            return false;
248        }
249    }
250
251    if (idx < bottomIndex) {
252        /* wrong segment */
253        ALOGV("Attempt to remove index outside index area (%d vs %d-%d)",
254            idx, bottomIndex, topIndex);
255        return false;
256    }
257    if (idx >= topIndex) {
258        /* bad -- stale reference? */
259        LOGD("Attempt to remove invalid index %d (bottom=%d top=%d)",
260            idx, bottomIndex, topIndex);
261        return false;
262    }
263
264    if (idx == topIndex-1) {
265        // Top-most entry.  Scan up and consume holes.
266
267        if (workAroundAppJniBugs == false && !checkEntry("remove", iref, idx)) {
268            return false;
269        }
270
271        table_[idx] = NULL;
272        int numHoles = segmentState.parts.numHoles - prevState.parts.numHoles;
273        if (numHoles != 0) {
274            while (--topIndex > bottomIndex && numHoles != 0) {
275                ALOGV("+++ checking for hole at %d (cookie=0x%08x) val=%p",
276                    topIndex-1, cookie, table_[topIndex-1]);
277                if (table_[topIndex-1] != NULL) {
278                    break;
279                }
280                ALOGV("+++ ate hole at %d", topIndex-1);
281                numHoles--;
282            }
283            segmentState.parts.numHoles = numHoles + prevState.parts.numHoles;
284            segmentState.parts.topIndex = topIndex;
285        } else {
286            segmentState.parts.topIndex = topIndex-1;
287            ALOGV("+++ ate last entry %d", topIndex-1);
288        }
289    } else {
290        /*
291         * Not the top-most entry.  This creates a hole.  We NULL out the
292         * entry to prevent somebody from deleting it twice and screwing up
293         * the hole count.
294         */
295        if (table_[idx] == NULL) {
296            ALOGV("--- WEIRD: removing null entry %d", idx);
297            return false;
298        }
299        if (workAroundAppJniBugs == false && !checkEntry("remove", iref, idx)) {
300            return false;
301        }
302
303        table_[idx] = NULL;
304        segmentState.parts.numHoles++;
305        ALOGV("+++ left hole at %d, holes=%d", idx, segmentState.parts.numHoles);
306    }
307
308    return true;
309}
310
311const char* indirectRefKindToString(IndirectRefKind kind)
312{
313    switch (kind) {
314    case kIndirectKindInvalid:      return "invalid";
315    case kIndirectKindLocal:        return "local";
316    case kIndirectKindGlobal:       return "global";
317    case kIndirectKindWeakGlobal:   return "weak global";
318    default:                        return "UNKNOWN";
319    }
320}
321
322void IndirectRefTable::dump(const char* descr) const
323{
324    dvmDumpReferenceTableContents(table_, capacity(), descr);
325}
326