diff --git a/CHANGELOG.md b/CHANGELOG.md index d45a9baa9..ad0eba3f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ All notable changes to this project will be documented in this file. - #1206 Switch with multiple default clauses - ASC2 §§push of function calls before returning from a method - Support for ASC2 and swftools try..catch..finally block +- Dot parent operator not detected in some cases ### Changed - AS3 test methods separated to classes 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 6ecefbdc2..ddc63f3d7 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 @@ -43,8 +43,10 @@ import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushByteIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.CoerceAIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.ConvertIIns; import com.jpexs.decompiler.flash.abc.avm2.model.AVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.ConstructAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.FilteredCheckAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.FullMultinameAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.GetLexAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.HasNextAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.InAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.IntegerValueAVM2Item; @@ -59,6 +61,7 @@ import com.jpexs.decompiler.flash.abc.avm2.model.SetSlotAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.SetTypeAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.ThrowAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.WithAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.WithEndAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.clauses.ExceptionAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.clauses.FilterAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.clauses.ForEachInAVM2Item; @@ -79,9 +82,11 @@ import com.jpexs.decompiler.graph.ScopeStack; import com.jpexs.decompiler.graph.TranslateStack; import com.jpexs.decompiler.graph.model.AnyItem; 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.DuplicateItem; 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.LoopItem; @@ -626,7 +631,6 @@ public class AVM2Graph extends Graph { return ret; } - private List checkTry(List currentRet, List output, List foundGotos, Map> partCodes, Map partCodePos, AVM2LocalData localData, GraphPart part, List stopPart, List loops, Set allParts, TranslateStack stack, int staticOperation, String path) throws InterruptedException { if (localData.parsedExceptions == null) { localData.parsedExceptions = new ArrayList<>(); @@ -1210,62 +1214,133 @@ public class AVM2Graph extends Graph { if (hn.obj.getNotCoerced().getThroughRegister().getNotCoerced() instanceof FilteredCheckAVM2Item) { if (w.commands.size() >= 3) { int pos = 0; + Set localRegsToKill = new HashSet<>(); + while (w.commands.get(pos) instanceof SetLocalAVM2Item) { + if (w.commands.get(pos).value instanceof NextValueAVM2Item) { + NextValueAVM2Item nextValueItem = (NextValueAVM2Item) w.commands.get(pos).value; + if (nextValueItem.index instanceof LocalRegAVM2Item) { + localRegsToKill.add(((LocalRegAVM2Item) nextValueItem.index).regIndex); + } + if (nextValueItem.obj instanceof LocalRegAVM2Item) { + localRegsToKill.add(((LocalRegAVM2Item) nextValueItem.obj).regIndex); + } + } pos++; } GraphTargetItem ft = w.commands.get(pos); if (ft instanceof WithAVM2Item) { pos++; - while (w.commands.get(pos) instanceof SetTypeAVM2Item) { + List withCommands = new ArrayList<>(); + while (!(w.commands.get(pos) instanceof WithEndAVM2Item)) { + withCommands.add(w.commands.get(pos)); pos++; } - ft = w.commands.get(pos); - if (ft instanceof IfItem) { - IfItem ift = (IfItem) ft; - if (ift.onTrue.size() > 0) { - ft = ift.onTrue.get(0); - if (ft instanceof SetPropertyAVM2Item) { - SetPropertyAVM2Item spt = (SetPropertyAVM2Item) ft; - if (spt.object instanceof LocalRegAVM2Item) { - int regIndex = ((LocalRegAVM2Item) spt.object).regIndex; - HashMap localRegs = aLocalData.localRegs; - localRegs.put(regIndex, new FilterAVM2Item(null, null, hn.obj.getThroughRegister(), ift.expression)); - Set localRegsToKill = new HashSet<>(); - localRegsToKill.add(regIndex); + GraphTargetItem expr = null; + int getLocalObjectIp = -1; + int regIndex = -1; + HashMap localRegs = aLocalData.localRegs; - if (hn.obj instanceof LocalRegAVM2Item) { - localRegsToKill.add(((LocalRegAVM2Item) hn.obj).regIndex); - } - if (spt.value instanceof LocalRegAVM2Item) { - localRegsToKill.add(((LocalRegAVM2Item) spt.value).regIndex); - } - if (spt.propertyName instanceof FullMultinameAVM2Item) { - if (((FullMultinameAVM2Item) spt.propertyName).name instanceof LocalRegAVM2Item) { - localRegsToKill.add(((LocalRegAVM2Item) ((FullMultinameAVM2Item) spt.propertyName).name).regIndex); + if (!withCommands.isEmpty()) { + if (withCommands.get(withCommands.size() - 1) instanceof IfItem) { + IfItem ift = (IfItem) withCommands.get(withCommands.size() - 1); + if (ift.onTrue.size() > 0) { + ft = ift.onTrue.get(0); + if (ft instanceof SetPropertyAVM2Item) { + SetPropertyAVM2Item spt = (SetPropertyAVM2Item) ft; + if (spt.object instanceof LocalRegAVM2Item) { + getLocalObjectIp = avm2code.adr2pos(spt.object.getSrc().getAddress()); + regIndex = ((LocalRegAVM2Item) spt.object).regIndex; + expr = ift.expression.getNotCoerced(); + if (withCommands.size() > 1) { + withCommands.remove(withCommands.size() - 1); + withCommands.add(expr); + expr = new CommaExpressionItem(null, localData.lineStartInstruction, withCommands); } } - - //TODO: maybe check its single usage - for (int i = output.size() - 2 /*last is loop*/; i >= 0; i--) { - if (localRegsToKill.isEmpty()) { - break; - } - if (output.get(i) instanceof SetLocalAVM2Item) { - SetLocalAVM2Item setLocal = (SetLocalAVM2Item) output.get(i); - if (localRegsToKill.contains(setLocal.regIndex)) { - output.remove(i); - } - } else { - break; - } - } - - return null; } } + } else { + //There is no if - this means there was something that + // can be evaluated on compiletime and compiler removed the whole if + // ASC2 does this + withCommands.add(new FalseItem(null, localData.lineStartInstruction)); + expr = new CommaExpressionItem(null, localData.lineStartInstruction, withCommands); + } + } else { + expr = new FalseItem(null, localData.lineStartInstruction); + } + FilteredCheckAVM2Item filteredCheck = (FilteredCheckAVM2Item) hn.obj.getThroughRegister().getNotCoerced(); + FilterAVM2Item filter = new FilterAVM2Item(null, null, filteredCheck.object, expr); + + if (regIndex == -1) { + for (int i = output.size() - 2 /*last is loop*/; i >= 0; i--) { + if (output.get(i) instanceof SetLocalAVM2Item) { + SetLocalAVM2Item setLocal = (SetLocalAVM2Item) output.get(i); + if (setLocal.value instanceof ConstructAVM2Item) { + if ((((ConstructAVM2Item) setLocal.value).object instanceof GetLexAVM2Item)) { + GetLexAVM2Item lex = (GetLexAVM2Item) ((ConstructAVM2Item) setLocal.value).object; + if ("XMLList".equals(lex.getRawPropertyName())) { + regIndex = setLocal.regIndex; + } + } + } + } else { + break; + } } } + + localRegsToKill.add(regIndex); + + if (hn.obj instanceof LocalRegAVM2Item) { + localRegsToKill.add(((LocalRegAVM2Item) hn.obj).regIndex); + } + + int setLocalIp = -1; + for (int i = output.size() - 2 /*last is loop*/; i >= 0; i--) { + if (output.get(i) instanceof SetLocalAVM2Item) { + SetLocalAVM2Item setLocal = (SetLocalAVM2Item) output.get(i); + if (setLocal.regIndex == regIndex) { + setLocalIp = avm2code.adr2pos(setLocal.getSrc().getAddress()); + break; + } + } else { + break; + } + } + Set usages = new HashSet<>(); + if (setLocalIp > -1) { + usages = new HashSet<>(aLocalData.getSetLocalUsages(setLocalIp)); + usages.remove(getLocalObjectIp); + + } + + for (int i = output.size() - 2 /*last is loop*/; i >= 0; i--) { + if (localRegsToKill.isEmpty()) { + break; + } + if (output.get(i) instanceof SetLocalAVM2Item) { + SetLocalAVM2Item setLocal = (SetLocalAVM2Item) output.get(i); + if (localRegsToKill.contains(setLocal.regIndex)) { + output.remove(i); + } + if (setLocal.regIndex == regIndex) { + setLocal.value = filter; + } + } else { + break; + } + } + + if (usages.isEmpty()) { + output.add(filter); + } else { + localRegs.put(regIndex, filter); + } + return null; + } } } else if (!w.commands.isEmpty()) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/model/FilteredCheckAVM2Item.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/model/FilteredCheckAVM2Item.java index 6db286719..83504c230 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/model/FilteredCheckAVM2Item.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/model/FilteredCheckAVM2Item.java @@ -12,7 +12,8 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. */ + * License along with this library. + */ package com.jpexs.decompiler.flash.abc.avm2.model; import com.jpexs.decompiler.flash.helpers.GraphTextWriter; @@ -28,7 +29,7 @@ import com.jpexs.decompiler.graph.model.LocalData; */ public class FilteredCheckAVM2Item extends AVM2Item { - GraphTargetItem object; + public GraphTargetItem object; public FilteredCheckAVM2Item(GraphSourceItem instruction, GraphSourceItem lineStartIns, GraphTargetItem object) { super(instruction, lineStartIns, NOPRECEDENCE); @@ -42,7 +43,10 @@ public class FilteredCheckAVM2Item extends AVM2Item { @Override public GraphTextWriter appendTo(GraphTextWriter writer, LocalData localData) throws InterruptedException { - return object.toString(writer, localData); + writer.append("§§checkfilter("); + object.toString(writer, localData); + writer.append(")"); + return writer; } @Override diff --git a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3ClassicDecompileTest.java b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3ClassicDecompileTest.java index 168a28cee..f2325d06a 100644 --- a/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3ClassicDecompileTest.java +++ b/libsrc/ffdec_lib/test/com/jpexs/decompiler/flash/ActionScript3ClassicDecompileTest.java @@ -232,6 +232,30 @@ public class ActionScript3ClassicDecompileTest extends ActionScript3DecompileTes false); } + @Test + public void testDoWhile3() { + decompileMethod("classic", "testDoWhile3", "do\r\n" + + "{\r\n" + + "this.nextChar();\r\n" + + "}\r\n" + + "while(this.ch != \"\\n\" && this.ch != \"\");\r\n", + false); + } + + @Test + public void testDotParent() { + decompileMethod("classic", "testDotParent", "var d:* = undefined;\r\n" + + "var k:* = undefined;\r\n" + + "var g:* = undefined;\r\n" + + "d = new TestClass1();\r\n" + + "k = null;\r\n" + + "k.(d.attrib++, 0);\r\n" + + "trace(\"between\");\r\n" + + "g = k.(d.attrib++, 0);\r\n" + + "trace(\"end\");\r\n", + false); + } + @Test public void testExpressions() { decompileMethod("classic", "testExpressions", "var arr:Array = null;\r\n" diff --git a/libsrc/ffdec_lib/testdata/flashdevelop/bin/Main.air.swf b/libsrc/ffdec_lib/testdata/flashdevelop/bin/Main.air.swf index 979a2b2ce..cac503186 100644 Binary files a/libsrc/ffdec_lib/testdata/flashdevelop/bin/Main.air.swf and b/libsrc/ffdec_lib/testdata/flashdevelop/bin/Main.air.swf differ diff --git a/libsrc/ffdec_lib/testdata/flashdevelop/bin/Main.flex.swf b/libsrc/ffdec_lib/testdata/flashdevelop/bin/Main.flex.swf index 2be5ec89b..417265a72 100644 Binary files a/libsrc/ffdec_lib/testdata/flashdevelop/bin/Main.flex.swf and b/libsrc/ffdec_lib/testdata/flashdevelop/bin/Main.flex.swf differ diff --git a/libsrc/ffdec_lib/testdata/flashdevelop/src/Main.as b/libsrc/ffdec_lib/testdata/flashdevelop/src/Main.as index ed1412865..e036c2b08 100644 --- a/libsrc/ffdec_lib/testdata/flashdevelop/src/Main.as +++ b/libsrc/ffdec_lib/testdata/flashdevelop/src/Main.as @@ -22,6 +22,7 @@ package TestDecl2; TestDeclarations; TestDefaultNotLastGrouped; + TestDotParent; TestDoWhile; TestDoWhile2; TestDoWhile3; diff --git a/libsrc/ffdec_lib/testdata/flashdevelop/src/tests/TestDotParent.as b/libsrc/ffdec_lib/testdata/flashdevelop/src/tests/TestDotParent.as new file mode 100644 index 000000000..8fa10ec48 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/flashdevelop/src/tests/TestDotParent.as @@ -0,0 +1,22 @@ +package tests +{ + + public class TestDotParent + { + public function run():* + { + var d:* = new TestClass1(); + var k:* = null; + + k.(d.attrib++, 0); + trace("between"); + var g:* = k.(d.attrib++, 0); + trace("end"); + } + } +} + +class TestClass1 +{ + public var attrib:int = 5; +} \ No newline at end of file