/*
* Copyright (C) 2008 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.
*/
/*
* Reference table management.
*/
#include "Dalvik.h"
/*
* Initialize a ReferenceTable structure.
*/
bool dvmInitReferenceTable(ReferenceTable* pRef, int initialCount,
int maxCount)
{
assert(initialCount > 0);
assert(initialCount <= maxCount);
pRef->table = (Object**) malloc(initialCount * sizeof(Object*));
if (pRef->table == NULL)
return false;
#ifndef NDEBUG
memset(pRef->table, 0xdd, initialCount * sizeof(Object*));
#endif
pRef->nextEntry = pRef->table;
pRef->allocEntries = initialCount;
pRef->maxEntries = maxCount;
return true;
}
/*
* Clears out the contents of a ReferenceTable, freeing allocated storage.
*/
void dvmClearReferenceTable(ReferenceTable* pRef)
{
free(pRef->table);
pRef->table = pRef->nextEntry = NULL;
pRef->allocEntries = pRef->maxEntries = -1;
}
/*
* Add "obj" to "pRef".
*/
bool dvmAddToReferenceTable(ReferenceTable* pRef, Object* obj)
{
assert(obj != NULL);
assert(dvmIsHeapAddress(obj));
assert(pRef->table != NULL);
assert(pRef->allocEntries <= pRef->maxEntries);
if (pRef->nextEntry == pRef->table + pRef->allocEntries) {
/* reached end of allocated space; did we hit buffer max? */
if (pRef->nextEntry == pRef->table + pRef->maxEntries) {
LOGW("ReferenceTable overflow (max=%d)", pRef->maxEntries);
return false;
}
Object** newTable;
int newSize;
newSize = pRef->allocEntries * 2;
if (newSize > pRef->maxEntries)
newSize = pRef->maxEntries;
assert(newSize > pRef->allocEntries);
newTable = (Object**) realloc(pRef->table, newSize * sizeof(Object*));
if (newTable == NULL) {
LOGE("Unable to expand ref table (from %d to %d %d-byte entries)",
pRef->allocEntries, newSize, sizeof(Object*));
return false;
}
LOGVV("Growing %p from %d to %d", pRef, pRef->allocEntries, newSize);
/* update entries; adjust "nextEntry" in case memory moved */
pRef->nextEntry = newTable + (pRef->nextEntry - pRef->table);
pRef->table = newTable;
pRef->allocEntries = newSize;
}
*pRef->nextEntry++ = obj;
return true;
}
/*
* Returns NULL if not found.
*/
Object** dvmFindInReferenceTable(const ReferenceTable* pRef, Object** bottom,
Object* obj)
{
Object** ptr;
ptr = pRef->nextEntry;
while (--ptr >= bottom) {
if (*ptr == obj)
return ptr;
}
return NULL;
}
/*
* Remove "obj" from "pRef". We start at the end of the list (where the
* most-recently-added element is), and stop searching for a match after
* examining the element at "bottom".
*
* Most of the time "obj" is at or near the end of the list. If not, we
* compact it down.
*/
bool dvmRemoveFromReferenceTable(ReferenceTable* pRef, Object** bottom,
Object* obj)
{
Object** ptr;
assert(pRef->table != NULL);
/*
* Scan from the most-recently-added entry up to the bottom entry for
* this frame.
*/
ptr = dvmFindInReferenceTable(pRef, bottom, obj);
if (ptr == NULL)
return false;
/*
* Delete the entry.
*/
pRef->nextEntry--;
int moveCount = pRef->nextEntry - ptr;
if (moveCount != 0) {
/* remove from middle, slide the rest down */
memmove(ptr, ptr+1, moveCount * sizeof(Object*));
//LOGV("LREF delete %p, shift %d down", obj, moveCount);
} else {
/* last entry, falls off the end */
//LOGV("LREF delete %p from end", obj);
}
return true;
}
/*
* If "obj" is an array, return the number of elements in the array.
* Otherwise, return zero.
*/
static size_t getElementCount(const Object* obj)
{
const ArrayObject* arrayObj = (ArrayObject*) obj;
if (arrayObj == NULL || arrayObj == kClearedJniWeakGlobal ||
arrayObj->clazz == NULL || !dvmIsArray(arrayObj)) {
return 0;
}
return arrayObj->length;
}
/*
* This is a qsort() callback. We sort Object* by class, allocation size,
* and then by the Object* itself.
*/
static int compareObject(const void* vobj1, const void* vobj2)
{
const Object* obj1 = *((Object* const*) vobj1);
const Object* obj2 = *((Object* const*) vobj2);
// Ensure null references and cleared jweaks appear at the end.
if (obj1 == NULL) {
if (obj2 == NULL) {
return 0;
} else {
return 1;
}
} else if (obj2 == NULL) {
return -1;
}
if (obj1 == kClearedJniWeakGlobal) {
if (obj2 == kClearedJniWeakGlobal) {
return 0;
} else {
return 1;
}
} else if (obj2 == kClearedJniWeakGlobal) {
return -1;
}
if (obj1->clazz != obj2->clazz) {
return (u1*)obj1->clazz - (u1*)obj2->clazz;
} else {
size_t count1 = getElementCount(obj1);
size_t count2 = getElementCount(obj2);
if (count1 != count2) {
return count1 - count2;
} else {
return (u1*)obj1 - (u1*)obj2;
}
}
}
/*
* Log an object with some additional info.
*
* Pass in the number of elements in the array (or 0 if this is not an
* array object), and the number of additional objects that are identical
* or equivalent to the original.
*/
static void logSummaryLine(const Object* obj, size_t elems, int identical, int equiv)
{
if (obj == NULL) {
LOGW(" NULL reference (count=%d)", equiv);
return;
}
if (obj == kClearedJniWeakGlobal) {
LOGW(" cleared jweak (count=%d)", equiv);
return;
}
std::string className(dvmHumanReadableType(obj));
if (obj->clazz == gDvm.classJavaLangClass) {
// We're summarizing multiple instances, so using the exemplar
// Class' type parameter here would be misleading.
className = "java.lang.Class";
}
if (elems != 0) {
StringAppendF(&className, " (%zd elements)", elems);
}
size_t total = identical + equiv + 1;
std::string msg(StringPrintf("%5d of %s", total, className.c_str()));
if (identical + equiv != 0) {
StringAppendF(&msg, " (%d unique instances)", equiv + 1);
}
LOGW(" %s", msg.c_str());
}
/*
* Dump a summary of an array of references to the log file.
*
* This is used to dump the contents of ReferenceTable and IndirectRefTable
* structs.
*/
void dvmDumpReferenceTableContents(Object* const* refs, size_t count,
const char* descr)
{
LOGW("%s reference table (%p) dump:", descr, refs);
if (count == 0) {
LOGW(" (empty)");
return;
}
// Dump the most recent N entries.
const size_t kLast = 10;
int first = count - kLast;
if (first < 0) {
first = 0;
}
LOGW(" Last %d entries (of %d):", (count - first), count);
for (int idx = count - 1; idx >= first; --idx) {
const Object* ref = refs[idx];
if (ref == NULL) {
continue;
}
if (ref == kClearedJniWeakGlobal) {
LOGW(" %5d: cleared jweak", idx);
continue;
}
if (ref->clazz == NULL) {
// should only be possible right after a plain dvmMalloc().
size_t size = dvmObjectSizeInHeap(ref);
LOGW(" %5d: %p (raw) (%zd bytes)", idx, ref, size);
continue;
}
std::string className(dvmHumanReadableType(ref));
std::string extras;
size_t elems = getElementCount(ref);
if (elems != 0) {
StringAppendF(&extras, " (%zd elements)", elems);
} else if (ref->clazz == gDvm.classJavaLangString) {
const StringObject* str =
reinterpret_cast<const StringObject*>(ref);
extras += " \"";
size_t count = 0;
char* s = dvmCreateCstrFromString(str);
char* p = s;
for (; *p && count < 16; ++p, ++count) {
extras += *p;
}
if (*p == 0) {
extras += "\"";
} else {
StringAppendF(&extras, "... (%d chars)", str->length());
}
free(s);
}
LOGW(" %5d: %p %s%s", idx, ref, className.c_str(), extras.c_str());
}
// Make a copy of the table, and sort it.
Object** tableCopy = (Object**)malloc(sizeof(Object*) * count);
if (tableCopy == NULL) {
LOGE("Unable to copy table with %d elements", count);
return;
}
memcpy(tableCopy, refs, sizeof(Object*) * count);
qsort(tableCopy, count, sizeof(Object*), compareObject);
refs = tableCopy; // use sorted list
// Remove any uninteresting stuff from the list. The sort moved them all to the end.
while (count > 0 && refs[count-1] == NULL) {
--count;
}
while (count > 0 && refs[count-1] == kClearedJniWeakGlobal) {
--count;
}
if (count == 0) {
return;
}
// Dump a summary of the whole table.
LOGW(" Summary:");
size_t equiv, identical;
equiv = identical = 0;
size_t idx;
size_t elems;
for (idx = 1; idx < count; idx++) {
elems = getElementCount(refs[idx-1]);
if (refs[idx] == refs[idx-1]) {
// same reference, added more than once.
identical++;
} else if (refs[idx]->clazz == refs[idx-1]->clazz &&
getElementCount(refs[idx]) == elems)
{
// same class / element count, different object.
equiv++;
} else {
// different class.
logSummaryLine(refs[idx-1], elems, identical, equiv);
equiv = identical = 0;
}
}
// Handle the last entry (everything above outputs refs[i-1]).
elems = getElementCount(refs[idx-1]);
logSummaryLine(refs[count-1], elems, identical, equiv);
free(tableCopy);
}
/*
* Dump the contents of a ReferenceTable to the log.
*/
void dvmDumpReferenceTable(const ReferenceTable* pRef, const char* descr)
{
dvmDumpReferenceTableContents(pRef->table, dvmReferenceTableEntries(pRef),
descr);
}