private MethodNode createForwardMethod()

in TransformCore/src/main/java/com/facebook/ads/injkit/benchmark/BenchmarkInjector.java [294:461]


  private MethodNode createForwardMethod(
      String name, ClassNode owner, MethodNode destination, BenchmarkMetrics metrics) {
    MethodNode newMethod =
        new MethodNode(
            destination.access,
            name,
            destination.desc,
            destination.signature,
            toArray(destination.exceptions));

    InsnList instructions = new InsnList();

    // Get current nano time and save because we may have an exception.
    int tempVar =
        Type.getArgumentTypes(newMethod.desc).length
            + ((destination.access & Opcodes.ACC_STATIC) == 0 ? 1 : 0);

    instructions.add(
        new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false));
    instructions.add(new VarInsnNode(Opcodes.LSTORE, tempVar));

    // Mark method call start.
    LabelNode methodStartLabel = new LabelNode(new Label());
    instructions.add(methodStartLabel);

    // Call the forwarded method.
    Type[] arguments = Type.getArgumentTypes(destination.desc);
    int varPos = 0;
    if ((destination.access & Opcodes.ACC_STATIC) == 0) {
      varPos++;
      // Load "this".
      instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
    }

    for (Type argument : arguments) {
      instructions.add(new VarInsnNode(argument.getOpcode(Opcodes.ILOAD), varPos));
      varPos += argument.getSize();
    }

    if ((destination.access & Opcodes.ACC_STATIC) != 0) {
      instructions.add(
          new MethodInsnNode(
              Opcodes.INVOKESTATIC, owner.name, destination.name, destination.desc, false));
    } else {
      instructions.add(
          new MethodInsnNode(
              Opcodes.INVOKEVIRTUAL, owner.name, destination.name, destination.desc, false));
    }

    LabelNode methodCallEndLabel = new LabelNode(new Label());
    instructions.add(methodCallEndLabel);

    // At this point stack contains the return value. Add the parameters to notify. These will
    // be the same for all method callbacks.
    instructions.add(new LdcInsnNode(owner.name.replace('/', '.')));
    instructions.add(new LdcInsnNode(name));
    instructions.add(new LdcInsnNode(newMethod.desc));

    // Compute time difference in millis.
    instructions.add(new VarInsnNode(Opcodes.LLOAD, tempVar));
    instructions.add(
        new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false));
    instructions.add(new InsnNode(Opcodes.LSUB));
    instructions.add(new InsnNode(Opcodes.LNEG));

    // Handle failure.
    LabelNode failureHandler = new LabelNode();
    long failNanos = ((long) metrics.failAtMillis) * 1_000_000L;
    instructions.add(new InsnNode(Opcodes.DUP2));
    instructions.add(new LdcInsnNode(failNanos));
    instructions.add(new InsnNode(Opcodes.LCMP));
    instructions.add(new JumpInsnNode(Opcodes.IFGE, failureHandler));

    // Handle warning.
    LabelNode warningHandler = new LabelNode();
    long warnNanos = ((long) metrics.warnAtMillis) * 1_000_000L;
    instructions.add(new InsnNode(Opcodes.DUP2));
    instructions.add(new LdcInsnNode(warnNanos));
    instructions.add(new InsnNode(Opcodes.LCMP));
    instructions.add(new JumpInsnNode(Opcodes.IFGE, warningHandler));

    // Otherwise compute text.
    instructions.add(
        new MethodInsnNode(
            Opcodes.INVOKESTATIC,
            benchmarkReceiverClassIName,
            EXECUTED_SUCCESS_METHOD_NAME,
            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V",
            false));

    // Return the return value, if any.
    LabelNode endNode = new LabelNode();
    instructions.add(endNode);
    instructions.add(new InsnNode(Type.getReturnType(destination.desc).getOpcode(Opcodes.IRETURN)));

    // Failure handler.
    instructions.add(failureHandler);
    instructions.add(new LdcInsnNode(failNanos));
    instructions.add(
        new MethodInsnNode(
            Opcodes.INVOKESTATIC,
            benchmarkReceiverClassIName,
            EXECUTED_FAILED_METHOD_NAME,
            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JJ)V",
            false));
    instructions.add(new JumpInsnNode(Opcodes.GOTO, endNode));

    // Warning handler.
    instructions.add(warningHandler);
    instructions.add(new LdcInsnNode(warnNanos));
    instructions.add(
        new MethodInsnNode(
            Opcodes.INVOKESTATIC,
            benchmarkReceiverClassIName,
            EXECUTED_WARNING_METHOD_NAME,
            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JJ)V",
            false));
    instructions.add(new JumpInsnNode(Opcodes.GOTO, endNode));

    // Add catch handler.
    LabelNode catchLabel = new LabelNode(new Label());
    instructions.add(catchLabel);

    // Stack contains [exception]. Add parameters before exception, but keep a copy of the
    // exception on the bottom of the stack for later rethrowing.
    instructions.add(new InsnNode(Opcodes.DUP));
    instructions.add(new LdcInsnNode(owner.name.replace('/', '.')));
    instructions.add(new InsnNode(Opcodes.SWAP));
    instructions.add(new LdcInsnNode(name));
    instructions.add(new InsnNode(Opcodes.SWAP));
    instructions.add(new LdcInsnNode(newMethod.desc));
    instructions.add(new InsnNode(Opcodes.SWAP));

    // Compute time difference in millis.
    instructions.add(new VarInsnNode(Opcodes.LLOAD, tempVar));
    instructions.add(
        new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false));
    instructions.add(new InsnNode(Opcodes.LSUB));
    instructions.add(new InsnNode(Opcodes.LNEG));

    // Call the method to report the failure.
    instructions.add(
        new MethodInsnNode(
            Opcodes.INVOKESTATIC,
            benchmarkReceiverClassIName,
            EXECUTED_THROWN_METHOD_NAME,
            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;J)V",
            false));

    // Rethrow the exception.
    instructions.add(new InsnNode(Opcodes.ATHROW));

    // Mark the end of the catch block.
    LabelNode endCatchLabel = new LabelNode(new Label());
    instructions.add(endCatchLabel);

    if (newMethod.tryCatchBlocks == null) {
      newMethod.tryCatchBlocks = new ArrayList<>();
    }

    newMethod.tryCatchBlocks.add(
        new TryCatchBlockNode(
            methodStartLabel, methodCallEndLabel, catchLabel, "java/lang/Throwable"));

    newMethod.instructions = instructions;

    return newMethod;
  }