MethodGen.insertBlank(int)
[org.ibex.classgen.git] / src / org / ibex / classgen / MethodGen.java
1 package org.ibex.classgen;
2
3 import java.io.*;
4 import java.util.*;
5
6 /** A class representing a method in a generated classfile
7     @see ClassFile#addMethod */
8 public class MethodGen extends Type.Class.Method.Body {
9     private final static boolean EMIT_NOPS = false;
10     
11     private static final int NO_CODE = -1;
12
13     public final Type.Class.Method method;
14     private final ClassFile.AttrGen codeAttrs;
15     private final Vector exnTable = new Vector();
16     private final Hashtable thrownExceptions = new Hashtable();
17     
18     int maxStack = 16;
19     int maxLocals;
20     
21     private int size;
22     private int capacity;
23     private byte[] op;
24     private Object[] arg;
25     private ConstantPool.Ent[] cparg;
26
27     public void insertBlank(int idx) {
28         for(int i=0;i<size();i++) {
29             switch(op[i]) {
30                 case TABLESWITCH:
31                 case LOOKUPSWITCH: {
32                     Switch si = (Switch) arg[i];
33                     int pos = si.getDefaultTarget();
34                     if (pos >= idx) si.setDefaultTarget(pos+1);
35                     for(int j=0;j<si.size();j++) {
36                         pos = si.getTarget(j);
37                         if (pos >= idx) si.setTarget(j, pos+1);
38                     }
39                     break;
40                 }
41                 default:
42                     if (OP_BRANCH(op[i])) {
43                         int pos = ((Integer)arg[i]).intValue();
44                         if (pos >= idx) arg[i] = N(pos+1);
45                     }
46                     break;
47             }
48         }
49         size++;
50         for(int i=size; i>idx; i--) {
51             op[i] = op[i-1];
52             arg[i] = arg[i-1];
53             cparg = null;
54         }
55     }
56
57    
58     // Constructors //////////////////////////////////////////////////////////////////////////////
59
60     MethodGen(Type.Class.Method method, int flags) {
61         method.super(flags, new ClassFile.AttrGen());
62         this.method = method;
63         codeAttrs = new ClassFile.AttrGen();
64         if (!isConcrete()) size = capacity = -1;
65         maxLocals = Math.max(method.getNumArgs() + (flags&STATIC)==0 ? 1 : 0, 4);
66     }
67
68     MethodGen(Type.Class c, DataInput in, ConstantPool cp) throws IOException {
69         this(in.readShort(), cp.getUtf8KeyByIndex(in.readShort()), c, in, cp); }
70
71     private MethodGen(short flags, String name, Type.Class c, DataInput in, ConstantPool cp) throws IOException {
72         this(flags, name, c.method(name,cp.getUtf8KeyByIndex(in.readShort())), c, in, cp); }
73     private MethodGen(short flags, String name, Type.Class.Method m,
74                       Type.Class c, DataInput in, ConstantPool cp) throws IOException {
75         m.super(flags, new ClassFile.AttrGen(in,cp));
76         this.method = m;
77         
78         if (isConcrete())  {
79             byte[] codeAttr = (byte[]) attrs.get("Code");
80             if (codeAttr == null) throw new ClassFile.ClassReadExn("code attr expected");
81             DataInputStream ci = new DataInputStream(new ByteArrayInputStream(codeAttr));
82             maxStack = ci.readUnsignedShort();
83             maxLocals = ci.readUnsignedShort();
84             int codeLen = ci.readInt();
85             int[] bytecodeMap = parseCode(ci,codeLen,cp);
86             int numExns = ci.readUnsignedShort();
87             while(numExns-- > 0)
88                 exnTable.addElement(new ExnTableEnt(ci,cp,bytecodeMap));
89             codeAttrs = new ClassFile.AttrGen(ci,cp);
90             // FEATURE: Support these
91             // NOTE: Until we can support them properly we HAVE to delete them,
92             //       they'll be incorrect after we rewrite the constant pool, etc
93             codeAttrs.remove("LineNumberTable");
94             codeAttrs.remove("LocalVariableTable");
95             
96         } else {
97             codeAttrs = new ClassFile.AttrGen();
98         }
99
100         if (attrs.contains("Exceptions")) {
101             DataInputStream ei = new DataInputStream(new ByteArrayInputStream((byte[]) attrs.get("Exceptions")));
102             int exnCount = ei.readUnsignedShort();
103             while(exnCount-- > 0) {
104                 Type.Class t = (Type.Class) cp.getKeyByIndex(ei.readUnsignedShort());
105                 thrownExceptions.put(t,t);
106             }
107         }
108     }
109
110     // Parsing //////////////////////////////////////////////////////////////////////////////
111         
112     final int[] parseCode(DataInputStream in, int codeLen, ConstantPool cp) throws IOException {
113         int[] map = new int[codeLen];
114         int pc;
115         for(pc=0;pc<map.length;pc++) map[pc] = -1;
116         for(pc=0;pc<codeLen;) {
117             byte op = in.readByte();
118             int opdata = OP_DATA[op&0xff];
119             //System.err.println("Processing " + Integer.toString(op&0xff,16) + " at " + pc);
120             if ((opdata&OP_VALID_FLAG)==0) throw new ClassFile.ClassReadExn("invalid bytecode " + (op&0xff));
121             int argLength = opdata & OP_ARG_LENGTH_MASK;
122             int mypc = pc;
123             map[mypc] = size();
124             pc += 1 + (argLength == 7 ? 0 : argLength);
125             if (argLength == 0)  { add(op); continue; }
126             Object arg;
127             switch(op) {
128                 case IINC:
129                     arg = new Pair(in.readUnsignedByte(),in.readByte());
130                     break;
131                 case TABLESWITCH:
132                 case LOOKUPSWITCH:
133                     Switch si;
134                     for(;(pc&3) != 0;pc++) if (in.readByte() != 0) throw new ClassFile.ClassReadExn("invalid padding");
135                     int def = in.readInt() + mypc;
136                     pc += 4;
137                     if (op == LOOKUPSWITCH) {
138                         Switch.Lookup lsi = new Switch.Lookup(in.readInt());
139                         pc += 4;
140                         for(int i=0;i<lsi.size();i++) {
141                             lsi.setVal(i,in.readInt());
142                             lsi.setTarget(i,in.readInt() + mypc);
143                             pc += 8;
144                         }
145                         si = lsi;
146                     } else {
147                         int lo = in.readInt();
148                         int hi = in.readInt();
149                         pc += 8;
150                         Switch.Table tsi = new Switch.Table(lo,hi);
151                         for(int i=0;i<tsi.size();i++) { tsi.setTarget(i,in.readInt() + mypc); pc += 4; }
152                         si = tsi;
153                     }
154                     si.setDefaultTarget(def);
155                     arg = si;
156                     break;
157                 case WIDE: {
158                     byte wideop = in.readByte();
159                     arg = wideop == IINC 
160                         ? new Wide(wideop,in.readUnsignedShort(),in.readShort()) 
161                         : new Wide(wideop,in.readUnsignedShort());
162                     pc += wideop == IINC ? 5 : 3;
163                     break;
164                 }
165                 case MULTIANEWARRAY:
166                     arg = new MultiANewArray((Type.Array)cp.getKeyByIndex(in.readUnsignedShort()),in.readUnsignedByte());
167                     break;
168                 case INVOKEINTERFACE: {
169                     ConstantPool.Ent ent = cp.getByIndex(in.readUnsignedShort());
170                     if (ent.tag != CONSTANT_INTERFACEMETHODREF) throw new ClassFile.ClassReadExn("illegal argument to bytecode");
171                     arg = ((ConstantPool.InterfaceMethodKey)ent.key()).method;
172                     if (in.readByte() == 0 || in.readByte() != 0)
173                         throw new ClassFile.ClassReadExn("illegal count or 0 arg to invokeinterface");
174                     break;
175                 }
176                 default:
177                     if ((opdata&OP_CPENT_FLAG)!=0) {
178                         ConstantPool.Ent ent =
179                             cp.getByIndex(argLength == 2 ? in.readUnsignedShort() : argLength == 1 ? in.readUnsignedByte() : -1);
180                         int tag = ent.tag;
181                         Object key = ent.key();
182                         switch(op) {
183                             case LDC:
184                             case LDC_W:
185                             case LDC2_W:
186                                 switch(tag) {
187                                     case CONSTANT_INTEGER:
188                                     case CONSTANT_FLOAT:
189                                     case CONSTANT_LONG:
190                                     case CONSTANT_DOUBLE:
191                                     case CONSTANT_STRING:
192                                     case CONSTANT_CLASS:
193                                         break;
194                                     default:
195                                         throw new ClassFile.ClassReadExn("illegal argument to bytecode 0x" +
196                                                                          Integer.toString(op&0xff,16));
197                                 }
198                                 break;
199                             case GETSTATIC:
200                             case PUTSTATIC:
201                             case GETFIELD:
202                             case PUTFIELD:
203                                 if (tag != CONSTANT_FIELDREF)
204                                     throw new ClassFile.ClassReadExn("illegal argument to bytecode 0x" +
205                                                                      Integer.toString(op&0xff,16));
206                                 break;
207                             case INVOKEVIRTUAL:
208                             case INVOKESPECIAL:
209                             case INVOKESTATIC:
210                                 if (tag != CONSTANT_METHODREF)
211                                     throw new ClassFile.ClassReadExn("illegal argument to bytecode 0x" +
212                                                                      Integer.toString(op&0xff,16));
213                                 break;
214                             case NEW:
215                             case ANEWARRAY:
216                             case CHECKCAST:
217                             case INSTANCEOF:
218                                 if (tag != CONSTANT_CLASS)
219                                     throw new ClassFile.ClassReadExn("illegal argument to bytecode 0x" +
220                                                                      Integer.toString(op&0xff,16));
221                                 break;                        
222                             default:
223                                 throw new Error("should never happen");
224                         }
225                         arg = key;
226                     } else {
227                         // treat everything else (including branches for now) as plain old ints
228                         int n;
229                         boolean unsigned = (opdata&OP_UNSIGNED_FLAG)!=0;
230                         if (argLength == 1) n = unsigned ? in.readUnsignedByte() : in.readByte();
231                         else if (argLength == 2) n = unsigned ? in.readUnsignedShort() : in.readShort();
232                         else throw new Error("should never happen");
233                         if ((opdata&OP_BRANCH_FLAG)!=0) n += mypc;
234                         arg = N(n);
235                     }
236                     break;
237             }
238             add(op,arg);
239         }
240         if (pc != codeLen)
241             throw new ClassFile.ClassReadExn("didn't read enough code (" + pc + "/" + codeLen + " in " + method.name + ")");
242         for(int i=0;i<size();i++) {
243             switch(op[i]) {
244                 case TABLESWITCH:
245                 case LOOKUPSWITCH:
246                 {
247                     Switch si = (Switch) arg[i];
248                     
249                     int pos = map[si.getDefaultTarget()];
250                     if (pos < 0)
251                         throw new ClassFile.ClassReadExn("default target points to invalid bytecode: " + si.getDefaultTarget());
252                     si.setDefaultTarget(pos);
253                     
254                     for(int j=0;j<si.size();j++) {
255                         pos = map[si.getTarget(j)];
256                         if (pos < 0)  throw new ClassFile.ClassReadExn("target points to invalid bytecode");
257                         si.setTarget(j,pos);
258                     }
259                     break;
260                 }
261                 default:
262                     if (OP_BRANCH(op[i])) {
263                         int pos = map[((Integer)arg[i]).intValue()];
264                         if (pos < 0)  throw new ClassFile.ClassReadExn("branch points to invalid bytecode");
265                         arg[i] = N(pos);
266                     }
267                     break;
268             }
269         }
270         return map;
271     }
272
273     // Exception Table //////////////////////////////////////////////////////////////////////////////
274    
275     class ExnTableEnt {
276         final int start;
277         final int end;
278         final int handler;
279         final Type.Class type; // null type means all exceptions (for finally)
280         
281         ExnTableEnt(DataInput in, ConstantPool cp, int[] bytecodeMap) throws IOException {
282             int startPC = in.readUnsignedShort();
283             int endPC = in.readUnsignedShort();
284             int handlerPC = in.readUnsignedShort();
285             int index = in.readUnsignedShort();
286             this.type = index == 0 ? null : (Type.Class) cp.getKeyByIndex(index);
287             int max = bytecodeMap.length;
288             if (startPC >= max || bytecodeMap[startPC] < 0) throw new ClassFile.ClassReadExn("invalid startPC");
289             if (endPC >= max || bytecodeMap[endPC] < 0) throw new ClassFile.ClassReadExn("invalid startPC");
290             if (handlerPC >= max || bytecodeMap[handlerPC] < 0) throw new ClassFile.ClassReadExn("invalid startPC");
291             this.start = bytecodeMap[startPC];
292             this.end = bytecodeMap[endPC];
293             this.handler = bytecodeMap[handlerPC];
294         }
295         ExnTableEnt(int start, int end, int handler, Type.Class type) {
296             this.start = start;
297             this.end = end;
298             this.handler = handler;
299             this.type = type;
300         }
301         void finish(ConstantPool cp) { if (type != null) cp.add(type); }
302         void dump(DataOutput o, int[] pc, int endPC, ConstantPool cp) throws IOException {
303             o.writeShort(pc[start]);
304             o.writeShort(end==pc.length ? endPC : pc[end]);
305             o.writeShort(pc[handler]);
306             o.writeShort(type == null ? 0 : cp.getIndex(type));
307         }
308     }
309     
310     /** Adds an exception handler for the range [<i>start</i>, <i>end</i>) pointing to <i>handler</i>
311         @param start The instruction to start at (inclusive)
312         @param end The instruction to end at (exclusive)
313         @param handler The instruction of the excepton handler
314         @param type The type of exception that is to be handled (MUST inherit from Throwable)
315     */
316     public final void addExceptionHandler(int start, int end, int handler, Type.Class type) {
317         exnTable.addElement(new ExnTableEnt(start, end, handler, type));
318     }
319     
320     /** Adds a exception type that can be thrown from this method
321         NOTE: This isn't enforced by the JVM. This is for reference
322         only. A method can throw exceptions not declared to be thrown
323         @param type The type of exception that can be thrown 
324     */
325     public final void addThrow(Type.Class type) { thrownExceptions.put(type, type); }
326     
327     private final void grow() { if (size == capacity) grow(size+1); }
328     private final void grow(int newCap) {
329         if (capacity == NO_CODE) throw new IllegalStateException("method can't have code");
330         if (newCap <= capacity) return;
331         newCap = Math.max(newCap, capacity == 0 ? 256 : capacity*2);
332         
333         byte[] op2 = new byte[newCap];
334         if (capacity != 0) System.arraycopy(op, 0, op2, 0, size);
335         op = op2;
336         
337         Object[] arg2 = new Object[newCap];
338         if (capacity != 0) System.arraycopy(arg, 0, arg2, 0, size);
339         arg = arg2;
340         
341         capacity = newCap;
342     }
343
344     // Accessors //////////////////////////////////////////////////////////////////////////////
345     
346     public int getFlags() { return flags; }
347     public Hashtable getThrownExceptions() { return thrownExceptions; }
348
349     /** Returns the size (in instructions) of this method 
350         @return The size of the method (in instructions)
351     */
352     public final int size() { return size; }
353     
354     // These two are optimized for speed, they don't call set() below
355     /** Add a bytecode (with no argument) to the method */
356     public final int add(byte op) {
357         int s = size;
358         if (s == capacity) grow();
359         this.op[s] = op;
360         size++;
361         return s;
362     }
363
364     /** Set the bytecode at position <i>pos</i> to <i>op</i> */
365     public final void set(int pos, byte op) { this.op[pos] = op; }
366         
367     /** Adds a bytecode, <i>op</i>, with argument <i>arg</i> to the method 
368         @return The position of the new bytecode
369         */
370     public final int add(byte op, Object arg) { if (capacity == size) grow(); set(size, op, arg); return size++; }
371
372     /** Adds a bytecode with a boolean argument - equivalent to add(op, arg?1:0);
373         @return The position of the new bytecode
374         @see #add(byte, int)
375     */
376     public final int add(byte op, boolean arg) { if (capacity == size) grow(); set(size, op, arg); return size++; }
377
378     /** Adds a bytecode with an integer argument. This is equivalent
379      * to add(op, new Integer(arg)), but optimized to prevent the
380      * allocation when possible
381         @return The position of the new bytecode
382         @see #add(byte, Object)
383     */
384     public final int add(byte op, int arg) { if (capacity == size) grow(); set(size, op, arg); return size++; }
385     
386     /** Gets the bytecode at position <i>pos</i>
387         @exception ArrayIndexOutOfBoundException if pos < 0 || pos >= size()
388     */
389     public final byte get(int pos) { return op[pos]; }
390     
391     /** Gets the bytecode at position <i>pos</i>. NOTE: This isn't necessarily the same object that was set with add or set.
392         Arguments for instructions which access the constant pool (LDC, INVOKEVIRTUAL, etc) are converted to a more efficient
393         interal form when they are added. The value returned from this method for these instruction can be reused, but there
394         is no way to retrieve the original object 
395         @exception ArrayIndexOutOfBoundException if pos < 0 || pos >= size()
396     */    
397     public final Object getArg(int pos) { return arg[pos]; }
398     
399     /** Sets the argument for <i>pos</i> to <i>arg</i>. This is
400      * equivalent to set(pos, op, new Integer(arg)), but optimized to
401      * prevent the allocation when possible.
402         @exception ArrayIndexOutOfBoundException if pos < 0 || pos >= size()
403         @see #setArg(int, Object) */
404     public final void setArg(int pos, int arg) { set(pos, op[pos], N(arg)); }
405
406     /** Sets the argument for <i>pos</i> to <i>arg</i>.
407         @exception ArrayIndexOutOfBoundException if pos < 0 || pos >= size() */
408     public final void setArg(int pos, Object arg) { set(pos, op[pos], arg); }
409     
410     /** Sets the bytecode and argument  at <i>pos</i> to <i>op</i> and <i>arg</i> respectivly. 
411         This is equivalent to set(pos, op, arg?1:0) 
412         @exception ArrayIndexOutOfBoundException if pos < 0 || pos >= size()
413     */
414     public final void set(int pos, byte op, boolean arg) { set(pos, op, arg?1:0); }
415     
416     // This MUST handle x{LOAD, STORE} and LDC with an int arg WITHOUT falling back to set(int, byte, Object)
417     /** Sets the bytecode and argument  at <i>pos</i> to <i>op</i> and <i>n</i> respectivly.
418         This is equivalent to set(pos, op, new Integer(n)), but optimized to prevent the allocation when possible.
419         @exception ArrayIndexOutOfBoundException if pos < 0 || pos >= size()
420     */
421     public final void set(int pos, byte op, int n) {
422         Object arg = null;
423         OUTER: switch(op) {
424             case LDC:
425                 switch(n) {
426                     case -1: op = ICONST_M1; break OUTER;
427                     case 0:  op = ICONST_0;  break OUTER;
428                     case 1:  op = ICONST_1;  break OUTER;
429                     case 2:  op = ICONST_2;  break OUTER; 
430                     case 3:  op = ICONST_3;  break OUTER;
431                     case 4:  op = ICONST_4;  break OUTER;
432                     case 5:  op = ICONST_5;  break OUTER;
433                 }
434                 if (n >= -128 && n <= 127) { op = BIPUSH; arg = N(n); } 
435                 else if (n >= -32768 && n <= 32767) { op = SIPUSH; arg = N(n); }
436                 else { arg = N(n); }
437                 break;
438             case ILOAD: case ISTORE: case LLOAD: case LSTORE: case FLOAD:
439             case FSTORE: case DLOAD: case DSTORE: case ALOAD: case ASTORE:
440                 if (n >= maxLocals) maxLocals = n + 1;
441                 if (n >= 0 && n <= 3) {
442                     byte base = 0;
443                     switch(op) {
444                         case ILOAD:  base = ILOAD_0;  break;
445                         case ISTORE: base = ISTORE_0; break;
446                         case LLOAD:  base = LLOAD_0;  break;
447                         case LSTORE: base = LSTORE_0; break; 
448                         case FLOAD:  base = FLOAD_0;  break;
449                         case FSTORE: base = FSTORE_0; break;
450                         case DLOAD:  base = DLOAD_0;  break;
451                         case DSTORE: base = DSTORE_0; break;
452                         case ALOAD:  base = ALOAD_0;  break;
453                         case ASTORE: base = ASTORE_0; break;
454                     }
455                     op = (byte)((base&0xff) + n);
456                 } else {
457                     arg = N(n);
458                 }
459                 break;
460             default:
461                 set(pos, op, N(n));
462                 return;
463         }            
464         this.op[pos] = op;
465         this.arg[pos] = arg;
466     }
467     
468     /** Sets the bytecode and argument  at <i>pos</i> to <i>op</i> and <i>arg</i> respectivly.
469         @exception ArrayIndexOutOfBoundException if pos < 0 || pos >= size()
470         */
471     public final void set(int pos, byte op, Object arg) {
472         switch(op) {
473             case ILOAD: case ISTORE: case LLOAD: case LSTORE: case FLOAD:
474             case FSTORE: case DLOAD: case DSTORE: case ALOAD: case ASTORE:
475                 // set(int, byte, int) always handles these ops itself
476                 set(pos, op, ((Integer)arg).intValue());
477                 return;
478             case LDC:
479                 // set(int, byte, int) always handles these opts itself
480                 if (arg instanceof Integer) { set(pos, op, ((Integer)arg).intValue()); return; }
481                 if (arg instanceof Boolean) { set(pos, op, ((Boolean)arg).booleanValue()); return; }
482                 
483                 if (arg instanceof Long) {
484                     long l = ((Long)arg).longValue();
485                     if (l == 0L || l == 1L) {
486                         this.op[pos] = l == 0L ? LCONST_0 : LCONST_1;
487                         this.arg[pos] = null; 
488                         return;
489                     }
490                     op = LDC2_W;
491                 } else if (arg instanceof Double) {
492                     op = LDC2_W;
493                 }
494                 break;
495         }
496         if ((OP_DATA[op&0xff]&OP_VALID_FLAG) == 0) throw new IllegalArgumentException("unknown bytecode");
497         this.op[pos] = op;
498         this.arg[pos] = arg;
499     }
500     
501     /** Sets the maximum number of locals in the function to
502         <i>maxLocals</i>. NOTE: This defaults to 0 and is
503         automatically increased as necessary when *LOAD/*STORE
504         bytecodes are added. You do not need to call this function in
505         most cases */
506     public void setMaxLocals(int maxLocals) { this.maxLocals = maxLocals; }
507
508     /** Sets the maxinum size of th stack for this function to
509      * <i>maxStack</i>. This defaults to 16< */
510     public void setMaxStack(int maxStack) { this.maxStack = maxStack; }
511     
512
513     // Bytecode-Specific inner classes ////////////////////////////////////////////////////////////////////////////////
514
515     public static abstract class Switch {
516         public final Object[] targets;
517         public Object defaultTarget;
518
519         Switch(int size) { targets = new Object[size]; }
520         public void setTarget(int pos, Object val) { targets[pos] = val; }
521         public void setTarget(int pos, int val) { targets[pos] = N(val); }
522         public void setDefaultTarget(int val) { setDefaultTarget(N(val)); }
523         public void setDefaultTarget(Object o) { defaultTarget = o; }
524         public int size() { return targets.length; }
525         
526         public int getTarget(int pos) { return ((Integer)targets[pos]).intValue(); }
527         public int getDefaultTarget() { return ((Integer)defaultTarget).intValue(); }   
528         
529         abstract int length();
530     
531         public static class Table extends Switch {
532             public final int lo;
533             public final int hi;
534             public Table(int lo, int hi) {
535                 super(hi-lo+1);
536                 this.lo = lo;
537                 this.hi = hi;
538             }
539             public void setTargetForVal(int val, Object o) { setTarget(val-lo, o); }
540             public void setTargetForVal(int val, int n) { setTarget(val-lo, n); }
541             
542             int length() { return 12 + targets.length * 4; } // 4bytes/target, hi, lo, default
543         }
544     
545         public static class Lookup extends Switch {
546             public final int[] vals;
547             public Lookup(int size) {
548                 super(size);
549                 this.vals = new int[size];
550             }
551             public final void setVal(int pos, int val) { vals[pos] = val; }
552             
553             int length() { return 8 + targets.length * 8; } // key/val per target, default, count
554         }
555     }
556     
557     /** This class represents the arguments to byecodes that take two integer arguments. */
558     public static class Pair {
559         public int i1;
560         public int i2;
561         public Pair(int i1, int i2) { this.i1 = i1; this.i2 = i2; }
562     }
563     
564     public static class MultiANewArray {
565         public Type.Array type;
566         public int dims;
567         public MultiANewArray(Type.Array type, int dims) { this.type = type; this.dims = dims; }
568     }
569     
570     public static class Wide {
571         public final byte op;
572         public final int varNum;
573         public final int n;
574         Wide(byte op, int varNum) { this(op, varNum, 0); }
575         Wide(byte op, int varNum, int n) { this.op = op; this.varNum = varNum; this.n = n; }
576     }
577
578
579     // Emitting Bits //////////////////////////////////////////////////////////////////////////////
580    
581     private Object resolveTarget(Object arg) {
582         int target;
583         if (arg instanceof PhantomTarget) {
584             target = ((PhantomTarget)arg).getTarget();
585             if (target == -1) throw new IllegalStateException("unresolved phantom target");
586             arg = N(target);
587         } else {
588             target = ((Integer)arg).intValue();
589         }
590         if (target < 0 || target >= size)
591             throw new IllegalStateException("invalid target address " + target + "/" + size);
592         return arg;
593     }
594     
595     /** Computes the final bytecode for this method. 
596         @exception IllegalStateException if the data for a method is in an inconsistent state (required arguments missing, etc)
597         @exception Exn if the byteocode could not be generated for any other reason (constant pool full, etc)
598     */
599     void finish(ConstantPool cp) {
600         cp.addUtf8(method.name);
601         cp.addUtf8(method.getTypeDescriptor());
602         
603         for(Enumeration e = thrownExceptions.keys();e.hasMoreElements();)
604             cp.add(e.nextElement());
605         
606         if (size == NO_CODE) return;
607         for(int i=0;i<exnTable.size();i++)
608             ((ExnTableEnt)exnTable.elementAt(i)).finish(cp);
609         
610         // We'll set these correctly later
611         if ((flags & (NATIVE|ABSTRACT))==0) attrs.put("Code","");
612         if (thrownExceptions.size() > 0) attrs.put("Exceptions","");
613         attrs.finish(cp);
614         codeAttrs.finish(cp);
615         
616         cparg = new ConstantPool.Ent[size];
617         
618         for(int i=0, p=0;i<size;i++) {
619             switch(op[i]) {
620                 case LDC:
621                 case LDC_W:
622                 case LDC2_W:
623                 case GETSTATIC:
624                 case PUTSTATIC:
625                 case GETFIELD:
626                 case PUTFIELD:
627                 case INVOKEVIRTUAL:
628                 case INVOKESPECIAL:
629                 case INVOKESTATIC:
630                 case NEW:
631                 case ANEWARRAY:
632                 case CHECKCAST:
633                 case INSTANCEOF:
634                     cparg[i] = cp.add(arg[i]);
635                     break;
636                 case INVOKEINTERFACE:
637                     cparg[i] = cp.add(new ConstantPool.InterfaceMethodKey((Type.Class.Method)arg[i]));
638                     break;
639                 case MULTIANEWARRAY:
640                     cparg[i] = cp.add(((MultiANewArray)arg[i]).type);
641                     break;
642             }
643         }
644     }
645
646     private void generateCode(ConstantPool cp) {
647         try {
648             _generateCode(cp);
649         } catch(IOException e) {
650             throw new Error("should never happen");
651         }
652     }
653     
654     private void _generateCode(ConstantPool cp) throws IOException {
655         ByteArrayOutputStream baos = new ByteArrayOutputStream();
656         DataOutput o = new DataOutputStream(baos);
657     
658         int[] pc = new int[size];
659         int[] maxpc = pc;
660         int p, i;
661         
662         // Pass1 - Calculate maximum pc of each bytecode, widen some insns, resolve any unresolved jumps, etc
663         for(i=0, p=0;i<size;i++) {
664             byte op = this.op[i];
665             int opdata = OP_DATA[op&0xff];
666             int j;
667             maxpc[i] = p;
668             
669             if ((opdata & OP_BRANCH_FLAG)!= 0) { 
670                 try { 
671                     arg[i] = resolveTarget(arg[i]);
672                 } catch(RuntimeException e) {
673                     System.err.println("WARNING: Error resolving target for " + Integer.toHexString(op&0xff));
674                     throw e;
675                 }
676             }
677             
678             switch(op) {
679                 // Speical caculations
680                 case GOTO:
681                 case JSR: {
682                     int arg = ((Integer)this.arg[i]).intValue();
683                     if (arg < i && p - maxpc[arg] <= 32768) p += 3; 
684                     else p += 5;
685                     continue;
686                 }
687                 case NOP:
688                     if (EMIT_NOPS) p++;
689                     continue;
690                 case LOOKUPSWITCH:
691                 case TABLESWITCH: {
692                     Switch si = (Switch) arg[i];
693                     Object[] targets = si.targets;
694                     for(j=0;j<targets.length;j++) targets[j] = resolveTarget(targets[j]);
695                     si.defaultTarget = resolveTarget(si.defaultTarget);
696                     p += 1 + 3 + si.length(); // opcode itself, padding, data
697                     if (op == LOOKUPSWITCH) { // verify sanity of lookupswitch vals
698                         int[] vals = ((Switch.Lookup)si).vals;
699                         for(j=1;j<vals.length;j++)
700                             if (vals[j] <= vals[j-1])
701                                 throw new IllegalStateException("out of order/duplicate lookupswitch values");
702                     }
703                     continue;
704                 }
705                 case WIDE:
706                     p += ((Wide)arg[i]).op == IINC ? 5 : 3;
707                     continue;
708                 // May need widening
709                 case ILOAD: case ISTORE: case LLOAD: case LSTORE: case FLOAD:
710                 case FSTORE: case DLOAD: case DSTORE: case ALOAD: case ASTORE:
711                 case RET: {
712                     int arg = ((Integer)this.arg[i]).intValue();
713                     if (arg > 255) {
714                         this.op[i] = WIDE;
715                         this.arg[i] = new Wide(op, arg);
716                         p += 4;
717                         continue;
718                     }
719                     break;
720                 }
721                 case IINC: {
722                     Pair pair = (Pair) this.arg[i];
723                     if (pair.i1 > 255 || pair.i2 < -128 || pair.i2 > 127) {
724                         this.op[i] = WIDE;
725                         this.arg[i] = new Wide(IINC, pair.i1, pair.i2);
726                         p += 6;
727                         continue;
728                     }
729                     break;
730                 }
731                 case LDC:
732                     j = cp.getIndex(cparg[i]);
733                     if (j >= 256) {
734                         this.op[i] = op = LDC_W;
735                         p += 3;
736                         continue;
737                     }
738                     break;
739                 default:
740             }
741             if ((j = (opdata&OP_ARG_LENGTH_MASK)) == 7) throw new Error("shouldn't be here " + Integer.toString(op&0xff,16));
742             p += 1 + j;
743         }
744         
745         // Pass2 - Widen instructions if they can possibly be too short
746         for(i=0;i<size;i++) {
747             switch(op[i]) {
748                 case GOTO:
749                 case JSR: {
750                     int arg = ((Integer)this.arg[i]).intValue();
751                     int diff = maxpc[arg] - maxpc[i];
752                     if (diff < -32768 || diff > 32767)
753                         op[i] = op[i] == GOTO ? GOTO_W : JSR_W;
754                     break;
755                 }
756             }
757         }
758         
759         // Pass3 - Calculate actual pc
760         for(i=0, p=0;i<size;i++) {
761             byte op = this.op[i];
762             pc[i] = p;
763             switch(op) {
764                 case NOP:
765                     if (EMIT_NOPS) p++;
766                     break;
767                 case TABLESWITCH:
768                 case LOOKUPSWITCH: {
769                     Switch si = (Switch) arg[i];
770                     p++; // opcode itself
771                     p = (p + 3) & ~3; // padding
772                     p += 4; // default
773                     if (op == TABLESWITCH) p += 4 + 4 + si.size() * 4; // lo, hi, targets
774                     else p += 4 + si.size() * 4 * 2; // count, key, val * targets
775                     break;
776                 }
777                 case WIDE:
778                     p += 2 + (((Wide)arg[i]).op == IINC ? 4 : 2);
779                     break;                
780                 default: {
781                     int l = OP_DATA[op&0xff] & OP_ARG_LENGTH_MASK;
782                     if (l == 7) throw new Error("shouldn't be here");
783                     p += 1 + l;                    
784                 }
785             }
786         }
787         int codeSize = p;
788         
789         if (codeSize >= 65536) throw new ClassFile.Exn("method too large in size");
790         
791         o.writeShort(maxStack);
792         o.writeShort(maxLocals);
793         o.writeInt(codeSize);
794         
795         // Pass 4 - Actually write the bytecodes
796         for(i=0;i<size;i++) {
797             byte op = this.op[i];
798             int opdata = OP_DATA[op&0xff];
799             if (op == NOP && !EMIT_NOPS) continue;
800             o.writeByte(op);
801             int argLength = opdata & OP_ARG_LENGTH_MASK;
802             
803             if (argLength == 0) continue; // skip if no args
804             
805             // Write args
806             Object arg = this.arg[i];  
807             
808             switch(op) {
809                 case IINC: {
810                     Pair pair = (Pair) arg;
811                     if (pair.i1 > 255 || pair.i2 < -128 || pair.i2 > 127) throw new ClassFile.Exn("overflow of iinc arg"); 
812                     o.writeByte(pair.i1);
813                     o.writeByte(pair.i2);
814                     break;
815                 }
816                 case TABLESWITCH:
817                 case LOOKUPSWITCH: {
818                     Switch si = (Switch) arg;
819                     int mypc = pc[i];
820                     for(p = pc[i]+1;(p&3)!=0;p++) o.writeByte(0);
821                     o.writeInt(pc[si.getDefaultTarget()] - mypc);
822                     if (op == LOOKUPSWITCH) {
823                         int[] vals = ((Switch.Lookup)si).vals;
824                         o.writeInt(si.size());
825                         for(int j=0;j<si.size();j++) {
826                             o.writeInt(vals[j]);
827                             o.writeInt(pc[si.getTarget(j)] - mypc);
828                         }
829                     } else {
830                         Switch.Table tsi = (Switch.Table) si;
831                         o.writeInt(tsi.lo);
832                         o.writeInt(tsi.hi);
833                         for(int j=0;j<tsi.size();j++) o.writeInt(pc[tsi.getTarget(j)] - mypc);
834                     }
835                     break;
836                 }
837                 case WIDE: {
838                     Wide wide = (Wide) arg;
839                     o.writeByte(wide.op);
840                     o.writeShort(wide.varNum);
841                     if (wide.op == IINC) o.writeShort(wide.n);
842                     break;
843                 }
844                 case MULTIANEWARRAY: {
845                     o.writeShort(cp.getIndex(cparg[i]));
846                     int v = ((MultiANewArray) arg).dims;
847                     if (v >= 256) throw new ClassFile.Exn("overflow of dimensions in multianewarray");
848                     o.writeByte(v);
849                     break;
850                 }
851                 case INVOKEINTERFACE:
852                     o.writeShort(cp.getIndex(cparg[i]));
853                     o.writeByte(((Type.Class.Method)arg).argTypes.length + 1);
854                     o.writeByte(0);
855                     break;
856                 default:
857                     if ((opdata & OP_BRANCH_FLAG) != 0) {
858                         int v = pc[((Integer)arg).intValue()] - pc[i];
859                         if (argLength == 2) {
860                             if (v < -32768 || v > 32767) throw new ClassFile.Exn("overflow of s2 offset");
861                             o.writeShort(v);
862                         } else if (argLength == 4) {
863                             o.writeInt(v);
864                         } else {
865                             throw new Error("should never happen");
866                         }
867                     } else if ((opdata & OP_CPENT_FLAG) != 0) {
868                         int v = cp.getIndex(cparg[i]);
869                         if (argLength == 1) o.writeByte(v);
870                         else if (argLength == 2) o.writeShort(v);
871                         else throw new Error("should never happen");
872                     } else if (argLength == 7) {
873                         throw new Error("should never happen - variable length instruction not explicitly handled");
874                     } else {
875                         int iarg  = (arg instanceof Type.Primitive)
876                             ? ((Type.Primitive)arg).toArraySpec()
877                             : ((Integer)arg).intValue();
878                         if (argLength == 1) {
879                             if ((opdata & OP_UNSIGNED_FLAG) != 0 ? iarg >= 256 : (iarg < -128 || iarg >= 128))
880                                 throw new ClassFile.Exn("overflow of s/u1 option");
881                             o.writeByte(iarg);
882                         } else if (argLength == 2) {
883                             if ((opdata & OP_UNSIGNED_FLAG) != 0 ? iarg >= 65536 : (iarg < -32768 || iarg >= 32768))
884                                 throw new ClassFile.Exn("overflow of s/u2 option");
885                             o.writeShort(iarg);
886                         } else {
887                             throw new Error("should never happen");
888                         }
889                     }
890                     break;
891             }
892         }
893
894         //if (baos.size() - 8 != codeSize) throw new Error("we didn't output what we were supposed to");
895         
896         o.writeShort(exnTable.size());
897         for(i=0;i<exnTable.size();i++)
898             ((ExnTableEnt)exnTable.elementAt(i)).dump(o, pc, codeSize, cp);
899         
900         codeAttrs.dump(o,cp);
901         baos.close();
902         
903         byte[] codeAttribute = baos.toByteArray();
904         attrs.put("Code", codeAttribute);        
905     }
906         
907     void generateExceptions(ConstantPool cp) throws IOException {
908         if (thrownExceptions.size() > 0) {
909             ByteArrayOutputStream baos = new ByteArrayOutputStream();
910             DataOutputStream o = new DataOutputStream(baos);
911             o.writeShort(thrownExceptions.size());
912             for(Enumeration e = thrownExceptions.keys();e.hasMoreElements();)
913                 o.writeShort(cp.getIndex(thrownExceptions.get(e.nextElement())));
914             baos.close();
915             attrs.put("Exceptions", baos.toByteArray());
916         }
917     }
918     
919     void dump(DataOutput o, ConstantPool cp) throws IOException {
920         if ((flags & (NATIVE|ABSTRACT))==0) generateCode(cp);
921         generateExceptions(cp);
922         
923         o.writeShort(flags);
924         o.writeShort(cp.getUtf8Index(method.name));
925         o.writeShort(cp.getUtf8Index(method.getTypeDescriptor()));
926         attrs.dump(o,cp);
927     }
928     
929    
930     /** Class that represents a target that isn't currently know. The
931         target MUST be set with setTarget() before the classfile is
932         written.  This class is more or less a mutable integer */
933     public static class PhantomTarget {
934         private int target = -1;
935         public void setTarget(int target) { this.target = target; }
936         public int getTarget() { return target; }
937     }
938     
939     private static Integer N(int n) { return new Integer(n); }
940     private static Long N(long n) { return new Long(n); }
941     private static Float N(float f) { return new Float(f); }
942     private static Double N(double d) { return new Double(d); }
943     private static int max(int a, int b) { return a > b ? a : b; }
944     
945     private static final int OP_BRANCH_FLAG = 1<<3;
946     private static final int OP_CPENT_FLAG = 1<<4;
947     private static final int OP_UNSIGNED_FLAG = 1<<5;
948     private static final int OP_VALID_FLAG = 1<<6;
949     private static final int OP_ARG_LENGTH_MASK = 7;
950     private static final boolean OP_VALID(byte op) { return (OP_DATA[op&0xff] & OP_VALID_FLAG) != 0; }
951     private static final int OP_ARG_LENGTH(byte op) { return (OP_DATA[op&0xff]&OP_ARG_LENGTH_MASK); }
952     private static final boolean OP_CPENT(byte op) { return (OP_DATA[op&0xff]&OP_CPENT_FLAG) != 0; }
953     private static final boolean OP_BRANCH(byte op) { return (OP_DATA[op&0xff]&OP_BRANCH_FLAG ) != 0; }
954     private static final boolean OP_UNSIGNED(byte op) { return (OP_DATA[op&0xff]&OP_UNSIGNED_FLAG ) != 0; }
955     
956     // Run perl -x src/org/ibex/classgen/CGConst.java to generate this
957     private static final byte[] OP_DATA = {
958         0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 
959         0x41, 0x42, 0x51, 0x52, 0x52, 0x61, 0x61, 0x61, 0x61, 0x61, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 
960         0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 
961         0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x61, 0x61, 0x61, 0x61, 0x61, 0x40, 0x40, 0x40, 0x40, 0x40, 
962         0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 
963         0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 
964         0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 
965         0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 
966         0x40, 0x40, 0x40, 0x40, 0x42, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 
967         0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 
968         0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x41, 0x47, 0x47, 0x40, 0x40, 0x40, 0x40, 
969         0x40, 0x40, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x54, 0x01, 0x52, 0x41, 0x52, 0x40, 0x40, 
970         0x52, 0x52, 0x40, 0x40, 0x47, 0x53, 0x4a, 0x4a, 0x4c, 0x4c, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
971         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
972         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
973         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
974     };
975
976     // Debugging //////////////////////////////////////////////////////////////////////////////
977
978     public void debugBodyToString(StringBuffer sb) {
979         // This is intentionally a local variable so it can be removed by gcclass
980         final String[] OP_NAMES = new String[]{
981             "nop", "aconst_null", "iconst_m1", "iconst_0", "iconst_1", "iconst_2", 
982             "iconst_3", "iconst_4", "iconst_5", "lconst_0", "lconst_1", "fconst_0", 
983             "fconst_1", "fconst_2", "dconst_0", "dconst_1", "bipush", "sipush", 
984             "ldc", "ldc_w", "ldc2_w", "iload", "lload", "fload", 
985             "dload", "aload", "iload_0", "iload_1", "iload_2", "iload_3", 
986             "lload_0", "lload_1", "lload_2", "lload_3", "fload_0", "fload_1", 
987             "fload_2", "fload_3", "dload_0", "dload_1", "dload_2", "dload_3", 
988             "aload_0", "aload_1", "aload_2", "aload_3", "iaload", "laload", 
989             "faload", "daload", "aaload", "baload", "caload", "saload", 
990             "istore", "lstore", "fstore", "dstore", "astore", "istore_0", 
991             "istore_1", "istore_2", "istore_3", "lstore_0", "lstore_1", "lstore_2", 
992             "lstore_3", "fstore_0", "fstore_1", "fstore_2", "fstore_3", "dstore_0", 
993             "dstore_1", "dstore_2", "dstore_3", "astore_0", "astore_1", "astore_2", 
994             "astore_3", "iastore", "lastore", "fastore", "dastore", "aastore", 
995             "bastore", "castore", "sastore", "pop", "pop2", "dup", 
996             "dup_x1", "dup_x2", "dup2", "dup2_x1", "dup2_x2", "swap", 
997             "iadd", "ladd", "fadd", "dadd", "isub", "lsub", 
998             "fsub", "dsub", "imul", "lmul", "fmul", "dmul", 
999             "idiv", "ldiv", "fdiv", "ddiv", "irem", "lrem", 
1000             "frem", "drem", "ineg", "lneg", "fneg", "dneg", 
1001             "ishl", "lshl", "ishr", "lshr", "iushr", "lushr", 
1002             "iand", "land", "ior", "lor", "ixor", "lxor", 
1003             "iinc", "i2l", "i2f", "i2d", "l2i", "l2f", 
1004             "l2d", "f2i", "f2l", "f2d", "d2i", "d2l", 
1005             "d2f", "i2b", "i2c", "i2s", "lcmp", "fcmpl", 
1006             "fcmpg", "dcmpl", "dcmpg", "ifeq", "ifne", "iflt", 
1007             "ifge", "ifgt", "ifle", "if_icmpeq", "if_icmpne", "if_icmplt", 
1008             "if_icmpge", "if_icmpgt", "if_icmple", "if_acmpeq", "if_acmpne", "goto", 
1009             "jsr", "ret", "tableswitch", "lookupswitch", "ireturn", "lreturn", 
1010             "freturn", "dreturn", "areturn", "return", "getstatic", "putstatic", 
1011             "getfield", "putfield", "invokevirtual", "invokespecial", "invokestatic", "invokeinterface", 
1012             "", "new", "newarray", "anewarray", "arraylength", "athrow", 
1013             "checkcast", "instanceof", "monitorenter", "monitorexit", "wide", "multianewarray", 
1014             "ifnull", "ifnonnull", "goto_w", "jsr_w", "", "", 
1015             "", "", "", "", "", "", 
1016             "", "", "", "", "", "", 
1017             "", "", "", "", "", "", 
1018             "", "", "", "", "", "", 
1019             "", "", "", "", "", "", 
1020             "", "", "", "", "", "", 
1021             "", "", "", "", "", "", 
1022             "", "", "", "", "", "", 
1023             "", "", "", ""
1024         };
1025         for(int i=0;i<size();i++) {
1026             sb.append("    ");
1027             for(int j=i==0?1:i;j<10000;j*=10) sb.append(" ");
1028             sb.append(i).append(": ");
1029             sb.append(OP_NAMES[op[i]&0xff]);
1030             String s = null;
1031             if (arg[i] instanceof Type) s = ((Type)arg[i]).toString();
1032             else if (arg[i] instanceof Type.Class.Member) s = ((Type.Class.Member)arg[i]).toString();
1033             else if (arg[i] instanceof String) s = "\"" + arg[i] + "\"";
1034             else if (arg[i] != null) s = arg[i].toString();
1035             if (s != null) sb.append(" ").append(s);
1036             sb.append("\n");
1037         }
1038     }
1039
1040     // Unused //////////////////////////////////////////////////////////////////////////////
1041
1042     /** Negates the IF* instruction, <i>op</i>  (IF_ICMPGT -> IF_ICMPLE, IFNE -> IFEQ,  etc)
1043         @exception IllegalArgumentException if <i>op</i> isn't an IF* instruction */
1044     public static byte negate(byte op) {
1045         switch(op) {
1046             case IFEQ: return IFNE;
1047             case IFNE: return IFEQ;
1048             case IFLT: return IFGE;
1049             case IFGE: return IFLT;
1050             case IFGT: return IFLE;
1051             case IFLE: return IFGT;
1052             case IF_ICMPEQ: return IF_ICMPNE;
1053             case IF_ICMPNE: return IF_ICMPEQ;
1054             case IF_ICMPLT: return IF_ICMPGE;
1055             case IF_ICMPGE: return IF_ICMPLT;
1056             case IF_ICMPGT: return IF_ICMPLE;
1057             case IF_ICMPLE: return IF_ICMPGT;
1058             case IF_ACMPEQ: return IF_ACMPNE;
1059             case IF_ACMPNE: return IF_ACMPEQ;
1060             
1061             default:
1062                 throw new IllegalArgumentException("Can't negate " + Integer.toHexString(op));
1063         }
1064     }
1065
1066
1067 }