blob: 93fe6ac4ee116e851922bd54568d5aaff002730e [file] [log] [blame]
Stuart McCullochd00f9712009-07-13 10:06:47 +00001/* Copyright 2006 aQute SARL
2 * Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
3package aQute.lib.osgi;
4
5import java.io.*;
6import java.nio.*;
7import java.util.*;
8
9public class Clazz {
10 public static enum QUERY {
11 IMPLEMENTS, EXTENDS, IMPORTS, NAMED, ANY, VERSION
12 };
13
14 static protected class Assoc {
15 Assoc(byte tag, int a, int b) {
16 this.tag = tag;
17 this.a = a;
18 this.b = b;
19 }
20
21 byte tag;
22 int a;
23 int b;
24 }
25
26 final static byte SkipTable[] = { 0, // 0 non existent
27 -1, // 1 CONSTANT_utf8 UTF 8, handled in
28 // method
29 -1, // 2
30 4, // 3 CONSTANT_Integer
31 4, // 4 CONSTANT_Float
32 8, // 5 CONSTANT_Long (index +=2!)
33 8, // 6 CONSTANT_Double (index +=2!)
34 -1, // 7 CONSTANT_Class
35 2, // 8 CONSTANT_String
36 4, // 9 CONSTANT_FieldRef
37 4, // 10 CONSTANT_MethodRef
38 4, // 11 CONSTANT_InterfaceMethodRef
39 4, // 12 CONSTANT_NameAndType
40 };
41
42 String className;
43 Object pool[];
44 int intPool[];
45 Map<String, Map<String, String>> imports = new HashMap<String, Map<String, String>>();
46 String path;
47
48 // static String type = "([BCDFIJSZ\\[]|L[^<>]+;)";
49 // static Pattern descriptor = Pattern.compile("\\(" + type + "*\\)(("
50 // + type + ")|V)");
51 int minor = 0;
52 int major = 0;
53
54 String sourceFile;
55 Set<String> xref;
56 Set<Integer> classes;
57 Set<Integer> descriptors;
58 int forName = 0;
59 int class$ = 0;
60 String[] interfaces;
61 String zuper;
62 ClassDataCollector cd = new ClassDataCollector() {
63 public void addReference(
64 String token) {
65 }
66
67 public void classBegin(
68 int access,
69 String name) {
70 }
71
72 public void classEnd() {
73 }
74
75 public void extendsClass(
76 String name) {
77 }
78
79 public void field(
80 int access,
81 String descriptor) {
82 }
83
84 public void implementsInterfaces(
85 String[] name) {
86 }
87
88 public void method(
89 int access,
90 String descriptor) {
91 }
92 };
93
94 public Clazz(String path) {
95 this.path = path;
96 }
97
98 public Clazz(String path, InputStream in) throws IOException {
99 this.path = path;
100 DataInputStream din = new DataInputStream(in);
101 parseClassFile(din);
102 din.close();
103 }
104
105 Set<String> parseClassFile(DataInputStream in) throws IOException {
106
107 xref = new HashSet<String>();
108 classes = new HashSet<Integer>();
109 descriptors = new HashSet<Integer>();
110
111 boolean crawl = false; // Crawl the byte code
112 int magic = in.readInt();
113 if (magic != 0xCAFEBABE)
114 throw new IOException("Not a valid class file (no CAFEBABE header)");
115
116 minor = in.readUnsignedShort(); // minor version
117 major = in.readUnsignedShort(); // major version
118 int count = in.readUnsignedShort();
119 pool = new Object[count];
120 intPool = new int[count];
121
122 process: for (int poolIndex = 1; poolIndex < count; poolIndex++) {
123 byte tag = in.readByte();
124 switch (tag) {
125 case 0:
126 break process;
127 case 1:
128 constantUtf8(in, poolIndex);
129 break;
130
131 // For some insane optimization reason are
132 // the long and the double two entries in the
133 // constant pool. See 4.4.5
134 case 5:
135 constantLong(in, poolIndex);
136 poolIndex++;
137 break;
138
139 case 6:
140 constantDouble(in, poolIndex);
141 poolIndex++;
142 break;
143
144 case 7:
145 constantClass(in, poolIndex);
146 break;
147
148 case 8:
149 constantString(in, poolIndex);
150 break;
151
152 case 10: // Method ref
153 methodRef(in, poolIndex);
154 break;
155
156 // Name and Type
157 case 12:
158 nameAndType(in, poolIndex, tag);
159 break;
160
161 // We get the skip count for each record type
162 // from the SkipTable. This will also automatically
163 // abort when
164 default:
165 if (tag == 2)
166 throw new IOException("Invalid tag " + tag);
167 in.skipBytes(SkipTable[tag]);
168 break;
169 }
170 }
171
172 pool(pool, intPool);
173 /*
174 * Parse after the constant pool, code thanks to Hans Christian
175 * Falkenberg
176 */
177
178 int access_flags = in.readUnsignedShort(); // access
179 int this_class = in.readUnsignedShort();
180 className = (String) pool[intPool[this_class]];
181 cd.classBegin(access_flags, className);
182
183 try {
184
185 int super_class = in.readUnsignedShort();
186 zuper = (String) pool[intPool[super_class]];
187 if (zuper != null) {
188 addReference(zuper);
189 cd.extendsClass(zuper);
190 }
191
192 int interfacesCount = in.readUnsignedShort();
193 if (interfacesCount > 0) {
194 interfaces = new String[interfacesCount];
195 for (int i = 0; i < interfacesCount; i++)
196 interfaces[i] = (String) pool[intPool[in
197 .readUnsignedShort()]];
198 cd.implementsInterfaces(interfaces);
199 }
200
201 int fieldsCount = in.readUnsignedShort();
202 for (int i = 0; i < fieldsCount; i++) {
203 access_flags = in.readUnsignedShort(); // skip access flags
204 int name_index = in.readUnsignedShort();
205 int descriptor_index = in.readUnsignedShort();
206
207 // Java prior to 1.5 used a weird
208 // static variable to hold the com.X.class
209 // result construct. If it did not find it
210 // it would create a variable class$com$X
211 // that would be used to hold the class
212 // object gotten with Class.forName ...
213 // Stupidly, they did not actively use the
214 // class name for the field type, so bnd
215 // would not see a reference. We detect
216 // this case and add an artificial descriptor
217 String name = pool[name_index].toString(); // name_index
218 if (name.startsWith("class$")) {
219 crawl = true;
220 }
221 cd.field(access_flags, pool[descriptor_index].toString());
222 descriptors.add(new Integer(descriptor_index));
223 doAttributes(in, false);
224 }
225
226 //
227 // Check if we have to crawl the code to find
228 // the ldc(_w) <string constant> invokestatic Class.forName
229 // if so, calculate the method ref index so we
230 // can do this efficiently
231 //
232 if (crawl) {
233 forName = findMethod("java/lang/Class", "forName",
234 "(Ljava/lang/String;)Ljava/lang/Class;");
235 class$ = findMethod(className, "class$",
236 "(Ljava/lang/String;)Ljava/lang/Class;");
237 }
238
239 //
240 // Handle the methods
241 //
242 int methodCount = in.readUnsignedShort();
243 for (int i = 0; i < methodCount; i++) {
244 access_flags = in.readUnsignedShort();
245 /* int name_index = */in.readUnsignedShort();
246 int descriptor_index = in.readUnsignedShort();
247 // String s = (String) pool[name_index];
248 descriptors.add(new Integer(descriptor_index));
249 if ( pool[descriptor_index ] == null ) {
250 System.out.println("Value in pool=null, descriptor_index=" +descriptor_index);
251 }
252 cd.method(access_flags, pool[descriptor_index].toString());
253 doAttributes(in, crawl);
254 }
255
256 doAttributes(in, false);
257
258 //
259 // Now iterate over all classes we found and
260 // parse those as well. We skip duplicates
261 //
262
263 for (Iterator<Integer> e = classes.iterator(); e.hasNext();) {
264 int class_index = e.next().intValue();
265 doClassReference((String) pool[class_index]);
266 }
267
268 //
269 // Parse all the descriptors we found
270 //
271
272 for (Iterator<Integer> e = descriptors.iterator(); e.hasNext();) {
273 Integer index = e.next();
274 String prototype = (String) pool[index.intValue()];
275 if (prototype != null)
276 parseDescriptor(prototype);
277 else
278 System.err.println("Unrecognized descriptor: " + index);
279 }
280 Set<String> xref = this.xref;
281 reset();
282 return xref;
283 } finally {
284 cd.classEnd();
285 }
286 }
287
288 protected void pool(Object[] pool, int[] intPool) {
289 }
290
291 /**
292 * @param in
293 * @param poolIndex
294 * @param tag
295 * @throws IOException
296 */
297 protected void nameAndType(DataInputStream in, int poolIndex, byte tag)
298 throws IOException {
299 int name_index = in.readUnsignedShort();
300 int descriptor_index = in.readUnsignedShort();
301 descriptors.add(new Integer(descriptor_index));
302 pool[poolIndex] = new Assoc(tag, name_index, descriptor_index);
303 }
304
305 /**
306 * @param in
307 * @param poolIndex
308 * @param tag
309 * @throws IOException
310 */
311 private void methodRef(DataInputStream in, int poolIndex)
312 throws IOException {
313 int class_index = in.readUnsignedShort();
314 int name_and_type_index = in.readUnsignedShort();
315 pool[poolIndex] = new Assoc((byte) 10, class_index, name_and_type_index);
316 }
317
318 /**
319 * @param in
320 * @param poolIndex
321 * @throws IOException
322 */
323 private void constantString(DataInputStream in, int poolIndex)
324 throws IOException {
325 int string_index = in.readUnsignedShort();
326 intPool[poolIndex] = string_index;
327 }
328
329 /**
330 * @param in
331 * @param poolIndex
332 * @throws IOException
333 */
334 protected void constantClass(DataInputStream in, int poolIndex)
335 throws IOException {
336 int class_index = in.readUnsignedShort();
337 classes.add(new Integer(class_index));
338 intPool[poolIndex] = class_index;
339 }
340
341 /**
342 * @param in
343 * @throws IOException
344 */
345 protected void constantDouble(DataInputStream in, int poolIndex)
346 throws IOException {
347 in.skipBytes(8);
348 }
349
350 /**
351 * @param in
352 * @throws IOException
353 */
354 protected void constantLong(DataInputStream in, int poolIndex)
355 throws IOException {
356 in.skipBytes(8);
357 }
358
359 /**
360 * @param in
361 * @param poolIndex
362 * @throws IOException
363 */
364 protected void constantUtf8(DataInputStream in, int poolIndex)
365 throws IOException {
366 // CONSTANT_Utf8
367
368 String name = in.readUTF();
369 xref.add(name);
370 pool[poolIndex] = name;
371 }
372
373 /**
374 * Find a method reference in the pool that points to the given class,
375 * methodname and descriptor.
376 *
377 * @param clazz
378 * @param methodname
379 * @param descriptor
380 * @return index in constant pool
381 */
382 private int findMethod(String clazz, String methodname, String descriptor) {
383 for (int i = 1; i < pool.length; i++) {
384 if (pool[i] instanceof Assoc) {
385 Assoc methodref = (Assoc) pool[i];
386 if (methodref.tag == 10) {
387 // Method ref
388 int class_index = methodref.a;
389 int class_name_index = intPool[class_index];
390 if (clazz.equals(pool[class_name_index])) {
391 int name_and_type_index = methodref.b;
392 Assoc name_and_type = (Assoc) pool[name_and_type_index];
393 if (name_and_type.tag == 12) {
394 // Name and Type
395 int name_index = name_and_type.a;
396 int type_index = name_and_type.b;
397 if (methodname.equals(pool[name_index])) {
398 if (descriptor.equals(pool[type_index])) {
399 return i;
400 }
401 }
402 }
403 }
404 }
405 }
406 }
407 return -1;
408 }
409
410 private void doClassReference(String next) {
411 if (next != null) {
412 String normalized = normalize(next);
413 if (normalized != null) {
414 cd.addReference(next);
415 String pack = getPackage(normalized);
416 packageReference(pack);
417 }
418 } else
419 throw new IllegalArgumentException("Invalid class, parent=");
420 }
421
422 /**
423 * Called for each attribute in the class, field, or method.
424 *
425 * @param in
426 * The stream
427 * @throws IOException
428 */
429 private void doAttributes(DataInputStream in, boolean crawl)
430 throws IOException {
431 int attributesCount = in.readUnsignedShort();
432 for (int j = 0; j < attributesCount; j++) {
433 // skip name CONSTANT_Utf8 pointer
434 doAttribute(in, crawl);
435 }
436 }
437
438 /**
439 * Process a single attribute, if not recognized, skip it.
440 *
441 * @param in
442 * the data stream
443 * @throws IOException
444 */
445 private void doAttribute(DataInputStream in, boolean crawl)
446 throws IOException {
447 int attribute_name_index = in.readUnsignedShort();
448 String attributeName = (String) pool[attribute_name_index];
449 long attribute_length = in.readInt();
450 attribute_length &= 0xFFFFFFFF;
451 if ("RuntimeVisibleAnnotations".equals(attributeName))
452 doAnnotations(in);
453 else if ("RuntimeVisibleParameterAnnotations".equals(attributeName))
454 doParameterAnnotations(in);
455 else if ("SourceFile".equals(attributeName))
456 doSourceFile(in);
457 else if ("Code".equals(attributeName) && crawl)
458 doCode(in);
459// else if ("Signature".equals(attributeName))
460// doSignature(in);
461 else {
462 if (attribute_length > 0x7FFFFFFF) {
463 throw new IllegalArgumentException("Attribute > 2Gb");
464 }
465 in.skipBytes((int) attribute_length);
466 }
467 }
468
469 /**
470 * Handle a signature
471 *
472 * <pre>
473 * Signature_attribute {
474 * u2 attribute_name_index;
475 * u4 attribute_length;
476 * u2 signature_index;
477 * }
478 * </pre>
479 */
480
481// void doSignature(DataInputStream in) throws IOException {
482// /*int attribute_length =*/ in.readInt();
483// int signature_index = in.readUnsignedShort();
484// String signature = (String) pool[signature_index];
485// }
486
487 /**
488 * <pre>
489 * Code_attribute {
490 * u2 attribute_name_index;
491 * u4 attribute_length;
492 * u2 max_stack;
493 * u2 max_locals;
494 * u4 code_length;
495 * u1 code[code_length];
496 * u2 exception_table_length;
497 * { u2 start_pc;
498 * u2 end_pc;
499 * u2 handler_pc;
500 * u2 catch_type;
501 * } exception_table[exception_table_length];
502 * u2 attributes_count;
503 * attribute_info attributes[attributes_count];
504 * }
505 * </pre>
506 *
507 * @param in
508 * @param pool
509 * @throws IOException
510 */
511 private void doCode(DataInputStream in) throws IOException {
512 /* int max_stack = */in.readUnsignedShort();
513 /* int max_locals = */in.readUnsignedShort();
514 int code_length = in.readInt();
515 byte code[] = new byte[code_length];
516 in.readFully(code);
517 crawl(code);
518 int exception_table_length = in.readUnsignedShort();
519 in.skipBytes(exception_table_length * 8);
520 doAttributes(in, false);
521 }
522
523 /**
524 * We must find Class.forName references ...
525 *
526 * @param code
527 */
528 protected void crawl(byte[] code) {
529 ByteBuffer bb = ByteBuffer.wrap(code);
530 bb.order(ByteOrder.BIG_ENDIAN);
531 int lastReference = -1;
532
533 while (bb.remaining() > 0) {
534 int instruction = 0xFF & bb.get();
535 switch (instruction) {
536 case OpCodes.ldc:
537 lastReference = 0xFF & bb.get();
538 break;
539
540 case OpCodes.ldc_w:
541 lastReference = 0xFFFF & bb.getShort();
542 break;
543
544 case OpCodes.invokestatic:
545 int methodref = 0xFFFF & bb.getShort();
546 if ((methodref == forName || methodref == class$)
547 && lastReference != -1
548 && pool[intPool[lastReference]] instanceof String) {
549 String clazz = (String) pool[intPool[lastReference]];
550 doClassReference(clazz.replace('.', '/'));
551 }
552 break;
553
554 case OpCodes.tableswitch:
555 // Skip to place divisible by 4
556 while ((bb.position() & 0x3) != 0)
557 bb.get();
558 /* int deflt = */
559 bb.getInt();
560 int low = bb.getInt();
561 int high = bb.getInt();
562 bb.position(bb.position() + (high - low + 1) * 4);
563 lastReference = -1;
564 break;
565
566 case OpCodes.lookupswitch:
567 // Skip to place divisible by 4
568 while ((bb.position() & 0x3) != 0)
569 bb.get();
570 /* deflt = */
571 bb.getInt();
572 int npairs = bb.getInt();
573 bb.position(bb.position() + npairs * 8);
574 lastReference = -1;
575 break;
576
577 default:
578 lastReference = -1;
579 bb.position(bb.position() + OpCodes.OFFSETS[instruction]);
580 }
581 }
582 }
583
584 private void doSourceFile(DataInputStream in) throws IOException {
585 int sourcefile_index = in.readUnsignedShort();
586 this.sourceFile = pool[sourcefile_index].toString();
587 }
588
589 private void doParameterAnnotations(DataInputStream in) throws IOException {
590 int num_parameters = in.readUnsignedByte();
591 for (int p = 0; p < num_parameters; p++) {
592 int num_annotations = in.readUnsignedShort(); // # of annotations
593 for (int a = 0; a < num_annotations; a++) {
594 doAnnotation(in);
595 }
596 }
597 }
598
599 private void doAnnotations(DataInputStream in) throws IOException {
600 int num_annotations = in.readUnsignedShort(); // # of annotations
601 for (int a = 0; a < num_annotations; a++) {
602 doAnnotation(in);
603 }
604 }
605
606 private void doAnnotation(DataInputStream in) throws IOException {
607 int type_index = in.readUnsignedShort();
608 descriptors.add(new Integer(type_index));
609 int num_element_value_pairs = in.readUnsignedShort();
610 for (int v = 0; v < num_element_value_pairs; v++) {
611 /* int element_name_index = */in.readUnsignedShort();
612 doElementValue(in);
613 }
614 }
615
616 private void doElementValue(DataInputStream in) throws IOException {
617 int tag = in.readUnsignedByte();
618 switch (tag) {
619 case 'B':
620 case 'C':
621 case 'D':
622 case 'F':
623 case 'I':
624 case 'J':
625 case 'S':
626 case 'Z':
627 case 's':
628 /* int const_value_index = */
629 in.readUnsignedShort();
630 break;
631
632 case 'e':
633 int type_name_index = in.readUnsignedShort();
634 descriptors.add(new Integer(type_name_index));
635 /* int const_name_index = */
636 in.readUnsignedShort();
637 break;
638
639 case 'c':
640 int class_info_index = in.readUnsignedShort();
641 descriptors.add(new Integer(class_info_index));
642 break;
643
644 case '@':
645 doAnnotation(in);
646 break;
647
648 case '[':
649 int num_values = in.readUnsignedShort();
650 for (int i = 0; i < num_values; i++) {
651 doElementValue(in);
652 }
653 break;
654
655 default:
656 throw new IllegalArgumentException(
657 "Invalid value for Annotation ElementValue tag " + tag);
658 }
659 }
660
661 void packageReference(String pack) {
662 if (pack.indexOf('<') >= 0)
663 System.out.println("Oops: " + pack);
664 if (!imports.containsKey(pack))
665 imports.put(pack, new LinkedHashMap<String, String>());
666 }
667
668 public void parseDescriptor(String prototype) {
669 if (prototype.startsWith("("))
670 parseMethodDescriptor(prototype);
671 else
672 addReference(prototype);
673 }
674
675 void parseMethodDescriptor(String prototype) {
676 int last = prototype.indexOf(')');
677 if (last < 0)
678 throw new IllegalArgumentException(
679 "Invalid method descriptor in class file: " + className
680 + " " + prototype);
681
682 for (int i = 1; i < last; i++) {
683 if (prototype.charAt(i) == 'L') {
684 int next = prototype.indexOf(';', i);
685 addReference(prototype.substring(i, next));
686 i = next;
687 }
688 }
689 addReference(prototype.substring(last + 1));
690 }
691
692 private void addReference(String token) {
693 cd.addReference(token);
694 while (token.startsWith("["))
695 token = token.substring(1);
696
697 if (token.startsWith("L")) {
698 String clazz = normalize(token.substring(1));
699 if (clazz.startsWith("java/"))
700 return;
701 String pack = getPackage(clazz);
702 packageReference(pack);
703 }
704 }
705
706 static String normalize(String s) {
707 if (s.startsWith("[L"))
708 return normalize(s.substring(2));
709 if (s.startsWith("["))
710 if (s.length() == 2)
711 return null;
712 else
713 return normalize(s.substring(1));
714 if (s.endsWith(";"))
715 return normalize(s.substring(0, s.length() - 1));
716 return s + ".class";
717 }
718
719 public static String getPackage(String clazz) {
720 int n = clazz.lastIndexOf('/');
721 if (n < 0)
722 return ".";
723 return clazz.substring(0, n).replace('/', '.');
724 }
725
726 public Map<String, Map<String, String>> getReferred() {
727 return imports;
728 }
729
730 String getClassName() {
731 return className;
732 }
733
734 public String getPath() {
735 return path;
736 }
737
738 public Set<String> xref(InputStream in) throws IOException {
739 DataInputStream din = new DataInputStream(in);
740 Set<String> set = parseClassFile(din);
741 din.close();
742 return set;
743 }
744
745 public String getSourceFile() {
746 return sourceFile;
747 }
748
749 /**
750 * .class construct for different compilers
751 *
752 * sun 1.1 Detect static variable class$com$acme$MyClass 1.2 " 1.3 " 1.4 "
753 * 1.5 ldc_w (class) 1.6 "
754 *
755 * eclipse 1.1 class$0, ldc (string), invokestatic Class.forName 1.2 " 1.3 "
756 * 1.5 ldc (class) 1.6 "
757 *
758 * 1.5 and later is not an issue, sun pre 1.5 is easy to detect the static
759 * variable that decodes the class name. For eclipse, the class$0 gives away
760 * we have a reference encoded in a string.
761 * compilerversions/compilerversions.jar contains test versions of all
762 * versions/compilers.
763 */
764
765 public void reset() {
766 pool = null;
767 intPool = null;
768 xref = null;
769 classes = null;
770 descriptors = null;
771 }
772
773 public boolean is(QUERY query, Instruction instr,
774 Map<String, Clazz> classspace) {
775 switch (query) {
776 case ANY:
777 return true;
778
779 case NAMED:
780 if (instr.matches(getClassName()))
781 return !instr.isNegated();
782 return false;
783
784 case VERSION:
785 String v = major + "/" + minor;
786 if (instr.matches(v))
787 return !instr.isNegated();
788 return false;
789
790 case IMPLEMENTS:
791 for (int i = 0; interfaces != null && i < interfaces.length; i++) {
792 if (instr.matches(interfaces[i]))
793 return !instr.isNegated();
794 }
795 break;
796 case EXTENDS:
797 if (zuper == null)
798 return false;
799
800 if (instr.matches(zuper))
801 return !instr.isNegated();
802 break;
803
804 case IMPORTS:
805 for (String imp : imports.keySet()) {
806 if (instr.matches(imp.replace('.', '/')))
807 return !instr.isNegated();
808 }
809 }
810
811 if (zuper == null || classspace == null)
812 return false;
813
814 Clazz clazz = classspace.get(zuper);
815 if (clazz == null)
816 return false;
817
818 return clazz.is(query, instr, classspace);
819 }
820
821 public String toString() {
822 return getFQN();
823 }
824
825 public String getFQN() {
826 return getClassName().replace('/', '.');
827 }
828
829 public void setClassDataCollector(ClassDataCollector cd) {
830 this.cd = cd;
831 }
832}