Merge "SCCP: No need to continue phi analysis when one operand is bottom"
diff --git a/build.gradle b/build.gradle
index 2d9e9b4..8237781 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1339,10 +1339,6 @@
}
task buildPreNJdwpTestsDex(type: Exec, dependsOn: "buildPreNJdwpTestsJar") {
- onlyIf {
- // TODO(b/76135355): Update dx.bat on Windows to something that can build this.
- !OperatingSystem.current().isWindows()
- }
def inFile = buildPreNJdwpTestsJar.archivePath
def outFile = new File(buildPreNJdwpTestsJar.destinationDir, buildPreNJdwpTestsJar.baseName + '-dex.jar')
inputs.file inFile
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index f525565..3db9699 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -40,9 +40,9 @@
new ApplicationReader(app, options, timing).read(executor).toDirect();
AppInfoWithSubtyping appInfo = new AppInfoWithSubtyping(application);
RootSet mainDexRootSet =
- new RootSetBuilder(application, appInfo, options.mainDexKeepRules, options).run(executor);
+ new RootSetBuilder(appInfo, application, options.mainDexKeepRules, options).run(executor);
Enqueuer enqueuer = new Enqueuer(appInfo, options, true);
- AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, timing);
+ AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
// LiveTypes is the result.
Set<DexType> mainDexClasses =
new MainDexListBuilder(new HashSet<>(mainDexAppInfo.liveTypes), application).run();
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d062d13..4f12ce1 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -282,13 +282,13 @@
ProguardConfiguration.builder(application.dexItemFactory, options.reporter);
rootSet = new RootSetBuilder(
- application, appInfo, options.proguardConfiguration.getRules(), options)
+ appInfo, application, options.proguardConfiguration.getRules(), options)
.run(executorService);
ProtoLiteExtension protoLiteExtension =
options.forceProguardCompatibility ? null : new ProtoLiteExtension(appInfo);
Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility,
compatibility, protoLiteExtension);
- appInfo = enqueuer.traceApplication(rootSet, timing);
+ appInfo = enqueuer.traceApplication(rootSet, executorService, timing);
if (options.proguardConfiguration.isPrintSeeds()) {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
PrintStream out = new PrintStream(bytes);
@@ -396,9 +396,10 @@
Enqueuer enqueuer = new Enqueuer(appInfo, options, true);
// Lets find classes which may have code executed before secondary dex files installation.
RootSet mainDexRootSet =
- new RootSetBuilder(application, appInfo, options.mainDexKeepRules, options)
+ new RootSetBuilder(appInfo, application, options.mainDexKeepRules, options)
.run(executorService);
- AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, timing);
+ AppInfoWithLiveness mainDexAppInfo =
+ enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
// LiveTypes is the result.
Set<DexType> mainDexBaseClasses = new HashSet<>(mainDexAppInfo.liveTypes);
@@ -416,7 +417,7 @@
timing.begin("Post optimization code stripping");
try {
Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility);
- appInfo = enqueuer.traceApplication(rootSet, timing);
+ appInfo = enqueuer.traceApplication(rootSet, executorService, timing);
if (options.enableTreeShaking) {
TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
application = pruner.run();
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 6f062cf..5a0d2c4 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.2.4-dev";
+ public static final String LABEL = "v1.2.6-dev";
private Version() {
}
diff --git a/src/main/java/com/android/tools/r8/cf/CfPrinter.java b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
index 39d916a..181ebac 100644
--- a/src/main/java/com/android/tools/r8/cf/CfPrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfPrinter.java
@@ -16,6 +16,9 @@
import com.android.tools.r8.cf.code.CfConstString;
import com.android.tools.r8.cf.code.CfFieldInstruction;
import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.Uninitialized;
+import com.android.tools.r8.cf.code.CfFrame.UninitializedNew;
+import com.android.tools.r8.cf.code.CfFrame.UninitializedThis;
import com.android.tools.r8.cf.code.CfGoto;
import com.android.tools.r8.cf.code.CfIf;
import com.android.tools.r8.cf.code.CfIfCmp;
@@ -240,7 +243,17 @@
StringBuilder builder = new StringBuilder("frame: [");
String separator = "";
for (Entry<DexType> entry : frame.getLocals().int2ReferenceEntrySet()) {
- builder.append(separator).append(entry.getIntKey()).append(':').append(entry.getValue());
+ builder.append(separator).append(entry.getIntKey()).append(':');
+ Uninitialized allocator = frame.getAllocators().get(entry.getIntKey());
+ if (allocator == null) {
+ builder.append(entry.getValue());
+ } else if (allocator instanceof UninitializedThis) {
+ builder.append("uninitialized this");
+ } else {
+ builder
+ .append("uninitialized ")
+ .append(getLabel(((UninitializedNew) allocator).getLabel()));
+ }
separator = ", ";
}
builder.append("] ");
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
index e30d8ce..130362f 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFieldInstruction.java
@@ -16,10 +16,13 @@
private final int opcode;
private final DexField field;
+ private final DexField declaringField;
- public CfFieldInstruction(int opcode, DexField field) {
+ public CfFieldInstruction(int opcode, DexField field, DexField declaringField) {
this.opcode = opcode;
this.field = field;
+ this.declaringField = declaringField;
+ assert field.type == declaringField.type;
}
public DexField getField() {
@@ -33,7 +36,7 @@
@Override
public void write(MethodVisitor visitor, NamingLens lens) {
String owner = lens.lookupInternalName(field.getHolder());
- String name = lens.lookupName(field).toString();
+ String name = lens.lookupName(declaringField).toString();
String desc = lens.lookupDescriptor(field.type).toString();
visitor.visitFieldInsn(opcode, owner, name, desc);
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
index 2818e44..b5e759c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfFrame.java
@@ -17,11 +17,44 @@
public class CfFrame extends CfInstruction {
+ public abstract static class Uninitialized {
+ abstract Object getAsmLabel();
+ }
+
+ public static class UninitializedNew extends Uninitialized {
+ private final CfLabel label;
+
+ public UninitializedNew(CfLabel label) {
+ this.label = label;
+ }
+
+ @Override
+ Object getAsmLabel() {
+ return label.getLabel();
+ }
+
+ public CfLabel getLabel() {
+ return label;
+ }
+ }
+
+ public static class UninitializedThis extends Uninitialized {
+ @Override
+ Object getAsmLabel() {
+ return Opcodes.UNINITIALIZED_THIS;
+ }
+ }
+
private final Int2ReferenceSortedMap<DexType> locals;
+ private final Int2ReferenceSortedMap<Uninitialized> allocators;
private final List<DexType> stack;
- public CfFrame(Int2ReferenceSortedMap<DexType> locals, List<DexType> stack) {
+ public CfFrame(
+ Int2ReferenceSortedMap<DexType> locals,
+ Int2ReferenceSortedMap<Uninitialized> allocators,
+ List<DexType> stack) {
this.locals = locals;
+ this.allocators = allocators;
this.stack = stack;
}
@@ -29,6 +62,10 @@
return locals;
}
+ public Int2ReferenceSortedMap<Uninitialized> getAllocators() {
+ return allocators;
+ }
+
public List<DexType> getStack() {
return stack;
}
@@ -88,7 +125,8 @@
int localIndex = 0;
for (int i = 0; i <= maxRegister; i++) {
DexType type = locals.get(i);
- Object typeOpcode = getType(type, lens);
+ Uninitialized allocator = allocators.get(i);
+ Object typeOpcode = allocator == null ? getType(type, lens) : allocator.getAsmLabel();
localsTypes[localIndex++] = typeOpcode;
if (type != null && isWide(type)) {
i++;
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index ca8f7cb..f830427 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -4,9 +4,11 @@
package com.android.tools.r8.cf.code;
import com.android.tools.r8.cf.CfPrinter;
+import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexCallSite;
import com.android.tools.r8.graph.DexMethodHandle;
+import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DexValue;
import com.android.tools.r8.graph.DexValue.DexValueDouble;
@@ -41,11 +43,16 @@
bsmArgs[i] = decodeBootstrapArgument(bootstrapArgs.get(i), lens);
}
Handle bsmHandle = bootstrapMethod.toAsmHandle(lens);
+ DexString methodName;
+ if (lens.isIdentityLens()) {
+ methodName = callSite.methodName;
+ } else if (!callSite.interfaceMethods.isEmpty()) {
+ methodName = lens.lookupName(callSite.interfaceMethods.get(0));
+ } else {
+ throw new Unimplemented("Minification of non-lambda InvokeDynamic not supported");
+ }
visitor.visitInvokeDynamicInsn(
- callSite.methodName.toString(),
- callSite.methodProto.toDescriptorString(),
- bsmHandle,
- bsmArgs);
+ methodName.toString(), callSite.methodProto.toDescriptorString(lens), bsmHandle, bsmArgs);
}
private Object decodeBootstrapArgument(DexValue dexValue, NamingLens lens) {
@@ -60,9 +67,9 @@
} else if (dexValue instanceof DexValueString) {
return ((DexValueString) dexValue).getValue();
} else if (dexValue instanceof DexValueType) {
- return Type.getType(((DexValueType) dexValue).value.toDescriptorString());
+ return Type.getType(lens.lookupDescriptor(((DexValueType) dexValue).value).toString());
} else if (dexValue instanceof DexValueMethodType) {
- return Type.getMethodType(((DexValueMethodType) dexValue).value.toDescriptorString());
+ return Type.getMethodType(((DexValueMethodType) dexValue).value.toDescriptorString(lens));
} else if (dexValue instanceof DexValueMethodHandle) {
return ((DexValueMethodHandle) dexValue).value.toAsmHandle(lens);
} else {
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
index 45a62ef..e3753a7 100644
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
import com.android.tools.r8.utils.OptionsParsing;
import com.android.tools.r8.utils.OptionsParsing.ParseContext;
+import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -27,40 +28,80 @@
private static final boolean PRINT_ARGS = false;
public static class Options {
- List<String> inputArchives = new ArrayList<>();
- List<String> featureJars = new ArrayList<>();
- String splitBaseName = DEFAULT_OUTPUT_ARCHIVE_FILENAME;
- String featureSplitMapping;
- String proguardMap;
+ private List<String> inputArchives = new ArrayList<>();
+ private List<String> featureJars = new ArrayList<>();
+ private String splitBaseName = DEFAULT_OUTPUT_ARCHIVE_FILENAME;
+ private String featureSplitMapping;
+ private String proguardMap;
+
+ public String getSplitBaseName() {
+ return splitBaseName;
+ }
+
+ public void setSplitBaseName(String splitBaseName) {
+ this.splitBaseName = splitBaseName;
+ }
+
+ public String getFeatureSplitMapping() {
+ return featureSplitMapping;
+ }
+
+ public void setFeatureSplitMapping(String featureSplitMapping) {
+ this.featureSplitMapping = featureSplitMapping;
+ }
+
+ public String getProguardMap() {
+ return proguardMap;
+ }
+
+ public void setProguardMap(String proguardMap) {
+ this.proguardMap = proguardMap;
+ }
+
+ public void addInputArchive(String inputArchive) {
+ inputArchives.add(inputArchive);
+ }
+
+ public void addFeatureJar(String featureJar) {
+ featureJars.add(featureJar);
+ }
+
+ public ImmutableList<String> getInputArchives() {
+ return ImmutableList.copyOf(inputArchives);
+ }
+
+ public ImmutableList<String> getFeatureJars() {
+ return ImmutableList.copyOf(featureJars);
+ }
}
private static Options parseArguments(String[] args) throws IOException {
Options options = new Options();
ParseContext context = new ParseContext(args);
while (context.head() != null) {
- List<String> input = OptionsParsing.tryParseMulti(context, "--input");
- if (input != null) {
- options.inputArchives.addAll(input);
+ List<String> inputs = OptionsParsing.tryParseMulti(context, "--input");
+ if (inputs != null) {
+ inputs.stream().forEach(options::addInputArchive);
continue;
}
List<String> featureJars = OptionsParsing.tryParseMulti(context, "--feature-jar");
if (featureJars != null) {
- options.featureJars.addAll(featureJars);
+ featureJars.stream().forEach(options::addFeatureJar);
continue;
}
String output = OptionsParsing.tryParseSingle(context, "--output", "-o");
if (output != null) {
- options.splitBaseName = output;
+ options.setSplitBaseName(output);
continue;
}
String proguardMap = OptionsParsing.tryParseSingle(context, "--proguard-map", null);
if (proguardMap != null) {
- options.proguardMap = proguardMap;
+ options.setProguardMap(proguardMap);
continue;
}
String featureSplit = OptionsParsing.tryParseSingle(context, "--feature-splits", null);
if (featureSplit != null) {
- options.featureSplitMapping = featureSplit;
+ options.setFeatureSplitMapping(featureSplit);
continue;
}
throw new RuntimeException(String.format("Unknown options: '%s'.", context.head()));
@@ -70,8 +111,8 @@
private static FeatureClassMapping createFeatureClassMapping(Options options)
throws IOException, FeatureMappingException, ResourceException {
- if (options.featureSplitMapping != null) {
- return FeatureClassMapping.fromSpecification(Paths.get(options.featureSplitMapping));
+ if (options.getFeatureSplitMapping() != null) {
+ return FeatureClassMapping.fromSpecification(Paths.get(options.getFeatureSplitMapping()));
}
assert !options.featureJars.isEmpty();
return FeatureClassMapping.fromJarFiles(options.featureJars);
@@ -87,13 +128,13 @@
public static void run(Options options)
throws IOException, FeatureMappingException, ResourceException, CompilationException,
ExecutionException, CompilationFailedException {
- if (options.inputArchives.isEmpty()) {
+ if (options.getInputArchives().isEmpty()) {
throw new RuntimeException("Need at least one --input");
}
- if (options.featureSplitMapping == null && options.featureJars.isEmpty()) {
+ if (options.getFeatureSplitMapping() == null && options.getFeatureJars().isEmpty()) {
throw new RuntimeException("You must supply a feature split mapping or feature jars");
}
- if (options.featureSplitMapping != null && !options.featureJars.isEmpty()) {
+ if (options.getFeatureSplitMapping() != null && !options.getFeatureJars().isEmpty()) {
throw new RuntimeException("You can't supply both a feature split mapping and feature jars");
}
@@ -108,7 +149,7 @@
FeatureClassMapping featureClassMapping = createFeatureClassMapping(options);
DexSplitterHelper.run(
- builder.build(), featureClassMapping, options.splitBaseName, options.proguardMap);
+ builder.build(), featureClassMapping, options.getSplitBaseName(), options.getProguardMap());
}
public static void main(String[] args) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexCallSite.java b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
index 0c32750..ac954f8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCallSite.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
@@ -29,19 +29,27 @@
public final DexMethodHandle bootstrapMethod;
public final List<DexValue> bootstrapArgs;
+ public final List<DexMethod> interfaceMethods;
+
private DexEncodedArray encodedArray = null;
- DexCallSite(DexString methodName, DexProto methodProto,
- DexMethodHandle bootstrapMethod, List<DexValue> bootstrapArgs) {
+ DexCallSite(
+ DexString methodName,
+ DexProto methodProto,
+ DexMethodHandle bootstrapMethod,
+ List<DexValue> bootstrapArgs,
+ List<DexMethod> interfaceMethods) {
assert methodName != null;
assert methodProto != null;
assert bootstrapMethod != null;
assert bootstrapArgs != null;
+ assert interfaceMethods != null;
this.methodName = methodName;
this.methodProto = methodProto;
this.bootstrapMethod = bootstrapMethod;
this.bootstrapArgs = bootstrapArgs;
+ this.interfaceMethods = interfaceMethods;
}
public static DexCallSite fromAsmInvokeDynamic(
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 9d3c7a9..5fe9134 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -15,7 +15,9 @@
import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.graph.DexValue.DexValueMethodType;
import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.naming.NamingLens;
import com.google.common.base.Strings;
@@ -25,6 +27,7 @@
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
@@ -227,6 +230,33 @@
public final DexType annotationSynthesizedClassMap =
createType("Lcom/android/tools/r8/annotations/SynthesizedClassMap;");
+ private static final String METAFACTORY_METHOD_NAME = "metafactory";
+ private static final String METAFACTORY_ALT_METHOD_NAME = "altMetafactory";
+
+ public final DexType metafactoryType = createType("Ljava/lang/invoke/LambdaMetafactory;");
+ public final DexType callSiteType = createType("Ljava/lang/invoke/CallSite;");
+ public final DexType lookupType = createType("Ljava/lang/invoke/MethodHandles$Lookup;");
+ public final DexType serializableType = createType("Ljava/io/Serializable;");
+
+ public final DexMethod metafactoryMethod =
+ createMethod(
+ metafactoryType,
+ createProto(
+ callSiteType,
+ lookupType,
+ stringType,
+ methodTypeType,
+ methodTypeType,
+ methodHandleType,
+ methodTypeType),
+ createString(METAFACTORY_METHOD_NAME));
+
+ public final DexMethod metafactoryAltMethod =
+ createMethod(
+ metafactoryType,
+ createProto(callSiteType, lookupType, stringType, methodTypeType, objectArrayType),
+ createString(METAFACTORY_ALT_METHOD_NAME));
+
private boolean skipNameValidationForTesting = false;
public void setSkipNameValidationForTesting(boolean skipNameValidationForTesting) {
@@ -544,10 +574,46 @@
DexString methodName, DexProto methodProto,
DexMethodHandle bootstrapMethod, List<DexValue> bootstrapArgs) {
assert !sorted;
- DexCallSite callSite = new DexCallSite(methodName, methodProto, bootstrapMethod, bootstrapArgs);
+ List<DexMethod> interfaceMethods =
+ getCallSiteInterfaceMethods(methodName, methodProto, bootstrapMethod, bootstrapArgs);
+ DexCallSite callSite =
+ new DexCallSite(methodName, methodProto, bootstrapMethod, bootstrapArgs, interfaceMethods);
return canonicalize(callSites, callSite);
}
+ private List<DexMethod> getCallSiteInterfaceMethods(
+ DexString methodName,
+ DexProto methodProto,
+ DexMethodHandle bootstrapMethodHandle,
+ List<DexValue> bootstrapArgs) {
+ // TODO(mathiasr): Unify this with LambdaDescriptor.infer().
+ if (!bootstrapMethodHandle.type.isInvokeStatic()) {
+ return Collections.emptyList();
+ }
+ DexMethod bootstrapMethod = bootstrapMethodHandle.asMethod();
+ if (bootstrapMethod != metafactoryMethod && bootstrapMethod != metafactoryAltMethod) {
+ return Collections.emptyList();
+ }
+ DexType interfaceType = methodProto.returnType;
+ assert bootstrapMethod == metafactoryAltMethod || bootstrapArgs.size() == 3;
+ // Signature of main functional interface method.
+ // In Java docs, this argument is named 'samMethodType'.
+ DexValueMethodType funcErasedSignature = (DexValueMethodType) bootstrapArgs.get(0);
+ DexMethod mainMethod = createMethod(interfaceType, funcErasedSignature.value, methodName);
+ if (bootstrapMethod == metafactoryAltMethod) {
+ List<DexMethod> result = new ArrayList<>();
+ result.add(mainMethod);
+ LambdaDescriptor.extractAltMetafactory(
+ this,
+ bootstrapArgs,
+ type -> result.add(createMethod(type, funcErasedSignature.value, methodName)),
+ bridge -> {});
+ return result;
+ } else {
+ return Collections.singletonList(mainMethod);
+ }
+ }
+
public DexMethod createMethod(DexString clazzDescriptor, DexString name,
DexString returnTypeDescriptor,
DexString[] parameterDescriptors) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index c0c2e27..9c3e9a5 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -71,6 +71,14 @@
return false;
}
+ /**
+ * Returns true if the other method has the same name and prototype (including signature and
+ * return type), false otherwise.
+ */
+ public boolean hasSameProtoAndName(DexMethod other) {
+ return name == other.name && proto == other.proto;
+ }
+
@Override
public int compareTo(DexMethod other) {
return sortedCompareTo(other.getSortedIndex());
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index a7bce9d..660a713 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -153,7 +153,7 @@
@Override
public void buildCf(CfBuilder builder) {
- builder.add(new CfFieldInstruction(Opcodes.GETFIELD, field));
+ builder.add(new CfFieldInstruction(Opcodes.GETFIELD, field, builder.resolveField(field)));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index ec92b67..9591978 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -136,7 +136,7 @@
@Override
public void buildCf(CfBuilder builder) {
- builder.add(new CfFieldInstruction(Opcodes.PUTFIELD, field));
+ builder.add(new CfFieldInstruction(Opcodes.PUTFIELD, field, builder.resolveField(field)));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 47141bc..6db3b8d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -131,7 +131,7 @@
@Override
public void buildCf(CfBuilder builder) {
- builder.add(new CfFieldInstruction(Opcodes.GETSTATIC, field));
+ builder.add(new CfFieldInstruction(Opcodes.GETSTATIC, field, builder.resolveField(field)));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 0eca18d..0c78208 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -130,7 +130,7 @@
@Override
public void buildCf(CfBuilder builder) {
- builder.add(new CfFieldInstruction(Opcodes.PUTSTATIC, field));
+ builder.add(new CfFieldInstruction(Opcodes.PUTSTATIC, field, builder.resolveField(field)));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 933f66d..8dd9162 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -7,6 +7,9 @@
import com.android.tools.r8.cf.LoadStoreHelper;
import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.cf.code.CfFrame;
+import com.android.tools.r8.cf.code.CfFrame.Uninitialized;
+import com.android.tools.r8.cf.code.CfFrame.UninitializedNew;
+import com.android.tools.r8.cf.code.CfFrame.UninitializedThis;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfPosition;
@@ -16,7 +19,9 @@
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
import com.android.tools.r8.graph.DebugLocalInfo;
+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.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.Argument;
@@ -26,8 +31,10 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeDirect;
import com.android.tools.r8.ir.code.JumpInstruction;
import com.android.tools.r8.ir.code.Load;
+import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.StackValue;
import com.android.tools.r8.ir.code.Store;
@@ -40,9 +47,11 @@
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -73,6 +82,12 @@
private final Int2ReferenceMap<LocalVariableInfo> openLocalVariables =
new Int2ReferenceOpenHashMap<>();
+ private AppInfoWithSubtyping appInfo;
+
+ private Map<NewInstance, List<InvokeDirect>> initializers;
+ private List<InvokeDirect> thisInitializers;
+ private Map<NewInstance, CfLabel> newInstanceLabels;
+
// Internal abstraction of the stack values and height.
private static class Stack {
int maxHeight = 0;
@@ -106,6 +121,7 @@
public CfCode build(
CodeRewriter rewriter, InternalOptions options, AppInfoWithSubtyping appInfo) {
+ computeInitializers();
types = new TypeVerificationHelper(code, factory, appInfo).computeVerificationTypes();
splitExceptionalBlocks();
LoadStoreHelper loadStoreHelper = new LoadStoreHelper(code, types);
@@ -119,10 +135,49 @@
int instructionTableCount =
DexBuilder.instructionNumberToIndex(code.numberRemainingInstructions());
DexBuilder.removeRedundantDebugPositions(code, instructionTableCount);
+ this.appInfo = appInfo;
CfCode code = buildCfCode();
return code;
}
+ public DexField resolveField(DexField field) {
+ DexEncodedField resolvedField = appInfo.resolveFieldOn(field.clazz, field);
+ return resolvedField == null ? field : resolvedField.field;
+ }
+
+ private void computeInitializers() {
+ assert initializers == null;
+ assert thisInitializers == null;
+ initializers = new HashMap<>();
+ for (BasicBlock block : code.blocks) {
+ for (Instruction insn : block.getInstructions()) {
+ if (insn.isNewInstance()) {
+ initializers.put(insn.asNewInstance(), computeInitializers(insn.outValue()));
+ } else if (insn.isArgument() && method.isInstanceInitializer()) {
+ if (insn.outValue().isThis()) {
+ // By JVM8 §4.10.1.9 (invokespecial), a this() or super() call in a constructor
+ // changes the type of `this` from uninitializedThis
+ // to the type of the class of the <init> method.
+ thisInitializers = computeInitializers(insn.outValue());
+ }
+ }
+ }
+ }
+ assert !(method.isInstanceInitializer() && thisInitializers == null);
+ }
+
+ private List<InvokeDirect> computeInitializers(Value value) {
+ List<InvokeDirect> initializers = new ArrayList<>();
+ for (Instruction user : value.uniqueUsers()) {
+ if (user instanceof InvokeDirect
+ && user.inValues().get(0) == value
+ && user.asInvokeDirect().getInvokedMethod().name == factory.constructorMethodName) {
+ initializers.add(user.asInvokeDirect());
+ }
+ }
+ return initializers;
+ }
+
// Split all blocks with throwing instructions and exceptional edges such that any non-throwing
// instructions that might define values prior to the throwing exception are excluded from the
// try-catch range. Failure to do so will result in code that does not verify on the JVM.
@@ -193,6 +248,7 @@
List<CfTryCatch> tryCatchRanges = new ArrayList<>();
labels = new HashMap<>(code.blocks.size());
emittedLabels = new HashSet<>(code.blocks.size());
+ newInstanceLabels = new HashMap<>(initializers.size());
instructions = new ArrayList<>();
ListIterator<BasicBlock> blockIterator = code.listIterator();
BasicBlock block = blockIterator.next();
@@ -201,6 +257,7 @@
BasicBlock pendingFrame = null;
boolean previousFallthrough = false;
do {
+ assert stack.isEmpty();
CatchHandlers<BasicBlock> handlers = block.getCatchHandlers();
if (!tryCatchHandlers.equals(handlers)) {
if (!tryCatchHandlers.isEmpty()) {
@@ -220,7 +277,6 @@
// If previousBlock is fallthrough, then it is counted in getPredecessors().size(), but
// we only want to set a pendingFrame if we have a predecessor which is not previousBlock.
if (block.getPredecessors().size() > (previousFallthrough ? 1 : 0)) {
- assert stack.isEmpty();
pendingFrame = block;
emitLabel(getLabel(block));
}
@@ -308,6 +364,9 @@
pendingLocalChanges = true;
}
} else {
+ if (instruction.isNewInstance()) {
+ newInstanceLabels.put(instruction.asNewInstance(), ensureLabel());
+ }
updatePositionAndLocals(instruction);
instruction.buildCf(this);
}
@@ -390,9 +449,11 @@
Collection<Value> locals = registerAllocator.getLocalsAtBlockEntry(block);
Int2ReferenceSortedMap<DexType> mapping = new Int2ReferenceAVLTreeMap<>();
+ Int2ReferenceSortedMap<Uninitialized> allocators = new Int2ReferenceAVLTreeMap<>();
for (Value local : locals) {
DexType type;
+ Uninitialized allocator = null;
switch (local.outType()) {
case INT:
type = factory.intType;
@@ -408,14 +469,64 @@
break;
case OBJECT:
type = types.get(local);
+ allocator = findAllocator(block, local);
break;
default:
throw new Unreachable(
"Unexpected local type: " + local.outType() + " for local: " + local);
}
mapping.put(getLocalRegister(local), type);
+ if (allocator != null) {
+ allocators.put(getLocalRegister(local), allocator);
+ }
}
- instructions.add(new CfFrame(mapping, stackTypes));
+ instructions.add(new CfFrame(mapping, allocators, stackTypes));
+ }
+
+ private Uninitialized findAllocator(BasicBlock liveBlock, Value value) {
+ Instruction definition = value.definition;
+ while (definition != null && (definition.isStore() || definition.isLoad())) {
+ definition = definition.inValues().get(0).definition;
+ }
+ if (definition == null) {
+ return null;
+ }
+ Uninitialized res;
+ if (definition.isNewInstance()) {
+ res = new UninitializedNew(newInstanceLabels.get(definition.asNewInstance()));
+ } else if (definition.isArgument()
+ && method.isInstanceInitializer()
+ && definition.outValue().isThis()) {
+ res = new UninitializedThis();
+ } else {
+ return null;
+ }
+ BasicBlock definitionBlock = definition.getBlock();
+ Set<BasicBlock> visited = new HashSet<>();
+ Deque<BasicBlock> toVisit = new ArrayDeque<>();
+ List<InvokeDirect> valueInitializers =
+ definition.isArgument() ? thisInitializers : initializers.get(definition.asNewInstance());
+ for (InvokeDirect initializer : valueInitializers) {
+ BasicBlock initializerBlock = initializer.getBlock();
+ if (initializerBlock == liveBlock) {
+ return res;
+ }
+ if (initializerBlock != definitionBlock && visited.add(initializerBlock)) {
+ toVisit.addLast(initializerBlock);
+ }
+ }
+ while (!toVisit.isEmpty()) {
+ BasicBlock block = toVisit.removeLast();
+ for (BasicBlock predecessor : block.getPredecessors()) {
+ if (predecessor == liveBlock) {
+ return res;
+ }
+ if (predecessor != definitionBlock && visited.add(predecessor)) {
+ toVisit.addLast(predecessor);
+ }
+ }
+ }
+ return null;
}
private void emitLabel(CfLabel label) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 228f594..9c6bb86 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -172,7 +172,9 @@
}
@Override
- public void buildInstruction(IRBuilder builder, int instructionIndex) throws ApiLevelException {
+ public void buildInstruction(
+ IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
+ throws ApiLevelException {
updateCurrentCatchHandlers(instructionIndex);
updateDebugPosition(instructionIndex, builder);
currentDexInstruction = code.instructions[instructionIndex];
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 5bea64e..13d66c2 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
@@ -504,7 +504,7 @@
addToWorklist(info.block, i);
break;
}
- source.buildInstruction(this, i);
+ source.buildInstruction(this, i, i == item.firstInstructionIndex);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 2298bc6..7f89bdc 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -489,7 +489,9 @@
}
@Override
- public void buildInstruction(IRBuilder builder, int instructionIndex) throws ApiLevelException {
+ public void buildInstruction(
+ IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
+ throws ApiLevelException {
if (instructionIndex == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
buildExceptionalPostlude(builder);
return;
@@ -499,8 +501,17 @@
assert verifyExceptionEdgesAreRecorded(insn);
// If a new block is starting here, we restore the computed JarState.
- if (builder.getCFG().containsKey(instructionIndex) || instructionIndex == 0) {
+ // current position needs to be compute only for the first instruction of a block, thereafter
+ // current position will be updated by LineNumberNode into this block.
+ if (firstBlockInstruction || instructionIndex == 0) {
state.restoreState(instructionIndex);
+ // Don't include line changes when processing a label. Doing so will end up emitting local
+ // writes after the line has changed and thus causing locals to become visible too late.
+ currentPosition =
+ getDebugPositionAtOffset(
+ ((instructionIndex > 0) && (insn instanceof LabelNode))
+ ? instructionIndex - 1
+ : instructionIndex);
}
String preInstructionState;
@@ -508,14 +519,6 @@
preInstructionState = state.toString();
}
- // Don't include line changes when processing a label. Doing so will end up emitting local
- // writes after the line has changed and thus causing locals to become visible too late.
- currentPosition =
- getDebugPositionAtOffset(
- ((instructionIndex > 0) && (insn instanceof LabelNode))
- ? instructionIndex - 1
- : instructionIndex);
-
build(insn, builder);
if (Log.ENABLED && !(insn instanceof LineNumberNode)) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index 13d1551..be1c670 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -47,7 +47,9 @@
// Delegates for IR building.
void buildPrelude(IRBuilder builder);
- void buildInstruction(IRBuilder builder, int instructionIndex) throws ApiLevelException;
+
+ void buildInstruction(IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
+ throws ApiLevelException;
void buildPostlude(IRBuilder builder);
// Helper to resolve switch payloads and build switch instructions (dex code only).
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index c752fb5..2152364 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -32,8 +32,6 @@
private final Set<DexClass> processedClasses = Sets.newIdentityHashSet();
// Maps already created methods into default methods they were generated based on.
private final Map<DexEncodedMethod, DexEncodedMethod> createdMethods = new IdentityHashMap<>();
- // Caches default interface method info for already processed interfaces.
- private final Map<DexType, DefaultMethodsHelper.Collection> cache = new IdentityHashMap<>();
ClassProcessor(InterfaceMethodRewriter rewriter) {
this.rewriter = rewriter;
@@ -67,7 +65,7 @@
if (superClass != null && superType != rewriter.factory.objectType) {
if (superClass.isInterface()) {
throw new CompilationError("Interface `" + superClass.toSourceString()
- + "` used as super class of `" + clazz.toSourceString() + "`.");
+ + "` used as super class of `" + clazz.toSourceString() + "`.");
}
process(superClass);
}
@@ -136,7 +134,7 @@
// the future.
while (current.type != rewriter.factory.objectType) {
for (DexType type : current.interfaces.values) {
- helper.merge(getOrCreateInterfaceInfo(clazz, current, type));
+ helper.merge(rewriter.getOrCreateInterfaceInfo(clazz, current, type));
}
accumulatedVirtualMethods.addAll(Arrays.asList(clazz.virtualMethods()));
@@ -168,8 +166,8 @@
} else {
String message = "Default method desugaring of `" + clazz.toSourceString() + "` failed";
if (current == clazz) {
- message += " because its super class `" + clazz.superType.toSourceString()
- + "` is missing";
+ message += " because its super class `" +
+ clazz.superType.toSourceString() + "` is missing";
} else {
message +=
" because it's hierarchy is incomplete. The class `"
@@ -244,62 +242,4 @@
}
}
}
-
- private DefaultMethodsHelper.Collection getOrCreateInterfaceInfo(
- DexClass classToDesugar,
- DexClass implementing,
- DexType iface) {
- DefaultMethodsHelper.Collection collection = cache.get(iface);
- if (collection != null) {
- return collection;
- }
- collection = createInterfaceInfo(classToDesugar, implementing, iface);
- cache.put(iface, collection);
- return collection;
- }
-
- private DefaultMethodsHelper.Collection createInterfaceInfo(
- DexClass classToDesugar,
- DexClass implementing,
- DexType iface) {
- DefaultMethodsHelper helper = new DefaultMethodsHelper();
- DexClass definedInterface = rewriter.findDefinitionFor(iface);
- if (definedInterface == null) {
- rewriter.warnMissingInterface(classToDesugar, implementing, iface);
- return helper.wrapInCollection();
- }
-
- if (!definedInterface.isInterface()) {
- throw new CompilationError(
- "Type " + iface.toSourceString() + " is referenced as an interface of `"
- + implementing.toString() + "`.");
- }
-
- if (definedInterface.isLibraryClass()) {
- // NOTE: We intentionally ignore all candidates coming from android.jar
- // since it is only possible in case v24+ version of android.jar is provided.
- // WARNING: This may result in incorrect code if something else than Android bootclasspath
- // classes are given as libraries!
- return helper.wrapInCollection();
- }
-
- // Merge information from all superinterfaces.
- for (DexType superinterface : definedInterface.interfaces.values) {
- helper.merge(getOrCreateInterfaceInfo(classToDesugar, definedInterface, superinterface));
- }
-
- // Hide by virtual methods of this interface.
- for (DexEncodedMethod virtual : definedInterface.virtualMethods()) {
- helper.hideMatches(virtual.method);
- }
-
- // Add all default methods of this interface.
- for (DexEncodedMethod encoded : definedInterface.virtualMethods()) {
- if (rewriter.isDefaultMethod(encoded)) {
- helper.addDefaultMethod(encoded);
- }
- }
-
- return helper.wrapInCollection();
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
index df65e7e..fb051aa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DefaultMethodsHelper.java
@@ -40,6 +40,22 @@
this.live = live;
this.hidden = hidden;
}
+
+ // If there is just one live method having specified
+ // signature return it, otherwise return null.
+ DexMethod getSingleCandidate(DexMethod method) {
+ DexMethod candidate = null;
+ for (DexEncodedMethod encodedMethod : live) {
+ DexMethod current = encodedMethod.method;
+ if (current.proto == method.proto && current.name == method.name) {
+ if (candidate != null) {
+ return null;
+ }
+ candidate = current;
+ }
+ }
+ return candidate;
+ }
}
final void merge(Collection collection) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 9379a83..9b8b8e2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -26,6 +26,7 @@
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeSuper;
import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.desugar.DefaultMethodsHelper.Collection;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringDiagnostic;
@@ -33,6 +34,7 @@
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
//
// Default and static interface method desugaring rewriter (note that lambda
@@ -75,6 +77,9 @@
// to this collection since it is only filled in ClassProcessor running synchronously.
private final Set<DexEncodedMethod> forwardingMethods = Sets.newIdentityHashSet();
+ // Caches default interface method info for already processed interfaces.
+ private final Map<DexType, DefaultMethodsHelper.Collection> cache = new ConcurrentHashMap<>();
+
/**
* A set of dexitems we have reported missing to dedupe warnings.
*/
@@ -166,8 +171,10 @@
// of android.jar is provided.
// WARNING: This may result in incorrect code on older platforms!
// Retarget call to an appropriate method of companion class.
+ DexMethod amendedMethod = amendDefaultMethod(
+ findDefinitionFor(encodedMethod.method.holder), method);
instructions.replaceCurrentInstruction(
- new InvokeStatic(defaultAsMethodOfCompanionClass(method),
+ new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
invokeSuper.outValue(), invokeSuper.arguments()));
}
continue;
@@ -294,6 +301,15 @@
factory.createString(prefix + method.name.toString()));
}
+ // It is possible that referenced method actually points to an interface which does
+ // not define this default methods, but inherits it. We are making our best effort
+ // to find an appropriate method, but still use the original one in case we fail.
+ private DexMethod amendDefaultMethod(DexClass classToDesugar, DexMethod method) {
+ DexMethod singleCandidate = getOrCreateInterfaceInfo(
+ classToDesugar, classToDesugar, method.holder).getSingleCandidate(method);
+ return singleCandidate != null ? singleCandidate : method;
+ }
+
// Represent a default interface method as a method of companion class.
final DexMethod defaultAsMethodOfCompanionClass(DexMethod method) {
return instanceAsMethodOfCompanionClass(method, DEFAULT_METHOD_PREFIX);
@@ -329,6 +345,14 @@
for (DexEncodedMethod method : forwardingMethods) {
converter.optimizeSynthesizedMethod(method);
}
+
+ // Cached data is not needed any more.
+ clear();
+ }
+
+ private void clear() {
+ this.cache.clear();
+ this.forwardingMethods.clear();
}
private static boolean shouldProcess(
@@ -426,4 +450,62 @@
DexClass clazz = converter.appInfo.definitionFor(holder);
return clazz == null ? Origin.unknown() : clazz.getOrigin();
}
+
+ final DefaultMethodsHelper.Collection getOrCreateInterfaceInfo(
+ DexClass classToDesugar,
+ DexClass implementing,
+ DexType iface) {
+ DefaultMethodsHelper.Collection collection = cache.get(iface);
+ if (collection != null) {
+ return collection;
+ }
+ collection = createInterfaceInfo(classToDesugar, implementing, iface);
+ Collection existing = cache.putIfAbsent(iface, collection);
+ return existing != null ? existing : collection;
+ }
+
+ private DefaultMethodsHelper.Collection createInterfaceInfo(
+ DexClass classToDesugar,
+ DexClass implementing,
+ DexType iface) {
+ DefaultMethodsHelper helper = new DefaultMethodsHelper();
+ DexClass definedInterface = findDefinitionFor(iface);
+ if (definedInterface == null) {
+ warnMissingInterface(classToDesugar, implementing, iface);
+ return helper.wrapInCollection();
+ }
+
+ if (!definedInterface.isInterface()) {
+ throw new CompilationError(
+ "Type " + iface.toSourceString() + " is referenced as an interface from `"
+ + implementing.toString() + "`.");
+ }
+
+ if (definedInterface.isLibraryClass()) {
+ // NOTE: We intentionally ignore all candidates coming from android.jar
+ // since it is only possible in case v24+ version of android.jar is provided.
+ // WARNING: This may result in incorrect code if something else than Android bootclasspath
+ // classes are given as libraries!
+ return helper.wrapInCollection();
+ }
+
+ // Merge information from all superinterfaces.
+ for (DexType superinterface : definedInterface.interfaces.values) {
+ helper.merge(getOrCreateInterfaceInfo(classToDesugar, definedInterface, superinterface));
+ }
+
+ // Hide by virtual methods of this interface.
+ for (DexEncodedMethod virtual : definedInterface.virtualMethods()) {
+ helper.hideMatches(virtual.method);
+ }
+
+ // Add all default methods of this interface.
+ for (DexEncodedMethod encoded : definedInterface.virtualMethods()) {
+ if (isDefaultMethod(encoded)) {
+ helper.addDefaultMethod(encoded);
+ }
+ }
+
+ return helper.wrapInCollection();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 5d52523..49a797a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -21,9 +21,10 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import java.util.function.Consumer;
// Represents the lambda descriptor inferred from calls site.
-final class LambdaDescriptor {
+public final class LambdaDescriptor {
private static final int LAMBDA_ALT_SERIALIZABLE = 1;
private static final int LAMBDA_ALT_HAS_EXTRA_INTERFACES = 2;
private static final int LAMBDA_ALT_HAS_BRIDGES = 4;
@@ -227,8 +228,8 @@
}
DexMethod bootstrapMethod = callSite.bootstrapMethod.asMethod();
- boolean isMetafactoryMethod = bootstrapMethod == rewriter.metafactoryMethod;
- boolean isAltMetafactoryMethod = bootstrapMethod == rewriter.metafactoryAltMethod;
+ boolean isMetafactoryMethod = bootstrapMethod == rewriter.factory.metafactoryMethod;
+ boolean isAltMetafactoryMethod = bootstrapMethod == rewriter.factory.metafactoryAltMethod;
if (!isMetafactoryMethod && !isAltMetafactoryMethod) {
// It is not a lambda, thus no need to manage this call site.
return LambdaDescriptor.MATCH_FAILED;
@@ -240,18 +241,18 @@
// Signature of main functional interface method.
DexValue.DexValueMethodType funcErasedSignature =
- getBootstrapArgument(callSite, 0, DexValue.DexValueMethodType.class);
+ getBootstrapArgument(callSite.bootstrapArgs, 0, DexValue.DexValueMethodType.class);
// Method handle of the implementation method.
DexMethodHandle lambdaImplMethodHandle =
- getBootstrapArgument(callSite, 1, DexValue.DexValueMethodHandle.class).value;
+ getBootstrapArgument(callSite.bootstrapArgs, 1, DexValue.DexValueMethodHandle.class).value;
// Even though there are some limitations on which method handle kinds are
// allowed for lambda impl-methods, there is no way to detect unsupported
// handle kinds after they are transformed into DEX method handle.
// Signature to be enforced on main method.
DexValue.DexValueMethodType funcEnforcedSignature =
- getBootstrapArgument(callSite, 2, DexValue.DexValueMethodType.class);
+ getBootstrapArgument(callSite.bootstrapArgs, 2, DexValue.DexValueMethodType.class);
if (!isEnforcedSignatureValid(
rewriter, funcEnforcedSignature.value, funcErasedSignature.value)) {
throw new Unreachable(
@@ -277,67 +278,70 @@
"Unexpected number of metafactory method arguments in " + callSite.toString());
}
} else {
- extractExtraLambdaInfo(rewriter, callSite, match);
+ extractAltMetafactory(
+ rewriter.factory,
+ callSite.bootstrapArgs,
+ interfaceType -> {
+ if (!match.interfaces.contains(interfaceType)) {
+ match.interfaces.add(interfaceType);
+ }
+ },
+ match.bridges::add);
}
return match;
}
- private static void extractExtraLambdaInfo(
- LambdaRewriter rewriter, DexCallSite callSite, LambdaDescriptor match) {
+ public static void extractAltMetafactory(
+ DexItemFactory dexItemFactory,
+ List<DexValue> bootstrapArgs,
+ Consumer<DexType> interfaceConsumer,
+ Consumer<DexProto> bridgeConsumer) {
int argIndex = 3;
- int flagsArg = getBootstrapArgument(
- callSite, argIndex++, DexValue.DexValueInt.class).value;
+ int flagsArg =
+ getBootstrapArgument(bootstrapArgs, argIndex++, DexValue.DexValueInt.class).value;
assert (flagsArg & ~LAMBDA_ALT_MASK) == 0;
// Load extra interfaces if any.
if ((flagsArg & LAMBDA_ALT_HAS_EXTRA_INTERFACES) != 0) {
- int count = getBootstrapArgument(
- callSite, argIndex++, DexValue.DexValueInt.class).value;
+ int count = getBootstrapArgument(bootstrapArgs, argIndex++, DexValue.DexValueInt.class).value;
for (int i = 0; i < count; i++) {
- DexType type = getBootstrapArgument(
- callSite, argIndex++, DexValue.DexValueType.class).value;
- if (!match.interfaces.contains(type)) {
- match.interfaces.add(type);
- }
+ DexType interfaceType =
+ getBootstrapArgument(bootstrapArgs, argIndex++, DexValue.DexValueType.class).value;
+ interfaceConsumer.accept(interfaceType);
}
}
// If the lambda is serializable, add it.
if ((flagsArg & LAMBDA_ALT_SERIALIZABLE) != 0) {
- if (!match.interfaces.contains(rewriter.serializableType)) {
- match.interfaces.add(rewriter.serializableType);
- }
+ interfaceConsumer.accept(dexItemFactory.serializableType);
}
// Load bridges if any.
if ((flagsArg & LAMBDA_ALT_HAS_BRIDGES) != 0) {
- int count = getBootstrapArgument(
- callSite, argIndex++, DexValue.DexValueInt.class).value;
+ int count = getBootstrapArgument(bootstrapArgs, argIndex++, DexValue.DexValueInt.class).value;
for (int i = 0; i < count; i++) {
- DexProto bridgeProto = getBootstrapArgument(
- callSite, argIndex++, DexValue.DexValueMethodType.class).value;
- match.bridges.add(bridgeProto);
+ DexProto bridgeProto =
+ getBootstrapArgument(bootstrapArgs, argIndex++, DexValue.DexValueMethodType.class)
+ .value;
+ bridgeConsumer.accept(bridgeProto);
}
}
- if (callSite.bootstrapArgs.size() != argIndex) {
- throw new Unreachable(
- "Unexpected number of metafactory method arguments in " + callSite.toString());
+ if (bootstrapArgs.size() != argIndex) {
+ throw new Unreachable("Unexpected number of metafactory method arguments in DexCallSite");
}
}
@SuppressWarnings("unchecked")
- private static <T> T getBootstrapArgument(DexCallSite callSite, int i, Class<T> clazz) {
- List<DexValue> bootstrapArgs = callSite.bootstrapArgs;
+ private static <T> T getBootstrapArgument(List<DexValue> bootstrapArgs, int i, Class<T> clazz) {
if (bootstrapArgs.size() < i) {
- throw new Unreachable("Expected to find at least "
- + i + " bootstrap arguments in " + callSite.toString());
+ throw new Unreachable(
+ "Expected to find at least " + i + " bootstrap arguments in DexCallSite");
}
DexValue value = bootstrapArgs.get(i);
if (!clazz.isAssignableFrom(value.getClass())) {
- throw new Unreachable("Unexpected type of "
- + "bootstrap arguments #" + i + " in " + callSite.toString());
+ throw new Unreachable("Unexpected type of bootstrap arguments #" + i + " in DexCallSite");
}
return (T) value;
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index aad7961..82792df 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -41,16 +41,8 @@
* lambda class generation, and instruction patching.
*/
public class LambdaRewriter {
- private static final String METAFACTORY_TYPE_DESCR = "Ljava/lang/invoke/LambdaMetafactory;";
- private static final String CALLSITE_TYPE_DESCR = "Ljava/lang/invoke/CallSite;";
- private static final String LOOKUP_TYPE_DESCR = "Ljava/lang/invoke/MethodHandles$Lookup;";
- private static final String METHODTYPE_TYPE_DESCR = "Ljava/lang/invoke/MethodType;";
- private static final String METHODHANDLE_TYPE_DESCR = "Ljava/lang/invoke/MethodHandle;";
- private static final String SERIALIZABLE_TYPE_DESCR = "Ljava/io/Serializable;";
private static final String SERIALIZED_LAMBDA_TYPE_DESCR = "Ljava/lang/invoke/SerializedLambda;";
- private static final String METAFACTORY_METHOD_NAME = "metafactory";
- private static final String METAFACTORY_ALT_METHOD_NAME = "altMetafactory";
private static final String DESERIALIZE_LAMBDA_METHOD_NAME = "$deserializeLambda$";
// Public for testing.
@@ -62,12 +54,8 @@
final AppInfo appInfo;
final DexItemFactory factory;
- final DexMethod metafactoryMethod;
final DexMethod objectInitMethod;
- final DexMethod metafactoryAltMethod;
- final DexType serializableType;
-
final DexString constructorName;
final DexString classConstructorName;
final DexString instanceFieldName;
@@ -97,28 +85,11 @@
this.factory = converter.appInfo.dexItemFactory;
this.appInfo = converter.appInfo;
- DexType metafactoryType = factory.createType(METAFACTORY_TYPE_DESCR);
- DexType callSiteType = factory.createType(CALLSITE_TYPE_DESCR);
- DexType lookupType = factory.createType(LOOKUP_TYPE_DESCR);
- DexType methodTypeType = factory.createType(METHODTYPE_TYPE_DESCR);
- DexType methodHandleType = factory.createType(METHODHANDLE_TYPE_DESCR);
-
- this.metafactoryMethod = factory.createMethod(metafactoryType,
- factory.createProto(callSiteType, lookupType, factory.stringType, methodTypeType,
- methodTypeType, methodHandleType, methodTypeType),
- factory.createString(METAFACTORY_METHOD_NAME));
-
- this.metafactoryAltMethod = factory.createMethod(metafactoryType,
- factory.createProto(callSiteType, lookupType, factory.stringType, methodTypeType,
- factory.objectArrayType),
- factory.createString(METAFACTORY_ALT_METHOD_NAME));
-
this.constructorName = factory.createString(Constants.INSTANCE_INITIALIZER_NAME);
DexProto initProto = factory.createProto(factory.voidType);
this.objectInitMethod = factory.createMethod(factory.objectType, initProto, constructorName);
this.classConstructorName = factory.createString(Constants.CLASS_INITIALIZER_NAME);
this.instanceFieldName = factory.createString(LAMBDA_INSTANCE_FIELD_NAME);
- this.serializableType = factory.createType(SERIALIZABLE_TYPE_DESCR);
this.deserializeLambdaMethodName = factory.createString(DESERIALIZE_LAMBDA_METHOD_NAME);
this.deserializeLambdaMethodProto = factory.createProto(
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 3feb59a..bed4df3 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
@@ -2023,7 +2023,8 @@
simplifyIfWithKnownCondition(code, block, theIf, cond, color);
}
}
- } else if (theIf.isZeroTest() && !inValues.get(0).isConstNumber()) {
+ } else if (theIf.isZeroTest() && !inValues.get(0).isConstNumber()
+ && (theIf.getType() == Type.EQ || theIf.getType() == Type.NE)) {
if (inValues.get(0).isNeverNull()) {
simplifyIfWithKnownCondition(code, block, theIf, 1, color);
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index a5ac28c..678ddae 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -55,7 +55,10 @@
private final Set<DexMethod> blackList = Sets.newIdentityHashSet();
- public Inliner(AppInfoWithLiveness appInfo, GraphLense graphLense, InternalOptions options) {
+ public Inliner(
+ AppInfoWithLiveness appInfo,
+ GraphLense graphLense,
+ InternalOptions options) {
this.appInfo = appInfo;
this.graphLense = graphLense;
this.options = options;
@@ -202,7 +205,7 @@
}
return NEVER;
} else {
- /* package-private */
+ /* package-private */
return targetHolder.isSamePackage(contextHolder) ? PACKAGE : NEVER;
}
}
@@ -269,9 +272,6 @@
if (!target.isProcessed()) {
new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
}
- if (options.enableNonNullTracking) {
- new NonNullTracker().addNonNull(code);
- }
return code;
}
}
@@ -443,7 +443,17 @@
iterator.previous();
instruction_allowance -= numberOfInstructions(inlinee);
if (instruction_allowance >= 0 || result.ignoreInstructionBudget()) {
- iterator.inlineInvoke(code, inlinee, blockIterator, blocksToRemove, downcast);
+ BasicBlock invokeSuccessor =
+ iterator.inlineInvoke(code, inlinee, blockIterator, blocksToRemove, downcast);
+ if (options.enableNonNullTracking) {
+ // Move the cursor back to where the inlinee blocks are added.
+ blockIterator = code.blocks.listIterator(code.blocks.indexOf(block));
+ // Kick off the tracker to add non-null IRs only to the inlinee blocks.
+ new NonNullTracker()
+ .addNonNullInPart(code, blockIterator, inlinee.blocks::contains);
+ // Move the cursor forward to where the inlinee blocks end.
+ blockIterator = code.blocks.listIterator(code.blocks.indexOf(invokeSuccessor));
+ }
// Update type env for inlined blocks.
typeEnvironment.analyzeBlocks(inlinee.topologicallySortedBlocks());
// TODO(b/69964136): need a test where refined env in inlinee affects the caller.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 4eae3d2..9aff28e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -19,6 +19,7 @@
import com.google.common.collect.Sets;
import java.util.ListIterator;
import java.util.Set;
+import java.util.function.Predicate;
public class NonNullTracker {
@@ -56,9 +57,16 @@
}
public void addNonNull(IRCode code) {
- ListIterator<BasicBlock> blocks = code.blocks.listIterator();
- while (blocks.hasNext()) {
- BasicBlock block = blocks.next();
+ addNonNullInPart(code, code.blocks.listIterator(), b -> true);
+ }
+
+ public void addNonNullInPart(
+ IRCode code, ListIterator<BasicBlock> blockIterator, Predicate<BasicBlock> blockTester) {
+ while (blockIterator.hasNext()) {
+ BasicBlock block = blockIterator.next();
+ if (!blockTester.test(block)) {
+ continue;
+ }
// Add non-null after instructions that implicitly indicate receiver/array is not null.
InstructionListIterator iterator = block.listIterator();
while (iterator.hasNext()) {
@@ -90,7 +98,7 @@
// A: ...y // blockWithNonNullInstruction
//
BasicBlock blockWithNonNullInstruction =
- block.hasCatchHandlers() ? iterator.split(code, blocks) : block;
+ block.hasCatchHandlers() ? iterator.split(code, blockIterator) : block;
// Next, add non-null fake IR, e.g.,
// ...x
// invoke(rcv, ...)
@@ -191,7 +199,7 @@
}
}
// Avoid adding a non-null for the value without meaningful users.
- if (!dominatedUsers.isEmpty() && !dominatedPhiUsers.isEmpty()) {
+ if (!dominatedUsers.isEmpty() || !dominatedPhiUsers.isEmpty()) {
Value nonNullValue = code.createValue(
knownToBeNonNullValue.outType(), knownToBeNonNullValue.getLocalInfo());
NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue, theIf);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index dba6183..126b127 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -859,7 +859,8 @@
}
@Override
- public void buildInstruction(IRBuilder builder, int instructionIndex) {
+ public void buildInstruction(
+ IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
if (instructionIndex == outline.templateInstructions.size()) {
if (outline.returnType == dexItemFactory.voidType) {
builder.addReturn();
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
index 5d03dfd..5a6cc2d 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SyntheticSourceCode.java
@@ -188,7 +188,8 @@
}
@Override
- public final void buildInstruction(IRBuilder builder, int instructionIndex)
+ public final void buildInstruction(
+ IRBuilder builder, int instructionIndex, boolean firstBlockInstruction)
throws ApiLevelException {
constructors.get(instructionIndex).accept(builder);
}
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index c8be00e..a7b3f77 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -276,7 +276,7 @@
}
AnnotationVisitor v =
visitor.visit(
- dexAnnotation.annotation.type.toDescriptorString(),
+ namingLens.lookupDescriptor(dexAnnotation.annotation.type).toString(),
dexAnnotation.visibility == DexAnnotation.VISIBILITY_RUNTIME);
if (v != null) {
writeAnnotation(v, dexAnnotation.annotation);
@@ -295,7 +295,8 @@
if (value instanceof DexValueAnnotation) {
DexValueAnnotation valueAnnotation = (DexValueAnnotation) value;
AnnotationVisitor innerVisitor =
- visitor.visitAnnotation(name, valueAnnotation.value.type.toDescriptorString());
+ visitor.visitAnnotation(
+ name, namingLens.lookupDescriptor(valueAnnotation.value.type).toString());
if (innerVisitor != null) {
writeAnnotation(innerVisitor, valueAnnotation.value);
innerVisitor.visitEnd();
@@ -311,7 +312,8 @@
}
} else if (value instanceof DexValueEnum) {
DexValueEnum en = (DexValueEnum) value;
- visitor.visitEnum(name, en.value.type.toDescriptorString(), en.value.name.toString());
+ visitor.visitEnum(
+ name, namingLens.lookupDescriptor(en.value.type).toString(), en.value.name.toString());
} else if (value instanceof DexValueField) {
throw new Unreachable("writeAnnotationElement of DexValueField");
} else if (value instanceof DexValueMethod) {
@@ -325,7 +327,7 @@
visitor.visit(name, str.getValue().toString());
} else if (value instanceof DexValueType) {
DexValueType ty = (DexValueType) value;
- visitor.visit(name, Type.getType(ty.value.toDescriptorString()));
+ visitor.visit(name, Type.getType(namingLens.lookupDescriptor(ty.value).toString()));
} else if (value instanceof UnknownDexValue) {
throw new Unreachable("writeAnnotationElement of UnknownDexValue");
} else {
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 ce37763..25a84b6 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -12,7 +12,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.MethodJavaSignatureEquivalence;
import com.android.tools.r8.utils.MethodSignatureEquivalence;
import com.android.tools.r8.utils.Timing;
import com.google.common.base.Equivalence;
@@ -88,13 +87,10 @@
*/
class MethodNameMinifier extends MemberNameMinifier<DexMethod, DexProto> {
- private final Equivalence<DexMethod> equivalence;
+ private final Equivalence<DexMethod> equivalence = MethodSignatureEquivalence.get();
MethodNameMinifier(AppInfoWithSubtyping appInfo, RootSet rootSet, InternalOptions options) {
super(appInfo, rootSet, options);
- equivalence = overloadAggressively
- ? MethodSignatureEquivalence.get()
- : MethodJavaSignatureEquivalence.get();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index de171a3..3372a20 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -11,8 +11,9 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
-import com.google.common.collect.Sets;
+import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -124,7 +125,7 @@
// Rebind to the lowest library class or program class.
if (target != null && target.method != method) {
DexClass targetClass = appInfo.definitionFor(target.method.holder);
- // If the targetclass is not public but the targeted method is, we might run into
+ // If the target class is not public but the targeted method is, we might run into
// visibility problems when rebinding.
if (!targetClass.accessFlags.isPublic() && target.accessFlags.isPublic()) {
// If the original class is public and this method is public, it might have been called
@@ -178,27 +179,37 @@
return null;
}
- private void computeFieldRebinding(Set<DexField> fields,
+ private void computeFieldRebinding(Map<DexField, Set<DexEncodedMethod>> fields,
BiFunction<DexType, DexField, DexEncodedField> lookup,
BiFunction<DexClass, DexField, DexEncodedField> lookupTargetOnClass) {
- for (DexField field : fields) {
+ for (Map.Entry<DexField, Set<DexEncodedMethod>> entry : fields.entrySet()) {
+ DexField field = entry.getKey();
field = lense.lookupField(field, null);
DexEncodedField target = lookup.apply(field.getHolder(), field);
// Rebind to the lowest library class or program class. Do not rebind accesses to fields that
- // are not public, as this might lead to access violation errors.
- if (target != null && target.field != field && isVisibleFromOtherClasses(target)) {
+ // are not visible from the access context.
+ Set<DexEncodedMethod> contexts = entry.getValue();
+ if (target != null && target.field != field && contexts.stream().allMatch(context ->
+ isVisibleFromOriginalContext(context.method.getHolder(), target))) {
builder.map(field, validTargetFor(target.field, field, lookupTargetOnClass));
}
}
}
- private boolean isVisibleFromOtherClasses(DexEncodedField field) {
- // If the field is not public, the visibility on the class can not be a further constraint.
- if (!field.accessFlags.isPublic()) {
- return true;
+ private boolean isVisibleFromOriginalContext(DexType context, DexEncodedField field) {
+ DexType holderType = field.field.getHolder();
+ DexClass holder = appInfo.definitionFor(holderType);
+ if (holder == null) {
+ return false;
}
- // If the field is public, then a non-public holder class will further constrain visibility.
- return appInfo.definitionFor(field.field.getHolder()).accessFlags.isPublic();
+ Constraint classVisibility =
+ Constraint.deriveConstraint(context, holderType, holder.accessFlags, appInfo);
+ if (classVisibility == Constraint.NEVER) {
+ return false;
+ }
+ Constraint fieldVisibility =
+ Constraint.deriveConstraint(context, holderType, field.accessFlags, appInfo);
+ return fieldVisibility != Constraint.NEVER;
}
public GraphLense run() {
@@ -213,10 +224,15 @@
// Likewise static invokes.
computeMethodRebinding(appInfo.staticInvokes, this::anyLookup);
- computeFieldRebinding(Sets.union(appInfo.staticFieldReads, appInfo.staticFieldWrites),
+ computeFieldRebinding(appInfo.staticFieldReads,
appInfo::resolveFieldOn, DexClass::lookupField);
- computeFieldRebinding(Sets.union(appInfo.instanceFieldReads, appInfo.instanceFieldWrites),
+ computeFieldRebinding(appInfo.staticFieldWrites,
appInfo::resolveFieldOn, DexClass::lookupField);
+ computeFieldRebinding(appInfo.instanceFieldReads,
+ appInfo::resolveFieldOn, DexClass::lookupField);
+ computeFieldRebinding(appInfo.instanceFieldWrites,
+ appInfo::resolveFieldOn, DexClass::lookupField);
+
return builder.build(appInfo.dexItemFactory, lense);
}
}
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index c815cf8..5847a80 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -33,8 +33,9 @@
method.getCode().registerCodeReferences(targetExtractor);
DexMethod target = targetExtractor.getTarget();
InvokeKind kind = targetExtractor.getKind();
- if (target != null &&
- target.proto == method.method.proto) {
+ // javac-generated visibility forward bridge method has same descriptor (name, signature and
+ // return type).
+ if (target != null && target.hasSameProtoAndName(method.method)) {
assert !method.accessFlags.isPrivate() && !method.accessFlags.isConstructor();
if (kind == InvokeKind.SUPER) {
// This is a visibility forward, so check for the direct target.
diff --git a/src/main/java/com/android/tools/r8/shaking/DexStringCache.java b/src/main/java/com/android/tools/r8/shaking/DexStringCache.java
new file mode 100644
index 0000000..377dacd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/DexStringCache.java
@@ -0,0 +1,15 @@
+// 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;
+
+import com.android.tools.r8.graph.DexString;
+import java.util.concurrent.ConcurrentHashMap;
+
+final class DexStringCache {
+ private final ConcurrentHashMap<DexString, String> stringCache = new ConcurrentHashMap<>();
+
+ public String lookupString(DexString name) {
+ return stringCache.computeIfAbsent(name, DexString::toString);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 0b43509..cd04989 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -41,6 +41,7 @@
import com.android.tools.r8.ir.code.Invoke.Type;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.shaking.RootSetBuilder.ConsequentRootSet;
import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
import com.android.tools.r8.shaking.protolite.ProtoLiteExtension;
import com.android.tools.r8.utils.InternalOptions;
@@ -70,6 +71,8 @@
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -99,10 +102,14 @@
Maps.newIdentityHashMap();
private final Map<DexType, Set<DexMethod>> directInvokes = Maps.newIdentityHashMap();
private final Map<DexType, Set<DexMethod>> staticInvokes = Maps.newIdentityHashMap();
- private final Map<DexType, Set<DexField>> instanceFieldsWritten = Maps.newIdentityHashMap();
- private final Map<DexType, Set<DexField>> instanceFieldsRead = Maps.newIdentityHashMap();
- private final Map<DexType, Set<DexField>> staticFieldsRead = Maps.newIdentityHashMap();
- private final Map<DexType, Set<DexField>> staticFieldsWritten = Maps.newIdentityHashMap();
+ private final Map<DexType, Set<TargetWithContext<DexField>>> instanceFieldsWritten =
+ Maps.newIdentityHashMap();
+ private final Map<DexType, Set<TargetWithContext<DexField>>> instanceFieldsRead =
+ Maps.newIdentityHashMap();
+ private final Map<DexType, Set<TargetWithContext<DexField>>> staticFieldsRead =
+ Maps.newIdentityHashMap();
+ private final Map<DexType, Set<TargetWithContext<DexField>>> staticFieldsWritten =
+ Maps.newIdentityHashMap();
private final ProtoLiteExtension protoLiteExtension;
private final Set<DexField> protoLiteFields = Sets.newIdentityHashSet();
@@ -128,7 +135,7 @@
.newIdentityHashMap();
/**
- * Set of types that are mentioned in the program. We at least need an empty abstract classitem
+ * Set of types that are mentioned in the program. We at least need an empty abstract class item
* for these.
*/
private final Set<DexType> liveTypes = Sets.newIdentityHashSet();
@@ -361,7 +368,7 @@
@Override
public boolean registerInstanceFieldWrite(DexField field) {
- if (!registerItemWithTarget(instanceFieldsWritten, field)) {
+ if (!registerItemWithTargetAndContext(instanceFieldsWritten, field, currentMethod)) {
return false;
}
if (Log.ENABLED) {
@@ -374,7 +381,7 @@
@Override
public boolean registerInstanceFieldRead(DexField field) {
- if (!registerItemWithTarget(instanceFieldsRead, field)) {
+ if (!registerItemWithTargetAndContext(instanceFieldsRead, field, currentMethod)) {
return false;
}
if (Log.ENABLED) {
@@ -392,7 +399,7 @@
@Override
public boolean registerStaticFieldRead(DexField field) {
- if (!registerItemWithTarget(staticFieldsRead, field)) {
+ if (!registerItemWithTargetAndContext(staticFieldsRead, field, currentMethod)) {
return false;
}
if (Log.ENABLED) {
@@ -404,7 +411,7 @@
@Override
public boolean registerStaticFieldWrite(DexField field) {
- if (!registerItemWithTarget(staticFieldsWritten, field)) {
+ if (!registerItemWithTargetAndContext(staticFieldsWritten, field, currentMethod)) {
return false;
}
if (Log.ENABLED) {
@@ -712,12 +719,12 @@
assert clazz.accessFlags.isInterface();
SetWithReason<DexEncodedMethod> reachableMethods = reachableVirtualMethods.get(iface);
if (reachableMethods != null) {
- seen = seen.newNestedScope();
- transitionNonAbstractMethodsToLiveAndShadow(reachableMethods.getItems(), instantiatedType,
- seen);
- for (DexType subInterface : clazz.interfaces.values) {
- transitionDefaultMethodsForInstantiatedClass(subInterface, instantiatedType, seen);
- }
+ transitionNonAbstractMethodsToLiveAndShadow(
+ reachableMethods.getItems(), instantiatedType, seen.newNestedScope());
+ }
+ seen = seen.newNestedScope();
+ for (DexType subInterface : clazz.interfaces.values) {
+ transitionDefaultMethodsForInstantiatedClass(subInterface, instantiatedType, seen);
}
}
@@ -1027,30 +1034,36 @@
reachability, instantiatedTypes.getReasons());
}
- public AppInfoWithLiveness traceMainDex(RootSet rootSet, Timing timing) {
+ public AppInfoWithLiveness traceMainDex(
+ RootSet rootSet, ExecutorService executorService, Timing timing) throws ExecutionException {
this.tracingMainDex = true;
this.rootSet = rootSet;
// Translate the result of root-set computation into enqueuer actions.
enqueueRootItems(rootSet.noShrinking);
- AppInfoWithLiveness appInfo = trace(timing);
+ AppInfoWithLiveness appInfo = trace(executorService, timing);
options.reporter.failIfPendingErrors();
return appInfo;
}
- public AppInfoWithLiveness traceApplication(RootSet rootSet, Timing timing) {
+ public AppInfoWithLiveness traceApplication(
+ RootSet rootSet, ExecutorService executorService, Timing timing) throws ExecutionException {
this.rootSet = rootSet;
// Translate the result of root-set computation into enqueuer actions.
enqueueRootItems(rootSet.noShrinking);
appInfo.libraryClasses().forEach(this::markAllLibraryVirtualMethodsReachable);
- AppInfoWithLiveness result = trace(timing);
+ AppInfoWithLiveness result = trace(executorService, timing);
options.reporter.failIfPendingErrors();
return result;
}
- private AppInfoWithLiveness trace(Timing timing) {
+ private AppInfoWithLiveness trace(
+ ExecutorService executorService, Timing timing) throws ExecutionException {
timing.begin("Grow the tree.");
try {
while (true) {
+ long numOfLiveItems = (long) liveTypes.size();
+ numOfLiveItems += (long) liveMethods.items.size();
+ numOfLiveItems += (long) liveFields.items.size();
while (!workList.isEmpty()) {
Action action = workList.poll();
switch (action.kind) {
@@ -1083,6 +1096,24 @@
throw new IllegalArgumentException(action.kind.toString());
}
}
+
+ // Continue fix-point processing if -if rules are enabled by items that newly became live.
+ long numOfLiveItemsAfterProcessing = (long) liveTypes.size();
+ numOfLiveItemsAfterProcessing += (long) liveMethods.items.size();
+ numOfLiveItemsAfterProcessing += (long) liveFields.items.size();
+ if (numOfLiveItemsAfterProcessing > numOfLiveItems) {
+ RootSetBuilder consequentSetBuilder =
+ new RootSetBuilder(appInfo, rootSet.ifRules, options);
+ ConsequentRootSet consequentRootSet = consequentSetBuilder.runForIfRules(
+ executorService, liveTypes, liveMethods.getItems(), liveFields.getItems());
+ enqueueRootItems(consequentRootSet.noShrinking);
+ rootSet.noOptimization.addAll(consequentRootSet.noOptimization);
+ rootSet.noObfuscation.addAll(consequentRootSet.noObfuscation);
+ if (!workList.isEmpty()) {
+ continue;
+ }
+ }
+
// Continue fix-point processing while there are additional work items to ensure
// Proguard compatibility.
if (proguardCompatibilityWorkList.isEmpty()
@@ -1208,34 +1239,41 @@
}
}
- private Set<DexField> collectFields(Map<DexType, Set<DexField>> map) {
- return map.values().stream().flatMap(Collection::stream)
- .collect(Collectors.toCollection(Sets::newIdentityHashSet));
+ private Map<DexField, Set<DexEncodedMethod>> collectFields(
+ Map<DexType, Set<TargetWithContext<DexField>>> map) {
+ Map<DexField, Set<DexEncodedMethod>> result = new IdentityHashMap<>();
+ for (Map.Entry<DexType, Set<TargetWithContext<DexField>>> entry : map.entrySet()) {
+ for (TargetWithContext<DexField> fieldWithContext : entry.getValue()) {
+ DexField field = fieldWithContext.getTarget();
+ DexEncodedMethod context = fieldWithContext.getContext();
+ result.computeIfAbsent(field, k -> Sets.newIdentityHashSet())
+ .add(context);
+ }
+ }
+ return result;
}
- SortedSet<DexField> collectInstanceFieldsRead() {
- return ImmutableSortedSet.copyOf(
- PresortedComparable<DexField>::slowCompareTo, collectFields(instanceFieldsRead));
+ Map<DexField, Set<DexEncodedMethod>> collectInstanceFieldsRead() {
+ return Collections.unmodifiableMap(collectFields(instanceFieldsRead));
}
- SortedSet<DexField> collectInstanceFieldsWritten() {
- return ImmutableSortedSet.copyOf(
- PresortedComparable<DexField>::slowCompareTo, collectFields(instanceFieldsWritten));
+ Map<DexField, Set<DexEncodedMethod>> collectInstanceFieldsWritten() {
+ return Collections.unmodifiableMap(collectFields(instanceFieldsWritten));
}
- SortedSet<DexField> collectStaticFieldsRead() {
- return ImmutableSortedSet.copyOf(
- PresortedComparable<DexField>::slowCompareTo, collectFields(staticFieldsRead));
+ Map<DexField, Set<DexEncodedMethod>> collectStaticFieldsRead() {
+ return Collections.unmodifiableMap(collectFields(staticFieldsRead));
}
- SortedSet<DexField> collectStaticFieldsWritten() {
- return ImmutableSortedSet.copyOf(
- PresortedComparable<DexField>::slowCompareTo, collectFields(staticFieldsWritten));
+ Map<DexField, Set<DexEncodedMethod>> collectStaticFieldsWritten() {
+ return Collections.unmodifiableMap(collectFields(staticFieldsWritten));
}
- private Set<DexField> collectReachedFields(Map<DexType, Set<DexField>> map,
- Function<DexField, DexField> lookup) {
- return map.values().stream().flatMap(set -> set.stream().map(lookup).filter(Objects::nonNull))
+ private Set<DexField> collectReachedFields(
+ Set<DexField> set, Function<DexField, DexField> lookup) {
+ return set.stream()
+ .map(lookup)
+ .filter(Objects::nonNull)
.collect(Collectors.toCollection(Sets::newIdentityHashSet));
}
@@ -1249,16 +1287,11 @@
return target == null ? null : target.field;
}
- SortedSet<DexField> collectFieldsRead() {
+ SortedSet<DexField> mergeFieldAccesses(Set<DexField> instanceFields, Set<DexField> staticFields) {
return ImmutableSortedSet.copyOf(PresortedComparable<DexField>::slowCompareTo,
- Sets.union(collectReachedFields(instanceFieldsRead, this::tryLookupInstanceField),
- collectReachedFields(staticFieldsRead, this::tryLookupStaticField)));
- }
-
- SortedSet<DexField> collectFieldsWritten() {
- return ImmutableSortedSet.copyOf(PresortedComparable<DexField>::slowCompareTo,
- Sets.union(collectReachedFields(instanceFieldsWritten, this::tryLookupInstanceField),
- collectReachedFields(staticFieldsWritten, this::tryLookupStaticField)));
+ Sets.union(
+ collectReachedFields(instanceFields, this::tryLookupInstanceField),
+ collectReachedFields(staticFields, this::tryLookupStaticField)));
}
private void markClassAsInstantiatedWithCompatRule(DexClass clazz) {
@@ -1429,21 +1462,21 @@
*/
public final SortedSet<DexField> fieldsWritten;
/**
- * Set of all field ids used in instance field reads.
+ * Set of all field ids used in instance field reads, along with access context.
*/
- public final SortedSet<DexField> instanceFieldReads;
+ public final Map<DexField, Set<DexEncodedMethod>> instanceFieldReads;
/**
- * Set of all field ids used in instance field writes.
+ * Set of all field ids used in instance field writes, along with access context.
*/
- public final SortedSet<DexField> instanceFieldWrites;
+ public final Map<DexField, Set<DexEncodedMethod>> instanceFieldWrites;
/**
- * Set of all field ids used in static static field reads.
+ * Set of all field ids used in static static field reads, along with access context.
*/
- public final SortedSet<DexField> staticFieldReads;
+ public final Map<DexField, Set<DexEncodedMethod>> staticFieldReads;
/**
- * Set of all field ids used in static field writes.
+ * Set of all field ids used in static field writes, along with access context.
*/
- public final SortedSet<DexField> staticFieldWrites;
+ public final Map<DexField, Set<DexEncodedMethod>> staticFieldWrites;
/**
* Set of all methods referenced in virtual invokes;
*/
@@ -1515,8 +1548,10 @@
this.instanceFieldWrites = enqueuer.collectInstanceFieldsWritten();
this.staticFieldReads = enqueuer.collectStaticFieldsRead();
this.staticFieldWrites = enqueuer.collectStaticFieldsWritten();
- this.fieldsRead = enqueuer.collectFieldsRead();
- this.fieldsWritten = enqueuer.collectFieldsWritten();
+ this.fieldsRead = enqueuer.mergeFieldAccesses(
+ instanceFieldReads.keySet(), staticFieldReads.keySet());
+ this.fieldsWritten = enqueuer.mergeFieldAccesses(
+ instanceFieldWrites.keySet(), staticFieldWrites.keySet());
this.pinnedItems = rewritePinnedItemsToDescriptors(enqueuer.pinnedItems);
this.virtualInvokes = joinInvokedMethods(enqueuer.virtualInvokes);
this.interfaceInvokes = joinInvokedMethods(enqueuer.interfaceInvokes);
@@ -1532,8 +1567,8 @@
this.prunedTypes = Collections.emptySet();
this.switchMaps = Collections.emptyMap();
this.ordinalsMaps = Collections.emptyMap();
- assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
- assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
+ assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty();
+ assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty();
}
private AppInfoWithLiveness(AppInfoWithLiveness previous, DexApplication application,
@@ -1566,8 +1601,8 @@
this.prunedTypes = mergeSets(previous.prunedTypes, removedClasses);
this.switchMaps = previous.switchMaps;
this.ordinalsMaps = previous.ordinalsMaps;
- assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
- assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
+ assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty();
+ assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty();
}
private AppInfoWithLiveness(AppInfoWithLiveness previous,
@@ -1579,10 +1614,14 @@
this.targetedMethods = rewriteItems(previous.targetedMethods, lense::lookupMethod);
this.liveMethods = rewriteItems(previous.liveMethods, lense::lookupMethod);
this.liveFields = rewriteItems(previous.liveFields, lense::lookupField);
- this.instanceFieldReads = rewriteItems(previous.instanceFieldReads, lense::lookupField);
- this.instanceFieldWrites = rewriteItems(previous.instanceFieldWrites, lense::lookupField);
- this.staticFieldReads = rewriteItems(previous.staticFieldReads, lense::lookupField);
- this.staticFieldWrites = rewriteItems(previous.staticFieldWrites, lense::lookupField);
+ this.instanceFieldReads =
+ rewriteKeysWhileMergingValues(previous.instanceFieldReads, lense::lookupField);
+ this.instanceFieldWrites =
+ rewriteKeysWhileMergingValues(previous.instanceFieldWrites, lense::lookupField);
+ this.staticFieldReads =
+ rewriteKeysWhileMergingValues(previous.staticFieldReads, lense::lookupField);
+ this.staticFieldWrites =
+ rewriteKeysWhileMergingValues(previous.staticFieldWrites, lense::lookupField);
this.fieldsRead = rewriteItems(previous.fieldsRead, lense::lookupField);
this.fieldsWritten = rewriteItems(previous.fieldsWritten, lense::lookupField);
this.pinnedItems = rewriteMixedItems(previous.pinnedItems, lense);
@@ -1608,8 +1647,8 @@
this.ordinalsMaps = rewriteKeys(previous.ordinalsMaps, lense::lookupType);
this.protoLiteFields = previous.protoLiteFields;
// Sanity check sets after rewriting.
- assert Sets.intersection(instanceFieldReads, staticFieldReads).isEmpty();
- assert Sets.intersection(instanceFieldWrites, staticFieldWrites).isEmpty();
+ assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty();
+ assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty();
}
public AppInfoWithLiveness(AppInfoWithLiveness previous,
@@ -1744,6 +1783,18 @@
return builder.build();
}
+ private static <T extends PresortedComparable<T>, S> Map<T, Set<S>>
+ rewriteKeysWhileMergingValues(
+ Map<T, Set<S>> original, BiFunction<T, DexEncodedMethod, T> rewrite) {
+ Map<T, Set<S>> result = new IdentityHashMap<>();
+ for (T item : original.keySet()) {
+ T rewrittenKey = rewrite.apply(item, null);
+ result.computeIfAbsent(rewrittenKey, k -> Sets.newIdentityHashSet())
+ .addAll(original.get(item));
+ }
+ return Collections.unmodifiableMap(result);
+ }
+
private static ImmutableSet<DexItem> rewriteMixedItems(
Set<DexItem> original, GraphLense lense) {
ImmutableSet.Builder<DexItem> builder = ImmutableSet.builder();
@@ -2095,6 +2146,10 @@
return target;
}
+ public DexEncodedMethod getContext() {
+ return context;
+ }
+
@Override
public int hashCode() {
return target.hashCode() * 31 + context.hashCode();
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 35c8e47..c1af9f4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -336,8 +336,6 @@
configurationBuilder.addRule(parseIdentifierNameStringRule());
} else if (acceptString("if")) {
configurationBuilder.addRule(parseIfRule(optionStart));
- // TODO(b/73708139): remove warning once we add support -if <class_spec>
- warnIgnoringOptions("if", optionStart);
} else {
String unknownOption = acceptString();
reporter.error(new StringDiagnostic(
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index 6facec1..9659954 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -5,21 +5,19 @@
import java.util.Set;
-public class ProguardIfRule extends ProguardConfigurationRule {
+public class ProguardIfRule extends ProguardKeepRule {
final ProguardKeepRule subsequentRule;
- public static class Builder extends ProguardConfigurationRule.Builder {
+ public static class Builder extends ProguardKeepRule.Builder {
ProguardKeepRule subsequentRule = null;
- private Builder() {
- }
-
public void setSubsequentRule(ProguardKeepRule rule) {
subsequentRule = rule;
}
+ @Override
public ProguardIfRule build() {
assert subsequentRule != null : "Option -if without a subsequent rule.";
return new ProguardIfRule(classAnnotation, classAccessFlags,
@@ -37,7 +35,8 @@
Set<ProguardMemberRule> memberRules,
ProguardKeepRule subsequentRule) {
super(classAnnotation, classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType,
- classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+ classNames, inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules,
+ ProguardKeepRuleType.CONDITIONAL, ProguardKeepRuleModifiers.builder().build());
this.subsequentRule = subsequentRule;
}
@@ -46,6 +45,23 @@
}
@Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ProguardIfRule)) {
+ return false;
+ }
+ ProguardIfRule other = (ProguardIfRule) o;
+ if (subsequentRule != other.subsequentRule) {
+ return false;
+ }
+ return super.equals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() * 3 + subsequentRule.hashCode();
+ }
+
+ @Override
String typeString() {
return "if";
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
index 9b1bc0b..d3d6705 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
@@ -14,7 +14,7 @@
private final ProguardKeepRuleModifiers.Builder modifiersBuilder
= ProguardKeepRuleModifiers.builder();
- private Builder() {}
+ protected Builder() {}
public void setType(ProguardKeepRuleType type) {
this.type = type;
@@ -34,7 +34,7 @@
private final ProguardKeepRuleType type;
private final ProguardKeepRuleModifiers modifiers;
- private ProguardKeepRule(
+ protected ProguardKeepRule(
ProguardTypeMatcher classAnnotation,
ProguardAccessFlags classAccessFlags,
ProguardAccessFlags negatedClassAccessFlags,
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleType.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleType.java
index 4af299c..2018cbf 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleType.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleType.java
@@ -8,7 +8,8 @@
public enum ProguardKeepRuleType {
KEEP,
KEEP_CLASS_MEMBERS,
- KEEP_CLASSES_WITH_MEMBERS;
+ KEEP_CLASSES_WITH_MEMBERS,
+ CONDITIONAL;
@Override
public String toString() {
@@ -19,6 +20,8 @@
return "keepclassmembers";
case KEEP_CLASSES_WITH_MEMBERS:
return "keepclasseswithmembers";
+ case CONDITIONAL:
+ return "if";
default:
throw new Unreachable("Unknown ProguardKeepRuleType.");
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index db7dca8..5e1293f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -156,7 +156,7 @@
return type;
}
- public boolean matches(DexEncodedField field, RootSetBuilder builder) {
+ public boolean matches(DexEncodedField field, DexStringCache stringCache) {
switch (getRuleType()) {
case ALL:
case ALL_FIELDS:
@@ -169,7 +169,7 @@
return RootSetBuilder.containsAnnotation(annotation, field.annotations);
case FIELD:
// Name check.
- String name = builder.lookupString(field.field.name);
+ String name = stringCache.lookupString(field.field.name);
if (!getName().matches(name)) {
break;
}
@@ -196,7 +196,7 @@
return false;
}
- public boolean matches(DexEncodedMethod method, RootSetBuilder builder) {
+ public boolean matches(DexEncodedMethod method, DexStringCache stringCache) {
switch (getRuleType()) {
case ALL_METHODS:
if (method.isClassInitializer()) {
@@ -220,7 +220,7 @@
case CONSTRUCTOR:
case INIT:
// Name check.
- String name = builder.lookupString(method.method.name);
+ String name = stringCache.lookupString(method.method.name);
if (!getName().matches(name)) {
break;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 9c0dcdd..3912b1c 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.logging.Log;
@@ -40,17 +39,17 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
+import java.util.function.Function;
import java.util.stream.Collectors;
public class RootSetBuilder {
- private final DirectMappedDexApplication application;
private final AppInfo appInfo;
- private final List<ProguardConfigurationRule> rules;
+ private final DirectMappedDexApplication application;
+ private final Collection<ProguardConfigurationRule> rules;
private final Map<DexItem, ProguardKeepRule> noShrinking = new IdentityHashMap<>();
private final Set<DexItem> noOptimization = Sets.newIdentityHashSet();
private final Set<DexItem> noObfuscation = Sets.newIdentityHashSet();
@@ -67,18 +66,37 @@
private final Set<DexItem> identifierNameStrings = Sets.newIdentityHashSet();
private final InternalOptions options;
- public RootSetBuilder(DexApplication application, AppInfo appInfo,
- List<ProguardConfigurationRule> rules, InternalOptions options) {
- this.application = application.asDirect();
+ private final DexStringCache dexStringCache = new DexStringCache();
+ private final Set<ProguardIfRule> ifRules = Sets.newIdentityHashSet();
+
+ public RootSetBuilder(
+ AppInfo appInfo,
+ DexApplication application,
+ List<ProguardConfigurationRule> rules,
+ InternalOptions options) {
this.appInfo = appInfo;
- this.rules = rules;
+ this.application = application.asDirect();
+ this.rules = rules == null ? null : Collections.unmodifiableCollection(rules);
this.options = options;
}
- private boolean anySuperTypeMatches(DexType type, ProguardTypeMatcher name,
+ RootSetBuilder(
+ AppInfo appInfo,
+ Set<ProguardIfRule> ifRules,
+ InternalOptions options) {
+ this.appInfo = appInfo;
+ this.application = appInfo.app.asDirect();
+ this.rules = Collections.unmodifiableCollection(ifRules);
+ this.options = options;
+ }
+
+ private boolean anySuperTypeMatches(
+ DexType type,
+ Function<DexType, DexClass> definitionFor,
+ ProguardTypeMatcher name,
ProguardTypeMatcher annotation) {
while (type != null) {
- DexClass clazz = application.definitionFor(type);
+ DexClass clazz = definitionFor.apply(type);
if (clazz == null) {
// TODO(herhut): Warn about broken supertype chain?
return false;
@@ -91,36 +109,42 @@
return false;
}
- private boolean anyImplementedInterfaceMatches(DexClass clazz,
- ProguardTypeMatcher className, ProguardTypeMatcher annotation) {
+ private boolean anyImplementedInterfaceMatches(
+ DexClass clazz,
+ Function<DexType, DexClass> definitionFor,
+ ProguardTypeMatcher className,
+ ProguardTypeMatcher annotation) {
if (clazz == null) {
return false;
}
for (DexType iface : clazz.interfaces.values) {
- DexClass ifaceClass = application.definitionFor(iface);
+ DexClass ifaceClass = definitionFor.apply(iface);
if (ifaceClass == null) {
// TODO(herhut): Warn about broken supertype chain?
return false;
}
// TODO(herhut): Maybe it would be better to do this breadth first.
if ((className.matches(iface) && containsAnnotation(annotation, ifaceClass.annotations))
- || anyImplementedInterfaceMatches(ifaceClass, className, annotation)) {
+ || anyImplementedInterfaceMatches(ifaceClass, definitionFor, className, annotation)) {
return true;
}
}
if (clazz.superType == null) {
return false;
}
- DexClass superClass = application.definitionFor(clazz.superType);
+ DexClass superClass = definitionFor.apply(clazz.superType);
if (superClass == null) {
// TODO(herhut): Warn about broken supertype chain?
return false;
}
- return anyImplementedInterfaceMatches(superClass, className, annotation);
+ return anyImplementedInterfaceMatches(superClass, definitionFor, className, annotation);
}
// Process a class with the keep rule.
- private void process(DexClass clazz, ProguardConfigurationRule rule) {
+ private void process(
+ DexClass clazz,
+ ProguardConfigurationRule rule,
+ ProguardIfRule ifRule) {
if (rule.getClassType().matches(clazz) == rule.getClassTypeNegated()) {
return;
}
@@ -140,12 +164,18 @@
// TODO(herhut): One day make this do what it says.
if (rule.hasInheritanceClassName()) {
boolean extendsExpected =
- anySuperTypeMatches(clazz.superType, rule.getInheritanceClassName(),
+ anySuperTypeMatches(
+ clazz.superType,
+ application::definitionFor,
+ rule.getInheritanceClassName(),
rule.getInheritanceAnnotation());
boolean implementsExpected = false;
if (!extendsExpected) {
implementsExpected =
- anyImplementedInterfaceMatches(clazz, rule.getInheritanceClassName(),
+ anyImplementedInterfaceMatches(
+ clazz,
+ application::definitionFor,
+ rule.getInheritanceClassName(),
rule.getInheritanceAnnotation());
}
if (!extendsExpected && !implementsExpected) {
@@ -167,16 +197,15 @@
}
}
- if (rule instanceof ProguardIfRule) {
- // TODO(b/73708139): add support -if <class_spec>
- // Check if -if part matches or subsequent -keep part matches.
- } else if (rule.getClassNames().matches(clazz.type)) {
+ if (rule.getClassNames().matches(clazz.type)) {
Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
if (rule instanceof ProguardKeepRule) {
switch (((ProguardKeepRule) rule).getType()) {
case KEEP_CLASS_MEMBERS: {
- markMatchingVisibleMethods(clazz, memberKeepRules, rule, clazz.type);
- markMatchingFields(clazz, memberKeepRules, rule, clazz.type);
+ // If we're handling -if consequent part, that means precondition already met.
+ DexType precondition = ifRule != null ? null : clazz.type;
+ markMatchingVisibleMethods(clazz, memberKeepRules, rule, precondition);
+ markMatchingFields(clazz, memberKeepRules, rule, precondition);
break;
}
case KEEP_CLASSES_WITH_MEMBERS: {
@@ -191,6 +220,9 @@
markMatchingFields(clazz, memberKeepRules, rule, null);
break;
}
+ case CONDITIONAL:
+ assert rule instanceof ProguardIfRule;
+ throw new Unreachable("-if rule will be evaluated separately, not here.");
}
} else if (rule instanceof ProguardCheckDiscardRule) {
if (memberKeepRules.isEmpty()) {
@@ -220,6 +252,36 @@
}
}
+ private void runPerRule(
+ ExecutorService executorService,
+ List<Future<?>> futures,
+ ProguardConfigurationRule rule,
+ ProguardIfRule ifRule) {
+ List<DexType> specifics = rule.getClassNames().asSpecificDexTypes();
+ if (specifics != null) {
+ // This keep rule only lists specific type matches.
+ // This means there is no need to iterate over all classes.
+ for (DexType type : specifics) {
+ DexClass clazz = application.definitionFor(type);
+ // Ignore keep rule iff it does not reference a class in the app.
+ if (clazz != null) {
+ process(clazz, rule, ifRule);
+ }
+ }
+ } else {
+ futures.add(executorService.submit(() -> {
+ for (DexProgramClass clazz : application.classes()) {
+ process(clazz, rule, ifRule);
+ }
+ if (rule.applyToLibraryClasses()) {
+ for (DexLibraryClass clazz : application.libraryClasses()) {
+ process(clazz, rule, ifRule);
+ }
+ }
+ }));
+ }
+ }
+
public RootSet run(ExecutorService executorService) throws ExecutionException {
application.timing.begin("Build root set...");
try {
@@ -227,28 +289,11 @@
// Mark all the things explicitly listed in keep rules.
if (rules != null) {
for (ProguardConfigurationRule rule : rules) {
- List<DexType> specifics = rule.getClassNames().asSpecificDexTypes();
- if (specifics != null) {
- // This keep rule only lists specific type matches.
- // This means there is no need to iterate over all classes.
- for (DexType type : specifics) {
- DexClass clazz = application.definitionFor(type);
- // Ignore keep rule iff it does not reference a class in the app.
- if (clazz != null) {
- process(clazz, rule);
- }
- }
+ if (rule instanceof ProguardIfRule) {
+ ProguardIfRule ifRule = (ProguardIfRule) rule;
+ ifRules.add(ifRule);
} else {
- futures.add(executorService.submit(() -> {
- for (DexProgramClass clazz : application.classes()) {
- process(clazz, rule);
- }
- if (rule.applyToLibraryClasses()) {
- for (DexLibraryClass clazz : application.libraryClasses()) {
- process(clazz, rule);
- }
- }
- }));
+ runPerRule(executorService, futures, rule, null);
}
}
ThreadUtils.awaitFutures(futures);
@@ -267,33 +312,65 @@
noSideEffects,
assumedValues,
dependentNoShrinking,
- identifierNameStrings);
+ identifierNameStrings,
+ ifRules);
}
- private void markMatchingVisibleMethods(DexClass clazz,
- Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
+ ConsequentRootSet runForIfRules(
+ ExecutorService executorService,
+ Set<DexType> liveTypes,
+ Set<DexEncodedMethod> liveMethods,
+ Set<DexEncodedField> liveFields) throws ExecutionException {
+ application.timing.begin("Find consequent items for -if rules...");
+ try {
+ List<Future<?>> futures = new ArrayList<>();
+ if (rules != null) {
+ for (ProguardConfigurationRule rule : rules) {
+ assert rule instanceof ProguardIfRule;
+ ProguardIfRule ifRule = (ProguardIfRule) rule;
+ if (ruleSatisfiedWithLiveItems(
+ ifRule, appInfo::definitionFor, liveTypes, liveMethods, liveFields)) {
+ runPerRule(executorService, futures, ifRule.subsequentRule, ifRule);
+ }
+ }
+ ThreadUtils.awaitFutures(futures);
+ }
+ } finally {
+ application.timing.end();
+ }
+ return new ConsequentRootSet(noShrinking, noOptimization, noObfuscation);
+ }
+
+ private void markMatchingVisibleMethods(
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
DexType onlyIfClassKept) {
Set<Wrapper<DexMethod>> methodsMarked = new HashSet<>();
Arrays.stream(clazz.directMethods()).forEach(method ->
- markMethod(method, memberKeepRules, rule, methodsMarked, onlyIfClassKept));
+ markMethod(method, memberKeepRules, methodsMarked, rule, onlyIfClassKept));
while (clazz != null) {
Arrays.stream(clazz.virtualMethods()).forEach(method ->
- markMethod(method, memberKeepRules, rule, methodsMarked, onlyIfClassKept));
+ markMethod(method, memberKeepRules, methodsMarked, rule, onlyIfClassKept));
clazz = clazz.superType == null ? null : application.definitionFor(clazz.superType);
}
}
- private void markMatchingMethods(DexClass clazz,
- Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
+ private void markMatchingMethods(
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
DexType onlyIfClassKept) {
Arrays.stream(clazz.directMethods()).forEach(method ->
- markMethod(method, memberKeepRules, rule, null, onlyIfClassKept));
+ markMethod(method, memberKeepRules, null, rule, onlyIfClassKept));
Arrays.stream(clazz.virtualMethods()).forEach(method ->
- markMethod(method, memberKeepRules, rule, null, onlyIfClassKept));
+ markMethod(method, memberKeepRules, null, rule, onlyIfClassKept));
}
- private void markMatchingFields(DexClass clazz,
- Collection<ProguardMemberRule> memberKeepRules, ProguardConfigurationRule rule,
+ private void markMatchingFields(
+ DexClass clazz,
+ Collection<ProguardMemberRule> memberKeepRules,
+ ProguardConfigurationRule rule,
DexType onlyIfClassKept) {
clazz.forEachField(field -> markField(field, memberKeepRules, rule, onlyIfClassKept));
}
@@ -341,6 +418,79 @@
out.close();
}
+ private boolean satisfyInheritanceRule(
+ DexType type,
+ Function<DexType, DexClass> definitionFor,
+ ProguardConfigurationRule rule) {
+ DexClass clazz = definitionFor.apply(type);
+ if (clazz == null) {
+ return false;
+ }
+ return
+ anySuperTypeMatches(
+ clazz.superType,
+ definitionFor,
+ rule.getInheritanceClassName(),
+ rule.getInheritanceAnnotation())
+ || anyImplementedInterfaceMatches(
+ clazz,
+ definitionFor,
+ rule.getInheritanceClassName(),
+ rule.getInheritanceAnnotation());
+ }
+
+ private boolean ruleSatisfiedWithLiveItems(
+ ProguardIfRule ifRule,
+ Function<DexType, DexClass> definitionFor,
+ Set<DexType> liveTypes,
+ Set<DexEncodedMethod> liveMethods,
+ Set<DexEncodedField> liveFields) {
+ DexType matchedType = null;
+ Function<DexType, DexClass> definitionForWithLiveTypes = type -> {
+ DexClass clazz = definitionFor.apply(type);
+ if (clazz != null && liveTypes.contains(clazz.type)) {
+ return clazz;
+ }
+ return null;
+ };
+ for (DexType liveType : liveTypes) {
+ if (ifRule.hasInheritanceClassName()) {
+ if (!satisfyInheritanceRule(liveType, definitionForWithLiveTypes, ifRule)) {
+ // Try another live type since the current one doesn't satisfy the inheritance rule.
+ continue;
+ }
+ }
+ if (ifRule.getClassNames().matches(liveType)) {
+ matchedType = liveType;
+ final DexType filterType = matchedType;
+ Collection<ProguardMemberRule> memberKeepRules = ifRule.getMemberRules();
+ Set<DexEncodedMethod> filteredMethods =
+ liveMethods.stream().filter(method -> method.method.getHolder() == filterType)
+ .collect(Collectors.toSet());
+ Set<DexEncodedField> filteredFields =
+ liveFields.stream().filter(field -> field.field.getHolder() == filterType)
+ .collect(Collectors.toSet());
+ boolean notSatisfied = false;
+ for (ProguardMemberRule rule : memberKeepRules) {
+ if (!ruleSatisfiedByMethods(rule, filteredMethods)
+ && !ruleSatisfiedByFields(rule, filteredFields)) {
+ notSatisfied = true;
+ break;
+ }
+ }
+ // Try another live type if the current live type does not satisfy any member rule.
+ if (notSatisfied) {
+ continue;
+ }
+ // TODO(b/73800755): we may need to return the matched live type for back reference.
+ // Maybe, collect all matched live types.
+ return true;
+ }
+ }
+ // No live types satisfy the given -if rule.
+ return false;
+ }
+
private boolean allRulesSatisfied(Collection<ProguardMemberRule> memberKeepRules,
DexClass clazz) {
for (ProguardMemberRule rule : memberKeepRules) {
@@ -362,10 +512,36 @@
|| ruleSatisfiedByFields(rule, clazz.instanceFields());
}
+ private boolean ruleSatisfiedByMethods(
+ ProguardMemberRule rule,
+ Iterable<DexEncodedMethod> methods) {
+ if (rule.getRuleType().includesMethods()) {
+ for (DexEncodedMethod method : methods) {
+ if (rule.matches(method, dexStringCache)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private boolean ruleSatisfiedByMethods(ProguardMemberRule rule, DexEncodedMethod[] methods) {
if (rule.getRuleType().includesMethods()) {
for (DexEncodedMethod method : methods) {
- if (rule.matches(method, this)) {
+ if (rule.matches(method, dexStringCache)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean ruleSatisfiedByFields(
+ ProguardMemberRule rule,
+ Iterable<DexEncodedField> fields) {
+ if (rule.getRuleType().includesFields()) {
+ for (DexEncodedField field : fields) {
+ if (rule.matches(field, dexStringCache)) {
return true;
}
}
@@ -376,7 +552,7 @@
private boolean ruleSatisfiedByFields(ProguardMemberRule rule, DexEncodedField[] fields) {
if (rule.getRuleType().includesFields()) {
for (DexEncodedField field : fields) {
- if (rule.matches(field, this)) {
+ if (rule.matches(field, dexStringCache)) {
return true;
}
}
@@ -400,21 +576,18 @@
return false;
}
- private final ConcurrentHashMap<DexString, String> stringCache = new ConcurrentHashMap<>();
-
- public String lookupString(DexString name) {
- return stringCache.computeIfAbsent(name, DexString::toString);
- }
-
- private void markMethod(DexEncodedMethod method, Collection<ProguardMemberRule> rules,
- ProguardConfigurationRule context, Set<Wrapper<DexMethod>> methodsMarked,
- DexType onlyIfClassKept) {
+ private void markMethod(
+ DexEncodedMethod method,
+ Collection<ProguardMemberRule> rules,
+ Set<Wrapper<DexMethod>> methodsMarked,
+ ProguardConfigurationRule context,
+ DexItem precondition) {
if ((methodsMarked != null)
&& methodsMarked.contains(MethodSignatureEquivalence.get().wrap(method.method))) {
return;
}
for (ProguardMemberRule rule : rules) {
- if (rule.matches(method, this)) {
+ if (rule.matches(method, dexStringCache)) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Marking method `%s` due to `%s { %s }`.", method, context,
rule);
@@ -422,20 +595,23 @@
if (methodsMarked != null) {
methodsMarked.add(MethodSignatureEquivalence.get().wrap(method.method));
}
- addItemToSets(method, context, rule, onlyIfClassKept);
+ addItemToSets(method, context, rule, precondition);
}
}
}
- private void markField(DexEncodedField field, Collection<ProguardMemberRule> rules,
- ProguardConfigurationRule context, DexType onlyIfClassKept) {
+ private void markField(
+ DexEncodedField field,
+ Collection<ProguardMemberRule> rules,
+ ProguardConfigurationRule context,
+ DexItem precondition) {
for (ProguardMemberRule rule : rules) {
- if (rule.matches(field, this)) {
+ if (rule.matches(field, dexStringCache)) {
if (Log.ENABLED) {
Log.verbose(getClass(), "Marking field `%s` due to `%s { %s }`.", field, context,
rule);
}
- addItemToSets(field, context, rule, onlyIfClassKept);
+ addItemToSets(field, context, rule, precondition);
}
}
}
@@ -480,14 +656,17 @@
}
}
- private synchronized void addItemToSets(DexItem item, ProguardConfigurationRule context,
- ProguardMemberRule rule, DexType onlyIfClassKept) {
+ private synchronized void addItemToSets(
+ DexItem item,
+ ProguardConfigurationRule context,
+ ProguardMemberRule rule,
+ DexItem precondition) {
if (context instanceof ProguardKeepRule) {
ProguardKeepRule keepRule = (ProguardKeepRule) context;
ProguardKeepRuleModifiers modifiers = keepRule.getModifiers();
if (!modifiers.allowsShrinking) {
- if (onlyIfClassKept != null) {
- dependentNoShrinking.computeIfAbsent(onlyIfClassKept, x -> new IdentityHashMap<>())
+ if (precondition != null) {
+ dependentNoShrinking.computeIfAbsent(precondition, x -> new IdentityHashMap<>())
.put(item, keepRule);
} else {
noShrinking.put(item, keepRule);
@@ -540,6 +719,7 @@
public final Map<DexItem, ProguardMemberRule> assumedValues;
private final Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking;
public final Set<DexItem> identifierNameStrings;
+ public final Set<ProguardIfRule> ifRules;
private boolean isTypeEncodedMethodOrEncodedField(DexItem item) {
assert item instanceof DexType
@@ -573,10 +753,11 @@
Map<DexItem, ProguardMemberRule> noSideEffects,
Map<DexItem, ProguardMemberRule> assumedValues,
Map<DexItem, Map<DexItem, ProguardKeepRule>> dependentNoShrinking,
- Set<DexItem> identifierNameStrings) {
+ Set<DexItem> identifierNameStrings,
+ Set<ProguardIfRule> ifRules) {
this.noShrinking = Collections.unmodifiableMap(noShrinking);
- this.noOptimization = Collections.unmodifiableSet(noOptimization);
- this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
+ this.noOptimization = noOptimization;
+ this.noObfuscation = noObfuscation;
this.reasonAsked = Collections.unmodifiableSet(reasonAsked);
this.keepPackageName = Collections.unmodifiableSet(keepPackageName);
this.checkDiscarded = Collections.unmodifiableSet(checkDiscarded);
@@ -585,6 +766,7 @@
this.assumedValues = Collections.unmodifiableMap(assumedValues);
this.dependentNoShrinking = dependentNoShrinking;
this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
+ this.ifRules = Collections.unmodifiableSet(ifRules);
assert legalNoObfuscationItems(noObfuscation);
assert legalDependentNoShrinkingItems(dependentNoShrinking);
}
@@ -629,6 +811,7 @@
builder.append("\nassumedValues: " + assumedValues.size());
builder.append("\ndependentNoShrinking: " + dependentNoShrinking.size());
builder.append("\nidentifierNameStrings: " + identifierNameStrings.size());
+ builder.append("\nifRules: " + ifRules.size());
builder.append("\n\nNo Shrinking:");
noShrinking.keySet().stream()
@@ -639,4 +822,20 @@
return builder.toString();
}
}
+
+ // A partial RootSet that becomes live due to the enabled -if rule.
+ static class ConsequentRootSet {
+ final Map<DexItem, ProguardKeepRule> noShrinking;
+ final Set<DexItem> noOptimization;
+ final Set<DexItem> noObfuscation;
+
+ private ConsequentRootSet(
+ Map<DexItem, ProguardKeepRule> noShrinking,
+ Set<DexItem> noOptimization,
+ Set<DexItem> noObfuscation) {
+ this.noShrinking = Collections.unmodifiableMap(noShrinking);
+ this.noOptimization = Collections.unmodifiableSet(noOptimization);
+ this.noObfuscation = Collections.unmodifiableSet(noObfuscation);
+ }
+ }
}
diff --git a/src/test/examples/regress_76025099/Logger.java b/src/test/examples/regress_76025099/Logger.java
new file mode 100644
index 0000000..d6e9f95
--- /dev/null
+++ b/src/test/examples/regress_76025099/Logger.java
@@ -0,0 +1,8 @@
+// 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 regress_76025099;
+
+public interface Logger {
+ String getName();
+}
diff --git a/src/test/examples/regress_76025099/Main.java b/src/test/examples/regress_76025099/Main.java
new file mode 100644
index 0000000..659bdbd
--- /dev/null
+++ b/src/test/examples/regress_76025099/Main.java
@@ -0,0 +1,13 @@
+// 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 regress_76025099;
+
+import regress_76025099.impl.Factory;
+
+public class Main {
+ public static void main(String[] args) {
+ Logger l = Factory.getImpl(Main.class.getCanonicalName());
+ System.out.println(l.getName());
+ }
+}
diff --git a/src/test/examples/regress_76025099/helper/AbstractBase.java b/src/test/examples/regress_76025099/helper/AbstractBase.java
new file mode 100644
index 0000000..046e12f
--- /dev/null
+++ b/src/test/examples/regress_76025099/helper/AbstractBase.java
@@ -0,0 +1,15 @@
+// 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 regress_76025099.helper;
+
+import regress_76025099.Logger;
+
+abstract class AbstractBase implements Logger {
+ protected String name;
+
+ @Override
+ public String getName() {
+ return name;
+ }
+}
diff --git a/src/test/examples/regress_76025099/helper/AbstractSub.java b/src/test/examples/regress_76025099/helper/AbstractSub.java
new file mode 100644
index 0000000..9240e4a
--- /dev/null
+++ b/src/test/examples/regress_76025099/helper/AbstractSub.java
@@ -0,0 +1,7 @@
+// 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 regress_76025099.helper;
+
+public abstract class AbstractSub extends AbstractBase {
+}
diff --git a/src/test/examples/regress_76025099/impl/Factory.java b/src/test/examples/regress_76025099/impl/Factory.java
new file mode 100644
index 0000000..0cc1cc9
--- /dev/null
+++ b/src/test/examples/regress_76025099/impl/Factory.java
@@ -0,0 +1,10 @@
+// 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 regress_76025099.impl;
+
+public class Factory {
+ public static Impl getImpl(String name) {
+ return new Impl(name);
+ }
+}
diff --git a/src/test/examples/regress_76025099/impl/Impl.java b/src/test/examples/regress_76025099/impl/Impl.java
new file mode 100644
index 0000000..74eefcf
--- /dev/null
+++ b/src/test/examples/regress_76025099/impl/Impl.java
@@ -0,0 +1,12 @@
+// 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 regress_76025099.impl;
+
+import regress_76025099.helper.AbstractSub;
+
+public class Impl extends AbstractSub {
+ Impl(String name) {
+ this.name = name;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 4d7240e..923398d 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -550,24 +550,6 @@
// Tests where the output of R8 fails when run with Art.
private static final Multimap<String, TestCondition> failingRunWithArt =
new ImmutableListMultimap.Builder<String, TestCondition>()
- // This test relies on specific field access patterns, which we rewrite.
- .put("064-field-access",
- TestCondition.match(
- TestCondition.R8DEX_NOT_AFTER_D8_COMPILER,
- TestCondition.runtimesUpTo(DexVm.Version.V4_4_4)))
- .put("064-field-access",
- TestCondition.match(
- TestCondition.R8DEX_COMPILER,
- TestCondition.runtimes(
- DexVm.Version.DEFAULT, DexVm.Version.V7_0_0, DexVm.Version.V6_0_1,
- DexVm.Version.V5_1_1)))
- .put("064-field-access",
- TestCondition.match(
- TestCondition.tools(DexTool.NONE),
- TestCondition.D8_AFTER_R8CF_COMPILER,
- TestCondition.runtimes(
- DexVm.Version.DEFAULT, DexVm.Version.V7_0_0, DexVm.Version.V6_0_1,
- DexVm.Version.V5_1_1)))
// The growth limit test fails after processing by R8 because R8 will eliminate an
// "unneeded" const store. The following reflective call to the VM's GC will then see the
// large array as still live and the subsequent allocations will fail to reach the desired
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index d7d8b99..a29d673 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -398,6 +398,13 @@
+ (obfuscate ? "-printmapping\n" : "-dontobfuscate\n");
}
+ public static String keepMainProguardConfiguration(
+ String clazz, boolean allowaccessmodification, boolean obfuscate) {
+ return keepMainProguardConfiguration(clazz)
+ + (allowaccessmodification ? "-allowaccessmodification\n" : "")
+ + (obfuscate ? "-printmapping\n" : "-dontobfuscate\n");
+ }
+
/**
* Run application on the specified version of Art with the specified main class.
*/
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
index 6e3c114..35a7613 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/RemoveVisibilityBridgeMethodsTest.java
@@ -4,14 +4,26 @@
package com.android.tools.r8.bridgeremoval;
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.bridgeremoval.bridgestoremove.Main;
import com.android.tools.r8.bridgeremoval.bridgestoremove.Outer;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.lang.reflect.Method;
+import java.nio.file.Path;
+import java.util.Collections;
import java.util.List;
import org.junit.Test;
@@ -40,4 +52,81 @@
public void testWithoutObfuscation() throws Exception {
run(false);
}
+
+ @Test
+ public void regressionTest_b76383728_WithObfuscation() throws Exception {
+ runRegressionTest_b76383728(true);
+ }
+
+ @Test
+ public void regressionTest_b76383728_WithoutObfuscation() throws Exception {
+ runRegressionTest_b76383728(false);
+ }
+
+ /**
+ * Regression test for b76383728 to make sure we correctly identify and remove real visibility
+ * forward bridge methods synthesized by javac.
+ */
+ private void runRegressionTest_b76383728(boolean obfuscate) throws Exception {
+ JasminBuilder jasminBuilder = new JasminBuilder();
+
+ ClassBuilder superClass = jasminBuilder.addClass("SuperClass");
+ superClass.addDefaultConstructor();
+ superClass.addVirtualMethod("method", Collections.emptyList(), "Ljava/lang/String;",
+ ".limit stack 1",
+ "ldc \"Hello World\"",
+ "areturn");
+
+ // Generate a subclass with a method with same
+ ClassBuilder subclass = jasminBuilder.addClass("SubClass", superClass.name);
+ subclass.addBridgeMethod("getMethod", Collections.emptyList(), "Ljava/lang/String;",
+ ".limit stack 1",
+ "aload_0",
+ "invokespecial " + superClass.name + "/method()Ljava/lang/String;",
+ "areturn");
+
+ ClassBuilder mainClass = jasminBuilder.addClass("Main");
+ mainClass.addMainMethod(
+ ".limit stack 3",
+ ".limit locals 2",
+ "getstatic java/lang/System/out Ljava/io/PrintStream;",
+ "new " + subclass.name,
+ "dup",
+ "invokespecial " + subclass.name + "/<init>()V",
+ "invokevirtual " + subclass.name + "/getMethod()Ljava/lang/String;",
+ "invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ "return"
+ );
+
+ final String mainClassName = mainClass.name;
+
+ String proguardConfig = keepMainProguardConfiguration(mainClass.name, true, obfuscate);
+
+ // Run input program on java.
+ Path outputDirectory = temp.newFolder().toPath();
+ jasminBuilder.writeClassFiles(outputDirectory);
+ ProcessResult javaResult = ToolHelper.runJava(outputDirectory, mainClassName);
+ assertEquals(0, javaResult.exitCode);
+
+ AndroidApp optimizedApp = compileWithR8(jasminBuilder.build(), proguardConfig,
+ // Disable inlining to avoid the (short) tested method from being inlined then removed.
+ internalOptions -> internalOptions.enableInlining = false);
+
+ // Run optimized (output) program on ART
+ String artResult = runOnArt(optimizedApp, mainClassName);
+ assertEquals(javaResult.stdout, artResult);
+
+ DexInspector inspector = new DexInspector(optimizedApp);
+
+ ClassSubject classSubject = inspector.clazz(superClass.name);
+ assertThat(classSubject, isPresent());
+ MethodSubject methodSubject = classSubject
+ .method("java.lang.String", "method", Collections.emptyList());
+ assertThat(methodSubject, isPresent());
+
+ classSubject = inspector.clazz(subclass.name);
+ assertThat(classSubject, isPresent());
+ methodSubject = classSubject.method("java.lang.String", "getMethod", Collections.emptyList());
+ assertThat(methodSubject, isPresent());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/cf/BaseDefaultMethodTest.java b/src/test/java/com/android/tools/r8/cf/BaseDefaultMethodTest.java
new file mode 100644
index 0000000..a0d6122
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/BaseDefaultMethodTest.java
@@ -0,0 +1,32 @@
+// 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;
+
+public class BaseDefaultMethodTest {
+
+ public static final Class[] CLASSES = {
+ BaseDefaultMethodTest.class,
+ Base.class,
+ Derived.class,
+ Impl.class,
+ };
+
+ interface Base {
+ default void bar() {}
+ }
+
+ interface Derived extends Base {}
+
+ static class Impl implements Derived {}
+
+ static Derived foo() {
+ return new Impl();
+ }
+
+ public static void main(String[] args) {
+ Derived d = foo();
+ d.bar();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/BaseDefaultMethodTestRunner.java b/src/test/java/com/android/tools/r8/cf/BaseDefaultMethodTestRunner.java
new file mode 100644
index 0000000..414752f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/BaseDefaultMethodTestRunner.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.cf;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class BaseDefaultMethodTestRunner {
+ static final Class CLASS = BaseDefaultMethodTest.class;
+ static final Class[] CLASSES = BaseDefaultMethodTest.CLASSES;
+
+ @Rule
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Test
+ public void test() throws Exception {
+ ProcessResult runInput =
+ ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
+ assertEquals(0, runInput.exitCode);
+ List<String> config =
+ Arrays.asList(
+ "-keep public class " + CLASS.getCanonicalName() + " {",
+ " public static void main(...);",
+ "}");
+ Path out = temp.getRoot().toPath().resolve("out.jar");
+ Builder builder =
+ R8Command.builder()
+ .setMode(CompilationMode.DEBUG)
+ .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+ .setProgramConsumer(new ArchiveConsumer(out))
+ .addProguardConfiguration(config, Origin.unknown());
+ for (Class<?> c : CLASSES) {
+ builder.addClassProgramData(ToolHelper.getClassAsBytes(c), Origin.unknown());
+ }
+ // TODO(b/75997473): Enable inlining when supported
+ ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
+ ProcessResult runOutput = ToolHelper.runJava(out, CLASS.getCanonicalName());
+ assertEquals(runInput.toString(), runOutput.toString());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/UninitializedInFrameDump.java b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameDump.java
new file mode 100644
index 0000000..bac896e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameDump.java
@@ -0,0 +1,179 @@
+// 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 UninitializedInFrameDump implements Opcodes {
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
+ FieldVisitor fv;
+ MethodVisitor mv;
+ AnnotationVisitor av0;
+
+ cw.visit(
+ V1_8,
+ ACC_PUBLIC + ACC_SUPER,
+ "com/android/tools/r8/cf/UninitializedInFrameTest",
+ null,
+ "java/lang/Object",
+ null);
+
+ // The constructor UninitializedInFrameTest(int i) has been modified
+ // to add a jump back to the entry block.
+ {
+ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(I)V", null, null);
+ mv.visitCode();
+ Label l = new Label(); // Added
+ mv.visitLabel(l); // Added
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitVarInsn(ILOAD, 1);
+ mv.visitInsn(ICONST_1);
+ mv.visitInsn(ISUB);
+ mv.visitInsn(DUP); // Added
+ mv.visitIntInsn(BIPUSH, 42);
+ Label l0 = new Label();
+ mv.visitJumpInsn(IF_ICMPLT, l0);
+ // mv.visitInsn(ICONST_1);
+ mv.visitVarInsn(ISTORE, 1); // Added
+ mv.visitInsn(POP); // Added
+ mv.visitJumpInsn(GOTO, l); // Added
+ Label l1 = new Label();
+ mv.visitJumpInsn(GOTO, l1);
+ mv.visitLabel(l0);
+ mv.visitInsn(POP); // Added
+ mv.visitInsn(ICONST_0);
+ mv.visitLabel(l1);
+ mv.visitMethodInsn(
+ INVOKESPECIAL,
+ "com/android/tools/r8/cf/UninitializedInFrameTest",
+ "<init>",
+ "(Z)V",
+ false);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(-1, -1);
+ // mv.visitMaxs(3, 2);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PRIVATE, "<init>", "(Z)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.visitMaxs(1, 2);
+ mv.visitEnd();
+ }
+ {
+ mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ mv.visitCode();
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ARRAYLENGTH);
+ Label l0 = new Label();
+ mv.visitJumpInsn(IFEQ, l0);
+ mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+ mv.visitInsn(DUP);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ARRAYLENGTH);
+ mv.visitIntInsn(BIPUSH, 42);
+ Label l1 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l1);
+ // mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+ // mv.visitInsn(DUP);
+ mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "()V", false);
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/Throwable;)V", false);
+ mv.visitVarInsn(ASTORE, 1);
+ // At this point, stack is empty.
+ Label l2 = new Label();
+ mv.visitJumpInsn(GOTO, l2);
+ mv.visitLabel(l1);
+ // At this point, stack contains two copies of uninitialized RuntimeException.
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ // mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+ // mv.visitInsn(DUP);
+ mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
+ mv.visitInsn(DUP);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
+ mv.visitLdcInsn("You supplied ");
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/StringBuilder",
+ "append",
+ "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+ false);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ARRAYLENGTH);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/StringBuilder",
+ "append",
+ "(I)Ljava/lang/StringBuilder;",
+ false);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ARRAYLENGTH);
+ mv.visitInsn(ICONST_1);
+ Label l3 = new Label();
+ mv.visitJumpInsn(IF_ICMPNE, l3);
+ mv.visitLdcInsn(" arg");
+ Label l4 = new Label();
+ mv.visitJumpInsn(GOTO, l4);
+ mv.visitLabel(l3);
+ // At this point, stack contains two copies of uninitialized RuntimeException.
+ // Note that asmifier seems to produce incorrect labels for the uninitialized type.
+ // mv.visitFrame(Opcodes.F_FULL, 1, new Object[] {"[Ljava/lang/String;"}, 3, new Object[] {l1,
+ // l1, "java/lang/StringBuilder"});
+ mv.visitLdcInsn(" args");
+ mv.visitLabel(l4);
+ // At this point, stack contains two copies of uninitialized RuntimeException.
+ // mv.visitFrame(Opcodes.F_FULL, 1, new Object[] {"[Ljava/lang/String;"}, 4, new Object[] {l1,
+ // l1, "java/lang/StringBuilder", "java/lang/String"});
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "java/lang/StringBuilder",
+ "append",
+ "(Ljava/lang/String;)Ljava/lang/StringBuilder;",
+ false);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
+ mv.visitMethodInsn(
+ INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false);
+ mv.visitVarInsn(ASTORE, 1);
+ mv.visitLabel(l2);
+ // At this point, stack is empty, and local 1 contains an initialized RuntimeException.
+ // mv.visitFrame(Opcodes.F_APPEND,1, new Object[] {"java/lang/RuntimeException"}, 0, null);
+ mv.visitVarInsn(ALOAD, 0);
+ mv.visitInsn(ARRAYLENGTH);
+ mv.visitInsn(ICONST_2);
+ mv.visitInsn(IREM);
+ Label l5 = new Label();
+ mv.visitJumpInsn(IFNE, l5);
+ mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+ mv.visitLabel(l5);
+ // mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ mv.visitVarInsn(ALOAD, 1);
+ mv.visitInsn(ATHROW);
+ mv.visitLabel(l0);
+ // mv.visitFrame(Opcodes.F_CHOP,1, null, 0, null);
+ mv.visitInsn(RETURN);
+ mv.visitMaxs(-1, -1);
+ // mv.visitMaxs(5, 2);
+ mv.visitEnd();
+ }
+ cw.visitEnd();
+
+ return cw.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTest.java b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTest.java
new file mode 100644
index 0000000..4479796
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTest.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.cf;
+
+public class UninitializedInFrameTest {
+ int v;
+
+ public UninitializedInFrameTest(int i) {
+ // In UninitializedInFrameDump, this method is changed to:
+ // while (i-1 >= 42) {i = i-1;} this(i-1 >= 42);
+ // ...which is invalid in Java source since this() must be the first statement.
+ // Put "i-1 >= 42" in the code here to aid the manual editing in UninitializedInFrameDump.
+ this(i - 1 >= 42);
+ }
+
+ public UninitializedInFrameTest(boolean b) {
+ v = b ? 42 : 0;
+ System.out.println(this);
+ // Add an InvokeDirect that has 'this' as argument to ensure we don't consider it to be
+ // an initialization for 'this'.
+ if (!b) {
+ throw new AssertionError(this);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Hello world! " + v;
+ }
+
+ public static void main(String[] args) {
+ try {
+ new UninitializedInFrameTest(true);
+ new UninitializedInFrameTest(45);
+ } catch (AssertionError e) {
+ }
+ if (args.length != 0) {
+ RuntimeException e;
+ if (args.length == 42) {
+ e = new RuntimeException(new IllegalArgumentException());
+ } else {
+ e =
+ new RuntimeException(
+ "You supplied " + args.length + (args.length == 1 ? " arg" : " args"));
+ }
+ if (args.length % 2 == 0) {
+ System.out.println(e);
+ }
+ throw e;
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java
new file mode 100644
index 0000000..f433a9d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/UninitializedInFrameTestRunner.java
@@ -0,0 +1,64 @@
+// 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 com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+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.io.IOException;
+import java.nio.file.Path;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class UninitializedInFrameTestRunner {
+ static final Class CLASS = UninitializedInFrameTest.class;
+
+ @Rule
+ public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+ @Test
+ public void test() throws Exception {
+ test(ToolHelper.getClassAsBytes(CLASS));
+ }
+
+ @Test
+ public void testDump() throws Exception {
+ test(UninitializedInFrameDump.dump());
+ }
+
+ private void test(byte[] clazz) throws CompilationFailedException, IOException {
+ Path input = temp.getRoot().toPath().resolve("input.jar");
+ Path output = temp.getRoot().toPath().resolve("output.jar");
+
+ ArchiveConsumer inputConsumer = new ArchiveConsumer(input);
+ inputConsumer.accept(clazz, DescriptorUtils.javaTypeToDescriptor(CLASS.getName()), null);
+ inputConsumer.finished(null);
+ ProcessResult runInput = ToolHelper.runJava(input, CLASS.getCanonicalName());
+ if (runInput.exitCode != 0) {
+ System.out.println(runInput);
+ }
+ Assert.assertEquals(0, runInput.exitCode);
+
+ R8.run(
+ R8Command.builder()
+ .setMode(CompilationMode.DEBUG)
+ .addClassProgramData(clazz, Origin.unknown())
+ .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+ .setProgramConsumer(new ArchiveConsumer(output))
+ .build());
+ ProcessResult runOutput = ToolHelper.runJava(output, CLASS.getCanonicalName());
+ if (runOutput.exitCode != 0) {
+ System.out.println(runOutput);
+ }
+ Assert.assertEquals(runInput.toString(), runOutput.toString());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/MinificationTest.java b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
index 414fbc3..2f995db 100644
--- a/src/test/java/com/android/tools/r8/debug/MinificationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
@@ -185,11 +185,6 @@
String innerClassName,
MethodSignature innerMethod)
throws Throwable {
- if (ToolHelper.isWindows()) {
- // TODO(b/76135355): Update dx.bat on Windows to something that can build
- // jdwp-tests-preN-dex.jar.
- return;
- }
Path proguardMap = config.getProguardMap();
String mappingFile = proguardMap == null ? null : proguardMap.toString();
DexInspector inspector = new DexInspector(config.getPaths(), mappingFile);
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
index e07f319..d2cf708 100644
--- a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/InterfaceMethodDesugaringTests.java
@@ -4,8 +4,7 @@
package com.android.tools.r8.desugaring.interfacemethods;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
import com.android.tools.r8.AsmTestBase;
import com.android.tools.r8.ToolHelper;
@@ -33,7 +32,7 @@
ToolHelper.getMinApiLevelForDexVm(),
ToolHelper.getClassAsBytes(
com.android.tools.r8.desugaring.interfacemethods.test0.TestMain.class),
- introduceInvokeSpecial(ToolHelper.getClassAsBytes(
+ patchInterfaceWithDefaults(ToolHelper.getClassAsBytes(
com.android.tools.r8.desugaring.interfacemethods.test0.InterfaceWithDefaults.class)));
}
@@ -47,16 +46,33 @@
ToolHelper.getMinApiLevelForDexVm(),
ToolHelper.getClassAsBytes(
com.android.tools.r8.desugaring.interfacemethods.test1.TestMain.class),
- introduceInvokeSpecial(ToolHelper.getClassAsBytes(
+ patchInterfaceWithDefaults(ToolHelper.getClassAsBytes(
com.android.tools.r8.desugaring.interfacemethods.test1.InterfaceWithDefaults.class)));
}
- private class MutableBoolean {
- boolean value;
+ @Test
+ public void testInvokeSpecialToInheritedDefaultMethod() throws Exception {
+ ensureSameOutput(
+ com.android.tools.r8.desugaring.interfacemethods.test2.TestMain.class.getCanonicalName(),
+ ToolHelper.getMinApiLevelForDexVm(),
+ ToolHelper.getClassAsBytes(
+ com.android.tools.r8.desugaring.interfacemethods.test2.TestMain.class),
+ ToolHelper.getClassAsBytes(
+ com.android.tools.r8.desugaring.interfacemethods.test2.Test.class),
+ ToolHelper.getClassAsBytes(
+ com.android.tools.r8.desugaring.interfacemethods.test2.LeftTest.class),
+ ToolHelper.getClassAsBytes(
+ com.android.tools.r8.desugaring.interfacemethods.test2.RightTest.class),
+ ToolHelper.getClassAsBytes(
+ com.android.tools.r8.desugaring.interfacemethods.test2.Test2.class));
}
- private byte[] introduceInvokeSpecial(byte[] classBytes) throws IOException {
- MutableBoolean patched = new MutableBoolean();
+ private static class MutableInteger {
+ int value;
+ }
+
+ private byte[] patchInterfaceWithDefaults(byte[] classBytes) throws IOException {
+ MutableInteger patched = new MutableInteger();
try (InputStream input = new ByteArrayInputStream(classBytes)) {
ClassReader cr = new ClassReader(input);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
@@ -73,9 +89,9 @@
if (opcode == Opcodes.INVOKEINTERFACE &&
owner.endsWith("InterfaceWithDefaults") &&
name.equals("foo")) {
- assertFalse(patched.value);
+ assertEquals(0, patched.value);
super.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, itf);
- patched.value = true;
+ patched.value++;
} else {
super.visitMethodInsn(opcode, owner, name, desc, itf);
@@ -84,7 +100,7 @@
};
}
}, 0);
- assertTrue(patched.value);
+ assertEquals(1, patched.value);
return cw.toByteArray();
}
}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/LeftTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/LeftTest.java
new file mode 100644
index 0000000..4a5b2de
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/LeftTest.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugaring.interfacemethods.test2;
+
+public interface LeftTest extends Test {
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/RightTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/RightTest.java
new file mode 100644
index 0000000..6c6ea7e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/RightTest.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugaring.interfacemethods.test2;
+
+public interface RightTest extends Test {
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/Test.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/Test.java
new file mode 100644
index 0000000..1bf3295
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/Test.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugaring.interfacemethods.test2;
+
+public interface Test {
+ default String foo(String a) {
+ return "Test::foo(" + a + ")";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/Test2.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/Test2.java
new file mode 100644
index 0000000..b076cde
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/Test2.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugaring.interfacemethods.test2;
+
+public interface Test2 extends LeftTest, RightTest {
+ default String bar(String a) {
+ return "Test2::bar(" + LeftTest.super.foo(a) + " + " + RightTest.super.foo(a) + ")";
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/TestMain.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/TestMain.java
new file mode 100644
index 0000000..ca86963
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/test2/TestMain.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugaring.interfacemethods.test2;
+
+public class TestMain implements Test2 {
+ public static void main(String... args) {
+ TestMain m = new TestMain();
+ System.out.println(m.bar("first"));
+ System.out.println(m.foo("second"));
+ System.out.println(m.fooDelegate("third"));
+ }
+
+ private String fooDelegate(String a) {
+ return "TestMain::fooDelegate(" + Test2.super.foo(a) + ")";
+ }
+}
+
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
index 19a5ee2..cf87d46 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -89,9 +89,9 @@
if (useOptions) {
Options options = new Options();
- options.inputArchives.add(inputZip.toString());
- options.featureSplitMapping = splitSpec.toString();
- options.splitBaseName = output.toString();
+ options.addInputArchive(inputZip.toString());
+ options.setFeatureSplitMapping(splitSpec.toString());
+ options.setSplitBaseName(output.toString());
DexSplitter.run(options);
} else {
DexSplitter.main(
@@ -235,12 +235,12 @@
featureStream.close();
if (useOptions) {
Options options = new Options();
- options.inputArchives.add(inputZip.toString());
- options.splitBaseName = output.toString();
+ options.addInputArchive(inputZip.toString());
+ options.setSplitBaseName(output.toString());
if (explicitBase) {
- options.featureJars.add(baseJar.toString());
+ options.addFeatureJar(baseJar.toString());
}
- options.featureJars.add(featureJar.toString());
+ options.addFeatureJar(featureJar.toString());
DexSplitter.run(options);
} else {
List<String> args = Lists.newArrayList(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index 646cfaf..b6109d4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -185,6 +185,6 @@
buildAndTest(NonNullAfterNullCheck.class, bar, 1, this::checkInvokeGetsNonNullReceiver);
MethodSignature baz =
new MethodSignature("baz", "int", new String[]{"java.lang.String"});
- buildAndTest(NonNullAfterNullCheck.class, baz, 1, this::checkInvokeGetsNullReceiver);
+ buildAndTest(NonNullAfterNullCheck.class, baz, 2, this::checkInvokeGetsNullReceiver);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
index 92e43d0..525e21b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
@@ -30,8 +30,8 @@
private void buildAndTest(Class<?> testClass, List<MethodSignature> signatures) throws Exception {
AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(testClass));
- AndroidApp r8Result = compileWithR8(
- app, keepMainProguardConfiguration(testClass), o -> o.enableInlining = false);
+ AndroidApp r8Result = compileWithR8(app,
+ "-keep class " + testClass.getCanonicalName() + " { *; }");
DexInspector dexInspector = new DexInspector(r8Result);
for (MethodSignature signature : signatures) {
DexEncodedMethod method =
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/B77240639.java b/src/test/java/com/android/tools/r8/ir/regalloc/B77240639.java
new file mode 100644
index 0000000..440f4f3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/B77240639.java
@@ -0,0 +1,820 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.regalloc;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import java.util.List;
+import java.util.Map;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class B77240639 extends TestBase {
+ @Ignore("b/77240639")
+ @Test
+ public void test() throws Exception {
+ AndroidApp app = compileWithD8(readClasses(TestClass.class));
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz(TestClass.class);
+ assertThat(clazz, isPresent());
+ }
+}
+
+class TestClass {
+ private boolean b;
+ private boolean b_flag = false;
+ private Boolean ob;
+ private boolean ob_flag = false;
+ private List<Boolean> vb;
+ private boolean vb_flag = false;
+ private List<Boolean> vob;
+ private boolean vob_flag = false;
+ private Map<String, Boolean> db;
+ private boolean db_flag = false;
+ private Map<String, Boolean> dob;
+ private boolean dob_flag = false;
+ private int i;
+ private boolean i_flag = false;
+ private Integer oi;
+ private boolean oi_flag = false;
+ private List<Boolean> vi;
+ private boolean vi_flag = false;
+ private List<Boolean> voi;
+ private boolean voi_flag = false;
+ private Map<String, Boolean> di;
+ private boolean di_flag = false;
+ private Map<String, Boolean> doi;
+ private boolean doi_flag = false;
+ private int ui;
+ private boolean ui_flag = false;
+ private Integer oui;
+ private boolean oui_flag = false;
+ private List<Boolean> vui;
+ private boolean vui_flag = false;
+ private List<Boolean> voui;
+ private boolean voui_flag = false;
+ private Map<String, Boolean> dui;
+ private boolean dui_flag = false;
+ private Map<String, Boolean> doui;
+ private boolean doui_flag = false;
+ private long i64;
+ private boolean i64_flag = false;
+ private Long oi64;
+ private boolean oi64_flag = false;
+ private List<Boolean> vi64;
+ private boolean vi64_flag = false;
+ private List<Boolean> voi64;
+ private boolean voi64_flag = false;
+ private Map<String, Boolean> di64;
+ private boolean di64_flag = false;
+ private Map<String, Boolean> doi64;
+ private boolean doi64_flag = false;
+ private float fl;
+ private boolean fl_flag = false;
+ private Float ofl;
+ private boolean ofl_flag = false;
+ private List<Boolean> vfl;
+ private boolean vfl_flag = false;
+ private List<Boolean> vofl;
+ private boolean vofl_flag = false;
+ private Map<String, Boolean> dfl;
+ private boolean dfl_flag = false;
+ private Map<String, Boolean> dofl;
+ private boolean dofl_flag = false;
+ private double d;
+ private boolean d_flag = false;
+ private Double od;
+ private boolean od_flag = false;
+ private List<Boolean> vd;
+ private boolean vd_flag = false;
+ private List<Boolean> vod;
+ private boolean vod_flag = false;
+ private Map<String, Boolean> dd;
+ private boolean dd_flag = false;
+ private Map<String, Boolean> dod;
+ private boolean dod_flag = false;
+ private String s;
+ private boolean s_flag = false;
+ private String os;
+ private boolean os_flag = false;
+ private List<Boolean> vs;
+ private boolean vs_flag = false;
+ private List<Boolean> vos;
+ private boolean vos_flag = false;
+ private Map<String, Boolean> ds;
+ private boolean ds_flag = false;
+ private Map<String, Boolean> dos;
+ private boolean dos_flag = false;
+ private long ti;
+ private boolean ti_flag = false;
+ private Long oti;
+ private boolean oti_flag = false;
+ private List<Boolean> vti;
+ private boolean vti_flag = false;
+ private List<Boolean> voti;
+ private boolean voti_flag = false;
+ private Map<String, Boolean> dti;
+ private boolean dti_flag = false;
+ private Map<String, Boolean> doti;
+ private boolean doti_flag = false;
+ private long at;
+ private boolean at_flag = false;
+ private Long oat;
+ private boolean oat_flag = false;
+ private List<Boolean> vat;
+ private boolean vat_flag = false;
+ private List<Boolean> voat;
+ private boolean voat_flag = false;
+ private Map<String, Boolean> dat;
+ private boolean dat_flag = false;
+ private Map<String, Boolean> doat;
+ private boolean doat_flag = false;
+ private long rt;
+ private boolean rt_flag = false;
+ private Long ort;
+ private boolean ort_flag = false;
+ private List<Boolean> vrt;
+ private boolean vrt_flag = false;
+ private List<Boolean> vort;
+ private boolean vort_flag = false;
+ private Map<String, Boolean> drt;
+ private boolean drt_flag = false;
+ private Map<String, Boolean> dort;
+ private boolean dort_flag = false;
+ private byte[] by;
+ private boolean by_flag = false;
+ private byte[] oby;
+ private boolean oby_flag = false;
+ private int c;
+ private boolean c_flag = false;
+ private Integer oc;
+ private boolean oc_flag = false;
+ private List<Integer> vc;
+ private boolean vc_flag = false;
+ private List<Integer> voc;
+ private boolean voc_flag = false;
+ private Map<String, Integer> dc;
+ private boolean dc_flag = false;
+ private Map<String, Integer> doc;
+ private boolean doc_flag = false;
+ private Object p;
+ private boolean p_flag = false;
+ private Object op;
+ private boolean op_flag = false;
+ private List<Object> vp;
+ private boolean vp_flag = false;
+ private List<Object> vop;
+ private boolean vop_flag = false;
+ private Map<String, Object> dp;
+ private boolean dp_flag = false;
+ private Map<String, Object> dop;
+ private boolean dop_flag = false;
+ private Object e;
+ private boolean e_flag = false;
+ private Object oe;
+ private boolean oe_flag = false;
+ private List<Object> ve;
+ private boolean ve_flag = false;
+ private List<Object> voe;
+ private boolean voe_flag = false;
+ private Map<String, Object> de;
+ private boolean de_flag = false;
+ private Map<String, Object> doe;
+ private boolean doe_flag = false;
+ private int be;
+ private boolean be_flag = false;
+ private Integer obe;
+ private boolean obe_flag = false;
+ private List<Object> vbe;
+ private boolean vbe_flag = false;
+ private List<Object> vobe;
+ private boolean vobe_flag = false;
+ private Map<String, Object> dbe;
+ private boolean dbe_flag = false;
+ private Map<String, Object> dobe;
+ private boolean dobe_flag = false;
+ private Object ts;
+ private boolean ts_flag = false;
+ private Object ots;
+ private boolean ots_flag = false;
+ private List<Object> vts;
+ private boolean vts_flag = false;
+ private List<Object> vots;
+ private boolean vots_flag = false;
+ private Map<String, Object> dts;
+ private boolean dts_flag = false;
+ private Map<String, Object> dots;
+ private boolean dots_flag = false;
+ private Object lts;
+ private boolean lts_flag = false;
+ private Object olts;
+ private boolean olts_flag = false;
+ private List<Object> vlts;
+ private boolean vlts_flag = false;
+ private List<Object> volts;
+ private boolean volts_flag = false;
+ private Map<String, Object> dlts;
+ private boolean dlts_flag = false;
+ private Map<String, Object> dolts;
+ private boolean dolts_flag = false;
+ private Object opts;
+ private boolean opts_flag = false;
+ private Object oopts;
+ private boolean oopts_flag = false;
+ private List<Object> vopts;
+ private boolean vopts_flag = false;
+ private List<Object> voopts;
+ private boolean voopts_flag = false;
+ private Map<String, Object> dopts;
+ private boolean dopts_flag = false;
+ private Map<String, Object> doopts;
+ private boolean doopts_flag = false;
+ private Object nativeObject;
+
+ public TestClass() {}
+
+ public TestClass(
+ boolean b,
+ Boolean ob,
+ List<Boolean> vb,
+ List<Boolean> vob,
+ Map<String, Boolean> db,
+ Map<String, Boolean> dob,
+ int i,
+ Integer oi,
+ List<Boolean> vi,
+ List<Boolean> voi,
+ Map<String, Boolean> di,
+ Map<String, Boolean> doi,
+ int ui,
+ Integer oui,
+ List<Boolean> vui,
+ List<Boolean> voui,
+ Map<String, Boolean> dui,
+ Map<String, Boolean> doui,
+ long i64,
+ Long oi64,
+ List<Boolean> vi64,
+ List<Boolean> voi64,
+ Map<String, Boolean> di64,
+ Map<String, Boolean> doi64,
+ float fl,
+ Float ofl,
+ List<Boolean> vfl,
+ List<Boolean> vofl,
+ Map<String, Boolean> dfl,
+ Map<String, Boolean> dofl,
+ double d,
+ Double od,
+ List<Boolean> vd,
+ List<Boolean> vod,
+ Map<String, Boolean> dd,
+ Map<String, Boolean> dod,
+ String s,
+ String os,
+ List<Boolean> vs,
+ List<Boolean> vos,
+ Map<String, Boolean> ds,
+ Map<String, Boolean> dos,
+ long ti,
+ Long oti,
+ List<Boolean> vti,
+ List<Boolean> voti,
+ Map<String, Boolean> dti,
+ Map<String, Boolean> doti,
+ long at,
+ Long oat,
+ List<Boolean> vat,
+ List<Boolean> voat,
+ Map<String, Boolean> dat,
+ Map<String, Boolean> doat,
+ long rt,
+ Long ort,
+ List<Boolean> vrt,
+ List<Boolean> vort,
+ Map<String, Boolean> drt,
+ Map<String, Boolean> dort,
+ byte[] by,
+ byte[] oby,
+ int c,
+ Integer oc,
+ List<Integer> vc,
+ List<Integer> voc,
+ Map<String, Integer> dc,
+ Map<String, Integer> doc,
+ Object p,
+ Object op,
+ List<Object> vp,
+ List<Object> vop,
+ Map<String, Object> dp,
+ Map<String, Object> dop,
+ Object e,
+ Object oe,
+ List<Object> ve,
+ List<Object> voe,
+ Map<String, Object> de,
+ Map<String, Object> doe,
+ int be,
+ Integer obe,
+ List<Object> vbe,
+ List<Object> vobe,
+ Map<String, Object> dbe,
+ Map<String, Object> dobe,
+ Object ts,
+ Object ots,
+ List<Object> vts,
+ List<Object> vots,
+ Map<String, Object> dts,
+ Map<String, Object> dots,
+ Object lts,
+ Object olts,
+ List<Object> vlts,
+ List<Object> volts,
+ Map<String, Object> dlts,
+ Map<String, Object> dolts,
+ Object opts,
+ Object oopts,
+ List<Object> vopts,
+ List<Object> voopts,
+ Map<String, Object> dopts,
+ Map<String, Object> doopts) {
+ if (vb == null) {
+ throw new IllegalArgumentException("vb");
+ } else if (vob == null) {
+ throw new IllegalArgumentException("vob");
+ } else if (db == null) {
+ throw new IllegalArgumentException("db");
+ } else if (dob == null) {
+ throw new IllegalArgumentException("dob");
+ } else if (vi == null) {
+ throw new IllegalArgumentException("vi");
+ } else if (voi == null) {
+ throw new IllegalArgumentException("voi");
+ } else if (di == null) {
+ throw new IllegalArgumentException("di");
+ } else if (doi == null) {
+ throw new IllegalArgumentException("doi");
+ } else if (vui == null) {
+ throw new IllegalArgumentException("vui");
+ } else if (voui == null) {
+ throw new IllegalArgumentException("voui");
+ } else if (dui == null) {
+ throw new IllegalArgumentException("dui");
+ } else if (doui == null) {
+ throw new IllegalArgumentException("doui");
+ } else if (vi64 == null) {
+ throw new IllegalArgumentException("vi64");
+ } else if (voi64 == null) {
+ throw new IllegalArgumentException("voi64");
+ } else if (di64 == null) {
+ throw new IllegalArgumentException("di64");
+ } else if (doi64 == null) {
+ throw new IllegalArgumentException("doi64");
+ } else if (vfl == null) {
+ throw new IllegalArgumentException("vfl");
+ } else if (vofl == null) {
+ throw new IllegalArgumentException("vofl");
+ } else if (dfl == null) {
+ throw new IllegalArgumentException("dfl");
+ } else if (dofl == null) {
+ throw new IllegalArgumentException("dofl");
+ } else if (vd == null) {
+ throw new IllegalArgumentException("vd");
+ } else if (vod == null) {
+ throw new IllegalArgumentException("vod");
+ } else if (dd == null) {
+ throw new IllegalArgumentException("dd");
+ } else if (dod == null) {
+ throw new IllegalArgumentException("dod");
+ } else if (s == null) {
+ throw new IllegalArgumentException("s");
+ } else if (vs == null) {
+ throw new IllegalArgumentException("vs");
+ } else if (vos == null) {
+ throw new IllegalArgumentException("vos");
+ } else if (ds == null) {
+ throw new IllegalArgumentException("ds");
+ } else if (dos == null) {
+ throw new IllegalArgumentException("dos");
+ } else if (vti == null) {
+ throw new IllegalArgumentException("vti");
+ } else if (voti == null) {
+ throw new IllegalArgumentException("voti");
+ } else if (dti == null) {
+ throw new IllegalArgumentException("dti");
+ } else if (doti == null) {
+ throw new IllegalArgumentException("doti");
+ } else if (vat == null) {
+ throw new IllegalArgumentException("vat");
+ } else if (voat == null) {
+ throw new IllegalArgumentException("voat");
+ } else if (dat == null) {
+ throw new IllegalArgumentException("dat");
+ } else if (doat == null) {
+ throw new IllegalArgumentException("doat");
+ } else if (vrt == null) {
+ throw new IllegalArgumentException("vrt");
+ } else if (vort == null) {
+ throw new IllegalArgumentException("vort");
+ } else if (drt == null) {
+ throw new IllegalArgumentException("drt");
+ } else if (dort == null) {
+ throw new IllegalArgumentException("dort");
+ } else if (by == null) {
+ throw new IllegalArgumentException("by");
+ } else if (vc == null) {
+ throw new IllegalArgumentException("vc");
+ } else if (voc == null) {
+ throw new IllegalArgumentException("voc");
+ } else if (dc == null) {
+ throw new IllegalArgumentException("dc");
+ } else if (doc == null) {
+ throw new IllegalArgumentException("doc");
+ } else if (p == null) {
+ throw new IllegalArgumentException("p");
+ } else if (vp == null) {
+ throw new IllegalArgumentException("vp");
+ } else if (vop == null) {
+ throw new IllegalArgumentException("vop");
+ } else if (dp == null) {
+ throw new IllegalArgumentException("dp");
+ } else if (dop == null) {
+ throw new IllegalArgumentException("dop");
+ } else if (e == null) {
+ throw new IllegalArgumentException("e");
+ } else if (ve == null) {
+ throw new IllegalArgumentException("ve");
+ } else if (voe == null) {
+ throw new IllegalArgumentException("voe");
+ } else if (de == null) {
+ throw new IllegalArgumentException("de");
+ } else if (doe == null) {
+ throw new IllegalArgumentException("doe");
+ } else if (vbe == null) {
+ throw new IllegalArgumentException("vbe");
+ } else if (vobe == null) {
+ throw new IllegalArgumentException("vobe");
+ } else if (dbe == null) {
+ throw new IllegalArgumentException("dbe");
+ } else if (dobe == null) {
+ throw new IllegalArgumentException("dobe");
+ } else if (ts == null) {
+ throw new IllegalArgumentException("ts");
+ } else if (vts == null) {
+ throw new IllegalArgumentException("vts");
+ } else if (vots == null) {
+ throw new IllegalArgumentException("vots");
+ } else if (dts == null) {
+ throw new IllegalArgumentException("dts");
+ } else if (dots == null) {
+ throw new IllegalArgumentException("dots");
+ } else if (lts == null) {
+ throw new IllegalArgumentException("lts");
+ } else if (vlts == null) {
+ throw new IllegalArgumentException("vlts");
+ } else if (volts == null) {
+ throw new IllegalArgumentException("volts");
+ } else if (dlts == null) {
+ throw new IllegalArgumentException("dlts");
+ } else if (dolts == null) {
+ throw new IllegalArgumentException("dolts");
+ } else if (opts == null) {
+ throw new IllegalArgumentException("opts");
+ } else if (vopts == null) {
+ throw new IllegalArgumentException("vopts");
+ } else if (voopts == null) {
+ throw new IllegalArgumentException("voopts");
+ } else if (dopts == null) {
+ throw new IllegalArgumentException("dopts");
+ } else if (doopts == null) {
+ throw new IllegalArgumentException("doopts");
+ } else {
+ this.nativeObject =
+ this.init(
+ b, ob, vb, vob, db, dob, i, oi, vi, voi, di, doi, ui, oui, vui, voui, dui, doui, i64,
+ oi64, vi64, voi64, di64, doi64, fl, ofl, vfl, vofl, dfl, dofl, d, od, vd, vod, dd,
+ dod, s, os, vs, vos, ds, dos, ti, oti, vti, voti, dti, doti, at, oat, vat, voat, dat,
+ doat, rt, ort, vrt, vort, drt, dort, by, oby, c, oc, vc, voc, dc, doc, p, op, vp, vop,
+ dp, dop, e, oe, ve, voe, de, doe, be, obe, vbe, vobe, dbe, dobe, ts, ots, vts, vots,
+ dts, dots, lts, olts, vlts, volts, dlts, dolts, opts, oopts, vopts, voopts, dopts,
+ doopts);
+ this.b = b;
+ this.b_flag = true;
+ this.ob = ob;
+ this.ob_flag = true;
+ this.vb = vb;
+ this.vb_flag = true;
+ this.vob = vob;
+ this.vob_flag = true;
+ this.db = db;
+ this.db_flag = true;
+ this.dob = dob;
+ this.dob_flag = true;
+ this.i = i;
+ this.i_flag = true;
+ this.oi = oi;
+ this.oi_flag = true;
+ this.vi = vi;
+ this.vi_flag = true;
+ this.voi = voi;
+ this.voi_flag = true;
+ this.di = di;
+ this.di_flag = true;
+ this.doi = doi;
+ this.doi_flag = true;
+ this.ui = ui;
+ this.ui_flag = true;
+ this.oui = oui;
+ this.oui_flag = true;
+ this.vui = vui;
+ this.vui_flag = true;
+ this.voui = voui;
+ this.voui_flag = true;
+ this.dui = dui;
+ this.dui_flag = true;
+ this.doui = doui;
+ this.doui_flag = true;
+ this.i64 = i64;
+ this.i64_flag = true;
+ this.oi64 = oi64;
+ this.oi64_flag = true;
+ this.vi64 = vi64;
+ this.vi64_flag = true;
+ this.voi64 = voi64;
+ this.voi64_flag = true;
+ this.di64 = di64;
+ this.di64_flag = true;
+ this.doi64 = doi64;
+ this.doi64_flag = true;
+ this.fl = fl;
+ this.fl_flag = true;
+ this.ofl = ofl;
+ this.ofl_flag = true;
+ this.vfl = vfl;
+ this.vfl_flag = true;
+ this.vofl = vofl;
+ this.vofl_flag = true;
+ this.dfl = dfl;
+ this.dfl_flag = true;
+ this.dofl = dofl;
+ this.dofl_flag = true;
+ this.d = d;
+ this.d_flag = true;
+ this.od = od;
+ this.od_flag = true;
+ this.vd = vd;
+ this.vd_flag = true;
+ this.vod = vod;
+ this.vod_flag = true;
+ this.dd = dd;
+ this.dd_flag = true;
+ this.dod = dod;
+ this.dod_flag = true;
+ this.s = s;
+ this.s_flag = true;
+ this.os = os;
+ this.os_flag = true;
+ this.vs = vs;
+ this.vs_flag = true;
+ this.vos = vos;
+ this.vos_flag = true;
+ this.ds = ds;
+ this.ds_flag = true;
+ this.dos = dos;
+ this.dos_flag = true;
+ this.ti = ti;
+ this.ti_flag = true;
+ this.oti = oti;
+ this.oti_flag = true;
+ this.vti = vti;
+ this.vti_flag = true;
+ this.voti = voti;
+ this.voti_flag = true;
+ this.dti = dti;
+ this.dti_flag = true;
+ this.doti = doti;
+ this.doti_flag = true;
+ this.at = at;
+ this.at_flag = true;
+ this.oat = oat;
+ this.oat_flag = true;
+ this.vat = vat;
+ this.vat_flag = true;
+ this.voat = voat;
+ this.voat_flag = true;
+ this.dat = dat;
+ this.dat_flag = true;
+ this.doat = doat;
+ this.doat_flag = true;
+ this.rt = rt;
+ this.rt_flag = true;
+ this.ort = ort;
+ this.ort_flag = true;
+ this.vrt = vrt;
+ this.vrt_flag = true;
+ this.vort = vort;
+ this.vort_flag = true;
+ this.drt = drt;
+ this.drt_flag = true;
+ this.dort = dort;
+ this.dort_flag = true;
+ this.by = by;
+ this.by_flag = true;
+ this.oby = oby;
+ this.oby_flag = true;
+ this.c = c;
+ this.c_flag = true;
+ this.oc = oc;
+ this.oc_flag = true;
+ this.vc = vc;
+ this.vc_flag = true;
+ this.voc = voc;
+ this.voc_flag = true;
+ this.dc = dc;
+ this.dc_flag = true;
+ this.doc = doc;
+ this.doc_flag = true;
+ this.p = p;
+ this.p_flag = true;
+ this.op = op;
+ this.op_flag = true;
+ this.vp = vp;
+ this.vp_flag = true;
+ this.vop = vop;
+ this.vop_flag = true;
+ this.dp = dp;
+ this.dp_flag = true;
+ this.dop = dop;
+ this.dop_flag = true;
+ this.e = e;
+ this.e_flag = true;
+ this.oe = oe;
+ this.oe_flag = true;
+ this.ve = ve;
+ this.ve_flag = true;
+ this.voe = voe;
+ this.voe_flag = true;
+ this.de = de;
+ this.de_flag = true;
+ this.doe = doe;
+ this.doe_flag = true;
+ this.be = be;
+ this.be_flag = true;
+ this.obe = obe;
+ this.obe_flag = true;
+ this.vbe = vbe;
+ this.vbe_flag = true;
+ this.vobe = vobe;
+ this.vobe_flag = true;
+ this.dbe = dbe;
+ this.dbe_flag = true;
+ this.dobe = dobe;
+ this.dobe_flag = true;
+ this.ts = ts;
+ this.ts_flag = true;
+ this.ots = ots;
+ this.ots_flag = true;
+ this.vts = vts;
+ this.vts_flag = true;
+ this.vots = vots;
+ this.vots_flag = true;
+ this.dts = dts;
+ this.dts_flag = true;
+ this.dots = dots;
+ this.dots_flag = true;
+ this.lts = lts;
+ this.lts_flag = true;
+ this.olts = olts;
+ this.olts_flag = true;
+ this.vlts = vlts;
+ this.vlts_flag = true;
+ this.volts = volts;
+ this.volts_flag = true;
+ this.dlts = dlts;
+ this.dlts_flag = true;
+ this.dolts = dolts;
+ this.dolts_flag = true;
+ this.opts = opts;
+ this.opts_flag = true;
+ this.oopts = oopts;
+ this.oopts_flag = true;
+ this.vopts = vopts;
+ this.vopts_flag = true;
+ this.voopts = voopts;
+ this.voopts_flag = true;
+ this.dopts = dopts;
+ this.dopts_flag = true;
+ this.doopts = doopts;
+ this.doopts_flag = true;
+ }
+ }
+
+ private native Object init(
+ boolean v1,
+ Boolean v2,
+ List<Boolean> v3,
+ List<Boolean> v4,
+ Map<String, Boolean> v5,
+ Map<String, Boolean> v6,
+ int v7, Integer var8,
+ List<Boolean> v9,
+ List<Boolean> v10,
+ Map<String, Boolean> v11,
+ Map<String, Boolean> v12,
+ int v13,
+ Integer v14,
+ List<Boolean> v15,
+ List<Boolean> v16,
+ Map<String, Boolean> v17,
+ Map<String, Boolean> v18,
+ long v19, Long var21,
+ List<Boolean> v22,
+ List<Boolean> v23,
+ Map<String, Boolean> v24,
+ Map<String, Boolean> v25,
+ float v26,
+ Float v27,
+ List<Boolean> v28,
+ List<Boolean> v29,
+ Map<String, Boolean> v30,
+ Map<String, Boolean> v31,
+ double v32,
+ Double v34,
+ List<Boolean> v35,
+ List<Boolean> v36,
+ Map<String, Boolean> v37,
+ Map<String, Boolean> v38,
+ String v39,
+ String v40,
+ List<Boolean> v41,
+ List<Boolean> v42,
+ Map<String, Boolean> v43,
+ Map<String, Boolean> v44,
+ long v45, Long var47,
+ List<Boolean> v48,
+ List<Boolean> v49,
+ Map<String, Boolean> v50,
+ Map<String, Boolean> v51,
+ long v52,
+ Long v54,
+ List<Boolean> v55,
+ List<Boolean> v56,
+ Map<String, Boolean> v57,
+ Map<String, Boolean> v58,
+ long v59,
+ Long v61,
+ List<Boolean> v62,
+ List<Boolean> v63,
+ Map<String, Boolean> v64,
+ Map<String, Boolean> v65,
+ byte[] v66,
+ byte[] v67,
+ int v68,
+ Integer v69,
+ List<Integer> v70,
+ List<Integer> v71,
+ Map<String, Integer> v72,
+ Map<String, Integer> v73,
+ Object v74,
+ Object v75,
+ List<Object> v76,
+ List<Object> v77,
+ Map<String, Object> v78,
+ Map<String, Object> v79,
+ Object v80,
+ Object v81,
+ List<Object> v82,
+ List<Object> v83,
+ Map<String, Object> v84,
+ Map<String, Object> v85,
+ int v86,
+ Integer v87,
+ List<Object> v88,
+ List<Object> v89,
+ Map<String, Object> v90,
+ Map<String, Object> v91,
+ Object v92,
+ Object v93,
+ List<Object> v94,
+ List<Object> v95,
+ Map<String, Object> v96,
+ Map<String, Object> v97,
+ Object v98,
+ Object v99,
+ List<Object> v100,
+ List<Object> v101,
+ Map<String, Object> v102,
+ Map<String, Object> v103,
+ Object v104,
+ Object v105,
+ List<Object> v106,
+ List<Object> v107,
+ Map<String, Object> v108,
+ Map<String, Object> v109);
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
index 64d092b..ff3f81a 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminBuilder.java
@@ -120,6 +120,15 @@
return addMethod("public", name, argumentTypes, returnType, lines);
}
+ public MethodSignature addBridgeMethod(
+ String name,
+ List<String> argumentTypes,
+ String returnType,
+ String... lines) {
+ makeInit = true;
+ return addMethod("public bridge", name, argumentTypes, returnType, lines);
+ }
+
public MethodSignature addPrivateVirtualMethod(
String name,
List<String> argumentTypes,
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index 3e46ff8..93f4249 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -47,7 +47,7 @@
// invoke the tested method.
private static final String JASMIN_MAIN_CLASS = "TestMain";
- @Parameters(name = "{0}_{1}")
+ @Parameters(name = "allowAccessModification: {0} target: {1}")
public static Collection<Object[]> data() {
ImmutableList.Builder<Object[]> builder = new ImmutableList.Builder<>();
for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
@@ -171,9 +171,16 @@
return proguardRules.toString();
}
+ protected String keepAllMembers(String className) {
+ return "-keep class " + className + " {" + System.lineSeparator()
+ + " *;" + System.lineSeparator()
+ + "}";
+ }
+
protected String keepClassMethod(String className, MethodSignature methodSignature) {
- return "-keep class " + className + " {" + System.lineSeparator() +
- methodSignature.toString() + ";" + System.lineSeparator() + "}";
+ return "-keep class " + className + " {" + System.lineSeparator()
+ + methodSignature.toString() + ";" + System.lineSeparator()
+ + "}";
}
protected void runTest(String folder, String mainClass, AndroidAppInspector inspector)
@@ -199,7 +206,7 @@
// Build with R8
AndroidApp.Builder builder = AndroidApp.builder();
builder.addProgramFiles(classpath);
- AndroidApp app = compileWithR8(builder.build(), proguardRules.toString());
+ AndroidApp app = compileWithR8(builder.build(), proguardRules);
// Materialize file for execution.
Path generatedDexFile = temp.getRoot().toPath().resolve("classes.jar");
diff --git a/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
new file mode 100644
index 0000000..ab6e949
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -0,0 +1,82 @@
+// 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.kotlin;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.code.Format21t;
+import com.android.tools.r8.code.Format22t;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import org.junit.Test;
+
+public class SimplifyIfNotNullKotlinTest extends AbstractR8KotlinTestBase {
+ private static final String FOLDER = "non_null";
+ private static final String STRING = "java.lang.String";
+
+ private static boolean isIf(Instruction instruction) {
+ return instruction instanceof Format21t || instruction instanceof Format22t;
+ }
+
+ @Test
+ public void test_example1() throws Exception {
+ final TestKotlinClass ex1 = new TestKotlinClass("non_null.Example1Kt");
+ final MethodSignature testMethodSignature =
+ new MethodSignature("forMakeAndModel", "java.util.SortedMap",
+ ImmutableList.of("java.util.Collection", STRING, STRING, "java.lang.Integer"));
+
+ final String mainClassName = ex1.getClassName();
+ final String extraRules = keepAllMembers(mainClassName);
+ runTest(FOLDER, mainClassName, extraRules, app -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject clazz = checkClassExists(dexInspector, ex1.getClassName());
+
+ MethodSubject testMethod = checkMethodIsPresent(clazz, testMethodSignature);
+ DexCode dexCode = getDexCode(testMethod);
+ long count = Arrays.stream(dexCode.instructions)
+ .filter(SimplifyIfNotNullKotlinTest::isIf).count();
+ if (allowAccessModification) {
+ // Three null-check's from inlined checkParameterIsNotNull for receiver and two arguments.
+ assertEquals(5, count);
+ } else {
+ // One after Iterator#hasNext, and another in the filter predicate: sinceYear != null.
+ assertEquals(2, count);
+ }
+ });
+ }
+
+ @Test
+ public void test_example2() throws Exception {
+ final TestKotlinClass ex2 = new TestKotlinClass("non_null.Example2Kt");
+ final MethodSignature testMethodSignature =
+ new MethodSignature("aOrDefault", STRING, ImmutableList.of(STRING, STRING));
+
+ final String mainClassName = ex2.getClassName();
+ final String extraRules = keepAllMembers(mainClassName);
+ runTest(FOLDER, mainClassName, extraRules, app -> {
+ DexInspector dexInspector = new DexInspector(app);
+ ClassSubject clazz = checkClassExists(dexInspector, ex2.getClassName());
+
+ MethodSubject testMethod = checkMethodIsPresent(clazz, testMethodSignature);
+ DexCode dexCode = getDexCode(testMethod);
+ long count = Arrays.stream(dexCode.instructions)
+ .filter(SimplifyIfNotNullKotlinTest::isIf).count();
+ if (allowAccessModification) {
+ // TODO(b/76202537): 3 -> 2,
+ // Yet another null-check from checkParameterIsNotNull should subsume another from ?:
+ assertEquals(3, count);
+ } else {
+ // One null-check from force inlined coalesce and another from ?:
+ assertEquals(2, count);
+ }
+ });
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 35df33e..a6d0177 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -707,7 +707,8 @@
}
@Override
- public void buildInstruction(IRBuilder builder, int instructionIndex) {
+ public void buildInstruction(
+ IRBuilder builder, int instructionIndex, boolean firstBlockInstruction) {
assert instructionIndex == 0;
builder.addReturn();
}
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTest.java b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTest.java
new file mode 100644
index 0000000..d42800a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTest.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming;
+
+public class InterfaceRenamingTest {
+
+ public static final Class[] CLASSES = {
+ InterfaceRenamingTest.class,
+ InterfaceA.class,
+ InterfaceB.class,
+ ImplementationA1.class,
+ ImplementationB1.class,
+ ImplementationA2.class,
+ ImplementationB2.class,
+ };
+
+ // Since the names and parameter lists of the two methods in the two interfaces are equal,
+ // non-aggressive minification gives the methods the same minified name.
+ // However, the return types are different, so looking up the renamed name by prototype
+ // only gives a result for one of the two interfaces.
+ interface InterfaceA {
+ Boolean interfaceMethod();
+ }
+
+ interface InterfaceB {
+ Integer interfaceMethod();
+ }
+
+ // Two implementations of each interface are required
+ // to avoid the Devirtualizer hiding the buggy behavior.
+ static class ImplementationA1 implements InterfaceA {
+ @Override
+ public Boolean interfaceMethod() {
+ System.out.println("interfaceMethod1");
+ return true;
+ }
+ }
+
+ static class ImplementationA2 implements InterfaceA {
+ @Override
+ public Boolean interfaceMethod() {
+ System.out.println("interfaceMethod1 dummy");
+ return false;
+ }
+ }
+
+ static class ImplementationB1 implements InterfaceB {
+ @Override
+ public Integer interfaceMethod() {
+ System.out.println("interfaceMethod2");
+ return 10;
+ }
+ }
+
+ static class ImplementationB2 implements InterfaceB {
+ @Override
+ public Integer interfaceMethod() {
+ System.out.println("interfaceMethod2 dummy");
+ return 20;
+ }
+ }
+
+ public static void main(String[] args) {
+ invokeA(new ImplementationA1());
+ invokeB(new ImplementationB1());
+ invokeA(new ImplementationA2());
+ invokeB(new ImplementationB2());
+ }
+
+ private static void invokeA(InterfaceA instance) {
+ System.out.println("invokeA: " + instance.interfaceMethod());
+ }
+
+ private static void invokeB(InterfaceB instance) {
+ System.out.println("invokeB: " + instance.interfaceMethod());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
new file mode 100644
index 0000000..6c054e5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/InterfaceRenamingTestRunner.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.ProgramConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+
+public class InterfaceRenamingTestRunner extends TestBase {
+ static final Class CLASS = InterfaceRenamingTest.class;
+ static final Class[] CLASSES = InterfaceRenamingTest.CLASSES;
+
+ @Test
+ public void testCfNoMinify() throws Exception {
+ testCf(MinifyMode.NONE);
+ }
+
+ @Test
+ public void testCfMinify() throws Exception {
+ testCf(MinifyMode.JAVA);
+ }
+
+ @Test
+ public void testCfMinifyAggressive() throws Exception {
+ testCf(MinifyMode.AGGRESSIVE);
+ }
+
+ @Test
+ public void testDexNoMinify() throws Exception {
+ testDex(MinifyMode.NONE);
+ }
+
+ @Test
+ public void testDexMinify() throws Exception {
+ testDex(MinifyMode.JAVA);
+ }
+
+ @Test
+ public void testDexMinifyAggressive() throws Exception {
+ testDex(MinifyMode.AGGRESSIVE);
+ }
+
+ private void testCf(MinifyMode minify) throws Exception {
+ ProcessResult runInput =
+ ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
+ assertEquals(0, runInput.exitCode);
+ Path outCf = temp.getRoot().toPath().resolve("cf.zip");
+ build(new ClassFileConsumer.ArchiveConsumer(outCf), minify);
+ ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName());
+ assertEquals(runInput.toString(), runCf.toString());
+ }
+
+ private void testDex(MinifyMode minify) throws Exception {
+ ProcessResult runInput =
+ ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
+ assertEquals(0, runInput.exitCode);
+ Path outDex = temp.getRoot().toPath().resolve("dex.zip");
+ build(new DexIndexedConsumer.ArchiveConsumer(outDex), minify);
+ ProcessResult runDex =
+ ToolHelper.runArtNoVerificationErrorsRaw(outDex.toString(), CLASS.getCanonicalName());
+ assertEquals(runInput.stdout, runDex.stdout);
+ assertEquals(runInput.exitCode, runDex.exitCode);
+ }
+
+ private void build(ProgramConsumer consumer, MinifyMode minify) throws Exception {
+ List<String> config =
+ Arrays.asList(
+ "-keep public class " + CLASS.getCanonicalName() + " {",
+ " public static void main(...);",
+ "}");
+
+ Builder builder =
+ ToolHelper.addProguardConfigurationConsumer(
+ R8Command.builder(),
+ pgConfig -> {
+ pgConfig.setPrintMapping(true);
+ pgConfig.setOverloadAggressively(minify == MinifyMode.AGGRESSIVE);
+ if (!minify.isMinify()) {
+ pgConfig.disableObfuscation();
+ }
+ })
+ .setMode(CompilationMode.DEBUG)
+ .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+ .setProgramConsumer(consumer)
+ .addProguardConfiguration(config, Origin.unknown());
+ for (Class<?> c : CLASSES) {
+ builder.addClassProgramData(ToolHelper.getClassAsBytes(c), Origin.unknown());
+ }
+ if (consumer instanceof ClassFileConsumer) {
+ // TODO(b/75997473): Enable inlining when supported by CF backend
+ ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
+ } else {
+ ToolHelper.runR8(builder.build());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index 5b1e7e3..b60ede7 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -30,6 +30,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
import org.junit.Before;
@@ -74,10 +75,11 @@
ClassAndMemberPublicizer.run(program, dexItemFactory);
}
- RootSet rootSet = new RootSetBuilder(program, appInfo, configuration.getRules(), options)
- .run(ThreadUtils.getExecutorService(options));
+ ExecutorService executor = ThreadUtils.getExecutorService(1);
+ RootSet rootSet = new RootSetBuilder(appInfo, program, configuration.getRules(), options)
+ .run(executor);
Enqueuer enqueuer = new Enqueuer(appInfo, options, options.forceProguardCompatibility);
- appInfo = enqueuer.traceApplication(rootSet, timing);
+ appInfo = enqueuer.traceApplication(rootSet, executor, timing);
return new Minifier(appInfo.withLiveness(), rootSet, options).run(timing);
}
diff --git a/src/test/java/com/android/tools/r8/regress/B76025099.java b/src/test/java/com/android/tools/r8/regress/B76025099.java
new file mode 100644
index 0000000..767420b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/B76025099.java
@@ -0,0 +1,128 @@
+// 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.regress;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.code.InvokeDirect;
+import com.android.tools.r8.code.IputObject;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import regress_76025099.Main;
+import regress_76025099.impl.Impl;
+
+@RunWith(VmTestRunner.class)
+public class B76025099 extends TestBase {
+ private static final String PRG =
+ ToolHelper.EXAMPLES_BUILD_DIR + "regress_76025099" + FileUtils.JAR_EXTENSION;
+
+ private AndroidApp runR8(AndroidApp app) throws Exception {
+ R8Command command =
+ ToolHelper.addProguardConfigurationConsumer(
+ ToolHelper.prepareR8CommandBuilder(app),
+ pgConfig -> {
+ pgConfig.setPrintMapping(true);
+ pgConfig.setPrintMappingFile(map);
+ })
+ .addProguardConfigurationFiles(pgConfig)
+ .setOutput(tempRoot.toPath(), OutputMode.DexIndexed)
+ .build();
+ return ToolHelper.runR8(command, o -> {
+ o.enableMinification = false;
+ });
+ }
+
+ private File tempRoot;
+ private Path jarPath;
+ private AndroidApp originalApp;
+ private String mainName;
+ private Path pgConfig;
+ private Path map;
+
+ @Before
+ public void setUp() throws Exception {
+ tempRoot = temp.getRoot();
+ jarPath = Paths.get(PRG);
+ originalApp = readJar(jarPath);
+ mainName = Main.class.getCanonicalName();
+ pgConfig = File.createTempFile("keep-rules", ".config", tempRoot).toPath();
+ String config = keepMainProguardConfiguration(Main.class);
+ config += System.lineSeparator() + "-dontobfuscate";
+ Files.write(pgConfig, config.getBytes());
+ map = File.createTempFile("proguard", ".map", tempRoot).toPath();
+ }
+
+ @Test
+ public void testProguardAndD8() throws Exception {
+ if (!isRunProguard()) {
+ return;
+ }
+
+ ProcessResult jvmOutput = ToolHelper.runJava(ImmutableList.of(jarPath), mainName);
+ assertEquals(0, jvmOutput.exitCode);
+
+ Path proguarded =
+ File.createTempFile("proguarded", FileUtils.JAR_EXTENSION, tempRoot).toPath();
+ ProcessResult proguardResult = ToolHelper.runProguardRaw(jarPath, proguarded, pgConfig, map);
+ assertEquals(0, proguardResult.exitCode);
+
+ AndroidApp processedApp = ToolHelper.runD8(readJar(proguarded));
+ verifyFieldAccess(processedApp, jvmOutput);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ ProcessResult jvmOutput = ToolHelper.runJava(ImmutableList.of(jarPath), mainName);
+ assertEquals(0, jvmOutput.exitCode);
+
+ AndroidApp processedApp = runR8(originalApp);
+ verifyFieldAccess(processedApp, jvmOutput);
+ }
+
+ private void verifyFieldAccess(AndroidApp processedApp, ProcessResult jvmOutput)
+ throws Exception {
+ DexInspector inspector = new DexInspector(processedApp);
+ ClassSubject impl = inspector.clazz(Impl.class);
+ assertThat(impl, isPresent());
+ MethodSubject init = impl.init(ImmutableList.of("java.lang.String"));
+ assertThat(init, isPresent());
+ DexCode dexCode = init.getMethod().getCode().asDexCode();
+ checkInstructions(dexCode, ImmutableList.of(
+ InvokeDirect.class,
+ IputObject.class,
+ ReturnVoid.class
+ ));
+ IputObject iput = (IputObject) dexCode.instructions[1];
+ DexField fld = iput.getField();
+ assertEquals("name", fld.name.toString());
+ assertEquals("regress_76025099.impl.Impl", fld.getHolder().toSourceString());
+
+ ProcessResult artOutput = runOnArtRaw(processedApp, mainName);
+ assertEquals(0, artOutput.exitCode);
+ assertEquals(jvmOutput.stdout, artOutput.stdout);
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTest.java b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTest.java
new file mode 100644
index 0000000..7733339
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTest.java
@@ -0,0 +1,70 @@
+// 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.resolution;
+
+public class PublicFieldInnerClassTest {
+ public static final Class<?>[] CLASSES = {
+ PrivateBase.class,
+ PrivateSubclass.class,
+ PackageBase.class,
+ PackageSubclass.class,
+ ProtectedBase.class,
+ ProtectedSubclass.class,
+ PublicBase.class,
+ PublicSubclass.class,
+ PublicFieldInnerClassTest.class
+ };
+
+ private static class PrivateBase {
+ public int value;
+ }
+
+ private static class PrivateSubclass extends PrivateBase {
+ }
+
+ static class PackageBase {
+ public int value;
+ }
+
+ private static class PackageSubclass extends PackageBase {
+ }
+
+ protected static class ProtectedBase {
+ public int value;
+ }
+
+ private static class ProtectedSubclass extends ProtectedBase {
+ }
+
+ public static class PublicBase {
+ public int value;
+ }
+
+ private static class PublicSubclass extends PublicBase {
+ }
+
+ private static int getPrivate(PrivateSubclass instance) {
+ return instance.value;
+ }
+
+ private static int getPackage(PackageSubclass instance) {
+ return instance.value;
+ }
+
+ private static int getProtected(ProtectedSubclass instance) {
+ return instance.value;
+ }
+
+ private static int getPublic(PublicSubclass instance) {
+ return instance.value;
+ }
+
+ public static void main(String[] args) {
+ System.out.println(getPrivate(new PrivateSubclass()));
+ System.out.println(getPackage(new PackageSubclass()));
+ System.out.println(getProtected(new ProtectedSubclass()));
+ System.out.println(getPublic(new PublicSubclass()));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
new file mode 100644
index 0000000..6642352
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
@@ -0,0 +1,78 @@
+// 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.resolution;
+
+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.ProgramConsumer;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.R8Command.Builder;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+
+public class PublicFieldInnerClassTestRunner extends TestBase {
+ static final Class CLASS = PublicFieldInnerClassTest.class;
+ static final Class<?>[] CLASSES = PublicFieldInnerClassTest.CLASSES;
+
+ @Test
+ public void testCf() throws Exception {
+ ProcessResult runInput =
+ ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
+ assertEquals(0, runInput.exitCode);
+ Path outCf = temp.getRoot().toPath().resolve("cf.jar");
+ build(new ClassFileConsumer.ArchiveConsumer(outCf));
+ ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName());
+ assertEquals(runInput.toString(), runCf.toString());
+ assertEquals(
+ -1,
+ runCf.stderr.indexOf("java.lang.NoSuchFieldError"));
+ }
+
+ @Test
+ public void testDex() throws Exception {
+ ProcessResult runInput =
+ ToolHelper.runJava(ToolHelper.getClassPathForTests(), CLASS.getCanonicalName());
+ assertEquals(0, runInput.exitCode);
+ Path outDex = temp.getRoot().toPath().resolve("dex.zip");
+ build(new DexIndexedConsumer.ArchiveConsumer(outDex));
+ ProcessResult runDex = ToolHelper.runArtNoVerificationErrorsRaw(
+ outDex.toString(), CLASS.getCanonicalName());
+ assertEquals(runInput.stdout, runDex.stdout);
+ assertEquals(runInput.exitCode, runDex.exitCode);
+ assertEquals(
+ -1,
+ runDex.stderr.indexOf("java.lang.NoSuchFieldError"));
+ }
+
+ private void build(ProgramConsumer consumer) throws Exception {
+ List<String> config = Arrays.asList(
+ "-keep public class " + CLASS.getCanonicalName() + " {",
+ " public static void main(...);",
+ "}"
+ );
+ Builder builder = R8Command.builder()
+ .setMode(CompilationMode.DEBUG)
+ .addLibraryFiles(ToolHelper.getAndroidJar(ToolHelper.getMinApiLevelForDexVm()))
+ .setProgramConsumer(consumer)
+ .addProguardConfiguration(config, Origin.unknown());
+ for (Class<?> c : CLASSES) {
+ builder.addClassProgramData(ToolHelper.getClassAsBytes(c), Origin.unknown());
+ }
+ if (consumer instanceof ClassFileConsumer) {
+ // TODO(b/75997473): Enable inlining when supported by CF.
+ ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
+ } else {
+ ToolHelper.runR8(builder.build());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index c483267..1631a1f 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -42,11 +42,13 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import org.junit.Assert;
@@ -103,11 +105,11 @@
DexApplication application = new ApplicationReader(app, options, timing).read().toDirect();
AppInfoWithSubtyping appInfoWithSubtyping = new AppInfoWithSubtyping(application);
- RootSet rootSet = new RootSetBuilder(application, appInfoWithSubtyping,
- buildKeepRuleForClass(Main.class, application.dexItemFactory), options).run(
- Executors.newSingleThreadExecutor());
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ RootSet rootSet = new RootSetBuilder(appInfoWithSubtyping, application,
+ buildKeepRuleForClass(Main.class, application.dexItemFactory), options).run(executor);
appInfo = new Enqueuer(appInfoWithSubtyping, options, options.forceProguardCompatibility)
- .traceApplication(rootSet, timing);
+ .traceApplication(rootSet, executor, timing);
// We do not run the tree pruner to ensure that the hierarchy is as designed and not modified
// due to liveness.
}
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 f9e0422..5dc4ec6 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -1163,10 +1163,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- assertEquals(3, handler.warnings.size());
- for (int i = 0; i < 3; i++) {
- assertTrue(handler.warnings.get(i).getDiagnosticMessage().contains("Ignoring option: -if"));
- }
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
// Three -if rules and one independent -keepnames
assertEquals(4, config.getRules().size());
@@ -1196,8 +1193,7 @@
ProguardConfigurationParser parser =
new ProguardConfigurationParser(new DexItemFactory(), reporter);
parser.parse(proguardConfig);
- checkDiagnostic(handler.warnings, proguardConfig, 1, 1,
- "Ignoring", "-if");
+ verifyParserEndsCleanly();
ProguardConfiguration config = parser.getConfig();
assertEquals(1, config.getRules().size());
ProguardIfRule if0 = (ProguardIfRule) config.getRules().get(0);
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
index 004c31f..3976f89 100644
--- a/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
+++ b/src/test/java/com/android/tools/r8/shaking/forceproguardcompatibility/ProguardCompatabilityTestBase.java
@@ -1,9 +1,12 @@
// 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 static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertThat;
+
import com.android.tools.r8.CompatProguardCommandBuilder;
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.R8Command;
@@ -12,6 +15,7 @@
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.DexInspector.ClassSubject;
import com.android.tools.r8.utils.FileUtils;
import com.google.common.collect.ImmutableList;
import java.io.File;
@@ -113,4 +117,20 @@
throws Exception {
return runProguard6AndD8(programClasses, keepMainProguardConfiguration(mainClass));
}
+
+ protected void verifyClassesPresent(
+ DexInspector dexInspector, Class<?>... classesOfInterest) {
+ for (Class klass : classesOfInterest) {
+ ClassSubject c = dexInspector.clazz(klass);
+ assertThat(c, isPresent());
+ }
+ }
+
+ protected void verifyClassesAbsent(
+ DexInspector dexInspector, Class<?>... classesOfInterest) {
+ for (Class klass : classesOfInterest) {
+ ClassSubject c = dexInspector.clazz(klass);
+ assertThat(c, not(isPresent()));
+ }
+ }
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
new file mode 100644
index 0000000..080df2a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTest.java
@@ -0,0 +1,91 @@
+// 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.ifrule;
+
+import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IfOnAnnotationTest extends ProguardCompatabilityTestBase {
+ private final static List<Class> CLASSES = ImmutableList.of(
+ UsedAnnotation.class, UnusedAnnotation.class,
+ UsedAnnotationDependent.class, UnusedAnnotationDependent.class,
+ AnnotationUser.class, MainUsesAnnotationUser.class);
+
+ private final Shrinker shrinker;
+
+ public IfOnAnnotationTest(Shrinker shrinker) {
+ this.shrinker = shrinker;
+ }
+
+ @Parameters(name = "shrinker: {0}")
+ public static Collection<Object> data() {
+ return ImmutableList.of(Shrinker.PROGUARD6, Shrinker.R8);
+ }
+
+ @Test
+ public void ifOnAnnotation_withoutNthWildcard() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keepattributes *Annotation*",
+ "-keep class **.Main* {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ // @UsedAnnotation <methods> -> UsedAnnotationDependent
+ "-if class **.*User {",
+ " @**.UsedAnnotation <methods>;",
+ "}",
+ "-keep class **.UsedAnnotation*",
+ // @UnusedAnnotation <methods> -> UnusedAnnotationDependent
+ "-if class **.*User {",
+ " @**.UnusedAnnotation <methods>;",
+ "}",
+ "-keep class **.UnusedAnnotation*"
+ );
+
+ DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ verifyClassesAbsent(dexInspector,
+ UnusedAnnotation.class, UnusedAnnotationDependent.class);
+ verifyClassesPresent(dexInspector,
+ UsedAnnotation.class, UsedAnnotationDependent.class);
+ }
+
+ @Test
+ public void ifOnAnnotation_withNthWildcard() throws Exception {
+ // TODO(b/73800755): not implemented yet.
+ if (shrinker == Shrinker.R8) {
+ return;
+ }
+
+ List<String> config = ImmutableList.of(
+ "-keepattributes *Annotation*",
+ "-keep class **.Main* {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ // @UsedAnnotation <methods> -> UsedAnnotationDependent
+ "-if class **.*User {",
+ " @<1>.Used<2> <methods>;",
+ "}",
+ "-keep class <1>.Used<2>*",
+ // @UnusedAnnotation <methods> -> UnusedAnnotationDependent
+ "-if class **.*User {",
+ " @<1>.Unused<2> <methods>;",
+ "}",
+ "-keep class <1>.Unused<2>*"
+ );
+
+ DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ verifyClassesAbsent(dexInspector,
+ UnusedAnnotation.class, UnusedAnnotationDependent.class);
+ verifyClassesPresent(dexInspector,
+ UsedAnnotation.class, UsedAnnotationDependent.class);
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTestClasses.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTestClasses.java
new file mode 100644
index 0000000..56fb4c1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAnnotationTestClasses.java
@@ -0,0 +1,39 @@
+// 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.ifrule;
+
+@interface UsedAnnotation {
+ // Intentionally left empty.
+}
+
+@interface UnusedAnnotation {
+ // Intentionally left empty.
+}
+
+class UsedAnnotationDependent {
+ // Intentionally left empty.
+}
+
+class UnusedAnnotationDependent {
+ // Intentionally left empty.
+}
+
+class AnnotationUser {
+ private int intField;
+
+ @UsedAnnotation void live() {
+ System.out.println("live(" + intField++ + ")");
+ }
+
+ @UnusedAnnotation void dead() {
+ throw new AssertionError("Should be removed.");
+ }
+}
+
+class MainUsesAnnotationUser {
+ public static void main(String[] args) {
+ AnnotationUser user = new AnnotationUser();
+ user.live();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
index 5f3cae1..c26f43f 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnClassTest.java
@@ -78,7 +78,44 @@
}
@Test
- public void ifThenKeep() throws Exception {
+ public void ifThenKeep_withoutNthWildcard() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-if class **.Precondition",
+ "-keep,allowobfuscation class **.*User",
+ "-if class **.DependentUser",
+ "-keep,allowobfuscation class **.Dependent"
+ );
+
+ DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ if (!keepPrecondition) {
+ // TODO(b/73708139): Proguard6 kept Dependent (w/o any members), which is not necessary.
+ if (shrinker == Shrinker.PROGUARD6) {
+ return;
+ }
+ assertEquals(1, dexInspector.allClasses().size());
+ return;
+ }
+
+ ClassSubject clazz = dexInspector.clazz(DependentUser.class);
+ assertThat(clazz, isRenamed());
+ // Members of DependentUser are not used anywhere.
+ MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
+ assertThat(m, not(isPresent()));
+ FieldSubject f = clazz.field("int", "canBeShrinked");
+ assertThat(f, not(isPresent()));
+
+ // Although DependentUser#callFoo is shrinked, Dependent is kept via -if.
+ clazz = dexInspector.clazz(Dependent.class);
+ assertThat(clazz, isRenamed());
+ // But, its members are gone.
+ m = clazz.method("java.lang.String", "foo", ImmutableList.of());
+ assertThat(m, not(isPresent()));
+ f = clazz.field("int", "intField");
+ assertThat(f, not(isPresent()));
+ }
+
+ @Test
+ public void ifThenKeep_withNthWildcard() throws Exception {
List<String> config = ImmutableList.of(
"-if class **.Precondition",
"-keep,allowobfuscation class **.*User",
@@ -96,7 +133,7 @@
return;
}
- // TODO(b/73708139): not implemented yet.
+ // TODO(b/73800755): not implemented yet.
if (shrinker == Shrinker.R8) {
return;
}
@@ -134,8 +171,43 @@
return;
}
- // TODO(b/73708139): not implemented yet.
- if (shrinker == Shrinker.R8) {
+ ClassSubject clazz = dexInspector.clazz(DependentUser.class);
+ assertThat(clazz, isRenamed());
+ MethodSubject m = clazz.method("void", "callFoo", ImmutableList.of());
+ assertThat(m, isRenamed());
+ FieldSubject f = clazz.field("int", "canBeShrinked");
+ assertThat(f, not(isPresent()));
+
+ // Dependent is kept due to DependentUser#callFoo, but renamed.
+ clazz = dexInspector.clazz(Dependent.class);
+ assertThat(clazz, isRenamed());
+ m = clazz.method("java.lang.String", "foo", ImmutableList.of());
+ assertThat(m, isRenamed());
+ f = clazz.field("int", "intField");
+ assertThat(f, isRenamed());
+ }
+
+ @Test
+ public void ifThenKeepClassMembers_withoutNthWildcard() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-if class **.Precondition",
+ "-keepclassmembers,allowobfuscation class **.*User {",
+ " static void callFoo(...);",
+ "}",
+ // If the member is kept, keep the enclosing class too.
+ "-if class **.*User {",
+ " static void callFoo(...);",
+ "}",
+ "-keep,allowobfuscation class **.*User"
+ );
+
+ DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ if (!keepPrecondition) {
+ // TODO(b/73708139): Proguard6 kept DependentUser (w/o any members), which is not necessary.
+ if (shrinker == Shrinker.PROGUARD6) {
+ return;
+ }
+ assertEquals(1, dexInspector.allClasses().size());
return;
}
@@ -156,15 +228,15 @@
}
@Test
- public void ifThenKeepClassMembers() throws Exception {
+ public void ifThenKeepClassMembers_withNthWildcard() throws Exception {
List<String> config = ImmutableList.of(
"-if class **.Precondition",
"-keepclassmembers,allowobfuscation class **.*User {",
" static void callFoo(...);",
"}",
- // If members are kept, keep the enclosing class too.
+ // If the member is kept, keep the enclosing class too.
"-if class **.*User {",
- " <methods>;",
+ " static void callFoo(...);",
"}",
"-keep,allowobfuscation class <1>.<2>User"
);
@@ -179,7 +251,7 @@
return;
}
- // TODO(b/73708139): not implemented yet.
+ // TODO(b/73800755): not implemented yet.
if (shrinker == Shrinker.R8) {
return;
}
@@ -214,11 +286,6 @@
DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- // TODO(b/73708139): not implemented yet.
- if (shrinker == Shrinker.R8) {
- return;
- }
-
ClassSubject clazz = dexInspector.clazz(Dependent.class);
// Only class name is not renamed, if triggered.
assertThat(clazz, keepPrecondition ? isNotRenamed() : isRenamed());
@@ -244,11 +311,6 @@
DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- // TODO(b/73708139): not implemented yet.
- if (shrinker == Shrinker.R8) {
- return;
- }
-
ClassSubject clazz = dexInspector.clazz(Dependent.class);
// Class name is not renamed, if triggered.
assertThat(clazz, keepPrecondition ? isNotRenamed() : isRenamed());
@@ -275,11 +337,6 @@
DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
- // TODO(b/73708139): not implemented yet.
- if (shrinker == Shrinker.R8) {
- return;
- }
-
ClassSubject clazz = dexInspector.clazz(Dependent.class);
assertThat(clazz, isRenamed());
MethodSubject m = clazz.method("java.lang.String", "foo", ImmutableList.of());
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
index 07e94cd..2bf8a27 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTest.java
@@ -3,15 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking.ifrule;
-import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatabilityTestBase;
import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.DexInspector.ClassSubject;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
@@ -27,7 +23,8 @@
D.class, D1.class, D2.class,
R.class, R1.class, R2.class,
MainWithInner.InnerR.class, MainWithInner.InnerD.class,
- MainUsesR.class, MainWithIf.class, MainWithInner.class);
+ I.class, Impl.class,
+ MainUsesR.class, MainWithIf.class, MainWithInner.class, MainUsesImpl.class);
private final Shrinker shrinker;
@@ -59,24 +56,39 @@
return super.runProguard6(programClasses, adaptConfiguration(proguardConfig));
}
- private void verifyClassesPresent(
- DexInspector dexInspector, Class<?>... classesOfInterest) {
- for (Class klass : classesOfInterest) {
- ClassSubject c = dexInspector.clazz(klass);
- assertThat(c, isPresent());
- }
- }
+ @Test
+ public void ifOnField_withoutNthWildcard() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **.MainUsesR {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ // R.id1 -> D1
+ "-if class **.R {",
+ " public static int id1;",
+ "}",
+ "-keep class **.D1",
+ // R.id2 -> D2
+ "-if class **.R {",
+ " public static int id2;",
+ "}",
+ "-keep class **.D2",
+ // R.id1 && R.id2 -> D
+ "-if class **.R {",
+ " public static int id1;",
+ " public static int id2;",
+ "}",
+ "-keep class **.D"
+ );
- private void verifyClassesAbsent(
- DexInspector dexInspector, Class<?>... classesOfInterest) {
- for (Class klass : classesOfInterest) {
- ClassSubject c = dexInspector.clazz(klass);
- assertThat(c, not(isPresent()));
- }
+ DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ verifyClassesAbsent(dexInspector,
+ R1.class, R2.class, D.class, D2.class);
+ verifyClassesPresent(dexInspector,
+ R.class, D1.class);
}
@Test
- public void ifOnField() throws Exception {
+ public void ifOnField_withNthWildcard() throws Exception {
// TODO(b/73800755): not implemented yet.
if (shrinker == Shrinker.R8) {
return;
@@ -87,13 +99,9 @@
" public static void main(java.lang.String[]);",
"}",
"-if class **.R {",
- " public static int id1;",
+ " public static int id?;",
"}",
- "-keep class **.D1",
- "-if class **.R {",
- " public static int id2;",
- "}",
- "-keep class **.D2"
+ "-keep class **.D<2>"
);
DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
@@ -101,26 +109,33 @@
R1.class, R2.class, D.class, D2.class);
verifyClassesPresent(dexInspector,
R.class, D1.class);
-
- config = ImmutableList.of(
- "-keep class **.MainUsesR {",
- " public static void main(java.lang.String[]);",
- "}",
- "-if class **.R {",
- " public static int id?;",
- "}",
- "-keep class **.D<2>"
- );
-
- dexInspector = runShrinker(shrinker, CLASSES, config);
- verifyClassesAbsent(dexInspector,
- R1.class, R2.class, D.class, D2.class);
- verifyClassesPresent(dexInspector,
- R.class, D1.class);
}
@Test
- public void ifOnFieldWithCapture() throws Exception {
+ public void ifOnFieldWithCapture_withoutNthWildcard() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **.MainWithIf {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ "-if class **.R1 {",
+ " public static int id*;",
+ "}",
+ "-keep class **.D1",
+ "-if class **.R2 {",
+ " public static int id*;",
+ "}",
+ "-keep class **.D2"
+ );
+
+ DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ verifyClassesAbsent(dexInspector,
+ R.class, D.class, R1.class, D1.class);
+ verifyClassesPresent(dexInspector,
+ R2.class, D2.class);
+ }
+
+ @Test
+ public void ifOnFieldWithCapture_withNthWildcard() throws Exception {
// TODO(b/73800755): not implemented yet.
if (shrinker == Shrinker.R8) {
return;
@@ -131,8 +146,7 @@
" public static void main(java.lang.String[]);",
"}",
"-if class **.R* {",
- " public static int id1;",
- " public static int id2;",
+ " public static int id*;",
"}",
"-keep class **.D<2>"
);
@@ -145,7 +159,27 @@
}
@Test
- public void ifOnFieldWithInner() throws Exception {
+ public void ifOnFieldWithInner_withoutNthWildcard() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **.MainWithInner {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ "-if class **$*R {",
+ " public static int id1;",
+ " public static int id2;",
+ "}",
+ "-keep class **$*D"
+ );
+
+ DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ verifyClassesAbsent(dexInspector,
+ R.class, D.class, R1.class, D1.class, R2.class, D2.class);
+ verifyClassesPresent(dexInspector,
+ MainWithInner.InnerR.class, MainWithInner.InnerD.class);
+ }
+
+ @Test
+ public void ifOnFieldWithInner_withNthWildcard() throws Exception {
// TODO(b/73800755): not implemented yet.
if (shrinker == Shrinker.R8) {
return;
@@ -196,4 +230,53 @@
}
}
+ @Test
+ public void ifOnFieldInImplementer_withoutNthWildcard() throws Exception {
+ List<String> config = ImmutableList.of(
+ "-keep class **.MainUsesImpl {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ "-if class ** implements **.I {",
+ " private <fields>;",
+ "}",
+ "-keep class **.D1",
+ "-if class ** implements **.I {",
+ " public <fields>;",
+ "}",
+ "-keep class **.D2"
+ );
+
+ DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ verifyClassesAbsent(dexInspector, D2.class);
+ verifyClassesPresent(dexInspector,
+ I.class, Impl.class, D1.class);
+ }
+
+ @Test
+ public void ifOnFieldInImplementer_withNthWildcard() throws Exception {
+ // TODO(b/73800755): not implemented yet.
+ if (shrinker == Shrinker.R8) {
+ return;
+ }
+
+ List<String> config = ImmutableList.of(
+ "-keep class **.MainUsesImpl {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ "-if class ** implements **.I {",
+ " private <fields>;",
+ "}",
+ "-keep class <2>.D1",
+ "-if class ** implements **.I {",
+ " public <fields>;",
+ "}",
+ "-keep class <2>.D2"
+ );
+
+ DexInspector dexInspector = runShrinker(shrinker, CLASSES, config);
+ verifyClassesAbsent(dexInspector, D2.class);
+ verifyClassesPresent(dexInspector,
+ I.class, Impl.class, D1.class);
+ }
+
}
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTestClasses.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTestClasses.java
index 892b63e..bd8e5c2 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTestClasses.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnFieldTestClasses.java
@@ -27,6 +27,21 @@
public static int id2 = 2;
}
+interface I {
+ void ack();
+}
+
+class Impl implements I {
+ private int usedPrivateIntField;
+ private int unusedPrivateIntField;
+ public int unusedPublicIntField;
+ public String unusedPublicStringField;
+
+ public void ack() {
+ System.out.println("ack(" + usedPrivateIntField++ + ")");
+ }
+}
+
class MainUsesR {
public static void main(String[] args) {
System.out.println(R.id1);
@@ -62,3 +77,9 @@
}
}
+class MainUsesImpl {
+ public static void main(String[] args) {
+ I instance = new Impl();
+ instance.ack();
+ }
+}
diff --git a/src/test/kotlinR8TestResources/non_null/example1.kt b/src/test/kotlinR8TestResources/non_null/example1.kt
new file mode 100644
index 0000000..02a3fcf
--- /dev/null
+++ b/src/test/kotlinR8TestResources/non_null/example1.kt
@@ -0,0 +1,29 @@
+// 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 non_null
+
+data class Car(
+ val make: String,
+ val model: String,
+ val year: Int,
+ val plateNumber: String)
+
+fun Collection<Car>.forMakeAndModel(
+ make: String, model: String, sinceYear: Int?
+) = this.asSequence()
+ .filter { it.make == make }
+ .filter { it.model == model }
+ .filter { sinceYear != null && it.year >= sinceYear }
+ .groupBy { it.year }
+ .toSortedMap()
+
+fun main(args: Array<String>) {
+ val leaf = Car("Nissan", "Leaf", 2015, " LEAF ")
+ val ms1 = Car("Tesla", "Model S", 2015, " LGTM1 ")
+ val ms2 = Car("Tesla", "Model S", 2017, " LGTM2 ")
+ val m3 = Car("Tesla", "Model 3", 2018, " LGTM3 ")
+ val cars: List<Car> = mutableListOf(leaf, ms1, ms2, m3)
+ println(cars.forMakeAndModel("Tesla", "Model S", null))
+}
diff --git a/src/test/kotlinR8TestResources/non_null/example2.kt b/src/test/kotlinR8TestResources/non_null/example2.kt
new file mode 100644
index 0000000..e35b2e8
--- /dev/null
+++ b/src/test/kotlinR8TestResources/non_null/example2.kt
@@ -0,0 +1,14 @@
+// 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 non_null
+
+inline fun coalesce(a: String?, b: String?): String? = a ?: b
+fun aOrDefault(a: String?, default: String): String =
+ coalesce(a, default) ?: throw AssertionError()
+
+fun main(args: Array<String>) {
+ println(aOrDefault(null, "null"))
+ println(aOrDefault("null", "non-null"))
+}
\ No newline at end of file