aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android/desugar/io/HeaderClassLoader.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android/desugar/io/HeaderClassLoader.java')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/io/HeaderClassLoader.java234
1 files changed, 234 insertions, 0 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/io/HeaderClassLoader.java b/src/tools/android/java/com/google/devtools/build/android/desugar/io/HeaderClassLoader.java
new file mode 100644
index 0000000000..f70dc0e3e2
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/io/HeaderClassLoader.java
@@ -0,0 +1,234 @@
+// Copyright 2016 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.io;
+
+import com.google.common.collect.ImmutableList;
+import java.io.IOError;
+import java.io.IOException;
+import java.io.InputStream;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+/**
+ * Class loader that can "load" classes from header Jars. This class loader stubs in missing code
+ * attributes on the fly to make {@link ClassLoader#defineClass} happy. Classes loaded are unusable
+ * other than to resolve method references, so this class loader should only be used to process or
+ * inspect classes, not to execute their code. Also note that the resulting classes may be missing
+ * private members, which header Jars may omit.
+ *
+ * @see java.net.URLClassLoader
+ */
+public class HeaderClassLoader extends ClassLoader {
+
+ private final IndexedInputs indexedInputs;
+ private final CoreLibraryRewriter rewriter;
+
+ public HeaderClassLoader(
+ IndexedInputs indexedInputs, CoreLibraryRewriter rewriter, ClassLoader parent) {
+ super(parent);
+ this.rewriter = rewriter;
+ this.indexedInputs = indexedInputs;
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ String filename = rewriter.unprefix(name.replace('.', '/') + ".class");
+ InputFileProvider inputFileProvider = indexedInputs.getInputFileProvider(filename);
+ if (inputFileProvider == null) {
+ throw new ClassNotFoundException("Class " + name + " not found");
+ }
+ byte[] bytecode;
+ try (InputStream content = inputFileProvider.getInputStream(filename)) {
+ ClassReader reader = rewriter.reader(content);
+ // Have ASM compute maxs so we don't need to figure out how many formal parameters there are
+ ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+ ImmutableList<FieldInfo> interfaceFieldNames = getFieldsIfReaderIsInterface(reader);
+ // TODO(kmb): Consider SKIP_CODE and stubbing everything so class loader doesn't verify code
+ reader.accept(new CodeStubber(writer, interfaceFieldNames), ClassReader.SKIP_DEBUG);
+ bytecode = writer.toByteArray();
+ } catch (IOException e) {
+ throw new IOError(e);
+ }
+ return defineClass(name, bytecode, 0, bytecode.length);
+ }
+
+ /**
+ * If the {@code reader} is an interface, then extract all the declared fields in it. Otherwise,
+ * return an empty list.
+ */
+ private static ImmutableList<FieldInfo> getFieldsIfReaderIsInterface(ClassReader reader) {
+ if (BitFlags.isSet(reader.getAccess(), Opcodes.ACC_INTERFACE)) {
+ NonPrimitiveFieldCollector collector = new NonPrimitiveFieldCollector();
+ reader.accept(collector, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
+ return collector.declaredNonPrimitiveFields.build();
+ }
+ return ImmutableList.of();
+ }
+
+ /** Collect the fields defined in a class. */
+ private static class NonPrimitiveFieldCollector extends ClassVisitor {
+
+ final ImmutableList.Builder<FieldInfo> declaredNonPrimitiveFields = ImmutableList.builder();
+ private String internalName;
+
+ public NonPrimitiveFieldCollector() {
+ super(Opcodes.ASM6);
+ }
+
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ this.internalName = name;
+ }
+
+ @Override
+ public FieldVisitor visitField(
+ int access, String name, String desc, String signature, Object value) {
+ if (isNonPrimitiveType(desc)) {
+ declaredNonPrimitiveFields.add(FieldInfo.create(internalName, name, desc));
+ }
+ return null;
+ }
+
+ private static boolean isNonPrimitiveType(String type) {
+ char firstChar = type.charAt(0);
+ return firstChar == '[' || firstChar == 'L';
+ }
+ }
+
+
+ /**
+ * Class visitor that stubs in missing code attributes, and erases the body of the static
+ * initializer of functional interfaces if the interfaces have default methods. The erasion of the
+ * clinit is mainly because when we are desugaring lambdas, we need to load the functional
+ * interfaces via class loaders, and since the interfaces have default methods, according to the
+ * JVM spec, these interfaces will be executed. This should be prevented due to security concerns.
+ */
+ private static class CodeStubber extends ClassVisitor {
+
+ private String internalName;
+ private boolean isInterface;
+ private final ImmutableList<FieldInfo> interfaceFields;
+
+ public CodeStubber(ClassVisitor cv, ImmutableList<FieldInfo> interfaceFields) {
+ super(Opcodes.ASM6, cv);
+ this.interfaceFields = interfaceFields;
+ }
+
+ @Override
+ public void visit(
+ int version,
+ int access,
+ String name,
+ String signature,
+ String superName,
+ String[] interfaces) {
+ super.visit(version, access, name, signature, superName, interfaces);
+ isInterface = BitFlags.isSet(access, Opcodes.ACC_INTERFACE);
+ internalName = name;
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String desc, String signature, String[] exceptions) {
+ MethodVisitor dest = super.visitMethod(access, name, desc, signature, exceptions);
+ if ((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) != 0) {
+ // No need to stub out abstract or native methods
+ return dest;
+ }
+ if (isInterface && "<clinit>".equals(name)) {
+ // Delete class initializers, to avoid code gets executed when we desugar lambdas.
+ // See b/62184142
+ return new InterfaceInitializerEraser(dest, internalName, interfaceFields);
+ }
+ return new BodyStubber(dest);
+ }
+ }
+
+ /**
+ * Erase the static initializer of an interface. Given an interface with non-primitive fields,
+ * this eraser discards the original body of clinit, and initializes each non-primitive field to
+ * null
+ */
+ private static class InterfaceInitializerEraser extends MethodVisitor {
+
+ private final MethodVisitor dest;
+ private final ImmutableList<FieldInfo> interfaceFields;
+
+ public InterfaceInitializerEraser(
+ MethodVisitor mv, String internalName, ImmutableList<FieldInfo> interfaceFields) {
+ super(Opcodes.ASM6);
+ dest = mv;
+ this.interfaceFields = interfaceFields;
+ }
+
+ @Override
+ public void visitCode() {
+ dest.visitCode();
+ }
+
+ @Override
+ public void visitEnd() {
+ for (FieldInfo fieldInfo : interfaceFields) {
+ dest.visitInsn(Opcodes.ACONST_NULL);
+ dest.visitFieldInsn(
+ Opcodes.PUTSTATIC, fieldInfo.owner(), fieldInfo.name(), fieldInfo.desc());
+ }
+ dest.visitInsn(Opcodes.RETURN);
+ dest.visitMaxs(0, 0);
+ dest.visitEnd();
+ }
+ }
+
+ /** Method visitor used by {@link CodeStubber} to put code into methods without code. */
+ private static class BodyStubber extends MethodVisitor {
+
+ private static final String EXCEPTION_INTERNAL_NAME = "java/lang/UnsupportedOperationException";
+
+ private boolean hasCode = false;
+
+ public BodyStubber(MethodVisitor mv) {
+ super(Opcodes.ASM6, mv);
+ }
+
+ @Override
+ public void visitCode() {
+ hasCode = true;
+ super.visitCode();
+ }
+
+ @Override
+ public void visitEnd() {
+ if (!hasCode) {
+ super.visitTypeInsn(Opcodes.NEW, EXCEPTION_INTERNAL_NAME);
+ super.visitInsn(Opcodes.DUP);
+ super.visitMethodInsn(
+ Opcodes.INVOKESPECIAL, EXCEPTION_INTERNAL_NAME, "<init>", "()V", /*itf*/ false);
+ super.visitInsn(Opcodes.ATHROW);
+ super.visitMaxs(0, 0); // triggers computation of the actual max's
+ }
+ super.visitEnd();
+ }
+ }
+}