| // 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; |
| |
| import java.io.IOError; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.jar.JarFile; |
| import java.util.zip.ZipEntry; |
| 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; |
| |
| /** |
| * 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 |
| */ |
| class HeaderClassLoader extends ClassLoader { |
| |
| private final IndexedJars indexedJars; |
| private final CoreLibraryRewriter rewriter; |
| |
| public HeaderClassLoader( |
| IndexedJars indexedJars, CoreLibraryRewriter rewriter, ClassLoader parent) { |
| super(parent); |
| this.rewriter = rewriter; |
| this.indexedJars = indexedJars; |
| } |
| |
| @Override |
| protected Class<?> findClass(String name) throws ClassNotFoundException { |
| String filename = rewriter.unprefix(name.replace('.', '/') + ".class"); |
| JarFile jarfile = indexedJars.getJarFile(filename); |
| if (jarfile == null) { |
| throw new ClassNotFoundException(); |
| } |
| ZipEntry entry = jarfile.getEntry(filename); |
| byte[] bytecode; |
| try (InputStream content = jarfile.getInputStream(entry)) { |
| 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); |
| reader.accept(new CodeStubber(writer), 0); |
| bytecode = writer.toByteArray(); |
| } catch (IOException e) { |
| throw new IOError(e); |
| } |
| return defineClass(name, bytecode, 0, bytecode.length); |
| } |
| |
| /** Class visitor that stubs in missing code attributes. */ |
| private static class CodeStubber extends ClassVisitor { |
| |
| public CodeStubber(ClassVisitor cv) { |
| super(Opcodes.ASM5, cv); |
| } |
| |
| @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; |
| } |
| return new BodyStubber(dest); |
| } |
| |
| } |
| |
| /** 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.ASM5, 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(); |
| } |
| } |
| } |