View Javadoc

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