353 lines
16 KiB
Java
353 lines
16 KiB
Java
package com.squareup.leakcanary;
|
|
|
|
import android.os.Build;
|
|
import com.squareup.haha.perflib.ArrayInstance;
|
|
import com.squareup.haha.perflib.ClassInstance;
|
|
import com.squareup.haha.perflib.ClassObj;
|
|
import com.squareup.haha.perflib.Field;
|
|
import com.squareup.haha.perflib.HprofParser;
|
|
import com.squareup.haha.perflib.Instance;
|
|
import com.squareup.haha.perflib.RootObj;
|
|
import com.squareup.haha.perflib.RootType;
|
|
import com.squareup.haha.perflib.Snapshot;
|
|
import com.squareup.haha.perflib.Type;
|
|
import com.squareup.haha.perflib.io.MemoryMappedFileBuffer;
|
|
import com.squareup.leakcanary.AnalyzerProgressListener;
|
|
import com.squareup.leakcanary.LeakTraceElement;
|
|
import com.squareup.leakcanary.Reachability;
|
|
import com.squareup.leakcanary.ShortestPathFinder;
|
|
import com.unity3d.ads.metadata.MediationMetaData;
|
|
import gnu.trove.THashMap;
|
|
import gnu.trove.TObjectProcedure;
|
|
import java.io.File;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
/* loaded from: classes.dex */
|
|
public final class HeapAnalyzer {
|
|
private static final String ANONYMOUS_CLASS_NAME_PATTERN = "^.+\\$\\d+$";
|
|
private final ExcludedRefs excludedRefs;
|
|
private final AnalyzerProgressListener listener;
|
|
private final List<Reachability.Inspector> reachabilityInspectors;
|
|
|
|
@Deprecated
|
|
public HeapAnalyzer(ExcludedRefs excludedRefs) {
|
|
this(excludedRefs, AnalyzerProgressListener.NONE, Collections.emptyList());
|
|
}
|
|
|
|
private LeakTraceElement buildLeakElement(LeakNode leakNode) {
|
|
LeakTraceElement.Holder holder;
|
|
String str;
|
|
LeakTraceElement.Holder holder2;
|
|
LeakNode leakNode2 = leakNode.parent;
|
|
String str2 = null;
|
|
if (leakNode2 == null) {
|
|
return null;
|
|
}
|
|
Instance instance = leakNode2.instance;
|
|
if (instance instanceof RootObj) {
|
|
return null;
|
|
}
|
|
List<LeakReference> describeFields = describeFields(instance);
|
|
String className = getClassName(instance);
|
|
ArrayList arrayList = new ArrayList();
|
|
arrayList.add(className);
|
|
String name = Object.class.getName();
|
|
if (instance instanceof ClassInstance) {
|
|
ClassObj classObj = instance.getClassObj();
|
|
while (true) {
|
|
classObj = classObj.getSuperClassObj();
|
|
if (classObj.getClassName().equals(name)) {
|
|
break;
|
|
}
|
|
arrayList.add(classObj.getClassName());
|
|
}
|
|
}
|
|
if (instance instanceof ClassObj) {
|
|
holder = LeakTraceElement.Holder.CLASS;
|
|
} else if (instance instanceof ArrayInstance) {
|
|
holder = LeakTraceElement.Holder.ARRAY;
|
|
} else {
|
|
ClassObj classObj2 = instance.getClassObj();
|
|
if (HahaHelper.extendsThread(classObj2)) {
|
|
LeakTraceElement.Holder holder3 = LeakTraceElement.Holder.THREAD;
|
|
str = "(named '" + HahaHelper.threadName(instance) + "')";
|
|
holder2 = holder3;
|
|
return new LeakTraceElement(leakNode.leakReference, holder2, arrayList, str, leakNode.exclusion, describeFields);
|
|
}
|
|
if (className.matches(ANONYMOUS_CLASS_NAME_PATTERN)) {
|
|
String className2 = classObj2.getSuperClassObj().getClassName();
|
|
if (name.equals(className2)) {
|
|
holder = LeakTraceElement.Holder.OBJECT;
|
|
try {
|
|
Class<?>[] interfaces = Class.forName(classObj2.getClassName()).getInterfaces();
|
|
if (interfaces.length > 0) {
|
|
str2 = "(anonymous implementation of " + interfaces[0].getName() + ")";
|
|
} else {
|
|
str2 = "(anonymous subclass of java.lang.Object)";
|
|
}
|
|
} catch (ClassNotFoundException unused) {
|
|
}
|
|
} else {
|
|
str2 = "(anonymous subclass of " + className2 + ")";
|
|
holder = LeakTraceElement.Holder.OBJECT;
|
|
}
|
|
} else {
|
|
holder = LeakTraceElement.Holder.OBJECT;
|
|
}
|
|
}
|
|
holder2 = holder;
|
|
str = str2;
|
|
return new LeakTraceElement(leakNode.leakReference, holder2, arrayList, str, leakNode.exclusion, describeFields);
|
|
}
|
|
|
|
private LeakTrace buildLeakTrace(LeakNode leakNode) {
|
|
ArrayList arrayList = new ArrayList();
|
|
for (LeakNode leakNode2 = new LeakNode(null, null, leakNode, null); leakNode2 != null; leakNode2 = leakNode2.parent) {
|
|
LeakTraceElement buildLeakElement = buildLeakElement(leakNode2);
|
|
if (buildLeakElement != null) {
|
|
arrayList.add(0, buildLeakElement);
|
|
}
|
|
}
|
|
return new LeakTrace(arrayList, computeExpectedReachability(arrayList));
|
|
}
|
|
|
|
private List<Reachability> computeExpectedReachability(List<LeakTraceElement> list) {
|
|
Reachability expectedReachability;
|
|
int i = 1;
|
|
int size = list.size() - 1;
|
|
int i2 = 0;
|
|
int i3 = 0;
|
|
loop0: while (true) {
|
|
if (i >= size) {
|
|
break;
|
|
}
|
|
LeakTraceElement leakTraceElement = list.get(i);
|
|
Iterator<Reachability.Inspector> it = this.reachabilityInspectors.iterator();
|
|
do {
|
|
if (!it.hasNext()) {
|
|
break;
|
|
}
|
|
expectedReachability = it.next().expectedReachability(leakTraceElement);
|
|
if (expectedReachability == Reachability.REACHABLE) {
|
|
i3 = i;
|
|
}
|
|
i++;
|
|
} while (expectedReachability != Reachability.UNREACHABLE);
|
|
size = i;
|
|
break loop0;
|
|
}
|
|
ArrayList arrayList = new ArrayList();
|
|
while (i2 < list.size()) {
|
|
arrayList.add(i2 <= i3 ? Reachability.REACHABLE : i2 >= size ? Reachability.UNREACHABLE : Reachability.UNKNOWN);
|
|
i2++;
|
|
}
|
|
return arrayList;
|
|
}
|
|
|
|
private long computeIgnoredBitmapRetainedSize(Snapshot snapshot, Instance instance) {
|
|
ArrayInstance arrayInstance;
|
|
long j = 0;
|
|
for (Instance instance2 : snapshot.findClass("android.graphics.Bitmap").getInstancesList()) {
|
|
if (isIgnoredDominator(instance, instance2) && (arrayInstance = (ArrayInstance) HahaHelper.fieldValue(HahaHelper.classInstanceValues(instance2), "mBuffer")) != null) {
|
|
long totalRetainedSize = arrayInstance.getTotalRetainedSize();
|
|
long totalRetainedSize2 = instance2.getTotalRetainedSize();
|
|
if (totalRetainedSize2 < totalRetainedSize) {
|
|
totalRetainedSize2 += totalRetainedSize;
|
|
}
|
|
j += totalRetainedSize2;
|
|
}
|
|
}
|
|
return j;
|
|
}
|
|
|
|
private List<LeakReference> describeFields(Instance instance) {
|
|
ArrayList arrayList = new ArrayList();
|
|
if (instance instanceof ClassObj) {
|
|
for (Map.Entry<Field, Object> entry : ((ClassObj) instance).getStaticFieldValues().entrySet()) {
|
|
arrayList.add(new LeakReference(LeakTraceElement.Type.STATIC_FIELD, entry.getKey().getName(), HahaHelper.valueAsString(entry.getValue())));
|
|
}
|
|
} else if (instance instanceof ArrayInstance) {
|
|
ArrayInstance arrayInstance = (ArrayInstance) instance;
|
|
if (arrayInstance.getArrayType() == Type.OBJECT) {
|
|
Object[] values = arrayInstance.getValues();
|
|
for (int i = 0; i < values.length; i++) {
|
|
arrayList.add(new LeakReference(LeakTraceElement.Type.ARRAY_ENTRY, Integer.toString(i), HahaHelper.valueAsString(values[i])));
|
|
}
|
|
}
|
|
} else {
|
|
for (Map.Entry<Field, Object> entry2 : instance.getClassObj().getStaticFieldValues().entrySet()) {
|
|
arrayList.add(new LeakReference(LeakTraceElement.Type.STATIC_FIELD, entry2.getKey().getName(), HahaHelper.valueAsString(entry2.getValue())));
|
|
}
|
|
for (ClassInstance.FieldValue fieldValue : ((ClassInstance) instance).getValues()) {
|
|
arrayList.add(new LeakReference(LeakTraceElement.Type.INSTANCE_FIELD, fieldValue.getField().getName(), HahaHelper.valueAsString(fieldValue.getValue())));
|
|
}
|
|
}
|
|
return arrayList;
|
|
}
|
|
|
|
private AnalysisResult findLeakTrace(long j, Snapshot snapshot, Instance instance, boolean z) {
|
|
long j2;
|
|
this.listener.onProgressUpdate(AnalyzerProgressListener.Step.FINDING_SHORTEST_PATH);
|
|
ShortestPathFinder.Result findPath = new ShortestPathFinder(this.excludedRefs).findPath(snapshot, instance);
|
|
String className = instance.getClassObj().getClassName();
|
|
if (findPath.leakingNode == null) {
|
|
return AnalysisResult.noLeak(className, since(j));
|
|
}
|
|
this.listener.onProgressUpdate(AnalyzerProgressListener.Step.BUILDING_LEAK_TRACE);
|
|
LeakTrace buildLeakTrace = buildLeakTrace(findPath.leakingNode);
|
|
if (z) {
|
|
this.listener.onProgressUpdate(AnalyzerProgressListener.Step.COMPUTING_DOMINATORS);
|
|
snapshot.computeDominators();
|
|
Instance instance2 = findPath.leakingNode.instance;
|
|
j2 = instance2.getTotalRetainedSize();
|
|
if (Build.VERSION.SDK_INT <= 25) {
|
|
this.listener.onProgressUpdate(AnalyzerProgressListener.Step.COMPUTING_BITMAP_SIZE);
|
|
j2 += computeIgnoredBitmapRetainedSize(snapshot, instance2);
|
|
}
|
|
} else {
|
|
j2 = -1;
|
|
}
|
|
return AnalysisResult.leakDetected(findPath.excludingKnownLeaks, className, buildLeakTrace, j2, since(j));
|
|
}
|
|
|
|
private Instance findLeakingReference(String str, Snapshot snapshot) {
|
|
ClassObj findClass = snapshot.findClass(KeyedWeakReference.class.getName());
|
|
if (findClass == null) {
|
|
throw new IllegalStateException("Could not find the " + KeyedWeakReference.class.getName() + " class in the heap dump.");
|
|
}
|
|
ArrayList arrayList = new ArrayList();
|
|
Iterator<Instance> it = findClass.getInstancesList().iterator();
|
|
while (it.hasNext()) {
|
|
List<ClassInstance.FieldValue> classInstanceValues = HahaHelper.classInstanceValues(it.next());
|
|
Object fieldValue = HahaHelper.fieldValue(classInstanceValues, "key");
|
|
if (fieldValue == null) {
|
|
arrayList.add(null);
|
|
} else {
|
|
String asString = HahaHelper.asString(fieldValue);
|
|
if (asString.equals(str)) {
|
|
return (Instance) HahaHelper.fieldValue(classInstanceValues, "referent");
|
|
}
|
|
arrayList.add(asString);
|
|
}
|
|
}
|
|
throw new IllegalStateException("Could not find weak reference with key " + str + " in " + arrayList);
|
|
}
|
|
|
|
private String generateRootKey(RootObj rootObj) {
|
|
return String.format("%s@0x%08x", rootObj.getRootType().getName(), Long.valueOf(rootObj.getId()));
|
|
}
|
|
|
|
private String getClassName(Instance instance) {
|
|
return instance instanceof ClassObj ? ((ClassObj) instance).getClassName() : instance instanceof ArrayInstance ? ((ArrayInstance) instance).getClassObj().getClassName() : instance.getClassObj().getClassName();
|
|
}
|
|
|
|
private boolean isIgnoredDominator(Instance instance, Instance instance2) {
|
|
boolean z = false;
|
|
do {
|
|
Instance immediateDominator = instance2.getImmediateDominator();
|
|
if ((immediateDominator instanceof RootObj) && ((RootObj) immediateDominator).getRootType() == RootType.UNKNOWN) {
|
|
instance2 = instance2.getNextInstanceToGcRoot();
|
|
z = true;
|
|
} else {
|
|
instance2 = immediateDominator;
|
|
}
|
|
if (instance2 == null) {
|
|
return false;
|
|
}
|
|
} while (instance2 != instance);
|
|
return z;
|
|
}
|
|
|
|
private long since(long j) {
|
|
return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - j);
|
|
}
|
|
|
|
@Deprecated
|
|
public AnalysisResult checkForLeak(File file, String str) {
|
|
return checkForLeak(file, str, true);
|
|
}
|
|
|
|
void deduplicateGcRoots(Snapshot snapshot) {
|
|
final THashMap tHashMap = new THashMap();
|
|
final Collection<RootObj> gCRoots = snapshot.getGCRoots();
|
|
for (RootObj rootObj : gCRoots) {
|
|
String generateRootKey = generateRootKey(rootObj);
|
|
if (!tHashMap.containsKey(generateRootKey)) {
|
|
tHashMap.put(generateRootKey, rootObj);
|
|
}
|
|
}
|
|
gCRoots.clear();
|
|
tHashMap.forEach(new TObjectProcedure<String>() { // from class: com.squareup.leakcanary.HeapAnalyzer.1
|
|
@Override // gnu.trove.TObjectProcedure
|
|
public boolean execute(String str) {
|
|
return gCRoots.add(tHashMap.get(str));
|
|
}
|
|
});
|
|
}
|
|
|
|
public List<TrackedReference> findTrackedReferences(File file) {
|
|
if (!file.exists()) {
|
|
throw new IllegalArgumentException("File does not exist: " + file);
|
|
}
|
|
try {
|
|
Snapshot parse = new HprofParser(new MemoryMappedFileBuffer(file)).parse();
|
|
deduplicateGcRoots(parse);
|
|
ClassObj findClass = parse.findClass(KeyedWeakReference.class.getName());
|
|
ArrayList arrayList = new ArrayList();
|
|
Iterator<Instance> it = findClass.getInstancesList().iterator();
|
|
while (it.hasNext()) {
|
|
List<ClassInstance.FieldValue> classInstanceValues = HahaHelper.classInstanceValues(it.next());
|
|
String asString = HahaHelper.asString(HahaHelper.fieldValue(classInstanceValues, "key"));
|
|
String asString2 = HahaHelper.hasField(classInstanceValues, MediationMetaData.KEY_NAME) ? HahaHelper.asString(HahaHelper.fieldValue(classInstanceValues, MediationMetaData.KEY_NAME)) : "(No name field)";
|
|
Instance instance = (Instance) HahaHelper.fieldValue(classInstanceValues, "referent");
|
|
if (instance != null) {
|
|
arrayList.add(new TrackedReference(asString, asString2, getClassName(instance), describeFields(instance)));
|
|
}
|
|
}
|
|
return arrayList;
|
|
} catch (Throwable th) {
|
|
throw new RuntimeException(th);
|
|
}
|
|
}
|
|
|
|
public AnalysisResult checkForLeak(File file, String str, boolean z) {
|
|
long nanoTime = System.nanoTime();
|
|
if (!file.exists()) {
|
|
return AnalysisResult.failure(new IllegalArgumentException("File does not exist: " + file), since(nanoTime));
|
|
}
|
|
try {
|
|
this.listener.onProgressUpdate(AnalyzerProgressListener.Step.READING_HEAP_DUMP_FILE);
|
|
HprofParser hprofParser = new HprofParser(new MemoryMappedFileBuffer(file));
|
|
this.listener.onProgressUpdate(AnalyzerProgressListener.Step.PARSING_HEAP_DUMP);
|
|
Snapshot parse = hprofParser.parse();
|
|
this.listener.onProgressUpdate(AnalyzerProgressListener.Step.DEDUPLICATING_GC_ROOTS);
|
|
deduplicateGcRoots(parse);
|
|
this.listener.onProgressUpdate(AnalyzerProgressListener.Step.FINDING_LEAKING_REF);
|
|
Instance findLeakingReference = findLeakingReference(str, parse);
|
|
return findLeakingReference == null ? AnalysisResult.noLeak(findLeakingReference.getClassObj().getClassName(), since(nanoTime)) : findLeakTrace(nanoTime, parse, findLeakingReference, z);
|
|
} catch (Throwable th) {
|
|
return AnalysisResult.failure(th, since(nanoTime));
|
|
}
|
|
}
|
|
|
|
public HeapAnalyzer(ExcludedRefs excludedRefs, AnalyzerProgressListener analyzerProgressListener, List<Class<? extends Reachability.Inspector>> list) {
|
|
this.excludedRefs = excludedRefs;
|
|
this.listener = analyzerProgressListener;
|
|
this.reachabilityInspectors = new ArrayList();
|
|
Iterator<Class<? extends Reachability.Inspector>> it = list.iterator();
|
|
while (it.hasNext()) {
|
|
try {
|
|
this.reachabilityInspectors.add(it.next().getDeclaredConstructor(new Class[0]).newInstance(new Object[0]));
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
}
|
|
}
|