content: /*
   * Copyright The OpenTelemetry Authors
   * SPDX-License-Identifier: Apache-2.0
   */
  
  package io.opentelemetry.javaagent.tooling.bytebuddy;
  
  import io.opentelemetry.javaagent.bootstrap.ExceptionLogger;
  import net.bytebuddy.ClassFileVersion;
  import net.bytebuddy.asm.Advice.ExceptionHandler;
  import net.bytebuddy.implementation.Implementation;
  import net.bytebuddy.implementation.bytecode.StackManipulation;
  import org.objectweb.asm.Label;
  import org.objectweb.asm.MethodVisitor;
  import org.objectweb.asm.Opcodes;
  
  public final class ExceptionHandlers {
    // Bootstrap ExceptionLogger.class will always be resolvable, so we'll use it in the log name
    private static final String LOGGER_NAME = ExceptionLogger.class.getName().replace('.', '/');
  
    private static final ExceptionHandler EXCEPTION_STACK_HANDLER =
        new ExceptionHandler.Simple(
            new StackManipulation() {
              // Pops one Throwable off the stack. Maxes the stack to at least 2 (throwable, string).
              private final StackManipulation.Size size = new StackManipulation.Size(-1, 2);
  
              @Override
              public boolean isValid() {
                return true;
              }
  
              @Override
              public StackManipulation.Size apply(MethodVisitor mv, Implementation.Context context) {
                String name = context.getInstrumentedType().getName();
  
                // writes the following bytecode:
                // try {
                //   ExceptionLogger.logSuppressedError("exception in instrumentation", t);
                // } catch (Throwable t2) {
                // }
                Label logStart = new Label();
                Label logEnd = new Label();
                Label eatException = new Label();
                Label handlerExit = new Label();
  
                // Frames are only meaningful for class files in version 6 or later.
                boolean frames = context.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V6);
  
                mv.visitTryCatchBlock(logStart, logEnd, eatException, "java/lang/Throwable");
  
                // stack: (top) throwable
                mv.visitLabel(logStart);
                mv.visitLdcInsn("Failed to handle exception in instrumentation for " + name);
                mv.visitInsn(Opcodes.SWAP); // stack: (top) throwable,string
  
                mv.visitMethodInsn(
                    Opcodes.INVOKESTATIC,
                    LOGGER_NAME,
                    "logSuppressedError",
                    "(Ljava/lang/String;Ljava/lang/Throwable;)V",
                    /* isInterface= */ false);
                mv.visitLabel(logEnd);
                mv.visitJumpInsn(Opcodes.GOTO, handlerExit);
  
                // if the runtime can't reach our ExceptionLogger,
                //   silently eat the exception
                mv.visitLabel(eatException);
                if (frames) {
                  mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"});
                }
                mv.visitInsn(Opcodes.POP);
                // mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Throwable",
                //    "printStackTrace", "()V", false);
  
                mv.visitLabel(handlerExit);
                if (frames) {
                  mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
                  // there may be at most one frame at given code location, we need to add an extra
                  // NOP instruction to ensure that there isn't a duplicate frame
                  mv.visitInsn(Opcodes.NOP);
                }
  
                return size;
              }
            });
  
    public static ExceptionHandler defaultExceptionHandler() {
      return EXCEPTION_STACK_HANDLER;
    }
  
    private ExceptionHandlers() {}
  }
  