Merge "Make sure nullability in type lattice conforms to neverNull bit."
diff --git a/src/main/java/com/android/tools/r8/ClassFileConsumer.java b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
index 3521429..364a794 100644
--- a/src/main/java/com/android/tools/r8/ClassFileConsumer.java
+++ b/src/main/java/com/android/tools/r8/ClassFileConsumer.java
@@ -161,7 +161,7 @@
String className = resource.getClassDescriptors().iterator().next();
String entryName = getClassFileName(className);
byte[] bytes = ByteStreams.toByteArray(closer.register(resource.getByteStream()));
- ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.STORED);
+ ZipUtils.writeToZipStream(out, entryName, bytes, ZipEntry.DEFLATED);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index b5814d2..4d27b8a 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.5.2-dev";
+ public static final String LABEL = "1.5.3-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
index 6bebb0d..8c79ea1 100644
--- a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -243,7 +243,7 @@
stackValue, constant.asConstString().getValue(), ThrowingInfo.NO_THROW);
} else if (constant.isDexItemBasedConstString()) {
return new DexItemBasedConstString(
- stackValue, constant.asDexItemBasedConstString().getItem());
+ stackValue, constant.asDexItemBasedConstString().getItem(), ThrowingInfo.NO_THROW);
} else if (constant.isConstClass()) {
return new ConstClass(stackValue, constant.asConstClass().getValue());
} else {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
index c38cf60..0467fcd 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstString.java
@@ -49,7 +49,7 @@
@Override
public boolean canThrow() {
- // The ldc instruction may throw in Java bytecode.
+ // const-string* may throw in dex. (Not in CF, see CfSourceCode.canThrowHelper)
return true;
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
index f0c9636..8ba58b7 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfDexItemBasedConstString.java
@@ -57,7 +57,7 @@
@Override
public boolean canThrow() {
- // The ldc instruction may throw in Java bytecode.
+ // const-string* may throw in dex. (Not in CF, see CfSourceCode.canThrowHelper)
return true;
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 755402d..2042392 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -267,7 +267,8 @@
encodedMethod,
graphLense.getOriginalMethodSignature(encodedMethod.method),
callerPosition,
- origin);
+ origin,
+ options.getInternalOutputMode());
return new IRBuilder(encodedMethod, appInfo, source, options, origin, generator, graphLense)
.build(context);
}
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 04e9a1b..692dba2 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
@@ -98,6 +98,9 @@
@Override
public boolean instructionInstanceCanThrow() {
+ if (throwingInfo == ThrowingInfo.NO_THROW) {
+ return false;
+ }
// The const-string instruction can be a throwing instruction in DEX, if decode() fails.
try {
value.toString();
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index fa4e6c5..c3b8f80 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
@@ -20,22 +21,27 @@
private final DexReference item;
private final ClassNameComputationInfo classNameComputationInfo;
+ private final ThrowingInfo throwingInfo;
- public DexItemBasedConstString(Value dest, DexReference item) {
- this(dest, item, ClassNameComputationInfo.none());
+ public DexItemBasedConstString(Value dest, DexReference item, ThrowingInfo throwingInfo) {
+ this(dest, item, throwingInfo, ClassNameComputationInfo.none());
}
public DexItemBasedConstString(
- Value dest, DexReference item, ClassNameComputationInfo classNameComputationInfo) {
+ Value dest,
+ DexReference item,
+ ThrowingInfo throwingInfo,
+ ClassNameComputationInfo classNameComputationInfo) {
super(dest);
dest.markNeverNull();
this.item = item;
this.classNameComputationInfo = classNameComputationInfo;
+ this.throwingInfo = throwingInfo;
}
public static DexItemBasedConstString copyOf(Value newValue, DexItemBasedConstString original) {
return new DexItemBasedConstString(
- newValue, original.getItem(), original.classNameComputationInfo);
+ newValue, original.getItem(), original.throwingInfo, original.classNameComputationInfo);
}
public DexReference getItem() {
@@ -88,7 +94,7 @@
@Override
public boolean instructionTypeCanThrow() {
- return true;
+ return throwingInfo == ThrowingInfo.CAN_THROW;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index a745659..2c10fbd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -30,6 +30,7 @@
import com.android.tools.r8.ir.conversion.CfState.Snapshot;
import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOutputMode;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@@ -196,13 +197,15 @@
private Int2ObjectMap<DebugLocalInfo> outgoingLocals;
private Int2ReferenceMap<CfState.Snapshot> incomingState = new Int2ReferenceOpenHashMap<>();
private final CanonicalPositions canonicalPositions;
+ private final InternalOutputMode internalOutputMode;
public CfSourceCode(
CfCode code,
DexEncodedMethod method,
DexMethod originalMethod,
Position callerPosition,
- Origin origin) {
+ Origin origin,
+ InternalOutputMode internalOutputMode) {
this.code = code;
this.method = method;
this.origin = origin;
@@ -218,6 +221,7 @@
}
this.state = new CfState(origin);
canonicalPositions = new CanonicalPositions(callerPosition, cfPositionCount, originalMethod);
+ this.internalOutputMode = internalOutputMode;
}
@Override
@@ -249,8 +253,9 @@
// Utility method that treats constant strings as not throwing in the case of having CF output.
// This is the only instruction that differ in throwing between DEX and CF. If we find more
// consider rewriting CfInstruction.canThrow to take in options.
- private boolean canThrowHelper(IRBuilder builder, CfInstruction instruction) {
- if (builder.isGeneratingClassFiles() && instruction.isConstString()) {
+ private boolean canThrowHelper(CfInstruction instruction) {
+ if (internalOutputMode.isGeneratingClassFiles()
+ && (instruction.isConstString() || instruction.isDexItemBasedConstString())) {
return false;
}
return instruction.canThrow();
@@ -259,7 +264,8 @@
@Override
public int traceInstruction(int instructionIndex, IRBuilder builder) {
CfInstruction instruction = code.getInstructions().get(instructionIndex);
- if (canThrowHelper(builder, instruction)) {
+ assert builder.isGeneratingClassFiles() == internalOutputMode.isGeneratingClassFiles();
+ if (canThrowHelper(instruction)) {
TryHandlerList tryHandlers = getTryHandlers(instructionIndex);
if (!tryHandlers.isEmpty()) {
// Ensure the block starts at the start of the try-range (don't enqueue, not a target).
@@ -433,7 +439,7 @@
assert currentBlockInfo != null;
setLocalVariableLists();
- if (canThrowHelper(builder, instruction)) {
+ if (canThrowHelper(instruction)) {
Snapshot exceptionTransfer =
state.getSnapshot().exceptionTransfer(builder.getFactory().throwableType);
for (int target : currentBlockInfo.exceptionalSuccessors) {
@@ -632,7 +638,7 @@
@Override
public boolean verifyCurrentInstructionCanThrow() {
- return code.getInstructions().get(currentInstructionIndex).canThrow();
+ return canThrowHelper(code.getInstructions().get(currentInstructionIndex));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 37b3d1b..31b0ec6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -1147,11 +1147,14 @@
add(instruction);
}
+ private ThrowingInfo throwingInfoForConstStrings() {
+ return options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
+ }
+
public void addConstString(int dest, DexString string) {
TypeLatticeElement typeLattice =
TypeLatticeElement.stringClassType(appInfo, definitelyNotNull());
- ThrowingInfo throwingInfo =
- options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
+ ThrowingInfo throwingInfo = throwingInfoForConstStrings();
add(new ConstString(writeRegister(dest, typeLattice, throwingInfo), string, throwingInfo));
}
@@ -1159,8 +1162,9 @@
assert method.getOptimizationInfo().useIdentifierNameString();
TypeLatticeElement typeLattice =
TypeLatticeElement.stringClassType(appInfo, definitelyNotNull());
- Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
- DexItemBasedConstString instruction = new DexItemBasedConstString(out, item);
+ ThrowingInfo throwingInfo = throwingInfoForConstStrings();
+ Value out = writeRegister(dest, typeLattice, throwingInfo);
+ DexItemBasedConstString instruction = new DexItemBasedConstString(out, item, throwingInfo);
add(instruction);
}
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 5d2daed..5bf9154 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
@@ -194,7 +194,7 @@
options.processCovariantReturnTypeAnnotations
? new CovariantReturnTypeAnnotationTransformer(this, appInfo.dexItemFactory)
: null;
- this.stringOptimizer = new StringOptimizer(appInfo, options);
+ this.stringOptimizer = new StringOptimizer(appInfo, options.getInternalOutputMode());
this.enableWholeProgramOptimizations = appView != null;
if (enableWholeProgramOptimizations) {
assert appInfo.hasLiveness();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 0ff9331..fd326d9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -32,7 +32,7 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOutputMode;
import com.google.common.annotations.VisibleForTesting;
import java.util.Set;
import java.util.function.BiFunction;
@@ -44,11 +44,11 @@
private final DexItemFactory factory;
private final ThrowingInfo throwingInfo;
- public StringOptimizer(AppInfo appInfo, InternalOptions options) {
+ public StringOptimizer(AppInfo appInfo, InternalOutputMode outputMode) {
this.appInfo = appInfo;
this.factory = appInfo.dexItemFactory;
- throwingInfo = options.isGeneratingClassFiles()
- ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
+ this.throwingInfo =
+ outputMode.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
}
// int String#length()
@@ -194,8 +194,12 @@
String name = null;
if (invokedMethod == factory.classMethods.getName) {
if (code.options.enableMinification && !rootSet.noObfuscation.contains(holder)) {
- deferred = new DexItemBasedConstString(
- invoke.outValue(), baseType, new ClassNameComputationInfo(NAME, arrayDepth));
+ deferred =
+ new DexItemBasedConstString(
+ invoke.outValue(),
+ baseType,
+ throwingInfo,
+ new ClassNameComputationInfo(NAME, arrayDepth));
} else {
name = computeClassName(descriptor, holder, NAME, arrayDepth);
}
@@ -218,6 +222,7 @@
new DexItemBasedConstString(
invoke.outValue(),
baseType,
+ throwingInfo,
new ClassNameComputationInfo(CANONICAL_NAME, arrayDepth));
} else {
name = computeClassName(descriptor, holder, CANONICAL_NAME, arrayDepth);
@@ -234,8 +239,12 @@
continue;
}
if (code.options.enableMinification && !rootSet.noObfuscation.contains(holder)) {
- deferred = new DexItemBasedConstString(
- invoke.outValue(), baseType, new ClassNameComputationInfo(SIMPLE_NAME, arrayDepth));
+ deferred =
+ new DexItemBasedConstString(
+ invoke.outValue(),
+ baseType,
+ throwingInfo,
+ new ClassNameComputationInfo(SIMPLE_NAME, arrayDepth));
} else {
name = computeClassName(descriptor, holder, SIMPLE_NAME, arrayDepth);
}
@@ -313,8 +322,8 @@
code.createValue(
TypeLatticeElement.stringClassType(appInfo, definitelyNotNull()),
invoke.getLocalInfo());
- ConstString nullString = new ConstString(
- nullStringValue, factory.createString("null"), throwingInfo);
+ ConstString nullString =
+ new ConstString(nullStringValue, factory.createString("null"), throwingInfo);
it.replaceCurrentInstruction(nullString);
} else if (inType.nullability().isDefinitelyNotNull()
&& inType.isClassType()
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 95fcfd2..0638816 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.graph.DexValue.DexItemBasedValueString;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
import com.android.tools.r8.ir.code.DexItemBasedConstString;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
@@ -87,6 +88,8 @@
if (!code.hasConstString) {
return;
}
+ final ThrowingInfo throwingInfo =
+ options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.NO_THROW;
DexType originHolder = code.method.method.getHolder();
ListIterator<BasicBlock> blocks = code.listIterator();
while (blocks.hasNext()) {
@@ -131,7 +134,8 @@
iterator.previous();
// Prepare $decoupled just before $fieldPut
Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
- DexItemBasedConstString decoupled = new DexItemBasedConstString(newIn, itemBasedString);
+ DexItemBasedConstString decoupled =
+ new DexItemBasedConstString(newIn, itemBasedString, throwingInfo);
decoupled.setPosition(fieldPut.getPosition());
// If the current block has catch handler, split into two blocks.
// Because const-string we're about to add is also a throwing instr, we need to split
@@ -191,7 +195,8 @@
iterator.previous();
// Prepare $decoupled just before $invoke
Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
- DexItemBasedConstString decoupled = new DexItemBasedConstString(newIn, itemBasedString);
+ DexItemBasedConstString decoupled =
+ new DexItemBasedConstString(newIn, itemBasedString, throwingInfo);
decoupled.setPosition(invoke.getPosition());
changes[positionOfIdentifier] = newIn;
// If the current block has catch handler, split into two blocks.
@@ -238,7 +243,7 @@
// Prepare $decoupled just before $invoke
Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
DexItemBasedConstString decoupled =
- new DexItemBasedConstString(newIn, itemBasedString);
+ new DexItemBasedConstString(newIn, itemBasedString, throwingInfo);
decoupled.setPosition(invoke.getPosition());
changes[i] = newIn;
// If the current block has catch handler, split into two blocks.
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index 69d65ea..2276790 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -150,7 +150,7 @@
private void writeFileNow(String name, ByteDataView content, DiagnosticsHandler handler) {
try {
- ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.STORED);
+ ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.DEFLATED);
} catch (IOException e) {
handleIOException(e, handler);
}
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 31a76ab..84b6675 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -190,11 +190,11 @@
}
}
- ProgramResource mergeClassFiles(List<ProgramResource> dexFiles, Path out) throws Throwable {
+ AndroidApp mergeClassFiles(List<ProgramResource> dexFiles, Path out) throws Throwable {
return mergeClassFiles(dexFiles, out, OutputMode.DexIndexed);
}
- ProgramResource mergeClassFiles(
+ AndroidApp mergeClassFiles(
List<ProgramResource> dexFiles, Path outputPath, OutputMode outputMode) throws Throwable {
Builder builder = D8Command.builder();
for (ProgramResource dexFile : dexFiles) {
@@ -215,7 +215,7 @@
try {
AndroidApp app = ToolHelper.runD8(builder, this::combinedOptionConsumer);
assert app.getDexProgramResourcesForTesting().size() == 1;
- return app.getDexProgramResourcesForTesting().get(0);
+ return app;
} catch (Unimplemented | CompilationError | InternalCompilerError re) {
throw re;
} catch (RuntimeException re) {
@@ -249,13 +249,16 @@
Assert.assertArrayEquals(readResource(entry.getValue()), readResource(otherResource));
}
- ProgramResource mergedFromCompiledSeparately =
+ AndroidApp mergedFromCompiledSeparately =
test.mergeClassFiles(Lists.newArrayList(compiledSeparately.values()), null);
- ProgramResource mergedFromCompiledTogether =
+ AndroidApp mergedFromCompiledTogether =
test.mergeClassFiles(Lists.newArrayList(compiledTogether.values()), null);
+
+ // TODO(b/123504206): Add a main method and test the output runs.
+
Assert.assertArrayEquals(
- readResource(mergedFromCompiledSeparately),
- readResource(mergedFromCompiledTogether));
+ readResource(mergedFromCompiledSeparately.getDexProgramResourcesForTesting().get(0)),
+ readResource(mergedFromCompiledTogether.getDexProgramResourcesForTesting().get(0)));
}
@Test
@@ -269,16 +272,24 @@
D8IncrementalTestRunner test = test(testName, testPackage, mainClass);
test.withInterfaceMethodDesugaring(OffOrAuto.Auto);
- ProgramResource mergedFromCompiledSeparately =
+ AndroidApp mergedFromCompiledSeparately =
test.mergeClassFiles(
Lists.newArrayList(test.compileClassesSeparately(inputJarFile).values()), null);
- ProgramResource mergedFromCompiledTogether =
+ AndroidApp mergedFromCompiledTogether =
test.mergeClassFiles(
Lists.newArrayList(test.compileClassesTogether(inputJarFile, null).values()), null);
+ Path out1 = temp.newFolder().toPath().resolve("out-together.zip");
+ mergedFromCompiledTogether.writeToZip(out1, OutputMode.DexIndexed);
+ ToolHelper.runArtNoVerificationErrors(out1.toString(), testPackage + "." + mainClass);
+
+ Path out2 = temp.newFolder().toPath().resolve("out-separate.zip");
+ mergedFromCompiledSeparately.writeToZip(out2, OutputMode.DexIndexed);
+ ToolHelper.runArtNoVerificationErrors(out2.toString(), testPackage + "." + mainClass);
+
Assert.assertArrayEquals(
- readResource(mergedFromCompiledSeparately),
- readResource(mergedFromCompiledTogether));
+ readResource(mergedFromCompiledSeparately.getDexProgramResourcesForTesting().get(0)),
+ readResource(mergedFromCompiledTogether.getDexProgramResourcesForTesting().get(0)));
}
@Test
@@ -292,16 +303,19 @@
D8IncrementalTestRunner test = test(testName, testPackage, mainClass);
test.withInterfaceMethodDesugaring(OffOrAuto.Auto);
- ProgramResource mergedFromCompiledSeparately =
+ AndroidApp mergedFromCompiledSeparately =
test.mergeClassFiles(
Lists.newArrayList(test.compileClassesSeparately(inputJarFile).values()), null);
- ProgramResource mergedFromCompiledTogether =
+ AndroidApp mergedFromCompiledTogether =
test.mergeClassFiles(
Lists.newArrayList(test.compileClassesTogether(inputJarFile, null).values()), null);
+ // TODO(b/123504206): This test throws an index out of bounds exception.
+ // Re-write or verify running fails in the expected way.
+
Assert.assertArrayEquals(
- readResource(mergedFromCompiledSeparately),
- readResource(mergedFromCompiledTogether));
+ readResource(mergedFromCompiledSeparately.getDexProgramResourcesForTesting().get(0)),
+ readResource(mergedFromCompiledTogether.getDexProgramResourcesForTesting().get(0)));
}
@Test
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 1df6808..7493eba 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -903,18 +903,22 @@
return extractor.getClassInternalType();
}
- protected Path writeToJar(List<byte[]> classes) throws IOException {
- Path result = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
+ protected static void writeToJar(Path output, List<byte[]> classes) throws IOException {
try (ZipOutputStream out =
new ZipOutputStream(
Files.newOutputStream(
- result, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
+ output, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
for (byte[] clazz : classes) {
String name = extractClassName(clazz);
ZipUtils.writeToZipStream(
out, DescriptorUtils.getPathFromJavaType(name), clazz, ZipEntry.STORED);
}
}
+ }
+
+ protected Path writeToJar(List<byte[]> classes) throws IOException {
+ Path result = File.createTempFile("junit", ".jar", temp.getRoot()).toPath();
+ writeToJar(result, classes);
return result;
}
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 fb61f45..1c0287e 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.naming.MemberNaming;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.MemberNaming.Signature;
+import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.TestDescriptionWatcher;
@@ -50,6 +51,7 @@
import org.apache.harmony.jpda.tests.framework.jdwp.EventPacket;
import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ObjectReferenceCommandSet;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.ReferenceTypeCommandSet;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands.StackFrameCommandSet;
import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
@@ -153,6 +155,12 @@
}
protected final void runDebugTest(
+ DebugTestConfig config, Class<?> debuggeeClass, JUnit3Wrapper.Command... commands)
+ throws Throwable {
+ runInternal(config, debuggeeClass.getTypeName(), Arrays.asList(commands));
+ }
+
+ protected final void runDebugTest(
DebugTestConfig config, String debuggeeClass, JUnit3Wrapper.Command... commands)
throws Throwable {
runInternal(config, debuggeeClass, Arrays.asList(commands));
@@ -324,6 +332,10 @@
return new JUnit3Wrapper.Command.RunCommand();
}
+ protected final JUnit3Wrapper.Command breakpoint(MethodReference method) {
+ return breakpoint(method.getHolderClass().getTypeName(), method.getMethodName());
+ }
+
protected final JUnit3Wrapper.Command breakpoint(String className, String methodName) {
return breakpoint(className, methodName, null);
}
@@ -477,6 +489,10 @@
});
}
+ protected final JUnit3Wrapper.Command checkLine(int line) {
+ return inspect(t -> t.checkLine(null, line));
+ }
+
protected final JUnit3Wrapper.Command checkLine(String sourceFile, int line) {
return inspect(t -> t.checkLine(sourceFile, line));
}
@@ -532,6 +548,16 @@
});
}
+ protected final JUnit3Wrapper.Command checkFieldOnThis(
+ String fieldName, String fieldSignature, Value expectedValue) {
+ return inspect(
+ t -> {
+ Value value = t.getFieldOnThis(fieldName, fieldSignature);
+ Assert.assertEquals(
+ "Incorrect value for field 'this." + fieldName + "'", expectedValue, value);
+ });
+ }
+
protected final JUnit3Wrapper.Command inspect(Consumer<JUnit3Wrapper.DebuggeeState> inspector) {
return t -> inspector.accept(t.debuggeeState);
}
@@ -1298,6 +1324,7 @@
@Override
public void checkLine(String sourceFile, int line) {
+ sourceFile = sourceFile != null ? sourceFile : getSourceFile();
if (!Objects.equals(sourceFile, getSourceFile()) || line != getLineNumber()) {
String locationString = convertCurrentLocationToString();
Assert.fail(
@@ -1501,7 +1528,15 @@
// The class is available, lookup and read the field.
long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
- return getField(getMirror(), classId, fieldId);
+ return internalStaticField(getMirror(), classId, fieldId);
+ }
+
+ public Value getFieldOnThis(String fieldName, String fieldSignature) {
+ long thisObjectId = getMirror().getThisObject(getThreadId(), getFrameId());
+ long classId = getMirror().getReferenceType(thisObjectId);
+ // TODO(zerny): Search supers too. This will only get the field if directly on the class.
+ long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
+ return internalInstanceField(getMirror(), thisObjectId, fieldId);
}
private long findField(VmMirror mirror, long classId, String fieldName,
@@ -1547,10 +1582,10 @@
return matchingFieldIds.getLong(0);
}
- private Value getField(VmMirror mirror, long classId, long fieldId) {
-
- CommandPacket commandPacket = new CommandPacket(ReferenceTypeCommandSet.CommandSetID,
- ReferenceTypeCommandSet.GetValuesCommand);
+ private static Value internalStaticField(VmMirror mirror, long classId, long fieldId) {
+ CommandPacket commandPacket =
+ new CommandPacket(
+ ReferenceTypeCommandSet.CommandSetID, ReferenceTypeCommandSet.GetValuesCommand);
commandPacket.setNextValueAsReferenceTypeID(classId);
commandPacket.setNextValueAsInt(1);
commandPacket.setNextValueAsFieldID(fieldId);
@@ -1565,6 +1600,23 @@
}
}
+ private static Value internalInstanceField(VmMirror mirror, long objectId, long fieldId) {
+ CommandPacket commandPacket =
+ new CommandPacket(
+ ObjectReferenceCommandSet.CommandSetID, ObjectReferenceCommandSet.GetValuesCommand);
+ commandPacket.setNextValueAsObjectID(objectId);
+ commandPacket.setNextValueAsInt(1);
+ commandPacket.setNextValueAsFieldID(fieldId);
+ ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+ assert replyPacket.getErrorCode() == Error.NONE;
+
+ int fieldsCount = replyPacket.getNextValueAsInt();
+ assert fieldsCount == 1;
+ Value result = replyPacket.getNextValueAsValue();
+ Assert.assertTrue(replyPacket.isAllDataRead());
+ return result;
+ }
+
public static Optional<Variable> getVariableAt(VmMirror mirror, Location location,
String localName) {
return getVariablesAt(mirror, location).stream()
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 17d5077..c398e8b 100644
--- a/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/ExamplesDebugTest.java
@@ -255,7 +255,8 @@
@Test
public void testRegress62300145() throws Exception {
- testDebugging("regress_62300145", "Regress");
+ // TODO(b/67936230): Executes differently for Java 8 and 9, so don't compare to DEX output.
+ testDebuggingJvmOnly("regress_62300145", "Regress");
}
@Test
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java
new file mode 100644
index 0000000..9991b57
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTest.java
@@ -0,0 +1,32 @@
+// 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.debug;
+
+public class LambdaOuterContextTest {
+
+ public interface Converter {
+ String convert(int value);
+ }
+
+ public int outer;
+
+ public LambdaOuterContextTest(int outer) {
+ this.outer = outer;
+ }
+
+ public void foo(Converter converter) {
+ System.out.println(converter.convert(outer));
+ }
+
+ public void bar(int arg) {
+ foo(value -> {
+ // Ensure lambda uses parts of the outer context, otherwise javac will optimize it out.
+ return Integer.toString(outer + value + arg);
+ });
+ }
+
+ public static void main(String[] args) {
+ new LambdaOuterContextTest(args.length + 42).bar(args.length);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
new file mode 100644
index 0000000..8af2a6e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaOuterContextTestRunner.java
@@ -0,0 +1,67 @@
+// 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.debug;
+
+import static com.android.tools.r8.references.Reference.methodFromMethod;
+
+import com.android.tools.r8.D8TestCompileResult;
+import com.android.tools.r8.JvmTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.debug.LambdaOuterContextTest.Converter;
+import com.android.tools.r8.utils.StringUtils;
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class LambdaOuterContextTestRunner extends DebugTestBase {
+
+ public static final Class<?> CLASS = LambdaOuterContextTest.class;
+ public static final String EXPECTED = StringUtils.lines("84");
+
+ @Test
+ public void testJvm() throws Throwable {
+ JvmTestBuilder jvmTestBuilder = testForJvm().addTestClasspath();
+ jvmTestBuilder.run(CLASS).assertSuccessWithOutput(EXPECTED);
+ runDebugger(jvmTestBuilder.debugConfig());
+ }
+
+ @Test
+ @Ignore("b/123068053")
+ public void testD8() throws Throwable {
+ D8TestCompileResult compileResult =
+ testForD8().addProgramClassesAndInnerClasses(CLASS).compile();
+ compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
+ runDebugger(compileResult.debugConfig());
+ }
+
+ @Test
+ public void testR8Cf() throws Throwable {
+ R8TestCompileResult compileResult =
+ testForR8(Backend.CF)
+ .addProgramClassesAndInnerClasses(CLASS)
+ .debug()
+ .noMinification()
+ .noTreeShaking()
+ .compile();
+ compileResult.run(CLASS).assertSuccessWithOutput(EXPECTED);
+ runDebugger(compileResult.debugConfig());
+ }
+
+ private void runDebugger(DebugTestConfig config) throws Throwable {
+ runDebugTest(
+ config,
+ CLASS,
+ breakpoint(methodFromMethod(CLASS.getMethod("foo", Converter.class))),
+ run(),
+ checkLine(19),
+ checkLocals("this", "converter"),
+ checkFieldOnThis("outer", null, Value.createInt(42)),
+ stepInto(INTELLIJ_FILTER),
+ checkLine(25),
+ checkLocals("this", "value", "arg"),
+ checkNoLocal("outer"),
+ checkFieldOnThis("outer", null, Value.createInt(42)),
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaTest.java b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
index 1b413ba..f9e6c34 100644
--- a/src/test/java/com/android/tools/r8/debug/LambdaTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
@@ -13,7 +13,6 @@
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
-// TODO(shertz) test local variables
@RunWith(Parameterized.class)
public class LambdaTest extends DebugTestBase {
@@ -47,8 +46,11 @@
run(),
checkMethod(debuggeeClass, initialMethodName),
checkLine(SOURCE_FILE, 12),
+ checkLocals("i"),
+ checkNoLocal("j"),
stepInto(INTELLIJ_FILTER),
checkLine(SOURCE_FILE, 16),
+ checkLocals("i", "j"),
run());
}
@@ -63,8 +65,10 @@
run(),
checkMethod(debuggeeClass, initialMethodName),
checkLine(SOURCE_FILE, 32),
+ checkLocals("i", "a", "b"),
stepInto(INTELLIJ_FILTER),
checkLine(SOURCE_FILE, 37),
+ checkLocals("a", "b"),
run());
}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTest.java
new file mode 100644
index 0000000..0da8519
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTest.java
@@ -0,0 +1,404 @@
+// 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.naming.applymapping;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+class LibraryClass {
+ static String LIB_MSG = "LibraryClass::foo";
+ void foo() {
+ System.out.println(LIB_MSG);
+ }
+}
+
+class AnotherLibraryClass {
+ static String ANOTHERLIB_MSG = "AnotherLibraryClass::foo";
+ void foo() {
+ System.out.println(ANOTHERLIB_MSG);
+ }
+}
+
+class ProgramClass extends LibraryClass {
+ static String PRG_MSG = "ProgramClass::bar";
+ void bar() {
+ System.out.println(PRG_MSG);
+ }
+
+ public static void main(String[] args) {
+ new AnotherLibraryClass().foo();
+ ProgramClass instance = new ProgramClass();
+ instance.foo();
+ instance.bar();
+ }
+}
+
+public class NameClashTest extends TestBase {
+
+ @ClassRule
+ public static TemporaryFolder temporaryFolder = ToolHelper.getTemporaryFolderForTest();
+
+ private static Class<?> MAIN = ProgramClass.class;
+ private static String EXPECTED_OUTPUT =
+ StringUtils.lines(
+ AnotherLibraryClass.ANOTHERLIB_MSG, LibraryClass.LIB_MSG, ProgramClass.PRG_MSG);
+
+ private static Path prgJarThatUsesOriginalLib;
+ private static Path prgJarThatUsesMinifiedLib;
+ private static Path libJar;
+ private Path mappingFile;
+
+ @BeforeClass
+ public static void setUpJars() throws Exception {
+ prgJarThatUsesOriginalLib =
+ temporaryFolder.newFile("prgOrginalLib.jar").toPath().toAbsolutePath();
+ writeToJar(prgJarThatUsesOriginalLib, ImmutableList.of(ToolHelper.getClassAsBytes(MAIN)));
+ prgJarThatUsesMinifiedLib =
+ temporaryFolder.newFile("prgMinifiedLib.jar").toPath().toAbsolutePath();
+ writeToJar(prgJarThatUsesMinifiedLib, ImmutableList.of(ProgramClassDump.dump()));
+ libJar = temporaryFolder.newFile("lib.jar").toPath().toAbsolutePath();
+ writeToJar(libJar, ImmutableList.of(
+ ToolHelper.getClassAsBytes(LibraryClass.class),
+ ToolHelper.getClassAsBytes(AnotherLibraryClass.class)));
+ }
+
+ @Before
+ public void setUpMappingFile() throws Exception {
+ mappingFile = temp.newFile("mapping.txt").toPath().toAbsolutePath();
+ }
+
+ private String invertedMapping() {
+ return StringUtils.lines(
+ "A -> " + LibraryClass.class.getTypeName() + ":",
+ " void a() -> foo",
+ "B -> " + AnotherLibraryClass.class.getTypeName() + ":",
+ " void a() -> foo"
+ );
+ }
+
+ // Note that all the test mappings below still need identity mappings for classes/memebers that
+ // are not renamed, for some reasons:
+ // 1) to mimic how R8 generates the mapping, where identity mappings are used in the same way.
+ // 2) otherwise, those classes/members will be renamed if minification is enabled, resulting in
+ // no name clash, which is definitely not intended.
+
+ private String mappingToExistingClassName() {
+ return StringUtils.lines(
+ LibraryClass.class.getTypeName()
+ + " -> " + AnotherLibraryClass.class.getTypeName() + ":",
+ AnotherLibraryClass.class.getTypeName()
+ + " -> " + AnotherLibraryClass.class.getTypeName() + ":"
+ );
+ }
+
+ private String mappingToTheSameClassName() {
+ return StringUtils.lines(
+ LibraryClass.class.getTypeName() + " -> Clash:",
+ AnotherLibraryClass.class.getTypeName() + " -> Clash:"
+ );
+ }
+
+ private String mappingToExistingMethodName() {
+ return StringUtils.lines(
+ LibraryClass.class.getTypeName() + " -> A:",
+ " void foo() -> bar",
+ AnotherLibraryClass.class.getTypeName() + " -> B:",
+ ProgramClass.class.getTypeName() + " -> " + ProgramClass.class.getTypeName() + ":",
+ " void bar() -> bar"
+ );
+ }
+
+ private String mappingToTheSameMethodName() {
+ return StringUtils.lines(
+ LibraryClass.class.getTypeName() + " -> A:",
+ " void foo() -> clash",
+ AnotherLibraryClass.class.getTypeName() + " -> B:",
+ " void foo() -> clash",
+ ProgramClass.class.getTypeName() + " -> " + ProgramClass.class.getTypeName() + ":",
+ " void bar() -> clash"
+ );
+ }
+
+ private void testProguard_inputJar(Path mappingFile) throws Exception {
+ testForProguard()
+ .addProgramFiles(libJar)
+ .addProgramFiles(prgJarThatUsesOriginalLib)
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-applymapping " + mappingFile)
+ .noTreeShaking()
+ .compile()
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void testR8_inputJar(Path mappingFile) throws Exception {
+ testForR8(Backend.DEX)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .addProgramFiles(libJar)
+ .addProgramFiles(prgJarThatUsesOriginalLib)
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-applymapping " + mappingFile)
+ .noTreeShaking()
+ .compile()
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void testProguard_originalLibraryJar(Path mappingFile) throws Exception {
+ testForProguard()
+ .addLibraryFiles(libJar)
+ .addProgramFiles(prgJarThatUsesOriginalLib)
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-applymapping " + mappingFile)
+ .noTreeShaking()
+ .compile()
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void testR8_originalLibraryJar(Path mappingFile) throws Exception {
+ testForR8(Backend.DEX)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar(), libJar)
+ .addProgramFiles(prgJarThatUsesOriginalLib)
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-applymapping " + mappingFile)
+ .noTreeShaking()
+ .compile()
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void testProguard_minifiedLibraryJar(Path mappingFile) throws Exception {
+ testForProguard()
+ .addLibraryFiles(ToolHelper.getJava8RuntimeJar(), libJar)
+ .addProgramFiles(prgJarThatUsesMinifiedLib)
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-applymapping " + mappingFile)
+ .noTreeShaking()
+ .compile()
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void testR8_minifiedLibraryJar(Path mappingFile) throws Exception {
+ testForR8(Backend.DEX)
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar(), libJar)
+ .addProgramFiles(prgJarThatUsesMinifiedLib)
+ .addKeepMainRule(MAIN)
+ .addKeepRules("-applymapping " + mappingFile)
+ .noTreeShaking()
+ .compile()
+ .run(MAIN)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testProguard_prgClassRenamedToExistingPrgClass() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingClassName());
+ try {
+ testProguard_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("Duplicate jar entry"));
+ assertThat(e.getMessage(), containsString("AnotherLibraryClass.class"));
+ }
+ }
+
+ @Test
+ public void testR8_prgClassRenamedToExistingPrgClass() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingClassName());
+ try {
+ testR8_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getCause().getMessage(), containsString("Program type already present"));
+ assertThat(e.getCause().getMessage(), containsString("AnotherLibraryClass"));
+ }
+ }
+
+ @Test
+ public void testProguard_originalLibClassRenamedToExistingLibClass() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingClassName());
+ try {
+ testProguard_originalLibraryJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("can't find referenced method"));
+ assertThat(e.getMessage(), containsString("ProgramClass"));
+ }
+ }
+
+ @Ignore("b/123092153")
+ @Test
+ public void testR8_originalLibClassRenamedToExistingLibClass() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingClassName());
+ testR8_originalLibraryJar(mappingFile);
+ }
+
+ @Test
+ public void testProguard_prgClassesRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameClassName());
+ try {
+ testProguard_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("Duplicate jar entry [Clash.class]"));
+ }
+ }
+
+ @Test
+ public void testR8_prgClassesRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameClassName());
+ try {
+ testR8_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getCause().getMessage(), containsString("Program type already present"));
+ assertThat(e.getCause().getMessage(), containsString("Clash"));
+ }
+ }
+
+ @Test
+ public void testProguard_originalLibClassesRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameClassName());
+ try {
+ testProguard_originalLibraryJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("can't find referenced method"));
+ assertThat(e.getMessage(), containsString("ProgramClass"));
+ }
+ }
+
+ @Ignore("b/123092153")
+ @Test
+ public void testR8_originalLibClassesRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameClassName());
+ testR8_originalLibraryJar(mappingFile);
+ }
+
+ @Test
+ public void testProguard_prgMethodRenamedToExistingName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingMethodName());
+ try {
+ testProguard_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("method 'void bar()' can't be mapped to 'bar'"));
+ assertThat(e.getMessage(), containsString("it would conflict with method 'foo'"));
+ assertThat(e.getMessage(), containsString("which is already being mapped to 'bar'"));
+ }
+ }
+
+ @Ignore("b/123092153")
+ @Test
+ public void testR8_prgMethodRenamedToExistingName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingMethodName());
+ try {
+ testR8_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("method 'void bar()' can't be mapped to 'bar'"));
+ assertThat(e.getMessage(), containsString("it would conflict with method 'foo'"));
+ assertThat(e.getMessage(), containsString("which is already being mapped to 'bar'"));
+ }
+ }
+
+ @Test
+ public void testProguard_originalLibMethodRenamedToExistingName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingMethodName());
+ try {
+ testProguard_originalLibraryJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("can't find referenced method"));
+ assertThat(e.getMessage(), containsString("ProgramClass"));
+ }
+ }
+
+ @Ignore("b/123092153")
+ @Test
+ public void testR8_originalLibMethodRenamedToExistingName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToExistingMethodName());
+ testR8_originalLibraryJar(mappingFile);
+ }
+
+ @Test
+ public void testProguard_prgMethodRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameMethodName());
+ try {
+ testProguard_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("method 'void bar()' can't be mapped to 'clash'"));
+ assertThat(e.getMessage(), containsString("it would conflict with method 'foo'"));
+ assertThat(e.getMessage(), containsString("which is already being mapped to 'clash'"));
+ }
+ }
+
+ @Ignore("b/123092153")
+ @Test
+ public void testR8_prgMethodRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameMethodName());
+ try {
+ testR8_inputJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("method 'void bar()' can't be mapped to 'bar'"));
+ assertThat(e.getMessage(), containsString("it would conflict with method 'foo'"));
+ assertThat(e.getMessage(), containsString("which is already being mapped to 'bar'"));
+ }
+ }
+
+ @Test
+ public void testProguard_originalLibMethodRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameMethodName());
+ try {
+ testProguard_originalLibraryJar(mappingFile);
+ fail("Expect compilation failure.");
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("can't find referenced method"));
+ assertThat(e.getMessage(), containsString("ProgramClass"));
+ }
+ }
+
+ @Ignore("b/123092153")
+ @Test
+ public void testR8_originalLibMethodRenamedToSameName() throws Exception {
+ FileUtils.writeTextFile(mappingFile, mappingToTheSameMethodName());
+ testR8_originalLibraryJar(mappingFile);
+ }
+
+ @Test
+ public void testProguard_minifiedLib() throws Exception {
+ FileUtils.writeTextFile(mappingFile, invertedMapping());
+ try {
+ testProguard_minifiedLibraryJar(mappingFile);
+ } catch (CompilationFailedException e) {
+ assertThat(e.getMessage(), containsString("can't find superclass or interface A"));
+ }
+ }
+
+ @Ignore("b/121305642")
+ @Test
+ public void testR8_minifiedLib() throws Exception {
+ FileUtils.writeTextFile(mappingFile, invertedMapping());
+ testR8_minifiedLibraryJar(mappingFile);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTestDump.java b/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTestDump.java
new file mode 100644
index 0000000..7da3800
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/NameClashTestDump.java
@@ -0,0 +1,131 @@
+// 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.naming.applymapping;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+// asmified the following:
+// class ProgramClass extends LibraryClass {
+// static String PRG_MSG = "ProgramClass::bar";
+// void bar() {
+// System.out.println(PRG_MSG);
+// }
+// public static void main(String[] args) {
+// new AnotherLibraryClass().foo();
+// ProgramClass instance = new ProgramClass();
+// instance.foo();
+// instance.bar();
+// }
+// }
+//
+// then replaced the use of LibraryClass and AnotherLibraryClass with A and B so that providing
+// LibraryClass and AnotherLibraryClass, along with mapping, as a minified library:
+// A -> LibraryClass:
+// void a() -> foo
+// B -> AnotherLibraryClass:
+// void a() -> foo
+class ProgramClassDump implements Opcodes {
+
+ public static byte[] dump () throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ FieldVisitor fieldVisitor;
+ MethodVisitor methodVisitor;
+
+ String pkg = LibraryClass.class.getPackage().getName();
+
+ classWriter.visit(V1_8, ACC_SUPER, pkg.replace('.', '/') + "/ProgramClass", null, "A", null);
+
+ classWriter.visitSource("Test.java", null);
+
+ {
+ fieldVisitor = classWriter.visitField(
+ ACC_STATIC, "PRG_MSG", "Ljava/lang/String;", null, null);
+ fieldVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(15, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "A", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(0, "bar", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(18, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitFieldInsn(GETSTATIC, "ProgramClass", "PRG_MSG", "Ljava/lang/String;");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(19, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(22, label0);
+ methodVisitor.visitTypeInsn(NEW, "B");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "B", "<init>", "()V", false);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "B", "a", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(23, label1);
+ methodVisitor.visitTypeInsn(NEW, "ProgramClass");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "ProgramClass", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(24, label2);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "ProgramClass", "a", "()V", false);
+ Label label3 = new Label();
+ methodVisitor.visitLabel(label3);
+ methodVisitor.visitLineNumber(25, label3);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "ProgramClass", "bar", "()V", false);
+ Label label4 = new Label();
+ methodVisitor.visitLabel(label4);
+ methodVisitor.visitLineNumber(26, label4);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(16, label0);
+ methodVisitor.visitLdcInsn("ProgramClass::bar");
+ methodVisitor.visitFieldInsn(PUTSTATIC, "ProgramClass", "PRG_MSG", "Ljava/lang/String;");
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 0);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java
new file mode 100644
index 0000000..25497e3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/WhenToApplyTest.java
@@ -0,0 +1,138 @@
+// 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.naming.applymapping;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ProguardTestRunResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+class ToBeRenamedClass {
+ void toBeRenamedMethod() {
+ System.out.println("foo");
+ }
+}
+
+class WhenToApplyTestRunner {
+ public static void main(String[] args) {
+ ToBeRenamedClass instance = new ToBeRenamedClass();
+ instance.toBeRenamedMethod();
+ System.out.println(instance.getClass().getSimpleName());
+ }
+}
+
+@RunWith(Parameterized.class)
+public class WhenToApplyTest extends TestBase {
+
+ @ClassRule
+ public static TemporaryFolder temporaryFolder = ToolHelper.getTemporaryFolderForTest();
+
+ private static Class<?> MAIN = WhenToApplyTestRunner.class;
+ private static String RENAMED_CLASS_NAME =
+ ToBeRenamedClass.class.getPackage().getName() + ".ABC";
+ private static String NORMAL_OUTPUT = StringUtils.lines("foo", "ToBeRenamedClass");
+ private static String APPLIED_OUTPUT = StringUtils.lines("foo", "ABC");
+ private static String RENAMED_OUTPUT = StringUtils.lines("foo", "a");
+
+ private static Path mappingFile;
+ private static Path configuration;
+ private boolean minification;
+
+ @Parameterized.Parameters(name = "minification: {0}")
+ public static Boolean[] data() {
+ return BooleanUtils.values();
+ }
+
+ public WhenToApplyTest(boolean minification) {
+ this.minification = minification;
+ }
+
+ @BeforeClass
+ public static void setUpMappingFile() throws Exception {
+ mappingFile = temporaryFolder.newFile("mapping.txt").toPath().toAbsolutePath();
+ FileUtils.writeTextFile(mappingFile, StringUtils.lines(
+ ToBeRenamedClass.class.getTypeName() + " -> " + RENAMED_CLASS_NAME + ":",
+ " void toBeRenamedMethod() -> abc"));
+ configuration = temporaryFolder.newFile("pg.conf").toPath().toAbsolutePath();
+ FileUtils.writeTextFile(configuration, StringUtils.lines(
+ "-dontoptimize",
+ "-applymapping " + mappingFile
+ ));
+ }
+
+ @Test
+ public void testProguard() throws Exception {
+ ProguardTestRunResult result = testForProguard()
+ .addProgramClasses(ToBeRenamedClass.class, MAIN)
+ .addKeepMainRule(MAIN)
+ .addKeepRuleFiles(configuration)
+ .minification(minification)
+ .compile().run(MAIN);
+ if (minification) {
+ result.assertSuccessWithOutput(APPLIED_OUTPUT);
+ } else {
+ result.assertSuccessWithOutput(NORMAL_OUTPUT);
+ }
+ result.inspect(inspector -> {
+ ClassSubject classSubject = inspector.clazz(ToBeRenamedClass.class);
+ assertThat(classSubject, isPresent());
+ // As renaming won't happen again, we can use the original name to search for the method.
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName("toBeRenamedMethod");
+ assertThat(methodSubject, isPresent());
+ String methodName =
+ minification
+ ? "abc" // mapped name with minification
+ : "toBeRenamedMethod"; // original name without minification
+ assertEquals(methodName, methodSubject.getFinalName());
+ });
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8TestRunResult result = testForR8(Backend.DEX)
+ .addProgramClasses(ToBeRenamedClass.class, MAIN)
+ .addKeepMainRule(MAIN)
+ .addKeepRuleFiles(configuration)
+ .minification(minification)
+ .compile().run(MAIN);
+ if (minification) {
+ result.assertSuccessWithOutput(RENAMED_OUTPUT);
+ } else {
+ result.assertSuccessWithOutput(APPLIED_OUTPUT);
+ }
+ result.inspect(inspector -> {
+ ClassSubject classSubject = inspector.clazz(RENAMED_CLASS_NAME);
+ assertThat(classSubject, isPresent());
+ // Mapped name will be regarded as an original name if minification is disabled.
+ String methodName =
+ minification
+ ? "toBeRenamedMethod" // original name
+ : "abc"; // mapped name without minification
+ MethodSubject methodSubject = classSubject.uniqueMethodWithName(methodName);
+ assertThat(methodSubject, isPresent());
+ methodName =
+ minification
+ ? "a" // minified name
+ : "abc"; // mapped name without minification
+ assertEquals(methodName, methodSubject.getFinalName());
+ });
+ }
+
+}
diff --git a/tools/apk_masseur.py b/tools/apk_masseur.py
index fa46485..be380ed 100755
--- a/tools/apk_masseur.py
+++ b/tools/apk_masseur.py
@@ -74,7 +74,16 @@
def align(signed_apk, temp, quiet):
utils.Print('Aligning', quiet=quiet)
aligned_apk = os.path.join(temp, 'aligned.apk')
- cmd = ['zipalign', '-f', '4', signed_apk, aligned_apk]
+ zipalign_path = (
+ 'zipalign' if 'build_tools' in os.environ.get('PATH')
+ else os.path.join(utils.ANDROID_BUILD_TOOLS, 'zipalign'))
+ cmd = [
+ zipalign_path,
+ '-f',
+ '4',
+ signed_apk,
+ aligned_apk
+ ]
utils.RunCmd(cmd, quiet=quiet)
return signed_apk
diff --git a/tools/run_bootstrap_benchmark.py b/tools/run_bootstrap_benchmark.py
index ad26959..9f2f7e4 100755
--- a/tools/run_bootstrap_benchmark.py
+++ b/tools/run_bootstrap_benchmark.py
@@ -60,11 +60,11 @@
sys.exit(return_code)
dex(r8_output, d8_r8_output)
- print "BootstrapR8(CodeSize):", os.path.getsize(r8_output)
- print "BootstrapR8Dex(CodeSize):", os.path.getsize(d8_r8_output)
+ print "BootstrapR8(CodeSize):", utils.uncompressed_size(r8_output)
+ print "BootstrapR8Dex(CodeSize):", utils.uncompressed_size(d8_r8_output)
dex(PINNED_PGR8_JAR, d8_pg_output)
- print "BootstrapR8PG(CodeSize):", os.path.getsize(PINNED_PGR8_JAR)
- print "BootstrapR8PGDex(CodeSize):", os.path.getsize(d8_pg_output)
+ print "BootstrapR8PG(CodeSize):", utils.uncompressed_size(PINNED_PGR8_JAR)
+ print "BootstrapR8PGDex(CodeSize):", utils.uncompressed_size(d8_pg_output)
sys.exit(0)
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 2454863..aacfbe9 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -75,6 +75,11 @@
'app_id': 'org.schabi.newpipe',
'git_repo': 'https://github.com/christofferqa/NewPipe',
},
+ 'rover-android': {
+ 'app_id': 'io.rover.app.debug',
+ 'app_module': 'debug-app',
+ 'git_repo': 'https://github.com/mkj-gram/rover-android.git',
+ },
'Signal-Android': {
'app_id': 'org.thoughtcrime.securesms',
'app_module': '',
@@ -117,13 +122,6 @@
},
}
-# Common environment setup.
-user_home = os.path.expanduser('~')
-android_home = os.path.join(user_home, 'Android', 'Sdk')
-android_build_tools_version = '28.0.3'
-android_build_tools = os.path.join(
- android_home, 'build-tools', android_build_tools_version)
-
# TODO(christofferqa): Do not rely on 'emulator-5554' name
emulator_id = 'emulator-5554'
@@ -375,7 +373,7 @@
app, config, checkout_dir, proguard_config_dest)
env = {}
- env['ANDROID_HOME'] = android_home
+ env['ANDROID_HOME'] = utils.ANDROID_HOME
env['JAVA_OPTS'] = '-ea:com.android.tools.r8...'
releaseTarget = config.get('releaseTarget')
@@ -420,7 +418,7 @@
keystore = 'app.keystore'
keystore_password = 'android'
apk_utils.sign_with_apksigner(
- android_build_tools,
+ utils.ANDROID_BUILD_TOOLS,
unsigned_apk,
signed_apk,
keystore,
diff --git a/tools/utils.py b/tools/utils.py
index b39a168..ad85089 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -12,6 +12,7 @@
import sys
import tarfile
import tempfile
+import zipfile
ANDROID_JAR_DIR = 'third_party/android_jar/lib-v{api}'
ANDROID_JAR = os.path.join(ANDROID_JAR_DIR, 'android.jar')
@@ -54,6 +55,13 @@
R8LIB_KEEP_RULES = os.path.join(REPO_ROOT, 'src/main/keep.txt')
RETRACE_JAR = os.path.join(THIRD_PARTY, 'proguard', 'proguard6.0.1', 'lib', 'retrace.jar')
+# Common environment setup.
+USER_HOME = os.path.expanduser('~')
+ANDROID_HOME = os.path.join(USER_HOME, 'Android', 'Sdk')
+ANDROID_BUILD_TOOLS_VERSION = '28.0.3'
+ANDROID_BUILD_TOOLS = os.path.join(
+ ANDROID_HOME, 'build-tools', ANDROID_BUILD_TOOLS_VERSION)
+
def Print(s, quiet=False):
if quiet:
return
@@ -405,3 +413,9 @@
def is_bot():
return 'BUILDBOT_BUILDERNAME' in os.environ
+
+
+def uncompressed_size(path):
+ return sum(z.file_size for z in zipfile.ZipFile(path).infolist())
+
+