/* * 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; import com.jpexs.decompiler.flash.EndOfStreamException; import com.jpexs.decompiler.flash.abc.ABC; import com.jpexs.decompiler.flash.abc.ABCInputStream; import com.jpexs.decompiler.flash.abc.CopyOutputStream; import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.AVM2DeobfuscatorGetSet; import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.AVM2DeobfuscatorJumps; import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.AVM2DeobfuscatorRegistersOld; import com.jpexs.decompiler.flash.abc.avm2.deobfuscation.AVM2DeobfuscatorSimpleOld; import com.jpexs.decompiler.flash.abc.avm2.exceptions.AVM2ExecutionException; import com.jpexs.decompiler.flash.abc.avm2.exceptions.AVM2VerifyErrorException; import com.jpexs.decompiler.flash.abc.avm2.graph.AVM2Graph; import com.jpexs.decompiler.flash.abc.avm2.graph.AVM2GraphSource; import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instruction; import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2InstructionFlag; import com.jpexs.decompiler.flash.abc.avm2.instructions.AVM2Instructions; import com.jpexs.decompiler.flash.abc.avm2.instructions.IfTypeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.InstructionDefinition; import com.jpexs.decompiler.flash.abc.avm2.instructions.UnknownInstruction; import com.jpexs.decompiler.flash.abc.avm2.instructions.alchemy.Lf32Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.alchemy.Lf64Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.alchemy.Li16Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.alchemy.Li32Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.alchemy.Li8Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.alchemy.Sf32Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.alchemy.Sf64Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.alchemy.Si16Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.alchemy.Si32Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.alchemy.Si8Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.alchemy.Sxi16Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.alchemy.Sxi1Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.alchemy.Sxi8Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.AddIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.AddIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.DecrementIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.DecrementIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.DivideIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.IncrementIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.IncrementIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.ModuloIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.MultiplyIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.MultiplyIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.NegateIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.NegateIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.NotIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.SubtractIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.arithmetic.SubtractIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.bitwise.BitAndIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.bitwise.BitNotIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.bitwise.BitOrIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.bitwise.BitXorIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.bitwise.LShiftIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.bitwise.RShiftIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.bitwise.URShiftIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.comparison.EqualsIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.comparison.GreaterEqualsIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.comparison.GreaterThanIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.comparison.LessEqualsIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.comparison.LessThanIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.comparison.StrictEqualsIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.ConstructIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.ConstructPropIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.ConstructSuperIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.NewActivationIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.NewArrayIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.NewCatchIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.NewClassIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.NewFunctionIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.construction.NewObjectIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.debug.DebugFileIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.debug.DebugIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.debug.DebugLineIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.executing.CallIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.executing.CallMethodIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.executing.CallPropLexIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.executing.CallPropVoidIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.executing.CallPropertyIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.executing.CallStaticIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.executing.CallSuperIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.executing.CallSuperVoidIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfEqIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfFalseIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfGeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfGtIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfLeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfLtIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfNGeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfNGtIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfNLeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfNLtIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.jumps.IfNeIns; 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.IfTrueIns; 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; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.DecLocalIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.GetLocal0Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.GetLocal1Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.GetLocal2Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.GetLocal3Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.GetLocalIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.GetLocalTypeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.IncLocalIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.IncLocalIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.KillIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.SetLocal0Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.SetLocal1Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.SetLocal2Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.SetLocal3Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.SetLocalIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.localregs.SetLocalTypeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.DeletePropertyIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.FindDefIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.FindPropertyIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.FindPropertyStrictIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.GetDescendantsIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.GetGlobalScopeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.GetGlobalSlotIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.GetLexIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.GetPropertyIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.GetScopeObjectIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.GetSlotIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.GetSuperIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.HasNext2Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.HasNextIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.InIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.InitPropertyIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.LabelIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.NextNameIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.NextValueIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.NopIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.ReturnValueIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.ReturnVoidIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.SetGlobalSlotIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.SetPropertyIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.SetSlotIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.SetSuperIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other.ThrowIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.AbsJumpIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.AddDIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.AddPIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.AllocIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.BkptIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.BkptLineIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.CallInterfaceIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.CallSuperIdIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.CodeGenOpIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.CoerceBIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.CoerceDIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.CoerceIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.CoerceOIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.CoerceUIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.ConcatIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.ConvertF4Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.ConvertFIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.ConvertMIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.ConvertMPIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.DecLocalPIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.DecodeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.DecrementPIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.DelDescendantsIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.DeletePropertyLateIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.DividePIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.DoubleToAtomIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.FindPropGlobalIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.GetOuterScopeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.IncLocalPIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.IncrementPIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.InvalidIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.Lf32x4Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.MarkIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.ModuloPIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.MultiplyPIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.NegatePIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.PrologueIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.PushConstantIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.PushDNanIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.PushDecimalIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.PushFloat4Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.PushFloatIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.SendEnterIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.SetPropertyLateIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.Sf32x4Ins; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.SubtractPIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.SweepIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.TimestampIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.UnPlusIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.VerifyOpIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.VerifyPassIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.other2.WbIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.DupIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PopIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PopScopeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushByteIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushDoubleIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushFalseIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushIntIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushNamespaceIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushNanIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushNullIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushScopeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushShortIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushStringIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushTrueIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushUIntIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushUndefinedIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.PushWithIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.stack.SwapIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.ApplyTypeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.AsTypeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.AsTypeLateIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.CoerceAIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.CoerceIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.CoerceOrConvertTypeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.CoerceSIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.ConvertBIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.ConvertDIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.ConvertIIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.ConvertOIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.ConvertSIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.ConvertUIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.InstanceOfIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.IsTypeIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.IsTypeLateIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.types.TypeOfIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.xml.CheckFilterIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.xml.DXNSIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.xml.DXNSLateIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.xml.EscXAttrIns; import com.jpexs.decompiler.flash.abc.avm2.instructions.xml.EscXElemIns; 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.FullMultinameAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.InitPropertyAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.LocalRegAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.NewFunctionAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.NullAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.ReturnVoidAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.SetLocalAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.SetPropertyAVM2Item; 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.UndefinedAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.WithAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.clauses.DeclarationAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.clauses.ForEachInAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.model.clauses.ForInAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.parser.script.PropertyAVM2Item; import com.jpexs.decompiler.flash.abc.avm2.parser.script.Reference; import com.jpexs.decompiler.flash.abc.types.ABCException; import com.jpexs.decompiler.flash.abc.types.AssignedValue; import com.jpexs.decompiler.flash.abc.types.ConvertData; import com.jpexs.decompiler.flash.abc.types.MethodBody; import com.jpexs.decompiler.flash.abc.types.MethodInfo; import com.jpexs.decompiler.flash.abc.types.Multiname; import com.jpexs.decompiler.flash.abc.types.ValueKind; import com.jpexs.decompiler.flash.abc.types.traits.Trait; import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction; import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter; import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst; import com.jpexs.decompiler.flash.abc.types.traits.Traits; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.dumpview.DumpInfo; import com.jpexs.decompiler.flash.ecma.Undefined; import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; import com.jpexs.decompiler.flash.helpers.GraphTextWriter; import com.jpexs.decompiler.flash.helpers.HighlightedTextWriter; import com.jpexs.decompiler.flash.helpers.SWFDecompilerPlugin; import com.jpexs.decompiler.flash.helpers.hilight.HighlightSpecialType; import com.jpexs.decompiler.graph.Block; import com.jpexs.decompiler.graph.DottedChain; import com.jpexs.decompiler.graph.GraphPart; import com.jpexs.decompiler.graph.GraphSourceItem; import com.jpexs.decompiler.graph.GraphTargetItem; import com.jpexs.decompiler.graph.ScopeStack; import com.jpexs.decompiler.graph.SimpleValue; import com.jpexs.decompiler.graph.TranslateStack; import com.jpexs.decompiler.graph.TypeItem; import com.jpexs.decompiler.graph.model.BinaryOpItem; import com.jpexs.decompiler.graph.model.ExitItem; import com.jpexs.decompiler.graph.model.IfItem; import com.jpexs.decompiler.graph.model.ScriptEndItem; import com.jpexs.helpers.Helper; import com.jpexs.helpers.ReflectionTools; import com.jpexs.helpers.stat.Statistics; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author JPEXS */ public class AVM2Code implements Cloneable { private static final Logger logger = Logger.getLogger(AVM2Code.class.getName()); private static final boolean DEBUG_MODE = false; public static int toSourceLimit = -1; public List<AVM2Instruction> code; public static boolean DEBUG_REWRITE = false; public static final int OPT_U30 = 0x100; public static final int OPT_U8 = 0x200; public static final int OPT_S24 = 0x300; public static final int OPT_CASE_OFFSETS = 0x400; public static final int OPT_S8 = 0x500; public static final int OPT_S16 = 0x600; public static final int DAT_MULTINAME_INDEX = OPT_U30 + 0x01; public static final int DAT_ARG_COUNT = OPT_U30 + 0x02; public static final int DAT_METHOD_INDEX = OPT_U30 + 0x03; public static final int DAT_STRING_INDEX = OPT_U30 + 0x04; public static final int DAT_DEBUG_TYPE = OPT_U8 + 0x05; public static final int DAT_REGISTER_INDEX = OPT_U8 + 0x06; public static final int DAT_LINENUM = OPT_U30 + 0x07; public static final int DAT_LOCAL_REG_INDEX = OPT_U30 + 0x08; public static final int DAT_SLOT_INDEX = OPT_U30 + 0x09; public static final int DAT_SCOPE_INDEX = OPT_U30 + 0x0A; public static final int DAT_OFFSET = OPT_S24 + 0x0B; public static final int DAT_EXCEPTION_INDEX = OPT_U30 + 0x0C; public static final int DAT_CLASS_INDEX = OPT_U30 + 0x0D; public static final int DAT_INT_INDEX = OPT_U30 + 0x0E; public static final int DAT_UINT_INDEX = OPT_U30 + 0x0F; public static final int DAT_DOUBLE_INDEX = OPT_U30 + 0x10; public static final int DAT_DECIMAL_INDEX = OPT_U30 + 0x11; public static final int DAT_CASE_BASEOFFSET = OPT_S24 + 0x12; public static final int DAT_NUMBER_CONTEXT = OPT_U30 + 0x13; public static final int DAT_DISPATCH_ID = OPT_U30 + 0x14; public static final int DAT_FLOAT_INDEX = OPT_U30 + 0x15; public static final int DAT_FLOAT4_INDEX = OPT_U30 + 0x16; public static final int DAT_NAMESPACE_INDEX = OPT_U30 + 0x17; public static String operandTypeSizeToString(int ot) { int sizeType = ot & 0xff00; switch (sizeType) { case OPT_U30: return "U30"; case OPT_S16: return "S16"; case OPT_U8: return "U8"; case OPT_S8: return "S8"; case OPT_S24: return "S24"; case OPT_CASE_OFFSETS: return "S24(=n), S24[n]"; } return ""; } private static Map<Integer, String> operandDataTypeIdentifiers = ReflectionTools.getConstNamesMap(AVM2Code.class, Integer.class, "^DAT_(.*)$"); public static String operandTypeToString(int ot, boolean withTypeSize) { String typeSize = operandTypeSizeToString(ot); if (ot == OPT_CASE_OFFSETS) { return "number" + (withTypeSize ? "(U30)" : "") + ", offset" + (withTypeSize ? "(S24)" : "") + ", offset" + (withTypeSize ? "(S24)" : "") + ", ..."; } if (operandDataTypeIdentifiers.containsKey(ot)) { String dataType = operandDataTypeIdentifiers.get(ot); return dataType + (withTypeSize ? "(" + typeSize + ")" : ""); } else { return typeSize; } } public static final InstructionDefinition[] instructionSet = new InstructionDefinition[256]; public static final InstructionDefinition[] allInstructionSet = new InstructionDefinition[]{ /*0x00*/null, /*0x01*/ new BkptIns(), /*0x02*/ new NopIns(), /*0x03*/ new ThrowIns(), /*0x04*/ new GetSuperIns(), /*0x05*/ new SetSuperIns(), /*0x06*/ new DXNSIns(), /*0x07*/ new DXNSLateIns(), /*0x08*/ new KillIns(), /*0x09*/ new LabelIns(), /*0x0A*/ new Lf32x4Ins(), /*0x0B*/ new Sf32x4Ins(), /*0x0C*/ new IfNLtIns(), /*0x0D*/ new IfNLeIns(), /*0x0E*/ new IfNGtIns(), /*0x0F*/ new IfNGeIns(), /*0x10*/ new JumpIns(), /*0x11*/ new IfTrueIns(), /*0x12*/ new IfFalseIns(), /*0x13*/ new IfEqIns(), /*0x14*/ new IfNeIns(), /*0x15*/ new IfLtIns(), /*0x16*/ new IfLeIns(), /*0x17*/ new IfGtIns(), /*0x18*/ new IfGeIns(), /*0x19*/ new IfStrictEqIns(), /*0x1A*/ new IfStrictNeIns(), /*0x1B*/ new LookupSwitchIns(), /*0x1C*/ new PushWithIns(), /*0x1D*/ new PopScopeIns(), /*0x1E*/ new NextNameIns(), /*0x1F*/ new HasNextIns(), /*0x20*/ new PushNullIns(), /*0x21*/ new PushUndefinedIns(), /*0x22*/ new PushFloatIns(), //major 47+ /*0x22*/ new PushConstantIns(), //before major 47 /*0x23*/ new NextValueIns(), /*0x24*/ new PushByteIns(), /*0x25*/ new PushShortIns(), /*0x26*/ new PushTrueIns(), /*0x27*/ new PushFalseIns(), /*0x28*/ new PushNanIns(), /*0x29*/ new PopIns(), /*0x2A*/ new DupIns(), /*0x2B*/ new SwapIns(), /*0x2C*/ new PushStringIns(), /*0x2D*/ new PushIntIns(), /*0x2E*/ new PushUIntIns(), /*0x2F*/ new PushDoubleIns(), /*0x30*/ new PushScopeIns(), /*0x31*/ new PushNamespaceIns(), /*0x32*/ new HasNext2Ins(), /*0x33*/ new PushDecimalIns(), //pushdecimal(minor 17), lix8 (internal-only) according to Tamarin /*0x34*/ new PushDNanIns(), //pushdnan according to Flex SDK, lix16 (internal-only) according to Tamarin /*0x35*/ new Li8Ins(), /*0x36*/ new Li16Ins(), /*0x37*/ new Li32Ins(), /*0x38*/ new Lf32Ins(), /*0x39*/ new Lf64Ins(), /*0x3A*/ new Si8Ins(), /*0x3B*/ new Si16Ins(), /*0x3C*/ new Si32Ins(), /*0x3D*/ new Sf32Ins(), /*0x3E*/ new Sf64Ins(), /*0x3F*/ null, /*0x40*/ new NewFunctionIns(), /*0x41*/ new CallIns(), /*0x42*/ new ConstructIns(), /*0x43*/ new CallMethodIns(), /*0x44*/ new CallStaticIns(), /*0x45*/ new CallSuperIns(), /*0x46*/ new CallPropertyIns(), /*0x47*/ new ReturnVoidIns(), /*0x48*/ new ReturnValueIns(), /*0x49*/ new ConstructSuperIns(), /*0x4A*/ new ConstructPropIns(), /*0x4B*/ new CallSuperIdIns(), /*0x4C*/ new CallPropLexIns(), /*0x4D*/ new CallInterfaceIns(), /*0x4E*/ new CallSuperVoidIns(), /*0x4F*/ new CallPropVoidIns(), /*0x50*/ new Sxi1Ins(), /*0x51*/ new Sxi8Ins(), /*0x52*/ new Sxi16Ins(), /*0x53*/ new ApplyTypeIns(), /*0x54*/ new PushFloat4Ins(), //major 47+ /*0x55*/ new NewObjectIns(), /*0x56*/ new NewArrayIns(), /*0x57*/ new NewActivationIns(), /*0x58*/ new NewClassIns(), /*0x59*/ new GetDescendantsIns(), /*0x5A*/ new NewCatchIns(), /*0x5B*/ new DelDescendantsIns(), //deldescendants according to Flex, findpropglobalstrict(internal-only) according to Tamarin /*0x5C*/ new FindPropGlobalIns(), //Tamarin (internal-only) /*0x5D*/ new FindPropertyStrictIns(), /*0x5E*/ new FindPropertyIns(), /*0x5F*/ new FindDefIns(), /*0x60*/ new GetLexIns(), /*0x61*/ new SetPropertyIns(), /*0x62*/ new GetLocalIns(), /*0x63*/ new SetLocalIns(), /*0x64*/ new GetGlobalScopeIns(), /*0x65*/ new GetScopeObjectIns(), /*0x66*/ new GetPropertyIns(), /*0x67*/ new GetOuterScopeIns(), // new GetPropertyLateIns() /*0x68*/ new InitPropertyIns(), /*0x69*/ new SetPropertyLateIns(), /*0x6A*/ new DeletePropertyIns(), /*0x6B*/ new DeletePropertyLateIns(), /*0x6C*/ new GetSlotIns(), /*0x6D*/ new SetSlotIns(), /*0x6E*/ new GetGlobalSlotIns(), /*0x6F*/ new SetGlobalSlotIns(), /*0x70*/ new ConvertSIns(), /*0x71*/ new EscXElemIns(), /*0x72*/ new EscXAttrIns(), /*0x73*/ new ConvertIIns(), /*0x74*/ new ConvertUIns(), /*0x75*/ new ConvertDIns(), /*0x76*/ new ConvertBIns(), /*0x77*/ new ConvertOIns(), /*0x78*/ new CheckFilterIns(), /*0x79*/ new ConvertMIns(), //minor 17 (Flex) /*0x79*/ new ConvertFIns(), //major 47+, SWF 15+ /*0x7A*/ new ConvertMPIns(), //minor 17 (Flex) /*0x7A*/ new UnPlusIns(), //major 47+, SWF 15+ /*0x7B*/ new ConvertF4Ins(), //major 47+, SWF 15+ /*0x7C*/ null, /*0x7D*/ null, /*0x7E*/ null, /*0x7F*/ null, /*0x80*/ new CoerceIns(), /*0x81*/ new CoerceBIns(), /*0x82*/ new CoerceAIns(), /*0x83*/ new CoerceIIns(), /*0x84*/ new CoerceDIns(), /*0x85*/ new CoerceSIns(), /*0x86*/ new AsTypeIns(), /*0x87*/ new AsTypeLateIns(), /*0x88*/ new CoerceUIns(), /*0x89*/ new CoerceOIns(), /*0x8A*/ null, /*0x8B*/ null, /*0x8C*/ null, /*0x8D*/ null, /*0x8E*/ null, /*0x8F*/ new NegatePIns(), /*0x90*/ new NegateIns(), /*0x91*/ new IncrementIns(), /*0x92*/ new IncLocalIns(), /*0x93*/ new DecrementIns(), /*0x94*/ new DecLocalIns(), /*0x95*/ new TypeOfIns(), /*0x96*/ new NotIns(), /*0x97*/ new BitNotIns(), /*0x98*/ null, /*0x99*/ null, /*0x9A*/ new ConcatIns(), /*0x9B*/ new AddDIns(), /*0x9C*/ new IncrementPIns(), /*0x9D*/ new IncLocalPIns(), /*0x9E*/ new DecrementPIns(), /*0x9F*/ new DecLocalPIns(), /*0xA0*/ new AddIns(), /*0xA1*/ new SubtractIns(), /*0xA2*/ new MultiplyIns(), /*0xA3*/ new DivideIns(), /*0xA4*/ new ModuloIns(), /*0xA5*/ new LShiftIns(), /*0xA6*/ new RShiftIns(), /*0xA7*/ new URShiftIns(), /*0xA8*/ new BitAndIns(), /*0xA9*/ new BitOrIns(), /*0xAA*/ new BitXorIns(), /*0xAB*/ new EqualsIns(), /*0xAC*/ new StrictEqualsIns(), /*0xAD*/ new LessThanIns(), /*0xAE*/ new LessEqualsIns(), /*0xAF*/ new GreaterThanIns(), /*0xB0*/ new GreaterEqualsIns(), /*0xB1*/ new InstanceOfIns(), /*0xB2*/ new IsTypeIns(), /*0xB3*/ new IsTypeLateIns(), /*0xB4*/ new InIns(), /*0xB5*/ new AddPIns(), /*0xB6*/ new SubtractPIns(), /*0xB7*/ new MultiplyPIns(), /*0xB8*/ new DividePIns(), /*0xB9*/ new ModuloPIns(), /*0xBA*/ null, /*0xBB*/ null, /*0xBC*/ null, /*0xBD*/ null, /*0xBE*/ null, /*0xBF*/ null, /*0xC0*/ new IncrementIIns(), /*0xC1*/ new DecrementIIns(), /*0xC2*/ new IncLocalIIns(), /*0xC3*/ new DecLocalIIns(), /*0xC4*/ new NegateIIns(), /*0xC5*/ new AddIIns(), /*0xC6*/ new SubtractIIns(), /*0xC7*/ new MultiplyIIns(), /*0xC8*/ null, /*0xC9*/ null, /*0xCA*/ null, /*0xCB*/ null, /*0xCC*/ null, /*0xCD*/ null, /*0xCE*/ null, /*0xCF*/ null, /*0xD0*/ new GetLocal0Ins(), /*0xD1*/ new GetLocal1Ins(), /*0xD2*/ new GetLocal2Ins(), /*0xD3*/ new GetLocal3Ins(), /*0xD4*/ new SetLocal0Ins(), /*0xD5*/ new SetLocal1Ins(), /*0xD6*/ new SetLocal2Ins(), /*0xD7*/ new SetLocal3Ins(), /*0xD8*/ null, /*0xD9*/ null, /*0xDA*/ null, /*0xDB*/ null, /*0xDC*/ null, /*0xDD*/ null, /*0xDE*/ null, /*0xDF*/ null, /*0xE0*/ null, /*0xE1*/ null, /*0xE2*/ null, /*0xE3*/ null, /*0xE4*/ null, /*0xE5*/ null, /*0xE6*/ null, /*0xE7*/ null, /*0xE8*/ null, /*0xE9*/ null, /*0xEA*/ null, /*0xEB*/ null, /*0xEC*/ null, /*0xED*/ new InvalidIns(), /*0xEE*/ new AbsJumpIns(), /*0xEF*/ new DebugIns(), /*0xF0*/ new DebugLineIns(), /*0xF1*/ new DebugFileIns(), /*0xF2*/ new BkptLineIns(), /*0xF3*/ new TimestampIns(), /*0xF4*/ null, /*0xF5*/ new VerifyPassIns(), /*0xF6*/ new AllocIns(), /*0xF7*/ new MarkIns(), /*0xF8*/ new WbIns(), /*0xF9*/ new PrologueIns(), /*0xFA*/ new SendEnterIns(), /*0xFB*/ new DoubleToAtomIns(), /*0xFC*/ new SweepIns(), /*0xFD*/ new CodeGenOpIns(), /*0xFE*/ new VerifyOpIns(), /*0xFF*/ new DecodeIns(),}; // endoflist static { for (int i = 0; i < allInstructionSet.length; i++) { if (allInstructionSet[i] != null) { int opCode = allInstructionSet[i].instructionCode; if (instructionSet[opCode] == null) { instructionSet[opCode] = allInstructionSet[i]; } else if (instructionSet[opCode].hasFlag(AVM2InstructionFlag.NO_FLASH_PLAYER) && !allInstructionSet[i].hasFlag(AVM2InstructionFlag.NO_FLASH_PLAYER)) { instructionSet[opCode] = allInstructionSet[i]; } //Prefer without decimal: else if (instructionSet[opCode].hasFlag(AVM2InstructionFlag.ES4_NUMERICS_MINOR) && !allInstructionSet[i].hasFlag(AVM2InstructionFlag.ES4_NUMERICS_MINOR)) { instructionSet[opCode] = allInstructionSet[i]; } //Prefer without float: else if (instructionSet[opCode].hasFlag(AVM2InstructionFlag.FLOAT_MAJOR) && !allInstructionSet[i].hasFlag(AVM2InstructionFlag.FLOAT_MAJOR)) { instructionSet[opCode] = allInstructionSet[i]; } } } for (int i = 0; i < instructionSet.length; i++) { if (instructionSet[i] == null) { instructionSet[i] = new UnknownInstruction(i); } } } public static final String IDENTOPEN = "/*IDENTOPEN*/"; public static final String IDENTCLOSE = "/*IDENTCLOSE*/"; public AVM2Code() { code = new ArrayList<>(); } public AVM2Code(int capacity) { code = new ArrayList<>(capacity); } public AVM2Code(ArrayList<AVM2Instruction> instructions) { code = instructions; } public Object execute(HashMap<Integer, Object> arguments, AVM2ConstantPool constants) throws AVM2ExecutionException { return execute(arguments, constants, null); } public Object execute(HashMap<Integer, Object> arguments, AVM2ConstantPool constants, AVM2RuntimeInfo runtimeInfo) throws AVM2ExecutionException { int pos = 0; LocalDataArea lda = new LocalDataArea(); lda.methodName = "methodName"; // todo: needed for VerifyError exception message lda.localRegisters = arguments; lda.runtimeInfo = runtimeInfo; for (AVM2Instruction ins : code) { ins.definition.verify(lda, constants, ins); } while (pos < code.size()) { AVM2Instruction ins = code.get(pos); if (!ins.definition.execute(lda, constants, ins)) { return null; } if (lda.jump != null) { try { pos = adr2pos(lda.jump); } catch (ConvertException ex) { throw new AVM2VerifyErrorException(AVM2VerifyErrorException.BRANCH_TARGET_INVALID_INSTRUCTION, lda.isDebug()); } lda.jump = null; } else { pos++; } if (lda.returnValue != null) { return lda.returnValue; } } return Undefined.INSTANCE; } public void calculateDebugFileLine(ABC abc) { calculateDebugFileLine(null, 0, 0, abc, new HashSet<>()); } private boolean calculateDebugFileLine(String debugFile, int debugLine, int pos, ABC abc, Set<Integer> seen) { while (pos < code.size()) { AVM2Instruction ins = code.get(pos); if (seen.contains(pos)) { return true; } seen.add(pos); if (ins.definition instanceof DebugFileIns) { debugFile = abc.constants.getString(ins.operands[0]); } if (ins.definition instanceof DebugLineIns) { debugLine = ins.operands[0]; } ins.setFileLine(debugFile, debugLine); if (ins.definition instanceof NewFunctionIns) { //Only analyze NewFunction objects that are not immediately discarded by Pop. //This avoids bogus functions used in obfuscation or special compilers that can lead to infinite recursion. if ((pos + 1 < code.size()) && !(code.get(pos + 1).definition instanceof PopIns)) { MethodBody innerBody = abc.findBody(ins.operands[0]); if (innerBody != null) { //Ignore functions without body innerBody.getCode().calculateDebugFileLine(debugFile, debugLine, 0, abc, new HashSet<>()); } } } if (ins.definition instanceof ReturnValueIns) { return true; } if (ins.definition instanceof ReturnVoidIns) { return true; } if (ins.definition instanceof JumpIns) { try { pos = adr2pos(ins.getTargetAddress()); continue; } catch (ConvertException ex) { return false; } } else if (ins.definition instanceof IfTypeIns) { try { int newpos = adr2pos(ins.getTargetAddress()); calculateDebugFileLine(debugFile, debugLine, newpos, abc, seen); } catch (ConvertException ex) { return false; } } if (ins.definition instanceof LookupSwitchIns) { for (int i = 0; i < ins.operands.length; i++) { if (i == 1) { continue; } try { int newpos = adr2pos(pos2adr(pos) + ins.operands[i]); if (!calculateDebugFileLine(debugFile, debugLine, newpos, abc, seen)) { return false; } } catch (ConvertException ex) { return false; } } } pos++; } return true; } /** * Removes nonexistent indices to constants from instruction operands. * * @param constants */ public void removeWrongIndices(AVM2ConstantPool constants) { for (AVM2Instruction ins : code) { for (int i = 0; i < ins.definition.operands.length; i++) { if (ins.definition.operands[i] == DAT_MULTINAME_INDEX && ins.operands[i] >= constants.getMultinameCount()) { ins.operands[i] = 0; } if (ins.definition.operands[i] == DAT_DOUBLE_INDEX && ins.operands[i] >= constants.getDoubleCount()) { ins.operands[i] = 0; } if (ins.definition.operands[i] == DAT_INT_INDEX && ins.operands[i] >= constants.getIntCount()) { ins.operands[i] = 0; } if (ins.definition.operands[i] == DAT_UINT_INDEX && ins.operands[i] >= constants.getUIntCount()) { ins.operands[i] = 0; } if (ins.definition.operands[i] == DAT_STRING_INDEX && ins.operands[i] >= constants.getStringCount()) { ins.operands[i] = 0; } } } } public AVM2Code(ABCInputStream ais, MethodBody body) throws IOException { Map<Long, AVM2Instruction> codeMap = new HashMap<>(); DumpInfo diParent = ais.dumpInfo; List<Long> addresses = new ArrayList<>(); //Do not add new jumps when processing these addresses (unreachable code,etc.) List<Long> unAdresses = new ArrayList<>(); //Handle lookupswitches at the end - they can be invalid. Handle other instruction first so we can decide lookupswitch to be invalid based on other instructions inside it //Flashplayer does not check casecount in lookupswitch instruction so the instruction can "be" long and over other instructions List<Long> switchAddresses = new ArrayList<>(); int availableBytes = ais.available(); for (int i = 0; i < availableBytes; i++) { codeMap.put((long) i, new AVM2Instruction(i, AVM2Instructions.Nop, null)); } long startPos = ais.getPosition(); addresses.add(startPos); if (body != null) { for (ABCException e : body.exceptions) { addresses.add((long) e.start); addresses.add((long) e.end); addresses.add((long) e.target); } } loopaddr: while (!addresses.isEmpty() || !switchAddresses.isEmpty() || !unAdresses.isEmpty()) { long address; boolean isSwitch = false; boolean handleJumps = true; if (!addresses.isEmpty()) { address = addresses.remove(0); } else if (!switchAddresses.isEmpty()) { address = switchAddresses.remove(0); isSwitch = true; } else { address = unAdresses.remove(0); handleJumps = false; } if (address < startPos) // no jump outside block { continue; } try { ais.seek(address); while (ais.available() > 0) { long startOffset = ais.getPosition(); if (codeMap.containsKey(startOffset) && !(codeMap.get(startOffset).definition instanceof NopIns)) { continue loopaddr; } DumpInfo di = ais.newDumpLevel("instruction", "instruction"); InstructionDefinition instr = null; try { int instructionCode = ais.read("instructionCode"); instr = instructionSet[instructionCode]; if (instructionCode == AVM2Instructions.LookupSwitch) { if (!isSwitch) { switchAddresses.add(startOffset); continue loopaddr; } else { isSwitch = false; } } if (di != null) { di.name = instr.instructionName; } if (instr != null) { int[] actualOperands = null; if (instructionCode == AVM2Instructions.LookupSwitch) { // switch int firstOperand = ais.readS24("default_offset"); int case_count = ais.readU30("case_count"); long afterCasePos = ais.getPosition() + 3 * (case_count + 1); boolean invalidSwitch = false; //If there are already some instructions in the lookupswitch bytes, the lookupswitch is invalid (obfuscation) for (long a = startOffset; a < afterCasePos; a++) { if (codeMap.containsKey(a) && (!(codeMap.get(a).definition instanceof NopIns))) { invalidSwitch = true; break; } } long totalBytes = ais.getPosition() + ais.available(); //If the lookupswitch case_count are larger than available bytes, the lookupswitch is invalid (obfuscation) if (afterCasePos > totalBytes) { invalidSwitch = true; } if (invalidSwitch) { continue loopaddr; } else { actualOperands = new int[case_count + 3]; actualOperands[0] = firstOperand; actualOperands[1] = case_count; for (int c = 0; c < case_count + 1; c++) { actualOperands[2 + c] = ais.readS24("actualOperand"); } } } else if (instr.operands.length > 0) { actualOperands = new int[instr.operands.length]; for (int op = 0; op < instr.operands.length; op++) { switch (instr.operands[op] & 0xff00) { case OPT_U30: actualOperands[op] = ais.readU30("operand"); break; case OPT_S16: actualOperands[op] = (short) ais.readU30("operand"); break; case OPT_U8: actualOperands[op] = ais.read("operand"); break; case OPT_S8: actualOperands[op] = (byte) ais.read("operand"); break; case OPT_S24: actualOperands[op] = ais.readS24("operand"); break; } } } AVM2Instruction ai = new AVM2Instruction(startOffset, instr, actualOperands); long endOffset = ais.getPosition(); boolean hasRoom = true; for (long p = startOffset; p < endOffset; p++) { if (codeMap.containsKey(p) && !(codeMap.get(p).definition instanceof NopIns)) { hasRoom = false; } } //There is no room for this instruction (it is invalid?) if (!hasRoom) { continue loopaddr; } for (long p = startOffset; p < endOffset; p++) { codeMap.put(p, ai); } if ((instr instanceof IfTypeIns)) { if (handleJumps) { long target = ais.getPosition() + actualOperands[0]; addresses.add(target); } else { actualOperands[0] = 0; } } if (instr instanceof JumpIns) { if (handleJumps) { long target = ais.getPosition() + actualOperands[0]; addresses.add(target); unAdresses.add(ais.getPosition()); continue loopaddr; } else { actualOperands[0] = 0; } } if (instr.isExitInstruction()) { //do not process jumps if there is return/throw instruction if (handleJumps) { unAdresses.add(ais.getPosition()); continue loopaddr; } } if ((instr instanceof LookupSwitchIns) && actualOperands != null) { if (handleJumps) { addresses.add(startOffset + actualOperands[0]); for (int c = 2; c < actualOperands.length; c++) { addresses.add(startOffset + actualOperands[c]); } unAdresses.add(ais.getPosition()); continue loopaddr; } else { int swlen = (int) (endOffset - startOffset); actualOperands[0] = swlen; for (int c = 2; c < actualOperands.length; c++) { actualOperands[c] = swlen; } } } } else { break; // Unknown instructions are ignored (Some of the obfuscators add unknown instructions) //throw new UnknownInstructionCode(instructionCode); } } finally { if (instr == null) { ais.endDumpLevel(); } else { ais.endDumpLevel(instr.instructionCode); } } } } catch (EndOfStreamException ex) { // lookupswitch obfuscation, ignore ais.endDumpLevelUntil(diParent); } } if (diParent != null) { diParent.sortChildren(); } code = new ArrayList<>(codeMap.size()); AVM2Instruction prev = null; for (int i = 0; i < availableBytes; i++) { AVM2Instruction ins = codeMap.get((long) i); if (prev != ins) { code.add(ins); } prev = ins; } } public void compact() { if (code instanceof ArrayList) { ((ArrayList) code).trimToSize(); } } public void setInstructionOperand(int ip, int operandIndex, int value, MethodBody body) { int oldVal = code.get(ip).operands[ip]; code.get(ip).operands[ip] = value; } public byte[] getBytes() { return getBytes(null); } public byte[] getBytes(byte[] origBytes) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); OutputStream cos; if ((origBytes != null) && (DEBUG_REWRITE)) { ByteArrayInputStream origis = new ByteArrayInputStream(origBytes); cos = new CopyOutputStream(bos, origis); } else { cos = bos; } try { for (AVM2Instruction instruction : code) { cos.write(instruction.getBytes()); } } catch (IOException ex) { } return bos.toByteArray(); } public void markOffsets() { long address = 0; for (int i = 0; i < code.size(); i++) { code.get(i).setAddress(address); address += code.get(i).getBytesLength(); } } @Override public String toString() { StringBuilder s = new StringBuilder(); for (AVM2Instruction instruction : code) { s.append(instruction.toString()); s.append(Helper.newLine); } return s.toString(); } public String toASMSource() { return toASMSource(new AVM2ConstantPool()); } public String toASMSource(AVM2ConstantPool constants) { HighlightedTextWriter writer = new HighlightedTextWriter(Configuration.getCodeFormatting(), false); toASMSource(constants, null, null, new ArrayList<>(), ScriptExportMode.PCODE, writer); return writer.toString(); } public GraphTextWriter toASMSource(AVM2ConstantPool constants, MethodInfo info, MethodBody body, ScriptExportMode exportMode, GraphTextWriter writer) { return toASMSource(constants, info, body, new ArrayList<>(), exportMode, writer); } public GraphTextWriter toASMSource(AVM2ConstantPool constants, MethodInfo info, MethodBody body, List<Integer> outputMap, ScriptExportMode exportMode, GraphTextWriter writer) { if (info != null) { writer.appendNoHilight("method").newLine(); writer.appendNoHilight("name "); writer.hilightSpecial(info.name_index == 0 ? "null" : "\"" + Helper.escapeActionScriptString(info.getName(constants)) + "\"", HighlightSpecialType.METHOD_NAME); writer.newLine(); if (info.flagExplicit()) { writer.appendNoHilight("flag "); writer.hilightSpecial("EXPLICIT", HighlightSpecialType.FLAG_EXPLICIT); writer.newLine(); } if (info.flagHas_optional()) { writer.appendNoHilight("flag "); writer.hilightSpecial("HAS_OPTIONAL", HighlightSpecialType.FLAG_HAS_OPTIONAL); writer.newLine(); writer.appendNoHilight("flag HAS_OPTIONAL").newLine(); } if (info.flagHas_paramnames()) { writer.appendNoHilight("flag "); writer.hilightSpecial("HAS_PARAM_NAMES", HighlightSpecialType.FLAG_HAS_PARAM_NAMES); writer.newLine(); } if (info.flagIgnore_rest()) { writer.appendNoHilight("flag "); writer.hilightSpecial("EXPLICIT", HighlightSpecialType.FLAG_IGNORE_REST); writer.newLine(); } if (info.flagNeed_activation()) { writer.appendNoHilight("flag "); writer.hilightSpecial("NEED_ACTIVATION", HighlightSpecialType.FLAG_NEED_ACTIVATION); writer.newLine(); } if (info.flagNeed_arguments()) { writer.appendNoHilight("flag "); writer.hilightSpecial("NEED_ARGUMENTS", HighlightSpecialType.FLAG_NEED_ARGUMENTS); writer.newLine(); } if (info.flagNeed_rest()) { writer.appendNoHilight("flag "); writer.hilightSpecial("NEED_REST", HighlightSpecialType.FLAG_NEED_REST); writer.newLine(); } if (info.flagSetsdxns()) { writer.appendNoHilight("flag "); writer.hilightSpecial("SET_DXNS", HighlightSpecialType.FLAG_SET_DXNS); writer.newLine(); } for (int p = 0; p < info.param_types.length; p++) { writer.appendNoHilight("param "); writer.hilightSpecial(constants.multinameToString(info.param_types[p]), HighlightSpecialType.PARAM, p); writer.newLine(); } if (info.flagHas_paramnames()) { for (int n : info.paramNames) { writer.appendNoHilight("paramname "); if (n == 0) { writer.appendNoHilight("null"); } else { writer.appendNoHilight("\""); writer.appendNoHilight(constants.getString(n)); writer.appendNoHilight("\""); } writer.newLine(); } } if (info.flagHas_optional()) { for (int i = 0; i < info.optional.length; i++) { ValueKind vk = info.optional[i]; writer.appendNoHilight("optional "); writer.hilightSpecial(vk.toString(constants), HighlightSpecialType.OPTIONAL, i); writer.newLine(); } } writer.appendNoHilight("returns "); writer.hilightSpecial(constants.multinameToString(info.ret_type), HighlightSpecialType.RETURNS); writer.newLine(); } writer.newLine(); Set<Long> importantOffsets = getImportantOffsets(body, true); if (body != null) { writer.appendNoHilight("body").newLine(); writer.appendNoHilight("maxstack "); writer.appendNoHilight(body.max_stack); writer.newLine(); writer.appendNoHilight("localcount "); writer.appendNoHilight(body.max_regs); writer.newLine(); writer.appendNoHilight("initscopedepth "); writer.appendNoHilight(body.init_scope_depth); writer.newLine(); writer.appendNoHilight("maxscopedepth "); writer.appendNoHilight(body.max_scope_depth); writer.newLine(); for (int e = 0; e < body.exceptions.length; e++) { ABCException exception = body.exceptions[e]; writer.appendNoHilight("try"); writer.appendNoHilight(" from "); writer.appendNoHilight("ofs"); writer.appendNoHilight(Helper.formatAddress(exception.start)); writer.appendNoHilight(" to "); writer.appendNoHilight("ofs"); writer.appendNoHilight(Helper.formatAddress(exception.end)); writer.appendNoHilight(" target "); writer.appendNoHilight("ofs"); writer.appendNoHilight(Helper.formatAddress(exception.target)); writer.appendNoHilight(" type "); writer.hilightSpecial(exception.type_index == 0 ? "null" : constants.getMultiname(exception.type_index).toString(constants, new ArrayList<>()), HighlightSpecialType.TRY_TYPE, e); writer.appendNoHilight(" name "); writer.hilightSpecial(exception.name_index == 0 ? "null" : constants.getMultiname(exception.name_index).toString(constants, new ArrayList<>()), HighlightSpecialType.TRY_NAME, e); writer.newLine(); } } writer.newLine(); writer.appendNoHilight("code").newLine(); int ip = 0; int largeLimit = 20000; boolean markOffsets = code.size() <= largeLimit; if (exportMode == ScriptExportMode.HEX) { Helper.byteArrayToHexWithHeader(writer, getBytes()); } else if (exportMode == ScriptExportMode.PCODE || exportMode == ScriptExportMode.PCODE_HEX) { for (AVM2Instruction ins : code) { long addr = ins.getAddress(); if (exportMode == ScriptExportMode.PCODE_HEX) { writer.appendNoHilight("; "); writer.appendNoHilight(Helper.bytesToHexString(ins.getBytes())); writer.newLine(); } if (Configuration.showAllAddresses.get() || importantOffsets.contains(addr)) { writer.appendNoHilight("ofs" + Helper.formatAddress(addr) + ":"); } /*for (int e = 0; e < body.exceptions.length; e++) { if (body.exceptions[e].start == ofs) { ret.append("exceptionstart " + e + ":"); } if (body.exceptions[e].end == ofs) { ret.append("exceptionend " + e + ":"); } if (body.exceptions[e].target == ofs) { ret.append("exceptiontarget " + e + ":"); } }*/ if (!ins.isIgnored()) { if (markOffsets) { writer.append("", addr, ins.getFileOffset()); } writer.appendNoHilight(ins.toStringNoAddress(constants, new ArrayList<>())); writer.newLine(); outputMap.add(ip); } ip++; } } else if (exportMode == ScriptExportMode.CONSTANTS) { writer.appendNoHilight("Constant export mode is not supported.").newLine(); } writer.appendNoHilight("end ; code").newLine(); if (body != null) { writer.appendNoHilight("end ; body").newLine(); } if (info != null) { writer.appendNoHilight("end ; method").newLine(); } return writer; } public Set<Long> getImportantOffsets(MethodBody body, boolean tryEnds) { Set<Long> ret = new HashSet<>(); if (body != null) { for (ABCException exception : body.exceptions) { ret.add((long) exception.start); if (tryEnds) { ret.add((long) exception.end); } ret.add((long) exception.target); } } for (AVM2Instruction ins : code) { ret.addAll(ins.getOffsets()); } return ret; } public AVM2Instruction adr2ins(long address) throws ConvertException { int pos = adr2pos(address, false); if (pos == code.size()) { // end return null; } return code.get(pos); } public int adr2pos(long address) throws ConvertException { return adr2pos(address, false); } public int adr2pos(long address, boolean nearest) throws ConvertException { int ret = adr2posNoEx(address); if (ret < 0) { if (nearest && address < getEndOffset()) { return -ret - 1; } throw new ConvertException("Invalid jump to ofs" + Helper.formatAddress(address), -1); } return ret; } private int adr2posNoEx(long address) { int min = 0; int max = code.size() - 1; while (max >= min) { int mid = (min + max) / 2; long midValue = code.get(mid).getAddress(); if (midValue == address) { return mid; } else if (midValue < address) { min = mid + 1; } else { max = mid - 1; } } if (address == getEndOffset()) { return code.size(); } return -min - 1; } public long pos2adr(int pos) { if (pos == code.size()) { return getEndOffset(); } return (int) code.get(pos).getAddress(); } public long getEndOffset() { if (code.isEmpty()) { return 0; } AVM2Instruction ins = code.get(code.size() - 1); return (int) (ins.getAddress() + ins.getBytesLength()); } /** * Test for killed register. CalcKilledStats must be called before * * @param regName * @param start * @param end * @return */ public boolean isKilled(int regName, int start, int end) { if (!killedRegs.containsKey(regName)) { return false; } for (int ip : killedRegs.get(regName)) { if (ip >= start && ip <= end) { return true; } } return false; } private int toSourceCount = 0; public Map<Integer, String> getLocalRegNamesFromDebug(ABC abc) { Map<Integer, String> localRegNames = new HashMap<>(); for (AVM2Instruction ins : code) { if (ins.definition instanceof DebugIns) { if (ins.operands[0] == 1) { String v = abc.constants.getString(ins.operands[1]); // Same name already exists, it may be wrong names inserted by obfuscator if (localRegNames.values().contains(v)) { return new HashMap<>(); } localRegNames.put(ins.operands[2] + 1, v); } } } // TODO: Make this immune to using existing multinames (?) return localRegNames; } private Map<Integer, Set<Integer>> killedRegs = new HashMap<>(); public void calcKilledStats(MethodBody body) throws InterruptedException { killedRegs.clear(); HashMap<Integer, List<Integer>> vis = visitCode(body); for (int k = 0; k < code.size(); k++) { if (vis.get(k).isEmpty()) { continue; } if (code.get(k).definition instanceof KillIns) { int regid = code.get(k).operands[0]; if (!killedRegs.containsKey(regid)) { killedRegs.put(regid, new HashSet<>()); } killedRegs.get(regid).add(k); } } } public List<GraphTargetItem> clearTemporaryRegisters(List<GraphTargetItem> input) { List<GraphTargetItem> output = new ArrayList<>(input); for (int i = 0; i < output.size(); i++) { if (output.get(i) instanceof SetLocalAVM2Item) { if (isKilled(((SetLocalAVM2Item) output.get(i)).regIndex, 0, code.size() - 1)) { SetLocalAVM2Item lsi = (SetLocalAVM2Item) output.get(i); if (i + 1 < output.size()) { if (output.get(i + 1) instanceof ExitItem) { GraphTargetItem rv = output.get(i + 1); if (rv.value instanceof LocalRegAVM2Item) { LocalRegAVM2Item lr = (LocalRegAVM2Item) rv.value; if (lr.regIndex == lsi.regIndex) { rv.value = lsi.value; } } } } output.remove(i); i--; } } else if (output.get(i) instanceof WithAVM2Item) { clearTemporaryRegisters(((WithAVM2Item) output.get(i)).items); } } return output; } public int fixIPAfterDebugLine(int ip) { if (code.isEmpty()) { return ip; } if (ip >= code.size()) { return code.size() - 1; } while (code.get(ip).definition instanceof DebugLineIns) { ip++; } return ip; } public long fixAddrAfterDebugLine(long addr) throws ConvertException { return pos2adr(fixIPAfterDebugLine(adr2pos(addr, true))); } public ConvertOutput toSourceOutput(boolean thisHasDefaultToPrimitive, Reference<GraphSourceItem> lineStartItem, String path, GraphPart part, boolean processJumps, boolean isStatic, int scriptIndex, int classIndex, HashMap<Integer, GraphTargetItem> localRegs, TranslateStack stack, ScopeStack scopeStack, ABC abc, MethodBody body, int start, int end, HashMap<Integer, String> localRegNames, List<DottedChain> fullyQualifiedNames, boolean[] visited, HashMap<Integer, Integer> localRegAssigmentIps, HashMap<Integer, List<Integer>> refs) throws ConvertException, InterruptedException { calcKilledStats(body); boolean debugMode = DEBUG_MODE; if (debugMode) { System.err.println("OPEN SubSource:" + start + "-" + end + " " + code.get(start).toString() + " to " + code.get(end).toString()); } if (visited == null) { visited = new boolean[code.size()]; } //if(true) return ""; toSourceCount++; if (toSourceLimit > 0 && toSourceCount >= toSourceLimit) { throw new ConvertException("Limit of subs(" + toSourceLimit + ") was reached", start); } List<GraphTargetItem> output = new ArrayList<>(); int ip = start; //try { //int addr; iploop: while (ip <= end) { boolean processTry = processJumps; //addr = pos2adr(ip); //int ipfix = fixIPAfterDebugLine(ip); //int addrfix = pos2adr(ipfix); //int maxend = -1; if (ip > end) { break; } if (visited[ip]) { //logger.warning(path + ": Code already visited, ofs:" + Helper.formatAddress(pos2adr(ip)) + ", ip:" + ip); break; } if (Configuration.simplifyExpressions.get()) { stack.simplify(); } visited[ip] = true; AVM2Instruction ins = code.get(ip); if (stack.isEmpty()) { lineStartItem.setVal(ins); } if (debugMode) { System.err.println("translating ip " + ip + " ins " + ins.toString() + " stack:" + stack.toString() + " scopeStack:" + scopeStack.toString()); } if (ins.definition instanceof NewFunctionIns) { if (ip + 1 <= end) { if (code.get(ip + 1).definition instanceof PopIns) { ip += 2; continue; } } } /*if ((ip + 8 < code.size())) { //return in finally clause if (ins.definition instanceof SetLocalTypeIns) { if (code.get(ip + 1).definition instanceof PushByteIns) { AVM2Instruction jmp = code.get(ip + 2); if (jmp.definition instanceof JumpIns) { if (jmp.operands[0] == 0) { if (code.get(ip + 3).definition instanceof LabelIns) { if (code.get(ip + 4).definition instanceof PopIns) { if (code.get(ip + 5).definition instanceof LabelIns) { AVM2Instruction gl = code.get(ip + 6); if (gl.definition instanceof GetLocalTypeIns) { if (((GetLocalTypeIns) gl.definition).getRegisterId(gl) == ((SetLocalTypeIns) ins.definition).getRegisterId(ins)) { AVM2Instruction ki = code.get(ip + 7); if (ki.definition instanceof KillIns) { if (ki.operands[0] == ((SetLocalTypeIns) ins.definition).getRegisterId(ins)) { if (code.get(ip + 8).definition instanceof ReturnValueIns) { ip = ip + 8; continue; } } } } } } } } } } } } }//*/ /*if ((ip + 2 < code.size()) && (ins.definition instanceof NewCatchIns)) { // Filling local register in catch clause if (code.get(ip + 1).definition instanceof DupIns) { if (code.get(ip + 2).definition instanceof SetLocalTypeIns) { ins.definition.translate(isStatic, classIndex, localRegs, stack, scopeStack, constants, ins, method_info, output, body, abc, localRegNames, fullyQualifiedNames); ip += 3; continue; } } }*/ if ((ins.definition instanceof GetLocalTypeIns) && (!output.isEmpty()) && (output.get(output.size() - 1) instanceof SetLocalAVM2Item) && (((SetLocalAVM2Item) output.get(output.size() - 1)).regIndex == ((GetLocalTypeIns) ins.definition).getRegisterId(ins)) && isKilled(((SetLocalAVM2Item) output.get(output.size() - 1)).regIndex, start, end)) { SetLocalAVM2Item slt = (SetLocalAVM2Item) output.remove(output.size() - 1); stack.push(slt.getValue()); ip++; } else if ((ins.definition instanceof SetLocalTypeIns) && (ip + 1 <= end) && (isKilled(((SetLocalTypeIns) ins.definition).getRegisterId(ins), ip, end))) { // set_local_x,get_local_x..kill x AVM2Instruction insAfter = code.get(ip + 1); if ((insAfter.definition instanceof GetLocalTypeIns) && (((GetLocalTypeIns) insAfter.definition).getRegisterId(insAfter) == ((SetLocalTypeIns) ins.definition).getRegisterId(ins))) { GraphTargetItem before = stack.peek(); ins.definition.translate(lineStartItem, isStatic, scriptIndex, classIndex, localRegs, stack, scopeStack, ins, output, body, abc, localRegNames, fullyQualifiedNames, path, localRegAssigmentIps, ip, refs, this, thisHasDefaultToPrimitive); stack.push(before); ip += 2; continue iploop; } else { ins.definition.translate(lineStartItem, isStatic, scriptIndex, classIndex, localRegs, stack, scopeStack, ins, output, body, abc, localRegNames, fullyQualifiedNames, path, localRegAssigmentIps, ip, refs, this, thisHasDefaultToPrimitive); ip++; continue iploop; } } else if (ins.definition instanceof DupIns) { int nextPos; do { AVM2Instruction insAfter = ip + 1 < code.size() ? code.get(ip + 1) : null; if (insAfter == null) { ins.definition.translate(lineStartItem, isStatic, scriptIndex, classIndex, localRegs, stack, scopeStack, ins, output, body, abc, localRegNames, fullyQualifiedNames, path, localRegAssigmentIps, ip, refs, this, thisHasDefaultToPrimitive); ip++; break; } AVM2Instruction insBefore = ins; if (ip - 1 >= start) { insBefore = code.get(ip - 1); } if (insAfter.definition instanceof ConvertBIns) { // SWF compiled with debug contain convert_b ip++; //addr = pos2adr(ip); insAfter = code.get(ip + 1); } boolean isAnd; if (processJumps && (insAfter.definition instanceof IfFalseIns)) { //stack.add("(" + stack.pop() + ")&&"); isAnd = true; } else if (processJumps && (insAfter.definition instanceof IfTrueIns)) { //stack.add("(" + stack.pop() + ")||"); isAnd = false; } else if (insAfter.definition instanceof SetLocalTypeIns) { // chained assignments int reg = (((SetLocalTypeIns) insAfter.definition).getRegisterId(insAfter)); for (int t = ip + 1; t <= end - 1; t++) { if (code.get(t).definition instanceof KillIns) { if (code.get(t).operands[0] == reg) { break; } } if (code.get(t).definition instanceof GetLocalTypeIns) { if (((GetLocalTypeIns) code.get(t).definition).getRegisterId(code.get(t)) == reg) { if (code.get(t + 1).definition instanceof KillIns) { if (code.get(t + 1).operands[0] == reg) { ConvertOutput assignment = toSourceOutput(thisHasDefaultToPrimitive, lineStartItem, path, part, processJumps, isStatic, scriptIndex, classIndex, localRegs, stack, scopeStack, abc, body, ip + 2, t - 1, localRegNames, fullyQualifiedNames, visited, localRegAssigmentIps, refs); if (!assignment.output.isEmpty()) { GraphTargetItem tar = assignment.output.remove(assignment.output.size() - 1); tar.firstPart = part; stack.push(tar); ip = t + 2; continue iploop; } } } } } } if (!isKilled(reg, 0, end)) { GraphTargetItem vx = stack.pop().getThroughDuplicate(); int dupCnt = 1; for (int i = ip - 1; i >= start; i--) { if (code.get(i).definition instanceof DupIns) { if (stack.isEmpty()) { break; // FIXME?o } stack.pop(); dupCnt++; //stack.push(v); } else { break; } } for (int i = 0; i < dupCnt; i++) { stack.push(new LocalRegAVM2Item(ins, (AVM2Instruction) lineStartItem.getVal(), reg, vx)); } stack.push(vx); } else { ins.definition.translate(lineStartItem, isStatic, scriptIndex, classIndex, localRegs, stack, scopeStack, ins, output, body, abc, localRegNames, fullyQualifiedNames, path, localRegAssigmentIps, ip, refs, this, thisHasDefaultToPrimitive); } ip++; break; //} } else { ins.definition.translate(lineStartItem, isStatic, scriptIndex, classIndex, localRegs, stack, scopeStack, ins, output, body, abc, localRegNames, fullyQualifiedNames, path, localRegAssigmentIps, ip, refs, this, thisHasDefaultToPrimitive); ip++; break; //throw new ConvertException("Unknown pattern after DUP:" + insComparsion.toString()); } } while (ins.definition instanceof DupIns); } else if ((ins.definition instanceof ReturnValueIns) || (ins.definition instanceof ReturnVoidIns) || (ins.definition instanceof ThrowIns)) { ins.definition.translate(lineStartItem, isStatic, scriptIndex, classIndex, localRegs, stack, scopeStack, ins, output, body, abc, localRegNames, fullyQualifiedNames, path, localRegAssigmentIps, ip, refs, this, thisHasDefaultToPrimitive); //ip = end + 1; break; } else if (ins.definition instanceof NewFunctionIns) { String functionName = ""; if ((ip >= start + 2) && (ip <= end - 4)) { AVM2Instruction prev2 = code.get(ip - 2); if (prev2.definition instanceof NewObjectIns) { if (prev2.operands[0] == 0) { if (code.get(ip - 1).definition instanceof PushWithIns) { boolean hasDup = false; int plus = 0; if (code.get(ip + 1).definition instanceof DupIns) { hasDup = true; plus = 1; } AVM2Instruction psco = code.get(ip + 1 + plus); if (psco.definition instanceof GetScopeObjectIns) { if (psco.operands[0] == scopeStack.size() - 1) { if (code.get(ip + plus + 2).definition instanceof SwapIns) { if (code.get(ip + plus + 4).definition instanceof PopScopeIns) { if (code.get(ip + plus + 3).definition instanceof SetPropertyIns) { functionName = abc.constants.getMultiname(code.get(ip + plus + 3).operands[0]).getName(abc.constants, fullyQualifiedNames, true, true); scopeStack.pop();// with output.remove(output.size() - 1); // with ip = ip + plus + 4; // +1 below } } } } } } } } } // What to do when hasDup is false? ins.definition.translate(lineStartItem, isStatic, scriptIndex, classIndex, localRegs, stack, scopeStack, ins, output, body, abc, localRegNames, fullyQualifiedNames, path, localRegAssigmentIps, ip, refs, this, thisHasDefaultToPrimitive); NewFunctionAVM2Item nft = (NewFunctionAVM2Item) stack.peek(); nft.functionName = functionName; ip++; } else { try { ins.definition.translate(lineStartItem, isStatic, scriptIndex, classIndex, localRegs, stack, scopeStack, ins, output, body, abc, localRegNames, fullyQualifiedNames, path, localRegAssigmentIps, ip, refs, this, thisHasDefaultToPrimitive); } catch (RuntimeException re) { /*String last=""; int len=5; for(int i=(ip-len<0?0:ip-len);i<ip+len && ip<code.size();i++){ if(i==ip){ last+=">"; } last+=""+i+": "+code.get(i).toString()+"\r\n"; } Logger.getLogger(AVM2Code.class.getName()).log(Level.SEVERE, "ins list:\r\n{0}", last);*/ throw re; } ip++; //addr = pos2adr(ip); } } if (debugMode) { System.err.println("CLOSE SubSource:" + start + "-" + end + " " + code.get(start).toString() + " to " + code.get(end).toString()); } /*if (hideTemporaryRegisters) { clearTemporaryRegisters(output); }*/ return new ConvertOutput(stack, output); /*} catch (ConvertException cex) { throw cex; }*/ } public int getRegisterCount() { int maxRegister = -1; for (AVM2Instruction ins : code) { int regId = -1; if (ins.definition instanceof SetLocalTypeIns) { regId = ((SetLocalTypeIns) ins.definition).getRegisterId(ins); } if (ins.definition instanceof GetLocalTypeIns) { regId = ((GetLocalTypeIns) ins.definition).getRegisterId(ins); } if (regId > maxRegister) { maxRegister = regId; } } return maxRegister + 1; } public HashMap<Integer, GraphTargetItem> getLocalRegTypes(AVM2ConstantPool constants, List<DottedChain> fullyQualifiedNames) { HashMap<Integer, GraphTargetItem> ret = new HashMap<>(); AVM2Instruction prev = null; for (AVM2Instruction ins : code) { if (ins.definition instanceof SetLocalTypeIns) { if (prev != null) { if (prev.definition instanceof CoerceOrConvertTypeIns) { ret.put(((SetLocalTypeIns) ins.definition).getRegisterId(ins), ((CoerceOrConvertTypeIns) prev.definition).getTargetType(constants, prev)); } } } prev = ins; } return ret; } private class Slot { public final GraphTargetItem scope; public final Multiname multiname; public Slot(GraphTargetItem scope, Multiname multiname) { this.scope = scope; this.multiname = multiname; } @Override public boolean equals(Object obj) { if (obj instanceof Slot) { Slot slot = (Slot) obj; return (slot.scope.getThroughRegister() == scope.getThroughRegister()) && (slot.multiname == multiname); } return false; } @Override public int hashCode() { int hash = 7; hash = 59 * hash + (scope != null ? Objects.hashCode(scope.getThroughRegister()) : 0); hash = 59 * hash + Objects.hashCode(multiname); return hash; } } public void initToSource() { toSourceCount = 0; } private GraphTargetItem handleDeclareReg(int minreg, GraphTargetItem assignment, DeclarationAVM2Item[] declaredRegisters, List<Slot> declaredSlots, int reg) { //do not add declarations for reserved local registers like function arguments if (reg < minreg) { return assignment; } GraphTargetItem vtype = TypeItem.UNBOUNDED; if (assignment.value instanceof ConvertAVM2Item) { vtype = ((ConvertAVM2Item) assignment.value).type; } if (vtype.equals(TypeItem.UNBOUNDED) && (assignment.value instanceof CoerceAVM2Item)) { vtype = ((CoerceAVM2Item) assignment.value).typeObj; } if (vtype.equals(TypeItem.UNBOUNDED) && (assignment.value instanceof SimpleValue) && ((SimpleValue) assignment.value).isSimpleValue()) { vtype = assignment.value.returnType(); } if (declaredRegisters[reg] == null) { declaredRegisters[reg] = new DeclarationAVM2Item(assignment, vtype); if (assignment instanceof SetTypeAVM2Item) { ((SetTypeAVM2Item) assignment).setDeclaration(declaredRegisters[reg]); } return declaredRegisters[reg]; } if (declaredRegisters[reg].type == TypeItem.UNBOUNDED) { } else if (!declaredRegisters[reg].type.equals(vtype)) { //already declared with different type declaredRegisters[reg].type = TypeItem.UNBOUNDED; } if (assignment instanceof SetTypeAVM2Item) { ((SetTypeAVM2Item) assignment).setDeclaration(declaredRegisters[reg]); } return assignment; } private GraphTargetItem injectDeclarations(int minreg, GraphTargetItem ti, DeclarationAVM2Item[] declaredRegisters, List<Slot> declaredSlots, List<DeclarationAVM2Item> declaredSlotsDec, ABC abc, MethodBody body) { if (ti.value != null) { ti.value = injectDeclarations(minreg, ti.value, declaredRegisters, declaredSlots, declaredSlotsDec, abc, body); } //TODO: walk whole tree... some walker? if (ti instanceof IfItem) { ((IfItem) ti).expression = injectDeclarations(minreg, ((IfItem) ti).expression, declaredRegisters, declaredSlots, declaredSlotsDec, abc, body); } if (ti instanceof BinaryOpItem) { ((BinaryOpItem) ti).leftSide = injectDeclarations(minreg, ((BinaryOpItem) ti).leftSide, declaredRegisters, declaredSlots, declaredSlotsDec, abc, body); ((BinaryOpItem) ti).rightSide = injectDeclarations(minreg, ((BinaryOpItem) ti).rightSide, declaredRegisters, declaredSlots, declaredSlotsDec, abc, body); } if (ti instanceof ForEachInAVM2Item) { ForEachInAVM2Item fei = (ForEachInAVM2Item) ti; if (fei.expression.object instanceof LocalRegAVM2Item) { int reg = ((LocalRegAVM2Item) fei.expression.object).regIndex; if (declaredRegisters[reg] == null) { fei.expression.object = handleDeclareReg(minreg, fei.expression.object, declaredRegisters, declaredSlots, reg); } } } if (ti instanceof ForInAVM2Item) { ForInAVM2Item fi = (ForInAVM2Item) ti; if (fi.expression.object instanceof LocalRegAVM2Item) { int reg = ((LocalRegAVM2Item) fi.expression.object).regIndex; fi.expression.object = handleDeclareReg(minreg, fi.expression.object, declaredRegisters, declaredSlots, reg); //nowdeclaredRegs.add(reg); } } if (ti instanceof Block) { Block bl = (Block) ti; for (List<GraphTargetItem> s : bl.getSubs()) { injectDeclarations(minreg, s, declaredRegisters, declaredSlots, declaredSlotsDec, abc, body); } } if (ti instanceof SetLocalAVM2Item) { int reg = ((SetLocalAVM2Item) ti).regIndex; ti = handleDeclareReg(minreg, ti, declaredRegisters, declaredSlots, reg); return ti; } if (ti instanceof SetSlotAVM2Item) { SetSlotAVM2Item ssti = (SetSlotAVM2Item) ti; Slot sl = new Slot(ssti.scope, ssti.slotName); if (!declaredSlots.contains(sl)) { GraphTargetItem type = TypeItem.UNBOUNDED; for (int t = 0; t < body.traits.traits.size(); t++) { if (body.traits.traits.get(t).getName(abc) == sl.multiname) { if (body.traits.traits.get(t) instanceof TraitSlotConst) { type = PropertyAVM2Item.multinameToType(((TraitSlotConst) body.traits.traits.get(t)).type_index, abc.constants); } } } DeclarationAVM2Item d = new DeclarationAVM2Item(ti, type); ssti.setDeclaration(d); declaredSlotsDec.add(d); declaredSlots.add(sl); return d; //nowdeclaredSlots.add(sl); } else { int idx = declaredSlots.indexOf(sl); ssti.setDeclaration(declaredSlotsDec.get(idx)); } } return ti; } private void injectDeclarations(int minreg, List<GraphTargetItem> list, DeclarationAVM2Item[] declaredRegisters, List<Slot> declaredSlots, List<DeclarationAVM2Item> declaredSlotsDec, ABC abc, MethodBody body) { //List<Integer> nowdeclaredRegs=new ArrayList<>(); //List<Slot> nowdeclaredSlots=new ArrayList<>(); for (int i = 0; i < list.size(); i++) { GraphTargetItem ti = list.get(i); GraphTargetItem ti2 = injectDeclarations(minreg, ti, declaredRegisters, declaredSlots, declaredSlotsDec, abc, body); if (ti != ti2) { list.set(i, ti2); } } /* //undeclare registers at the end of the block? for(int reg:nowdeclaredRegs){ declaredRegisters[reg] = false; } for(Slot s:nowdeclaredSlots){ declaredSlots.remove(s); }*/ } public List<GraphTargetItem> toGraphTargetItems(boolean thisHasDefaultToPrimitive, ConvertData convertData, String path, int methodIndex, boolean isStatic, int scriptIndex, int classIndex, ABC abc, MethodBody body, HashMap<Integer, String> localRegNames, ScopeStack scopeStack, int initializerType, List<DottedChain> fullyQualifiedNames, List<Traits> initTraits, int staticOperation, HashMap<Integer, Integer> localRegAssigmentIps, HashMap<Integer, List<Integer>> refs) throws InterruptedException { initToSource(); List<GraphTargetItem> list; HashMap<Integer, GraphTargetItem> localRegs = new HashMap<>(); int regCount = getRegisterCount(); for (int i = 0; i < regCount; i++) { localRegs.put(0, new UndefinedAVM2Item(null, null)); } //try { list = AVM2Graph.translateViaGraph(path, this, abc, body, isStatic, scriptIndex, classIndex, localRegs, scopeStack, localRegNames, fullyQualifiedNames, staticOperation, localRegAssigmentIps, refs, thisHasDefaultToPrimitive); if (initTraits != null) { loopi: for (int i = 0; i < list.size(); i++) { GraphTargetItem ti = list.get(i); if ((ti instanceof InitPropertyAVM2Item) || (ti instanceof SetPropertyAVM2Item)) { int multinameIndex = 0; GraphTargetItem value = null; if (ti instanceof InitPropertyAVM2Item) { multinameIndex = ((InitPropertyAVM2Item) ti).propertyName.multinameIndex; value = ((InitPropertyAVM2Item) ti).value; } if (ti instanceof SetPropertyAVM2Item) { multinameIndex = ((FullMultinameAVM2Item) ((SetPropertyAVM2Item) ti).propertyName).multinameIndex; value = ((SetPropertyAVM2Item) ti).value; } Multiname m = abc.constants.getMultiname(multinameIndex); for (Traits ts : initTraits) { for (Trait t : ts.traits) { Multiname tm = abc.constants.getMultiname(t.name_index); if (tm != null && tm.equals(m)) { if ((t instanceof TraitSlotConst)) { if (((TraitSlotConst) t).isConst() || initializerType == GraphTextWriter.TRAIT_CLASS_INITIALIZER || initializerType == GraphTextWriter.TRAIT_SCRIPT_INITIALIZER) { TraitSlotConst tsc = (TraitSlotConst) t; if (value != null && !convertData.assignedValues.containsKey(tsc)) { if (value instanceof NewFunctionAVM2Item) { NewFunctionAVM2Item f = (NewFunctionAVM2Item) value; f.functionName = tsc.getName(abc).getName(abc.constants, fullyQualifiedNames, true, true); } AssignedValue av = new AssignedValue(value, initializerType, methodIndex); convertData.assignedValues.put(tsc, av); list.remove(i); i--; continue; } } break; } } } } } else { // In obfuscated code, SetLocal instructions comes first //break; } } } if (initializerType == GraphTextWriter.TRAIT_CLASS_INITIALIZER || initializerType == GraphTextWriter.TRAIT_SCRIPT_INITIALIZER) { List<GraphTargetItem> newList = new ArrayList<>(); for (GraphTargetItem ti : list) { if (!(ti instanceof ReturnVoidAVM2Item)) { if (!(ti instanceof InitPropertyAVM2Item)) { newList.add(ti); } } } list = newList; if (list.isEmpty()) { return list; } } // Declarations DeclarationAVM2Item[] d = new DeclarationAVM2Item[regCount]; int[] param_types = abc.method_info.get(body.method_info).param_types; int r = 1; for (int i = 0; i < param_types.length; i++) { GraphTargetItem type; if (param_types[i] == 0) { type = TypeItem.UNBOUNDED; } else { type = new TypeItem(abc.constants.getMultiname(param_types[i]).getNameWithNamespace(abc.constants, true)); } if (d.length > r) { d[r] = new DeclarationAVM2Item(new SetLocalAVM2Item(null, null, r, new NullAVM2Item(null, null)), type); } r++; } if (abc.method_info.get(body.method_info).flagNeed_arguments()) { if (d.length > r) { d[r] = new DeclarationAVM2Item(new SetLocalAVM2Item(null, null, r, new NullAVM2Item(null, null)), TypeItem.ARRAY /*?*/); } r++; } if (abc.method_info.get(body.method_info).flagNeed_rest()) { if (d.length > r) { d[r] = new DeclarationAVM2Item(new SetLocalAVM2Item(null, null, r, new NullAVM2Item(null, null)), TypeItem.ARRAY/*?*/); } r++; } // //int minreg = abc.method_info.get(body.method_info).getMaxReservedReg() + 1; injectDeclarations(1, list, d, new ArrayList<>(), new ArrayList<>(), abc, body); int lastPos = list.size() - 1; if (lastPos < 0) { lastPos = 0; } if ((list.size() > lastPos) && (list.get(lastPos) instanceof ScriptEndItem)) { lastPos--; } if (lastPos < 0) { lastPos = 0; } if ((list.size() > lastPos) && (list.get(lastPos) instanceof ReturnVoidAVM2Item)) { list.remove(lastPos); } return list; } public void updateInstructionByteCountByAddr(long instructionAddress, int byteDelta, MethodBody body) { if (byteDelta != 0) { updateOffsets(new OffsetUpdater() { @Override public long updateInstructionOffset(long address) { if (address > instructionAddress) { return address + byteDelta; } return address; } @Override public int updateOperandOffset(long insAddr, long targetAddress, int offset) { if (targetAddress > instructionAddress && insAddr <= instructionAddress) { return offset + byteDelta; } if (targetAddress <= instructionAddress && insAddr > instructionAddress) { return offset - byteDelta; } return offset; } }, body); body.setModified(); } } public void updateInstructionByteCount(int pos, int byteDelta, MethodBody body) { AVM2Instruction instruction = code.get(pos); updateInstructionByteCountByAddr(instruction.getAddress(), byteDelta, body); } public void updateOffsets(OffsetUpdater updater, MethodBody body) { for (int i = 0; i < code.size(); i++) { AVM2Instruction ins = code.get(i); if (ins.definition instanceof LookupSwitchIns) { long target = ins.getAddress() + ins.operands[0]; ins.operands[0] = updater.updateOperandOffset(ins.getAddress(), target, ins.operands[0]); for (int k = 2; k < ins.operands.length; k++) { target = ins.getAddress() + ins.operands[k]; ins.operands[k] = updater.updateOperandOffset(ins.getAddress(), target, ins.operands[k]); } } else /*for (int j = 0; j < ins.definition.operands.length; j++) { if (ins.definition.operands[j] == AVM2Code.DAT_OFFSET) { long target = ins.offset + ins.getBytes().length + ins.operands[j]; ins.operands[j] = updater.updateOperandOffset(target, ins.operands[j]); } }*/ //Faster, but not so universal if (ins.definition instanceof IfTypeIns) { long target = ins.getTargetAddress(); try { ins.operands[0] = updater.updateOperandOffset(ins.getAddress(), target, ins.operands[0]); } catch (ConvertException cex) { throw new ConvertException("Invalid offset (" + ins + ")", i); } } ins.setAddress(updater.updateInstructionOffset(ins.getAddress())); //Note: changing operands here does not change instruction byte length as offsets are always S24 (not variable length) } for (ABCException ex : body.exceptions) { ex.start = updater.updateOperandOffset(-1, ex.start, ex.start); ex.end = updater.updateOperandOffset(-1, ex.end, ex.end); ex.target = updater.updateOperandOffset(-1, ex.target, ex.target); } } public void fixJumps(final String path, MethodBody body) throws InterruptedException { if (code.isEmpty()) { return; } final List<Long> insAddrToRemove = new ArrayList<>(); final long endOffset = getEndOffset(); updateOffsets(new OffsetUpdater() { @Override public long updateInstructionOffset(long address) { return address; } @Override public int updateOperandOffset(long insAddr, long targetAddress, int offset) { if (targetAddress > endOffset || targetAddress < 0 || adr2posNoEx(targetAddress) < 0) { insAddrToRemove.add(insAddr); } return offset; } }, body); boolean someIgnored = false; for (Long insAddr : insAddrToRemove) { int pos = adr2posNoEx(insAddr); if (pos > -1) { code.get(pos).setIgnored(true, 0); someIgnored = true; } } if (someIgnored) { logger.log(Level.WARNING, path + ": One or more invalid jump offsets found in the code. Those instructions were ignored."); } removeIgnored(body); } public void checkValidOffsets(MethodBody body) { updateOffsets(new OffsetUpdater() { @Override public long updateInstructionOffset(long address) { adr2pos(address); return address; } @Override public int updateOperandOffset(long insAddr, long targetAddress, int offset) { /*if (insAddr == -1) { return offset; }*/ adr2pos(targetAddress); return offset; } }, body); } public void removeInstruction(int pos, MethodBody body) { if ((pos < 0) || (pos >= code.size())) { throw new IndexOutOfBoundsException(); } AVM2Instruction ins = code.get(pos); final long remOffset = ins.getAddress(); int bc = ins.getBytesLength(); final int byteCount = bc; updateOffsets(new OffsetUpdater() { @Override public long updateInstructionOffset(long address) { if (address > remOffset) { return address - byteCount; } return address; } @Override public int updateOperandOffset(long jumpInsAddr, long jumpTargetAddr, int jumpOffset) { /* a:jump d: b: c:X d: */ if (jumpTargetAddr > remOffset && jumpInsAddr < remOffset) { return jumpOffset - byteCount; } /* a:X1 b:X2 c: d:jump a: */ if (jumpTargetAddr <= remOffset && jumpInsAddr > remOffset) { return jumpOffset + byteCount; } return jumpOffset; } }, body); code.remove(pos); //checkValidOffsets(body); } /** * Inserts instruction at specified point. Handles offsets properly. Note: * If newinstruction is jump, the offset operand must be handled properly by * caller. All old jump offsets to pos are targeted before new instruction. * * @param pos Position in the list * @param instruction Instruction False means before new instruction * @param body Method body (used for try handling) */ public void insertInstruction(int pos, AVM2Instruction instruction, MethodBody body) { insertInstruction(pos, instruction, false, body); } /** * Replaces instruction by another. Properly handles offsets. Note: If * newinstruction is jump, the offset operand must be handled properly by * caller. * * @param pos * @param instruction * @param body */ public void replaceInstruction(int pos, AVM2Instruction instruction, MethodBody body) { AVM2Instruction oldInstruction = code.get(pos); instruction.setAddress(oldInstruction.getAddress()); int oldByteCount = oldInstruction.getBytesLength(); int newByteCount = instruction.getBytesLength(); int byteDelta = newByteCount - oldByteCount; if (byteDelta != 0) { updateOffsets(new OffsetUpdater() { @Override public long updateInstructionOffset(long address) { if (address > instruction.getAddress()) { return address + byteDelta; } return address; } @Override public int updateOperandOffset(long insAddr, long targetAddress, int offset) { if (targetAddress > instruction.getAddress() && insAddr <= instruction.getAddress()) { return offset + byteDelta; } if (targetAddress <= instruction.getAddress() && insAddr > instruction.getAddress()) { return offset - byteDelta; } return offset; } }, body); } code.set(pos, instruction); } /** * Inserts instruction at specified point. Handles offsets properly. Note: * If newinstruction is jump, the offset operand must be handled properly by * caller. * * @param pos Position in the list * @param instruction Instruction * @param mapOffsetsAfterIns Map all jumps to the pos after new instruction? * False means before new instruction * @param body Method body (used for try handling) */ public void insertInstruction(int pos, AVM2Instruction instruction, boolean mapOffsetsAfterIns, MethodBody body) { //checkValidOffsets(body); if (pos < 0) { pos = 0; } if (pos > code.size()) { pos = code.size(); } final int byteCount = instruction.getBytesLength(); if (pos == code.size()) { instruction.setAddress(code.get(pos - 1).getAddress() + code.get(pos - 1).getBytesLength()); } else { instruction.setAddress(code.get(pos).getAddress()); } final long x = instruction.getAddress(); updateOffsets(new OffsetUpdater() { @Override public long updateInstructionOffset(long offset) { if (offset >= x) { return offset + byteCount; } return offset; } @Override public int updateOperandOffset(long j, long t, int offset_jt) { /* j:jump t: n: n: x:# t: */ if (((t > x) || (mapOffsetsAfterIns && (t == x))) && (j < x)) { return offset_jt + byteCount; } /* t: x:# n: n: j:jump t: */ if (((t < x) || (mapOffsetsAfterIns && (t == x))) && (j > x)) { return offset_jt - byteCount; } /* t: n: n: j:x: # jump t: */ if ((j == x) && (t < x)) { return offset_jt - byteCount; } return offset_jt; } }, body); instruction.setAddress(x); code.add(pos, instruction); //checkValidOffsets(body); } public int removeTraps(Trait trait, int methodInfo, MethodBody body, ABC abc, int scriptIndex, int classIndex, boolean isStatic, String path) throws InterruptedException { SWFDecompilerPlugin.fireAvm2CodeRemoveTraps(path, classIndex, isStatic, scriptIndex, abc, trait, methodInfo, body); try (Statistics s = new Statistics("AVM2DeobfuscatorGetSet")) { new AVM2DeobfuscatorGetSet().avm2CodeRemoveTraps(path, classIndex, isStatic, scriptIndex, abc, trait, methodInfo, body); } try (Statistics s = new Statistics("AVM2DeobfuscatorSimple")) { new AVM2DeobfuscatorSimpleOld().avm2CodeRemoveTraps(path, classIndex, isStatic, scriptIndex, abc, trait, methodInfo, body); } try (Statistics s = new Statistics("AVM2DeobfuscatorRegisters")) { new AVM2DeobfuscatorRegistersOld().avm2CodeRemoveTraps(path, classIndex, isStatic, scriptIndex, abc, trait, methodInfo, body); } try (Statistics s = new Statistics("AVM2DeobfuscatorJumps")) { new AVM2DeobfuscatorJumps().avm2CodeRemoveTraps(path, classIndex, isStatic, scriptIndex, abc, trait, methodInfo, body); } return 1; } private void handleRegister(CodeStats stats, int reg) { if (reg + 1 > stats.maxlocal) { stats.maxlocal = reg + 1; } } private boolean walkCode(CodeStats stats, int pos, int stack, int scope, ABC abc) { while (pos < code.size()) { AVM2Instruction ins = code.get(pos); if (stats.instructionStats[pos].seen) { // check stack mismatch here return true; } if (ins.definition instanceof NewFunctionIns) { MethodBody innerBody = abc.findBody(ins.operands[0]); innerBody.autoFillStats(abc, stats.initscope + (stats.has_activation ? 1 : 0), false); } stats.instructionStats[pos].seen = true; stats.instructionStats[pos].stackpos = stack; stats.instructionStats[pos].scopepos = scope; int stackDelta = ins.definition.getStackDelta(ins, abc); int scopeDelta = ins.definition.getScopeStackDelta(ins, abc); int oldStack = stack; //+" deltaScope:"+(scopeDelta>0?"+"+scopeDelta:scopeDelta)+" stack:"+stack+" scope:"+scope); stack += stackDelta; scope += scopeDelta; stats.instructionStats[pos].stackpos_after = stack; stats.instructionStats[pos].scopepos_after = scope; if (stack > stats.maxstack) { stats.maxstack = stack; } if (scope > stats.maxscope) { stats.maxscope = scope; } //System.out.println("stack "+oldStack+(stackDelta>=0?"+"+stackDelta:stackDelta)+" max:"+stats.maxstack+" "+ins); if ((ins.definition instanceof DXNSIns) || (ins.definition instanceof DXNSLateIns)) { stats.has_set_dxns = true; } if (ins.definition instanceof NewActivationIns) { stats.has_activation = true; } if (ins.definition instanceof SetLocalTypeIns) { handleRegister(stats, ((SetLocalTypeIns) ins.definition).getRegisterId(ins)); } else if (ins.definition instanceof GetLocalTypeIns) { handleRegister(stats, ((GetLocalTypeIns) ins.definition).getRegisterId(ins)); } else { for (int i = 0; i < ins.definition.operands.length; i++) { int op = ins.definition.operands[i]; if (op == DAT_LOCAL_REG_INDEX) { handleRegister(stats, ins.operands[i]); } } } if (ins.definition instanceof ReturnValueIns) { // check stack=1 return true; } if (ins.definition instanceof ReturnVoidIns) { // check stack=0 return true; } if (ins.definition instanceof JumpIns) { try { pos = adr2pos(ins.getTargetAddress()); continue; } catch (ConvertException ex) { return false; } } else if (ins.definition instanceof IfTypeIns) { try { int newpos = adr2pos(ins.getTargetAddress()); walkCode(stats, newpos, stack, scope, abc); } catch (ConvertException ex) { return false; } } if (ins.definition instanceof LookupSwitchIns) { for (int i = 0; i < ins.operands.length; i++) { if (i == 1) { continue; } try { int newpos = adr2pos(pos2adr(pos) + ins.operands[i]); if (!walkCode(stats, newpos, stack, scope, abc)) { return false; } } catch (ConvertException ex) { return false; } } } pos++; } return true; } public CodeStats getStats(ABC abc, MethodBody body, int initScope) { CodeStats stats = new CodeStats(this); stats.initscope = initScope; if (!walkCode(stats, 0, 0, initScope, abc)) { return null; } int scopePos = -1; int prevStart = 0; for (int e = 0; e < body.exceptions.length; e++) { ABCException ex = body.exceptions[e]; try { if (scopePos == -1) { scopePos = stats.instructionStats[adr2pos(ex.end) - 1].scopepos_after; } List<Integer> visited = new ArrayList<>(); for (int i = 0; i < stats.instructionStats.length; i++) { if (stats.instructionStats[i].seen) { visited.add(i); } } if (!walkCode(stats, adr2pos(ex.target), 1 + (ex.isFinally() ? 1 : 0), scopePos, abc)) { return null; } int maxIp = 0; // searching for visited instruction in second run which has maximum position for (int i = 0; i < stats.instructionStats.length; i++) { if (stats.instructionStats[i].seen && !visited.contains(i)) { maxIp = i; } } scopePos = stats.instructionStats[maxIp].scopepos_after; int stackPos = stats.instructionStats[maxIp].stackpos_after; int nextIp = maxIp + 1; if (code.get(maxIp).definition instanceof JumpIns) { nextIp = adr2pos(pos2adr(nextIp) + code.get(maxIp).operands[0]); } if (nextIp < stats.instructionStats.length) { InstructionStats nextIpStat = stats.instructionStats[nextIp]; int origScopePos = nextIpStat.scopepos; int origStackPos = nextIpStat.stackpos; if (prevStart == ex.start && ex.isFinally() && !code.get(nextIp).isExit() && nextIpStat.seen) { for (int i = 0; i < stats.instructionStats.length; i++) { stats.instructionStats[i].seen = false; } // Rerun rest with new scopePos, stackPos if (!walkCode(stats, nextIp, origStackPos + 1/*magic!*/, scopePos - 1 /*magic!*/, abc)) { return null; } scopePos--; } } prevStart = ex.start; } catch (ConvertException ex1) { // ignore } } //stats.maxscope+=initScope; return stats; } private void visitCode(int ip, int lastIp, HashMap<Integer, List<Integer>> refs) throws InterruptedException { List<Integer> toVisit = new ArrayList<>(); List<Integer> toVisitLast = new ArrayList<>(); toVisit.add(ip); toVisitLast.add(lastIp); while (!toVisit.isEmpty()) { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); } ip = toVisit.remove(0); lastIp = toVisitLast.remove(0); while (ip < code.size()) { if (!refs.containsKey(ip)) { refs.put(ip, new ArrayList<>()); } refs.get(ip).add(lastIp); lastIp = ip; if (refs.get(ip).size() > 1) { break; } AVM2Instruction ins = code.get(ip); if (ins.definition instanceof ThrowIns) { break; } if (ins.definition instanceof ReturnValueIns) { break; } if (ins.definition instanceof ReturnVoidIns) { break; } if (ins.definition instanceof LookupSwitchIns) { try { for (int i = 2; i < ins.operands.length; i++) { toVisit.add(adr2pos(pos2adr(ip) + ins.operands[i])); toVisitLast.add(ip); } ip = adr2pos(pos2adr(ip) + ins.operands[0]); continue; } catch (ConvertException ex) { } } if (ins.definition instanceof JumpIns) { try { ip = adr2pos(ins.getTargetAddress()); continue; } catch (ConvertException ex) { logger.log(Level.FINE, null, ex); } } else if (ins.definition instanceof IfTypeIns) { try { toVisit.add(adr2pos(ins.getTargetAddress())); toVisitLast.add(ip); } catch (ConvertException ex) { logger.log(Level.FINE, null, ex); } } ip++; } }; } public HashMap<Integer, List<Integer>> visitCode(MethodBody body) throws InterruptedException { HashMap<Integer, List<Integer>> refs = new HashMap<>(); for (int i = 0; i < code.size(); i++) { refs.put(i, new ArrayList<>()); } visitCode(0, 0, refs); int pos = 0; for (ABCException e : body.exceptions) { pos++; try { visitCode(adr2pos(e.start, true), adr2pos(e.start, true) - 1, refs); visitCode(adr2pos(e.start, true), -1, refs); visitCode(adr2pos(e.target), adr2pos(e.end, true), refs); visitCode(adr2pos(e.end, true), -pos, refs); } catch (ConvertException ex) { logger.log(Level.SEVERE, "Visitcode error", ex); } } return refs; } public void removeIgnored(MethodBody body) throws InterruptedException { //System.err.println("removing ignored..."); for (int i = 0; i < code.size(); i++) { if (code.get(i).isIgnored()) { removeInstruction(i, body); i--; } } //System.err.println("/ignored removed"); } public int removeDeadCode(MethodBody body) throws InterruptedException { HashMap<Integer, List<Integer>> refs = visitCode(body); int cnt = 0; for (int i = code.size() - 1; i >= 0; i--) { if (refs.get(i).isEmpty()) { code.get(i).setIgnored(true, 0); cnt++; } } removeIgnored(body); for (int i = code.size() - 1; i >= 0; i--) { AVM2Instruction ins = code.get(i); if (ins.definition instanceof JumpIns) { if (ins.operands[0] == 0) { ins.setIgnored(true, 0); cnt++; } } } removeIgnored(body); return cnt; } public boolean inlineJumpExit() { boolean modified = false; int csize = code.size(); for (int i = 0; i < csize; i++) { AVM2Instruction ins = code.get(i); int insLen = code.get(i).getBytesLength(); long ofs = pos2adr(i); if (ins.definition instanceof JumpIns) { long targetOfs = ofs + insLen + ins.operands[0]; try { int ni = adr2pos(targetOfs); if (ni < code.size() && ni > -1) { AVM2Instruction ins2 = code.get(ni); if (ins2.isExit()) { code.set(i, new AVM2Instruction(ofs, ins2.definition, ins2.operands)); modified = true; } } } catch (ConvertException ex) { //ignore } } } return modified; } private static int getMostCommonIp(AVM2GraphSource code, List<Integer> branches) { List<List<Integer>> reachable = new ArrayList<>(); for (int i = 0; i < branches.size(); i++) { List<Integer> r = new ArrayList<>(); getReachableIps(code, branches.get(i), r); } int commonLevel; Map<Integer, Integer> levelMap = new HashMap<>(); for (List<Integer> first : reachable) { int maxclevel = 0; Set<Integer> visited = new HashSet<>(); for (Integer p : first) { if (visited.contains(p)) { continue; } visited.add(p); boolean common = true; commonLevel = 1; for (List<Integer> r : reachable) { if (r == first) { continue; } if (r.contains(p)) { commonLevel++; } } if (commonLevel <= maxclevel) { continue; } maxclevel = commonLevel; if (levelMap.containsKey(p)) { if (levelMap.get(p) > commonLevel) { commonLevel = levelMap.get(p); } } levelMap.put(p, commonLevel); if (common) { //return p; } } } for (int i = reachable.size() - 1; i >= 2; i--) { for (Integer p : levelMap.keySet()) { if (levelMap.get(p) == i) { return p; } } } for (Integer p : levelMap.keySet()) { if (levelMap.get(p) == branches.size()) { return p; } } return -1; } public static void getReachableIps(AVM2GraphSource code, int ip, List<Integer> reachable) { do { if (reachable.contains(ip)) { return; } reachable.add(ip); GraphSourceItem ins = code.get(ip); if (ins.isJump() || ins.isBranch()) { List<Integer> branches = ins.getBranches(code); for (int i = 1; i < branches.size(); i++) { getReachableIps(code, branches.get(i), reachable); } ip = branches.get(0); continue; } ip++; } while (ip < code.size()); } public static boolean isDirectAncestor(int currentIp, int ancestor, HashMap<Integer, List<Integer>> refs) { return isDirectAncestor(currentIp, ancestor, refs, new ArrayList<>()); } private static boolean isDirectAncestor(int currentIp, int ancestor, HashMap<Integer, List<Integer>> refs, List<Integer> visited) { if (currentIp == -1) { return true; } do { if (currentIp == ancestor) { return true; } if (currentIp == 0) { return false; } if (visited.contains(currentIp)) { return true; } visited.add(currentIp); if (refs.containsKey(currentIp)) { List<Integer> currentRefs = refs.get(currentIp); if ((currentRefs != null) && (!currentRefs.isEmpty())) { for (int i = 1; i < currentRefs.size(); i++) { if (!isDirectAncestor(currentRefs.get(i), ancestor, refs, visited)) { return false; } } currentIp = currentRefs.get(0); continue; } } currentIp--; } while (currentIp >= 0); return false; } public static boolean getPreviousReachableIps(int currentIp, HashMap<Integer, List<Integer>> refs, Set<Integer> reachable, Set<Integer> visited) { do { if (visited.contains(currentIp)) { return false; } reachable.add(currentIp); visited.add(currentIp); if (refs.containsKey(currentIp)) { List<Integer> currentRefs = refs.get(currentIp); if ((currentRefs != null) && (!currentRefs.isEmpty())) { if (currentRefs.size() == 1) { currentIp = currentRefs.get(0); continue; } boolean r = false; for (int i = 0; i < currentRefs.size(); i++) { Set<Integer> nr = new HashSet<>(); boolean v = getPreviousReachableIps(currentRefs.get(i), refs, nr, visited); if ((!v) || nr.contains(0)) { reachable.addAll(nr); } r = r || v; } return r; } } currentIp--; } while (currentIp >= 0); return true; } @Override public AVM2Code clone() { try { AVM2Code ret = (AVM2Code) super.clone(); if (code != null) { List<AVM2Instruction> codeCopy = new ArrayList<>(code.size()); for (AVM2Instruction ins : code) { codeCopy.add(ins.clone()); } ret.code = codeCopy; } ret.killedRegs = new HashMap<>(); return ret; } catch (CloneNotSupportedException ex) { throw new RuntimeException(); } } }