blob: 77f5fe7660ab3e558a80e94ee48a77e08b881956 [file] [log] [blame]
/*
* Copyright (C) 2012 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.
*/
package com.android.tools.lint.checks;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.lint.client.api.LintDriver;
import com.android.tools.lint.client.api.SdkInfo;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.ClassContext;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Detector.ClassScanner;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.Speed;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.BasicInterpreter;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;
import java.util.Collections;
import java.util.List;
/**
* Checks for missing view tag detectors
*/
public class ViewTagDetector extends Detector implements ClassScanner {
/** Using setTag and leaking memory */
public static final Issue ISSUE = Issue.create(
"ViewTag", //$NON-NLS-1$
"Tagged object leaks",
"Prior to Android 4.0, the implementation of `View.setTag(int, Object)` would " +
"store the objects in a static map, where the values were strongly referenced. " +
"This means that if the object contains any references pointing back to the " +
"context, the context (which points to pretty much everything else) will leak. " +
"If you pass a view, the view provides a reference to the context " +
"that created it. Similarly, view holders typically contain a view, and cursors " +
"are sometimes also associated with views.",
Category.PERFORMANCE,
6,
Severity.WARNING,
new Implementation(
ViewTagDetector.class,
Scope.CLASS_FILE_SCOPE));
/** Constructs a new {@link ViewTagDetector} */
public ViewTagDetector() {
}
@NonNull
@Override
public Speed getSpeed() {
return Speed.FAST;
}
// ---- Implements ClassScanner ----
@Override
@Nullable
public List<String> getApplicableCallNames() {
return Collections.singletonList("setTag"); //$NON-NLS-1$
}
@Override
public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
@NonNull MethodNode method, @NonNull MethodInsnNode call) {
// The leak behavior is fixed in ICS:
// http://code.google.com/p/android/issues/detail?id=18273
if (context.getMainProject().getMinSdk() >= 14) {
return;
}
String owner = call.owner;
String desc = call.desc;
if (owner.equals("android/view/View") //$NON-NLS-1$
&& desc.equals("(ILjava/lang/Object;)V")) { //$NON-NLS-1$
Analyzer analyzer = new Analyzer(new BasicInterpreter() {
@Override
public BasicValue newValue(Type type) {
if (type == null) {
return BasicValue.UNINITIALIZED_VALUE;
} else if (type.getSort() == Type.VOID) {
return null;
} else {
return new BasicValue(type);
}
}
});
try {
Frame[] frames = analyzer.analyze(classNode.name, method);
InsnList instructions = method.instructions;
Frame frame = frames[instructions.indexOf(call)];
if (frame.getStackSize() < 3) {
return;
}
BasicValue stackValue = (BasicValue) frame.getStack(2);
Type type = stackValue.getType();
if (type == null) {
return;
}
String internalName = type.getInternalName();
String className = type.getClassName();
LintDriver driver = context.getDriver();
SdkInfo sdkInfo = context.getClient().getSdkInfo(context.getMainProject());
String objectType = null;
while (className != null) {
if (className.equals("android.view.View")) { //$NON-NLS-1$
objectType = "views";
break;
} else if (className.endsWith("ViewHolder")) { //$NON-NLS-1$
objectType = "view holders";
break;
} else if (className.endsWith("Cursor") //$NON-NLS-1$
&& className.startsWith("android.")) { //$NON-NLS-1$
objectType = "cursors";
break;
}
// TBD: Bitmaps, drawables? That's tricky, because as explained in
// http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
// apparently these are used along with nulling out the callbacks,
// and that's harder to detect here
String parent = sdkInfo.getParentViewClass(className);
if (parent == null) {
if (internalName == null) {
internalName = className.replace('.', '/');
}
assert internalName != null;
parent = driver.getSuperClass(internalName);
}
className = parent;
internalName = null;
}
if (objectType != null) {
Location location = context.getLocation(call);
String message = String.format("Avoid setting %1$s as values for `setTag`: " +
"Can lead to memory leaks in versions older than Android 4.0",
objectType);
context.report(ISSUE, method, call, location, message);
}
} catch (AnalyzerException e) {
context.log(e, null);
}
}
}
}