blob: 2696d067baaa4a3e0cd3a0a4e9f7587ee5aac5e0 [file] [log] [blame]
/*
* 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);
}
}
}