Handle missing objects
diff --git a/shark-graph/src/main/java/shark/HeapGraph.kt b/shark-graph/src/main/java/shark/HeapGraph.kt index 1b64a97..93fafa9 100644 --- a/shark-graph/src/main/java/shark/HeapGraph.kt +++ b/shark-graph/src/main/java/shark/HeapGraph.kt
@@ -46,6 +46,12 @@ fun findObjectById(objectId: Long): HeapObject /** + * Returns the [HeapObject] corresponding to the provided [objectId] or null if it cannot be + * found. + */ + fun findObjectByIdOrNull(objectId: Long): HeapObject? + + /** * Returns the [HeapClass] corresponding to the provided [className], or null if the * class cannot be found. */
diff --git a/shark-graph/src/main/java/shark/HeapValue.kt b/shark-graph/src/main/java/shark/HeapValue.kt index b28d8cd..574ef06 100644 --- a/shark-graph/src/main/java/shark/HeapValue.kt +++ b/shark-graph/src/main/java/shark/HeapValue.kt
@@ -112,12 +112,16 @@ /** * If this [HeapValue] if it represents a non null object reference to an instance of the - * [String] class, returns a [String] instance with content that matches the string in the heap - * dump. Otherwise returns null. + * [String] class that exists in the heap dump, returns a [String] instance with content that + * matches the string in the heap dump. Otherwise returns null. * * This may trigger IO reads. */ fun readAsJavaString(): String? { - return asObject?.asInstance?.readAsJavaString() + if (holder is ReferenceHolder && !holder.isNull) { + val heapObject = graph.findObjectByIdOrNull(holder.value) + return heapObject?.asInstance?.readAsJavaString() + } + return null } }
diff --git a/shark-graph/src/main/java/shark/HprofHeapGraph.kt b/shark-graph/src/main/java/shark/HprofHeapGraph.kt index 0a23e73..f238e6a 100644 --- a/shark-graph/src/main/java/shark/HprofHeapGraph.kt +++ b/shark-graph/src/main/java/shark/HprofHeapGraph.kt
@@ -72,7 +72,14 @@ private val objectCache = LruCache<Long, ObjectRecord>(3000) override fun findObjectById(objectId: Long): HeapObject { - return wrapIndexedObject(index.indexedObject(objectId), objectId) + return findObjectByIdOrNull(objectId) ?: throw IllegalArgumentException( + "Object id $objectId not found in heap dump." + ) + } + + override fun findObjectByIdOrNull(objectId: Long): HeapObject? { + val indexedObject = index.indexedObjectOrNull(objectId) ?: return null + return wrapIndexedObject(indexedObject, objectId) } override fun findClassByName(className: String): HeapClass? {
diff --git a/shark-graph/src/main/java/shark/internal/HprofInMemoryIndex.kt b/shark-graph/src/main/java/shark/internal/HprofInMemoryIndex.kt index 91eb174..5e0d7d0 100644 --- a/shark-graph/src/main/java/shark/internal/HprofInMemoryIndex.kt +++ b/shark-graph/src/main/java/shark/internal/HprofInMemoryIndex.kt
@@ -143,14 +143,8 @@ return gcRoots } - fun indexedObject(objectId: Long): IndexedObject { - return indexedObjectOrNull(objectId) ?: throw IllegalArgumentException( - "Object id $objectId not found in heap dump." - ) - } - @Suppress("ReturnCount") - private fun indexedObjectOrNull(objectId: Long): IndexedObject? { + fun indexedObjectOrNull(objectId: Long): IndexedObject? { var array: ByteSubArray? = classIndex[objectId] if (array != null) { return IndexedClass(
diff --git a/shark/src/main/java/shark/internal/PathFinder.kt b/shark/src/main/java/shark/internal/PathFinder.kt index aeb344b..ff3a5b1 100644 --- a/shark/src/main/java/shark/internal/PathFinder.kt +++ b/shark/src/main/java/shark/internal/PathFinder.kt
@@ -257,29 +257,34 @@ enqueue(NormalRootNode(gcRoot.id, gcRoot)) } is JavaFrame -> { - val (threadInstance, threadRoot) = threadsBySerialNumber.getValue( - gcRoot.threadSerialNumber - ) - val threadName = threadNames[threadInstance] ?: { - val name = threadInstance[Thread::class, "name"]?.value?.readAsJavaString() ?: "" - threadNames[threadInstance] = name - name - }() - val referenceMatcher = threadNameReferenceMatchers[threadName] + val threadPair = threadsBySerialNumber[gcRoot.threadSerialNumber] + if (threadPair == null) { + // Could not find the thread that this java frame is for. + enqueue(NormalRootNode(gcRoot.id, gcRoot)) + } else { - if (referenceMatcher !is IgnoredReferenceMatcher) { - val rootNode = NormalRootNode(threadRoot.id, gcRoot) - // Unfortunately Android heap dumps do not include stack trace data, so - // JavaFrame.frameNumber is always -1 and we cannot know which method is causing the - // reference to be held. - val leakReference = LeakReference(LOCAL, "") + val (threadInstance, threadRoot) = threadPair + val threadName = threadNames[threadInstance] ?: { + val name = threadInstance[Thread::class, "name"]?.value?.readAsJavaString() ?: "" + threadNames[threadInstance] = name + name + }() + val referenceMatcher = threadNameReferenceMatchers[threadName] - val childNode = if (referenceMatcher is LibraryLeakReferenceMatcher) { - LibraryLeakChildNode(gcRoot.id, rootNode, leakReference, referenceMatcher) - } else { - NormalNode(gcRoot.id, rootNode, leakReference) + if (referenceMatcher !is IgnoredReferenceMatcher) { + val rootNode = NormalRootNode(threadRoot.id, gcRoot) + // Unfortunately Android heap dumps do not include stack trace data, so + // JavaFrame.frameNumber is always -1 and we cannot know which method is causing the + // reference to be held. + val leakReference = LeakReference(LOCAL, "") + + val childNode = if (referenceMatcher is LibraryLeakReferenceMatcher) { + LibraryLeakChildNode(gcRoot.id, rootNode, leakReference, referenceMatcher) + } else { + NormalNode(gcRoot.id, rootNode, leakReference) + } + enqueue(childNode) } - enqueue(childNode) } } is JniGlobal -> {