content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.apache.cassandra.simulator.asm;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.util.Arrays;
  import java.util.List;
  import java.util.function.BiConsumer;
  import java.util.function.Consumer;
  
  import org.objectweb.asm.AnnotationVisitor;
  import org.objectweb.asm.Label;
  import org.objectweb.asm.MethodVisitor;
  import org.objectweb.asm.Opcodes;
  import org.objectweb.asm.Type;
  import org.objectweb.asm.tree.AbstractInsnNode;
  
  import static org.apache.cassandra.simulator.asm.InterceptClasses.BYTECODE_VERSION;
  import static org.objectweb.asm.Opcodes.ATHROW;
  import static org.objectweb.asm.Opcodes.F_SAME1;
  import static org.objectweb.asm.Opcodes.INVOKESTATIC;
  
  public class Utils
  {
      public static String toInternalName(Class<?> clazz)
      {
          return toInternalName(clazz.getName());
      }
  
      public static String toInternalName(String className)
      {
          return className.replace('.', '/');
      }
  
      public static String toPath(Class<?> clazz)
      {
          return toInternalName(clazz) + ".class";
      }
  
      public static byte[] readDefinition(Class<?> clazz) throws IOException
      {
          return readDefinition(toPath(clazz));
      }
  
      public static byte[] readDefinition(String path) throws IOException
      {
          byte[] bytes = new byte[1024];
          try (InputStream in = ClassLoader.getSystemResourceAsStream(path))
          {
              int count = 0;
              while (true)
              {
                  int add = in.read(bytes, count, bytes.length - count);
                  if (add < 0)
                      break;
                  if (add == 0)
                      bytes = Arrays.copyOf(bytes, bytes.length * 2);
                  count += add;
              }
              return Arrays.copyOf(bytes, count);
          }
      }
  
      /**
       * Generate a proxy method call, i.e. one whose only job is forwarding the parameters to a different method
       * (and perhaps within a superclass, or another class entirely if static) with the same signature but perhaps
       * different properties.
       */
      private static long visitProxyCall(MethodVisitor visitor, String calleeClassName, String calleeMethodName, String descriptor, int access, boolean isInstanceMethod, boolean isInterface)
      {
          Type[] argTypes = Type.getArgumentTypes(descriptor);
          Type returnType = Type.getReturnType(descriptor);
  
          int stackSize = argTypes.length;
          int localsSize = 0;
          if (isInstanceMethod)
          {
              visitor.visitIntInsn(Opcodes.ALOAD, 0);
              localsSize += 2;
              stackSize += 1;
          }
  
          int i = 1;
          for (Type type : argTypes)
          {
              int opcode;
              switch (type.getDescriptor().charAt(0))
              {
                  case 'L':
                  case '[':
                      opcode = Opcodes.ALOAD;
                      localsSize += 1;
                      break;
                  case 'J':
                      opcode = Opcodes.LLOAD;
                      localsSize += 2;
                      break;
                  case 'D':
                      opcode = Opcodes.DLOAD;
                      localsSize += 2;
                      break;
                  case 'F':
                      opcode = Opcodes.FLOAD;
                      localsSize += 1;
                      break;
                  default:
                      opcode = Opcodes.ILOAD;
                      localsSize += 1;
                      break;
              }
              visitor.visitIntInsn(opcode, i++);
          }
  
          int returnCode;
          switch (returnType.getDescriptor().charAt(0))
          {
              case 'L':
              case '[':
                  returnCode = Opcodes.ARETURN;
                  localsSize = Math.max(localsSize, 1);
                  break;
              case 'J':
                  returnCode = Opcodes.LRETURN;
                  localsSize = Math.max(localsSize, 2);
                  break;
              case 'D':
                  returnCode = Opcodes.DRETURN;
                  localsSize = Math.max(localsSize, 2);
                  break;
              case 'F':
                  returnCode = Opcodes.FRETURN;
                  localsSize = Math.max(localsSize, 1);
                  break;
              case 'V':
                  returnCode = Opcodes.RETURN;
                  break;
              default:
                  returnCode = Opcodes.IRETURN;
                  localsSize = Math.max(localsSize, 1);
                  break;
          }
  
          int invokeCode;
          if (isInstanceMethod && (access & Opcodes.ACC_PRIVATE) != 0 || calleeMethodName.equals("<init>")) invokeCode = Opcodes.INVOKESPECIAL;
          else if (isInstanceMethod) invokeCode = Opcodes.INVOKEVIRTUAL;
          else invokeCode = Opcodes.INVOKESTATIC;
  
          visitor.visitMethodInsn(invokeCode, calleeClassName, calleeMethodName, descriptor, isInterface);
  
          return localsSize | (((long)stackSize) << 28) | (((long) returnCode) << 56);
      }
  
      /**
       * Generate a proxy method call, i.e. one whose only job is forwarding the parameters to a different method
       * (and perhaps within a superclass, or another class entirely if static) with the same signature but perhaps
       * different properties.
       */
      public static void generateProxyCall(MethodVisitor visitor, String calleeClassName, String calleeMethodName, String descriptor, int access, boolean isInstanceMethod, boolean isInterface)
      {
          Label start = new Label(), end = new Label();
          visitor.visitLabel(start);
  
          long sizesAndReturnCode = visitProxyCall(visitor, calleeClassName, calleeMethodName, descriptor, access, isInstanceMethod, isInterface);
          visitor.visitLabel(end);
          visitor.visitInsn((int)(sizesAndReturnCode >>> 56) & 0xff);
          visitor.visitMaxs((int)(sizesAndReturnCode >>> 28) & 0xfffffff, (int)(sizesAndReturnCode & 0xfffffff));
          visitor.visitEnd();
      }
  
      /**
       * Generate a proxy method call, i.e. one whose only job is forwarding the parameters to a different method
       * (and perhaps within a superclass, or another class entirely if static) with the same signature but perhaps
       * different properties.
       *
       * Invoke within a try/catch block, invoking the provided setup/cleanup instructions.
       * As designed these must not assign any local variables, and the catch block must be exception free.
       */
      public static void generateTryFinallyProxyCall(MethodVisitor visitor, String calleeClassName, String calleeMethodName, String descriptor, int access, boolean isInstanceMethod, boolean isInterface,
                                                     List<AbstractInsnNode> setup, List<AbstractInsnNode> cleanup)
      {
          Label startMethod = new Label(), startTry = new Label(), endTry = new Label(), startCatch = new Label(), endMethod = new Label();
          visitor.visitLabel(startMethod);
          visitor.visitTryCatchBlock(startTry, endTry, startCatch, null);
          setup.forEach(i -> i.accept(visitor));
          visitor.visitLabel(startTry);
          long sizesAndReturnCode = visitProxyCall(visitor, calleeClassName, calleeMethodName, descriptor, access, isInstanceMethod, isInterface);
          int returnCode = (int)(sizesAndReturnCode >>> 56) & 0xff;
          visitor.visitLabel(endTry);
          cleanup.forEach(i -> i.accept(visitor));
          visitor.visitInsn(returnCode);
          visitor.visitLabel(startCatch);
          visitor.visitFrame(F_SAME1, 0, null, 1, new Object[] { "java/lang/Throwable" });
          cleanup.forEach(i -> i.accept(visitor));
          visitor.visitInsn(ATHROW);
          visitor.visitLabel(endMethod);
          if (isInstanceMethod)
              visitor.visitLocalVariable("this", "L" + calleeClassName + ';', null, startMethod, endMethod, 0);
          visitor.visitMaxs((int)(sizesAndReturnCode >>> 28) & 0xfffffff, (int)(sizesAndReturnCode & 0xfffffff));
          visitor.visitEnd();
      }
  
      public static AnnotationVisitor checkForSimulationAnnotations(int api, String descriptor, AnnotationVisitor wrap, BiConsumer<Flag, Boolean> annotations)
      {
          if (!descriptor.equals("Lorg/apache/cassandra/utils/Simulate;"))
              return wrap;
  
          return new AnnotationVisitor(api, wrap)
          {
              @Override
              public AnnotationVisitor visitArray(String name)
              {
                  if (!name.equals("with") && !name.equals("without"))
                      return super.visitArray(name);
  
                  boolean add = name.equals("with");
                  return new AnnotationVisitor(api, super.visitArray(name))
                  {
                      @Override
                      public void visitEnum(String name, String descriptor, String value)
                      {
                          super.visitEnum(name, descriptor, value);
                          if (descriptor.equals("Lorg/apache/cassandra/utils/Simulate$With;"))
                              annotations.accept(Flag.valueOf(value), add);
                      }
                  };
              }
          };
      }
  
      public static MethodVisitor deterministicToString(MethodVisitor wrap)
      {
          return new MethodVisitor(BYTECODE_VERSION, wrap)
          {
              @Override
              public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface)
              {
                  if (name.equals("hashCode") && owner.equals("java/lang/Object"))
                  {
                      super.visitMethodInsn(INVOKESTATIC, "java/lang/System", "identityHashCode", "(Ljava/lang/Object;)I", false);
                  }
                  else
                  {
                      super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                  }
              }
          };
      }
  
      public static void visitEachRefType(String descriptor, Consumer<String> forEach)
      {
          Type[] argTypes = Type.getArgumentTypes(descriptor);
          Type retType = Type.getReturnType(descriptor);
          for (Type argType : argTypes)
              visitIfRefType(argType.getDescriptor(), forEach);
          visitIfRefType(retType.getDescriptor(), forEach);
      }
  
      public static void visitIfRefType(String descriptor, Consumer<String> forEach)
      {
          if (descriptor.charAt(0) != '[' && descriptor.charAt(descriptor.length() - 1) != ';')
          {
              if (descriptor.length() > 1)
                  forEach.accept(descriptor);
          }
          else
          {
              int i = 1;
              while (descriptor.charAt(i) == '[') ++i;
              if (descriptor.charAt(i) == 'L')
                  forEach.accept(descriptor.substring(i + 1, descriptor.length() - 1));
          }
      }
  
      public static String descriptorToClassName(String desc)
      {
          // samples: "Ljdk/internal/misc/Unsafe;", "Lsun/misc/Unsafe;"
          if (!(desc.startsWith("L") && desc.endsWith(";")))
              throw new IllegalArgumentException("Unable to parse descriptor: " + desc);
          return desc.substring(1, desc.length() - 1);
      }
  }
  