/* * Copyright 2017-present Facebook, Inc. * * 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.facebook.buck.jvm.java.abi; import com.google.common.base.Preconditions; import javax.annotation.Nullable; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; /** A {@link ClassVisitor} that only passes to its delegate events for the class's ABI. */ class AbiFilteringClassVisitor extends ClassVisitor { @Nullable private String name; @Nullable private String outerName = null; private int classAccess; private boolean hasVisibleConstructor = false; public AbiFilteringClassVisitor(ClassVisitor cv) { super(Opcodes.ASM5, cv); } @Override public void visit( int version, int access, String name, String signature, String superName, String[] interfaces) { this.name = name; classAccess = access; super.visit(version, access, name, signature, superName, interfaces); } @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { boolean isAnonymousOrLocalClass = (outerName == null); String currentClassName = Preconditions.checkNotNull(this.name); if (name.equals(currentClassName)) { this.outerName = outerName; } else if (!shouldInclude(access) || isAnonymousOrLocalClass) { return; } super.visitInnerClass(name, outerName, innerName, access); } @Override @Nullable public FieldVisitor visitField( int access, String name, String desc, String signature, Object value) { if (!shouldInclude(access)) { return null; } return super.visitField(access, name, desc, signature, value); } @Override @Nullable public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { // Per JVMS8 2.9, "Class and interface initialization methods are invoked // implicitly by the Java Virtual Machine; they are never invoked directly from any // Java Virtual Machine instruction, but are invoked only indirectly as part of the class // initialization process." Thus we don't need to emit a stub of <clinit>. if (!shouldInclude(access) || (name.equals("<clinit>") && (access & Opcodes.ACC_STATIC) > 0)) { return null; } // We don't stub private constructors, but if stripping these constructors results in no // constructors at all, we want to include a default private constructor. This is because // removing all these private methods will make the class look like it has no constructors at // all, which is not possible. We track if this class has a public, non-synthetic constructor // and is not an interface or annotation to determine if a default private constructor is // generated when visitEnd() is called. if (name.equals("<init>") && (access & Opcodes.ACC_SYNTHETIC) == 0) { hasVisibleConstructor = true; } // Bridge methods are created by the compiler, and don't appear in source. It would be nice to // skip them, but they're used by the compiler to cover the fact that type erasure has occurred. // Normally the compiler adds these as public methods, but if you're compiling against a stub // produced using our ABI generator, we don't want people calling it accidentally. Oh well, I // guess it happens IRL too. // // Synthetic methods are also generated by the compiler, unless it's one of the methods named in // section 4.7.8 of the JVM spec, which are "<init>" and "Enum.valueOf()" and "Enum.values". // None of these are actually harmful to the ABI, so we allow synthetic methods through. // http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.8 return super.visitMethod(access, name, desc, signature, exceptions); } @Override public void visitEnd() { if (!hasVisibleConstructor && !isInterface(classAccess) && !isAnnotation(classAccess)) { String desc; if (isEnum(classAccess)) { desc = Type.getMethodType( Type.VOID_TYPE, Type.getObjectType("java/lang/String"), Type.INT_TYPE) .getDescriptor(); } else { desc = outerName == null ? Type.getMethodType(Type.VOID_TYPE).getDescriptor() : Type.getMethodType(Type.VOID_TYPE, Type.getObjectType(outerName)).getDescriptor(); } super.visitMethod(Opcodes.ACC_PRIVATE, "<init>", desc, null, null); } super.visitEnd(); } private boolean shouldInclude(int access) { if ((access & Opcodes.ACC_PRIVATE) == Opcodes.ACC_PRIVATE) { return false; } if ((access & (Opcodes.ACC_SYNTHETIC | Opcodes.ACC_BRIDGE)) == Opcodes.ACC_SYNTHETIC) { return false; } return true; } private boolean isInterface(int access) { return (access & Opcodes.ACC_INTERFACE) > 0; } private boolean isAnnotation(int access) { return (access & Opcodes.ACC_ANNOTATION) > 0; } private boolean isEnum(int access) { return (access & Opcodes.ACC_ENUM) > 0; } }