diff options
author | 2018-02-06 10:57:49 -0800 | |
---|---|---|
committer | 2018-02-06 10:59:37 -0800 | |
commit | 33c419bb0952721f056f01a0374f9f2d238e98bd (patch) | |
tree | 2d85645b03699f85231cd28c8d9865058c9e3925 /src | |
parent | 11c59283ec83db0c62ad67e7f0ffbfb8a5183b06 (diff) |
Add a new tool to check the deps of aar_import. This is the first cl of a
series. The following CLs will integrate this into bazel.
RELNOTES:n/a.
PiperOrigin-RevId: 184706507
Diffstat (limited to 'src')
31 files changed, 2616 insertions, 0 deletions
diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/AbstractClassEntryState.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/AbstractClassEntryState.java new file mode 100644 index 0000000000..209bf83fcb --- /dev/null +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/AbstractClassEntryState.java @@ -0,0 +1,124 @@ +// 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 com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import java.util.Optional; + +/** + * The state for a class entry used in {@link ClassCache}. A state can be + * + * <ul> + * <li>EXISTING: this class exists. + * <li>INCOMPLETE: this class exists, but at least one of its ancestor is missing. + * <li>MISSING: this class is missing on the classpath. + * </ul> + */ +public abstract class AbstractClassEntryState { + + public boolean isMissingState() { + return this instanceof MissingState; + } + + public MissingState asMissingState() { + throw new IllegalStateException("Not a missing state " + this); + } + + public boolean isExistingState() { + return this instanceof ExistingState; + } + + public ExistingState asExistingState() { + throw new IllegalStateException("Not an existing state " + this); + } + + public boolean isIncompleteState() { + return this instanceof IncompleteState; + } + + public IncompleteState asIncompleteState() { + throw new IllegalStateException("Not an incomplete state " + this); + } + + public abstract Optional<ClassInfo> classInfo(); + + /** A state to indicate that a class exists. */ + @AutoValue + public abstract static class ExistingState extends AbstractClassEntryState { + + public static ExistingState create(ClassInfo classInfo) { + return new AutoValue_AbstractClassEntryState_ExistingState(Optional.of(classInfo)); + } + + @Override + public ExistingState asExistingState() { + return this; + } + } + + /** A state to indicate that a class is missing. */ + public static final class MissingState extends AbstractClassEntryState { + + private static final MissingState SINGLETON = new MissingState(); + + public static MissingState singleton() { + return SINGLETON; + } + + private MissingState() {} + + @Override + public MissingState asMissingState() { + return this; + } + + @Override + public Optional<ClassInfo> classInfo() { + return Optional.empty(); + } + } + + /** + * A state to indicate that a class is incomplete, that is, some ancesotor is missing on the + * classpath. + */ + @AutoValue + public abstract static class IncompleteState extends AbstractClassEntryState { + + public static IncompleteState create( + ClassInfo classInfo, ImmutableList<String> resolutionFailurePath) { + checkArgument( + !resolutionFailurePath.isEmpty(), + "The resolution path should contain at least one element, the missing ancestor. %s", + resolutionFailurePath); + return new AutoValue_AbstractClassEntryState_IncompleteState( + Optional.of(classInfo), resolutionFailurePath); + } + + public abstract ImmutableList<String> getResolutionFailurePath(); + + public String getMissingAncestor() { + ImmutableList<String> path = getResolutionFailurePath(); + return path.get(path.size() - 1); + } + + @Override + public IncompleteState asIncompleteState() { + return this; + } + } +} diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/BUILD b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/BUILD new file mode 100644 index 0000000000..a9d4cd32aa --- /dev/null +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/BUILD @@ -0,0 +1,34 @@ +# Description: +# A checker to check the completeness of the deps of java_import or aar_import targets. + +package( + default_visibility = ["//src:__subpackages__"], +) + +java_library( + name = "import_deps_checker", + srcs = glob( + ["*.java"], + exclude = ["Main.java"], + ), + deps = [ + "//third_party:asm", + "//third_party:auto_value", + "//third_party:guava", + "//third_party:jsr305", + "//third_party/java/asm:asm-commons", + "//third_party/java/asm:asm-tree", + ], +) + +java_binary( + name = "ImportDepsChecker", + srcs = ["Main.java"], + main_class = "com.google.devtools.build.importdeps.Main", + deps = [ + ":import_deps_checker", + "//src/main/java/com/google/devtools/common/options", + "//src/main/protobuf:worker_protocol_java_proto", + "//third_party:guava", + ], +) diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassCache.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassCache.java new file mode 100644 index 0000000000..c855afd6d3 --- /dev/null +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassCache.java @@ -0,0 +1,277 @@ +// 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.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.Closer; +import com.google.devtools.build.importdeps.AbstractClassEntryState.ExistingState; +import com.google.devtools.build.importdeps.AbstractClassEntryState.IncompleteState; +import com.google.devtools.build.importdeps.AbstractClassEntryState.MissingState; +import com.google.devtools.build.importdeps.ClassInfo.MemberInfo; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import javax.annotation.Nullable; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** A cache that stores all the accessible classes. */ +public final class ClassCache implements Closeable { + + private final ImmutableMap<String, LazyClassEntry> classIndex; + /** + * If the cache is open, then the {@code closer} is nonnull. After the cache is closed, the {@code + * closer} is set to {@literal null}. + */ + @Nullable private Closer closer; + + public ClassCache(Path... jars) throws IOException { + this(ImmutableList.copyOf(jars)); + } + + public ClassCache(ImmutableList<Path> jars) throws IOException { + closer = Closer.create(); + this.classIndex = buildClassIndex(jars, closer); + } + + public AbstractClassEntryState getClassState(String internalName) { + ensureCacheIsOpen(); + LazyClassEntry entry = classIndex.get(internalName); + if (entry == null) { + return MissingState.singleton(); + } + return entry.getState(classIndex); + } + + @Override + public void close() throws IOException { + if (closer == null) { + return; + } + closer.close(); + closer = null; + } + + private static ImmutableMap<String, LazyClassEntry> buildClassIndex( + ImmutableList<Path> jars, Closer closer) throws IOException { + HashMap<String, LazyClassEntry> result = new HashMap<>(); + for (Path jarPath : jars) { + try { + ZipFile zipFile = closer.register(new ZipFile(jarPath.toFile())); + zipFile + .stream() + .forEach( + entry -> { + String name = entry.getName(); + if (!name.endsWith(".class")) { + return; // Not a class file. + } + String internalName = name.substring(0, name.lastIndexOf('.')); + result.computeIfAbsent(internalName, key -> new LazyClassEntry(key, zipFile)); + }); + } catch (Throwable e) { + throw new RuntimeException("Error in reading zip file " + jarPath, e); + } + } + return ImmutableMap.copyOf(result); + } + + private void ensureCacheIsOpen() { + checkState(closer != null, "The cache should be open!"); + } + + static class LazyClassEntry { + private final String internalName; + private final ZipFile zipFile; + + /** + * The state of this class entry. If {@literal null}, then this class has not been resolved yet. + */ + @Nullable private AbstractClassEntryState state = null; + + private LazyClassEntry(String internalName, ZipFile zipFile) { + this.internalName = internalName; + this.zipFile = zipFile; + } + + ZipFile getZipFile() { + return zipFile; + } + + @Nullable + public AbstractClassEntryState getState(ImmutableMap<String, LazyClassEntry> classIndex) { + resolveIfNot(classIndex); + checkState( + state != null && !state.isMissingState(), + "The state cannot be null or MISSING. %s", + state); + return state; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("internalName", internalName) + .add("state", state) + .toString(); + } + + private void resolveIfNot(ImmutableMap<String, LazyClassEntry> classIndex) { + if (state != null) { + return; + } + resolveClassEntry(this, classIndex); + checkNotNull(state, "After resolution, the state cannot be null"); + } + + private static void resolveClassEntry( + LazyClassEntry classEntry, ImmutableMap<String, LazyClassEntry> classIndex) { + if (classEntry.state != null) { + // Already resolved. See if it is the existing state. + return; + } + + String entryName = classEntry.internalName + ".class"; + ZipEntry zipEntry = + checkNotNull( + classEntry.zipFile.getEntry(entryName), "The zip entry %s is null.", entryName); + try (InputStream inputStream = classEntry.zipFile.getInputStream(zipEntry)) { + ClassReader classReader = new ClassReader(inputStream); + ImmutableList<String> resolutionFailurePath = null; + for (String superName : + combineWithoutNull(classReader.getSuperName(), classReader.getInterfaces())) { + LazyClassEntry superClassEntry = classIndex.get(superName); + + if (superClassEntry == null) { + resolutionFailurePath = ImmutableList.of(superName); + break; + } else { + resolveClassEntry(superClassEntry, classIndex); + AbstractClassEntryState superState = superClassEntry.state; + if (superState instanceof ExistingState) { + // Do nothing. Good to proceed. + continue; + } else if (superState instanceof IncompleteState) { + resolutionFailurePath = + ImmutableList.<String>builder() + .add(superName) + .addAll(((IncompleteState) superState).getResolutionFailurePath()) + .build(); + break; + } else { + throw new RuntimeException("Cannot reach here. superState is " + superState); + } + } + } + ClassInfoBuilder classInfoBuilder = new ClassInfoBuilder(); + classReader.accept(classInfoBuilder, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + if (resolutionFailurePath == null) { + classEntry.state = ExistingState.create(classInfoBuilder.build(classIndex)); + } else { + classEntry.state = + IncompleteState.create(classInfoBuilder.build(classIndex), resolutionFailurePath); + } + } catch (IOException e) { + throw new RuntimeException("Error when resolving class entry " + entryName); + } catch (RuntimeException e) { + System.err.println( + "A runtime exception occurred. The following is the content in the class index. " + + e.getMessage()); + int counter = 0; + for (Map.Entry<String, LazyClassEntry> entry : classIndex.entrySet()) { + System.err.printf("%d %s\n %s\n\n", ++counter, entry.getKey(), entry.getValue()); + } + throw e; + } + } + } + + private static ImmutableList<String> combineWithoutNull( + @Nullable String first, @Nullable String[] others) { + ImmutableList.Builder<String> list = ImmutableList.builder(); + if (first != null) { + list.add(first); + } + if (others != null) { + list.add(others); + } + return list.build(); + } + + /** Builder to build a ClassInfo object from the class file. */ + private static class ClassInfoBuilder extends ClassVisitor { + + private String internalName; + private final ImmutableSet.Builder<MemberInfo> members = ImmutableSet.builder(); + private ImmutableList<String> superClasses; + + public ClassInfoBuilder() { + super(Opcodes.ASM6); + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces) { + checkState(internalName == null && superClasses == null, "This visitor is already used."); + internalName = name; + superClasses = combineWithoutNull(superName, interfaces); + } + + @Override + public FieldVisitor visitField( + int access, String name, String desc, String signature, Object value) { + members.add(MemberInfo.create(internalName, name, desc)); + return null; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions) { + members.add(MemberInfo.create(internalName, name, desc)); + return null; + } + + public ClassInfo build(ImmutableMap<String, LazyClassEntry> classIndex) { + return ClassInfo.create( + checkNotNull(internalName), + superClasses + .stream() + .map(classIndex::get) + .filter(Objects::nonNull) + .map(entry -> entry.state.classInfo().get()) + .collect(ImmutableList.toImmutableList()), + checkNotNull(members).build()); + } + } +} diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassInfo.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassInfo.java new file mode 100644 index 0000000000..d0914640ac --- /dev/null +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ClassInfo.java @@ -0,0 +1,94 @@ +// 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 com.google.auto.value.AutoValue; +import com.google.auto.value.extension.memoized.Memoized; +import com.google.common.base.Strings; +import com.google.common.collect.ComparisonChain; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +/** + * Representation of a class. It maintains the internal name, declared members, as well as the super + * classes. + */ +@AutoValue +public abstract class ClassInfo { + + public static ClassInfo create( + String internalName, + ImmutableList<ClassInfo> superClasses, + ImmutableSet<MemberInfo> declaredMembers) { + return new AutoValue_ClassInfo(internalName, superClasses, declaredMembers); + } + + public abstract String internalName(); + + /** + * Returns all the available super classes. There may be more super classes (super class or + * interfaces), but those do not exist on the classpath. + */ + public abstract ImmutableList<ClassInfo> superClasses(); + + public abstract ImmutableSet<MemberInfo> declaredMembers(); + + public final boolean containsMember(MemberInfo memberInfo) { + if (declaredMembers().contains(memberInfo)) { + return true; + } + for (ClassInfo superClass : superClasses()) { + if (superClass.containsMember(memberInfo)) { + return true; + } + } + return false; + } + + /** A member is either a method or a field. */ + @AutoValue + public abstract static class MemberInfo implements Comparable<MemberInfo> { + + public static MemberInfo create(String owner, String memberName, String descriptor) { + checkArgument(!Strings.isNullOrEmpty(owner), "Empty owner name: %s", owner); + checkArgument(!Strings.isNullOrEmpty(memberName), "Empty method name: %s", memberName); + checkArgument(!Strings.isNullOrEmpty(descriptor), "Empty descriptor: %s", descriptor); + return new AutoValue_ClassInfo_MemberInfo(owner, memberName, descriptor); + } + + /** The declaring class of this member. */ + public abstract String owner(); + + /** The name of the member. */ + public abstract String memberName(); + + /** The descriptor of the member. */ + public abstract String descriptor(); + + @Memoized + @Override + public abstract int hashCode(); + + @Override + public int compareTo(MemberInfo other) { + return ComparisonChain.start() + .compare(this.owner(), other.owner()) + .compare(this.memberName(), other.memberName()) + .compare(this.descriptor(), other.descriptor()) + .result(); + } + } +} diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/DepsCheckerClassVisitor.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/DepsCheckerClassVisitor.java new file mode 100644 index 0000000000..8c4b1cee69 --- /dev/null +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/DepsCheckerClassVisitor.java @@ -0,0 +1,338 @@ +// 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.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) { + 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(owner, name, desc); + if (!classInfo.get().containsMember(member)) { + resultCollector.addMissingMember(member); + } + } + + 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); + } + } + + private AbstractClassEntryState checkInternalName(String 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); + } + 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; + } + if (PRIMITIVE_TYPES.contains(value.getClass())) { + checkType(Type.getType(value.getClass())); + return; + } + throw new UnsupportedOperationException("Unhandled value " + value); + } + + @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) { + 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) { + checkInternalName(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; + } + } +} diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ImportDepsChecker.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ImportDepsChecker.java new file mode 100644 index 0000000000..e73a665be6 --- /dev/null +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ImportDepsChecker.java @@ -0,0 +1,153 @@ +// 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.checkState; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.importdeps.AbstractClassEntryState.IncompleteState; +import com.google.devtools.build.importdeps.ClassInfo.MemberInfo; +import java.io.BufferedWriter; +import java.io.Closeable; +import java.io.IOError; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Collectors; +import java.util.zip.ZipFile; +import org.objectweb.asm.ClassReader; + +/** + * Checker that checks the classes in the input jars have complete dependencies. If not, output the + * missing dependencies to a file. + */ +public class ImportDepsChecker implements Closeable { + + private final ClassCache classCache; + private final ResultCollector resultCollector; + private final ImmutableList<Path> inputJars; + + public ImportDepsChecker( + ImmutableList<Path> bootclasspath, + ImmutableList<Path> classpath, + ImmutableList<Path> inputJars) + throws IOException { + this.classCache = + new ClassCache( + ImmutableList.<Path>builder() + .addAll(bootclasspath) + .addAll(classpath) + .addAll(inputJars) + .build()); + this.resultCollector = new ResultCollector(); + this.inputJars = inputJars; + } + + public ImportDepsChecker check() throws IOException { + for (Path path : inputJars) { + try (ZipFile jarFile = new ZipFile(path.toFile())) { + jarFile + .stream() + .forEach( + entry -> { + String name = entry.getName(); + if (!name.endsWith(".class")) { + return; + } + try (InputStream inputStream = jarFile.getInputStream(entry)) { + ClassReader reader = new ClassReader(inputStream); + DepsCheckerClassVisitor checker = + new DepsCheckerClassVisitor(classCache, resultCollector); + reader.accept(checker, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + } catch (IOException e) { + throw new IOError(e); + } + }); + } + } + return this; + } + + private static final String INDENT = " "; + + public void saveResult(Path resultFile) throws IOException { + if (!Files.exists(resultFile)) { + Files.createFile(resultFile); // Make sure the file exists. + } + try (BufferedWriter writer = Files.newBufferedWriter(resultFile, StandardCharsets.UTF_8)) { + ImmutableList<String> missingClasses = resultCollector.getSortedMissingClassInternalNames(); + for (String missing : missingClasses) { + writer.append("Missing ").append(missing.replace('/', '.')).append('\n'); + } + + ImmutableList<IncompleteState> incompleteClasses = + resultCollector.getSortedIncompleteClasses(); + for (IncompleteState incomplete : incompleteClasses) { + writer + .append("Incomplete ancestor classpath for ") + .append(incomplete.classInfo().get().internalName().replace('/', '.')) + .append('\n'); + + ImmutableList<String> failurePath = incomplete.getResolutionFailurePath(); + checkState(!failurePath.isEmpty(), "The resolution failure path is empty. %s", failurePath); + writer + .append(INDENT) + .append("missing ancestor: ") + .append(failurePath.get(failurePath.size() - 1).replace('/', '.')) + .append('\n'); + writer + .append(INDENT) + .append("resolution failure path: ") + .append( + failurePath + .stream() + .map(internalName -> internalName.replace('/', '.')) + .collect(Collectors.joining(" -> "))) + .append('\n'); + } + ImmutableList<MemberInfo> missingMembers = resultCollector.getSortedMissingMembers(); + for (MemberInfo missing : missingMembers) { + writer + .append("Missing member '") + .append(missing.memberName()) + .append("' in class ") + .append(missing.owner().replace('/', '.')) + .append(" : name=") + .append(missing.memberName()) + .append(", descriptor=") + .append(missing.descriptor()) + .append('\n'); + } + if (missingClasses.size() + incompleteClasses.size() + missingMembers.size() != 0) { + writer + .append("===Total===\n") + .append("missing=") + .append(String.valueOf(missingClasses.size())) + .append('\n') + .append("incomplete=") + .append(String.valueOf(incompleteClasses.size())) + .append('\n') + .append("missing_members=") + .append(String.valueOf(missingMembers.size())); + } + } + } + + @Override + public void close() throws IOException { + classCache.close(); + } +} diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/Main.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/Main.java new file mode 100644 index 0000000000..d549bc2e84 --- /dev/null +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/Main.java @@ -0,0 +1,162 @@ +// 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 com.google.common.collect.ImmutableList; +import com.google.devtools.common.options.Converter; +import com.google.devtools.common.options.Option; +import com.google.devtools.common.options.OptionDocumentationCategory; +import com.google.devtools.common.options.OptionEffectTag; +import com.google.devtools.common.options.OptionsBase; +import com.google.devtools.common.options.OptionsParser; +import com.google.devtools.common.options.OptionsParsingException; +import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor; +import java.io.IOException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.util.List; + +/** + * A checker that checks the completeness of the dependencies of an import target (java_import or + * aar_import). If incomplete, it prints out the list of missing class names to the output file. + */ +public class Main { + + /** Command line options. */ + public static class Options extends OptionsBase { + @Option( + name = "input", + allowMultiple = true, + defaultValue = "", + category = "input", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + converter = ExistingPathConverter.class, + abbrev = 'i', + help = "Input jars with classes to check the completeness of their dependencies." + ) + public List<Path> inputJars; + + @Option( + name = "classpath_entry", + allowMultiple = true, + defaultValue = "", + category = "input", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + converter = ExistingPathConverter.class, + help = + "Ordered classpath (Jar) to resolve symbols in the --input jars, like javac's -cp flag." + ) + public List<Path> classpath; + + @Option( + name = "bootclasspath_entry", + allowMultiple = true, + defaultValue = "", + category = "input", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + converter = ExistingPathConverter.class, + help = + "Bootclasspath that was used to compile the --input Jar with, like javac's " + + "-bootclasspath_entry flag (required)." + ) + public List<Path> bootclasspath; + + @Option( + name = "output", + defaultValue = "", + category = "output", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + converter = PathConverter.class, + help = "Output path to save the result." + ) + public Path output; + } + + public static void main(String[] args) throws IOException { + Options options = parseCommandLineOptions(args); + try (ImportDepsChecker checker = + new ImportDepsChecker( + ImmutableList.copyOf(options.bootclasspath), + ImmutableList.copyOf(options.classpath), + ImmutableList.copyOf(options.inputJars))) { + checker.check().saveResult(options.output); + } + } + + private static Options parseCommandLineOptions(String[] args) throws IOException { + OptionsParser optionsParser = OptionsParser.newOptionsParser(Options.class); + optionsParser.setAllowResidue(false); + optionsParser.enableParamsFileSupport( + new ShellQuotedParamsFilePreProcessor(FileSystems.getDefault())); + optionsParser.parseAndExitUponError(args); + Options options = optionsParser.getOptions(Options.class); + + checkArgument(!options.inputJars.isEmpty(), "--input is required"); + checkArgument( + !options.classpath.isEmpty(), "--classpath_entry is required, at least the bootclasspath"); + checkArgument(!options.bootclasspath.isEmpty(), "--bootclasspath_entry is required"); + return options; + } + + /** Validating converter for Paths. A Path is considered valid if it resolves to a file. */ + public static class PathConverter implements Converter<Path> { + + private final boolean mustExist; + + public PathConverter() { + this.mustExist = false; + } + + protected PathConverter(boolean mustExist) { + this.mustExist = mustExist; + } + + @Override + public Path convert(String input) throws OptionsParsingException { + try { + Path path = FileSystems.getDefault().getPath(input); + if (mustExist && !Files.exists(path)) { + throw new OptionsParsingException( + String.format("%s is not a valid path: it does not exist.", input)); + } + return path; + } catch (InvalidPathException e) { + throw new OptionsParsingException( + String.format("%s is not a valid path: %s.", input, e.getMessage()), e); + } + } + + @Override + public String getTypeDescription() { + return "a valid filesystem path"; + } + } + + /** + * Validating converter for Paths. A Path is considered valid if it resolves to a file and exists. + */ + public static class ExistingPathConverter extends PathConverter { + public ExistingPathConverter() { + super(true); + } + } +} diff --git a/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ResultCollector.java b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ResultCollector.java new file mode 100644 index 0000000000..34253aa8ef --- /dev/null +++ b/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps/ResultCollector.java @@ -0,0 +1,71 @@ +// 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.ImmutableList; +import com.google.devtools.build.importdeps.AbstractClassEntryState.IncompleteState; +import com.google.devtools.build.importdeps.ClassInfo.MemberInfo; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; + +/** The collector that saves all the missing classes. */ +public class ResultCollector { + + private final HashSet<String> missingClasss = new HashSet<>(); + private final HashMap<String, IncompleteState> incompleteClasses = new HashMap<>(); + private final HashSet<MemberInfo> missingMembers = new HashSet<>(); + + public ResultCollector() {} + + public void addMissingOrIncompleteClass(String internalName, AbstractClassEntryState state) { + checkArgument( + internalName.length() > 0 && Character.isJavaIdentifierStart(internalName.charAt(0)), + "The internal name is invalid. %s", + internalName); + if (state.isIncompleteState()) { + IncompleteState oldValue = incompleteClasses.put(internalName, state.asIncompleteState()); + checkState( + oldValue == null || oldValue == state, + "The old value and the new value are not the same object. old=%s, new=%s", + oldValue, + state); + missingClasss.add(state.asIncompleteState().getMissingAncestor()); // Add the real missing. + } else if (state.isMissingState()) { + missingClasss.add(internalName); + } else { + throw new UnsupportedOperationException("Unsupported state " + state); + } + } + + public void addMissingMember(MemberInfo member) { + missingMembers.add(member); + } + + public ImmutableList<String> getSortedMissingClassInternalNames() { + return ImmutableList.sortedCopyOf(missingClasss); + } + + public ImmutableList<IncompleteState> getSortedIncompleteClasses() { + return ImmutableList.sortedCopyOf( + Comparator.comparing(a -> a.classInfo().get().internalName()), incompleteClasses.values()); + } + + public ImmutableList<MemberInfo> getSortedMissingMembers() { + return ImmutableList.sortedCopyOf(missingMembers); + } +} diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/AbstractClassCacheTest.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/AbstractClassCacheTest.java new file mode 100644 index 0000000000..33460f6614 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/AbstractClassCacheTest.java @@ -0,0 +1,96 @@ +// 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.checkNotNull; + +import com.google.common.collect.ImmutableList; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +/** Base class for {@link ClassCacheTest} and {@link DepsCheckerClassVisitorTest}. */ +public abstract class AbstractClassCacheTest { + + static final String PACKAGE_NAME = "com/google/devtools/build/importdeps/testdata/"; + + final Path bootclasspath = getPathFromSystemProperty("classcache.test.bootclasspath"); + + final Path clientJar = getPathFromSystemProperty("classcache.test.Client"); + final ImmutableList<String> clientJarPositives = + ImmutableList.of("Client", "Client$NestedAnnotation") + .stream() + .map(s -> PACKAGE_NAME + s) + .collect(ImmutableList.toImmutableList()); + + final Path libraryJar = getPathFromSystemProperty("classcache.test.Library"); + final ImmutableList<String> libraryJarPositives = + ImmutableList.<String>builder() + .add("Library") + .addAll( + IntStream.range(1, 11) + .mapToObj(i -> "Library$Class" + i) + .collect(ImmutableList.toImmutableList())) + .build() + .stream() + .map(s -> PACKAGE_NAME + s) + .collect(ImmutableList.toImmutableList()); + + final Path libraryWoMembersJar = getPathFromSystemProperty("classcache.test.Library_no_members"); + + final Path libraryAnnotationsJar = + getPathFromSystemProperty("classcache.test.LibraryAnnotations"); + final ImmutableList<String> libraryAnnotationsJarPositives = + ImmutableList.<String>builder() + .add("LibraryAnnotations") + .addAll( + Stream.of( + "ClassAnnotation", + "MethodAnnotation", + "FieldAnnotation", + "ConstructorAnnotation", + "ParameterAnnotation", + "TypeAnnotation", + "AnnotationAnnotation") + .map(name -> "LibraryAnnotations$" + name) + .collect(ImmutableList.toImmutableList())) + .build() + .stream() + .map(s -> PACKAGE_NAME + s) + .collect(ImmutableList.toImmutableList()); + + final Path libraryExceptionJar = getPathFromSystemProperty("classcache.test.LibraryException"); + final ImmutableList<String> libraryExceptionJarPositives = + ImmutableList.of(PACKAGE_NAME + "LibraryException"); + + final Path libraryInterfaceJar = getPathFromSystemProperty("classcache.test.LibraryInterface"); + final ImmutableList<String> libraryInterfacePositives = + ImmutableList.of(PACKAGE_NAME + "LibraryInterface", PACKAGE_NAME + "LibraryInterface$Func"); + + static Path getPathFromSystemProperty(String propertyName) { + String path = + checkNotNull( + System.getProperty(propertyName), "The system property %s is not set.", propertyName); + return Paths.get(path); + } + + /** Flattern a list of lists. */ + static <T> ImmutableList<T> combine(ImmutableList<T>... lists) { + return Arrays.stream(lists) + .flatMap(ImmutableList::stream) + .collect(ImmutableList.toImmutableList()); + } +} diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/BUILD b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/BUILD new file mode 100644 index 0000000000..bbf7085f1a --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/BUILD @@ -0,0 +1,160 @@ +# Description: +# Tests for the checker to check the completeness of the deps of java_import or aar_import targets. +package( + default_testonly = 1, + default_visibility = ["//src:__subpackages__"], +) + +java_test( + name = "ClassInfoTest", + srcs = ["ClassInfoTest.java"], + deps = [ + "//src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps:import_deps_checker", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "ResultCollectorTest", + srcs = ["ResultCollectorTest.java"], + deps = [ + "//src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps:import_deps_checker", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "LazyClassEntryStateTest", + srcs = ["LazyClassEntryStateTest.java"], + deps = [ + "//src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps:import_deps_checker", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "ClassCacheTest", + srcs = [ + "AbstractClassCacheTest.java", + "ClassCacheTest.java", + ], + data = [ + "//src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_client", + "//src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_Library", + "//src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_LibraryAnnotations", + "//src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_LibraryException", + "//src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_LibraryInterface", + "//src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_Library_no_members", + "//third_party/java/jdk:jdk8_rt_jar", + ], + jvm_flags = [ + "-Dclasscache.test.bootclasspath=$(location //third_party/java/jdk:jdk8_rt_jar)", + "-Dclasscache.test.Client=$(location //src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_client)", + "-Dclasscache.test.Library=$(location //src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_Library)", + "-Dclasscache.test.Library_no_members=$(location //src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_Library_no_members)", + "-Dclasscache.test.LibraryAnnotations=$(location //src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_LibraryAnnotations)", + "-Dclasscache.test.LibraryException=$(location //src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_LibraryException)", + "-Dclasscache.test.LibraryInterface=$(location //src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_LibraryInterface)", + ], + test_class = "com.google.devtools.build.importdeps.ClassCacheTest", + deps = [ + "//src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps:import_deps_checker", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + ], +) + +java_test( + name = "DepsCheckerClassVisitorTest", + srcs = [ + "AbstractClassCacheTest.java", + "DepsCheckerClassVisitorTest.java", + ], + data = [ + "//src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_client", + "//src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_Library", + "//src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_LibraryAnnotations", + "//src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_LibraryException", + "//src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_LibraryInterface", + "//src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_Library_no_members", + "//third_party/java/jdk:jdk8_rt_jar", + ], + jvm_flags = [ + "-Dclasscache.test.bootclasspath=$(location //third_party/java/jdk:jdk8_rt_jar)", + "-Dclasscache.test.Client=$(location //src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_client)", + "-Dclasscache.test.Library=$(location //src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_Library)", + "-Dclasscache.test.Library_no_members=$(location //src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_Library_no_members)", + "-Dclasscache.test.LibraryAnnotations=$(location //src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_LibraryAnnotations)", + "-Dclasscache.test.LibraryException=$(location //src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_LibraryException)", + "-Dclasscache.test.LibraryInterface=$(location //src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata:testdata_lib_LibraryInterface)", + ], + test_class = "com.google.devtools.build.importdeps.DepsCheckerClassVisitorTest", + deps = [ + "//src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps:import_deps_checker", + "//third_party:asm", + "//third_party:guava", + "//third_party:junit4", + "//third_party:truth", + "//third_party/java/jdk:jdk8_rt_jar", + ], +) + +load("//src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps:tests.bzl", "create_golden_test") + +create_golden_test( + name = "bootclasspath_missing_golden_test", + golden_file = "golden_bootclasspath_missing.txt", + has_bootclasspath = False, + missing_jar = None, +) + +create_golden_test( + name = "library_exception_missing_golden_test", + golden_file = "golden_library_exception_missing.txt", + has_bootclasspath = True, + missing_jar = "testdata_lib_LibraryException", +) + +create_golden_test( + name = "library_annotation_missing_golden_test", + golden_file = "golden_library_annotation_missing.txt", + has_bootclasspath = True, + missing_jar = "testdata_lib_LibraryAnnotations", +) + +create_golden_test( + name = "library_missing_golden_test", + golden_file = "golden_library_missing.txt", + has_bootclasspath = True, + missing_jar = "testdata_lib_Library", +) + +create_golden_test( + name = "library_interface_missing_golden_test", + golden_file = "golden_library_interface_missing.txt", + has_bootclasspath = True, + missing_jar = "testdata_lib_LibraryInterface", +) + +create_golden_test( + name = "library_members_missing_golden_test", + golden_file = "golden_library_members_missing.txt", + has_bootclasspath = True, + missing_jar = "testdata_lib_Library", + replacing_jar = "testdata_lib_Library_no_members", +) + +create_golden_test( + name = "complete_classpath_golden_test", + golden_file = "golden_complete_classpath.txt", + has_bootclasspath = True, + missing_jar = None, + replacing_jar = None, +) diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ClassCacheTest.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ClassCacheTest.java new file mode 100644 index 0000000000..241afd98c6 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ClassCacheTest.java @@ -0,0 +1,142 @@ +// 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.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.importdeps.AbstractClassEntryState.ExistingState; +import java.io.IOException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test for {@link ClassCache}. */ +@RunWith(JUnit4.class) +public class ClassCacheTest extends AbstractClassCacheTest { + + @Test + public void testLibraryJar() throws Exception { + try (ClassCache cache = new ClassCache(bootclasspath, libraryJar)) { + assertCache( + cache, + libraryJarPositives, + combine( + libraryInterfacePositives, + libraryAnnotationsJarPositives, + libraryExceptionJarPositives)); + } + } + + @Test + public void testClientJarWithSuperClasses() throws IOException { + try (ClassCache cache = + new ClassCache(bootclasspath, clientJar, libraryJar, libraryInterfaceJar)) { + assertCache( + cache, + clientJarPositives, + combine(libraryExceptionJarPositives, libraryAnnotationsJarPositives)); + } + } + + @Test + public void testClientJarWithoutSuperClasses() throws IOException { + try (ClassCache cache = new ClassCache(bootclasspath, clientJar)) { + // Client should be incomplete, as its parent class and interfaces are not available on the + // classpath. The following is the resolution path. + { + AbstractClassEntryState state = cache.getClassState(PACKAGE_NAME + "Client"); + assertThat(state.isIncompleteState()).isTrue(); + + ImmutableList<String> failureCause = state.asIncompleteState().getResolutionFailurePath(); + assertThat(failureCause).containsExactly(PACKAGE_NAME + "Library").inOrder(); + } + assertThat(cache.getClassState(PACKAGE_NAME + "Client").isIncompleteState()).isTrue(); + assertThat(cache.getClassState(PACKAGE_NAME + "Client$NestedAnnotation")) + .isInstanceOf(ExistingState.class); + assertThat(cache.getClassState(PACKAGE_NAME + "Client$NestedAnnotation").isExistingState()) + .isTrue(); + assertThat(cache.getClassState("java/lang/Object").isExistingState()).isTrue(); + assertThat(cache.getClassState("java/util/List").isExistingState()).isTrue(); + } + } + + @Test + public void testLibraryException() throws IOException { + try (ClassCache cache = new ClassCache(bootclasspath, libraryExceptionJar)) { + assertCache( + cache, + libraryExceptionJarPositives, + combine(libraryAnnotationsJarPositives, libraryInterfacePositives, libraryJarPositives)); + } + } + + @Test + public void testLibraryAnnotations() throws IOException { + try (ClassCache cache = new ClassCache(bootclasspath, libraryAnnotationsJar)) { + assertCache( + cache, + libraryAnnotationsJarPositives, + combine(libraryExceptionJarPositives, libraryInterfacePositives, libraryJarPositives)); + } + } + + @Test + public void testCannotAccessClosedCache() throws IOException { + ClassCache cache = new ClassCache(ImmutableList.of()); + cache.close(); + cache.close(); // Can close multiple times. + assertThrows(IllegalStateException.class, () -> cache.getClassState("empty")); + } + + /** + * A regression test. First query the super class, which does not exist. Then query the subclass, + * which does not exist either. + */ + @Test + public void testSuperNotExistThenSubclassNotExist() throws IOException { + try (ClassCache cache = + new ClassCache( + libraryJar, libraryJar, libraryAnnotationsJar, libraryInterfaceJar, clientJar)) { + assertThat( + cache + .getClassState("com/google/devtools/build/importdeps/testdata/Library$Class9") + .isIncompleteState()) + .isTrue(); + assertThat( + cache + .getClassState("com/google/devtools/build/importdeps/testdata/Library$Class10") + .isIncompleteState()) + .isTrue(); + } + } + + private void assertCache( + ClassCache cache, ImmutableList<String> positives, ImmutableList<String> negatives) { + for (String positive : positives) { + AbstractClassEntryState state = cache.getClassState(positive); + assertWithMessage(positive).that(state.isExistingState()).isTrue(); + assertWithMessage(positive).that(state.asExistingState()).isInstanceOf(ExistingState.class); + assertWithMessage(positive) + .that(state.asExistingState().classInfo().get().internalName()) + .isEqualTo(positive); + } + for (String negative : negatives) { + AbstractClassEntryState state = cache.getClassState(negative); + assertWithMessage(negative).that(state.isExistingState()).isFalse(); + } + } +} diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ClassInfoTest.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ClassInfoTest.java new file mode 100644 index 0000000000..54d53f2f22 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ClassInfoTest.java @@ -0,0 +1,82 @@ +// 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.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.importdeps.ClassInfo.MemberInfo; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test for {@link ClassInfo} */ +@RunWith(JUnit4.class) +public class ClassInfoTest { + + public static final String JAVA_LANG_OBJECT = "java/lang/Object"; + private final MemberInfo hashCodeMethod = MemberInfo.create(JAVA_LANG_OBJECT, "hashCode", "()I"); + private final MemberInfo sizeMethod = MemberInfo.create(JAVA_LANG_OBJECT, "clear", "()V"); + + private final ClassInfo objectClass = + ClassInfo.create(JAVA_LANG_OBJECT, ImmutableList.of(), ImmutableSet.of(hashCodeMethod)); + + private final ClassInfo listClass = + ClassInfo.create( + "java/util/List", ImmutableList.of(objectClass), ImmutableSet.of(sizeMethod)); + + @Test + public void testMemberInfo() { + MemberInfo memberInfo = MemberInfo.create(JAVA_LANG_OBJECT, "a", "I"); + assertThat(memberInfo.memberName()).isEqualTo("a"); + assertThat(memberInfo.descriptor()).isEqualTo("I"); + assertThat(memberInfo).isEqualTo(MemberInfo.create("java/lang/Object", "a", "I")); + + assertThat(hashCodeMethod).isEqualTo(MemberInfo.create("java/lang/Object", "hashCode", "()I")); + assertThat(sizeMethod).isEqualTo(MemberInfo.create("java/lang/Object", "clear", "()V")); + } + + @Test + public void testClassInfoCorrectlySet() { + assertThat(objectClass.internalName()).isEqualTo("java/lang/Object"); + assertThat(objectClass.declaredMembers()) + .containsExactly(MemberInfo.create("java/lang/Object", "hashCode", "()I")) + .inOrder(); + assertThat(objectClass.containsMember(MemberInfo.create("java/lang/Object", "hashCode", "()I"))) + .isTrue(); + + assertThat(listClass.internalName()).isEqualTo("java/util/List"); + assertThat(listClass.declaredMembers()).containsExactly(sizeMethod); + assertThat(listClass.containsMember(hashCodeMethod)).isTrue(); + } + + @Test + public void testContainsMember() { + ClassInfo parent = objectClass; + ClassInfo child = listClass; + assertThat(child.superClasses()).contains(parent); + assertThat(parent.containsMember(MemberInfo.create("java/lang/Object", "hashCode", "()I"))) + .isTrue(); + assertThat(parent.containsMember(MemberInfo.create("java/lang/Object", "size", "()I"))) + .isFalse(); + assertThat(parent.containsMember(MemberInfo.create("java/lang/Object", "clear", "()V"))) + .isFalse(); + + assertThat(child.containsMember(MemberInfo.create("java/lang/Object", "hashCode", "()I"))) + .isTrue(); + assertThat(child.containsMember(MemberInfo.create("java/lang/Object", "clear", "()V"))) + .isTrue(); + } +} diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/DepsCheckerClassVisitorTest.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/DepsCheckerClassVisitorTest.java new file mode 100644 index 0000000000..3d19b06638 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/DepsCheckerClassVisitorTest.java @@ -0,0 +1,128 @@ +// 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.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.importdeps.ClassInfo.MemberInfo; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.objectweb.asm.ClassReader; + +/** Test for {@link DepsCheckerClassVisitor}. */ +@RunWith(JUnit4.class) +public class DepsCheckerClassVisitorTest extends AbstractClassCacheTest { + + @Test + public void testMissingLibraryException() throws IOException { + assertThat( + getMissingClassesInClient( + bootclasspath, libraryJar, libraryInterfaceJar, libraryAnnotationsJar, clientJar)) + .containsExactlyElementsIn(libraryExceptionJarPositives); + } + + @Test + public void testMissingLibraryInterface() throws IOException { + assertThat( + getMissingClassesInClient( + bootclasspath, libraryJar, libraryAnnotationsJar, libraryExceptionJar, clientJar)) + .containsExactlyElementsIn(libraryInterfacePositives); + } + + @Test + public void testMissingLibraryAnnotations() throws IOException { + assertThat( + getMissingClassesInClient( + bootclasspath, libraryJar, libraryExceptionJar, libraryInterfaceJar, clientJar)) + .containsExactlyElementsIn(libraryAnnotationsJarPositives); + } + + @Test + public void testMissingLibraryInClient() throws IOException { + assertThat( + getMissingClassesInClient( + bootclasspath, + libraryExceptionJar, + libraryInterfaceJar, + libraryAnnotationsJar, + clientJar)) + .containsExactlyElementsIn(libraryJarPositives); + } + + @Test + public void testMissingMembersInClient() throws IOException { + ResultCollector collector = + getResultCollector( + bootclasspath, + libraryAnnotationsJar, + libraryInterfaceJar, + libraryWoMembersJar, + libraryExceptionJar, + clientJar); + assertThat(collector.getSortedMissingClassInternalNames()).isEmpty(); + assertThat(collector.getSortedMissingMembers()) + .containsExactly( + MemberInfo.create( + "com/google/devtools/build/importdeps/testdata/Library$Class1", + "I", + "Lcom/google/devtools/build/importdeps/testdata/Library$Class1;"), + MemberInfo.create( + "com/google/devtools/build/importdeps/testdata/Library$Class3", + "field", + "Lcom/google/devtools/build/importdeps/testdata/Library$Class4;"), + MemberInfo.create( + "com/google/devtools/build/importdeps/testdata/Library$Class4", + "createClass5", + "()Lcom/google/devtools/build/importdeps/testdata/Library$Class5;"), + MemberInfo.create( + "com/google/devtools/build/importdeps/testdata/Library$Class5", + "create", + "(Lcom/google/devtools/build/importdeps/testdata/Library$Class7;)" + + "Lcom/google/devtools/build/importdeps/testdata/Library$Class6;")) + .inOrder(); + } + + private ImmutableList<String> getMissingClassesInClient(Path... classpath) throws IOException { + ResultCollector resultCollector = getResultCollector(classpath); + return resultCollector.getSortedMissingClassInternalNames(); + } + + private ResultCollector getResultCollector(Path... classpath) throws IOException { + ImmutableList<String> clientClasses = + ImmutableList.of(PACKAGE_NAME + "Client", PACKAGE_NAME + "Client$NestedAnnotation"); + ResultCollector resultCollector = new ResultCollector(); + try (ClassCache cache = new ClassCache(ImmutableList.copyOf(classpath)); + ZipFile zipFile = new ZipFile(clientJar.toFile())) { + + AbstractClassEntryState state = cache.getClassState("java/lang/invoke/LambdaMetafactory"); + System.out.println(state); + for (String clientClass : clientClasses) { + ZipEntry entry = zipFile.getEntry(clientClass + ".class"); + try (InputStream classStream = zipFile.getInputStream(entry)) { + ClassReader reader = new ClassReader(classStream); + DepsCheckerClassVisitor checker = new DepsCheckerClassVisitor(cache, resultCollector); + reader.accept(checker, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + } + } + } + return resultCollector; + } +} diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/LazyClassEntryStateTest.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/LazyClassEntryStateTest.java new file mode 100644 index 0000000000..b2bc7f2973 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/LazyClassEntryStateTest.java @@ -0,0 +1,97 @@ +// 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.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.importdeps.AbstractClassEntryState.ExistingState; +import com.google.devtools.build.importdeps.AbstractClassEntryState.IncompleteState; +import com.google.devtools.build.importdeps.AbstractClassEntryState.MissingState; +import com.google.devtools.build.importdeps.ClassInfo.MemberInfo; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test for {@link AbstractClassEntryState} */ +@RunWith(JUnit4.class) +public class LazyClassEntryStateTest { + + public static final String LIST_CLASS_NAME = "java/util/List"; + public static final ImmutableSet<MemberInfo> METHOD_LIST = + ImmutableSet.of(MemberInfo.create(LIST_CLASS_NAME, "hashCode", "()I")); + public static final ClassInfo LIST_CLASS_INFO = + ClassInfo.create(LIST_CLASS_NAME, ImmutableList.of(), METHOD_LIST); + + @Test + public void testExistingState() { + ExistingState state = ExistingState.create(LIST_CLASS_INFO); + + assertThat(state.isExistingState()).isTrue(); + assertThat(state.isIncompleteState()).isFalse(); + assertThat(state.isMissingState()).isFalse(); + + assertThat(state.asExistingState()).isSameAs(state); + assertThrows(IllegalStateException.class, () -> state.asIncompleteState()); + assertThrows(IllegalStateException.class, () -> state.asMissingState()); + + ClassInfo classInfo = state.classInfo().get(); + assertThat(classInfo.internalName()).isEqualTo("java/util/List"); + assertThat(classInfo.declaredMembers()).hasSize(1); + assertThat(classInfo.declaredMembers()) + .containsExactly(MemberInfo.create("java/util/List", "hashCode", "()I")); + } + + @Test + public void testIncompleteState() { + assertThrows( + IllegalArgumentException.class, + () -> IncompleteState.create(LIST_CLASS_INFO, ImmutableList.of())); + IncompleteState state = + IncompleteState.create(LIST_CLASS_INFO, ImmutableList.of("java/lang/Object")); + + assertThat(state.isExistingState()).isFalse(); + assertThat(state.isIncompleteState()).isTrue(); + assertThat(state.isMissingState()).isFalse(); + + assertThat(state.asIncompleteState()).isSameAs(state); + assertThrows(IllegalStateException.class, () -> state.asExistingState()); + assertThrows(IllegalStateException.class, () -> state.asMissingState()); + + ClassInfo classInfo = state.classInfo().get(); + assertThat(classInfo.internalName()).isEqualTo("java/util/List"); + assertThat(classInfo.declaredMembers()).hasSize(1); + assertThat(classInfo.declaredMembers()) + .containsExactly(MemberInfo.create("java/util/List", "hashCode", "()I")); + + ImmutableList<String> failurePath = state.getResolutionFailurePath(); + assertThat(failurePath).hasSize(1); + assertThat(failurePath).containsExactly("java/lang/Object"); + } + + @Test + public void testMissingState() { + MissingState state = MissingState.singleton(); + + assertThat(state.isMissingState()).isTrue(); + assertThat(state.isExistingState()).isFalse(); + assertThat(state.isIncompleteState()).isFalse(); + + assertThat(state.asMissingState()).isSameAs(state); + assertThrows(IllegalStateException.class, () -> state.asExistingState()); + assertThrows(IllegalStateException.class, () -> state.asIncompleteState()); + } +} diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ResultCollectorTest.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ResultCollectorTest.java new file mode 100644 index 0000000000..0d78504634 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/ResultCollectorTest.java @@ -0,0 +1,81 @@ +// 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.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.importdeps.AbstractClassEntryState.IncompleteState; +import com.google.devtools.build.importdeps.AbstractClassEntryState.MissingState; +import com.google.devtools.build.importdeps.ClassInfo.MemberInfo; +import java.io.IOException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test for {@link ResultCollector}. */ +@RunWith(JUnit4.class) +public class ResultCollectorTest { + + private ResultCollector collector; + + @Before + public void setup() throws IOException { + collector = new ResultCollector(); + } + + @Test + public void testEmptyCollector() throws IOException { + assertThat(collector.getSortedMissingClassInternalNames()).isEmpty(); + assertThat(collector.getSortedMissingMembers()).isEmpty(); + assertThat(collector.getSortedIncompleteClasses()).isEmpty(); + } + + @Test + public void testOneMissingClass() throws IOException { + collector.addMissingOrIncompleteClass("java.lang.String", MissingState.singleton()); + assertThat(collector.getSortedMissingClassInternalNames()).containsExactly("java.lang.String"); + assertThat(collector.getSortedMissingMembers()).isEmpty(); + + collector.addMissingMember(MemberInfo.create("java/lang/Object", "field", "I")); + assertThat(collector.getSortedMissingMembers()) + .containsExactly(MemberInfo.create("java/lang/Object", "field", "I")); + assertThat(collector.getSortedMissingClassInternalNames()).containsExactly("java.lang.String"); + } + + @Test + public void testIncompleteClasses() throws IOException { + collector.addMissingOrIncompleteClass( + "java/lang/String", + IncompleteState.create( + ClassInfo.create("java/lang/String", ImmutableList.of(), ImmutableSet.of()), + ImmutableList.of("java/lang/Object"))); + assertThat(collector.getSortedIncompleteClasses()).hasSize(1); + assertThat(collector.getSortedIncompleteClasses().get(0).classInfo().get()) + .isEqualTo(ClassInfo.create("java/lang/String", ImmutableList.of(), ImmutableSet.of())); + } + + @Test + public void testResultsAreSorted() throws IOException { + collector.addMissingOrIncompleteClass("java.lang.String", MissingState.singleton()); + collector.addMissingOrIncompleteClass("java.lang.Integer", MissingState.singleton()); + collector.addMissingOrIncompleteClass("java.lang.Object", MissingState.singleton()); + + assertThat(collector.getSortedMissingClassInternalNames()) + .containsExactly("java.lang.String", "java.lang.Integer", "java.lang.Object"); + assertThat(collector.getSortedMissingMembers()).isEmpty(); + } +} diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_bootclasspath_missing.txt b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_bootclasspath_missing.txt new file mode 100644 index 0000000000..5a4e67edfe --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_bootclasspath_missing.txt @@ -0,0 +1,87 @@ +Missing java.lang.Exception +Missing java.lang.Object +Missing java.lang.String +Missing java.lang.annotation.Annotation +Missing java.lang.annotation.ElementType +Missing java.lang.annotation.Retention +Missing java.lang.annotation.RetentionPolicy +Missing java.lang.annotation.Target +Missing java.lang.invoke.CallSite +Missing java.lang.invoke.LambdaMetafactory +Missing java.lang.invoke.MethodHandle +Missing java.lang.invoke.MethodHandles$Lookup +Missing java.lang.invoke.MethodType +Missing java.util.Objects +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.Client + missing ancestor: java.lang.Object + resolution failure path: com.google.devtools.build.importdeps.testdata.Library -> java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.Library + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.Library$Class1 + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.Library$Class10 + missing ancestor: java.lang.Object + resolution failure path: com.google.devtools.build.importdeps.testdata.Library$Class9 -> java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.Library$Class2 + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.Library$Class3 + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.Library$Class4 + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.Library$Class5 + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.Library$Class6 + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.Library$Class7 + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.Library$Class8 + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.Library$Class9 + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.LibraryAnnotations + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.LibraryAnnotations$AnnotationAnnotation + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.LibraryAnnotations$ClassAnnotation + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.LibraryAnnotations$ConstructorAnnotation + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.LibraryAnnotations$FieldAnnotation + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.LibraryAnnotations$MethodAnnotation + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.LibraryAnnotations$ParameterAnnotation + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.LibraryAnnotations$TypeAnnotation + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.LibraryException + missing ancestor: java.lang.Exception + resolution failure path: java.lang.Exception +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.LibraryInterface + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.LibraryInterface$Func + missing ancestor: java.lang.Object + resolution failure path: java.lang.Object +===Total=== +missing=14 +incomplete=23 +missing_members=0
\ No newline at end of file diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_complete_classpath.txt b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_complete_classpath.txt new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_complete_classpath.txt diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_annotation_missing.txt b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_annotation_missing.txt new file mode 100644 index 0000000000..60af5de912 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_annotation_missing.txt @@ -0,0 +1,12 @@ +Missing com.google.devtools.build.importdeps.testdata.LibraryAnnotations +Missing com.google.devtools.build.importdeps.testdata.LibraryAnnotations$AnnotationAnnotation +Missing com.google.devtools.build.importdeps.testdata.LibraryAnnotations$ClassAnnotation +Missing com.google.devtools.build.importdeps.testdata.LibraryAnnotations$ConstructorAnnotation +Missing com.google.devtools.build.importdeps.testdata.LibraryAnnotations$FieldAnnotation +Missing com.google.devtools.build.importdeps.testdata.LibraryAnnotations$MethodAnnotation +Missing com.google.devtools.build.importdeps.testdata.LibraryAnnotations$ParameterAnnotation +Missing com.google.devtools.build.importdeps.testdata.LibraryAnnotations$TypeAnnotation +===Total=== +missing=8 +incomplete=0 +missing_members=0
\ No newline at end of file diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_exception_missing.txt b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_exception_missing.txt new file mode 100644 index 0000000000..767f4eb0b9 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_exception_missing.txt @@ -0,0 +1,5 @@ +Missing com.google.devtools.build.importdeps.testdata.LibraryException +===Total=== +missing=1 +incomplete=0 +missing_members=0
\ No newline at end of file diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_interface_missing.txt b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_interface_missing.txt new file mode 100644 index 0000000000..3c3568eb59 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_interface_missing.txt @@ -0,0 +1,9 @@ +Missing com.google.devtools.build.importdeps.testdata.LibraryInterface +Missing com.google.devtools.build.importdeps.testdata.LibraryInterface$Func +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.Client + missing ancestor: com.google.devtools.build.importdeps.testdata.LibraryInterface + resolution failure path: com.google.devtools.build.importdeps.testdata.LibraryInterface +===Total=== +missing=2 +incomplete=1 +missing_members=0
\ No newline at end of file diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_members_missing.txt b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_members_missing.txt new file mode 100644 index 0000000000..73831162b3 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_members_missing.txt @@ -0,0 +1,8 @@ +Missing member 'I' in class com.google.devtools.build.importdeps.testdata.Library$Class1 : name=I, descriptor=Lcom/google/devtools/build/importdeps/testdata/Library$Class1; +Missing member 'field' in class com.google.devtools.build.importdeps.testdata.Library$Class3 : name=field, descriptor=Lcom/google/devtools/build/importdeps/testdata/Library$Class4; +Missing member 'createClass5' in class com.google.devtools.build.importdeps.testdata.Library$Class4 : name=createClass5, descriptor=()Lcom/google/devtools/build/importdeps/testdata/Library$Class5; +Missing member 'create' in class com.google.devtools.build.importdeps.testdata.Library$Class5 : name=create, descriptor=(Lcom/google/devtools/build/importdeps/testdata/Library$Class7;)Lcom/google/devtools/build/importdeps/testdata/Library$Class6; +===Total=== +missing=0 +incomplete=0 +missing_members=4
\ No newline at end of file diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_missing.txt b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_missing.txt new file mode 100644 index 0000000000..12447fc23a --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_library_missing.txt @@ -0,0 +1,18 @@ +Missing com.google.devtools.build.importdeps.testdata.Library +Missing com.google.devtools.build.importdeps.testdata.Library$Class1 +Missing com.google.devtools.build.importdeps.testdata.Library$Class10 +Missing com.google.devtools.build.importdeps.testdata.Library$Class2 +Missing com.google.devtools.build.importdeps.testdata.Library$Class3 +Missing com.google.devtools.build.importdeps.testdata.Library$Class4 +Missing com.google.devtools.build.importdeps.testdata.Library$Class5 +Missing com.google.devtools.build.importdeps.testdata.Library$Class6 +Missing com.google.devtools.build.importdeps.testdata.Library$Class7 +Missing com.google.devtools.build.importdeps.testdata.Library$Class8 +Missing com.google.devtools.build.importdeps.testdata.Library$Class9 +Incomplete ancestor classpath for com.google.devtools.build.importdeps.testdata.Client + missing ancestor: com.google.devtools.build.importdeps.testdata.Library + resolution failure path: com.google.devtools.build.importdeps.testdata.Library +===Total=== +missing=11 +incomplete=1 +missing_members=0
\ No newline at end of file diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_test.sh b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_test.sh new file mode 100755 index 0000000000..d3f090c82c --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/golden_test.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# +# 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. + +gold_file=$1 +shift + +if [ -d "$TEST_TMPDIR" ]; then + # Running as part of blaze test + tmpdir="$TEST_TMPDIR" +else + # Manual run from command line + tmpdir="$(mktemp -d)" +fi + +if [ -d "$TEST_UNDECLARED_OUTPUTS_DIR" ]; then + # Running as part of blaze test: capture test output + output="$TEST_UNDECLARED_OUTPUTS_DIR" +else + # Manual run from command line: just write into temp dir + output="${tmpdir}" +fi + +actual_file="${output}/actual_result.txt" +# Run the checker command. +$@ --output "${actual_file}" &> ${output}/checker_output.txt + +checker_ret=$? +if [[ "${checker_ret}" != 0 ]] ; then + echo "Checker error!!! ${checker_ret}" + cat ${output}/checker_output.txt + exit ${checker_ret} +fi + +diff "${gold_file}" "${actual_file}" + +ret=$? +if [[ "${ret}" != 0 ]] ; then + echo "============== Actual Output ==============" + cat "${actual_file}" + echo "" # New line. + echo "===========================================" +fi +exit ${ret} diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/BUILD b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/BUILD new file mode 100644 index 0000000000..ae40513a05 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/BUILD @@ -0,0 +1,43 @@ +# Description: +# Test data for testing dependency checking. +package( + default_testonly = 1, + default_visibility = ["//src:__subpackages__"], +) + +java_library( + name = "testdata_client", + srcs = ["Client.java"], + deps = [":testdata_lib_neverlink"], +) + +java_library( + name = "testdata_lib_neverlink", + srcs = glob(["Library*.java"]), + neverlink = 1, +) + +java_library( + name = "testdata_lib_Library", + srcs = ["Library.java"], +) + +java_library( + name = "testdata_lib_Library_no_members", + srcs = ["library_no_members/com/google/devtools/build/importdeps/testdata/Library.java"], +) + +java_library( + name = "testdata_lib_LibraryAnnotations", + srcs = ["LibraryAnnotations.java"], +) + +java_library( + name = "testdata_lib_LibraryException", + srcs = ["LibraryException.java"], +) + +java_library( + name = "testdata_lib_LibraryInterface", + srcs = ["LibraryInterface.java"], +) diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/Client.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/Client.java new file mode 100644 index 0000000000..6b833c3b61 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/Client.java @@ -0,0 +1,67 @@ +// 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.testdata; + +import com.google.devtools.build.importdeps.testdata.LibraryAnnotations.AnnotationAnnotation; +import com.google.devtools.build.importdeps.testdata.LibraryAnnotations.ClassAnnotation; +import com.google.devtools.build.importdeps.testdata.LibraryAnnotations.ConstructorAnnotation; +import com.google.devtools.build.importdeps.testdata.LibraryAnnotations.FieldAnnotation; +import com.google.devtools.build.importdeps.testdata.LibraryAnnotations.MethodAnnotation; +import com.google.devtools.build.importdeps.testdata.LibraryAnnotations.ParameterAnnotation; +import com.google.devtools.build.importdeps.testdata.LibraryAnnotations.TypeAnnotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Objects; + +/** Client class that uses several libraries. */ +@ClassAnnotation +public class Client<@TypeAnnotation T> extends Library implements LibraryInterface { + + @SuppressWarnings("unused") + @FieldAnnotation + private Library.Class1 field; + + @SuppressWarnings("unused") + @FieldAnnotation + private LibraryAnnotations annotations; + + public static final Class1 I = Class1.I; + + @ConstructorAnnotation + public Client() {} + + @MethodAnnotation + public void method(@ParameterAnnotation int p, Library.Class2 p2) throws LibraryException { + Objects.nonNull(p2); // javac9 silently uses Objects. + Class3 c3 = new Class3(); + Class4 c4 = c3.field; + c3.field = c4; + Func<Class5> func = c4::createClass5; + Class5 class5 = func.get(); + @SuppressWarnings("unused") + Class6 class6 = class5.create(new Class7()); + @SuppressWarnings("unused") + Class8[][] array = new Class8[10][10]; + Class9[] array2 = new Class9[10]; + array2[0] = new Class10(); + } + + /** An inner annotation. */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @AnnotationAnnotation + public @interface NestedAnnotation {} +} diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/Library.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/Library.java new file mode 100644 index 0000000000..31049ba2d5 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/Library.java @@ -0,0 +1,60 @@ +// 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.testdata; + +/** A library class for testing. */ +public class Library { + + /** A library class for testing. */ + public static class Class1 { + public static final Class1 I = new Class1(); + } + + /** A library class for testing. */ + public static class Class2 {} + + /** A library class for testing. */ + public static class Class3 { + public Class4 field; + } + + /** A library class for testing. */ + public static class Class4 { + Class5 createClass5() { + return new Class5(); + } + } + + /** A library class for testing. */ + public static class Class5 { + public Class6 create(Class7 class7) { + return new Class6(); + } + } + + /** A library class for testing. */ + public static class Class6 {} + + /** A library class for testing. */ + public static class Class7 {} + + /** A library class for testing. */ + public static class Class8 {} + + /** A library class for testing. */ + public static class Class9 {} + + /** A library class for testing. */ + public static class Class10 extends Class9 {} +} diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/LibraryAnnotations.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/LibraryAnnotations.java new file mode 100644 index 0000000000..d0b883ae57 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/LibraryAnnotations.java @@ -0,0 +1,57 @@ +// 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.testdata; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** A library annotation for testing. */ +public class LibraryAnnotations { + /** A library annotation for testing. */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface ClassAnnotation {} + + /** A library annotation for testing. */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface MethodAnnotation {} + + /** A library annotation for testing. */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface FieldAnnotation {} + + /** A library annotation for testing. */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.CONSTRUCTOR) + public @interface ConstructorAnnotation {} + + /** A library annotation for testing. */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + public @interface ParameterAnnotation {} + + /** A library annotation for testing. */ + @Retention(RetentionPolicy.RUNTIME) + @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) + public @interface TypeAnnotation {} + + /** A library annotation for testing. */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.ANNOTATION_TYPE) + public @interface AnnotationAnnotation {} +} diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/LibraryException.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/LibraryException.java new file mode 100644 index 0000000000..ff572a005a --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/LibraryException.java @@ -0,0 +1,17 @@ +// 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.testdata; + +/** A library exception for testing. */ +public class LibraryException extends Exception {} diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/LibraryInterface.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/LibraryInterface.java new file mode 100644 index 0000000000..649adc105a --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/LibraryInterface.java @@ -0,0 +1,23 @@ +// 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.testdata; + +/** A library interface for testing. */ +public interface LibraryInterface { + + /** A nested interface for testing. */ + interface Func<T> { + T get(); + } +} diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/library_no_members/com/google/devtools/build/importdeps/testdata/Library.java b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/library_no_members/com/google/devtools/build/importdeps/testdata/Library.java new file mode 100644 index 0000000000..991baa0cd7 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata/library_no_members/com/google/devtools/build/importdeps/testdata/Library.java @@ -0,0 +1,51 @@ +// 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.testdata; + +/** + * A library class for testing. This library class is very similar to {@link Library}, but it does + * not have any members (fields or methods). + */ +public class Library { + + /** A library class for testing. */ + public static class Class1 {} + + /** A library class for testing. */ + public static class Class2 {} + + /** A library class for testing. */ + public static class Class3 {} + + /** A library class for testing. */ + public static class Class4 {} + + /** A library class for testing. */ + public static class Class5 {} + + /** A library class for testing. */ + public static class Class6 {} + + /** A library class for testing. */ + public static class Class7 {} + + /** A library class for testing. */ + public static class Class8 {} + + /** A library class for testing. */ + public static class Class9 {} + + /** A library class for testing. */ + public static class Class10 extends Class9 {} +} diff --git a/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/tests.bzl b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/tests.bzl new file mode 100644 index 0000000000..3edf246089 --- /dev/null +++ b/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/tests.bzl @@ -0,0 +1,64 @@ +# 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. +'''Helpers to create golden tests, to minimize code duplication.''' + +def create_golden_test(name, golden_file, has_bootclasspath, missing_jar = None, replacing_jar = None): + '''Create a golden test for the dependency checker.''' + all_dep_jars = [ + "testdata_client", + "testdata_lib_Library", + "testdata_lib_LibraryAnnotations", + "testdata_lib_LibraryException", + "testdata_lib_LibraryInterface", + ] + testdata_pkg = "//third_party/bazel/src/java_tools/import_deps_checker/javatests/com/google/devtools/build/importdeps/testdata" + import_deps_checker = "//third_party/bazel/src/java_tools/import_deps_checker/java/com/google/devtools/build/importdeps:ImportDepsChecker" + client_jar = testdata_pkg + ":testdata_client" + data = [ + golden_file, + import_deps_checker, + "//third_party/java/jdk:jdk8_rt_jar" + ] + [testdata_pkg + ":" + x for x in all_dep_jars] + if (replacing_jar): + data.append(testdata_pkg + ":" + replacing_jar) + + args = [ + "$(location %s)" % golden_file, + "$(location %s)" % import_deps_checker, + ] + args.append("--bootclasspath_entry") + if has_bootclasspath: + args.append("$(location //third_party/java/jdk:jdk8_rt_jar)") + else: + args.append("$(location %s)" % client_jar) # Fake bootclasspath. + + for dep in all_dep_jars: + if dep == missing_jar: + if replacing_jar: + args.append("--classpath_entry") + args.append("$(location %s:%s)" % (testdata_pkg, replacing_jar)) + continue + args.append("--classpath_entry") + args.append("$(location %s:%s)" % (testdata_pkg, dep)) + + args = args + [ + "--input", + "$(location %s:testdata_client)" % testdata_pkg, + ] + native.sh_test( + name=name, + srcs = ["golden_test.sh"], + args = args, + data = data, + ) |