| /* |
| * 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.JavaParser.ResolvedClass; |
| import com.android.tools.lint.client.api.JavaParser.ResolvedMethod; |
| import com.android.tools.lint.client.api.JavaParser.TypeDescriptor; |
| import com.android.tools.lint.detector.api.Category; |
| import com.android.tools.lint.detector.api.Detector; |
| import com.android.tools.lint.detector.api.Implementation; |
| import com.android.tools.lint.detector.api.Issue; |
| import com.android.tools.lint.detector.api.JavaContext; |
| 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 java.lang.reflect.Modifier; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import lombok.ast.ClassDeclaration; |
| import lombok.ast.Node; |
| |
| /** |
| * Checks that Handler implementations are top level classes or static. |
| * See the corresponding check in the android.os.Handler source code. |
| */ |
| public class HandlerDetector extends Detector implements Detector.JavaScanner { |
| |
| /** Potentially leaking handlers */ |
| public static final Issue ISSUE = Issue.create( |
| "HandlerLeak", //$NON-NLS-1$ |
| "Handler reference leaks", |
| |
| "Since this Handler is declared as an inner class, it may prevent the outer " + |
| "class from being garbage collected. If the Handler is using a Looper or " + |
| "MessageQueue for a thread other than the main thread, then there is no issue. " + |
| "If the Handler is using the Looper or MessageQueue of the main thread, you " + |
| "need to fix your Handler declaration, as follows: Declare the Handler as a " + |
| "static class; In the outer class, instantiate a WeakReference to the outer " + |
| "class and pass this object to your Handler when you instantiate the Handler; " + |
| "Make all references to members of the outer class using the WeakReference object.", |
| |
| Category.PERFORMANCE, |
| 4, |
| Severity.WARNING, |
| new Implementation( |
| HandlerDetector.class, |
| Scope.JAVA_FILE_SCOPE)); |
| |
| private static final String LOOPER_CLS = "android.os.Looper"; |
| private static final String HANDLER_CLS = "android.os.Handler"; |
| |
| /** Constructs a new {@link HandlerDetector} */ |
| public HandlerDetector() { |
| } |
| |
| @NonNull |
| @Override |
| public Speed getSpeed() { |
| return Speed.FAST; |
| } |
| |
| // ---- Implements JavaScanner ---- |
| |
| @Nullable |
| @Override |
| public List<String> applicableSuperClasses() { |
| return Collections.singletonList(HANDLER_CLS); |
| } |
| |
| @Override |
| public void checkClass(@NonNull JavaContext context, @Nullable ClassDeclaration declaration, |
| @NonNull Node node, @NonNull ResolvedClass cls) { |
| if (!isInnerClass(declaration)) { |
| return; |
| } |
| |
| if (isStaticClass(declaration)) { |
| return; |
| } |
| |
| // Only flag handlers using the default looper |
| if (hasLooperConstructorParameter(cls)) { |
| return; |
| } |
| |
| Node locationNode = node instanceof ClassDeclaration |
| ? ((ClassDeclaration) node).astName() : node; |
| Location location = context.getLocation(locationNode); |
| context.report(ISSUE, locationNode, location, String.format( |
| "This Handler class should be static or leaks might occur (%1$s)", |
| cls.getName())); |
| } |
| |
| private static boolean isInnerClass(@Nullable ClassDeclaration node) { |
| return node == null || // null class declarations means anonymous inner class |
| JavaContext.getParentOfType(node, ClassDeclaration.class, true) != null; |
| } |
| |
| private static boolean isStaticClass(@Nullable ClassDeclaration node) { |
| if (node == null) { |
| // A null class declaration means anonymous inner class, and these can't be static |
| return false; |
| } |
| |
| int flags = node.astModifiers().getEffectiveModifierFlags(); |
| return (flags & Modifier.STATIC) != 0; |
| } |
| |
| private static boolean hasLooperConstructorParameter(@NonNull ResolvedClass cls) { |
| for (ResolvedMethod constructor : cls.getConstructors()) { |
| for (int i = 0, n = constructor.getArgumentCount(); i < n; i++) { |
| TypeDescriptor type = constructor.getArgumentType(i); |
| if (type.matchesSignature(LOOPER_CLS)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| } |