Fixed #1940 AS3 decompilation - wrong assignment

This commit is contained in:
Jindra Petřík
2023-01-29 12:22:09 +01:00
parent d9e8291178
commit 6f4cb4a8d6
17 changed files with 344 additions and 23 deletions

View File

@@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file.
- [#1953] Save as EXE - add file extension when missing
- [#1954] Incorrect calculation of empty button bounds causing OutOfMemory
- [#1944] Scroll position not retained on Ctrl+click in the tag tree
- [#1940] AS3 decompilation - wrong assignment
## [18.3.3] - 2023-01-22
### Added
@@ -2927,6 +2928,7 @@ All notable changes to this project will be documented in this file.
[#1953]: https://www.free-decompiler.com/flash/issues/1953
[#1954]: https://www.free-decompiler.com/flash/issues/1954
[#1944]: https://www.free-decompiler.com/flash/issues/1944
[#1940]: https://www.free-decompiler.com/flash/issues/1940
[#1913]: https://www.free-decompiler.com/flash/issues/1913
[#1894]: https://www.free-decompiler.com/flash/issues/1894
[#1801]: https://www.free-decompiler.com/flash/issues/1801

View File

@@ -19,7 +19,10 @@ package com.jpexs.decompiler.flash.abc.avm2;
import com.jpexs.decompiler.flash.FinalProcessLocalData;
import com.jpexs.decompiler.graph.Loop;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
*
@@ -28,10 +31,22 @@ import java.util.List;
public class AVM2FinalProcessLocalData extends FinalProcessLocalData {
public HashMap<Integer, String> localRegNames;
public Map<Integer, Set<Integer>> setLocalPosToGetLocalPos = new HashMap<>();
public AVM2FinalProcessLocalData(List<Loop> loops, HashMap<Integer, String> localRegNames) {
public AVM2FinalProcessLocalData(List<Loop> loops, HashMap<Integer, String> localRegNames, Map<Integer, Set<Integer>> setLocalPosToGetLocalPos) {
super(loops);
this.localRegNames = localRegNames;
this.setLocalPosToGetLocalPos = setLocalPosToGetLocalPos;
}
public Set<Integer> getSetLocalUsages(int setLocalPos) {
if (setLocalPosToGetLocalPos == null) {
return new HashSet<>();
}
if (!setLocalPosToGetLocalPos.containsKey(setLocalPos)) {
return new HashSet<>();
}
return setLocalPosToGetLocalPos.get(setLocalPos);
}
}

View File

@@ -138,7 +138,7 @@ public class AVM2Graph extends Graph {
private final ABC abc;
private final MethodBody body;
private final AbcIndexing abcIndex;
private final Logger logger = Logger.getLogger(AVM2Graph.class.getName());
@@ -485,7 +485,6 @@ public class AVM2Graph extends Graph {
}
}
}*/
localData.ignoredSwitches.put(e, switchPart);
} else {
//there is probably return in all branches and no other way outside finally
@@ -650,7 +649,7 @@ public class AVM2Graph extends Graph {
Set<GraphPart> allParts = new HashSet<>();
for (GraphPart head : g.heads) {
populateParts(head, allParts);
}
}
return g.translate(localData, staticOperation, path);
}
@@ -1792,7 +1791,7 @@ public class AVM2Graph extends Graph {
setLocalIp = avm2code.adr2pos(setLocal.getSrc().getAddress());
break;
}
} else if (output.get(i) instanceof PushItem){
} else if (output.get(i) instanceof PushItem) {
//allowed
} else {
break;
@@ -1826,7 +1825,7 @@ public class AVM2Graph extends Graph {
for (int i = output.size() - 2 /*last is loop*/; i >= 0; i--) {
if (output.get(i) instanceof PushItem) {
PushItem pu = (PushItem)output.remove(i);
PushItem pu = (PushItem) output.remove(i);
stack.push(pu.value);
} else {
break;
@@ -1941,6 +1940,7 @@ public class AVM2Graph extends Graph {
Reference<Integer> kindRef = new Reference<>(0);
first.visitRecursively(new AbstractGraphTargetVisitor() {
private boolean handled = false;
@Override
public void visit(GraphTargetItem item) {
if (handled) {
@@ -1948,28 +1948,28 @@ public class AVM2Graph extends Graph {
}
if ((item instanceof NextNameAVM2Item) || (item instanceof NextValueAVM2Item)) {
handled = true;
if (item instanceof NextValueAVM2Item){
if (item instanceof NextValueAVM2Item) {
NextValueAVM2Item nv = (NextValueAVM2Item) item;
nv.localReg = hn.index;
nv.localReg = hn.index;
kindRef.setVal(1);
}
if (item instanceof NextNameAVM2Item){
if (item instanceof NextNameAVM2Item) {
NextNameAVM2Item nn = (NextNameAVM2Item) item;
nn.localReg = hn.index;
kindRef.setVal(2);
}
}
}
}
}
});
if (kindRef.getVal() == 1) {
return new ForEachInAVM2Item(w.getSrc(), w.getLineStartItem(), w.loop, new InAVM2Item(hn.getInstruction(), hn.getLineStartIns(), hn.index, hn.obj), w.commands);
return new ForEachInAVM2Item(w.getSrc(), w.getLineStartItem(), w.loop, new InAVM2Item(hn.getInstruction(), hn.getLineStartIns(), hn.index, hn.obj), w.commands);
}
if (kindRef.getVal() == 2) {
return new ForInAVM2Item(w.getSrc(), w.getLineStartItem(), w.loop, new InAVM2Item(hn.getInstruction(), hn.getLineStartIns(),hn.index , hn.obj), w.commands);
}
return new ForInAVM2Item(w.getSrc(), w.getLineStartItem(), w.loop, new InAVM2Item(hn.getInstruction(), hn.getLineStartIns(), hn.index, hn.obj), w.commands);
}
}
}
}
}
@@ -2069,10 +2069,10 @@ public class AVM2Graph extends Graph {
}
}
}
}
}
}
}
}
}
}
List<GraphTargetItem> ret = list;
@@ -2235,13 +2235,143 @@ public class AVM2Graph extends Graph {
}
}
/*
convert this situation:
loc1.x = (loc1 = create()).x + 1;
where loc1.x references newly created loc1.
to:
loc1 = create();
loc1.x = loc1.x + 1;
It's TestSwapAssignment assembled test case.
*/
for (int i = 0; i < list.size(); i++) {
GraphTargetItem item = list.get(i);
Map<Integer, List<SetLocalAVM2Item>> setRegisters = new HashMap<>();
item.visitRecursivelyNoBlock(new AbstractGraphTargetVisitor() {
@Override
public void visit(GraphTargetItem item) {
if (item instanceof SetLocalAVM2Item) {
SetLocalAVM2Item setLoc = (SetLocalAVM2Item) item;
if (setLoc.causedByDup) {
if (!setRegisters.containsKey(setLoc.regIndex)) {
setRegisters.put(setLoc.regIndex, new ArrayList<>());
}
setRegisters.get(setLoc.regIndex).add(setLoc);
}
}
}
});
Set<Integer> nextReferencedRegisters = new HashSet<>();
for (int regIndex : setRegisters.keySet()) {
Set<GraphTargetItem> visitedItems = new HashSet<>();
item.visitNoBlock(new AbstractGraphTargetVisitor() {
boolean foundGetLoc = false;
boolean foundSetLoc = false;
@Override
public void visit(GraphTargetItem item) {
if (foundGetLoc || foundSetLoc) {
return;
}
if (item != null && !visitedItems.contains(item)) {
visitedItems.add(item);
if (item instanceof SetLocalAVM2Item) {
SetLocalAVM2Item setLoc = (SetLocalAVM2Item) item;
if (setLoc.regIndex == regIndex) {
foundSetLoc = true;
return;
}
}
if (item instanceof LocalRegAVM2Item) {
LocalRegAVM2Item getLoc = (LocalRegAVM2Item) item;
if (getLoc.regIndex == regIndex) {
//This depends on visit order assuming visitorder is execution order.
if (!foundSetLoc) {
AVM2FinalProcessLocalData aLocalData = (AVM2FinalProcessLocalData) localData;
boolean isSetLocUsage = true;
if (getLoc.getSrc() != null) {
int getIp = code.adr2pos(getLoc.getSrc().getAddress());
isSetLocUsage = false;
for (SetLocalAVM2Item setLoc : setRegisters.get(regIndex)) {
if (setLoc.getSrc() != null) {
Set<Integer> usages = aLocalData.getSetLocalUsages(code.adr2pos(setLoc.getSrc().getAddress()));
if (usages.contains(getIp)) {
isSetLocUsage = true;
break;
}
}
}
}
if (isSetLocUsage) {
nextReferencedRegisters.add(regIndex);
foundGetLoc = true;
return;
}
}
}
}
item.visitNoBlock(this);
}
}
}
);
}
Set<GraphTargetItem> visitedItems = new HashSet<>();
Reference<Integer> newI = new Reference<>(i);
item.visitNoBlock(new AbstractGraphTargetVisitor() {
boolean found = false;
@Override
public void visit(GraphTargetItem item) {
if (found) {
return;
}
if (item != null && !visitedItems.contains(item)) {
visitedItems.add(item);
if (item instanceof SetLocalAVM2Item) {
SetLocalAVM2Item setLoc = (SetLocalAVM2Item) item;
if (nextReferencedRegisters.contains(setLoc.regIndex)) {
SetLocalAVM2Item setlocClone = (SetLocalAVM2Item) setLoc.clone();
//TODO: handle the replacing better. It should convert SetLocal to LocalReg
setLoc.hideValue = true;
list.add(newI.getVal(), setlocClone);
newI.setVal(newI.getVal() + 1);
return;
}
}
item.visitNoBlock(this);
}
}
});
i = newI.getVal();
}
//Handle for loops at the end:
super.finalProcess(list, level, localData, path);
}
@Override
protected FinalProcessLocalData getFinalData(BaseLocalData localData, List<Loop> loops, List<ThrowState> throwStates) {
FinalProcessLocalData finalProcess = new AVM2FinalProcessLocalData(loops, ((AVM2LocalData) localData).localRegNames);
FinalProcessLocalData finalProcess = new AVM2FinalProcessLocalData(loops, ((AVM2LocalData) localData).localRegNames, ((AVM2LocalData) localData).setLocalPosToGetLocalPos);
finalProcess.registerUsage = ((AVM2LocalData) localData).setLocalPosToGetLocalPos;
return finalProcess;
}
@@ -2440,5 +2570,5 @@ public class AVM2Graph extends Graph {
@Override
protected SecondPassData prepareSecondPass(List<GraphTargetItem> list) {
return new SecondPassData();
}
}
}

