aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools
diff options
context:
space:
mode:
authorGravatar Colin Cross <ccross@google.com>2017-02-22 23:02:44 +0000
committerGravatar Yue Gan <yueg@google.com>2017-02-23 11:31:00 +0000
commit19e126a1a7643e170ceec65a11f6a4d330ae1b3b (patch)
tree122df8eaa766e0eae39dab89aabca53772d131d3 /src/tools
parent1d7db156291762b46550b9610ee1b1c73fde58ad (diff)
Add a flag to enable desugaring java.* classes by rewriting package names
java.* and sun.* classes cannot be desugared directly because there are hard coded restrictions in the JVM that prevent using loading or using reflection on them. Add a --core_library flag that rewrites package names to have a __desugar__ prefix when reading them, and strips the prefix when writing them back out. -- PiperOrigin-RevId: 148273386 MOS_MIGRATED_REVID=148273386
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/BUILD1
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java8
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryRewriter.java155
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java54
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java15
5 files changed, 209 insertions, 24 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD
index cc2dcead72..73c28453de 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD
@@ -18,6 +18,7 @@ java_library(
"//src/main/protobuf:worker_protocol_java_proto",
"//src/tools/android/java/com/google/devtools/build/android:android_builder_lib",
"//third_party:asm",
+ "//third_party:asm-commons",
"//third_party:asm-tree",
"//third_party:auto_value",
"//third_party:guava",
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java b/src/tools/android/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java
index d324723853..6ec4e0d4c9 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java
@@ -22,9 +22,11 @@ import org.objectweb.asm.ClassReader;
class ClassReaderFactory {
private final ZipFile jar;
+ private final CoreLibraryRewriter rewriter;
- public ClassReaderFactory(ZipFile jar) {
+ public ClassReaderFactory(ZipFile jar, CoreLibraryRewriter rewriter) {
this.jar = jar;
+ this.rewriter = rewriter;
}
/**
@@ -34,13 +36,13 @@ class ClassReaderFactory {
*/
@Nullable
public ClassReader readIfKnown(String internalClassName) {
- ZipEntry entry = jar.getEntry(internalClassName + ".class");
+ ZipEntry entry = jar.getEntry(rewriter.unprefix(internalClassName) + ".class");
if (entry == null) {
return null;
}
try (InputStream bytecode = jar.getInputStream(entry)) {
// ClassReader doesn't take ownership and instead eagerly reads the stream's contents
- return new ClassReader(bytecode);
+ return rewriter.reader(bytecode);
} catch (IOException e) {
// We should've already read through all files in the Jar once at this point, so we don't
// expect failures reading some files a second time.
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryRewriter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryRewriter.java
new file mode 100644
index 0000000000..4a2510908d
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/CoreLibraryRewriter.java
@@ -0,0 +1,155 @@
+// 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 java.io.IOException;
+import java.io.InputStream;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.ClassRemapper;
+import org.objectweb.asm.commons.Remapper;
+
+/** Utility class to prefix or unprefix class names of core library classes */
+class CoreLibraryRewriter {
+ private final String prefix;
+
+ public CoreLibraryRewriter(String prefix) {
+ this.prefix = prefix;
+ }
+
+ /**
+ * Factory method that returns either a normal ClassReader if prefix is empty, or a ClassReader
+ * with a ClassRemapper that prefixes class names of core library classes if prefix is not empty.
+ */
+ public ClassReader reader(InputStream content) throws IOException {
+ if (prefix.length() != 0) {
+ return new PrefixingClassReader(content);
+ } else {
+ return new ClassReader(content);
+ }
+ }
+
+ /**
+ * Factory method that returns a ClassVisitor that delegates to a ClassWriter, removing prefix
+ * from core library class names if it is not empty.
+ */
+ public UnprefixingClassWriter writer(int flags) {
+ return new UnprefixingClassWriter(flags);
+ }
+
+ private static boolean shouldPrefix(String typeName) {
+ return (typeName.startsWith("java/") || typeName.startsWith("sun/")) && !except(typeName);
+ }
+
+ private static boolean except(String typeName) {
+ if (typeName.startsWith("java/lang/invoke/")) {
+ return true;
+ }
+
+ switch (typeName) {
+ // Autoboxed types
+ case "java/lang/Boolean":
+ case "java/lang/Byte":
+ case "java/lang/Character":
+ case "java/lang/Double":
+ case "java/lang/Float":
+ case "java/lang/Integer":
+ case "java/lang/Long":
+ case "java/lang/Number":
+ case "java/lang/Short":
+
+ // Special types
+ case "java/lang/Class":
+ case "java/lang/Object":
+ case "java/lang/String":
+ case "java/lang/Throwable":
+ return true;
+
+ default: // fall out
+ }
+
+ return false;
+ }
+
+ /** Prefixes core library class names with prefix */
+ public String prefix(String typeName) {
+ if (prefix.length() > 0 && shouldPrefix(typeName)) {
+ return prefix + typeName;
+ }
+ return typeName;
+ }
+
+ /** Removes prefix from class names */
+ public String unprefix(String typeName) {
+ if (prefix.length() == 0 || !typeName.startsWith(prefix)) {
+ return typeName;
+ }
+ return typeName.substring(prefix.length());
+ }
+
+ /**
+ * ClassReader that prefixes core library class names as they are read
+ */
+ private class PrefixingClassReader extends ClassReader {
+ PrefixingClassReader(InputStream content) throws IOException {
+ super(content);
+ }
+
+ @Override
+ public void accept(ClassVisitor cv, Attribute[] attrs, int flags) {
+ cv =
+ new ClassRemapper(
+ cv,
+ new Remapper() {
+ @Override
+ public String map(String typeName) {
+ return prefix(typeName);
+ }
+ });
+ super.accept(cv, attrs, flags);
+ }
+ }
+
+ /**
+ * ClassVisitor that delegates to a ClassWriter, but removes a prefix as each class is written.
+ * The unprefixing is optimized out if prefix is empty.
+ */
+ public class UnprefixingClassWriter extends ClassVisitor {
+ private final ClassWriter writer;
+
+ UnprefixingClassWriter(int flags) {
+ super(Opcodes.ASM5);
+ this.writer = new ClassWriter(flags);
+ this.cv = this.writer;
+ if (prefix.length() != 0) {
+ this.cv =
+ new ClassRemapper(
+ this.cv,
+ new Remapper() {
+ @Override
+ public String map(String typeName) {
+ return unprefix(typeName);
+ }
+ });
+ }
+ }
+
+ byte[] toByteArray() {
+ return writer.toByteArray();
+ }
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
index 687e513937..9fdd82988e 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
@@ -105,6 +105,12 @@ class Desugar {
help = "Minimum targeted sdk version. If >= 24, enables default methods in interfaces."
)
public int minSdkVersion;
+
+ @Option(name = "core_library",
+ defaultValue = "false",
+ category = "undocumented",
+ help = "Enables rewriting to desugar java.* classes.")
+ public boolean coreLibrary;
}
public static void main(String[] args) throws Exception {
@@ -142,13 +148,19 @@ class Desugar {
parent = new ThrowingClassLoader();
}
+ CoreLibraryRewriter rewriter =
+ new CoreLibraryRewriter(options.coreLibrary ? "__desugar__/" : "");
+
ClassLoader loader =
- createClassLoader(options.bootclasspath, options.inputJar, options.classpath, parent);
+ createClassLoader(
+ rewriter, options.bootclasspath, options.inputJar, options.classpath, parent);
+
try (ZipFile in = new ZipFile(options.inputJar.toFile());
ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(
Files.newOutputStream(options.outputJar)))) {
LambdaClassMaker lambdas = new LambdaClassMaker(dumpDirectory);
- ClassReaderFactory readerFactory = new ClassReaderFactory(in);
+ ClassReaderFactory readerFactory = new ClassReaderFactory(in, rewriter);
+
ImmutableSet.Builder<String> interfaceLambdaMethodCollector = ImmutableSet.builder();
// Process input Jar, desugaring as we go
@@ -159,16 +171,23 @@ class Desugar {
// Android anyways. Resources are written as they were in the input jar to avoid any
// danger of accidentally uncompressed resources ending up in an .apk.
if (entry.getName().endsWith(".class")) {
- ClassReader reader = new ClassReader(content);
- ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS /*for bridge methods*/);
+ ClassReader reader = rewriter.reader(content);
+ CoreLibraryRewriter.UnprefixingClassWriter writer =
+ rewriter.writer(ClassWriter.COMPUTE_MAXS /*for bridge methods*/);
ClassVisitor visitor = writer;
if (!allowDefaultMethods) {
visitor = new Java7Compatibility(visitor, readerFactory);
}
- reader.accept(
- new LambdaDesugaring(
- visitor, loader, lambdas, interfaceLambdaMethodCollector, allowDefaultMethods),
- 0);
+
+ visitor = new LambdaDesugaring(
+ visitor,
+ loader,
+ lambdas,
+ interfaceLambdaMethodCollector,
+ allowDefaultMethods);
+
+ reader.accept(visitor, 0);
+
writeStoredEntry(out, entry.getName(), writer.toByteArray());
} else {
// TODO(bazel-team): Avoid de- and re-compressing resource files
@@ -191,13 +210,16 @@ class Desugar {
for (Map.Entry<Path, LambdaInfo> lambdaClass : lambdas.drain().entrySet()) {
try (InputStream bytecode =
Files.newInputStream(dumpDirectory.resolve(lambdaClass.getKey()))) {
- ClassReader reader = new ClassReader(bytecode);
- ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS /*for invoking bridges*/);
+ ClassReader reader = rewriter.reader(bytecode);
+ CoreLibraryRewriter.UnprefixingClassWriter writer =
+ rewriter.writer(ClassWriter.COMPUTE_MAXS /*for invoking bridges*/);
ClassVisitor visitor = writer;
+
if (!allowDefaultMethods) {
// null ClassReaderFactory b/c we don't expect to need it for lambda classes
visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null);
}
+
LambdaClassFixer lambdaFixer =
new LambdaClassFixer(
visitor,
@@ -209,7 +231,8 @@ class Desugar {
// instructions in generated lambda classes (checkState below will fail)
reader.accept(
new LambdaDesugaring(lambdaFixer, loader, lambdas, null, allowDefaultMethods), 0);
- writeStoredEntry(out, lambdaFixer.getInternalName() + ".class", writer.toByteArray());
+ String name = rewriter.unprefix(lambdaFixer.getInternalName() + ".class");
+ writeStoredEntry(out, name, writer.toByteArray());
}
}
@@ -237,8 +260,9 @@ class Desugar {
out.closeEntry();
}
- private static ClassLoader createClassLoader(List<Path> bootclasspath, Path inputJar,
- List<Path> classpath, ClassLoader parent) throws IOException {
+ private static ClassLoader createClassLoader(CoreLibraryRewriter rewriter,
+ List<Path> bootclasspath, Path inputJar, List<Path> classpath,
+ ClassLoader parent) throws IOException {
// Prepend classpath with input jar itself so LambdaDesugaring can load classes with lambdas.
// Note that inputJar and classpath need to be in the same classloader because we typically get
// the header Jar for inputJar on the classpath and having the header Jar in a parent loader
@@ -247,9 +271,9 @@ class Desugar {
// Use a classloader that as much as possible uses the provided bootclasspath instead of
// the tool's system classloader. Unfortunately we can't do that for java. classes.
if (!bootclasspath.isEmpty()) {
- parent = HeaderClassLoader.fromClassPath(bootclasspath, parent);
+ parent = HeaderClassLoader.fromClassPath(bootclasspath, rewriter, parent);
}
- return HeaderClassLoader.fromClassPath(classpath, parent);
+ return HeaderClassLoader.fromClassPath(classpath, rewriter, parent);
}
private static class ThrowingClassLoader extends ClassLoader {
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java b/src/tools/android/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java
index 90abfd44ec..053d52d4b2 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java
@@ -42,11 +42,12 @@ import org.objectweb.asm.Opcodes;
class HeaderClassLoader extends ClassLoader {
private final Map<String, JarFile> jarfiles;
+ private final CoreLibraryRewriter rewriter;
/** Creates a classloader from the given classpath with the given parent. */
- public static HeaderClassLoader fromClassPath(List<Path> classpath, ClassLoader parent)
- throws IOException {
- return new HeaderClassLoader(indexJars(classpath), parent);
+ public static HeaderClassLoader fromClassPath(
+ List<Path> classpath, CoreLibraryRewriter rewriter, ClassLoader parent) throws IOException {
+ return new HeaderClassLoader(indexJars(classpath), rewriter, parent);
}
/**
@@ -67,14 +68,16 @@ class HeaderClassLoader extends ClassLoader {
return result;
}
- private HeaderClassLoader(Map<String, JarFile> jarfiles, ClassLoader parent) {
+ private HeaderClassLoader(
+ Map<String, JarFile> jarfiles, CoreLibraryRewriter rewriter, ClassLoader parent) {
super(parent);
+ this.rewriter = rewriter;
this.jarfiles = jarfiles;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
- String filename = name.replace('.', '/') + ".class";
+ String filename = rewriter.unprefix(name.replace('.', '/') + ".class");
JarFile jarfile = jarfiles.get(filename);
if (jarfile == null) {
throw new ClassNotFoundException();
@@ -82,7 +85,7 @@ class HeaderClassLoader extends ClassLoader {
ZipEntry entry = jarfile.getEntry(filename);
byte[] bytecode;
try (InputStream content = jarfile.getInputStream(entry)) {
- ClassReader reader = new ClassReader(content);
+ 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);