Merge "Parse <n> wildcard in members' names and type descriptors."
diff --git a/build.gradle b/build.gradle
index f857f9e..2cf3fe1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -36,7 +36,7 @@
joptSimpleVersion = '4.6'
jsonSimpleVersion = '1.1'
junitVersion = '4.12'
- kotlinVersion = '1.2.0'
+ kotlinVersion = '1.2.30'
protobufVersion = '3.0.0'
smaliVersion = '2.2b4'
}
@@ -300,7 +300,7 @@
"android_jar/lib-v25",
"android_jar/lib-v26",
"proguard/proguard5.2.1",
- "proguard/proguard6.0",
+ "proguard/proguard6.0.1",
"gradle/gradle",
"jdwp-tests",
"jasmin",
@@ -347,6 +347,7 @@
def x20Dependencies = [
"third_party": [
+ "benchmarks/santa-tracker",
"gmail/gmail_android_170604.16",
"gmscore/v4",
"gmscore/v5",
@@ -356,6 +357,7 @@
"gmscore/gmscore_v9",
"gmscore/gmscore_v10",
"gmscore/latest",
+ "gradle-plugin",
"photos/2017-06-06",
"youtube/youtube.android_12.10",
"youtube/youtube.android_12.17",
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
new file mode 100644
index 0000000..1df78be
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -0,0 +1,463 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import com.android.tools.r8.code.Const;
+import com.android.tools.r8.code.Const16;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ConstHigh16;
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.ConstStringJumbo;
+import com.android.tools.r8.code.ConstWide16;
+import com.android.tools.r8.code.ConstWide32;
+import com.android.tools.r8.code.FillArrayData;
+import com.android.tools.r8.code.FillArrayDataPayload;
+import com.android.tools.r8.code.Format35c;
+import com.android.tools.r8.code.Format3rc;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.InvokeDirect;
+import com.android.tools.r8.code.InvokeDirectRange;
+import com.android.tools.r8.code.InvokeInterface;
+import com.android.tools.r8.code.InvokeInterfaceRange;
+import com.android.tools.r8.code.InvokeStatic;
+import com.android.tools.r8.code.InvokeStaticRange;
+import com.android.tools.r8.code.InvokeSuper;
+import com.android.tools.r8.code.InvokeSuperRange;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.code.InvokeVirtualRange;
+import com.android.tools.r8.code.NewArray;
+import com.android.tools.r8.code.Sget;
+import com.android.tools.r8.code.SgetBoolean;
+import com.android.tools.r8.code.SgetByte;
+import com.android.tools.r8.code.SgetChar;
+import com.android.tools.r8.code.SgetObject;
+import com.android.tools.r8.code.SgetShort;
+import com.android.tools.r8.code.SgetWide;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.ir.code.SingleConstant;
+import com.android.tools.r8.ir.code.WideConstant;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Streams;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Stream;
+
+/**
+ * This class is deprecated and should not be used. It is a temporary solution to use R8 to analyze
+ * dex files and compute resource shrinker related bits.
+ *
+ * <p>Users or this API should implement {@link ReferenceChecker} interface, and through callbacks
+ * in that interface, they will be notified when element relevant for resource shrinking is found.
+ *
+ * <p>This class extracts all integer constants and string constants, which might refer to resource.
+ * More specifically, we look for the following while analyzing dex:
+ * <ul>
+ * <li>const instructions that might load integers or strings
+ * <li>static fields that have an initial value. This initial value might be integer, string,
+ * or array of integers.
+ * <li>integer array payloads. Only payloads referenced in fill-array-data instructions will be
+ * processed. More specifically, if a payload is referenced in fill-array-data, and we are able
+ * to determine that array is not array of integers, payload will be ignored. Otherwise, it will
+ * be processed once fill-array-data-payload instruction is encountered.
+ * <li>all annotations (class, field, method) that contain annotation element whose value is
+ * integer, string or array of integers are processed.
+ * </ul>
+ *
+ * <p>Please note that switch payloads are not analyzed. Although they might contain integer
+ * constants, ones referring to resource ids would have to be loaded in the code analyzed in list
+ * above.
+ *
+ * <p>Usage of this feature is intentionally not supported from the command line.
+ */
+@Deprecated
+final public class ResourceShrinker {
+
+ final static class Command extends BaseCommand {
+
+ Command(AndroidApp app) {
+ super(app);
+ }
+
+ @Override
+ InternalOptions getInternalOptions() {
+ return new InternalOptions();
+ }
+ }
+
+ final public static class Builder extends BaseCommand.Builder<Command, Builder> {
+
+ @Override
+ Builder self() {
+ return this;
+ }
+
+ @Override
+ Command makeCommand() {
+ return new Command(getAppBuilder().build());
+ }
+ }
+
+ /**
+ * Classes that would like to process data relevant to resource shrinking should implement this
+ * interface.
+ */
+ interface ReferenceChecker {
+
+ /**
+ * Returns if the class with specified internal name should be processed. Typically,
+ * resource type classes like R$drawable, R$styleable etc. should be skipped.
+ */
+ boolean shouldProcess(String internalName);
+
+ void referencedInt(int value);
+
+ void referencedString(String value);
+
+ void referencedStaticField(String internalName, String fieldName);
+
+ void referencedMethod(String internalName, String methodName, String methodDescriptor);
+ }
+
+ private static final class DexClassUsageVisitor {
+
+ private final DexProgramClass classDef;
+ private final ReferenceChecker callback;
+
+ DexClassUsageVisitor(DexProgramClass classDef, ReferenceChecker callback) {
+ this.classDef = classDef;
+ this.callback = callback;
+ }
+
+ public void visit() {
+ if (!callback.shouldProcess(classDef.type.getInternalName())) {
+ return;
+ }
+
+ for (DexEncodedField field : classDef.staticFields()) {
+ if (field.staticValue != null) {
+ processFieldValue(field.staticValue);
+ }
+ }
+
+ for (DexEncodedMethod method : classDef.allMethodsSorted()) {
+ processMethod(method);
+ }
+
+ if (classDef.hasAnnotations()) {
+ processAnnotations(classDef);
+ }
+ }
+
+ private void processFieldValue(DexValue value) {
+ if (value instanceof DexValue.DexValueString) {
+ callback.referencedString(((DexValue.DexValueString) value).value.toString());
+ } else if (value instanceof DexValue.DexValueInt) {
+ int constantValue = ((DexValue.DexValueInt) value).getValue();
+ callback.referencedInt(constantValue);
+ } else if (value instanceof DexValue.DexValueArray) {
+ DexValue.DexValueArray arrayEncodedValue = (DexValue.DexValueArray) value;
+ for (DexValue encodedValue : arrayEncodedValue.getValues()) {
+ if (encodedValue instanceof DexValue.DexValueInt) {
+ int constantValue = ((DexValue.DexValueInt) encodedValue).getValue();
+ callback.referencedInt(constantValue);
+ }
+ }
+ }
+ }
+
+ private void processMethod(DexEncodedMethod method) {
+ Code implementation = method.getCode();
+ if (implementation != null) {
+
+ // Tracks the offsets of integer array payloads.
+ final Set<Integer> methodIntArrayPayloadOffsets = Sets.newHashSet();
+ // First we collect payloads, and then we process them because payload can be before the
+ // fill-array-data instruction referencing it.
+ final List<FillArrayDataPayload> payloads = Lists.newArrayList();
+
+ Instruction[] instructions = implementation.asDexCode().instructions;
+ int current = 0;
+ while (current < instructions.length) {
+ Instruction instruction = instructions[current];
+ if (isIntConstInstruction(instruction)) {
+ processIntConstInstruction(instruction);
+ } else if (isStringConstInstruction(instruction)) {
+ processStringConstantInstruction(instruction);
+ } else if (isGetStatic(instruction)) {
+ processGetStatic(instruction);
+ } else if (isInvokeInstruction(instruction)) {
+ processInvokeInstruction(instruction);
+ } else if (isInvokeRangeInstruction(instruction)) {
+ processInvokeRangeInstruction(instruction);
+ } else if (instruction instanceof FillArrayData) {
+ processFillArray(instructions, current, methodIntArrayPayloadOffsets);
+ } else if (instruction instanceof FillArrayDataPayload) {
+ payloads.add((FillArrayDataPayload) instruction);
+ }
+ current++;
+ }
+
+ for (FillArrayDataPayload payload : payloads) {
+ if (isIntArrayPayload(payload, methodIntArrayPayloadOffsets)) {
+ processIntArrayPayload(payload);
+ }
+ }
+ }
+ }
+
+ private void processAnnotations(DexProgramClass classDef) {
+ Stream<DexAnnotation> instanceFieldAnnotations =
+ Arrays.stream(classDef.instanceFields())
+ .filter(DexEncodedField::hasAnnotation)
+ .flatMap(f -> Arrays.stream(f.annotations.annotations));
+ Stream<DexAnnotation> staticFieldAnnotations =
+ Arrays.stream(classDef.staticFields())
+ .filter(DexEncodedField::hasAnnotation)
+ .flatMap(f -> Arrays.stream(f.annotations.annotations));
+ Stream<DexAnnotation> virtualMethodAnnotations =
+ Arrays.stream(classDef.virtualMethods())
+ .filter(DexEncodedMethod::hasAnnotation)
+ .flatMap(m -> Arrays.stream(m.annotations.annotations));
+ Stream<DexAnnotation> directMethodAnnotations =
+ Arrays.stream(classDef.directMethods())
+ .filter(DexEncodedMethod::hasAnnotation)
+ .flatMap(m -> Arrays.stream(m.annotations.annotations));
+ Stream<DexAnnotation> classAnnotations = Arrays.stream(classDef.annotations.annotations);
+
+ Streams.concat(
+ instanceFieldAnnotations,
+ staticFieldAnnotations,
+ virtualMethodAnnotations,
+ directMethodAnnotations,
+ classAnnotations)
+ .forEach(annotation -> {
+ for (DexAnnotationElement element : annotation.annotation.elements) {
+ DexValue value = element.value;
+ processAnnotationValue(value);
+ }
+ });
+ }
+
+ private void processIntArrayPayload(Instruction instruction) {
+ FillArrayDataPayload payload = (FillArrayDataPayload) instruction;
+
+ for (int i = 0; i < payload.data.length / 2; i++) {
+ int intValue = payload.data[2 * i + 1] << 16 | payload.data[2 * i];
+ callback.referencedInt(intValue);
+ }
+ }
+
+ private boolean isIntArrayPayload(
+ Instruction instruction, Set<Integer> methodIntArrayPayloadOffsets) {
+ if (!(instruction instanceof FillArrayDataPayload)) {
+ return false;
+ }
+
+ FillArrayDataPayload payload = (FillArrayDataPayload) instruction;
+ return methodIntArrayPayloadOffsets.contains(payload.getOffset());
+ }
+
+ private void processFillArray(
+ Instruction[] instructions, int current, Set<Integer> methodIntArrayPayloadOffsets) {
+ FillArrayData fillArrayData = (FillArrayData) instructions[current];
+ if (current > 0 && instructions[current - 1] instanceof NewArray) {
+ NewArray newArray = (NewArray) instructions[current - 1];
+ if (!Objects.equals(newArray.getType().descriptor.toString(), "[I")) {
+ return;
+ }
+ // Typically, new-array is right before fill-array-data. If not, assume referenced array is
+ // of integers. This can be improved later, but for now we make sure no ints are missed.
+ }
+
+ methodIntArrayPayloadOffsets.add(fillArrayData.getPayloadOffset() + fillArrayData.offset);
+ }
+
+ private void processAnnotationValue(DexValue value) {
+ if (value instanceof DexValue.DexValueInt) {
+ DexValue.DexValueInt dexValueInt = (DexValue.DexValueInt) value;
+ callback.referencedInt(dexValueInt.value);
+ } else if (value instanceof DexValue.DexValueString) {
+ DexValue.DexValueString dexValueString = (DexValue.DexValueString) value;
+ callback.referencedString(dexValueString.value.toString());
+ } else if (value instanceof DexValue.DexValueArray) {
+ DexValue.DexValueArray dexValueArray = (DexValue.DexValueArray) value;
+ for (DexValue dexValue : dexValueArray.getValues()) {
+ processAnnotationValue(dexValue);
+ }
+ } else if (value instanceof DexValue.DexValueAnnotation) {
+ DexValue.DexValueAnnotation dexValueAnnotation = (DexValue.DexValueAnnotation) value;
+ for (DexAnnotationElement element : dexValueAnnotation.value.elements) {
+ processAnnotationValue(element.value);
+ }
+ }
+ }
+
+ private boolean isIntConstInstruction(Instruction instruction) {
+ int opcode = instruction.getOpcode();
+ return opcode == Const4.OPCODE
+ || opcode == Const16.OPCODE
+ || opcode == Const.OPCODE
+ || opcode == ConstWide32.OPCODE
+ || opcode == ConstHigh16.OPCODE
+ || opcode == ConstWide16.OPCODE;
+ }
+
+ private void processIntConstInstruction(Instruction instruction) {
+ assert isIntConstInstruction(instruction);
+
+ int constantValue;
+ if (instruction instanceof SingleConstant) {
+ SingleConstant singleConstant = (SingleConstant) instruction;
+ constantValue = singleConstant.decodedValue();
+ } else if (instruction instanceof WideConstant) {
+ WideConstant wideConstant = (WideConstant) instruction;
+ if (((int) wideConstant.decodedValue()) != wideConstant.decodedValue()) {
+ // We care only about values that fit in int range.
+ return;
+ }
+ constantValue = (int) wideConstant.decodedValue();
+ } else {
+ throw new AssertionError("Not an int const instruction.");
+ }
+
+ callback.referencedInt(constantValue);
+ }
+
+ private boolean isStringConstInstruction(Instruction instruction) {
+ int opcode = instruction.getOpcode();
+ return opcode == ConstString.OPCODE || opcode == ConstStringJumbo.OPCODE;
+ }
+
+ private void processStringConstantInstruction(Instruction instruction) {
+ assert isStringConstInstruction(instruction);
+
+ String constantValue;
+ if (instruction instanceof ConstString) {
+ ConstString constString = (ConstString) instruction;
+ constantValue = constString.getString().toString();
+ } else if (instruction instanceof ConstStringJumbo) {
+ ConstStringJumbo constStringJumbo = (ConstStringJumbo) instruction;
+ constantValue = constStringJumbo.getString().toString();
+ } else {
+ throw new AssertionError("Not a string constant instruction.");
+ }
+
+ callback.referencedString(constantValue);
+ }
+
+ private boolean isGetStatic(Instruction instruction) {
+ int opcode = instruction.getOpcode();
+ return opcode == Sget.OPCODE
+ || opcode == SgetBoolean.OPCODE
+ || opcode == SgetByte.OPCODE
+ || opcode == SgetChar.OPCODE
+ || opcode == SgetObject.OPCODE
+ || opcode == SgetShort.OPCODE
+ || opcode == SgetWide.OPCODE;
+ }
+
+ private void processGetStatic(Instruction instruction) {
+ assert isGetStatic(instruction);
+
+ DexField field;
+ if (instruction instanceof Sget) {
+ Sget sget = (Sget) instruction;
+ field = sget.getField();
+ } else if (instruction instanceof SgetBoolean) {
+ SgetBoolean sgetBoolean = (SgetBoolean) instruction;
+ field = sgetBoolean.getField();
+ } else if (instruction instanceof SgetByte) {
+ SgetByte sgetByte = (SgetByte) instruction;
+ field = sgetByte.getField();
+ } else if (instruction instanceof SgetChar) {
+ SgetChar sgetChar = (SgetChar) instruction;
+ field = sgetChar.getField();
+ } else if (instruction instanceof SgetObject) {
+ SgetObject sgetObject = (SgetObject) instruction;
+ field = sgetObject.getField();
+ } else if (instruction instanceof SgetShort) {
+ SgetShort sgetShort = (SgetShort) instruction;
+ field = sgetShort.getField();
+ } else if (instruction instanceof SgetWide) {
+ SgetWide sgetWide = (SgetWide) instruction;
+ field = sgetWide.getField();
+ } else {
+ throw new AssertionError("Not a get static instruction");
+ }
+
+ callback.referencedStaticField(field.clazz.getInternalName(), field.name.toString());
+ }
+
+ private boolean isInvokeInstruction(Instruction instruction) {
+ int opcode = instruction.getOpcode();
+ return opcode == InvokeVirtual.OPCODE
+ || opcode == InvokeSuper.OPCODE
+ || opcode == InvokeDirect.OPCODE
+ || opcode == InvokeStatic.OPCODE
+ || opcode == InvokeInterface.OPCODE;
+ }
+
+ private void processInvokeInstruction(Instruction instruction) {
+ assert isInvokeInstruction(instruction);
+
+ Format35c ins35c = (Format35c) instruction;
+ DexMethod method = (DexMethod) ins35c.BBBB;
+
+ callback.referencedMethod(
+ method.holder.getInternalName(),
+ method.name.toString(),
+ method.proto.toDescriptorString());
+ }
+
+ private boolean isInvokeRangeInstruction(Instruction instruction) {
+ int opcode = instruction.getOpcode();
+ return opcode == InvokeVirtualRange.OPCODE
+ || opcode == InvokeSuperRange.OPCODE
+ || opcode == InvokeDirectRange.OPCODE
+ || opcode == InvokeStaticRange.OPCODE
+ || opcode == InvokeInterfaceRange.OPCODE;
+ }
+
+ private void processInvokeRangeInstruction(Instruction instruction) {
+ assert isInvokeRangeInstruction(instruction);
+
+ Format3rc ins3rc = (Format3rc) instruction;
+ DexMethod method = (DexMethod) ins3rc.BBBB;
+
+ callback.referencedMethod(
+ method.holder.getInternalName(),
+ method.name.toString(),
+ method.proto.toDescriptorString());
+ }
+ }
+
+ public static void run(Command command, ReferenceChecker callback)
+ throws IOException, ExecutionException {
+ AndroidApp inputApp = command.getInputApp();
+ Timing timing = new Timing("resource shrinker analyzer");
+ DexApplication dexApplication =
+ new ApplicationReader(inputApp, command.getInternalOptions(), timing).read();
+ for (DexProgramClass programClass : dexApplication.classes()) {
+ new DexClassUsageVisitor(programClass, callback).visit();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/StringConsumer.java b/src/main/java/com/android/tools/r8/StringConsumer.java
index 543e8cd..9232e08 100644
--- a/src/main/java/com/android/tools/r8/StringConsumer.java
+++ b/src/main/java/com/android/tools/r8/StringConsumer.java
@@ -14,7 +14,6 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.Collections;
/** Interface for receiving String resource. */
public interface StringConsumer {
@@ -101,7 +100,7 @@
public void accept(String string, DiagnosticsHandler handler) {
super.accept(string, handler);
try {
- Files.write(outputPath, Collections.singletonList(string), encoding);
+ Files.write(outputPath, string.getBytes(encoding));
} catch (IOException e) {
handler.error(new IOExceptionDiagnostic(e, new PathOrigin(outputPath)));
}
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 670211f..e51c08a 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 = "v1.1.9-dev";
+ public static final String LABEL = "v1.2.0-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/code/CheckCast.java b/src/main/java/com/android/tools/r8/code/CheckCast.java
index d3576c1..9416a7b 100644
--- a/src/main/java/com/android/tools/r8/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/code/CheckCast.java
@@ -39,7 +39,7 @@
@Override
public void registerUse(UseRegistry registry) {
- registry.registerTypeReference(getType());
+ registry.registerCheckCast(getType());
}
public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/code/ConstClass.java b/src/main/java/com/android/tools/r8/code/ConstClass.java
index f140022..ef8b257 100644
--- a/src/main/java/com/android/tools/r8/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/code/ConstClass.java
@@ -39,7 +39,7 @@
@Override
public void registerUse(UseRegistry registry) {
- registry.registerTypeReference(getType());
+ registry.registerConstClass(getType());
}
public DexType getType() {
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index f9ea993..8933353 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -187,8 +187,14 @@
if (context != null) {
// The SecondVistor is in charge of setting the context to null.
DexProgramClass owner = context.owner;
- new ClassReader(context.classCache).accept(new SecondVisitor(context, application),
- ClassReader.SKIP_FRAMES);
+ int flags = ClassReader.SKIP_FRAMES;
+ if (application.options.isGeneratingClassFiles()) {
+ // TODO(mathiasr): Keep frames in JarCode until IR->CF construction is complete.
+ // When we throw Unimplemented in IR->CF construction, the original JarCode is output
+ // instead. In this case we must output frames as well, or we will fail verification.
+ flags = 0;
+ }
+ new ClassReader(context.classCache).accept(new SecondVisitor(context, application), flags);
assert verifyNoReparseContext(owner);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 78b2f56..48ef91d 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -27,6 +27,14 @@
public abstract boolean registerTypeReference(DexType type);
+ public boolean registerConstClass(DexType type) {
+ return registerTypeReference(type);
+ }
+
+ public boolean registerCheckCast(DexType type) {
+ return registerTypeReference(type);
+ }
+
public void registerMethodHandle(DexMethodHandle methodHandle) {
switch (methodHandle.type) {
case INSTANCE_GET:
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 1d5a2f5..cea44bc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -143,7 +144,14 @@
@Override
public DexType computeVerificationType(TypeVerificationHelper helper) {
- return helper.getType(array()).toArrayElementType(helper.getFactory());
+ // This method is not called for ArrayGet on primitive array.
+ assert this.outValue.type.isObject();
+ DexType arrayType = helper.getType(array());
+ if (arrayType == DexItemFactory.nullValueType) {
+ // JVM 8 §4.10.1.9.aaload: Array component type of null is null.
+ return arrayType;
+ }
+ return arrayType.toArrayElementType(helper.getFactory());
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index 4d18b92..d8e78e5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -62,4 +63,9 @@
// They should also have a non-empty set of debug values (see RegAlloc::computeDebugInfo)
return false;
}
+
+ @Override
+ public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+ // Non-materializing so no stack values are needed.
+ }
}
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 76a1a43..20051f9 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
@@ -396,10 +396,6 @@
// necessary.
ir.splitCriticalEdges();
- // Create block order and make sure that all blocks are immediately followed by their
- // fallthrough block if any.
- ir.traceBlocks();
-
// Clear the code so we don't build multiple times.
source.clear();
source = null;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 42b06ef..0f0b44a 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
@@ -476,6 +476,7 @@
Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s", method.toSourceString(), code);
}
assert code.isConsistentSSA();
+ code.traceBlocks();
RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
method.setCode(code, registerAllocator, options);
if (Log.ENABLED) {
@@ -635,7 +636,6 @@
if (options.testing.invertConditionals) {
invertConditionalsForTesting(code);
- code.traceBlocks();
}
if (options.enableNonNullTracking && nonNullTracker != null) {
@@ -699,6 +699,7 @@
}
private void finalizeIR(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
+ code.traceBlocks();
if (options.isGeneratingClassFiles()) {
finalizeToCf(method, code, feedback);
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 12429e9..894ba4f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -53,6 +53,7 @@
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
+import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
@@ -218,8 +219,16 @@
return (node.access & Opcodes.ACC_STATIC) > 0;
}
- private boolean isSynchronized() {
- return (node.access & Opcodes.ACC_SYNCHRONIZED) > 0;
+ /**
+ * Determine if we should emit monitor enter/exit instructions at method entry/exit.
+ *
+ * @return true if we are generating Dex and method is marked synchronized, otherwise false.
+ */
+ private boolean generateMethodSynchronization() {
+ // When generating class files, don't treat the method specially because it is synchronized.
+ // At runtime, the JVM will automatically perform the correct monitor enter/exit instructions.
+ return !application.options.isGeneratingClassFiles()
+ && (node.access & Opcodes.ACC_SYNCHRONIZED) > 0;
}
private int formalParameterCount() {
@@ -348,7 +357,7 @@
}
}
- if (isSynchronized()) {
+ if (generateMethodSynchronization()) {
generatingMethodSynchronization = true;
Type clazzType = application.getAsmType(clazz.toDescriptorString());
int monitorRegister;
@@ -450,7 +459,7 @@
@Override
public void buildPostlude(IRBuilder builder) {
- if (isSynchronized()) {
+ if (generateMethodSynchronization()) {
generatingMethodSynchronization = true;
buildMonitorExit(builder);
generatingMethodSynchronization = false;
@@ -458,7 +467,7 @@
}
private void buildExceptionalPostlude(IRBuilder builder) {
- assert isSynchronized();
+ assert generateMethodSynchronization();
generatingMethodSynchronization = true;
currentPosition = getExceptionalExitPosition();
buildMonitorExit(builder);
@@ -748,7 +757,7 @@
handlers.add(tryCatchBlock);
}
}
- if (isSynchronized()) {
+ if (generateMethodSynchronization()) {
// Add synchronized exceptional exit for synchronized-method instructions without a default.
assert handlers.isEmpty() || handlers.get(handlers.size() - 1).getType() != null;
handlers.add(EXCEPTIONAL_SYNC_EXIT);
@@ -1165,6 +1174,9 @@
case AbstractInsnNode.LINE:
updateState((LineNumberNode) insn);
break;
+ case AbstractInsnNode.FRAME:
+ updateState((FrameNode) insn);
+ break;
default:
throw new Unreachable("Unexpected instruction " + insn);
}
@@ -1786,6 +1798,11 @@
// Intentionally empty.
}
+ private void updateState(FrameNode insn) {
+ assert application.options.isGeneratingClassFiles();
+ // Intentionally empty.
+ }
+
private void updateStateForConversion(Type from, Type to) {
state.pop();
state.push(to);
@@ -1840,6 +1857,9 @@
case AbstractInsnNode.LINE:
build((LineNumberNode) insn, builder);
break;
+ case AbstractInsnNode.FRAME:
+ build((FrameNode) insn, builder);
+ break;
default:
throw new Unreachable("Unexpected instruction " + insn);
}
@@ -2296,7 +2316,7 @@
if (handlers.isEmpty()) {
return true;
}
- if (!isSynchronized() || handlers.size() > 1) {
+ if (!generateMethodSynchronization() || handlers.size() > 1) {
return false;
}
return handlers.get(0) == EXCEPTIONAL_SYNC_EXIT;
@@ -2908,6 +2928,11 @@
builder.addDebugPosition(currentPosition);
}
+ private void build(FrameNode insn, IRBuilder builder) {
+ assert application.options.isGeneratingClassFiles();
+ // Intentionally empty.
+ }
+
@Override
public Position getDebugPositionAtOffset(int offset) {
if (offset == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 3d1f79d..9379a83 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -180,9 +180,6 @@
continue;
}
- // This is a private instance method call. Note that the referenced method
- // is expected to be in the current class since it is private, but desugaring
- // may move some methods or their code into other classes.
DexClass clazz = findDefinitionFor(method.holder);
if (clazz == null) {
// Report missing class since we don't know if it is an interface.
@@ -195,9 +192,32 @@
getMethodOrigin(encodedMethod.method));
}
- instructions.replaceCurrentInstruction(
- new InvokeStatic(privateAsMethodOfCompanionClass(method),
- invokeDirect.outValue(), invokeDirect.arguments()));
+
+ // This might be either private method call, or a call to default
+ // interface method made via invoke-direct.
+ DexEncodedMethod virtualTarget = null;
+ for (DexEncodedMethod candidate : clazz.virtualMethods()) {
+ if (candidate.method == method) {
+ virtualTarget = candidate;
+ break;
+ }
+ }
+
+ if (virtualTarget != null) {
+ // This is a invoke-direct call to a virtual method.
+ instructions.replaceCurrentInstruction(
+ new InvokeStatic(defaultAsMethodOfCompanionClass(method),
+ invokeDirect.outValue(), invokeDirect.arguments()));
+
+ } else {
+ // Otherwise this must be a private instance method call. Note that the referenced
+ // method is expected to be in the current class since it is private, but desugaring
+ // may move some methods or their code into other classes.
+
+ instructions.replaceCurrentInstruction(
+ new InvokeStatic(privateAsMethodOfCompanionClass(method),
+ invokeDirect.outValue(), invokeDirect.arguments()));
+ }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index a6b29e8..30e9bae4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -565,7 +565,6 @@
// being split on the way in but does not maintain this property. We therefore split
// critical edges at exit.
code.splitCriticalEdges();
- code.traceBlocks();
assert code.isConsistentSSA();
}
@@ -2028,9 +2027,6 @@
}
code.removeMarkedBlocks(color);
code.returnMarkingColor(color);
- if (ifBranchFlipped) {
- code.traceBlocks();
- }
assert code.isConsistentSSA();
}
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index acf4395..85e0060 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -54,6 +54,8 @@
import java.util.PriorityQueue;
import java.util.Set;
import java.util.TreeSet;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
/**
* Linear scan register allocator.
@@ -1348,10 +1350,17 @@
// free position.
int candidate = getLargestValidCandidate(
unhandledInterval, registerConstraint, needsRegisterPair, freePositions, Type.ANY);
- assert candidate != REGISTER_CANDIDATE_NOT_FOUND;
- int largestFreePosition = freePositions.get(candidate);
- if (needsRegisterPair) {
- largestFreePosition = Math.min(largestFreePosition, freePositions.get(candidate + 1));
+
+ // It is not always possible to find a largest valid candidate. If none of the usable register
+ // are free we typically get the last candidate. However, if that candidate has to be
+ // discarded in order to workaround bugs we get REGISTER_CANDIDATE_NOT_FOUND. In both cases
+ // we need to spill a valid candidate. That path is triggered when largestFreePosition is 0.
+ int largestFreePosition = 0;
+ if (candidate != REGISTER_CANDIDATE_NOT_FOUND) {
+ largestFreePosition = freePositions.get(candidate);
+ if (needsRegisterPair) {
+ largestFreePosition = Math.min(largestFreePosition, freePositions.get(candidate + 1));
+ }
}
// Determine what to do based on how long the selected candidate is free.
@@ -1554,57 +1563,47 @@
return candidate;
}
+ private int handleWorkaround(
+ Predicate<LiveIntervals> workaroundNeeded,
+ BiPredicate<LiveIntervals, Integer> workaroundNeededForCandidate,
+ int candidate, LiveIntervals unhandledInterval, int registerConstraint,
+ boolean needsRegisterPair, RegisterPositions freePositions, RegisterPositions.Type type) {
+ if (workaroundNeeded.test(unhandledInterval)) {
+ int lastCandidate = candidate;
+ while (workaroundNeededForCandidate.test(unhandledInterval, candidate)) {
+ // Make the unusable register unavailable for allocation and try again.
+ freePositions.set(candidate, 0);
+ candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
+ // If there are only invalid candidates of the give type we will end up with the same
+ // candidate returned again once we have tried them all. In that case we didn't find a
+ // valid register candidate and we need to broaden the search to other types.
+ if (lastCandidate == candidate) {
+ return REGISTER_CANDIDATE_NOT_FOUND;
+ }
+ lastCandidate = candidate;
+ }
+ }
+ return candidate;
+ }
+
private int getLargestValidCandidate(LiveIntervals unhandledInterval, int registerConstraint,
boolean needsRegisterPair, RegisterPositions freePositions, RegisterPositions.Type type) {
int candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
if (candidate == REGISTER_CANDIDATE_NOT_FOUND) {
return candidate;
}
- if (needsLongResultOverlappingLongOperandsWorkaround(unhandledInterval)) {
- int lastCandidate = candidate;
- while (isLongResultOverlappingLongOperands(unhandledInterval, candidate)) {
- // Make the overlapping register unavailable for allocation and try again.
- freePositions.set(candidate, 0);
- candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
- // If there are only invalid candidates of the give type we will end up with the same
- // candidate returned again once we have tried them all. In that case we didn't find a
- // valid register candidate and we need to broaden the search to other types.
- if (lastCandidate == candidate) {
- return REGISTER_CANDIDATE_NOT_FOUND;
- }
- lastCandidate = candidate;
- }
- }
- if (needsSingleResultOverlappingLongOperandsWorkaround(unhandledInterval)) {
- int lastCandidate = candidate;
- while (isSingleResultOverlappingLongOperands(unhandledInterval, candidate)) {
- // Make the overlapping register unavailable for allocation and try again.
- freePositions.set(candidate, 0);
- candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
- // If there are only invalid candidates of the give type we will end up with the same
- // candidate returned again once we have tried them all. In that case we didn't find a
- // valid register candidate and we need to broaden the search to other types.
- if (lastCandidate == candidate) {
- return REGISTER_CANDIDATE_NOT_FOUND;
- }
- lastCandidate = candidate;
- }
- }
- if (needsArrayGetWideWorkaround(unhandledInterval)) {
- int lastCandidate = candidate;
- while (isArrayGetArrayRegister(unhandledInterval, candidate)) {
- // Make the overlapping register unavailable for allocation and try again.
- freePositions.set(candidate, 0);
- candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
- // If there are only invalid candidates of the give type we will end up with the same
- // candidate returned again once we have tried them all. In that case we didn't find a
- // valid register candidate and we need to broaden the search to other types.
- if (lastCandidate == candidate) {
- return REGISTER_CANDIDATE_NOT_FOUND;
- }
- lastCandidate = candidate;
- }
- }
+ candidate = handleWorkaround(
+ this::needsLongResultOverlappingLongOperandsWorkaround,
+ this::isLongResultOverlappingLongOperands,
+ candidate, unhandledInterval, registerConstraint, needsRegisterPair, freePositions, type);
+ candidate = handleWorkaround(
+ this::needsSingleResultOverlappingLongOperandsWorkaround,
+ this::isSingleResultOverlappingLongOperands,
+ candidate, unhandledInterval, registerConstraint, needsRegisterPair, freePositions, type);
+ candidate = handleWorkaround(
+ this::needsArrayGetWideWorkaround,
+ this::isArrayGetArrayRegister,
+ candidate, unhandledInterval, registerConstraint, needsRegisterPair, freePositions, type);
return candidate;
}
diff --git a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
index f9a7f49..37c9aea 100644
--- a/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/JarRegisterEffectsVisitor.java
@@ -35,6 +35,8 @@
DexType type = application.getTypeFromName(name);
if (opcode == org.objectweb.asm.Opcodes.NEW) {
registry.registerNewInstance(type);
+ } else if (opcode == Opcodes.CHECKCAST) {
+ registry.registerCheckCast(type);
} else {
registry.registerTypeReference(type);
}
@@ -51,7 +53,7 @@
// Nothing to register for method type, it represents only a prototype not associated with a
// method name.
if (((Type) cst).getSort() != Type.METHOD) {
- registry.registerTypeReference(application.getType((Type) cst));
+ registry.registerConstClass(application.getType((Type) cst));
}
} else if (cst instanceof Handle) {
registerMethodHandleType((Handle) cst);
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
index 1fa8aaa..9e85188 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNameMinifier.java
@@ -32,8 +32,8 @@
this.dictionary = options.proguardConfiguration.getObfuscationDictionary();
this.useUniqueMemberNames = options.proguardConfiguration.isUseUniqueClassMemberNames();
this.overloadAggressively = options.proguardConfiguration.isOverloadAggressively();
- this.globalState =
- NamingState.createRoot(appInfo.dexItemFactory, dictionary, getKeyTransform());
+ this.globalState = NamingState.createRoot(
+ appInfo.dexItemFactory, dictionary, getKeyTransform(), useUniqueMemberNames);
}
abstract Function<StateType, ?> getKeyTransform();
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 936a06a..ce37763 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -143,22 +143,34 @@
private void assignNamesToClassesMethods(DexType type, boolean doPrivates) {
DexClass holder = appInfo.definitionFor(type);
if (holder != null && !holder.isLibraryClass()) {
+ Map<DexMethod, DexString> renamingAtThisLevel = new IdentityHashMap<>();
NamingState<DexProto, ?> state =
computeStateIfAbsent(type, k -> getState(holder.superType).createChild());
- holder.forEachMethod(method -> assignNameToMethod(method, state, doPrivates));
+ holder.forEachMethod(method ->
+ assignNameToMethod(method, state, renamingAtThisLevel, doPrivates));
+ if (!doPrivates && !useUniqueMemberNames) {
+ renamingAtThisLevel.forEach((method, candidate) ->
+ state.addRenaming(method.name, method.proto, candidate));
+ }
}
type.forAllExtendsSubtypes(subtype -> assignNamesToClassesMethods(subtype, doPrivates));
}
private void assignNameToMethod(
- DexEncodedMethod encodedMethod, NamingState<DexProto, ?> state, boolean doPrivates) {
+ DexEncodedMethod encodedMethod,
+ NamingState<DexProto, ?> state,
+ Map<DexMethod, DexString> renamingAtThisLevel,
+ boolean doPrivates) {
if (encodedMethod.accessFlags.isPrivate() != doPrivates) {
return;
}
DexMethod method = encodedMethod.method;
if (!state.isReserved(method.name, method.proto)
&& !encodedMethod.accessFlags.isConstructor()) {
- renaming.put(method, state.assignNewNameFor(method.name, method.proto, !doPrivates));
+ DexString renamedName =
+ state.assignNewNameFor(method.name, method.proto, useUniqueMemberNames);
+ renaming.put(method, renamedName);
+ renamingAtThisLevel.put(method, renamedName);
}
}
@@ -331,7 +343,8 @@
computeStateIfAbsent(
libraryFrontier,
ignore -> parent == null
- ? NamingState.createRoot(appInfo.dexItemFactory, dictionary, getKeyTransform())
+ ? NamingState.createRoot(
+ appInfo.dexItemFactory, dictionary, getKeyTransform(), useUniqueMemberNames)
: parent.createChild());
if (holder != null) {
boolean keepAll = holder.isLibraryClass() || holder.accessFlags.isAnnotation();
diff --git a/src/main/java/com/android/tools/r8/naming/NamingState.java b/src/main/java/com/android/tools/r8/naming/NamingState.java
index b3ff544..abfa3c1 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingState.java
@@ -7,9 +7,11 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.utils.StringUtils;
-import com.google.common.collect.HashBiMap;
+import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
+import com.google.common.collect.Table;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -20,80 +22,86 @@
class NamingState<ProtoType extends CachedHashValueDexItem, KeyType> {
private final NamingState<ProtoType, KeyType> parent;
- private final Map<KeyType, InternalState> usedNames = new HashMap<>();
+ private final Map<KeyType, InternalState<ProtoType>> usedNames = new HashMap<>();
private final DexItemFactory itemFactory;
private final ImmutableList<String> dictionary;
private final Function<ProtoType, KeyType> keyTransform;
+ private final boolean useUniqueMemberNames;
static <S, T extends CachedHashValueDexItem> NamingState<T, S> createRoot(
- DexItemFactory itemFactory, ImmutableList<String> dictionary, Function<T, S> keyTransform) {
- return new NamingState<>(null, itemFactory, dictionary, keyTransform);
+ DexItemFactory itemFactory,
+ ImmutableList<String> dictionary,
+ Function<T, S> keyTransform,
+ boolean useUniqueMemberNames) {
+ return new NamingState<>(null, itemFactory, dictionary, keyTransform, useUniqueMemberNames);
}
private NamingState(
NamingState<ProtoType, KeyType> parent,
DexItemFactory itemFactory,
ImmutableList<String> dictionary,
- Function<ProtoType, KeyType> keyTransform) {
+ Function<ProtoType, KeyType> keyTransform,
+ boolean useUniqueMemberNames) {
this.parent = parent;
this.itemFactory = itemFactory;
this.dictionary = dictionary;
this.keyTransform = keyTransform;
+ this.useUniqueMemberNames = useUniqueMemberNames;
}
public NamingState<ProtoType, KeyType> createChild() {
- return new NamingState<>(this, itemFactory, dictionary, keyTransform);
+ return new NamingState<>(this, itemFactory, dictionary, keyTransform, useUniqueMemberNames);
}
- private InternalState findInternalStateFor(ProtoType proto) {
+ private InternalState<ProtoType> findInternalStateFor(ProtoType proto) {
KeyType key = keyTransform.apply(proto);
- InternalState result = usedNames.get(key);
+ InternalState<ProtoType> result = usedNames.get(key);
if (result == null && parent != null) {
result = parent.findInternalStateFor(proto);
}
return result;
}
- private InternalState getOrCreateInternalStateFor(ProtoType proto) {
+ private InternalState<ProtoType> getOrCreateInternalStateFor(ProtoType proto) {
// TODO(herhut): Maybe allocate these sparsely and search via state chain.
KeyType key = keyTransform.apply(proto);
- InternalState result = usedNames.get(key);
+ InternalState<ProtoType> result = usedNames.get(key);
if (result == null) {
if (parent != null) {
- InternalState parentState = parent.getOrCreateInternalStateFor(proto);
+ InternalState<ProtoType> parentState = parent.getOrCreateInternalStateFor(proto);
result = parentState.createChild();
} else {
- result = new InternalState(itemFactory, null, dictionary);
+ result = new InternalState<>(itemFactory, null, dictionary);
}
usedNames.put(key, result);
}
return result;
}
- public DexString getAssignedNameFor(DexString name, ProtoType proto) {
- InternalState state = findInternalStateFor(proto);
+ private DexString getAssignedNameFor(DexString name, ProtoType proto) {
+ InternalState<ProtoType> state = findInternalStateFor(proto);
if (state == null) {
return null;
}
- return state.getAssignedNameFor(name);
+ return state.getAssignedNameFor(name, proto);
}
public DexString assignNewNameFor(DexString original, ProtoType proto, boolean markAsUsed) {
DexString result = getAssignedNameFor(original, proto);
if (result == null) {
- InternalState state = getOrCreateInternalStateFor(proto);
- result = state.getNameFor(original, markAsUsed);
+ InternalState<ProtoType> state = getOrCreateInternalStateFor(proto);
+ result = state.getNameFor(original, proto, markAsUsed);
}
return result;
}
public void reserveName(DexString name, ProtoType proto) {
- InternalState state = getOrCreateInternalStateFor(proto);
+ InternalState<ProtoType> state = getOrCreateInternalStateFor(proto);
state.reserveName(name);
}
public boolean isReserved(DexString name, ProtoType proto) {
- InternalState state = findInternalStateFor(proto);
+ InternalState<ProtoType> state = findInternalStateFor(proto);
if (state == null) {
return false;
}
@@ -101,32 +109,34 @@
}
public boolean isAvailable(DexString original, ProtoType proto, DexString candidate) {
- InternalState state = findInternalStateFor(proto);
+ InternalState<ProtoType> state = findInternalStateFor(proto);
if (state == null) {
return true;
}
- assert state.getAssignedNameFor(original) != candidate;
+ assert state.getAssignedNameFor(original, proto) != candidate;
return state.isAvailable(candidate);
}
public void addRenaming(DexString original, ProtoType proto, DexString newName) {
- InternalState state = getOrCreateInternalStateFor(proto);
- state.addRenaming(original, newName);
+ InternalState<ProtoType> state = getOrCreateInternalStateFor(proto);
+ state.addRenaming(original, proto, newName);
}
- private static class InternalState {
+ private class InternalState<InternalProtoType extends CachedHashValueDexItem> {
private static final int INITIAL_NAME_COUNT = 1;
- private final static char[] EMPTY_CHAR_ARRARY = new char[0];
+ private final char[] EMPTY_CHAR_ARRARY = new char[0];
protected final DexItemFactory itemFactory;
- private final InternalState parentInternalState;
+ private final InternalState<InternalProtoType> parentInternalState;
private Set<DexString> reservedNames = null;
- private Map<DexString, DexString> renamings = null;
+ private Table<DexString, InternalProtoType, DexString> renamings = null;
private int nameCount;
private final Iterator<String> dictionaryIterator;
- private InternalState(DexItemFactory itemFactory, InternalState parentInternalState,
+ private InternalState(
+ DexItemFactory itemFactory,
+ InternalState<InternalProtoType> parentInternalState,
Iterator<String> dictionaryIterator) {
this.itemFactory = itemFactory;
this.parentInternalState = parentInternalState;
@@ -135,7 +145,9 @@
this.dictionaryIterator = dictionaryIterator;
}
- private InternalState(DexItemFactory itemFactory, InternalState parentInternalState,
+ private InternalState(
+ DexItemFactory itemFactory,
+ InternalState<InternalProtoType> parentInternalState,
List<String> dictionary) {
this(itemFactory, parentInternalState, dictionary.iterator());
}
@@ -151,27 +163,40 @@
&& (parentInternalState == null || parentInternalState.isAvailable(name));
}
- public InternalState createChild() {
- return new InternalState(itemFactory, this, dictionaryIterator);
+ InternalState<InternalProtoType> createChild() {
+ return new InternalState<>(itemFactory, this, dictionaryIterator);
}
- public void reserveName(DexString name) {
+ void reserveName(DexString name) {
if (reservedNames == null) {
reservedNames = Sets.newIdentityHashSet();
}
reservedNames.add(name);
}
- public DexString getAssignedNameFor(DexString original) {
- DexString result = renamings == null ? null : renamings.get(original);
+ DexString getAssignedNameFor(DexString original, InternalProtoType proto) {
+ DexString result = null;
+ if (renamings != null) {
+ if (useUniqueMemberNames) {
+ Map<InternalProtoType, DexString> row = renamings.row(original);
+ if (row != null) {
+ // Either not renamed yet (0) or renamed (1). If renamed, return the same renamed name
+ // so that other members with the same name can be renamed to the same renamed name.
+ assert row.values().size() <= 1;
+ result = Iterables.getOnlyElement(row.values(), null);
+ }
+ } else {
+ result = renamings.get(original, proto);
+ }
+ }
if (result == null && parentInternalState != null) {
- result = parentInternalState.getAssignedNameFor(original);
+ result = parentInternalState.getAssignedNameFor(original, proto);
}
return result;
}
- public DexString getNameFor(DexString original, boolean markAsUsed) {
- DexString name = getAssignedNameFor(original);
+ DexString getNameFor(DexString original, InternalProtoType proto, boolean markAsUsed) {
+ DexString name = getAssignedNameFor(original, proto);
if (name != null) {
return name;
}
@@ -179,19 +204,19 @@
name = itemFactory.createString(nextSuggestedName());
} while (!isAvailable(name));
if (markAsUsed) {
- addRenaming(original, name);
+ addRenaming(original, proto, name);
}
return name;
}
- public void addRenaming(DexString original, DexString newName) {
+ void addRenaming(DexString original, InternalProtoType proto, DexString newName) {
if (renamings == null) {
- renamings = HashBiMap.create();
+ renamings = HashBasedTable.create();
}
- renamings.put(original, newName);
+ renamings.put(original, proto, newName);
}
- protected String nextSuggestedName() {
+ String nextSuggestedName() {
if (dictionaryIterator.hasNext()) {
return dictionaryIterator.next();
} else {
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index f36f8a6..9aa73de 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -203,11 +203,30 @@
}
private void enqueueRootItems(Map<DexItem, ProguardKeepRule> items) {
- workList.addAll(
- items.entrySet().stream().map(Action::forRootItem).collect(Collectors.toList()));
+ items.entrySet().forEach(this::enqueueRootItem);
pinnedItems.addAll(items.keySet());
}
+ private void enqueueRootItem(Map.Entry<DexItem, ProguardKeepRule> root) {
+ DexItem item = root.getKey();
+ KeepReason reason = KeepReason.dueToKeepRule(root.getValue());
+ if (item instanceof DexClass) {
+ DexClass clazz = (DexClass) item;
+ workList.add(Action.markInstantiated(clazz, reason));
+ if (options.forceProguardCompatibility && clazz.hasDefaultInitializer()) {
+ ProguardKeepRule rule = ProguardConfigurationUtils.buildDefaultInitializerKeepRule(clazz);
+ proguardCompatibilityWorkList.add(Action.markMethodLive(
+ clazz.getDefaultInitializer(), KeepReason.dueToProguardCompatibilityKeepRule(rule)));
+ }
+ } else if (item instanceof DexEncodedField) {
+ workList.add(Action.markFieldKept((DexEncodedField) item, reason));
+ } else if (item instanceof DexEncodedMethod) {
+ workList.add(Action.markMethodKept((DexEncodedMethod) item, reason));
+ } else {
+ throw new IllegalArgumentException(item.toString());
+ }
+ }
+
//
// Things to do with registering events. This is essentially the interface for byte-code
// traversals.
@@ -372,6 +391,16 @@
}
@Override
+ public boolean registerConstClass(DexType type) {
+ return registerConstClassOrCheckCast(type);
+ }
+
+ @Override
+ public boolean registerCheckCast(DexType type) {
+ return registerConstClassOrCheckCast(type);
+ }
+
+ @Override
public boolean registerTypeReference(DexType type) {
DexType baseType = type.toBaseType(appInfo.dexItemFactory);
if (baseType.isClassType()) {
@@ -380,6 +409,25 @@
}
return false;
}
+
+ private boolean registerConstClassOrCheckCast(DexType type) {
+ if (options.forceProguardCompatibility) {
+ DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+ if (baseType.isClassType()) {
+ DexClass baseClass = appInfo.definitionFor(baseType);
+ if (baseClass != null) {
+ markClassAsInstantiatedWithCompatRule(baseClass);
+ } else {
+ // This handles reporting of missing classes.
+ markTypeAsLive(baseType);
+ }
+ return true;
+ }
+ return false;
+ } else {
+ return registerTypeReference(type);
+ }
+ }
}
private DexMethod getInvokeSuperTarget(DexMethod method, DexEncodedMethod currentMethod) {
@@ -439,14 +487,12 @@
annotations.forEach(this::handleAnnotationOfLiveType);
}
- // Add all dependent static members to the workqueue.
- enqueueRootItems(rootSet.getDependentStaticMembers(type));
-
- // For Proguard compatibility keep the default initializer for live types.
if (options.forceProguardCompatibility) {
- if (holder.isProgramClass() && holder.hasDefaultInitializer()) {
- markClassAsInstantiatedWithCompatRule(holder);
- }
+ // Add all dependent members to the workqueue.
+ enqueueRootItems(rootSet.getDependentItems(type));
+ } else {
+ // Add all dependent static members to the workqueue.
+ enqueueRootItems(rootSet.getDependentStaticMembers(type));
}
}
}
@@ -566,6 +612,7 @@
if (!instantiatedTypes.add(clazz.type, reason)) {
return;
}
+
collectProguardCompatibilityRule(reason);
if (Log.ENABLED) {
Log.verbose(getClass(), "Class `%s` is instantiated, processing...", clazz);
@@ -1286,20 +1333,6 @@
return new Action(Kind.MARK_FIELD_KEPT, method, null, reason);
}
- public static Action forRootItem(Map.Entry<DexItem, ProguardKeepRule> root) {
- DexItem item = root.getKey();
- KeepReason reason = KeepReason.dueToKeepRule(root.getValue());
- if (item instanceof DexClass) {
- return markInstantiated((DexClass) item, reason);
- } else if (item instanceof DexEncodedField) {
- return markFieldKept((DexEncodedField) item, reason);
- } else if (item instanceof DexEncodedMethod) {
- return markMethodKept((DexEncodedMethod) item, reason);
- } else {
- throw new IllegalArgumentException(item.toString());
- }
- }
-
private enum Kind {
MARK_REACHABLE_VIRTUAL,
MARK_REACHABLE_INTERFACE,
diff --git a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
index 46669ec..c37102c 100644
--- a/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/protolite/ProtoLitePruner.java
@@ -348,7 +348,6 @@
// the fallthrough label. This can introduce critical edges. Therefore, we split critical
// edges to maintain our edge-split form.
code.splitCriticalEdges();
- code.traceBlocks();
assert code.isConsistentSSA();
}
diff --git a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
index 8ff72c7..c4351ce 100644
--- a/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
+++ b/src/test/java/com/android/tools/r8/JctfTestSpecifications.java
@@ -4542,7 +4542,7 @@
// java.lang.AssertionError: Failed to load serialization resource file: serialization/com/google/jctf/test/lib/java/util/concurrent/PriorityBlockingQueue/serialization/PriorityBlockingQueue_serialization_A01.golden.0.ser
.put("lang.ThreadGroup.destroy.ThreadGroup_destroy_A01",
- match(D8_COMPILER, runtimesUpTo(Version.V6_0_1)))
+ match(runtimesUpTo(Version.V6_0_1)))
// 1) t02
// java.lang.IllegalThreadStateException: Thread group still contains threads: Test group
// 2) t04
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index a410f07..ebb8100 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -13,6 +13,7 @@
import com.android.tools.r8.ToolHelper.DexVm.Kind;
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.ArtErrorParser;
@@ -67,6 +68,7 @@
@Rule
public ExpectedException thrown = ExpectedException.none();
+ private boolean expectedException = false;
public enum DexTool {
JACK,
@@ -77,7 +79,8 @@
public enum CompilerUnderTest {
D8,
R8,
- R8_AFTER_D8 // refers to the R8 (default: debug) step but implies a previous D8 step as well
+ R8_AFTER_D8, // refers to the R8 (default: debug) step but implies a previous D8 step as well
+ D8_AFTER_R8CF
}
private static final String ART_TESTS_DIR = "tests/2017-10-04/art";
@@ -145,10 +148,6 @@
.put("974-verify-interface-super", AndroidApiLevel.N)
// Desugaring of interface private methods is not yet supported.
.put("975-iface-private", AndroidApiLevel.N)
- // The extended check for overlapping long registers cause this to run out of registers.
- .put("421-large-frame", AndroidApiLevel.N)
- // The extended check for overlapping long registers cause this to run out of registers.
- .put("551-checker-shifter-operand", AndroidApiLevel.N)
.build();
// Tests that timeout when run with Art.
@@ -554,11 +553,18 @@
// This test relies on specific field access patterns, which we rewrite.
.put("064-field-access",
TestCondition.match(
- TestCondition.R8_NOT_AFTER_D8_COMPILER,
+ TestCondition.R8DEX_NOT_AFTER_D8_COMPILER,
TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
.put("064-field-access",
TestCondition.match(
- TestCondition.R8_COMPILER,
+ TestCondition.R8DEX_COMPILER,
+ TestCondition.runtimes(
+ DexVm.Version.DEFAULT, DexVm.Version.V7_0_0, DexVm.Version.V6_0_1,
+ DexVm.Version.V5_1_1)))
+ .put("064-field-access",
+ TestCondition.match(
+ TestCondition.tools(DexTool.NONE),
+ TestCondition.D8_AFTER_R8CF_COMPILER,
TestCondition.runtimes(
DexVm.Version.DEFAULT, DexVm.Version.V7_0_0, DexVm.Version.V6_0_1,
DexVm.Version.V5_1_1)))
@@ -608,15 +614,10 @@
.put("600-verifier-fails",
TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
// VFY: args to if-eq/if-ne must both be refs or cat1.
- .put("134-reg-promotion",
+ .put(
+ "134-reg-promotion",
TestCondition.match(
- TestCondition.R8_COMPILER,
- TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
- .put("134-reg-promotion",
- TestCondition.match(
- TestCondition.tools(DexTool.NONE, DexTool.JACK),
- TestCondition.D8_COMPILER,
- TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
+ TestCondition.R8DEX_COMPILER, TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
// VFY: tried to get class from non-ref register.
.put("506-verify-aput",
TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
@@ -647,7 +648,7 @@
.put("551-implicit-null-checks",
TestCondition.match(
TestCondition.tools(DexTool.NONE, DexTool.DX),
- TestCondition.R8_COMPILER,
+ TestCondition.R8DEX_COMPILER,
TestCondition.runtimes(DexVm.Version.V5_1_1)))
// Contains a method (B.<init>) which pass too few arguments to invoke. Also, contains an
// iput on a static field.
@@ -679,6 +680,7 @@
// Switch regression still present in Dalvik 4.0.4.
.put("095-switch-MAX_INT",
TestCondition.match(
+ TestCondition.tools(DexTool.DX),
TestCondition.D8_COMPILER,
TestCondition.runtimes(DexVm.Version.V4_0_4)))
.build();
@@ -711,7 +713,7 @@
.put("474-fp-sub-neg",
TestCondition.match(
TestCondition.tools(DexTool.NONE, DexTool.JACK),
- TestCondition.D8_COMPILER,
+ TestCondition.D8_NOT_AFTER_R8CF_COMPILER,
TestCondition.runtimes(DexVm.Version.V6_0_1)))
.build();
@@ -766,13 +768,7 @@
TestCondition.match(
TestCondition.runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4,
DexVm.Version.V5_1_1, DexVm.Version.V6_0_1, DexVm.Version.V7_0_0)))
- .put(
- "454-get-vreg",
- TestCondition.match(
- TestCondition.tools(DexTool.NONE),
- TestCondition.D8_COMPILER,
- TestCondition.runtimes(DexVm.Version.DEFAULT)))
- .put("454-get-vreg", TestCondition.match(TestCondition.R8_COMPILER))
+ .put("454-get-vreg", TestCondition.match(TestCondition.R8DEX_COMPILER))
// Fails: regs_jni.cc:42] Check failed: GetVReg(m, 0, kIntVReg, &value)
// The R8/D8 code does not put values in the same registers as the tests expects.
.put(
@@ -780,20 +776,14 @@
TestCondition.match(
TestCondition.runtimes(DexVm.Version.V4_0_4, DexVm.Version.V4_4_4,
DexVm.Version.V5_1_1, DexVm.Version.V6_0_1, DexVm.Version.V7_0_0)))
- .put(
- "457-regs",
- TestCondition.match(
- TestCondition.tools(DexTool.NONE),
- TestCondition.D8_COMPILER,
- TestCondition.runtimes(DexVm.Version.DEFAULT)))
- .put("457-regs", TestCondition.match(TestCondition.R8_COMPILER))
+ .put("457-regs", TestCondition.match(TestCondition.R8DEX_COMPILER))
// Class not found.
.put("529-checker-unresolved", TestCondition.any())
// Fails: env_long_ref.cc:44] Check failed: GetVReg(m, 1, kReferenceVReg, &value)
// The R8/D8 code does not produce values in the same registers as the tests expects in
// the stack frame for TestCase.testCase checked by the native Main.lookForMyRegisters
// (v1 vs v0).
- .put("543-env-long-ref", TestCondition.match(TestCondition.R8_COMPILER))
+ .put("543-env-long-ref", TestCondition.match(TestCondition.R8DEX_COMPILER))
// Array index out of bounds exception.
.put("555-UnsafeGetLong-regression", TestCondition.any())
// Array index out of bounds exception.
@@ -814,9 +804,9 @@
// Main.testThisWithInstanceCall checked by the native Main.doNativeCallRef (v0 vs. v1 and
// only 1 register instead fof 2).
.put("461-get-reference-vreg", TestCondition.match(TestCondition.R8_COMPILER))
- // This test uses register r1 in method that is declared to only use 1 register (r0). This
- // is in dex code which D8 does not convert. Therefore the error is a verification error
- // at runtime and that is expected.
+ // This test uses register r1 in method that is declared to only use 1 register (r0).
+ // This is in dex code which D8 does not convert and which R8/CF does not process.
+ // Therefore the error is a verification error at runtime and that is expected.
.put("142-classloader2", TestCondition.match(TestCondition.D8_COMPILER))
// Invoke-custom is supported by D8 and R8, but it can only run on our newest version
// of art.
@@ -839,9 +829,6 @@
.put("973-default-multidex", beforeAndroidN) // --min-sdk = 24
.put("974-verify-interface-super", beforeAndroidN) // --min-sdk = 24
.put("975-iface-private", beforeAndroidN) // --min-sdk = 24
- // These tests have min-api set to N.
- .put("421-large-frame", beforeAndroidN) // --min-sdk = 24
- .put("551-checker-shifter-operand", beforeAndroidN) // --min-sdk = 24
// Uses dex file version 37 and therefore only runs on Android N and above.
.put("972-iface-super-multidex",
TestCondition.match(TestCondition.tools(DexTool.JACK, DexTool.DX),
@@ -862,9 +849,9 @@
new ImmutableListMultimap.Builder<String, TestCondition>()
// Contains two methods with the same name and signature but different code.
.put("097-duplicate-method", TestCondition.any())
- // Contains a method (B.<init>) which pass too few arguments to invoke. Also, contains an
- // iput on a static field.
- .put("600-verifier-fails", TestCondition.match(TestCondition.R8_COMPILER))
+ // Dex code contains a method (B.<init>) which pass too few arguments to invoke, and it
+ // also contains an iput on a static field.
+ .put("600-verifier-fails", TestCondition.match(TestCondition.R8DEX_COMPILER))
// Contains a method that falls off the end without a return.
.put("606-erroneous-class", TestCondition.match(
TestCondition.tools(DexTool.JACK),
@@ -873,11 +860,15 @@
TestCondition.tools(DexTool.DX),
TestCondition.R8_NOT_AFTER_D8_COMPILER,
LEGACY_RUNTIME))
- // Contains an illegal invoke that R8 will fail to compile.
- .put("594-invoke-super", TestCondition.match(
- TestCondition.R8_COMPILER))
- .put("974-verify-interface-super", TestCondition.match(
- TestCondition.R8_COMPILER))
+ // Dex input contains an illegal InvokeSuper in Z.foo() to Y.foo()
+ // that R8 will fail to compile.
+ .put("594-invoke-super", TestCondition.match(TestCondition.R8DEX_COMPILER))
+ .put("974-verify-interface-super", TestCondition.match(TestCondition.R8DEX_COMPILER))
+ // R8 generates too large code in Goto.bigGoto(). b/74327727
+ .put("003-omnibus-opcodes", TestCondition.match(TestCondition.D8_AFTER_R8CF_COMPILER))
+ // Contains a subset of JUnit which collides with library definitions of JUnit.
+ .put("021-string2", TestCondition.match(TestCondition.D8_AFTER_R8CF_COMPILER))
+ .put("082-inline-execute", TestCondition.match(TestCondition.D8_AFTER_R8CF_COMPILER))
.build();
// Tests that are invalid dex files and on which R8/D8 fails and that is OK.
@@ -889,12 +880,12 @@
// only one A$B class because of a custom build script that merges them.
.put("121-modifiers", TestCondition.match(TestCondition.tools(DexTool.NONE)))
// This test uses register r1 in method that is declared to only use 1 register (r0).
- .put("142-classloader2", TestCondition.match(TestCondition.R8_COMPILER))
+ .put("142-classloader2", TestCondition.match(TestCondition.R8DEX_COMPILER))
// This test uses an uninitialized register.
- .put("471-uninitialized-locals", TestCondition.match(TestCondition.R8_COMPILER))
+ .put("471-uninitialized-locals", TestCondition.match(TestCondition.R8DEX_COMPILER))
// This test is starting from invalid dex code. It splits up a double value and uses
// the first register of a double with the second register of another double.
- .put("800-smali", TestCondition.match(TestCondition.R8_COMPILER))
+ .put("800-smali", TestCondition.match(TestCondition.R8DEX_COMPILER))
// Contains a loop in the class hierarchy.
.put("804-class-extends-itself", TestCondition.any())
// These tests have illegal class flag combinations, so we reject them.
@@ -1115,18 +1106,28 @@
// Has missing classes.
private final boolean hasMissingClasses;
- TestSpecification(String name, DexTool dexTool,
- File directory, boolean skipArt, boolean skipTest, boolean failsWithX8,
- boolean failsWithArt, boolean failsWithArtOutput, boolean failsWithArtOriginalOnly,
- String nativeLibrary, boolean expectedToFailWithX8, boolean outputMayDiffer,
- boolean disableInlining, boolean hasMissingClasses) {
+ TestSpecification(
+ String name,
+ DexTool dexTool,
+ File directory,
+ boolean skipArt,
+ boolean skipTest,
+ boolean failsWithX8,
+ boolean failsWithArt,
+ boolean failsWithArtOutput,
+ boolean failsWithArtOriginalOnly,
+ String nativeLibrary,
+ boolean expectedToFailWithX8,
+ boolean outputMayDiffer,
+ boolean disableInlining,
+ boolean hasMissingClasses,
+ DexVm dexVm) {
this.name = name;
this.dexTool = dexTool;
this.nativeLibrary = nativeLibrary;
this.directory = directory;
this.skipArt = skipArt;
- this.skipTest =
- skipTest || (ToolHelper.isWindows() && ToolHelper.getDexVm().getKind() == Kind.HOST);
+ this.skipTest = skipTest || (ToolHelper.isWindows() && dexVm.getKind() == Kind.HOST);
this.failsWithX8 = failsWithX8;
this.failsWithArt = failsWithArt;
this.failsWithArtOutput = failsWithArtOutput;
@@ -1137,10 +1138,30 @@
this.hasMissingClasses = hasMissingClasses;
}
- TestSpecification(String name, DexTool dexTool, File directory, boolean skipArt,
- boolean failsWithArt, boolean disableInlining) {
- this(name, dexTool, directory, skipArt,
- false, false, failsWithArt, false, false, null, false, false, disableInlining, false);
+ TestSpecification(
+ String name,
+ DexTool dexTool,
+ File directory,
+ boolean skipArt,
+ boolean failsWithArt,
+ boolean disableInlining,
+ DexVm dexVm) {
+ this(
+ name,
+ dexTool,
+ directory,
+ skipArt,
+ false,
+ false,
+ failsWithArt,
+ false,
+ false,
+ null,
+ false,
+ false,
+ disableInlining,
+ false,
+ dexVm);
}
public File resolveFile(String name) {
@@ -1195,7 +1216,8 @@
}
private static Map<SpecificationKey, TestSpecification> getTestsMap(
- CompilerUnderTest compilerUnderTest, CompilationMode compilationMode, DexVm.Version version) {
+ CompilerUnderTest compilerUnderTest, CompilationMode compilationMode, DexVm dexVm) {
+ DexVm.Version version = dexVm.getVersion();
File defaultArtTestDir = new File(ART_TESTS_DIR);
File legacyArtTestDir = new File(ART_LEGACY_TESTS_DIR);
if (!defaultArtTestDir.exists() || !legacyArtTestDir.exists()) {
@@ -1255,12 +1277,11 @@
failsWithArt.addAll(tmpSet);
}
- if (!ToolHelper.isDefaultDexVm(ToolHelper.getDexVm())) {
+ if (!ToolHelper.isDefaultDexVm(dexVm)) {
// Generally failing when not TOT art.
failsWithArt.addAll(expectedToFailRunWithArtNonDefault);
// Version specific failures
- failsWithArt
- .addAll(expectedToFailRunWithArtVersion.get(ToolHelper.getDexVm().getVersion()));
+ failsWithArt.addAll(expectedToFailRunWithArtVersion.get(version));
}
// Collect the tests failing with output differences in Art.
@@ -1303,7 +1324,8 @@
expectedToFailWithCompilerSet.contains(name),
outputMayDiffer.contains(name),
requireInliningToBeDisabled.contains(name),
- hasMissingClasses.contains(name)));
+ hasMissingClasses.contains(name),
+ dexVm));
}
}
return data;
@@ -1317,6 +1339,7 @@
break;
case D8:
case R8_AFTER_D8:
+ case D8_AFTER_R8CF:
compilationMode = CompilationMode.DEBUG;
break;
default:
@@ -1356,11 +1379,6 @@
return builder;
}
- protected void runArtTest() throws Throwable {
- // Use the default dex VM specified.
- runArtTest(ToolHelper.getDexVm(), CompilerUnderTest.R8);
- }
-
protected void runArtTest(CompilerUnderTest compilerUnderTest) throws Throwable {
// Use the default dex VM specified.
runArtTest(ToolHelper.getDexVm(), compilerUnderTest);
@@ -1390,6 +1408,97 @@
CompilationFailedException {
assert mode != null;
switch (compilerUnderTest) {
+ case D8_AFTER_R8CF:
+ {
+ assert keepRulesFile == null : "Keep-rules file specified for D8.";
+
+ List<ProgramResource> dexInputs = new ArrayList<>();
+ List<ProgramResource> cfInputs = new ArrayList<>();
+ for (String f : fileNames) {
+ Path p = Paths.get(f);
+ if (FileUtils.isDexFile(p)) {
+ dexInputs.add(ProgramResource.fromFile(ProgramResource.Kind.DEX, p));
+ } else if (FileUtils.isClassFile(p)) {
+ cfInputs.add(ProgramResource.fromFile(ProgramResource.Kind.CF, p));
+ } else {
+ assert FileUtils.isArchive(p);
+ ArchiveProgramResourceProvider provider =
+ ArchiveProgramResourceProvider.fromArchive(p);
+
+ try {
+ for (ProgramResource pr : provider.getProgramResources()) {
+ if (pr.getKind() == ProgramResource.Kind.DEX) {
+ dexInputs.add(pr);
+ } else {
+ assert pr.getKind() == ProgramResource.Kind.CF;
+ cfInputs.add(pr);
+ }
+ }
+ } catch (ResourceException e) {
+ throw new CompilationException(e);
+ }
+ }
+ }
+
+ D8Command.Builder builder =
+ D8Command.builder()
+ .setMode(mode)
+ .addProgramResourceProvider(
+ new ProgramResourceProvider() {
+ @Override
+ public Collection<ProgramResource> getProgramResources()
+ throws ResourceException {
+ return dexInputs;
+ }
+ })
+ .setOutput(Paths.get(resultPath), OutputMode.DexIndexed);
+
+ Origin cfOrigin =
+ new Origin(Origin.root()) {
+ @Override
+ public String part() {
+ return "R8/CF";
+ }
+ };
+
+ R8Command.Builder r8builder =
+ R8Command.builder()
+ .setMode(mode)
+ .setProgramConsumer(
+ new ClassFileConsumer() {
+
+ @Override
+ public synchronized void accept(
+ byte[] data, String descriptor, DiagnosticsHandler handler) {
+ builder.addClassProgramData(data, cfOrigin);
+ }
+
+ @Override
+ public void finished(DiagnosticsHandler handler) {}
+ })
+ .addProgramResourceProvider(
+ new ProgramResourceProvider() {
+ @Override
+ public Collection<ProgramResource> getProgramResources()
+ throws ResourceException {
+ return cfInputs;
+ }
+ });
+
+ AndroidApiLevel minSdkVersion = needMinSdkVersion.get(name);
+ if (minSdkVersion != null) {
+ builder.setMinApiLevel(minSdkVersion.getLevel());
+ r8builder.setMinApiLevel(minSdkVersion.getLevel());
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion));
+ r8builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion));
+ } else {
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.getDefault()));
+ r8builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.getDefault()));
+ }
+ ToolHelper.runR8(r8builder.build(), options -> options.ignoreMissingClasses = true);
+ D8.run(builder.build());
+ break;
+ }
case D8: {
assert keepRulesFile == null : "Keep-rules file specified for D8.";
D8Command.Builder builder =
@@ -1496,12 +1605,17 @@
}
private static BiFunction<Outcome, Boolean, TestSpecification> jctfOutcomeToSpecification(
- String name, DexTool dexTool, File resultDir) {
- return (outcome, noInlining) -> new TestSpecification(name, dexTool, resultDir,
- outcome == JctfTestSpecifications.Outcome.TIMEOUTS_WITH_ART
- || outcome == JctfTestSpecifications.Outcome.FLAKY_WITH_ART,
- outcome == JctfTestSpecifications.Outcome.FAILS_WITH_ART,
- noInlining);
+ String name, DexTool dexTool, File resultDir, DexVm dexVm) {
+ return (outcome, noInlining) ->
+ new TestSpecification(
+ name,
+ dexTool,
+ resultDir,
+ outcome == JctfTestSpecifications.Outcome.TIMEOUTS_WITH_ART
+ || outcome == JctfTestSpecifications.Outcome.FLAKY_WITH_ART,
+ outcome == JctfTestSpecifications.Outcome.FAILS_WITH_ART,
+ noInlining,
+ dexVm);
}
protected void runJctfTest(CompilerUnderTest compilerUnderTest, String classFilePath,
@@ -1518,9 +1632,13 @@
File resultDir = temp.newFolder(firstCompilerUnderTest.toString().toLowerCase() + "-output");
- TestSpecification specification = JctfTestSpecifications.getExpectedOutcome(
- name, firstCompilerUnderTest, dexVm, compilationMode,
- jctfOutcomeToSpecification(name, DexTool.NONE, resultDir));
+ TestSpecification specification =
+ JctfTestSpecifications.getExpectedOutcome(
+ name,
+ firstCompilerUnderTest,
+ dexVm,
+ compilationMode,
+ jctfOutcomeToSpecification(name, DexTool.NONE, resultDir, dexVm));
if (specification.skipTest) {
return;
@@ -1603,9 +1721,13 @@
.collect(Collectors.toList());
File r8ResultDir = temp.newFolder("r8-output");
compilationMode = CompilationMode.DEBUG;
- specification = JctfTestSpecifications.getExpectedOutcome(
- name, CompilerUnderTest.R8_AFTER_D8, dexVm, compilationMode,
- jctfOutcomeToSpecification(name, DexTool.DX, r8ResultDir));
+ specification =
+ JctfTestSpecifications.getExpectedOutcome(
+ name,
+ CompilerUnderTest.R8_AFTER_D8,
+ dexVm,
+ compilationMode,
+ jctfOutcomeToSpecification(name, DexTool.DX, r8ResultDir, dexVm));
if (specification.skipTest) {
return;
}
@@ -1661,7 +1783,7 @@
}
ArtCommandBuilder builder = buildArtCommand(processedFile, specification, dexVm);
- if (ToolHelper.getDexVm().isNewerThan(DexVm.ART_4_4_4_HOST)) {
+ if (dexVm.isNewerThan(DexVm.ART_4_4_4_HOST)) {
builder.appendArtOption("-Ximage:/system/non/existent/image.art");
}
for (String s : ToolHelper.getBootLibs()) {
@@ -1671,7 +1793,7 @@
builder.appendProgramArgument(fullClassName);
if (specification.failsWithArt) {
- thrown.expect(AssertionError.class);
+ expectException(AssertionError.class);
}
try {
@@ -1687,8 +1809,7 @@
}
}
- protected void runArtTest(DexVm version, CompilerUnderTest compilerUnderTest)
- throws Throwable {
+ protected void runArtTest(DexVm dexVm, CompilerUnderTest compilerUnderTest) throws Throwable {
CompilerUnderTest firstCompilerUnderTest =
compilerUnderTest == CompilerUnderTest.R8_AFTER_D8
? CompilerUnderTest.D8
@@ -1697,11 +1818,11 @@
CompilationMode compilationMode = defaultCompilationMode(compilerUnderTest);
TestSpecification specification =
- getTestsMap(firstCompilerUnderTest, compilationMode, version.getVersion())
+ getTestsMap(firstCompilerUnderTest, compilationMode, dexVm)
.get(new SpecificationKey(name, toolchain));
if (specification == null) {
- if (version.getVersion() == DexVm.Version.DEFAULT) {
+ if (dexVm.getVersion() == DexVm.Version.DEFAULT) {
throw new RuntimeException("Test " + name + " has no specification for toolchain"
+ toolchain + ".");
} else {
@@ -1715,7 +1836,7 @@
return;
}
- if (specification.nativeLibrary != null && ToolHelper.getDexVm().getKind() == Kind.TARGET) {
+ if (specification.nativeLibrary != null && dexVm.getKind() == Kind.TARGET) {
// JNI tests not yet supported for devices
return;
}
@@ -1746,12 +1867,17 @@
File resultDir = temp.newFolder(firstCompilerUnderTest.toString().toLowerCase() + "-output");
runArtTestDoRunOnArt(
- version, firstCompilerUnderTest, specification, fileNames, resultDir, compilationMode);
+ dexVm, firstCompilerUnderTest, specification, fileNames, resultDir, compilationMode);
if (compilerUnderTest == CompilerUnderTest.R8_AFTER_D8) {
+ if (expectedException) {
+ // The expected exception was not thrown while running D8.
+ return;
+ }
+
compilationMode = CompilationMode.DEBUG;
specification =
- getTestsMap(CompilerUnderTest.R8_AFTER_D8, compilationMode, version.getVersion())
+ getTestsMap(CompilerUnderTest.R8_AFTER_D8, compilationMode, dexVm)
.get(new SpecificationKey(name, DexTool.DX));
if (specification == null) {
@@ -1771,7 +1897,7 @@
resultDir = temp.newFolder("r8-output");
runArtTestDoRunOnArt(
- version, CompilerUnderTest.R8, specification, fileNames, resultDir, compilationMode);
+ dexVm, CompilerUnderTest.R8, specification, fileNames, resultDir, compilationMode);
}
}
@@ -1796,7 +1922,7 @@
CompilationMode compilationMode)
throws Throwable {
if (specification.expectedToFailWithX8) {
- thrown.expect(CompilationError.class);
+ expectException(CompilationError.class);
try {
executeCompilerUnderTest(
compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
@@ -1809,7 +1935,7 @@
System.err.println("Should have failed R8/D8 compilation with a CompilationError.");
return;
} else if (specification.failsWithX8) {
- thrown.expect(Throwable.class);
+ expectException(Throwable.class);
executeCompilerUnderTest(
compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
specification.disableInlining, specification.hasMissingClasses);
@@ -1842,7 +1968,7 @@
String expected =
com.google.common.io.Files.asCharSource(expectedFile, Charsets.UTF_8).read();
if (specification.failsWithArt) {
- thrown.expect(AssertionError.class);
+ expectException(AssertionError.class);
}
ArtCommandBuilder builder = buildArtCommand(processedFile, specification, version);
@@ -1879,7 +2005,7 @@
// produces.
originalFile = specification.resolveFile(specification.name + ".jar");
if (specification.failsWithArtOriginalOnly) {
- thrown.expect(AssertionError.class);
+ expectException(AssertionError.class);
}
builder = buildArtCommand(originalFile, specification, version);
expected = ToolHelper.runArt(builder);
@@ -1889,7 +2015,7 @@
}
}
if (specification.failsWithArtOutput) {
- thrown.expect(ComparisonFailure.class);
+ expectException(ComparisonFailure.class);
}
if (!specification.outputMayDiffer) {
assertEquals(expected, output);
@@ -1898,6 +2024,11 @@
}
}
+ private void expectException(Class<? extends Throwable> exception) {
+ thrown.expect(exception);
+ expectedException = true;
+ }
+
private void failWithDexDiff(File originalFile, File processedFile)
throws IOException, ExecutionException {
DexInspector inspectOriginal =
diff --git a/src/test/java/com/android/tools/r8/ResourceShrinkerTest.java b/src/test/java/com/android/tools/r8/ResourceShrinkerTest.java
new file mode 100644
index 0000000..301f78f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ResourceShrinkerTest.java
@@ -0,0 +1,245 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.utils.AndroidApp;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Tests for resource shrinker analyzer. This is checking that dex files are processed correctly.
+ */
+public class ResourceShrinkerTest extends TestBase {
+
+ @Rule
+ public TemporaryFolder tmp = new TemporaryFolder();
+
+ private static class TrackAll implements ResourceShrinker.ReferenceChecker {
+ Set<Integer> integers = Sets.newHashSet();
+ Set<String> strings = Sets.newHashSet();
+ List<List<String>> fields = Lists.newArrayList();
+ List<List<String>> methods = Lists.newArrayList();
+
+ @Override
+ public boolean shouldProcess(String internalName) {
+ return !internalName.equals(ResourceClassToSkip.class.getName());
+ }
+
+ @Override
+ public void referencedInt(int value) {
+ integers.add(value);
+ }
+
+ @Override
+ public void referencedString(String value) {
+ strings.add(value);
+ }
+
+ @Override
+ public void referencedStaticField(String internalName, String fieldName) {
+ fields.add(Lists.newArrayList(internalName, fieldName));
+ }
+
+ @Override
+ public void referencedMethod(String internalName, String methodName, String methodDescriptor) {
+ if (Objects.equals(internalName, "java/lang/Object")
+ && Objects.equals(methodName, "<init>")) {
+ return;
+ }
+ methods.add(Lists.newArrayList(internalName, methodName, methodDescriptor));
+ }
+ }
+
+ private static class EmptyClass {
+ }
+
+ @Test
+ public void testEmptyClass()
+ throws CompilationFailedException, IOException, ExecutionException, CompilationException {
+ TrackAll analysis = runAnalysis(EmptyClass.class);
+
+ assertThat(analysis.integers, is(Sets.newHashSet()));
+ assertThat(analysis.strings, is(Sets.newHashSet()));
+ assertThat(analysis.fields, is(Lists.newArrayList()));
+ assertThat(analysis.methods, is(Lists.newArrayList()));
+ }
+
+ private static class ConstInCode {
+ public void foo() {
+ int i = 10;
+ System.out.print(i);
+ System.out.print(11);
+ String s = "my_layout";
+ System.out.print("another_layout");
+ }
+ }
+
+ @Test
+ public void testConstsAndFieldAndMethods()
+ throws CompilationFailedException, IOException, ExecutionException, CompilationException {
+ TrackAll analysis = runAnalysis(ConstInCode.class);
+
+ assertThat(analysis.integers, is(Sets.newHashSet(10, 11)));
+ assertThat(analysis.strings, is(Sets.newHashSet("my_layout", "another_layout")));
+
+ assertEquals(3, analysis.fields.size());
+ assertThat(analysis.fields.get(0), is(Lists.newArrayList("java/lang/System", "out")));
+ assertThat(analysis.fields.get(1), is(Lists.newArrayList("java/lang/System", "out")));
+ assertThat(analysis.fields.get(2), is(Lists.newArrayList("java/lang/System", "out")));
+
+ assertEquals(3, analysis.methods.size());
+ assertThat(analysis.methods.get(0),
+ is(Lists.newArrayList("java/io/PrintStream", "print", "(I)V")));
+ assertThat(analysis.methods.get(1),
+ is(Lists.newArrayList("java/io/PrintStream", "print", "(I)V")));
+ assertThat(analysis.methods.get(2),
+ is(Lists.newArrayList("java/io/PrintStream", "print", "(Ljava/lang/String;)V")));
+ }
+
+ @SuppressWarnings("unused")
+ private static class StaticFields {
+ static final String sStringValue = "staticValue";
+ static final int sIntValue = 10;
+ static final int[] sIntArrayValue = {11, 12, 13};
+ static final String[] sStringArrayValue = {"a", "b", "c"};
+ }
+
+ @Test
+ public void testStaticValues()
+ throws CompilationFailedException, IOException, ExecutionException, CompilationException {
+ TrackAll analysis = runAnalysis(StaticFields.class);
+
+ assertThat(analysis.integers, hasItems(10, 11, 12, 13));
+ assertThat(analysis.strings, hasItems("staticValue", "a", "b", "c"));
+ assertThat(analysis.fields, is(Lists.newArrayList()));
+ assertThat(analysis.methods, is(Lists.newArrayList()));
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface IntAnnotation {
+ int value() default 10;
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ private @interface OuterAnnotation {
+ IntAnnotation inner() default @IntAnnotation(11);
+ }
+
+ @IntAnnotation(42)
+ private static class Annotated {
+ @OuterAnnotation
+ Object defaultAnnotated = new Object();
+ @IntAnnotation(12)
+ Object withValueAnnotated = new Object();
+ @IntAnnotation(13)
+ static Object staticValueAnnotated = new Object();
+
+ @IntAnnotation(14)
+ public void annotatedPublic() {
+ }
+
+ @IntAnnotation(15)
+ public void annotatedPrivate() {
+ }
+ }
+
+ @Test
+ public void testAnnotations()
+ throws CompilationFailedException, IOException, ExecutionException, CompilationException {
+ TrackAll analysis = runAnalysis(IntAnnotation.class, OuterAnnotation.class, Annotated.class);
+
+ assertThat(analysis.integers, hasItems(10, 11, 12, 13, 14, 15, 42));
+ assertThat(analysis.strings, is(Sets.newHashSet()));
+ assertThat(analysis.fields, is(Lists.newArrayList()));
+ assertThat(analysis.methods, is(Lists.newArrayList()));
+ }
+
+ private static class ResourceClassToSkip {
+ int[] i = {100, 101, 102};
+ }
+
+ private static class ToProcess {
+ int[] i = {10, 11, 12};
+ String[] s = {"10", "11", "12"};
+ }
+
+ @Test
+ public void testWithSkippingSome()
+ throws ExecutionException, CompilationFailedException, CompilationException, IOException {
+ TrackAll analysis = runAnalysis(ResourceClassToSkip.class, ToProcess.class);
+
+ assertThat(analysis.integers, hasItems(10, 11, 12));
+ assertThat(analysis.strings, is(Sets.newHashSet("10", "11", "12")));
+ assertThat(analysis.fields, is(Lists.newArrayList()));
+ assertThat(analysis.methods, is(Lists.newArrayList()));
+ }
+
+ @Test
+ public void testPayloadBeforeFillArrayData() throws Exception {
+ SmaliBuilder builder = new SmaliBuilder("Test");
+ builder.addMainMethod(
+ 2,
+ "goto :start",
+ "",
+ ":array_data",
+ ".array-data 4",
+ " 4 5 6",
+ ".end array-data",
+ "",
+ ":start",
+ "const/4 v1, 3",
+ "new-array v0, v1, [I",
+ "fill-array-data v0, :array_data",
+ "return-object v0"
+ );
+ AndroidApp app =
+ AndroidApp.builder().addDexProgramData(builder.compile(), Origin.unknown()).build();
+ TrackAll analysis = runOnApp(app);
+
+ assertThat(analysis.integers, hasItems(4, 5, 6));
+ assertThat(analysis.strings, is(Sets.newHashSet()));
+ assertThat(analysis.fields, is(Lists.newArrayList()));
+ assertThat(analysis.methods, is(Lists.newArrayList()));
+ }
+
+ private TrackAll runAnalysis(Class<?>... classes)
+ throws IOException, CompilationException, ExecutionException, CompilationFailedException {
+ AndroidApp app = readClasses(classes);
+ return runOnApp(app);
+ }
+
+ private TrackAll runOnApp(AndroidApp app)
+ throws IOException, ExecutionException, CompilationFailedException, CompilationException {
+ AndroidApp outputApp = compileWithD8(app);
+ Path outputDex = tmp.newFolder().toPath().resolve("classes.dex");
+ outputApp.writeToDirectory(outputDex.getParent(), OutputMode.DexIndexed);
+
+ ProgramResourceProvider provider =
+ () -> Lists.newArrayList(ProgramResource.fromFile(ProgramResource.Kind.DEX, outputDex));
+ ResourceShrinker.Command command =
+ new ResourceShrinker.Builder().addProgramResourceProvider(provider).build();
+
+ TrackAll analysis = new TrackAll();
+ ResourceShrinker.run(command, analysis);
+ return analysis;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 2b73eb9..d30b2e2 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -34,31 +34,39 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.jar.JarOutputStream;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
+import joptsimple.internal.Strings;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
public class TestBase {
+ // Actually running Proguard should only be during development.
+ private boolean runProguard = System.getProperty("run_proguard") != null;
+
@Rule
public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
/**
+ * Check if tests should also run Proguard when applicable.
+ */
+ protected boolean isRunProguard() {
+ return runProguard;
+ }
+
+ /**
* Write lines of text to a temporary file.
*/
protected Path writeTextToTempFile(String... lines) throws IOException {
@@ -80,6 +88,15 @@
}
/**
+ * Build an AndroidApp with the specified jar.
+ */
+ protected AndroidApp readJar(Path jar) {
+ return AndroidApp.builder()
+ .addProgramResourceProvider(ArchiveProgramResourceProvider.fromArchive(jar))
+ .build();
+ }
+
+ /**
* Build an AndroidApp with the specified test classes.
*/
protected static AndroidApp readClasses(Class... classes) throws IOException {
@@ -174,27 +191,9 @@
}
/**
- * Read the names of classes in a jar.
- */
- protected Set<String> readClassesInJar(Path jar) throws IOException {
- Set<String> result = new HashSet<>();
- try (ZipFile zipFile = new ZipFile(jar.toFile())) {
- final Enumeration<? extends ZipEntry> entries = zipFile.entries();
- while (entries.hasMoreElements()) {
- ZipEntry entry = entries.nextElement();
- String name = entry.getName();
- if (name.endsWith(".class")) {
- result.add(name.substring(0, name.length() - ".class".length()).replace('/', '.'));
- }
- }
- }
- return result;
- }
-
- /**
* Get the class name generated by javac.
*/
- protected String getJavacGeneratedClassName(Class clazz) {
+ protected static String getJavacGeneratedClassName(Class clazz) {
List<String> parts = Lists.newArrayList(clazz.getCanonicalName().split("\\."));
Class enclosing = clazz;
while (enclosing.getEnclosingClass() != null) {
@@ -356,13 +355,29 @@
* the specified class.
*/
public static String keepMainProguardConfiguration(Class clazz) {
- return keepMainProguardConfiguration(clazz.getCanonicalName());
+ return keepMainProguardConfiguration(clazz, ImmutableList.of());
}
/**
* Generate a Proguard configuration for keeping the "public static void main(String[])" method of
* the specified class.
*/
+ public static String keepMainProguardConfiguration(Class clazz, List<String> additionalLines) {
+ String modifier = (clazz.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC ? "public " : "";
+ return Strings.join(ImmutableList.of(
+ "-keep " + modifier + "class " + getJavacGeneratedClassName(clazz) + " {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ "-printmapping"
+ ), "\n") + (additionalLines.size() > 0 ? ("\n" + Strings.join(additionalLines, "\n")) : "");
+ }
+
+ /**
+ * Generate a Proguard configuration for keeping the "public static void main(String[])" method of
+ * the specified class.
+ *
+ * The class is assumed to be public.
+ */
public static String keepMainProguardConfiguration(String clazz) {
return "-keep public class " + clazz + " {\n"
+ " public static void main(java.lang.String[]);\n"
diff --git a/src/test/java/com/android/tools/r8/TestCondition.java b/src/test/java/com/android/tools/r8/TestCondition.java
index 309f631..b85e2f2 100644
--- a/src/test/java/com/android/tools/r8/TestCondition.java
+++ b/src/test/java/com/android/tools/r8/TestCondition.java
@@ -53,14 +53,23 @@
}
}
- public static final CompilerSet D8_COMPILER = compilers(CompilerUnderTest.D8);
+ public static final CompilerSet D8_COMPILER =
+ compilers(CompilerUnderTest.D8, CompilerUnderTest.D8_AFTER_R8CF);
+ public static final CompilerSet D8_NOT_AFTER_R8CF_COMPILER = compilers(CompilerUnderTest.D8);
+ public static final CompilerSet D8_AFTER_R8CF_COMPILER =
+ compilers(CompilerUnderTest.D8_AFTER_R8CF);
// R8_COMPILER refers to R8 both in the standalone setting and after D8
// R8_NOT_AFTER_D8_COMPILER and R8_AFTER_D8_COMPILER refers to the standalone and the combined
// settings, respectively
public static final CompilerSet R8_COMPILER =
+ compilers(
+ CompilerUnderTest.R8, CompilerUnderTest.R8_AFTER_D8, CompilerUnderTest.D8_AFTER_R8CF);
+ public static final CompilerSet R8DEX_COMPILER =
compilers(CompilerUnderTest.R8, CompilerUnderTest.R8_AFTER_D8);
public static final CompilerSet R8_AFTER_D8_COMPILER = compilers(CompilerUnderTest.R8_AFTER_D8);
- public static final CompilerSet R8_NOT_AFTER_D8_COMPILER = compilers(CompilerUnderTest.R8);
+ public static final CompilerSet R8_NOT_AFTER_D8_COMPILER =
+ compilers(CompilerUnderTest.R8, CompilerUnderTest.D8_AFTER_R8CF);
+ public static final CompilerSet R8DEX_NOT_AFTER_D8_COMPILER = compilers(CompilerUnderTest.R8);
public static final CompilationModeSet DEBUG_MODE =
new CompilationModeSet(EnumSet.of(CompilationMode.DEBUG));
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 0c0cd84..5dcb059 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -91,7 +91,7 @@
private static final AndroidApiLevel DEFAULT_MIN_SDK = AndroidApiLevel.I;
private static final String PROGUARD5_2_1 = "third_party/proguard/proguard5.2.1/bin/proguard.sh";
- private static final String PROGUARD6_0 = "third_party/proguard/proguard6.0/bin/proguard.sh";
+ private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard.sh";
private static final String PROGUARD = PROGUARD5_2_1;
public enum DexVm {
@@ -1119,7 +1119,8 @@
}
}
- public static String runProguard(Path inJar, Path outJar, Path config) throws IOException {
+ public static String runProguard(Path inJar, Path outJar, Path config, Path map)
+ throws IOException {
List<String> command = new ArrayList<>();
command.add(PROGUARD);
command.add("-forceprocessing"); // Proguard just checks the creation time on the in/out jars.
@@ -1131,6 +1132,9 @@
command.add("-outjar");
command.add(outJar.toString());
command.add("-printmapping");
+ if (map != null) {
+ command.add(map.toString());
+ }
ProcessBuilder builder = new ProcessBuilder(command);
ToolHelper.ProcessResult result = ToolHelper.runProcess(builder);
if (result.exitCode != 0) {
@@ -1139,7 +1143,6 @@
return result.stdout;
}
-
public static class ProcessResult {
public final int exitCode;
diff --git a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemDump.java b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemDump.java
new file mode 100644
index 0000000..9bc385a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemDump.java
@@ -0,0 +1,231 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf;
+
+import org.objectweb.asm.AnnotationVisitor;
+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;
+
+// Generated with
+// tools/asmifier.py build/classes/test/com/android/tools/r8/cf/AlwaysNullGetItemTest.class
+// and edited to replace calls to get{Object,Typed}Array() with ACONST_NULL (without CHECKCAST).
+public class AlwaysNullGetItemDump implements Opcodes {
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(0);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(
+ V1_8,
+ ACC_PUBLIC + ACC_SUPER,
+ "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+ null,
+ "java/lang/Object",
+ null);
+
+ cw.visitInnerClass(
+ "com/android/tools/r8/cf/AlwaysNullGetItemTest$A",
+ "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+ "A",
+ ACC_PRIVATE + ACC_STATIC);
+
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(1, 1);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ Label l0 = new Label();
+ Label l1 = new Label();
+ mv.visitTryCatchBlock(l0, l1, l1, "java/lang/NullPointerException");
+ mv.visitLabel(l0);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+ "foo",
+ "()Ljava/lang/Object;",
+ false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+ "bar",
+ "()Ljava/lang/Object;",
+ false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+ "hello",
+ "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+ false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "com/android/tools/r8/cf/AlwaysNullGetItemTest$A",
+ "hello",
+ "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+ false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitMethodInsn(
+ INVOKESTATIC,
+ "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+ "goodbye",
+ "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+ false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "com/android/tools/r8/cf/AlwaysNullGetItemTest$A",
+ "hello",
+ "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+ false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+ mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+ mv.visitInsn(DUP);
+ mv.visitLdcInsn("Expected NullPointerException");
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false);
+ mv.visitInsn(ATHROW);
+ mv.visitLabel(l1);
+ mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/NullPointerException"});
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitLdcInsn("NullPointerException");
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(3, 2);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "foo", "()Ljava/lang/Object;", null, null);
+ mv.visitCode();
+ mv.visitInsn(ACONST_NULL);
+ // mv.visitTypeInsn(CHECKCAST, "[Ljava/lang/Object;");
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(2, 0);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC, "bar", "()Ljava/lang/Object;", null, null);
+ mv.visitCode();
+ // mv.visitMethodInsn(
+ // INVOKESTATIC,
+ // "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+ // "getObjectArray",
+ // "()[Ljava/lang/Object;",
+ // false);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(2, 0);
+ mv.visitEnd();
+ }
+ {
+ mv =
+ cw.visitMethod(
+ ACC_PRIVATE + ACC_STATIC,
+ "hello",
+ "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+ null,
+ null);
+ mv.visitCode();
+ // mv.visitMethodInsn(
+ // INVOKESTATIC,
+ // "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+ // "getTypedArray",
+ // "()[Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+ // false);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "com/android/tools/r8/cf/AlwaysNullGetItemTest$A",
+ "hello",
+ "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+ false);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(2, 0);
+ mv.visitEnd();
+ }
+ {
+ mv =
+ cw.visitMethod(
+ ACC_PRIVATE + ACC_STATIC,
+ "goodbye",
+ "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+ null,
+ null);
+ mv.visitCode();
+ // mv.visitMethodInsn(
+ // INVOKESTATIC,
+ // "com/android/tools/r8/cf/AlwaysNullGetItemTest",
+ // "getTypedArray",
+ // "()[Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+ // false);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInsn(ICONST_0);
+ mv.visitInsn(AALOAD);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "com/android/tools/r8/cf/AlwaysNullGetItemTest$A",
+ "goodbye",
+ "()Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+ false);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(2, 0);
+ mv.visitEnd();
+ }
+ {
+ mv =
+ cw.visitMethod(
+ ACC_PRIVATE + ACC_STATIC, "getObjectArray", "()[Ljava/lang/Object;", null, null);
+ mv.visitCode();
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(1, 0);
+ mv.visitEnd();
+ }
+ {
+ mv =
+ cw.visitMethod(
+ ACC_PRIVATE + ACC_STATIC,
+ "getTypedArray",
+ "()[Lcom/android/tools/r8/cf/AlwaysNullGetItemTest$A;",
+ null,
+ null);
+ mv.visitCode();
+ mv.visitInsn(ACONST_NULL);
+ mv.visitInsn(ARETURN);
+ mv.visitMaxs(1, 0);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTest.java b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTest.java
new file mode 100644
index 0000000..8a0a3b7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTest.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.cf;
+
+import java.util.*;
+import org.objectweb.asm.*;
+
+public class AlwaysNullGetItemTest {
+ public static void main(String[] args) {
+ try {
+ System.out.println(foo());
+ System.out.println(bar());
+ System.out.println(hello().hello());
+ System.out.println(goodbye().hello());
+ throw new RuntimeException("Expected NullPointerException");
+ } catch (NullPointerException e) {
+ System.out.println("NullPointerException");
+ }
+ }
+
+ private static Object foo() {
+ return ((Object[]) null)[0];
+ }
+
+ private static Object bar() {
+ return getObjectArray()[0];
+ }
+
+ private static A hello() {
+ return getTypedArray()[0].hello();
+ }
+
+ private static A goodbye() {
+ return getTypedArray()[0].goodbye();
+ }
+
+ private static Object[] getObjectArray() {
+ return null;
+ }
+
+ private static A[] getTypedArray() {
+ return null;
+ }
+
+ private static class A {
+ A hello() {
+ return this;
+ }
+
+ A goodbye() {
+ return null;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
new file mode 100644
index 0000000..3aa8f4a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/AlwaysNullGetItemTestRunner.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.cf;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.nio.file.Path;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class AlwaysNullGetItemTestRunner {
+ static final Class CLASS = AlwaysNullGetItemTest.class;
+
+ @Rule
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Test
+ public void test() throws Exception {
+ ProcessResult runInput =
+ ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
+ assertEquals(0, runInput.exitCode);
+ Path outCf = temp.getRoot().toPath().resolve("cf.jar");
+ Path outDex = temp.getRoot().toPath().resolve("dex.zip");
+ R8.run(
+ R8Command.builder()
+ .setMode(CompilationMode.DEBUG)
+ .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
+ .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+ .setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(outDex))
+ .build());
+ R8.run(
+ R8Command.builder()
+ .setMode(CompilationMode.DEBUG)
+ .addClassProgramData(ToolHelper.getClassAsBytes(CLASS), Origin.unknown())
+ .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+ .setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(outCf))
+ .build());
+ ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName());
+ ProcessResult runDex = ToolHelper.runArtRaw(outDex.toString(), CLASS.getCanonicalName());
+ assertEquals(runInput.toString(), runCf.toString());
+ // Only compare stdout and exitCode since dex2oat prints to stderr.
+ assertEquals(runInput.stdout, runDex.stdout);
+ assertEquals(runInput.exitCode, runDex.exitCode);
+ }
+
+ @Test
+ public void testNoCheckCast() throws Exception {
+ // Test that JVM accepts javac output when method calls have been replaced by ACONST_NULL.
+ Path out = temp.getRoot().toPath().resolve("aaload-null.jar");
+ ClassFileConsumer.ArchiveConsumer archiveConsumer = new ClassFileConsumer.ArchiveConsumer(out);
+ archiveConsumer.accept(
+ AlwaysNullGetItemDump.dump(),
+ DescriptorUtils.javaTypeToDescriptor(CLASS.getCanonicalName()),
+ null);
+ archiveConsumer.finished(null);
+ ProcessResult processResult = ToolHelper.runJava(out, CLASS.getCanonicalName());
+ if (processResult.exitCode != 0) {
+ System.out.println(processResult);
+ }
+ assertEquals(0, processResult.exitCode);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java b/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java
index 5c48073..aeddc54 100644
--- a/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/AnnotationTestRunner.java
@@ -13,17 +13,17 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.origin.Origin;
import java.nio.file.Path;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class AnnotationTestRunner {
static final Class CLASS = AnnotationTest.class;
- @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Rule
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
@Test
- @Ignore
public void test() throws Exception {
ProcessResult runInput =
ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
diff --git a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
index 20acb9f..7565dee 100644
--- a/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/SynchronizedNoopTestRunner.java
@@ -6,6 +6,8 @@
package com.android.tools.r8.cf;
+import static org.junit.Assert.assertFalse;
+
import com.android.tools.r8.R8;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
@@ -53,7 +55,6 @@
insn ->
insn.getOpcode() == Opcodes.MONITORENTER
|| insn.getOpcode() == Opcodes.MONITOREXIT);
- // TODO(b/73921688): Should not have monitor instruction here
- assert hasMonitor;
+ assertFalse(hasMonitor);
}
}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
new file mode 100644
index 0000000..40d8ad0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugaring.interfacemethods;
+
+import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.desugaring.interfacemethods.test0.InterfaceWithDefaults;
+import com.android.tools.r8.desugaring.interfacemethods.test0.TestMain;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(VmTestRunner.class)
+public class InterfaceMethodDesugaringTests extends AsmTestBase {
+
+ @Test
+ public void testInvokeSpecialToDefaultMethod() throws Exception {
+ ensureSameOutput(TestMain.class.getCanonicalName(),
+ ToolHelper.getMinApiLevelForDexVm(),
+ ToolHelper.getClassAsBytes(TestMain.class),
+ introduceInvokeSpecial(ToolHelper.getClassAsBytes(InterfaceWithDefaults.class)));
+ }
+
+ private byte[] introduceInvokeSpecial(byte[] classBytes) throws IOException {
+ try (InputStream input = new ByteArrayInputStream(classBytes)) {
+ ClassReader cr = new ClassReader(input);
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+ cr.accept(
+ new ClassVisitor(Opcodes.ASM6, cw) {
+ @Override
+ public MethodVisitor visitMethod(int access, String name,
+ String desc, String signature, String[] exceptions) {
+ MethodVisitor visitor = super.visitMethod(access, name, desc, signature, exceptions);
+ return new MethodVisitor(Opcodes.ASM6, visitor) {
+ @Override
+ public void visitMethodInsn(
+ int opcode, String owner, String name, String desc, boolean itf) {
+ if (opcode == Opcodes.INVOKEINTERFACE &&
+ owner.endsWith("test0/InterfaceWithDefaults") &&
+ name.equals("foo")) {
+ super.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, itf);
+
+ } else {
+ super.visitMethodInsn(opcode, owner, name, desc, itf);
+ }
+ }
+ };
+ }
+ }, 0);
+ return cw.toByteArray();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/InterfaceWithDefaults.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/InterfaceWithDefaults.java
new file mode 100644
index 0000000..fc5ab9e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/InterfaceWithDefaults.java
@@ -0,0 +1,14 @@
+package com.android.tools.r8.desugaring.interfacemethods.test0;
+
+public interface InterfaceWithDefaults {
+ default void foo() {
+ System.out.println("InterfaceWithDefaults::foo()");
+ }
+
+ default void bar() {
+ System.out.println("InterfaceWithDefaults::bar()");
+ this.foo();
+ }
+
+ void test();
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/TestMain.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/TestMain.java
new file mode 100644
index 0000000..b57cbf3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test0/TestMain.java
@@ -0,0 +1,19 @@
+package com.android.tools.r8.desugaring.interfacemethods.test0;
+
+public class TestMain implements InterfaceWithDefaults {
+ @Override
+ public void test() {
+ System.out.println("TestMain::test()");
+ this.foo();
+ this.bar();
+ }
+
+ @Override
+ public void foo() {
+ System.out.println("TestMain::foo()");
+ }
+
+ public static void main(String[] args) {
+ new TestMain().test();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
index fbb14f1..60ed129 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinAccessorTest.java
@@ -233,7 +233,7 @@
}
AndroidApp app = compileWithR8(jasminBuilder.build(),
- keepMainProguardConfiguration("Foo") + "\ndontobfuscate");
+ keepMainProguardConfiguration("Foo") + "\n-dontobfuscate");
String artOutput = runOnArt(app, "Foo");
System.out.println(artOutput);
}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
index 51523f2..a510cee 100644
--- a/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
@@ -128,14 +128,13 @@
FieldSubject f2 = clazz.field("java.lang.Object", "same");
assertTrue(f2.isPresent());
assertFalse(f2.isRenamed());
- assertEquals(f1.getField().field.name, f2.getField().field.name);
+ assertEquals(f1.getFinalName(), f2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
assertEquals(javaOutput.stdout, artOutput.stdout);
}
-
@Test
public void remainFieldNameConflict_useuniqueclassmembernames() throws Exception {
Assume.assumeTrue(ToolHelper.artSupported());
@@ -158,7 +157,7 @@
FieldSubject f2 = clazz.field("java.lang.Object", "same");
assertTrue(f2.isPresent());
assertTrue(f2.isRenamed());
- assertEquals(f1.getField().field.name, f2.getField().field.name);
+ assertEquals(f1.getFinalName(), f2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
@@ -189,7 +188,7 @@
FieldSubject f2 = clazz.field("java.lang.Object", "same");
assertTrue(f2.isPresent());
assertTrue(f2.isRenamed());
- assertEquals(f1.getField().field.name, f2.getField().field.name);
+ assertEquals(f1.getFinalName(), f2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
@@ -217,7 +216,7 @@
FieldSubject f2 = clazz.field("java.lang.Object", "same");
assertTrue(f2.isPresent());
assertTrue(f2.isRenamed());
- assertNotEquals(f1.getField().field.name, f2.getField().field.name);
+ assertNotEquals(f1.getFinalName(), f2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
@@ -246,7 +245,7 @@
FieldSubject f2 = clazz.field("java.lang.Object", "same");
assertTrue(f2.isPresent());
assertTrue(f2.isRenamed());
- assertEquals(f1.getField().field.name, f2.getField().field.name);
+ assertEquals(f1.getFinalName(), f2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
@@ -291,7 +290,6 @@
+ " static <methods>;"
+ "}\n",
keepMainProguardConfiguration(CLASS_NAME),
- "-useuniqueclassmembernames",
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
@@ -304,7 +302,7 @@
MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(m2.isPresent());
assertFalse(m2.isRenamed());
- assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+ assertEquals(m1.getFinalName(), m2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
@@ -333,7 +331,7 @@
MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(m2.isPresent());
assertTrue(m2.isRenamed());
- assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+ assertEquals(m1.getFinalName(), m2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
@@ -364,14 +362,13 @@
MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(m2.isPresent());
assertTrue(m2.isRenamed());
- assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+ assertEquals(m1.getFinalName(), m2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
assertEquals(javaOutput.stdout, artOutput.stdout);
}
-
@Test
public void resolveMethodNameConflict_no_options() throws Exception {
Assume.assumeTrue(ToolHelper.artSupported());
@@ -393,8 +390,7 @@
MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(m2.isPresent());
assertTrue(m2.isRenamed());
- // TODO(b/73149686): R8 should be able to fix this conflict w/o -overloadaggressively.
- assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+ assertNotEquals(m1.getFinalName(), m2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
@@ -423,7 +419,7 @@
MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(m2.isPresent());
assertTrue(m2.isRenamed());
- assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+ assertEquals(m1.getFinalName(), m2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
@@ -481,7 +477,6 @@
+ " <methods>;"
+ "}\n",
keepMainProguardConfiguration(CLASS_NAME),
- "-useuniqueclassmembernames",
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null);
@@ -494,7 +489,7 @@
MethodSubject m2 = sup.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(m2.isPresent());
assertFalse(m2.isRenamed());
- assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+ assertEquals(m1.getFinalName(), m2.getFinalName());
ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
assertTrue(sub.isPresent());
@@ -504,11 +499,11 @@
MethodSubject subM2 = sub.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(subM2.isPresent());
assertFalse(subM2.isRenamed());
- assertEquals(subM1.getMethod().method.name, subM2.getMethod().method.name);
+ assertEquals(subM1.getFinalName(), subM2.getFinalName());
// No matter what, overloading methods should be renamed to the same name.
- assertEquals(m1.getMethod().method.name, subM1.getMethod().method.name);
- assertEquals(m2.getMethod().method.name, subM2.getMethod().method.name);
+ assertEquals(m1.getFinalName(), subM1.getFinalName());
+ assertEquals(m2.getFinalName(), subM2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
@@ -537,7 +532,7 @@
MethodSubject m2 = sup.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(m2.isPresent());
assertTrue(m2.isRenamed());
- assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+ assertEquals(m1.getFinalName(), m2.getFinalName());
ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
assertTrue(sub.isPresent());
@@ -547,11 +542,11 @@
MethodSubject subM2 = sub.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(subM2.isPresent());
assertTrue(subM2.isRenamed());
- assertEquals(subM1.getMethod().method.name, subM2.getMethod().method.name);
+ assertEquals(subM1.getFinalName(), subM2.getFinalName());
// No matter what, overloading methods should be renamed to the same name.
- assertEquals(m1.getMethod().method.name, subM1.getMethod().method.name);
- assertEquals(m2.getMethod().method.name, subM2.getMethod().method.name);
+ assertEquals(m1.getFinalName(), subM1.getFinalName());
+ assertEquals(m2.getFinalName(), subM2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
@@ -582,7 +577,7 @@
MethodSubject m2 = sup.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(m2.isPresent());
assertTrue(m2.isRenamed());
- assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+ assertEquals(m1.getFinalName(), m2.getFinalName());
ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
assertTrue(sub.isPresent());
@@ -592,18 +587,17 @@
MethodSubject subM2 = sub.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(subM2.isPresent());
assertTrue(subM2.isRenamed());
- assertEquals(subM1.getMethod().method.name, subM2.getMethod().method.name);
+ assertEquals(subM1.getFinalName(), subM2.getFinalName());
// No matter what, overloading methods should be renamed to the same name.
- assertEquals(m1.getMethod().method.name, subM1.getMethod().method.name);
- assertEquals(m2.getMethod().method.name, subM2.getMethod().method.name);
+ assertEquals(m1.getFinalName(), subM1.getFinalName());
+ assertEquals(m2.getFinalName(), subM2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
assertEquals(javaOutput.stdout, artOutput.stdout);
}
-
@Test
public void resolveMethodNameConflictInHierarchy_no_options() throws Exception {
Assume.assumeTrue(ToolHelper.artSupported());
@@ -625,8 +619,7 @@
MethodSubject m2 = sup.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(m2.isPresent());
assertTrue(m2.isRenamed());
- // TODO(b/73149686): R8 should be able to fix this conflict w/o -overloadaggressively.
- assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+ assertNotEquals(m1.getFinalName(), m2.getFinalName());
ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
assertTrue(sub.isPresent());
@@ -636,12 +629,11 @@
MethodSubject subM2 = sub.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(subM2.isPresent());
assertTrue(subM2.isRenamed());
- // TODO(b/73149686): R8 should be able to fix this conflict w/o -overloadaggressively.
- assertEquals(subM1.getMethod().method.name, subM2.getMethod().method.name);
+ assertNotEquals(subM1.getFinalName(), subM2.getFinalName());
// No matter what, overloading methods should be renamed to the same name.
- assertEquals(m1.getMethod().method.name, subM1.getMethod().method.name);
- assertEquals(m2.getMethod().method.name, subM2.getMethod().method.name);
+ assertEquals(m1.getFinalName(), subM1.getFinalName());
+ assertEquals(m2.getFinalName(), subM2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
@@ -671,7 +663,7 @@
MethodSubject m2 = sup.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(m2.isPresent());
assertTrue(m2.isRenamed());
- assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+ assertEquals(m1.getFinalName(), m2.getFinalName());
ClassSubject sub = dexInspector.clazz(ANOTHER_CLASS);
assertTrue(sub.isPresent());
@@ -681,11 +673,11 @@
MethodSubject subM2 = sub.method("java.lang.Object", "same", ImmutableList.of());
assertTrue(subM2.isPresent());
assertTrue(subM2.isRenamed());
- assertEquals(subM1.getMethod().method.name, subM2.getMethod().method.name);
+ assertEquals(subM1.getFinalName(), subM2.getFinalName());
// No matter what, overloading methods should be renamed to the same name.
- assertEquals(m1.getMethod().method.name, subM1.getMethod().method.name);
- assertEquals(m2.getMethod().method.name, subM2.getMethod().method.name);
+ assertEquals(m1.getFinalName(), subM1.getFinalName());
+ assertEquals(m2.getFinalName(), subM2.getFinalName());
ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
assertEquals(0, artOutput.exitCode);
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
index cfbfa84..434f213 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ForceProguardCompatibilityTest.java
@@ -47,9 +47,6 @@
import org.junit.Test;
public class ForceProguardCompatibilityTest extends TestBase {
- // Actually running Proguard should only be during development.
- private final boolean RUN_PROGUARD = false;
-
private void test(Class mainClass, Class mentionedClass, boolean forceProguardCompatibility)
throws Exception {
String proguardConfig = keepMainProguardConfiguration(mainClass, true, false);
@@ -160,11 +157,13 @@
assertEquals(0, configuration.getRules().size());
}
- if (RUN_PROGUARD) {
+ if (isRunProguard()) {
Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+ Path proguardMapFile = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
- ToolHelper.runProguard(jarTestClasses(testClass), proguardedJar, proguardConfigFile);
+ ToolHelper.runProguard(jarTestClasses(testClass),
+ proguardedJar, proguardConfigFile, proguardMapFile);
}
}
@@ -199,16 +198,16 @@
assertEquals(forceProguardCompatibility && containsCheckCast, !clazz.isAbstract());
}
- if (RUN_PROGUARD) {
+ if (isRunProguard()) {
Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
ToolHelper.runProguard(jarTestClasses(ImmutableList.of(mainClass, instantiatedClass)),
- proguardedJar, proguardConfigFile);
- Set<String> classesAfterProguard = readClassesInJar(proguardedJar);
- assertTrue(classesAfterProguard.contains(mainClass.getCanonicalName()));
+ proguardedJar, proguardConfigFile, null);
+ DexInspector proguardInspector = new DexInspector(readJar(proguardedJar));
+ assertTrue(proguardInspector.clazz(mainClass).isPresent());
assertEquals(
- containsCheckCast, classesAfterProguard.contains(instantiatedClass.getCanonicalName()));
+ containsCheckCast, proguardInspector.clazz(instantiatedClass).isPresent());
}
}
@@ -288,19 +287,20 @@
assertEquals(0, configuration.getRules().size());
}
- if (RUN_PROGUARD) {
+ if (isRunProguard()) {
Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+ Path proguardMapFile = File.createTempFile("proguard", ".map", temp.getRoot()).toPath();
FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
ToolHelper.runProguard(jarTestClasses(
ImmutableList.of(mainClass, forNameClass1, forNameClass2)),
- proguardedJar, proguardConfigFile);
- Set<String> classesAfterProguard = readClassesInJar(proguardedJar);
- assertEquals(3, classesAfterProguard.size());
- assertTrue(classesAfterProguard.contains(mainClass.getCanonicalName()));
- if (!allowObfuscation) {
- assertTrue(classesAfterProguard.contains(forNameClass1.getCanonicalName()));
- assertTrue(classesAfterProguard.contains(forNameClass2.getCanonicalName()));
+ proguardedJar, proguardConfigFile, proguardMapFile);
+ DexInspector proguardedInspector = new DexInspector(readJar(proguardedJar), proguardMapFile);
+ assertEquals(3, proguardedInspector.allClasses().size());
+ assertTrue(proguardedInspector.clazz(mainClass).isPresent());
+ for (Class clazz : ImmutableList.of(forNameClass1, forNameClass2)) {
+ assertTrue(proguardedInspector.clazz(clazz).isPresent());
+ assertEquals(allowObfuscation, proguardedInspector.clazz(clazz).isRenamed());
}
}
}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
new file mode 100644
index 0000000..d098ef7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.forceproguardcompatibility;
+
+import com.android.tools.r8.CompatProguardCommandBuilder;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.nio.file.Path;
+import java.util.List;
+
+public class ProguardCompatabilityTestBase extends TestBase {
+
+ protected DexInspector runR8Compat(
+ List<Class> programClasses, String proguardConfig) throws Exception {
+ CompatProguardCommandBuilder builder = new CompatProguardCommandBuilder(true);
+ builder.addProguardConfiguration(ImmutableList.of(proguardConfig), Origin.unknown());
+ programClasses.forEach(
+ clazz -> builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz)));
+ builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+ return new DexInspector(ToolHelper.runR8(builder.build()));
+ }
+
+ protected DexInspector runR8CompatKeepingMain(Class mainClass, List<Class> programClasses)
+ throws Exception {
+ return runR8Compat(programClasses, keepMainProguardConfiguration(mainClass));
+ }
+
+ protected DexInspector runProguard(
+ List<Class> programClasses, String proguardConfig) throws Exception {
+ Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
+ Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+ FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+ ToolHelper.runProguard(jarTestClasses(programClasses), proguardedJar, proguardConfigFile, null);
+ return new DexInspector(readJar(proguardedJar));
+ }
+
+ protected DexInspector runProguardAndD8(
+ List<Class> programClasses, String proguardConfig) throws Exception {
+ Path proguardedJar = File.createTempFile("proguarded", ".jar", temp.getRoot()).toPath();
+ Path proguardConfigFile = File.createTempFile("proguard", ".config", temp.getRoot()).toPath();
+ FileUtils.writeTextFile(proguardConfigFile, proguardConfig);
+ ToolHelper.runProguard(jarTestClasses(programClasses), proguardedJar, proguardConfigFile, null);
+ AndroidApp app = ToolHelper.runD8(readJar(proguardedJar));
+ return new DexInspector(app);
+ }
+
+ protected DexInspector runProguardKeepingMain(Class mainClass, List<Class> programClasses)
+ throws Exception {
+ return runProguardAndD8(programClasses, keepMainProguardConfiguration(mainClass));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
new file mode 100644
index 0000000..4cb7750
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -0,0 +1,319 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.forceproguardcompatibility.defaultctor;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.hasDefaultConstructor;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.smali.ConstantFoldingTest.TriConsumer;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+
+class SuperClass {
+
+}
+
+class SubClass extends SuperClass {
+
+}
+
+class MainInstantiationSubClass {
+
+ public static void main(String[] args) {
+ new SubClass();
+ }
+}
+
+class MainGetClassSubClass {
+
+ public static void main(String[] args) {
+ System.out.println(SubClass.class);
+ }
+}
+
+class MainCheckCastSubClass {
+
+ public static void main(String[] args) {
+ System.out.println((SubClass) null);
+ }
+}
+
+class MainClassForNameSubClass {
+
+ public static void main(String[] args) throws Exception{
+ System.out.println(Class.forName(
+ "com.android.tools.r8.shaking.forceproguardcompatibility.defaultctor.SubClass"));
+ }
+}
+
+class StaticFieldNotInitialized {
+ public static int field;
+}
+
+class MainGetStaticFieldNotInitialized {
+
+ public static void main(String[] args) {
+ System.out.println(StaticFieldNotInitialized.field);
+ }
+}
+
+class StaticMethod {
+ public static int method() {
+ return 1;
+ };
+}
+
+class MainCallStaticMethod {
+
+ public static void main(String[] args) {
+ System.out.println(StaticMethod.method());
+ }
+}
+
+class StaticFieldInitialized {
+ public static int field = 1;
+}
+
+class MainGetStaticFieldInitialized {
+
+ public static void main(String[] args) {
+ System.out.println(StaticFieldInitialized.field);
+ }
+}
+
+public class ImplicitlyKeptDefaultConstructorTest extends ProguardCompatabilityTestBase {
+
+ private void checkPresentWithDefaultConstructor(ClassSubject clazz) {
+ assertThat(clazz, isPresent());
+ assertThat(clazz, hasDefaultConstructor());
+ }
+
+ private void checkPresentWithoutDefaultConstructor(ClassSubject clazz) {
+ assertThat(clazz, isPresent());
+ assertThat(clazz, not(hasDefaultConstructor()));
+ }
+
+ private void checkAllClassesPresentWithDefaultConstructor(
+ Class mainClass, List<Class> programClasses, DexInspector inspector) {
+ assert programClasses.contains(mainClass);
+ assertEquals(programClasses.size(), inspector.allClasses().size());
+ inspector.forAllClasses(this::checkPresentWithDefaultConstructor);
+ }
+
+ private void checkAllClassesPresentOnlyMainWithDefaultConstructor(
+ Class mainClass, List<Class> programClasses, DexInspector inspector) {
+ assert programClasses.contains(mainClass);
+ assertEquals(programClasses.size(), inspector.allClasses().size());
+ checkPresentWithDefaultConstructor(inspector.clazz(mainClass));
+ inspector.allClasses()
+ .stream()
+ .filter(subject -> !subject.getOriginalName().equals(mainClass.getCanonicalName()))
+ .forEach(this::checkPresentWithoutDefaultConstructor);
+ }
+
+ private void checkOnlyMainPresent(
+ Class mainClass, List<Class> programClasses, DexInspector inspector) {
+ assert programClasses.contains(mainClass);
+ assertEquals(1, inspector.allClasses().size());
+ inspector.forAllClasses(this::checkPresentWithDefaultConstructor);
+ }
+
+ private void runTest(
+ Class mainClass, List<Class> programClasses, String proguardConfiguration,
+ TriConsumer<Class, List<Class>, DexInspector> r8Checker,
+ TriConsumer<Class, List<Class>, DexInspector> proguardChecker) throws Exception {
+ DexInspector inspector = runR8Compat(programClasses, proguardConfiguration);
+ r8Checker.accept(mainClass, programClasses, inspector);
+
+ if (isRunProguard()) {
+ inspector = runProguard(programClasses, proguardConfiguration);
+ proguardChecker.accept(mainClass, programClasses, inspector);
+ inspector = runProguardAndD8(programClasses, proguardConfiguration);
+ proguardChecker.accept(mainClass, programClasses, inspector);
+ }
+ }
+
+ private void runTest(
+ Class mainClass, List<Class> programClasses, String proguardConfiguration,
+ TriConsumer<Class, List<Class>, DexInspector> checker) throws Exception {
+ runTest(mainClass, programClasses, proguardConfiguration, checker, checker);
+ }
+
+ @Test
+ public void testInstantiation() throws Exception {
+ // A new instance call keeps the default constructor.
+ Class mainClass = MainInstantiationSubClass.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
+ keepMainProguardConfiguration(mainClass),
+ this::checkAllClassesPresentWithDefaultConstructor);
+ }
+
+ @Test
+ public void testGetClass() throws Exception {
+ // Reference to the class constant keeps the default constructor.
+ Class mainClass = MainGetClassSubClass.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
+ keepMainProguardConfiguration(mainClass),
+ this::checkAllClassesPresentWithDefaultConstructor);
+ }
+
+ @Test
+ public void testCheckCast() throws Exception {
+ // Reference to the class constant keeps the default constructor.
+ Class mainClass = MainCheckCastSubClass.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
+ keepMainProguardConfiguration(mainClass),
+ // TODO(74423424): Proguard eliminates the check-cast on null.
+ this::checkAllClassesPresentWithDefaultConstructor,
+ this::checkOnlyMainPresent);
+ }
+
+
+ @Test
+ public void testCheckCastWithoutInlining() throws Exception {
+ // Reference to the class constant keeps the default constructor.
+ Class mainClass = MainCheckCastSubClass.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
+ keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+ this::checkAllClassesPresentWithDefaultConstructor);
+ }
+
+ @Test
+ public void testClassForName() throws Exception {
+ // Class.forName with a constant string keeps the default constructor.
+ Class mainClass = MainClassForNameSubClass.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, SuperClass.class, SubClass.class),
+ keepMainProguardConfiguration(mainClass),
+ this::checkAllClassesPresentWithDefaultConstructor);
+ }
+
+ @Test
+ public void testStaticFieldWithoutInitializationStaticClassKept() throws Exception {
+ // An explicit keep rule keeps the default constructor.
+ Class mainClass = MainGetStaticFieldNotInitialized.class;
+ String proguardConfiguration = keepMainProguardConfiguration(
+ mainClass,
+ ImmutableList.of(
+ "-keep class " + getJavacGeneratedClassName(StaticFieldNotInitialized.class) + " {",
+ "}"));
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, StaticFieldNotInitialized.class),
+ proguardConfiguration,
+ this::checkAllClassesPresentWithDefaultConstructor);
+ }
+
+ @Test
+ public void testStaticFieldWithInitializationStaticClassKept() throws Exception {
+ // An explicit keep rule keeps the default constructor.
+ Class mainClass = MainGetStaticFieldInitialized.class;
+ String proguardConfiguration = keepMainProguardConfiguration(
+ mainClass,
+ ImmutableList.of(
+ "-keep class " + getJavacGeneratedClassName(StaticFieldInitialized.class) + " {",
+ "}"));
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, StaticFieldInitialized.class),
+ proguardConfiguration,
+ this::checkAllClassesPresentWithDefaultConstructor);
+ }
+
+ @Test
+ public void testStaticMethodStaticClassKept() throws Exception {
+ // An explicit keep rule keeps the default constructor.
+ Class mainClass = MainCallStaticMethod.class;
+ String proguardConfiguration = keepMainProguardConfiguration(
+ mainClass,
+ ImmutableList.of(
+ "-keep class " + getJavacGeneratedClassName(StaticMethod.class) + " {",
+ "}"));
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, StaticMethod.class),
+ proguardConfiguration,
+ this::checkAllClassesPresentWithDefaultConstructor);
+ }
+
+ @Test
+ public void testStaticFieldWithoutInitialization() throws Exception {
+ Class mainClass = MainGetStaticFieldNotInitialized.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, StaticFieldNotInitialized.class),
+ keepMainProguardConfiguration(mainClass),
+ // TODO(74379749): Proguard does not keep the class with the un-initialized static field.
+ this::checkAllClassesPresentOnlyMainWithDefaultConstructor,
+ this::checkOnlyMainPresent);
+ }
+
+ @Test
+ public void testStaticFieldWithInitialization() throws Exception {
+ Class mainClass = MainGetStaticFieldInitialized.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, StaticFieldInitialized.class),
+ keepMainProguardConfiguration(mainClass),
+ this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
+ }
+
+ @Test
+ public void testStaticMethodStaticClassNotKept() throws Exception {
+ // Due to inlining only the main method is left.
+ Class mainClass = MainCallStaticMethod.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, StaticMethod.class),
+ keepMainProguardConfiguration(mainClass),
+ this::checkOnlyMainPresent);
+ }
+
+ @Test
+ public void testStaticFieldWithoutInitializationWithoutInlining() throws Exception {
+ Class mainClass = MainGetStaticFieldNotInitialized.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, StaticFieldNotInitialized.class),
+ keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+ this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
+ }
+
+ @Test
+ public void testStaticFieldWithInitializationWithoutInlining() throws Exception {
+ Class mainClass = MainGetStaticFieldInitialized.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, StaticFieldInitialized.class),
+ keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+ this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
+ }
+
+ @Test
+ public void testStaticMethodStaticWithoutInlining() throws Exception {
+ Class mainClass = MainCallStaticMethod.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, StaticMethod.class),
+ keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+ this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
index ca3fd2a..d578815 100644
--- a/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/includedescriptorclasses/IncludeDescriptorClassesTest.java
@@ -14,48 +14,42 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
-import java.util.Set;
import org.junit.Test;
public class IncludeDescriptorClassesTest extends TestBase {
- // Actually running Proguard should only be during development.
- private final boolean RUN_PROGUARD = false;
-
private class Result {
final DexInspector inspector;
- final Set<String> classesAfterProguard;
+ final DexInspector proguardedInspector;
- Result(DexInspector inspector, Set<String> classesAfterProguard) {
+ Result(DexInspector inspector, DexInspector proguardedInspector) {
this.inspector = inspector;
- this.classesAfterProguard = classesAfterProguard;
+ this.proguardedInspector = proguardedInspector;
}
void assertKept(Class clazz) {
assertTrue(inspector.clazz(clazz.getCanonicalName()).isPresent());
assertFalse(inspector.clazz(clazz.getCanonicalName()).isRenamed());
- if (classesAfterProguard != null) {
- assertTrue(classesAfterProguard.contains(clazz.getCanonicalName()));
+ if (proguardedInspector != null) {
+ assertTrue(proguardedInspector.clazz(clazz).isPresent());
}
}
// NOTE: 'synchronized' is supposed to disable inlining of this method.
synchronized void assertRemoved(Class clazz) {
assertFalse(inspector.clazz(clazz.getCanonicalName()).isPresent());
- // TODO(sgjesse): Also check that it was not just renamed...
- if (classesAfterProguard != null) {
- assertFalse(classesAfterProguard.contains(clazz.getCanonicalName()));
+ if (proguardedInspector != null) {
+ assertFalse(proguardedInspector.clazz(clazz).isPresent());
}
}
void assertRenamed(Class clazz) {
assertTrue(inspector.clazz(clazz.getCanonicalName()).isPresent());
assertTrue(inspector.clazz(clazz.getCanonicalName()).isRenamed());
- // TODO(sgjesse): Also check that it was actually renamed...
- if (classesAfterProguard != null) {
- assertFalse(classesAfterProguard.contains(clazz.getCanonicalName()));
+ if (proguardedInspector != null) {
+ assertTrue(proguardedInspector.clazz(clazz).isPresent());
+ assertTrue(proguardedInspector.clazz(clazz).isRenamed());
}
}
-
}
private List<Class> applicationClasses = ImmutableList.of(
@@ -70,15 +64,16 @@
DexInspector inspector = new DexInspector(compileWithR8(classes, proguardConfig));
- Set<String> classesAfterProguard = null;
+ DexInspector proguardedInspector = null;
// Actually running Proguard should only be during development.
- if (RUN_PROGUARD) {
+ if (isRunProguard()) {
Path proguardedJar = temp.newFolder().toPath().resolve("proguarded.jar");
- ToolHelper.runProguard(jarTestClasses(classes), proguardedJar, proguardConfig);
- classesAfterProguard = readClassesInJar(proguardedJar);
+ Path proguardedMap = temp.newFolder().toPath().resolve("proguarded.map");
+ ToolHelper.runProguard(jarTestClasses(classes), proguardedJar, proguardConfig, proguardedMap);
+ proguardedInspector = new DexInspector(readJar(proguardedJar), proguardedMap);
}
- return new Result(inspector, classesAfterProguard);
+ return new Result(inspector, proguardedInspector);
}
@Test
diff --git a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java
index 7904400..7f8e5fb 100644
--- a/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/keepclassmembers/KeepClassMembersTest.java
@@ -4,26 +4,61 @@
package com.android.tools.r8.shaking.keepclassmembers;
-import static org.junit.Assert.assertEquals;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isAbstract;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.TestBase;
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
import com.android.tools.r8.utils.DexInspector;
import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
-public class KeepClassMembersTest extends TestBase {
+public class KeepClassMembersTest extends ProguardCompatabilityTestBase {
- public void runTest(Class mainClass, Class<?> staticClass,
- boolean forceProguardCompatibility) throws Exception {
- boolean staticClassHasDefaultConstructor = true;
- try {
- staticClass.getDeclaredConstructor();
- } catch (NoSuchMethodException e) {
- staticClassHasDefaultConstructor = false;
+ private void check(DexInspector inspector, Class mainClass, Class<?> staticClass,
+ boolean forceProguardCompatibility, boolean fromProguard) {
+ assertTrue(inspector.clazz(mainClass).isPresent());
+ ClassSubject staticClassSubject = inspector.clazz(staticClass);
+ assertThat(staticClassSubject, isPresent());
+ assertThat(staticClassSubject.method("int", "getA", ImmutableList.of()), isPresent());
+ assertThat(staticClassSubject.method("int", "getB", ImmutableList.of()), not(isPresent()));
+ assertThat(staticClassSubject.field("int", "a"), isPresent());
+ assertThat(staticClassSubject.field("int", "b"), isPresent());
+ assertThat(staticClassSubject.field("int", "c"), not(isPresent()));
+ // Neither Proguard not R8 keeps any constructors.
+ staticClassSubject.forAllMethods(method -> assertFalse(method.isInstanceInitializer()));
+ assertThat(staticClassSubject.init(ImmutableList.of()), not(isPresent()));
+ MethodSubject getIMethod = staticClassSubject.method("int", "getI", ImmutableList.of());
+ FieldSubject iField = staticClassSubject.field("int", "i");
+ if (forceProguardCompatibility) {
+ if (fromProguard) {
+ // Proguard keeps the instance method and it code even though the class does not have a
+ // constructor, and therefore cannot be instantiated.
+ assertThat(getIMethod, isPresent());
+ assertThat(iField, isPresent());
+ } else {
+ // Force Proguard compatibility keeps the instance method, but makes it abstract as
+ // the class does not have a constructor, and therefore cannot be instantiated.
+ assertThat(getIMethod, isAbstract());
+ // As the method is abstract the referenced field is not present.
+ assertThat(iField, not(isPresent()));
+ }
+ } else {
+ assertThat(getIMethod, not(isPresent()));
+ assertThat(iField, not(isPresent()));
}
+ assertThat(staticClassSubject.method("int", "getJ", ImmutableList.of()), not(isPresent()));
+ assertThat(staticClassSubject.field("int", "j"), not(isPresent()));
+ }
+
+ private void runTest(Class mainClass, Class<?> staticClass,
+ boolean forceProguardCompatibility) throws Exception {
String proguardConfig = String.join("\n", ImmutableList.of(
"-keepclassmembers class **.PureStatic* {",
" public static int b;",
@@ -35,27 +70,16 @@
"}",
"-dontoptimize", "-dontobfuscate"
));
- DexInspector inspector = new DexInspector(
- compileWithR8(ImmutableList.of(mainClass, staticClass), proguardConfig,
- options -> options.forceProguardCompatibility = forceProguardCompatibility));
- assertTrue(inspector.clazz(mainClass).isPresent());
- ClassSubject staticClassSubject = inspector.clazz(staticClass);
- assertTrue(staticClassSubject.isPresent());
- assertTrue(staticClassSubject.method("int", "getA", ImmutableList.of()).isPresent());
- assertFalse(staticClassSubject.method("int", "getB", ImmutableList.of()).isPresent());
- assertTrue(staticClassSubject.field("int", "a").isPresent());
- assertTrue(staticClassSubject.field("int", "b").isPresent());
- assertFalse(staticClassSubject.field("int", "c").isPresent());
- // Force Proguard compatibility keeps the default constructor if present and then assumes
- // instantiated, hence keeps the instance method as well.
- assertEquals(forceProguardCompatibility && staticClassHasDefaultConstructor,
- staticClassSubject.init(ImmutableList.of()).isPresent());
- assertEquals(forceProguardCompatibility && staticClassHasDefaultConstructor,
- staticClassSubject.method("int", "getI", ImmutableList.of()).isPresent());
- assertEquals(forceProguardCompatibility && staticClassHasDefaultConstructor,
- staticClassSubject.field("int", "i").isPresent());
- assertFalse(staticClassSubject.method("int", "getJ", ImmutableList.of()).isPresent());
- assertFalse(staticClassSubject.field("int", "j").isPresent());
+ DexInspector inspector;
+ inspector = new DexInspector(
+ compileWithR8(ImmutableList.of(mainClass, staticClass), proguardConfig,
+ options -> options.forceProguardCompatibility = forceProguardCompatibility));
+ check(inspector, mainClass, staticClass, forceProguardCompatibility, false);
+
+ if (isRunProguard()) {
+ inspector = runProguard(ImmutableList.of(mainClass, staticClass), proguardConfig);
+ check(inspector, mainClass, staticClass, true, true);
+ }
}
@Test
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 95ecd4f..f0d5d37 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils;
+import com.android.tools.r8.StringResource;
import com.android.tools.r8.code.Const4;
import com.android.tools.r8.code.ConstString;
import com.android.tools.r8.code.Goto;
@@ -135,6 +136,12 @@
.read(app.getProguardMapOutputData()));
}
+ public DexInspector(AndroidApp app, Path proguardMap) throws IOException, ExecutionException {
+ this(
+ new ApplicationReader(app, new InternalOptions(), new Timing("DexInspector"))
+ .read(StringResource.fromFile(proguardMap)));
+ }
+
public DexInspector(DexApplication application) {
dexItemFactory = application.dexItemFactory;
this.application = application;
@@ -614,6 +621,16 @@
public abstract Signature getOriginalSignature();
public abstract Signature getFinalSignature();
+
+ public String getOriginalName() {
+ Signature originalSignature = getOriginalSignature();
+ return originalSignature == null ? null : originalSignature.name;
+ }
+
+ public String getFinalName() {
+ Signature finalSignature = getFinalSignature();
+ return finalSignature == null ? null : finalSignature.name;
+ }
}
public abstract class MethodSubject extends MemberSubject {
@@ -905,6 +922,11 @@
public DexEncodedField getField() {
return dexField;
}
+
+ @Override
+ public String toString() {
+ return dexField.toSourceString();
+ }
}
public class TypeSubject extends Subject {
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java b/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
new file mode 100644
index 0000000..fc9bc29
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.DexInspector.Subject;
+import com.google.common.collect.ImmutableList;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class DexInspectorMatchers {
+
+ public static Matcher<Subject> isPresent() {
+ return new TypeSafeMatcher<Subject>() {
+ private String type(Subject subject) {
+ String type = "<unknown subject type>";
+ if (subject instanceof ClassSubject) {
+ type = "class";
+ } else if (subject instanceof MethodSubject) {
+ type = "method";
+ } else if (subject instanceof FieldSubject) {
+ type = "field";
+ }
+ return type;
+ }
+
+ private String name(Subject subject) {
+ String name = "<unknown>";
+ if (subject instanceof ClassSubject) {
+ name = ((ClassSubject) subject).getOriginalName();
+ } else if (subject instanceof MethodSubject) {
+ name = ((MethodSubject) subject).getOriginalName();
+ } else if (subject instanceof FieldSubject) {
+ name = ((FieldSubject) subject).getOriginalName();
+ }
+ return name;
+ }
+
+ @Override
+ public boolean matchesSafely(final Subject clazz) {
+ return clazz.isPresent();
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(" present");
+ }
+
+ @Override
+ public void describeMismatchSafely(final Subject subject, Description description) {
+ description
+ .appendText(type(subject) + " ").appendValue(name(subject)).appendText(" was not");
+ }
+ };
+ }
+
+ public static Matcher<ClassSubject> hasDefaultConstructor() {
+ return new TypeSafeMatcher<ClassSubject>() {
+ @Override
+ public boolean matchesSafely(final ClassSubject clazz) {
+ return clazz.init(ImmutableList.of()).isPresent();
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("class having default constructor");
+ }
+
+ @Override
+ public void describeMismatchSafely(final ClassSubject clazz, Description description) {
+ description
+ .appendText("class ").appendValue(clazz.getOriginalName()).appendText(" did not");
+ }
+ };
+ }
+
+ public static Matcher<MethodSubject> isAbstract() {
+ return new TypeSafeMatcher<MethodSubject>() {
+ @Override
+ public boolean matchesSafely(final MethodSubject method) {
+ return method.isPresent() && method.isAbstract();
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("method abstract");
+ }
+
+ @Override
+ public void describeMismatchSafely(final MethodSubject method, Description description) {
+ description
+ .appendText("method ").appendValue(method.getOriginalName()).appendText(" was not");
+ }
+ };
+ }
+}
diff --git a/third_party/benchmarks/santa-tracker.tar.gz.sha1 b/third_party/benchmarks/santa-tracker.tar.gz.sha1
new file mode 100644
index 0000000..d37a000
--- /dev/null
+++ b/third_party/benchmarks/santa-tracker.tar.gz.sha1
@@ -0,0 +1 @@
+88150a9a2215f1f821ea9321e61b5fc8276dffd3
\ No newline at end of file
diff --git a/third_party/gradle-plugin.tar.gz.sha1 b/third_party/gradle-plugin.tar.gz.sha1
new file mode 100644
index 0000000..2fd1633
--- /dev/null
+++ b/third_party/gradle-plugin.tar.gz.sha1
@@ -0,0 +1 @@
+a0bea68a3b71d485296fe6aec54f995cdb6dd300
\ No newline at end of file
diff --git a/third_party/kotlin.tar.gz.sha1 b/third_party/kotlin.tar.gz.sha1
index e55712d..40f26fa 100644
--- a/third_party/kotlin.tar.gz.sha1
+++ b/third_party/kotlin.tar.gz.sha1
@@ -1 +1 @@
-6dc49791e5fcf4318ae5246eacc73718180508ce
\ No newline at end of file
+4b18d827485f53990ad47b81db2a025abaa325d1
\ No newline at end of file
diff --git a/third_party/proguard/README.google b/third_party/proguard/README.google
index 4f85601..abe0568 100644
--- a/third_party/proguard/README.google
+++ b/third_party/proguard/README.google
@@ -1,8 +1,8 @@
URL: https://sourceforge.net/projects/proguard/files/proguard/5.2/
URL: https://sourceforge.net/projects/proguard/files/proguard/6.0/
-Version: 5.2.1, 6.0
+Version: 5.2.1, 6.0.1
License: GPL
-License File: proguard5.2.1/docs/license.html, proguard6.0/docs/license.html
+License File: proguard5.2.1/docs/license.html, proguard6.0.1/docs/license.html
Description:
ProGuard Java Optimizer and Obfuscator
diff --git a/third_party/proguard/proguard6.0.1.tar.gz.sha1 b/third_party/proguard/proguard6.0.1.tar.gz.sha1
new file mode 100644
index 0000000..e5e8e3c
--- /dev/null
+++ b/third_party/proguard/proguard6.0.1.tar.gz.sha1
@@ -0,0 +1 @@
+ef075c414299327dae5f96cce539422dc9088946
\ No newline at end of file
diff --git a/third_party/proguard/proguard6.0.tar.gz.sha1 b/third_party/proguard/proguard6.0.tar.gz.sha1
deleted file mode 100644
index 4596bc8..0000000
--- a/third_party/proguard/proguard6.0.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-57d0702f38196c81ff506d2e34a4a5569c3af583
\ No newline at end of file
diff --git a/tools/create_art_tests.py b/tools/create_art_tests.py
index feb36a2..13757a9 100755
--- a/tools/create_art_tests.py
+++ b/tools/create_art_tests.py
@@ -16,7 +16,7 @@
JACK_TEST = os.path.join('tests', '2016-12-19', 'art')
TEST_DIR = os.path.join('tests', '2017-10-04', 'art')
TOOLCHAINS = ["dx", "jack", "none"]
-TOOLS = ["r8", "d8"]
+TOOLS = ["r8", "d8", "r8cf"]
TEMPLATE = Template(
"""// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
@@ -54,6 +54,7 @@
rmtree(toolchain_dir)
makedirs(join(toolchain_dir, "d8"))
makedirs(join(toolchain_dir, "r8"))
+ makedirs(join(toolchain_dir, "r8cf"))
def write_file(toolchain, tool, class_name, contents):
file_name = join(OUTPUT_DIR, toolchain, tool, class_name + ".java")
@@ -74,6 +75,10 @@
tool_enum = 'R8_AFTER_D8'
else:
tool_enum = upper(tool)
+ if tool == "r8cf":
+ if toolchain != "none":
+ continue
+ tool_enum = 'D8_AFTER_R8CF'
contents = TEMPLATE.substitute(
name=dir,
compilerUnderTestEnum=tool_enum,