Merge "Let MinifiedNameMapPrinter use StringBuilder"
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
new file mode 100644
index 0000000..f55e362
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -0,0 +1,184 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.IOExceptionDiagnostic;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Consumer for Java classfile encoded programs.
+ *
+ * <p>This consumer can only be provided to R8.
+ */
+public interface ClassFileConsumer extends ProgramConsumer {
+
+ /**
+ * Callback to receive Java classfile data for a compilation output.
+ *
+ * <p>There is no guaranteed order and files might be written concurrently.
+ *
+ * <p>The consumer is expected not to throw, but instead report any errors via the diagnostics
+ * {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown,
+ * then the compiler guaranties to exit with an error.
+ *
+ * @param data Java class-file encoded data.
+ * @param descriptor Class descriptor of the class the data pertains to.
+ * @param handler Diagnostics handler for reporting.
+ */
+ void accept(byte[] data, String descriptor, DiagnosticsHandler handler);
+
+ /** Empty consumer to request the production of the resource but ignore its value. */
+ class EmptyConsumer implements ClassFileConsumer {
+
+ @Override
+ public void accept(byte[] data, String descriptor, DiagnosticsHandler handler) {
+ // Ignore data.
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ // Nothing to close.
+ }
+ }
+
+ /** Forwarding consumer to delegate to an optional existing consumer. */
+ class ForwardingConsumer implements ClassFileConsumer {
+
+ private final ClassFileConsumer consumer;
+
+ public ForwardingConsumer(ClassFileConsumer consumer) {
+ this.consumer = consumer;
+ }
+
+ @Override
+ public void accept(byte[] data, String descriptor, DiagnosticsHandler handler) {
+ if (consumer != null) {
+ consumer.accept(data, descriptor, handler);
+ }
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ if (consumer != null) {
+ consumer.finished(handler);
+ }
+ }
+ }
+
+ /** Archive consumer to write program resources to a zip archive. */
+ class ArchiveConsumer extends ForwardingConsumer {
+
+ private final Path archive;
+ private final Origin origin;
+ private ZipOutputStream stream = null;
+ private boolean closed = false;
+
+ public ArchiveConsumer(Path archive) {
+ this(archive, null);
+ }
+
+ public ArchiveConsumer(Path archive, ClassFileConsumer consumer) {
+ super(consumer);
+ this.archive = archive;
+ origin = new PathOrigin(archive);
+ }
+
+ @Override
+ public void accept(byte[] data, String descriptor, DiagnosticsHandler handler) {
+ super.accept(data, descriptor, handler);
+ synchronizedWrite(getClassFileName(descriptor), data, handler);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ super.finished(handler);
+ assert !closed;
+ closed = true;
+ try {
+ if (stream != null) {
+ stream.close();
+ stream = null;
+ }
+ } catch (IOException e) {
+ handler.error(new IOExceptionDiagnostic(e, origin));
+ }
+ }
+
+ private ZipOutputStream getStream(DiagnosticsHandler handler) {
+ assert !closed;
+ if (stream == null) {
+ try {
+ stream =
+ new ZipOutputStream(
+ Files.newOutputStream(
+ archive, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING));
+ } catch (IOException e) {
+ handler.error(new IOExceptionDiagnostic(e, origin));
+ }
+ }
+ return stream;
+ }
+
+ private synchronized void synchronizedWrite(
+ String entry, byte[] content, DiagnosticsHandler handler) {
+ try {
+ ZipUtils.writeToZipStream(getStream(handler), entry, content);
+ } catch (IOException e) {
+ handler.error(new IOExceptionDiagnostic(e, origin));
+ }
+ }
+
+ private static String getClassFileName(String classDescriptor) {
+ assert classDescriptor != null && DescriptorUtils.isClassDescriptor(classDescriptor);
+ return DescriptorUtils.getClassBinaryNameFromDescriptor(classDescriptor) + CLASS_EXTENSION;
+ }
+ }
+
+ /** Directory consumer to write program resources to a directory. */
+ class DirectoryConsumer extends ForwardingConsumer {
+
+ private final Path directory;
+
+ public DirectoryConsumer(Path directory) {
+ this(directory, null);
+ }
+
+ public DirectoryConsumer(Path directory, ClassFileConsumer consumer) {
+ super(consumer);
+ this.directory = directory;
+ }
+
+ @Override
+ public void accept(byte[] data, String descriptor, DiagnosticsHandler handler) {
+ super.accept(data, descriptor, handler);
+ Path target = directory.resolve(ArchiveConsumer.getClassFileName(descriptor));
+ try {
+ writeFileFromDescriptor(data, target);
+ } catch (IOException e) {
+ handler.error(new IOExceptionDiagnostic(e, new PathOrigin(target)));
+ }
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {
+ super.finished(handler);
+ }
+
+ private static void writeFileFromDescriptor(byte[] contents, Path target) throws IOException {
+ Files.createDirectories(target.getParent());
+ FileUtils.writeToFile(target, null, contents);
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index adb780e..0e7f329 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -217,6 +217,7 @@
R8.unwrapExecutionException(e);
throw new AssertionError(e); // unwrapping method should have thrown
} finally {
+ options.closeProgramConsumer();
outputSink.close();
}
}
diff --git a/src/main/java/com/android/tools/r8/OutputSink.java b/src/main/java/com/android/tools/r8/OutputSink.java
index 316df01..a8d934a 100644
--- a/src/main/java/com/android/tools/r8/OutputSink.java
+++ b/src/main/java/com/android/tools/r8/OutputSink.java
@@ -54,28 +54,6 @@
throws IOException;
/**
- * Write a Java classfile that contains the class primaryClassName and its companion classes.
- * <p>
- * This is equivalent to writing out the file com/foo/bar/Test.class given a primaryClassName of
- * com.foo.bar.Test.
- * <p>
- * There is no guaranteed order and files might be written concurrently. However, for each
- * primaryClassName only one file is ever written.
- * <p>
- * This method is only invoked by R8 and only if compiling to Java bytecode. If this method is
- * called, the other writeDexFile and writeClassFile methods will not be called.
- */
- void writeClassFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
- throws IOException;
-
- /**
- * Provides the raw bytes that would be generated for the <code>-printseeds</code> flag.
- * <p>
- * This method is only invoked by R8 and only if R8 is instructed to generate seeds information.
- */
- void writeProguardSeedsFile(byte[] contents) throws IOException;
-
- /**
* Closes the output sink.
* <p>
* This method is invokes once all output has been generated.
diff --git a/src/main/java/com/android/tools/r8/ProgramConsumer.java b/src/main/java/com/android/tools/r8/ProgramConsumer.java
new file mode 100644
index 0000000..061741c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ProgramConsumer.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+/**
+ * Base for all program consumers to allow abstracting which concrete consumer is provided to D8/R8.
+ */
+public interface ProgramConsumer {
+
+ /**
+ * Callback signifying that compilation of program resources has finished.
+ *
+ * <p>Called only once after all program outputs have been generated and consumed.
+ *
+ * @param handler Diagnostics handler for reporting.
+ */
+ void finished(DiagnosticsHandler handler);
+}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 73a5b1a..3e30ebf 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -106,14 +106,15 @@
OutputSink outputSink,
String deadCode,
NamingLens namingLens,
- byte[] proguardSeedsData,
+ String proguardSeedsData,
InternalOptions options,
ProguardMapSupplier proguardMapSupplier)
throws ExecutionException, DexOverflowException {
try {
Marker marker = getMarker(options);
- if (options.outputClassFiles) {
- new CfApplicationWriter(application, options).write(outputSink, executorService);
+ if (options.isGeneratingClassFiles()) {
+ new CfApplicationWriter(application, options)
+ .write(options.getClassFileConsumer(), executorService);
} else {
new ApplicationWriter(
application,
@@ -173,7 +174,7 @@
AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
RootSet rootSet;
- byte[] proguardSeedsData = null;
+ String proguardSeedsData = null;
timing.begin("Strip unused code");
try {
Set<DexType> missingClasses = appInfo.getMissingClasses();
@@ -202,7 +203,7 @@
PrintStream out = new PrintStream(bytes);
RootSetBuilder.writeSeeds(appInfo.withLiveness(), out);
out.flush();
- proguardSeedsData = bytes.toByteArray();
+ proguardSeedsData = bytes.toString();
}
if (options.useTreeShaking) {
TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
@@ -392,6 +393,7 @@
throw new AssertionError(e); // unwrapping method should have thrown
} finally {
outputSink.close();
+ options.closeProgramConsumer();
// Dump timings.
if (options.printTimes) {
timing.report();
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index d2455f3..dd8d0ff 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -569,6 +569,13 @@
: new StringConsumer.StreamConsumer(StandardOutOrigin.instance(), System.out);
}
+ // Setup pg-seeds consumer.
+ if (proguardConfiguration.isPrintSeeds()) {
+ internal.proguardSeedsConsumer = proguardConfiguration.getSeedFile() != null
+ ? new StringConsumer.FileConsumer(proguardConfiguration.getSeedFile())
+ : new StringConsumer.StreamConsumer(StandardOutOrigin.instance(), System.out);
+ }
+
// Amend the proguard-map consumer with options from the proguard configuration.
{
StringConsumer wrappedConsumer;
diff --git a/src/main/java/com/android/tools/r8/StringConsumer.java b/src/main/java/com/android/tools/r8/StringConsumer.java
index 9e5ad47..33f364e 100644
--- a/src/main/java/com/android/tools/r8/StringConsumer.java
+++ b/src/main/java/com/android/tools/r8/StringConsumer.java
@@ -25,6 +25,7 @@
* <p>The consumer is expected not to throw, but instead report any errors via the diagnostics
* {@param handler}. If an error is reported via {@param handler} and no exceptions are thrown,
* then the compiler guaranties to exit with an error.
+ *
* @param string String resource.
* @param handler Diagnostics handler for reporting.
*/
@@ -141,8 +142,11 @@
@Override
public void accept(String string, DiagnosticsHandler handler) {
super.accept(string, handler);
- try (BufferedWriter writer =
- new BufferedWriter(new OutputStreamWriter(outputStream, encoding.newEncoder()))) {
+ // Don't close this writer as it will close the underlying stream, which we specifically do
+ // not want.
+ BufferedWriter writer =
+ new BufferedWriter(new OutputStreamWriter(outputStream, encoding.newEncoder()));
+ try {
writer.write(string);
} catch (IOException e) {
handler.error(new IOExceptionDiagnostic(e, origin));
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 76c756b..0d92201 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -45,7 +45,7 @@
public final DexApplication application;
public final String deadCode;
public final NamingLens namingLens;
- public final byte[] proguardSeedsData;
+ public final String proguardSeedsData;
public final InternalOptions options;
public DexString markerString;
public final ProguardMapSupplier proguardMapSupplier;
@@ -113,7 +113,7 @@
Marker marker,
String deadCode,
NamingLens namingLens,
- byte[] proguardSeedsData,
+ String proguardSeedsData,
ProguardMapSupplier proguardMapSupplier) {
assert application != null;
this.application = application;
@@ -208,22 +208,21 @@
if (options.usageInformationConsumer != null && deadCode != null) {
ExceptionUtils.withConsumeResourceHandler(
- options.reporter, deadCode, options.usageInformationConsumer);
+ options.reporter, options.usageInformationConsumer, deadCode);
}
// Write the proguard map file after writing the dex files, as the map writer traverses
// the DexProgramClass structures, which are destructively updated during dex file writing.
if (proguardMapSupplier != null && options.proguardMapConsumer != null) {
ExceptionUtils.withConsumeResourceHandler(
- options.reporter, proguardMapSupplier.get(), options.proguardMapConsumer);
+ options.reporter, options.proguardMapConsumer, proguardMapSupplier.get());
}
- if (options.proguardConfiguration.isPrintSeeds()) {
- if (proguardSeedsData != null) {
- outputSink.writeProguardSeedsFile(proguardSeedsData);
- }
+ if (options.proguardSeedsConsumer != null && proguardSeedsData != null) {
+ ExceptionUtils.withConsumeResourceHandler(
+ options.reporter, options.proguardSeedsConsumer, proguardSeedsData);
}
if (options.mainDexListConsumer != null) {
ExceptionUtils.withConsumeResourceHandler(
- options.reporter, writeMainDexList(), options.mainDexListConsumer);
+ options.reporter, options.mainDexListConsumer, writeMainDexList());
}
} finally {
application.timing.end();
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index 88c187b..da6fca3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -105,8 +105,8 @@
}
}
- if (!isBlockExit && emittedPc != pc && pcAdvancing) {
- // For non-exit / pc-advancing instructions emit any pending changes.
+ if (emittedPc != pc && pcAdvancing) {
+ // For pc-advancing instructions emit any pending changes.
emitLocalChanges(pc);
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index 4bc9a50..8557054 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.IdentifierUtils;
import com.android.tools.r8.utils.StringUtils;
import java.io.UTFDataFormatException;
import java.util.Arrays;
@@ -206,37 +207,6 @@
return slowCompareTo(other);
}
- private boolean isSimpleNameChar(char ch) {
- if (ch >= 'A' && ch <= 'Z') {
- return true;
- }
- if (ch >= 'a' && ch <= 'z') {
- return true;
- }
- if (ch >= '0' && ch <= '9') {
- return true;
- }
- if (ch == '$' || ch == '-' || ch == '_') {
- return true;
- }
- if (ch >= 0x00a1 && ch <= 0x1fff) {
- return true;
- }
- if (ch >= 0x2010 && ch <= 0x2027) {
- return true;
- }
- if (ch >= 0x2030 && ch <= 0xd7ff) {
- return true;
- }
- if (ch >= 0xe000 && ch <= 0xffef) {
- return true;
- }
- if (ch >= 0x10000 && ch <= 0x10ffff) {
- return true;
- }
- return false;
- }
-
private boolean isValidClassDescriptor(String string) {
if (string.length() < 3
|| string.charAt(0) != 'L'
@@ -251,7 +221,7 @@
if (ch == '/') {
continue;
}
- if (isSimpleNameChar(ch)) {
+ if (IdentifierUtils.isDexIdentifierPart(ch)) {
continue;
}
return false;
@@ -273,7 +243,7 @@
}
for (int i = 0; i < string.length(); i++) {
char ch = string.charAt(i);
- if (isSimpleNameChar(ch)) {
+ if (IdentifierUtils.isDexIdentifierPart(ch)) {
continue;
}
return false;
@@ -296,7 +266,7 @@
}
}
for (int i = start; i < end; i++) {
- if (isSimpleNameChar(string.charAt(i))) {
+ if (IdentifierUtils.isDexIdentifierPart(string.charAt(i))) {
continue;
}
return false;
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
index 6bf1a17..14c7a6b 100644
--- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
import com.android.tools.r8.utils.InternalOptions;
import java.util.List;
@@ -21,12 +22,24 @@
public final InternalOptions options;
+ private ConcurrentHashMap<String, Type> asmObjectTypeCache = new ConcurrentHashMap<>();
+
+ private ConcurrentHashMap<String, Type> asmTypeCache = new ConcurrentHashMap<>();
+
ConcurrentHashMap<String, DexString> stringCache = new ConcurrentHashMap<>();
public JarApplicationReader(InternalOptions options) {
this.options = options;
}
+ public Type getAsmObjectType(String name) {
+ return asmObjectTypeCache.computeIfAbsent(name, (key) -> Type.getObjectType(key));
+ }
+
+ public Type getAsmType(String name) {
+ return asmTypeCache.computeIfAbsent(name, (key) -> Type.getType(key));
+ }
+
public DexItemFactory getFactory() {
return options.itemFactory;
}
@@ -41,7 +54,7 @@
public DexType getTypeFromName(String name) {
assert isValidInternalName(name);
- return getType(Type.getObjectType(name));
+ return getType(getAsmObjectType(name));
}
public DexType getTypeFromDescriptor(String desc) {
@@ -101,9 +114,8 @@
public DexProto getProto(String desc) {
assert isValidDescriptor(desc);
- Type returnType = Type.getReturnType(desc);
- Type[] arguments = Type.getArgumentTypes(desc);
-
+ Type returnType = getReturnType(desc);
+ Type[] arguments = getArgumentTypes(desc);
StringBuilder shortyDescriptor = new StringBuilder();
String[] argumentDescriptors = new String[arguments.length];
shortyDescriptor.append(getShortyDescriptor(returnType));
@@ -131,10 +143,86 @@
}
private boolean isValidDescriptor(String desc) {
- return Type.getType(desc).getDescriptor().equals(desc);
+ return getAsmType(desc).getDescriptor().equals(desc);
}
private boolean isValidInternalName(String name) {
- return Type.getObjectType(name).getInternalName().equals(name);
+ return getAsmObjectType(name).getInternalName().equals(name);
+ }
+
+ public Type getReturnType(final String methodDescriptor) {
+ assert methodDescriptor.indexOf(')') != -1;
+ return getAsmType(methodDescriptor.substring(methodDescriptor.indexOf(')') + 1));
+ }
+
+ public int getArgumentCount(final String methodDescriptor) {
+ int charIdx = 1;
+ char c;
+ int argCount = 0;
+ while ((c = methodDescriptor.charAt(charIdx++)) != ')') {
+ if (c == 'L') {
+ while (methodDescriptor.charAt(charIdx++) != ';');
+ argCount++;
+ } else if (c != '[') {
+ argCount++;
+ }
+ }
+ return argCount;
+ }
+
+ public Type[] getArgumentTypes(final String methodDescriptor) {
+ Type[] args = new Type[getArgumentCount(methodDescriptor)];
+ int charIdx = 1;
+ char c;
+ int argIdx = 0;
+ int startType;
+ while ((c = methodDescriptor.charAt(charIdx)) != ')') {
+ switch (c) {
+ case 'V':
+ throw new Unreachable();
+ case 'Z':
+ args[argIdx++] = Type.BOOLEAN_TYPE;
+ break;
+ case 'C':
+ args[argIdx++] = Type.CHAR_TYPE;
+ break;
+ case 'B':
+ args[argIdx++] = Type.BYTE_TYPE;
+ break;
+ case 'S':
+ args[argIdx++] = Type.SHORT_TYPE;
+ break;
+ case 'I':
+ args[argIdx++] = Type.INT_TYPE;
+ break;
+ case 'F':
+ args[argIdx++] = Type.FLOAT_TYPE;
+ break;
+ case 'J':
+ args[argIdx++] = Type.LONG_TYPE;
+ break;
+ case 'D':
+ args[argIdx++] = Type.DOUBLE_TYPE;
+ break;
+ case '[':
+ startType = charIdx;
+ while (methodDescriptor.charAt(++charIdx) == '[') {
+ }
+ if (methodDescriptor.charAt(charIdx) == 'L') {
+ while (methodDescriptor.charAt(++charIdx) != ';');
+ }
+ args[argIdx++] = getAsmType(methodDescriptor.substring(startType, charIdx + 1));
+ break;
+ case 'L':
+ startType = charIdx;
+ while (methodDescriptor.charAt(++charIdx) != ';');
+ args[argIdx++] = getAsmType(methodDescriptor.substring(startType, charIdx + 1));
+ break;
+ default:
+ throw new Unreachable();
+ }
+ charIdx++;
+ }
+ return args;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index a9832dc..7976085 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -210,7 +210,7 @@
@Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
- return new CreateMethodVisitor(access, name, desc, signature, exceptions, this);
+ return new CreateMethodVisitor(application, access, name, desc, signature, exceptions, this);
}
@Override
@@ -406,14 +406,14 @@
private List<DexValue> parameterNames = null;
private List<DexValue> parameterFlags = null;
- public CreateMethodVisitor(int access, String name, String desc, String signature,
- String[] exceptions, CreateDexClassVisitor parent) {
+ public CreateMethodVisitor(JarApplicationReader application, int access, String name,
+ String desc, String signature, String[] exceptions, CreateDexClassVisitor parent) {
super(ASM6);
this.access = access;
this.name = name;
this.desc = desc;
this.parent = parent;
- parameterCount = Type.getArgumentTypes(desc).length;
+ parameterCount = application.getArgumentCount(desc);
if (exceptions != null && exceptions.length > 0) {
DexValue[] values = new DexValue[exceptions.length];
for (int i = 0; i < exceptions.length; i++) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 1cfd2ae..a3a730d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -103,7 +103,7 @@
@Override
public boolean canBeDeadCode(IRCode code, InternalOptions options) {
// No side-effect, such as throwing an exception, in CF.
- return options.outputClassFiles || !instructionInstanceCanThrow();
+ return options.isGeneratingClassFiles() || !instructionInstanceCanThrow();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 6d399fc..15245db 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -67,7 +67,7 @@
@Override
public boolean canBeDeadCode(IRCode code, InternalOptions options) {
- return !options.debug && !options.outputClassFiles;
+ return !options.debug && options.isGeneratingDex();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 7ab9696..9004a44 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -609,9 +609,10 @@
}
private void finalizeIR(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
- if (options.outputClassFiles) {
+ if (options.isGeneratingClassFiles()) {
finalizeToCf(method, code, feedback);
} else {
+ assert options.isGeneratingDex();
finalizeToDex(method, code, feedback);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 700d9da..157e818 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -197,7 +197,7 @@
this.method = method;
this.clazz = clazz;
this.callerPosition = callerPosition;
- parameterTypes = Arrays.asList(Type.getArgumentTypes(node.desc));
+ parameterTypes = Arrays.asList(application.getArgumentTypes(node.desc));
state = new JarState(node.maxLocals, node.localVariables, this, application);
AbstractInsnNode first = node.instructions.getFirst();
initialLabel = first instanceof LabelNode ? (LabelNode) first : null;
@@ -263,7 +263,7 @@
LocalVariableNode local = (LocalVariableNode) o;
Type localType;
ValueType localValueType;
- switch (Type.getType(local.desc).getSort()) {
+ switch (application.getAsmType(local.desc).getSort()) {
case Type.OBJECT:
case Type.ARRAY: {
localType = JarState.NULL_TYPE;
@@ -331,7 +331,7 @@
if (isSynchronized()) {
generatingMethodSynchronization = true;
- Type clazzType = Type.getType(clazz.toDescriptorString());
+ Type clazzType = application.getAsmType(clazz.toDescriptorString());
int monitorRegister;
if (isStatic()) {
// Load the class using a temporary on the stack.
@@ -354,7 +354,7 @@
private void buildArgumentInstructions(IRBuilder builder) {
int argumentRegister = 0;
if (!isStatic()) {
- Type thisType = Type.getType(clazz.descriptor.toString());
+ Type thisType = application.getAsmType(clazz.descriptor.toString());
Slot slot = state.readLocal(argumentRegister++, thisType);
builder.addThisArgument(slot.register);
}
@@ -375,7 +375,7 @@
new Int2ReferenceOpenHashMap<>(node.localVariables.size());
int argumentRegister = 0;
if (!isStatic()) {
- Type thisType = Type.getType(clazz.descriptor.toString());
+ Type thisType = application.getAsmType(clazz.descriptor.toString());
int register = state.writeLocal(argumentRegister++, thisType);
initializedLocals.put(register, valueType(thisType));
}
@@ -878,8 +878,8 @@
}
}
- private static MemberType memberType(String fieldDesc) {
- return memberType(Type.getType(fieldDesc));
+ private MemberType memberType(String fieldDesc) {
+ return memberType(application.getAsmType(fieldDesc));
}
private static NumericType numericType(Type type) {
@@ -927,8 +927,8 @@
}
}
- private static Type makeArrayType(Type elementType) {
- return Type.getObjectType("[" + elementType.getDescriptor());
+ private Type makeArrayType(Type elementType) {
+ return application.getAsmObjectType("[" + elementType.getDescriptor());
}
private static String arrayTypeDesc(int arrayTypeCode) {
@@ -1205,7 +1205,7 @@
}
case Opcodes.POP: {
Slot value = state.pop();
- assert value.isCategory1();
+ value.isCategory1();
break;
}
case Opcodes.POP2: {
@@ -1539,7 +1539,7 @@
}
case Opcodes.NEWARRAY: {
String desc = arrayTypeDesc(insn.operand);
- Type type = Type.getType(desc);
+ Type type = application.getAsmType(desc);
state.pop();
state.push(type);
break;
@@ -1594,7 +1594,7 @@
}
private void updateState(TypeInsnNode insn) {
- Type type = Type.getObjectType(insn.desc);
+ Type type = application.getAsmObjectType(insn.desc);
switch (insn.getOpcode()) {
case Opcodes.NEW: {
state.push(type);
@@ -1624,7 +1624,7 @@
}
private void updateState(FieldInsnNode insn) {
- Type type = Type.getType(insn.desc);
+ Type type = application.getAsmType(insn.desc);
switch (insn.getOpcode()) {
case Opcodes.GETSTATIC:
state.push(type);
@@ -1657,14 +1657,13 @@
private void updateStateForInvoke(String desc, boolean implicitReceiver) {
// Pop arguments.
- Type[] parameterTypes = Type.getArgumentTypes(desc);
- state.popReverse(parameterTypes.length);
+ state.popReverse(application.getArgumentCount(desc));
// Pop implicit receiver if needed.
if (implicitReceiver) {
state.pop();
}
// Push return value if needed.
- Type returnType = Type.getReturnType(desc);
+ Type returnType = application.getReturnType(desc);
if (returnType != Type.VOID_TYPE) {
state.push(returnType);
}
@@ -1747,7 +1746,7 @@
private void updateState(MultiANewArrayInsnNode insn) {
// Type of the full array.
- Type arrayType = Type.getObjectType(insn.desc);
+ Type arrayType = application.getAsmObjectType(insn.desc);
state.popReverse(insn.dims, Type.INT_TYPE);
state.push(arrayType);
}
@@ -2368,7 +2367,7 @@
}
case Opcodes.NEWARRAY: {
String desc = arrayTypeDesc(insn.operand);
- Type type = Type.getType(desc);
+ Type type = application.getAsmType(desc);
DexType dexType = application.getTypeFromDescriptor(desc);
int count = state.pop(Type.INT_TYPE).register;
int array = state.push(type);
@@ -2423,7 +2422,7 @@
}
private void build(TypeInsnNode insn, IRBuilder builder) {
- Type type = Type.getObjectType(insn.desc);
+ Type type = application.getAsmObjectType(insn.desc);
switch (insn.getOpcode()) {
case Opcodes.NEW: {
DexType dexType = application.getTypeFromName(insn.desc);
@@ -2461,7 +2460,7 @@
private void build(FieldInsnNode insn, IRBuilder builder) {
DexField field = application.getField(insn.owner, insn.name, insn.desc);
- Type type = Type.getType(insn.desc);
+ Type type = application.getAsmType(insn.desc);
switch (insn.getOpcode()) {
case Opcodes.GETSTATIC:
builder.addStaticGet(state.push(type), field);
@@ -2492,7 +2491,7 @@
buildInvoke(
insn.desc,
- Type.getObjectType(insn.owner),
+ application.getAsmObjectType(insn.owner),
insn.getOpcode() != Opcodes.INVOKESTATIC,
builder,
(types, registers) -> {
@@ -2517,7 +2516,7 @@
// Build the argument list of the form [owner, param1, ..., paramN].
// The arguments are in reverse order on the stack, so we pop off the parameters here.
- Type[] parameterTypes = Type.getArgumentTypes(methodDesc);
+ Type[] parameterTypes = application.getArgumentTypes(methodDesc);
Slot[] parameterRegisters = state.popReverse(parameterTypes.length);
List<ValueType> types = new ArrayList<>(parameterTypes.length + 1);
@@ -2537,7 +2536,7 @@
creator.accept(types, registers);
// Move the result to the "top of stack".
- Type returnType = Type.getReturnType(methodDesc);
+ Type returnType = application.getReturnType(methodDesc);
if (returnType != Type.VOID_TYPE) {
builder.addMoveResult(state.push(returnType));
}
@@ -2780,7 +2779,7 @@
private void build(MultiANewArrayInsnNode insn, IRBuilder builder) throws ApiLevelException {
// Type of the full array.
- Type arrayType = Type.getObjectType(insn.desc);
+ Type arrayType = application.getAsmObjectType(insn.desc);
DexType dexArrayType = application.getType(arrayType);
// Type of the members. Can itself be of array type, eg, 'int[]' for 'new int[x][y][]'
DexType memberType = application.getTypeFromDescriptor(insn.desc.substring(insn.dims));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index f74d60b..791c216 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -709,7 +709,7 @@
// Replace result uses for methods where something is known about what is returned.
public void rewriteMoveResult(IRCode code) {
- if (options.outputClassFiles) {
+ if (options.isGeneratingClassFiles()) {
return;
}
AppInfoWithSubtyping appInfoWithSubtyping = appInfo.withSubtyping();
@@ -739,7 +739,7 @@
argumentIndex)) {
Value argument = invoke.arguments().get(argumentIndex);
assert invoke.outType().compatible(argument.outType())
- || (!options.outputClassFiles && verifyCompatibleFromDex(invoke, argument));
+ || (options.isGeneratingDex() && verifyCompatibleFromDex(invoke, argument));
invoke.outValue().replaceUsers(argument);
invoke.setOutValue(null);
}
@@ -1397,7 +1397,7 @@
* and fill-array-data / filled-new-array.
*/
public void simplifyArrayConstruction(IRCode code) {
- if (options.outputClassFiles) {
+ if (options.isGeneratingClassFiles()) {
return;
}
for (BasicBlock block : code.blocks) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 2db0673..6747809 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -284,62 +284,46 @@
// For each debug position compute the set of live locals.
boolean localsChanged = false;
- Int2ReferenceMap<DebugLocalInfo> currentLocals = new Int2ReferenceOpenHashMap<>();
-
LinkedList<LocalRange> openRanges = new LinkedList<>();
Iterator<LocalRange> rangeIterator = ranges.iterator();
LocalRange nextStartingRange = rangeIterator.next();
- // Open initial (argument) ranges.
- while (nextStartingRange != null && nextStartingRange.start == 0) {
- currentLocals.put(nextStartingRange.register, nextStartingRange.local);
- openRanges.add(nextStartingRange);
- nextStartingRange = rangeIterator.hasNext() ? rangeIterator.next() : null;
- }
-
for (BasicBlock block : blocks) {
- ListIterator<Instruction> instructionIterator = block.listIterator();
+ // Skip past all spill moves to obtain the instruction number of the actual first instruction.
+ InstructionListIterator instructionIterator = block.listIterator();
+ instructionIterator.nextUntil(
+ i -> !i.isArgument() && !i.isMoveException() && !isSpillInstruction(i));
+ Instruction firstInstruction = instructionIterator.previous();
+ int firstIndex = firstInstruction.getNumber();
+
// Close ranges up-to but excluding the first instruction. Ends are exclusive but the values
- // might be live upon entering the first instruction (if they are used by it).
- int entryIndex = block.entry().getNumber();
- if (block.entry().isMoveException()) {
- // Close locals at a move exception since they close as part of the exceptional transfer.
- entryIndex++;
- }
- {
- ListIterator<LocalRange> it = openRanges.listIterator(0);
- while (it.hasNext()) {
- LocalRange openRange = it.next();
- if (openRange.end < entryIndex ||
- // Don't close the local if it is used by the entry instruction. Exclude move
- // exception instruction that are managed in other way that should be clean.
- (openRange.end == entryIndex
- && !block.entry().isMoveException()
- && !usesValues(openRange.value, block.entry()))) {
- it.remove();
- assert currentLocals.get(openRange.register) == openRange.local;
- currentLocals.remove(openRange.register);
- }
- }
- }
+ // might be live upon entering the first instruction (if they are used by it). Since we
+ // skipped move-exception this closes locals at the move exception which should close as part
+ // of the exceptional transfer.
+ openRanges.removeIf(openRange -> !isLocalLiveAtInstruction(openRange, firstInstruction));
+
// Open ranges up-to but excluding the first instruction. Starts are inclusive but entry is
// prior to the first instruction.
- while (nextStartingRange != null && nextStartingRange.start < entryIndex) {
+ while (nextStartingRange != null && nextStartingRange.start < firstIndex) {
// If the range is live at this index open it. Again the end is inclusive here because the
// instruction is live at block entry if it is live at entry to the first instruction.
- if (entryIndex <= nextStartingRange.end) {
+ if (isLocalLiveAtInstruction(nextStartingRange, firstInstruction)) {
openRanges.add(nextStartingRange);
- assert !currentLocals.containsKey(nextStartingRange.register);
- currentLocals.put(nextStartingRange.register, nextStartingRange.local);
}
nextStartingRange = rangeIterator.hasNext() ? rangeIterator.next() : null;
}
- if (block.entry().isMoveException()) {
- fixupLocalsLiveAtMoveException(
- block, instructionIterator, openRanges, currentLocals, allocator);
- } else {
- block.setLocalsAtEntry(new Int2ReferenceOpenHashMap<>(currentLocals));
+
+ // Initialize current locals (registers after any spill instructions).
+ Int2ReferenceMap<DebugLocalInfo> currentLocals =
+ new Int2ReferenceOpenHashMap<>(openRanges.size());
+ for (LocalRange openRange : openRanges) {
+ currentLocals.put(openRange.register, openRange.local);
}
+
+ // Set locals at entry. This will adjust initial local registers in case of spilling.
+ setLocalsAtEntry(block, instructionIterator, openRanges, currentLocals, allocator);
+
+ // Iterate the block instructions and emit locals changed events.
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.next();
if (instruction.isDebugLocalRead()) {
@@ -388,36 +372,45 @@
}
}
+ private static boolean isLocalLiveAtInstruction(LocalRange range, Instruction instruction) {
+ int number = instruction.getNumber();
+ assert range.start < number;
+ return number < range.end || (number == range.end && usesValues(range.value, instruction));
+ }
+
private static boolean usesValues(Value usedValue, Instruction instruction) {
return instruction.inValues().contains(usedValue)
|| instruction.getDebugValues().contains(usedValue);
}
- private static void fixupLocalsLiveAtMoveException(
+ private static void setLocalsAtEntry(
BasicBlock block,
- ListIterator<Instruction> instructionIterator,
+ InstructionListIterator instructionIterator,
List<LocalRange> openRanges,
Int2ReferenceMap<DebugLocalInfo> finalLocals,
RegisterAllocator allocator) {
- Int2ReferenceMap<DebugLocalInfo> initialLocals = new Int2ReferenceOpenHashMap<>();
- int exceptionalIndex = block.getPredecessors().get(0).exceptionalExit().getNumber();
+ // If this is the graph-entry or there are no moves entry locals are current locals.
+ if (block.getPredecessors().isEmpty() || block.entry() == instructionIterator.peekNext()) {
+ assert !block.entry().isMoveException();
+ assert !isSpillInstruction(block.entry());
+ block.setLocalsAtEntry(new Int2ReferenceOpenHashMap<>(finalLocals));
+ return;
+ }
+ // Otherwise entry locals are the registers of the predecessor, ie, prior to spill instructions.
+ Int2ReferenceMap<DebugLocalInfo> initialLocals =
+ new Int2ReferenceOpenHashMap<>(openRanges.size());
+ int predecessorExitIndex =
+ block.entry().isMoveException()
+ ? block.getPredecessors().get(0).exceptionalExit().getNumber()
+ : block.getPredecessors().get(0).exit().getNumber();
for (LocalRange open : openRanges) {
- int exceptionalRegister =
- allocator.getArgumentOrAllocateRegisterForValue(open.value, exceptionalIndex);
- initialLocals.put(exceptionalRegister, open.local);
+ int predecessorRegister =
+ allocator.getArgumentOrAllocateRegisterForValue(open.value, predecessorExitIndex);
+ initialLocals.put(predecessorRegister, open.local);
}
- block.setLocalsAtEntry(new Int2ReferenceOpenHashMap<>(initialLocals));
- Instruction entry = instructionIterator.next();
- assert block.entry() == entry;
- assert block.entry().isMoveException();
- // Skip past spill instructions.
- while (instructionIterator.hasNext()) {
- if (!isSpillInstruction(instructionIterator.next())) {
- instructionIterator.previous();
- break;
- }
- }
- // Compute the final change in locals and insert it after the last move.
+ block.setLocalsAtEntry(initialLocals);
+
+ // Compute the final change in locals and insert it after the last spill instruction.
Int2ReferenceMap<DebugLocalInfo> ending = new Int2ReferenceOpenHashMap<>();
Int2ReferenceMap<DebugLocalInfo> starting = new Int2ReferenceOpenHashMap<>();
for (Entry<DebugLocalInfo> initialLocal : initialLocals.int2ReferenceEntrySet()) {
@@ -1941,7 +1934,7 @@
}
intervals.addRange(new LiveRange(instructionNumber, end));
assert unconstrainedForCf(intervals.getRegisterLimit(), options);
- if (!options.outputClassFiles && !value.isPhi()) {
+ if (options.isGeneratingDex() && !value.isPhi()) {
int constraint = value.definition.maxOutValueRegister();
intervals.addUse(new LiveIntervalsUse(instructionNumber, constraint));
}
@@ -2003,7 +1996,7 @@
instruction.getNumber() + INSTRUCTION_NUMBER_DELTA,
liveIntervals,
options);
- assert !options.outputClassFiles || instruction.isArgument()
+ assert !options.isGeneratingClassFiles() || instruction.isArgument()
: "Arguments should be the only potentially unused local in CF";
}
live.remove(definition);
@@ -2015,7 +2008,7 @@
live.add(use);
addLiveRange(use, block, instruction.getNumber(), liveIntervals, options);
}
- if (!options.outputClassFiles) {
+ if (options.isGeneratingDex()) {
int inConstraint = instruction.maxInValueRegister();
LiveIntervals useIntervals = use.getLiveIntervals();
useIntervals.addUse(new LiveIntervalsUse(instruction.getNumber(), inConstraint));
@@ -2037,7 +2030,7 @@
}
private static boolean unconstrainedForCf(int constraint, InternalOptions options) {
- return !options.outputClassFiles || constraint == Constants.U16BIT_MAX;
+ return !options.isGeneratingClassFiles() || constraint == Constants.U16BIT_MAX;
}
private void clearUserInfo() {
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 88e012e..019283a 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -5,7 +5,7 @@
import static org.objectweb.asm.Opcodes.ASM6;
-import com.android.tools.r8.OutputSink;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexApplication;
@@ -13,11 +13,11 @@
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
-import java.util.Collections;
import java.util.concurrent.ExecutorService;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
@@ -37,20 +37,20 @@
this.options = options;
}
- public void write(OutputSink outputSink, ExecutorService executor) throws IOException {
+ public void write(ClassFileConsumer consumer, ExecutorService executor) throws IOException {
application.timing.begin("CfApplicationWriter.write");
try {
- writeApplication(outputSink, executor);
+ writeApplication(consumer, executor);
} finally {
application.timing.end();
}
}
- private void writeApplication(OutputSink outputSink, ExecutorService executor)
+ private void writeApplication(ClassFileConsumer consumer, ExecutorService executor)
throws IOException {
for (DexProgramClass clazz : application.classes()) {
if (clazz.getSynthesizedFrom().isEmpty()) {
- writeClass(clazz, outputSink);
+ writeClass(clazz, consumer);
} else {
throw new Unimplemented("No support for synthetics in the Java bytecode backend.");
}
@@ -62,7 +62,7 @@
return version > 49 ? 49 : version;
}
- private void writeClass(DexProgramClass clazz, OutputSink outputSink) throws IOException {
+ private void writeClass(DexProgramClass clazz, ClassFileConsumer consumer) throws IOException {
ClassWriter writer = new ClassWriter(0);
writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, null);
int version = downgrade(clazz.getClassFileVersion());
@@ -102,7 +102,8 @@
byte[] result = writer.toByteArray();
assert verifyCf(result);
- outputSink.writeClassFile(result, Collections.singleton(desc), desc);
+ ExceptionUtils.withConsumeResourceHandler(
+ options.reporter, handler -> consumer.accept(result, desc, handler));
}
private Object getStaticValue(DexEncodedField field) {
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 9daffb0..23a92a0 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.utils.IdentifierUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.HashMap;
@@ -78,6 +79,10 @@
: '\n';
}
+ private boolean hasNext() {
+ return lineOffset < line.length();
+ }
+
private char next() {
try {
return line.charAt(lineOffset++);
@@ -129,6 +134,9 @@
}
private char expect(char c) {
+ if (!hasNext()) {
+ throw new ParseException("Expected '" + c + "'", true);
+ }
if (next() != c) {
throw new ParseException("Expected '" + c + "'");
}
@@ -151,14 +159,19 @@
String before = parseType(false);
skipWhitespace();
// Workaround for proguard map files that contain entries for package-info.java files.
- if (!acceptArrow()) {
- // If this was a package-info line, we parsed the "package" string.
- if (!before.endsWith("package") || !acceptString("-info")) {
- throw new ParseException("Expected arrow after class name " + before);
- }
+ assert IdentifierUtils.isDexIdentifierPart('-');
+ if (before.endsWith("package-info")) {
skipLine();
continue;
}
+ if (before.endsWith("-") && acceptString(">")) {
+ // With - as a legal identifier part the grammar is ambiguous, and we treat a->b as a -> b,
+ // and not as a- > b (which would be a parse error).
+ before = before.substring(0, before.length() - 1);
+ } else {
+ skipWhitespace();
+ acceptArrow();
+ }
skipWhitespace();
String after = parseType(false);
expect(':');
@@ -286,17 +299,17 @@
next();
isInit = true;
}
- if (!Character.isJavaIdentifierStart(peek())) {
+ if (!IdentifierUtils.isDexIdentifierStart(peek())) {
throw new ParseException("Identifier expected");
}
next();
- while (Character.isJavaIdentifierPart(peek())) {
+ while (IdentifierUtils.isDexIdentifierPart(peek())) {
next();
}
if (isInit) {
expect('>');
}
- if (Character.isJavaIdentifierPart(peek())) {
+ if (IdentifierUtils.isDexIdentifierPart(peek())) {
throw new ParseException("End of identifier expected");
}
}
@@ -422,17 +435,27 @@
private final int lineNo;
private final int lineOffset;
+ private final boolean eol;
private final String msg;
ParseException(String msg) {
+ this(msg, false);
+ }
+
+ ParseException(String msg, boolean eol) {
lineNo = ProguardMapReader.this.lineNo;
lineOffset = ProguardMapReader.this.lineOffset;
+ this.eol = eol;
this.msg = msg;
}
@Override
public String toString() {
- return "Parse error [" + lineNo + ":" + lineOffset + "] " + msg;
+ if (eol) {
+ return "Parse error [" + lineNo + ":eol] " + msg;
+ } else {
+ return "Parse error [" + lineNo + ":" + lineOffset + "] " + msg;
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
index 23580a5..ee771ff 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidAppOutputSink.java
@@ -103,20 +103,6 @@
}
@Override
- public synchronized void writeClassFile(
- byte[] contents, Set<String> classDescriptors, String primaryClassName) throws IOException {
- assert dexFilesWithPrimary.isEmpty() && dexFilesWithId.isEmpty();
- classFiles.add(new DescriptorsWithContents(classDescriptors, contents));
- super.writeClassFile(contents, classDescriptors, primaryClassName);
- }
-
- @Override
- public void writeProguardSeedsFile(byte[] contents) throws IOException {
- builder.setProguardSeedsData(contents);
- super.writeProguardSeedsFile(contents);
- }
-
- @Override
public void close() throws IOException {
assert !closed;
if (!dexFilesWithPrimary.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java b/src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java
index 0bca98a..74b1b21 100644
--- a/src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/DirectoryOutputSink.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.DEX_EXTENSION;
import java.io.IOException;
@@ -49,12 +48,6 @@
writeFileFromDescriptor(contents, primaryClassName, DEX_EXTENSION);
}
- @Override
- public void writeClassFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
- throws IOException {
- writeFileFromDescriptor(contents, primaryClassName, CLASS_EXTENSION);
- }
-
private void writeFileFromDescriptor(byte[] contents, String descriptor, String extension)
throws IOException {
Path target = outputDirectory.resolve(getOutputFileName(descriptor, extension));
diff --git a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
index 2b7d907..80d08ff 100644
--- a/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ExceptionUtils.java
@@ -3,14 +3,21 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.StringConsumer;
+import java.util.function.Consumer;
public abstract class ExceptionUtils {
public static void withConsumeResourceHandler(
- Reporter reporter, String data, StringConsumer consumer) {
+ Reporter reporter, StringConsumer consumer, String data) {
+ withConsumeResourceHandler(reporter, handler -> consumer.accept(data, handler));
+ }
+
+ public static void withConsumeResourceHandler(
+ Reporter reporter, Consumer<DiagnosticsHandler> consumer) {
// Unchecked exceptions simply propagate out, aborting the compilation forcefully.
- consumer.accept(data, reporter);
+ consumer.accept(reporter);
// Fail fast for now. We might consider delaying failure since consumer failure does not affect
// the compilation. We might need to be careful to correctly identify errors so as to exit
// compilation with an error code.
diff --git a/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java b/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
index 6e5b33f..b2699fc 100644
--- a/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/FileSystemOutputSink.java
@@ -25,7 +25,6 @@
}
String getOutputFileName(int index) {
- assert !options.outputClassFiles;
return index == 0 ? "classes.dex" : ("classes" + (index + 1) + FileUtils.DEX_EXTENSION);
}
@@ -34,11 +33,6 @@
return DescriptorUtils.getClassBinaryNameFromDescriptor(classDescriptor) + extension;
}
- @Override
- public void writeProguardSeedsFile(byte[] contents) throws IOException {
- FileUtils.writeToFile(options.proguardConfiguration.getSeedFile(), System.out, contents);
- }
-
protected OutputMode getOutputMode() {
return options.outputMode;
}
diff --git a/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java b/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
index a6fb545..71e48cf 100644
--- a/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/ForwardingOutputSink.java
@@ -33,17 +33,6 @@
}
@Override
- public void writeClassFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
- throws IOException {
- forwardTo.writeClassFile(contents, classDescriptors, primaryClassName);
- }
-
- @Override
- public void writeProguardSeedsFile(byte[] contents) throws IOException {
- forwardTo.writeProguardSeedsFile(contents);
- }
-
- @Override
public void close() throws IOException {
forwardTo.close();
}
diff --git a/src/main/java/com/android/tools/r8/utils/IdentifierUtils.java b/src/main/java/com/android/tools/r8/utils/IdentifierUtils.java
new file mode 100644
index 0000000..3097a07
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/IdentifierUtils.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+public class IdentifierUtils {
+ public static boolean isDexIdentifierStart(char ch) {
+ // Dex does not have special restrictions on the first char of an identifier.
+ return isDexIdentifierPart(ch);
+ }
+
+ public static boolean isDexIdentifierPart(char ch) {
+ return isSimpleNameChar(ch);
+ }
+
+ private static boolean isSimpleNameChar(char ch) {
+ if (ch >= 'A' && ch <= 'Z') {
+ return true;
+ }
+ if (ch >= 'a' && ch <= 'z') {
+ return true;
+ }
+ if (ch >= '0' && ch <= '9') {
+ return true;
+ }
+ if (ch == '$' || ch == '-' || ch == '_') {
+ return true;
+ }
+ if (ch >= 0x00a1 && ch <= 0x1fff) {
+ return true;
+ }
+ if (ch >= 0x2010 && ch <= 0x2027) {
+ return true;
+ }
+ if (ch >= 0x2030 && ch <= 0xd7ff) {
+ return true;
+ }
+ if (ch >= 0xe000 && ch <= 0xffef) {
+ return true;
+ }
+ if (ch >= 0x10000 && ch <= 0x10ffff) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java b/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
index 21f98f6..76f03bd 100644
--- a/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/IgnoreContentsOutputSink.java
@@ -20,17 +20,6 @@
}
@Override
- public void writeClassFile(
- byte[] contents, Set<String> classDescriptors, String primaryClassName) {
- // Intentionally left empty.
- }
-
- @Override
- public void writeProguardSeedsFile(byte[] contents) {
- // Intentionally left empty.
- }
-
- @Override
public void close() throws IOException {
// Intentionally left empty.
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index dd9f970..9ae315f 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.ProgramConsumer;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.dex.Marker;
import com.android.tools.r8.errors.InvalidDebugInfoException;
@@ -35,6 +37,9 @@
public final ProguardConfiguration proguardConfiguration;
public final Reporter reporter;
+ // TODO(zerny): Make this private-final once we have full program-consumer support.
+ public ProgramConsumer programConsumer = null;
+
// Constructor for testing and/or other utilities.
public InternalOptions() {
reporter = new Reporter(new DefaultDiagnosticsHandler());
@@ -62,8 +67,6 @@
public boolean printTimes = false;
- public boolean outputClassFiles = false;
-
// Optimization-related flags. These should conform to -dontoptimize.
public boolean skipDebugLineNumberOpt = false;
public boolean skipClassMerging = true;
@@ -99,6 +102,24 @@
return marker;
}
+ public boolean isGeneratingDex() {
+ return programConsumer == null;
+ }
+
+ public boolean isGeneratingClassFiles() {
+ return programConsumer instanceof ClassFileConsumer;
+ }
+
+ public ClassFileConsumer getClassFileConsumer() {
+ return (ClassFileConsumer) programConsumer;
+ }
+
+ public void closeProgramConsumer() {
+ if (programConsumer != null) {
+ programConsumer.finished(reporter);
+ }
+ }
+
public List<String> methodsFilter = ImmutableList.of();
public int minApiLevel = AndroidApiLevel.getDefault().getLevel();
// Skipping min_api check and compiling an intermediate result intended for later merging.
@@ -178,6 +199,10 @@
// If non null it must be and passed to the consumer.
public StringConsumer proguardMapConsumer = null;
+ // If null, no proguad seeds info needs to be computed.
+ // If non null it must be and passed to the consumer.
+ public StringConsumer proguardSeedsConsumer = null;
+
// If null, no usage information needs to be computed.
// If non-null, it must be and is passed to the consumer.
public StringConsumer usageInformationConsumer = null;
diff --git a/src/main/java/com/android/tools/r8/utils/ZipFileOutputSink.java b/src/main/java/com/android/tools/r8/utils/ZipFileOutputSink.java
index ac6b341..1e1037e 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipFileOutputSink.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipFileOutputSink.java
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
import static com.android.tools.r8.utils.FileUtils.DEX_EXTENSION;
import java.io.IOException;
@@ -11,8 +10,6 @@
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Set;
-import java.util.zip.CRC32;
-import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class ZipFileOutputSink extends FileSystemOutputSink {
@@ -39,26 +36,11 @@
}
@Override
- public void writeClassFile(byte[] contents, Set<String> classDescriptors, String primaryClassName)
- throws IOException {
- writeToZipFile(getOutputFileName(primaryClassName, CLASS_EXTENSION), contents);
- }
-
- @Override
public void close() throws IOException {
outputStream.close();
}
private synchronized void writeToZipFile(String outputPath, byte[] content) throws IOException {
- CRC32 crc = new CRC32();
- crc.update(content);
- ZipEntry zipEntry = new ZipEntry(outputPath);
- zipEntry.setMethod(ZipEntry.STORED);
- zipEntry.setSize(content.length);
- zipEntry.setCompressedSize(content.length);
- zipEntry.setCrc(crc.getValue());
- outputStream.putNextEntry(zipEntry);
- outputStream.write(content);
- outputStream.closeEntry();
+ ZipUtils.writeToZipStream(outputStream, outputPath, content);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/ZipUtils.java b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
index 76d84c2..8f46f3e 100644
--- a/src/main/java/com/android/tools/r8/utils/ZipUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ZipUtils.java
@@ -15,8 +15,10 @@
import java.util.Enumeration;
import java.util.List;
import java.util.function.Predicate;
+import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
public class ZipUtils {
@@ -62,4 +64,18 @@
});
return outFiles;
}
+
+ public static void writeToZipStream(ZipOutputStream stream, String entry, byte[] content)
+ throws IOException {
+ CRC32 crc = new CRC32();
+ crc.update(content);
+ ZipEntry zipEntry = new ZipEntry(entry);
+ zipEntry.setMethod(ZipEntry.STORED);
+ zipEntry.setSize(content.length);
+ zipEntry.setCompressedSize(content.length);
+ zipEntry.setCrc(crc.getValue());
+ stream.putNextEntry(zipEntry);
+ stream.write(content);
+ stream.closeEntry();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index d3b5c14..2eb37a6 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -10,7 +10,6 @@
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
-import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -47,7 +46,7 @@
protected ProcessResult runOnJava(String main, byte[]... classes) throws IOException {
Path file = writeToZip(classes);
- return ToolHelper.runJavaNoVerify(ImmutableList.of(file.toString()), main);
+ return ToolHelper.runJavaNoVerify(file, main);
}
private Path writeToZip(byte[]... classes) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 4b042fe..0963631 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.errors.Unreachable;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
@@ -257,10 +256,14 @@
case R8: {
ToolHelper.runR8(R8Command.builder()
.addProgramFiles(getInputFile())
- .setOutputPath(getOutputFile())
+ .setOutputPath(output == Output.CF ? null : getOutputFile())
.setMode(mode)
.build(),
- options -> options.outputClassFiles = (output == Output.CF));
+ options -> {
+ if (output == Output.CF) {
+ options.programConsumer = new ClassFileConsumer.ArchiveConsumer(getOutputFile());
+ }
+ });
break;
}
default:
@@ -277,8 +280,7 @@
String original = getOriginalDexFile().toString();
Path generated = getOutputFile();
- ToolHelper.ProcessResult javaResult =
- ToolHelper.runJava(ImmutableList.of(getOriginalJarFile("").toString()), mainClass);
+ ToolHelper.ProcessResult javaResult = ToolHelper.runJava(getOriginalJarFile(""), mainClass);
if (javaResult.exitCode != 0) {
System.out.println(javaResult.stdout);
System.err.println(javaResult.stderr);
@@ -295,8 +297,7 @@
}
if (output == Output.CF) {
- ToolHelper.ProcessResult result =
- ToolHelper.runJava(ImmutableList.of(generated.toString()), mainClass);
+ ToolHelper.ProcessResult result = ToolHelper.runJava(generated, mainClass);
if (result.exitCode != 0) {
System.err.println(result.stderr);
fail("JVM failed on compiled output for: " + mainClass);
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
index fd29dc1..bb8c686 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
@@ -11,8 +11,6 @@
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OffOrAuto;
@@ -87,8 +85,7 @@
}
String output = ToolHelper.runArtNoVerificationErrors(out.toString(), qualifiedMainClass);
if (!expectedToFail) {
- ProcessResult javaResult =
- ToolHelper.runJava(ImmutableList.of(inputFile.toString()), qualifiedMainClass);
+ ProcessResult javaResult = ToolHelper.runJava(inputFile, qualifiedMainClass);
assertEquals("JVM run failed", javaResult.exitCode, 0);
assertTrue(
"JVM output does not match art output.\n\tjvm: "
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 8caff67..3bd3086 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -397,9 +397,7 @@
null);
if (!expectedToFail && !skipRunningOnJvm(testName)) {
ToolHelper.ProcessResult javaResult =
- ToolHelper.runJava(
- Arrays.stream(jars).map(path -> path.toString()).collect(Collectors.toList()),
- qualifiedMainClass);
+ ToolHelper.runJava(ImmutableList.copyOf(jars), qualifiedMainClass);
assertEquals("JVM run failed", javaResult.exitCode, 0);
assertTrue(
"JVM output does not match art output.\n\tjvm: "
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
index 4ff65ba..c8f50fa 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidPTest.java
@@ -244,9 +244,7 @@
null);
if (!expectedToFail) {
ToolHelper.ProcessResult javaResult =
- ToolHelper.runJava(
- Arrays.stream(jars).map(path -> path.toString()).collect(Collectors.toList()),
- qualifiedMainClass);
+ ToolHelper.runJava(ImmutableList.copyOf(jars), qualifiedMainClass);
assertEquals("JVM run failed", javaResult.exitCode, 0);
assertTrue(
"JVM output does not match art output.\n\tjvm: "
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index 37a1697..afa0f67 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -246,9 +246,7 @@
null);
if (!expectedToFail) {
ToolHelper.ProcessResult javaResult =
- ToolHelper.runJava(
- Arrays.stream(jars).map(path -> path.toString()).collect(Collectors.toList()),
- qualifiedMainClass);
+ ToolHelper.runJava(ImmutableList.copyOf(jars), qualifiedMainClass);
assertEquals("JVM run failed", javaResult.exitCode, 0);
assertTrue(
"JVM output does not match art output.\n\tjvm: "
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 208a8f9..46a769d 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -783,25 +783,35 @@
public static ProcessResult runJava(Class clazz) throws Exception {
String main = clazz.getCanonicalName();
Path path = getClassPathForTests();
- return runJava(ImmutableList.of(path.toString()), main);
+ return runJava(path, main);
}
public static ProcessResult runJavaNoVerify(Class clazz) throws Exception {
String main = clazz.getCanonicalName();
Path path = getClassPathForTests();
- return runJavaNoVerify(ImmutableList.of(path.toString()), main);
+ return runJavaNoVerify(path, main);
}
- public static ProcessResult runJava(List<String> classpath, String mainClass) throws IOException {
- ProcessBuilder builder = new ProcessBuilder(
- getJavaExecutable(), "-cp", String.join(PATH_SEPARATOR, classpath), mainClass);
+ public static ProcessResult runJava(Path classpath, String mainClass) throws IOException {
+ return runJava(ImmutableList.of(classpath), mainClass);
+ }
+
+ public static ProcessResult runJava(List<Path> classpath, String mainClass) throws IOException {
+ String cp = classpath.stream().map(Path::toString).collect(Collectors.joining(PATH_SEPARATOR));
+ ProcessBuilder builder = new ProcessBuilder(getJavaExecutable(), "-cp", cp, mainClass);
return runProcess(builder);
}
- public static ProcessResult runJavaNoVerify(List<String> classpath, String mainClass)
+ public static ProcessResult runJavaNoVerify(Path classpath, String mainClass)
throws IOException {
+ return runJavaNoVerify(ImmutableList.of(classpath), mainClass);
+ }
+
+ public static ProcessResult runJavaNoVerify(List<Path> classpath, String mainClass)
+ throws IOException {
+ String cp = classpath.stream().map(Path::toString).collect(Collectors.joining(PATH_SEPARATOR));
ProcessBuilder builder = new ProcessBuilder(
- getJavaExecutable(), "-cp", String.join(PATH_SEPARATOR, classpath), "-noverify", mainClass);
+ getJavaExecutable(), "-cp", cp, "-noverify", mainClass);
return runProcess(builder);
}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
index 20ce22d..46b930d 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugStreamComparator.java
@@ -84,25 +84,24 @@
}
} catch (AssertionError e) {
for (int i = 0; i < names.size(); i++) {
- print(names.get(i), states.get(i));
+ System.err.println(names.get(i) + ": " + prettyPrintState(states.get(i)));
}
throw e;
}
}
}
- private void print(String name, DebuggeeState state) {
- System.err.println(
- name
- + ": "
- + state.getSourceFile()
- + ":"
- + state.getLineNumber()
- + " "
- + state.getClassName()
- + "."
- + state.getMethodName()
- + state.getMethodSignature());
+ public static String prettyPrintState(DebuggeeState state) {
+ StringBuilder builder = new StringBuilder()
+ .append(state.getSourceFile())
+ .append(':')
+ .append(state.getLineNumber())
+ .append(' ')
+ .append(state.getClassName())
+ .append('.')
+ .append(state.getMethodName())
+ .append(state.getMethodSignature());
+ return builder.toString();
}
private void verifyStatesEqual(List<DebuggeeState> states) {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index e81f9b3..0878a30 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -7,6 +7,7 @@
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.ClassNamingForNameMapper;
import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRange;
@@ -147,9 +148,32 @@
new JUnit3Wrapper(config, debuggeeClass, commands, classNameMapper).runBare();
}
- /** Lazily debug-step an execution producing a stream of successive debuggee states. */
+ /**
+ * Lazily debug-step an execution starting from main(String[]) in {@code debuggeeClass}.
+ *
+ * @return A stream of successive debuggee states.
+ * */
public Stream<JUnit3Wrapper.DebuggeeState> streamDebugTest(
DebugTestConfig config, String debuggeeClass, StepFilter filter) throws Throwable {
+ return streamDebugTest(
+ config,
+ debuggeeClass,
+ breakpoint(debuggeeClass, "main", "([Ljava/lang/String;)V"),
+ filter);
+ }
+
+ /**
+ * Lazily debug-step an execution starting from {@code breakpoint}.
+ *
+ * @return A stream of successive debuggee states.
+ */
+ public Stream<JUnit3Wrapper.DebuggeeState> streamDebugTest(
+ DebugTestConfig config,
+ String debuggeeClass,
+ JUnit3Wrapper.Command breakpoint,
+ StepFilter filter)
+ throws Throwable {
+ assert breakpoint instanceof JUnit3Wrapper.Command.BreakpointCommand;
// Continuous single-step command.
// The execution of the command pushes itself onto the command queue ensuring the next step.
@@ -171,7 +195,7 @@
new JUnit3Wrapper(
config,
debuggeeClass,
- ImmutableList.of(breakpoint(debuggeeClass, "main", "([Ljava/lang/String;)V"), run()),
+ ImmutableList.of(breakpoint, run()),
null);
// Setup the initial state for the JDWP test base and run the program to the initial breakpoint.
@@ -822,6 +846,22 @@
Exit
}
+ // We expect to have either a single event or two events with one being an installed breakpoint.
+ private ParsedEvent getPrimaryEvent(ParsedEvent[] events) {
+ assertTrue(events.length == 1 || events.length == 2);
+ if (events.length == 1) {
+ return events[0];
+ }
+ assertEquals(2, events.length);
+ for (ParsedEvent event : events) {
+ if (event.getEventKind() == EventKind.BREAKPOINT) {
+ return event;
+ }
+ }
+ fail("Expected breakpoint when receiving multiple events.");
+ throw new Unreachable();
+ }
+
private void processEvents() {
EventPacket eventPacket = getMirror().receiveEvent();
ParsedEvent[] parsedEvents = ParsedEvent.parseEventPacket(eventPacket);
@@ -837,9 +877,7 @@
logWriter.println(msg);
}
}
- // We only expect one event at a time.
- assertEquals(1, parsedEvents.length);
- ParsedEvent parsedEvent = parsedEvents[0];
+ ParsedEvent parsedEvent = getPrimaryEvent(parsedEvents);
byte eventKind = parsedEvent.getEventKind();
int requestID = parsedEvent.getRequestID();
diff --git a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
index 02403f6..e5e3c83 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.debug;
+import com.android.tools.r8.ClassFileConsumer;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
@@ -35,11 +36,10 @@
Path output = temp.newFolder().toPath().resolve("r8_debug_cf_output.jar");
ToolHelper.runR8(
R8Command.builder()
- .setOutputPath(output)
.addProgramFiles(input)
.setMode(CompilationMode.DEBUG)
.build(),
- options -> options.outputClassFiles = true);
+ options -> options.programConsumer = new ClassFileConsumer.ArchiveConsumer(output));
return new CfDebugTestConfig(output);
}
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 9c8b8fe..3f59cef 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -741,9 +741,11 @@
final String methodName = "localVisibilityIntoLoop";
List<Command> commands = new ArrayList<>();
- commands.add(breakpoint(className, methodName, 359));
+ commands.add(breakpoint(className, methodName, 358));
commands.add(run());
commands.add(checkMethod(className, methodName));
+ commands.add(checkLine(SOURCE_FILE, 358));
+ commands.add(stepOver());
commands.add(checkLine(SOURCE_FILE, 359));
commands.add(checkNoLocal("Ai"));
commands.add(checkNoLocal("Bi"));
@@ -766,7 +768,9 @@
commands.add(checkLocal("Ai"));
commands.add(checkLocal("Bi"));
commands.add(checkLocal("i", Value.createInt(0)));
- commands.add(run());
+ commands.add(stepOver());
+ commands.add(stepOver());
+ commands.add(stepOver());
commands.add(checkMethod(className, methodName));
commands.add(checkLine(SOURCE_FILE, 359));
commands.add(checkNoLocal("Ai"));
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index 757dd63..ff95ab388 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -5,6 +5,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationException;
@@ -16,6 +17,8 @@
import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
import com.android.tools.r8.Resource;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.naming.MemberNaming.FieldSignature;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
@@ -23,6 +26,9 @@
import com.android.tools.r8.utils.ArtErrorParser.ArtErrorInfo;
import com.android.tools.r8.utils.ArtErrorParser.ArtErrorParserException;
import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.FoundClassSubject;
+import com.android.tools.r8.utils.DexInspector.FoundFieldSubject;
+import com.android.tools.r8.utils.DexInspector.FoundMethodSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.OutputMode;
@@ -34,8 +40,11 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.junit.ComparisonFailure;
import org.junit.Rule;
@@ -173,4 +182,143 @@
}
}
}
+
+ public void assertIdenticalApplicationsUpToCode(
+ AndroidApp app1, AndroidApp app2, boolean allowNewClassesInApp2)
+ throws IOException, ExecutionException {
+ DexInspector inspect1 = new DexInspector(app1);
+ DexInspector inspect2 = new DexInspector(app2);
+
+ class Pair<T> {
+ private T first;
+ private T second;
+
+ private void set(boolean selectFirst, T value) {
+ if (selectFirst) {
+ first = value;
+ } else {
+ second = value;
+ }
+ }
+ }
+
+ // Collect all classes from both inspectors, indexed by finalDescriptor.
+ Map<String, Pair<FoundClassSubject>> allClasses = new HashMap<>();
+
+ BiConsumer<DexInspector, Boolean> collectClasses =
+ (inspector, selectFirst) -> {
+ inspector.forAllClasses(
+ clazz -> {
+ String finalDescriptor = clazz.getFinalDescriptor();
+ allClasses.compute(
+ finalDescriptor,
+ (k, v) -> {
+ if (v == null) {
+ v = new Pair<>();
+ }
+ v.set(selectFirst, clazz);
+ return v;
+ });
+ });
+ };
+
+ collectClasses.accept(inspect1, true);
+ collectClasses.accept(inspect2, false);
+
+ for (Map.Entry<String, Pair<FoundClassSubject>> classEntry : allClasses.entrySet()) {
+ String className = classEntry.getKey();
+ FoundClassSubject class1 = classEntry.getValue().first;
+ FoundClassSubject class2 = classEntry.getValue().second;
+
+ assert class1 != null || class2 != null;
+
+ if (!allowNewClassesInApp2) {
+ assertNotNull(String.format("Class %s is missing from the first app.", className), class1);
+ }
+ assertNotNull(String.format("Class %s is missing from the second app.", className), class2);
+
+ if (class1 == null) {
+ continue;
+ }
+
+ // Collect all methods for this class from both apps.
+ Map<MethodSignature, Pair<FoundMethodSubject>> allMethods = new HashMap<>();
+
+ BiConsumer<FoundClassSubject, Boolean> collectMethods =
+ (classSubject, selectFirst) -> {
+ classSubject.forAllMethods(
+ m -> {
+ MethodSignature fs = m.getFinalSignature();
+ allMethods.compute(
+ fs,
+ (k, v) -> {
+ if (v == null) {
+ v = new Pair<>();
+ }
+ v.set(selectFirst, m);
+ return v;
+ });
+ });
+ };
+
+ collectMethods.accept(class1, true);
+ collectMethods.accept(class2, false);
+
+ for (Map.Entry<MethodSignature, Pair<FoundMethodSubject>> methodEntry :
+ allMethods.entrySet()) {
+ MethodSignature signature = methodEntry.getKey();
+ FoundMethodSubject method1 = methodEntry.getValue().first;
+ FoundMethodSubject method2 = methodEntry.getValue().second;
+ assert method1 != null || method2 != null;
+
+ assertNotNull(
+ String.format(
+ "Method %s of class %s is missing from the first app.", signature, className),
+ method1);
+ assertNotNull(
+ String.format(
+ "Method %s of class %s is missing from the second app.", signature, className),
+ method2);
+ }
+
+ // Collect all fields for this class from both apps.
+ Map<FieldSignature, Pair<FoundFieldSubject>> allFields = new HashMap<>();
+
+ BiConsumer<FoundClassSubject, Boolean> collectFields =
+ (classSubject, selectFirst) -> {
+ classSubject.forAllFields(
+ f -> {
+ FieldSignature fs = f.getFinalSignature();
+ allFields.compute(
+ fs,
+ (k, v) -> {
+ if (v == null) {
+ v = new Pair<>();
+ }
+ v.set(selectFirst, f);
+ return v;
+ });
+ });
+ };
+
+ collectFields.accept(class1, true);
+ collectFields.accept(class2, false);
+
+ for (Map.Entry<FieldSignature, Pair<FoundFieldSubject>> fieldEntry : allFields.entrySet()) {
+ FieldSignature signature = fieldEntry.getKey();
+ FoundFieldSubject field1 = fieldEntry.getValue().first;
+ FoundFieldSubject field2 = fieldEntry.getValue().second;
+ assert field1 != null || field2 != null;
+
+ assertNotNull(
+ String.format(
+ "Field %s of class %s is missing from the first app.", signature, className),
+ field1);
+ assertNotNull(
+ String.format(
+ "Field %s of class %s is missing from the second app.", signature, className),
+ field2);
+ }
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreFixedPointTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreFixedPointTest.java
index cb3d810..6fd6e1b 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreFixedPointTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreFixedPointTest.java
@@ -3,8 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.internal;
-import static org.junit.Assert.assertEquals;
-
import com.android.tools.r8.CompilationException;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.ToolHelper;
@@ -45,9 +43,6 @@
options.proguardMapConsumer = StringConsumer.emptyConsumer();
});
- // TODO: Require that the results of the two compilations are the same.
- assertEquals(
- app1.getDexProgramResources().size(),
- app2.getDexProgramResources().size());
+ assertIdenticalApplicationsUpToCode(app1, app2, false);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index 99cba4d..4ac8f0e 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -128,6 +128,7 @@
null,
options,
null);
+ options.closeProgramConsumer();
compatSink.close();
return compatSink.build();
} catch (ExecutionException | IOException e) {
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index cb50865..4a2e861 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -31,14 +31,14 @@
protected ProcessResult runOnJavaRaw(JasminBuilder builder, String main) throws Exception {
Path out = temp.newFolder().toPath();
builder.writeClassFiles(out);
- return ToolHelper.runJava(ImmutableList.of(out.toString()), main);
+ return ToolHelper.runJava(out, main);
}
protected ProcessResult runOnJavaNoVerifyRaw(JasminBuilder builder, String main)
throws Exception {
Path out = temp.newFolder().toPath();
builder.writeClassFiles(out);
- return ToolHelper.runJavaNoVerify(ImmutableList.of(out.toString()), main);
+ return ToolHelper.runJavaNoVerify(out, main);
}
protected ProcessResult runOnJavaNoVerifyRaw(JasminBuilder program, JasminBuilder library,
@@ -48,8 +48,7 @@
program.writeClassFiles(out);
Path libraryOut = temp.newFolder().toPath();
library.writeClassFiles(libraryOut);
- return ToolHelper.runJavaNoVerify(ImmutableList.of(out.toString(), libraryOut.toString()),
- main);
+ return ToolHelper.runJavaNoVerify(ImmutableList.of(out, libraryOut), main);
}
private String assertNormalExitAndGetStdout(ProcessResult result) {
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index feecda0..9d9702f 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -631,6 +631,7 @@
executor.shutdown();
}
compatSink.close();
+ options.closeProgramConsumer();
return compatSink.build();
}
diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
index 99577de..6c769aa 100644
--- a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderTest.java
@@ -31,6 +31,22 @@
}
@Test
+ public void parseIdentifierArrowAmbiguity1() throws IOException {
+ ClassNameMapper mapper = ClassNameMapper.mapperFromString("a->b:");
+ ClassNameMapper.Builder builder = ClassNameMapper.builder();
+ builder.classNamingBuilder("b", "a");
+ Assert.assertEquals(builder.build(), mapper);
+ }
+
+ @Test
+ public void parseIdentifierArrowAmbiguity2() throws IOException {
+ ClassNameMapper mapper = ClassNameMapper.mapperFromString("-->b:");
+ ClassNameMapper.Builder builder = ClassNameMapper.builder();
+ builder.classNamingBuilder("b", "-");
+ Assert.assertEquals(builder.build(), mapper);
+ }
+
+ @Test
public void parseMapWithPackageInfo() throws IOException {
ClassNameMapper mapper = ClassNameMapper.mapperFromString(EXAMPLE_MAP_WITH_PACKAGE_INFO);
Assert.assertTrue(mapper.getObfuscatedToOriginalMapping().isEmpty());
diff --git a/src/test/java/com/android/tools/r8/rewrite/longcompare/RequireNonNullRewriteTest.java b/src/test/java/com/android/tools/r8/rewrite/longcompare/RequireNonNullRewriteTest.java
index 39eebad..a1bece1 100644
--- a/src/test/java/com/android/tools/r8/rewrite/longcompare/RequireNonNullRewriteTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/longcompare/RequireNonNullRewriteTest.java
@@ -4,19 +4,18 @@
package com.android.tools.r8.rewrite.longcompare;
import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.D8;
import com.android.tools.r8.D8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.android.tools.r8.utils.DexInspector.InstructionSubject;
import com.android.tools.r8.utils.DexInspector.InvokeInstructionSubject;
import com.android.tools.r8.utils.DexInspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -50,8 +49,7 @@
builder.setMainClass(mainClass);
try {
String output = ToolHelper.runArt(builder);
- ProcessResult javaResult = ToolHelper
- .runJava(ImmutableList.of(jarFile.toString()), mainClass);
+ ProcessResult javaResult = ToolHelper.runJava(jarFile, mainClass);
Assert.assertEquals(javaResult.stdout, output);
} catch (IOException e) {
Assert.fail();