Merge "Update patch version for release"
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/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/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index ca8f7cb..a681943 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.interfaceMethod != null) {
+ methodName = lens.lookupName(callSite.interfaceMethod);
+ } 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..9e22713 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCallSite.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
@@ -29,10 +29,12 @@
public final DexMethodHandle bootstrapMethod;
public final List<DexValue> bootstrapArgs;
+ public final DexMethod interfaceMethod;
+
private DexEncodedArray encodedArray = null;
DexCallSite(DexString methodName, DexProto methodProto,
- DexMethodHandle bootstrapMethod, List<DexValue> bootstrapArgs) {
+ DexMethodHandle bootstrapMethod, List<DexValue> bootstrapArgs, DexMethod interfaceMethod) {
assert methodName != null;
assert methodProto != null;
assert bootstrapMethod != null;
@@ -42,6 +44,7 @@
this.methodProto = methodProto;
this.bootstrapMethod = bootstrapMethod;
this.bootstrapArgs = bootstrapArgs;
+ this.interfaceMethod = interfaceMethod;
}
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..75f2ee8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -15,6 +15,7 @@
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.kotlin.Kotlin;
import com.android.tools.r8.naming.NamingLens;
@@ -544,7 +545,20 @@
DexString methodName, DexProto methodProto,
DexMethodHandle bootstrapMethod, List<DexValue> bootstrapArgs) {
assert !sorted;
- DexCallSite callSite = new DexCallSite(methodName, methodProto, bootstrapMethod, bootstrapArgs);
+ String bootstrapClass = bootstrapMethod.asMethod().holder.toDescriptorString();
+ DexMethod interfaceMethod = null;
+ if (bootstrapClass.equals("Ljava/lang/invoke/LambdaMetafactory;")) {
+ if (methodName.toString().equals("metafactory")) {
+ DexType interfaceType = methodProto.returnType;
+ assert bootstrapArgs.size() == 3;
+ // bootstrapArgs contains samMethodType, implMethod and instantiatedMethodType.
+ DexValueMethodType samMethodType = (DexValueMethodType) bootstrapArgs.get(0);
+ interfaceMethod = createMethod(interfaceType, samMethodType.value, methodName);
+ }
+ // TODO(mathiasr): Support altMetafactory, possibly using ir.desugar.LambdaDescriptor
+ }
+ DexCallSite callSite =
+ new DexCallSite(methodName, methodProto, bootstrapMethod, bootstrapArgs, interfaceMethod);
return canonicalize(callSites, callSite);
}
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..7c04392 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
@@ -16,7 +16,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;
@@ -73,6 +75,8 @@
private final Int2ReferenceMap<LocalVariableInfo> openLocalVariables =
new Int2ReferenceOpenHashMap<>();
+ private AppInfoWithSubtyping appInfo;
+
// Internal abstraction of the stack values and height.
private static class Stack {
int maxHeight = 0;
@@ -119,10 +123,16 @@
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;
+ }
+
// 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.
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/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/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 0b43509..a726391 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -712,12 +712,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);
}
}
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/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/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/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..e324dee
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/SimplifyIfNotNullKotlinTest.java
@@ -0,0 +1,83 @@
+// 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) {
+ // TODO(b/76200247): 6 -> 5
+ // Three null-check's from inlined checkParameterIsNotNull for receiver and two arguments.
+ assertEquals(6, 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/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/regress/B76025099.java b/src/test/java/com/android/tools/r8/regress/B76025099.java
new file mode 100644
index 0000000..9fa694d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/B76025099.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.regress;
+
+import static org.junit.Assert.assertEquals;
+
+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.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import regress_76025099.Main;
+
+@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, Class main, Path out) throws Exception {
+ R8Command command =
+ ToolHelper.addProguardConfigurationConsumer(
+ ToolHelper.prepareR8CommandBuilder(app),
+ pgConfig -> {
+ pgConfig.setPrintMapping(true);
+ pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+ })
+ .addProguardConfiguration(
+ ImmutableList.of(keepMainProguardConfiguration(main)),
+ Origin.unknown())
+ .setOutput(out, OutputMode.DexIndexed)
+ .build();
+ return ToolHelper.runR8(command, o -> {
+ o.enableMinification = false;
+ });
+ }
+
+ @Ignore("b/76025099")
+ @Test
+ public void test() throws Exception {
+ Path out = temp.getRoot().toPath();
+ Path jarPath = Paths.get(PRG);
+ String mainName = Main.class.getCanonicalName();
+ ProcessResult jvmOutput = ToolHelper.runJava(ImmutableList.of(jarPath), mainName);
+ assertEquals(0, jvmOutput.exitCode);
+ AndroidApp processedApp = runR8(readJar(jarPath), Main.class, out);
+ 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..b13a08e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/PublicFieldInnerClassTestRunner.java
@@ -0,0 +1,80 @@
+// 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 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 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));
+ // TODO(b/76191597): Change to runArtNoVerificationErrors + assertEquals when bug is fixed
+ ProcessResult runDex = ToolHelper.runArtRaw(
+ outDex.toString(), CLASS.getCanonicalName());
+ assertNotEquals(runInput.stdout, runDex.stdout);
+ assertNotEquals(runInput.exitCode, runDex.exitCode);
+ assertNotEquals(
+ -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/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