1 // ASM: a very small and fast Java bytecode manipulation framework 2 // Copyright (c) 2000-2011 INRIA, France Telecom 3 // All rights reserved. 4 // 5 // Redistribution and use in source and binary forms, with or without 6 // modification, are permitted provided that the following conditions 7 // are met: 8 // 1. Redistributions of source code must retain the above copyright 9 // notice, this list of conditions and the following disclaimer. 10 // 2. Redistributions in binary form must reproduce the above copyright 11 // notice, this list of conditions and the following disclaimer in the 12 // documentation and/or other materials provided with the distribution. 13 // 3. Neither the name of the copyright holders nor the names of its 14 // contributors may be used to endorse or promote products derived from 15 // this software without specific prior written permission. 16 // 17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 27 // THE POSSIBILITY OF SUCH DAMAGE. 28 package org.objectweb.asm.tree; 29 30 import java.util.ListIterator; 31 import java.util.NoSuchElementException; 32 import org.objectweb.asm.MethodVisitor; 33 34 /** 35 * A doubly linked list of {@link AbstractInsnNode} objects. <i>This implementation is not thread 36 * safe</i>. 37 */ 38 public class InsnList implements Iterable<AbstractInsnNode> { 39 40 /** The number of instructions in this list. */ 41 private int size; 42 43 /** The first instruction in this list. May be {@literal null}. */ 44 private AbstractInsnNode firstInsn; 45 46 /** The last instruction in this list. May be {@literal null}. */ 47 private AbstractInsnNode lastInsn; 48 49 /** 50 * A cache of the instructions of this list. This cache is used to improve the performance of the 51 * {@link #get} method. 52 */ 53 AbstractInsnNode[] cache; 54 55 /** 56 * Returns the number of instructions in this list. 57 * 58 * @return the number of instructions in this list. 59 */ size()60 public int size() { 61 return size; 62 } 63 64 /** 65 * Returns the first instruction in this list. 66 * 67 * @return the first instruction in this list, or {@literal null} if the list is empty. 68 */ getFirst()69 public AbstractInsnNode getFirst() { 70 return firstInsn; 71 } 72 73 /** 74 * Returns the last instruction in this list. 75 * 76 * @return the last instruction in this list, or {@literal null} if the list is empty. 77 */ getLast()78 public AbstractInsnNode getLast() { 79 return lastInsn; 80 } 81 82 /** 83 * Returns the instruction whose index is given. This method builds a cache of the instructions in 84 * this list to avoid scanning the whole list each time it is called. Once the cache is built, 85 * this method runs in constant time. This cache is invalidated by all the methods that modify the 86 * list. 87 * 88 * @param index the index of the instruction that must be returned. 89 * @return the instruction whose index is given. 90 * @throws IndexOutOfBoundsException if (index < 0 || index >= size()). 91 */ get(final int index)92 public AbstractInsnNode get(final int index) { 93 if (index < 0 || index >= size) { 94 throw new IndexOutOfBoundsException(); 95 } 96 if (cache == null) { 97 cache = toArray(); 98 } 99 return cache[index]; 100 } 101 102 /** 103 * Returns {@literal true} if the given instruction belongs to this list. This method always scans 104 * the instructions of this list until it finds the given instruction or reaches the end of the 105 * list. 106 * 107 * @param insnNode an instruction. 108 * @return {@literal true} if the given instruction belongs to this list. 109 */ contains(final AbstractInsnNode insnNode)110 public boolean contains(final AbstractInsnNode insnNode) { 111 AbstractInsnNode currentInsn = firstInsn; 112 while (currentInsn != null && currentInsn != insnNode) { 113 currentInsn = currentInsn.nextInsn; 114 } 115 return currentInsn != null; 116 } 117 118 /** 119 * Returns the index of the given instruction in this list. This method builds a cache of the 120 * instruction indexes to avoid scanning the whole list each time it is called. Once the cache is 121 * built, this method run in constant time. The cache is invalidated by all the methods that 122 * modify the list. 123 * 124 * @param insnNode an instruction <i>of this list</i>. 125 * @return the index of the given instruction in this list. <i>The result of this method is 126 * undefined if the given instruction does not belong to this list</i>. Use {@link #contains } 127 * to test if an instruction belongs to an instruction list or not. 128 */ indexOf(final AbstractInsnNode insnNode)129 public int indexOf(final AbstractInsnNode insnNode) { 130 if (cache == null) { 131 cache = toArray(); 132 } 133 return insnNode.index; 134 } 135 136 /** 137 * Makes the given visitor visit all the instructions in this list. 138 * 139 * @param methodVisitor the method visitor that must visit the instructions. 140 */ accept(final MethodVisitor methodVisitor)141 public void accept(final MethodVisitor methodVisitor) { 142 AbstractInsnNode currentInsn = firstInsn; 143 while (currentInsn != null) { 144 currentInsn.accept(methodVisitor); 145 currentInsn = currentInsn.nextInsn; 146 } 147 } 148 149 /** 150 * Returns an iterator over the instructions in this list. 151 * 152 * @return an iterator over the instructions in this list. 153 */ 154 @Override iterator()155 public ListIterator<AbstractInsnNode> iterator() { 156 return iterator(0); 157 } 158 159 /** 160 * Returns an iterator over the instructions in this list. 161 * 162 * @param index index of instruction for the iterator to start at. 163 * @return an iterator over the instructions in this list. 164 */ 165 @SuppressWarnings("unchecked") iterator(final int index)166 public ListIterator<AbstractInsnNode> iterator(final int index) { 167 return new InsnListIterator(index); 168 } 169 170 /** 171 * Returns an array containing all the instructions in this list. 172 * 173 * @return an array containing all the instructions in this list. 174 */ toArray()175 public AbstractInsnNode[] toArray() { 176 int currentInsnIndex = 0; 177 AbstractInsnNode currentInsn = firstInsn; 178 AbstractInsnNode[] insnNodeArray = new AbstractInsnNode[size]; 179 while (currentInsn != null) { 180 insnNodeArray[currentInsnIndex] = currentInsn; 181 currentInsn.index = currentInsnIndex++; 182 currentInsn = currentInsn.nextInsn; 183 } 184 return insnNodeArray; 185 } 186 187 /** 188 * Replaces an instruction of this list with another instruction. 189 * 190 * @param oldInsnNode an instruction <i>of this list</i>. 191 * @param newInsnNode another instruction, <i>which must not belong to any {@link InsnList}</i>. 192 */ set(final AbstractInsnNode oldInsnNode, final AbstractInsnNode newInsnNode)193 public void set(final AbstractInsnNode oldInsnNode, final AbstractInsnNode newInsnNode) { 194 AbstractInsnNode nextInsn = oldInsnNode.nextInsn; 195 newInsnNode.nextInsn = nextInsn; 196 if (nextInsn != null) { 197 nextInsn.previousInsn = newInsnNode; 198 } else { 199 lastInsn = newInsnNode; 200 } 201 AbstractInsnNode previousInsn = oldInsnNode.previousInsn; 202 newInsnNode.previousInsn = previousInsn; 203 if (previousInsn != null) { 204 previousInsn.nextInsn = newInsnNode; 205 } else { 206 firstInsn = newInsnNode; 207 } 208 if (cache != null) { 209 int index = oldInsnNode.index; 210 cache[index] = newInsnNode; 211 newInsnNode.index = index; 212 } else { 213 newInsnNode.index = 0; // newInsnNode now belongs to an InsnList. 214 } 215 oldInsnNode.index = -1; // oldInsnNode no longer belongs to an InsnList. 216 oldInsnNode.previousInsn = null; 217 oldInsnNode.nextInsn = null; 218 } 219 220 /** 221 * Adds the given instruction to the end of this list. 222 * 223 * @param insnNode an instruction, <i>which must not belong to any {@link InsnList}</i>. 224 */ add(final AbstractInsnNode insnNode)225 public void add(final AbstractInsnNode insnNode) { 226 ++size; 227 if (lastInsn == null) { 228 firstInsn = insnNode; 229 lastInsn = insnNode; 230 } else { 231 lastInsn.nextInsn = insnNode; 232 insnNode.previousInsn = lastInsn; 233 } 234 lastInsn = insnNode; 235 cache = null; 236 insnNode.index = 0; // insnNode now belongs to an InsnList. 237 } 238 239 /** 240 * Adds the given instructions to the end of this list. 241 * 242 * @param insnList an instruction list, which is cleared during the process. This list must be 243 * different from 'this'. 244 */ add(final InsnList insnList)245 public void add(final InsnList insnList) { 246 if (insnList.size == 0) { 247 return; 248 } 249 size += insnList.size; 250 if (lastInsn == null) { 251 firstInsn = insnList.firstInsn; 252 lastInsn = insnList.lastInsn; 253 } else { 254 AbstractInsnNode firstInsnListElement = insnList.firstInsn; 255 lastInsn.nextInsn = firstInsnListElement; 256 firstInsnListElement.previousInsn = lastInsn; 257 lastInsn = insnList.lastInsn; 258 } 259 cache = null; 260 insnList.removeAll(false); 261 } 262 263 /** 264 * Inserts the given instruction at the beginning of this list. 265 * 266 * @param insnNode an instruction, <i>which must not belong to any {@link InsnList}</i>. 267 */ insert(final AbstractInsnNode insnNode)268 public void insert(final AbstractInsnNode insnNode) { 269 ++size; 270 if (firstInsn == null) { 271 firstInsn = insnNode; 272 lastInsn = insnNode; 273 } else { 274 firstInsn.previousInsn = insnNode; 275 insnNode.nextInsn = firstInsn; 276 } 277 firstInsn = insnNode; 278 cache = null; 279 insnNode.index = 0; // insnNode now belongs to an InsnList. 280 } 281 282 /** 283 * Inserts the given instructions at the beginning of this list. 284 * 285 * @param insnList an instruction list, which is cleared during the process. This list must be 286 * different from 'this'. 287 */ insert(final InsnList insnList)288 public void insert(final InsnList insnList) { 289 if (insnList.size == 0) { 290 return; 291 } 292 size += insnList.size; 293 if (firstInsn == null) { 294 firstInsn = insnList.firstInsn; 295 lastInsn = insnList.lastInsn; 296 } else { 297 AbstractInsnNode lastInsnListElement = insnList.lastInsn; 298 firstInsn.previousInsn = lastInsnListElement; 299 lastInsnListElement.nextInsn = firstInsn; 300 firstInsn = insnList.firstInsn; 301 } 302 cache = null; 303 insnList.removeAll(false); 304 } 305 306 /** 307 * Inserts the given instruction after the specified instruction. 308 * 309 * @param previousInsn an instruction <i>of this list</i> after which insnNode must be inserted. 310 * @param insnNode the instruction to be inserted, <i>which must not belong to any {@link 311 * InsnList}</i>. 312 */ insert(final AbstractInsnNode previousInsn, final AbstractInsnNode insnNode)313 public void insert(final AbstractInsnNode previousInsn, final AbstractInsnNode insnNode) { 314 ++size; 315 AbstractInsnNode nextInsn = previousInsn.nextInsn; 316 if (nextInsn == null) { 317 lastInsn = insnNode; 318 } else { 319 nextInsn.previousInsn = insnNode; 320 } 321 previousInsn.nextInsn = insnNode; 322 insnNode.nextInsn = nextInsn; 323 insnNode.previousInsn = previousInsn; 324 cache = null; 325 insnNode.index = 0; // insnNode now belongs to an InsnList. 326 } 327 328 /** 329 * Inserts the given instructions after the specified instruction. 330 * 331 * @param previousInsn an instruction <i>of this list</i> after which the instructions must be 332 * inserted. 333 * @param insnList the instruction list to be inserted, which is cleared during the process. This 334 * list must be different from 'this'. 335 */ insert(final AbstractInsnNode previousInsn, final InsnList insnList)336 public void insert(final AbstractInsnNode previousInsn, final InsnList insnList) { 337 if (insnList.size == 0) { 338 return; 339 } 340 size += insnList.size; 341 AbstractInsnNode firstInsnListElement = insnList.firstInsn; 342 AbstractInsnNode lastInsnListElement = insnList.lastInsn; 343 AbstractInsnNode nextInsn = previousInsn.nextInsn; 344 if (nextInsn == null) { 345 lastInsn = lastInsnListElement; 346 } else { 347 nextInsn.previousInsn = lastInsnListElement; 348 } 349 previousInsn.nextInsn = firstInsnListElement; 350 lastInsnListElement.nextInsn = nextInsn; 351 firstInsnListElement.previousInsn = previousInsn; 352 cache = null; 353 insnList.removeAll(false); 354 } 355 356 /** 357 * Inserts the given instruction before the specified instruction. 358 * 359 * @param nextInsn an instruction <i>of this list</i> before which insnNode must be inserted. 360 * @param insnNode the instruction to be inserted, <i>which must not belong to any {@link 361 * InsnList}</i>. 362 */ insertBefore(final AbstractInsnNode nextInsn, final AbstractInsnNode insnNode)363 public void insertBefore(final AbstractInsnNode nextInsn, final AbstractInsnNode insnNode) { 364 ++size; 365 AbstractInsnNode previousInsn = nextInsn.previousInsn; 366 if (previousInsn == null) { 367 firstInsn = insnNode; 368 } else { 369 previousInsn.nextInsn = insnNode; 370 } 371 nextInsn.previousInsn = insnNode; 372 insnNode.nextInsn = nextInsn; 373 insnNode.previousInsn = previousInsn; 374 cache = null; 375 insnNode.index = 0; // insnNode now belongs to an InsnList. 376 } 377 378 /** 379 * Inserts the given instructions before the specified instruction. 380 * 381 * @param nextInsn an instruction <i>of this list</i> before which the instructions must be 382 * inserted. 383 * @param insnList the instruction list to be inserted, which is cleared during the process. This 384 * list must be different from 'this'. 385 */ insertBefore(final AbstractInsnNode nextInsn, final InsnList insnList)386 public void insertBefore(final AbstractInsnNode nextInsn, final InsnList insnList) { 387 if (insnList.size == 0) { 388 return; 389 } 390 size += insnList.size; 391 AbstractInsnNode firstInsnListElement = insnList.firstInsn; 392 AbstractInsnNode lastInsnListElement = insnList.lastInsn; 393 AbstractInsnNode previousInsn = nextInsn.previousInsn; 394 if (previousInsn == null) { 395 firstInsn = firstInsnListElement; 396 } else { 397 previousInsn.nextInsn = firstInsnListElement; 398 } 399 nextInsn.previousInsn = lastInsnListElement; 400 lastInsnListElement.nextInsn = nextInsn; 401 firstInsnListElement.previousInsn = previousInsn; 402 cache = null; 403 insnList.removeAll(false); 404 } 405 406 /** 407 * Removes the given instruction from this list. 408 * 409 * @param insnNode the instruction <i>of this list</i> that must be removed. 410 */ remove(final AbstractInsnNode insnNode)411 public void remove(final AbstractInsnNode insnNode) { 412 --size; 413 AbstractInsnNode nextInsn = insnNode.nextInsn; 414 AbstractInsnNode previousInsn = insnNode.previousInsn; 415 if (nextInsn == null) { 416 if (previousInsn == null) { 417 firstInsn = null; 418 lastInsn = null; 419 } else { 420 previousInsn.nextInsn = null; 421 lastInsn = previousInsn; 422 } 423 } else { 424 if (previousInsn == null) { 425 firstInsn = nextInsn; 426 nextInsn.previousInsn = null; 427 } else { 428 previousInsn.nextInsn = nextInsn; 429 nextInsn.previousInsn = previousInsn; 430 } 431 } 432 cache = null; 433 insnNode.index = -1; // insnNode no longer belongs to an InsnList. 434 insnNode.previousInsn = null; 435 insnNode.nextInsn = null; 436 } 437 438 /** 439 * Removes all the instructions of this list. 440 * 441 * @param mark if the instructions must be marked as no longer belonging to any {@link InsnList}. 442 */ removeAll(final boolean mark)443 void removeAll(final boolean mark) { 444 if (mark) { 445 AbstractInsnNode currentInsn = firstInsn; 446 while (currentInsn != null) { 447 AbstractInsnNode next = currentInsn.nextInsn; 448 currentInsn.index = -1; // currentInsn no longer belongs to an InsnList. 449 currentInsn.previousInsn = null; 450 currentInsn.nextInsn = null; 451 currentInsn = next; 452 } 453 } 454 size = 0; 455 firstInsn = null; 456 lastInsn = null; 457 cache = null; 458 } 459 460 /** Removes all the instructions of this list. */ clear()461 public void clear() { 462 removeAll(false); 463 } 464 465 /** 466 * Resets all the labels in the instruction list. This method should be called before reusing an 467 * instruction list between several <code>ClassWriter</code>s. 468 */ resetLabels()469 public void resetLabels() { 470 AbstractInsnNode currentInsn = firstInsn; 471 while (currentInsn != null) { 472 if (currentInsn instanceof LabelNode) { 473 ((LabelNode) currentInsn).resetLabel(); 474 } 475 currentInsn = currentInsn.nextInsn; 476 } 477 } 478 479 // Note: this class is not generified because it would create bridges. 480 @SuppressWarnings("rawtypes") 481 private final class InsnListIterator implements ListIterator { 482 483 AbstractInsnNode nextInsn; 484 485 AbstractInsnNode previousInsn; 486 487 AbstractInsnNode remove; 488 InsnListIterator(final int index)489 InsnListIterator(final int index) { 490 if (index < 0 || index > size()) { 491 throw new IndexOutOfBoundsException(); 492 } else if (index == size()) { 493 nextInsn = null; 494 previousInsn = getLast(); 495 } else { 496 AbstractInsnNode currentInsn = getFirst(); 497 for (int i = 0; i < index; i++) { 498 currentInsn = currentInsn.nextInsn; 499 } 500 501 nextInsn = currentInsn; 502 previousInsn = currentInsn.previousInsn; 503 } 504 } 505 506 @Override hasNext()507 public boolean hasNext() { 508 return nextInsn != null; 509 } 510 511 @Override next()512 public Object next() { 513 if (nextInsn == null) { 514 throw new NoSuchElementException(); 515 } 516 AbstractInsnNode result = nextInsn; 517 previousInsn = result; 518 nextInsn = result.nextInsn; 519 remove = result; 520 return result; 521 } 522 523 @Override remove()524 public void remove() { 525 if (remove != null) { 526 if (remove == nextInsn) { 527 nextInsn = nextInsn.nextInsn; 528 } else { 529 previousInsn = previousInsn.previousInsn; 530 } 531 InsnList.this.remove(remove); 532 remove = null; 533 } else { 534 throw new IllegalStateException(); 535 } 536 } 537 538 @Override hasPrevious()539 public boolean hasPrevious() { 540 return previousInsn != null; 541 } 542 543 @Override previous()544 public Object previous() { 545 if (previousInsn == null) { 546 throw new NoSuchElementException(); 547 } 548 AbstractInsnNode result = previousInsn; 549 nextInsn = result; 550 previousInsn = result.previousInsn; 551 remove = result; 552 return result; 553 } 554 555 @Override nextIndex()556 public int nextIndex() { 557 if (nextInsn == null) { 558 return size(); 559 } 560 if (cache == null) { 561 cache = toArray(); 562 } 563 return nextInsn.index; 564 } 565 566 @Override previousIndex()567 public int previousIndex() { 568 if (previousInsn == null) { 569 return -1; 570 } 571 if (cache == null) { 572 cache = toArray(); 573 } 574 return previousInsn.index; 575 } 576 577 @Override add(final Object o)578 public void add(final Object o) { 579 if (nextInsn != null) { 580 InsnList.this.insertBefore(nextInsn, (AbstractInsnNode) o); 581 } else if (previousInsn != null) { 582 InsnList.this.insert(previousInsn, (AbstractInsnNode) o); 583 } else { 584 InsnList.this.add((AbstractInsnNode) o); 585 } 586 previousInsn = (AbstractInsnNode) o; 587 remove = null; 588 } 589 590 @Override set(final Object o)591 public void set(final Object o) { 592 if (remove != null) { 593 InsnList.this.set(remove, (AbstractInsnNode) o); 594 if (remove == previousInsn) { 595 previousInsn = (AbstractInsnNode) o; 596 } else { 597 nextInsn = (AbstractInsnNode) o; 598 } 599 } else { 600 throw new IllegalStateException(); 601 } 602 } 603 } 604 } 605