Repository navigation
Conversation
683c9c3 to
e3d581b
Compare
Inspectors described an object with labels only, and each one formatted the values it read its own way: `Thread name: 'main'`, `Fragment.mTag=home`, `mRunning = true`, `key = ...`. Shark Dive read strings, threads, arrays and bitmaps itself instead of asking the inspectors. An inspector now calls ObjectReporter.addSyntheticField(name, value). The name is the class declaring the field and then the field, and the value is a string. A leak trace prints each one as `name = value` before the labels, and LeakTraceObject.syntheticFields carries them into the analysis. Labels stay for sentences. Every built-in inspector that read a value reports it as a synthetic field. Three new inspectors report what Shark Dive used to read on its own: STRING, ARRAY and BITMAP. Shark Dive passes the synthetic fields into the leak traces it renders, so they stay identical to LeakCanary's. LeakCanary stores LeakTraceObject in its database with Java serialization, so its serialVersionUID and the database version change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Shark Dive read a string's characters, a thread's name, an array's length and a bitmap's size itself, into an ObjectContent of its own. The inspectors read the same fields and the leak trace beside it printed them differently. ObjectContent is gone. Every model type carries the inspectors' synthetic fields, and every surface prints them as the leak trace does, without picking any out by name. The details panel and a path list one per line. A row, a leaking object and the card at the pointer join them on one line. The agent answers with a `syntheticFields` object keyed by name. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
e3d581b to
d058eed
Compare
|
|
||
| The other process holds a proxy to the stub, and the stub stays in memory until that process garbage | ||
| collects the proxy. Your app has no say in when that happens. LeakCanary reports binder stubs as expected. | ||
| The objects a stub holds keep their own verdict, and the leak is usually one of them. |
There was a problem hiding this comment.
LeakCanary reports binder stubs as expected. The objects a stub holds keep their own verdict, and the leak is usually one of them.
it's not clear that this word has special meaning. Also the second sentence isn't super clear. "The leak is usually one of them" is pointing to it a bit too much.
If a leaktrace contains a binder stub on its path, LeakCanary marks that binder stub as "Expected", i.e. binder stubs are expected to stay in memory potentially forever. Unfortunately, developers often don't realize that, and forget to clear out the fields of binder stub objects when they stop being used, which is often a cause of leak.
There was a problem hiding this comment.
🤖 Rewrote it along the lines of your version: the stub stays in memory until the other process collects its proxy, potentially forever, and LeakCanary gives it the verdict Expected. A second paragraph says developers often forget to clear a stub's fields once it's no longer needed, and that this is a common cause of leaks. The changelog bullet said the same sentence, so I changed it too. Folded into the binder stub commit: d64715e
| override fun inspect( | ||
| reporter: ObjectReporter | ||
| ) { | ||
| reporter.whenInstanceOf(String::class) { instance -> |
There was a problem hiding this comment.
iirc object inspectors have an abstract method to find leaking objects, which delegates to inspect when not implemented. I think here we want to override it and just return nothing, right? To avoid running this inspector which only does synthetic fields . Unless that's already the default behavior, which it probably is otherwise this would be a problem with other inspectors that don't set a verdict.
There was a problem hiding this comment.
🤖 That's already the default, so I left this inspector as it is. The hook is leakingObjectFilter, which is null unless an inspector overrides it (ObjectInspectors.kt#L162), and createLeakingObjectFilters drops the nulls with mapNotNull (L183-L187). THREAD, STRING and ARRAY have no filter, so leak finding never runs them. They only run on the objects of a path once a leak trace is being built.
The label on a binder stub was a 1,500 character essay, printed in every leak trace that went through one. The verdict reason is now "X is a binder stub", the label says to look for the leak in what the stub holds and links to a new page, Leaks through a binder stub, with a real trace and the fixes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
"Message.when is 7200000 ms before the heap dump" makes the reader count digits to learn the message has waited two hours. The label now follows the milliseconds with the same duration in the largest unit that fits, to a tenth: "7200000 ms (2 hours)". Under a second the label is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
d058eed to
0dcad9e
Compare
LeakCanary and Shark Dive described an object in two ways. LeakCanary's inspectors wrote labels, each spelling its values its own way:
Thread name: 'main',Fragment.mTag=home,mRunning = true. Shark Dive read strings, threads, arrays and bitmaps itself, into anObjectContentof its own. Now an inspector callsObjectReporter.addSyntheticField(name, value)for each value it reads. A leak trace prints it like a field,Thread.name = "main", and Shark Dive prints the same line.Four commits, meant to be read one at a time:
SyntheticField(name, value),ObjectReporter.addSyntheticField()andObjectReporter.syntheticFields,LeakTraceObject.syntheticFields.android.os.Handler@0x12c0a6b8, a constant by name, as inTYPE_TOAST (2005).STRING,ARRAYandBITMAPinspectors, in the default lists.LeakTraceObject'sserialVersionUIDis bumped.ObjectContentis gone, and nothing in Shark Dive picks a field out by name. The details panel and a path print one field per line. A row of a list, a leaking object and the card at the pointer join them on one line. The agent JSON has asyntheticFieldsobject keyed by name.X is a binder stub. The label isLook for the leak in what X holds, see https://square.github.io/leakcanary/binder-stubs/, anddocs/binder-stubs.mdis that page.Message.when is 7200000 ms (2 hours) before the heap dump.A leak trace now ends like this, from
shark/shark-android/src/test/resources/gcroot_unknown_object.hprof:API
LeakTraceObjecthassyntheticFieldsas a new last constructor parameter with a default. Its<init>andcopysignatures change, and Kotlin source compiles unchanged.shark.apiandshark-android.apiis new API.Left as they were
Message.whenis from the heap dump stays a label, next to theMessage.whensynthetic field.CONTEXT_FIELDlines, such asmContext instance of … with mDestroyed = false, stay labels.🤖 Generated with Claude Code