| /* |
| * Copyright 2020 The Bazel Authors. All rights reserved. |
| * |
| * Licensed 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 com.google.devtools.build.android.desugar.covariantreturn; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.devtools.build.android.desugar.langmodel.ClassName; |
| import com.google.devtools.build.android.desugar.langmodel.MethodKey; |
| import com.google.devtools.build.android.desugar.langmodel.TypeMapper; |
| import org.objectweb.asm.ClassVisitor; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| import org.objectweb.asm.Type; |
| |
| /** |
| * A bytecode converter that supports to use covariant return types in the NIO buffer hierarchy. |
| * |
| * @see https://bugs.openjdk.java.net/browse/JDK-4774077. |
| */ |
| public final class NioBufferRefConverter extends ClassVisitor { |
| |
| /** The inheritance hierarchy root of Java NIO buffer family. */ |
| private static final ClassName NIO_BUFFER_BASE = ClassName.create("java/nio/Buffer"); |
| |
| /** All overloading methods in {@link java.nio.Buffer} with covariant return type. */ |
| private static final ImmutableList<MethodKey> BASE_METHODS_WITH_COVARIANT_RETURN_TYPES = |
| ImmutableList.of( |
| MethodKey.create(NIO_BUFFER_BASE, "position", "(I)Ljava/nio/Buffer;"), |
| MethodKey.create(NIO_BUFFER_BASE, "limit", "(I)Ljava/nio/Buffer;"), |
| MethodKey.create(NIO_BUFFER_BASE, "mark", "()Ljava/nio/Buffer;"), |
| MethodKey.create(NIO_BUFFER_BASE, "reset", "()Ljava/nio/Buffer;"), |
| MethodKey.create(NIO_BUFFER_BASE, "clear", "()Ljava/nio/Buffer;"), |
| MethodKey.create(NIO_BUFFER_BASE, "flip", "()Ljava/nio/Buffer;"), |
| MethodKey.create(NIO_BUFFER_BASE, "rewind", "()Ljava/nio/Buffer;")); |
| |
| /** All public type-specific NIO buffer classes derived from {@link java.nio.Buffer}. */ |
| private static final ImmutableSet<ClassName> TYPE_SPECIFIC_NIO_BUFFERS = |
| ImmutableSet.of( |
| ClassName.create("java/nio/IntBuffer"), |
| ClassName.create("java/nio/CharBuffer"), |
| ClassName.create("java/nio/FloatBuffer"), |
| ClassName.create("java/nio/DoubleBuffer"), |
| ClassName.create("java/nio/ShortBuffer"), |
| ClassName.create("java/nio/LongBuffer"), |
| ClassName.create("java/nio/ByteBuffer")); |
| |
| /** Used to find the replacement method from the original method invocation specification. */ |
| private final ImmutableMap<MethodKey, MethodKey> methodInvocationMappings; |
| |
| /** The public factory API for this class. */ |
| public static NioBufferRefConverter create( |
| ClassVisitor classVisitor, TypeMapper corePackagePrefixer) { |
| return new NioBufferRefConverter( |
| classVisitor, corePackagePrefixer.map(getMethodInvocationMappings())); |
| } |
| |
| private NioBufferRefConverter( |
| ClassVisitor classVisitor, ImmutableMap<MethodKey, MethodKey> methodInvocationMappings) { |
| super(Opcodes.ASM7, classVisitor); |
| this.methodInvocationMappings = methodInvocationMappings; |
| } |
| |
| @Override |
| public MethodVisitor visitMethod( |
| int access, String name, String descriptor, String signature, String[] exceptions) { |
| MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); |
| return mv == null ? null : new NioBufferMethodVisitor(api, mv, methodInvocationMappings); |
| } |
| |
| /** Computes methods in Java NIO buffer family that are subject to invocation conversion. */ |
| private static ImmutableMap<MethodKey, MethodKey> getMethodInvocationMappings() { |
| ImmutableMap.Builder<MethodKey, MethodKey> methodMappings = ImmutableMap.builder(); |
| for (ClassName typeSpecificNioBuffer : TYPE_SPECIFIC_NIO_BUFFERS) { |
| for (MethodKey baseMethod : BASE_METHODS_WITH_COVARIANT_RETURN_TYPES) { |
| methodMappings.put( |
| MethodKey.create( |
| typeSpecificNioBuffer, |
| baseMethod.name(), |
| Type.getMethodDescriptor( |
| typeSpecificNioBuffer.toAsmObjectType(), baseMethod.getArgumentTypeArray())), |
| MethodKey.create(typeSpecificNioBuffer, baseMethod.name(), baseMethod.descriptor())); |
| } |
| } |
| return methodMappings.build(); |
| } |
| |
| private static class NioBufferMethodVisitor extends MethodVisitor { |
| |
| private final ImmutableMap<MethodKey, MethodKey> methodInvocationMappings; |
| |
| NioBufferMethodVisitor( |
| int api, |
| MethodVisitor methodVisitor, |
| ImmutableMap<MethodKey, MethodKey> methodInvocationMappings) { |
| super(api, methodVisitor); |
| this.methodInvocationMappings = methodInvocationMappings; |
| } |
| |
| @Override |
| public void visitMethodInsn( |
| int opcode, String owner, String name, String descriptor, boolean isInterface) { |
| MethodKey methodKey = MethodKey.create(ClassName.create(owner), name, descriptor); |
| if (methodInvocationMappings.containsKey(methodKey)) { |
| MethodKey mappedMethodKey = methodInvocationMappings.get(methodKey); |
| super.visitMethodInsn( |
| opcode, |
| mappedMethodKey.ownerName(), |
| mappedMethodKey.name(), |
| mappedMethodKey.descriptor(), |
| isInterface); |
| super.visitTypeInsn(Opcodes.CHECKCAST, owner); |
| return; |
| } |
| super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); |
| } |
| } |
| } |