content: /*
   * Copyright The OpenTelemetry Authors
   * SPDX-License-Identifier: Apache-2.0
   */
  
  package io.opentelemetry.javaagent.tooling.field;
  
  import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getRealFieldName;
  import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getRealGetterName;
  import static io.opentelemetry.javaagent.tooling.field.GeneratedVirtualFieldNames.getRealSetterName;
  
  import com.google.errorprone.annotations.CanIgnoreReturnValue;
  import io.opentelemetry.javaagent.bootstrap.VirtualFieldInstalledMarker;
  import io.opentelemetry.javaagent.extension.instrumentation.internal.AsmApi;
  import io.opentelemetry.javaagent.tooling.Utils;
  import java.util.Arrays;
  import java.util.LinkedHashSet;
  import java.util.Set;
  import net.bytebuddy.asm.AsmVisitorWrapper;
  import net.bytebuddy.description.field.FieldDescription;
  import net.bytebuddy.description.field.FieldList;
  import net.bytebuddy.description.method.MethodList;
  import net.bytebuddy.description.type.TypeDescription;
  import net.bytebuddy.implementation.Implementation;
  import net.bytebuddy.pool.TypePool;
  import org.objectweb.asm.ClassVisitor;
  import org.objectweb.asm.ClassWriter;
  import org.objectweb.asm.FieldVisitor;
  import org.objectweb.asm.MethodVisitor;
  import org.objectweb.asm.Opcodes;
  
  final class RealFieldInjector implements AsmVisitorWrapper {
  
    private static final String INSTALLED_FIELDS_MARKER_CLASS_NAME =
        Utils.getInternalName(VirtualFieldInstalledMarker.class);
  
    private final FieldAccessorInterfaces fieldAccessorInterfaces;
    private final String typeName;
    private final String fieldTypeName;
  
    RealFieldInjector(
        FieldAccessorInterfaces fieldAccessorInterfaces, String typeName, String fieldTypeName) {
      this.fieldAccessorInterfaces = fieldAccessorInterfaces;
      this.typeName = typeName;
      this.fieldTypeName = fieldTypeName;
    }
  
    @Override
    public int mergeWriter(int flags) {
      return flags | ClassWriter.COMPUTE_MAXS;
    }
  
    @Override
    @CanIgnoreReturnValue
    public int mergeReader(int flags) {
      return flags;
    }
  
    @Override
    public ClassVisitor wrap(
        TypeDescription instrumentedType,
        ClassVisitor classVisitor,
        Implementation.Context implementationContext,
        TypePool typePool,
        FieldList<FieldDescription.InDefinedShape> fields,
        MethodList<?> methods,
        int writerFlags,
        int readerFlags) {
  
      return new ClassVisitor(AsmApi.VERSION, classVisitor) {
        // We are using Object class name instead of fieldTypeName here because this gets
        // injected onto the bootstrap class loader where context class may be unavailable
        private final TypeDescription fieldType = TypeDescription.ForLoadedType.of(Object.class);
        private final String fieldName = getRealFieldName(typeName, fieldTypeName);
        private final String getterMethodName = getRealGetterName(typeName, fieldTypeName);
        private final String setterMethodName = getRealSetterName(typeName, fieldTypeName);
        private final TypeDescription interfaceType =
            fieldAccessorInterfaces.find(typeName, fieldTypeName);
        private boolean foundField = false;
        private boolean foundGetter = false;
        private boolean foundSetter = false;
  
        @Override
        public void visit(
            int version,
            int access,
            String name,
            String signature,
            String superName,
            String[] interfaces) {
          if (interfaces == null) {
            interfaces = new String[] {};
          }
          Set<String> set = new LinkedHashSet<>(Arrays.asList(interfaces));
          set.add(INSTALLED_FIELDS_MARKER_CLASS_NAME);
          set.add(interfaceType.getInternalName());
          super.visit(version, access, name, signature, superName, set.toArray(new String[] {}));
        }
  
        @Override
        public FieldVisitor visitField(
            int access, String name, String descriptor, String signature, Object value) {
          if (name.equals(fieldName)) {
            foundField = true;
          }
          return super.visitField(access, name, descriptor, signature, value);
        }
  
        @Override
        public MethodVisitor visitMethod(
            int access, String name, String descriptor, String signature, String[] exceptions) {
          if (name.equals(getterMethodName)) {
            foundGetter = true;
          }
          if (name.equals(setterMethodName)) {
            foundSetter = true;
          }
          return super.visitMethod(access, name, descriptor, signature, exceptions);
        }
  
        @Override
        public void visitEnd() {
          // Checking only for field existence is not enough as libraries like CGLIB only copy
          // public/protected methods and not fields (neither public nor private ones) when
          // they enhance a class.
          // For this reason we check separately for the field and for the two accessors.
          if (!foundField) {
            cv.visitField(
                // Field should be transient to avoid being serialized with the object.
                Opcodes.ACC_PRIVATE
                    | Opcodes.ACC_VOLATILE
                    | Opcodes.ACC_TRANSIENT
                    | Opcodes.ACC_SYNTHETIC,
                fieldName,
                fieldType.getDescriptor(),
                null,
                null);
          }
          if (!foundGetter) {
            addGetter();
          }
          if (!foundSetter) {
            addSetter();
          }
          super.visitEnd();
        }
  
        // just 'standard' getter implementation
        private void addGetter() {
          MethodVisitor mv = getAccessorMethodVisitor(getterMethodName);
          mv.visitCode();
          mv.visitVarInsn(Opcodes.ALOAD, 0);
          mv.visitFieldInsn(
              Opcodes.GETFIELD,
              instrumentedType.getInternalName(),
              fieldName,
              fieldType.getDescriptor());
          mv.visitInsn(Opcodes.ARETURN);
          mv.visitMaxs(0, 0);
          mv.visitEnd();
        }
  
        // just 'standard' setter implementation
        private void addSetter() {
          MethodVisitor mv = getAccessorMethodVisitor(setterMethodName);
          mv.visitCode();
          mv.visitVarInsn(Opcodes.ALOAD, 0);
          mv.visitVarInsn(Opcodes.ALOAD, 1);
          mv.visitFieldInsn(
              Opcodes.PUTFIELD,
              instrumentedType.getInternalName(),
              fieldName,
              fieldType.getDescriptor());
          mv.visitInsn(Opcodes.RETURN);
          mv.visitMaxs(0, 0);
          mv.visitEnd();
        }
  
        private MethodVisitor getAccessorMethodVisitor(String methodName) {
          return cv.visitMethod(
              Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC,
              methodName,
              Utils.getMethodDefinition(interfaceType, methodName).getDescriptor(),
              null,
              null);
        }
      };
    }
  }
  