/* * Copyright (C) 2010-2016 JPEXS, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.0 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. */ package com.jpexs.decompiler.flash.abc.avm2.fastavm2; import com.jpexs.decompiler.flash.abc.avm2.AVM2Code; import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; import com.jpexs.decompiler.flash.abc.avm2.instructions.IfTypeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.JumpIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.LookupSwitchIns; import com.jpexs.decompiler.flash.abc.types.ABCException; import com.jpexs.decompiler.flash.abc.types.MethodBody; import com.jpexs.decompiler.graph.GraphSourceItemContainer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * * @author JPEXS */ public class FastAVM2List implements Collection<AVM2InstructionItem> { private int size; private AVM2InstructionItem firstItem; private final Map<AVM2Instruction, AVM2InstructionItem> actionItemMap; private final Set<AVM2InstructionItem> actionItemSet; public FastAVM2List(MethodBody body) { // exceptions todo AVM2Code avm2code = body.getCode(); List<AVM2Instruction> code = avm2code.code; actionItemMap = new HashMap<>(code.size()); actionItemSet = new HashSet<>(code.size()); for (AVM2Instruction action : code) { insertItemAfter(null, action); } size = code.size(); // getContainerLastActions(avm2code, actionItemMap); getJumps(avm2code, actionItemMap); } public final AVM2InstructionItem insertItemBefore(AVM2InstructionItem item, AVM2Instruction action) { AVM2InstructionItem newItem = new AVM2InstructionItem(action); return insertItemBefore(item, newItem); } public final AVM2InstructionItem insertItemAfter(AVM2InstructionItem item, AVM2Instruction action) { AVM2InstructionItem newItem = new AVM2InstructionItem(action); return insertItemAfter(item, newItem); } public final AVM2InstructionItem insertItemBefore(AVM2InstructionItem item, AVM2InstructionItem newItem) { insertItemAfter(item.prev, newItem); if (item == firstItem) { firstItem = newItem; } return newItem; } public final AVM2InstructionItem insertItemAfter(AVM2InstructionItem item, AVM2InstructionItem newItem) { if (item == null && firstItem == null) { firstItem = newItem; newItem.next = newItem; newItem.prev = newItem; } else { if (item == null) { // insert to the end item = firstItem.prev; } AVM2InstructionItem oldNext = item.next; newItem.prev = item; newItem.next = oldNext; item.next = newItem; oldNext.prev = newItem; } size++; actionItemMap.put(newItem.ins, newItem); actionItemSet.add(newItem); return newItem; } public AVM2InstructionItem removeItem(AVM2InstructionItem item) { AVM2InstructionItem next = null; if (item == firstItem) { if (item.next == item) { // there is only 1 item firstItem = null; } else { next = item.next; firstItem = next; next.prev = item.prev; item.prev.next = next; } } else { next = item.next; item.prev.next = next; next.prev = item.prev; } size--; actionItemMap.remove(item.ins); actionItemSet.remove(item); item.removeJumpTarget(); item.removeContainerLastInstructions(); if (item.jumpsHere != null) { for (AVM2InstructionItem item1 : new ArrayList<>(item.jumpsHere)) { item1.setJumpTarget(item.next); } } if (item.lastInsOf != null) { for (AVM2InstructionItem item1 : new ArrayList<>(item.lastInsOf)) { item1.replaceContainerLastInstruction(item, item.prev); } } return next; } public void removeItem(int index, int count) { FastAVM2ListIterator iterator = new FastAVM2ListIterator(this, index); for (int i = 0; i < count; i++) { iterator.next(); iterator.remove(); } } public AVM2InstructionItem get(int index) { FastAVM2ListIterator iterator = new FastAVM2ListIterator(this, index); return iterator.next(); } public void replaceJumpTargets(AVM2InstructionItem target, AVM2InstructionItem newTarget) { if (target.jumpsHere != null) { for (AVM2InstructionItem item : new ArrayList<>(target.jumpsHere)) { item.setJumpTarget(newTarget); } } } // private void getContainerLastActions(AVM2Code actions, Map<AVM2Instruction, AVM2InstructionItem> actionItemMap) { // AVM2InstructionItem item = firstItem; // if (item == null) { // return; // } // // do { // AVM2Instruction action = item.ins; // if (action instanceof GraphSourceItemContainer) { // item.setContainerLastInstructions(getContainerLastActions(actions, action, actionItemMap)); // } // // item = item.next; // } while (item != firstItem); // } // private List<AVM2InstructionItem> getContainerLastActions(AVM2Code actions, AVM2Instruction action, Map<AVM2Instruction, AVM2InstructionItem> actionItemMap) { // GraphSourceItemContainer container = (GraphSourceItemContainer) action; // List<Long> sizes = container.getContainerSizes(); // long endAddress = action.getAddress() + container.getHeaderSize(); // List<AVM2InstructionItem> lasts = new ArrayList<>(sizes.size()); // for (long size : sizes) { // endAddress += size; // long lastActionAddress = getNearAddress(actions.code, endAddress - 1, false); // AVM2Instruction lastAction = null; // if (lastActionAddress != -1) { // lastAction = actions.getByAddress(lastActionAddress); // } // // if (lastAction != null) { // lasts.add(actionItemMap.get(lastAction)); // } else { // lasts.add(null); // } // } // return lasts; // } private long getNearAddress(List<AVM2Instruction> instructions, long address, boolean next) { int min = 0; int max = instructions.size() - 1; while (max >= min) { int mid = (min + max) / 2; long midValue = instructions.get(mid).getAddress(); if (midValue == address) { return address; } else if (midValue < address) { min = mid + 1; } else { max = mid - 1; } } return next ? (min < instructions.size() ? instructions.get(min).getAddress() : -1) : (max >= 0 ? instructions.get(max).getAddress() : -1); } private void getJumps(AVM2Code actions, Map<AVM2Instruction, AVM2InstructionItem> actionItemMap) { AVM2InstructionItem item = firstItem; if (item == null) { return; } do { AVM2Instruction action = item.ins; long target = -1; if (action.definition instanceof IfTypeIns) { target = action.getTargetAddress(); } else if (action.definition instanceof LookupSwitchIns) { // todo } if (target >= 0) { AVM2Instruction targetAction = actions.adr2ins(target); item.setJumpTarget(actionItemMap.get(targetAction)); } item = item.next; } while (item != firstItem); } private void updateActionAddressesAndLengths() { AVM2InstructionItem item = firstItem; if (item == null) { return; } long offset = item.ins.getAddress(); do { AVM2Instruction action = item.ins; action.setAddress(offset); offset += action.getBytesLength(); item = item.next; } while (item != firstItem); } private void updateJumps() { AVM2InstructionItem item = firstItem; if (item == null) { return; } long endAddress = item.prev.ins.getAddress(); do { AVM2Instruction action = item.ins; if (action.definition instanceof IfTypeIns) { AVM2Instruction target = item.getJumpTargetInstruction(); long offset; if (target != null) { offset = target.getAddress() - action.getAddress() - action.getBytesLength(); } else { offset = endAddress - action.getAddress() - action.getBytesLength(); } action.setTargetOffset((int) offset); } else if (action.definition instanceof LookupSwitchIns) { // todo } item = item.next; } while (item != firstItem); } private void updateContainerSizes() { AVM2InstructionItem item = firstItem; if (item == null) { return; } do { AVM2Instruction action = item.ins; if (action instanceof GraphSourceItemContainer) { GraphSourceItemContainer container = (GraphSourceItemContainer) action; List<AVM2InstructionItem> lastActions = item.getContainerLastInstructions(); long startAddress = action.getAddress() + container.getHeaderSize(); for (int j = 0; j < lastActions.size(); j++) { AVM2Instruction lastAction = lastActions.get(j).ins; int length = (int) (lastAction.getAddress() + lastAction.getBytesLength() - startAddress); container.setContainerSize(j, length); startAddress += length; } } item = item.next; } while (item != firstItem); } public AVM2InstructionItem getContainer(AVM2InstructionItem item) { while (!(item.ins instanceof GraphSourceItemContainer) && item != firstItem) { item = item.prev; } if (item.ins instanceof GraphSourceItemContainer) { return item; } return null; } public void removeZeroJumps() { AVM2InstructionItem item = firstItem; if (item == null) { return; } do { AVM2Instruction ins = item.ins; if (ins.definition instanceof JumpIns) { if (item.getJumpTarget() == item.next && item.getJumpTarget() != firstItem) { item = removeItem(item); continue; } } item = item.next; } while (item != firstItem); } public void removeUnreachableActions() { AVM2InstructionItem item = firstItem; if (item == null) { return; } updateReachableFlags(null, null); do { if (item.reachable == 0) { item = removeItem(item); continue; } item = item.next; } while (item != firstItem); } public void removeIncludedActions() { AVM2InstructionItem item = firstItem; if (item == null) { return; } do { if (!item.excluded) { item = removeItem(item); continue; } item = item.next; } while (item != firstItem); } public int getUnreachableActionCount(AVM2InstructionItem jump, AVM2InstructionItem jumpTarget) { AVM2InstructionItem item = firstItem; if (item == null) { return 0; } updateReachableFlags(jump, jumpTarget); jump.reachable = 0; int count = 0; do { if (item.reachable == 0) { count++; } item = item.next; } while (item != firstItem); return count; } private void clearReachableFlags() { AVM2InstructionItem item = firstItem; if (item == null) { return; } do { item.reachable = 0; item = item.next; } while (item != firstItem); } public void setExcludedFlags(boolean value) { AVM2InstructionItem item = firstItem; if (item == null) { return; } do { item.excluded = value; item = item.next; } while (item != firstItem); } private void updateReachableFlags(AVM2InstructionItem jump, AVM2InstructionItem jumpTarget) { if (firstItem == null) { return; } clearReachableFlags(); firstItem.reachable = 1; AVM2InstructionItem firstItem2 = firstItem; boolean modified = true; while (modified) { modified = false; AVM2InstructionItem item = firstItem2; do { AVM2InstructionItem next = item.next; //AVM2InstructionItem alternativeNext = null; AVM2Instruction action = item.ins; if (item.reachable == 1) { item.reachable = 2; modified = true; if (item == firstItem2) { firstItem2 = next; } if (item == jump) { if (jumpTarget.reachable == 0) { jumpTarget.reachable = 1; //alternativeNext = jumpTarget; } } else { if (!action.isExit() && !(action.definition instanceof JumpIns)) { if (next.reachable == 0) { next.reachable = 1; } } if (action instanceof GraphSourceItemContainer) { for (AVM2InstructionItem lastActionItem : item.getContainerLastInstructions()) { if (lastActionItem != null && lastActionItem.next != null && lastActionItem.next.reachable == 0) { lastActionItem.next.reachable = 1; //alternativeNext = lastActionItem.next; } } } AVM2InstructionItem target = item.getJumpTarget(); if (target != null) { if (target.reachable == 0) { target.reachable = 1; //alternativeNext = target; } } } } //item = alternativeNext == null || next.reachable == 1 ? next : alternativeNext; item = next; } while (item != firstItem); } } public void updateActions(MethodBody body) { AVM2Code result = new AVM2Code(size); AVM2InstructionItem item = firstItem; if (item == null) { body.setCode(result); body.exceptions = new ABCException[0]; return; } List<AVM2Instruction> resultList = result.code; do { resultList.add(item.ins); item = item.next; } while (item != firstItem); updateActionAddressesAndLengths(); updateJumps(); updateContainerSizes(); } public AVM2InstructionItem first() { return firstItem; } public AVM2InstructionItem last() { return firstItem == null ? null : firstItem.prev; } public void toMethodBody(MethodBody body) { updateActions(body); } @Override public int size() { return size; } @Override public boolean isEmpty() { return size == 0; } @Override public boolean contains(Object o) { if (o instanceof AVM2InstructionItem) { return actionItemSet.contains(o); } else if (o instanceof AVM2Instruction) { return actionItemMap.containsKey((AVM2Instruction) o); } return false; } @Override public FastAVM2ListIterator iterator() { return new FastAVM2ListIterator(this); } @Override public Object[] toArray() { Object[] result = new Object[size]; AVM2InstructionItem item = firstItem; if (item == null) { return result; } int i = 0; do { result[i] = item.ins; item = item.next; i++; } while (item != firstItem); return null; } @Override @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { if (a.length != size) { a = (T[]) new AVM2InstructionItem[size]; } AVM2InstructionItem item = firstItem; if (item == null) { return a; } int i = 0; do { a[i] = (T) item; item = item.next; i++; } while (item != firstItem); return null; } @Override public boolean add(AVM2InstructionItem e) { insertItemAfter(null, e); return true; } @Override public boolean remove(Object o) { AVM2InstructionItem item = null; if (o instanceof AVM2InstructionItem) { item = (AVM2InstructionItem) o; } else if (o instanceof AVM2Instruction) { item = actionItemMap.get((AVM2Instruction) o); } if (item == null) { return false; } removeItem(item); return true; } @Override public boolean containsAll(Collection<?> c) { for (Object c1 : c) { if (!contains(c1)) { return false; } } return true; } @Override public boolean addAll(Collection<? extends AVM2InstructionItem> c) { for (AVM2InstructionItem c1 : c) { insertItemAfter(null, c1); } return true; } @Override public boolean removeAll(Collection<?> c) { boolean result = false; for (Object c1 : c) { result |= remove(c1); } return result; } @Override public boolean retainAll(Collection<?> c) { AVM2InstructionItem item = firstItem; if (item == null) { return false; } boolean modified = false; do { if (!c.contains(item)) { item = removeItem(item); modified = true; continue; } item = item.next; } while (item != firstItem); return modified; } @Override public void clear() { firstItem = null; size = 0; } }