• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.lint.checks;
18 
19 import com.android.tools.lint.detector.api.Category;
20 import com.android.tools.lint.detector.api.ClassContext;
21 import com.android.tools.lint.detector.api.Context;
22 import com.android.tools.lint.detector.api.Detector;
23 import com.android.tools.lint.detector.api.Issue;
24 import com.android.tools.lint.detector.api.Location;
25 import com.android.tools.lint.detector.api.Scope;
26 import com.android.tools.lint.detector.api.Severity;
27 import com.android.tools.lint.detector.api.Speed;
28 import com.google.common.collect.Maps;
29 
30 import org.objectweb.asm.Opcodes;
31 import org.objectweb.asm.tree.AbstractInsnNode;
32 import org.objectweb.asm.tree.ClassNode;
33 import org.objectweb.asm.tree.FieldInsnNode;
34 import org.objectweb.asm.tree.InsnList;
35 import org.objectweb.asm.tree.LineNumberNode;
36 import org.objectweb.asm.tree.MethodInsnNode;
37 import org.objectweb.asm.tree.MethodNode;
38 
39 import java.io.File;
40 import java.util.ArrayList;
41 import java.util.EnumSet;
42 import java.util.HashSet;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Set;
46 
47 /**
48  * Looks for getter calls within the same class that could be replaced by
49  * direct field references instead.
50  */
51 public class FieldGetterDetector extends Detector implements Detector.ClassScanner {
52     /** The main issue discovered by this detector */
53     public static final Issue ISSUE = Issue.create(
54             "FieldGetter", //$NON-NLS-1$
55             "Suggests replacing uses of getters with direct field access within a class",
56 
57             "Accessing a field within the class that defines a getter for that field is " +
58             "at least 3 times faster than calling the getter. For simple getters that do " +
59             "nothing other than return the field, you might want to just reference the " +
60             "local field directly instead.",
61 
62             Category.PERFORMANCE,
63             4,
64             Severity.WARNING,
65             FieldGetterDetector.class,
66             EnumSet.of(Scope.CLASS_FILE)).
67             // This is a micro-optimization: not enabled by default
68             setEnabledByDefault(false).setMoreInfo(
69            "http://developer.android.com/guide/practices/design/performance.html#internal_get_set"); //$NON-NLS-1$
70 
71     /** Constructs a new {@link FieldGetterDetector} check */
FieldGetterDetector()72     public FieldGetterDetector() {
73     }
74 
75     @Override
appliesTo(Context context, File file)76     public boolean appliesTo(Context context, File file) {
77         return true;
78     }
79 
80     @Override
getSpeed()81     public Speed getSpeed() {
82         return Speed.FAST;
83     }
84 
85     // ---- Implements ClassScanner ----
86 
87     @SuppressWarnings("rawtypes")
88     @Override
checkClass(ClassContext context, ClassNode classNode)89     public void checkClass(ClassContext context, ClassNode classNode) {
90         List<Entry> pendingCalls = null;
91         int currentLine = 0;
92         List methodList = classNode.methods;
93         for (Object m : methodList) {
94             MethodNode method = (MethodNode) m;
95             InsnList nodes = method.instructions;
96             for (int i = 0, n = nodes.size(); i < n; i++) {
97                 AbstractInsnNode instruction = nodes.get(i);
98                 int type = instruction.getType();
99                 if (type == AbstractInsnNode.LINE) {
100                     currentLine = ((LineNumberNode) instruction).line;
101                 } else if (type == AbstractInsnNode.METHOD_INSN) {
102                     MethodInsnNode node = (MethodInsnNode) instruction;
103                     String name = node.name;
104                     String owner = node.owner;
105 
106                     if (((name.startsWith("get") && name.length() > 3     //$NON-NLS-1$
107                             && Character.isUpperCase(name.charAt(3)))
108                         || (name.startsWith("is") && name.length() > 2    //$NON-NLS-1$
109                             && Character.isUpperCase(name.charAt(2))))
110                             && owner.equals(classNode.name)) {
111                         // Calling a potential getter method on self. We now need to
112                         // investigate the method body of the getter call and make sure
113                         // it's really a plain getter, not just a method which happens
114                         // to have a method name like a getter, or a method which not
115                         // only returns a field but possibly computes it or performs
116                         // other initialization or side effects. This is done in a
117                         // second pass over the bytecode, initiated by the finish()
118                         // method.
119                         if (pendingCalls == null) {
120                             pendingCalls = new ArrayList<Entry>();
121                         }
122 
123                         pendingCalls.add(new Entry(name, currentLine, method));
124                     }
125                 }
126             }
127         }
128 
129         if (pendingCalls != null) {
130             Set<String> names = new HashSet<String>(pendingCalls.size());
131             for (Entry entry : pendingCalls) {
132                 names.add(entry.name);
133             }
134 
135             Map<String, String> getters = checkMethods(context.getClassNode(), names);
136             if (getters.size() > 0) {
137                 File source = context.getSourceFile();
138                 String contents = context.getSourceContents();
139                 for (String getter : getters.keySet()) {
140                     for (Entry entry : pendingCalls) {
141                         String name = entry.name;
142                         // There can be more than one reference to the same name:
143                         // one for each call site
144                         if (name.equals(getter)) {
145                             int line = entry.lineNumber;
146                             Location location = null;
147                             if (source != null) {
148                                 // ASM line numbers are 1-based, Lint needs 0-based
149                                 location = Location.create(source, contents, line - 1, name,
150                                         null);
151                             } else {
152                                 location = Location.create(context.file);
153                             }
154                             String fieldName = getters.get(getter);
155                             if (fieldName == null) {
156                                 fieldName = "";
157                             }
158                             context.report(ISSUE, entry.method, location, String.format(
159                                 "Calling getter method %1$s() on self is " +
160                                 "slower than field access (%2$s)", getter, fieldName), fieldName);
161                         }
162                     }
163                 }
164             }
165         }
166     }
167 
168     // Holder class for getters to be checked
169     private static class Entry {
170         public final String name;
171         public final int lineNumber;
172         public final MethodNode method;
173 
Entry(String name, int lineNumber, MethodNode method)174         public Entry(String name, int lineNumber, MethodNode method) {
175             super();
176             this.name = name;
177             this.lineNumber = lineNumber;
178             this.method = method;
179         }
180     }
181 
182     // Validate that these getter methods are really just simple field getters
183     // like these int and STring getters:
184     // public int getFoo();
185     //   Code:
186     //    0:   aload_0
187     //    1:   getfield    #21; //Field mFoo:I
188     //    4:   ireturn
189     //
190     // public java.lang.String getBar();
191     //   Code:
192     //    0:   aload_0
193     //    1:   getfield    #25; //Field mBar:Ljava/lang/String;
194     //    4:   areturn
195     //
196     // Returns a map of valid getters as keys, and if the field name is found, the field name
197     // for each getter as its value.
checkMethods(ClassNode classNode, Set<String> names)198     private static Map<String, String> checkMethods(ClassNode classNode, Set<String> names) {
199         Map<String, String> validGetters = Maps.newHashMap();
200         @SuppressWarnings("rawtypes")
201         List methods = classNode.methods;
202         String fieldName = null;
203         checkMethod:
204         for (Object methodObject : methods) {
205             MethodNode method = (MethodNode) methodObject;
206             if (names.contains(method.name)
207                     && method.desc.startsWith("()")) { //$NON-NLS-1$ // (): No arguments
208                 InsnList instructions = method.instructions;
209                 int mState = 1;
210                 for (AbstractInsnNode curr = instructions.getFirst();
211                         curr != null;
212                         curr = curr.getNext()) {
213                     switch (curr.getOpcode()) {
214                         case -1:
215                             // Skip label and line number nodes
216                             continue;
217                         case Opcodes.ALOAD:
218                             if (mState == 1) {
219                                 fieldName = null;
220                                 mState = 2;
221                             } else {
222                                 continue checkMethod;
223                             }
224                             break;
225                         case Opcodes.GETFIELD:
226                             if (mState == 2) {
227                                 FieldInsnNode field = (FieldInsnNode) curr;
228                                 fieldName = field.name;
229                                 mState = 3;
230                             } else {
231                                 continue checkMethod;
232                             }
233                             break;
234                         case Opcodes.ARETURN:
235                         case Opcodes.FRETURN:
236                         case Opcodes.IRETURN:
237                         case Opcodes.DRETURN:
238                         case Opcodes.LRETURN:
239                         case Opcodes.RETURN:
240                             if (mState == 3) {
241                                 validGetters.put(method.name, fieldName);
242                             }
243                             continue checkMethod;
244                         default:
245                             continue checkMethod;
246                     }
247                 }
248             }
249         }
250 
251         return validGetters;
252     }
253 }
254