View File

@@ -20,8 +20,8 @@ import com.jpexs.decompiler.flash.abc.AVM2LocalData;
import com.jpexs.decompiler.flash.abc.avm2.model.AVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.CoerceAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.ConvertAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.IntegerValueAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.LocalRegAVM2Item;
import com.jpexs.decompiler.flash.abc.avm2.model.SetLocalAVM2Item;
import com.jpexs.decompiler.graph.GraphTargetItem;
import com.jpexs.decompiler.graph.TranslateStack;
import com.jpexs.decompiler.graph.TypeItem;
@@ -75,8 +75,8 @@ public interface SetTypeIns {
stack.push(new LocalRegAVM2Item(null, localData.lineStartInstruction, regId, value, localData.localRegTypes.containsKey(regId) ? localData.localRegTypes.get(regId) : value.returnType()));
}
return;
} else {
} else {
if ((value instanceof CoerceAVM2Item) || (value instanceof ConvertAVM2Item)) {
value.value = insideDup;
} else {
@@ -84,6 +84,10 @@ public interface SetTypeIns {
}
result.value = value;
if ((result instanceof SetLocalAVM2Item) && regId > -1) {
((SetLocalAVM2Item)result).causedByDup = true;
}
if (regId > -1 && AVM2Item.mustStayIntact2(insideDup.getNotCoerced())) { //hack
output.add(result);

View File

@@ -46,6 +46,10 @@ public class SetLocalAVM2Item extends AVM2Item implements SetTypeAVM2Item, Assig
public String compoundOperator;
public GraphTargetItem type;
public boolean hideValue = false;
public boolean causedByDup = false;
@Override
public DeclarationAVM2Item getDeclaration() {
@@ -74,6 +78,9 @@ public class SetLocalAVM2Item extends AVM2Item implements SetTypeAVM2Item, Assig
writer.append("= ");
return compoundValue.toString(writer, localData);
}
if (hideValue) {
return writer;
}
writer.append(" = ");
/*if (declaration != null && !declaration.type.equals(TypeItem.UNBOUNDED) && (value instanceof ConvertAVM2Item)) {
return value.value.toString(writer, localData);
@@ -200,4 +207,14 @@ public class SetLocalAVM2Item extends AVM2Item implements SetTypeAVM2Item, Assig
public String getCompoundOperator() {
return compoundOperator;
}
@Override
public int getPrecedence() {
if (hideValue) {
return PRECEDENCE_PRIMARY;
}
return precedence;
}
}

View File

@@ -170,6 +170,14 @@ public class ActionScript3AssembledDecompileTest extends ActionScript3DecompileT
false);
}
@Test
public void testSwapAssignment() {
decompileMethod("assembled", "testSwapAssignment", "var _loc6_:Bitmap = MyFactory.createBitmap();\r\n"
+ "_loc6_.x = _loc6_.x + 5;\r\n"
+ "_loc6_.y = -10;\r\n",
false);
}
@Test
public void testSwitch() {
decompileMethod("assembled", "testSwitch", "switch(int(somevar))\r\n"

View File

@@ -495,6 +495,14 @@ public class ActionScript3ClassicAirDecompileTest extends ActionScript3Decompile
false);
}
@Test
public void testExecutionOrder() {
decompileMethod("classic_air", "testExecutionOrder", "var m:MyClass = null;\r\n"
+ "m.x = (m = create() as MyClass).x + 5;\r\n"
+ "trace(m.x);\r\n",
false);
}
@Test
public void testExpressions() {
decompileMethod("classic_air", "testExpressions", "var arr:Array = null;\r\n"

View File

@@ -497,6 +497,14 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes
false);
}
@Test
public void testExecutionOrder() {
decompileMethod("classic", "testExecutionOrder", "var m:MyClass = null;\r\n"
+ "m.x = (m = create() as MyClass).x + 5;\r\n"
+ "trace(m.x);\r\n",
false);
}
@Test
public void testExpressions() {
decompileMethod("classic", "testExpressions", "var arr:Array = null;\r\n"

View File

@@ -30,5 +30,6 @@ program
#include "tests/TestTryWhile.script.asasm"
#include "tests/TestPushWhile.script.asasm"
#include "tests/TestActivationProps.script.asasm"
#include "tests/TestSwapAssignment.script.asasm"
; place to add next
end ; program

View File

@@ -0,0 +1,75 @@
class
refid "tests:TestSwapAssignment"
instance QName(PackageNamespace("tests"), "TestSwapAssignment")
extends QName(PackageNamespace(""), "Object")
flag SEALED
flag PROTECTEDNS
protectedns ProtectedNamespace("tests:TestSwapAssignment")
iinit
refid "tests:TestSwapAssignment/instance/init"
body
maxstack 1
localcount 1
initscopedepth 4
maxscopedepth 5
code
getlocal0
pushscope
getlocal0
constructsuper 0
returnvoid
end ; code
end ; body
end ; method
trait method QName(PackageNamespace(""), "run")
method
refid "tests:TestSwapAssignment/instance/run"
returns QName(PackageNamespace(""), "void")
body
maxstack 2
localcount 4
initscopedepth 4
maxscopedepth 5
code
getlocal0
pushscope
getlex QName(PackageNamespace("mypkg"),"MyFactory")
callproperty QName(PackageNamespace(""),"createBitmap"), 0
coerce QName(PackageNamespace("flash.display"),"Bitmap")
dup
setlocal 6
getproperty QName(PackageNamespace(""),"x")
pushbyte 5
add
getlocal 6
swap
setproperty QName(PackageNamespace(""),"x")
getlocal 6
pushbyte -10
setproperty QName(PackageNamespace(""),"y")
returnvoid
end ; code
end ; body
end ; method
end ; trait
end ; instance
cinit
refid "tests:TestSwapAssignment/class/init"
body
maxstack 1
localcount 1
initscopedepth 3
maxscopedepth 4
code
getlocal0
pushscope
returnvoid
end ; code
end ; body
end ; method
end ; class

View File

@@ -0,0 +1,29 @@
script
sinit
refid "tests:TestSwapAssignment/init"
body
maxstack 2
localcount 1
initscopedepth 1
maxscopedepth 3
code
getlocal0
pushscope
findpropstrict Multiname("TestSwapAssignment", [PackageNamespace("tests")])
getlex QName(PackageNamespace(""), "Object")
pushscope
getlex Multiname("Object", [PrivateNamespace(null, "tests:TestSwapAssignment"), PackageNamespace(""), PackageNamespace("tests"), PackageInternalNs("tests"), Namespace("http://adobe.com/AS3/2006/builtin")])
newclass "tests:TestSwapAssignment"
popscope
initproperty QName(PackageNamespace("tests"), "TestSwapAssignment")
returnvoid
end ; code
end ; body
end ; method
trait class QName(PackageNamespace("tests"), "TestSwapAssignment")
#include "TestSwapAssignment.class.asasm"
end ; trait
end ; script

View File

@@ -38,6 +38,7 @@ package
TestDoWhile2;
TestDoWhile3;
TestDoWhile4;
TestExecutionOrder;
TestExpressions;
TestFinallyZeroJump;
TestFor;

View File

@@ -0,0 +1,23 @@
package tests
{
public class TestExecutionOrder
{
public function run():*
{
var m:MyClass;
m.x = (m = create() as MyClass).x + 5;
trace(m.x);
}
private static function create(): Object {
return new MyClass();
}
}
}
class MyClass {
public var x:int = 1;
public var y:int = 1;
}