Add Java Classfile support
Change-Id: Ifcc1d88dabb07dc05b2e5c934743ad52f9b6dc1d
diff --git a/build/tools.atree b/build/tools.atree
index c3b4232..30aaec1 100644
--- a/build/tools.atree
+++ b/build/tools.atree
@@ -126,6 +126,8 @@
framework/org.eclipse.jface_3.4.2.M20090107-0800.jar tools/lib/org.eclipse.jface_3.4.2.M20090107-0800.jar
framework/osgi.jar tools/lib/osgi.jar
framework/swing-worker-1.1.jar tools/lib/swing-worker-1.1.jar
+prebuilts/tools/common/asm-tools/asm-4.0.jar tools/lib/asm-4.0.jar
+prebuilts/tools/common/asm-tools/asm-tree-4.0.jar tools/lib/asm-tree-4.0.jar
# Proguard
external/proguard/docs/license.html tools/proguard/license.html
diff --git a/changes.txt b/changes.txt
index af7bab8..14572c0 100644
--- a/changes.txt
+++ b/changes.txt
@@ -1,5 +1,8 @@
Change log for Android SDK Tools.
+Revision 17:
+* New lint rules which analyze Java source and class files.
+
Revision 16:
* New "lint" tool which scans Android project trees for potential
problems such as missing translations, duplicate ids between layouts
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt
index 068590c..c7e5331 100644
--- a/eclipse/dictionary.txt
+++ b/eclipse/dictionary.txt
@@ -157,6 +157,7 @@
marquee
mdpi
metadata
+micro
min
mipmap
monospace
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
index d7bf98e..7493662 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
@@ -24,5 +24,7 @@
<classpathentry kind="lib" path="libs/assetstudio.jar" sourcepath="/assetstudio"/>
<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
<classpathentry combineaccessrules="false" kind="src" path="/ddmuilib"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
index ae9f406..58111cc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
@@ -22,6 +22,8 @@
libs/httpcore-4.1.jar,
libs/httpmime-4.1.1.jar,
libs/httpclient-4.1.1.jar,
+ libs/asm-4.0.jar,
+ libs/asm-tree-4.0.jar,
libs/commons-logging-1.1.1.jar,
libs/commons-codec-1.4.jar
Bundle-Activator: com.android.ide.eclipse.adt.AdtPlugin
diff --git a/eclipse/scripts/create_all_symlinks.sh b/eclipse/scripts/create_all_symlinks.sh
index c6cd467..1000143 100755
--- a/eclipse/scripts/create_all_symlinks.sh
+++ b/eclipse/scripts/create_all_symlinks.sh
@@ -62,6 +62,8 @@
ADT_LIBS="sdkstats androidprefs common layoutlib_api lint_api lint_checks ide_common rule_api ninepatch sdklib sdkuilib assetstudio"
ADT_PREBUILTS="\
prebuilt/common/kxml2/kxml2-2.3.0.jar \
+ prebuilts/tools/common/asm-tools/asm-4.0.jar \
+ prebuilts/tools/common/asm-tools/asm-tree-4.0.jar \
prebuilt/common/commons-compress/commons-compress-1.0.jar \
prebuilt/common/http-client/httpclient-4.1.1.jar \
prebuilt/common/http-client/httpcore-4.1.jar \
diff --git a/lint/cli/.classpath b/lint/cli/.classpath
index f6efb87..cd864d8 100644
--- a/lint/cli/.classpath
+++ b/lint/cli/.classpath
@@ -4,5 +4,7 @@
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry combineaccessrules="false" kind="src" path="/lint-api"/>
<classpathentry combineaccessrules="false" kind="src" path="/lint-checks"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/lint/cli/Android.mk b/lint/cli/Android.mk
index 5b99dc4..63448d4 100644
--- a/lint/cli/Android.mk
+++ b/lint/cli/Android.mk
@@ -12,6 +12,10 @@
LOCAL_JAVA_LIBRARIES := \
lint_api \
lint_checks
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ asm-tools \
+ asm-tree-tools
+
LOCAL_MODULE := lint
LOCAL_MODULE_TAGS := optional
diff --git a/lint/cli/etc/manifest.txt b/lint/cli/etc/manifest.txt
index e75087d..b75ca8a 100644
--- a/lint/cli/etc/manifest.txt
+++ b/lint/cli/etc/manifest.txt
@@ -1,2 +1,2 @@
Main-Class: com.android.tools.lint.Main
-Class-Path: lint_api.jar lint_checks.jar
+Class-Path: lint_api.jar lint_checks.jar asm-4.0.jar asm-tree-4.0.jar
diff --git a/lint/libs/lint_api/.classpath b/lint/libs/lint_api/.classpath
index 012d361..12d1046 100644
--- a/lint/libs/lint_api/.classpath
+++ b/lint/libs/lint_api/.classpath
@@ -3,5 +3,7 @@
<classpathentry excluding="Android.mk" kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry combineaccessrules="false" kind="src" path="/common"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/lint/libs/lint_api/Android.mk b/lint/libs/lint_api/Android.mk
index 847f52e..b089ef1 100644
--- a/lint/libs/lint_api/Android.mk
+++ b/lint/libs/lint_api/Android.mk
@@ -21,6 +21,9 @@
LOCAL_JAVA_RESOURCE_DIRS := src
LOCAL_JAVA_LIBRARIES := \
common
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ asm-tools \
+ asm-tree-tools
LOCAL_MODULE := lint_api
LOCAL_MODULE_TAGS := optional
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java
index 42c2ec7..2a1d587 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java
@@ -22,6 +22,7 @@
import com.android.resources.ResourceFolderType;
import com.android.tools.lint.client.api.LintListener.EventType;
import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
@@ -33,7 +34,11 @@
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.XmlContext;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.tree.ClassNode;
+
import java.io.File;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -395,7 +400,7 @@
if (mScope.contains(Scope.CLASS_FILE)) {
List<Detector> detectors = mScopeDetectors.get(Scope.CLASS_FILE);
- if (detectors != null) {
+ if (detectors != null && detectors.size() > 0) {
List<File> binFolders = project.getJavaClassFolders();
checkClasses(project, binFolders, detectors);
}
@@ -441,13 +446,63 @@
}
private void checkClasses(Project project, List<File> binFolders, List<Detector> checks) {
- Context context = new Context(mClient, project, project.getDir(), mScope);
- fireEvent(EventType.SCANNING_FILE, context);
- for (Detector detector : checks) {
- ((Detector.ClassScanner) detector).checkJavaClasses(context);
+ if (binFolders.size() == 0) {
+ //mClient.log(null, "Warning: Class-file checks are enabled, but no " +
+ // "output folders found. Does the project need to be built first?");
+ }
- if (mCanceled) {
- return;
+ for (File binDir : binFolders) {
+ List<File> classFiles = new ArrayList<File>();
+ addClassFiles(binDir, classFiles);
+
+ for (File file : classFiles) {
+ try {
+ byte[] bytes = LintUtils.readBytes(file);
+ if (bytes != null) {
+ ClassReader reader = new ClassReader(bytes);
+ ClassNode classNode = new ClassNode();
+ reader.accept(classNode, 0 /*flags*/);
+ ClassContext context = new ClassContext(mClient, project, file, mScope,
+ binDir, bytes, classNode);
+
+ for (Detector detector : checks) {
+ if (detector.appliesTo(context, file)) {
+ fireEvent(EventType.SCANNING_FILE, context);
+ detector.beforeCheckFile(context);
+
+ Detector.ClassScanner scanner = (Detector.ClassScanner) detector;
+ scanner.checkClass(context, classNode);
+ detector.afterCheckFile(context);
+ }
+
+ if (mCanceled) {
+ return;
+ }
+ }
+ }
+ } catch (IOException e) {
+ mClient.log(e, null);
+ continue;
+ }
+
+ if (mCanceled) {
+ return;
+ }
+ }
+ }
+ }
+
+ private void addClassFiles(File dir, List<File> classFiles) {
+ // Process the resource folder
+ File[] files = dir.listFiles();
+ if (files != null && files.length > 0) {
+ for (File file : files) {
+ if (file.isFile() && file.getName().endsWith(DOT_CLASS)) {
+ classFiles.add(file);
+ } else if (file.isDirectory()) {
+ // Recurse
+ addClassFiles(file, classFiles);
+ }
}
}
}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
new file mode 100644
index 0000000..f35889c
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2011 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.detector.api;
+
+import static com.android.tools.lint.detector.api.LintConstants.DOT_CLASS;
+import static com.android.tools.lint.detector.api.LintConstants.DOT_JAVA;
+
+import com.android.tools.lint.client.api.LintClient;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.tree.ClassNode;
+
+import java.io.File;
+import java.util.EnumSet;
+import java.util.List;
+
+/**
+ * A {@link Context} used when checking .class files.
+ * <p/>
+ * <b>NOTE: This is not a public or final API; if you rely on this be prepared
+ * to adjust your code for the next tools release.</b>
+ */
+public class ClassContext extends Context {
+ private final File mBinDir;
+ private ClassNode mClassNode;
+ private byte[] mBytes;
+
+ /**
+ * Construct a new {@link ClassContext}
+ *
+ * @param client the client requesting a lint check
+ * @param project the project containing the file being checked
+ * @param file the file being checked
+ * @param scope the scope for the lint job
+ * @param binDir the root binary directory containing this .class file.
+ * @param bytes the bytecode raw data
+ * @param classNode the bytecode object model
+ */
+ public ClassContext(LintClient client, Project project, File file,
+ EnumSet<Scope> scope, File binDir, byte[] bytes, ClassNode classNode) {
+ super(client, project, file, scope);
+ mBinDir = binDir;
+ mBytes = bytes;
+ mClassNode = classNode;
+ }
+
+ /**
+ * Returns the raw bytecode data for this class file
+ *
+ * @return the byte array containing the bytecode data, or null
+ */
+ public byte[] getBytecode() {
+ return mBytes;
+ }
+
+ /**
+ * Returns the bytecode object model
+ *
+ * @return the bytecode object model
+ */
+ public ClassNode getClassNode() {
+ return mClassNode;
+ }
+
+ /**
+ * Finds the corresponding source file for the current class file.
+ *
+ * @param source the name of the source file, if known (which is often
+ * stored in the bytecode and provided by the
+ * {@link ClassVisitor#visitSource(String, String)} method). If
+ * not known, this method will guess by stripping out inner-class
+ * suffixes like $1.
+ * @return the source file corresponding to the {@link #file} field.
+ */
+ public File findSourceFile(String source) {
+ if (source == null) {
+ source = file.getName();
+ if (source.endsWith(DOT_CLASS)) {
+ source = source.substring(0, source.length() - DOT_CLASS.length()) + DOT_JAVA;
+ }
+ int index = source.indexOf('$');
+ if (index != -1) {
+ source = source.substring(0, index) + DOT_JAVA;
+ }
+ }
+ if (source != null) {
+ // Determine package
+ String topPath = mBinDir.getPath();
+ String parentPath = file.getParentFile().getPath();
+ if (parentPath.startsWith(topPath)) {
+ String relative = parentPath.substring(topPath.length() + 1);
+ List<File> sources = getProject().getJavaSourceFolders();
+ for (File dir : sources) {
+ File sourceFile = new File(dir, relative + File.separator + source);
+ if (sourceFile.exists()) {
+ return sourceFile;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
index aaeee5a..c502e11 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
@@ -16,6 +16,7 @@
package com.android.tools.lint.detector.api;
+import org.objectweb.asm.tree.ClassNode;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -51,10 +52,14 @@
/** Specialized interface for detectors that scan Java class files */
public interface ClassScanner {
- // TODO: Class file / bytecode scanning support not yet implemented. This
- // is a placeholder.
- @SuppressWarnings("javadoc")
- void checkJavaClasses(Context context);
+ /**
+ * Checks the given class' bytecode for issues.
+ *
+ * @param context the context of the lint check, pointing to for example
+ * the file
+ * @param classNode the root class node
+ */
+ void checkClass(ClassContext context, ClassNode classNode);
}
/** Specialized interface for detectors that scan XML files */
@@ -236,4 +241,10 @@
public void checkJavaSources(Context context, List<File> sourceFolders) {
}
+ // ---- Dummy implementations to make implementing a ClassScanner easier: ----
+
+ @SuppressWarnings("javadoc")
+ public void checkClass(ClassContext context, ClassNode classNode) {
+
+ }
}
diff --git a/lint/libs/lint_checks/.classpath b/lint/libs/lint_checks/.classpath
index 773c06d..eab475b 100644
--- a/lint/libs/lint_checks/.classpath
+++ b/lint/libs/lint_checks/.classpath
@@ -5,5 +5,7 @@
<classpathentry combineaccessrules="false" kind="src" path="/lint-api"/>
<classpathentry combineaccessrules="false" kind="src" path="/common"/>
<classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/lint/libs/lint_checks/Android.mk b/lint/libs/lint_checks/Android.mk
index b4eb2a1..d51d3c8 100644
--- a/lint/libs/lint_checks/Android.mk
+++ b/lint/libs/lint_checks/Android.mk
@@ -14,6 +14,9 @@
common \
androidprefs \
lint_api
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ asm-tools \
+ asm-tree-tools
LOCAL_MODULE := lint_checks
LOCAL_MODULE_TAGS := optional
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
index 8522ea4..b1644d9 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
@@ -54,6 +54,8 @@
List<Issue> issues = new ArrayList<Issue>();
issues.add(AccessibilityDetector.ISSUE);
+ issues.add(MathDetector.ISSUE);
+ issues.add(FieldGetterDetector.ISSUE);
issues.add(DuplicateIdDetector.CROSS_LAYOUT);
issues.add(DuplicateIdDetector.WITHIN_LAYOUT);
issues.add(StateListDetector.ISSUE);
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java
new file mode 100644
index 0000000..a9085f7
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2011 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.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+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 com.android.util.Pair;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Looks for getter calls within the same class that could be replaced by
+ * direct field references instead.
+ */
+public class FieldGetterDetector extends Detector implements Detector.ClassScanner {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "FieldGetter", //$NON-NLS-1$
+ "Suggests replacing uses of getters with direct field access within a class",
+
+ "Accessing a field within the class that defines a getter for that field is " +
+ "at least 3 times faster than calling the getter. For simple getters that do " +
+ "nothing other than return the field, you might want to just reference the " +
+ "local field directly instead.",
+
+ Category.PERFORMANCE,
+ 4,
+ Severity.WARNING,
+ FieldGetterDetector.class,
+ EnumSet.of(Scope.CLASS_FILE)).
+ // This is a micro-optimization: not enabled by default
+ setEnabledByDefault(false).setMoreInfo(
+ "http://developer.android.com/guide/practices/design/performance.html#internal_get_set"); //$NON-NLS-1$
+
+ /** Constructs a new accessibility check */
+ public FieldGetterDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(Context context, File file) {
+ return true;
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements ClassScanner ----
+
+ @Override
+ public void checkClass(ClassContext context, ClassNode classNode) {
+ classNode.accept(new Visitor(context));
+ }
+
+ private static class Visitor extends ClassVisitor {
+ private final ClassContext mContext;
+ private int mCurrentLine;
+ private String mClass;
+ private List<Pair<String, Integer>> mPendingCalls;
+
+ public Visitor(ClassContext context) {
+ super(Opcodes.ASM4);
+ mContext = context;
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ mClass = name;
+ }
+
+ @Override
+ public void visitEnd() {
+ if (mPendingCalls != null) {
+ Set<String> names = new HashSet<String>(mPendingCalls.size());
+ for (Pair<String, Integer> pair : mPendingCalls) {
+ String name = pair.getFirst();
+ names.add(name);
+ }
+
+ List<String> getters = checkMethods(mContext.getClassNode(), names);
+ if (getters.size() > 0) {
+ File source = mContext.findSourceFile(null);
+ String contents = null;
+ if (source != null) {
+ contents = mContext.getClient().readFile(source);
+ }
+ for (String getter : getters) {
+ for (Pair<String, Integer> pair : mPendingCalls) {
+ String name = pair.getFirst();
+ // There can be more than one reference to the same name:
+ // one for each call site
+ if (name.equals(getter)) {
+ Integer line = pair.getSecond();
+ Location location = null;
+ if (source != null) {
+ location = Location.create(source, contents, line);
+ } else {
+ location = Location.create(mContext.file);
+ }
+ mContext.report(ISSUE, location, String.format(
+ "Calling getter method %1$s() on self is " +
+ "slower than field access", getter), null);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature,
+ String[] exceptions) {
+ return new MethodVisitor(Opcodes.ASM4) {
+ @SuppressWarnings("hiding")
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ if (((name.startsWith("get") && name.length() > 3 //$NON-NLS-1$
+ && Character.isUpperCase(name.charAt(3)))
+ || (name.startsWith("is") && name.length() > 2 //$NON-NLS-1$
+ && Character.isUpperCase(name.charAt(2))))
+ && owner.equals(mClass)) {
+ // Calling a potential getter method on self. We now need to
+ // investigate the method body of the getter call and make sure
+ // it's really a plain getter, not just a method which happens
+ // to have a method name like a getter, or a method which not
+ // only returns a field but possibly computes it or performs
+ // other initialization or side effects. This is done in a
+ // second pass over the bytecode, initiated by the finish()
+ // method.
+ if (mPendingCalls == null) {
+ mPendingCalls = new ArrayList<Pair<String,Integer>>();
+ }
+ mPendingCalls.add(Pair.of(name, mCurrentLine));
+ }
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ mCurrentLine = line;
+ }
+ };
+ }
+ }
+
+ // Validate that these getter methods are really just simple field getters
+ // like these int and STring getters:
+ // public int getFoo();
+ // Code:
+ // 0: aload_0
+ // 1: getfield #21; //Field mFoo:I
+ // 4: ireturn
+ //
+ // public java.lang.String getBar();
+ // Code:
+ // 0: aload_0
+ // 1: getfield #25; //Field mBar:Ljava/lang/String;
+ // 4: areturn
+ private static List<String> checkMethods(ClassNode classNode, Set<String> names) {
+ List<String> validGetters = new ArrayList<String>();
+ @SuppressWarnings("rawtypes")
+ List methods = classNode.methods;
+ checkMethod:
+ for (Object methodObject : methods) {
+ MethodNode method = (MethodNode) methodObject;
+ if (names.contains(method.name)
+ && method.desc.startsWith("()")) { //$NON-NLS-1$ // (): No arguments
+ InsnList instructions = method.instructions;
+ int mState = 1;
+ for (AbstractInsnNode curr = instructions.getFirst();
+ curr != null;
+ curr = curr.getNext()) {
+ switch (curr.getOpcode()) {
+ case -1:
+ // Skip label and line number nodes
+ continue;
+ case Opcodes.ALOAD:
+ if (mState == 1) {
+ mState = 2;
+ } else {
+ continue checkMethod;
+ }
+ break;
+ case Opcodes.GETFIELD:
+ if (mState == 2) {
+ mState = 3;
+ } else {
+ continue checkMethod;
+ }
+ break;
+ case Opcodes.ARETURN:
+ case Opcodes.FRETURN:
+ case Opcodes.IRETURN:
+ case Opcodes.DRETURN:
+ case Opcodes.LRETURN:
+ case Opcodes.RETURN:
+ validGetters.add(method.name);
+ continue checkMethod;
+ default:
+ continue checkMethod;
+ }
+ curr = curr.getNext();
+ }
+ }
+ }
+
+ return validGetters;
+ }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java
new file mode 100644
index 0000000..9eccaef
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2011 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.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+import com.android.tools.lint.detector.api.Context;
+import com.android.tools.lint.detector.api.Detector;
+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.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.ClassNode;
+
+import java.io.File;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Looks for usages of {@link java.lang.Math} methods which can be replaced with
+ * {@code android.util.FloatMath} methods to avoid casting.
+ */
+public class MathDetector extends Detector implements Detector.ClassScanner {
+ /** The main issue discovered by this detector */
+ public static final Issue ISSUE = Issue.create(
+ "FloatMath", //$NON-NLS-1$
+ "Suggests replacing java.lang.Math calls with android.util.FloatMath to " +
+ "avoid conversions",
+
+ "On modern hardware, \"double\" is just as fast as \"float\" though of course " +
+ "it takes more memory. However, if you are using floats and you need to compute " +
+ "the sine, cosine or square root, then it is better to use the " +
+ "android.util.FloatMath class instead of java.lang.Math since you can call methods " +
+ "written to operate on floats, so you avoid conversions back and forth to double.",
+
+ Category.PERFORMANCE,
+ 3,
+ Severity.WARNING,
+ MathDetector.class,
+ EnumSet.of(Scope.CLASS_FILE)).setMoreInfo(
+ //"http://developer.android.com/reference/android/util/FloatMath.html"); //$NON-NLS-1$
+ "http://developer.android.com/guide/practices/design/performance.html#avoidfloat"); //$NON-NLS-1$
+
+ /** Constructs a new accessibility check */
+ public MathDetector() {
+ }
+
+ @Override
+ public boolean appliesTo(Context context, File file) {
+ return true;
+ }
+
+ @Override
+ public Speed getSpeed() {
+ return Speed.FAST;
+ }
+
+ // ---- Implements ClassScanner ----
+
+ @Override
+ public void checkClass(ClassContext context, ClassNode classNode) {
+ classNode.accept(new MathCallFinder(context));
+ }
+
+ /** Methods on java.lang.Math that we want to find and suggest replacements for */
+ private static final Set<String> sFloatMethods = new HashSet<String>();
+ static {
+ sFloatMethods.add("sin"); //$NON-NLS-1$
+ sFloatMethods.add("cos"); //$NON-NLS-1$
+ sFloatMethods.add("ceil"); //$NON-NLS-1$
+ sFloatMethods.add("sqrt"); //$NON-NLS-1$
+ sFloatMethods.add("floor"); //$NON-NLS-1$
+ }
+
+ private static class MathCallFinder extends ClassVisitor {
+ private final ClassContext mContext;
+ private String mCurrentSource;
+
+ public MathCallFinder(ClassContext context) {
+ super(Opcodes.ASM4);
+ mContext = context;
+ }
+
+ @Override
+ public void visitSource(String source, String debug) {
+ mCurrentSource = source;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc, String signature,
+ String[] exceptions) {
+ return new MyMethodVisitor(mContext, mCurrentSource);
+ }
+ }
+
+ private static class MyMethodVisitor extends MethodVisitor {
+ private final ClassContext mContext;
+ private int mCurrentLine;
+ private String mCurrentSource;
+ private int mLastInsn;
+ private String mPendingMethod;
+
+ public MyMethodVisitor(ClassContext context, String currentSource) {
+ super(Opcodes.ASM4);
+ mContext = context;
+ mCurrentSource = currentSource;
+ }
+
+ private Location getCurrentLocation() {
+ // Make utility method!
+ File file = mContext.file; // The binary .class file. Try to find source instead.
+ int line = 0;
+
+ // Determine package
+ File source = mContext.findSourceFile(mCurrentSource);
+ if (source != null) {
+ file = source;
+ line = mCurrentLine;
+
+ if (line > 0) {
+ String contents = mContext.getClient().readFile(file);
+ if (contents != null) {
+ // bytecode line numbers are 1-based
+ return Location.create(file, contents, line - 1);
+ }
+ }
+ }
+
+ return Location.create(file);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ if (sFloatMethods.contains(name) && owner.equals("java/lang/Math")) { //$NON-NLS-1$
+ if (mLastInsn != Opcodes.F2D) {
+ mPendingMethod = name;
+ return;
+ }
+ String message = String.format(
+ "Use android.util.FloatMath#%1$s() instead of java.lang.Math#%1$s to " +
+ "avoid argument float to double conversion", name);
+ mContext.report(ISSUE, getCurrentLocation(), message, null /*data*/);
+ }
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ if (opcode == Opcodes.D2F) {
+ if (mPendingMethod != null) {
+ String message = String.format(
+ "Use android.util.FloatMath#%1$s() instead of java.lang.Math#%1$s " +
+ "to avoid double to float return value conversion", mPendingMethod);
+ mContext.report(ISSUE, getCurrentLocation(), message, null /*data*/);
+
+ }
+ mPendingMethod = null;
+ }
+ mLastInsn = opcode;
+ }
+
+ @Override
+ public void visitLineNumber(int line, Label start) {
+ mCurrentLine = line;
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ mLastInsn = opcode;
+ }
+
+ @Override
+ public void visitIntInsn(int opcode, int operand) {
+ mLastInsn = opcode;
+ }
+
+ @Override
+ public void visitJumpInsn(int opcode, Label label) {
+ mLastInsn = opcode;
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ mLastInsn = opcode;
+ }
+
+ @Override
+ public void visitVarInsn(int opcode, int var) {
+ mLastInsn = opcode;
+ }
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/FieldGetterDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/FieldGetterDetectorTest.java
new file mode 100644
index 0000000..e50e6a6
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/FieldGetterDetectorTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 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.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class FieldGetterDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new FieldGetterDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(
+ "GetterTest.java:48: Warning: Calling getter method getFoo1() on self is slower than field access\n" +
+ "GetterTest.java:49: Warning: Calling getter method getFoo2() on self is slower than field access\n" +
+ "GetterTest.java:53: Warning: Calling getter method isBar1() on self is slower than field access\n" +
+ "GetterTest.java:55: Warning: Calling getter method getFoo1() on self is slower than field access\n" +
+ "GetterTest.java:56: Warning: Calling getter method getFoo2() on self is slower than field access",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/GetterTest.java.txt=>src/test/bytecode/GetterTest.java",
+ "bytecode/GetterTest.class.data=>bin/classes/test/bytecode/GetterTest.class"
+ ));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MathDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MathDetectorTest.java
new file mode 100644
index 0000000..4c5580d
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MathDetectorTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2011 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.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class MathDetectorTest extends AbstractCheckTest {
+ @Override
+ protected Detector getDetector() {
+ return new MathDetector();
+ }
+
+ public void test() throws Exception {
+ assertEquals(
+ "MathTest.java:12: Warning: Use android.util.FloatMath#sqrt() instead of java.lang.Math#sqrt to avoid argument float to double conversion\n" +
+ "MathTest.java:18: Warning: Use android.util.FloatMath#sqrt() instead of java.lang.Math#sqrt to avoid double to float return value conversion\n" +
+ "MathTest.java:23: Warning: Use android.util.FloatMath#sqrt() instead of java.lang.Math#sqrt to avoid argument float to double conversion",
+
+ lintProject(
+ "bytecode/.classpath=>.classpath",
+ "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+ "bytecode/MathTest.java.txt=>src/test/bytecode/MathTest.java",
+ "bytecode/MathTest.class.data=>bin/classes/test/bytecode/MathTest.class"
+ ));
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/.classpath b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/.classpath
new file mode 100644
index 0000000..a4763d1
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="gen"/>
+ <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+ <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+ <classpathentry kind="output" path="bin/classes"/>
+</classpath>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/AndroidManifest.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/AndroidManifest.xml
new file mode 100644
index 0000000..2896fc8
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="test.bytecode"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="10" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:name=".BytecodeTestsActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/GetterTest.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/GetterTest.class.data
new file mode 100644
index 0000000..73a9947
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/GetterTest.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/GetterTest.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/GetterTest.java.txt
new file mode 100644
index 0000000..25f3421
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/GetterTest.java.txt
@@ -0,0 +1,59 @@
+package test.bytecode;
+
+public class GetterTest {
+ private int mFoo1;
+ private String mFoo2;
+ private int mBar1;
+ private static int sFoo4;
+
+ public int getFoo1() {
+ return mFoo1;
+ }
+
+ public String getFoo2() {
+ return mFoo2;
+ }
+
+ public int isBar1() {
+ return mBar1;
+ }
+
+ // Not "plain" getters:
+
+ public String getFoo3() {
+ // NOT a plain getter
+ if (mFoo2 == null) {
+ mFoo2 = "";
+ }
+ return mFoo2;
+ }
+
+ public int getFoo4() {
+ // NOT a plain getter (using static)
+ return sFoo4;
+ }
+
+ public int getFoo5(int x) {
+ // NOT a plain getter (has extra argument)
+ return sFoo4;
+ }
+
+ public int isBar2(String s) {
+ // NOT a plain getter (has extra argument)
+ return mFoo1;
+ }
+
+ public void test() {
+ getFoo1();
+ getFoo2();
+ getFoo3();
+ getFoo4();
+ getFoo5(42);
+ isBar1();
+ isBar2("foo");
+ this.getFoo1();
+ this.getFoo2();
+ this.getFoo3();
+ this.getFoo4();
+ }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/MathTest.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/MathTest.class.data
new file mode 100644
index 0000000..9b419fa
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/MathTest.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/MathTest.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/MathTest.java.txt
new file mode 100644
index 0000000..684e654
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/MathTest.java.txt
@@ -0,0 +1,30 @@
+package test.bytecode;
+
+//Test data for the MathDetector
+public class MathTest {
+ public float floatResult;
+ public double doubleResult;
+
+ public void floatToFloatTest(float x) {
+ // This forces a cast of the argument
+ // from a float to a double, as well
+ // as a cast of the return code back to a float
+ floatResult = (float) Math.sqrt(x);
+ }
+
+ public void doubleToFloat(double x) {
+ // This forces a cast of the return value
+ // to a float
+ floatResult = (float) Math.sqrt(x);
+ }
+
+ public void floatToDouble(float x) {
+ // This causes a cast of the float argument to a double
+ doubleResult = Math.sqrt(x);
+ }
+
+ public void doubleToDouble(double x) {
+ // No casting
+ doubleResult = Math.sqrt(x);
+ }
+}