@Override
  public void deobfuscate(
      String path,
      int classIndex,
      boolean isStatic,
      int scriptIndex,
      ABC abc,
      AVM2ConstantPool cpool,
      Trait trait,
      MethodInfo minfo,
      MethodBody body)
      throws InterruptedException {

    // body.getCode().markMappedOffsets();
    // removeUnreachableActions(body.getCode(), cpool, trait, minfo, body);
    AVM2Code code = body.getCode();

    boolean found;
    do {
      found = false;
      Map<Integer, List<Integer>> refs = body.getCode().visitCode(body);
      loopi:
      for (int i = 0; i < code.code.size(); i++) {
        AVM2Instruction ins = code.code.get(i);
        if (ins.definition instanceof JumpIns) {
          long targetAddr = ins.offset + ins.operands[0] + ins.getBytesLength();
          {
            for (int r : refs.get(i)) {
              if (r >= 0) { // Not Exception start/end
                AVM2Instruction srcIns = code.code.get(r);

                if ((srcIns.definition instanceof JumpIns)
                    || ((srcIns.definition instanceof IfTypeIns) && (r != i - 1))) {
                  {
                    int oldop = srcIns.operands[0];
                    srcIns.operands[0] =
                        (int) (targetAddr - (srcIns.offset + srcIns.getBytesLength()));
                    if (srcIns.operands[0] != oldop) {
                      found = true;
                    }
                  }
                }
              }
            }
          }
        }
      }
      removeUnreachableActions(body.getCode(), cpool, trait, minfo, body);
    } while (found);
  }
  private int getFirstRegisterSetter(
      Reference<AVM2Instruction> assignment,
      int classIndex,
      boolean isStatic,
      int scriptIndex,
      ABC abc,
      MethodBody body,
      Set<Integer> ignoredRegisters,
      Set<Integer> ignoredGets)
      throws InterruptedException {
    AVM2Code code = body.getCode();

    if (code.code.isEmpty()) {
      return -1;
    }

    return visitCode(
        assignment,
        new HashSet<>(),
        new TranslateStack("deo"),
        classIndex,
        isStatic,
        body,
        scriptIndex,
        abc,
        code,
        0,
        code.code.size() - 1,
        ignoredRegisters,
        ignoredGets);
  }
  protected boolean removeObfuscationIfs(
      int classIndex,
      boolean isStatic,
      int scriptIndex,
      ABC abc,
      MethodBody body,
      AVM2Instruction inlineIns)
      throws InterruptedException {
    AVM2Code code = body.getCode();
    if (code.code.isEmpty()) {
      return false;
    }

    Map<Integer, Object> staticRegs = new HashMap<>();
    if (inlineIns != null && inlineIns.definition instanceof GetLocalTypeIns) {
      staticRegs.put(
          ((GetLocalTypeIns) inlineIns.definition).getRegisterId(inlineIns), Undefined.INSTANCE);
    }

    if (code.code.isEmpty()) {
      return false;
    }

    FixItemCounterStack stack = new FixItemCounterStack();
    LocalDataArea localData = new LocalDataArea();
    localData.operandStack = stack;

    int localReservedCount = body.getLocalReservedCount();
    for (int i = 0; i < code.code.size(); i++) {
      if (Thread.currentThread().isInterrupted()) {
        throw new InterruptedException();
      }

      localData.clear();
      initLocalRegs(localData, localReservedCount, body.max_regs, i == 0);

      if (executeInstructions(
          staticRegs, body, abc, code, localData, i, code.code.size() - 1, null, inlineIns)) {
        code.removeDeadCode(body);
        i--;
      }
    }

    return false;
  }
 @Override
 public void avm2CodeRemoveTraps(
     String path,
     int classIndex,
     boolean isStatic,
     int scriptIndex,
     ABC abc,
     Trait trait,
     int methodInfo,
     MethodBody body)
     throws InterruptedException {
   AVM2Code code = body.getCode();
   code.removeDeadCode(body);
   removeObfuscationIfs(classIndex, isStatic, scriptIndex, abc, body, null);
   removeZeroJumps(code, body);
 }
  @Override
  public void avm2CodeRemoveTraps(
      String path,
      int classIndex,
      boolean isStatic,
      int scriptIndex,
      ABC abc,
      Trait trait,
      int methodInfo,
      MethodBody body)
      throws InterruptedException {
    // System.err.println("regdeo:" + path);

    MethodBody originalBody = body;
    body.getCode().removeDeadCode(body);
    Set<Integer> ignoredRegs = new HashSet<>();

    int localReservedCount = body.getLocalReservedCount();
    for (int i = 0; i < localReservedCount; i++) {
      ignoredRegs.add(i);
    }

    int setReg = 0;
    List<Integer> listedRegs = new ArrayList<>();
    List<MethodBody> listedLastBodies = new ArrayList<>();
    Set<Integer> ignoredRegGets = new HashSet<>();
    Reference<AVM2Instruction> assignmentRef = new Reference<>(null);

    while (setReg > -1) {
      if (Thread.currentThread().isInterrupted()) {
        throw new InterruptedException();
      }

      MethodBody bodybefore = body;
      body = bodybefore.clone();
      setReg =
          getFirstRegisterSetter(
              assignmentRef,
              classIndex,
              isStatic,
              scriptIndex,
              abc,
              body,
              ignoredRegs,
              ignoredRegGets);
      // System.err.println("setreg " + setReg + " ass:" + assignmentRef.getVal());
      if (setReg < 0) {
        break;
      }

      // if there is second assignment
      if (listedRegs.contains(setReg)) {
        // System.err.println("second assignment of loc" + setReg + ", ignoring");
        int lindex = listedRegs.indexOf(setReg);
        body = listedLastBodies.get(lindex); // switch to body before
        ignoredRegs.add(setReg); // this is not obfuscated
        for (int i = listedRegs.size() - 1; i >= lindex; i--) {
          int r = listedRegs.get(i);
          listedRegs.remove(i);
          listedLastBodies.remove(i);
          ignoredRegGets.remove(r);
        }
        continue;
      }

      AVM2Instruction assignment = assignmentRef.getVal();
      InstructionDefinition def = assignment.definition;
      if ((def instanceof SetLocalTypeIns)
          || (def instanceof GetLocalTypeIns /*First usage -> value undefined*/)) {
        super.removeObfuscationIfs(
            classIndex, isStatic, scriptIndex, abc, body, Arrays.asList(assignment));
      }

      if (def instanceof GetLocalTypeIns) {
        ignoredRegGets.add(setReg);
      }

      listedRegs.add(setReg);
      listedLastBodies.add(bodybefore);
    }

    body.getCode().removeDeadCode(body);

    originalBody.exceptions = body.exceptions;
    originalBody.setCode(body.getCode());
  }
  private int visitCode(
      Reference<AVM2Instruction> assignment,
      Set<Integer> visited,
      TranslateStack stack,
      int classIndex,
      boolean isStatic,
      MethodBody body,
      int scriptIndex,
      ABC abc,
      AVM2Code code,
      int idx,
      int endIdx,
      Set<Integer> ignored,
      Set<Integer> ignoredGets)
      throws InterruptedException {
    List<GraphTargetItem> output = new ArrayList<>();
    AVM2LocalData localData =
        newLocalData(scriptIndex, abc, abc.constants, body, isStatic, classIndex);
    initLocalRegs(localData, body.getLocalReservedCount(), body.max_regs);
    localData.localRegs.put(0, new NullAVM2Item(null, null)); // this

    List<Integer> toVisit = new ArrayList<>();
    toVisit.add(idx);
    List<TranslateStack> toVisitStacks = new ArrayList<>();
    toVisitStacks.add(stack);
    outer:
    while (!toVisit.isEmpty()) {
      if (Thread.currentThread().isInterrupted()) {
        throw new InterruptedException();
      }

      idx = toVisit.remove(0);
      stack = toVisitStacks.remove(0);

      while (true) {
        if (idx > endIdx) {
          break;
        }
        if (visited.contains(idx)) {
          break;
        }
        visited.add(idx);

        AVM2Instruction ins = code.code.get(idx);
        InstructionDefinition def = ins.definition;
        // System.err.println("" + idx + ": " + ins + " stack:" + stack.size());

        // do not throw EmptyStackException, much faster
        int requiredStackSize = ins.getStackPopCount(localData);
        if (stack.size() < requiredStackSize) {
          continue outer;
        }

        ins.translate(localData, stack, output, Graph.SOP_USE_STATIC, "");
        if (def instanceof SetLocalTypeIns) {
          int regId = ((SetLocalTypeIns) def).getRegisterId(ins);
          if (!ignored.contains(regId)) {
            assignment.setVal(ins);
            return regId;
          }
        } else if (def instanceof GetLocalTypeIns) {
          int regId = ((GetLocalTypeIns) def).getRegisterId(ins);
          if (!ignored.contains(regId) && !ignoredGets.contains(regId)) {
            assignment.setVal(ins);
            return regId;
          }
        } else if (!(def instanceof KillIns) && !(def instanceof DebugIns)) {
          // can be inclocal, declocal, hasnext...
          for (int p = 0; p < ins.definition.operands.length; p++) {
            int op = ins.definition.operands[p];
            if (op == AVM2Code.DAT_LOCAL_REG_INDEX) {
              int regId = ins.operands[p];
              if (!ignored.contains(regId)) {
                assignment.setVal(ins);
                return regId;
              }
            }
          }
        }

        idx++;

        if (ins.definition instanceof JumpIns) {

          long address = ins.offset + ins.getBytesLength() + ins.operands[0];
          idx = code.adr2pos(address); // code.indexOf(code.getByAddress(address));
          if (idx == -1) {
            throw new TranslateException("Jump target not found: " + address);
          }
        }

        if (ins.isBranch()) {
          List<Integer> branches =
              ins.getBranches(
                  new GraphSource() {

                    @Override
                    public int size() {
                      throw new UnsupportedOperationException(
                          "Not supported yet."); // To change body of generated methods, choose
                      // Tools | Templates.
                    }

                    @Override
                    public GraphSourceItem get(int pos) {
                      throw new UnsupportedOperationException(
                          "Not supported yet."); // To change body of generated methods, choose
                      // Tools | Templates.
                    }

                    @Override
                    public boolean isEmpty() {
                      throw new UnsupportedOperationException(
                          "Not supported yet."); // To change body of generated methods, choose
                      // Tools | Templates.
                    }

                    @Override
                    public List<GraphTargetItem> translatePart(
                        GraphPart part,
                        BaseLocalData localData,
                        TranslateStack stack,
                        int start,
                        int end,
                        int staticOperation,
                        String path)
                        throws InterruptedException {
                      throw new UnsupportedOperationException(
                          "Not supported yet."); // To change body of generated methods, choose
                      // Tools | Templates.
                    }

                    @Override
                    public int adr2pos(long adr) {
                      return code.adr2pos(adr);
                    }

                    @Override
                    public long pos2adr(int pos) {
                      return code.pos2adr(pos);
                    }
                  });
          idx = branches.get(0);
          for (int n = 1; n < branches.size(); n++) {
            // visitCode(visited, (TranslateStack) stack.clone(), classIndex, isStatic, body,
            // scriptIndex, abc, code, branches.get(n), endIdx, result);
            int nidx = branches.get(n);
            if (visited.contains(nidx)) {
              continue;
            }
            toVisit.add(nidx);
            toVisitStacks.add((TranslateStack) stack.clone());
          }
        }
        /*if (ins.definition instanceof IfTypeIns) {
        long address = ins.offset + ins.getBytes().length + ins.operands[0];
        int newIdx = code.adr2pos(address);
        if (newIdx == -1) {
        throw new TranslateException("If target not found: " + address);
        }
        visitCode(visited, (TranslateStack) stack.clone(), classIndex, isStatic, body, scriptIndex, abc, code, newIdx, endIdx, result);
        }*/

        if (ins.definition instanceof ReturnValueIns) {
          break;
        }

        if (ins.definition instanceof ThrowIns) {
          break;
        }

        if (ins.definition instanceof ReturnVoidIns) {
          break;
        }
      }
    }
    return -1;
  }
  private void addTraitButtonActionPerformed(ActionEvent evt) {
    int class_index = decompiledTextArea.getClassIndex();
    if (class_index < 0) {
      return;
    }
    if (newTraitDialog == null) {
      newTraitDialog = new NewTraitDialog();
    }
    int void_type =
        abc.constants.getPublicQnameId(
            "void", true); // abc.constants.forceGetMultinameId(new Multiname(Multiname.QNAME,
    // abc.constants.forceGetStringId("void"), abc.constants.forceGetNamespaceId(new
    // Namespace(Namespace.KIND_PACKAGE, abc.constants.forceGetStringId("")), 0), -1,
    // -1, new ArrayList<Integer>()));
    int int_type =
        abc.constants.getPublicQnameId(
            "int", true); // abc.constants.forceGetMultinameId(new Multiname(Multiname.QNAME,
    // abc.constants.forceGetStringId("int"), abc.constants.forceGetNamespaceId(new
    // Namespace(Namespace.KIND_PACKAGE, abc.constants.forceGetStringId("")), 0), -1,
    // -1, new ArrayList<Integer>()));

    Trait t = null;
    int kind;
    int nskind;
    String name = null;
    boolean isStatic;
    Multiname m;

    boolean again = false;
    loopm:
    do {
      if (again) {
        View.showMessageDialog(
            null,
            AppStrings.translate("error.trait.exists").replace("%name%", name),
            AppStrings.translate("error"),
            JOptionPane.ERROR_MESSAGE);
      }
      again = false;
      if (newTraitDialog.showDialog() != AppDialog.OK_OPTION) {
        return;
      }
      kind = newTraitDialog.getTraitType();
      nskind = newTraitDialog.getNamespaceKind();
      name = newTraitDialog.getTraitName();
      isStatic = newTraitDialog.getStatic();
      m =
          new Multiname(
              Multiname.QNAME,
              abc.constants.getStringId(name, true),
              abc.constants.getNamespaceId(
                  new Namespace(nskind, abc.constants.getStringId("", true)), 0, true),
              0,
              0,
              new ArrayList<>());
      int mid = abc.constants.getMultinameId(m);
      if (mid == 0) {
        break;
      }
      for (Trait tr : abc.class_info.get(class_index).static_traits.traits) {
        if (tr.name_index == mid) {
          again = true;
          break;
        }
      }

      for (Trait tr : abc.instance_info.get(class_index).instance_traits.traits) {
        if (tr.name_index == mid) {
          again = true;
          break;
        }
      }
    } while (again);
    switch (kind) {
      case Trait.TRAIT_GETTER:
      case Trait.TRAIT_SETTER:
      case Trait.TRAIT_METHOD:
        TraitMethodGetterSetter tm = new TraitMethodGetterSetter();
        MethodInfo mi =
            new MethodInfo(
                new int[0],
                void_type,
                abc.constants.getStringId(name, true),
                0,
                new ValueKind[0],
                new int[0]);
        int method_info = abc.addMethodInfo(mi);
        tm.method_info = method_info;
        MethodBody body = new MethodBody();
        body.method_info = method_info;
        body.init_scope_depth = 1;
        body.max_regs = 1;
        body.max_scope_depth = 1;
        body.max_stack = 1;
        body.exceptions = new ABCException[0];
        AVM2Code code = new AVM2Code();
        code.code.add(new AVM2Instruction(0, new GetLocal0Ins(), new int[0]));
        code.code.add(new AVM2Instruction(0, new PushScopeIns(), new int[0]));
        code.code.add(new AVM2Instruction(0, new ReturnVoidIns(), new int[0]));
        body.setCode(code);
        Traits traits = new Traits();
        traits.traits = new ArrayList<>();
        body.traits = traits;
        abc.addMethodBody(body);
        mi.setBody(body);
        t = tm;
        break;
      case Trait.TRAIT_SLOT:
      case Trait.TRAIT_CONST:
        TraitSlotConst ts = new TraitSlotConst();
        ts.type_index = int_type;
        ts.value_kind = ValueKind.CONSTANT_Int;
        ts.value_index = abc.constants.getIntId(0, true);
        t = ts;
        break;
    }
    if (t != null) {
      t.kindType = kind;
      t.name_index = abc.constants.getMultinameId(m, true);
      int traitId;
      if (isStatic) {
        traitId = abc.class_info.get(class_index).static_traits.addTrait(t);
      } else {
        traitId =
            abc.class_info.get(class_index).static_traits.traits.size()
                + abc.instance_info.get(class_index).instance_traits.addTrait(t);
      }
      reload();
      decompiledTextArea.gotoTrait(traitId);
    }
  }