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.detector.api; 18 19 import com.android.annotations.NonNull; 20 import com.android.annotations.Nullable; 21 import com.android.tools.lint.client.api.LintDriver; 22 import com.google.common.annotations.Beta; 23 24 import org.objectweb.asm.tree.ClassNode; 25 import org.w3c.dom.Attr; 26 import org.w3c.dom.Document; 27 import org.w3c.dom.Element; 28 29 import java.io.File; 30 import java.util.ArrayList; 31 import java.util.Collection; 32 import java.util.List; 33 34 import lombok.ast.AstVisitor; 35 import lombok.ast.MethodInvocation; 36 37 /** 38 * A detector is able to find a particular problem. It might also be thought of as enforcing 39 * a rule, but "rule" is a bit overloaded in ADT terminology since ViewRules are used in 40 * the Rules API to allow views to specify designtime behavior in the graphical layout editor. 41 * <p> 42 * Each detector provides information about the issues it can find, such as an explanation 43 * of how to fix the issue, the priority, the category, etc. It also has an id which is 44 * used to persistently identify a particular type of error. 45 * <p> 46 * Detectors will be called in a predefined order: 47 * <ol> 48 * <li> Manifest file 49 * <li> Resource files, in alphabetical order by resource type 50 * (therefore, "layout" is checked before "values", "values-de" is checked before 51 * "values-en" but after "values", and so on. 52 * <li> Java sources 53 * <li> Java classes 54 * <li> Proguard files 55 * </ol> 56 * If a detector needs information when processing a file type that comes from a type of 57 * file later in the order above, they can request a second phase; see 58 * {@link LintDriver#requestRepeat}. 59 * <p> 60 * NOTE: Detectors might be constructed just once and shared between lint runs, so 61 * any per-detector state should be initialized and reset via the before/after 62 * methods. 63 * <p/> 64 * <b>NOTE: This is not a public or final API; if you rely on this be prepared 65 * to adjust your code for the next tools release.</b> 66 */ 67 @Beta 68 public abstract class Detector { 69 /** Specialized interface for detectors that scan Java source file parse trees */ 70 public interface JavaScanner { 71 /** 72 * Create a parse tree visitor to process the parse tree. All 73 * {@link JavaScanner} detectors must provide a visitor, unless they 74 * either return true from {@link #appliesToResourceRefs()} or return 75 * non null from {@link #getApplicableMethodNames()}. 76 * <p> 77 * If you return specific AST node types from 78 * {@link #getApplicableNodeTypes()}, then the visitor will <b>only</b> 79 * be called for the specific requested node types. This is more 80 * efficient, since it allows many detectors that apply to only a small 81 * part of the AST (such as method call nodes) to share iteration of the 82 * majority of the parse tree. 83 * <p> 84 * If you return null from {@link #getApplicableNodeTypes()}, then your 85 * visitor will be called from the top and all node types visited. 86 * <p> 87 * Note that a new visitor is created for each separate compilation 88 * unit, so you can store per file state in the visitor. 89 * 90 * @param context the {@link Context} for the file being analyzed 91 * @return a visitor, or null. 92 */ 93 @Nullable createJavaVisitor(@onNull JavaContext context)94 AstVisitor createJavaVisitor(@NonNull JavaContext context); 95 96 /** 97 * Return the types of AST nodes that the visitor returned from 98 * {@link #createJavaVisitor(JavaContext)} should visit. See the 99 * documentation for {@link #createJavaVisitor(JavaContext)} for details 100 * on how the shared visitor is used. 101 * <p> 102 * If you return null from this method, then the visitor will process 103 * the full tree instead. 104 * <p> 105 * Note that for the shared visitor, the return codes from the visit 106 * methods are ignored: returning true will <b>not</b> prune iteration 107 * of the subtree, since there may be other node types interested in the 108 * children. If you need to ensure that your visitor only processes a 109 * part of the tree, use a full visitor instead. See the 110 * OverdrawDetector implementation for an example of this. 111 * 112 * @return the list of applicable node types (AST node classes), or null 113 */ 114 @Nullable getApplicableNodeTypes()115 List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes(); 116 117 /** 118 * Return the list of method names this detector is interested in, or 119 * null. If this method returns non-null, then any AST nodes that match 120 * a method call in the list will be passed to the 121 * {@link #visitMethod(JavaContext, AstVisitor, MethodInvocation)} 122 * method for processing. The visitor created by 123 * {@link #createJavaVisitor(JavaContext)} is also passed to that 124 * method, although it can be null. 125 * <p> 126 * This makes it easy to write detectors that focus on some fixed calls. 127 * For example, the StringFormatDetector uses this mechanism to look for 128 * "format" calls, and when found it looks around (using the AST's 129 * {@link lombok.ast.Node#getParent()} method) to see if it's called on 130 * a String class instance, and if so do its normal processing. Note 131 * that since it doesn't need to do any other AST processing, that 132 * detector does not actually supply a visitor. 133 * 134 * @return a set of applicable method names, or null. 135 */ 136 @Nullable getApplicableMethodNames()137 List<String> getApplicableMethodNames(); 138 139 /** 140 * Method invoked for any method calls found that matches any names 141 * returned by {@link #getApplicableMethodNames()}. This also passes 142 * back the visitor that was created by 143 * {@link #createJavaVisitor(JavaContext)}, but a visitor is not 144 * required. It is intended for detectors that need to do additional AST 145 * processing, but also want the convenience of not having to look for 146 * method names on their own. 147 * 148 * @param context the context of the lint request 149 * @param visitor the visitor created from 150 * {@link #createJavaVisitor(JavaContext)}, or null 151 * @param node the {@link MethodInvocation} node for the invoked method 152 */ visitMethod( @onNull JavaContext context, @Nullable AstVisitor visitor, @NonNull MethodInvocation node)153 void visitMethod( 154 @NonNull JavaContext context, 155 @Nullable AstVisitor visitor, 156 @NonNull MethodInvocation node); 157 158 /** 159 * Returns whether this detector cares about Android resource references 160 * (such as {@code R.layout.main} or {@code R.string.app_name}). If it 161 * does, then the visitor will look for these patterns, and if found, it 162 * will invoke {@link #visitResourceReference} passing the resource type 163 * and resource name. It also passes the visitor, if any, that was 164 * created by {@link #createJavaVisitor(JavaContext)}, such that a 165 * detector can do more than just look for resources. 166 * 167 * @return true if this detector wants to be notified of R resource 168 * identifiers found in the code. 169 */ appliesToResourceRefs()170 boolean appliesToResourceRefs(); 171 172 /** 173 * Called for any resource references (such as {@code R.layout.main} 174 * found in Java code, provided this detector returned {@code true} from 175 * {@link #appliesToResourceRefs()}. 176 * 177 * @param context the lint scanning context 178 * @param visitor the visitor created from 179 * {@link #createJavaVisitor(JavaContext)}, or null 180 * @param node the variable reference for the resource 181 * @param type the resource type, such as "layout" or "string" 182 * @param name the resource name, such as "main" from 183 * {@code R.layout.main} 184 * @param isFramework whether the resource is a framework resource 185 * (android.R) or a local project resource (R) 186 */ visitResourceReference( @onNull JavaContext context, @Nullable AstVisitor visitor, @NonNull lombok.ast.Node node, @NonNull String type, @NonNull String name, boolean isFramework)187 void visitResourceReference( 188 @NonNull JavaContext context, 189 @Nullable AstVisitor visitor, 190 @NonNull lombok.ast.Node node, 191 @NonNull String type, 192 @NonNull String name, 193 boolean isFramework); 194 } 195 196 /** Specialized interface for detectors that scan Java class files */ 197 public interface ClassScanner { 198 /** 199 * Checks the given class' bytecode for issues. 200 * 201 * @param context the context of the lint check, pointing to for example 202 * the file 203 * @param classNode the root class node 204 */ checkClass(@onNull ClassContext context, @NonNull ClassNode classNode)205 void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode); 206 } 207 208 /** Specialized interface for detectors that scan XML files */ 209 public interface XmlScanner { 210 /** 211 * Visit the given document. The detector is responsible for its own iteration 212 * through the document. 213 * @param context information about the document being analyzed 214 * @param document the document to examine 215 */ visitDocument(@onNull XmlContext context, @NonNull Document document)216 void visitDocument(@NonNull XmlContext context, @NonNull Document document); 217 218 /** 219 * Visit the given element. 220 * @param context information about the document being analyzed 221 * @param element the element to examine 222 */ visitElement(@onNull XmlContext context, @NonNull Element element)223 void visitElement(@NonNull XmlContext context, @NonNull Element element); 224 225 /** 226 * Visit the given element after its children have been analyzed. 227 * @param context information about the document being analyzed 228 * @param element the element to examine 229 */ visitElementAfter(@onNull XmlContext context, @NonNull Element element)230 void visitElementAfter(@NonNull XmlContext context, @NonNull Element element); 231 232 /** 233 * Visit the given attribute. 234 * @param context information about the document being analyzed 235 * @param attribute the attribute node to examine 236 */ visitAttribute(@onNull XmlContext context, @NonNull Attr attribute)237 void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute); 238 239 /** 240 * Returns the list of elements that this detector wants to analyze. If non 241 * null, this detector will be called (specifically, the 242 * {@link #visitElement} method) for each matching element in the document. 243 * <p> 244 * If this method returns null, and {@link #getApplicableAttributes()} also returns 245 * null, then the {@link #visitDocument} method will be called instead. 246 * 247 * @return a collection of elements, or null, or the special 248 * {@link XmlScanner#ALL} marker to indicate that every single 249 * element should be analyzed. 250 */ 251 @Nullable getApplicableElements()252 Collection<String> getApplicableElements(); 253 254 /** 255 * Returns the list of attributes that this detector wants to analyze. If non 256 * null, this detector will be called (specifically, the 257 * {@link #visitAttribute} method) for each matching attribute in the document. 258 * <p> 259 * If this method returns null, and {@link #getApplicableElements()} also returns 260 * null, then the {@link #visitDocument} method will be called instead. 261 * 262 * @return a collection of attributes, or null, or the special 263 * {@link XmlScanner#ALL} marker to indicate that every single 264 * attribute should be analyzed. 265 */ 266 @Nullable getApplicableAttributes()267 Collection<String> getApplicableAttributes(); 268 269 /** 270 * Special marker collection returned by {@link #getApplicableElements()} or 271 * {@link #getApplicableAttributes()} to indicate that the check should be 272 * invoked on all elements or all attributes 273 */ 274 @NonNull 275 public static final List<String> ALL = new ArrayList<String>(0); // NOT Collections.EMPTY! 276 // We want to distinguish this from just an *empty* list returned by the caller! 277 } 278 279 /** 280 * Runs the detector. This method will not be called for certain specialized 281 * detectors, such as {@link XmlScanner} and {@link JavaScanner}, where 282 * there are specialized analysis methods instead such as 283 * {@link XmlScanner#visitElement(XmlContext, Element)}. 284 * 285 * @param context the context describing the work to be done 286 */ run(@onNull Context context)287 public void run(@NonNull Context context) { 288 } 289 290 /** 291 * Returns true if this detector applies to the given file 292 * 293 * @param context the context to check 294 * @param file the file in the context to check 295 * @return true if this detector applies to the given context and file 296 */ appliesTo(@onNull Context context, @NonNull File file)297 public boolean appliesTo(@NonNull Context context, @NonNull File file) { 298 return false; 299 } 300 301 /** 302 * Analysis is about to begin, perform any setup steps. 303 * 304 * @param context the context for the check referencing the project, lint 305 * client, etc 306 */ beforeCheckProject(@onNull Context context)307 public void beforeCheckProject(@NonNull Context context) { 308 } 309 310 /** 311 * Analysis has just been finished for the whole project, perform any 312 * cleanup or report issues that require project-wide analysis. 313 * 314 * @param context the context for the check referencing the project, lint 315 * client, etc 316 */ afterCheckProject(@onNull Context context)317 public void afterCheckProject(@NonNull Context context) { 318 } 319 320 /** 321 * Analysis is about to begin for the given library project, perform any setup steps. 322 * 323 * @param context the context for the check referencing the project, lint 324 * client, etc 325 */ beforeCheckLibraryProject(@onNull Context context)326 public void beforeCheckLibraryProject(@NonNull Context context) { 327 } 328 329 /** 330 * Analysis has just been finished for the given library project, perform any 331 * cleanup or report issues that require library-project-wide analysis. 332 * 333 * @param context the context for the check referencing the project, lint 334 * client, etc 335 */ afterCheckLibraryProject(@onNull Context context)336 public void afterCheckLibraryProject(@NonNull Context context) { 337 } 338 339 /** 340 * Analysis is about to be performed on a specific file, perform any setup 341 * steps. 342 * <p> 343 * Note: When this method is called at the beginning of checking an XML 344 * file, the context is guaranteed to be an instance of {@link XmlContext}, 345 * and similarly for a Java source file, the context will be a 346 * {@link JavaContext} and so on. 347 * 348 * @param context the context for the check referencing the file to be 349 * checked, the project, etc. 350 */ beforeCheckFile(@onNull Context context)351 public void beforeCheckFile(@NonNull Context context) { 352 } 353 354 /** 355 * Analysis has just been finished for a specific file, perform any cleanup 356 * or report issues found 357 * <p> 358 * Note: When this method is called at the end of checking an XML 359 * file, the context is guaranteed to be an instance of {@link XmlContext}, 360 * and similarly for a Java source file, the context will be a 361 * {@link JavaContext} and so on. 362 * 363 * @param context the context for the check referencing the file to be 364 * checked, the project, etc. 365 */ afterCheckFile(@onNull Context context)366 public void afterCheckFile(@NonNull Context context) { 367 } 368 369 /** 370 * Returns the expected speed of this detector 371 * 372 * @return the expected speed of this detector 373 */ 374 @NonNull getSpeed()375 public abstract Speed getSpeed(); 376 377 // ---- Dummy implementations to make implementing XmlScanner easier: ---- 378 379 @SuppressWarnings("javadoc") visitDocument(@onNull XmlContext context, @NonNull Document document)380 public void visitDocument(@NonNull XmlContext context, @NonNull Document document) { 381 // This method must be overridden if your detector does 382 // not return something from getApplicableElements or 383 // getApplicableATtributes 384 assert false; 385 } 386 387 @SuppressWarnings("javadoc") visitElement(@onNull XmlContext context, @NonNull Element element)388 public void visitElement(@NonNull XmlContext context, @NonNull Element element) { 389 // This method must be overridden if your detector returns 390 // tag names from getApplicableElements 391 assert false; 392 } 393 394 @SuppressWarnings("javadoc") visitElementAfter(@onNull XmlContext context, @NonNull Element element)395 public void visitElementAfter(@NonNull XmlContext context, @NonNull Element element) { 396 } 397 398 @SuppressWarnings("javadoc") visitAttribute(@onNull XmlContext context, @NonNull Attr attribute)399 public void visitAttribute(@NonNull XmlContext context, @NonNull Attr attribute) { 400 // This method must be overridden if your detector returns 401 // attribute names from getApplicableAttributes 402 assert false; 403 } 404 405 @SuppressWarnings("javadoc") 406 @Nullable getApplicableElements()407 public Collection<String> getApplicableElements() { 408 return null; 409 } 410 411 @Nullable 412 @SuppressWarnings("javadoc") getApplicableAttributes()413 public Collection<String> getApplicableAttributes() { 414 return null; 415 } 416 417 // ---- Dummy implementations to make implementing JavaScanner easier: ---- 418 419 @Nullable @SuppressWarnings("javadoc") getApplicableMethodNames()420 public List<String> getApplicableMethodNames() { 421 return null; 422 } 423 424 @Nullable @SuppressWarnings("javadoc") createJavaVisitor(@onNull JavaContext context)425 public AstVisitor createJavaVisitor(@NonNull JavaContext context) { 426 return null; 427 } 428 429 @Nullable @SuppressWarnings("javadoc") getApplicableNodeTypes()430 public List<Class<? extends lombok.ast.Node>> getApplicableNodeTypes() { 431 return null; 432 } 433 434 @SuppressWarnings("javadoc") visitMethod(@onNull JavaContext context, @Nullable AstVisitor visitor, @NonNull MethodInvocation node)435 public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor, 436 @NonNull MethodInvocation node) { 437 } 438 439 @SuppressWarnings("javadoc") appliesToResourceRefs()440 public boolean appliesToResourceRefs() { 441 return false; 442 } 443 444 @SuppressWarnings("javadoc") visitResourceReference(@onNull JavaContext context, @Nullable AstVisitor visitor, @NonNull lombok.ast.Node node, @NonNull String type, @NonNull String name, boolean isFramework)445 public void visitResourceReference(@NonNull JavaContext context, @Nullable AstVisitor visitor, 446 @NonNull lombok.ast.Node node, @NonNull String type, @NonNull String name, 447 boolean isFramework) { 448 } 449 450 // ---- Dummy implementations to make implementing a ClassScanner easier: ---- 451 452 @SuppressWarnings("javadoc") checkClass(@onNull ClassContext context, @NonNull ClassNode classNode)453 public void checkClass(@NonNull ClassContext context, @NonNull ClassNode classNode) { 454 } 455 } 456