diff --git a/deobfuscator-api/src/main/java/org/objectweb/asm/tree/InsnList.java b/deobfuscator-api/src/main/java/org/objectweb/asm/tree/InsnList.java
new file mode 100644
index 0000000..b81966e
--- /dev/null
+++ b/deobfuscator-api/src/main/java/org/objectweb/asm/tree/InsnList.java
@@ -0,0 +1,655 @@
+// ASM: a very small and fast Java bytecode manipulation framework
+// Copyright (c) 2000-2011 INRIA, France Telecom
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions
+// are met:
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// 3. Neither the name of the copyright holders nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+package org.objectweb.asm.tree;
+
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * A doubly linked list of {@link AbstractInsnNode} objects. This implementation is not thread
+ * safe.
+ *
+ * Needed to merge https://gitlab.ow2.org/asm/asm/-/merge_requests/413 into our deobfuscator
+ */
+public class InsnList implements Iterable {
+
+ /** The number of instructions in this list. */
+ private int size;
+
+ /** The first instruction in this list. May be {@literal null}. */
+ private AbstractInsnNode firstInsn;
+
+ /** The last instruction in this list. May be {@literal null}. */
+ private AbstractInsnNode lastInsn;
+
+ /**
+ * A cache of the instructions of this list. This cache is used to improve the performance of the
+ * {@link #get} method.
+ */
+ AbstractInsnNode[] cache;
+
+ /**
+ * Returns the number of instructions in this list.
+ *
+ * @return the number of instructions in this list.
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Returns the first instruction in this list.
+ *
+ * @return the first instruction in this list, or {@literal null} if the list is empty.
+ */
+ public AbstractInsnNode getFirst() {
+ return firstInsn;
+ }
+
+ /**
+ * Returns the last instruction in this list.
+ *
+ * @return the last instruction in this list, or {@literal null} if the list is empty.
+ */
+ public AbstractInsnNode getLast() {
+ return lastInsn;
+ }
+
+ /**
+ * Returns the instruction whose index is given. This method builds a cache of the instructions in
+ * this list to avoid scanning the whole list each time it is called. Once the cache is built,
+ * this method runs in constant time. This cache is invalidated by all the methods that modify the
+ * list.
+ *
+ * @param index the index of the instruction that must be returned.
+ * @return the instruction whose index is given.
+ * @throws IndexOutOfBoundsException if (index < 0 || index >= size()).
+ */
+ public AbstractInsnNode get(final int index) {
+ if (index < 0 || index >= size) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (cache == null) {
+ cache = toArray();
+ }
+ return cache[index];
+ }
+
+ /**
+ * Returns {@literal true} if the given instruction belongs to this list. This method always scans
+ * the instructions of this list until it finds the given instruction or reaches the end of the
+ * list.
+ *
+ * @param insnNode an instruction.
+ * @return {@literal true} if the given instruction belongs to this list.
+ */
+ public boolean contains(final AbstractInsnNode insnNode) {
+ AbstractInsnNode currentInsn = firstInsn;
+ while (currentInsn != null && currentInsn != insnNode) {
+ currentInsn = currentInsn.nextInsn;
+ }
+ return currentInsn != null;
+ }
+
+ /**
+ * Returns the index of the given instruction in this list. This method builds a cache of the
+ * instruction indexes to avoid scanning the whole list each time it is called. Once the cache is
+ * built, this method run in constant time. The cache is invalidated by all the methods that
+ * modify the list.
+ *
+ * @param insnNode an instruction of this list.
+ * @return the index of the given instruction in this list. The result of this method is
+ * undefined if the given instruction does not belong to this list. Use {@link #contains }
+ * to test if an instruction belongs to an instruction list or not.
+ */
+ public int indexOf(final AbstractInsnNode insnNode) {
+ if (cache == null) {
+ cache = toArray();
+ }
+ return insnNode.index;
+ }
+
+ /**
+ * Makes the given visitor visit all the instructions in this list.
+ *
+ * @param methodVisitor the method visitor that must visit the instructions.
+ */
+ public void accept(final MethodVisitor methodVisitor) {
+ AbstractInsnNode currentInsn = firstInsn;
+ while (currentInsn != null) {
+ currentInsn.accept(methodVisitor);
+ currentInsn = currentInsn.nextInsn;
+ }
+ }
+
+ /**
+ * Returns an iterator over the instructions in this list.
+ *
+ * @return an iterator over the instructions in this list.
+ */
+ @Override
+ public ListIterator iterator() {
+ return iterator(0);
+ }
+
+ /**
+ * Returns an iterator over the instructions in this list.
+ *
+ * @param index index of instruction for the iterator to start at.
+ * @return an iterator over the instructions in this list.
+ */
+ @SuppressWarnings("unchecked")
+ public ListIterator iterator(final int index) {
+ return new InsnListIterator(index);
+ }
+
+ /**
+ * Returns an array containing all the instructions in this list.
+ *
+ * @return an array containing all the instructions in this list.
+ */
+ public AbstractInsnNode[] toArray() {
+ int currentInsnIndex = 0;
+ AbstractInsnNode currentInsn = firstInsn;
+ AbstractInsnNode[] insnNodeArray = new AbstractInsnNode[size];
+ while (currentInsn != null) {
+ insnNodeArray[currentInsnIndex] = currentInsn;
+ currentInsn.index = currentInsnIndex++;
+ currentInsn = currentInsn.nextInsn;
+ }
+ return insnNodeArray;
+ }
+
+ /**
+ * Replaces an instruction of this list with another instruction.
+ *
+ * @param oldInsnNode an instruction of this list.
+ * @param newInsnNode another instruction, which must not belong to any {@link InsnList}.
+ */
+ public void set(final AbstractInsnNode oldInsnNode, final AbstractInsnNode newInsnNode) {
+ // Narumii start
+ if (oldInsnNode.index == -1) {
+ throw new IllegalArgumentException("oldInsnNode does not belong to InsnList");
+ }
+ if (newInsnNode.index != -1) {
+ throw new IllegalArgumentException("newInsnNode already belongs to another InsnList");
+ }
+ // Narumii end
+ AbstractInsnNode nextInsn = oldInsnNode.nextInsn;
+ newInsnNode.nextInsn = nextInsn;
+ if (nextInsn != null) {
+ nextInsn.previousInsn = newInsnNode;
+ } else {
+ lastInsn = newInsnNode;
+ }
+ AbstractInsnNode previousInsn = oldInsnNode.previousInsn;
+ newInsnNode.previousInsn = previousInsn;
+ if (previousInsn != null) {
+ previousInsn.nextInsn = newInsnNode;
+ } else {
+ firstInsn = newInsnNode;
+ }
+ if (cache != null) {
+ int index = oldInsnNode.index;
+ cache[index] = newInsnNode;
+ newInsnNode.index = index;
+ } else {
+ newInsnNode.index = 0; // newInsnNode now belongs to an InsnList.
+ }
+ oldInsnNode.index = -1; // oldInsnNode no longer belongs to an InsnList.
+ oldInsnNode.previousInsn = null;
+ oldInsnNode.nextInsn = null;
+ }
+
+ /**
+ * Adds the given instruction to the end of this list.
+ *
+ * @param insnNode an instruction, which must not belong to any {@link InsnList}.
+ */
+ public void add(final AbstractInsnNode insnNode) {
+ // Narumii start
+ if (insnNode.index != -1) {
+ throw new IllegalArgumentException("insnNode already belongs to another InsnList");
+ }
+ // Narumii end
+ ++size;
+ if (lastInsn == null) {
+ firstInsn = insnNode;
+ lastInsn = insnNode;
+ } else {
+ lastInsn.nextInsn = insnNode;
+ insnNode.previousInsn = lastInsn;
+ }
+ lastInsn = insnNode;
+ cache = null;
+ insnNode.index = 0; // insnNode now belongs to an InsnList.
+ }
+
+ /**
+ * Adds the given instructions to the end of this list.
+ *
+ * @param insnList an instruction list, which is cleared during the process. This list must be
+ * different from 'this'.
+ */
+ public void add(final InsnList insnList) {
+ if (insnList.size == 0) {
+ return;
+ }
+ size += insnList.size;
+ if (lastInsn == null) {
+ firstInsn = insnList.firstInsn;
+ lastInsn = insnList.lastInsn;
+ } else {
+ AbstractInsnNode firstInsnListElement = insnList.firstInsn;
+ lastInsn.nextInsn = firstInsnListElement;
+ firstInsnListElement.previousInsn = lastInsn;
+ lastInsn = insnList.lastInsn;
+ }
+ cache = null;
+ insnList.removeAll(false);
+ }
+
+ /**
+ * Inserts the given instruction at the beginning of this list.
+ *
+ * @param insnNode an instruction, which must not belong to any {@link InsnList}.
+ */
+ public void insert(final AbstractInsnNode insnNode) {
+ // Narumii start
+ if (insnNode.index != -1) {
+ throw new IllegalArgumentException("insnNode already belongs to another InsnList");
+ }
+ // Narumii end
+ ++size;
+ if (firstInsn == null) {
+ firstInsn = insnNode;
+ lastInsn = insnNode;
+ } else {
+ firstInsn.previousInsn = insnNode;
+ insnNode.nextInsn = firstInsn;
+ }
+ firstInsn = insnNode;
+ cache = null;
+ insnNode.index = 0; // insnNode now belongs to an InsnList.
+ }
+
+ /**
+ * Inserts the given instructions at the beginning of this list.
+ *
+ * @param insnList an instruction list, which is cleared during the process. This list must be
+ * different from 'this'.
+ */
+ public void insert(final InsnList insnList) {
+ if (insnList.size == 0) {
+ return;
+ }
+ size += insnList.size;
+ if (firstInsn == null) {
+ firstInsn = insnList.firstInsn;
+ lastInsn = insnList.lastInsn;
+ } else {
+ AbstractInsnNode lastInsnListElement = insnList.lastInsn;
+ firstInsn.previousInsn = lastInsnListElement;
+ lastInsnListElement.nextInsn = firstInsn;
+ firstInsn = insnList.firstInsn;
+ }
+ cache = null;
+ insnList.removeAll(false);
+ }
+
+ /**
+ * Inserts the given instruction after the specified instruction.
+ *
+ * @param previousInsn an instruction of this list after which insnNode must be inserted.
+ * @param insnNode the instruction to be inserted, which must not belong to any {@link
+ * InsnList}.
+ */
+ public void insert(final AbstractInsnNode previousInsn, final AbstractInsnNode insnNode) {
+ // Narumii start
+ if (previousInsn.index == -1) {
+ throw new IllegalArgumentException("previousInsn does not belong to InsnList");
+ }
+ if (insnNode.index != -1) {
+ throw new IllegalArgumentException("insnNode already belongs to another InsnList");
+ }
+ // Narumii end
+ ++size;
+ AbstractInsnNode nextInsn = previousInsn.nextInsn;
+ if (nextInsn == null) {
+ lastInsn = insnNode;
+ } else {
+ nextInsn.previousInsn = insnNode;
+ }
+ previousInsn.nextInsn = insnNode;
+ insnNode.nextInsn = nextInsn;
+ insnNode.previousInsn = previousInsn;
+ cache = null;
+ insnNode.index = 0; // insnNode now belongs to an InsnList.
+ }
+
+ /**
+ * Inserts the given instructions after the specified instruction.
+ *
+ * @param previousInsn an instruction of this list after which the instructions must be
+ * inserted.
+ * @param insnList the instruction list to be inserted, which is cleared during the process. This
+ * list must be different from 'this'.
+ */
+ public void insert(final AbstractInsnNode previousInsn, final InsnList insnList) {
+ // Narumii start
+ if (previousInsn.index == -1) {
+ throw new IllegalArgumentException("previousInsn does not belong to InsnList");
+ }
+ // Narumii end
+ if (insnList.size == 0) {
+ return;
+ }
+ size += insnList.size;
+ AbstractInsnNode firstInsnListElement = insnList.firstInsn;
+ AbstractInsnNode lastInsnListElement = insnList.lastInsn;
+ AbstractInsnNode nextInsn = previousInsn.nextInsn;
+ if (nextInsn == null) {
+ lastInsn = lastInsnListElement;
+ } else {
+ nextInsn.previousInsn = lastInsnListElement;
+ }
+ previousInsn.nextInsn = firstInsnListElement;
+ lastInsnListElement.nextInsn = nextInsn;
+ firstInsnListElement.previousInsn = previousInsn;
+ cache = null;
+ insnList.removeAll(false);
+ }
+
+ /**
+ * Inserts the given instruction before the specified instruction.
+ *
+ * @param nextInsn an instruction of this list before which insnNode must be inserted.
+ * @param insnNode the instruction to be inserted, which must not belong to any {@link
+ * InsnList}.
+ */
+ public void insertBefore(final AbstractInsnNode nextInsn, final AbstractInsnNode insnNode) {
+ // Narumii start
+ if (nextInsn.index == -1) {
+ throw new IllegalArgumentException("nextInsn does not belong to InsnList");
+ }
+ if (insnNode.index != -1) {
+ throw new IllegalArgumentException("insnNode already belongs to another InsnList");
+ }
+ // Narumii end
+ ++size;
+ AbstractInsnNode previousInsn = nextInsn.previousInsn;
+ if (previousInsn == null) {
+ firstInsn = insnNode;
+ } else {
+ previousInsn.nextInsn = insnNode;
+ }
+ nextInsn.previousInsn = insnNode;
+ insnNode.nextInsn = nextInsn;
+ insnNode.previousInsn = previousInsn;
+ cache = null;
+ insnNode.index = 0; // insnNode now belongs to an InsnList.
+ }
+
+ /**
+ * Inserts the given instructions before the specified instruction.
+ *
+ * @param nextInsn an instruction of this list before which the instructions must be
+ * inserted.
+ * @param insnList the instruction list to be inserted, which is cleared during the process. This
+ * list must be different from 'this'.
+ */
+ public void insertBefore(final AbstractInsnNode nextInsn, final InsnList insnList) {
+ // Narumii start
+ if (nextInsn.index == -1) {
+ throw new IllegalArgumentException("nextInsn does not belong to InsnList");
+ }
+ // Narumii end
+ if (insnList.size == 0) {
+ return;
+ }
+ size += insnList.size;
+ AbstractInsnNode firstInsnListElement = insnList.firstInsn;
+ AbstractInsnNode lastInsnListElement = insnList.lastInsn;
+ AbstractInsnNode previousInsn = nextInsn.previousInsn;
+ if (previousInsn == null) {
+ firstInsn = firstInsnListElement;
+ } else {
+ previousInsn.nextInsn = firstInsnListElement;
+ }
+ nextInsn.previousInsn = lastInsnListElement;
+ lastInsnListElement.nextInsn = nextInsn;
+ firstInsnListElement.previousInsn = previousInsn;
+ cache = null;
+ insnList.removeAll(false);
+ }
+
+ /**
+ * Removes the given instruction from this list.
+ *
+ * @param insnNode the instruction of this list that must be removed.
+ */
+ public void remove(final AbstractInsnNode insnNode) {
+ // Narumii start
+ if (insnNode.index == -1) {
+ throw new IllegalArgumentException("insnNode does not belong to InsnList");
+ }
+ // Narumii end
+ --size;
+ AbstractInsnNode nextInsn = insnNode.nextInsn;
+ AbstractInsnNode previousInsn = insnNode.previousInsn;
+ if (nextInsn == null) {
+ if (previousInsn == null) {
+ firstInsn = null;
+ lastInsn = null;
+ } else {
+ previousInsn.nextInsn = null;
+ lastInsn = previousInsn;
+ }
+ } else {
+ if (previousInsn == null) {
+ firstInsn = nextInsn;
+ nextInsn.previousInsn = null;
+ } else {
+ previousInsn.nextInsn = nextInsn;
+ nextInsn.previousInsn = previousInsn;
+ }
+ }
+ cache = null;
+ insnNode.index = -1; // insnNode no longer belongs to an InsnList.
+ insnNode.previousInsn = null;
+ insnNode.nextInsn = null;
+ }
+
+ /**
+ * Removes all the instructions of this list.
+ *
+ * @param mark if the instructions must be marked as no longer belonging to any {@link InsnList}.
+ */
+ void removeAll(final boolean mark) {
+ if (mark) {
+ AbstractInsnNode currentInsn = firstInsn;
+ while (currentInsn != null) {
+ AbstractInsnNode next = currentInsn.nextInsn;
+ currentInsn.index = -1; // currentInsn no longer belongs to an InsnList.
+ currentInsn.previousInsn = null;
+ currentInsn.nextInsn = null;
+ currentInsn = next;
+ }
+ }
+ size = 0;
+ firstInsn = null;
+ lastInsn = null;
+ cache = null;
+ }
+
+ /** Removes all the instructions of this list. */
+ public void clear() {
+ removeAll(false);
+ }
+
+ /**
+ * Resets all the labels in the instruction list. This method should be called before reusing an
+ * instruction list between several ClassWriter
s.
+ */
+ public void resetLabels() {
+ AbstractInsnNode currentInsn = firstInsn;
+ while (currentInsn != null) {
+ if (currentInsn instanceof LabelNode) {
+ ((LabelNode) currentInsn).resetLabel();
+ }
+ currentInsn = currentInsn.nextInsn;
+ }
+ }
+
+ // Note: this class is not generified because it would create bridges.
+ @SuppressWarnings("rawtypes")
+ private final class InsnListIterator implements ListIterator {
+
+ AbstractInsnNode nextInsn;
+
+ AbstractInsnNode previousInsn;
+
+ AbstractInsnNode remove;
+
+ InsnListIterator(final int index) {
+ if (index < 0 || index > size()) {
+ throw new IndexOutOfBoundsException();
+ } else if (index == size()) {
+ nextInsn = null;
+ previousInsn = getLast();
+ } else {
+ AbstractInsnNode currentInsn = getFirst();
+ for (int i = 0; i < index; i++) {
+ currentInsn = currentInsn.nextInsn;
+ }
+
+ nextInsn = currentInsn;
+ previousInsn = currentInsn.previousInsn;
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return nextInsn != null;
+ }
+
+ @Override
+ public Object next() {
+ if (nextInsn == null) {
+ throw new NoSuchElementException();
+ }
+ AbstractInsnNode result = nextInsn;
+ previousInsn = result;
+ nextInsn = result.nextInsn;
+ remove = result;
+ return result;
+ }
+
+ @Override
+ public void remove() {
+ if (remove != null) {
+ if (remove == nextInsn) {
+ nextInsn = nextInsn.nextInsn;
+ } else {
+ previousInsn = previousInsn.previousInsn;
+ }
+ InsnList.this.remove(remove);
+ remove = null;
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public boolean hasPrevious() {
+ return previousInsn != null;
+ }
+
+ @Override
+ public Object previous() {
+ if (previousInsn == null) {
+ throw new NoSuchElementException();
+ }
+ AbstractInsnNode result = previousInsn;
+ nextInsn = result;
+ previousInsn = result.previousInsn;
+ remove = result;
+ return result;
+ }
+
+ @Override
+ public int nextIndex() {
+ if (nextInsn == null) {
+ return size();
+ }
+ if (cache == null) {
+ cache = toArray();
+ }
+ return nextInsn.index;
+ }
+
+ @Override
+ public int previousIndex() {
+ if (previousInsn == null) {
+ return -1;
+ }
+ if (cache == null) {
+ cache = toArray();
+ }
+ return previousInsn.index;
+ }
+
+ @Override
+ public void add(final Object o) {
+ if (nextInsn != null) {
+ InsnList.this.insertBefore(nextInsn, (AbstractInsnNode) o);
+ } else if (previousInsn != null) {
+ InsnList.this.insert(previousInsn, (AbstractInsnNode) o);
+ } else {
+ InsnList.this.add((AbstractInsnNode) o);
+ }
+ previousInsn = (AbstractInsnNode) o;
+ remove = null;
+ }
+
+ @Override
+ public void set(final Object o) {
+ if (remove != null) {
+ InsnList.this.set(remove, (AbstractInsnNode) o);
+ if (remove == previousInsn) {
+ previousInsn = (AbstractInsnNode) o;
+ } else {
+ nextInsn = (AbstractInsnNode) o;
+ }
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+ }
+}