blob: 4b8ef7278e9fad53fe5b3aa4fdcace0d1601969e [file] [log] [blame]
// Copyright 2019 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.nest;
import static com.google.devtools.build.android.desugar.langmodel.LangModelConstants.NEST_COMPANION_CLASS_SIMPLE_NAME;
import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
import com.google.devtools.build.android.desugar.langmodel.ClassName;
import com.google.devtools.build.android.desugar.langmodel.FieldKey;
import com.google.devtools.build.android.desugar.langmodel.MethodDeclInfo;
import com.google.devtools.build.android.desugar.langmodel.MethodKey;
import javax.annotation.Nullable;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* A class visitor that rewrites visited classes and interfaces with nested-based access [JEP 181].
* For any private class member with access from another class or interface within the same nest,
* The visitor generates visibility-broadened bridge methods and update all call sites. For any
* private interface member within access from another class or interface within the same nest, the
* visitor broadens the visibility of private interface methods, renames these interface members and
* update their call sites.
*/
public final class NestDesugaring extends ClassVisitor {
private static final int NEST_COMPANION_CLASS_ACCESS_CODE =
Opcodes.ACC_SYNTHETIC | Opcodes.ACC_ABSTRACT | Opcodes.ACC_STATIC;
private final NestDigest nestDigest;
private FieldAccessBridgeEmitter fieldAccessBridgeEmitter;
private MethodAccessorEmitter methodAccessorEmitter;
private ClassName className;
private int classAccess;
@Nullable private ClassVisitor nestCompanionVisitor;
/** Whether the class being visited is a nest host with with a nest companion class. */
private boolean isNestHostWithNestCompanion;
public NestDesugaring(ClassVisitor classVisitor, NestDigest nestDigest) {
super(Opcodes.ASM7, classVisitor);
this.nestDigest = nestDigest;
}
@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
className = ClassName.create(name);
classAccess = access;
nestCompanionVisitor = nestDigest.getCompanionClassWriter(className);
isNestHostWithNestCompanion =
(nestCompanionVisitor != null) && className.equals(nestDigest.nestHost(className).get());
fieldAccessBridgeEmitter = new FieldAccessBridgeEmitter();
methodAccessorEmitter = new MethodAccessorEmitter(nestDigest);
super.visit(
Math.min(version, NestDesugarConstants.MIN_VERSION),
access,
name,
signature,
superName,
interfaces);
if (isNestHostWithNestCompanion) {
nestCompanionVisitor.visit(
Math.min(version, NestDesugarConstants.MIN_VERSION),
ACC_SYNTHETIC | ACC_ABSTRACT,
nestDigest.nestCompanion(className).binaryName(),
/* signature= */ null,
/* superName= */ "java/lang/Object",
/* interfaces= */ new String[0]);
}
}
@Override
public FieldVisitor visitField(
int access, String name, String descriptor, String signature, Object value) {
FieldKey fieldKey = FieldKey.create(className, name, descriptor);
// Generates necessary bridge methods for this field, including field getter methods and field
// setter methods. See FieldAccessBridgeEmitter.
nestDigest
.findAllMemberUseKinds(fieldKey)
.forEach(useKind -> fieldKey.accept(useKind, fieldAccessBridgeEmitter, cv));
return super.visitField(access, name, descriptor, signature, value);
}
@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
MethodKey methodKey = MethodKey.create(className, name, descriptor);
if (nestDigest.hasAnyTrackingReason(methodKey)) {
MethodDeclInfo methodDeclInfo =
MethodDeclInfo.create(methodKey, classAccess, access, signature, exceptions);
// For interfaces, the following .accept converts the original method into a
// visibility-broadened renamed method in-place while preserving the method body
// implementation; for classes, the following .accept generates synthetic bridge methods and
// preserve the original methods. The returned method visitor is subject to further desugar
// operations for interfaces. See {@link MethodAccessorEmitter}
MethodVisitor targetMethodVisitor = methodDeclInfo.accept(methodAccessorEmitter, cv);
// For classes, we continue to desugar the code of the bridge's originating method. For
// interfaces, we continue to desugar the header-modified methods. The code of the bridge
// method is one simple delegation call to its originating method without any code subject to
// desugar.
MethodVisitor primaryMethodVisitor =
(classAccess & ACC_INTERFACE) != 0
? targetMethodVisitor
: super.visitMethod(access, name, descriptor, signature, exceptions);
return new NestBridgeRefConverter(primaryMethodVisitor, methodKey, nestDigest);
}
// In absence of method invocation record, fallback to the delegate method visitor.
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
return mv == null ? null : new NestBridgeRefConverter(mv, methodKey, nestDigest);
}
@Override
public void visitNestHost(String nestHost) {
// Intentionally omit super call to avoid writing NestHost attribute to the class file.
}
@Override
public void visitNestMember(String nestMember) {
// Intentionally omit super call to avoid writing the NestMembers attribute to the class file.
}
@Override
public void visitSource(String source, String debug) {
super.visitSource(source, debug);
if (isNestHostWithNestCompanion) {
nestCompanionVisitor.visitSource(source, debug);
}
}
@Override
public void visitEnd() {
if (isNestHostWithNestCompanion) {
ClassName nestCompanion = nestDigest.nestCompanion(className);
// In the nest companion class, marks its outer class as the nest host.
nestCompanionVisitor.visitInnerClass(
nestCompanion.binaryName(),
className.binaryName(),
NEST_COMPANION_CLASS_SIMPLE_NAME,
NEST_COMPANION_CLASS_ACCESS_CODE);
// In the nest host class, marks the nest companion as one of its inner classes.
cv.visitInnerClass(
nestCompanion.binaryName(),
className.binaryName(),
NEST_COMPANION_CLASS_SIMPLE_NAME,
NEST_COMPANION_CLASS_ACCESS_CODE);
}
super.visitEnd();
}
}