content: package org.aion.avm.core.arraywrapping;
  
  import a.BooleanArray;
  import a.ByteArray;
  import org.aion.avm.core.rejection.RejectedClassException;
  import org.aion.avm.core.types.ClassHierarchy;
  import org.aion.avm.utilities.Utilities;
  
  import i.IObjectArray;
  import i.RuntimeAssertionError;
  import org.objectweb.asm.MethodVisitor;
  import org.objectweb.asm.Opcodes;
  import org.objectweb.asm.tree.AbstractInsnNode;
  import org.objectweb.asm.tree.MethodInsnNode;
  import org.objectweb.asm.tree.MethodNode;
  import org.objectweb.asm.tree.TypeInsnNode;
  import org.objectweb.asm.tree.analysis.Analyzer;
  import org.objectweb.asm.tree.analysis.AnalyzerException;
  import org.objectweb.asm.tree.analysis.BasicValue;
  import org.objectweb.asm.tree.analysis.Frame;
  
  
  /**
   * A method visitor that replace access bytecode
   *
   * AALOAD -> INVOKEINTERFACE + CHECKCAST
   * AASTORE -> CHECKCAST + INVOKEINTERFACE
   * BALOAD -> INVOKEVIRTUAL
   * BASTORE -> INVOKEVIRTUAL
   * 
   * Opcodes associated with the other types are handled in {@link org.aion.avm.core.arraywrapping.ArraysWithKnownTypesClassVisitor}.
   * 
   * Static analysis is required with {@link org.aion.avm.core.arraywrapping.ArrayWrappingInterpreter} so it can perform
   * type inference.
   * 
   * Note that this static analysis is the reason why only these bytecodes are handled here - they are the only ones which
   * require additional type knowledge not associated 1-to-1 with the specific opcode.
   */
  class ArraysRequiringAnalysisMethodNode extends MethodNode {
      private final ClassHierarchy hierarchy;
  
      private String className;
      private MethodVisitor mv;
  
      public ArraysRequiringAnalysisMethodNode(final int access,
                                           final String name,
                                           final String descriptor,
                                           final String signature,
                                           final String[] exceptions,
                                           MethodVisitor mv,
                                           String className,
                                           ClassHierarchy hierarchy)
      {
          super(Opcodes.ASM6, access, name, descriptor, signature, exceptions);
          this.className = className;
          this.mv = mv;
          this.hierarchy = hierarchy;
      }
  
      @Override
      public void visitEnd(){
  
          Frame<BasicValue>[] frames = null;
          if (instructions.size() > 0) {
              try{
                  Analyzer<BasicValue> analyzer = new Analyzer<>(new ArrayWrappingInterpreter(this.hierarchy));
                  analyzer.analyze(this.className, this);
                  frames = analyzer.getFrames();
              }catch (AnalyzerException e){
                  // If we fail to run the analyzer, that is a serious internal error. It might be an actual bug
                  // in the AVM, or it might be the result of corrupt input.
                  // Since we're not sure, we "blame" the contract, and throw a Rejection Error.
                  throw new RejectedClassException("Something went wrong when trying to analyze a wrapped array: " + e.getMessage());
              }
          }
  
          AbstractInsnNode[] insns = instructions.toArray();
  
          if (null != insns && null != frames) {
              RuntimeAssertionError.assertTrue(insns.length == frames.length);
          }
  
          for(int i = 0; i < insns.length; i++) {
              AbstractInsnNode insn = insns[i];
              Frame<BasicValue> f = frames[i];
  
              // We only handle aaload here since aastore is generic
              // the log is the following
              // check instruction -> check stack map frame -> replace instruction with invokeV and checkcast
              if (insn.getOpcode() == Opcodes.AALOAD) {
                  //we pop the second slot on stack
                  f.pop();
                  BasicValue t = (BasicValue) (f.pop());
                  String targetDesc = t.toString();
                  String elementType = ArrayNameMapper.getElementType(targetDesc);
  
                  MethodInsnNode invokeVNode =
                      new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                              Utilities.fulllyQualifiedNameToInternalName(IObjectArray.class.getName()),
                                          "get",
                                          "(I)Ljava/lang/Object;",
                                          true);
  
                  TypeInsnNode checkcastNode =
                      new TypeInsnNode(Opcodes.CHECKCAST,
                                          elementType);
  
                  // Insert indicate reverse order, we want
                  // invokevirtual -> checkcast here
                  instructions.insert(insn, checkcastNode);
                  instructions.insert(insn, invokeVNode);
                  instructions.remove(insn);
              }
  
              if (insn.getOpcode() == Opcodes.AASTORE) {
                  //we pop the third slot on stack
                  f.pop();
                  f.pop();
                  BasicValue t = (BasicValue) (f.pop());
                  String targetDesc = t.toString();
                  String elementType = ArrayNameMapper.getElementType(targetDesc);
  
                  MethodInsnNode invokeVNode =
                          new MethodInsnNode(Opcodes.INVOKEINTERFACE,
                                  Utilities.fulllyQualifiedNameToInternalName(IObjectArray.class.getName()),
                                  "set",
                                  "(ILjava/lang/Object;)V",
                                  true);
  
                  TypeInsnNode checkcastNode =
                          new TypeInsnNode(Opcodes.CHECKCAST,
                                  elementType);
  
                  // Insert indicate reverse order, we want
                  // checkcast -> invokevirtual here
                  instructions.insert(insn, invokeVNode);
                  instructions.insert(insn, checkcastNode);
                  instructions.remove(insn);
              }
  
              if (insn.getOpcode() == Opcodes.BALOAD) {
                  f.pop();
                  BasicValue t = f.pop();
                  String targetDesc = t.toString();
  
                  MethodInsnNode invokeVNode;
                  if (targetDesc.equals("[Z")) {
                          invokeVNode = new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                          Utilities.fulllyQualifiedNameToInternalName(BooleanArray.class.getName()),
                                          "get",
                                          "(I)Z",
                                          false);
                  } else {
                          invokeVNode = new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                          Utilities.fulllyQualifiedNameToInternalName(ByteArray.class.getName()),
                                          "get",
                                          "(I)B",
                                          false);
                  }
  
                  instructions.insert(insn, invokeVNode);
                  instructions.remove(insn);
              }
  
              if (insn.getOpcode() == Opcodes.BASTORE) {
                  f.pop();
                  f.pop();
                  BasicValue t = f.pop();
                  String targetDesc = t.toString();
  
                  MethodInsnNode invokeVNode;
                  if (targetDesc.equals("[Z")) {
                          invokeVNode = new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                          Utilities.fulllyQualifiedNameToInternalName(BooleanArray.class.getName()),
                                          "set",
                                          "(IZ)V",
                                          false);
                      } else {
                          invokeVNode = new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
                                          Utilities.fulllyQualifiedNameToInternalName(ByteArray.class.getName()),
                                          "set",
                                          "(IB)V",
                                          false);
                      }
                  instructions.insert(insn, invokeVNode);
                  instructions.remove(insn);
              }
          }
  
          accept(mv);
      }
  }
  