001: /*
002: * Janino - An embedded Java[TM] compiler
003: *
004: * Copyright (c) 2006, Arno Unkrig
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: * 2. Redistributions in binary form must reproduce the above
014: * copyright notice, this list of conditions and the following
015: * disclaimer in the documentation and/or other materials
016: * provided with the distribution.
017: * 3. The name of the author may not be used to endorse or promote
018: * products derived from this software without specific prior
019: * written permission.
020: *
021: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
022: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
023: * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
024: * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
025: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
026: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
027: * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
028: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
029: * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
030: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
031: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
032: */
033:
034: package org.codehaus.janino;
035:
036: import java.io.*;
037: import java.util.*;
038:
039: import org.codehaus.janino.util.ClassFile;
040:
041: /**
042: * The context of the compilation of a function (constructor or method). Manages generation of
043: * byte code, the exception table, generation of line number tables, allocation of local variables,
044: * determining of stack size and local variable table size and flow analysis.
045: */
046: public class CodeContext {
047: private static final boolean DEBUG = false;
048:
049: private static final int INITIAL_SIZE = 100;
050: private static final int SIZE_INCREMENT = 128;
051:
052: private/*final*/ClassFile classFile;
053:
054: private short maxStack;
055: private short maxLocals;
056: private byte[] code;
057: private/*final*/Offset beginning;
058: private/*final*/Inserter end;
059: private Inserter currentInserter;
060: private/*final*/List exceptionTableEntries; // ExceptionTableEntry
061:
062: /**
063: * Create an empty "Code" attribute.
064: */
065: public CodeContext(ClassFile classFile) {
066: this .classFile = classFile;
067:
068: this .maxStack = 0;
069: this .maxLocals = 0;
070: this .code = new byte[CodeContext.INITIAL_SIZE];
071: this .beginning = new Offset();
072: this .end = new Inserter();
073: this .currentInserter = this .end;
074: this .exceptionTableEntries = new ArrayList();
075:
076: this .beginning.offset = 0;
077: this .end.offset = 0;
078: this .beginning.next = this .end;
079: this .end.prev = this .beginning;
080: }
081:
082: public ClassFile getClassFile() {
083: return this .classFile;
084: }
085:
086: /**
087: * Allocate space for a local variable of the given size (1 or 2)
088: * on the local variable array.
089: *
090: * As a side effect, the "max_locals" field of the "Code" attribute
091: * is updated.
092: *
093: * The only way to deallocate local variables is to
094: * {@link #saveLocalVariables()} and later {@link
095: * #restoreLocalVariables()}.
096: */
097: public short allocateLocalVariable(short size // 1 or 2
098: ) {
099: short res = this .localVariableArrayLength;
100: this .localVariableArrayLength += size;
101: if (this .localVariableArrayLength > this .maxLocals) {
102: this .maxLocals = this .localVariableArrayLength;
103: }
104:
105: return res;
106: }
107:
108: /**
109: * Remember the current size of the local variables array.
110: */
111: public void saveLocalVariables() {
112: this .savedLocalVariableArrayLengths.push(new Short(
113: this .localVariableArrayLength));
114: }
115:
116: /**
117: * Restore the previous size of the local variables array.
118: */
119: public void restoreLocalVariables() {
120: this .localVariableArrayLength = ((Short) this .savedLocalVariableArrayLengths
121: .pop()).shortValue();
122: }
123:
124: private static final byte UNEXAMINED = -1;
125: private static final byte INVALID_OFFSET = -2;
126:
127: /**
128: *
129: * @param dos
130: * @param lineNumberTableAttributeNameIndex 0 == don't generate a "LineNumberTable" attribute
131: * @throws IOException
132: */
133: protected void storeCodeAttributeBody(DataOutputStream dos,
134: short lineNumberTableAttributeNameIndex) throws IOException {
135: dos.writeShort(this .maxStack); // max_stack
136: dos.writeShort(this .maxLocals); // max_locals
137: dos.writeInt(0xffff & this .end.offset); // code_length
138: dos.write(this .code, 0, 0xffff & this .end.offset); // code
139: dos.writeShort(this .exceptionTableEntries.size()); // exception_table_length
140: for (int i = 0; i < this .exceptionTableEntries.size(); ++i) { // exception_table
141: ExceptionTableEntry exceptionTableEntry = (ExceptionTableEntry) this .exceptionTableEntries
142: .get(i);
143: dos.writeShort(exceptionTableEntry.startPC.offset);
144: dos.writeShort(exceptionTableEntry.endPC.offset);
145: dos.writeShort(exceptionTableEntry.handlerPC.offset);
146: dos.writeShort(exceptionTableEntry.catchType);
147: }
148:
149: List attributes = new ArrayList(); // ClassFile.AttributeInfo
150:
151: // Add "LineNumberTable" attribute.
152: if (lineNumberTableAttributeNameIndex != 0) {
153: List lnt = new ArrayList();
154: for (Offset o = this .beginning; o != null; o = o.next) {
155: if (o instanceof LineNumberOffset) {
156: lnt
157: .add(new ClassFile.LineNumberTableAttribute.Entry(
158: o.offset,
159: ((LineNumberOffset) o).lineNumber));
160: }
161: }
162: ClassFile.LineNumberTableAttribute.Entry[] lnte = (ClassFile.LineNumberTableAttribute.Entry[]) lnt
163: .toArray(new ClassFile.LineNumberTableAttribute.Entry[lnt
164: .size()]);
165: attributes.add(new ClassFile.LineNumberTableAttribute(
166: lineNumberTableAttributeNameIndex, // attributeNameIndex
167: lnte // lineNumberTableEntries
168: ));
169: }
170:
171: dos.writeShort(attributes.size()); // attributes_count
172: for (Iterator it = attributes.iterator(); it.hasNext();) { // attributes;
173: ClassFile.AttributeInfo attribute = (ClassFile.AttributeInfo) it
174: .next();
175: attribute.store(dos);
176: }
177: }
178:
179: /**
180: * Checks the code for consistency; updates the "maxStack" member.
181: *
182: * Notice: On inconsistencies, a "RuntimeException" is thrown (KLUDGE).
183: */
184: public void flowAnalysis(String functionName) {
185: byte[] stackSizes = new byte[0xffff & this .end.offset];
186: Arrays.fill(stackSizes, CodeContext.UNEXAMINED);
187:
188: // Analyze flow from offset zero.
189: this .flowAnalysis(functionName, this .code, // code
190: 0xffff & this .end.offset, // codeSize
191: 0, // offset
192: 0, // stackSize
193: stackSizes // stackSizes
194: );
195:
196: // Analyze flow from exception handler entry points.
197: int analyzedExceptionHandlers = 0;
198: while (analyzedExceptionHandlers != this .exceptionTableEntries
199: .size()) {
200: for (int i = 0; i < this .exceptionTableEntries.size(); ++i) {
201: ExceptionTableEntry exceptionTableEntry = (ExceptionTableEntry) this .exceptionTableEntries
202: .get(i);
203: if (stackSizes[0xffff & exceptionTableEntry.startPC.offset] != CodeContext.UNEXAMINED) {
204: this
205: .flowAnalysis(
206: functionName,
207: this .code, // code
208: 0xffff & this .end.offset, // codeSize
209: 0xffff & exceptionTableEntry.handlerPC.offset, // offset
210: stackSizes[0xffff & exceptionTableEntry.startPC.offset] + 1, // stackSize
211: stackSizes // stackSizes
212: );
213: ++analyzedExceptionHandlers;
214: }
215: }
216: }
217:
218: // Check results and determine maximum stack size.
219: this .maxStack = 0;
220: for (int i = 0; i < stackSizes.length; ++i) {
221: byte ss = stackSizes[i];
222: if (ss == CodeContext.UNEXAMINED) {
223: if (CodeContext.DEBUG) {
224: System.out.println(functionName
225: + ": Unexamined code at offset " + i);
226: return;
227: } else {
228: throw new RuntimeException(functionName
229: + ": Unexamined code at offset " + i);
230: }
231: }
232: if (ss > this .maxStack)
233: this .maxStack = ss;
234: }
235: }
236:
237: private void flowAnalysis(String functionName, byte[] code, // Bytecode
238: int codeSize, // Size
239: int offset, // Current PC
240: int stackSize, // Stack size on entry
241: byte[] stackSizes // Stack sizes in code
242: ) {
243: for (;;) {
244: if (CodeContext.DEBUG)
245: System.out.println("Offset = " + offset
246: + ", stack size = " + stackSize);
247:
248: // Check current bytecode offset.
249: if (offset < 0 || offset >= codeSize)
250: throw new RuntimeException(functionName
251: + ": Offset out of range");
252:
253: // Have we hit an area that has already been analyzed?
254: byte css = stackSizes[offset];
255: if (css == stackSize)
256: return; // OK.
257: if (css == CodeContext.INVALID_OFFSET)
258: throw new RuntimeException(functionName
259: + ": Invalid offset");
260: if (css != CodeContext.UNEXAMINED) {
261: if (CodeContext.DEBUG) {
262: System.err.println(functionName
263: + ": Operand stack inconsistent at offset "
264: + offset + ": Previous size " + css
265: + ", now " + stackSize);
266: return;
267: } else {
268: throw new RuntimeException(functionName
269: + ": Operand stack inconsistent at offset "
270: + offset + ": Previous size " + css
271: + ", now " + stackSize);
272: }
273: }
274: stackSizes[offset] = (byte) stackSize;
275:
276: // Analyze current opcode.
277: byte opcode = code[offset];
278: int operandOffset = offset + 1;
279: short props;
280: if (opcode == Opcode.WIDE) {
281: opcode = code[operandOffset++];
282: props = Opcode.WIDE_OPCODE_PROPERTIES[0xff & opcode];
283: } else {
284: props = Opcode.OPCODE_PROPERTIES[0xff & opcode];
285: }
286: if (props == Opcode.INVALID_OPCODE)
287: throw new RuntimeException(functionName
288: + ": Invalid opcode " + (0xff & opcode)
289: + " at offset " + offset);
290:
291: switch (props & Opcode.SD_MASK) {
292:
293: case Opcode.SD_M4:
294: case Opcode.SD_M3:
295: case Opcode.SD_M2:
296: case Opcode.SD_M1:
297: case Opcode.SD_P0:
298: case Opcode.SD_P1:
299: case Opcode.SD_P2:
300: stackSize += (props & Opcode.SD_MASK) - Opcode.SD_P0;
301: break;
302:
303: case Opcode.SD_0:
304: stackSize = 0;
305: break;
306:
307: case Opcode.SD_GETFIELD:
308: --stackSize;
309: /* FALL THROUGH */
310: case Opcode.SD_GETSTATIC:
311: stackSize += this
312: .determineFieldSize((short) ((code[operandOffset] << 8) + (0xff & code[operandOffset + 1])));
313: break;
314:
315: case Opcode.SD_PUTFIELD:
316: --stackSize;
317: /* FALL THROUGH */
318: case Opcode.SD_PUTSTATIC:
319: stackSize -= this
320: .determineFieldSize((short) ((code[operandOffset] << 8) + (0xff & code[operandOffset + 1])));
321: break;
322:
323: case Opcode.SD_INVOKEVIRTUAL:
324: case Opcode.SD_INVOKESPECIAL:
325: case Opcode.SD_INVOKEINTERFACE:
326: --stackSize;
327: /* FALL THROUGH */
328: case Opcode.SD_INVOKESTATIC:
329: stackSize -= this
330: .determineArgumentsSize((short) ((code[operandOffset] << 8) + (0xff & code[operandOffset + 1])));
331: break;
332:
333: case Opcode.SD_MULTIANEWARRAY:
334: stackSize -= code[operandOffset + 2] - 1;
335: break;
336:
337: default:
338: throw new RuntimeException(functionName
339: + ": Invalid stack delta");
340: }
341:
342: if (stackSize < 0) {
343: String msg = this .classFile.getThisClassName() + '.'
344: + functionName
345: + ": Operand stack underrun at offset "
346: + offset;
347: if (CodeContext.DEBUG) {
348: System.err.println(msg);
349: return;
350: } else {
351: throw new RuntimeException(msg);
352: }
353: }
354:
355: if (stackSize > Byte.MAX_VALUE) {
356: String msg = this .classFile.getThisClassName() + '.'
357: + functionName
358: + ": Operand stack overflow at offset "
359: + offset;
360: if (CodeContext.DEBUG) {
361: System.err.println(msg);
362: return;
363: } else {
364: throw new RuntimeException(msg);
365: }
366: }
367:
368: switch (props & Opcode.OP1_MASK) {
369:
370: case 0:
371: ;
372: break;
373:
374: case Opcode.OP1_SB:
375: case Opcode.OP1_UB:
376: case Opcode.OP1_CP1:
377: case Opcode.OP1_LV1:
378: ++operandOffset;
379: break;
380:
381: case Opcode.OP1_SS:
382: case Opcode.OP1_CP2:
383: case Opcode.OP1_LV2:
384: operandOffset += 2;
385: break;
386:
387: case Opcode.OP1_BO2:
388: if (CodeContext.DEBUG) {
389: System.out.println("Offset = " + offset);
390: System.out.println("Operand offset = "
391: + operandOffset);
392: System.out.println(code[operandOffset]);
393: System.out.println(code[operandOffset + 1]);
394: }
395: this
396: .flowAnalysis(
397: functionName,
398: code,
399: codeSize,
400: offset
401: + (((code[operandOffset++]) << 8) + ((0xff & code[operandOffset++]))),
402: stackSize, stackSizes);
403: break;
404:
405: case Opcode.OP1_JSR:
406: if (CodeContext.DEBUG) {
407: System.out.println("Offset = " + offset);
408: System.out.println("Operand offset = "
409: + operandOffset);
410: System.out.println(code[operandOffset]);
411: System.out.println(code[operandOffset + 1]);
412: }
413: int targetOffset = offset
414: + (((code[operandOffset++]) << 8) + ((0xff & code[operandOffset++])));
415: if (stackSizes[targetOffset] == CodeContext.UNEXAMINED) {
416: this .flowAnalysis(functionName, code, codeSize,
417: targetOffset, stackSize + 1, stackSizes);
418: }
419: break;
420:
421: case Opcode.OP1_BO4:
422: this
423: .flowAnalysis(
424: functionName,
425: code,
426: codeSize,
427: offset
428: + (((code[operandOffset++]) << 24)
429: + ((0xff & code[operandOffset++]) << 16)
430: + ((0xff & code[operandOffset++]) << 8) + ((0xff & code[operandOffset++]))),
431: stackSize, stackSizes);
432: break;
433:
434: case Opcode.OP1_LOOKUPSWITCH:
435: while ((operandOffset & 3) != 0)
436: ++operandOffset;
437: this
438: .flowAnalysis(
439: functionName,
440: code,
441: codeSize,
442: offset
443: + (((code[operandOffset++]) << 24)
444: + ((0xff & code[operandOffset++]) << 16)
445: + ((0xff & code[operandOffset++]) << 8) + ((0xff & code[operandOffset++]))),
446: stackSize, stackSizes);
447: int npairs = (((code[operandOffset++]) << 24)
448: + ((0xff & code[operandOffset++]) << 16)
449: + ((0xff & code[operandOffset++]) << 8) + ((0xff & code[operandOffset++])));
450: for (int i = 0; i < npairs; ++i) {
451: operandOffset += 4;
452: this
453: .flowAnalysis(
454: functionName,
455: code,
456: codeSize,
457: offset
458: + (((code[operandOffset++]) << 24)
459: + ((0xff & code[operandOffset++]) << 16)
460: + ((0xff & code[operandOffset++]) << 8) + ((0xff & code[operandOffset++]))),
461: stackSize, stackSizes);
462: }
463: break;
464:
465: case Opcode.OP1_TABLESWITCH:
466: while ((operandOffset & 3) != 0)
467: ++operandOffset;
468: this
469: .flowAnalysis(
470: functionName,
471: code,
472: codeSize,
473: offset
474: + (((code[operandOffset++]) << 24)
475: + ((0xff & code[operandOffset++]) << 16)
476: + ((0xff & code[operandOffset++]) << 8) + ((0xff & code[operandOffset++]))),
477: stackSize, stackSizes);
478: int low = (((code[operandOffset++]) << 24)
479: + ((0xff & code[operandOffset++]) << 16)
480: + ((0xff & code[operandOffset++]) << 8) + ((0xff & code[operandOffset++])));
481: int hi = (((code[operandOffset++]) << 24)
482: + ((0xff & code[operandOffset++]) << 16)
483: + ((0xff & code[operandOffset++]) << 8) + ((0xff & code[operandOffset++])));
484: for (int i = low; i <= hi; ++i) {
485: this
486: .flowAnalysis(
487: functionName,
488: code,
489: codeSize,
490: offset
491: + (((code[operandOffset++]) << 24)
492: + ((0xff & code[operandOffset++]) << 16)
493: + ((0xff & code[operandOffset++]) << 8) + ((0xff & code[operandOffset++]))),
494: stackSize, stackSizes);
495: }
496: break;
497:
498: default:
499: throw new RuntimeException(functionName
500: + ": Invalid OP1");
501: }
502:
503: switch (props & Opcode.OP2_MASK) {
504:
505: case 0:
506: ;
507: break;
508:
509: case Opcode.OP2_SB:
510: ++operandOffset;
511: break;
512:
513: case Opcode.OP2_SS:
514: operandOffset += 2;
515: break;
516:
517: default:
518: throw new RuntimeException(functionName
519: + ": Invalid OP2");
520: }
521:
522: switch (props & Opcode.OP3_MASK) {
523:
524: case 0:
525: ;
526: break;
527:
528: case Opcode.OP3_SB:
529: ++operandOffset;
530: break;
531:
532: default:
533: throw new RuntimeException(functionName
534: + ": Invalid OP3");
535: }
536:
537: Arrays.fill(stackSizes, offset + 1, operandOffset,
538: CodeContext.INVALID_OFFSET);
539:
540: if ((props & Opcode.NO_FALLTHROUGH) != 0)
541: return;
542: offset = operandOffset;
543: }
544: }
545:
546: /**
547: * Fix up all offsets.
548: */
549: public void fixUp() {
550: for (Offset o = this .beginning; o != this .end; o = o.next) {
551: if (o instanceof FixUp)
552: ((FixUp) o).fixUp();
553: }
554: }
555:
556: public void relocate() {
557: for (int i = 0; i < this .relocatables.size(); ++i) {
558: ((Relocatable) this .relocatables.get(i)).relocate();
559: }
560: }
561:
562: /**
563: * Analyse the descriptor of the Fieldref and return its size.
564: */
565: private int determineFieldSize(short idx) {
566: ClassFile.ConstantFieldrefInfo cfi = (ClassFile.ConstantFieldrefInfo) this .classFile
567: .getConstantPoolInfo(idx);
568: ClassFile.ConstantNameAndTypeInfo cnati = (ClassFile.ConstantNameAndTypeInfo) this .classFile
569: .getConstantPoolInfo(cfi.getNameAndTypeIndex());
570: ClassFile.ConstantUtf8Info cui = (ClassFile.ConstantUtf8Info) this .classFile
571: .getConstantPoolInfo(cnati.getDescriptorIndex());
572: return Descriptor.size(cui.getString());
573: }
574:
575: /**
576: * Analyse the descriptor of the Methodref and return the sum of the
577: * arguments' sizes minus the return value's size.
578: */
579: private int determineArgumentsSize(short idx) {
580: ClassFile.ConstantPoolInfo cpi = this .classFile
581: .getConstantPoolInfo(idx);
582: ClassFile.ConstantNameAndTypeInfo nat = (ClassFile.ConstantNameAndTypeInfo) this .classFile
583: .getConstantPoolInfo(cpi instanceof ClassFile.ConstantInterfaceMethodrefInfo ? ((ClassFile.ConstantInterfaceMethodrefInfo) cpi)
584: .getNameAndTypeIndex()
585: : ((ClassFile.ConstantMethodrefInfo) cpi)
586: .getNameAndTypeIndex());
587: ClassFile.ConstantUtf8Info cui = (ClassFile.ConstantUtf8Info) this .classFile
588: .getConstantPoolInfo(nat.getDescriptorIndex());
589: String desc = cui.getString();
590:
591: if (desc.charAt(0) != '(')
592: throw new RuntimeException(
593: "Method descriptor does not start with \"(\"");
594: int i = 1;
595: int res = 0;
596: for (;;) {
597: switch (desc.charAt(i++)) {
598: case ')':
599: return res - Descriptor.size(desc.substring(i));
600: case 'B':
601: case 'C':
602: case 'F':
603: case 'I':
604: case 'S':
605: case 'Z':
606: res += 1;
607: break;
608: case 'D':
609: case 'J':
610: res += 2;
611: break;
612: case '[':
613: res += 1;
614: while (desc.charAt(i) == '[')
615: ++i;
616: if ("BCFISZDJ".indexOf(desc.charAt(i)) != -1) {
617: ++i;
618: break;
619: }
620: if (desc.charAt(i) != 'L')
621: throw new RuntimeException(
622: "Invalid char after \"[\"");
623: ++i;
624: while (desc.charAt(i++) != ';')
625: ;
626: break;
627: case 'L':
628: res += 1;
629: while (desc.charAt(i++) != ';')
630: ;
631: break;
632: default:
633: throw new RuntimeException("Invalid method descriptor");
634: }
635: }
636: }
637:
638: /**
639: * Inserts a sequence of bytes at the current insertion position. Creates
640: * {@link LineNumberOffset}s as necessary.
641: *
642: * @param lineNumber The line number that corresponds to the byte code, or -1
643: * @param b
644: */
645: public void write(short lineNumber, byte[] b) {
646: if (b.length == 0)
647: return;
648:
649: INSERT_LINE_NUMBER_OFFSET: if (lineNumber != -1) {
650: Offset o;
651: for (o = this .currentInserter.prev; o != this .beginning; o = o.prev) {
652: if (o instanceof LineNumberOffset) {
653: if (((LineNumberOffset) o).lineNumber == lineNumber)
654: break INSERT_LINE_NUMBER_OFFSET;
655: break;
656: }
657: }
658: LineNumberOffset lno = new LineNumberOffset(
659: this .currentInserter.offset, lineNumber);
660: lno.prev = this .currentInserter.prev;
661: lno.next = this .currentInserter;
662: this .currentInserter.prev.next = lno;
663: this .currentInserter.prev = lno;
664: }
665:
666: int ico = 0xffff & this .currentInserter.offset;
667: if ((0xffff & this .end.offset) + b.length <= this .code.length) {
668: System.arraycopy(this .code, ico, this .code, ico + b.length,
669: (0xffff & this .end.offset) - ico);
670: } else {
671: byte[] oldCode = this .code;
672: this .code = new byte[this .code.length
673: + CodeContext.SIZE_INCREMENT];
674: if (this .code.length >= 0xffff)
675: throw new RuntimeException("Code attribute in class \""
676: + this .classFile.getThisClassName()
677: + "\" grows beyond 64 KB");
678: System.arraycopy(oldCode, 0, this .code, 0, ico);
679: System.arraycopy(oldCode, ico, this .code, ico + b.length,
680: (0xffff & this .end.offset) - ico);
681: }
682: System.arraycopy(b, 0, this .code, ico, b.length);
683: for (Offset o = this .currentInserter; o != null; o = o.next)
684: o.offset += b.length;
685: }
686:
687: public void writeShort(short lineNumber, int v) {
688: this
689: .write(lineNumber, new byte[] { (byte) (v >> 8),
690: (byte) v });
691: }
692:
693: public void writeBranch(short lineNumber, int opcode,
694: final Offset dst) {
695: this .relocatables.add(new Branch(this .newOffset(), dst));
696: this .write(lineNumber, new byte[] { (byte) opcode, -1, -1 });
697: }
698:
699: private class Branch extends Relocatable {
700: public Branch(Offset source, Offset destination) {
701: this .source = source;
702: this .destination = destination;
703: }
704:
705: public void relocate() {
706: if (this .destination.offset == Offset.UNSET)
707: throw new RuntimeException(
708: "Cannot relocate branch to unset destination offset");
709: int offset = this .destination.offset - this .source.offset;
710: if (offset > Short.MAX_VALUE || offset < Short.MIN_VALUE)
711: throw new RuntimeException("Branch offset out of range");
712: byte[] ba = new byte[] { (byte) (offset >> 8),
713: (byte) offset };
714: System.arraycopy(ba, 0, CodeContext.this .code,
715: (0xffff & this .source.offset) + 1, 2);
716: }
717:
718: private final Offset source;
719: private final Offset destination;
720: }
721:
722: public void writeOffset(short lineNumber, Offset src,
723: final Offset dst) {
724: this .relocatables.add(new OffsetBranch(this .newOffset(), src,
725: dst));
726: this .write(lineNumber, new byte[] { -1, -1, -1, -1 });
727: }
728:
729: private class OffsetBranch extends Relocatable {
730: public OffsetBranch(Offset where, Offset source,
731: Offset destination) {
732: this .where = where;
733: this .source = source;
734: this .destination = destination;
735: }
736:
737: public void relocate() {
738: if (this .source.offset == Offset.UNSET
739: || this .destination.offset == Offset.UNSET)
740: throw new RuntimeException(
741: "Cannot relocate offset branch to unset destination offset");
742: int offset = this .destination.offset - this .source.offset;
743: byte[] ba = new byte[] { (byte) (offset >> 24),
744: (byte) (offset >> 16), (byte) (offset >> 8),
745: (byte) offset };
746: System.arraycopy(ba, 0, CodeContext.this .code,
747: 0xffff & this .where.offset, 4);
748: }
749:
750: private final Offset where, source, destination;
751: }
752:
753: public Offset newOffset() {
754: Offset o = new Offset();
755: o.set();
756: return o;
757: }
758:
759: /**
760: * Allocate an {@link Inserter}, set it to the current offset, and
761: * insert it before the current offset.
762: *
763: * In clear text, this means that you can continue writing to the
764: * "Code" attribute, then {@link #pushInserter(CodeContext.Inserter)} the
765: * {@link Inserter}, then write again (which inserts bytes into the
766: * "Code" attribute at the previously remembered position), and then
767: * {@link #popInserter()}.
768: */
769: public Inserter newInserter() {
770: Inserter i = new Inserter();
771: i.set();
772: return i;
773: }
774:
775: /**
776: * Remember the current {@link Inserter}, then replace it with the
777: * new one.
778: */
779: public void pushInserter(Inserter ins) {
780: if (ins.nextInserter != null)
781: throw new RuntimeException(
782: "An Inserter can only be pushed once at a time");
783: ins.nextInserter = this .currentInserter;
784: this .currentInserter = ins;
785: }
786:
787: /**
788: * Replace the current {@link Inserter} with the remembered one (see
789: * {@link #pushInserter(CodeContext.Inserter)}).
790: */
791: public void popInserter() {
792: Inserter ni = this .currentInserter.nextInserter;
793: if (ni == null)
794: throw new RuntimeException("Code inserter stack underflow");
795: this .currentInserter.nextInserter = null; // Mark it as "unpushed".
796: this .currentInserter = ni;
797: }
798:
799: /**
800: * A class that represents an offset within a "Code" attribute.
801: *
802: * The concept of an "offset" is that if one writes into the middle of
803: * a "Code" attribute, all offsets behind the insertion point are
804: * automatically shifted.
805: */
806: public class Offset {
807: short offset = Offset.UNSET;
808: Offset prev = null, next = null;
809: final static short UNSET = -1;
810:
811: /**
812: * Set this "Offset" to the offset of the current inserter; insert
813: * this "Offset" before the current inserter.
814: */
815: public void set() {
816: if (this .offset != Offset.UNSET)
817: throw new RuntimeException(
818: "Cannot \"set()\" Offset more than once");
819:
820: this .offset = CodeContext.this .currentInserter.offset;
821: this .prev = CodeContext.this .currentInserter.prev;
822: this .next = CodeContext.this .currentInserter;
823:
824: this .prev.next = this ;
825: this .next.prev = this ;
826: }
827:
828: public final CodeContext getCodeContext() {
829: return CodeContext.this ;
830: }
831:
832: public String toString() {
833: return CodeContext.this .classFile.getThisClassName() + ": "
834: + (0xffff & this .offset);
835: }
836: }
837:
838: /**
839: * Add another entry to the "exception_table" of this code attribute (see JVMS 4.7.3).
840: * @param startPC
841: * @param endPC
842: * @param handlerPC
843: * @param catchTypeFD
844: */
845: public void addExceptionTableEntry(Offset startPC, Offset endPC,
846: Offset handlerPC, String catchTypeFD // null == "finally" clause
847: ) {
848: this .exceptionTableEntries.add(new ExceptionTableEntry(startPC,
849: endPC, handlerPC, catchTypeFD == null ? (short) 0
850: : this .classFile
851: .addConstantClassInfo(catchTypeFD)));
852: }
853:
854: /**
855: * Representation of an entry in the "exception_table" of a "Code" attribute (see JVMS
856: * 4.7.3).
857: */
858: private static class ExceptionTableEntry {
859: public ExceptionTableEntry(Offset startPC, Offset endPC,
860: Offset handlerPC, short catchType) {
861: this .startPC = startPC;
862: this .endPC = endPC;
863: this .handlerPC = handlerPC;
864: this .catchType = catchType;
865: }
866:
867: private final Offset startPC, endPC, handlerPC;
868: private final short catchType; // 0 == "finally" clause
869: }
870:
871: /**
872: * A class that implements an insertion point into a "Code"
873: * attribute.
874: */
875: public class Inserter extends Offset {
876: private Inserter nextInserter = null; // null == not in "currentInserter" stack
877: }
878:
879: public class LineNumberOffset extends Offset {
880: private final short lineNumber;
881:
882: public LineNumberOffset(short offset, short lineNumber) {
883: this .lineNumber = lineNumber;
884: this .offset = offset;
885: }
886: }
887:
888: private abstract class Relocatable {
889: public abstract void relocate();
890: }
891:
892: private short localVariableArrayLength = 0;
893: private final Stack savedLocalVariableArrayLengths = new Stack();
894: private final List relocatables = new ArrayList();
895:
896: /**
897: * A throw-in interface that marks {@link CodeContext.Offset}s
898: * as "fix-ups": During the execution of
899: * {@link CodeContext#fixUp}, all "fix-ups" are invoked and
900: * can do last touches to the code attribute.
901: * <p>
902: * This is currently used for inserting the "padding bytes" into the
903: * TABLESWITCH and LOOKUPSWITCH instructions.
904: */
905: public interface FixUp {
906: void fixUp();
907: }
908: }
|