From 3f1ed100be79622bc6ede37ad95c6683069af330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Thu, 4 Sep 2025 19:45:58 +0200 Subject: [PATCH] AS3 switch compilation reorganized --- .../flash/abc/avm2/graph/AVM2Graph.java | 78 +++++++++++-------- .../parser/script/AVM2SourceGenerator.java | 44 ++++++----- .../src/com/jpexs/decompiler/graph/Graph.java | 7 +- .../decompiler/graph/model/SwitchItem.java | 10 +++ 4 files changed, 89 insertions(+), 50 deletions(-) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2Graph.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2Graph.java index 0cbe9a6ef..edef899f9 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/graph/AVM2Graph.java @@ -1292,7 +1292,7 @@ public class AVM2Graph extends Graph { AVM2LocalData localData2 = new AVM2LocalData(localData); //localData2.scopeStack = new ScopeStack(); localData2.localScopeStack = new ScopeStack(); - + try { //We are assuming Finally target has only 1 part translatePart(finallyTargetItems, localData2, finallyTryTargetPart, st2, staticOperation, path); @@ -2286,14 +2286,14 @@ public class AVM2Graph extends Graph { } int firstPushPos = -1; - + for (int i = output.size() - 2 /*last is loop*/; i >= 0; i--) { if (output.get(i) instanceof PushItem) { firstPushPos = i; } else { break; } - } + } if (firstPushPos > -1) { int max = output.size() - 2; for (int i = firstPushPos; i <= max; i++) { @@ -2566,7 +2566,7 @@ public class AVM2Graph extends Graph { if (debugDoNotProcess) { return; } - + loopi: for (int i = 1 /*not first*/; i < list.size(); i++) { GraphTargetItem item = list.get(i); @@ -2644,7 +2644,6 @@ public class AVM2Graph extends Graph { } } - List thisExpressions = new ArrayList<>(); int defaultIndex = -1; for (int k = 0; k < thisSwitch.caseValues.size(); k++) { if (thisSwitch.caseValues.get(k) instanceof DefaultItem) { @@ -2654,6 +2653,47 @@ public class AVM2Graph extends Graph { if (!(thisSwitch.caseValues.get(k) instanceof IntegerValueTypeItem)) { continue loopi; } + } + + if (defaultIndex > -1 && !thisSwitch.additionalDefaultValues.isEmpty() && thisSwitch.additionalDefaultPosition > -1) { + + int defaultBodyIndex = thisSwitch.valuesMapping.get(defaultIndex); + + if (thisSwitch.additionalDefaultPosition != defaultIndex) { + int previousBodyIndex = thisSwitch.valuesMapping.get(thisSwitch.additionalDefaultPosition); + List defaultBody = thisSwitch.caseCommands.remove(defaultBodyIndex); + thisSwitch.caseCommands.add(previousBodyIndex, defaultBody); + for (int p = 0; p < thisSwitch.valuesMapping.size(); p++) { + if (thisSwitch.valuesMapping.get(p) >= previousBodyIndex) { + thisSwitch.valuesMapping.set(p, thisSwitch.valuesMapping.get(p) + 1); + } + } + defaultBodyIndex = previousBodyIndex; + GraphTargetItem lastCmd = defaultBody.isEmpty() ? null : defaultBody.get(defaultBody.size() - 1); + if (lastCmd != null && !(lastCmd instanceof ExitItem) && !(lastCmd instanceof BreakItem) && !(lastCmd instanceof ContinueItem)) { + defaultBody.add(new BreakItem(thisSwitch.dialect, null, null, thisSwitch.loop.id)); + } + } + thisSwitch.caseValues.remove(defaultIndex); + thisSwitch.valuesMapping.remove(defaultIndex); + + thisSwitch.caseValues.add(thisSwitch.additionalDefaultPosition, new DefaultItem(thisSwitch.dialect)); + thisSwitch.valuesMapping.add(thisSwitch.additionalDefaultPosition, defaultBodyIndex); + for (int m = thisSwitch.additionalDefaultValues.size() - 1; m >= 0; m--) { + thisSwitch.caseValues.add(thisSwitch.additionalDefaultPosition, thisSwitch.additionalDefaultValues.get(m)); + thisSwitch.valuesMapping.add(thisSwitch.additionalDefaultPosition, defaultBodyIndex); + } + } + + List thisExpressions = new ArrayList<>(); + + for (int k = 0; k < thisSwitch.caseValues.size(); k++) { + if (thisSwitch.caseValues.get(k) instanceof DefaultItem) { + continue; + } + if (!(thisSwitch.caseValues.get(k) instanceof IntegerValueTypeItem)) { + continue loopi; + } int cv = ((IntegerValueTypeItem) thisSwitch.caseValues.get(k)).intValue(); /*if (!expressionsMap.containsKey(cv)) { continue loopi; @@ -2674,32 +2714,8 @@ public class AVM2Graph extends Graph { return o1 - o2; } }); - - /*if (!noCaseExpressions.isEmpty() && defaultIndex == -1) { - thisSwitch.caseValues.add(new DefaultItem(dialect)); - thisSwitch.valuesMapping.add(thisSwitch.caseCommands.size()); - if (!thisSwitch.caseCommands.isEmpty()) { - List lastCommands = thisSwitch.caseCommands.get(thisSwitch.caseCommands.size() - 1); - if ( - lastCommands.isEmpty() - || !( - (lastCommands.get(lastCommands.size() - 1) instanceof ExitItem) - || (lastCommands.get(lastCommands.size() - 1) instanceof ContinueItem) - || (lastCommands.get(lastCommands.size() - 1) instanceof BreakItem) - ) - ) { - lastCommands.add(new BreakItem(dialect, null, null, thisSwitch.loop.id)); - } - } - thisSwitch.caseCommands.add(new ArrayList<>()); - }*/ for (int k = 0; k < thisSwitch.caseValues.size(); k++) { - /*if (thisSwitch.caseValues.get(k) instanceof DefaultItem) { - thisSwitch.removeValue(k); - k--; - continue; - }*/ if (thisSwitch.caseValues.get(k) instanceof DefaultItem) { int m = thisSwitch.valuesMapping.get(k); thisSwitch.caseValues.remove(k); @@ -2730,12 +2746,12 @@ public class AVM2Graph extends Graph { continue; } int cv = ((IntegerValueTypeItem) thisSwitch.caseValues.get(k)).intValue(); - + if (!expressionsMap.containsKey(cv)) { thisSwitch.removeValue(k); k--; continue; - } + } thisSwitch.caseValues.set(k, expressionsMap.get(cv)); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AVM2SourceGenerator.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AVM2SourceGenerator.java index d0d755f7a..4219a6955 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AVM2SourceGenerator.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/AVM2SourceGenerator.java @@ -2909,34 +2909,33 @@ public class AVM2SourceGenerator implements SourceGenerator { ret.add(forwardJump); int defIndex = item.caseValues.size(); + boolean hasDefault = false; for (int i = item.caseValues.size() - 1; i >= 0; i--) { if (item.caseValues.get(i) instanceof DefaultItem) { + hasDefault = true; defIndex = i; break; } } + + int realCasesCount = item.caseValues.size() + (hasDefault ? -1 : 0); List cases = new ArrayList<>(); - cases.addAll(toInsList(new IntegerValueAVM2Item(null, null, defIndex).toSource(localData, this))); - int cLen = insToBytes(cases).length; - List caseLast = new ArrayList<>(); - caseLast.add(0, ins(AVM2Instructions.Jump, cLen)); - caseLast.addAll(0, toInsList(new IntegerValueAVM2Item(null, null, defIndex).toSource(localData, this))); - int cLastLen = insToBytes(caseLast).length; - caseLast.add(0, ins(AVM2Instructions.Jump, cLastLen)); - cases.addAll(0, caseLast); + cases.addAll(0, toInsList(new IntegerValueAVM2Item(null, null, -1).toSource(localData, this))); List preCases = new ArrayList<>(); preCases.addAll(toInsList(item.switchedObject.toSource(localData, this))); preCases.addAll(toInsList(AssignableAVM2Item.setTemp(localData, this, switchedReg))); + + int pos = realCasesCount - 1; for (int i = item.caseValues.size() - 1; i >= 0; i--) { if (item.caseValues.get(i) instanceof DefaultItem) { continue; - } + } List sub = new ArrayList<>(); - sub.addAll(toInsList(new IntegerValueAVM2Item(null, null, i).toSource(localData, this))); + sub.addAll(toInsList(new IntegerValueAVM2Item(null, null, pos--).toSource(localData, this))); sub.add(ins(AVM2Instructions.Jump, insToBytes(cases).length)); int subLen = insToBytes(sub).length; @@ -2946,8 +2945,10 @@ public class AVM2SourceGenerator implements SourceGenerator { cases.addAll(0, toInsList(item.caseValues.get(i).toSource(localData, this))); } cases.addAll(0, preCases); + + - AVM2Instruction lookupOp = new AVM2Instruction(0, AVM2Instructions.LookupSwitch, new int[1 + 1 + item.caseValues.size() + 1]); + AVM2Instruction lookupOp = new AVM2Instruction(0, AVM2Instructions.LookupSwitch, new int[1 + 1 + realCasesCount]); cases.addAll(toInsList(AssignableAVM2Item.killTemp(localData, this, Arrays.asList(switchedReg)))); List bodies = new ArrayList<>(); List bodiesOffsets = new ArrayList<>(); @@ -2956,18 +2957,25 @@ public class AVM2SourceGenerator implements SourceGenerator { bodies.add(0, ins(AVM2Instructions.Label)); bodies.add(ins(new BreakJumpIns(item.loop.id), 0)); //There could be two breaks when default clause ends with break, but official compiler does this too, so who cares... defOffset = -(insToBytes(bodies).length + casesLen); - for (int i = item.caseCommands.size() - 1; i >= 0; i--) { + for (int i = item.caseCommands.size() - 1; i >= 0; i--) { bodies.addAll(0, generateToInsList(localData, item.caseCommands.get(i))); bodies.add(0, ins(AVM2Instructions.Label)); bodiesOffsets.add(0, -(insToBytes(bodies).length + casesLen)); } - lookupOp.operands[0] = defOffset; - lookupOp.operands[1] = item.valuesMapping.size(); - // as per avm2 spec: "There are case_count+1 case offsets" - for (int i = 0; i < item.valuesMapping.size(); i++) { - lookupOp.operands[2 + i] = bodiesOffsets.get(item.valuesMapping.get(i)); + if (hasDefault) { + defOffset = bodiesOffsets.get(item.valuesMapping.get(defIndex)); } - lookupOp.operands[2 + item.valuesMapping.size()] = defOffset; + lookupOp.operands[0] = defOffset; + lookupOp.operands[1] = realCasesCount - 1; + // as per avm2 spec: "There are case_count+1 case offsets" + pos = 0; + for (int i = 0; i < item.valuesMapping.size(); i++) { + if (item.caseValues.get(i) instanceof DefaultItem) { + continue; + } + lookupOp.operands[2 + pos++] = bodiesOffsets.get(item.valuesMapping.get(i)); + } + //lookupOp.operands[2 + item.valuesMapping.size()] = defOffset; forwardJump.operands[0] = insToBytes(bodies).length; ret.addAll(bodies); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java index 911d08cf7..f842d383e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/Graph.java @@ -3753,10 +3753,13 @@ public class Graph { GraphPart defaultPart = hasExpr ? getNextParts(localData, part).get(1 + defaultBranch) : getNextParts(localData, part).get(0); List caseBodyParts = new ArrayList<>(); + List additionalDefaultValues = new ArrayList<>(); + int additionalDefaultPosition = -1; for (int i = 1; i < getNextParts(localData, part).size(); i++) { if (!hasExpr) { - //This if probably should be removed, but it fails some tests... if (getNextParts(localData, part).get(i) == defaultPart) { + additionalDefaultPosition = caseValues.size(); + additionalDefaultValues.add(new IntegerValueItem(dialect, null, localData.lineStartInstruction, pos)); pos++; continue; } @@ -3781,6 +3784,8 @@ public class Graph { makeAllCommands(currentRet, stack); SwitchItem sw = handleSwitch(switchedItem, originalSwitchedItem.getSrc(), foundGotos, partCodes, partCodePos, visited, allParts, stack, stopPart, stopPartKind, loops, throwStates, localData, staticOperation, path, caseValues, defaultPart, caseBodyParts, nextRef, tiRef); + sw.additionalDefaultPosition = additionalDefaultPosition; + sw.additionalDefaultValues = additionalDefaultValues; GraphPart next = nextRef.getVal(); checkSwitch(localData, sw, caseExpressionOtherSides.values(), currentRet); currentRet.add(sw); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/SwitchItem.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/SwitchItem.java index d1525b537..5ab01b600 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/SwitchItem.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/graph/model/SwitchItem.java @@ -58,7 +58,17 @@ public class SwitchItem extends LoopItem implements Block { * Values mapping */ public List valuesMapping; + + /** + * Additional default position. Mark for additional processing... + */ + public int additionalDefaultPosition = -1; + /** + * Additional default values - they are not printed. This is just mark for additional processing... + */ + public List additionalDefaultValues = new ArrayList<>(); + /** * Label used */