Merge "Avoid direct use of ValueType fields during Value creations."
diff --git a/build.gradle b/build.gradle
index a542ea4..e6432e4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1243,6 +1243,8 @@
}
dependsOn getJarsFromSupportLibs
+ // R8.jar is required for running bootstrap tests.
+ dependsOn R8
testLogging.exceptionFormat = 'full'
if (project.hasProperty('print_test_stdout')) {
testLogging.showStandardStreams = true
diff --git a/src/main/java/com/android/tools/r8/ExtractMarker.java b/src/main/java/com/android/tools/r8/ExtractMarker.java
index 6fd04b6..fd85c25 100644
--- a/src/main/java/com/android/tools/r8/ExtractMarker.java
+++ b/src/main/java/com/android/tools/r8/ExtractMarker.java
@@ -26,6 +26,7 @@
import java.util.Collection;
import java.util.concurrent.ExecutionException;
+@Keep
public class ExtractMarker {
public static class VdexOrigin extends Origin {
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 43989ff..6fe8ad0 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -28,6 +28,7 @@
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
+@Keep
public class GenerateMainDexList {
private final Timing timing = new Timing("maindex");
private final InternalOptions options;
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 0d4e79d..aeaac60 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
// This field is accessed from release scripts using simple pattern matching.
// Therefore, changing this field could break our release scripts.
- public static final String LABEL = "1.4.0-dev";
+ public static final String LABEL = "1.4.1-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
index b5db1ae..7cbf816 100644
--- a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
+++ b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
@@ -73,6 +73,22 @@
}
throw new AssertionError("Unknown: " + this);
}
+
+ public static MultidexStrategy parse(String value) {
+ switch (value) {
+ case "off":
+ return OFF;
+ case "given_shard":
+ return GIVEN_SHARD;
+ case "minimal":
+ return MINIMAL;
+ case "best_effort":
+ return BEST_EFFORT;
+ default:
+ throw new RuntimeException(
+ "Multidex argument must be either 'off', 'given_shard', 'minimal' or 'best_effort'.");
+ }
+ }
}
private static class Options {
@@ -129,7 +145,7 @@
}
string = OptionsParsing.tryParseSingle(context, "--multidex", null);
if (string != null) {
- options.multidexMode = MultidexStrategy.valueOf(string.toUpperCase());
+ options.multidexMode = MultidexStrategy.parse(string);
continue;
}
string = OptionsParsing.tryParseSingle(context, "--main-dex-list", null);
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 262f670..7e5759a 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
@@ -643,6 +643,26 @@
}
}
+ public void optimizeMethodOnSynthesizedClass(DexProgramClass clazz, DexEncodedMethod method) {
+ if (!method.isProcessed()) {
+ try {
+ enterCachedClass(clazz);
+ // Process the generated method, but don't apply any outlining.
+ optimizeSynthesizedMethod(method);
+ } finally {
+ leaveCachedClass(clazz);
+ }
+ }
+ }
+
+ public void optimizeSynthesizedMethod(DexEncodedMethod method) {
+ if (!method.isProcessed()) {
+ // Process the generated method, but don't apply any outlining.
+ processMethod(method, ignoreOptimizationFeedback, x -> false, CallSiteInformation.empty(),
+ Outliner::noProcessing);
+ }
+ }
+
private void enterCachedClass(DexProgramClass clazz) {
DexProgramClass previous = cachedClasses.put(clazz.type, clazz);
assert previous == null;
@@ -653,12 +673,6 @@
assert existing == clazz;
}
- public void optimizeSynthesizedMethod(DexEncodedMethod method) {
- // Process the generated method, but don't apply any outlining.
- processMethod(method, ignoreOptimizationFeedback, x -> false, CallSiteInformation.empty(),
- Outliner::noProcessing);
- }
-
private String logCode(InternalOptions options, DexEncodedMethod method) {
return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString();
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 03a3818..01555da 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -128,6 +128,8 @@
}
private DexProgramClass synthesizeLambdaClass() {
+ DexMethod mainMethod = rewriter.factory
+ .createMethod(type, descriptor.erasedProto, descriptor.name);
DexProgramClass clazz =
new DexProgramClass(
type,
@@ -146,8 +148,11 @@
synthesizeStaticFields(),
synthesizeInstanceFields(),
synthesizeDirectMethods(),
- synthesizeVirtualMethods(),
+ synthesizeVirtualMethods(mainMethod),
rewriter.factory.getSkipNameValidationForTesting());
+ // Optimize main method.
+ rewriter.converter.optimizeMethodOnSynthesizedClass(
+ clazz, clazz.lookupVirtualMethod(mainMethod));
// The method addSynthesizedFrom() may be called concurrently. To avoid a Concurrent-
// ModificationException we must use synchronization.
synchronized (synthesizedFrom) {
@@ -177,13 +182,11 @@
}
// Synthesize virtual methods.
- private DexEncodedMethod[] synthesizeVirtualMethods() {
+ private DexEncodedMethod[] synthesizeVirtualMethods(DexMethod mainMethod) {
DexEncodedMethod[] methods = new DexEncodedMethod[1 + descriptor.bridges.size()];
int index = 0;
// Synthesize main method.
- DexMethod mainMethod = rewriter.factory
- .createMethod(type, descriptor.erasedProto, descriptor.name);
methods[index++] =
new DexEncodedMethod(
mainMethod,
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 1ac301a..30244bf 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
@@ -1261,9 +1261,7 @@
Value argument = invoke.arguments().get(argumentIndex);
assert invoke.outType().verifyCompatible(argument.outType());
invoke.outValue().replaceUsers(argument);
- if (!options.isGeneratingClassFiles()) {
- invoke.setOutValue(null);
- }
+ invoke.setOutValue(null);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 6fcade8..98de5be 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -252,7 +252,7 @@
if (target != null) {
// Remove writes to dead (i.e. never read) fields.
if (!isFieldRead(target, true)) {
- iterator.remove();
+ iterator.removeOrReplaceByDebugLocalRead();
}
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 37e6de9..051b81f 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -42,6 +42,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -188,8 +189,11 @@
}
}
- private void parseError(DexDefinition item, Origin origin, GenericSignatureFormatError e) {
- StringBuilder message = new StringBuilder("Invalid signature for ");
+ private void parseError(
+ DexDefinition item, Origin origin, String signature, GenericSignatureFormatError e) {
+ StringBuilder message = new StringBuilder("Invalid signature '");
+ message.append(signature);
+ message.append("' for ");
if (item.isDexClass()) {
message.append("class ");
message.append((item.asDexClass()).getType().toSourceString());
@@ -202,43 +206,54 @@
message.append(item.toSourceString());
}
message.append(".\n");
+ message.append("Signature is ignored and will not be present in the output.\n");
+ message.append("Parser error: ");
message.append(e.getMessage());
reporter.warning(new StringDiagnostic(message.toString(), origin));
}
private void renameTypesInGenericSignatures() {
for (DexClass clazz : appInfo.classes()) {
- clazz.annotations = rewriteGenericSignatures(clazz.annotations,
- genericSignatureParser::parseClassSignature,
- e -> parseError(clazz, clazz.getOrigin(), e));
- clazz.forEachField(field ->
- field.annotations = rewriteGenericSignatures(
- field.annotations, genericSignatureParser::parseFieldSignature,
- e -> parseError(field, clazz.getOrigin(), e)));
- clazz.forEachMethod(method ->
- method.annotations = rewriteGenericSignatures(
- method.annotations, genericSignatureParser::parseMethodSignature,
- e -> parseError(method, clazz.getOrigin(), e)));
+ clazz.annotations =
+ rewriteGenericSignatures(
+ clazz.annotations,
+ genericSignatureParser::parseClassSignature,
+ (signature, e) -> parseError(clazz, clazz.getOrigin(), signature, e));
+ clazz.forEachField(
+ field ->
+ field.annotations =
+ rewriteGenericSignatures(
+ field.annotations,
+ genericSignatureParser::parseFieldSignature,
+ (signature, e) -> parseError(field, clazz.getOrigin(), signature, e)));
+ clazz.forEachMethod(
+ method ->
+ method.annotations =
+ rewriteGenericSignatures(
+ method.annotations,
+ genericSignatureParser::parseMethodSignature,
+ (signature, e) -> parseError(method, clazz.getOrigin(), signature, e)));
}
}
private DexAnnotationSet rewriteGenericSignatures(
DexAnnotationSet annotations,
Consumer<String> parser,
- Consumer<GenericSignatureFormatError> parseError) {
+ BiConsumer<String, GenericSignatureFormatError> parseError) {
// There can be no more than one signature annotation in an annotation set.
final int VALID = -1;
int invalid = VALID;
for (int i = 0; i < annotations.annotations.length && invalid == VALID; i++) {
DexAnnotation annotation = annotations.annotations[i];
if (DexAnnotation.isSignatureAnnotation(annotation, appInfo.dexItemFactory)) {
+ String signature = DexAnnotation.getSignature(annotation);
try {
- parser.accept(DexAnnotation.getSignature(annotation));
+ parser.accept(signature);
annotations.annotations[i] = DexAnnotation.createSignatureAnnotation(
genericSignatureRewriter.getRenamedSignature(),
appInfo.dexItemFactory);
} catch (GenericSignatureFormatError e) {
- parseError.accept(e);
+ parseError.accept(signature, e);
invalid = i;
}
}
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index fe002ec..68d634e 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -1354,8 +1354,12 @@
"lang.Character.isWhitespaceI.Character_isWhitespace_A01",
match(runtimes(Runtime.ART_V4_0_4)))
.put("lang.Character.getDirectionalityI.Character_getDirectionality_A01", any())
- .put("lang.Character.UnicodeBlock.ofC.UnicodeBlock_of_A01", any())
- .put("lang.Character.UnicodeBlock.ofI.UnicodeBlock_of_A01", any())
+ .put(
+ "lang.Character.UnicodeBlock.ofC.UnicodeBlock_of_A01",
+ match(artRuntimesFromAndJava(Runtime.ART_V4_4_4)))
+ .put(
+ "lang.Character.UnicodeBlock.ofI.UnicodeBlock_of_A01",
+ match(artRuntimesFromAndJava(Runtime.ART_V4_4_4)))
.put("lang.Character.isLowerCaseI.Character_isLowerCase_A01", anyDexVm())
.put("lang.Process.waitFor.Process_waitFor_A01", anyDexVm())
.put("lang.System.getProperties.System_getProperties_A01", anyDexVm())
@@ -1527,7 +1531,7 @@
match(artRuntimesUpTo(Runtime.ART_V6_0_1)))
.put(
"lang.reflect.AccessibleObject.setAccessibleZ.AccessibleObject_setAccessible_A04",
- match(runtimes(Runtime.ART_V4_4_4, Runtime.JAVA)))
+ match(artRuntimesUpToAndJava(Runtime.ART_V4_4_4)))
.put(
"lang.reflect.AccessibleObject.setAccessible_Ljava_lang_reflect_AccessibleObjectZ.AccessibleObject_setAccessible_A04",
match(artRuntimesUpToAndJava(Runtime.ART_V6_0_1)))
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index bf80acc..403ee70 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -706,6 +706,11 @@
}
}
+ protected ProcessResult runOnVMRaw(AndroidApp app, Class<?> mainClass, Backend backend)
+ throws IOException {
+ return runOnVMRaw(app, mainClass.getCanonicalName(), backend);
+ }
+
protected ProcessResult runOnVMRaw(AndroidApp app, String mainClass, Backend backend)
throws IOException {
switch (backend) {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e34282d..f5eaa25 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -93,7 +93,7 @@
public static final String SMALI_BUILD_DIR = TESTS_BUILD_DIR + "smali/";
public static final String LINE_SEPARATOR = StringUtils.LINE_SEPARATOR;
- public final static String PATH_SEPARATOR = File.pathSeparator;
+ public static final String PATH_SEPARATOR = File.pathSeparator;
public static final String DEFAULT_DEX_FILENAME = "classes.dex";
public static final String DEFAULT_PROGUARD_MAP_FILE = "proguard.map";
@@ -106,6 +106,8 @@
private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard";
private static final String PROGUARD = PROGUARD5_2_1;
+ public static final Path R8_JAR = Paths.get(LIBS_DIR, "r8.jar");
+
public enum DexVm {
ART_4_0_4_TARGET(Version.V4_0_4, Kind.TARGET),
ART_4_0_4_HOST(Version.V4_0_4, Kind.HOST),
@@ -997,7 +999,7 @@
public static ProcessResult forkR8Jar(Path dir, String... args)
throws IOException, InterruptedException {
- String r8Jar = Paths.get(LIBS_DIR, "r8.jar").toAbsolutePath().toString();
+ String r8Jar = R8_JAR.toAbsolutePath().toString();
return forkJavaWithJar(dir, r8Jar, Arrays.asList(args));
}
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
new file mode 100644
index 0000000..02df3d1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
@@ -0,0 +1,205 @@
+// Copyright (c) 2018, 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.cf;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static com.google.common.io.ByteStreams.toByteArray;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.codeinspector.ClassHierarchyVerifier;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.base.Charsets;
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class BootstrapCurrentEqualityTest extends TestBase {
+
+ private static final String R8_NAME = "com.android.tools.r8.R8";
+ private static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
+
+ private static final String HELLO_NAME = "hello.Hello";
+ private static final String[] KEEP_HELLO = {
+ "-keep class " + HELLO_NAME + " {", " public static void main(...);", "}",
+ };
+
+ private class R8Result {
+
+ final ProcessResult processResult;
+ final Path outputJar;
+ final String pgMap;
+
+ R8Result(ProcessResult processResult, Path outputJar, String pgMap) {
+ this.processResult = processResult;
+ this.outputJar = outputJar;
+ this.pgMap = pgMap;
+ }
+
+ @Override
+ public String toString() {
+ return processResult.toString() + "\n\n" + pgMap;
+ }
+ }
+
+ private static Path r8R8Debug;
+ private static Path r8R8Release;
+
+ @ClassRule public static TemporaryFolder testFolder = new TemporaryFolder();
+
+ @BeforeClass
+ public static void beforeAll() throws Exception {
+ r8R8Debug = compileR8(CompilationMode.DEBUG);
+ r8R8Release = compileR8(CompilationMode.RELEASE);
+ }
+
+ private static Path compileR8(CompilationMode mode) throws Exception {
+ // Run R8 on r8.jar.
+ Path output = runR8(ToolHelper.R8_JAR, testFolder.newFolder().toPath(), mode);
+ // Check that all non-abstract classes in the R8'd R8 implement all abstract/interface methods
+ // from their supertypes. This is a sanity check for the tree shaking and minification.
+ AndroidApp app = AndroidApp.builder().addProgramFile(output).build();
+ new ClassHierarchyVerifier(new CodeInspector(app)).run();
+ return output;
+ }
+
+ @Test
+ public void test() throws Exception {
+ Path helloJar = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "hello" + JAR_EXTENSION);
+ ProcessResult runResult = ToolHelper.runJava(helloJar, "hello.Hello");
+ assertEquals(0, runResult.exitCode);
+ compareR8(helloJar, runResult, KEEP_HELLO, "hello.Hello");
+ }
+
+ private void compareR8(Path program, ProcessResult runResult, String[] keep, String... args)
+ throws Exception {
+ R8Result runR8Debug =
+ runExternalR8(ToolHelper.R8_JAR, program, temp.newFolder().toPath(), keep, "--debug");
+ assertEquals(runResult.toString(), ToolHelper.runJava(runR8Debug.outputJar, args).toString());
+ R8Result runR8Release =
+ runExternalR8(ToolHelper.R8_JAR, program, temp.newFolder().toPath(), keep, "--release");
+ assertEquals(runResult.toString(), ToolHelper.runJava(runR8Release.outputJar, args).toString());
+ RunR8AndCheck(r8R8Debug, program, runR8Debug, keep, "--debug");
+ RunR8AndCheck(r8R8Debug, program, runR8Release, keep, "--release");
+ RunR8AndCheck(r8R8Release, program, runR8Debug, keep, "--debug");
+ RunR8AndCheck(r8R8Release, program, runR8Release, keep, "--release");
+ }
+
+ private void RunR8AndCheck(Path r8, Path program, R8Result result, String[] keep, String mode)
+ throws Exception {
+ R8Result runR8R8 = runExternalR8(r8, program, temp.newFolder().toPath(), keep, mode);
+ // Check that the process outputs (exit code, stdout, stderr) are the same.
+ assertEquals(result.toString(), runR8R8.toString());
+ // Check that the output jars are the same.
+ assertProgramsEqual(result.outputJar, runR8R8.outputJar);
+ }
+
+ private static Path runR8(Path inputJar, Path outputPath, CompilationMode mode) throws Exception {
+ Path outputJar = outputPath.resolve("output.jar");
+ ToolHelper.runR8(
+ R8Command.builder()
+ .setMode(mode)
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+ .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outputJar, true))
+ .addProgramFiles(inputJar)
+ .addProguardConfigurationFiles(MAIN_KEEP)
+ .build());
+ return outputJar;
+ }
+
+ private R8Result runExternalR8(
+ Path r8Jar, Path inputJar, Path output, String[] keepRules, String mode) throws Exception {
+ Path pgConfigFile = output.resolve("keep.rules");
+ Path outputJar = output.resolve("output.jar");
+ Path pgMapFile = output.resolve("map.txt");
+ FileUtils.writeTextFile(pgConfigFile, keepRules);
+ ProcessResult processResult =
+ ToolHelper.runJava(
+ r8Jar,
+ R8_NAME,
+ "--lib",
+ ToolHelper.JAVA_8_RUNTIME,
+ "--classfile",
+ inputJar.toString(),
+ "--output",
+ outputJar.toString(),
+ "--pg-conf",
+ pgConfigFile.toString(),
+ mode,
+ "--pg-map-output",
+ pgMapFile.toString());
+ assertEquals(0, processResult.exitCode);
+ String pgMap = FileUtils.readTextFile(pgMapFile, Charsets.UTF_8);
+ return new R8Result(processResult, outputJar, pgMap);
+ }
+
+ private static void assertProgramsEqual(Path expectedJar, Path actualJar) throws Exception {
+ if (filesAreEqual(expectedJar, actualJar)) {
+ return;
+ }
+ ArchiveClassFileProvider expected = new ArchiveClassFileProvider(expectedJar);
+ ArchiveClassFileProvider actual = new ArchiveClassFileProvider(actualJar);
+ assertEquals(getSortedDescriptorList(expected), getSortedDescriptorList(actual));
+ for (String descriptor : expected.getClassDescriptors()) {
+ assertArrayEquals(
+ "Class " + descriptor + " differs",
+ getClassAsBytes(expected, descriptor),
+ getClassAsBytes(actual, descriptor));
+ }
+ }
+
+ private static boolean filesAreEqual(Path file1, Path file2) throws IOException {
+ long size = Files.size(file1);
+ long sizeOther = Files.size(file2);
+ if (size != sizeOther) {
+ return false;
+ }
+ if (size < 4096) {
+ return Arrays.equals(Files.readAllBytes(file1), Files.readAllBytes(file2));
+ }
+ int byteRead1 = 0;
+ int byteRead2 = 0;
+ try (FileInputStream fs1 = new FileInputStream(file1.toString());
+ FileInputStream fs2 = new FileInputStream(file2.toString())) {
+ BufferedInputStream bs1 = new BufferedInputStream(fs1);
+ BufferedInputStream bs2 = new BufferedInputStream(fs2);
+ while (byteRead1 == byteRead2 && byteRead1 != -1) {
+ byteRead1 = bs1.read();
+ byteRead2 = bs2.read();
+ }
+ }
+ return byteRead1 == byteRead2;
+ }
+
+ private static List<String> getSortedDescriptorList(ArchiveClassFileProvider inputJar) {
+ ArrayList<String> descriptorList = new ArrayList<>(inputJar.getClassDescriptors());
+ Collections.sort(descriptorList);
+ return descriptorList;
+ }
+
+ private static byte[] getClassAsBytes(ArchiveClassFileProvider inputJar, String descriptor)
+ throws Exception {
+ return toByteArray(inputJar.getProgramResource(descriptor).getByteStream());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynthesizedLambdaClass.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynthesizedLambdaClass.java
new file mode 100644
index 0000000..dc1c40b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynthesizedLambdaClass.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2018, 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.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+
+public class InlineSynthesizedLambdaClass extends TestBase {
+
+ @Test
+ public void test() throws Exception {
+ AndroidApp input = readClasses(Lambda.class, Lambda.Consumer.class);
+ AndroidApp output =
+ compileWithR8(
+ input,
+ String.join(
+ System.lineSeparator(),
+ keepMainProguardConfiguration(Lambda.class),
+ "-allowaccessmodification"),
+ options -> options.enableMinification = false);
+
+ // Check that everything has been inlined into main.
+ CodeInspector inspector = new CodeInspector(output);
+ assertEquals(1, inspector.allClasses().size());
+
+ ClassSubject classSubject = inspector.clazz(Lambda.class);
+ assertThat(classSubject, isPresent());
+ assertEquals(1, classSubject.allMethods().size());
+
+ // Check that the program gives the expected result.
+ assertEquals(runOnJava(Lambda.class), runOnArt(output, Lambda.class));
+ }
+}
+
+class Lambda {
+
+ interface Consumer<T> {
+ void accept(T value);
+ }
+
+ public static void main(String... args) {
+ load(s -> System.out.println(s));
+ load(s -> System.out.println(s));
+ }
+
+ public static void load(Consumer<String> c) {
+ c.accept("Hello!");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/EnumMinification.java b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
new file mode 100644
index 0000000..a8708eb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/EnumMinification.java
@@ -0,0 +1,77 @@
+// Copyright (c) 2018, 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.naming;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnumMinification extends TestBase {
+
+ private Backend backend;
+
+ @Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public EnumMinification(Backend backend) {
+ this.backend = backend;
+ }
+
+ @Test
+ public void test() throws Exception {
+ AndroidApp output =
+ ToolHelper.runR8(
+ R8Command.builder()
+ .addClassProgramData(ToolHelper.getClassAsBytes(Main.class), Origin.unknown())
+ .addClassProgramData(ToolHelper.getClassAsBytes(Enum.class), Origin.unknown())
+ .addProguardConfiguration(
+ ImmutableList.of(keepMainProguardConfiguration(Main.class)), Origin.unknown())
+ .setProgramConsumer(emptyConsumer(backend))
+ .build());
+
+ // TODO(117299356): valueOf on enum fails for minified enums.
+ ProcessResult result = runOnVMRaw(output, Main.class, backend);
+ assertEquals(1, result.exitCode);
+ assertThat(
+ result.stderr,
+ containsString(
+ backend == Backend.DEX
+ ? ToolHelper.getDexVm().isNewerThan(DexVm.ART_4_4_4_HOST)
+ ? "java.lang.NoSuchMethodException"
+ : "java.lang.NullPointerException"
+ : "java.lang.IllegalArgumentException"));
+ }
+}
+
+class Main {
+
+ public static void main(String[] args) {
+ Enum e = Enum.valueOf("VALUE1");
+ System.out.println(e);
+ }
+}
+
+enum Enum {
+ VALUE1,
+ VALUE2
+}
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index 3240365..8d62033 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -498,7 +498,7 @@
testSingleClass("Outer", "X", diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
- "Invalid signature for class Outer", "Expected L at position 1");
+ "Invalid signature 'X' for class Outer", "Expected L at position 1");
}, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
}
@@ -507,7 +507,7 @@
testSingleClass("Outer", "<L", diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
- "Invalid signature for class Outer", "Unexpected end of signature at position 3");
+ "Invalid signature '<L' for class Outer", "Unexpected end of signature at position 3");
}, inspector -> noSignatureAttribute(inspector.clazz("Outer")));
}
@@ -516,7 +516,7 @@
testSingleClass("Outer$ExtendsInner", "X", diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
- "Invalid signature for class Outer$ExtendsInner", "Expected L at position 1");
+ "Invalid signature 'X' for class Outer$ExtendsInner", "Expected L at position 1");
}, inspector -> noSignatureAttribute(inspector.clazz("Outer$ExtendsInner")));
}
@@ -525,7 +525,7 @@
testSingleClass("Outer$Inner$ExtendsInnerInner", "X", diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
- "Invalid signature for class Outer$Inner$ExtendsInnerInner",
+ "Invalid signature 'X' for class Outer$Inner$ExtendsInnerInner",
"Expected L at position 1");
}, inspector -> noSignatureAttribute(inspector.clazz("Outer$Inner$ExtendsInnerInner")));
}
@@ -548,8 +548,11 @@
String signature = "LOuter<TT;>.com/example/Inner;";
testSingleClass("Outer$ExtendsInner", signature, diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
- DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
- "Invalid signature for class Outer$ExtendsInner", "Expected ; at position 16");
+ DiagnosticsChecker.checkDiagnostic(
+ diagnostics.warnings.get(0),
+ this::isOriginUnknown,
+ "Invalid signature '" + signature + "' for class Outer$ExtendsInner",
+ "Expected ; at position 16");
}, inspector -> {
noSignatureAttribute(inspector.clazz("Outer$ExtendsInner"));
});
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
index 4c81632..f73b8d2 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
@@ -278,7 +278,7 @@
assertEquals(1, diagnostics.warnings.size());
// TODO(sgjesse): The position 2 reported here is one off.
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
- "Invalid signature for field",
+ "Invalid signature 'X' for field",
"java.lang.String Fields.anX",
"Expected L, [ or T at position 2");
}, inspector -> noSignatureAttribute(lookupAnX(inspector)));
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
index a1ab483..d9d4774 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
@@ -299,7 +299,7 @@
testSingleMethod("generic", "X", diagnostics -> {
assertEquals(1, diagnostics.warnings.size());
DiagnosticsChecker.checkDiagnostic(diagnostics.warnings.get(0), this::isOriginUnknown,
- "Invalid signature for method",
+ "Invalid signature 'X' for method",
"java.lang.Throwable Methods.generic(java.lang.Throwable, Methods$Inner)",
"Expected ( at position 1");
}, inspector -> noSignatureAttribute(lookupGeneric(inspector)));
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 6331f5d..90013b0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -17,6 +17,12 @@
public abstract void forAllMethods(Consumer<FoundMethodSubject> inspection);
+ public final List<FoundMethodSubject> allMethods() {
+ ImmutableList.Builder<FoundMethodSubject> builder = ImmutableList.builder();
+ forAllMethods(builder::add);
+ return builder.build();
+ }
+
public MethodSubject method(Method method) {
List<String> parameters = new ArrayList<>();
for (Class<?> parameterType : method.getParameterTypes()) {
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index dbf393b..1e6ebfd 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -3,6 +3,7 @@
# BSD-style license that can be found in the LICENSE file.
import gradle
+import os
import subprocess
import utils
@@ -23,6 +24,9 @@
if profile:
cmd.append('-agentlib:hprof=cpu=samples,interval=1,depth=8')
cmd.extend(['-jar', utils.R8_JAR, tool])
+ lib, args = extract_lib_from_args(args)
+ if lib:
+ cmd.extend(["--lib", lib])
cmd.extend(args)
utils.PrintCmd(cmd)
return subprocess.call(cmd)
@@ -36,3 +40,15 @@
else:
args.append(arg)
return build, args
+
+def extract_lib_from_args(input_args):
+ lib = None
+ args = []
+ for arg in input_args:
+ if arg == '--lib-android':
+ lib = utils.get_android_jar(26)
+ elif arg == '--lib-java':
+ lib = utils.RT_JAR
+ else:
+ args.append(arg)
+ return lib, args