blob: e182e1fa366c7d887e14641e5c363764367fea2e [file] [log] [blame]
Stuart McCullochbb014372012-06-07 21:57:32 +00001package aQute.lib.osgi;
2
3import java.io.*;
4import java.lang.annotation.*;
5import java.lang.reflect.*;
6import java.nio.*;
7import java.util.*;
8import java.util.regex.*;
9
10import aQute.lib.osgi.Descriptors.Descriptor;
11import aQute.lib.osgi.Descriptors.PackageRef;
12import aQute.lib.osgi.Descriptors.TypeRef;
13import aQute.libg.generics.*;
14
15public class Clazz {
16
17 static Pattern METHOD_DESCRIPTOR = Pattern.compile("\\((.*)\\)(.+)");
18
19 public class ClassConstant {
20 int cname;
21
22 public ClassConstant(int class_index) {
23 this.cname = class_index;
24 }
25
26 public String getName() {
27 return (String) pool[cname];
28 }
29 }
30
31 public static enum JAVA {
32 JDK1_1(45, "JRE-1.1"), JDK1_2(46, "J2SE-1.2"), //
33 JDK1_3(47, "J2SE-1.3"), //
34 JDK1_4(48, "J2SE-1.4"), //
35 J2SE5(49, "J2SE-1.5"), //
36 J2SE6(50, "JavaSE-1.6"), //
37 OpenJDK7(51, "JavaSE-1.7"), //
38 UNKNOWN(Integer.MAX_VALUE, "<>")//
39 ;
40
41 final int major;
42 final String ee;
43
44 JAVA(int major, String ee) {
45 this.major = major;
46 this.ee = ee;
47 }
48
49 static JAVA format(int n) {
50 for (JAVA e : JAVA.values())
51 if (e.major == n)
52 return e;
53 return UNKNOWN;
54 }
55
56 public int getMajor() {
57 return major;
58 }
59
60 public boolean hasAnnotations() {
61 return major >= J2SE5.major;
62 }
63
64 public boolean hasGenerics() {
65 return major >= J2SE5.major;
66 }
67
68 public boolean hasEnums() {
69 return major >= J2SE5.major;
70 }
71
72 public static JAVA getJava(int major, int minor) {
73 for (JAVA j : JAVA.values()) {
74 if (j.major == major)
75 return j;
76 }
77 return UNKNOWN;
78 }
79
80 public String getEE() {
81 return ee;
82 }
83 }
84
85 public static enum QUERY {
86 IMPLEMENTS, EXTENDS, IMPORTS, NAMED, ANY, VERSION, CONCRETE, ABSTRACT, PUBLIC, ANNOTATED, RUNTIMEANNOTATIONS, CLASSANNOTATIONS;
87
88 }
89
90 public final static EnumSet<QUERY> HAS_ARGUMENT = EnumSet.of(QUERY.IMPLEMENTS,
91 QUERY.EXTENDS, QUERY.IMPORTS,
92 QUERY.NAMED, QUERY.VERSION,
93 QUERY.ANNOTATED);
94
95 /**
96 * <pre>
97 * ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its
98 * package.
99 * ACC_FINAL 0x0010 Declared final; no subclasses allowed.
100 * ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the
101 * invokespecial instruction.
102 * ACC_INTERFACE 0x0200 Is an interface, not a
103 * class.
104 * ACC_ABSTRACT 0x0400 Declared abstract; may not be instantiated.
105 * </pre>
106 *
107 * @param mod
108 */
109 final static int ACC_PUBLIC = 0x0001; // Declared
110 // public;
111 // may
112 // be
113 // accessed
114 // from outside its package.
115 final static int ACC_FINAL = 0x0010; // Declared
116 // final;
117 // no
118 // subclasses
119 // allowed.
120 final static int ACC_SUPER = 0x0020; // Treat
121 // superclass
122 // methods
123 // specially when invoked by the
124 // invokespecial instruction.
125 final static int ACC_INTERFACE = 0x0200; // Is
126 // an
127 // interface,
128 // not
129 // a
130 // classs
131 final static int ACC_ABSTRACT = 0x0400; // Declared
132
133 // a thing not in the source code
134 final static int ACC_SYNTHETIC = 0x1000;
135 final static int ACC_ANNOTATION = 0x2000;
136 final static int ACC_ENUM = 0x4000;
137
138 static protected class Assoc {
139 Assoc(byte tag, int a, int b) {
140 this.tag = tag;
141 this.a = a;
142 this.b = b;
143 }
144
145 byte tag;
146 int a;
147 int b;
148 }
149
150 public class Def {
151 final int access;
152 Set<TypeRef> annotations;
153
154 public Def(int access) {
155 this.access = access;
156 }
157
158 public int getAccess() {
159 return access;
160 }
161
162 public boolean isEnum() {
163 return (access & ACC_ENUM) != 0;
164 }
165
166 public boolean isPublic() {
167 return Modifier.isPublic(access);
168 }
169
170 public boolean isAbstract() {
171 return Modifier.isAbstract(access);
172 }
173
174 public boolean isProtected() {
175 return Modifier.isProtected(access);
176 }
177
178 public boolean isFinal() {
179 return Modifier.isFinal(access) || Clazz.this.isFinal();
180 }
181
182 public boolean isStatic() {
183 return Modifier.isStatic(access);
184 }
185
186 public boolean isPrivate() {
187 return Modifier.isPrivate(access);
188 }
189
190 public boolean isNative() {
191 return Modifier.isNative(access);
192 }
193
194 public boolean isTransient() {
195 return Modifier.isTransient(access);
196 }
197
198 public boolean isVolatile() {
199 return Modifier.isVolatile(access);
200 }
201
202 public boolean isInterface() {
203 return Modifier.isInterface(access);
204 }
205
206 public boolean isSynthetic() {
207 return (access & ACC_SYNTHETIC) != 0;
208 }
209
210 void addAnnotation(Annotation a) {
211 if (annotations == null)
212 annotations = Create.set();
213 annotations.add(analyzer.getTypeRef(a.name.getBinary()));
214 }
215
216 public Collection<TypeRef> getAnnotations() {
217 return annotations;
218 }
219 }
220
221 public class FieldDef extends Def {
222 final String name;
223 final Descriptor descriptor;
224 String signature;
225 Object constant;
226 boolean deprecated;
227
228 public boolean isDeprecated() {
229 return deprecated;
230 }
231
232 public void setDeprecated(boolean deprecated) {
233 this.deprecated = deprecated;
234 }
235
236 public FieldDef(int access, String name, String descriptor) {
237 super(access);
238 this.name = name;
239 this.descriptor = analyzer.getDescriptor(descriptor);
240 }
241
242 public String getName() {
243 return name;
244 }
245
246 public String toString() {
247 return getName();
248 }
249
250 public TypeRef getType() {
251 return descriptor.getType();
252 }
253
254 public TypeRef getContainingClass() {
255 return getClassName();
256 }
257
258 public Descriptor getDescriptor() {
259 return descriptor;
260 }
261
262 public void setConstant(Object o) {
263 this.constant = o;
264 }
265
266 public Object getConstant() {
267 return this.constant;
268 }
269
270 // TODO change to use proper generics
271 public String getGenericReturnType() {
272 String use = descriptor.toString();
273 if (signature != null)
274 use = signature;
275
276 Matcher m = METHOD_DESCRIPTOR.matcher(use);
277 if (!m.matches())
278 throw new IllegalArgumentException("Not a valid method descriptor: " + descriptor);
279
280 String returnType = m.group(2);
281 return objectDescriptorToFQN(returnType);
282 }
283
284 public String getSignature() {
285 return signature;
286 }
287
288 }
289
290 public class MethodDef extends FieldDef {
291 public MethodDef(int access, String method, String descriptor) {
292 super(access, method, descriptor);
293 }
294
295 public boolean isConstructor() {
296 return name.equals("<init>") || name.equals("<clinit>");
297 }
298
299 public TypeRef[] getPrototype() {
300 return descriptor.getPrototype();
301 }
302
303 }
304
305 final static byte SkipTable[] = { //
306 0, // 0 non existent
307 -1, // 1 CONSTANT_utf8 UTF 8, handled in
308 // method
309 -1, // 2
310 4, // 3 CONSTANT_Integer
311 4, // 4 CONSTANT_Float
312 8, // 5 CONSTANT_Long (index +=2!)
313 8, // 6 CONSTANT_Double (index +=2!)
314 -1, // 7 CONSTANT_Class
315 2, // 8 CONSTANT_String
316 4, // 9 CONSTANT_FieldRef
317 4, // 10 CONSTANT_MethodRef
318 4, // 11 CONSTANT_InterfaceMethodRef
319 4, // 12 CONSTANT_NameAndType
320 -1, // 13 Not defined
321 -1, // 14 Not defined
322 3, // 15 CONSTANT_MethodHandle
323 2, // 16 CONSTANT_MethodType
324 -1, // 17 Not defined
325 4, // 18 CONSTANT_InvokeDynamic
326 };
327
328 boolean hasRuntimeAnnotations;
329 boolean hasClassAnnotations;
330
331 TypeRef className;
332 Object pool[];
333 int intPool[];
334 Set<PackageRef> imports = Create.set();
335 String path;
336 int minor = 0;
337 int major = 0;
338 int innerAccess = -1;
339 int accessx = 0;
340 String sourceFile;
341 Set<TypeRef> xref;
342 Set<Integer> classes;
343 Set<Integer> descriptors;
344 Set<TypeRef> annotations;
345 int forName = 0;
346 int class$ = 0;
347 TypeRef[] interfaces;
348 TypeRef zuper;
349 ClassDataCollector cd = null;
350 Resource resource;
351 FieldDef last = null;
352 boolean deprecated;
353
354 final Analyzer analyzer;
355
356 public Clazz(Analyzer analyzer, String path, Resource resource) {
357 this.path = path;
358 this.resource = resource;
359 this.analyzer = analyzer;
360 }
361
362 public Set<TypeRef> parseClassFile() throws Exception {
363 return parseClassFileWithCollector(null);
364 }
365
366 public Set<TypeRef> parseClassFile(InputStream in) throws Exception {
367 return parseClassFile(in, null);
368 }
369
370 public Set<TypeRef> parseClassFileWithCollector(ClassDataCollector cd) throws Exception {
371 InputStream in = resource.openInputStream();
372 try {
373 return parseClassFile(in, cd);
374 } finally {
375 in.close();
376 }
377 }
378
379 public Set<TypeRef> parseClassFile(InputStream in, ClassDataCollector cd) throws Exception {
380 DataInputStream din = new DataInputStream(in);
381 try {
382 this.cd = cd;
383 return parseClassFile(din);
384 } finally {
385 cd = null;
386 din.close();
387 }
388 }
389
390 Set<TypeRef> parseClassFile(DataInputStream in) throws Exception {
391 xref = new HashSet<TypeRef>();
392 classes = new HashSet<Integer>();
393 descriptors = new HashSet<Integer>();
394
395 boolean crawl = cd != null; // Crawl the byte code if we have a
396 // collector
397 int magic = in.readInt();
398 if (magic != 0xCAFEBABE)
399 throw new IOException("Not a valid class file (no CAFEBABE header)");
400
401 minor = in.readUnsignedShort(); // minor version
402 major = in.readUnsignedShort(); // major version
403 if (cd != null)
404 cd.version(minor, major);
405 int count = in.readUnsignedShort();
406 pool = new Object[count];
407 intPool = new int[count];
408
409 process: for (int poolIndex = 1; poolIndex < count; poolIndex++) {
410 byte tag = in.readByte();
411 switch (tag) {
412 case 0:
413 break process;
414 case 1:
415 constantUtf8(in, poolIndex);
416 break;
417
418 case 3:
419 constantInteger(in, poolIndex);
420 break;
421
422 case 4:
423 constantFloat(in, poolIndex);
424 break;
425
426 // For some insane optimization reason are
427 // the long and the double two entries in the
428 // constant pool. See 4.4.5
429 case 5:
430 constantLong(in, poolIndex);
431 poolIndex++;
432 break;
433
434 case 6:
435 constantDouble(in, poolIndex);
436 poolIndex++;
437 break;
438
439 case 7:
440 constantClass(in, poolIndex);
441 break;
442
443 case 8:
444 constantString(in, poolIndex);
445 break;
446
447 case 10: // Method ref
448 case 11: // Interface Method ref
449 methodRef(in, poolIndex);
450 break;
451
452 // Name and Type
453 case 12:
454 nameAndType(in, poolIndex, tag);
455 break;
456
457 // We get the skip count for each record type
458 // from the SkipTable. This will also automatically
459 // abort when
460 default:
461 if (tag == 2)
462 throw new IOException("Invalid tag " + tag);
463 in.skipBytes(SkipTable[tag]);
464 break;
465 }
466 }
467
468 pool(pool, intPool);
469 /*
470 * Parse after the constant pool, code thanks to Hans Christian
471 * Falkenberg
472 */
473
474 accessx = in.readUnsignedShort(); // access
475
476 int this_class = in.readUnsignedShort();
477 className = analyzer.getTypeRef((String) pool[intPool[this_class]]);
478
479 try {
480
481 if (cd != null) {
482 if (!cd.classStart(accessx, className))
483 return null;
484 }
485
486 int super_class = in.readUnsignedShort();
487 String superName = (String) pool[intPool[super_class]];
488 if (superName != null) {
489 zuper = analyzer.getTypeRef(superName);
490 }
491
492 if (zuper != null) {
493 referTo(zuper);
494 if (cd != null)
495 cd.extendsClass(zuper);
496 }
497
498 int interfacesCount = in.readUnsignedShort();
499 if (interfacesCount > 0) {
500 interfaces = new TypeRef[interfacesCount];
501 for (int i = 0; i < interfacesCount; i++)
502 interfaces[i] = analyzer.getTypeRef((String) pool[intPool[in
503 .readUnsignedShort()]]);
504 if (cd != null)
505 cd.implementsInterfaces(interfaces);
506 }
507
508 int fieldsCount = in.readUnsignedShort();
509 for (int i = 0; i < fieldsCount; i++) {
510 int access_flags = in.readUnsignedShort(); // skip access flags
511 int name_index = in.readUnsignedShort();
512 int descriptor_index = in.readUnsignedShort();
513
514 // Java prior to 1.5 used a weird
515 // static variable to hold the com.X.class
516 // result construct. If it did not find it
517 // it would create a variable class$com$X
518 // that would be used to hold the class
519 // object gotten with Class.forName ...
520 // Stupidly, they did not actively use the
521 // class name for the field type, so bnd
522 // would not see a reference. We detect
523 // this case and add an artificial descriptor
524 String name = pool[name_index].toString(); // name_index
525 if (name.startsWith("class$")) {
526 crawl = true;
527 }
528 if (cd != null)
529 cd.field(last = new FieldDef(access_flags, name, pool[descriptor_index]
530 .toString()));
531 descriptors.add(Integer.valueOf(descriptor_index));
532 doAttributes(in, ElementType.FIELD, false);
533 }
534
535 //
536 // Check if we have to crawl the code to find
537 // the ldc(_w) <string constant> invokestatic Class.forName
538 // if so, calculate the method ref index so we
539 // can do this efficiently
540 //
541 if (crawl) {
542 forName = findMethodReference("java/lang/Class", "forName",
543 "(Ljava/lang/String;)Ljava/lang/Class;");
544 class$ = findMethodReference(className.getBinary(), "class$",
545 "(Ljava/lang/String;)Ljava/lang/Class;");
546 } else if (major == 48 ) {
547 forName = findMethodReference("java/lang/Class", "forName",
548 "(Ljava/lang/String;)Ljava/lang/Class;");
549 if (forName > 0) {
550 crawl = true;
551 class$ = findMethodReference(className.getBinary(), "class$",
552 "(Ljava/lang/String;)Ljava/lang/Class;");
553 }
554 }
555
556 // There are some serious changes in the
557 // class file format. So we do not do any crawling
558 // it has also become less important
559 if ( major >= JAVA.OpenJDK7.major )
560 crawl = false;
561
562 //
563 // Handle the methods
564 //
565 int methodCount = in.readUnsignedShort();
566 for (int i = 0; i < methodCount; i++) {
567 int access_flags = in.readUnsignedShort();
568 int name_index = in.readUnsignedShort();
569 int descriptor_index = in.readUnsignedShort();
570 descriptors.add(Integer.valueOf(descriptor_index));
571 String name = pool[name_index].toString();
572 String descriptor = pool[descriptor_index].toString();
573 if (cd != null) {
574 MethodDef mdef = new MethodDef(access_flags, name, descriptor);
575 last = mdef;
576 cd.method(mdef);
577 }
578
579 if ("<init>".equals(name)) {
580 doAttributes(in, ElementType.CONSTRUCTOR, crawl);
581 } else {
582 doAttributes(in, ElementType.METHOD, crawl);
583 }
584 }
585 if (cd != null)
586 cd.memberEnd();
587
588 doAttributes(in, ElementType.TYPE, false);
589
590 //
591 // Now iterate over all classes we found and
592 // parse those as well. We skip duplicates
593 //
594
595 for (int n : classes) {
596 String descr = (String) pool[n];
597
598 TypeRef clazz = analyzer.getTypeRef(descr);
599 referTo(clazz);
600 }
601
602 //
603 // Parse all the descriptors we found
604 //
605
606 for (Iterator<Integer> e = descriptors.iterator(); e.hasNext();) {
607 Integer index = e.next();
608 String prototype = (String) pool[index.intValue()];
609 if (prototype != null)
610 parseDescriptor(prototype);
611 else
612 System.err.println("Unrecognized descriptor: " + index);
613 }
614 Set<TypeRef> xref = this.xref;
615 reset();
616 return xref;
617 } finally {
618 if (cd != null)
619 cd.classEnd();
620 }
621 }
622
623 private void constantFloat(DataInputStream in, int poolIndex) throws IOException {
624 if (cd != null)
625 pool[poolIndex] = in.readFloat(); // ALU
626 else
627 in.skipBytes(4);
628 }
629
630 private void constantInteger(DataInputStream in, int poolIndex) throws IOException {
631 intPool[poolIndex] = in.readInt();
632 if (cd != null)
633 pool[poolIndex] = intPool[poolIndex];
634 }
635
636 protected void pool(Object[] pool, int[] intPool) {
637 }
638
639 /**
640 * @param in
641 * @param poolIndex
642 * @param tag
643 * @throws IOException
644 */
645 protected void nameAndType(DataInputStream in, int poolIndex, byte tag) throws IOException {
646 int name_index = in.readUnsignedShort();
647 int descriptor_index = in.readUnsignedShort();
648 descriptors.add(Integer.valueOf(descriptor_index));
649 pool[poolIndex] = new Assoc(tag, name_index, descriptor_index);
650 }
651
652 /**
653 * @param in
654 * @param poolIndex
655 * @param tag
656 * @throws IOException
657 */
658 private void methodRef(DataInputStream in, int poolIndex) throws IOException {
659 int class_index = in.readUnsignedShort();
660 int name_and_type_index = in.readUnsignedShort();
661 pool[poolIndex] = new Assoc((byte) 10, class_index, name_and_type_index);
662 }
663
664 /**
665 * @param in
666 * @param poolIndex
667 * @throws IOException
668 */
669 private void constantString(DataInputStream in, int poolIndex) throws IOException {
670 int string_index = in.readUnsignedShort();
671 intPool[poolIndex] = string_index;
672 }
673
674 /**
675 * @param in
676 * @param poolIndex
677 * @throws IOException
678 */
679 protected void constantClass(DataInputStream in, int poolIndex) throws IOException {
680 int class_index = in.readUnsignedShort();
681 classes.add(Integer.valueOf(class_index));
682 intPool[poolIndex] = class_index;
683 ClassConstant c = new ClassConstant(class_index);
684 pool[poolIndex] = c;
685 }
686
687 /**
688 * @param in
689 * @throws IOException
690 */
691 protected void constantDouble(DataInputStream in, int poolIndex) throws IOException {
692 if (cd != null)
693 pool[poolIndex] = in.readDouble();
694 else
695 in.skipBytes(8);
696 }
697
698 /**
699 * @param in
700 * @throws IOException
701 */
702 protected void constantLong(DataInputStream in, int poolIndex) throws IOException {
703 if (cd != null) {
704 pool[poolIndex] = in.readLong();
705 } else
706 in.skipBytes(8);
707 }
708
709 /**
710 * @param in
711 * @param poolIndex
712 * @throws IOException
713 */
714 protected void constantUtf8(DataInputStream in, int poolIndex) throws IOException {
715 // CONSTANT_Utf8
716
717 String name = in.readUTF();
718 pool[poolIndex] = name;
719 }
720
721 /**
722 * Find a method reference in the pool that points to the given class,
723 * methodname and descriptor.
724 *
725 * @param clazz
726 * @param methodname
727 * @param descriptor
728 * @return index in constant pool
729 */
730 private int findMethodReference(String clazz, String methodname, String descriptor) {
731 for (int i = 1; i < pool.length; i++) {
732 if (pool[i] instanceof Assoc) {
733 Assoc methodref = (Assoc) pool[i];
734 if (methodref.tag == 10) {
735 // Method ref
736 int class_index = methodref.a;
737 int class_name_index = intPool[class_index];
738 if (clazz.equals(pool[class_name_index])) {
739 int name_and_type_index = methodref.b;
740 Assoc name_and_type = (Assoc) pool[name_and_type_index];
741 if (name_and_type.tag == 12) {
742 // Name and Type
743 int name_index = name_and_type.a;
744 int type_index = name_and_type.b;
745 if (methodname.equals(pool[name_index])) {
746 if (descriptor.equals(pool[type_index])) {
747 return i;
748 }
749 }
750 }
751 }
752 }
753 }
754 }
755 return -1;
756 }
757
758 /**
759 * Called for each attribute in the class, field, or method.
760 *
761 * @param in
762 * The stream
763 * @throws Exception
764 */
765 private void doAttributes(DataInputStream in, ElementType member, boolean crawl)
766 throws Exception {
767 int attributesCount = in.readUnsignedShort();
768 for (int j = 0; j < attributesCount; j++) {
769 // skip name CONSTANT_Utf8 pointer
770 doAttribute(in, member, crawl);
771 }
772 }
773
774 /**
775 * Process a single attribute, if not recognized, skip it.
776 *
777 * @param in
778 * the data stream
779 * @throws Exception
780 */
781 private void doAttribute(DataInputStream in, ElementType member, boolean crawl)
782 throws Exception {
783 int attribute_name_index = in.readUnsignedShort();
784 String attributeName = (String) pool[attribute_name_index];
785 long attribute_length = in.readInt();
786 attribute_length &= 0xFFFFFFFF;
787 if ("Deprecated".equals(attributeName)) {
788 if (cd != null)
789 cd.deprecated();
790 } else if ("RuntimeVisibleAnnotations".equals(attributeName))
791 doAnnotations(in, member, RetentionPolicy.RUNTIME);
792 else if ("RuntimeVisibleParameterAnnotations".equals(attributeName))
793 doParameterAnnotations(in, member, RetentionPolicy.RUNTIME);
794 else if ("RuntimeInvisibleAnnotations".equals(attributeName))
795 doAnnotations(in, member, RetentionPolicy.CLASS);
796 else if ("RuntimeInvisibleParameterAnnotations".equals(attributeName))
797 doParameterAnnotations(in, member, RetentionPolicy.CLASS);
798 else if ("InnerClasses".equals(attributeName))
799 doInnerClasses(in);
800 else if ("EnclosingMethod".equals(attributeName))
801 doEnclosingMethod(in);
802 else if ("SourceFile".equals(attributeName))
803 doSourceFile(in);
804 else if ("Code".equals(attributeName) && crawl)
805 doCode(in);
806 else if ("Signature".equals(attributeName))
807 doSignature(in, member);
808 else if ("ConstantValue".equals(attributeName))
809 doConstantValue(in);
810 else {
811 if (attribute_length > 0x7FFFFFFF) {
812 throw new IllegalArgumentException("Attribute > 2Gb");
813 }
814 in.skipBytes((int) attribute_length);
815 }
816 }
817
818 /**
819 * <pre>
820 * EnclosingMethod_attribute {
821 * u2 attribute_name_index;
822 * u4 attribute_length;
823 * u2 class_index
824 * u2 method_index;
825 * }
826 * </pre>
827 *
828 *
829 * @param in
830 * @throws IOException
831 */
832 private void doEnclosingMethod(DataInputStream in) throws IOException {
833 int cIndex = in.readShort();
834 int mIndex = in.readShort();
835
836 if (cd != null) {
837 int nameIndex = intPool[cIndex];
838 TypeRef cName = analyzer.getTypeRef((String) pool[nameIndex]);
839
840 String mName = null;
841 String mDescriptor = null;
842
843 if (mIndex != 0) {
844 Assoc nameAndType = (Assoc) pool[mIndex];
845 mName = (String) pool[nameAndType.a];
846 mDescriptor = (String) pool[nameAndType.b];
847 }
848 cd.enclosingMethod(cName, mName, mDescriptor);
849 }
850 }
851
852 /**
853 * <pre>
854 * InnerClasses_attribute {
855 * u2 attribute_name_index;
856 * u4 attribute_length;
857 * u2 number_of_classes; {
858 * u2 inner_class_info_index;
859 * u2 outer_class_info_index;
860 * u2 inner_name_index;
861 * u2 inner_class_access_flags;
862 * } classes[number_of_classes];
863 * }
864 * </pre>
865 *
866 * @param in
867 * @throws Exception
868 */
869 private void doInnerClasses(DataInputStream in) throws Exception {
870 int number_of_classes = in.readShort();
871 for (int i = 0; i < number_of_classes; i++) {
872 int inner_class_info_index = in.readShort();
873 int outer_class_info_index = in.readShort();
874 int inner_name_index = in.readShort();
875 int inner_class_access_flags = in.readShort() & 0xFFFF;
876
877 if (cd != null) {
878 TypeRef innerClass = null;
879 TypeRef outerClass = null;
880 String innerName = null;
881
882 if (inner_class_info_index != 0) {
883 int nameIndex = intPool[inner_class_info_index];
884 innerClass = analyzer.getTypeRef((String) pool[nameIndex]);
885 }
886
887 if (outer_class_info_index != 0) {
888 int nameIndex = intPool[outer_class_info_index];
889 outerClass = analyzer.getTypeRef((String) pool[nameIndex]);
890 }
891
892 if (inner_name_index != 0)
893 innerName = (String) pool[inner_name_index];
894
895 cd.innerClass(innerClass, outerClass, innerName, inner_class_access_flags);
896 }
897 }
898 }
899
900 /**
901 * Handle a signature
902 *
903 * <pre>
904 * Signature_attribute {
905 * u2 attribute_name_index;
906 * u4 attribute_length;
907 * u2 signature_index;
908 * }
909 * </pre>
910 *
911 * @param member
912 */
913
914 void doSignature(DataInputStream in, ElementType member) throws IOException {
915 int signature_index = in.readUnsignedShort();
916 String signature = (String) pool[signature_index];
917
918 // s.println("Signature " + signature );
919
920 // // The type signature is kind of weird,
921 // // lets skip it for now. Seems to be some kind of
922 // // type variable name index but it does not seem to
923 // // conform to the language specification.
924 // if (member != ElementType.TYPE)
925 parseDescriptor(signature);
926
927 if (last != null)
928 last.signature = signature;
929
930 if (cd != null)
931 cd.signature(signature);
932 }
933
934 /**
935 * Handle a constant value call the data collector with it
936 */
937 void doConstantValue(DataInputStream in) throws IOException {
938 int constantValue_index = in.readUnsignedShort();
939 if (cd == null)
940 return;
941
942 Object object = pool[constantValue_index];
943 if (object == null)
944 object = pool[intPool[constantValue_index]];
945
946 last.constant = object;
947 cd.constant(object);
948 }
949
950 /**
951 * <pre>
952 * Code_attribute {
953 * u2 attribute_name_index;
954 * u4 attribute_length;
955 * u2 max_stack;
956 * u2 max_locals;
957 * u4 code_length;
958 * u1 code[code_length];
959 * u2 exception_table_length;
960 * { u2 start_pc;
961 * u2 end_pc;
962 * u2 handler_pc;
963 * u2 catch_type;
964 * } exception_table[exception_table_length];
965 * u2 attributes_count;
966 * attribute_info attributes[attributes_count];
967 * }
968 * </pre>
969 *
970 * @param in
971 * @param pool
972 * @throws Exception
973 */
974 private void doCode(DataInputStream in) throws Exception {
975 /* int max_stack = */in.readUnsignedShort();
976 /* int max_locals = */in.readUnsignedShort();
977 int code_length = in.readInt();
978 byte code[] = new byte[code_length];
979 in.readFully(code);
980 crawl(code);
981 int exception_table_length = in.readUnsignedShort();
982 in.skipBytes(exception_table_length * 8);
983 doAttributes(in, ElementType.METHOD, false);
984 }
985
986 /**
987 * We must find Class.forName references ...
988 *
989 * @param code
990 */
991 protected void crawl(byte[] code) {
992 ByteBuffer bb = ByteBuffer.wrap(code);
993 bb.order(ByteOrder.BIG_ENDIAN);
994 int lastReference = -1;
995
996 while (bb.remaining() > 0) {
997 int instruction = 0xFF & bb.get();
998 switch (instruction) {
999 case OpCodes.ldc:
1000 lastReference = 0xFF & bb.get();
1001 break;
1002
1003 case OpCodes.ldc_w:
1004 lastReference = 0xFFFF & bb.getShort();
1005 break;
1006
1007 case OpCodes.invokespecial: {
1008 int mref = 0xFFFF & bb.getShort();
1009 if (cd != null)
1010 getMethodDef(0, mref);
1011 break;
1012 }
1013
1014 case OpCodes.invokevirtual: {
1015 int mref = 0xFFFF & bb.getShort();
1016 if (cd != null)
1017 getMethodDef(0, mref);
1018 break;
1019 }
1020
1021 case OpCodes.invokeinterface: {
1022 int mref = 0xFFFF & bb.getShort();
1023 if (cd != null)
1024 getMethodDef(0, mref);
1025 break;
1026 }
1027
1028 case OpCodes.invokestatic: {
1029 int methodref = 0xFFFF & bb.getShort();
1030 if (cd != null)
1031 getMethodDef(0, methodref);
1032
1033 if ((methodref == forName || methodref == class$) && lastReference != -1
1034 && pool[intPool[lastReference]] instanceof String) {
1035 String fqn = (String) pool[intPool[lastReference]];
1036 if (!fqn.equals("class") && fqn.indexOf('.') > 0) {
1037 TypeRef clazz = analyzer.getTypeRefFromFQN(fqn);
1038 referTo(clazz);
1039 }
1040 lastReference = -1;
1041 }
1042 break;
1043 }
1044
1045 case OpCodes.tableswitch:
1046 // Skip to place divisible by 4
1047 while ((bb.position() & 0x3) != 0)
1048 bb.get();
1049 /* int deflt = */
1050 bb.getInt();
1051 int low = bb.getInt();
1052 int high = bb.getInt();
1053 try {
1054 bb.position(bb.position() + (high - low + 1) * 4);
1055 } catch (Exception e) {
1056 // TODO Auto-generated catch block
1057 e.printStackTrace();
1058 }
1059 lastReference = -1;
1060 break;
1061
1062 case OpCodes.lookupswitch:
1063 // Skip to place divisible by 4
1064 while ((bb.position() & 0x3) != 0)
1065 bb.get();
1066 /* deflt = */
1067 bb.getInt();
1068 int npairs = bb.getInt();
1069 bb.position(bb.position() + npairs * 8);
1070 lastReference = -1;
1071 break;
1072
1073 default:
1074 lastReference = -1;
1075 bb.position(bb.position() + OpCodes.OFFSETS[instruction]);
1076 }
1077 }
1078 }
1079
1080 private void doSourceFile(DataInputStream in) throws IOException {
1081 int sourcefile_index = in.readUnsignedShort();
1082 this.sourceFile = pool[sourcefile_index].toString();
1083 }
1084
1085 private void doParameterAnnotations(DataInputStream in, ElementType member,
1086 RetentionPolicy policy) throws IOException {
1087 int num_parameters = in.readUnsignedByte();
1088 for (int p = 0; p < num_parameters; p++) {
1089 if (cd != null)
1090 cd.parameter(p);
1091 doAnnotations(in, member, policy);
1092 }
1093 }
1094
1095 private void doAnnotations(DataInputStream in, ElementType member, RetentionPolicy policy)
1096 throws IOException {
1097 int num_annotations = in.readUnsignedShort(); // # of annotations
1098 for (int a = 0; a < num_annotations; a++) {
1099 if (cd == null)
1100 doAnnotation(in, member, policy, false);
1101 else {
1102 Annotation annotion = doAnnotation(in, member, policy, true);
1103 cd.annotation(annotion);
1104 }
1105 }
1106 }
1107
1108 private Annotation doAnnotation(DataInputStream in, ElementType member, RetentionPolicy policy,
1109 boolean collect) throws IOException {
1110 int type_index = in.readUnsignedShort();
1111 if (annotations == null)
1112 annotations = new HashSet<TypeRef>();
1113
1114 TypeRef tr = analyzer.getTypeRef(pool[type_index].toString());
1115 annotations.add(tr);
1116
1117 if (policy == RetentionPolicy.RUNTIME) {
1118 descriptors.add(Integer.valueOf(type_index));
1119 hasRuntimeAnnotations = true;
1120 } else {
1121 hasClassAnnotations = true;
1122 }
1123 TypeRef name = analyzer.getTypeRef((String) pool[type_index]);
1124 int num_element_value_pairs = in.readUnsignedShort();
1125 Map<String, Object> elements = null;
1126 for (int v = 0; v < num_element_value_pairs; v++) {
1127 int element_name_index = in.readUnsignedShort();
1128 String element = (String) pool[element_name_index];
1129 Object value = doElementValue(in, member, policy, collect);
1130 if (collect) {
1131 if (elements == null)
1132 elements = new LinkedHashMap<String, Object>();
1133 elements.put(element, value);
1134 }
1135 }
1136 if (collect)
1137 return new Annotation(name, elements, member, policy);
1138 else
1139 return null;
1140 }
1141
1142 private Object doElementValue(DataInputStream in, ElementType member, RetentionPolicy policy,
1143 boolean collect) throws IOException {
1144 char tag = (char) in.readUnsignedByte();
1145 switch (tag) {
1146 case 'B': // Byte
1147 case 'C': // Character
1148 case 'I': // Integer
1149 case 'S': // Short
1150 int const_value_index = in.readUnsignedShort();
1151 return intPool[const_value_index];
1152
1153 case 'D': // Double
1154 case 'F': // Float
1155 case 's': // String
1156 case 'J': // Long
1157 const_value_index = in.readUnsignedShort();
1158 return pool[const_value_index];
1159
1160 case 'Z': // Boolean
1161 const_value_index = in.readUnsignedShort();
1162 return pool[const_value_index] == null || pool[const_value_index].equals(0) ? false
1163 : true;
1164
1165 case 'e': // enum constant
1166 int type_name_index = in.readUnsignedShort();
1167 if (policy == RetentionPolicy.RUNTIME)
1168 descriptors.add(Integer.valueOf(type_name_index));
1169 int const_name_index = in.readUnsignedShort();
1170 return pool[const_name_index];
1171
1172 case 'c': // Class
1173 int class_info_index = in.readUnsignedShort();
1174 if (policy == RetentionPolicy.RUNTIME)
1175 descriptors.add(Integer.valueOf(class_info_index));
1176 return pool[class_info_index];
1177
1178 case '@': // Annotation type
1179 return doAnnotation(in, member, policy, collect);
1180
1181 case '[': // Array
1182 int num_values = in.readUnsignedShort();
1183 Object[] result = new Object[num_values];
1184 for (int i = 0; i < num_values; i++) {
1185 result[i] = doElementValue(in, member, policy, collect);
1186 }
1187 return result;
1188
1189 default:
1190 throw new IllegalArgumentException("Invalid value for Annotation ElementValue tag "
1191 + tag);
1192 }
1193 }
1194
1195 /**
1196 * Add a new package reference.
1197 *
1198 * @param packageRef
1199 * A '.' delimited package name
1200 */
1201 void referTo(TypeRef typeRef) {
1202 if (xref != null)
1203 xref.add(typeRef);
1204 if (typeRef.isPrimitive())
1205 return;
1206
1207 PackageRef packageRef = typeRef.getPackageRef();
1208 if (packageRef.isPrimitivePackage())
1209 return;
1210
1211 imports.add(packageRef);
1212 }
1213
1214 /**
1215 * This method parses a descriptor and adds the package of the descriptor to
1216 * the referenced packages.
1217 *
1218 * The syntax of the descriptor is:
1219 *
1220 * <pre>
1221 * descriptor ::= ( '(' reference * ')' )? reference
1222 * reference ::= 'L' classname ( '&lt;' references '&gt;' )? ';' | 'B' | 'Z' | ... | '+' | '-' | '['
1223 * </pre>
1224 *
1225 * This methods uses heavy recursion to parse the descriptor and a roving
1226 * pointer to limit the creation of string objects.
1227 *
1228 * @param descriptor
1229 * The to be parsed descriptor
1230 * @param rover
1231 * The pointer to start at
1232 */
1233 public void parseDescriptor(String descriptor) {
1234 // Some descriptors are weird, they start with a generic
1235 // declaration that contains ':', not sure what they mean ...
1236 int rover = 0;
1237 if (descriptor.charAt(0) == '<') {
1238 rover = parseFormalTypeParameters(descriptor, rover);
1239 }
1240
1241 if (descriptor.charAt(rover) == '(') {
1242 rover = parseReferences(descriptor, rover + 1, ')');
1243 rover++;
1244 }
1245 parseReferences(descriptor, rover, (char) 0);
1246 }
1247
1248 /**
1249 * Parse a sequence of references. A sequence ends with a given character or
1250 * when the string ends.
1251 *
1252 * @param descriptor
1253 * The whole descriptor.
1254 * @param rover
1255 * The index in the descriptor
1256 * @param delimiter
1257 * The end character or 0
1258 * @return the last index processed, one character after the delimeter
1259 */
1260 int parseReferences(String descriptor, int rover, char delimiter) {
1261 int r = rover;
1262 while (r < descriptor.length() && descriptor.charAt(r) != delimiter) {
1263 r = parseReference(descriptor, r);
1264 }
1265 return r;
1266 }
1267
1268 /**
1269 * Parse a single reference. This can be a single character or an object
1270 * reference when it starts with 'L'.
1271 *
1272 * @param descriptor
1273 * The descriptor
1274 * @param rover
1275 * The place to start
1276 * @return The return index after the reference
1277 */
1278 int parseReference(String descriptor, int rover) {
1279 int r = rover;
1280 char c = descriptor.charAt(r);
1281 while (c == '[')
1282 c = descriptor.charAt(++r);
1283
1284 if (c == '<') {
1285 r = parseReferences(descriptor, r + 1, '>');
1286 } else if (c == 'T') {
1287 // Type variable name
1288 r++;
1289 while (descriptor.charAt(r) != ';')
1290 r++;
1291 } else if (c == 'L') {
1292 StringBuilder sb = new StringBuilder();
1293 r++;
1294 while ((c = descriptor.charAt(r)) != ';') {
1295 if (c == '<') {
1296 r = parseReferences(descriptor, r + 1, '>');
1297 } else
1298 sb.append(c);
1299 r++;
1300 }
1301 TypeRef ref = analyzer.getTypeRef(sb.toString());
1302 if (cd != null)
1303 cd.addReference(ref);
1304
1305 referTo(ref);
1306 } else {
1307 if ("+-*BCDFIJSZV".indexOf(c) < 0)
1308 ;// System.err.println("Should not skip: " + c);
1309 }
1310
1311 // this skips a lot of characters
1312 // [, *, +, -, B, etc.
1313
1314 return r + 1;
1315 }
1316
1317 /**
1318 * FormalTypeParameters
1319 *
1320 * @param descriptor
1321 * @param index
1322 * @return
1323 */
1324 private int parseFormalTypeParameters(String descriptor, int index) {
1325 index++;
1326 while (descriptor.charAt(index) != '>') {
1327 // Skip IDENTIFIER
1328 index = descriptor.indexOf(':', index) + 1;
1329 if (index == 0)
1330 throw new IllegalArgumentException("Expected IDENTIFIER: " + descriptor);
1331
1332 // ClassBound? InterfaceBounds
1333
1334 char c = descriptor.charAt(index);
1335
1336 // Class Bound?
1337 if (c == 'L' || c == 'T') {
1338 index = parseReference(descriptor, index); // class reference
1339 c = descriptor.charAt(index);
1340 }
1341
1342 // Interface Bounds
1343 while (c == ':') {
1344 index++;
1345 index = parseReference(descriptor, index);
1346 c = descriptor.charAt(index);
1347 } // for each interface
1348
1349 } // for each formal parameter
1350 return index + 1; // skip >
1351 }
1352
1353 public Set<PackageRef> getReferred() {
1354 return imports;
1355 }
1356
1357 public String getAbsolutePath() {
1358 return path;
1359 }
1360
1361 public String getSourceFile() {
1362 return sourceFile;
1363 }
1364
1365 /**
1366 * .class construct for different compilers
1367 *
1368 * sun 1.1 Detect static variable class$com$acme$MyClass 1.2 " 1.3 " 1.4 "
1369 * 1.5 ldc_w (class) 1.6 "
1370 *
1371 * eclipse 1.1 class$0, ldc (string), invokestatic Class.forName 1.2 " 1.3 "
1372 * 1.5 ldc (class) 1.6 "
1373 *
1374 * 1.5 and later is not an issue, sun pre 1.5 is easy to detect the static
1375 * variable that decodes the class name. For eclipse, the class$0 gives away
1376 * we have a reference encoded in a string.
1377 * compilerversions/compilerversions.jar contains test versions of all
1378 * versions/compilers.
1379 */
1380
1381 public void reset() {
1382 pool = null;
1383 intPool = null;
1384 xref = null;
1385 classes = null;
1386 descriptors = null;
1387 }
1388
1389 public boolean is(QUERY query, Instruction instr, Analyzer analyzer) throws Exception {
1390 switch (query) {
1391 case ANY:
1392 return true;
1393
1394 case NAMED:
1395 if (instr.matches(getClassName().getDottedOnly()))
1396 return !instr.isNegated();
1397 return false;
1398
1399 case VERSION:
1400 String v = major + "." + minor;
1401 if (instr.matches(v))
1402 return !instr.isNegated();
1403 return false;
1404
1405 case IMPLEMENTS:
1406 for (int i = 0; interfaces != null && i < interfaces.length; i++) {
1407 if (instr.matches(interfaces[i].getDottedOnly()))
1408 return !instr.isNegated();
1409 }
1410 break;
1411
1412 case EXTENDS:
1413 if (zuper == null)
1414 return false;
1415
1416 if (instr.matches(zuper.getDottedOnly()))
1417 return !instr.isNegated();
1418 break;
1419
1420 case PUBLIC:
1421 return Modifier.isPublic(accessx);
1422
1423 case CONCRETE:
1424 return !Modifier.isAbstract(accessx);
1425
1426 case ANNOTATED:
1427 if (annotations == null)
1428 return false;
1429
1430 for (TypeRef annotation : annotations) {
1431 if (instr.matches(annotation.getFQN()))
1432 return !instr.isNegated();
1433 }
1434
1435 return false;
1436
1437 case RUNTIMEANNOTATIONS:
1438 return hasClassAnnotations;
1439 case CLASSANNOTATIONS:
1440 return hasClassAnnotations;
1441
1442 case ABSTRACT:
1443 return Modifier.isAbstract(accessx);
1444
1445 case IMPORTS:
1446 for (PackageRef imp : imports) {
1447 if (instr.matches(imp.getFQN()))
1448 return !instr.isNegated();
1449 }
1450 }
1451
1452 if (zuper == null)
1453 return false;
1454
1455 Clazz clazz = analyzer.findClass(zuper);
1456 if (clazz == null)
1457 return false;
1458
1459 return clazz.is(query, instr, analyzer);
1460 }
1461
1462 public String toString() {
1463 return className.getFQN();
1464 }
1465
1466 /**
1467 * Called when crawling the byte code and a method reference is found
1468 *
1469 */
1470 void getMethodDef(int access, int methodRefPoolIndex) {
1471 if ( methodRefPoolIndex == 0)
1472 return;
1473
1474 Object o = pool[methodRefPoolIndex];
1475 if (o != null && o instanceof Assoc) {
1476 Assoc assoc = (Assoc) o;
1477 if (assoc.tag == 10) {
1478 int string_index = intPool[assoc.a];
1479 TypeRef className = analyzer.getTypeRef((String) pool[string_index]);
1480 int name_and_type_index = assoc.b;
1481 Assoc name_and_type = (Assoc) pool[name_and_type_index];
1482 if (name_and_type.tag == 12) {
1483 // Name and Type
1484 int name_index = name_and_type.a;
1485 int type_index = name_and_type.b;
1486 String method = (String) pool[name_index];
1487 String descriptor = (String) pool[type_index];
1488 cd.referenceMethod(access, className, method, descriptor);
1489 } else
1490 throw new IllegalArgumentException(
1491 "Invalid class file (or parsing is wrong), assoc is not type + name (12)");
1492 } else
1493 throw new IllegalArgumentException(
1494 "Invalid class file (or parsing is wrong), Assoc is not method ref! (10)");
1495 } else
1496 throw new IllegalArgumentException(
1497 "Invalid class file (or parsing is wrong), Not an assoc at a method ref");
1498 }
1499
1500 public boolean isPublic() {
1501 return Modifier.isPublic(accessx);
1502 }
1503
1504 public boolean isProtected() {
1505 return Modifier.isProtected(accessx);
1506 }
1507
1508 public boolean isEnum() {
1509 return zuper != null && zuper.getBinary().equals("java/lang/Enum");
1510 }
1511
1512 public JAVA getFormat() {
1513 return JAVA.format(major);
1514
1515 }
1516
1517 public static String objectDescriptorToFQN(String string) {
1518 if (string.startsWith("L") && string.endsWith(";"))
1519 return string.substring(1, string.length() - 1).replace('/', '.');
1520
1521 switch (string.charAt(0)) {
1522 case 'V':
1523 return "void";
1524 case 'B':
1525 return "byte";
1526 case 'C':
1527 return "char";
1528 case 'I':
1529 return "int";
1530 case 'S':
1531 return "short";
1532 case 'D':
1533 return "double";
1534 case 'F':
1535 return "float";
1536 case 'J':
1537 return "long";
1538 case 'Z':
1539 return "boolean";
1540 case '[': // Array
1541 return objectDescriptorToFQN(string.substring(1)) + "[]";
1542 }
1543 throw new IllegalArgumentException("Invalid type character in descriptor " + string);
1544 }
1545
1546 public static String unCamel(String id) {
1547 StringBuilder out = new StringBuilder();
1548 for (int i = 0; i < id.length(); i++) {
1549 char c = id.charAt(i);
1550 if (c == '_' || c == '$' || c == '.') {
1551 if (out.length() > 0 && !Character.isWhitespace(out.charAt(out.length() - 1)))
1552 out.append(' ');
1553 continue;
1554 }
1555
1556 int n = i;
1557 while (n < id.length() && Character.isUpperCase(id.charAt(n))) {
1558 n++;
1559 }
1560 if (n == i)
1561 out.append(id.charAt(i));
1562 else {
1563 boolean tolower = (n - i) == 1;
1564 if (i > 0 && !Character.isWhitespace(out.charAt(out.length() - 1)))
1565 out.append(' ');
1566
1567 for (; i < n;) {
1568 if (tolower)
1569 out.append(Character.toLowerCase(id.charAt(i)));
1570 else
1571 out.append(id.charAt(i));
1572 i++;
1573 }
1574 i--;
1575 }
1576 }
1577 if (id.startsWith("."))
1578 out.append(" *");
1579 out.replace(0, 1, Character.toUpperCase(out.charAt(0)) + "");
1580 return out.toString();
1581 }
1582
1583 public boolean isInterface() {
1584 return Modifier.isInterface(accessx);
1585 }
1586
1587 public boolean isAbstract() {
1588 return Modifier.isAbstract(accessx);
1589 }
1590
1591 public int getAccess() {
1592 if (innerAccess == -1)
1593 return accessx;
1594 else
1595 return innerAccess;
1596 }
1597
1598 public TypeRef getClassName() {
1599 return className;
1600 }
1601
1602 /**
1603 * To provide an enclosing instance
1604 *
1605 * @param access
1606 * @param name
1607 * @param descriptor
1608 * @return
1609 */
1610 public MethodDef getMethodDef(int access, String name, String descriptor) {
1611 return new MethodDef(access, name, descriptor);
1612 }
1613
1614 public TypeRef getSuper() {
1615 return zuper;
1616 }
1617
1618 public String getFQN() {
1619 return className.getFQN();
1620 }
1621
1622 public TypeRef[] getInterfaces() {
1623 return interfaces;
1624 }
1625
1626 public void setInnerAccess(int access) {
1627 innerAccess = access;
1628 }
1629
1630 public boolean isFinal() {
1631 return Modifier.isFinal(accessx);
1632 }
1633
1634 public void setDeprecated(boolean b) {
1635 deprecated = b;
1636 }
1637
1638 public boolean isDeprecated() {
1639 return deprecated;
1640 }
1641
1642 public boolean isAnnotation() {
1643 return (accessx & ACC_ANNOTATION) != 0;
1644 }
1645}