Merge "No logging of output when running with --golem on run_on_as_app"
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 7b2c688..ab06751 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugInfo;
import com.android.tools.r8.graph.DexEncodedArray;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
@@ -49,7 +50,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
-import java.util.LinkedHashMap;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -227,77 +228,58 @@
SortAnnotations sortAnnotations = new SortAnnotations();
application.classes().forEach((clazz) -> clazz.addDependencies(sortAnnotations));
- // Collect the indexed items sets for all files and perform JumboString processing.
- // This is required to ensure that shared code blocks have a single and consistent code
- // item that is valid for all dex files.
- // Use a linked hash map as the order matters when addDexProgramData is called below.
- Map<VirtualFile, Future<ObjectToOffsetMapping>> offsetMappingFutures = new LinkedHashMap<>();
- for (VirtualFile newFile : distribute(executorService)) {
- if (!newFile.isEmpty()) {
- offsetMappingFutures
- .put(newFile, executorService.submit(() -> {
- ObjectToOffsetMapping mapping = newFile.computeMapping(application);
- rewriteCodeWithJumboStrings(mapping, newFile.classes(), application);
- return mapping;
- }));
- }
- }
-
- // Wait for all spawned futures to terminate to ensure jumbo string writing is complete.
- ThreadUtils.awaitFutures(offsetMappingFutures.values());
-
// Generate the dex file contents.
List<Future<Boolean>> dexDataFutures = new ArrayList<>();
- try {
- for (VirtualFile virtualFile : offsetMappingFutures.keySet()) {
- assert !virtualFile.isEmpty();
- final ObjectToOffsetMapping mapping = offsetMappingFutures.get(virtualFile).get();
- dexDataFutures.add(
- executorService.submit(
- () -> {
- ProgramConsumer consumer;
- ByteBufferProvider byteBufferProvider;
- if (programConsumer != null) {
- consumer = programConsumer;
- byteBufferProvider = programConsumer;
- } else if (virtualFile.getPrimaryClassDescriptor() != null) {
- consumer = options.getDexFilePerClassFileConsumer();
- byteBufferProvider = options.getDexFilePerClassFileConsumer();
- } else {
- consumer = options.getDexIndexedConsumer();
- byteBufferProvider = options.getDexIndexedConsumer();
- }
- ByteBufferResult result = writeDexFile(mapping, byteBufferProvider);
- ByteDataView data =
- new ByteDataView(
- result.buffer.array(), result.buffer.arrayOffset(), result.length);
- if (consumer instanceof DexFilePerClassFileConsumer) {
- ((DexFilePerClassFileConsumer) consumer)
- .accept(
- virtualFile.getPrimaryClassDescriptor(),
- data,
- virtualFile.getClassDescriptors(),
- options.reporter);
- } else {
- ((DexIndexedConsumer) consumer)
- .accept(
- virtualFile.getId(),
- data,
- virtualFile.getClassDescriptors(),
- options.reporter);
- }
- // Release use of the backing buffer now that accept has returned.
- data.invalidate();
- byteBufferProvider.releaseByteBuffer(result.buffer.asByteBuffer());
- return true;
- }));
+ Iterable<VirtualFile> virtualFiles = distribute(executorService);
+ for (VirtualFile virtualFile : virtualFiles) {
+ if (virtualFile.isEmpty()) {
+ continue;
}
- } catch (InterruptedException e) {
- throw new RuntimeException("Interrupted while waiting for future.", e);
+ dexDataFutures.add(
+ executorService.submit(
+ () -> {
+ ProgramConsumer consumer;
+ ByteBufferProvider byteBufferProvider;
+ if (programConsumer != null) {
+ consumer = programConsumer;
+ byteBufferProvider = programConsumer;
+ } else if (virtualFile.getPrimaryClassDescriptor() != null) {
+ consumer = options.getDexFilePerClassFileConsumer();
+ byteBufferProvider = options.getDexFilePerClassFileConsumer();
+ } else {
+ consumer = options.getDexIndexedConsumer();
+ byteBufferProvider = options.getDexIndexedConsumer();
+ }
+ ObjectToOffsetMapping objectMapping = virtualFile.computeMapping(application);
+ MethodToCodeObjectMapping codeMapping =
+ rewriteCodeWithJumboStrings(
+ objectMapping, virtualFile.classes(), application);
+ ByteBufferResult result =
+ writeDexFile(objectMapping, codeMapping, byteBufferProvider);
+ ByteDataView data =
+ new ByteDataView(
+ result.buffer.array(), result.buffer.arrayOffset(), result.length);
+ if (consumer instanceof DexFilePerClassFileConsumer) {
+ ((DexFilePerClassFileConsumer) consumer)
+ .accept(
+ virtualFile.getPrimaryClassDescriptor(),
+ data,
+ virtualFile.getClassDescriptors(),
+ options.reporter);
+ } else {
+ ((DexIndexedConsumer) consumer)
+ .accept(
+ virtualFile.getId(),
+ data,
+ virtualFile.getClassDescriptors(),
+ options.reporter);
+ }
+ // Release use of the backing buffer now that accept has returned.
+ data.invalidate();
+ byteBufferProvider.releaseByteBuffer(result.buffer.asByteBuffer());
+ return true;
+ }));
}
-
- // Clear out the map, as it is no longer needed.
- offsetMappingFutures.clear();
// Wait for all files to be processed before moving on.
ThreadUtils.awaitFutures(dexDataFutures);
// Fail if there are pending errors, e.g., the program consumers may have reported errors.
@@ -482,36 +464,58 @@
}
/**
- * Rewrites the code for all methods in the given file so that they use JumboString for at
- * least the strings that require it in mapping.
- * <p>
- * If run multiple times on a class, the lowest index that is required to be a JumboString will
+ * Rewrites the code for all methods in the given file so that they use JumboString for at least
+ * the strings that require it in mapping.
+ *
+ * <p>If run multiple times on a class, the lowest index that is required to be a JumboString will
* be used.
*/
- private void rewriteCodeWithJumboStrings(ObjectToOffsetMapping mapping,
- Collection<DexProgramClass> classes, DexApplication application) {
+ private MethodToCodeObjectMapping rewriteCodeWithJumboStrings(
+ ObjectToOffsetMapping mapping,
+ Collection<DexProgramClass> classes,
+ DexApplication application) {
// Do not bail out early if forcing jumbo string processing.
if (!options.testing.forceJumboStringProcessing) {
// If there are no strings with jumbo indices at all this is a no-op.
if (!mapping.hasJumboStrings()) {
- return;
+ return MethodToCodeObjectMapping.fromMethodBacking();
}
// If the globally highest sorting string is not a jumbo string this is also a no-op.
if (application.highestSortingString != null &&
application.highestSortingString.slowCompareTo(mapping.getFirstJumboString()) < 0) {
- return;
+ return MethodToCodeObjectMapping.fromMethodBacking();
}
}
- // At least one method needs a jumbo string.
+ // At least one method needs a jumbo string in which case we construct a thread local mapping
+ // for all code objects and write the processed results into that map.
+ Map<DexEncodedMethod, DexCode> codeMapping = new IdentityHashMap<>();
for (DexProgramClass clazz : classes) {
- clazz.forEachMethod(method -> method.rewriteCodeWithJumboStrings(
- mapping, application, options.testing.forceJumboStringProcessing));
+ boolean isSharedSynthetic = clazz.getSynthesizedFrom().size() > 1;
+ clazz.forEachMethod(
+ method -> {
+ DexCode code =
+ method.rewriteCodeWithJumboStrings(
+ mapping,
+ application.dexItemFactory,
+ options.testing.forceJumboStringProcessing);
+ codeMapping.put(method, code);
+ if (!isSharedSynthetic) {
+ // If the class is not a shared class the mapping now has ownership of the methods
+ // code object. This ensures freeing of code resources once the map entry is cleared
+ // and also ensures that we don't end up using the incorrect code pointer again later!
+ method.removeCode();
+ }
+ });
}
+ return MethodToCodeObjectMapping.fromMapBacking(codeMapping);
}
private ByteBufferResult writeDexFile(
- ObjectToOffsetMapping mapping, ByteBufferProvider provider) {
- FileWriter fileWriter = new FileWriter(provider, mapping, application, options, namingLens);
+ ObjectToOffsetMapping objectMapping,
+ MethodToCodeObjectMapping codeMapping,
+ ByteBufferProvider provider) {
+ FileWriter fileWriter =
+ new FileWriter(provider, objectMapping, codeMapping, application, options, namingLens);
// Collect the non-fixed sections.
fileWriter.collect();
// Generate and write the bytes.
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 1cee816..fa44619 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -58,7 +58,7 @@
import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import java.security.MessageDigest;
-import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -86,6 +86,7 @@
}
private final ObjectToOffsetMapping mapping;
+ private final MethodToCodeObjectMapping codeMapping;
private final DexApplication application;
private final InternalOptions options;
private final NamingLens namingLens;
@@ -95,19 +96,21 @@
public FileWriter(
ByteBufferProvider provider,
ObjectToOffsetMapping mapping,
+ MethodToCodeObjectMapping codeMapping,
DexApplication application,
InternalOptions options,
NamingLens namingLens) {
this.mapping = mapping;
+ this.codeMapping = codeMapping;
this.application = application;
this.options = options;
this.namingLens = namingLens;
this.dest = new DexOutputBuffer(provider);
- this.mixedSectionOffsets = new MixedSectionOffsets(options);
+ this.mixedSectionOffsets = new MixedSectionOffsets(options, codeMapping);
}
- public static void writeEncodedAnnotation(DexEncodedAnnotation annotation, DexOutputBuffer dest,
- ObjectToOffsetMapping mapping) {
+ public static void writeEncodedAnnotation(
+ DexEncodedAnnotation annotation, DexOutputBuffer dest, ObjectToOffsetMapping mapping) {
if (Log.ENABLED) {
Log.verbose(FileWriter.class, "Writing encoded annotation @ %08x", dest.position());
}
@@ -154,8 +157,11 @@
Layout layout = Layout.from(mapping);
layout.setCodesOffset(layout.dataSectionOffset);
+ // Check code objects in the code-mapping are consistent with the collected code objects.
+ assert codeMapping.verifyCodeObjects(mixedSectionOffsets.getCodes());
+
// Sort the codes first, as their order might impact size due to alignment constraints.
- List<DexCode> codes = sortDexCodesByClassName(mixedSectionOffsets.getCodes(), application);
+ List<DexCode> codes = sortDexCodesByClassName();
// Output the debug_info_items first, as they have no dependencies.
dest.moveTo(layout.getCodesOffset() + sizeOfCodeItems(codes));
@@ -276,35 +282,40 @@
}
}
- private List<DexCode> sortDexCodesByClassName(Collection<DexCode> codes,
- DexApplication application) {
+ private List<DexCode> sortDexCodesByClassName() {
Map<DexCode, String> codeToSignatureMap = new IdentityHashMap<>();
+ List<DexCode> codesSorted = new ArrayList<>();
for (DexProgramClass clazz : mapping.getClasses()) {
- clazz.forEachMethod(method ->
- addSignaturesFromMethod(method, codeToSignatureMap, application.getProguardMap()));
+ clazz.forEachMethod(
+ method -> {
+ DexCode code = codeMapping.getCode(method);
+ assert code != null || method.shouldNotHaveCode();
+ if (code != null) {
+ codesSorted.add(code);
+ addSignaturesFromMethod(
+ method, code, codeToSignatureMap, application.getProguardMap());
+ }
+ });
}
- DexCode[] codesArray = codes.toArray(new DexCode[codes.size()]);
- Arrays.sort(codesArray, Comparator.comparing(codeToSignatureMap::get));
- return Arrays.asList(codesArray);
+ codesSorted.sort(Comparator.comparing(codeToSignatureMap::get));
+ return codesSorted;
}
- private static void addSignaturesFromMethod(DexEncodedMethod method,
+ private static void addSignaturesFromMethod(
+ DexEncodedMethod method,
+ DexCode code,
Map<DexCode, String> codeToSignatureMap,
ClassNameMapper proguardMap) {
- if (!method.hasCode()) {
- assert method.shouldNotHaveCode();
+ Signature signature;
+ String originalClassName;
+ if (proguardMap != null) {
+ signature = proguardMap.originalSignatureOf(method.method);
+ originalClassName = proguardMap.originalNameOf(method.method.holder);
} else {
- Signature signature;
- String originalClassName;
- if (proguardMap != null) {
- signature = proguardMap.originalSignatureOf(method.method);
- originalClassName = proguardMap.originalNameOf(method.method.holder);
- } else {
- signature = MethodSignature.fromDexMethod(method.method);
- originalClassName = method.method.holder.toSourceString();
- }
- codeToSignatureMap.put(method.getCode().asDexCode(), originalClassName + signature);
+ signature = MethodSignature.fromDexMethod(method.method);
+ originalClassName = method.method.holder.toSourceString();
}
+ codeToSignatureMap.put(code, originalClassName + signature);
}
private <T extends IndexedDexItem> void writeFixedSectionItems(
@@ -573,7 +584,7 @@
}
}
- private void writeEncodedMethods(List<DexEncodedMethod> methods, boolean clearBodies) {
+ private void writeEncodedMethods(List<DexEncodedMethod> methods, boolean isSharedSynthetic) {
assert PresortedComparable.isSorted(methods);
int currentOffset = 0;
for (DexEncodedMethod method : methods) {
@@ -582,16 +593,15 @@
dest.putUleb128(nextOffset - currentOffset);
currentOffset = nextOffset;
dest.putUleb128(method.accessFlags.getAsDexAccessFlags());
- if (!method.hasCode()) {
+ DexCode code = codeMapping.getCode(method);
+ if (code == null) {
assert method.shouldNotHaveCode();
dest.putUleb128(0);
} else {
- dest.putUleb128(mixedSectionOffsets.getOffsetFor(method.getCode().asDexCode()));
+ dest.putUleb128(mixedSectionOffsets.getOffsetFor(code));
// Writing the methods starts to take up memory so we are going to flush the
// code objects since they are no longer necessary after this.
- if (clearBodies) {
- method.removeCode();
- }
+ codeMapping.clearCode(method, isSharedSynthetic);
}
}
}
@@ -605,10 +615,9 @@
dest.putUleb128(clazz.virtualMethods().size());
writeEncodedFields(clazz.staticFields());
writeEncodedFields(clazz.instanceFields());
-
boolean isSharedSynthetic = clazz.getSynthesizedFrom().size() > 1;
- writeEncodedMethods(clazz.directMethods(), !isSharedSynthetic);
- writeEncodedMethods(clazz.virtualMethods(), !isSharedSynthetic);
+ writeEncodedMethods(clazz.directMethods(), isSharedSynthetic);
+ writeEncodedMethods(clazz.virtualMethods(), isSharedSynthetic);
}
private void addStaticFieldValues(DexProgramClass clazz) {
@@ -1001,6 +1010,8 @@
private static final int NOT_SET = -1;
private static final int NOT_KNOWN = -2;
+ private final MethodToCodeObjectMapping codeMapping;
+
private final Reference2IntMap<DexCode> codes = createReference2IntMap();
private final Object2IntMap<DexDebugInfo> debugInfos = createObject2IntMap();
private final Object2IntMap<DexTypeList> typeLists = createObject2IntMap();
@@ -1030,8 +1041,9 @@
return result;
}
- private MixedSectionOffsets(InternalOptions options) {
+ private MixedSectionOffsets(InternalOptions options, MethodToCodeObjectMapping codeMapping) {
this.minApiLevel = options.minApiLevel;
+ this.codeMapping = codeMapping;
}
private <T> boolean add(Object2IntMap<T> map, T item) {
@@ -1071,6 +1083,11 @@
}
@Override
+ public void visit(DexEncodedMethod method) {
+ method.collectMixedSectionItemsWithCodeMapping(this, codeMapping);
+ }
+
+ @Override
public boolean add(DexCode code) {
return add(codes, code);
}
diff --git a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
index 1c18107..b2a3e09 100644
--- a/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
+++ b/src/main/java/com/android/tools/r8/dex/JumboStringRewriter.java
@@ -107,7 +107,7 @@
this.factory = factory;
}
- public void rewrite() {
+ public DexCode rewrite() {
// Build maps from everything in the code that uses offsets or direct addresses to reference
// instructions to the actual instruction referenced.
recordTargets();
@@ -133,7 +133,7 @@
// As we have rewritten the code, we now know that its highest string index that is not
// a jumbo-string is firstJumboString (actually the previous string, but we do not have that).
newCode.highestSortingString = firstJumboString;
- method.setCode(newCode);
+ return newCode;
}
private void rewriteInstructionOffsets(List<Instruction> instructions) {
diff --git a/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java b/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java
new file mode 100644
index 0000000..feeb64b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dex/MethodToCodeObjectMapping.java
@@ -0,0 +1,78 @@
+// Copyright (c) 2019, 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.dex;
+
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import java.util.Collection;
+import java.util.Map;
+
+public abstract class MethodToCodeObjectMapping {
+
+ public abstract DexCode getCode(DexEncodedMethod method);
+
+ public abstract void clearCode(DexEncodedMethod method, boolean isSharedSynthetic);
+
+ public abstract boolean verifyCodeObjects(Collection<DexCode> codes);
+
+ public static MethodToCodeObjectMapping fromMethodBacking() {
+ return MethodBacking.INSTANCE;
+ }
+
+ public static MethodToCodeObjectMapping fromMapBacking(Map<DexEncodedMethod, DexCode> map) {
+ return new MapBacking(map);
+ }
+
+ private static class MethodBacking extends MethodToCodeObjectMapping {
+
+ private static final MethodBacking INSTANCE = new MethodBacking();
+
+ @Override
+ public DexCode getCode(DexEncodedMethod method) {
+ Code code = method.getCode();
+ assert code == null || code.isDexCode();
+ return code == null ? null : code.asDexCode();
+ }
+
+ @Override
+ public void clearCode(DexEncodedMethod method, boolean isSharedSynthetic) {
+ // When using methods directly any shared class needs to maintain its methods as read-only.
+ if (!isSharedSynthetic) {
+ method.removeCode();
+ }
+ }
+
+ @Override
+ public boolean verifyCodeObjects(Collection<DexCode> codes) {
+ return true;
+ }
+ }
+
+ private static class MapBacking extends MethodToCodeObjectMapping {
+
+ private final Map<DexEncodedMethod, DexCode> codes;
+
+ public MapBacking(Map<DexEncodedMethod, DexCode> codes) {
+ this.codes = codes;
+ }
+
+ @Override
+ public DexCode getCode(DexEncodedMethod method) {
+ return codes.get(method);
+ }
+
+ @Override
+ public void clearCode(DexEncodedMethod method, boolean isSharedSynthetic) {
+ // We can safely clear the thread local pointer to even shared methods.
+ codes.put(method, null);
+ }
+
+ @Override
+ public boolean verifyCodeObjects(Collection<DexCode> codes) {
+ assert this.codes.values().containsAll(codes);
+ return true;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
index dee12fc..2cedf5c 100644
--- a/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
+++ b/src/main/java/com/android/tools/r8/dex/MixedSectionCollection.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexDebugInfo;
import com.android.tools.r8.graph.DexEncodedArray;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItem;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexTypeList;
@@ -53,6 +54,16 @@
public abstract boolean add(DexAnnotationSet dexAnnotationSet);
/**
+ * Recurse on the given encoded method to add items to the collection.
+ *
+ * <p>Allows overriding the behavior when dex-file writing.
+ */
+ public void visit(DexEncodedMethod dexEncodedMethod) {
+ dexEncodedMethod.collectMixedSectionItemsWithCodeMapping(
+ this, MethodToCodeObjectMapping.fromMethodBacking());
+ }
+
+ /**
* Adds the given code item to the collection.
*
* Does not add any dependencies.
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 9fa16f1..d4699ca 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -23,6 +23,7 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.dex.IndexedItemCollection;
import com.android.tools.r8.dex.JumboStringRewriter;
+import com.android.tools.r8.dex.MethodToCodeObjectMapping;
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.errors.InternalCompilerError;
import com.android.tools.r8.errors.Unreachable;
@@ -366,6 +367,12 @@
@Override
void collectMixedSectionItems(MixedSectionCollection mixedItems) {
+ mixedItems.visit(this);
+ }
+
+ public void collectMixedSectionItemsWithCodeMapping(
+ MixedSectionCollection mixedItems, MethodToCodeObjectMapping mapping) {
+ DexCode code = mapping.getCode(this);
if (code != null) {
code.collectMixedSectionItems(mixedItems);
}
@@ -678,19 +685,13 @@
return method;
}
- /**
- * Rewrites the code in this method to have JumboString bytecode if required by mapping.
- * <p>
- * Synchronized such that it can be called concurrently for different mappings. As a side-effect,
- * this will also update the highestSortingString to the index of the strings up to which the code
- * was rewritten to avoid rewriting again unless needed.
- */
- public synchronized void rewriteCodeWithJumboStrings(
- ObjectToOffsetMapping mapping, DexApplication application, boolean force) {
+ /** Rewrites the code in this method to have JumboString bytecode if required by mapping. */
+ public DexCode rewriteCodeWithJumboStrings(
+ ObjectToOffsetMapping mapping, DexItemFactory factory, boolean force) {
checkIfObsolete();
assert code == null || code.isDexCode();
if (code == null) {
- return;
+ return null;
}
DexCode code = this.code.asDexCode();
DexString firstJumboString = null;
@@ -706,10 +707,10 @@
}
}
if (firstJumboString != null) {
- JumboStringRewriter rewriter =
- new JumboStringRewriter(this, firstJumboString, application.dexItemFactory);
- rewriter.rewrite();
+ JumboStringRewriter rewriter = new JumboStringRewriter(this, firstJumboString, factory);
+ return rewriter.rewrite();
}
+ return code;
}
public String codeToString() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 9f7d021..07b32ec 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -177,7 +177,7 @@
reporter.failIfPendingErrors();
}
- private static enum IdentifierType {
+ private enum IdentifierType {
CLASS_NAME,
ANY
}
@@ -288,11 +288,9 @@
}
skipWhitespace();
char quote = acceptQuoteIfPresent();
+ configurationBuilder.setPackagePrefix(parsePackageNameOrEmptyString());
if (isQuote(quote)) {
- configurationBuilder.setPackagePrefix(parsePackageNameOrEmptyString());
expectClosingQuote(quote);
- } else {
- configurationBuilder.setPackagePrefix("");
}
} else if (acceptString("flattenpackagehierarchy")) {
if (configurationBuilder.getPackageObfuscationMode() == PackageObfuscationMode.REPACKAGE) {
@@ -304,11 +302,9 @@
} else {
skipWhitespace();
char quote = acceptQuoteIfPresent();
+ configurationBuilder.setFlattenPackagePrefix(parsePackageNameOrEmptyString());
if (isQuote(quote)) {
- configurationBuilder.setFlattenPackagePrefix(parsePackageNameOrEmptyString());
expectClosingQuote(quote);
- } else {
- configurationBuilder.setFlattenPackagePrefix("");
}
}
} else if (acceptString("overloadaggressively")) {
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 208fb9c..d408623 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -906,7 +906,8 @@
if (context instanceof ProguardKeepRule) {
if (item.isDexEncodedMethod()) {
DexEncodedMethod encodedMethod = item.asDexEncodedMethod();
- if (encodedMethod.method.isLambdaDeserializeMethod(appView.dexItemFactory())) {
+ if (options.isGeneratingDex()
+ && encodedMethod.method.isLambdaDeserializeMethod(appView.dexItemFactory())) {
// Don't keep lambda deserialization methods.
return;
}
diff --git a/src/test/java/com/android/tools/r8/TestBuilder.java b/src/test/java/com/android/tools/r8/TestBuilder.java
index 5fccb4a..6eea205 100644
--- a/src/test/java/com/android/tools/r8/TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestBuilder.java
@@ -9,6 +9,7 @@
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
+import java.util.concurrent.ExecutionException;
public abstract class TestBuilder<RR extends TestRunResult, T extends TestBuilder<RR, T>> {
@@ -34,18 +35,20 @@
}
@Deprecated
- public abstract RR run(String mainClass) throws IOException, CompilationFailedException;
+ public abstract RR run(String mainClass)
+ throws CompilationFailedException, ExecutionException, IOException;
public abstract RR run(TestRuntime runtime, String mainClass)
- throws IOException, CompilationFailedException;
+ throws CompilationFailedException, ExecutionException, IOException;
@Deprecated
- public RR run(Class mainClass) throws IOException, CompilationFailedException {
+ public RR run(Class<?> mainClass)
+ throws CompilationFailedException, ExecutionException, IOException {
return run(mainClass.getTypeName());
}
- public RR run(TestRuntime runtime, Class mainClass)
- throws IOException, CompilationFailedException {
+ public RR run(TestRuntime runtime, Class<?> mainClass)
+ throws CompilationFailedException, ExecutionException, IOException {
return run(runtime, mainClass.getTypeName());
}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 7fcc59a..1c4d7e1 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -4,6 +4,8 @@
package com.android.tools.r8;
import static com.android.tools.r8.TestBase.Backend.DEX;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertThat;
import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
import com.android.tools.r8.TestBase.Backend;
@@ -15,6 +17,7 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
@@ -48,33 +51,38 @@
protected abstract RR createRunResult(ProcessResult result);
@Deprecated
- public RR run(Class<?> mainClass) throws IOException {
+ public RR run(Class<?> mainClass) throws ExecutionException, IOException {
return run(mainClass.getTypeName());
}
@Deprecated
- public RR run(String mainClass) throws IOException {
+ public RR run(String mainClass) throws ExecutionException, IOException {
+ ClassSubject mainClassSubject = inspector().clazz(mainClass);
+ assertThat(mainClassSubject, isPresent());
switch (getBackend()) {
case DEX:
- return runArt(null, additionalRunClassPath, mainClass);
+ return runArt(null, additionalRunClassPath, mainClassSubject.getFinalName());
case CF:
- return runJava(null, additionalRunClassPath, mainClass);
+ return runJava(null, additionalRunClassPath, mainClassSubject.getFinalName());
default:
throw new Unreachable();
}
}
- public RR run(TestRuntime runtime, Class<?> mainClass) throws IOException {
+ public RR run(TestRuntime runtime, Class<?> mainClass) throws ExecutionException, IOException {
return run(runtime, mainClass.getTypeName());
}
- public RR run(TestRuntime runtime, String mainClass) throws IOException {
+ public RR run(TestRuntime runtime, String mainClass) throws ExecutionException, IOException {
assert getBackend() == runtime.getBackend();
+ ClassSubject mainClassSubject = inspector().clazz(mainClass);
+ assertThat(mainClassSubject, isPresent());
if (runtime.isDex()) {
- return runArt(runtime.asDex().getVm(), additionalRunClassPath, mainClass);
+ return runArt(
+ runtime.asDex().getVm(), additionalRunClassPath, mainClassSubject.getFinalName());
}
assert runtime.isCf();
- return runJava(runtime, additionalRunClassPath, mainClass);
+ return runJava(runtime, additionalRunClassPath, mainClassSubject.getFinalName());
}
public CR addRunClasspathFiles(Path... classpath) {
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 8dbc309..c648668 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -14,6 +14,7 @@
import java.io.PrintStream;
import java.nio.file.Path;
import java.util.Collection;
+import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -85,13 +86,14 @@
}
@Override
- public RR run(String mainClass) throws IOException, CompilationFailedException {
+ public RR run(String mainClass)
+ throws CompilationFailedException, ExecutionException, IOException {
return compile().run(mainClass);
}
@Override
public RR run(TestRuntime runtime, String mainClass)
- throws IOException, CompilationFailedException {
+ throws CompilationFailedException, ExecutionException, IOException {
return compile().run(runtime, mainClass);
}
diff --git a/src/test/java/com/android/tools/r8/TestRunResult.java b/src/test/java/com/android/tools/r8/TestRunResult.java
index e2f4239..f22b23e 100644
--- a/src/test/java/com/android/tools/r8/TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/TestRunResult.java
@@ -83,6 +83,12 @@
return self();
}
+ public RR assertSuccessWithOutputThatMatches(Matcher<String> matcher) {
+ assertSuccess();
+ assertThat(errorMessage("Run stdout incorrect.", matcher.toString()), result.stdout, matcher);
+ return self();
+ }
+
public CodeInspector inspector() throws IOException, ExecutionException {
// Inspection post run implies success. If inspection of an invalid program is needed it should
// be done on the compilation result or on the input.
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 511f328..5e3e100 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -18,7 +18,7 @@
B extends BaseCompilerCommand.Builder<C, B>,
CR extends TestCompileResult<CR, RR>,
RR extends TestRunResult,
- T extends TestCompilerBuilder<C, B, CR, RR, T>>
+ T extends TestShrinkerBuilder<C, B, CR, RR, T>>
extends TestCompilerBuilder<C, B, CR, RR, T> {
protected boolean enableMinification = true;
diff --git a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTest.java b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTest.java
new file mode 100644
index 0000000..4dc81f4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTest.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2019, 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 java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+class KeepDeserializeLambdaMethodTest {
+ static final String LAMBDA_MESSAGE = "[I'm the lambda.]";
+
+ static void invokeLambda(Object o)
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ Method runMethod = o.getClass().getMethod("run");
+ runMethod.invoke(o);
+ }
+}
+
+class KeepDeserializeLambdaMethodTestDex extends KeepDeserializeLambdaMethodTest {
+ public static void main(String[] args) throws Exception {
+ Serializable myLambda =
+ (Runnable & Serializable)
+ () -> System.out.println(KeepDeserializeLambdaMethodTest.LAMBDA_MESSAGE);
+ invokeLambda(myLambda);
+ }
+}
+
+class KeepDeserializeLambdaMethodTestCf extends KeepDeserializeLambdaMethodTest {
+
+ public static void main(String[] args) throws Exception {
+ Serializable myLambda = (Runnable & Serializable) () -> System.out.println(LAMBDA_MESSAGE);
+
+ byte[] bytes;
+ {
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ ObjectOutputStream out = new ObjectOutputStream(byteStream);
+ out.writeObject(myLambda);
+ out.close();
+ bytes = byteStream.toByteArray();
+ }
+ Object o;
+ {
+ ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
+ ObjectInputStream in = new ObjectInputStream(byteStream);
+ o = in.readObject();
+ in.close();
+ }
+ String name = o.getClass().getName();
+ if (!name.contains("KeepDeserializeLambdaMethodTestCf")) {
+ throw new RuntimeException("Unexpected class name " + name);
+ }
+ invokeLambda(o);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
new file mode 100644
index 0000000..c2dc7b7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/KeepDeserializeLambdaMethodTestRunner.java
@@ -0,0 +1,85 @@
+// Copyright (c) 2019, 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 org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+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 KeepDeserializeLambdaMethodTestRunner extends TestBase {
+
+ private static final Class TEST_CLASS_CF =
+ com.android.tools.r8.cf.KeepDeserializeLambdaMethodTestCf.class;
+ private static final Class TEST_CLASS_DEX =
+ com.android.tools.r8.cf.KeepDeserializeLambdaMethodTestDex.class;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection params() {
+ return getTestParameters().withCfRuntimes().withDexRuntime(Version.last()).build();
+ }
+
+ private final TestParameters parameters;
+
+ public KeepDeserializeLambdaMethodTestRunner(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testNoTreeShakingCf() throws Exception {
+ test(false);
+ }
+
+ @Test
+ public void testKeepRuleCf() throws Exception {
+ test(true);
+ }
+
+ private void test(boolean keepRule)
+ throws IOException, CompilationFailedException, ExecutionException {
+ Class testClass = parameters.isCfRuntime() ? TEST_CLASS_CF : TEST_CLASS_DEX;
+ R8TestBuilder builder =
+ testForR8Compat(parameters.getBackend())
+ .addProgramClasses(
+ com.android.tools.r8.cf.KeepDeserializeLambdaMethodTest.class, testClass)
+ .apply(parameters::setMinApiForRuntime)
+ .addKeepMainRule(testClass)
+ .noMinification();
+ if (keepRule) {
+ builder.addKeepRules(
+ "-keepclassmembers class * {",
+ "private static synthetic java.lang.Object "
+ + "$deserializeLambda$(java.lang.invoke.SerializedLambda);",
+ "}");
+ } else {
+ builder.noTreeShaking();
+ }
+ R8TestRunResult result =
+ builder
+ .run(parameters.getRuntime(), testClass)
+ .assertSuccessWithOutputThatMatches(
+ containsString(KeepDeserializeLambdaMethodTest.LAMBDA_MESSAGE))
+ .inspect(
+ inspector -> {
+ MethodSubject method =
+ inspector.clazz(testClass).uniqueMethodWithName("$deserializeLambda$");
+ assertEquals(parameters.isCfRuntime(), method.isPresent());
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
index e11b957..95e1e76 100644
--- a/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
+++ b/src/test/java/com/android/tools/r8/dex/JumboStringProcessing.java
@@ -159,7 +159,6 @@
null);
MethodAccessFlags flags = MethodAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC, false);
DexEncodedMethod method = new DexEncodedMethod(null, flags, null, null, code);
- new JumboStringRewriter(method, string, factory).rewrite();
- return method.getCode().asDexCode();
+ return new JumboStringRewriter(method, string, factory).rewrite();
}
}
diff --git a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
index df2d769..298ffe1 100644
--- a/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
+++ b/src/test/java/com/android/tools/r8/dex/SharedClassWritingTest.java
@@ -48,7 +48,6 @@
import java.util.concurrent.ExecutorService;
import org.junit.Assert;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
public class SharedClassWritingTest {
@@ -111,7 +110,6 @@
synthesizedFrom);
}
- @Ignore("b/128281550")
@Test
public void manyFilesWithSharedSynthesizedClass() throws ExecutionException, IOException {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ExtraMethodNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ExtraMethodNullTest.java
index 42b0ce4..3ece2da 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ExtraMethodNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ExtraMethodNullTest.java
@@ -6,16 +6,14 @@
import static org.hamcrest.CoreMatchers.containsString;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
-import java.io.IOException;
import org.junit.Test;
public class ExtraMethodNullTest extends TestBase {
@Test
- public void test() throws IOException, CompilationFailedException {
+ public void test() throws Exception {
testForR8(Backend.DEX)
.addProgramClassesAndInnerClasses(One.class)
.addKeepMainRule(One.class)
diff --git a/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java b/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
index f599f4d..9ade2f9 100644
--- a/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/signature/GenericSignatureRenamingTest.java
@@ -4,11 +4,9 @@
package com.android.tools.r8.naming.signature;
-import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.R8TestBuilder;
import com.android.tools.r8.TestBase;
-import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
@@ -17,37 +15,37 @@
public class GenericSignatureRenamingTest extends TestBase {
@Test
- public void testJVM() throws IOException, CompilationFailedException {
+ public void testJVM() throws Exception {
testForJvm().addTestClasspath().run(Main.class).assertSuccess();
}
@Test
- public void testR8Dex() throws IOException, CompilationFailedException {
+ public void testR8Dex() throws Exception {
test(testForR8(Backend.DEX));
}
@Test
- public void testR8CompatDex() throws IOException, CompilationFailedException {
+ public void testR8CompatDex() throws Exception {
test(testForR8Compat(Backend.DEX));
}
@Test
- public void testR8DexNoMinify() throws IOException, CompilationFailedException {
+ public void testR8DexNoMinify() throws Exception {
test(testForR8(Backend.DEX).addKeepRules("-dontobfuscate"));
}
@Test
- public void testR8Cf() throws IOException, CompilationFailedException {
+ public void testR8Cf() throws Exception {
test(testForR8(Backend.CF));
}
@Test
- public void testR8CfNoMinify() throws IOException, CompilationFailedException {
+ public void testR8CfNoMinify() throws Exception {
test(testForR8(Backend.CF).addKeepRules("-dontobfuscate"));
}
@Test
- public void testD8() throws IOException, CompilationFailedException {
+ public void testD8() throws Exception {
testForD8()
.addProgramClasses(Main.class)
.addProgramClassesAndInnerClasses(A.class, B.class, CY.class, CYY.class)
@@ -58,7 +56,7 @@
.assertSuccess();
}
- private void test(R8TestBuilder builder) throws IOException, CompilationFailedException {
+ private void test(R8TestBuilder builder) throws Exception {
builder
.addKeepRules("-dontoptimize")
.addKeepRules("-keepattributes InnerClasses,EnclosingMethod,Signature")
diff --git a/src/test/java/com/android/tools/r8/proguard/configuration/RepackagingCompatibilityTest.java b/src/test/java/com/android/tools/r8/proguard/configuration/RepackagingCompatibilityTest.java
new file mode 100644
index 0000000..afaf999
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/proguard/configuration/RepackagingCompatibilityTest.java
@@ -0,0 +1,135 @@
+// Copyright (c) 2019, 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.proguard.configuration;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+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 RepackagingCompatibilityTest extends TestBase {
+
+ private enum Quote {
+ SINGLE,
+ DOUBLE,
+ NONE
+ }
+
+ private static final String expectedOutput = StringUtils.lines("Hello world!");
+ private static final Class<?> mainClass = RepackagingCompatabilityTestClass.class;
+
+ private final String directive;
+ private final Quote quote;
+ private final boolean repackageToRoot;
+
+ @Parameters(name = "Directive: {0}, quote: {1}, repackage to root: {2}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ ImmutableList.of("-flattenpackagehierarchy", "-repackageclasses"),
+ Quote.values(),
+ BooleanUtils.values());
+ }
+
+ public RepackagingCompatibilityTest(String directive, Quote quote, boolean repackageToRoot) {
+ this.directive = directive;
+ this.quote = quote;
+ this.repackageToRoot = repackageToRoot;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ runTest(testForR8(Backend.DEX), "R8");
+ }
+
+ @Test
+ public void testProguard() throws Exception {
+ runTest(testForProguard(), "Proguard");
+ }
+
+ private void runTest(TestShrinkerBuilder<?, ?, ?, ?, ?> builder, String shrinker)
+ throws Exception {
+ assumeFalse(
+ String.format(
+ "Only repackage to root when there are no quotes"
+ + " (repackageToRoot: %s, quote: %s, shrinker: %s)",
+ repackageToRoot, quote, shrinker),
+ repackageToRoot && quote != Quote.NONE);
+
+ TestRunResult<?> result =
+ builder
+ .addProgramClasses(mainClass)
+ .addKeepRules(getKeepRules())
+ .run(mainClass)
+ .assertSuccessWithOutput(expectedOutput);
+
+ ClassSubject testClassSubject = result.inspector().clazz(mainClass);
+ assertThat(testClassSubject, isPresent());
+ if (repackageToRoot) {
+ if (directive.equals("-flattenpackagehierarchy")) {
+ assertThat(testClassSubject.getFinalName(), startsWith("a."));
+ } else if (directive.equals("-repackageclasses")) {
+ assertThat(testClassSubject.getFinalName(), not(containsString(".")));
+ } else {
+ fail();
+ }
+ } else {
+ assertThat(testClassSubject.getFinalName(), startsWith("greeter."));
+ }
+ }
+
+ private List<String> getKeepRules() {
+ return ImmutableList.of(
+ // Keep main(), but allow obfuscation
+ "-keep,allowobfuscation class " + mainClass.getTypeName() + " {",
+ " public static void main(...);",
+ "}",
+ // Ensure main() is not renamed
+ "-keepclassmembernames class " + mainClass.getTypeName() + " {",
+ " public static void main(...);",
+ "}",
+ getRepackagingRule());
+ }
+
+ private String getRepackagingRule() {
+ if (repackageToRoot) {
+ return directive;
+ }
+ switch (quote) {
+ case SINGLE:
+ return directive + " 'greeter'";
+ case DOUBLE:
+ return directive + " \"greeter\"";
+ case NONE:
+ return directive + " greeter";
+ default:
+ throw new Unreachable();
+ }
+ }
+}
+
+class RepackagingCompatabilityTestClass {
+
+ public static void main(String[] args) {
+ System.out.println("Hello world!");
+ }
+}