Fixed: #2486 AS3 switches detection in some cases

This commit is contained in:
Jindra Petřík
2025-07-20 11:55:37 +02:00
parent 4e62cddacf
commit 7076cdfc36
12 changed files with 816 additions and 63 deletions

View File

@@ -38,6 +38,7 @@ All notable changes to this project will be documented in this file.
- Highlighter nullpointer
- AS3 search slot name Index out of bounds
- AS text search - not being able to cancel search over multiple swf files
- [#2486] AS3 switches detection in some cases
### Changed
- Icon of "Deobfuscation options" menu from pile of pills to medkit

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2010-2025 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.deobfuscation;
import com.jpexs.decompiler.flash.abc.ABC;
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.jumps.IfFalseIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.JumpIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.other.NopIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushFalseIns;
import com.jpexs.decompiler.flash.abc.types.MethodBody;
import com.jpexs.decompiler.flash.abc.types.traits.Trait;
import com.jpexs.decompiler.flash.helpers.SWFDecompilerAdapter;
import java.util.List;
import java.util.Map;
/**
* This will fix some if(false) in Flex code in switch default statement.
* It is not a real deobufscation, just fix for specific case.
* @author JPEXS
*/
public class AVM2DeobfuscatorPushFalseIfFalse extends SWFDecompilerAdapter {
@Override
public void avm2CodeRemoveTraps(String path, int classIndex, boolean isStatic, int scriptIndex, ABC abc, Trait trait, int methodInfo, MethodBody body) throws InterruptedException {
Map<Integer, List<Integer>> refs = body.getCode().visitCode(body);
AVM2Code code = body.getCode();
for (int ip = 1; ip < code.code.size(); ip++) {
AVM2Instruction ins = code.code.get(ip);
AVM2Instruction insPrev = code.code.get(ip - 1);
if (!(ins.definition instanceof IfFalseIns)) {
continue;
}
if (!(insPrev.definition instanceof PushFalseIns)) {
continue;
}
if (refs.containsKey(ip) && refs.get(ip).size() > 1) {
continue;
}
insPrev.definition = new NopIns();
ins.definition = new JumpIns();
}
}
}

View File

@@ -27,7 +27,9 @@ import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction;
import com.jpexs.decompiler.flash.abc.avm2.instructions.InstructionDefinition;
import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.NewCatchIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.debug.DebugLineIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfFalseIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfStrictEqIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfStrictNeIns;
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.avm2.instructions.localregs.DecLocalIIns;
@@ -44,6 +46,7 @@ import com.jpexs.decompiler.flash.abc.avm2.instructions.other.decimalsupport.Dec
import com.jpexs.decompiler.flash.abc.avm2.instructions.other.decimalsupport.IncLocalPIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PopIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushByteIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushFalseIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushScopeIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.types.CoerceAIns;
import com.jpexs.decompiler.flash.abc.avm2.instructions.types.ConvertIIns;
@@ -84,6 +87,7 @@ import com.jpexs.decompiler.flash.abc.avm2.model.clauses.TryAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.operations.EqAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.operations.NeqAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.operations.StrictEqAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.operations.StrictNeqAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.parser.script.AbcIndexing;
import com.jpexs.decompiler.flash.abc.types.ABCException;
import com.jpexs.decompiler.flash.abc.types.MethodBody;
@@ -110,13 +114,17 @@ import com.jpexs.decompiler.graph.model.BinaryOpItem;
import com.jpexs.decompiler.graph.model.BreakItem;
import com.jpexs.decompiler.graph.model.CommaExpressionItem;
import com.jpexs.decompiler.graph.model.ContinueItem;
import com.jpexs.decompiler.graph.model.DefaultItem;
import com.jpexs.decompiler.graph.model.ExitItem;
import com.jpexs.decompiler.graph.model.FalseItem;
import com.jpexs.decompiler.graph.model.GotoItem;
import com.jpexs.decompiler.graph.model.IfItem;
import com.jpexs.decompiler.graph.model.IntegerValueItem;
import com.jpexs.decompiler.graph.model.IntegerValueTypeItem;
import com.jpexs.decompiler.graph.model.LoopItem;
import com.jpexs.decompiler.graph.model.NotItem;
import com.jpexs.decompiler.graph.model.OrItem;
import com.jpexs.decompiler.graph.model.PopItem;
import com.jpexs.decompiler.graph.model.PushItem;
import com.jpexs.decompiler.graph.model.SwitchItem;
import com.jpexs.decompiler.graph.model.TernarOpItem;
@@ -129,6 +137,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -1735,11 +1744,8 @@ public class AVM2Graph extends Graph {
Loop currentLoop, int staticOperation, String path) throws InterruptedException {
List<GraphTargetItem> ret = null;
/*if (ret != null) {
return ret;
}*/
//Detect switch
if ((part.nextParts.size() == 2) && (!stack.isEmpty()) && (stack.peek() instanceof StrictEqAVM2Item)) {
if ((part.nextParts.size() == 2) && (!stack.isEmpty()) && ((stack.peek() instanceof StrictEqAVM2Item) || (stack.peek() instanceof StrictNeqAVM2Item))) {
GraphSourceItem switchStartItem = code.get(part.start);
GraphTargetItem switchedObject = null;
@@ -1750,29 +1756,82 @@ public class AVM2Graph extends Graph {
}
List<GraphTargetItem> caseValuesMapLeft = new ArrayList<>();
List<GraphTargetItem> caseValuesMapRight = new ArrayList<>();
List<List<GraphTargetItem>> outs = new ArrayList<>();
StrictEqAVM2Item set = (StrictEqAVM2Item) stack.pop();
StrictEqAVM2Item firstSet = set;
outs.add(new ArrayList<>());
BinaryOpItem set = (BinaryOpItem) stack.pop();
BinaryOpItem firstSet = set;
caseValuesMapLeft.add(set.leftSide);
caseValuesMapRight.add(set.rightSide);
int branchNum;
GraphPart origPart = part;
List<GraphPart> caseBodyParts = new ArrayList<>();
caseBodyParts.add(part.nextParts.get(0));
if (((AVM2Instruction) code.get(part.end)).definition instanceof IfStrictNeIns) {
branchNum = 0;
caseBodyParts.add(part.nextParts.get(1));
} else {
branchNum = 1;
caseBodyParts.add(part.nextParts.get(0));
}
GraphTargetItem top = null;
int cnt = 1;
int branchCount = 2;
try {
while (part.nextParts.size() > 1
&& part.nextParts.get(1).getHeight() > 1
&& ((AVM2Instruction) code.get(part.nextParts.get(1).end >= code.size() ? code.size() - 1 : part.nextParts.get(1).end)).definition instanceof IfStrictEqIns
&& ((top = translatePartGetStack(localData, part.nextParts.get(1), stack, staticOperation)) instanceof StrictEqAVM2Item)) {
cnt++;
part = part.nextParts.get(1);
caseBodyParts.add(part.nextParts.get(0));
while (true) {
List<GraphTargetItem> out = new ArrayList<>();
//Special: In Flex (not air) there are these blocks sometimes:
// if(false) {
// §§push(5);
// break;
//}
//AVM2DeobfuscatorPushFalseIfFalse changes it to
// nop
// jump
// (to first case to work)
if (part.nextParts.size() == branchCount
&& part.nextParts.get(branchNum).getHeight() == 2
&& ((AVM2Instruction) code.get(part.nextParts.get(branchNum).start >= code.size() ? code.size() - 1 : part.nextParts.get(branchNum).start)).definition instanceof NopIns
&& ((AVM2Instruction) code.get(part.nextParts.get(branchNum).end >= code.size() ? code.size() - 1 : part.nextParts.get(branchNum).end)).definition instanceof JumpIns) {
part = part.nextParts.get(branchNum);
branchNum = 0;
branchCount = 1;
} else if (part.nextParts.size() == branchCount
&& part.nextParts.get(branchNum).getHeight() > 1
&& ((AVM2Instruction) code.get(part.nextParts.get(branchNum).end >= code.size() ? code.size() - 1 : part.nextParts.get(branchNum).end)).definition instanceof IfStrictEqIns
&& ((top = translatePartGetStack(localData, part.nextParts.get(branchNum), stack, staticOperation, out)) instanceof StrictEqAVM2Item)) {
cnt++;
part = part.nextParts.get(branchNum);
caseBodyParts.add(part.nextParts.get(0));
branchNum = 1;
set = (StrictEqAVM2Item) top;
caseValuesMapLeft.add(set.leftSide);
caseValuesMapRight.add(set.rightSide);
outs.add(out);
out = new ArrayList<>();
set = (StrictEqAVM2Item) top;
caseValuesMapLeft.add(set.leftSide);
caseValuesMapRight.add(set.rightSide);
branchCount = 2;
} else if (part.nextParts.size() == branchCount
&& part.nextParts.get(branchNum).getHeight() > 1
&& ((AVM2Instruction) code.get(part.nextParts.get(branchNum).end >= code.size() ? code.size() - 1 : part.nextParts.get(branchNum).end)).definition instanceof IfStrictNeIns
&& ((top = translatePartGetStack(localData, part.nextParts.get(branchNum), stack, staticOperation, out)) instanceof StrictNeqAVM2Item)) {
cnt++;
part = part.nextParts.get(branchNum);
caseBodyParts.add(part.nextParts.get(1));
branchNum = 0;
outs.add(out);
out = new ArrayList<>();
set = (StrictNeqAVM2Item) top;
caseValuesMapLeft.add(set.leftSide);
caseValuesMapRight.add(set.rightSide);
branchCount = 2;
} else {
break;
}
}
} catch (GraphPartChangeException gpc) {
//ignore
@@ -1818,10 +1877,17 @@ public class AVM2Graph extends Graph {
otherSide = caseValuesMapRight;
}
for (int i = 0; i < caseValuesMap.size(); i++) {
if (!outs.get(i).isEmpty()) {
outs.get(i).add(caseValuesMap.get(i));
caseValuesMap.set(i, new CommaExpressionItem(dialect, null, null, outs.get(i)));
}
}
if ((leftReg < 0 && rightReg < 0) || (cnt == 1)) {
stack.push(firstSet);
} else {
part = part.nextParts.get(1);
part = part.nextParts.get(branchNum);
GraphPart defaultPart = part;
if (code.size() > defaultPart.start && ((AVM2Instruction) code.get(defaultPart.start)).definition instanceof JumpIns
&& defaultPart.refs.size() == 1
@@ -2443,9 +2509,11 @@ public class AVM2Graph extends Graph {
if (item instanceof IntegerValueAVM2Item) {
return true;
}
/*
if ((item instanceof PushItem) && (item.value instanceof IntegerValueAVM2Item)) {
return true;
}
*/
return false;
}
@@ -2454,6 +2522,159 @@ public class AVM2Graph extends Graph {
if (debugDoNotProcess) {
return;
}
loopi:
for (int i = 1 /*not first*/; i < list.size(); i++) {
GraphTargetItem item = list.get(i);
GraphTargetItem prevItem = list.get(i - 1);
if ((item instanceof SwitchItem) && (prevItem instanceof SwitchItem)) {
SwitchItem thisSwitch = (SwitchItem) item;
SwitchItem prevSwitch = (SwitchItem) prevItem;
if (thisSwitch.switchedObject instanceof PopItem) {
List<Integer> caseValues = new ArrayList<>();
Map<Integer, GraphTargetItem> expressionsMap = new LinkedHashMap<>();
List<GraphTargetItem> defaultCommands = null;
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 defaultInt = -1;
for (int j = 0; j < prevSwitch.caseCommands.size(); j++) {
int valueIndex = prevSwitch.valuesMapping.indexOf(j);
if (valueIndex == -1) {
continue loopi;
}
GraphTargetItem currentExpression = prevSwitch.caseValues.get(valueIndex);
if (prevSwitch.caseCommands.get(j).size() > 2) {
continue loopi;
}
int delta = 0;
if (currentExpression instanceof DefaultItem && prevSwitch.caseCommands.size() >= 2) {
//Special case - in flex (not air), the default clause has weird
// if (false) {
// §§push(5);
// }
// §§push(5);
if (prevSwitch.caseCommands.get(j).get(0) instanceof IfItem) {
IfItem ifi = (IfItem) prevSwitch.caseCommands.get(j).get(0);
if (ifi.expression instanceof FalseItem) {
delta++;
}
}
}
if (j == prevSwitch.caseCommands.size() - 1 && prevSwitch.caseCommands.get(j).size() == delta + 1) {
//empty
} else if (prevSwitch.caseCommands.get(j).size() != 2 + delta) {
continue loopi;
} else {
if (!(prevSwitch.caseCommands.get(j).get(delta + 1) instanceof BreakItem)) {
continue loopi;
}
BreakItem br = (BreakItem) prevSwitch.caseCommands.get(j).get(delta + 1);
if (br.loopId != prevSwitch.loop.id) {
continue loopi;
}
}
if (!(prevSwitch.caseCommands.get(j).get(delta) instanceof PushItem)) {
continue loopi;
}
PushItem pi = (PushItem) prevSwitch.caseCommands.get(j).get(delta);
if (!(pi.value instanceof IntegerValueTypeItem)) {
continue loopi;
}
Integer pushedInt = ((IntegerValueTypeItem) pi.value).intValue();
expressionsMap.put(pushedInt, currentExpression);
if (currentExpression instanceof DefaultItem) {
defaultInt = pushedInt;
}
}
List<Integer> thisExpressions = new ArrayList<>();
int defaultIndex = -1;
for (int k = 0; k < thisSwitch.caseValues.size(); k++) {
if (thisSwitch.caseValues.get(k) instanceof DefaultItem) {
defaultIndex = k;
continue;
}
if (!(thisSwitch.caseValues.get(k) instanceof IntegerValueTypeItem)) {
continue loopi;
}
int cv = ((IntegerValueTypeItem) thisSwitch.caseValues.get(k)).intValue();
if (!expressionsMap.containsKey(cv)) {
continue loopi;
}
thisExpressions.add(cv);
}
List<Integer> noCaseExpressions = new ArrayList<>();
for (int key : expressionsMap.keySet()) {
if (!thisExpressions.contains(key)) {
if (defaultIndex == -1 && defaultInt != key) {
continue loopi;
}
noCaseExpressions.add(key);
}
}
noCaseExpressions.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
for (int k = 0; k < thisSwitch.caseValues.size(); k++) {
if (thisSwitch.caseValues.get(k) instanceof DefaultItem) {
int m = thisSwitch.valuesMapping.get(k);
thisSwitch.caseValues.remove(k);
thisSwitch.valuesMapping.remove(k);
for (int e : noCaseExpressions) {
thisSwitch.caseValues.add(k, expressionsMap.get(e));
thisSwitch.valuesMapping.add(k, m);
k++;
}
k--;
boolean foundElsewhere = false;
for (int h = 0; h < thisSwitch.valuesMapping.size(); h++) {
if (thisSwitch.valuesMapping.get(h) == m) {
foundElsewhere = true;
break;
}
}
if (!foundElsewhere) {
thisSwitch.caseCommands.remove(m);
for (int h = 0; h < thisSwitch.valuesMapping.size(); h++) {
if (thisSwitch.valuesMapping.get(h) > m) {
thisSwitch.valuesMapping.set(h, thisSwitch.valuesMapping.get(h) - 1);
}
}
}
continue;
}
int cv = ((IntegerValueTypeItem) thisSwitch.caseValues.get(k)).intValue();
thisSwitch.caseValues.set(k, expressionsMap.get(cv));
}
thisSwitch.switchedObject = prevSwitch.switchedObject;
list.remove(i - 1);
i--;
}
}
}
if (level == 0) {
if (!list.isEmpty()) {
if (list.get(list.size() - 1) instanceof ReturnVoidAVM2Item) {
@@ -2890,7 +3111,7 @@ public class AVM2Graph extends Graph {
});
i = newI.getVal();
}
*/
*/
//Handle for loops at the end:
super.finalProcess(parent, list, level, localData, path);
}
@@ -2931,7 +3152,7 @@ public class AVM2Graph extends Graph {
AVM2LocalData avm2LocalData = (AVM2LocalData) localData;
SetLocalAVM2Item setLocal = (SetLocalAVM2Item) output.get(output.size() - 1);
int setLocalIp = InstructionDefinition.getItemIp(avm2LocalData, setLocal);
;
Set<Integer> allUsages = new HashSet<>(avm2LocalData.getSetLocalUsages(setLocalIp));
boolean isOtherSideReg = false;
for (GraphTargetItem otherSide : otherSides) {
@@ -3263,5 +3484,5 @@ public class AVM2Graph extends Graph {
}
return ternar;
}
}
}

View File

@@ -23,6 +23,7 @@ import com.jpexs.decompiler.flash.abc.avm2.AVM2Code;
import com.jpexs.decompiler.flash.abc.avm2.AVM2ConstantPool;
import com.jpexs.decompiler.flash.abc.avm2.CodeStats;
import com.jpexs.decompiler.flash.abc.avm2.UnknownInstructionCodeException;
import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.AVM2DeobfuscatorPushFalseIfFalse;
import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.DeobfuscationLevel;
import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction;
import com.jpexs.decompiler.flash.abc.avm2.parser.script.AbcIndexing;
@@ -642,6 +643,11 @@ public final class MethodBody implements Cloneable {
code.fixJumps(path, body);
return body;
}
} else {
//This needs to be done otherwise some switch testcases fail. (Flex)
try (Statistics s = new Statistics("AVM2DeobfuscatorPushFalseIfFalse")) {
new AVM2DeobfuscatorPushFalseIfFalse().avm2CodeRemoveTraps(path, classIndex, isStatic, scriptIndex, abc, trait, method_info, body);
}
}
lastConvertedBody = body;

View File

@@ -27,6 +27,7 @@ import com.jpexs.decompiler.graph.model.BinaryOpItem;
import com.jpexs.decompiler.graph.model.BranchStackResistant;
import com.jpexs.decompiler.graph.model.BreakItem;
import com.jpexs.decompiler.graph.model.CommaExpressionItem;
import com.jpexs.decompiler.graph.model.CommentItem;
import com.jpexs.decompiler.graph.model.ContinueItem;
import com.jpexs.decompiler.graph.model.DefaultItem;
import com.jpexs.decompiler.graph.model.DoWhileItem;
@@ -1383,7 +1384,7 @@ public class Graph {
* @param list List of GraphTargetItems
* @param lastLoopId Last loop id
*/
protected final void processSwitches(List<GraphTargetItem> list, long lastLoopId) {
protected void processSwitches(List<GraphTargetItem> list, long lastLoopId) {
loopi:
for (int i = 0; i < list.size(); i++) {
GraphTargetItem item = list.get(i);
@@ -2311,6 +2312,25 @@ public class Graph {
translatePart(localData, part, stack, staticOperation, null);
return stack.pop();
}
/**
* Translates part and get its stack with output
*
* @param localData Local data
* @param part Part
* @param stack Translate stack
* @param staticOperation Unused
* @return Top of the stack
* @throws InterruptedException On interrupt
* @throws GraphPartChangeException On graph part change
*/
//@SuppressWarnings("unchecked")
protected final GraphTargetItem translatePartGetStack(BaseLocalData localData, GraphPart part, TranslateStack stack, int staticOperation, List<GraphTargetItem> output) throws InterruptedException, GraphPartChangeException {
stack = (TranslateStack) stack.clone();
output.clear();
output.addAll(translatePart(localData, part, stack, staticOperation, null));
return stack.pop();
}
/**
* Translates part.
@@ -3194,6 +3214,7 @@ public class Graph {
if (debugPrintGraph) {
System.err.println("Adding break");
}
makeAllCommands(ret, stack);
ret.add(new BreakItem(dialect, null, localData.lineStartInstruction, el.id));
return ret;
}
@@ -3204,6 +3225,7 @@ public class Graph {
if (debugPrintGraph) {
System.err.println("Adding precontinue");
}
makeAllCommands(ret, stack);
ret.add(new ContinueItem(dialect, null, localData.lineStartInstruction, el.id));
return ret;
}
@@ -3214,6 +3236,7 @@ public class Graph {
if (debugPrintGraph) {
System.err.println("Adding continue");
}
makeAllCommands(ret, stack);
ret.add(new ContinueItem(dialect, null, localData.lineStartInstruction, el.id));
return ret;
}
@@ -3267,8 +3290,10 @@ public class Graph {
}
}
if (code.size() <= part.start) {
ret.add(new ScriptEndItem(dialect));
if (code.size() <= part.start) {
if (!(!ret.isEmpty() && ret.get(ret.size() - 1) instanceof ExitItem)) {
ret.add(new ScriptEndItem(dialect));
}
return ret;
}
@@ -3428,7 +3453,9 @@ public class Graph {
}
} while (exHappened);
if ((part.end >= code.size() - 1) && getNextParts(localData, part).isEmpty()) {
output.add(new ScriptEndItem(dialect));
if (!(!output.isEmpty() && output.get(output.size() - 1) instanceof ExitItem)) {
output.add(new ScriptEndItem(dialect));
}
}
}
@@ -4651,7 +4678,8 @@ public class Graph {
stopPart2x.add(breakPart);
stopPartKind2x.add(StopPartKind.OTHER);
}
currentCaseCommands = printGraph(foundGotos, partCodes, partCodePos, visited, localData, stack, allParts, null, caseBodies.get(i), stopPart2x, stopPartKind2x, loops, throwStates, staticOperation, path);
TranslateStack subStack = (TranslateStack) stack.clone();
currentCaseCommands = printGraph(foundGotos, partCodes, partCodePos, visited, localData, subStack, allParts, null, caseBodies.get(i), stopPart2x, stopPartKind2x, loops, throwStates, staticOperation, path);
if (willHaveBreak) {
if (!currentCaseCommands.isEmpty()) {
GraphTargetItem last = currentCaseCommands.get(currentCaseCommands.size() - 1);
@@ -4660,8 +4688,7 @@ public class Graph {
}
}
}
caseCommands.add(currentCaseCommands);
makeAllCommands(currentCaseCommands, stack);
caseCommands.add(currentCaseCommands);
}
/*

View File

@@ -17,7 +17,6 @@
package com.jpexs.decompiler.flash;
import com.jpexs.decompiler.flash.abc.ABC;
import com.jpexs.decompiler.flash.abc.ScriptPack;
import com.jpexs.decompiler.flash.abc.avm2.AVM2Code;
import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.AVM2DeobfuscatorGroupParts;
import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.AVM2DeobfuscatorJumps;
@@ -169,10 +168,10 @@ public class ActionScript3DeobfuscatorTest extends ActionScriptTestBase {
code.removeTraps(null, 0, b, abc, 0, -1, true, pCode);
code.removeLabelsAndDebugLine(b);
HighlightedTextWriter writer = new HighlightedTextWriter(new CodeFormatting(), false);
String actual = b.toSource(10, new ArrayList<>(), swf.getAbcIndex(), 0, new HashSet<>());
String actual = b.toSource(10, new ArrayList<>(), swf.getAbcIndex(), 0, new HashSet<>());
actual = actual.replace("\r\n", "\n");
assertEquals(actual, expected);
}
}
private String recompilePCode(String str, SWFDecompilerAdapter deobfuscator) throws IOException, AVM2ParseException, InterruptedException {
str = "code\r\n"
@@ -543,37 +542,383 @@ public class ActionScript3DeobfuscatorTest extends ActionScriptTestBase {
@Test
public void testWhileTrue() throws Exception {
decompilePCode(
" getlex QName(PackageNamespace(\"\"),\"Math\")\n" +
" getlex QName(PackageNamespace(\"\"),\"Math\")\n" +
" debugline 8\n" +
" callproperty QName(PackageNamespace(\"\"),\"random\"), 0\n" +
" pushbyte 6\n" +
" multiply\n" +
" callproperty QName(PackageNamespace(\"\"),\"floor\"), 1\n" +
" convert_i\n" +
" setlocal1\n" +
" getlocal1\n" +
" debugline 10\n" +
" pushbyte 4\n" +
" ifngt ofs0034\n" +
" jump ofs0030\n" +
" ofs002d:\n" +
" label\n" +
" debugline 11\n" +
" ofs0030:\n" +
" jump ofs002d\n" +
" ofs0034:\n" +
" ", " param1 = Math.floor(Math.random() * 6);\n" +
" if(param1 <= 4)\n" +
" {\n" +
" return;\n" +
" }\n" +
" while(true)\n" +
" {\n" +
" }\n");
" getlex QName(PackageNamespace(\"\"),\"Math\")\n"
+ " getlex QName(PackageNamespace(\"\"),\"Math\")\n"
+ " debugline 8\n"
+ " callproperty QName(PackageNamespace(\"\"),\"random\"), 0\n"
+ " pushbyte 6\n"
+ " multiply\n"
+ " callproperty QName(PackageNamespace(\"\"),\"floor\"), 1\n"
+ " convert_i\n"
+ " setlocal1\n"
+ " getlocal1\n"
+ " debugline 10\n"
+ " pushbyte 4\n"
+ " ifngt ofs0034\n"
+ " jump ofs0030\n"
+ " ofs002d:\n"
+ " label\n"
+ " debugline 11\n"
+ " ofs0030:\n"
+ " jump ofs002d\n"
+ " ofs0034:\n"
+ " ", " param1 = Math.floor(Math.random() * 6);\n"
+ " if(param1 <= 4)\n"
+ " {\n"
+ " return;\n"
+ " }\n"
+ " while(true)\n"
+ " {\n"
+ " }\n");
}
@Test
public void testObfuscatedSwitch() throws Exception {
decompilePCode("getlocal0\n"
+ " pushbyte 0\n"
+ " initproperty QName(PackageNamespace(\"\"),\"testA\")\n"
+ " jump ofs0012\n"
+ " call 7\n"
+ " throw\n"
+ " callmethod 1413, 7\n"
+ " ofs0012:\n"
+ " jump ofs001a\n"
+ " ifge ofs001a\n"
+ " ofs001a:\n"
+ " jump ofs0024\n"
+ " equals\n"
+ " pushwith\n"
+ " newfunction 30\n"
+ " pop\n"
+ " setlocal2\n"
+ " ofs0024:\n"
+ " jump ofs0132\n"
+ " pushwith\n"
+ " setlocal3\n"
+ " ofs002a:\n"
+ " label\n"
+ " pushtrue\n"
+ " iftrue ofs0036\n"
+ " returnvoid\n"
+ " popscope\n"
+ " callsuper QName(PackageNamespace(\"aaa\"),\"xxx\"), 10\n"
+ " pushwith\n"
+ " ofs0036:\n"
+ " getlocal0\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " jump ofs01ff\n"
+ " pushwith\n"
+ " setlocal0\n"
+ " ofs0040:\n"
+ " label\n"
+ " getlocal0\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " getlocal0\n"
+ " pushtrue\n"
+ " iftrue ofs0052\n"
+ " returnvoid\n"
+ " popscope\n"
+ " pushwith\n"
+ " newfunction 48\n"
+ " pop\n"
+ " divide\n"
+ " ofs0052:\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " getlocal0\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " jump ofs01ff\n"
+ " ifgt ofs0061\n"
+ " ofs0061:\n"
+ " label\n"
+ " jump ofs006a\n"
+ " ifstricteq ofs006a\n"
+ " ofs006a:\n"
+ " getlocal0\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " jump ofs0078\n"
+ " popscope\n"
+ " pushwith\n"
+ " newfunction 4\n"
+ " throw\n"
+ " checkfilter\n"
+ " ofs0078:\n"
+ " getlocal0\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " jump ofs01ff\n"
+ " pushwith\n"
+ " setlocal0\n"
+ " getlocal3\n"
+ " ofs0083:\n"
+ " label\n"
+ " getlocal0\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " pushfalse\n"
+ " iffalse ofs0094\n"
+ " returnvoid\n"
+ " popscope\n"
+ " equals\n"
+ " setlocal1\n"
+ " newactivation\n"
+ " increment\n"
+ " decrement\n"
+ " ofs0094:\n"
+ " jump ofs01ff\n"
+ " pushwith\n"
+ " setlocal2\n"
+ " ofs009a:\n"
+ " label\n"
+ " getlocal0\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " getlocal0\n"
+ " jump ofs00a9\n"
+ " modulo\n"
+ " callsupervoid QName(PackageInternalNs(\"xxxx\"),\"tmp\"), 5\n"
+ " pushwith\n"
+ " ofs00a9:\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " jump ofs00b4\n"
+ " ifge ofs00b4\n"
+ " ofs00b4:\n"
+ " getlocal0\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " jump ofs00c1\n"
+ " popscope\n"
+ " callproplex QName(PackageNamespace(\"\"),\"xxx\"), 18\n"
+ " setlocal2\n"
+ " ofs00c1:\n"
+ " jump ofs01ff\n"
+ " ifstrictne ofs00c9\n"
+ " ofs00c9:\n"
+ " label\n"
+ " getlocal0\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " jump ofs00d8\n"
+ " nextname\n"
+ " dxns \"position\"\n"
+ " pushwith\n"
+ " setlocal2\n"
+ " returnvalue\n"
+ " ofs00d8:\n"
+ " jump ofs01ff\n"
+ " pushwith\n"
+ " setlocal3\n"
+ " getlocal1\n"
+ " ofs00df:\n"
+ " label\n"
+ " getlocal0\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " getlocal0\n"
+ " jump ofs00ef\n"
+ " popscope\n"
+ " pushwith\n"
+ " newfunction 24\n"
+ " pop\n"
+ " setlocal3\n"
+ " ofs00ef:\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " jump ofs00fa\n"
+ " ifne ofs00fa\n"
+ " ofs00fa:\n"
+ " getlocal0\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " jump ofs0107\n"
+ " popscope\n"
+ " callproplex TypeName(QName(PackageNamespace(\"__AS3__.vec\"),\"Vector\")<QName(PackageNamespace(\"xxx\"),\"aaa\")>), 15\n"
+ " throw\n"
+ " ofs0107:\n"
+ " getlocal0\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " pushtrue\n"
+ " iftrue ofs0117\n"
+ " returnvoid\n"
+ " checkfilter\n"
+ " pushwith\n"
+ " newfunction 51\n"
+ " throw\n"
+ " instanceof\n"
+ " ofs0117:\n"
+ " getlocal0\n"
+ " callpropvoid QName(PackageNamespace(\"\"),\"test\"), 0\n"
+ " pushfalse\n"
+ " iffalse ofs0127\n"
+ " returnvoid\n"
+ " popscope\n"
+ " setlocal1\n"
+ " istypelate\n"
+ " setlocal1\n"
+ " setlocal2\n"
+ " divide\n"
+ " ofs0127:\n"
+ " jump ofs01ff\n"
+ " pushwith\n"
+ " setlocal2\n"
+ " getlocal1\n"
+ " jump ofs0132\n"
+ " ofs0132:\n"
+ " getlex QName(PackageNamespace(\"\"),\"Abc\")\n"
+ " getproperty QName(PackageNamespace(\"\"),\"value\")\n"
+ " setlocal1\n"
+ " jump ofs013f\n"
+ " ifle ofs013f\n"
+ " ofs013f:\n"
+ " pushbyte 1\n"
+ " getlocal1\n"
+ " ifstricteq ofs014a\n"
+ " jump ofs015e\n"
+ " ofs014a:\n"
+ " pushbyte 0\n"
+ " jump ofs0156\n"
+ " pushscope\n"
+ " setlocal0\n"
+ " istypelate\n"
+ " nextvalue\n"
+ " istypelate\n"
+ " decrement\n"
+ " ofs0156:\n"
+ " jump ofs01e3\n"
+ " ifgt ofs015e\n"
+ " ofs015e:\n"
+ " pushbyte 2\n"
+ " getlocal1\n"
+ " ifstricteq ofs0169\n"
+ " jump ofs0178\n"
+ " ofs0169:\n"
+ " pushbyte 1\n"
+ " jump ofs01e3\n"
+ " jump ofs0173\n"
+ " ofs0173:\n"
+ " dup\n"
+ " callsuper QName(PackageNamespace(\"xxx\"),\"aaa\"), 27\n"
+ " pop\n"
+ " ofs0178:\n"
+ " pushbyte 3\n"
+ " getlocal1\n"
+ " ifstricteq ofs0183\n"
+ " jump ofs018b\n"
+ " ofs0183:\n"
+ " pushbyte 2\n"
+ " jump ofs01e3\n"
+ " pushwith\n"
+ " setlocal0\n"
+ " ofs018b:\n"
+ " pushbyte 4\n"
+ " getlocal1\n"
+ " ifstrictne ofs01a2\n"
+ " pushbyte 3\n"
+ " jump ofs01e3\n"
+ " jump ofs019c\n"
+ " ofs019c:\n"
+ " convert_s\n"
+ " construct 110\n"
+ " pushwith\n"
+ " nextvalue\n"
+ " setlocal2\n"
+ " ofs01a2:\n"
+ " pushbyte 5\n"
+ " getlocal1\n"
+ " ifstricteq ofs01ad\n"
+ " jump ofs01b5\n"
+ " ofs01ad:\n"
+ " pushbyte 4\n"
+ " jump ofs01e3\n"
+ " pushwith\n"
+ " setlocal2\n"
+ " ofs01b5:\n"
+ " pushbyte 6\n"
+ " jump ofs01bf\n"
+ " ifgt ofs01bf\n"
+ " ofs01bf:\n"
+ " getlocal1\n"
+ " ifstrictne ofs01d4\n"
+ " pushbyte 5\n"
+ " jump ofs01e3\n"
+ " jump ofs01ce\n"
+ " ofs01ce:\n"
+ " popscope\n"
+ " pushwith\n"
+ " newfunction 49\n"
+ " pop\n"
+ " checkfilter\n"
+ " ofs01d4:\n"
+ " jump ofs01e1\n"
+ " throw\n"
+ " setlocal3\n"
+ " getlocal2\n"
+ " pushbyte 6\n"
+ " jump ofs01e1\n"
+ " ofs01e1:\n"
+ " pushbyte 6\n"
+ " ofs01e3:\n"
+ " kill 1\n"
+ " lookupswitch ofs00df, [ofs002a, ofs0040, ofs0061, ofs0083, ofs009a, ofs00c9, ofs00df]\n"
+ " ofs01ff:\n"
+ " getlocal0\n"
+ " pushbyte 0\n"
+ " initproperty QName(PackageNamespace(\"\"),\"testX\")\n"
+ " jump ofs020e\n"
+ " popscope\n"
+ " pushwith\n"
+ " setlocal1\n"
+ " bitxor\n"
+ " convert_d\n"
+ " setlocal2\n"
+ " ofs020e:\n"
+ " pushfalse\n"
+ " iffalse ofs021a\n"
+ " returnvoid\n"
+ " typeof\n"
+ " checkfilter\n"
+ " bitand\n"
+ " in\n"
+ " convert_s\n"
+ " nextvalue\n"
+ " ofs021a:\n"
+ " jump ofs0222\n"
+ " ifgt ofs0222\n"
+ " ofs0222:\n"
+ " pushtrue\n"
+ " iftrue ofs022d\n"
+ " returnvoid\n"
+ " setlocal2\n"
+ " callsuper QName(PackageNamespace(\"xxx\"),\"aaa\"), 15\n"
+ " throw\n"
+ " ofs022d:", " this.testA = 0;\n"
+ " switch(Abc.value)\n"
+ " {\n"
+ " case 1:\n"
+ " this.test();\n"
+ " break;\n"
+ " case 2:\n"
+ " this.test();\n"
+ " this.test();\n"
+ " this.test();\n"
+ " break;\n"
+ " case 3:\n"
+ " this.test();\n"
+ " this.test();\n"
+ " break;\n"
+ " case 4:\n"
+ " this.test();\n"
+ " break;\n"
+ " case 5:\n"
+ " this.test();\n"
+ " this.test();\n"
+ " this.test();\n"
+ " break;\n"
+ " case 6:\n"
+ " this.test();\n"
+ " break;\n"
+ " default:\n"
+ " this.test();\n"
+ " this.test();\n"
+ " this.test();\n"
+ " this.test();\n"
+ " this.test();\n"
+ " }\n"
+ " this.testX = 0;\n");
}
// TODO: JPEXS @Test
public void testNotRemoveParams() throws Exception {
String res = recompile("function tst(p1,p2){"

View File

@@ -1937,6 +1937,35 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile
false);
}
@Test
public void testSwitchBig() {
decompileMethod("classic_air", "testSwitchBig", "var k:int = 10;\r\n"
+ "switch(k)\r\n"
+ "{\r\n"
+ "case \"A\":\r\n"
+ "trace(\"A\");\r\n"
+ "break;\r\n"
+ "case \"B\":\r\n"
+ "case \"C\":\r\n"
+ "trace(\"BC\");\r\n"
+ "break;\r\n"
+ "case \"D\":\r\n"
+ "default:\r\n"
+ "case \"E\":\r\n"
+ "trace(\"D-default-E\");\r\n"
+ "break;\r\n"
+ "case \"F\":\r\n"
+ "trace(\"F no break\");\r\n"
+ "case \"G\":\r\n"
+ "trace(\"G\");\r\n"
+ "break;\r\n"
+ "case \"H\":\r\n"
+ "trace(\"H last\");\r\n"
+ "}\r\n"
+ "trace(\"after switch\");\r\n",
false);
}
@Test
public void testSwitchComma() {
decompileMethod("classic_air", "testSwitchComma", "var b:int = 5;\r\n"
@@ -1948,7 +1977,7 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile
+ "break;\r\n"
+ "case \"B\":\r\n"
+ "trace(\"is B\");\r\n"
+ "case \"C\":\r\n"
+ "case 7, \"C\":\r\n"
+ "trace(\"is C\");\r\n"
+ "}\r\n",
false);

View File

@@ -424,8 +424,8 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes
decompileMethod("classic", "testDefaultNotLastGrouped", "var k:* = 10;\r\n"
+ "switch(k)\r\n"
+ "{\r\n"
+ "case \"six\":\r\n"
+ "default:\r\n"
+ "case \"six\":\r\n"
+ "trace(\"def and 6\");\r\n"
+ "case \"five\":\r\n"
+ "trace(\"def and 6 and 5\");\r\n"
@@ -1924,6 +1924,35 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes
false);
}
@Test
public void testSwitchBig() {
decompileMethod("classic", "testSwitchBig", "var k:* = 10;\r\n"
+ "switch(k)\r\n"
+ "{\r\n"
+ "case \"A\":\r\n"
+ "trace(\"A\");\r\n"
+ "break;\r\n"
+ "case \"B\":\r\n"
+ "case \"C\":\r\n"
+ "trace(\"BC\");\r\n"
+ "break;\r\n"
+ "case \"D\":\r\n"
+ "default:\r\n"
+ "case \"E\":\r\n"
+ "trace(\"D-default-E\");\r\n"
+ "break;\r\n"
+ "case \"F\":\r\n"
+ "trace(\"F no break\");\r\n"
+ "case \"G\":\r\n"
+ "trace(\"G\");\r\n"
+ "break;\r\n"
+ "case \"H\":\r\n"
+ "trace(\"H last\");\r\n"
+ "}\r\n"
+ "trace(\"after switch\");\r\n",
false);
}
@Test
public void testSwitchComma() {
decompileMethod("classic", "testSwitchComma", "var b:int = 5;\r\n"

View File

@@ -118,6 +118,7 @@ package
TestStringConcat;
TestStrings;
TestSwitch;
TestSwitchBig;
TestSwitchContinue;
TestSwitchComma;
TestSwitchDefault;

View File

@@ -0,0 +1,34 @@
package tests
{
public class TestSwitchBig
{
public function run():*
{
var k:* = 10;
switch (k)
{
case "A":
trace("A");
break;
case "B":
case "C":
trace("BC");
break;
case "D":
default:
case "E":
trace("D-default-E");
break;
case "F":
trace("F no break");
case "G":
trace("G");
break;
case "H":
trace("H last");
}
trace("after switch");
}
}
}