Merge "Add santa-tracker benchmark app"
diff --git a/build.gradle b/build.gradle
index f1abcb7..64c13e3 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",
@@ -357,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/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 42acb7c..d6b15eb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -146,6 +146,46 @@
return result;
}
+ /**
+ * For all annotations on the class and all annotations on its methods and fields apply the
+ * specified consumer.
+ */
+ public void forEachAnnotation(Consumer<DexAnnotation> consumer) {
+ for (DexAnnotation annotation : annotations.annotations) {
+ consumer.accept(annotation);
+ }
+ for (DexEncodedMethod method : directMethods()) {
+ for (DexAnnotation annotation : method.annotations.annotations) {
+ consumer.accept(annotation);
+ }
+ for (DexAnnotationSet parameterAnnotations : method.parameterAnnotations.values) {
+ for (DexAnnotation annotation : parameterAnnotations.annotations) {
+ consumer.accept(annotation);
+ }
+ }
+ }
+ for (DexEncodedMethod method : virtualMethods()) {
+ for (DexAnnotation annotation : method.annotations.annotations) {
+ consumer.accept(annotation);
+ }
+ for (DexAnnotationSet parameterAnnotations : method.parameterAnnotations.values) {
+ for (DexAnnotation annotation : parameterAnnotations.annotations) {
+ consumer.accept(annotation);
+ }
+ }
+ }
+ for (DexEncodedField field : instanceFields()) {
+ for (DexAnnotation annotation : field.annotations.annotations) {
+ consumer.accept(annotation);
+ }
+ }
+ for (DexEncodedField field : staticFields()) {
+ for (DexAnnotation annotation : field.annotations.annotations) {
+ consumer.accept(annotation);
+ }
+ }
+ }
+
public void forEachField(Consumer<DexEncodedField> consumer) {
for (DexEncodedField field : staticFields()) {
consumer.accept(field);
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/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/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/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/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index de21187..5c51786 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -85,13 +85,13 @@
addMainDexType(dexType);
continue;
}
- for (DexAnnotation annotation : clazz.annotations.annotations) {
- if (annotation.visibility == DexAnnotation.VISIBILITY_RUNTIME
+ clazz.forEachAnnotation(annotation -> {
+ if (!mainDexTypes.contains(dexType)
+ && annotation.visibility == DexAnnotation.VISIBILITY_RUNTIME
&& isAnnotationWithEnum(annotation.annotation.type)) {
addMainDexType(dexType);
- break;
}
- }
+ });
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index b526973..e96de6f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -535,7 +535,7 @@
private ProguardIfRule parseIfRule(TextPosition optionStart)
throws ProguardRuleParserException {
ProguardIfRule.Builder ifRuleBuilder = ProguardIfRule.builder();
- parseClassSpec(ifRuleBuilder, true);
+ parseClassSpec(ifRuleBuilder, false);
// Required a subsequent keep rule.
skipWhitespace();
@@ -1086,15 +1086,52 @@
return Integer.parseInt(s);
}
+ private final Predicate<Character> CLASS_NAME_PREDICATE =
+ character -> IdentifierUtils.isDexIdentifierPart(character)
+ || character == '.'
+ || character == '*'
+ || character == '?'
+ || character == '%'
+ || character == '['
+ || character == ']';
+
private String acceptClassName() {
- return acceptString(character ->
- IdentifierUtils.isDexIdentifierPart(character)
- || character == '.'
- || character == '*'
- || character == '?'
- || character == '%'
- || character == '['
- || character == ']');
+ return acceptString(CLASS_NAME_PREDICATE);
+ }
+
+ private String acceptClassNameWithNthWildcard() {
+ StringBuilder nthWildcard = null;
+ skipWhitespace();
+ int start = position;
+ int end = position;
+ while (!eof(end)) {
+ char current = contents.charAt(end);
+ if (nthWildcard != null) {
+ if (current == '>') {
+ try {
+ Integer.parseUnsignedInt(nthWildcard.toString());
+ } catch (NullPointerException | NumberFormatException e) {
+ return null;
+ }
+ nthWildcard = null;
+ } else {
+ nthWildcard.append(current);
+ }
+ end++;
+ } else if (CLASS_NAME_PREDICATE.test(current)) {
+ end++;
+ } else if (current == '<') {
+ nthWildcard = new StringBuilder();
+ end++;
+ } else {
+ break;
+ }
+ }
+ if (start == end) {
+ return null;
+ }
+ position = end;
+ return contents.substring(start, end);
}
private String acceptFieldNameOrIntegerForReturn() {
@@ -1194,7 +1231,7 @@
}
private String parseClassName() throws ProguardRuleParserException {
- String name = acceptClassName();
+ String name = acceptClassNameWithNthWildcard();
if (name == null) {
throw parseError("Class name expected");
}
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/examples/multidex002/AnnotatedDirectMethod.java b/src/test/examples/multidex002/AnnotatedDirectMethod.java
new file mode 100644
index 0000000..761ea9a
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotatedDirectMethod.java
@@ -0,0 +1,12 @@
+// 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 multidex002;
+
+public class AnnotatedDirectMethod {
+
+ @AnnotationWithEnum3(AnnotationWithEnum3.Value3.VAL3_2)
+ private void foo() {
+ }
+}
diff --git a/src/test/examples/multidex002/AnnotatedInstanceField.java b/src/test/examples/multidex002/AnnotatedInstanceField.java
new file mode 100644
index 0000000..0209f44
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotatedInstanceField.java
@@ -0,0 +1,11 @@
+// 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 multidex002;
+
+public class AnnotatedInstanceField {
+
+ @AnnotationWithEnum3(AnnotationWithEnum3.Value3.VAL3_2)
+ public String value = "myValue";
+}
diff --git a/src/test/examples/multidex002/AnnotatedMethodParameter.java b/src/test/examples/multidex002/AnnotatedMethodParameter.java
new file mode 100644
index 0000000..49a9e6d
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotatedMethodParameter.java
@@ -0,0 +1,11 @@
+// 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 multidex002;
+
+public class AnnotatedMethodParameter {
+
+ public void foo(@AnnotationWithEnum3(AnnotationWithEnum3.Value3.VAL3_2) String val) {
+ }
+}
diff --git a/src/test/examples/multidex002/AnnotatedNotKept.java b/src/test/examples/multidex002/AnnotatedNotKept.java
new file mode 100644
index 0000000..500e7f0
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotatedNotKept.java
@@ -0,0 +1,10 @@
+// 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 multidex002;
+
+@AnnotationWithoutEnum
+public class AnnotatedNotKept {
+
+}
diff --git a/src/test/examples/multidex002/AnnotatedStaticField.java b/src/test/examples/multidex002/AnnotatedStaticField.java
new file mode 100644
index 0000000..385d1d9
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotatedStaticField.java
@@ -0,0 +1,9 @@
+// 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 multidex002;
+
+public class AnnotatedStaticField {
+ @AnnotationWithEnum3(AnnotationWithEnum3.Value3.VAL3_2) public static String sValue = "myValue";
+}
diff --git a/src/test/examples/multidex002/AnnotatedVirtualMethod.java b/src/test/examples/multidex002/AnnotatedVirtualMethod.java
new file mode 100644
index 0000000..569bf84
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotatedVirtualMethod.java
@@ -0,0 +1,13 @@
+// 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 multidex002;
+
+public class AnnotatedVirtualMethod {
+
+ @AnnotationWithEnum3(AnnotationWithEnum3.Value3.VAL3_2)
+ public void foo() {
+
+ }
+}
diff --git a/src/test/examples/multidex002/AnnotationWithEnum3.java b/src/test/examples/multidex002/AnnotationWithEnum3.java
new file mode 100644
index 0000000..a8f8344
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotationWithEnum3.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package multidex002;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AnnotationWithEnum3 {
+
+ enum Value3 {
+ VAL3_1,
+ VAL3_2,
+ }
+
+ Value3 value();
+}
diff --git a/src/test/examples/multidex002/AnnotationWithoutEnum.java b/src/test/examples/multidex002/AnnotationWithoutEnum.java
new file mode 100644
index 0000000..5244971
--- /dev/null
+++ b/src/test/examples/multidex002/AnnotationWithoutEnum.java
@@ -0,0 +1,13 @@
+// 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 multidex002;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.RUNTIME)
+public @interface AnnotationWithoutEnum {
+
+}
diff --git a/src/test/examples/multidex002/ref-list-1.txt b/src/test/examples/multidex002/ref-list-1.txt
index 26dae34..922d417 100644
--- a/src/test/examples/multidex002/ref-list-1.txt
+++ b/src/test/examples/multidex002/ref-list-1.txt
@@ -1,8 +1,15 @@
Lmultidex002/Annotated2;
Lmultidex002/Annotated;
+Lmultidex002/AnnotatedDirectMethod;
+Lmultidex002/AnnotatedInstanceField;
+Lmultidex002/AnnotatedMethodParameter;
+Lmultidex002/AnnotatedStaticField;
+Lmultidex002/AnnotatedVirtualMethod;
Lmultidex002/AnnotationWithClass;
Lmultidex002/AnnotationWithEnum2;
+Lmultidex002/AnnotationWithEnum3;
Lmultidex002/AnnotationWithEnum;
+Lmultidex002/AnnotationWithoutEnum;
Lmultidex002/InterfaceWithEnum;
Lmultidex002/IntermediateClass;
Lmultidex002/MainActivity;
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 bf409d8..4b6b962 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -67,6 +67,7 @@
@Rule
public ExpectedException thrown = ExpectedException.none();
+ private boolean expectedException = false;
public enum DexTool {
JACK,
@@ -145,10 +146,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.
@@ -612,11 +609,6 @@
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)))
// VFY: tried to get class from non-ref register.
.put("506-verify-aput",
TestCondition.match(TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
@@ -679,6 +671,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();
@@ -766,12 +759,6 @@
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))
// 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.
@@ -780,12 +767,6 @@
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))
// Class not found.
.put("529-checker-unresolved", TestCondition.any())
@@ -1112,18 +1093,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;
@@ -1134,10 +1125,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) {
@@ -1192,7 +1203,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()) {
@@ -1252,12 +1264,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.
@@ -1300,7 +1311,8 @@
expectedToFailWithCompilerSet.contains(name),
outputMayDiffer.contains(name),
requireInliningToBeDisabled.contains(name),
- hasMissingClasses.contains(name)));
+ hasMissingClasses.contains(name),
+ dexVm));
}
}
return data;
@@ -1353,11 +1365,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);
@@ -1493,12 +1500,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,
@@ -1515,9 +1527,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;
@@ -1600,9 +1616,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;
}
@@ -1658,7 +1678,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()) {
@@ -1668,7 +1688,7 @@
builder.appendProgramArgument(fullClassName);
if (specification.failsWithArt) {
- thrown.expect(AssertionError.class);
+ expectException(AssertionError.class);
}
try {
@@ -1684,8 +1704,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
@@ -1694,11 +1713,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 {
@@ -1712,7 +1731,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;
}
@@ -1743,12 +1762,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) {
@@ -1768,7 +1792,7 @@
resultDir = temp.newFolder("r8-output");
runArtTestDoRunOnArt(
- version, CompilerUnderTest.R8, specification, fileNames, resultDir, compilationMode);
+ dexVm, CompilerUnderTest.R8, specification, fileNames, resultDir, compilationMode);
}
}
@@ -1793,7 +1817,7 @@
CompilationMode compilationMode)
throws Throwable {
if (specification.expectedToFailWithX8) {
- thrown.expect(CompilationError.class);
+ expectException(CompilationError.class);
try {
executeCompilerUnderTest(
compilerUnderTest, fileNames, resultDir.getCanonicalPath(), compilationMode,
@@ -1806,7 +1830,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);
@@ -1839,7 +1863,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);
@@ -1876,7 +1900,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);
@@ -1886,7 +1910,7 @@
}
}
if (specification.failsWithArtOutput) {
- thrown.expect(ComparisonFailure.class);
+ expectException(ComparisonFailure.class);
}
if (!specification.outputMayDiffer) {
assertEquals(expected, output);
@@ -1895,6 +1919,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/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/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/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 5f6a5cc..84f6a08 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -37,6 +37,7 @@
import java.util.Collections;
import java.util.List;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
public class ProguardConfigurationParserTest extends TestBase {
@@ -544,7 +545,7 @@
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
assertTrue(
- config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
+ config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobar;")));
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
}
@@ -561,12 +562,47 @@
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
assertTrue(
- config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobaz;")));
+ config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboobar;")));
assertTrue(
config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
}
@Test
+ public void testAdaptClassStringsNthWildcard() throws Exception {
+ DexItemFactory dexItemFactory = new DexItemFactory();
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(dexItemFactory, reporter);
+ String wildcard = "-adaptclassstrings *foo<1>";
+ parser.parse(createConfigurationForTesting(ImmutableList.of(wildcard)));
+ verifyParserEndsCleanly();
+ ProguardConfiguration config = parser.getConfig();
+ assertFalse(
+ config.getAdaptClassStrings().matches(dexItemFactory.createType("Lfoobar;")));
+ assertFalse(
+ config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboofoobar;")));
+ // TODO(b/73800755): Use <n> while matching class name list.
+ //assertTrue(
+ // config.getAdaptClassStrings().matches(dexItemFactory.createType("Lboofooboo;")));
+ }
+
+ @Ignore("b/73800755: verify the range of <n>")
+ @Test
+ public void testAdaptClassStringsNthWildcard_outOfRange() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-adaptclassstrings *foo<2>"
+ );
+ try {
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ fail();
+ } catch (AbortException e) {
+ checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+ "wildcard", "out", "range");
+ }
+ }
+
+ @Test
public void testIdentifierNameString() throws Exception {
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
@@ -1173,6 +1209,43 @@
}
@Test
+ public void parse_if_nthWildcard() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-if class **$R**",
+ "-keep class **$D<2>" // <2> corresponds to the 2nd ** in -if rule.
+ );
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
+ "Ignoring", "-if");
+ ProguardConfiguration config = parser.getConfig();
+ assertEquals(1, config.getRules().size());
+ ProguardIfRule if0 = (ProguardIfRule) config.getRules().get(0);
+ assertEquals("**$R**", if0.getClassNames().toString());
+ assertEquals(ProguardKeepRuleType.KEEP, if0.subsequentRule.getType());
+ assertEquals("**$D<2>", if0.subsequentRule.getClassNames().toString());
+ }
+
+ @Ignore("b/73800755: verify the range of <n>")
+ @Test
+ public void parse_if_nthWildcard_outOfRange() throws Exception {
+ Path proguardConfig = writeTextToTempFile(
+ "-if class **$R**",
+ "-keep class **D<4>" // There are 3 ** in this rule.
+ );
+ try {
+ ProguardConfigurationParser parser =
+ new ProguardConfigurationParser(new DexItemFactory(), reporter);
+ parser.parse(proguardConfig);
+ fail();
+ } catch (AbortException e) {
+ checkDiagnostic(handler.errors, proguardConfig, 1, 1,
+ "wildcard", "out", "range");
+ }
+ }
+
+ @Test
public void parse_if_if() throws Exception {
Path proguardConfig = writeTextToTempFile(
"-if class **$$ModuleAdapter",
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..39e804a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/defaultctor/ImplicitlyKeptDefaultConstructorTest.java
@@ -0,0 +1,299 @@
+// 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 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 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::checkAllClassesPresentWithDefaultConstructor,
+ this::checkOnlyMainPresent);
+ }
+
+ @Test
+ public void testStaticFieldWithInitialization() throws Exception {
+ Class mainClass = MainGetStaticFieldInitialized.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, StaticFieldInitialized.class),
+ keepMainProguardConfiguration(mainClass),
+ // TODO(74233021): Proguard does not keep the default constructor for the class with the
+ // un-initialized static field.
+ this::checkAllClassesPresentWithDefaultConstructor,
+ 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")),
+ // TODO(74233021): Proguard does not keep the default constructor for the class with the
+ // initialized static field.
+ this::checkAllClassesPresentWithDefaultConstructor,
+ this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
+ }
+
+ @Test
+ public void testStaticFieldWithInitializationWithoutInlining() throws Exception {
+ Class mainClass = MainGetStaticFieldInitialized.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, StaticFieldInitialized.class),
+ keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+ // TODO(74233021): Proguard does not keep the default constructor for the class with the
+ // un-initialized static field.
+ this::checkAllClassesPresentWithDefaultConstructor,
+ this::checkAllClassesPresentOnlyMainWithDefaultConstructor);
+ }
+
+ @Test
+ public void testStaticMethodStaticWithoutInlining() throws Exception {
+ Class mainClass = MainCallStaticMethod.class;
+ runTest(
+ mainClass,
+ ImmutableList.of(mainClass, StaticMethod.class),
+ keepMainProguardConfiguration(mainClass, ImmutableList.of("-dontoptimize")),
+ // TODO(74233021): Proguard does not keep the default constructor for the class with the
+ // initialized static field.
+ this::checkAllClassesPresentWithDefaultConstructor,
+ 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/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..8e1db2c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/DexInspectorMatchers.java
@@ -0,0 +1,54 @@
+// 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.google.common.collect.ImmutableList;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class DexInspectorMatchers {
+
+ public static Matcher<ClassSubject> isPresent() {
+ return new TypeSafeMatcher<ClassSubject>() {
+ @Override
+ public boolean matchesSafely(final ClassSubject clazz) {
+ return clazz.isPresent();
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("class present");
+ }
+
+ @Override
+ public void describeMismatchSafely(final ClassSubject clazz, Description description) {
+ description
+ .appendText("class ").appendValue(clazz.getOriginalName()).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");
+ }
+ };
+ }
+}
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