// Copyright 2017 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.android.desugar; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import static com.google.devtools.build.android.desugar.DefaultMethodClassFixer.SubtypeComparator.INSTANCE; import com.google.common.collect.ImmutableList; import com.google.common.io.Closer; import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter; import com.google.devtools.build.android.desugar.io.HeaderClassLoader; import com.google.devtools.build.android.desugar.io.IndexedInputs; import com.google.devtools.build.android.desugar.io.ThrowingClassLoader; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.TreeSet; import java.util.concurrent.Callable; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; /** Unit Test for {@link DefaultMethodClassFixer} */ @RunWith(JUnit4.class) public class DefaultMethodClassFixerTest { private ClassReaderFactory classpathReader; private ClassReaderFactory bootclassPath; private ClassLoader classLoader; private Closer closer; @Before public void setup() throws IOException { closer = Closer.create(); CoreLibraryRewriter rewriter = new CoreLibraryRewriter(""); IndexedInputs indexedInputs = toIndexedInputs(closer, System.getProperty("DefaultMethodClassFixerTest.input")); IndexedInputs indexedClasspath = toIndexedInputs(closer, System.getProperty("DefaultMethodClassFixerTest.classpath")); IndexedInputs indexedBootclasspath = toIndexedInputs(closer, System.getProperty("DefaultMethodClassFixerTest.bootclasspath")); bootclassPath = new ClassReaderFactory(indexedBootclasspath, rewriter); IndexedInputs indexedClasspathAndInputFiles = indexedClasspath.withParent(indexedInputs); classpathReader = new ClassReaderFactory(indexedClasspathAndInputFiles, rewriter); ClassLoader bootclassloader = new HeaderClassLoader(indexedBootclasspath, rewriter, new ThrowingClassLoader()); classLoader = new HeaderClassLoader(indexedClasspathAndInputFiles, rewriter, bootclassloader); } @After public void teardown() throws IOException { closer.close(); } private static IndexedInputs toIndexedInputs(Closer closer, String stringPathList) throws IOException { final List pathList = readPathListFromString(stringPathList); return new IndexedInputs(Desugar.toRegisteredInputFileProvider(closer, pathList)); } private static List readPathListFromString(String pathList) { return Arrays.stream(checkNotNull(pathList).split(File.pathSeparator)) .map(Paths::get) .collect(ImmutableList.toImmutableList()); } private byte[] desugar(String classname) { ClassReader reader = classpathReader.readIfKnown(classname); return desugar(reader); } private byte[] desugar(ClassReader reader) { ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); DefaultMethodClassFixer fixer = new DefaultMethodClassFixer( writer, classpathReader, DependencyCollector.NoWriteCollectors.FAIL_ON_MISSING, /*coreLibrarySupport=*/ null, bootclassPath, classLoader); reader.accept(fixer, 0); return writer.toByteArray(); } private byte[] desugar(byte[] classContent) { ClassReader reader = new ClassReader(classContent); return desugar(reader); } @Test public void testDesugaringDirectImplementation() { byte[] desugaredClass = desugar( ("com.google.devtools.build.android.desugar.testdata.java8." + "DefaultInterfaceMethodWithStaticInitializer$TestInterfaceSetOne$C") .replace('.', '/')); checkClinitForDefaultInterfaceMethodWithStaticInitializerTestInterfaceSetOneC(desugaredClass); byte[] desugaredClassAgain = desugar(desugaredClass); checkClinitForDefaultInterfaceMethodWithStaticInitializerTestInterfaceSetOneC( desugaredClassAgain); desugaredClassAgain = desugar(desugaredClassAgain); checkClinitForDefaultInterfaceMethodWithStaticInitializerTestInterfaceSetOneC( desugar(desugaredClassAgain)); } private void checkClinitForDefaultInterfaceMethodWithStaticInitializerTestInterfaceSetOneC( byte[] classContent) { ClassReader reader = new ClassReader(classContent); reader.accept( new ClassVisitor(Opcodes.ASM5) { class ClinitMethod extends MethodNode { public ClinitMethod( int access, String name, String desc, String signature, String[] exceptions) { super(Opcodes.ASM5, access, name, desc, signature, exceptions); } } private ClinitMethod clinit; @Override public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions) { if ("".equals(name)) { assertThat(clinit).isNull(); clinit = new ClinitMethod(access, name, desc, signature, exceptions); return clinit; } return super.visitMethod(access, name, desc, signature, exceptions); } @Override public void visitEnd() { assertThat(clinit).isNotNull(); assertThat(clinit.instructions.size()).isEqualTo(3); AbstractInsnNode instruction = clinit.instructions.getFirst(); { assertThat(instruction).isInstanceOf(MethodInsnNode.class); MethodInsnNode field = (MethodInsnNode) instruction; assertThat(field.owner) .isEqualTo( "com/google/devtools/build/android/desugar/testdata/java8/" + "DefaultInterfaceMethodWithStaticInitializer" + "$TestInterfaceSetOne$I1$$CC"); assertThat(field.name) .isEqualTo(InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME); assertThat(field.desc) .isEqualTo(InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC); } { instruction = instruction.getNext(); assertThat(instruction).isInstanceOf(MethodInsnNode.class); MethodInsnNode field = (MethodInsnNode) instruction; assertThat(field.owner) .isEqualTo( "com/google/devtools/build/android/desugar/testdata/java8/" + "DefaultInterfaceMethodWithStaticInitializer" + "$TestInterfaceSetOne$I2$$CC"); assertThat(field.name) .isEqualTo(InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME); assertThat(field.desc) .isEqualTo(InterfaceDesugaring.COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC); } { instruction = instruction.getNext(); assertThat(instruction).isInstanceOf(InsnNode.class); assertThat(instruction.getOpcode()).isEqualTo(Opcodes.RETURN); } } }, 0); } @Test public void testInterfaceComparator() { assertThat(INSTANCE.compare(Runnable.class, Runnable.class)).isEqualTo(0); assertThat(INSTANCE.compare(Runnable.class, MyRunnable1.class)).isEqualTo(1); assertThat(INSTANCE.compare(MyRunnable2.class, Runnable.class)).isEqualTo(-1); assertThat(INSTANCE.compare(MyRunnable3.class, Runnable.class)).isEqualTo(-1); assertThat(INSTANCE.compare(MyRunnable1.class, MyRunnable3.class)).isEqualTo(1); assertThat(INSTANCE.compare(MyRunnable3.class, MyRunnable2.class)).isEqualTo(-1); assertThat(INSTANCE.compare(MyRunnable2.class, MyRunnable1.class)).isGreaterThan(0); assertThat(INSTANCE.compare(Runnable.class, Serializable.class)).isGreaterThan(0); assertThat(INSTANCE.compare(Serializable.class, Runnable.class)).isLessThan(0); TreeSet> orderedSet = new TreeSet<>(INSTANCE); orderedSet.add(Serializable.class); orderedSet.add(Runnable.class); orderedSet.add(MyRunnable2.class); orderedSet.add(Callable.class); orderedSet.add(Serializable.class); orderedSet.add(MyRunnable1.class); orderedSet.add(MyRunnable3.class); assertThat(orderedSet) .containsExactly( MyRunnable3.class, // subtype before supertype(s) MyRunnable1.class, MyRunnable2.class, Serializable.class, // java... comes textually after com.google... Runnable.class, Callable.class) .inOrder(); } private static interface MyRunnable1 extends Runnable {} private static interface MyRunnable2 extends Runnable {} private static interface MyRunnable3 extends MyRunnable1, MyRunnable2 {} }