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