Java tutorial
// 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.importdeps; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import com.google.common.collect.ImmutableSet; import com.google.devtools.build.importdeps.ClassInfo.MemberInfo; import java.util.Optional; import javax.annotation.Nullable; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.TypePath; /** Checker to check whether a class has missing dependencies on its classpath. */ public class DepsCheckerClassVisitor extends ClassVisitor { private String internalName; private final ClassCache classCache; private final ResultCollector resultCollector; private final DepsCheckerAnnotationVisitor defaultAnnotationChecker = new DepsCheckerAnnotationVisitor(); private final DepsCheckerFieldVisitor defaultFieldChecker = new DepsCheckerFieldVisitor(); private final DepsCheckerMethodVisitor defaultMethodChecker = new DepsCheckerMethodVisitor(); public DepsCheckerClassVisitor(ClassCache classCache, ResultCollector resultCollector) { super(Opcodes.ASM6); this.classCache = classCache; this.resultCollector = resultCollector; } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { checkState(internalName == null, "Cannot reuse this class visitor %s", getClass()); this.internalName = name; checkInternalName(superName); checkInternalNameArray(interfaces); super.visit(version, access, name, signature, superName, interfaces); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { checkDescriptor(desc); return defaultAnnotationChecker; } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { checkDescriptor(desc); return defaultFieldChecker; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { checkInternalNameArray(exceptions); checkDescriptor(desc); return defaultMethodChecker; } @Override public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { checkDescriptor(desc); return defaultAnnotationChecker; } private void checkMember(String owner, String name, String desc) { try { if (checkInternalNameOrArrayDescriptor(owner)) { // The owner is an array descriptor. return; // Assume all methods of arrays exist by default. } checkDescriptor(desc); AbstractClassEntryState state = checkInternalName(owner); Optional<ClassInfo> classInfo = state.classInfo(); if (!classInfo.isPresent()) { checkState(state.isMissingState(), "The state should be MissingState. %s", state); return; // The class is already missing. } MemberInfo member = MemberInfo.create(name, desc); if (!classInfo.get().containsMember(member)) { resultCollector.addMissingMember(owner, member); } } catch (RuntimeException e) { System.err.printf("A runtime exception occurred when checking the member: owner=%s, name=%s, desc=%s\n", owner, name, desc); throw e; } } private void checkDescriptor(String desc) { checkType(Type.getType(desc)); } private void checkType(Type type) { switch (type.getSort()) { case Type.BOOLEAN: case Type.BYTE: case Type.CHAR: case Type.SHORT: case Type.INT: case Type.LONG: case Type.FLOAT: case Type.DOUBLE: case Type.VOID: return; // Ignore primitive types. case Type.ARRAY: checkType(type.getElementType()); return; case Type.METHOD: for (Type argumentType : type.getArgumentTypes()) { checkType(argumentType); } checkType(type.getReturnType()); return; case Type.OBJECT: checkInternalName(type.getInternalName()); return; default: throw new UnsupportedOperationException("Unhandled type: " + type); } } /** * Checks the type, and returns {@literal true} if the type is an array descriptor, otherwise * {@literal false} */ private boolean checkInternalNameOrArrayDescriptor(String type) { if (type.charAt(0) == '[') { checkDescriptor(type); return true; } else { checkInternalName(type); return false; } } private AbstractClassEntryState checkInternalName(String internalName) { checkArgument(internalName.length() > 0 && Character.isJavaIdentifierStart(internalName.charAt(0)), "The internal name is invalid. %s", internalName); AbstractClassEntryState state = classCache.getClassState(internalName); if (state.isMissingState()) { resultCollector.addMissingOrIncompleteClass(internalName, state); } else { if (state.isIncompleteState()) { String missingAncestor = state.asIncompleteState().getMissingAncestor(); AbstractClassEntryState ancestorState = classCache.getClassState(missingAncestor); checkState(ancestorState.isMissingState(), "The ancestor should be missing. %s", ancestorState); resultCollector.addMissingOrIncompleteClass(missingAncestor, ancestorState); resultCollector.addMissingOrIncompleteClass(internalName, state); } ClassInfo info = state.classInfo().get(); if (!info.directDep()) { resultCollector.addIndirectDep(info.jarPath()); } } return state; } private void checkInternalNameArray(@Nullable String[] internalNames) { if (internalNames == null) { return; } for (String internalName : internalNames) { checkInternalName(internalName); } } private static final ImmutableSet<Class<?>> PRIMITIVE_TYPES = ImmutableSet.of(Boolean.class, Byte.class, Short.class, Character.class, Integer.class, Long.class, Float.class, Double.class, String.class); /** Annotation checker to check for missing classes in the annotation body. */ private class DepsCheckerAnnotationVisitor extends AnnotationVisitor { DepsCheckerAnnotationVisitor() { super(Opcodes.ASM6); } @Override public AnnotationVisitor visitAnnotation(String name, String desc) { checkDescriptor(desc); return this; // Recursively reuse this annotation visitor. } @Override public void visit(String name, Object value) { if (value instanceof Type) { checkType(((Type) value)); // Class literals. return; } Class<?> clazz = value.getClass(); if (PRIMITIVE_TYPES.contains(clazz)) { return; } checkState(clazz.isArray() && clazz.getComponentType().isPrimitive(), "Unexpected value %s of type %s", value, clazz); } @Override public AnnotationVisitor visitArray(String name) { return this; // Recursively reuse this annotation visitor. } @Override public void visitEnum(String name, String desc, String value) { checkMember(Type.getType(desc).getInternalName(), value, desc); } } /** Field checker to check for missing classes in the field declaration. */ private class DepsCheckerFieldVisitor extends FieldVisitor { DepsCheckerFieldVisitor() { super(Opcodes.ASM6); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { checkDescriptor(desc); return defaultAnnotationChecker; } @Override public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { checkDescriptor(desc); return defaultAnnotationChecker; } } /** Method visitor to check whether there are missing classes in the method body. */ private class DepsCheckerMethodVisitor extends MethodVisitor { DepsCheckerMethodVisitor() { super(Opcodes.ASM6); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { checkDescriptor(desc); return defaultAnnotationChecker; } @Override public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { checkDescriptor(desc); return defaultAnnotationChecker; } @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { if ("Ljava/lang/Synthetic;".equals(desc)) { return null; // ASM sometimes makes up this annotation, so we can ignore it (b/78024300) } checkDescriptor(desc); return defaultAnnotationChecker; } @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { checkDescriptor(desc); super.visitLocalVariable(name, desc, signature, start, end, index); } @Override public void visitTypeInsn(int opcode, String type) { checkInternalNameOrArrayDescriptor(type); super.visitTypeInsn(opcode, type); } @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { checkMember(owner, name, desc); super.visitFieldInsn(opcode, owner, name, desc); } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { checkMember(owner, name, desc); super.visitMethodInsn(opcode, owner, name, desc, itf); } @Override public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { checkDescriptor(desc); checkHandle(bsm); for (Object bsmArg : bsmArgs) { if (bsmArg instanceof Type) { checkType(((Type) bsmArg)); // Class literals. continue; } if (PRIMITIVE_TYPES.contains(bsmArg.getClass())) { checkType(Type.getType(bsmArg.getClass())); continue; } if (bsmArg instanceof Handle) { checkHandle((Handle) bsmArg); continue; } throw new UnsupportedOperationException("Unsupported bsmarg type: " + bsmArg); } super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); } private void checkHandle(Handle handle) { checkMember(handle.getOwner(), handle.getName(), handle.getDesc()); } @Override public void visitMultiANewArrayInsn(String desc, int dims) { checkDescriptor(desc); super.visitMultiANewArrayInsn(desc, dims); } @Override public AnnotationVisitor visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { checkDescriptor(desc); return defaultAnnotationChecker; } @Override public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) { checkDescriptor(desc); return defaultAnnotationChecker; } } }