1 /* 2 * Copyright (C) 2012 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.layoutlib.create; 18 19 import com.android.tools.layoutlib.annotations.VisibleForTesting; 20 import com.android.tools.layoutlib.annotations.VisibleForTesting.Visibility; 21 22 import org.objectweb.asm.AnnotationVisitor; 23 import org.objectweb.asm.Attribute; 24 import org.objectweb.asm.ClassReader; 25 import org.objectweb.asm.ClassVisitor; 26 import org.objectweb.asm.FieldVisitor; 27 import org.objectweb.asm.Label; 28 import org.objectweb.asm.MethodVisitor; 29 import org.objectweb.asm.Type; 30 import org.objectweb.asm.signature.SignatureReader; 31 import org.objectweb.asm.signature.SignatureVisitor; 32 33 import java.io.IOException; 34 import java.util.ArrayList; 35 import java.util.Enumeration; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Map.Entry; 39 import java.util.Set; 40 import java.util.TreeMap; 41 import java.util.TreeSet; 42 import java.util.zip.ZipEntry; 43 import java.util.zip.ZipFile; 44 45 /** 46 * Analyzes the input JAR using the ASM java bytecode manipulation library 47 * to list the classes and their dependencies. A "dependency" is a class 48 * used by another class. 49 */ 50 public class DependencyFinder { 51 52 // Note: a bunch of stuff has package-level access for unit tests. Consider it private. 53 54 /** Output logger. */ 55 private final Log mLog; 56 57 /** 58 * Creates a new analyzer. 59 * 60 * @param log The log output. 61 */ DependencyFinder(Log log)62 public DependencyFinder(Log log) { 63 mLog = log; 64 } 65 66 /** 67 * Starts the analysis using parameters from the constructor. 68 * 69 * @param osJarPath The input source JARs to parse. 70 * @return A pair: [0]: map { class FQCN => set of FQCN class dependencies }. 71 * [1]: map { missing class FQCN => set of FQCN class that uses it. } 72 */ findDeps(List<String> osJarPath)73 public List<Map<String, Set<String>>> findDeps(List<String> osJarPath) throws IOException { 74 75 Map<String, ClassReader> zipClasses = parseZip(osJarPath); 76 mLog.info("Found %d classes in input JAR%s.", 77 zipClasses.size(), 78 osJarPath.size() > 1 ? "s" : ""); 79 80 Map<String, Set<String>> deps = findClassesDeps(zipClasses); 81 82 Map<String, Set<String>> missing = findMissingClasses(deps, zipClasses.keySet()); 83 84 List<Map<String, Set<String>>> result = new ArrayList<>(2); 85 result.add(deps); 86 result.add(missing); 87 return result; 88 } 89 90 /** 91 * Prints dependencies to the current logger, found stuff and missing stuff. 92 */ printAllDeps(List<Map<String, Set<String>>> result)93 public void printAllDeps(List<Map<String, Set<String>>> result) { 94 assert result.size() == 2; 95 Map<String, Set<String>> deps = result.get(0); 96 Map<String, Set<String>> missing = result.get(1); 97 98 // Print all dependences found in the format: 99 // +Found: <FQCN from zip> 100 // uses: FQCN 101 102 mLog.info("++++++ %d Entries found in source JARs", deps.size()); 103 mLog.info(""); 104 105 for (Entry<String, Set<String>> entry : deps.entrySet()) { 106 mLog.info( "+Found : %s", entry.getKey()); 107 for (String dep : entry.getValue()) { 108 mLog.info(" uses: %s", dep); 109 } 110 111 mLog.info(""); 112 } 113 114 115 // Now print all missing dependences in the format: 116 // -Missing <FQCN>: 117 // used by: <FQCN> 118 119 mLog.info(""); 120 mLog.info("------ %d Entries missing from source JARs", missing.size()); 121 mLog.info(""); 122 123 for (Entry<String, Set<String>> entry : missing.entrySet()) { 124 mLog.info( "-Missing : %s", entry.getKey()); 125 for (String dep : entry.getValue()) { 126 mLog.info(" used by: %s", dep); 127 } 128 129 mLog.info(""); 130 } 131 } 132 133 /** 134 * Prints only a summary of the missing dependencies to the current logger. 135 */ printMissingDeps(List<Map<String, Set<String>>> result)136 public void printMissingDeps(List<Map<String, Set<String>>> result) { 137 assert result.size() == 2; 138 @SuppressWarnings("unused") Map<String, Set<String>> deps = result.get(0); 139 Map<String, Set<String>> missing = result.get(1); 140 141 for (String fqcn : missing.keySet()) { 142 mLog.info("%s", fqcn); 143 } 144 } 145 146 // ---------------- 147 148 /** 149 * Parses a JAR file and returns a list of all classes founds using a map 150 * class name => ASM ClassReader. Class names are in the form "android.view.View". 151 */ parseZip(List<String> jarPathList)152 Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException { 153 TreeMap<String, ClassReader> classes = new TreeMap<>(); 154 155 for (String jarPath : jarPathList) { 156 ZipFile zip = new ZipFile(jarPath); 157 Enumeration<? extends ZipEntry> entries = zip.entries(); 158 ZipEntry entry; 159 while (entries.hasMoreElements()) { 160 entry = entries.nextElement(); 161 if (entry.getName().endsWith(".class")) { 162 ClassReader cr = new ClassReader(zip.getInputStream(entry)); 163 String className = classReaderToClassName(cr); 164 classes.put(className, cr); 165 } 166 } 167 } 168 169 return classes; 170 } 171 172 /** 173 * Utility that returns the fully qualified binary class name for a ClassReader. 174 * E.g. it returns something like android.view.View. 175 */ classReaderToClassName(ClassReader classReader)176 static String classReaderToClassName(ClassReader classReader) { 177 if (classReader == null) { 178 return null; 179 } else { 180 return classReader.getClassName().replace('/', '.'); 181 } 182 } 183 184 /** 185 * Utility that returns the fully qualified binary class name from a path-like FQCN. 186 * E.g. it returns android.view.View from android/view/View. 187 */ internalToBinaryClassName(String className)188 static String internalToBinaryClassName(String className) { 189 if (className == null) { 190 return null; 191 } else { 192 return className.replace('/', '.'); 193 } 194 } 195 196 /** 197 * Finds all dependencies for all classes in keepClasses which are also 198 * listed in zipClasses. Returns a map of all the dependencies found. 199 */ findClassesDeps(Map<String, ClassReader> zipClasses)200 Map<String, Set<String>> findClassesDeps(Map<String, ClassReader> zipClasses) { 201 202 // The dependencies that we'll collect. 203 // It's a map Class name => uses class names. 204 Map<String, Set<String>> dependencyMap = new TreeMap<>(); 205 206 DependencyVisitor visitor = getVisitor(); 207 208 int count = 0; 209 try { 210 for (Entry<String, ClassReader> entry : zipClasses.entrySet()) { 211 String name = entry.getKey(); 212 213 TreeSet<String> set = new TreeSet<>(); 214 dependencyMap.put(name, set); 215 visitor.setDependencySet(set); 216 217 ClassReader cr = entry.getValue(); 218 cr.accept(visitor, 0 /* flags */); 219 220 visitor.setDependencySet(null); 221 222 mLog.debugNoln("Visited %d classes\r", ++count); 223 } 224 } finally { 225 mLog.debugNoln("\n"); 226 } 227 228 return dependencyMap; 229 } 230 231 /** 232 * Computes which classes FQCN were found as dependencies that are NOT listed 233 * in the original JAR classes. 234 * 235 * @param deps The map { FQCN => dependencies[] } returned by {@link #findClassesDeps(Map)}. 236 * @param zipClasses The set of all classes FQCN found in the JAR files. 237 * @return A map { FQCN not found in the zipClasses => classes using it } 238 */ findMissingClasses( Map<String, Set<String>> deps, Set<String> zipClasses)239 private Map<String, Set<String>> findMissingClasses( 240 Map<String, Set<String>> deps, 241 Set<String> zipClasses) { 242 Map<String, Set<String>> missing = new TreeMap<>(); 243 244 for (Entry<String, Set<String>> entry : deps.entrySet()) { 245 String name = entry.getKey(); 246 247 for (String dep : entry.getValue()) { 248 if (!zipClasses.contains(dep)) { 249 // This dependency doesn't exist in the zip classes. 250 Set<String> set = missing.get(dep); 251 if (set == null) { 252 set = new TreeSet<>(); 253 missing.put(dep, set); 254 } 255 set.add(name); 256 } 257 } 258 259 } 260 261 return missing; 262 } 263 264 265 // ---------------------------------- 266 267 /** 268 * Instantiates a new DependencyVisitor. Useful for unit tests. 269 */ 270 @VisibleForTesting(visibility=Visibility.PRIVATE) getVisitor()271 DependencyVisitor getVisitor() { 272 return new DependencyVisitor(); 273 } 274 275 /** 276 * Visitor to collect all the type dependencies from a class. 277 */ 278 public class DependencyVisitor extends ClassVisitor { 279 280 private Set<String> mCurrentDepSet; 281 282 /** 283 * Creates a new visitor that will find all the dependencies for the visited class. 284 */ DependencyVisitor()285 public DependencyVisitor() { 286 super(Main.ASM_VERSION); 287 } 288 289 /** 290 * Sets the {@link Set} where to record direct dependencies for this class. 291 * This will change before each {@link ClassReader#accept(ClassVisitor, int)} call. 292 */ setDependencySet(Set<String> set)293 public void setDependencySet(Set<String> set) { 294 mCurrentDepSet = set; 295 } 296 297 /** 298 * Considers the given class name as a dependency. 299 */ considerName(String className)300 public void considerName(String className) { 301 if (className == null) { 302 return; 303 } 304 305 className = internalToBinaryClassName(className); 306 307 try { 308 // exclude classes that are part of the default JRE (the one executing this program) 309 // or in java package (we won't be able to load them anyway). 310 if (className.startsWith("java.") || 311 getClass().getClassLoader().loadClass(className) != null) { 312 return; 313 } 314 } catch (ClassNotFoundException e) { 315 // ignore 316 } 317 318 // Add it to the dependency set for the currently visited class, as needed. 319 assert mCurrentDepSet != null; 320 mCurrentDepSet.add(className); 321 } 322 323 /** 324 * Considers this array of names using considerName(). 325 */ considerNames(String[] classNames)326 public void considerNames(String[] classNames) { 327 if (classNames != null) { 328 for (String className : classNames) { 329 considerName(className); 330 } 331 } 332 } 333 334 /** 335 * Considers this signature or type signature by invoking the {@link SignatureVisitor} 336 * on it. 337 */ considerSignature(String signature)338 public void considerSignature(String signature) { 339 if (signature != null) { 340 SignatureReader sr = new SignatureReader(signature); 341 // SignatureReader.accept will call accessType so we don't really have 342 // to differentiate where the signature comes from. 343 sr.accept(new MySignatureVisitor()); 344 } 345 } 346 347 /** 348 * Considers this {@link Type}. For arrays, the element type is considered. 349 * If the type is an object, it's internal name is considered. 350 */ considerType(Type t)351 public void considerType(Type t) { 352 if (t != null) { 353 if (t.getSort() == Type.ARRAY) { 354 t = t.getElementType(); 355 } 356 if (t.getSort() == Type.OBJECT) { 357 considerName(t.getInternalName()); 358 } 359 } 360 } 361 362 /** 363 * Considers a descriptor string. The descriptor is converted to a {@link Type} 364 * and then considerType() is invoked. 365 */ considerDesc(String desc)366 public boolean considerDesc(String desc) { 367 if (desc != null) { 368 try { 369 if (desc.length() > 0 && desc.charAt(0) == '(') { 370 // This is a method descriptor with arguments and a return type. 371 Type t = Type.getReturnType(desc); 372 considerType(t); 373 374 for (Type arg : Type.getArgumentTypes(desc)) { 375 considerType(arg); 376 } 377 378 } else { 379 Type t = Type.getType(desc); 380 considerType(t); 381 } 382 return true; 383 } catch (ArrayIndexOutOfBoundsException e) { 384 // ignore, not a valid type. 385 } 386 } 387 return false; 388 } 389 390 391 // --------------------------------------------------- 392 // --- ClassVisitor, FieldVisitor 393 // --------------------------------------------------- 394 395 // Visits a class header 396 @Override visit(int version, int access, String name, String signature, String superName, String[] interfaces)397 public void visit(int version, int access, String name, 398 String signature, String superName, String[] interfaces) { 399 // signature is the signature of this class. May be null if the class is not a generic 400 // one, and does not extend or implement generic classes or interfaces. 401 402 if (signature != null) { 403 considerSignature(signature); 404 } 405 406 // superName is the internal of name of the super class (see getInternalName). 407 // For interfaces, the super class is Object. May be null but only for the Object class. 408 considerName(superName); 409 410 // interfaces is the internal names of the class's interfaces (see getInternalName). 411 // May be null. 412 considerNames(interfaces); 413 } 414 415 416 @Override visitAnnotation(String desc, boolean visible)417 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 418 // desc is the class descriptor of the annotation class. 419 considerDesc(desc); 420 return new MyAnnotationVisitor(); 421 } 422 423 @Override visitAttribute(Attribute attr)424 public void visitAttribute(Attribute attr) { 425 // pass 426 } 427 428 // Visits the end of a class 429 @Override visitEnd()430 public void visitEnd() { 431 // pass 432 } 433 434 private class MyFieldVisitor extends FieldVisitor { 435 MyFieldVisitor()436 public MyFieldVisitor() { 437 super(Main.ASM_VERSION); 438 } 439 440 @Override visitAnnotation(String desc, boolean visible)441 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 442 // desc is the class descriptor of the annotation class. 443 considerDesc(desc); 444 return new MyAnnotationVisitor(); 445 } 446 447 @Override visitAttribute(Attribute attr)448 public void visitAttribute(Attribute attr) { 449 // pass 450 } 451 452 // Visits the end of a class 453 @Override visitEnd()454 public void visitEnd() { 455 // pass 456 } 457 } 458 459 @Override visitField(int access, String name, String desc, String signature, Object value)460 public FieldVisitor visitField(int access, String name, String desc, 461 String signature, Object value) { 462 // desc is the field's descriptor (see Type). 463 considerDesc(desc); 464 465 // signature is the field's signature. May be null if the field's type does not use 466 // generic types. 467 considerSignature(signature); 468 469 return new MyFieldVisitor(); 470 } 471 472 @Override visitInnerClass(String name, String outerName, String innerName, int access)473 public void visitInnerClass(String name, String outerName, String innerName, int access) { 474 // name is the internal name of an inner class (see getInternalName). 475 // Note: outerName/innerName seems to be null when we're reading the 476 // _Original_ClassName classes generated by layoutlib_create. 477 if (outerName != null) { 478 considerName(name); 479 } 480 } 481 482 @Override visitMethod(int access, String name, String desc, String signature, String[] exceptions)483 public MethodVisitor visitMethod(int access, String name, String desc, 484 String signature, String[] exceptions) { 485 // desc is the method's descriptor (see Type). 486 considerDesc(desc); 487 // signature is the method's signature. May be null if the method parameters, return 488 // type and exceptions do not use generic types. 489 considerSignature(signature); 490 491 return new MyMethodVisitor(); 492 } 493 494 @Override visitOuterClass(String owner, String name, String desc)495 public void visitOuterClass(String owner, String name, String desc) { 496 // pass 497 } 498 499 @Override visitSource(String source, String debug)500 public void visitSource(String source, String debug) { 501 // pass 502 } 503 504 505 // --------------------------------------------------- 506 // --- MethodVisitor 507 // --------------------------------------------------- 508 509 private class MyMethodVisitor extends MethodVisitor { 510 MyMethodVisitor()511 public MyMethodVisitor() { 512 super(Main.ASM_VERSION); 513 } 514 515 516 @Override visitAnnotationDefault()517 public AnnotationVisitor visitAnnotationDefault() { 518 return new MyAnnotationVisitor(); 519 } 520 521 @Override visitCode()522 public void visitCode() { 523 // pass 524 } 525 526 // field instruction 527 @Override visitFieldInsn(int opcode, String owner, String name, String desc)528 public void visitFieldInsn(int opcode, String owner, String name, String desc) { 529 // owner is the class that declares the field. 530 considerName(owner); 531 // desc is the field's descriptor (see Type). 532 considerDesc(desc); 533 } 534 535 @Override visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2)536 public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) { 537 // pass 538 } 539 540 @Override visitIincInsn(int var, int increment)541 public void visitIincInsn(int var, int increment) { 542 // pass -- an IINC instruction 543 } 544 545 @Override visitInsn(int opcode)546 public void visitInsn(int opcode) { 547 // pass -- a zero operand instruction 548 } 549 550 @Override visitIntInsn(int opcode, int operand)551 public void visitIntInsn(int opcode, int operand) { 552 // pass -- a single int operand instruction 553 } 554 555 @Override visitJumpInsn(int opcode, Label label)556 public void visitJumpInsn(int opcode, Label label) { 557 // pass -- a jump instruction 558 } 559 560 @Override visitLabel(Label label)561 public void visitLabel(Label label) { 562 // pass -- a label target 563 } 564 565 // instruction to load a constant from the stack 566 @Override visitLdcInsn(Object cst)567 public void visitLdcInsn(Object cst) { 568 if (cst instanceof Type) { 569 considerType((Type) cst); 570 } 571 } 572 573 @Override visitLineNumber(int line, Label start)574 public void visitLineNumber(int line, Label start) { 575 // pass 576 } 577 578 @Override visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index)579 public void visitLocalVariable(String name, String desc, 580 String signature, Label start, Label end, int index) { 581 // desc is the type descriptor of this local variable. 582 considerDesc(desc); 583 // signature is the type signature of this local variable. May be null if the local 584 // variable type does not use generic types. 585 considerSignature(signature); 586 } 587 588 @Override visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels)589 public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { 590 // pass -- a lookup switch instruction 591 } 592 593 @Override visitMaxs(int maxStack, int maxLocals)594 public void visitMaxs(int maxStack, int maxLocals) { 595 // pass 596 } 597 598 // instruction that invokes a method 599 @Override visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf)600 public void visitMethodInsn(int opcode, String owner, String name, String desc, 601 boolean itf) { 602 603 // owner is the internal name of the method's owner class 604 if (!considerDesc(owner) && owner.indexOf('/') != -1) { 605 considerName(owner); 606 } 607 // desc is the method's descriptor (see Type). 608 considerDesc(desc); 609 } 610 611 // instruction multianewarray, whatever that is 612 @Override visitMultiANewArrayInsn(String desc, int dims)613 public void visitMultiANewArrayInsn(String desc, int dims) { 614 615 // desc an array type descriptor. 616 considerDesc(desc); 617 } 618 619 @Override visitParameterAnnotation(int parameter, String desc, boolean visible)620 public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, 621 boolean visible) { 622 // desc is the class descriptor of the annotation class. 623 considerDesc(desc); 624 return new MyAnnotationVisitor(); 625 } 626 627 @Override visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels)628 public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { 629 // pass -- table switch instruction 630 631 } 632 633 @Override visitTryCatchBlock(Label start, Label end, Label handler, String type)634 public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { 635 // type is the internal name of the type of exceptions handled by the handler, 636 // or null to catch any exceptions (for "finally" blocks). 637 considerName(type); 638 } 639 640 // type instruction 641 @Override visitTypeInsn(int opcode, String type)642 public void visitTypeInsn(int opcode, String type) { 643 // type is the operand of the instruction to be visited. This operand must be the 644 // internal name of an object or array class. 645 considerName(type); 646 } 647 648 @Override visitVarInsn(int opcode, int var)649 public void visitVarInsn(int opcode, int var) { 650 // pass -- local variable instruction 651 } 652 } 653 654 private class MySignatureVisitor extends SignatureVisitor { 655 MySignatureVisitor()656 public MySignatureVisitor() { 657 super(Main.ASM_VERSION); 658 } 659 660 // --------------------------------------------------- 661 // --- SignatureVisitor 662 // --------------------------------------------------- 663 664 private String mCurrentSignatureClass = null; 665 666 // Starts the visit of a signature corresponding to a class or interface type 667 @Override visitClassType(String name)668 public void visitClassType(String name) { 669 mCurrentSignatureClass = name; 670 considerName(name); 671 } 672 673 // Visits an inner class 674 @Override visitInnerClassType(String name)675 public void visitInnerClassType(String name) { 676 if (mCurrentSignatureClass != null) { 677 mCurrentSignatureClass += "$" + name; 678 considerName(mCurrentSignatureClass); 679 } 680 } 681 682 @Override visitArrayType()683 public SignatureVisitor visitArrayType() { 684 return new MySignatureVisitor(); 685 } 686 687 @Override visitBaseType(char descriptor)688 public void visitBaseType(char descriptor) { 689 // pass -- a primitive type, ignored 690 } 691 692 @Override visitClassBound()693 public SignatureVisitor visitClassBound() { 694 return new MySignatureVisitor(); 695 } 696 697 @Override visitExceptionType()698 public SignatureVisitor visitExceptionType() { 699 return new MySignatureVisitor(); 700 } 701 702 @Override visitFormalTypeParameter(String name)703 public void visitFormalTypeParameter(String name) { 704 // pass 705 } 706 707 @Override visitInterface()708 public SignatureVisitor visitInterface() { 709 return new MySignatureVisitor(); 710 } 711 712 @Override visitInterfaceBound()713 public SignatureVisitor visitInterfaceBound() { 714 return new MySignatureVisitor(); 715 } 716 717 @Override visitParameterType()718 public SignatureVisitor visitParameterType() { 719 return new MySignatureVisitor(); 720 } 721 722 @Override visitReturnType()723 public SignatureVisitor visitReturnType() { 724 return new MySignatureVisitor(); 725 } 726 727 @Override visitSuperclass()728 public SignatureVisitor visitSuperclass() { 729 return new MySignatureVisitor(); 730 } 731 732 @Override visitTypeArgument(char wildcard)733 public SignatureVisitor visitTypeArgument(char wildcard) { 734 return new MySignatureVisitor(); 735 } 736 737 @Override visitTypeVariable(String name)738 public void visitTypeVariable(String name) { 739 // pass 740 } 741 742 @Override visitTypeArgument()743 public void visitTypeArgument() { 744 // pass 745 } 746 } 747 748 749 // --------------------------------------------------- 750 // --- AnnotationVisitor 751 // --------------------------------------------------- 752 753 private class MyAnnotationVisitor extends AnnotationVisitor { 754 MyAnnotationVisitor()755 public MyAnnotationVisitor() { 756 super(Main.ASM_VERSION); 757 } 758 759 // Visits a primitive value of an annotation 760 @Override visit(String name, Object value)761 public void visit(String name, Object value) { 762 // value is the actual value, whose type must be Byte, Boolean, Character, Short, 763 // Integer, Long, Float, Double, String or Type 764 if (value instanceof Type) { 765 considerType((Type) value); 766 } 767 } 768 769 @Override visitAnnotation(String name, String desc)770 public AnnotationVisitor visitAnnotation(String name, String desc) { 771 // desc is the class descriptor of the nested annotation class. 772 considerDesc(desc); 773 return new MyAnnotationVisitor(); 774 } 775 776 @Override visitArray(String name)777 public AnnotationVisitor visitArray(String name) { 778 return new MyAnnotationVisitor(); 779 } 780 781 @Override visitEnum(String name, String desc, String value)782 public void visitEnum(String name, String desc, String value) { 783 // desc is the class descriptor of the enumeration class. 784 considerDesc(desc); 785 } 786 } 787 } 788 } 789