| /* |
| * Copyright (C) 2009 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * Indirect reference table management. |
| */ |
| #include "Dalvik.h" |
| |
| static void abortMaybe() { |
| // If CheckJNI is on, it'll give a more detailed error before aborting. |
| // Otherwise, we want to abort rather than hand back a bad reference. |
| if (!gDvmJni.useCheckJni) { |
| dvmAbort(); |
| } |
| } |
| |
| bool IndirectRefTable::init(size_t initialCount, |
| size_t maxCount, IndirectRefKind desiredKind) |
| { |
| assert(initialCount > 0); |
| assert(initialCount <= maxCount); |
| assert(desiredKind != kIndirectKindInvalid); |
| |
| table_ = (IndirectRefSlot*) malloc(initialCount * sizeof(IndirectRefSlot)); |
| if (table_ == NULL) { |
| return false; |
| } |
| memset(table_, 0xd1, initialCount * sizeof(IndirectRefSlot)); |
| |
| segmentState.all = IRT_FIRST_SEGMENT; |
| alloc_entries_ = initialCount; |
| max_entries_ = maxCount; |
| kind_ = desiredKind; |
| |
| return true; |
| } |
| |
| /* |
| * Clears out the contents of a IndirectRefTable, freeing allocated storage. |
| */ |
| void IndirectRefTable::destroy() |
| { |
| free(table_); |
| table_ = NULL; |
| alloc_entries_ = max_entries_ = -1; |
| } |
| |
| IndirectRef IndirectRefTable::add(u4 cookie, Object* obj) |
| { |
| IRTSegmentState prevState; |
| prevState.all = cookie; |
| size_t topIndex = segmentState.parts.topIndex; |
| |
| assert(obj != NULL); |
| assert(dvmIsHeapAddress(obj)); |
| assert(table_ != NULL); |
| assert(alloc_entries_ <= max_entries_); |
| assert(segmentState.parts.numHoles >= prevState.parts.numHoles); |
| |
| /* |
| * We know there's enough room in the table. Now we just need to find |
| * the right spot. If there's a hole, find it and fill it; otherwise, |
| * add to the end of the list. |
| */ |
| IndirectRef result; |
| IndirectRefSlot* slot; |
| int numHoles = segmentState.parts.numHoles - prevState.parts.numHoles; |
| if (numHoles > 0) { |
| assert(topIndex > 1); |
| /* find the first hole; likely to be near the end of the list, |
| * we know the item at the topIndex is not a hole */ |
| slot = &table_[topIndex - 1]; |
| assert(slot->obj != NULL); |
| while ((--slot)->obj != NULL) { |
| assert(slot >= table_ + prevState.parts.topIndex); |
| } |
| segmentState.parts.numHoles--; |
| } else { |
| /* add to the end, grow if needed */ |
| if (topIndex == alloc_entries_) { |
| /* reached end of allocated space; did we hit buffer max? */ |
| if (topIndex == max_entries_) { |
| ALOGE("JNI ERROR (app bug): %s reference table overflow (max=%d)", |
| indirectRefKindToString(kind_), max_entries_); |
| return NULL; |
| } |
| |
| size_t newSize = alloc_entries_ * 2; |
| if (newSize > max_entries_) { |
| newSize = max_entries_; |
| } |
| assert(newSize > alloc_entries_); |
| |
| IndirectRefSlot* newTable = |
| (IndirectRefSlot*) realloc(table_, newSize * sizeof(IndirectRefSlot)); |
| if (table_ == NULL) { |
| ALOGE("JNI ERROR (app bug): unable to expand %s reference table " |
| "(from %d to %d, max=%d)", |
| indirectRefKindToString(kind_), |
| alloc_entries_, newSize, max_entries_); |
| return NULL; |
| } |
| |
| memset(newTable + alloc_entries_, 0xd1, |
| (newSize - alloc_entries_) * sizeof(IndirectRefSlot)); |
| |
| alloc_entries_ = newSize; |
| table_ = newTable; |
| } |
| slot = &table_[topIndex++]; |
| segmentState.parts.topIndex = topIndex; |
| } |
| |
| slot->obj = obj; |
| slot->serial = nextSerial(slot->serial); |
| result = toIndirectRef(slot - table_, slot->serial, kind_); |
| |
| assert(result != NULL); |
| return result; |
| } |
| |
| /* |
| * Get the referent of an indirect ref from the table. |
| * |
| * Returns kInvalidIndirectRefObject if iref is invalid. |
| */ |
| Object* IndirectRefTable::get(IndirectRef iref) const { |
| IndirectRefKind kind = indirectRefKind(iref); |
| if (kind != kind_) { |
| if (iref == NULL) { |
| ALOGW("Attempt to look up NULL %s reference", indirectRefKindToString(kind_)); |
| return kInvalidIndirectRefObject; |
| } |
| if (kind == kIndirectKindInvalid) { |
| ALOGE("JNI ERROR (app bug): invalid %s reference %p", |
| indirectRefKindToString(kind_), iref); |
| abortMaybe(); |
| return kInvalidIndirectRefObject; |
| } |
| // References of the requested kind cannot appear within this table. |
| return kInvalidIndirectRefObject; |
| } |
| |
| u4 topIndex = segmentState.parts.topIndex; |
| u4 index = extractIndex(iref); |
| if (index >= topIndex) { |
| /* bad -- stale reference? */ |
| ALOGE("JNI ERROR (app bug): accessed stale %s reference %p (index %d in a table of size %d)", |
| indirectRefKindToString(kind_), iref, index, topIndex); |
| abortMaybe(); |
| return kInvalidIndirectRefObject; |
| } |
| |
| Object* obj = table_[index].obj; |
| if (obj == NULL) { |
| ALOGI("JNI ERROR (app bug): accessed deleted %s reference %p", |
| indirectRefKindToString(kind_), iref); |
| abortMaybe(); |
| return kInvalidIndirectRefObject; |
| } |
| |
| u4 serial = extractSerial(iref); |
| if (serial != table_[index].serial) { |
| ALOGE("JNI ERROR (app bug): attempt to use stale %s reference %p", |
| indirectRefKindToString(kind_), iref); |
| abortMaybe(); |
| return kInvalidIndirectRefObject; |
| } |
| |
| return obj; |
| } |
| |
| static int findObject(const Object* obj, int bottomIndex, int topIndex, |
| const IndirectRefSlot* table) { |
| for (int i = bottomIndex; i < topIndex; ++i) { |
| if (table[i].obj == obj) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| bool IndirectRefTable::contains(const Object* obj) const { |
| return findObject(obj, 0, segmentState.parts.topIndex, table_) >= 0; |
| } |
| |
| /* |
| * Remove "obj" from "pRef". We extract the table offset bits from "iref" |
| * and zap the corresponding entry, leaving a hole if it's not at the top. |
| * |
| * If the entry is not between the current top index and the bottom index |
| * specified by the cookie, we don't remove anything. This is the behavior |
| * required by JNI's DeleteLocalRef function. |
| * |
| * Note this is NOT called when a local frame is popped. This is only used |
| * for explicit single removals. |
| * |
| * Returns "false" if nothing was removed. |
| */ |
| bool IndirectRefTable::remove(u4 cookie, IndirectRef iref) |
| { |
| IRTSegmentState prevState; |
| prevState.all = cookie; |
| u4 topIndex = segmentState.parts.topIndex; |
| u4 bottomIndex = prevState.parts.topIndex; |
| |
| assert(table_ != NULL); |
| assert(alloc_entries_ <= max_entries_); |
| assert(segmentState.parts.numHoles >= prevState.parts.numHoles); |
| |
| IndirectRefKind kind = indirectRefKind(iref); |
| u4 index; |
| if (kind == kind_) { |
| index = extractIndex(iref); |
| if (index < bottomIndex) { |
| /* wrong segment */ |
| ALOGV("Attempt to remove index outside index area (%ud vs %ud-%ud)", |
| index, bottomIndex, topIndex); |
| return false; |
| } |
| if (index >= topIndex) { |
| /* bad -- stale reference? */ |
| ALOGD("Attempt to remove invalid index %ud (bottom=%ud top=%ud)", |
| index, bottomIndex, topIndex); |
| return false; |
| } |
| if (table_[index].obj == NULL) { |
| ALOGD("Attempt to remove cleared %s reference %p", |
| indirectRefKindToString(kind_), iref); |
| return false; |
| } |
| u4 serial = extractSerial(iref); |
| if (table_[index].serial != serial) { |
| ALOGD("Attempt to remove stale %s reference %p", |
| indirectRefKindToString(kind_), iref); |
| return false; |
| } |
| } else if (kind == kIndirectKindInvalid && gDvmJni.workAroundAppJniBugs) { |
| // reference looks like a pointer, scan the table to find the index |
| int i = findObject(reinterpret_cast<Object*>(iref), bottomIndex, topIndex, table_); |
| if (i < 0) { |
| ALOGW("trying to work around app JNI bugs, but didn't find %p in table!", iref); |
| return false; |
| } |
| index = i; |
| } else { |
| // References of the requested kind cannot appear within this table. |
| return false; |
| } |
| |
| if (index == topIndex - 1) { |
| // Top-most entry. Scan up and consume holes. |
| int numHoles = segmentState.parts.numHoles - prevState.parts.numHoles; |
| if (numHoles != 0) { |
| while (--topIndex > bottomIndex && numHoles != 0) { |
| ALOGV("+++ checking for hole at %d (cookie=0x%08x) val=%p", |
| topIndex-1, cookie, table_[topIndex-1].obj); |
| if (table_[topIndex-1].obj != NULL) { |
| break; |
| } |
| ALOGV("+++ ate hole at %d", topIndex-1); |
| numHoles--; |
| } |
| segmentState.parts.numHoles = numHoles + prevState.parts.numHoles; |
| segmentState.parts.topIndex = topIndex; |
| } else { |
| segmentState.parts.topIndex = topIndex-1; |
| ALOGV("+++ ate last entry %d", topIndex-1); |
| } |
| } else { |
| /* |
| * Not the top-most entry. This creates a hole. We NULL out the |
| * entry to prevent somebody from deleting it twice and screwing up |
| * the hole count. |
| */ |
| table_[index].obj = NULL; |
| segmentState.parts.numHoles++; |
| ALOGV("+++ left hole at %d, holes=%d", index, segmentState.parts.numHoles); |
| } |
| |
| return true; |
| } |
| |
| const char* indirectRefKindToString(IndirectRefKind kind) |
| { |
| switch (kind) { |
| case kIndirectKindInvalid: return "invalid"; |
| case kIndirectKindLocal: return "local"; |
| case kIndirectKindGlobal: return "global"; |
| case kIndirectKindWeakGlobal: return "weak global"; |
| default: return "UNKNOWN"; |
| } |
| } |
| |
| void IndirectRefTable::dump(const char* descr) const |
| { |
| size_t count = capacity(); |
| Object** copy = new Object*[count]; |
| for (size_t i = 0; i < count; i++) { |
| copy[i] = table_[i].obj; |
| } |
| dvmDumpReferenceTableContents(copy, count, descr); |
| delete[] copy; |
| } |