Skip to content

Commit

Permalink
Add reason for ignoring to analysis result
Browse files Browse the repository at this point in the history
* When creating an ExcludedRef rule, we can now provide a name for that rule and a reason.
* When such a rule applies to an analysis result, it is stored in the leak trace element.
* Also moved the lengthy comments in AndroidExcludedRefs to now be ExcludedRef reasons, embedded in LeakCanary.

Fixes square#365
  • Loading branch information
pyricau committed Jan 23, 2016
1 parent 2a04119 commit 7ca518e
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 67 deletions.
178 changes: 113 additions & 65 deletions src/main/java/com/squareup/leakcanary/ExcludedRefs.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,130 +32,178 @@
*/
public final class ExcludedRefs implements Serializable {

public final Map<String, Map<String, Boolean>> fieldNameByClassName;
public final Map<String, Map<String, Boolean>> staticFieldNameByClassName;
public final Map<String, Boolean> threadNames;
public final Map<String, Boolean> classNames;
public final Map<String, Boolean> rootSuperClassNames;

ExcludedRefs(Map<String, Map<String, Boolean>> fieldNameByClassName,
Map<String, Map<String, Boolean>> staticFieldNameByClassName,
Map<String, Boolean> threadNames, Map<String, Boolean> classNames,
Map<String, Boolean> rootSuperClassNames) {
// Copy + unmodifiable.
this.fieldNameByClassName = unmodifiableMap(new LinkedHashMap<>(fieldNameByClassName));
this.staticFieldNameByClassName =
unmodifiableMap(new LinkedHashMap<>(staticFieldNameByClassName));
this.threadNames = unmodifiableMap(new LinkedHashMap<>(threadNames));
this.classNames = unmodifiableMap(new LinkedHashMap<>(classNames));
this.rootSuperClassNames = unmodifiableMap(new LinkedHashMap<>(rootSuperClassNames));
public static Builder builder() {
return new BuilderWithParams();
}

public final Map<String, Map<String, Exclusion>> fieldNameByClassName;
public final Map<String, Map<String, Exclusion>> staticFieldNameByClassName;
public final Map<String, Exclusion> threadNames;
public final Map<String, Exclusion> classNames;
public final Map<String, Exclusion> rootClassNames;

ExcludedRefs(BuilderWithParams builder) {
this.fieldNameByClassName = unmodifiableRefStringMap(builder.fieldNameByClassName);
this.staticFieldNameByClassName = unmodifiableRefStringMap(builder.staticFieldNameByClassName);
this.threadNames = unmodifiableRefMap(builder.threadNames);
this.classNames = unmodifiableRefMap(builder.classNames);
this.rootClassNames = unmodifiableRefMap(builder.rootClassNames);
}

private Map<String, Map<String, Exclusion>> unmodifiableRefStringMap(
Map<String, Map<String, ParamsBuilder>> mapmap) {
LinkedHashMap<String, Map<String, Exclusion>> fieldNameByClassName = new LinkedHashMap<>();
for (Map.Entry<String, Map<String, ParamsBuilder>> entry : mapmap.entrySet()) {
fieldNameByClassName.put(entry.getKey(), unmodifiableRefMap(entry.getValue()));
}
return unmodifiableMap(fieldNameByClassName);
}

private Map<String, Exclusion> unmodifiableRefMap(Map<String, ParamsBuilder> fieldBuilderMap) {
Map<String, Exclusion> fieldMap = new LinkedHashMap<>();
for (Map.Entry<String, ParamsBuilder> fieldEntry : fieldBuilderMap.entrySet()) {
fieldMap.put(fieldEntry.getKey(), new Exclusion(fieldEntry.getValue()));
}
return unmodifiableMap(fieldMap);
}

@Override public String toString() {
String string = "";
for (Map.Entry<String, Map<String, Boolean>> classes : fieldNameByClassName.entrySet()) {
for (Map.Entry<String, Map<String, Exclusion>> classes : fieldNameByClassName.entrySet()) {
String clazz = classes.getKey();
for (Map.Entry<String, Boolean> field : classes.getValue().entrySet()) {
String always = field.getValue() ? " (always)" : "";
for (Map.Entry<String, Exclusion> field : classes.getValue().entrySet()) {
String always = field.getValue().alwaysExclude ? " (always)" : "";
string += "| Field: " + clazz + "." + field.getKey() + always + "\n";
}
}
for (Map.Entry<String, Map<String, Boolean>> classes : staticFieldNameByClassName.entrySet()) {
for (Map.Entry<String, Map<String, Exclusion>> classes : staticFieldNameByClassName.entrySet()) {
String clazz = classes.getKey();
for (Map.Entry<String, Boolean> field : classes.getValue().entrySet()) {
String always = field.getValue() ? " (always)" : "";
for (Map.Entry<String, Exclusion> field : classes.getValue().entrySet()) {
String always = field.getValue().alwaysExclude ? " (always)" : "";
string += "| Static field: " + clazz + "." + field.getKey() + always + "\n";
}
}
for (Map.Entry<String, Boolean> thread : threadNames.entrySet()) {
String always = thread.getValue() ? " (always)" : "";
for (Map.Entry<String, Exclusion> thread : threadNames.entrySet()) {
String always = thread.getValue().alwaysExclude ? " (always)" : "";
string += "| Thread:" + thread.getKey() + always + "\n";
}
for (Map.Entry<String, Boolean> clazz : classNames.entrySet()) {
String always = clazz.getValue() ? " (always)" : "";
for (Map.Entry<String, Exclusion> clazz : classNames.entrySet()) {
String always = clazz.getValue().alwaysExclude ? " (always)" : "";
string += "| Class:" + clazz.getKey() + always + "\n";
}
for (Map.Entry<String, Boolean> clazz : rootSuperClassNames.entrySet()) {
String always = clazz.getValue() ? " (always)" : "";
for (Map.Entry<String, Exclusion> clazz : rootClassNames.entrySet()) {
String always = clazz.getValue().alwaysExclude ? " (always)" : "";
string += "| Root Class:" + clazz.getKey() + always + "\n";
}
return string;
}

public static final class Builder {
private final Map<String, Map<String, Boolean>> fieldNameByClassName = new LinkedHashMap<>();
private final Map<String, Map<String, Boolean>> staticFieldNameByClassName =
static final class ParamsBuilder {
String name;
String reason;
boolean alwaysExclude;
final String matching;

ParamsBuilder(String matching) {
this.matching = matching;
}
}

public interface Builder {
BuilderWithParams instanceField(String className, String fieldName);

BuilderWithParams staticField(String className, String fieldName);

BuilderWithParams thread(String threadName);

BuilderWithParams clazz(String className);

BuilderWithParams rootClass(String rootSuperClassName);

ExcludedRefs build();
}

public static final class BuilderWithParams implements Builder {

private final Map<String, Map<String, ParamsBuilder>> fieldNameByClassName =
new LinkedHashMap<>();
private final Map<String, Boolean> threadNames = new LinkedHashMap<>();
private final Map<String, Boolean> classNames = new LinkedHashMap<>();
private final Map<String, Boolean> rootSuperClassNames = new LinkedHashMap<>();
private final Map<String, Map<String, ParamsBuilder>> staticFieldNameByClassName =
new LinkedHashMap<>();
private final Map<String, ParamsBuilder> threadNames = new LinkedHashMap<>();
private final Map<String, ParamsBuilder> classNames = new LinkedHashMap<>();
private final Map<String, ParamsBuilder> rootClassNames = new LinkedHashMap<>();

private ParamsBuilder lastParams;

public Builder instanceField(String className, String fieldName) {
return instanceField(className, fieldName, false);
BuilderWithParams() {
}

public Builder instanceField(String className, String fieldName, boolean always) {
@Override public BuilderWithParams instanceField(String className, String fieldName) {
checkNotNull(className, "className");
checkNotNull(fieldName, "fieldName");
Map<String, Boolean> excludedFields = fieldNameByClassName.get(className);
Map<String, ParamsBuilder> excludedFields = fieldNameByClassName.get(className);
if (excludedFields == null) {
excludedFields = new LinkedHashMap<>();
fieldNameByClassName.put(className, excludedFields);
}
excludedFields.put(fieldName, always);
lastParams = new ParamsBuilder("field " + className + "#" + fieldName);
excludedFields.put(fieldName, lastParams);
return this;
}

public Builder staticField(String className, String fieldName) {
return staticField(className, fieldName, false);
}

public Builder staticField(String className, String fieldName, boolean always) {
@Override public BuilderWithParams staticField(String className, String fieldName) {
checkNotNull(className, "className");
checkNotNull(fieldName, "fieldName");
Map<String, Boolean> excludedFields = staticFieldNameByClassName.get(className);
Map<String, ParamsBuilder> excludedFields = staticFieldNameByClassName.get(className);
if (excludedFields == null) {
excludedFields = new LinkedHashMap<>();
staticFieldNameByClassName.put(className, excludedFields);
}
excludedFields.put(fieldName, always);
lastParams = new ParamsBuilder("static field " + className + "#" + fieldName);
excludedFields.put(fieldName, lastParams);
return this;
}

public Builder thread(String threadName) {
return thread(threadName, false);
@Override public BuilderWithParams thread(String threadName) {
checkNotNull(threadName, "threadName");
lastParams = new ParamsBuilder("any threads named " + threadName);
threadNames.put(threadName, lastParams);
return this;
}

public Builder thread(String threadName, boolean always) {
checkNotNull(threadName, "threadName");
threadNames.put(threadName, always);
/** Ignores all fields and static fields of all subclasses of the provided class name. */
@Override public BuilderWithParams clazz(String className) {
checkNotNull(className, "className");
lastParams = new ParamsBuilder("any subclass of " + className);
classNames.put(className, lastParams);
return this;
}

public Builder clazz(String className) {
return thread(className, false);
/** Ignores any GC root that belongs to a subclass of the provided class name. */
@Override public BuilderWithParams rootClass(String rootClassName) {
checkNotNull(rootClassName, "rootClassName");
lastParams = new ParamsBuilder("any GC root subclass of " + rootClassName);
rootClassNames.put(rootClassName, lastParams);
return this;
}

public Builder clazz(String className, boolean always) {
checkNotNull(className, "className");
classNames.put(className, always);
public BuilderWithParams named(String name) {
lastParams.name = name;
return this;
}

public Builder rootSuperClass(String rootSuperClassName) {
return rootSuperClass(rootSuperClassName, false);
public BuilderWithParams reason(String reason) {
lastParams.reason = reason;
return this;
}

/** Ignores any GC root that is a subclass of the provided class name. */
public Builder rootSuperClass(String rootSuperClassName, boolean always) {
checkNotNull(rootSuperClassName, "rootSuperClassName");
rootSuperClassNames.put(rootSuperClassName, always);
public BuilderWithParams alwaysExclude() {
lastParams.alwaysExclude = true;
return this;
}

public ExcludedRefs build() {
return new ExcludedRefs(fieldNameByClassName, staticFieldNameByClassName, threadNames,
classNames, rootSuperClassNames);
return new ExcludedRefs(this);
}
}
}
17 changes: 17 additions & 0 deletions src/main/java/com/squareup/leakcanary/Exclusion.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.squareup.leakcanary;

import java.io.Serializable;

public final class Exclusion implements Serializable {
public final String name;
public final String reason;
public final boolean alwaysExclude;
public final String matching;

Exclusion(ExcludedRefs.ParamsBuilder builder) {
this.name = builder.name;
this.reason = builder.reason;
this.alwaysExclude = builder.alwaysExclude;
this.matching = builder.matching;
}
}
2 changes: 1 addition & 1 deletion src/main/java/com/squareup/leakcanary/RefWatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public final class RefWatcher {
}, new HeapDump.Listener() {
@Override public void analyze(HeapDump heapDump) {
}
}, new ExcludedRefs.Builder().build());
}, new ExcludedRefs.BuilderWithParams().build());

private final Executor watchExecutor;
private final DebuggerControl debuggerControl;
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/com/squareup/leakcanary/RefWatcherTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

public class RefWatcherTest {

static final ExcludedRefs NO_REF = new ExcludedRefs.Builder().build();
static final ExcludedRefs NO_REF = new ExcludedRefs.BuilderWithParams().build();

static class TestDumper implements HeapDumper {
boolean called;
Expand Down

0 comments on commit 7ca518e

Please sign in to comment.