summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Cross <ccross@android.com>2017-03-14 10:45:24 -0700
committerColin Cross <ccross@android.com>2017-03-14 12:38:17 -0700
commite8cc22765a3016e1dc64008612c123aaf4d36a80 (patch)
tree3d73cbb2c0f93d2163e3a2ceb4ee55a0a6b42898
parent14eebfcc4518e66afc961c6ec053a3531deb8d35 (diff)
parent52f7c03d041742e9a5aa5a00e26a4ef0c7de2848 (diff)
downloaddesugar-e8cc22765a3016e1dc64008612c123aaf4d36a80.tar.gz
Merge remote-tracking branch 'aosp/upstream-master' into master
* aosp/upstream-master: Clean up android desugar tool's flags a bit Change how desugar finds desugared classes to have it working on Windows Remove bootclasspath fallback in Android desugaring tool Add an --copy_bridges_from_classpath argument Desugar calls to Objects.requireNonNull(Object o) to o.getClass() Avoid factory methods when desugaring stateless lambdas for Android RELNOTES: Avoid factory methods when desugaring stateless lambdas for Android Remove duplicate class. More stable naming scheme for lambda classes in desugared android code RELNOTES: More stable naming scheme for lambda classes in desugared android code Test: m -j ANDROID_COMPILE_WITH_JACK=false Change-Id: I0ff5b9eb3cac6a020f347c51b79633af13b0576f
-rw-r--r--java/com/google/devtools/build/android/desugar/ClassReaderFactory.java22
-rw-r--r--java/com/google/devtools/build/android/desugar/Desugar.java194
-rw-r--r--java/com/google/devtools/build/android/desugar/HeaderClassLoader.java40
-rw-r--r--java/com/google/devtools/build/android/desugar/IndexedJars.java84
-rw-r--r--java/com/google/devtools/build/android/desugar/LambdaClassFixer.java98
-rw-r--r--java/com/google/devtools/build/android/desugar/LambdaClassMaker.java25
-rw-r--r--java/com/google/devtools/build/android/desugar/LambdaDesugaring.java30
-rw-r--r--java/com/google/devtools/build/android/desugar/LambdaInfo.java8
-rw-r--r--java/com/google/devtools/build/android/desugar/ObjectsRequireNonNullMethodInliner.java68
-rw-r--r--java/com/google/devtools/common/options/OptionsParser.java46
-rw-r--r--java/com/google/devtools/common/options/OptionsParserImpl.java185
11 files changed, 490 insertions, 310 deletions
diff --git a/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java b/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java
index 6ec4e0d..56f99a3 100644
--- a/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java
+++ b/java/com/google/devtools/build/android/desugar/ClassReaderFactory.java
@@ -15,31 +15,39 @@ package com.google.devtools.build.android.desugar;
import java.io.IOException;
import java.io.InputStream;
+import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.annotation.Nullable;
import org.objectweb.asm.ClassReader;
class ClassReaderFactory {
- private final ZipFile jar;
+ private final IndexedJars indexedJars;
private final CoreLibraryRewriter rewriter;
- public ClassReaderFactory(ZipFile jar, CoreLibraryRewriter rewriter) {
- this.jar = jar;
+ public ClassReaderFactory(IndexedJars indexedJars, CoreLibraryRewriter rewriter) {
this.rewriter = rewriter;
+ this.indexedJars = indexedJars;
}
/**
* Returns a reader for the given/internal/Class$Name if the class is defined in the wrapped Jar
- * and {@code null} otherwise. For simplicity this method turns checked into runtime excpetions
+ * and {@code null} otherwise. For simplicity this method turns checked into runtime exceptions
* under the assumption that all classes have already been read once when this method is called.
*/
@Nullable
public ClassReader readIfKnown(String internalClassName) {
- ZipEntry entry = jar.getEntry(rewriter.unprefix(internalClassName) + ".class");
- if (entry == null) {
- return null;
+ String filename = rewriter.unprefix(internalClassName) + ".class";
+ JarFile jarFile = indexedJars.getJarFile(filename);
+
+ if (jarFile != null) {
+ return getClassReader(internalClassName, jarFile, jarFile.getEntry(filename));
}
+
+ return null;
+ }
+
+ private ClassReader getClassReader(String internalClassName, ZipFile jar, ZipEntry entry) {
try (InputStream bytecode = jar.getInputStream(entry)) {
// ClassReader doesn't take ownership and instead eagerly reads the stream's contents
return rewriter.reader(bytecode);
diff --git a/java/com/google/devtools/build/android/desugar/Desugar.java b/java/com/google/devtools/build/android/desugar/Desugar.java
index 2d973de..877d230 100644
--- a/java/com/google/devtools/build/android/desugar/Desugar.java
+++ b/java/com/google/devtools/build/android/desugar/Desugar.java
@@ -50,55 +50,63 @@ import org.objectweb.asm.ClassWriter;
*/
class Desugar {
- /**
- * Commandline options for {@link Desugar}.
- */
+ /** Commandline options for {@link Desugar}. */
public static class Options extends OptionsBase {
- @Option(name = "input",
- defaultValue = "null",
- category = "input",
- converter = ExistingPathConverter.class,
- abbrev = 'i',
- help = "Input Jar with classes to desugar.")
+ @Option(
+ name = "input",
+ defaultValue = "null",
+ category = "input",
+ converter = ExistingPathConverter.class,
+ abbrev = 'i',
+ help = "Input Jar with classes to desugar (required)."
+ )
public Path inputJar;
- @Option(name = "classpath_entry",
- allowMultiple = true,
- defaultValue = "",
- category = "input",
- converter = ExistingPathConverter.class,
- help = "Ordered classpath to resolve symbols in the --input Jar, like javac's -cp flag.")
+ @Option(
+ name = "classpath_entry",
+ allowMultiple = true,
+ defaultValue = "",
+ category = "input",
+ converter = ExistingPathConverter.class,
+ help = "Ordered classpath to resolve symbols in the --input Jar, like javac's -cp flag."
+ )
public List<Path> classpath;
- @Option(name = "bootclasspath_entry",
- allowMultiple = true,
- defaultValue = "",
- category = "input",
- converter = ExistingPathConverter.class,
- help = "Bootclasspath that was used to compile the --input Jar with, like javac's "
- + "-bootclasspath flag. If no bootclasspath is explicitly given then the tool's own "
- + "bootclasspath is used.")
+ @Option(
+ name = "bootclasspath_entry",
+ allowMultiple = true,
+ defaultValue = "",
+ category = "input",
+ converter = ExistingPathConverter.class,
+ help = "Bootclasspath that was used to compile the --input Jar with, like javac's "
+ + "-bootclasspath flag (required)."
+ )
public List<Path> bootclasspath;
- @Option(name = "allow_empty_bootclasspath",
- defaultValue = "false",
- category = "misc",
- help = "Don't use the tool's bootclasspath if no bootclasspath is given.")
+ @Option(
+ name = "allow_empty_bootclasspath",
+ defaultValue = "false",
+ category = "undocumented"
+ )
public boolean allowEmptyBootclasspath;
- @Option(name = "output",
- defaultValue = "null",
- category = "output",
- converter = PathConverter.class,
- abbrev = 'o',
- help = "Output Jar to write desugared classes into.")
+ @Option(
+ name = "output",
+ defaultValue = "null",
+ category = "output",
+ converter = PathConverter.class,
+ abbrev = 'o',
+ help = "Output Jar to write desugared classes into (required)."
+ )
public Path outputJar;
- @Option(name = "verbose",
- defaultValue = "false",
- category = "misc",
- abbrev = 'v',
- help = "Enables verbose debugging output.")
+ @Option(
+ name = "verbose",
+ defaultValue = "false",
+ category = "misc",
+ abbrev = 'v',
+ help = "Enables verbose debugging output."
+ )
public boolean verbose;
@Option(
@@ -109,10 +117,21 @@ class Desugar {
)
public int minSdkVersion;
- @Option(name = "core_library",
- defaultValue = "false",
- category = "undocumented",
- help = "Enables rewriting to desugar java.* classes.")
+ @Option(
+ name = "copy_bridges_from_classpath",
+ defaultValue = "false",
+ category = "misc",
+ help = "Copy bridges from classpath to desugared classes."
+ )
+ public boolean copyBridgesFromClasspath;
+
+ @Option(
+ name = "core_library",
+ defaultValue = "false",
+ category = "undocumented",
+ implicitRequirements = "--allow_empty_bootclasspath",
+ help = "Enables rewriting to desugar java.* classes."
+ )
public boolean coreLibrary;
}
@@ -133,38 +152,40 @@ class Desugar {
args = Files.readAllLines(Paths.get(args[0].substring(1)), ISO_8859_1).toArray(new String[0]);
}
- OptionsParser optionsParser =
- OptionsParser.newOptionsParser(Options.class);
+ OptionsParser optionsParser = OptionsParser.newOptionsParser(Options.class);
+ optionsParser.setAllowResidue(false);
optionsParser.parseAndExitUponError(args);
Options options = optionsParser.getOptions(Options.class);
+ checkState(options.inputJar != null, "--input is required");
+ checkState(options.outputJar != null, "--output is required");
+ checkState(!options.bootclasspath.isEmpty() || options.allowEmptyBootclasspath,
+ "Bootclasspath required to desugar %s", options.inputJar);
+
if (options.verbose) {
System.out.printf("Lambda classes will be written under %s%n", dumpDirectory);
}
- boolean allowDefaultMethods = options.minSdkVersion >= 24;
-
- ClassLoader parent;
- if (options.bootclasspath.isEmpty() && !options.allowEmptyBootclasspath) {
- // TODO(b/31547323): Require bootclasspath once Bazel always provides it. Using the tool's
- // bootclasspath as a fallback is iffy at best and produces wrong results at worst.
- parent = ClassLoader.getSystemClassLoader();
- } else {
- parent = new ThrowingClassLoader();
- }
-
CoreLibraryRewriter rewriter =
new CoreLibraryRewriter(options.coreLibrary ? "__desugar__/" : "");
+ IndexedJars appIndexedJar = new IndexedJars(ImmutableList.of(options.inputJar));
+ IndexedJars appAndClasspathIndexedJars = new IndexedJars(options.classpath, appIndexedJar);
ClassLoader loader =
- createClassLoader(
- rewriter, options.bootclasspath, options.inputJar, options.classpath, parent);
-
+ createClassLoader(rewriter, options.bootclasspath, appAndClasspathIndexedJars);
+ boolean allowDefaultMethods = options.minSdkVersion >= 24;
+ boolean allowCallsToObjectsNonNull = options.minSdkVersion >= 19;
try (ZipFile in = new ZipFile(options.inputJar.toFile());
- ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(
- Files.newOutputStream(options.outputJar)))) {
+ ZipOutputStream out =
+ new ZipOutputStream(
+ new BufferedOutputStream(Files.newOutputStream(options.outputJar)))) {
LambdaClassMaker lambdas = new LambdaClassMaker(dumpDirectory);
- ClassReaderFactory readerFactory = new ClassReaderFactory(in, rewriter);
+ ClassReaderFactory readerFactory =
+ new ClassReaderFactory(
+ (options.copyBridgesFromClasspath && !allowDefaultMethods)
+ ? appAndClasspathIndexedJars
+ : appIndexedJar,
+ rewriter);
ImmutableSet.Builder<String> interfaceLambdaMethodCollector = ImmutableSet.builder();
@@ -184,13 +205,13 @@ class Desugar {
visitor = new Java7Compatibility(visitor, readerFactory);
}
- visitor = new LambdaDesugaring(
- visitor,
- loader,
- lambdas,
- interfaceLambdaMethodCollector,
- allowDefaultMethods);
+ visitor =
+ new LambdaDesugaring(
+ visitor, loader, lambdas, interfaceLambdaMethodCollector, allowDefaultMethods);
+ if (!allowCallsToObjectsNonNull) {
+ visitor = new ObjectsRequireNonNullMethodInliner(visitor);
+ }
reader.accept(visitor, 0);
writeStoredEntry(out, entry.getName(), writer.toByteArray());
@@ -207,7 +228,8 @@ class Desugar {
ImmutableSet<String> interfaceLambdaMethods = interfaceLambdaMethodCollector.build();
if (allowDefaultMethods) {
- checkState(interfaceLambdaMethods.isEmpty(),
+ checkState(
+ interfaceLambdaMethods.isEmpty(),
"Desugaring with default methods enabled moved interface lambdas");
}
@@ -225,7 +247,7 @@ class Desugar {
visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null);
}
- LambdaClassFixer lambdaFixer =
+ visitor =
new LambdaClassFixer(
visitor,
lambdaClass.getValue(),
@@ -234,10 +256,16 @@ class Desugar {
allowDefaultMethods);
// Send lambda classes through desugaring to make sure there's no invokedynamic
// instructions in generated lambda classes (checkState below will fail)
- reader.accept(
- new LambdaDesugaring(lambdaFixer, loader, lambdas, null, allowDefaultMethods), 0);
- String name = rewriter.unprefix(lambdaFixer.getInternalName() + ".class");
- writeStoredEntry(out, name, writer.toByteArray());
+ visitor = new LambdaDesugaring(visitor, loader, lambdas, null, allowDefaultMethods);
+ if (!allowCallsToObjectsNonNull) {
+ // Not sure whether there will be implicit null check emitted by javac, so we rerun the
+ // inliner again
+ visitor = new ObjectsRequireNonNullMethodInliner(visitor);
+ }
+ reader.accept(visitor, 0);
+ String filename =
+ rewriter.unprefix(lambdaClass.getValue().desiredInternalName()) + ".class";
+ writeStoredEntry(out, filename, writer.toByteArray());
}
}
@@ -266,25 +294,23 @@ class Desugar {
}
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
- // means the header version is preferred over the real thing.
- classpath = ImmutableList.<Path>builder().add(inputJar).addAll(classpath).build();
+ List<Path> bootclasspath, IndexedJars appAndClasspathIndexedJars) throws IOException {
// 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.
+ ClassLoader parent = new ThrowingClassLoader();
if (!bootclasspath.isEmpty()) {
- parent = HeaderClassLoader.fromClassPath(bootclasspath, rewriter, parent);
+ parent = new HeaderClassLoader(new IndexedJars(bootclasspath), rewriter, parent);
}
- return HeaderClassLoader.fromClassPath(classpath, rewriter, parent);
+ // 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
+ // means the header version is preferred over the real thing.
+ return new HeaderClassLoader(appAndClasspathIndexedJars, rewriter, parent);
}
private static class ThrowingClassLoader extends ClassLoader {
@Override
- protected Class<?> loadClass(String name, boolean resolve)
- throws ClassNotFoundException {
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.startsWith("java.")) {
// Use system class loader for java. classes, since ClassLoader.defineClass gets
// grumpy when those don't come from the standard place.
diff --git a/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java b/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java
index 053d52d..44c3932 100644
--- a/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java
+++ b/java/com/google/devtools/build/android/desugar/HeaderClassLoader.java
@@ -16,12 +16,6 @@ package com.google.devtools.build.android.desugar;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
-import java.nio.file.Path;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import org.objectweb.asm.ClassReader;
@@ -41,44 +35,20 @@ import org.objectweb.asm.Opcodes;
*/
class HeaderClassLoader extends ClassLoader {
- private final Map<String, JarFile> jarfiles;
+ private final IndexedJars indexedJars;
private final CoreLibraryRewriter rewriter;
- /** Creates a classloader from the given classpath with the given parent. */
- public static HeaderClassLoader fromClassPath(
- List<Path> classpath, CoreLibraryRewriter rewriter, ClassLoader parent) throws IOException {
- return new HeaderClassLoader(indexJars(classpath), rewriter, parent);
- }
-
- /**
- * Opens the given list of Jar files and returns an index of all classes in them, to avoid
- * scanning all Jars over and over for each class in {@link #findClass}.
- */
- private static Map<String, JarFile> indexJars(List<Path> classpath) throws IOException {
- HashMap<String, JarFile> result = new HashMap<>();
- for (Path jarfile : classpath) {
- JarFile jar = new JarFile(jarfile.toFile());
- for (Enumeration<JarEntry> cur = jar.entries(); cur.hasMoreElements(); ) {
- JarEntry entry = cur.nextElement();
- if (entry.getName().endsWith(".class") && !result.containsKey(entry.getName())) {
- result.put(entry.getName(), jar);
- }
- }
- }
- return result;
- }
-
- private HeaderClassLoader(
- Map<String, JarFile> jarfiles, CoreLibraryRewriter rewriter, ClassLoader parent) {
+ public HeaderClassLoader(
+ IndexedJars indexedJars, CoreLibraryRewriter rewriter, ClassLoader parent) {
super(parent);
this.rewriter = rewriter;
- this.jarfiles = jarfiles;
+ this.indexedJars = indexedJars;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String filename = rewriter.unprefix(name.replace('.', '/') + ".class");
- JarFile jarfile = jarfiles.get(filename);
+ JarFile jarfile = indexedJars.getJarFile(filename);
if (jarfile == null) {
throw new ClassNotFoundException();
}
diff --git a/java/com/google/devtools/build/android/desugar/IndexedJars.java b/java/com/google/devtools/build/android/desugar/IndexedJars.java
new file mode 100644
index 0000000..bdb5b47
--- /dev/null
+++ b/java/com/google/devtools/build/android/desugar/IndexedJars.java
@@ -0,0 +1,84 @@
+// 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 com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import javax.annotation.Nullable;
+
+/**
+ * Opens the given list of Jar files and compute an index of all classes in them, to avoid
+ * scanning all Jars over and over for each class to load. An indexed jars can have a parent
+ * that is firstly used when a file name is searched.
+ */
+class IndexedJars {
+
+ private final Map<String, JarFile> jarfiles = new HashMap<>();
+
+ /**
+ * Parent indexed jars to use before to search a file name into this indexed jars.
+ */
+ @Nullable
+ private final IndexedJars parentIndexedJar;
+
+ /**
+ * Index a list of Jar files without a parent indexed jars.
+ */
+ public IndexedJars(List<Path> jarFiles) throws IOException {
+ this(jarFiles, null);
+ }
+
+ /**
+ * Index a list of Jar files and set a parent indexed jars that is firstly used during the search
+ * of a file name.
+ */
+ public IndexedJars(List<Path> jarFiles, @Nullable IndexedJars parentIndexedJar)
+ throws IOException {
+ this.parentIndexedJar = parentIndexedJar;
+ for (Path jarfile : jarFiles) {
+ indexJar(jarfile);
+ }
+ }
+
+ @Nullable
+ public JarFile getJarFile(String filename) {
+ Preconditions.checkArgument(filename.endsWith(".class"));
+
+ if (parentIndexedJar != null) {
+ JarFile jarFile = parentIndexedJar.getJarFile(filename);
+ if (jarFile != null) {
+ return jarFile;
+ }
+ }
+
+ return jarfiles.get(filename);
+ }
+
+ private void indexJar(Path jarfile) throws IOException {
+ JarFile jar = new JarFile(jarfile.toFile());
+ for (Enumeration<JarEntry> cur = jar.entries(); cur.hasMoreElements(); ) {
+ JarEntry entry = cur.nextElement();
+ if (entry.getName().endsWith(".class") && !jarfiles.containsKey(entry.getName())) {
+ jarfiles.put(entry.getName(), jar);
+ }
+ }
+ }
+}
diff --git a/java/com/google/devtools/build/android/desugar/LambdaClassFixer.java b/java/com/google/devtools/build/android/desugar/LambdaClassFixer.java
index 97c0249..e942c95 100644
--- a/java/com/google/devtools/build/android/desugar/LambdaClassFixer.java
+++ b/java/com/google/devtools/build/android/desugar/LambdaClassFixer.java
@@ -27,7 +27,6 @@ import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
@@ -41,8 +40,10 @@ import org.objectweb.asm.tree.TypeInsnNode;
*/
class LambdaClassFixer extends ClassVisitor {
- /** Magic method name used by {@link java.lang.invoke.LambdaMetafactory} */
+ /** Magic method name used by {@link java.lang.invoke.LambdaMetafactory}. */
public static final String FACTORY_METHOD_NAME = "get$Lambda";
+ /** Field name we'll use to hold singleton instances where possible. */
+ public static final String SINGLETON_FIELD_NAME = "$instance";
private final LambdaInfo lambdaInfo;
private final ClassReaderFactory factory;
@@ -51,7 +52,7 @@ class LambdaClassFixer extends ClassVisitor {
private final HashSet<String> implementedMethods = new HashSet<>();
private final LinkedHashSet<String> methodsToMoveIn = new LinkedHashSet<>();
- private String internalName;
+ private String originalInternalName;
private ImmutableList<String> interfaces;
private boolean hasState;
@@ -59,7 +60,6 @@ class LambdaClassFixer extends ClassVisitor {
private String desc;
private String signature;
- private String[] exceptions;
public LambdaClassFixer(ClassVisitor dest, LambdaInfo lambdaInfo, ClassReaderFactory factory,
@@ -71,10 +71,6 @@ class LambdaClassFixer extends ClassVisitor {
this.allowDefaultMethods = allowDefaultMethods;
}
- public String getInternalName() {
- return internalName;
- }
-
@Override
public void visit(
int version,
@@ -84,15 +80,15 @@ class LambdaClassFixer extends ClassVisitor {
String superName,
String[] interfaces) {
checkArgument(BitFlags.noneSet(access, Opcodes.ACC_INTERFACE), "Not a class: %s", name);
- checkState(internalName == null, "Already visited %s, can't reuse for %s", internalName, name);
- internalName = name;
+ checkState(this.originalInternalName == null, "not intended for reuse but reused for %s", name);
+ originalInternalName = name;
hasState = false;
hasFactory = false;
desc = null;
this.signature = null;
- exceptions = null;
this.interfaces = ImmutableList.copyOf(interfaces);
- super.visit(version, access, name, signature, superName, interfaces);
+ // Rename to desired name
+ super.visit(version, access, getInternalName(), signature, superName, interfaces);
}
@Override
@@ -125,7 +121,6 @@ class LambdaClassFixer extends ClassVisitor {
} else if ("<init>".equals(name)) {
this.desc = desc;
this.signature = signature;
- this.exceptions = exceptions;
}
MethodVisitor methodVisitor =
new LambdaClassMethodRewriter(super.visitMethod(access, name, desc, signature, exceptions));
@@ -143,17 +138,19 @@ class LambdaClassFixer extends ClassVisitor {
@Override
public void visitEnd() {
checkState(!hasState || hasFactory,
- "Expected factory method for capturing lambda %s", internalName);
- if (!hasFactory) {
- // Fake factory method if LambdaMetafactory didn't generate it
+ "Expected factory method for capturing lambda %s", getInternalName());
+ if (!hasState) {
checkState(signature == null,
- "Didn't expect generic constructor signature %s %s", internalName, signature);
-
- // Since this is a stateless class we populate and use a static singleton field "$instance"
- String singletonFieldDesc = Type.getObjectType(internalName).getDescriptor();
+ "Didn't expect generic constructor signature %s %s", getInternalName(), signature);
+ checkState(lambdaInfo.factoryMethodDesc().startsWith("()"),
+ "Expected 0-arg factory method for %s but found %s", getInternalName(),
+ lambdaInfo.factoryMethodDesc());
+ // Since this is a stateless class we populate and use a static singleton field "$instance".
+ // Field is package-private so we can read it from the class that had the invokedynamic.
+ String singletonFieldDesc = lambdaInfo.factoryMethodDesc().substring("()".length());
super.visitField(
- Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL,
- "$instance",
+ Opcodes.ACC_STATIC | Opcodes.ACC_FINAL,
+ SINGLETON_FIELD_NAME,
singletonFieldDesc,
(String) null,
(Object) null)
@@ -166,35 +163,28 @@ class LambdaClassFixer extends ClassVisitor {
"()V",
(String) null,
new String[0]);
- codeBuilder.visitTypeInsn(Opcodes.NEW, internalName);
+ codeBuilder.visitTypeInsn(Opcodes.NEW, getInternalName());
codeBuilder.visitInsn(Opcodes.DUP);
- codeBuilder.visitMethodInsn(Opcodes.INVOKESPECIAL, internalName, "<init>",
- checkNotNull(desc, "didn't see a constructor for %s", internalName), /*itf*/ false);
- codeBuilder.visitFieldInsn(Opcodes.PUTSTATIC, internalName, "$instance", singletonFieldDesc);
+ codeBuilder.visitMethodInsn(Opcodes.INVOKESPECIAL, getInternalName(), "<init>",
+ checkNotNull(desc, "didn't see a constructor for %s", getInternalName()), /*itf*/ false);
+ codeBuilder.visitFieldInsn(
+ Opcodes.PUTSTATIC, getInternalName(), SINGLETON_FIELD_NAME, singletonFieldDesc);
codeBuilder.visitInsn(Opcodes.RETURN);
codeBuilder.visitMaxs(2, 0); // two values are pushed onto the stack
codeBuilder.visitEnd();
-
- codeBuilder = // reuse codeBuilder variable to avoid accidental additions to previous method
- super.visitMethod(
- Opcodes.ACC_STATIC,
- FACTORY_METHOD_NAME,
- lambdaInfo.factoryMethodDesc(),
- (String) null,
- exceptions);
- codeBuilder.visitFieldInsn(Opcodes.GETSTATIC, internalName, "$instance", singletonFieldDesc);
- codeBuilder.visitInsn(Opcodes.ARETURN);
- codeBuilder.visitMaxs(1, 0); // one value on the stack
}
copyRewrittenLambdaMethods();
-
if (!allowDefaultMethods) {
copyBridgeMethods(interfaces);
}
super.visitEnd();
}
+ private String getInternalName() {
+ return lambdaInfo.desiredInternalName();
+ }
+
private void copyRewrittenLambdaMethods() {
for (String rewritten : methodsToMoveIn) {
String interfaceInternalName = rewritten.substring(0, rewritten.indexOf('#'));
@@ -233,17 +223,41 @@ class LambdaClassFixer extends ClassVisitor {
// Rewrite invocations of lambda methods in interfaces to anticipate the lambda method being
// moved into the lambda class (i.e., the class being visited here).
checkArgument(opcode == Opcodes.INVOKESTATIC, "Cannot move instance method %s", method);
- owner = internalName;
+ owner = getInternalName();
itf = false; // owner was interface but is now a class
methodsToMoveIn.add(method);
- } else if (name.startsWith("lambda$")) {
- // Reflect renaming of lambda$ instance methods to avoid accidental overrides
- name = LambdaDesugaring.uniqueInPackage(owner, name);
+ } else {
+ if (originalInternalName.equals(owner)) {
+ // Reflect renaming of lambda classes
+ owner = getInternalName();
+ }
+ if (name.startsWith("lambda$")) {
+ // Reflect renaming of lambda$ instance methods to avoid accidental overrides
+ name = LambdaDesugaring.uniqueInPackage(owner, name);
+ }
}
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
@Override
+ public void visitTypeInsn(int opcode, String type) {
+ if (originalInternalName.equals(type)) {
+ // Reflect renaming of lambda classes
+ type = getInternalName();
+ }
+ super.visitTypeInsn(opcode, type);
+ }
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (originalInternalName.equals(owner)) {
+ // Reflect renaming of lambda classes
+ owner = getInternalName();
+ }
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+
+ @Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
// Drop annotation that's part of the generated lambda class that's not available on Android.
// Proguard complains about this otherwise.
diff --git a/java/com/google/devtools/build/android/desugar/LambdaClassMaker.java b/java/com/google/devtools/build/android/desugar/LambdaClassMaker.java
index 8a2ebea..d8f2e28 100644
--- a/java/com/google/devtools/build/android/desugar/LambdaClassMaker.java
+++ b/java/com/google/devtools/build/android/desugar/LambdaClassMaker.java
@@ -14,7 +14,6 @@
package com.google.devtools.build.android.desugar;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterators;
@@ -25,7 +24,6 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
-import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -41,7 +39,7 @@ class LambdaClassMaker {
this.rootDirectory = rootDirectory;
}
- public String generateLambdaClass(String invokerInternalName, LambdaInfo lambdaInfo,
+ public void generateLambdaClass(String invokerInternalName, LambdaInfo lambdaInfo,
MethodHandle bootstrapMethod, ArrayList<Object> bsmArgs) throws IOException {
// Invoking the bootstrap method will dump the generated class
try {
@@ -52,11 +50,7 @@ class LambdaClassMaker {
}
Path generatedClassFile = findOnlyUnprocessed(invokerInternalName + "$$Lambda$");
- String lambdaClassName = generatedClassFile.toString();
- checkState(lambdaClassName.endsWith(".class"), "Unexpected filename %s", lambdaClassName);
- lambdaClassName = lambdaClassName.substring(0, lambdaClassName.length() - ".class".length());
generatedClasses.put(generatedClassFile, lambdaInfo);
- return lambdaClassName;
}
/**
@@ -69,23 +63,22 @@ class LambdaClassMaker {
return result;
}
- private Path findOnlyUnprocessed(final String pathPrefix) throws IOException {
+ private Path findOnlyUnprocessed(String pathPrefix) throws IOException {
+ // pathPrefix is an internal class name prefix containing '/', but paths obtained on Windows
+ // will not contain '/' and searches will fail. So, construct an absolute path from the given
+ // string and use its string representation to find the file we need regardless of host
+ // system's file system
+ final String rootPathPrefixStr = rootDirectory.resolve(pathPrefix).toString();
+
// TODO(kmb): Investigate making this faster in the case of many lambdas
// TODO(bazel-team): This could be much nicer with lambdas
try (Stream<Path> results =
Files.walk(rootDirectory)
- .map(
- new Function<Path, Path>() {
- @Override
- public Path apply(Path path) {
- return rootDirectory.relativize(path);
- }
- })
.filter(
new Predicate<Path>() {
@Override
public boolean test(Path path) {
- return path.toString().startsWith(pathPrefix)
+ return path.toString().startsWith(rootPathPrefixStr)
&& !generatedClasses.containsKey(path);
}
})) {
diff --git a/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java b/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java
index 79be2a3..c52c4a4 100644
--- a/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java
+++ b/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java
@@ -55,6 +55,7 @@ class LambdaDesugaring extends ClassVisitor {
private String internalName;
private boolean isInterface;
+ private int lambdaCount;
public LambdaDesugaring(
ClassVisitor dest,
@@ -77,6 +78,7 @@ class LambdaDesugaring extends ClassVisitor {
String signature,
String superName,
String[] interfaces) {
+ checkState(internalName == null, "not intended for reuse but reused for %s", name);
internalName = name;
isInterface = BitFlags.isSet(access, Opcodes.ACC_INTERFACE);
super.visit(version, access, name, signature, superName, interfaces);
@@ -360,18 +362,28 @@ class LambdaDesugaring extends ClassVisitor {
// and ultimately we don't care if the bootstrap method was even on the bootclasspath
// when this class was compiled (although it must've been since javac is unhappy otherwise).
MethodHandle bsmMethod = toMethodHandle(publicLookup(), bsm, /*target*/ false);
- String lambdaClassName = lambdas.generateLambdaClass(
+ // Give generated classes to have more stable names (b/35643761). Use BSM's naming scheme
+ // but with separate counter for each surrounding class.
+ String lambdaClassName = internalName + "$$Lambda$" + (lambdaCount++);
+ lambdas.generateLambdaClass(
internalName,
- LambdaInfo.create(desc, bridgeInfo.methodReference(), bridgeInfo.bridgeMethod()),
+ LambdaInfo.create(
+ lambdaClassName, desc, bridgeInfo.methodReference(), bridgeInfo.bridgeMethod()),
bsmMethod,
args);
- // Emit invokestatic that calls the factory method generated in the lambda class
- super.visitMethodInsn(
- Opcodes.INVOKESTATIC,
- lambdaClassName,
- LambdaClassFixer.FACTORY_METHOD_NAME,
- desc,
- /*itf*/ false);
+ if (desc.startsWith("()")) {
+ // For stateless lambda classes we'll generate a singleton instance that we can just load
+ super.visitFieldInsn(Opcodes.GETSTATIC, lambdaClassName,
+ LambdaClassFixer.SINGLETON_FIELD_NAME, desc.substring("()".length()));
+ } else {
+ // Emit invokestatic that calls the factory method generated in the lambda class
+ super.visitMethodInsn(
+ Opcodes.INVOKESTATIC,
+ lambdaClassName,
+ LambdaClassFixer.FACTORY_METHOD_NAME,
+ desc,
+ /*itf*/ false);
+ }
} catch (IOException | ReflectiveOperationException e) {
throw new IllegalStateException("Couldn't desugar invokedynamic for " + internalName + "."
+ name + " using " + bsm + " with arguments " + Arrays.toString(bsmArgs), e);
diff --git a/java/com/google/devtools/build/android/desugar/LambdaInfo.java b/java/com/google/devtools/build/android/desugar/LambdaInfo.java
index e3bb84f..dad340c 100644
--- a/java/com/google/devtools/build/android/desugar/LambdaInfo.java
+++ b/java/com/google/devtools/build/android/desugar/LambdaInfo.java
@@ -19,11 +19,15 @@ import org.objectweb.asm.Handle;
@AutoValue
abstract class LambdaInfo {
public static LambdaInfo create(
- String factoryMethodDesc, Handle methodReference, Handle bridgeMethod) {
+ String desiredInternalName,
+ String factoryMethodDesc,
+ Handle methodReference,
+ Handle bridgeMethod) {
return new AutoValue_LambdaInfo(
- factoryMethodDesc, methodReference, bridgeMethod);
+ desiredInternalName, factoryMethodDesc, methodReference, bridgeMethod);
}
+ public abstract String desiredInternalName();
public abstract String factoryMethodDesc();
public abstract Handle methodReference();
public abstract Handle bridgeMethod();
diff --git a/java/com/google/devtools/build/android/desugar/ObjectsRequireNonNullMethodInliner.java b/java/com/google/devtools/build/android/desugar/ObjectsRequireNonNullMethodInliner.java
new file mode 100644
index 0000000..e56cfe1
--- /dev/null
+++ b/java/com/google/devtools/build/android/desugar/ObjectsRequireNonNullMethodInliner.java
@@ -0,0 +1,68 @@
+// 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 static org.objectweb.asm.Opcodes.ASM5;
+import static org.objectweb.asm.Opcodes.DUP;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+import static org.objectweb.asm.Opcodes.POP;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * This class desugars any call to Objects.requireNonNull(Object o), Objects.requireNonNull(Object
+ * o, String msg), and Objects.requireNonNull(Object o, Supplier msg), by replacing the call with
+ * o.getClass(). Note that currently we discard the message, as inlining the message involves
+ * changes to the control flow graph, which further requires re-computation of the stack map frames.
+ */
+public class ObjectsRequireNonNullMethodInliner extends ClassVisitor {
+
+ public ObjectsRequireNonNullMethodInliner(ClassVisitor cv) {
+ super(ASM5, cv);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(
+ int access, String name, String desc, String signature, String[] exceptions) {
+ MethodVisitor visitor = super.cv.visitMethod(access, name, desc, signature, exceptions);
+ return visitor == null ? visitor : new ObjectsMethodInlinerMethodVisitor(visitor);
+ }
+
+ private static class ObjectsMethodInlinerMethodVisitor extends MethodVisitor {
+
+ public ObjectsMethodInlinerMethodVisitor(MethodVisitor mv) {
+ super(ASM5, mv);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
+ if (opcode != INVOKESTATIC
+ || !owner.equals("java/util/Objects")
+ || !name.equals("requireNonNull")
+ || !desc.equals("(Ljava/lang/Object;)Ljava/lang/Object;")) {
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
+ return;
+ }
+
+ // a call to Objects.requireNonNull(Object o)
+ // duplicate the first argument 'o', as this method returns 'o'.
+ super.visitInsn(DUP);
+ super.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+ super.visitInsn(POP);
+ }
+ }
+}
diff --git a/java/com/google/devtools/common/options/OptionsParser.java b/java/com/google/devtools/common/options/OptionsParser.java
index 166379b..946d73b 100644
--- a/java/com/google/devtools/common/options/OptionsParser.java
+++ b/java/com/google/devtools/common/options/OptionsParser.java
@@ -19,6 +19,7 @@ import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.escape.Escaper;
@@ -243,25 +244,51 @@ public class OptionsParser implements OptionsProvider {
private final String source;
private final String implicitDependant;
private final String expandedFrom;
+ private final boolean allowMultiple;
- public OptionValueDescription(String name, Object value,
- OptionPriority priority, String source, String implicitDependant, String expandedFrom) {
+ public OptionValueDescription(
+ String name,
+ Object value,
+ OptionPriority priority,
+ String source,
+ String implicitDependant,
+ String expandedFrom,
+ boolean allowMultiple) {
this.name = name;
this.value = value;
this.priority = priority;
this.source = source;
this.implicitDependant = implicitDependant;
this.expandedFrom = expandedFrom;
+ this.allowMultiple = allowMultiple;
}
public String getName() {
return name;
}
+ // Need to suppress unchecked warnings, because the "multiple occurrence"
+ // options use unchecked ListMultimaps due to limitations of Java generics.
+ @SuppressWarnings({"unchecked", "rawtypes"})
public Object getValue() {
+ if (allowMultiple) {
+ // Sort the results by option priority and return them in a new list.
+ // The generic type of the list is not known at runtime, so we can't
+ // use it here. It was already checked in the constructor, so this is
+ // type-safe.
+ List result = Lists.newArrayList();
+ ListMultimap realValue = (ListMultimap) value;
+ for (OptionPriority priority : OptionPriority.values()) {
+ // If there is no mapping for this key, this check avoids object creation (because
+ // ListMultimap has to return a new object on get) and also an unnecessary addAll call.
+ if (realValue.containsKey(priority)) {
+ result.addAll(realValue.get(priority));
+ }
+ }
+ return result;
+ }
return value;
}
-
/**
* @return the priority of the thing that set this value for this flag
*/
@@ -306,6 +333,19 @@ public class OptionsParser implements OptionsProvider {
}
return result.toString();
}
+
+ // Need to suppress unchecked warnings, because the "multiple occurrence"
+ // options use unchecked ListMultimaps due to limitations of Java generics.
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ void addValue(OptionPriority addedPriority, Object addedValue) {
+ Preconditions.checkState(allowMultiple);
+ ListMultimap optionValueList = (ListMultimap) value;
+ if (addedValue instanceof List<?>) {
+ optionValueList.putAll(addedPriority, (List<?>) addedValue);
+ } else {
+ optionValueList.put(addedPriority, addedValue);
+ }
+ }
}
/**
diff --git a/java/com/google/devtools/common/options/OptionsParserImpl.java b/java/com/google/devtools/common/options/OptionsParserImpl.java
index fb7292c..c15f927 100644
--- a/java/com/google/devtools/common/options/OptionsParserImpl.java
+++ b/java/com/google/devtools/common/options/OptionsParserImpl.java
@@ -24,7 +24,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
@@ -145,85 +144,19 @@ class OptionsParserImpl {
}});
}
- /**
- * For every value, this class keeps track of its priority, its free-form source
- * description, whether it was set as an implicit dependency, and the value.
- */
- private static final class ParsedOptionEntry {
- private final Object value;
- private final OptionPriority priority;
- private final String source;
- private final String implicitDependant;
- private final String expandedFrom;
- private final boolean allowMultiple;
-
- ParsedOptionEntry(Object value,
- OptionPriority priority, String source, String implicitDependant, String expandedFrom,
- boolean allowMultiple) {
- this.value = value;
- this.priority = priority;
- this.source = source;
- this.implicitDependant = implicitDependant;
- this.expandedFrom = expandedFrom;
- this.allowMultiple = allowMultiple;
- }
-
- // Need to suppress unchecked warnings, because the "multiple occurrence"
- // options use unchecked ListMultimaps due to limitations of Java generics.
- @SuppressWarnings({"unchecked", "rawtypes"})
- Object getValue() {
- if (allowMultiple) {
- // Sort the results by option priority and return them in a new list.
- // The generic type of the list is not known at runtime, so we can't
- // use it here. It was already checked in the constructor, so this is
- // type-safe.
- List result = Lists.newArrayList();
- ListMultimap realValue = (ListMultimap) value;
- for (OptionPriority priority : OptionPriority.values()) {
- // If there is no mapping for this key, this check avoids object creation (because
- // ListMultimap has to return a new object on get) and also an unnecessary addAll call.
- if (realValue.containsKey(priority)) {
- result.addAll(realValue.get(priority));
- }
- }
- return result;
- }
- return value;
- }
-
- // Need to suppress unchecked warnings, because the "multiple occurrence"
- // options use unchecked ListMultimaps due to limitations of Java generics.
- @SuppressWarnings({"unchecked", "rawtypes"})
- void addValue(OptionPriority addedPriority, Object addedValue) {
- Preconditions.checkState(allowMultiple);
- ListMultimap optionValueList = (ListMultimap) value;
- if (addedValue instanceof List<?>) {
- for (Object element : (List<?>) addedValue) {
- optionValueList.put(addedPriority, element);
- }
- } else {
- optionValueList.put(addedPriority, addedValue);
- }
- }
-
- OptionValueDescription asOptionValueDescription(String fieldName) {
- return new OptionValueDescription(fieldName, getValue(), priority,
- source, implicitDependant, expandedFrom);
- }
- }
-
private final OptionsData optionsData;
/**
* We store the results of parsing the arguments in here. It'll look like
+ *
* <pre>
* Field("--host") -> "www.google.com"
* Field("--port") -> 80
* </pre>
- * This map is modified by repeated calls to
- * {@link #parse(OptionPriority,Function,List)}.
+ *
+ * This map is modified by repeated calls to {@link #parse(OptionPriority,Function,List)}.
*/
- private final Map<Field, ParsedOptionEntry> parsedValues = Maps.newHashMap();
+ private final Map<Field, OptionValueDescription> parsedValues = Maps.newHashMap();
/**
* We store the pre-parsed, explicit options for each priority in here.
@@ -376,16 +309,17 @@ class OptionsParserImpl {
*/
List<OptionValueDescription> asListOfEffectiveOptions() {
List<OptionValueDescription> result = Lists.newArrayList();
- for (Map.Entry<String,Field> mapEntry : optionsData.getAllNamedFields()) {
+ for (Map.Entry<String, Field> mapEntry : optionsData.getAllNamedFields()) {
String fieldName = mapEntry.getKey();
Field field = mapEntry.getValue();
- ParsedOptionEntry entry = parsedValues.get(field);
+ OptionValueDescription entry = parsedValues.get(field);
if (entry == null) {
Object value = optionsData.getDefaultValue(field);
- result.add(new OptionValueDescription(fieldName, value, OptionPriority.DEFAULT,
- null, null, null));
+ result.add(
+ new OptionValueDescription(
+ fieldName, value, OptionPriority.DEFAULT, null, null, null, false));
} else {
- result.add(entry.asOptionValueDescription(fieldName));
+ result.add(entry);
}
}
return result;
@@ -412,46 +346,71 @@ class OptionsParserImpl {
// Warnings should not end with a '.' because the internal reporter adds one automatically.
private void setValue(Field field, String name, Object value,
OptionPriority priority, String source, String implicitDependant, String expandedFrom) {
- ParsedOptionEntry entry = parsedValues.get(field);
+ OptionValueDescription entry = parsedValues.get(field);
if (entry != null) {
// Override existing option if the new value has higher or equal priority.
- if (priority.compareTo(entry.priority) >= 0) {
+ if (priority.compareTo(entry.getPriority()) >= 0) {
// Output warnings:
- if ((implicitDependant != null) && (entry.implicitDependant != null)) {
- if (!implicitDependant.equals(entry.implicitDependant)) {
- warnings.add("Option '" + name + "' is implicitly defined by both option '" +
- entry.implicitDependant + "' and option '" + implicitDependant + "'");
+ if ((implicitDependant != null) && (entry.getImplicitDependant() != null)) {
+ if (!implicitDependant.equals(entry.getImplicitDependant())) {
+ warnings.add(
+ "Option '"
+ + name
+ + "' is implicitly defined by both option '"
+ + entry.getImplicitDependant()
+ + "' and option '"
+ + implicitDependant
+ + "'");
}
- } else if ((implicitDependant != null) && priority.equals(entry.priority)) {
- warnings.add("Option '" + name + "' is implicitly defined by option '" +
- implicitDependant + "'; the implicitly set value overrides the previous one");
- } else if (entry.implicitDependant != null) {
- warnings.add("A new value for option '" + name + "' overrides a previous " +
- "implicit setting of that option by option '" + entry.implicitDependant + "'");
- } else if ((priority == entry.priority) &&
- ((entry.expandedFrom == null) && (expandedFrom != null))) {
+ } else if ((implicitDependant != null) && priority.equals(entry.getPriority())) {
+ warnings.add(
+ "Option '"
+ + name
+ + "' is implicitly defined by option '"
+ + implicitDependant
+ + "'; the implicitly set value overrides the previous one");
+ } else if (entry.getImplicitDependant() != null) {
+ warnings.add(
+ "A new value for option '"
+ + name
+ + "' overrides a previous implicit setting of that option by option '"
+ + entry.getImplicitDependant()
+ + "'");
+ } else if ((priority == entry.getPriority())
+ && ((entry.getExpansionParent() == null) && (expandedFrom != null))) {
// Create a warning if an expansion option overrides an explicit option:
warnings.add("The option '" + expandedFrom + "' was expanded and now overrides a "
+ "previous explicitly specified option '" + name + "'");
}
// Record the new value:
- parsedValues.put(field,
- new ParsedOptionEntry(value, priority, source, implicitDependant, expandedFrom, false));
+ parsedValues.put(
+ field,
+ new OptionValueDescription(
+ name, value, priority, source, implicitDependant, expandedFrom, false));
}
} else {
- parsedValues.put(field,
- new ParsedOptionEntry(value, priority, source, implicitDependant, expandedFrom, false));
+ parsedValues.put(
+ field,
+ new OptionValueDescription(
+ name, value, priority, source, implicitDependant, expandedFrom, false));
maybeAddDeprecationWarning(field);
}
}
private void addListValue(Field field, Object value, OptionPriority priority, String source,
String implicitDependant, String expandedFrom) {
- ParsedOptionEntry entry = parsedValues.get(field);
+ OptionValueDescription entry = parsedValues.get(field);
if (entry == null) {
- entry = new ParsedOptionEntry(ArrayListMultimap.create(), priority, source,
- implicitDependant, expandedFrom, true);
+ entry =
+ new OptionValueDescription(
+ field.getName(),
+ ArrayListMultimap.create(),
+ priority,
+ source,
+ implicitDependant,
+ expandedFrom,
+ true);
parsedValues.put(field, entry);
maybeAddDeprecationWarning(field);
}
@@ -472,9 +431,9 @@ class OptionsParserImpl {
Field field, Option option, Map<String, OptionValueDescription> clearedValues)
throws OptionsParsingException {
- ParsedOptionEntry removed = parsedValues.remove(field);
+ OptionValueDescription removed = parsedValues.remove(field);
if (removed != null) {
- clearedValues.put(option.name(), removed.asOptionValueDescription(option.name()));
+ clearedValues.put(option.name(), removed);
}
canonicalizeValues.removeAll(field);
@@ -496,11 +455,7 @@ class OptionsParserImpl {
if (field == null) {
throw new IllegalArgumentException("No such option '" + name + "'");
}
- ParsedOptionEntry entry = parsedValues.get(field);
- if (entry == null) {
- return null;
- }
- return entry.asOptionValueDescription(name);
+ return parsedValues.get(field);
}
OptionDescription getOptionDescription(String name) {
@@ -624,9 +579,12 @@ class OptionsParserImpl {
// Handle expansion options.
if (option.expansion().length > 0) {
- Function<Object, String> expansionSourceFunction = Functions.<String>constant(
- "expanded from option --" + originalName + " from " +
- sourceFunction.apply(originalName));
+ Function<Object, String> expansionSourceFunction =
+ Functions.constant(
+ "expanded from option --"
+ + originalName
+ + " from "
+ + sourceFunction.apply(originalName));
maybeAddDeprecationWarning(field);
List<String> unparsed = parse(priority, expansionSourceFunction, null, originalName,
ImmutableList.copyOf(option.expansion()));
@@ -673,10 +631,13 @@ class OptionsParserImpl {
// Now parse any implicit requirements that were collected.
// TODO(bazel-team): this should happen when the option is encountered.
if (!implicitRequirements.isEmpty()) {
- for (Map.Entry<String,List<String>> entry : implicitRequirements.entrySet()) {
- Function<Object, String> requirementSourceFunction = Functions.<String>constant(
- "implicit requirement of option --" + entry.getKey() + " from " +
- sourceFunction.apply(entry.getKey()));
+ for (Map.Entry<String, List<String>> entry : implicitRequirements.entrySet()) {
+ Function<Object, String> requirementSourceFunction =
+ Functions.constant(
+ "implicit requirement of option --"
+ + entry.getKey()
+ + " from "
+ + sourceFunction.apply(entry.getKey()));
List<String> unparsed = parse(priority, requirementSourceFunction, entry.getKey(), null,
entry.getValue());
@@ -797,7 +758,7 @@ class OptionsParserImpl {
// Set the fields
for (Field field : optionsData.getFieldsForClass(optionsClass)) {
Object value;
- ParsedOptionEntry entry = parsedValues.get(field);
+ OptionValueDescription entry = parsedValues.get(field);
if (entry == null) {
value = optionsData.getDefaultValue(field);
} else {