| /* |
| * 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.preanalysis; |
| |
| import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord.ClassAttributeRecordBuilder; |
| import com.google.devtools.build.android.desugar.langmodel.ClassAttributes; |
| import com.google.devtools.build.android.desugar.langmodel.ClassAttributes.ClassAttributesBuilder; |
| import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord; |
| import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord.ClassMemberRecordBuilder; |
| import com.google.devtools.build.android.desugar.langmodel.ClassName; |
| import com.google.devtools.build.android.desugar.langmodel.DesugarMethodAttribute; |
| import com.google.devtools.build.android.desugar.langmodel.FieldKey; |
| import com.google.devtools.build.android.desugar.langmodel.LangModelHelper; |
| import com.google.devtools.build.android.desugar.langmodel.MethodKey; |
| import org.objectweb.asm.Attribute; |
| import org.objectweb.asm.ClassVisitor; |
| import org.objectweb.asm.FieldVisitor; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Opcodes; |
| |
| /** |
| * A visitor that performs pre-transformation phase analysis that collects relevant class file |
| * information for the next transformation phase. |
| */ |
| public final class ClassMetadataCollector extends ClassVisitor { |
| |
| /** Class member records from a single input for nest-based access control analysis. */ |
| private final ClassMemberRecordBuilder nestAnalysisBasedMemberRecord; |
| |
| /** |
| * Record that tracks class-level attributes, such nest members, nest mates and desugar-custom |
| * attributes. |
| */ |
| private final ClassAttributeRecordBuilder classAttributeRecord; |
| |
| /** |
| * An class member record to stage member record candidates, merging into the project-wise member |
| * record during the {@link #visitEnd()} where eligible conditions are specified. |
| */ |
| private final ClassMemberRecordBuilder stagingMemberRecord = ClassMemberRecord.builder(); |
| |
| private final ClassAttributesBuilder classAttributesBuilder; |
| |
| private ClassName className; |
| private int classAccessCode; |
| private boolean isInNest; |
| |
| ClassMetadataCollector( |
| ClassMemberRecordBuilder nestAnalysisBasedMemberRecord, |
| ClassAttributeRecordBuilder classAttributeRecord) { |
| super(Opcodes.ASM8); |
| this.nestAnalysisBasedMemberRecord = nestAnalysisBasedMemberRecord; |
| this.classAttributeRecord = classAttributeRecord; |
| this.classAttributesBuilder = ClassAttributes.builder(); |
| } |
| |
| @Override |
| public void visit( |
| int version, |
| int access, |
| String name, |
| String signature, |
| String superName, |
| String[] interfaces) { |
| className = ClassName.create(name); |
| classAccessCode = access; |
| classAttributesBuilder.setClassBinaryName(className); |
| super.visit(Math.min(version, Opcodes.V1_7), access, name, signature, superName, interfaces); |
| } |
| |
| @Override |
| public FieldVisitor visitField( |
| int access, String name, String descriptor, String signature, Object value) { |
| if ((access & Opcodes.ACC_PRIVATE) != 0) { |
| stagingMemberRecord.logMemberDecl( |
| FieldKey.create(className, name, descriptor), classAccessCode, access); |
| } |
| 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 ((access & Opcodes.ACC_PRIVATE) != 0 && (access & Opcodes.ACC_STATIC) == 0) { |
| classAttributesBuilder.addPrivateInstanceMethod(methodKey); |
| } |
| if ((access & Opcodes.ACC_PRIVATE) != 0 && !isInDeclOmissionList(methodKey)) { |
| stagingMemberRecord.logMemberDecl(methodKey, classAccessCode, access); |
| } |
| return new MethodMetadataCollector( |
| super.visitMethod(access, name, descriptor, signature, exceptions), |
| methodKey, |
| classAttributesBuilder, |
| stagingMemberRecord); |
| } |
| |
| /** |
| * The method declaration will be explicitly omitted. |
| * |
| * <p>TODO(deltazulu): Refine this list and condition check. e.g. check ACC_SYNTHETIC flag. |
| */ |
| private static boolean isInDeclOmissionList(MethodKey methodKey) { |
| return methodKey.name().startsWith("lambda$") // handled by LambdaDesugaring. |
| || methodKey.name().equals("$deserializeLambda$") // handled by LambdaDesugaring. |
| || methodKey.name().contains("jacoco$") // handled by InterfaceDesugaring. |
| || methodKey.name().contains("$jacoco"); // handled by InterfaceDesugaring. |
| } |
| |
| @Override |
| public void visitNestHost(String nestHost) { |
| isInNest = true; |
| classAttributesBuilder.setNestHost(ClassName.create(nestHost)); |
| super.visitNestHost(nestHost); |
| } |
| |
| @Override |
| public void visitNestMember(String nestMember) { |
| isInNest = true; |
| classAttributesBuilder.addNestMember(ClassName.create(nestMember)); |
| super.visitNestMember(nestMember); |
| } |
| |
| @Override |
| public void visitSource(String source, String debug) { |
| classAttributesBuilder.setSourceFileName(source); |
| super.visitSource(source, debug); |
| } |
| |
| @Override |
| public void visitEnd() { |
| if (isInNest || (classAccessCode & Opcodes.ACC_INTERFACE) != 0) { |
| nestAnalysisBasedMemberRecord.mergeFrom(stagingMemberRecord.build()); |
| } |
| classAttributeRecord.addClassAttributes(classAttributesBuilder.build()); |
| super.visitEnd(); |
| } |
| |
| /** |
| * A visitor that collects privately referenced class members (fields/constructors/methods) within |
| * a nest. |
| */ |
| private static class MethodMetadataCollector extends MethodVisitor { |
| |
| /** The current enclosing the method. */ |
| private final MethodKey enclosingMethodKey; |
| |
| private final ClassAttributesBuilder classAttributesBuilder; |
| |
| /** |
| * A per-class class member record and will determined to merge or not into the main member |
| * record at visitEnd of its associated class visitor. |
| * |
| * <p>@see {@link ClassMetadataCollector#stagingMemberRecord} for more details. |
| */ |
| private final ClassMemberRecordBuilder nestAnalysisBasedMemberRecord; |
| |
| MethodMetadataCollector( |
| MethodVisitor methodVisitor, |
| MethodKey enclosingMethodKey, |
| ClassAttributesBuilder classAttributesBuilder, |
| ClassMemberRecordBuilder nestAnalysisBasedMemberRecord) { |
| super(Opcodes.ASM8, methodVisitor); |
| this.enclosingMethodKey = enclosingMethodKey; |
| this.classAttributesBuilder = classAttributesBuilder; |
| this.nestAnalysisBasedMemberRecord = nestAnalysisBasedMemberRecord; |
| } |
| |
| @Override |
| public void visitAttribute(Attribute attribute) { |
| if (attribute instanceof DesugarMethodAttribute) { |
| classAttributesBuilder.addDesugarIgnoredMethods(enclosingMethodKey); |
| } |
| super.visitAttribute(attribute); |
| } |
| |
| @Override |
| public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { |
| FieldKey memberKey = FieldKey.create(ClassName.create(owner), name, descriptor); |
| if (LangModelHelper.isCrossMateRefInNest(memberKey, enclosingMethodKey)) { |
| nestAnalysisBasedMemberRecord.logMemberUse(memberKey, opcode); |
| } |
| super.visitFieldInsn(opcode, owner, name, descriptor); |
| } |
| |
| @Override |
| public void visitMethodInsn( |
| int opcode, String owner, String name, String descriptor, boolean isInterface) { |
| MethodKey memberKey = MethodKey.create(ClassName.create(owner), name, descriptor); |
| if (isInterface || LangModelHelper.isCrossMateRefInNest(memberKey, enclosingMethodKey)) { |
| nestAnalysisBasedMemberRecord.logMemberUse(memberKey, opcode); |
| } |
| super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); |
| } |
| } |
| } |