| // Copyright 2018 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; |
| |
| import static com.google.common.base.Preconditions.checkState; |
| |
| import com.google.auto.value.AutoValue; |
| import com.google.common.collect.ImmutableList; |
| import com.google.devtools.build.android.desugar.io.BitFlags; |
| import com.google.errorprone.annotations.Immutable; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import javax.annotation.Nullable; |
| import org.objectweb.asm.ClassVisitor; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| import org.objectweb.asm.Type; |
| import org.objectweb.asm.commons.ClassRemapper; |
| import org.objectweb.asm.commons.MethodRemapper; |
| import org.objectweb.asm.commons.Remapper; |
| |
| /** |
| * A visitor that renames packages so configured using {@link CoreLibrarySupport}. Additionally |
| * generate bridge-like methods for core library overrides that should be preserved that call the |
| * renamed variants. |
| */ |
| class CorePackageRenamer extends ClassRemapper { |
| |
| private final CoreLibrarySupport coreLibrarySupport; |
| private final Map<String, PreservedMethod> preserveOriginals = new LinkedHashMap<>(); |
| private String internalName; |
| |
| public CorePackageRenamer(ClassVisitor cv, CoreLibrarySupport support) { |
| super(cv, new CorePackageRemapper(support)); |
| coreLibrarySupport = support; |
| } |
| |
| @Override |
| public void visit(int version, int access, String name, String signature, String superName, |
| String[] interfaces) { |
| checkState(internalName == null || internalName.equals(name), "Instance already used."); |
| internalName = name; |
| super.visit(version, access, name, signature, superName, interfaces); |
| } |
| |
| @Override |
| public MethodVisitor visitMethod( |
| int access, String name, String descriptor, String signature, String[] exceptions) { |
| if (coreLibrarySupport.preserveOriginalMethod(access, internalName, name, descriptor)) { |
| if (BitFlags.isSynthetic(access)) { |
| // Idempotency: this is the preserved override, which we mark synthetic for simplicity. |
| return cv != null ? cv.visitMethod(access, name, descriptor, signature, exceptions) : null; |
| } else { |
| PreservedMethod preserve = |
| PreservedMethod.create(access, name, descriptor, signature, exceptions); |
| preserveOriginals.put(name + ":" + descriptor, preserve); |
| } |
| } |
| return super.visitMethod(access, name, descriptor, signature, exceptions); |
| } |
| |
| @Override |
| public void visitEnd() { |
| if (cv == null) { |
| return; |
| } |
| |
| // Re-generate preserved method overrides to call their remapped implementations |
| for (PreservedMethod preserve : preserveOriginals.values()) { |
| CorePackageRemapper remapper = (CorePackageRemapper) this.remapper; |
| remapper.didSomething = false; |
| String remappedDesc = remapper.mapMethodDesc(preserve.desc()); |
| checkState(remapper.didSomething, "Unnecessarily preserving %s", preserve); |
| |
| // Create method using cv field instead of super so renaming isn't applied. Use synthetic |
| // flag so we can recognize and skip this method if desugar is run again over the output. |
| // Also drop any "abstract" bit just in case. |
| int access = (preserve.access() & ~Opcodes.ACC_ABSTRACT) | Opcodes.ACC_SYNTHETIC; |
| MethodVisitor stubMethod = |
| cv.visitMethod( |
| access, |
| preserve.name(), |
| preserve.desc(), |
| preserve.signature(), |
| preserve.exceptions().toArray(new String[0])); |
| |
| // Load all the arguments and call the previously encountered method, converting desugared |
| // core library types as needed as we go. |
| int slot = 0; |
| stubMethod.visitVarInsn(Opcodes.ALOAD, slot++); // receiver |
| Type neededType = Type.getMethodType(preserve.desc()); |
| for (Type arg : neededType.getArgumentTypes()) { |
| stubMethod.visitVarInsn(arg.getOpcode(Opcodes.ILOAD), slot); |
| slot += arg.getSize(); |
| String argDesc = arg.getDescriptor(); |
| String mappedArg = remapper.mapDesc(argDesc); |
| |
| // Insert conversion if necessary |
| if (!argDesc.equals(mappedArg)) { |
| // TODO(kmb): May need to support array types |
| checkState(arg.getSort() == Type.OBJECT, "Can only map object types: %s", arg); |
| String converter = coreLibrarySupport.getFromCoreLibraryConverter(arg.getInternalName()); |
| String simpleClassName = |
| arg.getInternalName().substring(arg.getInternalName().lastIndexOf('/') + 1); |
| stubMethod.visitMethodInsn( |
| Opcodes.INVOKESTATIC, |
| /*owner=*/ converter, |
| /*name=*/ "from" + simpleClassName, // naming convention for converter methods |
| /*descriptor=*/ "(" + argDesc + ")" + mappedArg, |
| /*isInterface=*/ false); |
| } |
| } |
| stubMethod.visitMethodInsn( |
| Opcodes.INVOKEVIRTUAL, |
| internalName, |
| preserve.name(), |
| remappedDesc, |
| /*isInterface=*/ false); |
| |
| // TODO(kmb): May need to support "to"-converting return types |
| String returnDesc = neededType.getReturnType().getDescriptor(); |
| checkState( |
| returnDesc.equals(remapper.mapDesc(returnDesc)), |
| "Return value conversions not supported: %s", |
| returnDesc); |
| stubMethod.visitInsn(neededType.getReturnType().getOpcode(Opcodes.IRETURN)); |
| stubMethod.visitMaxs(slot, slot); |
| stubMethod.visitEnd(); |
| } |
| super.visitEnd(); |
| } |
| |
| @Override |
| protected CoreMethodRemapper createMethodRemapper(MethodVisitor methodVisitor) { |
| return new CoreMethodRemapper(methodVisitor, remapper); |
| } |
| |
| private class CoreMethodRemapper extends MethodRemapper { |
| |
| public CoreMethodRemapper(MethodVisitor methodVisitor, |
| Remapper remapper) { |
| super(methodVisitor, remapper); |
| } |
| |
| @Override |
| public void visitMethodInsn(int opcode, String owner, String name, String descriptor, |
| boolean isInterface) { |
| CorePackageRemapper remapper = (CorePackageRemapper) this.remapper; |
| remapper.didSomething = false; |
| super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); |
| // TODO(b/79121791): Make this more precise: look for all unsupported core library members |
| checkState( |
| !remapper.didSomething |
| || !owner.startsWith("android/") |
| || owner.startsWith("android/arch/") |
| || owner.startsWith("android/support/"), |
| "%s calls %s.%s%s which is not supported with core library desugaring. Please file " |
| + "a feature request to support this method", |
| internalName, |
| owner, |
| name, |
| descriptor); |
| } |
| |
| @Override |
| public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { |
| CorePackageRemapper remapper = (CorePackageRemapper) this.remapper; |
| remapper.didSomething = false; |
| super.visitFieldInsn(opcode, owner, name, descriptor); |
| // TODO(b/79121791): Make this more precise: look for all unsupported core library members |
| checkState( |
| !remapper.didSomething |
| || !owner.startsWith("android/") |
| || owner.startsWith("android/arch/") |
| || owner.startsWith("android/support/"), |
| "%s accesses %s.%s: %s which is not supported with core library desugaring. Please file " |
| + "a feature request to support this field", |
| internalName, |
| owner, |
| name, |
| descriptor); |
| } |
| } |
| |
| /** ASM {@link Remapper} based on {@link CoreLibrarySupport}. */ |
| private static class CorePackageRemapper extends Remapper { |
| |
| private final CoreLibrarySupport support; |
| boolean didSomething = false; |
| |
| CorePackageRemapper(CoreLibrarySupport support) { |
| this.support = support; |
| } |
| |
| @Override |
| public String map(String typeName) { |
| if (support.isRenamedCoreLibrary(typeName)) { |
| didSomething = true; |
| return support.renameCoreLibrary(typeName); |
| } |
| return typeName; |
| } |
| } |
| |
| /** Undesugared core library override to preserve. */ |
| @AutoValue |
| @Immutable |
| abstract static class PreservedMethod { |
| private static PreservedMethod create( |
| int access, |
| String name, |
| String desc, |
| @Nullable String signature, |
| @Nullable String[] exceptions) { |
| return new AutoValue_CorePackageRenamer_PreservedMethod( |
| access, |
| name, |
| desc, |
| signature, |
| exceptions != null ? ImmutableList.copyOf(exceptions) : ImmutableList.of()); |
| } |
| |
| abstract int access(); |
| |
| abstract String name(); |
| |
| abstract String desc(); |
| |
| @Nullable |
| abstract String signature(); |
| |
| abstract ImmutableList<String> exceptions(); |
| } |
| } |