/Users/lyon/j4p/src/javassist/bytecode/CodeIterator.java
|
1 /*
2 * Javassist, a Java-bytecode translator toolkit.
3 * Copyright (C) 1999-2003 Shigeru Chiba. All Rights Reserved.
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. Alternatively, the contents of this file may be used under
8 * the terms of the GNU Lesser General Public License Version 2.1 or later.
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 */
15
16 package javassist.bytecode;
17
18 /**
19 * An iterator for editing a code attribute.
20 *
21 * <p>This iterator does not provide <code>remove()</code>.
22 * If a piece of code in a <code>Code_attribute</code> is unnecessary,
23 * it should be overwritten with <code>NOP</code>.
24 *
25 * @see CodeAttribute#iterator()
26 */
27 public class CodeIterator implements Opcode {
28 protected CodeAttribute codeAttr;
29 protected byte[] bytecode;
30 protected int endPos;
31 protected int currentPos;
32
33 CodeIterator(CodeAttribute ca) {
34 codeAttr = ca;
35 bytecode = ca.getCode();
36 begin();
37 }
38
39 /**
40 * Moves to the first instruction.
41 */
42 public void begin() {
43 currentPos = 0;
44 endPos = getCodeLength();
45 }
46
47 /**
48 * Moves to the given index.
49 *
50 * <p>The index of the next instruction is set to the given index.
51 * The successive call to <code>next()</code>
52 * returns the index that has been given to <code>move()</code>.
53 *
54 * <p>Note that the index is into the byte array returned by
55 * <code>get().getCode()</code>.
56 *
57 * @see CodeAttribute#getCode()
58 */
59 public void move(int index) {
60 currentPos = index;
61 }
62
63 /**
64 * Returns a Code attribute read with this iterator.
65 */
66 public CodeAttribute get() {
67 return codeAttr;
68 }
69
70 /**
71 * Returns <code>code_length</code> of <code>Code_attribute</code>.
72 */
73 public int getCodeLength() {
74 return bytecode.length;
75 }
76
77 /**
78 * Returns the unsigned 8bit value at the given index.
79 */
80 public int byteAt(int index) {
81 return bytecode[index] & 0xff;
82 }
83
84 /**
85 * Writes an 8bit value at the given index.
86 */
87 public void writeByte(int value, int index) {
88 bytecode[index] = (byte) value;
89 }
90
91 /**
92 * Returns the unsigned 16bit value at the given index.
93 */
94 public int u16bitAt(int index) {
95 return ByteArray.readU16bit(bytecode, index);
96 }
97
98 /**
99 * Returns the signed 16bit value at the given index.
100 */
101 public int s16bitAt(int index) {
102 return ByteArray.readS16bit(bytecode, index);
103 }
104
105 /**
106 * Writes a 16 bit integer at the index.
107 */
108 public void write16bit(int value, int index) {
109 ByteArray.write16bit(value, bytecode, index);
110 }
111
112 /**
113 * Returns the signed 32bit value at the given index.
114 */
115 public int s32bitAt(int index) {
116 return ByteArray.read32bit(bytecode, index);
117 }
118
119 /**
120 * Writes a 32bit integer at the index.
121 */
122 public void write32bit(int value, int index) {
123 ByteArray.write32bit(value, bytecode, index);
124 }
125
126 /**
127 * Writes a byte array at the index.
128 *
129 * @param code may be a zero-length array.
130 */
131 public void write(byte[] code, int index) {
132 int len = code.length;
133 for (int j = 0; j < len; ++j)
134 bytecode[index++] = code[j];
135 }
136
137 /**
138 * Returns true if there is more instructions.
139 */
140 public boolean hasNext() {
141 return currentPos < endPos;
142 }
143
144 /**
145 * Returns the index of the next instruction
146 * (not the operand following the current opcode).
147 *
148 * <p>Note that the index is into the byte array returned by
149 * <code>get().getCode()</code>.
150 *
151 * @see CodeAttribute#getCode()
152 * @see CodeIterator#byteAt(int)
153 */
154 public int next() throws BadBytecode {
155 int pos = currentPos;
156 currentPos = nextOpcode(bytecode, pos);
157 return pos;
158 }
159
160 /**
161 * Moves to the first instruction following
162 * constructor invocation <code>super()</code> or <code>this()</code>.
163 *
164 * <p>This method skips all the instructions for executing
165 * <code>super()</code> or <code>this()</code>, which should be
166 * placed at the beginning of a constructor body.
167 *
168 * <p>This method returns the index of INVOKESPECIAL instruction
169 * executing <code>super()</code> or <code>this()</code>.
170 * A successive call to <code>next()</code> returns the
171 * index of the next instruction following that INVOKESPECIAL.
172 *
173 * <p>This method works only for a constructor.
174 *
175 * @return the index of the INVOKESPECIAL instruction, or -1
176 * if a constructor invocation is not found.
177 */
178 public int skipConstructor() throws BadBytecode {
179 return skipSuperConstructor0(-1);
180 }
181
182 /**
183 * Moves to the first instruction following super
184 * constructor invocation <code>super()</code>.
185 *
186 * <p>This method skips all the instructions for executing
187 * <code>super()</code>, which should be
188 * placed at the beginning of a constructor body.
189 *
190 * <p>This method returns the index of INVOKESPECIAL instruction
191 * executing <code>super()</code>.
192 * A successive call to <code>next()</code> returns the
193 * index of the next instruction following that INVOKESPECIAL.
194 *
195 * <p>This method works only for a constructor.
196 *
197 * @return the index of the INVOKESPECIAL instruction, or -1
198 * if a super constructor invocation is not found
199 * but <code>this()</code> is found.
200 */
201 public int skipSuperConstructor() throws BadBytecode {
202 return skipSuperConstructor0(0);
203 }
204
205 /**
206 * Moves to the first instruction following explicit
207 * constructor invocation <code>this()</code>.
208 *
209 * <p>This method skips all the instructions for executing
210 * <code>this()</code>, which should be
211 * placed at the beginning of a constructor body.
212 *
213 * <p>This method returns the index of INVOKESPECIAL instruction
214 * executing <code>this()</code>.
215 * A successive call to <code>next()</code> returns the
216 * index of the next instruction following that INVOKESPECIAL.
217 *
218 * <p>This method works only for a constructor.
219 *
220 * @return the index of the INVOKESPECIAL instruction, or -1
221 * if a explicit constructor invocation is not found
222 * but <code>super()</code> is found.
223 */
224 public int skipThisConstructor() throws BadBytecode {
225 return skipSuperConstructor0(1);
226 }
227
228 /* skipSuper 1: this(), 0: super(), -1: both.
229 */
230 private int skipSuperConstructor0(int skipThis) throws BadBytecode {
231 begin();
232 ConstPool cp = codeAttr.getConstPool();
233 String thisClassName = codeAttr.getDeclaringClass();
234 int nested = 0;
235 while (hasNext()) {
236 int index = next();
237 int c = byteAt(index);
238 if (c == NEW)
239 ++nested;
240 else if (c == INVOKESPECIAL) {
241 int mref = ByteArray.readU16bit(bytecode, index + 1);
242 if (cp.getMethodrefName(mref).equals(MethodInfo.nameInit))
243 if (--nested < 0) {
244 if (skipThis < 0)
245 return index;
246
247 String cname = cp.getMethodrefClassName(mref);
248 if (cname.equals(thisClassName) == (skipThis > 0))
249 return index;
250 else
251 break;
252 }
253 }
254 }
255
256 begin();
257 return -1;
258 }
259
260 /**
261 * Inserts the given bytecode sequence
262 * before the next instruction that would be returned by
263 * <code>next()</code> (not before the instruction returned
264 * by tha last call to <code>next()</code>).
265 * Branch offsets and the exception table are also updated.
266 *
267 * <p>If the next instruction is at the beginning of a block statement,
268 * then the bytecode is inserted within that block.
269 *
270 * <p>An extra gap may be inserted at the end of the inserted
271 * bytecode sequence for adjusting alignment if the code attribute
272 * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>.
273 *
274 * @param code inserted bytecode sequence.
275 * @return the index indicating the first byte of the
276 * inserted byte sequence.
277 */
278 public int insert(byte[] code)
279 throws BadBytecode {
280 int pos = currentPos;
281 insert0(currentPos, code, false);
282 return pos;
283 }
284
285 /**
286 * Inserts the given bytecode sequence
287 * before the instruction at the given index <code>pos</code>.
288 * Branch offsets and the exception table are also updated.
289 *
290 * <p>If the instruction at the given index is at the beginning
291 * of a block statement,
292 * then the bytecode is inserted within that block.
293 *
294 * <p>An extra gap may be inserted at the end of the inserted
295 * bytecode sequence for adjusting alignment if the code attribute
296 * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>.
297 *
298 * @param pos the index at which a byte sequence is inserted.
299 * @param code inserted bytecode sequence.
300 */
301 public void insert(int pos, byte[] code) throws BadBytecode {
302 insert0(pos, code, false);
303 }
304
305 /**
306 * Inserts the given bytecode sequence exclusively
307 * before the next instruction that would be returned by
308 * <code>next()</code> (not before the instruction returned
309 * by tha last call to <code>next()</code>).
310 * Branch offsets and the exception table are also updated.
311 *
312 * <p>If the next instruction is at the beginning of a block statement,
313 * then the bytecode is excluded from that block.
314 *
315 * <p>An extra gap may be inserted at the end of the inserted
316 * bytecode sequence for adjusting alignment if the code attribute
317 * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>.
318 *
319 * @param code inserted bytecode sequence.
320 * @return the index indicating the first byte of the
321 * inserted byte sequence.
322 */
323 public int insertEx(byte[] code)
324 throws BadBytecode {
325 int pos = currentPos;
326 insert0(currentPos, code, true);
327 return pos;
328 }
329
330 /**
331 * Inserts the given bytecode sequence exclusively
332 * before the instruction at the given index <code>pos</code>.
333 * Branch offsets and the exception table are also updated.
334 *
335 * <p>If the instruction at the given index is at the beginning
336 * of a block statement,
337 * then the bytecode is excluded from that block.
338 *
339 * <p>An extra gap may be inserted at the end of the inserted
340 * bytecode sequence for adjusting alignment if the code attribute
341 * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>.
342 *
343 * @param pos the index at which a byte sequence is inserted.
344 * @param code inserted bytecode sequence.
345 */
346 public void insertEx(int pos, byte[] code) throws BadBytecode {
347 insert0(pos, code, true);
348 }
349
350 private void insert0(int pos, byte[] code, boolean exclusive)
351 throws BadBytecode {
352 int len = code.length;
353 if (len <= 0)
354 return;
355
356 insertGapCore(pos, len, exclusive); // currentPos will change.
357 for (int j = 0; j < len; ++j)
358 bytecode[pos++] = code[j];
359 }
360
361 /**
362 * Inserts a gap
363 * before the next instruction that would be returned by
364 * <code>next()</code> (not before the instruction returned
365 * by tha last call to <code>next()</code>).
366 * Branch offsets and the exception table are also updated.
367 * The inserted gap is filled with NOP. The gap length may be
368 * extended to a multiple of 4.
369 *
370 * <p>If the next instruction is at the beginning of a block statement,
371 * then the gap is inserted within that block.
372 *
373 * @param length gap length
374 * @return the index indicating the first byte of the inserted gap.
375 */
376 public int insertGap(int length) throws BadBytecode {
377 int pos = currentPos;
378 insertGapCore(currentPos, length, false);
379 return pos;
380 }
381
382 /**
383 * Inserts a gap in front of the instruction at the given
384 * index <code>pos</code>.
385 * Branch offsets and the exception table are also updated.
386 * The inserted gap is filled with NOP. The gap length may be
387 * extended to a multiple of 4.
388 *
389 * <p>If the instruction at the given index is at the beginning
390 * of a block statement,
391 * then the gap is inserted within that block.
392 *
393 * @param pos the index at which a gap is inserted.
394 * @param length gap length.
395 * @return the length of the inserted gap.
396 * It might be bigger than <code>length</code>.
397 */
398 public int insertGap(int pos, int length) throws BadBytecode {
399 return insertGapCore(pos, length, false);
400 }
401
402 /**
403 * Inserts an exclusive gap
404 * before the next instruction that would be returned by
405 * <code>next()</code> (not before the instruction returned
406 * by tha last call to <code>next()</code>).
407 * Branch offsets and the exception table are also updated.
408 * The inserted gap is filled with NOP. The gap length may be
409 * extended to a multiple of 4.
410 *
411 * <p>If the next instruction is at the beginning of a block statement,
412 * then the gap is excluded from that block.
413 *
414 * @param length gap length
415 * @return the index indicating the first byte of the inserted gap.
416 */
417 public int insertExGap(int length) throws BadBytecode {
418 int pos = currentPos;
419 insertGapCore(currentPos, length, true);
420 return pos;
421 }
422
423 /**
424 * Inserts an exclusive gap in front of the instruction at the given
425 * index <code>pos</code>.
426 * Branch offsets and the exception table are also updated.
427 * The inserted gap is filled with NOP. The gap length may be
428 * extended to a multiple of 4.
429 *
430 * <p>If the instruction at the given index is at the beginning
431 * of a block statement,
432 * then the gap is excluded from that block.
433 *
434 * @param pos the index at which a gap is inserted.
435 * @param length gap length.
436 * @return the length of the inserted gap.
437 * It might be bigger than <code>length</code>.
438 */
439 public int insertExGap(int pos, int length) throws BadBytecode {
440 return insertGapCore(pos, length, true);
441 }
442
443 /**
444 * @return the length of the really inserted gap.
445 */
446 private int insertGapCore(int pos, int length, boolean exclusive)
447 throws BadBytecode {
448 if (length <= 0)
449 return 0;
450
451 int cur = currentPos;
452 byte[] c = insertGap(bytecode, pos, length, exclusive,
453 get().getExceptionTable());
454 int length2 = c.length - bytecode.length;
455 if (cur >= pos)
456 currentPos = cur + length2;
457
458 codeAttr.setCode(c);
459 bytecode = c;
460 endPos = getCodeLength();
461 return length2;
462 }
463
464 /**
465 * Copies and inserts the entries in the given exception table
466 * at the beginning of the exception table in the code attribute
467 * edited by this object.
468 *
469 * @param offset the value added to the code positions included
470 * in the entries.
471 */
472 public void insert(ExceptionTable et, int offset) {
473 codeAttr.getExceptionTable().add(0, et, offset);
474 }
475
476 /**
477 * Appends the given bytecode sequence at the end.
478 *
479 * @param code the bytecode appended.
480 * @return the position of the first byte of the appended bytecode.
481 */
482 public int append(byte[] code) {
483 int size = getCodeLength();
484 int len = code.length;
485 if (len <= 0)
486 return size;
487
488 appendGap(len);
489 byte[] dest = bytecode;
490 for (int i = 0; i < len; ++i)
491 dest[i + size] = code[i];
492
493 return size;
494 }
495
496 /**
497 * Appends a gap at the end of the bytecode sequence.
498 *
499 * @param length gap length
500 */
501 public void appendGap(int gapLength) {
502 byte[] code = bytecode;
503 int codeLength = code.length;
504 byte[] newcode = new byte[codeLength + gapLength];
505
506 int i;
507 for (i = 0; i < codeLength; ++i)
508 newcode[i] = code[i];
509
510 for (i = codeLength; i < codeLength + gapLength; ++i)
511 newcode[i] = NOP;
512
513 codeAttr.setCode(newcode);
514 bytecode = newcode;
515 endPos = getCodeLength();
516 }
517
518 /**
519 * Copies and appends the entries in the given exception table
520 * at the end of the exception table in the code attribute
521 * edited by this object.
522 *
523 * @param offset the value added to the code positions included
524 * in the entries.
525 */
526 public void append(ExceptionTable et, int offset) {
527 ExceptionTable table = codeAttr.getExceptionTable();
528 table.add(table.size(), et, offset);
529 }
530
531 /* opcodeLegth is used for implementing nextOpcode().
532 */
533 private static final int opcodeLength[] = {
534 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 3,
535 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
536 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1,
537 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
538 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
539 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
540 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1,
541 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3,
542 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 0, 0, 1, 1, 1, 1, 1, 1, 3, 3,
543 3, 3, 3, 3, 3, 5, 0, 3, 2, 3, 1, 1, 3, 3, 1, 1, 0, 4, 3, 3,
544 5, 5
545 };
546 // 0 .. UNUSED (186), LOOKUPSWITCH, TABLESWITCH, WIDE
547
548 /**
549 * Calculates the index of the next opcode.
550 */
551 static int nextOpcode(byte[] code, int index)
552 throws BadBytecode {
553 int opcode;
554 try {
555 opcode = code[index] & 0xff;
556 } catch (IndexOutOfBoundsException e) {
557 throw new BadBytecode("invalid opcode address");
558 }
559
560 try {
561 int len = opcodeLength[opcode];
562 if (len > 0)
563 return index + len;
564 else if (opcode == WIDE)
565 if (code[index + 1] == (byte) IINC) // WIDE IINC
566 return index + 6;
567 else
568 return index + 4; // WIDE ...
569 else {
570 int index2 = (index & ~3) + 8;
571 if (opcode == LOOKUPSWITCH) {
572 int npairs = ByteArray.read32bit(code, index2);
573 return index2 + npairs * 8 + 4;
574 } else if (opcode == TABLESWITCH) {
575 int low = ByteArray.read32bit(code, index2);
576 int high = ByteArray.read32bit(code, index2 + 4);
577 return index2 + (high - low + 1) * 4 + 8;
578 }
579 // else
580 // throw new BadBytecode(opcode);
581 }
582 } catch (IndexOutOfBoundsException e) {
583 }
584
585 // opcode is UNUSED or an IndexOutOfBoundsException was thrown.
586 throw new BadBytecode(opcode);
587 }
588
589 // methods for implementing insertGap().
590
591 /* If "where" is the beginning of a block statement, then the inserted
592 * gap is also included in the block statement.
593 * "where" must indicate the first byte of an opcode.
594 * The inserted gap is filled with NOP. gapLength may be extended to
595 * a multiple of 4.
596 */
597 static byte[] insertGap(byte[] code, int where, int gapLength,
598 boolean exclusive, ExceptionTable etable)
599 throws BadBytecode {
600 if (gapLength <= 0)
601 return code;
602
603 try {
604 return insertGap0(code, where, gapLength, exclusive, etable);
605 } catch (AlignmentException e) {
606 try {
607 return insertGap0(code, where, (gapLength + 3) & ~3,
608 exclusive, etable);
609 } catch (AlignmentException e2) {
610 throw new RuntimeException("fatal error?");
611 }
612 }
613 }
614
615 private static byte[] insertGap0(byte[] code, int where, int gapLength,
616 boolean exclusive, ExceptionTable etable)
617 throws BadBytecode, AlignmentException {
618 int codeLength = code.length;
619 byte[] newcode = new byte[codeLength + gapLength];
620 insertGap2(code, where, gapLength, codeLength, newcode, exclusive);
621 etable.shiftPc(where, gapLength, exclusive);
622 return newcode;
623 }
624
625 private static void insertGap2(byte[] code, int where, int gapLength,
626 int endPos, byte[] newcode, boolean exclusive)
627 throws BadBytecode, AlignmentException {
628 int nextPos;
629 int i = 0;
630 int j = 0;
631 for (; i < endPos; i = nextPos) {
632 if (i == where) {
633 int j2 = j + gapLength;
634 while (j < j2)
635 newcode[j++] = NOP;
636 }
637
638 nextPos = nextOpcode(code, i);
639 int inst = code[i] & 0xff;
640 // if<cond>, if_icmp<cond>, if_acmp<cond>, goto, jsr
641 if ((153 <= inst && inst <= 168)
642 || inst == IFNULL || inst == IFNONNULL) {
643 /* 2bytes *signed* offset */
644 int offset = (code[i + 1] << 8) | (code[i + 2] & 0xff);
645 offset = newOffset(i, offset, where, gapLength, exclusive);
646 newcode[j] = code[i];
647 ByteArray.write16bit(offset, newcode, j + 1);
648 j += 3;
649 } else if (inst == GOTO_W || inst == JSR_W) {
650 /* 4bytes offset */
651 int offset = ByteArray.read32bit(code, i + 1);
652 offset = newOffset(i, offset, where, gapLength, exclusive);
653 newcode[j++] = code[i];
654 ByteArray.write32bit(offset, newcode, j);
655 j += 4;
656 } else if (inst == TABLESWITCH) {
657 if (i != j && (gapLength & 3) != 0)
658 throw new AlignmentException();
659
660 int i0 = i;
661 int i2 = (i & ~3) + 4; // 0-3 byte padding
662 while (i0 < i2)
663 newcode[j++] = code[i0++];
664
665 int defaultbyte = newOffset(i, ByteArray.read32bit(code, i2),
666 where, gapLength, exclusive);
667 ByteArray.write32bit(defaultbyte, newcode, j);
668 int lowbyte = ByteArray.read32bit(code, i2 + 4);
669 ByteArray.write32bit(lowbyte, newcode, j + 4);
670 int highbyte = ByteArray.read32bit(code, i2 + 8);
671 ByteArray.write32bit(highbyte, newcode, j + 8);
672 j += 12;
673 i0 = i2 + 12;
674 i2 = i0 + (highbyte - lowbyte + 1) * 4;
675 while (i0 < i2) {
676 int offset = newOffset(i, ByteArray.read32bit(code, i0),
677 where, gapLength, exclusive);
678 ByteArray.write32bit(offset, newcode, j);
679 j += 4;
680 i0 += 4;
681 }
682 } else if (inst == LOOKUPSWITCH) {
683 if (i != j && (gapLength & 3) != 0)
684 throw new AlignmentException();
685
686 int i0 = i;
687 int i2 = (i & ~3) + 4; // 0-3 byte padding
688 while (i0 < i2)
689 newcode[j++] = code[i0++];
690
691 int defaultbyte = newOffset(i, ByteArray.read32bit(code, i2),
692 where, gapLength, exclusive);
693 ByteArray.write32bit(defaultbyte, newcode, j);
694 int npairs = ByteArray.read32bit(code, i2 + 4);
695 ByteArray.write32bit(npairs, newcode, j + 4);
696 j += 8;
697 i0 = i2 + 8;
698 i2 = i0 + npairs * 8;
699 while (i0 < i2) {
700 ByteArray.copy32bit(code, i0, newcode, j);
701 int offset = newOffset(i,
702 ByteArray.read32bit(code, i0 + 4),
703 where, gapLength, exclusive);
704 ByteArray.write32bit(offset, newcode, j + 4);
705 j += 8;
706 i0 += 8;
707 }
708 } else
709 while (i < nextPos)
710 newcode[j++] = code[i++];
711 }
712 }
713
714 private static int newOffset(int i, int offset, int where,
715 int gapLength, boolean exclusive) {
716 int target = i + offset;
717 if (i < where) {
718 if (where < target || (exclusive && where == target))
719 offset += gapLength;
720 } else if (target < where || (!exclusive && where == target))
721 offset -= gapLength;
722
723 return offset;
724 }
725 }
726
727
728 class AlignmentException extends Exception {
729 }
730