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