001: package xtc.lang;
002:
003: import java.io.ByteArrayOutputStream;
004: import java.io.DataInputStream;
005: import java.io.DataOutputStream;
006: import java.io.FileInputStream;
007: import java.io.FileOutputStream;
008: import java.io.IOException;
009: import java.io.File;
010:
011: /**
012: * A class file post-processor to rewrite a class file for the remapped line
013: * number table or to append a SMAP attribute to the class file. This
014: * post-processor is a utility for the source-to-source transformation tools
015: * such as Jeannie. This post-processor provides the following two modes.
016: *
017: * First, stratify mode with -stratify command line flag rewrites the
018: * "LineNumberTable" attribute for each method in the class file, and modify the
019: * "SourceFile" attribute. This has an advantage of working well with both the
020: * current java VM and debugger. However, this does not work if the number of
021: * the orignal source files is more than one.
022: *
023: * Second, flatten mode with -flatten command line flag appends an SMAP to the
024: * end of the class file as "SourceDebugExtension." This is a general and
025: * powerful way to provide remapping information for the source-to-source
026: * transformation. We found that this SMAP works well with the current java
027: * debuggers (SUN jdb 1.6 and eclipse 3.2 Java debugger). However, JVMs in the
028: * SUN JDK 1.6 and IBM J9 1.5.0 do not use SMAP when they dump stack trace.
029: *
030: * By default, this remapper operates in the flatten mode. The first solution
031: * with the SMAP will be the right way in the long run as JVM supports better
032: * stack dump messgage with SMAP.
033: *
034: * @author Byeongcheol Lee
035: *
036: */
037: public class ClassfileSourceRemapper {
038:
039: /**
040: * Class file attribute names related to the line number table remapping. For
041: * further information, look at the Java virtual machine specification.
042: * http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
043: */
044: private static final String ANAME_SMAP = "SourceDebugExtension";
045: private static final String ANAME_LINENUMBERTABLE = "LineNumberTable";
046: private static final String ANAME_CODE = "Code";
047: private static final String ANAME_SOURCEFILE = "SourceFile";
048:
049: /**
050: * Constant pool tags for java class file. For further information, look at
051: * the Java virtual machine specification.
052: * http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html
053: */
054: public static final int CP_Class = 7;
055: public static final int CP_Fieldref = 9;
056: public static final int CP_Methodref = 10;
057: public static final int CP_InterfaceMethodref = 11;
058: public static final int CP_String = 8;
059: public static final int CP_Integer = 3;
060: public static final int CP_Float = 4;
061: public static final int CP_Long = 5;
062: public static final int CP_Double = 6;
063: public static final int CP_NameAndType = 12;
064: public static final int CP_Utf8 = 1;
065:
066: /**
067: * A line number entry in the "LineNumberTable" attribute.
068: */
069: private static class LineNumberTableEntry {
070: int start_pc;
071: int line_number;
072:
073: LineNumberTableEntry(int pc, int line) {
074: assert (pc <= 65535) & (line <= 65535);
075: start_pc = pc;
076: line_number = line;
077: }
078:
079: int getStartPC() {
080: return start_pc;
081: }
082:
083: int getLineNumber() {
084: return line_number;
085: }
086: }
087:
088: /**
089: * A command line usage.
090: *
091: * @param umsg An additional message to explain what is wrong.
092: */
093: private static void usage(String umsg) {
094: String msg = "usage: ClassfileSourceRemapper {-stratify|-flatten} [java source file] [class file]";
095: System.err.println(msg + "\n" + umsg);
096: System.exit(-1);
097: }
098:
099: /**
100: * Run the class file remapping process by taking a user command line.
101: *
102: * @param args The command line arguments.
103: */
104: public static void main(String[] args) {
105:
106: //default option vlaue
107: String javaSource = null;
108: String classFile = null;
109: boolean bStaratify = false;
110: boolean bFlatten = true;
111:
112: for (int i = 0; i < args.length; i++) {
113: String arg = args[i];
114: if (arg.equals("-stratify")) {
115: bFlatten = false;
116: bStaratify = true;
117: } else if (arg.equals("-flatten")) {
118: bFlatten = true;
119: bStaratify = false;
120: } else if (javaSource == null) {
121: if (new File(arg).canRead()) {
122: javaSource = arg;
123: } else {
124: usage("can not read " + arg);
125: }
126: } else if (classFile == null) {
127: if (new File(arg).canRead()) {
128: classFile = arg;
129: } else {
130: usage("can not write " + arg);
131: }
132: }
133: }
134:
135: //validate options
136: if (bStaratify && bFlatten) {
137: usage("specify only one of the -stratify and -flatten");
138: }
139: if (javaSource == null) {
140: usage("specify [java source file]");
141: }
142: if (classFile == null) {
143: usage("spcify [class file]");
144: }
145:
146: try {
147:
148: //get source-to-source map
149: SourceMapExtractor smap = new SourceMapExtractor(javaSource);
150: smap.genSMAP();
151:
152: //validate source-to-source map
153: if (smap.getNumberOfInputSourceFiles() < 1) {
154: System.err
155: .println("no source-to-source mapping found in the "
156: + javaSource);
157: System.exit(-1);
158: }
159: boolean bUseJSR45 = bStaratify;
160: if (!bUseJSR45) {
161: if (smap.getNumberOfInputSourceFiles() >= 2) {
162: System.err
163: .println("more than one input source files in the "
164: + javaSource);
165: System.err
166: .println("This tool can not process class file with -flatten option");
167: System.err
168: .println("Please consider using -stratify option");
169: System.exit(-1);
170: }
171: }
172:
173: //update class file for line number remapping..
174: ClassfileSourceRemapper lineRemapper = new ClassfileSourceRemapper(
175: smap, classFile, classFile, bUseJSR45);
176: lineRemapper.doRemapping();
177:
178: } catch (IOException e) {
179: System.err
180: .println("failed in remapping line number information");
181: e.printStackTrace();
182: }
183: }
184:
185: /**
186: * A flag to rewrite line number table in the class file.
187: */
188: private final boolean bRemapLineNumberTable;
189:
190: /**
191: * A flag to add SMAP table to the class file.
192: */
193: private final boolean bInsertSMAPTable;
194:
195: /**
196: * An input class file name.
197: */
198: private final String inputClassFile;
199:
200: /**
201: * An output class file name.
202: */
203: private final String outputClassFile;
204:
205: /**
206: * A source-to-source remapping information.
207: */
208: private final SourceMapExtractor smap;
209:
210: /**
211: * An input stream for class file.
212: */
213: private DataInputStream is;
214:
215: /**
216: * An output stream for the post processed class file.
217: */
218: private DataOutputStream os;
219:
220: /**
221: * An in-memory stream to hold the content of postprocessed class file.
222: */
223: private ByteArrayOutputStream bos;
224:
225: /**
226: * A constant pool index of the injected UTF8 string for the input source file
227: * name in the output class file. "sourcefile_index" field of the "SourceFile"
228: * class attribute will be redirected to reference this new source file name.
229: */
230: private int constantPoolEntryIndexForInputSourceFileName = -1;
231:
232: /**
233: * A constant pool index of the injected UTF8 string for SMAP attribute name
234: * in the output class file. This class file post-process will append to the
235: * end of class file a "SourceDebugExtension" class attribute, and
236: * "attribute_name_index" field of the debug class attribute will point to
237: * this new UTF8 constant pool entry.
238: */
239: private int constantPoolEntryIndexForSMAP = -1;
240:
241: /**
242: * A constant pool entries only for UTF8 string. For an constrant pool index
243: * number, i, UTF8ConstantPoolEntries[i] is non-null if the constant poll
244: * entry is UTF8 string. Otherwise, it's null.
245: */
246: private String[] UTF8ConstantPoolEntries;
247:
248: /**
249: * @param smap A Source-to-source mapping.
250: * @param inputClassFile An input class file.
251: * @param outputClassFile An output class file.
252: * @param bUseJSR45 Wheather or not to use JSR45 SMAP for remapping.
253: */
254: public ClassfileSourceRemapper(SourceMapExtractor smap,
255: String inputClassFile, String outputClassFile,
256: boolean bUseJSR45) {
257: this .smap = smap;
258: this .inputClassFile = inputClassFile;
259: this .outputClassFile = outputClassFile;
260:
261: if (bUseJSR45) {
262: bRemapLineNumberTable = false;
263: bInsertSMAPTable = true;
264: } else {
265: bRemapLineNumberTable = true;
266: bInsertSMAPTable = false;
267: }
268: }
269:
270: /**
271: * Define remapping of a line number table for a method.
272: *
273: * @param old An old line number table.
274: * @param mname A method name.
275: * @param mdesc A method signagure.
276: * @return A new byte code to source line mapping.
277: */
278: private LineNumberTableEntry[] adjustLineNumberTable(
279: LineNumberTableEntry[] old, String mname, String mdesc) {
280: assert (old != null);
281:
282: LineNumberTableEntry[] newtable = new LineNumberTableEntry[old.length];
283:
284: for (int i = 0; i < newtable.length; i++) {
285: LineNumberTableEntry oldEntry = old[i];
286: int pc = oldEntry.getStartPC();
287: int javaLine = oldEntry.getLineNumber();
288: int jniLine = smap.getSingleSourceLine(javaLine);
289: newtable[i] = new LineNumberTableEntry(pc, jniLine);
290: }
291:
292: return newtable;
293: }
294:
295: /**
296: * Update the line number mapping in the class file.
297: */
298: public void doRemapping() throws IOException {
299:
300: assert (smap != null) && (is == null) && (os == null)
301: && (bos == null);
302:
303: //read input class file and generate modified class file in the
304: //memory
305: try {
306: is = new DataInputStream(
307: new FileInputStream(inputClassFile));
308: bos = new ByteArrayOutputStream();
309: os = new DataOutputStream(bos);
310: processClass();
311: is.close();
312: } catch (IOException e) {
313: System.err.println("error while reading:" + inputClassFile);
314: throw e;
315: }
316:
317: //flush memory to the output class file
318: try {
319: byte cbytes[] = bos.toByteArray();
320: bos.close();
321: os.close();
322: assert (cbytes != null) && (cbytes.length > 0);
323: FileOutputStream fos = new FileOutputStream(outputClassFile);
324: fos.write(cbytes);
325: fos.close();
326: } catch (IOException e) {
327: System.err.println("error while writing to: "
328: + outputClassFile);
329: throw e;
330: }
331:
332: is = null;
333: os = null;
334: bos = null;
335: constantPoolEntryIndexForInputSourceFileName = -1;
336: constantPoolEntryIndexForSMAP = -1;
337: UTF8ConstantPoolEntries = null;
338: }
339:
340: /**
341: * Begin the line number remapping process.
342: */
343: private void processClass() throws IOException {
344: int magic = processInt();
345: assert magic == 0xcafebabe;
346:
347: processUnsignedShort(); //minor
348: processUnsignedShort(); //major
349: int numCP = is.readUnsignedShort();
350:
351: assert numCP >= 1;
352: UTF8ConstantPoolEntries = new String[numCP];
353: os.writeShort(numCP + 1);
354: for (int i = 1; i < numCP; i++) {
355: int tag = processCP(i);
356:
357: //Long, and double take two constant pool entry
358: if (tag == CP_Double || tag == CP_Long) {
359: i++;
360: }
361: }
362:
363: int next_cp_index = numCP;
364: if (bRemapLineNumberTable) {
365: //inject UTF8 JNI source file name
366: final String jniSourceFile = smap.getSingleSourceFileName();
367: final byte[] utf8InputSourceFileName = jniSourceFile
368: .getBytes("UTF8");
369: constantPoolEntryIndexForInputSourceFileName = next_cp_index++;
370: os.writeByte(CP_Utf8);
371: os.writeShort(utf8InputSourceFileName.length);
372: os.write(utf8InputSourceFileName);
373: }
374: if (bInsertSMAPTable) {
375: constantPoolEntryIndexForSMAP = next_cp_index++;
376: byte[] smap_section_name = ANAME_SMAP.getBytes("UTF8");
377: os.writeByte(1);
378: os.writeShort(smap_section_name.length);
379: os.write(smap_section_name);
380: }
381:
382: int flag = processShort(); //flags
383: int this _class = processUnsignedShort(); //this_class
384: int super _class = processUnsignedShort(); //super_class
385:
386: //interfaces
387: int numInterfaces = processUnsignedShort();
388: if (numInterfaces > 0) {
389: processBytes(numInterfaces * 2);
390: }
391:
392: //fields
393: int numFields = processUnsignedShort();
394: for (int i = 0; i < numFields; i++) {
395: //access flags(u2), name_index(u2), descriptor_index(u2)
396: processBytes(6);
397:
398: //attributes_count(u2)
399: int numFieldAttributes = processUnsignedShort();
400: for (int j = 0; j < numFieldAttributes; j++) {
401: processAttribute();
402: }
403: }
404:
405: //methods
406: int numMethods = processUnsignedShort();
407: for (int i = 0; i < numMethods; i++) {
408: processMethodInfo(i);
409: }
410:
411: //class attributes
412: int numClassAttributes = is.readUnsignedShort();
413: if (bInsertSMAPTable) {
414: os.writeShort(numClassAttributes + 1);
415: } else {
416: os.writeShort(numClassAttributes);
417: }
418:
419: for (int i = 0; i < numClassAttributes; i++) {
420:
421: int name_index = processUnsignedShort();
422: String attr_name = UTF8ConstantPoolEntries[name_index];
423: assert (attr_name != null);
424:
425: if (bRemapLineNumberTable
426: && attr_name.equals(ANAME_SOURCEFILE)) {
427: processSourceFile();
428: } else {
429: processBytes(processInt());
430: }
431: }
432:
433: if (bInsertSMAPTable) {
434: //append SMAP attribute
435: byte[] smap_content = smap.toStringInSMAPFormat().getBytes(
436: "UTF8");
437: os.writeShort(constantPoolEntryIndexForSMAP);
438: os.writeInt(smap_content.length);
439: os.write(smap_content);
440: }
441:
442: assert is.read() == -1;
443:
444: return;
445: }
446:
447: /**
448: * Handle a constant pool entyr at an index.
449: *
450: * @param index A constant pool index number.
451: * @return A tag value for the constant pool entry.
452: */
453: private int processCP(int index) throws IOException {
454: byte tag = processByte();
455: switch (tag) {
456: case CP_Class:
457: processBytes(2);
458: break;
459:
460: case CP_Fieldref:
461: case CP_Methodref:
462: case CP_InterfaceMethodref:
463: processBytes(2);
464: processBytes(2);
465: break;
466:
467: case CP_String:
468: processBytes(2);
469: break;
470:
471: case CP_Integer:
472: case CP_Float:
473: processBytes(4);
474: break;
475:
476: case CP_Long:
477: case CP_Double:
478: processBytes(8);
479: break;
480:
481: case CP_NameAndType:
482: processBytes(4);
483: break;
484:
485: case CP_Utf8: {
486: int len = processUnsignedShort();
487: final byte[] utf8string = processBytes(len);
488: UTF8ConstantPoolEntries[index] = new String(utf8string,
489: "UTF8");
490: break;
491: }
492: default:
493: assert false;
494: break;
495: }
496: return tag;
497: }
498:
499: /**
500: * Process i's method info.
501: *
502: * @param i An index number of the current method info to be processed.
503: */
504: private void processMethodInfo(int i) throws IOException {
505:
506: // access flags(u2)
507: int flag = processUnsignedShort();
508:
509: //name_index(u2)
510: int name_index = processUnsignedShort();
511: String mname = UTF8ConstantPoolEntries[name_index];
512:
513: //descriptor_index(u2)
514: int desc_index = processUnsignedShort();
515: String mdesc = UTF8ConstantPoolEntries[desc_index];
516:
517: //attributes_count(u2)
518: int numMethodAttributes = processUnsignedShort();
519: if (bRemapLineNumberTable) {
520: for (int j = 0; j < numMethodAttributes; j++) {
521: int attr_name_index = processUnsignedShort();
522: String attr_name = UTF8ConstantPoolEntries[attr_name_index];
523: if (attr_name.equals(ANAME_CODE)) {
524: processMethodCodeAttribute(attr_name_index, mname,
525: mdesc);
526: } else {
527: processBytes(processInt());
528: }
529: }
530: } else {
531: for (int j = 0; j < numMethodAttributes; j++) {
532: processAttribute();
533: }
534: }
535: }
536:
537: /**
538: * Handle a line number table for each method.
539: *
540: * @param mname A method name.
541: * @param mdesc A method descriptor.
542: */
543: private void processLineNumberTable(String mname, String mdesc)
544: throws IOException {
545:
546: //read old table
547: int ilen = is.readInt();
548: int inum_table_entries = is.readUnsignedShort();
549: LineNumberTableEntry[] oldTable = new LineNumberTableEntry[inum_table_entries];
550: for (int i = 0; i < inum_table_entries; i++) {
551: int pc = is.readUnsignedShort();
552: int line = is.readUnsignedShort();
553: oldTable[i] = new LineNumberTableEntry(pc, line);
554: }
555:
556: //write new table
557: LineNumberTableEntry[] newTable = adjustLineNumberTable(
558: oldTable, mname, mdesc);
559: assert (newTable.length == oldTable.length);
560:
561: int onum_table_entries = newTable.length;
562: int oAttributelen = 2 + onum_table_entries * (2 + 2);
563: os.writeInt(oAttributelen);
564: os.writeShort(onum_table_entries);
565: for (int i = 0; i < onum_table_entries; i++) {
566: int pc = newTable[i].getStartPC();
567: int line = newTable[i].getLineNumber();
568: os.writeShort(pc);
569: os.writeShort(line);
570: }
571: }
572:
573: /**
574: * Handle SourceFile attribute.
575: */
576: private void processSourceFile() throws IOException {
577:
578: //read old attribute
579: int ilen = is.readInt();
580: int iSourceFileIndex = is.readUnsignedShort();
581: String oldSourceFileName = UTF8ConstantPoolEntries[iSourceFileIndex];
582:
583: assert (ilen == 2)
584: && constantPoolEntryIndexForInputSourceFileName >= 1;
585:
586: //write new attribute
587: os.writeInt(2);
588: os.writeShort(constantPoolEntryIndexForInputSourceFileName);
589: }
590:
591: /**
592: * Handle a method code attribute.
593: *
594: * @param attr_name_index A constant pool index for UTF8 attribute index.
595: * @param mname A method name.
596: * @param mdesc A method signature.
597: */
598: private void processMethodCodeAttribute(int attr_name_index,
599: String mname, String mdesc) throws IOException {
600:
601: assert UTF8ConstantPoolEntries[attr_name_index]
602: .equals(ANAME_CODE);
603:
604: //u4 attribute_length
605: int codeAttrLen = processInt();
606:
607: //u2 max_stack
608: int max_stack = processUnsignedShort();
609:
610: //u2 max_locals
611: int max_locals = processUnsignedShort();
612:
613: //u4 code_length;u1 code[code_length];
614: int code_length = processInt();
615: processBytes(code_length);
616:
617: //u2 exception_table_length; exception_table[exception_table_length]
618: int ex_length = processUnsignedShort();
619: processBytes(8 * ex_length);
620:
621: int nested_attr_count = processUnsignedShort();
622: for (int i = 0; i < nested_attr_count; i++) {
623: int name_index = processUnsignedShort();
624: String attr_name = UTF8ConstantPoolEntries[name_index];
625: if (attr_name.equals(ANAME_LINENUMBERTABLE)) {
626: processLineNumberTable(mname, mdesc);
627: } else {
628: processBytes(processInt());
629: }
630: }
631: }
632:
633: /**
634: * Skip the current attribute section.
635: */
636: private void processAttribute() throws IOException {
637: processBytes(2);
638: int i = processInt();
639: processBytes(i);
640: }
641:
642: /**
643: * Skip specified number of bytes.
644: *
645: * @param i A number of bytes to skip.
646: * @return A byte array.
647: */
648: private byte[] processBytes(int i) throws IOException {
649: byte buf[] = new byte[i];
650: int j = is.read(buf);
651: assert j == i;
652:
653: os.write(buf);
654: return buf;
655: }
656:
657: /**
658: * Skip 4 bytes for intger value.
659: *
660: * @return An integer value.
661: */
662: private int processInt() throws IOException {
663: int i = is.readInt();
664: os.writeInt(i);
665: return i;
666: }
667:
668: /**
669: * Skip two bytes for the signed short.
670: *
671: * @return A signed short value.
672: */
673: private short processShort() throws IOException {
674: short word0 = is.readShort();
675: os.writeShort(word0);
676: return word0;
677: }
678:
679: /**
680: * Skip 2 bytes for unsigned shour value.
681: *
682: * @return An unsighed short value.
683: */
684: private int processUnsignedShort() throws IOException {
685: int i = is.readUnsignedShort();
686: os.writeShort(i);
687: return i;
688: }
689:
690: /**
691: * Skip 1 byte.
692: *
693: * @return A byte value.
694: */
695: private byte processByte() throws IOException {
696: byte byte0 = is.readByte();
697: os.writeByte(byte0);
698: return byte0;
699: }
700: }
|