Merge "Take into account catch handlers when adding item-based string."
diff --git a/.gitignore b/.gitignore
index f743d94..86e3098 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,12 +28,16 @@
third_party/android_jar/lib.tar.gz
third_party/android_jar/lib-v[0-9][0-9]
third_party/android_jar/lib-v[0-9][0-9].tar.gz
+third_party/benchmarks/santa-tracker
+third_party/benchmarks/santa-tracker.tar.gz
third_party/gmail/*
!third_party/gmail/*.sha1
third_party/gmscore/*
!third_party/gmscore/*.sha1
third_party/gradle/gradle.tar.gz
third_party/gradle/gradle
+third_party/gradle-plugin
+third_party/gradle-plugin.tar.gz
third_party/jasmin.tar.gz
third_party/jasmin
third_party/jdwp-tests.tar.gz
diff --git a/build.gradle b/build.gradle
index 2cf3fe1..e0c50ee 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1190,12 +1190,30 @@
def name = dir.getName()
def taskName = "jar_kotlinR8TestResources_${name}_${kotlinTargetVersion}"
def outputFile = "build/test/kotlinR8TestResources/${kotlinTargetVersion}/${name}.jar"
- task "${taskName}"(type: kotlin.Kotlinc) {
- source = fileTree(dir: file("${examplesDir}/${name}"), include: '**/*.kt')
+ def javaOutput = "build/test/kotlinR8TestResources/${kotlinTargetVersion}/${name}/java"
+ def javaOutputJarName = "${name}.java.jar"
+ def javaOutputJarDir = "build/test/kotlinR8TestResources/${kotlinTargetVersion}"
+ task "${taskName}Kotlin"(type: kotlin.Kotlinc) {
+ source = fileTree(dir: file("${examplesDir}/${name}"),
+ include: ['**/*.kt', '**/*.java'])
destination = file(outputFile)
targetVersion = kotlinTargetVersion
}
- dependsOn taskName
+ task "${taskName}Java"(type: JavaCompile) {
+ source = fileTree(dir: file("${examplesDir}/${name}"), include: '**/*.java')
+ destinationDir = file(javaOutput)
+ classpath = sourceSets.main.compileClasspath
+ sourceCompatibility = JavaVersion.VERSION_1_6
+ targetCompatibility = JavaVersion.VERSION_1_6
+ options.compilerArgs += ["-g", "-Xlint:-options"]
+ }
+ task "${taskName}JavaJar"(type: Jar, dependsOn: "${taskName}Java") {
+ archiveName = javaOutputJarName
+ destinationDir = file(javaOutputJarDir)
+ from javaOutput
+ include "**/*.class"
+ }
+ dependsOn "${taskName}Kotlin", "${taskName}JavaJar"
}
}
}
diff --git a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
index 42982d5..9a260ea 100644
--- a/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCompilerCommand.java
@@ -103,7 +103,7 @@
private OutputMode outputMode = OutputMode.DexIndexed;
private CompilationMode mode;
- private int minApiLevel = AndroidApiLevel.getDefault().getLevel();
+ private int minApiLevel = 0;
private boolean disableDesugaring = false;
Builder() {}
@@ -228,13 +228,20 @@
/** Get the minimum API level (aka SDK version). */
public int getMinApiLevel() {
- return minApiLevel;
+ return isMinApiLevelSet() ? minApiLevel : AndroidApiLevel.getDefault().getLevel();
+ }
+
+ boolean isMinApiLevelSet() {
+ return minApiLevel != 0;
}
/** Set the minimum required API level (aka SDK version). */
public B setMinApiLevel(int minApiLevel) {
- assert minApiLevel > 0;
- this.minApiLevel = minApiLevel;
+ if (minApiLevel <= 0) {
+ getReporter().error("Invalid minApiLevel: " + minApiLevel);
+ } else {
+ this.minApiLevel = minApiLevel;
+ }
return self();
}
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 4615fa5..5f27356 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -256,6 +256,9 @@
"R8 does not support compiling DEX inputs", new PathOrigin(file)));
}
}
+ if (getProgramConsumer() instanceof ClassFileConsumer && isMinApiLevelSet()) {
+ reporter.error("R8 does not support --min-api when compiling to class files");
+ }
super.validate();
}
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 5afe490..ff3e30d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -257,10 +257,13 @@
public class ObjectMethods {
public final DexMethod getClass;
+ public final DexMethod constructor;
private ObjectMethods() {
getClass = createMethod(objectDescriptor, getClassMethodName, classDescriptor,
DexString.EMPTY_ARRAY);
+ constructor = createMethod(objectDescriptor,
+ constructorMethodName, voidType.descriptor, DexString.EMPTY_ARRAY);
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index ad187ec..9036fd7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -25,7 +25,7 @@
public DexString(String string) {
this.size = string.length();
- this.content = encode(string);
+ this.content = encodeToMutf8(string);
}
@Override
@@ -118,7 +118,7 @@
}
// Inspired from /dex/src/main/java/com/android/dex/Mutf8.java
- private static byte[] encode(String string) {
+ public static byte[] encodeToMutf8(String string) {
byte[] result = new byte[countBytes(string)];
int offset = 0;
for (int i = 0; i < string.length(); i++) {
@@ -207,15 +207,12 @@
if (string.charAt(1) == '/' || string.charAt(string.length() - 2) == '/') {
return false;
}
- for (int i = 1; i < string.length() - 1; i++) {
- char ch = string.charAt(i);
- if (ch == '/') {
- continue;
+ int cp;
+ for (int i = 1; i < string.length() - 1; i += Character.charCount(cp)) {
+ cp = string.codePointAt(i);
+ if (cp != '/' && !IdentifierUtils.isDexIdentifierPart(cp)) {
+ return false;
}
- if (IdentifierUtils.isDexIdentifierPart(ch)) {
- continue;
- }
- return false;
}
return true;
}
@@ -232,12 +229,12 @@
string.equals(Constants.CLASS_INITIALIZER_NAME))) {
return true;
}
- for (int i = 0; i < string.length(); i++) {
- char ch = string.charAt(i);
- if (IdentifierUtils.isDexIdentifierPart(ch)) {
- continue;
+ int cp;
+ for (int i = 0; i < string.length(); i += Character.charCount(cp)) {
+ cp = string.codePointAt(i);
+ if (!IdentifierUtils.isDexIdentifierPart(cp)) {
+ return false;
}
- return false;
}
return true;
}
@@ -249,18 +246,19 @@
int start = 0;
int end = string.length();
if (string.charAt(0) == '<') {
- if (string.charAt(string.length() - 1) == '>') {
+ if (string.charAt(end - 1) == '>') {
start = 1;
- end = string.length() - 1;
+ --end;
} else {
return false;
}
}
- for (int i = start; i < end; i++) {
- if (IdentifierUtils.isDexIdentifierPart(string.charAt(i))) {
- continue;
+ int cp;
+ for (int i = start; i < end; i += Character.charCount(cp)) {
+ cp = string.codePointAt(i);
+ if (!IdentifierUtils.isDexIdentifierPart(cp)) {
+ return false;
}
- return false;
}
return true;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 718d23c..01cd5f5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -5,7 +5,6 @@
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.code.InvokePolymorphicRange;
-import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
@@ -14,7 +13,6 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.CfBuilder;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.conversion.JarSourceCode;
import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -87,15 +85,12 @@
public void buildCf(CfBuilder builder) {
DexMethod dexMethod = getInvokedMethod();
DexItemFactory factory = builder.getFactory();
-
- if (dexMethod.holder.getInternalName().equals(JarSourceCode.INTERNAL_NAME_METHOD_HANDLE)) {
- DexMethod method = factory.createMethod(dexMethod.holder, getProto(), dexMethod.name);
- builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method));
- } else {
- assert dexMethod.holder.getInternalName().equals(JarSourceCode.INTERNAL_NAME_VAR_HANDLE);
- // VarHandle is new in Java 9
- throw new Unimplemented();
- }
+ // When we translate InvokeVirtual on MethodHandle/VarHandle into InvokePolymorphic,
+ // we translate the invoked prototype into a generic prototype that simply accepts Object[].
+ // To translate InvokePolymorphic back into InvokeVirtual, use the original prototype
+ // that is stored in getProto().
+ DexMethod method = factory.createMethod(dexMethod.holder, getProto(), dexMethod.name);
+ builder.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method));
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 3ad0168..96b2122 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -984,7 +984,14 @@
int source = builder.getInfo(jump).getOffset();
Info targetInfo = builder.getTargetInfo(jump.getTarget());
int relativeOffset = targetInfo.getOffset() - source;
- if (size == relativeOffset) {
+ // Emit a return if the target is a return and the size of the return is the computed
+ // size of this instruction.
+ Return ret = targetInfo.getIR().asReturn();
+ if (ret != null && size == targetInfo.getSize() && ret.getPosition().isNone()) {
+ Instruction dex = ret.createDexInstruction(builder);
+ dex.setOffset(getOffset()); // for better printing of the dex code.
+ instructions.add(dex);
+ } else if (size == relativeOffset) {
// We should never generate a goto targeting the next instruction. However, if we do
// we replace it with nops. This works around a dalvik bug where the dalvik tracing
// jit crashes on 'goto next instruction' on Android 4.1.1.
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 26a94d2..b584448 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
@@ -2658,8 +2658,8 @@
Handle bsmHandle = insn.bsm;
if (bsmHandle.getTag() != Opcodes.H_INVOKESTATIC &&
bsmHandle.getTag() != Opcodes.H_NEWINVOKESPECIAL) {
- throw new Unreachable(
- "Bootstrap handle is not yet supported: tag == " + bsmHandle.getTag());
+ // JVM9 §4.7.23 note: Tag must be InvokeStatic or NewInvokeSpecial.
+ throw new Unreachable("Bootstrap handle invalid: tag == " + bsmHandle.getTag());
}
// Resolve the bootstrap method.
DexMethodHandle bootstrapMethod = getMethodHandle(application, bsmHandle);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
index daa7811..eb7826e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
@@ -72,7 +72,7 @@
}
// No-op strategy.
- public static final Strategy NoOp = new Strategy() {
+ static final Strategy NoOp = new Strategy() {
@Override
public LambdaGroup group() {
return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
index fea83eb..f3fecde 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroup.java
@@ -27,10 +27,14 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
// Represents a group of lambda classes which potentially can be represented
// by the same lambda _group_ class. Each lambda class inside the group is
// assigned an integer id.
+//
+// NOTE: access to lambdas in lambda group is NOT thread-safe.
public abstract class LambdaGroup {
public final LambdaGroupId id;
@@ -43,9 +47,6 @@
// Maps lambda classes belonging to the group into the index inside the
// group. Note usage of linked hash map to keep insertion ordering stable.
- //
- // WARNING: access to this map is NOT synchronized and must be performed in
- // thread-safe context.
private final Map<DexType, LambdaInfo> lambdas = new LinkedHashMap<>();
public static class LambdaInfo {
@@ -67,11 +68,28 @@
return classType;
}
- public final List<LambdaInfo> lambdas() {
- return Lists.newArrayList(lambdas.values());
+ public final int size() {
+ return lambdas.size();
}
- public final boolean shouldAddToMainDex(AppInfo appInfo) {
+ public final void forEachLambda(Consumer<LambdaInfo> action) {
+ assert verifyLambdaIds(false);
+ for (LambdaInfo info : lambdas.values()) {
+ action.accept(info);
+ }
+ }
+
+ public final boolean anyLambda(Predicate<LambdaInfo> predicate) {
+ assert verifyLambdaIds(false);
+ for (LambdaInfo info : lambdas.values()) {
+ if (predicate.test(info)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ final boolean shouldAddToMainDex(AppInfo appInfo) {
// We add the group class to main index if any of the
// lambda classes it replaces is added to main index.
for (DexType type : lambdas.keySet()) {
@@ -96,6 +114,13 @@
return lambdas.get(lambda).clazz.instanceFields();
}
+ protected final DexEncodedField lambdaSingletonField(DexType lambda) {
+ assert lambdas.containsKey(lambda);
+ DexEncodedField[] fields = lambdas.get(lambda).clazz.staticFields();
+ assert fields.length < 2;
+ return fields.length == 0 ? null : fields[0];
+ }
+
// Contains less than 2 elements?
final boolean isTrivial() {
return lambdas.size() < 2;
@@ -112,6 +137,7 @@
}
final void compact() {
+ assert verifyLambdaIds(false);
int lastUsed = -1;
int lastSeen = -1;
for (Entry<DexType, LambdaInfo> entry : lambdas.entrySet()) {
@@ -123,6 +149,7 @@
entry.getValue().id = lastUsed;
}
}
+ assert verifyLambdaIds(true);
}
public abstract Strategy getCodeStrategy();
@@ -133,15 +160,14 @@
// Package for a lambda group class to be created in.
protected abstract String getTypePackage();
- public final DexProgramClass synthesizeClass(DexItemFactory factory) {
+ protected abstract String getGroupSuffix();
+
+ final DexProgramClass synthesizeClass(DexItemFactory factory) {
assert classType == null;
+ assert verifyLambdaIds(true);
List<LambdaInfo> lambdas = Lists.newArrayList(this.lambdas.values());
classType = factory.createType(
- "L" + getTypePackage() + "-$$LambdaGroup$" + createHash(lambdas) + ";");
- // We need to register new subtype manually the newly introduced type
- // does not have 'hierarchyLevel' set, but it is actually needed during
- // synthetic class methods processing.
- factory.kotlin.functional.lambdaType.addDirectSubtype(classType);
+ "L" + getTypePackage() + "-$$LambdaGroup$" + getGroupSuffix() + createHash(lambdas) + ";");
return getBuilder(factory).synthesizeClass();
}
@@ -168,9 +194,25 @@
}
}
+ private boolean verifyLambdaIds(boolean strict) {
+ int previous = -1;
+ for (LambdaInfo info : lambdas.values()) {
+ assert strict ? (previous + 1) == info.id : previous < info.id;
+ previous = info.id;
+ }
+ return true;
+ }
+
public static class LambdaStructureError extends Exception {
+ final boolean reportable;
+
public LambdaStructureError(String cause) {
+ this(cause, true);
+ }
+
+ public LambdaStructureError(String cause, boolean reportable) {
super(cause);
+ this.reportable = reportable;
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
index 5c591ab..5127632 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaGroupClassBuilder.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaInfo;
import com.android.tools.r8.origin.SynthesizedOrigin;
import java.util.List;
@@ -24,22 +23,23 @@
protected final T group;
protected final DexItemFactory factory;
protected final String origin;
- protected final List<LambdaInfo> lambdas;
protected LambdaGroupClassBuilder(T group, DexItemFactory factory, String origin) {
this.group = group;
this.factory = factory;
this.origin = origin;
- this.lambdas = group.lambdas();
}
public final DexProgramClass synthesizeClass() {
+ DexType groupClassType = group.getGroupClassType();
+ DexType superClassType = getSuperClassType();
+
return new DexProgramClass(
- group.getGroupClassType(),
+ groupClassType,
null,
new SynthesizedOrigin(origin, getClass()),
buildAccessFlags(),
- getSuperClassType(),
+ superClassType,
buildInterfaces(),
factory.createString(origin),
buildEnclosingMethodAttribute(),
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 11976b6..03daf44 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -28,9 +28,8 @@
import com.android.tools.r8.ir.conversion.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.Outliner;
import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaInfo;
import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
-import com.android.tools.r8.ir.optimize.lambda.kstyle.KStyleLambdaGroupIdFactory;
+import com.android.tools.r8.ir.optimize.lambda.kotlin.KotlinLambdaGroupIdFactory;
import com.android.tools.r8.kotlin.Kotlin;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
@@ -59,7 +58,7 @@
// (c) j-style lambda classes synthesized by kotlin compiler
//
// Lambda merging is potentially applicable to all three of them, but
-// current implementation only deals with k-style lambdas.
+// current implementation deals with both k- and j-style lambdas.
//
// In general we merge lambdas in 5 phases:
// 1. collect all lambdas and compute group candidates. we do it synchronously
@@ -151,18 +150,20 @@
.filter(cls -> !infoWithLiveness.isPinned(cls.type))
.filter(cls -> cls.hasKotlinInfo() &&
cls.getKotlinInfo().isSyntheticClass() &&
- cls.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda())
+ cls.getKotlinInfo().asSyntheticClass().isLambda())
.sorted((a, b) -> a.type.slowCompareTo(b.type)) // Ensure stable ordering.
.forEachOrdered(lambda -> {
try {
- LambdaGroupId id = KStyleLambdaGroupIdFactory.create(kotlin, lambda, options);
+ LambdaGroupId id = KotlinLambdaGroupIdFactory.create(kotlin, lambda, options);
LambdaGroup group = groups.computeIfAbsent(id, LambdaGroupId::createGroup);
group.add(lambda);
lambdas.put(lambda.type, group);
} catch (LambdaStructureError error) {
- reporter.warning(
- new StringDiagnostic("Invalid Kotlin-style lambda [" +
- lambda.type.toSourceString() + "]: " + error.getMessage()));
+ if (error.reportable) {
+ reporter.warning(
+ new StringDiagnostic("Unrecognized Kotlin lambda [" +
+ lambda.type.toSourceString() + "]: " + error.getMessage()));
+ }
}
});
@@ -199,11 +200,12 @@
// Analyse more complex aspects of lambda classes including method code.
assert converter.appInfo.hasSubtyping();
- analyzeLambdaClassesStructure(converter.appInfo.withSubtyping(), executorService);
+ AppInfoWithSubtyping appInfo = converter.appInfo.withSubtyping();
+ analyzeLambdaClassesStructure(appInfo, executorService);
// Remove invalidated lambdas, compact groups to ensure
// sequential lambda ids, create group lambda classes.
- Map<LambdaGroup, DexProgramClass> lambdaGroupsClasses = finalizeLambdaGroups();
+ Map<LambdaGroup, DexProgramClass> lambdaGroupsClasses = finalizeLambdaGroups(appInfo);
// Switch to APPLY strategy.
this.strategyFactory = ApplyStrategy::new;
@@ -236,24 +238,25 @@
for (LambdaGroup group : groups.values()) {
ThrowingConsumer<DexClass, LambdaStructureError> validator =
group.lambdaClassValidator(kotlin, appInfo);
- for (LambdaInfo lambda : group.lambdas()) {
- futures.add(service.submit(() -> {
- try {
- validator.accept(lambda.clazz);
- } catch (LambdaStructureError error) {
- reporter.info(
- new StringDiagnostic("Unexpected Kotlin-style lambda structure [" +
- lambda.clazz.type.toSourceString() + "]: " + error.getMessage())
- );
- invalidateLambda(lambda.clazz.type);
- }
- }));
- }
+ group.forEachLambda(info ->
+ futures.add(service.submit(() -> {
+ try {
+ validator.accept(info.clazz);
+ } catch (LambdaStructureError error) {
+ if (error.reportable) {
+ reporter.info(
+ new StringDiagnostic("Unexpected Kotlin lambda structure [" +
+ info.clazz.type.toSourceString() + "]: " + error.getMessage())
+ );
+ }
+ invalidateLambda(info.clazz.type);
+ }
+ })));
}
ThreadUtils.awaitFutures(futures);
}
- private synchronized Map<LambdaGroup, DexProgramClass> finalizeLambdaGroups() {
+ private Map<LambdaGroup, DexProgramClass> finalizeLambdaGroups(AppInfoWithSubtyping appInfo) {
for (DexType lambda : invalidatedLambdas) {
LambdaGroup group = lambdas.get(lambda);
assert group != null;
@@ -270,7 +273,11 @@
for (LambdaGroup group : groups.values()) {
assert !group.isTrivial() : "No trivial group is expected here.";
group.compact();
- result.put(group, group.synthesizeClass(factory));
+ DexProgramClass lambdaGroupClass = group.synthesizeClass(factory);
+ result.put(group, lambdaGroupClass);
+
+ // We have to register this new class as a subtype of object.
+ appInfo.registerNewType(lambdaGroupClass.type, lambdaGroupClass.superType);
}
return result;
}
@@ -281,11 +288,8 @@
Entry<LambdaGroupId, LambdaGroup> group = iterator.next();
if (group.getValue().isTrivial()) {
iterator.remove();
- List<LambdaInfo> lambdas = group.getValue().lambdas();
- assert lambdas.size() < 2;
- for (LambdaInfo lambda : lambdas) {
- this.lambdas.remove(lambda.clazz.type);
- }
+ assert group.getValue().size() < 2;
+ group.getValue().forEachLambda(info -> this.lambdas.remove(info.clazz.type));
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
new file mode 100644
index 0000000..a5b6793
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.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.ir.optimize.lambda.kotlin;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
+import com.google.common.collect.Lists;
+import java.util.List;
+
+final class ClassInitializerSourceCode extends SyntheticSourceCode {
+ private final DexItemFactory factory;
+ private final KotlinLambdaGroup group;
+
+ ClassInitializerSourceCode(DexItemFactory factory, KotlinLambdaGroup group) {
+ super(null, factory.createProto(factory.voidType));
+ this.factory = factory;
+ this.group = group;
+ }
+
+ @Override
+ protected void prepareInstructions() {
+ DexType groupClassType = group.getGroupClassType();
+ DexMethod lambdaConstructorMethod = factory.createMethod(groupClassType,
+ factory.createProto(factory.voidType, factory.intType), factory.constructorMethodName);
+
+ int instance = nextRegister(ValueType.OBJECT);
+ int lambdaId = nextRegister(ValueType.INT);
+ List<ValueType> argTypes = Lists.newArrayList(ValueType.OBJECT, ValueType.INT);
+ List<Integer> argRegisters = Lists.newArrayList(instance, lambdaId);
+
+ group.forEachLambda(info -> {
+ DexType lambda = info.clazz.type;
+ if (group.isSingletonLambda(lambda)) {
+ int id = group.lambdaId(lambda);
+ add(builder -> builder.addNewInstance(instance, groupClassType));
+ add(builder -> builder.addConst(ValueType.INT, lambdaId, id));
+ add(builder -> builder.addInvoke(Type.DIRECT,
+ lambdaConstructorMethod, lambdaConstructorMethod.proto, argTypes, argRegisters));
+ add(builder -> builder.addStaticPut(
+ instance, group.getSingletonInstanceField(factory, id)));
+ }
+ });
+
+ assert this.nextInstructionIndex() > 0 : "no single field initialized";
+ add(IRBuilder::addReturn);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
new file mode 100644
index 0000000..b4a1865
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
@@ -0,0 +1,212 @@
+// 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.optimize.lambda.kotlin;
+
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexClass;
+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.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
+import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.collect.Lists;
+import java.util.function.IntFunction;
+
+// Represents a j-style lambda group created to combine several lambda classes
+// generated by kotlin compiler for kotlin lambda expressions passed to java receivers,
+// like:
+//
+// --- Java --------------------------------------------------------------
+// public static void acceptString(Supplier<String> s) {
+// // ...
+// }
+// -----------------------------------------------------------------------
+// acceptString({ "A" })
+// -----------------------------------------------------------------------
+//
+// Regular stateless j-style lambda class structure looks like below:
+// NOTE: stateless j-style lambdas do not always have INSTANCE field.
+//
+// -----------------------------------------------------------------------------------------------
+// signature <T:Ljava/lang/Object;>Ljava/lang/Object;
+// Ljava/util/function/Supplier<Ljava/lang/String;>;
+// final class lambdas/LambdasKt$foo$4 implements java/util/function/Supplier {
+//
+// public synthetic bridge get()Ljava/lang/Object;
+//
+// public final get()Ljava/lang/String;
+// @Lorg/jetbrains/annotations/NotNull;() // invisible
+//
+// <init>()V
+//
+// public final static Llambdas/LambdasKt$foo$4; INSTANCE
+//
+// static <clinit>()V
+//
+// OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
+// final static INNERCLASS lambdas/LambdasKt$foo$4 null null
+// }
+// -----------------------------------------------------------------------------------------------
+//
+// Regular stateful j-style lambda class structure looks like below:
+//
+// -----------------------------------------------------------------------------------------------
+// signature <T:Ljava/lang/Object;>
+// Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/String;>;
+// declaration: lambdas/LambdasKt$foo$5<T> implements java.util.function.Supplier<java.lang.String>
+// final class lambdas/LambdasKt$foo$5 implements java/util/function/Supplier {
+//
+// public synthetic bridge get()Ljava/lang/Object;
+//
+// public final get()Ljava/lang/String;
+// @Lorg/jetbrains/annotations/NotNull;() // invisible
+//
+// <init>(Ljava/lang/String;I)V
+//
+// final synthetic Ljava/lang/String; $m
+// final synthetic I $v
+//
+// OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
+// final static INNERCLASS lambdas/LambdasKt$foo$5 null null
+// }
+// -----------------------------------------------------------------------------------------------
+//
+// Key j-style lambda class details:
+// - extends java.lang.Object
+// - implements *any* functional interface (Kotlin does not seem to support scenarios when
+// lambda can implement multiple interfaces).
+// - lambda class is created as an anonymous inner class
+// - lambda class carries generic signature and kotlin metadata attribute
+// - class instance fields represent captured values and have an instance constructor
+// with matching parameters initializing them (see the second class above)
+// - stateless lambda *may* be implemented as a singleton with a static field storing the
+// only instance and initialized in static class constructor (see the first class above)
+// - main lambda method usually matches an exact lambda signature and may have
+// generic signature attribute and nullability parameter annotations
+// - optional bridge method created to satisfy interface implementation and
+// forwarding call to lambda main method
+//
+final class JStyleLambdaGroup extends KotlinLambdaGroup {
+ private JStyleLambdaGroup(GroupId id) {
+ super(id);
+ }
+
+ @Override
+ protected LambdaGroupClassBuilder getBuilder(DexItemFactory factory) {
+ return new ClassBuilder(factory, "java-style lambda group");
+ }
+
+ @Override
+ public ThrowingConsumer<DexClass, LambdaStructureError> lambdaClassValidator(
+ Kotlin kotlin, AppInfoWithSubtyping appInfo) {
+ return new ClassValidator(kotlin, appInfo);
+ }
+
+ @Override
+ protected String getGroupSuffix() {
+ return "js$";
+ }
+
+ // Specialized group id.
+ final static class GroupId extends KotlinLambdaGroupId {
+ GroupId(String capture, DexType iface,
+ String pkg, String signature, DexEncodedMethod mainMethod,
+ InnerClassAttribute inner, EnclosingMethodAttribute enclosing) {
+ super(capture, iface, pkg, signature, mainMethod, inner, enclosing);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof GroupId && computeEquals((KotlinLambdaGroupId) obj);
+ }
+
+ @Override
+ String getLambdaKindDescriptor() {
+ return "Kotlin j-style lambda group";
+ }
+
+ @Override
+ public LambdaGroup createGroup() {
+ return new JStyleLambdaGroup(this);
+ }
+ }
+
+ // Specialized class validator.
+ private class ClassValidator extends KotlinLambdaClassValidator {
+ ClassValidator(Kotlin kotlin, AppInfoWithSubtyping appInfo) {
+ super(kotlin, JStyleLambdaGroup.this, appInfo);
+ }
+
+ @Override
+ int getInstanceInitializerSize(DexEncodedField[] captures) {
+ return captures.length + 2;
+ }
+
+ @Override
+ int validateInstanceInitializerEpilogue(
+ com.android.tools.r8.code.Instruction[] instructions, int index)
+ throws LambdaStructureError {
+ if (!(instructions[index] instanceof com.android.tools.r8.code.InvokeDirect) ||
+ instructions[index].getMethod() != kotlin.factory.objectMethods.constructor) {
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
+ }
+ index++;
+ if (!(instructions[index] instanceof ReturnVoid)) {
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
+ }
+ return index + 1;
+ }
+ }
+
+ // Specialized class builder.
+ private final class ClassBuilder extends KotlinLambdaGroupClassBuilder<JStyleLambdaGroup> {
+ ClassBuilder(DexItemFactory factory, String origin) {
+ super(JStyleLambdaGroup.this, factory, origin);
+ }
+
+ @Override
+ protected DexType getSuperClassType() {
+ return factory.objectType;
+ }
+
+ @Override
+ SyntheticSourceCode createInstanceInitializerSourceCode(
+ DexType groupClassType, DexProto initializerProto) {
+ return new InstanceInitializerSourceCode(
+ factory, groupClassType, group.getLambdaIdField(factory),
+ id -> group.getCaptureField(factory, id), initializerProto);
+ }
+ }
+
+ // Specialized instance initializer code.
+ private static final class InstanceInitializerSourceCode
+ extends KotlinInstanceInitializerSourceCode {
+ private final DexMethod objectInitializer;
+
+ InstanceInitializerSourceCode(DexItemFactory factory, DexType lambdaGroupType,
+ DexField idField, IntFunction<DexField> fieldGenerator, DexProto proto) {
+ super(lambdaGroupType, idField, fieldGenerator, proto);
+ this.objectInitializer = factory.objectMethods.constructor;
+ }
+
+ @Override
+ void prepareSuperConstructorCall(int receiverRegister) {
+ add(builder -> builder.addInvoke(Type.DIRECT, objectInitializer, objectInitializer.proto,
+ Lists.newArrayList(ValueType.OBJECT), Lists.newArrayList(receiverRegister)));
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
new file mode 100644
index 0000000..f906137
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroupIdFactory.java
@@ -0,0 +1,69 @@
+// 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.optimize.lambda.kotlin;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.utils.InternalOptions;
+
+final class JStyleLambdaGroupIdFactory extends KotlinLambdaGroupIdFactory {
+ static final JStyleLambdaGroupIdFactory INSTANCE = new JStyleLambdaGroupIdFactory();
+
+ @Override
+ LambdaGroupId validateAndCreate(Kotlin kotlin, DexClass lambda, InternalOptions options)
+ throws LambdaStructureError {
+ boolean accessRelaxed = options.proguardConfiguration.isAccessModificationAllowed();
+
+ assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
+ assert lambda.getKotlinInfo().asSyntheticClass().isJavaStyleLambda();
+
+ checkAccessFlags("class access flags", lambda.accessFlags,
+ PUBLIC_LAMBDA_CLASS_FLAGS, LAMBDA_CLASS_FLAGS);
+
+ // Class and interface.
+ validateSuperclass(kotlin, lambda);
+ DexType iface = validateInterfaces(kotlin, lambda);
+
+ validateStaticFields(kotlin, lambda);
+ String captureSignature = validateInstanceFields(lambda, accessRelaxed);
+ validateDirectMethods(lambda);
+ DexEncodedMethod mainMethod = validateVirtualMethods(lambda);
+ String genericSignature = validateAnnotations(kotlin, lambda);
+ InnerClassAttribute innerClass = validateInnerClasses(lambda);
+
+ return new JStyleLambdaGroup.GroupId(captureSignature, iface,
+ accessRelaxed ? "" : lambda.type.getPackageDescriptor(),
+ genericSignature, mainMethod, innerClass, lambda.getEnclosingMethod());
+ }
+
+ @Override
+ void validateSuperclass(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
+ if (lambda.superType != kotlin.factory.objectType) {
+ throw new LambdaStructureError("implements " + lambda.superType.toSourceString() +
+ " instead of java.lang.Object");
+ }
+ }
+
+ @Override
+ DexType validateInterfaces(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
+ if (lambda.interfaces.size() == 0) {
+ throw new LambdaStructureError("does not implement any interfaces");
+ }
+ if (lambda.interfaces.size() > 1) {
+ throw new LambdaStructureError(
+ "implements more than one interface: " + lambda.interfaces.size());
+ }
+
+ // We don't validate that the interface is actually a functional interface,
+ // since it may be desugared, or optimized in any other way which should not
+ // affect lambda class merging.
+ return lambda.interfaces.values[0];
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
new file mode 100644
index 0000000..46c14e9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
@@ -0,0 +1,228 @@
+// 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.optimize.lambda.kotlin;
+
+import com.android.tools.r8.code.Const16;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ReturnVoid;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.DexClass;
+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.DexMethod;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
+import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.utils.ThrowingConsumer;
+import com.google.common.collect.Lists;
+import java.util.function.IntFunction;
+
+// Represents a k-style lambda group created to combine several lambda classes
+// generated by kotlin compiler for regular kotlin lambda expressions, like:
+//
+// -----------------------------------------------------------------------
+// fun foo(m: String, v: Int): () -> String {
+// val lambda: (String, Int) -> String = { s, i -> s.substring(i) }
+// return { "$m: $v" }
+// }
+// -----------------------------------------------------------------------
+//
+// Regular stateless k-style lambda class structure looks like below:
+// NOTE: stateless j-style lambdas do not always have INSTANCE field.
+//
+// -----------------------------------------------------------------------------------------------
+// // signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function2<
+// Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;>;
+// final class lambdas/LambdasKt$foo$lambda1$1
+// extends kotlin/jvm/internal/Lambda
+// implements kotlin/jvm/functions/Function2 {
+//
+// public synthetic bridge invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+//
+// public final invoke(Ljava/lang/String;I)Ljava/lang/String;
+// @Lorg/jetbrains/annotations/NotNull;() // invisible
+// @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
+//
+// <init>()V
+//
+// public final static Llambdas/LambdasKt$foo$lambda1$1; INSTANCE
+//
+// static <clinit>()V
+//
+// OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
+// final static INNERCLASS lambdas/LambdasKt$foo$lambda1$1 null null
+// }
+// -----------------------------------------------------------------------------------------------
+//
+// Regular stateful k-style lambda class structure looks like below:
+//
+// -----------------------------------------------------------------------------------------------
+// // signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function0<Ljava/lang/String;>;
+// final class lambdas/LambdasKt$foo$1
+// extends kotlin/jvm/internal/Lambda
+// implements kotlin/jvm/functions/Function0 {
+//
+// public synthetic bridge invoke()Ljava/lang/Object;
+//
+// public final invoke()Ljava/lang/String;
+// @Lorg/jetbrains/annotations/NotNull;() // invisible
+//
+// <init>(Ljava/lang/String;I)V
+//
+// final synthetic Ljava/lang/String; $m
+// final synthetic I $v
+//
+// OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
+// final static INNERCLASS lambdas/LambdasKt$foo$1 null null
+// }
+// -----------------------------------------------------------------------------------------------
+//
+// Key k-style lambda class details:
+// - extends kotlin.jvm.internal.Lambda
+// - implements one of kotlin.jvm.functions.Function0..Function22, or FunctionN
+// see: https://github.com/JetBrains/kotlin/blob/master/libraries/
+// stdlib/jvm/runtime/kotlin/jvm/functions/Functions.kt
+// and: https://github.com/JetBrains/kotlin/blob/master/spec-docs/function-types.md
+// - lambda class is created as an anonymous inner class
+// - lambda class carries generic signature and kotlin metadata attribute
+// - class instance fields represent captured values and have an instance constructor
+// with matching parameters initializing them (see the second class above)
+// - stateless lambda *may* be implemented as a singleton with a static field storing the
+// only instance and initialized in static class constructor (see the first class above)
+// - main lambda method usually matches an exact lambda signature and may have
+// generic signature attribute and nullability parameter annotations
+// - optional bridge method created to satisfy interface implementation and
+// forwarding call to lambda main method
+//
+final class KStyleLambdaGroup extends KotlinLambdaGroup {
+ private KStyleLambdaGroup(GroupId id) {
+ super(id);
+ }
+
+ @Override
+ protected LambdaGroupClassBuilder getBuilder(DexItemFactory factory) {
+ return new ClassBuilder(factory, "kotlin-style lambda group");
+ }
+
+ @Override
+ public ThrowingConsumer<DexClass, LambdaStructureError> lambdaClassValidator(
+ Kotlin kotlin, AppInfoWithSubtyping appInfo) {
+ return new ClassValidator(kotlin, appInfo);
+ }
+
+ @Override
+ protected String getGroupSuffix() {
+ return "ks$";
+ }
+
+ // Specialized group id.
+ final static class GroupId extends KotlinLambdaGroupId {
+ GroupId(String capture, DexType iface,
+ String pkg, String signature, DexEncodedMethod mainMethod,
+ InnerClassAttribute inner, EnclosingMethodAttribute enclosing) {
+ super(capture, iface, pkg, signature, mainMethod, inner, enclosing);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof GroupId && computeEquals((KotlinLambdaGroupId) obj);
+ }
+
+ @Override
+ String getLambdaKindDescriptor() {
+ return "Kotlin k-style lambda group";
+ }
+
+ @Override
+ public LambdaGroup createGroup() {
+ return new KStyleLambdaGroup(this);
+ }
+ }
+
+ // Specialized class validator.
+ private final class ClassValidator extends KotlinLambdaClassValidator {
+ ClassValidator(Kotlin kotlin, AppInfoWithSubtyping appInfo) {
+ super(kotlin, KStyleLambdaGroup.this, appInfo);
+ }
+
+ @Override
+ int getInstanceInitializerSize(DexEncodedField[] captures) {
+ return captures.length + 3;
+ }
+
+ @Override
+ int validateInstanceInitializerEpilogue(
+ com.android.tools.r8.code.Instruction[] instructions, int index)
+ throws LambdaStructureError {
+ if (!(instructions[index] instanceof Const4) &&
+ !(instructions[index] instanceof Const16)) {
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
+ }
+ index++;
+ if (!(instructions[index] instanceof com.android.tools.r8.code.InvokeDirect) ||
+ instructions[index].getMethod() != kotlin.functional.lambdaInitializerMethod) {
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
+ }
+ index++;
+ if (!(instructions[index] instanceof ReturnVoid)) {
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
+ }
+ return index + 1;
+ }
+ }
+
+ // Specialized class builder.
+ private final class ClassBuilder extends KotlinLambdaGroupClassBuilder<KStyleLambdaGroup> {
+ ClassBuilder(DexItemFactory factory, String origin) {
+ super(KStyleLambdaGroup.this, factory, origin);
+ }
+
+ @Override
+ protected DexType getSuperClassType() {
+ return factory.kotlin.functional.lambdaType;
+ }
+
+ @Override
+ SyntheticSourceCode createInstanceInitializerSourceCode(
+ DexType groupClassType, DexProto initializerProto) {
+ return new InstanceInitializerSourceCode(factory, groupClassType,
+ group.getLambdaIdField(factory), id -> group.getCaptureField(factory, id),
+ initializerProto, id.mainMethodProto.parameters.size());
+ }
+ }
+
+ // Specialized instance initializer code.
+ private final static class InstanceInitializerSourceCode
+ extends KotlinInstanceInitializerSourceCode {
+ private final int arity;
+ private final DexMethod lambdaInitializer;
+
+ InstanceInitializerSourceCode(DexItemFactory factory, DexType lambdaGroupType,
+ DexField idField, IntFunction<DexField> fieldGenerator, DexProto proto, int arity) {
+ super(lambdaGroupType, idField, fieldGenerator, proto);
+ this.arity = arity;
+ this.lambdaInitializer = factory.createMethod(factory.kotlin.functional.lambdaType,
+ factory.createProto(factory.voidType, factory.intType), factory.constructorMethodName);
+ }
+
+ @Override
+ void prepareSuperConstructorCall(int receiverRegister) {
+ int arityRegister = nextRegister(ValueType.INT);
+ add(builder -> builder.addConst(ValueType.INT, arityRegister, arity));
+ add(builder -> builder.addInvoke(Type.DIRECT, lambdaInitializer, lambdaInitializer.proto,
+ Lists.newArrayList(ValueType.OBJECT, ValueType.INT),
+ Lists.newArrayList(receiverRegister, arityRegister)));
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.java
new file mode 100644
index 0000000..36f9ecd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroupIdFactory.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.ir.optimize.lambda.kotlin;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.InnerClassAttribute;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaStructureError;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.utils.InternalOptions;
+
+final class KStyleLambdaGroupIdFactory extends KotlinLambdaGroupIdFactory {
+ static final KotlinLambdaGroupIdFactory INSTANCE = new KStyleLambdaGroupIdFactory();
+
+ @Override
+ LambdaGroupId validateAndCreate(Kotlin kotlin, DexClass lambda, InternalOptions options)
+ throws LambdaStructureError {
+ boolean accessRelaxed = options.proguardConfiguration.isAccessModificationAllowed();
+
+ assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
+ assert lambda.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda();
+
+ checkAccessFlags("class access flags", lambda.accessFlags,
+ PUBLIC_LAMBDA_CLASS_FLAGS, LAMBDA_CLASS_FLAGS);
+
+ // Class and interface.
+ validateSuperclass(kotlin, lambda);
+ DexType iface = validateInterfaces(kotlin, lambda);
+
+ validateStaticFields(kotlin, lambda);
+ String captureSignature = validateInstanceFields(lambda, accessRelaxed);
+ validateDirectMethods(lambda);
+ DexEncodedMethod mainMethod = validateVirtualMethods(lambda);
+ String genericSignature = validateAnnotations(kotlin, lambda);
+ InnerClassAttribute innerClass = validateInnerClasses(lambda);
+
+ return new KStyleLambdaGroup.GroupId(captureSignature, iface,
+ accessRelaxed ? "" : lambda.type.getPackageDescriptor(),
+ genericSignature, mainMethod, innerClass, lambda.getEnclosingMethod());
+ }
+
+ @Override
+ void validateSuperclass(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
+ if (lambda.superType != kotlin.functional.lambdaType) {
+ throw new LambdaStructureError("implements " + lambda.superType.toSourceString() +
+ " instead of kotlin.jvm.internal.Lambda");
+ }
+ }
+
+ @Override
+ DexType validateInterfaces(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
+ if (lambda.interfaces.size() == 0) {
+ throw new LambdaStructureError("does not implement any interfaces");
+ }
+ if (lambda.interfaces.size() > 1) {
+ throw new LambdaStructureError(
+ "implements more than one interface: " + lambda.interfaces.size());
+ }
+ DexType iface = lambda.interfaces.values[0];
+ if (!kotlin.functional.isFunctionInterface(iface)) {
+ throw new LambdaStructureError("implements " + iface.toSourceString() +
+ " instead of kotlin functional interface.");
+ }
+ return iface;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinInstanceInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinInstanceInitializerSourceCode.java
new file mode 100644
index 0000000..396cdca
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinInstanceInitializerSourceCode.java
@@ -0,0 +1,47 @@
+// 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.optimize.lambda.kotlin;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.IRBuilder;
+import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
+import java.util.function.IntFunction;
+
+abstract class KotlinInstanceInitializerSourceCode extends SyntheticSourceCode {
+ private final DexField idField;
+ private final IntFunction<DexField> fieldGenerator;
+
+ KotlinInstanceInitializerSourceCode(DexType lambdaGroupType,
+ DexField idField, IntFunction<DexField> fieldGenerator, DexProto proto) {
+ super(lambdaGroupType, proto);
+ this.idField = idField;
+ this.fieldGenerator = fieldGenerator;
+ }
+
+ @Override
+ protected void prepareInstructions() {
+ int receiverRegister = getReceiverRegister();
+
+ // Initialize lambda id field.
+ add(builder -> builder.addInstancePut(getParamRegister(0), receiverRegister, idField));
+
+ // Initialize capture values.
+ DexType[] values = proto.parameters.values;
+ for (int i = 1; i < values.length; i++) {
+ int index = i;
+ add(builder -> builder.addInstancePut(
+ getParamRegister(index), receiverRegister, fieldGenerator.apply(index - 1)));
+ }
+
+ // Call superclass constructor.
+ prepareSuperConstructorCall(receiverRegister);
+
+ add(IRBuilder::addReturn);
+ }
+
+ abstract void prepareSuperConstructorCall(int receiverRegister);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaClassValidator.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java
similarity index 67%
rename from src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaClassValidator.java
rename to src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java
index ffd0f7d..3410703 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaClassValidator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaClassValidator.java
@@ -2,11 +2,10 @@
// 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.optimize.lambda.kstyle;
+package com.android.tools.r8.ir.optimize.lambda.kotlin;
-import com.android.tools.r8.code.Const16;
-import com.android.tools.r8.code.Const4;
import com.android.tools.r8.code.Format22c;
+import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.Iput;
import com.android.tools.r8.code.IputBoolean;
import com.android.tools.r8.code.IputByte;
@@ -33,7 +32,7 @@
// Encapsulates the logic of deep-checking of the lambda class assumptions.
//
-// For k-style lambdas we only check the code of class and instance
+// For k- and j-style lambdas we only check the code of class and instance
// initializers to ensure that their code performs no unexpected actions:
//
// (a) Class initializer is only present for stateless lambdas and does
@@ -42,21 +41,33 @@
//
// (b) Instance initializers stores all captured values in proper capture
// fields and calls the super constructor passing arity to it.
-final class KStyleLambdaClassValidator implements ThrowingConsumer<DexClass, LambdaStructureError> {
- private final Kotlin kotlin;
- private final KStyleLambdaGroup group;
+abstract class KotlinLambdaClassValidator
+ implements ThrowingConsumer<DexClass, LambdaStructureError> {
+
+ static final String LAMBDA_INIT_CODE_VERIFICATION_FAILED =
+ "instance initializer code verification failed";
+ private static final String LAMBDA_CLINIT_CODE_VERIFICATION_FAILED =
+ "static initializer code verification failed";
+
+ final Kotlin kotlin;
+ private final KotlinLambdaGroup group;
private final AppInfoWithSubtyping appInfo;
- KStyleLambdaClassValidator(Kotlin kotlin, KStyleLambdaGroup group, AppInfoWithSubtyping appInfo) {
+ KotlinLambdaClassValidator(Kotlin kotlin, KotlinLambdaGroup group, AppInfoWithSubtyping appInfo) {
this.kotlin = kotlin;
this.group = group;
this.appInfo = appInfo;
}
+ // Report a structure error.
+ final LambdaStructureError structureError(String s) {
+ return new LambdaStructureError(s, false);
+ }
+
@Override
public void accept(DexClass lambda) throws LambdaStructureError {
if (!CaptureSignature.getCaptureSignature(lambda.instanceFields()).equals(group.id().capture)) {
- throw new LambdaStructureError("capture signature was modified");
+ throw structureError("capture signature was modified");
}
DexEncodedMethod classInitializer = null;
@@ -67,27 +78,27 @@
// same methods dispatching on lambda id to proper code.
if (method.isClassInitializer()) {
Code code = method.getCode();
- if (!group.isStateless()) {
- throw new LambdaStructureError("static initializer on stateful lambda");
+ if (!(group.isStateless() && group.isSingletonLambda(lambda.type))) {
+ throw structureError("static initializer on non-singleton lambda");
}
- if (classInitializer != null || code == null || !code.isDexCode() ||
- !validateStatelessLambdaClassInitializer(lambda, code.asDexCode())) {
- throw new LambdaStructureError("static initializer code verification failed");
+ if (classInitializer != null || code == null || !code.isDexCode()) {
+ throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
}
+ validateStatelessLambdaClassInitializer(lambda, code.asDexCode());
classInitializer = method;
} else if (method.isInstanceInitializer()) {
Code code = method.getCode();
- if (instanceInitializer != null || code == null || !code.isDexCode() ||
- !validateInstanceInitializer(lambda, code.asDexCode())) {
- throw new LambdaStructureError("instance initializer code verification failed");
+ if (instanceInitializer != null || code == null || !code.isDexCode()) {
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
+ validateInstanceInitializer(lambda, code.asDexCode());
instanceInitializer = method;
}
}
- if (group.isStateless() && (classInitializer == null)) {
- throw new LambdaStructureError("missing static initializer on stateless lambda");
+ if (group.isStateless() && group.isSingletonLambda(lambda.type) && (classInitializer == null)) {
+ throw structureError("missing static initializer on singleton lambda");
}
// This check is actually not required for lambda class merging, we only have to do
@@ -101,23 +112,38 @@
"L" + group.getTypePackage() + "-$$LambdaGroup$XXXX;");
for (DexEncodedMethod method : lambda.virtualMethods()) {
if (!method.isInliningCandidate(fakeLambdaGroupType, Reason.SIMPLE, appInfo)) {
- throw new LambdaStructureError("method " + method.method.toSourceString() +
+ throw structureError("method " + method.method.toSourceString() +
" is not inline-able into lambda group class");
}
}
}
- private boolean validateInstanceInitializer(DexClass lambda, Code code) {
+ abstract int getInstanceInitializerSize(DexEncodedField[] captures);
+
+ abstract int validateInstanceInitializerEpilogue(
+ com.android.tools.r8.code.Instruction[] instructions, int index) throws LambdaStructureError;
+
+ private void validateInstanceInitializer(DexClass lambda, Code code)
+ throws LambdaStructureError {
DexEncodedField[] captures = lambda.instanceFields();
com.android.tools.r8.code.Instruction[] instructions = code.asDexCode().instructions;
int index = 0;
- if (instructions.length != (captures.length + 3)) {
- return false;
+ if (instructions.length != getInstanceInitializerSize(captures)) {
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
// Capture field assignments: go through captured fields in assumed order
// and ensure they are assigned values from appropriate parameters.
+ index = validateInstanceInitializerParameterMapping(captures, instructions, index);
+
+ // Check the constructor epilogue: a call to superclass constructor.
+ index = validateInstanceInitializerEpilogue(instructions, index);
+ assert index == instructions.length;
+ }
+
+ private int validateInstanceInitializerParameterMapping(DexEncodedField[] captures,
+ Instruction[] instructions, int index) throws LambdaStructureError {
int wideFieldsSeen = 0;
for (DexEncodedField field : captures) {
switch (field.field.type.toShorty()) {
@@ -125,7 +151,7 @@
if (!(instructions[index] instanceof IputBoolean) ||
(instructions[index].getField() != field.field) ||
(((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
- return false;
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
break;
@@ -133,7 +159,7 @@
if (!(instructions[index] instanceof IputByte) ||
(instructions[index].getField() != field.field) ||
(((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
- return false;
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
break;
@@ -141,7 +167,7 @@
if (!(instructions[index] instanceof IputShort) ||
(instructions[index].getField() != field.field) ||
(((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
- return false;
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
break;
@@ -149,7 +175,7 @@
if (!(instructions[index] instanceof IputChar) ||
(instructions[index].getField() != field.field) ||
(((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
- return false;
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
break;
@@ -158,7 +184,7 @@
if (!(instructions[index] instanceof Iput) ||
(instructions[index].getField() != field.field) ||
(((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
- return false;
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
break;
@@ -167,7 +193,7 @@
if (!(instructions[index] instanceof IputWide) ||
(instructions[index].getField() != field.field) ||
(((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
- return false;
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
wideFieldsSeen++;
break;
@@ -176,7 +202,7 @@
if (!(instructions[index] instanceof IputObject) ||
(instructions[index].getField() != field.field) ||
(((Format22c) instructions[index]).A != (index + 1 + wideFieldsSeen))) {
- return false;
+ throw structureError(LAMBDA_INIT_CODE_VERIFICATION_FAILED);
}
break;
@@ -185,49 +211,31 @@
}
index++;
}
-
- // Epilogue.
- if (!(instructions[index] instanceof Const4) &&
- !(instructions[index] instanceof Const16)) {
- return false;
- }
- index++;
- if (!(instructions[index] instanceof com.android.tools.r8.code.InvokeDirect) ||
- instructions[index].getMethod() != kotlin.functional.lambdaInitializerMethod) {
- return false;
- }
- index++;
- if (!(instructions[index] instanceof ReturnVoid)) {
- return false;
- }
- index++;
-
- assert index == instructions.length;
- return true;
+ return index;
}
- private boolean validateStatelessLambdaClassInitializer(DexClass lambda, Code code) {
- assert group.isStateless();
+ private void validateStatelessLambdaClassInitializer(DexClass lambda, Code code)
+ throws LambdaStructureError {
+ assert group.isStateless() && group.isSingletonLambda(lambda.type);
com.android.tools.r8.code.Instruction[] instructions = code.asDexCode().instructions;
if (instructions.length != 4) {
- return false;
+ throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
}
if (!(instructions[0] instanceof com.android.tools.r8.code.NewInstance) ||
((com.android.tools.r8.code.NewInstance) instructions[0]).getType() != lambda.type) {
- return false;
+ throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
}
if (!(instructions[1] instanceof com.android.tools.r8.code.InvokeDirect) ||
!isLambdaInitializerMethod(lambda, instructions[1].getMethod())) {
- return false;
+ throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
}
if (!(instructions[2] instanceof SputObject) ||
!isLambdaSingletonField(lambda, instructions[2].getField())) {
- return false;
+ throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
}
if (!(instructions[3] instanceof ReturnVoid)) {
- return false;
+ throw structureError(LAMBDA_CLINIT_CODE_VERIFICATION_FAILED);
}
- return true;
}
private boolean isLambdaSingletonField(DexClass lambda, DexField field) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleConstants.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaConstants.java
similarity index 95%
rename from src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleConstants.java
rename to src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaConstants.java
index 619fa67..9ac5ca7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleConstants.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaConstants.java
@@ -2,14 +2,14 @@
// 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.optimize.lambda.kstyle;
+package com.android.tools.r8.ir.optimize.lambda.kotlin;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.FieldAccessFlags;
import com.android.tools.r8.graph.MethodAccessFlags;
-interface KStyleConstants {
+interface KotlinLambdaConstants {
// Default lambda class flags.
ClassAccessFlags LAMBDA_CLASS_FLAGS =
ClassAccessFlags.fromDexAccessFlags(Constants.ACC_FINAL);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroup.java
new file mode 100644
index 0000000..fc3f2bd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroup.java
@@ -0,0 +1,85 @@
+// 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.optimize.lambda.kotlin;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
+import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
+import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
+
+// Represents a lambda group created to combine several lambda classes generated
+// by kotlin compiler for either regular kotlin lambda expressions (k-style lambdas)
+// or lambda expressions created to implement java SAM interface.
+abstract class KotlinLambdaGroup extends LambdaGroup {
+ private final Strategy strategy = new KotlinLambdaGroupCodeStrategy(this);
+
+ KotlinLambdaGroup(LambdaGroupId id) {
+ super(id);
+ }
+
+ final KotlinLambdaGroupId id() {
+ return (KotlinLambdaGroupId) id;
+ }
+
+ final boolean isStateless() {
+ return id().capture.isEmpty();
+ }
+
+ final boolean hasAnySingletons() {
+ assert isStateless();
+ return anyLambda(info -> this.isSingletonLambda(info.clazz.type));
+ }
+
+ final boolean isSingletonLambda(DexType lambda) {
+ assert isStateless();
+ return lambdaSingletonField(lambda) != null;
+ }
+
+ // Field referencing singleton instance for a lambda with specified id.
+ final DexField getSingletonInstanceField(DexItemFactory factory, int id) {
+ return factory.createField(this.getGroupClassType(),
+ this.getGroupClassType(), factory.createString("INSTANCE$" + id));
+ }
+
+ @Override
+ protected String getTypePackage() {
+ String pkg = id().pkg;
+ return pkg.isEmpty() ? "" : (pkg + "/");
+ }
+
+ final DexProto createConstructorProto(DexItemFactory factory) {
+ String capture = id().capture;
+ DexType[] newParameters = new DexType[capture.length() + 1];
+ newParameters[0] = factory.intType; // Lambda id.
+ for (int i = 0; i < capture.length(); i++) {
+ newParameters[i + 1] = CaptureSignature.fieldType(factory, capture, i);
+ }
+ return factory.createProto(factory.voidType, newParameters);
+ }
+
+ final DexField getLambdaIdField(DexItemFactory factory) {
+ return factory.createField(this.getGroupClassType(), factory.intType, "$id$");
+ }
+
+ final int mapFieldIntoCaptureIndex(DexType lambda, DexField field) {
+ return CaptureSignature.mapFieldIntoCaptureIndex(
+ id().capture, lambdaCaptureFields(lambda), field);
+ }
+
+ final DexField getCaptureField(DexItemFactory factory, int index) {
+ assert index >= 0 && index < id().capture.length();
+ return factory.createField(this.getGroupClassType(),
+ CaptureSignature.fieldType(factory, id().capture, index), "$capture$" + index);
+ }
+
+ @Override
+ public Strategy getCodeStrategy() {
+ return strategy;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
similarity index 76%
rename from src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupClassBuilder.java
rename to src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
index 59de393..d441a00 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
@@ -2,7 +2,7 @@
// 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.optimize.lambda.kstyle;
+package com.android.tools.r8.ir.optimize.lambda.kotlin;
import com.android.tools.r8.graph.ClassAccessFlags;
import com.android.tools.r8.graph.DexAnnotation;
@@ -19,9 +19,9 @@
import com.android.tools.r8.graph.EnclosingMethodAttribute;
import com.android.tools.r8.graph.InnerClassAttribute;
import com.android.tools.r8.graph.MethodAccessFlags;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup.LambdaInfo;
import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.Collections;
@@ -30,21 +30,19 @@
import java.util.Map;
import java.util.Map.Entry;
-// Builds components of k-style lambda group class.
-final class KStyleLambdaGroupClassBuilder
- extends LambdaGroupClassBuilder<KStyleLambdaGroup> implements KStyleConstants {
+// Builds components of kotlin lambda group class.
+abstract class KotlinLambdaGroupClassBuilder<T extends KotlinLambdaGroup>
+ extends LambdaGroupClassBuilder<T> implements KotlinLambdaConstants {
- private final KStyleLambdaGroupId id;
+ final KotlinLambdaGroupId id;
- KStyleLambdaGroupClassBuilder(DexItemFactory factory, KStyleLambdaGroup group, String origin) {
+ KotlinLambdaGroupClassBuilder(T group, DexItemFactory factory, String origin) {
super(group, factory, origin);
this.id = group.id();
}
- @Override
- protected DexType getSuperClassType() {
- return factory.kotlin.functional.lambdaType;
- }
+ abstract SyntheticSourceCode createInstanceInitializerSourceCode(
+ DexType groupClassType, DexProto initializerProto);
// Always generate public final classes.
@Override
@@ -61,8 +59,7 @@
// Take the attribute from the group, if exists.
@Override
protected List<InnerClassAttribute> buildInnerClasses() {
- return id.innerClassAccess == KStyleLambdaGroupId.MISSING_INNER_CLASS_ATTRIBUTE
- ? Collections.emptyList()
+ return !id.hasInnerClassAttribute() ? Collections.emptyList()
: Lists.newArrayList(new InnerClassAttribute(
id.innerClassAccess, group.getGroupClassType(), null, null));
}
@@ -118,7 +115,7 @@
isMainMethod ? id.mainMethodAnnotations : DexAnnotationSet.empty(),
isMainMethod ? id.mainMethodParamAnnotations : DexAnnotationSetRefList.empty(),
new SynthesizedCode(
- new VirtualMethodSourceCode(factory, group.getGroupClassType(),
+ new KotlinLambdaVirtualMethodSourceCode(factory, group.getGroupClassType(),
methodProto, group.getLambdaIdField(factory), implMethods))));
}
}
@@ -132,37 +129,30 @@
// fact that corresponding lambda does not have a virtual method with this signature.
private Map<DexString, Map<DexProto, List<DexEncodedMethod>>> collectVirtualMethods() {
Map<DexString, Map<DexProto, List<DexEncodedMethod>>> methods = new LinkedHashMap<>();
- assert lambdaIdsOrdered();
- for (LambdaInfo lambda : lambdas) {
- for (DexEncodedMethod method : lambda.clazz.virtualMethods()) {
+ int size = group.size();
+ group.forEachLambda(info -> {
+ for (DexEncodedMethod method : info.clazz.virtualMethods()) {
List<DexEncodedMethod> list = methods
.computeIfAbsent(method.method.name,
k -> new LinkedHashMap<>())
.computeIfAbsent(method.method.proto,
- k -> Lists.newArrayList(Collections.nCopies(lambdas.size(), null)));
- assert list.get(lambda.id) == null;
- list.set(lambda.id, method);
+ k -> Lists.newArrayList(Collections.nCopies(size, null)));
+ assert list.get(info.id) == null;
+ list.set(info.id, method);
}
- }
+ });
return methods;
}
- private boolean lambdaIdsOrdered() {
- for (int i = 0; i < lambdas.size(); i++) {
- assert lambdas.get(i).id == i;
- }
- return true;
- }
-
@Override
protected DexEncodedMethod[] buildDirectMethods() {
// We only build an instance initializer and optional class
// initializer for stateless lambdas.
- boolean statelessLambda = group.isStateless();
+ boolean needsSingletonInstances = group.isStateless() && group.hasAnySingletons();
DexType groupClassType = group.getGroupClassType();
- DexEncodedMethod[] result = new DexEncodedMethod[statelessLambda ? 2 : 1];
+ DexEncodedMethod[] result = new DexEncodedMethod[needsSingletonInstances ? 2 : 1];
// Instance initializer mapping parameters into capture fields.
DexProto initializerProto = group.createConstructorProto(factory);
result[0] = new DexEncodedMethod(
@@ -170,13 +160,10 @@
CONSTRUCTOR_FLAGS_RELAXED, // always create access-relaxed constructor.
DexAnnotationSet.empty(),
DexAnnotationSetRefList.empty(),
- new SynthesizedCode(
- new InstanceInitializerSourceCode(factory, groupClassType,
- group.getLambdaIdField(factory), id -> group.getCaptureField(factory, id),
- initializerProto, id.mainMethodProto.parameters.size())));
+ new SynthesizedCode(createInstanceInitializerSourceCode(groupClassType, initializerProto)));
// Static class initializer for stateless lambdas.
- if (statelessLambda) {
+ if (needsSingletonInstances) {
result[1] = new DexEncodedMethod(
factory.createMethod(groupClassType,
factory.createProto(factory.voidType),
@@ -184,10 +171,7 @@
CLASS_INITIALIZER_FLAGS,
DexAnnotationSet.empty(),
DexAnnotationSetRefList.empty(),
- new SynthesizedCode(
- new ClassInitializerSourceCode(
- factory, groupClassType, lambdas.size(),
- id -> group.getSingletonInstanceField(factory, id))));
+ new SynthesizedCode(new ClassInitializerSourceCode(factory, group)));
}
return result;
@@ -216,15 +200,16 @@
if (!group.isStateless()) {
return DexEncodedField.EMPTY_ARRAY;
}
-
- // One field for each stateless lambda in the group.
- int size = lambdas.size();
- DexEncodedField[] result = new DexEncodedField[size];
- for (int id = 0; id < size; id++) {
- result[id] = new DexEncodedField(group.getSingletonInstanceField(factory, id),
- SINGLETON_FIELD_FLAGS, DexAnnotationSet.empty(), DexValueNull.NULL);
- }
- return result;
+ // One field for each singleton lambda in the group.
+ List<DexEncodedField> result = new ArrayList<>(group.size());
+ group.forEachLambda(info -> {
+ if (group.isSingletonLambda(info.clazz.type)) {
+ result.add(new DexEncodedField(group.getSingletonInstanceField(factory, info.id),
+ SINGLETON_FIELD_FLAGS, DexAnnotationSet.empty(), DexValueNull.NULL));
+ }
+ });
+ assert result.isEmpty() == !group.hasAnySingletons();
+ return result.toArray(new DexEncodedField[result.size()]);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupCodeStrategy.java
rename to src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
index dd066b6..d42ecf6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupCodeStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
@@ -2,7 +2,7 @@
// 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.optimize.lambda.kstyle;
+package com.android.tools.r8.ir.optimize.lambda.kotlin;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexField;
@@ -30,11 +30,11 @@
import java.util.ArrayList;
import java.util.List;
-// Defines the code processing strategy for k-style lambdas.
-final class KStyleLambdaGroupCodeStrategy implements Strategy {
- private final KStyleLambdaGroup group;
+// Defines the code processing strategy for kotlin lambdas.
+final class KotlinLambdaGroupCodeStrategy implements Strategy {
+ private final KotlinLambdaGroup group;
- KStyleLambdaGroupCodeStrategy(KStyleLambdaGroup group) {
+ KotlinLambdaGroupCodeStrategy(KotlinLambdaGroup group) {
this.group = group;
}
@@ -83,7 +83,7 @@
@Override
public boolean isValidNewInstance(CodeProcessor context, NewInstance invoke) {
// Only valid for stateful lambdas.
- return !group.isStateless();
+ return !(group.isStateless() && group.isSingletonLambda(invoke.clazz));
}
@Override
@@ -95,9 +95,10 @@
DexMethod method = invoke.getInvokedMethod();
DexType lambda = method.holder;
assert group.containsLambda(lambda);
- // Allow calls to a constructor from other classes if the lambda is stateful,
+ // Allow calls to a constructor from other classes if the lambda is singleton,
// otherwise allow such a call only from the same class static initializer.
- return (group.isStateless() == (context.method.method.holder == lambda)) &&
+ boolean isSingletonLambda = group.isStateless() && group.isSingletonLambda(lambda);
+ return (isSingletonLambda == (context.method.method.holder == lambda)) &&
invoke.isInvokeDirect() &&
context.factory.isConstructor(method) &&
CaptureSignature.getCaptureSignature(method.proto.parameters).equals(group.id().capture);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupId.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
similarity index 89%
rename from src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupId.java
rename to src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
index 6b6f73b..0540bef 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupId.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupId.java
@@ -2,7 +2,7 @@
// 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.optimize.lambda.kstyle;
+package com.android.tools.r8.ir.optimize.lambda.kotlin;
import com.android.tools.r8.graph.DexAnnotationSet;
import com.android.tools.r8.graph.DexAnnotationSetRefList;
@@ -15,8 +15,8 @@
import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
import com.android.tools.r8.ir.optimize.lambda.LambdaGroupId;
-final class KStyleLambdaGroupId implements LambdaGroupId {
- public static final int MISSING_INNER_CLASS_ATTRIBUTE = -1;
+abstract class KotlinLambdaGroupId implements LambdaGroupId {
+ private static final int MISSING_INNER_CLASS_ATTRIBUTE = -1;
private final int hash;
@@ -50,7 +50,7 @@
// access from InnerClassAttribute.
final int innerClassAccess;
- KStyleLambdaGroupId(String capture, DexType iface, String pkg, String signature,
+ KotlinLambdaGroupId(String capture, DexType iface, String pkg, String signature,
DexEncodedMethod mainMethod, InnerClassAttribute inner, EnclosingMethodAttribute enclosing) {
assert capture != null && iface != null && pkg != null && mainMethod != null;
assert inner == null || (inner.isAnonymous() && inner.getOuter() == null);
@@ -67,8 +67,12 @@
this.hash = computeHashCode();
}
+ final boolean hasInnerClassAttribute() {
+ return innerClassAccess != MISSING_INNER_CLASS_ATTRIBUTE;
+ }
+
@Override
- public int hashCode() {
+ public final int hashCode() {
return hash;
}
@@ -87,11 +91,9 @@
}
@Override
- public boolean equals(Object obj) {
- if (!(obj instanceof KStyleLambdaGroupId)) {
- return false;
- }
- KStyleLambdaGroupId other = (KStyleLambdaGroupId) obj;
+ public abstract boolean equals(Object obj);
+
+ boolean computeEquals(KotlinLambdaGroupId other) {
return capture.equals(other.capture) &&
iface == other.iface &&
pkg.equals(other.pkg) &&
@@ -108,7 +110,7 @@
@Override
public String toString() {
- StringBuilder builder = new StringBuilder("Kotlin-style lambda group")
+ StringBuilder builder = new StringBuilder(getLambdaKindDescriptor())
.append("\n capture: ").append(capture)
.append("\n interface: ").append(iface.descriptor)
.append("\n package: ").append(pkg)
@@ -131,8 +133,8 @@
return builder.toString();
}
+ abstract String getLambdaKindDescriptor();
+
@Override
- public LambdaGroup createGroup() {
- return new KStyleLambdaGroup(this);
- }
+ public abstract LambdaGroup createGroup();
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
similarity index 71%
rename from src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupIdFactory.java
rename to src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index 7a516cd..f5698aa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -2,7 +2,7 @@
// 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.optimize.lambda.kstyle;
+package com.android.tools.r8.ir.optimize.lambda.kotlin;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AccessFlags;
@@ -19,8 +19,8 @@
import com.android.tools.r8.utils.InternalOptions;
import java.util.List;
-public final class KStyleLambdaGroupIdFactory implements KStyleConstants {
- private KStyleLambdaGroupIdFactory() {
+public abstract class KotlinLambdaGroupIdFactory implements KotlinLambdaConstants {
+ KotlinLambdaGroupIdFactory() {
}
// Creates a lambda group id for kotlin style lambda. Should never return null, if the lambda
@@ -32,26 +32,24 @@
// they may not be converted yet, we'll do that in KStyleLambdaClassValidator.
public static LambdaGroupId create(Kotlin kotlin, DexClass lambda, InternalOptions options)
throws LambdaStructureError {
- boolean accessRelaxed = options.proguardConfiguration.isAccessModificationAllowed();
- checkAccessFlags("class access flags", lambda.accessFlags,
- PUBLIC_LAMBDA_CLASS_FLAGS, LAMBDA_CLASS_FLAGS);
+ assert lambda.hasKotlinInfo() && lambda.getKotlinInfo().isSyntheticClass();
+ if (lambda.getKotlinInfo().asSyntheticClass().isKotlinStyleLambda()) {
+ return KStyleLambdaGroupIdFactory.INSTANCE.validateAndCreate(kotlin, lambda, options);
+ }
- validateStaticFields(kotlin, lambda);
- String captureSignature = validateInstanceFields(lambda, accessRelaxed);
- validateDirectMethods(lambda);
- DexEncodedMethod mainMethod = validateVirtualMethods(lambda);
- DexType iface = validateInterfaces(kotlin, lambda);
- String genericSignature = validateAnnotations(kotlin, lambda);
- InnerClassAttribute innerClass = validateInnerClasses(lambda);
-
- return new KStyleLambdaGroupId(captureSignature, iface,
- accessRelaxed ? "" : lambda.type.getPackageDescriptor(),
- genericSignature, mainMethod, innerClass, lambda.getEnclosingMethod());
+ assert lambda.getKotlinInfo().asSyntheticClass().isJavaStyleLambda();
+ return JStyleLambdaGroupIdFactory.INSTANCE.validateAndCreate(kotlin, lambda, options);
}
- private static DexEncodedMethod validateVirtualMethods(DexClass lambda)
- throws LambdaStructureError {
+ abstract LambdaGroupId validateAndCreate(Kotlin kotlin, DexClass lambda, InternalOptions options)
+ throws LambdaStructureError;
+
+ abstract void validateSuperclass(Kotlin kotlin, DexClass lambda) throws LambdaStructureError;
+
+ abstract DexType validateInterfaces(Kotlin kotlin, DexClass lambda) throws LambdaStructureError;
+
+ DexEncodedMethod validateVirtualMethods(DexClass lambda) throws LambdaStructureError {
DexEncodedMethod mainMethod = null;
for (DexEncodedMethod method : lambda.virtualMethods()) {
@@ -68,31 +66,28 @@
}
if (mainMethod == null) {
- throw new LambdaStructureError("no main method found");
+ // Missing main method may be a result of tree shaking.
+ throw new LambdaStructureError("no main method found", false);
}
return mainMethod;
}
- private static InnerClassAttribute validateInnerClasses(DexClass lambda)
- throws LambdaStructureError {
+ InnerClassAttribute validateInnerClasses(DexClass lambda) throws LambdaStructureError {
List<InnerClassAttribute> innerClasses = lambda.getInnerClasses();
- InnerClassAttribute innerClass = null;
if (innerClasses != null) {
for (InnerClassAttribute inner : innerClasses) {
if (inner.getInner() == lambda.type) {
- innerClass = inner;
- if (!innerClass.isAnonymous()) {
+ if (!inner.isAnonymous()) {
throw new LambdaStructureError("is not anonymous");
}
- return innerClass;
+ return inner;
}
}
}
return null;
}
- private static String validateAnnotations(Kotlin kotlin, DexClass lambda)
- throws LambdaStructureError {
+ String validateAnnotations(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
String signature = null;
if (!lambda.annotations.isEmpty()) {
for (DexAnnotation annotation : lambda.annotations.annotations) {
@@ -115,8 +110,7 @@
return signature;
}
- private static void validateStaticFields(Kotlin kotlin, DexClass lambda)
- throws LambdaStructureError {
+ void validateStaticFields(Kotlin kotlin, DexClass lambda) throws LambdaStructureError {
DexEncodedField[] staticFields = lambda.staticFields();
if (staticFields.length == 1) {
DexEncodedField field = staticFields[0];
@@ -135,30 +129,10 @@
} else if (staticFields.length > 1) {
throw new LambdaStructureError(
"only one static field max expected, found " + staticFields.length);
-
- } else if (lambda.instanceFields().length == 0) {
- throw new LambdaStructureError("stateless lambda without INSTANCE field");
}
}
- private static DexType validateInterfaces(Kotlin kotlin, DexClass lambda)
- throws LambdaStructureError {
- if (lambda.interfaces.size() == 0) {
- throw new LambdaStructureError("does not implement any interfaces");
- }
- if (lambda.interfaces.size() > 1) {
- throw new LambdaStructureError(
- "implements more than one interface: " + lambda.interfaces.size());
- }
- DexType iface = lambda.interfaces.values[0];
- if (!kotlin.functional.isFunctionInterface(iface)) {
- throw new LambdaStructureError("implements " + iface.toSourceString() +
- " instead of kotlin functional interface.");
- }
- return iface;
- }
-
- private static String validateInstanceFields(DexClass lambda, boolean accessRelaxed)
+ String validateInstanceFields(DexClass lambda, boolean accessRelaxed)
throws LambdaStructureError {
DexEncodedField[] instanceFields = lambda.instanceFields();
for (DexEncodedField field : instanceFields) {
@@ -169,7 +143,7 @@
return CaptureSignature.getCaptureSignature(instanceFields);
}
- private static void validateDirectMethods(DexClass lambda) throws LambdaStructureError {
+ void validateDirectMethods(DexClass lambda) throws LambdaStructureError {
DexEncodedMethod[] directMethods = lambda.directMethods();
for (DexEncodedMethod method : directMethods) {
if (method.isClassInitializer()) {
@@ -194,8 +168,12 @@
throw new LambdaStructureError("constructor parameters don't match captured values.");
}
for (int i = 0; i < parameters.length; i++) {
+ // Kotlin compiler sometimes reshuffles the parameters so that their order
+ // in the constructor don't match order of capture fields. We could add
+ // support for it, but it happens quite rarely so don't bother for now.
if (parameters[i] != instanceFields[i].field.type) {
- throw new LambdaStructureError("constructor parameters don't match captured values.");
+ throw new LambdaStructureError(
+ "constructor parameters don't match captured values.", false);
}
}
checkAccessFlags("unexpected constructor access flags",
@@ -208,8 +186,7 @@
}
}
- private static void checkDirectMethodAnnotations(DexEncodedMethod method)
- throws LambdaStructureError {
+ void checkDirectMethodAnnotations(DexEncodedMethod method) throws LambdaStructureError {
if (!method.annotations.isEmpty()) {
throw new LambdaStructureError("unexpected method annotations [" +
method.annotations.toSmaliString() + "] on " + method.method.toSourceString());
@@ -228,7 +205,7 @@
}
@SafeVarargs
- private static <T extends AccessFlags> void checkAccessFlags(
+ static <T extends AccessFlags> void checkAccessFlags(
String message, T actual, T... expected) throws LambdaStructureError {
for (T flag : expected) {
if (flag.equals(actual)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/VirtualMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
similarity index 94%
rename from src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/VirtualMethodSourceCode.java
rename to src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
index 8921299..1bcc2c7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/VirtualMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
@@ -2,7 +2,7 @@
// 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.optimize.lambda.kstyle;
+package com.android.tools.r8.ir.optimize.lambda.kotlin;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
@@ -17,12 +17,12 @@
import java.util.ArrayList;
import java.util.List;
-final class VirtualMethodSourceCode extends SyntheticSourceCode {
+final class KotlinLambdaVirtualMethodSourceCode extends SyntheticSourceCode {
private final DexItemFactory factory;
private final DexField idField;
private final List<DexEncodedMethod> implMethods;
- VirtualMethodSourceCode(DexItemFactory factory, DexType groupClass,
+ KotlinLambdaVirtualMethodSourceCode(DexItemFactory factory, DexType groupClass,
DexProto proto, DexField idField, List<DexEncodedMethod> implMethods) {
super(groupClass, proto);
this.factory = factory;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/ClassInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/ClassInitializerSourceCode.java
deleted file mode 100644
index 1cc0dcd..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/ClassInitializerSourceCode.java
+++ /dev/null
@@ -1,53 +0,0 @@
-// 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.optimize.lambda.kstyle;
-
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
-import com.google.common.collect.Lists;
-import java.util.List;
-import java.util.function.IntFunction;
-
-final class ClassInitializerSourceCode extends SyntheticSourceCode {
- private final DexType lambdaGroupType;
- private final DexMethod lambdaConstructorMethod;
- private final int count;
- private final IntFunction<DexField> fieldGenerator;
-
- ClassInitializerSourceCode(DexItemFactory factory,
- DexType lambdaGroupType, int count, IntFunction<DexField> fieldGenerator) {
- super(null, factory.createProto(factory.voidType));
- this.lambdaGroupType = lambdaGroupType;
- this.count = count;
- this.fieldGenerator = fieldGenerator;
- this.lambdaConstructorMethod = factory.createMethod(lambdaGroupType,
- factory.createProto(factory.voidType, factory.intType), factory.constructorMethodName);
- }
-
- @Override
- protected void prepareInstructions() {
- int instance = nextRegister(ValueType.OBJECT);
- int lambdaId = nextRegister(ValueType.INT);
- List<ValueType> argValues = Lists.newArrayList(ValueType.OBJECT, ValueType.INT);
- List<Integer> argRegisters = Lists.newArrayList(instance, lambdaId);
-
- for (int id = 0; id < count; id++) {
- int finalId = id;
- add(builder -> builder.addNewInstance(instance, lambdaGroupType));
- add(builder -> builder.addConst(ValueType.INT, lambdaId, finalId));
- add(builder -> builder.addInvoke(Type.DIRECT,
- lambdaConstructorMethod, lambdaConstructorMethod.proto, argValues, argRegisters));
- add(builder -> builder.addStaticPut(instance, fieldGenerator.apply(finalId)));
- }
-
- add(IRBuilder::addReturn);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/InstanceInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/InstanceInitializerSourceCode.java
deleted file mode 100644
index 00571be..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/InstanceInitializerSourceCode.java
+++ /dev/null
@@ -1,55 +0,0 @@
-// 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.optimize.lambda.kstyle;
-
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.IRBuilder;
-import com.android.tools.r8.ir.synthetic.SyntheticSourceCode;
-import com.google.common.collect.Lists;
-import java.util.function.IntFunction;
-
-final class InstanceInitializerSourceCode extends SyntheticSourceCode {
- private final DexField idField;
- private final IntFunction<DexField> fieldGenerator;
- private final int arity;
- private final DexMethod lambdaInitializer;
-
- InstanceInitializerSourceCode(DexItemFactory factory, DexType lambdaGroupType,
- DexField idField, IntFunction<DexField> fieldGenerator, DexProto proto, int arity) {
- super(lambdaGroupType, proto);
- this.idField = idField;
- this.fieldGenerator = fieldGenerator;
- this.arity = arity;
- this.lambdaInitializer = factory.createMethod(factory.kotlin.functional.lambdaType,
- factory.createProto(factory.voidType, factory.intType), factory.constructorMethodName);
- }
-
- @Override
- protected void prepareInstructions() {
- int receiverRegister = getReceiverRegister();
-
- add(builder -> builder.addInstancePut(getParamRegister(0), receiverRegister, idField));
-
- DexType[] values = proto.parameters.values;
- for (int i = 1; i < values.length; i++) {
- int index = i;
- add(builder -> builder.addInstancePut(
- getParamRegister(index), receiverRegister, fieldGenerator.apply(index - 1)));
- }
-
- int arityRegister = nextRegister(ValueType.INT);
- add(builder -> builder.addConst(ValueType.INT, arityRegister, arity));
- add(builder -> builder.addInvoke(Type.DIRECT, lambdaInitializer, lambdaInitializer.proto,
- Lists.newArrayList(ValueType.OBJECT, ValueType.INT),
- Lists.newArrayList(receiverRegister, arityRegister)));
- add(IRBuilder::addReturn);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroup.java
deleted file mode 100644
index 54f1d3a..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kstyle/KStyleLambdaGroup.java
+++ /dev/null
@@ -1,163 +0,0 @@
-// 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.optimize.lambda.kstyle;
-
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexProto;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
-import com.android.tools.r8.ir.optimize.lambda.CodeProcessor.Strategy;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroup;
-import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
-import com.android.tools.r8.kotlin.Kotlin;
-import com.android.tools.r8.utils.ThrowingConsumer;
-
-// Represents a k-style lambda group created to combine several lambda classes
-// generated by kotlin compiler for regular kotlin lambda expressions, like:
-//
-// -----------------------------------------------------------------------
-// fun foo(m: String, v: Int): () -> String {
-// val lambda: (String, Int) -> String = { s, i -> s.substring(i) }
-// return { "$m: $v" }
-// }
-// -----------------------------------------------------------------------
-//
-// Regular stateless k-style lambda class structure looks like below:
-//
-// -----------------------------------------------------------------------------------------------
-// // signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function2<
-// Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/String;>;
-// final class lambdas/LambdasKt$foo$lambda1$1
-// extends kotlin/jvm/internal/Lambda
-// implements kotlin/jvm/functions/Function2 {
-//
-// public synthetic bridge invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-//
-// public final invoke(Ljava/lang/String;I)Ljava/lang/String;
-// @Lorg/jetbrains/annotations/NotNull;() // invisible
-// @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
-//
-// <init>()V
-//
-// public final static Llambdas/LambdasKt$foo$lambda1$1; INSTANCE
-//
-// static <clinit>()V
-//
-// OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
-// final static INNERCLASS lambdas/LambdasKt$foo$lambda1$1 null null
-// }
-// -----------------------------------------------------------------------------------------------
-//
-// Regular stateful k-style lambda class structure looks like below:
-//
-// -----------------------------------------------------------------------------------------------
-// // signature Lkotlin/jvm/internal/Lambda;Lkotlin/jvm/functions/Function0<Ljava/lang/String;>;
-// final class lambdas/LambdasKt$foo$1
-// extends kotlin/jvm/internal/Lambda
-// implements kotlin/jvm/functions/Function0 {
-//
-// public synthetic bridge invoke()Ljava/lang/Object;
-//
-// public final invoke()Ljava/lang/String;
-// @Lorg/jetbrains/annotations/NotNull;() // invisible
-//
-// <init>(Ljava/lang/String;I)V
-//
-// final synthetic Ljava/lang/String; $m
-// final synthetic I $v
-//
-// OUTERCLASS lambdas/LambdasKt foo (Ljava/lang/String;I)Lkotlin/jvm/functions/Function0;
-// final static INNERCLASS lambdas/LambdasKt$foo$1 null null
-// }
-// -----------------------------------------------------------------------------------------------
-//
-// Key k-style lambda class details:
-// - extends kotlin.jvm.internal.Lambda
-// - implements one of kotlin.jvm.functions.Function0..Function22, or FunctionN
-// see: https://github.com/JetBrains/kotlin/blob/master/libraries/
-// stdlib/jvm/runtime/kotlin/jvm/functions/Functions.kt
-// and: https://github.com/JetBrains/kotlin/blob/master/spec-docs/function-types.md
-// - lambda class is created as an anonymous inner class
-// - lambda class carries generic signature and kotlin metadata attribute
-// - class instance fields represent captured values and have an instance constructor
-// with matching parameters initializing them (see the second class above)
-// - stateless lambda is implemented as a singleton with a static field storing the only
-// instance and initialized in static class constructor (see the first class above)
-// - main lambda method usually matches an exact lambda signature and may have
-// generic signature attribute and nullability parameter annotations
-// - optional bridge method created to satisfy interface implementation and
-// forwarding call to lambda main method
-//
-final class KStyleLambdaGroup extends LambdaGroup {
- private final Strategy strategy = new KStyleLambdaGroupCodeStrategy(this);
-
- KStyleLambdaGroup(KStyleLambdaGroupId id) {
- super(id);
- }
-
- final KStyleLambdaGroupId id() {
- return (KStyleLambdaGroupId) id;
- }
-
- final boolean isStateless() {
- return id().capture.isEmpty();
- }
-
- // Field referencing singleton instance for a lambda with specified id.
- final DexField getSingletonInstanceField(DexItemFactory factory, int id) {
- return factory.createField(this.getGroupClassType(),
- this.getGroupClassType(), factory.createString("INSTANCE$" + id));
- }
-
- @Override
- protected String getTypePackage() {
- String pkg = id().pkg;
- return pkg.isEmpty() ? "" : (pkg + "/");
- }
-
- final DexProto createConstructorProto(DexItemFactory factory) {
- String capture = id().capture;
- DexType[] newParameters = new DexType[capture.length() + 1];
- newParameters[0] = factory.intType; // Lambda id.
- for (int i = 0; i < capture.length(); i++) {
- newParameters[i + 1] = CaptureSignature.fieldType(factory, capture, i);
- }
- return factory.createProto(factory.voidType, newParameters);
- }
-
- final DexField getLambdaIdField(DexItemFactory factory) {
- return factory.createField(this.getGroupClassType(), factory.intType, "$id$");
- }
-
- final int mapFieldIntoCaptureIndex(DexType lambda, DexField field) {
- return CaptureSignature.mapFieldIntoCaptureIndex(
- id().capture, lambdaCaptureFields(lambda), field);
- }
-
- final DexField getCaptureField(DexItemFactory factory, int index) {
- assert index >= 0 && index < id().capture.length();
- return factory.createField(this.getGroupClassType(),
- CaptureSignature.fieldType(factory, id().capture, index), "$capture$" + index);
- }
-
- @Override
- protected LambdaGroupClassBuilder getBuilder(DexItemFactory factory) {
- return new KStyleLambdaGroupClassBuilder(factory, this, "kotlin-style lambda group");
- }
-
- @Override
- public Strategy getCodeStrategy() {
- return strategy;
- }
-
- @Override
- public ThrowingConsumer<DexClass, LambdaStructureError> lambdaClassValidator(
- Kotlin kotlin, AppInfoWithSubtyping appInfo) {
- return new KStyleLambdaClassValidator(kotlin, this, appInfo);
- }
-}
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 9b0415b..cf2b2bc 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -13,7 +13,9 @@
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.DexValueArray;
import com.android.tools.r8.graph.DexValue.DexValueInt;
+import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.kotlin.KotlinSyntheticClass.Flavour;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.Sets;
@@ -118,7 +120,7 @@
case 2:
return new KotlinFile();
case 3:
- return createSyntheticClass(clazz);
+ return createSyntheticClass(clazz, meta);
case 4:
return new KotlinClassFacade();
case 5:
@@ -128,10 +130,14 @@
}
}
- private KotlinSyntheticClass createSyntheticClass(DexClass clazz) {
- KotlinSyntheticClass.Flavour flavour =
- isKotlinStyleLambda(clazz) ? Flavour.KotlinStyleLambda : Flavour.Unclassified;
- return new KotlinSyntheticClass(flavour);
+ private KotlinSyntheticClass createSyntheticClass(DexClass clazz, DexAnnotation meta) {
+ if (isKotlinStyleLambda(clazz)) {
+ return new KotlinSyntheticClass(Flavour.KotlinStyleLambda);
+ }
+ if (isJavaStyleLambda(clazz, meta)) {
+ return new KotlinSyntheticClass(Flavour.JavaStyleLambda);
+ }
+ return new KotlinSyntheticClass(Flavour.Unclassified);
}
private boolean isKotlinStyleLambda(DexClass clazz) {
@@ -139,6 +145,13 @@
return clazz.superType == this.functional.lambdaType;
}
+ private boolean isJavaStyleLambda(DexClass clazz, DexAnnotation meta) {
+ assert !isKotlinStyleLambda(clazz);
+ return clazz.superType == this.factory.objectType &&
+ clazz.interfaces.size() == 1 &&
+ isAnnotationElementNotEmpty(meta, metadata.elementNameD1);
+ }
+
private DexAnnotationElement getAnnotationElement(DexAnnotation annotation, DexString name) {
for (DexAnnotationElement element : annotation.annotation.elements) {
if (element.name == name) {
@@ -148,6 +161,20 @@
return null;
}
+ private boolean isAnnotationElementNotEmpty(DexAnnotation annotation, DexString name) {
+ for (DexAnnotationElement element : annotation.annotation.elements) {
+ if (element.name == name && element.value instanceof DexValueArray) {
+ DexValue[] values = ((DexValueArray) element.value).getValues();
+ if (values.length == 1 && values[0] instanceof DexValueString) {
+ return true;
+ }
+ // Must be broken metadata.
+ assert false;
+ }
+ }
+ return false;
+ }
+
private static class MetadataError extends RuntimeException {
MetadataError(String cause) {
super(cause);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index b0e37b4..68721bb 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -7,19 +7,28 @@
public final class KotlinSyntheticClass extends KotlinInfo {
public enum Flavour {
KotlinStyleLambda,
+ JavaStyleLambda,
Unclassified
}
- public final Flavour flavour;
+ private final Flavour flavour;
KotlinSyntheticClass(Flavour flavour) {
this.flavour = flavour;
}
+ public boolean isLambda() {
+ return flavour == Flavour.KotlinStyleLambda || flavour == Flavour.JavaStyleLambda;
+ }
+
public boolean isKotlinStyleLambda() {
return flavour == Flavour.KotlinStyleLambda;
}
+ public boolean isJavaStyleLambda() {
+ return flavour == Flavour.JavaStyleLambda;
+ }
+
@Override
public final Kind getKind() {
return Kind.Synthetic;
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 23a92a0..deab69f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -69,11 +69,11 @@
private int lineOffset = 0;
private String line;
- private char peek() {
- return peek(0);
+ private int peekCodePoint() {
+ return lineOffset < line.length() ? line.codePointAt(lineOffset) : '\n';
}
- private char peek(int distance) {
+ private char peekChar(int distance) {
return lineOffset + distance < line.length()
? line.charAt(lineOffset + distance)
: '\n';
@@ -83,7 +83,17 @@
return lineOffset < line.length();
}
- private char next() {
+ private int nextCodePoint() {
+ try {
+ int cp = line.codePointAt(lineOffset);
+ lineOffset += Character.charCount(cp);
+ return cp;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new ParseException("Unexpected end of line");
+ }
+ }
+
+ private char nextChar() {
try {
return line.charAt(lineOffset++);
} catch (ArrayIndexOutOfBoundsException e) {
@@ -128,8 +138,8 @@
// Helpers for common pattern
private void skipWhitespace() {
- while (Character.isWhitespace(peek())) {
- next();
+ while (Character.isWhitespace(peekCodePoint())) {
+ nextCodePoint();
}
}
@@ -137,7 +147,7 @@
if (!hasNext()) {
throw new ParseException("Expected '" + c + "'", true);
}
- if (next() != c) {
+ if (nextChar() != c) {
throw new ParseException("Expected '" + c + "'");
}
return c;
@@ -197,7 +207,7 @@
// In the last round we're only here to flush the last line read (which may trigger adding a
// new MemberNaming) and flush activeMemberNaming, so skip parsing.
if (!lastRound) {
- if (!Character.isWhitespace(peek())) {
+ if (!Character.isWhitespace(peekCodePoint())) {
lastRound = true;
continue;
}
@@ -212,9 +222,9 @@
expect(':');
}
signature = parseSignature();
- if (peek() == ':') {
+ if (peekChar(0) == ':') {
// This is a mapping or inlining definition
- next();
+ nextChar();
originalRange = maybeParseRangeOrInt();
if (originalRange == null) {
throw new ParseException("No number follows the colon after the method signature.");
@@ -294,22 +304,22 @@
private void skipIdentifier(boolean allowInit) {
boolean isInit = false;
- if (allowInit && peek() == '<') {
+ if (allowInit && peekChar(0) == '<') {
// swallow the leading < character
- next();
+ nextChar();
isInit = true;
}
- if (!IdentifierUtils.isDexIdentifierStart(peek())) {
+ if (!IdentifierUtils.isDexIdentifierStart(peekCodePoint())) {
throw new ParseException("Identifier expected");
}
- next();
- while (IdentifierUtils.isDexIdentifierPart(peek())) {
- next();
+ nextCodePoint();
+ while (IdentifierUtils.isDexIdentifierPart(peekCodePoint())) {
+ nextCodePoint();
}
if (isInit) {
expect('>');
}
- if (IdentifierUtils.isDexIdentifierPart(peek())) {
+ if (IdentifierUtils.isDexIdentifierPart(peekCodePoint())) {
throw new ParseException("End of identifier expected");
}
}
@@ -330,8 +340,8 @@
private String parseMethodName() {
int startPosition = lineOffset;
skipIdentifier(true);
- while (peek() == '.') {
- next();
+ while (peekChar(0) == '.') {
+ nextChar();
skipIdentifier(true);
}
return substring(startPosition);
@@ -340,13 +350,13 @@
private String parseType(boolean allowArray) {
int startPosition = lineOffset;
skipIdentifier(false);
- while (peek() == '.') {
- next();
+ while (peekChar(0) == '.') {
+ nextChar();
skipIdentifier(false);
}
if (allowArray) {
- while (peek() == '[') {
- next();
+ while (peekChar(0) == '[') {
+ nextChar();
expect(']');
}
}
@@ -358,15 +368,15 @@
expect(' ');
String name = parseMethodName();
Signature signature;
- if (peek() == '(') {
- next();
+ if (peekChar(0) == '(') {
+ nextChar();
String[] arguments;
- if (peek() == ')') {
+ if (peekChar(0) == ')') {
arguments = new String[0];
} else {
List<String> items = new LinkedList<>();
items.add(parseType(true));
- while (peek() != ')') {
+ while (peekChar(0) != ')') {
expect(',');
items.add(parseType(true));
}
@@ -386,9 +396,9 @@
}
private boolean acceptArrow() {
- if (peek() == '-' && peek(1) == '>') {
- next();
- next();
+ if (peekChar(0) == '-' && peekChar(1) == '>') {
+ nextChar();
+ nextChar();
return true;
}
return false;
@@ -396,22 +406,26 @@
private boolean acceptString(String s) {
for (int i = 0; i < s.length(); i++) {
- if (peek(i) != s.charAt(i)) {
+ if (peekChar(i) != s.charAt(i)) {
return false;
}
}
for (int i = 0; i < s.length(); i++) {
- next();
+ nextChar();
}
return true;
}
+ private boolean isSimpleDigit(char c) {
+ return '0' <= c && c <= '9';
+ }
+
private Object maybeParseRangeOrInt() {
- if (!Character.isDigit(peek())) {
+ if (!isSimpleDigit(peekChar(0))) {
return null;
}
int from = parseNumber();
- if (peek() != ':') {
+ if (peekChar(0) != ':') {
return from;
}
expect(':');
@@ -421,13 +435,13 @@
private int parseNumber() {
int result = 0;
- if (!Character.isDigit(peek())) {
+ if (!isSimpleDigit(peekChar(0))) {
throw new ParseException("Number expected");
}
do {
result *= 10;
- result += Character.getNumericValue(next());
- } while (Character.isDigit(peek()));
+ result += Character.getNumericValue(nextChar());
+ } while (isSimpleDigit(peekChar(0)));
return result;
}
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 5cb4ce8..35c8e47 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -1104,14 +1104,15 @@
return Integer.parseInt(s);
}
- private final Predicate<Character> CLASS_NAME_PREDICATE =
- character -> IdentifierUtils.isDexIdentifierPart(character)
- || character == '.'
- || character == '*'
- || character == '?'
- || character == '%'
- || character == '['
- || character == ']';
+ private final Predicate<Integer> CLASS_NAME_PREDICATE =
+ codePoint ->
+ IdentifierUtils.isDexIdentifierPart(codePoint)
+ || codePoint == '.'
+ || codePoint == '*'
+ || codePoint == '?'
+ || codePoint == '%'
+ || codePoint == '['
+ || codePoint == ']';
private String acceptClassName() {
return acceptString(CLASS_NAME_PREDICATE);
@@ -1123,7 +1124,7 @@
int start = position;
int end = position;
while (!eof(end)) {
- char current = contents.charAt(end);
+ int current = contents.codePointAt(end);
if (currentBackreference != null) {
if (current == '>') {
try {
@@ -1138,10 +1139,10 @@
origin, getPosition()));
}
currentBackreference = null;
- } else if (Character.isDigit(current)
+ } else if (('0' <= current && current <= '9')
// Only collect integer literal for the backreference.
|| (current == '-' && currentBackreference.length() == 0)) {
- currentBackreference.append(current);
+ currentBackreference.append((char) current);
} else if (kind == IdentifierType.CLASS_NAME) {
throw reporter.fatalError(new StringDiagnostic(
"Use of generics not allowed for java type.", origin, getPosition()));
@@ -1150,12 +1151,12 @@
// collection of the backreference.
currentBackreference = null;
}
- end++;
+ end += Character.charCount(current);
} else if (CLASS_NAME_PREDICATE.test(current) || current == '>') {
- end++;
+ end += Character.charCount(current);
} else if (current == '<') {
currentBackreference = new StringBuilder();
- end++;
+ ++end;
} else {
break;
}
@@ -1177,14 +1178,15 @@
int start = position;
int end = position;
while (!eof(end)) {
- char current = contents.charAt(end);
+ int current = contents.codePointAt(end);
if (current == '.' && !eof(end + 1) && peekCharAt(end + 1) == '.') {
// The grammar is ambiguous. End accepting before .. token used in return ranges.
break;
}
- if ((start == end && IdentifierUtils.isDexIdentifierStart(current)) ||
- ((start < end) && (IdentifierUtils.isDexIdentifierPart(current) || current == '.'))) {
- end++;
+ if ((start == end && IdentifierUtils.isDexIdentifierStart(current))
+ || ((start < end)
+ && (IdentifierUtils.isDexIdentifierPart(current) || current == '.'))) {
+ end += Character.charCount(current);
} else {
break;
}
@@ -1216,18 +1218,21 @@
}
private String acceptPattern() {
- return acceptString(character ->
- IdentifierUtils.isDexIdentifierPart(character) || character == '!' || character == '*');
+ return acceptString(
+ codePoint ->
+ IdentifierUtils.isDexIdentifierPart(codePoint)
+ || codePoint == '!'
+ || codePoint == '*');
}
- private String acceptString(Predicate<Character> characterAcceptor) {
+ private String acceptString(Predicate<Integer> codepointAcceptor) {
skipWhitespace();
int start = position;
int end = position;
while (!eof(end)) {
- char current = contents.charAt(end);
- if (characterAcceptor.test(current)) {
- end++;
+ int current = contents.codePointAt(end);
+ if (codepointAcceptor.test(current)) {
+ end += Character.charCount(current);
} else {
break;
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
index 019e18c..cf966ee 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationSourceStrings.java
@@ -9,7 +9,6 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
-import joptsimple.internal.Strings;
public class ProguardConfigurationSourceStrings implements ProguardConfigurationSource {
@@ -40,7 +39,7 @@
@Override
public String get() {
- return Strings.join(config, System.lineSeparator());
+ return String.join(System.lineSeparator(), config);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/utils/IdentifierUtils.java b/src/main/java/com/android/tools/r8/utils/IdentifierUtils.java
index 3097a07..5fb617b 100644
--- a/src/main/java/com/android/tools/r8/utils/IdentifierUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IdentifierUtils.java
@@ -5,43 +5,27 @@
package com.android.tools.r8.utils;
public class IdentifierUtils {
- public static boolean isDexIdentifierStart(char ch) {
+ public static boolean isDexIdentifierStart(int cp) {
// Dex does not have special restrictions on the first char of an identifier.
- return isDexIdentifierPart(ch);
+ return isDexIdentifierPart(cp);
}
- public static boolean isDexIdentifierPart(char ch) {
- return isSimpleNameChar(ch);
+ public static boolean isDexIdentifierPart(int cp) {
+ return isSimpleNameChar(cp);
}
- private static boolean isSimpleNameChar(char ch) {
- if (ch >= 'A' && ch <= 'Z') {
- return true;
- }
- if (ch >= 'a' && ch <= 'z') {
- return true;
- }
- if (ch >= '0' && ch <= '9') {
- return true;
- }
- if (ch == '$' || ch == '-' || ch == '_') {
- return true;
- }
- if (ch >= 0x00a1 && ch <= 0x1fff) {
- return true;
- }
- if (ch >= 0x2010 && ch <= 0x2027) {
- return true;
- }
- if (ch >= 0x2030 && ch <= 0xd7ff) {
- return true;
- }
- if (ch >= 0xe000 && ch <= 0xffef) {
- return true;
- }
- if (ch >= 0x10000 && ch <= 0x10ffff) {
- return true;
- }
- return false;
+ private static boolean isSimpleNameChar(int cp) {
+ // See https://source.android.com/devices/tech/dalvik/dex-format#string-syntax.
+ return ('A' <= cp && cp <= 'Z')
+ || ('a' <= cp && cp <= 'z')
+ || ('0' <= cp && cp <= '9')
+ || cp == '$'
+ || cp == '-'
+ || cp == '_'
+ || (0x00a1 <= cp && cp <= 0x1fff)
+ || (0x2010 <= cp && cp <= 0x2027)
+ || (0x2030 <= cp && cp <= 0xd7ff)
+ || (0xe000 <= cp && cp <= 0xffef)
+ || (0x10000 <= cp && cp <= 0x10ffff);
}
}
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 1073cf9..cc8c9ce 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -401,50 +401,54 @@
}
public boolean canUseInvokePolymorphicOnVarHandle() {
- return minApiLevel >= AndroidApiLevel.P.getLevel();
+ return hasMinApi(AndroidApiLevel.P);
}
public boolean canUseInvokePolymorphic() {
- return minApiLevel >= AndroidApiLevel.O.getLevel();
+ return hasMinApi(AndroidApiLevel.O);
}
public boolean canUseConstantMethodHandle() {
- return minApiLevel >= AndroidApiLevel.P.getLevel();
+ return hasMinApi(AndroidApiLevel.P);
+ }
+
+ private boolean hasMinApi(AndroidApiLevel level) {
+ return isGeneratingClassFiles() || minApiLevel >= level.getLevel();
}
public boolean canUseConstantMethodType() {
- return minApiLevel >= AndroidApiLevel.P.getLevel();
+ return hasMinApi(AndroidApiLevel.P);
}
public boolean canUseInvokeCustom() {
- return minApiLevel >= AndroidApiLevel.O.getLevel();
+ return hasMinApi(AndroidApiLevel.O);
}
public boolean canUseDefaultAndStaticInterfaceMethods() {
- return minApiLevel >= AndroidApiLevel.N.getLevel();
+ return hasMinApi(AndroidApiLevel.N);
}
public boolean canUsePrivateInterfaceMethods() {
- return minApiLevel >= AndroidApiLevel.N.getLevel();
+ return hasMinApi(AndroidApiLevel.N);
}
public boolean canUseMultidex() {
- return intermediate || minApiLevel >= AndroidApiLevel.L.getLevel();
+ return intermediate || hasMinApi(AndroidApiLevel.L);
}
public boolean canUseLongCompareAndObjectsNonNull() {
- return minApiLevel >= AndroidApiLevel.K.getLevel();
+ return hasMinApi(AndroidApiLevel.K);
}
public boolean canUseSuppressedExceptions() {
- return minApiLevel >= AndroidApiLevel.K.getLevel();
+ return hasMinApi(AndroidApiLevel.K);
}
// APIs for accessing parameter names annotations are not available before Android O, thus does
// not emit them to avoid wasting space in Dex files because runtimes before Android O will ignore
// them.
public boolean canUseParameterNameAnnotations() {
- return minApiLevel >= AndroidApiLevel.O.getLevel();
+ return hasMinApi(AndroidApiLevel.O);
}
// Dalvik x86-atom backend had a bug that made it crash on filled-new-array instructions for
@@ -456,7 +460,7 @@
//
// https://android.googlesource.com/platform/dalvik/+/ics-mr0/vm/mterp/out/InterpAsm-x86-atom.S#25106
public boolean canUseFilledNewArrayOfObjects() {
- return minApiLevel >= AndroidApiLevel.K.getLevel();
+ return hasMinApi(AndroidApiLevel.K);
}
// Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
@@ -492,7 +496,7 @@
// we can only use not instructions if we are targeting Art-based
// phones.
public boolean canUseNotInstruction() {
- return minApiLevel >= AndroidApiLevel.L.getLevel();
+ return hasMinApi(AndroidApiLevel.L);
}
// Art before M has a verifier bug where the type of the contents of the receiver register is
diff --git a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
new file mode 100644
index 0000000..f4d3da5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
@@ -0,0 +1,127 @@
+// 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;
+
+import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.UnaryOperator;
+
+public class R8CFRunExamplesJava9Test extends RunExamplesJava9Test<R8Command.Builder> {
+
+ class R8CFTestRunner extends TestRunner<R8CFTestRunner> {
+
+ R8CFTestRunner(String testName, String packageName, String mainClass) {
+ super(testName, packageName, mainClass);
+ }
+
+ @Override
+ R8CFTestRunner withMinApiLevel(int minApiLevel) {
+ return self();
+ }
+
+ @Override
+ void build(Path inputFile, Path out) throws Throwable {
+ R8Command.Builder builder = R8Command.builder();
+ for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
+ builder = transformation.apply(builder);
+ }
+ builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
+ R8Command command =
+ builder.addProgramFiles(inputFile).setOutput(out, OutputMode.ClassFile).build();
+ ToolHelper.runR8(command, this::combinedOptionConsumer);
+ }
+
+ @Override
+ void run() throws Throwable {
+ boolean expectedToThrow = minSdkErrorExpectedCf(testName);
+ if (expectedToThrow) {
+ thrown.expect(ApiLevelException.class);
+ }
+
+ String qualifiedMainClass = packageName + "." + mainClass;
+ Path inputFile = getInputJar();
+ Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
+
+ build(inputFile, out);
+
+ if (!ToolHelper.isJava9Runtime()) {
+ System.out.println("No Java 9 support; skip execution tests");
+ return;
+ }
+
+ if (!dexInspectorChecks.isEmpty()) {
+ DexInspector inspector = new DexInspector(out);
+ for (Consumer<DexInspector> check : dexInspectorChecks) {
+ check.accept(inspector);
+ }
+ }
+
+ execute(testName, qualifiedMainClass, new Path[] {inputFile}, new Path[] {out});
+
+ if (expectedToThrow) {
+ System.out.println("Did not throw ApiLevelException as expected");
+ }
+ }
+
+ @Override
+ R8CFTestRunner self() {
+ return this;
+ }
+ }
+
+ @Override
+ R8CFTestRunner test(String testName, String packageName, String mainClass) {
+ return new R8CFTestRunner(testName, packageName, mainClass);
+ }
+
+ void execute(String testName, String qualifiedMainClass, Path[] inputJars, Path[] outputJars)
+ throws IOException {
+ boolean expectedToFail = expectedToFailCf(testName);
+ if (expectedToFail) {
+ thrown.expect(Throwable.class);
+ }
+ ProcessResult outputResult = ToolHelper.runJava(Arrays.asList(outputJars), qualifiedMainClass);
+ ToolHelper.ProcessResult inputResult =
+ ToolHelper.runJava(ImmutableList.copyOf(inputJars), qualifiedMainClass);
+ assertEquals(inputResult.toString(), outputResult.toString());
+ if (inputResult.exitCode != 0) {
+ System.out.println(inputResult);
+ }
+ assertEquals(0, inputResult.exitCode);
+ if (expectedToFail) {
+ System.out.println("Did not fail as expected");
+ }
+ }
+
+ private static List<String> expectedFailures =
+ ImmutableList.of(
+ "native-private-interface-methods",
+ "desugared-private-interface-methods"
+ );
+
+ private boolean expectedToFailCf(String testName) {
+ System.out.println(testName + " " + expectedFailures.contains(testName));
+ return expectedFailures.contains(testName);
+ }
+
+ private static List<String> minSdkErrorExpected =
+ ImmutableList.of(
+ );
+
+ private boolean minSdkErrorExpectedCf(String testName) {
+ System.out.println(testName);
+ return minSdkErrorExpected.contains(testName);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index ebb8100..4d7240e 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1488,7 +1488,6 @@
AndroidApiLevel minSdkVersion = needMinSdkVersion.get(name);
if (minSdkVersion != null) {
builder.setMinApiLevel(minSdkVersion.getLevel());
- r8builder.setMinApiLevel(minSdkVersion.getLevel());
builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion));
r8builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion));
} else {
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index d30b2e2..024be59 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -28,6 +28,7 @@
import com.android.tools.r8.utils.PreloadedClassFileProvider;
import com.android.tools.r8.utils.ZipUtils;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import java.io.File;
@@ -47,7 +48,6 @@
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
-import joptsimple.internal.Strings;
import org.junit.Rule;
import org.junit.rules.TemporaryFolder;
@@ -364,12 +364,13 @@
*/
public static String keepMainProguardConfiguration(Class clazz, List<String> additionalLines) {
String modifier = (clazz.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC ? "public " : "";
- return Strings.join(ImmutableList.of(
- "-keep " + modifier + "class " + getJavacGeneratedClassName(clazz) + " {",
- " public static void main(java.lang.String[]);",
- "}",
- "-printmapping"
- ), "\n") + (additionalLines.size() > 0 ? ("\n" + Strings.join(additionalLines, "\n")) : "");
+ return String.join(System.lineSeparator(),
+ Iterables.concat(ImmutableList.of(
+ "-keep " + modifier + "class " + getJavacGeneratedClassName(clazz) + " {",
+ " public static void main(java.lang.String[]);",
+ "}",
+ "-printmapping"),
+ additionalLines));
}
/**
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index f63f4f2..6ffe54d 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -60,7 +60,6 @@
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
-import joptsimple.internal.Strings;
import org.junit.Assume;
import org.junit.rules.TemporaryFolder;
@@ -90,8 +89,8 @@
private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
private static final AndroidApiLevel DEFAULT_MIN_SDK = AndroidApiLevel.I;
- private static final String PROGUARD5_2_1 = "third_party/proguard/proguard5.2.1/bin/proguard.sh";
- private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard.sh";
+ private static final String PROGUARD5_2_1 = "third_party/proguard/proguard5.2.1/bin/proguard";
+ private static final String PROGUARD6_0_1 = "third_party/proguard/proguard6.0.1/bin/proguard";
private static final String PROGUARD = PROGUARD5_2_1;
public enum DexVm {
@@ -267,7 +266,7 @@
}
if (!classpaths.isEmpty()) {
result.add("-cp");
- result.add(Strings.join(classpaths, ":"));
+ result.add(String.join(":", classpaths));
}
if (!bootClassPaths.isEmpty()) {
result.add("-Xbootclasspath:" + String.join(":", bootClassPaths));
@@ -475,6 +474,20 @@
}
}
+ private static String getProguardScript() {
+ if (isWindows()) {
+ return PROGUARD + ".bat";
+ }
+ return PROGUARD + ".sh";
+ }
+
+ private static String getProguard6Script() {
+ if (isWindows()) {
+ return PROGUARD6_0_1 + ".bat";
+ }
+ return PROGUARD6_0_1 + ".sh";
+ }
+
private static Path getDxExecutablePath() {
String toolsDir = toolsDir();
String executableName = toolsDir.equals("windows") ? "dx.bat" : "dx";
@@ -1164,7 +1177,7 @@
public static ProcessResult runProguardRaw(Path inJar, Path outJar, Path config, Path map)
throws IOException {
- return runProguardRaw(PROGUARD, inJar, outJar, ImmutableList.of(config), map);
+ return runProguardRaw(getProguardScript(), inJar, outJar, ImmutableList.of(config), map);
}
public static String runProguard(Path inJar, Path outJar, Path config, Path map)
@@ -1174,12 +1187,12 @@
public static String runProguard(Path inJar, Path outJar, List<Path> config, Path map)
throws IOException {
- return runProguard(PROGUARD, inJar, outJar, config, map);
+ return runProguard(getProguardScript(), inJar, outJar, config, map);
}
public static ProcessResult runProguard6Raw(Path inJar, Path outJar, Path config, Path map)
throws IOException {
- return runProguardRaw(PROGUARD6_0_1, inJar, outJar, ImmutableList.of(config), map);
+ return runProguardRaw(getProguard6Script(), inJar, outJar, ImmutableList.of(config), map);
}
public static String runProguard6(Path inJar, Path outJar, Path config, Path map)
@@ -1189,7 +1202,7 @@
public static String runProguard6(Path inJar, Path outJar, List<Path> configs, Path map)
throws IOException {
- return runProguard(PROGUARD6_0_1, inJar, outJar, configs, map);
+ return runProguard(getProguard6Script(), inJar, outJar, configs, map);
}
public static class ProcessResult {
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index ba25396..d92ff90 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -85,10 +85,12 @@
AndroidApiLevel apiLevel = AndroidApiLevel.P;
Builder cfBuilder =
R8Command.builder()
- .setMinApiLevel(apiLevel.getLevel())
.setMode(CompilationMode.DEBUG)
.addLibraryFiles(ToolHelper.getAndroidJar(apiLevel))
.setProgramConsumer(programConsumer);
+ if (!(programConsumer instanceof ClassFileConsumer)) {
+ cfBuilder.setMinApiLevel(apiLevel.getLevel());
+ }
for (Class<?> c : inputClasses) {
byte[] classAsBytes = getClassAsBytes(c);
cfBuilder.addClassProgramData(classAsBytes, Origin.unknown());
diff --git a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
index 4916e1d..e4a88f0 100644
--- a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
@@ -25,7 +25,6 @@
AndroidAppConsumers sink = new AndroidAppConsumers();
R8.run(
R8Command.builder()
- .setMinApiLevel(minApi.getLevel())
.setMode(CompilationMode.DEBUG)
.addProgramFiles(DebugTestBase.DEBUGGEE_JAR)
.setProgramConsumer(sink.wrapClassFileConsumer(null))
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
index 7caac8e..bad64ac 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
@@ -4,14 +4,25 @@
package com.android.tools.r8.jasmin;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.smali.SmaliBuilder;
+import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.base.Strings;
+import com.google.common.primitives.Bytes;
+import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
+import java.util.zip.Adler32;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -20,64 +31,183 @@
@RunWith(Parameterized.class)
public class InvalidFieldNames extends JasminTestBase {
- public boolean runsOnJVM;
- public String name;
+ private static final String CLASS_NAME = "Test";
+ private static String FIELD_VALUE = "42";
- public InvalidFieldNames(String name, boolean runsOnJVM) {
- this.name = name;
- this.runsOnJVM = runsOnJVM;
- }
-
- private void runTest(JasminBuilder builder, String main, String expected) throws Exception {
- if (runsOnJVM) {
- String javaResult = runOnJava(builder, main);
- assertEquals(expected, javaResult);
- }
- String artResult = null;
- try {
- artResult = runOnArtD8(builder, main);
- fail();
- } catch (CompilationError t) {
- assertTrue(t.getMessage().contains(name));
- }
- assertNull("Invalid dex field names should be rejected.", artResult);
- }
-
- @Parameters
+ @Parameters(name = "\"{0}\", jvm: {1}, art: {2}")
public static Collection<Object[]> data() {
- return Arrays.asList(new Object[][]{
- {"\u00a0", !ToolHelper.isJava9Runtime()},
- {"\u2000", !ToolHelper.isJava9Runtime()},
- {"\u200f", !ToolHelper.isJava9Runtime()},
- {"\u2028", !ToolHelper.isJava9Runtime()},
- {"\u202f", !ToolHelper.isJava9Runtime()},
- {"\ud800", !ToolHelper.isJava9Runtime()},
- {"\udfff", !ToolHelper.isJava9Runtime()},
- {"\ufff0", !ToolHelper.isJava9Runtime()},
- {"\uffff", !ToolHelper.isJava9Runtime()},
- {"a/b", false},
- {"<a", false},
- {"a>", !ToolHelper.isJava9Runtime()},
- {"a<b>", !ToolHelper.isJava9Runtime()},
- {"<a>b", !ToolHelper.isJava9Runtime()}
- });
+ return Arrays.asList(
+ new Object[][] {
+ {new TestStringParameter("azAZ09$_"), true, true},
+ {new TestStringParameter("_"), !ToolHelper.isJava9Runtime(), true},
+ {new TestStringParameter("a-b"), !ToolHelper.isJava9Runtime(), true},
+ {new TestStringParameter("\u00a0"), !ToolHelper.isJava9Runtime(), false},
+ {new TestStringParameter("\u00a1"), !ToolHelper.isJava9Runtime(), true},
+ {new TestStringParameter("\u1fff"), !ToolHelper.isJava9Runtime(), true},
+ {new TestStringParameter("\u2000"), !ToolHelper.isJava9Runtime(), false},
+ {new TestStringParameter("\u200f"), !ToolHelper.isJava9Runtime(), false},
+ {new TestStringParameter("\u2010"), !ToolHelper.isJava9Runtime(), true},
+ {new TestStringParameter("\u2027"), !ToolHelper.isJava9Runtime(), true},
+ {new TestStringParameter("\u2028"), !ToolHelper.isJava9Runtime(), false},
+ {new TestStringParameter("\u202f"), !ToolHelper.isJava9Runtime(), false},
+ {new TestStringParameter("\u2030"), !ToolHelper.isJava9Runtime(), true},
+ {new TestStringParameter("\ud7ff"), !ToolHelper.isJava9Runtime(), true},
+
+ // Standalone high and low surrogates.
+ {new TestStringParameter("\ud800"), !ToolHelper.isJava9Runtime(), false},
+ {new TestStringParameter("\udbff"), !ToolHelper.isJava9Runtime(), false},
+ {new TestStringParameter("\udc00"), !ToolHelper.isJava9Runtime(), false},
+ {new TestStringParameter("\udfff"), !ToolHelper.isJava9Runtime(), false},
+ {new TestStringParameter("\ue000"), !ToolHelper.isJava9Runtime(), true},
+ {new TestStringParameter("\uffef"), !ToolHelper.isJava9Runtime(), true},
+ {new TestStringParameter("\ufff0"), !ToolHelper.isJava9Runtime(), false},
+ {new TestStringParameter("\uffff"), !ToolHelper.isJava9Runtime(), false},
+
+ // Single and double code points above 0x10000.
+ {new TestStringParameter("\ud800\udc00"), true, true},
+ {new TestStringParameter("\ud800\udcfa"), true, true},
+ {new TestStringParameter("\ud800\udcfb"), !ToolHelper.isJava9Runtime(), true},
+ {new TestStringParameter("\udbff\udfff"), !ToolHelper.isJava9Runtime(), true},
+ {new TestStringParameter("\ud800\udc00\ud800\udcfa"), true, true},
+ {new TestStringParameter("\ud800\udc00\udbff\udfff"), !ToolHelper.isJava9Runtime(), true},
+ {new TestStringParameter("a/b"), false, false},
+ {new TestStringParameter("<a"), !ToolHelper.isJava9Runtime(), false},
+ {new TestStringParameter("a>"), !ToolHelper.isJava9Runtime(), false},
+ {new TestStringParameter("a<b>"), !ToolHelper.isJava9Runtime(), false},
+ {new TestStringParameter("<a>b"), !ToolHelper.isJava9Runtime(), false}
+ });
}
- @Test
- public void invalidFieldNames() throws Exception {
- JasminBuilder builder = new JasminBuilder();
- JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+ // TestStringParameter is a String with modified toString() which prints \\uXXXX for
+ // characters outside 0x20..0x7e.
+ static class TestStringParameter {
+ private final String value;
- clazz.addStaticField(name, "I", "42");
+ TestStringParameter(String value) {
+ this.value = value;
+ }
+
+ String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return StringUtils.toASCIIString(value);
+ }
+ }
+
+ private String name;
+ private boolean validForJVM;
+ private boolean validForArt;
+
+ public InvalidFieldNames(TestStringParameter name, boolean validForJVM, boolean validForArt) {
+ this.name = name.getValue();
+ this.validForJVM = validForJVM;
+ this.validForArt = validForArt;
+ }
+
+ private byte[] trimLastZeroByte(byte[] bytes) {
+ assert bytes.length > 0 && bytes[bytes.length - 1] == 0;
+ byte[] result = new byte[bytes.length - 1];
+ System.arraycopy(bytes, 0, result, 0, result.length);
+ return result;
+ }
+
+ private JasminBuilder createJasminBuilder() {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass(CLASS_NAME);
+
+ clazz.addStaticField(name, "I", FIELD_VALUE);
clazz.addMainMethod(
".limit stack 2",
".limit locals 1",
" getstatic java/lang/System/out Ljava/io/PrintStream;",
- " getstatic Test/" + name + " I",
+ " getstatic " + CLASS_NAME + "/" + name + " I",
" invokevirtual java/io/PrintStream.print(I)V",
" return");
+ return builder;
+ }
- runTest(builder, clazz.name, "42");
+ private AndroidApp createAppWithSmali() throws Exception {
+
+ SmaliBuilder smaliBuilder = new SmaliBuilder(CLASS_NAME);
+ String originalSourceFile = CLASS_NAME + FileUtils.JAVA_EXTENSION;
+ smaliBuilder.setSourceFile(originalSourceFile);
+
+ // We're using a valid placeholder string which will be replaced by the actual name.
+ byte[] nameMutf8 = trimLastZeroByte(DexString.encodeToMutf8(name));
+
+ String placeholderString = Strings.repeat("A", nameMutf8.length);
+
+ smaliBuilder.addStaticField(placeholderString, "I", FIELD_VALUE);
+ MethodSignature mainSignature =
+ smaliBuilder.addMainMethod(
+ 1,
+ "sget-object p0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
+ "sget v0, LTest;->" + placeholderString + ":I",
+ "invoke-virtual {p0, v0}, Ljava/io/PrintStream;->print(I)V",
+ "return-void");
+ byte[] dexCode = smaliBuilder.compile();
+
+ // Replace placeholder by mutf8-encoded name
+ byte[] placeholderBytes = trimLastZeroByte(DexString.encodeToMutf8(placeholderString));
+ assert placeholderBytes.length == nameMutf8.length;
+ int index = Bytes.indexOf(dexCode, placeholderBytes);
+ if (index >= 0) {
+ System.arraycopy(nameMutf8, 0, dexCode, index, nameMutf8.length);
+ }
+ assert Bytes.indexOf(dexCode, placeholderBytes) < 0;
+
+ // Update checksum
+ Adler32 adler = new Adler32();
+ adler.update(dexCode, Constants.SIGNATURE_OFFSET, dexCode.length - Constants.SIGNATURE_OFFSET);
+ int checksum = (int) adler.getValue();
+ for (int i = 0; i < 4; ++i) {
+ dexCode[Constants.CHECKSUM_OFFSET + i] = (byte) (checksum >> (8 * i) & 0xff);
+ }
+
+ return AndroidApp.builder()
+ .addDexProgramData(dexCode, new PathOrigin(Paths.get(originalSourceFile)))
+ .build();
+ }
+
+ @Test
+ public void invalidFieldNames() throws Exception {
+ JasminBuilder jasminBuilder = createJasminBuilder();
+
+ if (validForJVM) {
+ String javaResult = runOnJava(jasminBuilder, CLASS_NAME);
+ assertEquals(FIELD_VALUE, javaResult);
+ } else {
+ try {
+ runOnJava(jasminBuilder, CLASS_NAME);
+ fail("Should have failed on JVM.");
+ } catch (AssertionError e) {
+ // Silent on expected failure.
+ }
+ }
+
+ if (validForArt) {
+ String artResult = runOnArtD8(jasminBuilder, CLASS_NAME);
+ assertEquals(FIELD_VALUE, artResult);
+ } else {
+ // Make sure the compiler fails.
+ try {
+ runOnArtD8(jasminBuilder, CLASS_NAME);
+ fail("D8 should have rejected this case.");
+ } catch (CompilationError t) {
+ assertTrue(t.getMessage().contains(name));
+ }
+
+ // Make sure ART also fail, if D8 rejects it.
+ try {
+ runOnArt(createAppWithSmali(), CLASS_NAME);
+ fail("Art should have failed.");
+ } catch (AssertionError e) {
+ // Silent on expected failure.
+ }
+ }
}
}
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 1419b95..3e46ff8 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -185,8 +185,6 @@
AndroidAppInspector inspector) throws Exception {
Assume.assumeTrue(ToolHelper.artSupported());
- Path jarFile = getJarFile(folder);
-
String proguardRules = buildProguardRules(mainClass);
if (extraProguardRules != null) {
proguardRules += extraProguardRules;
@@ -194,7 +192,8 @@
// Build classpath for compilation (and java execution)
List<Path> classpath = new ArrayList<>(extraClasspath.size() + 1);
- classpath.add(jarFile);
+ classpath.add(getKotlinJarFile(folder));
+ classpath.add(getJavaJarFile(folder));
classpath.addAll(extraClasspath);
// Build with R8
@@ -222,11 +221,16 @@
inspector.inspectApp(app);
}
- private Path getJarFile(String folder) {
+ private Path getKotlinJarFile(String folder) {
return Paths.get(ToolHelper.TESTS_BUILD_DIR, "kotlinR8TestResources",
targetVersion.getFolderName(), folder + FileUtils.JAR_EXTENSION);
}
+ private Path getJavaJarFile(String folder) {
+ return Paths.get(ToolHelper.TESTS_BUILD_DIR, "kotlinR8TestResources",
+ targetVersion.getFolderName(), folder + ".java" + FileUtils.JAR_EXTENSION);
+ }
+
@FunctionalInterface
interface AndroidAppInspector {
diff --git a/src/test/java/com/android/tools/r8/kotlin/KStyleLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KStyleLambdaMergingTest.java
deleted file mode 100644
index 48358f0..0000000
--- a/src/test/java/com/android/tools/r8/kotlin/KStyleLambdaMergingTest.java
+++ /dev/null
@@ -1,314 +0,0 @@
-// 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 junit.framework.TestCase.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.google.common.collect.Lists;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import org.junit.Test;
-
-public class KStyleLambdaMergingTest extends AbstractR8KotlinTestBase {
- private static final String KOTLIN_FUNCTION_IFACE = "Lkotlin/jvm/functions/Function";
- private static final String KEEP_INNER_AND_ENCLOSING =
- "-keepattributes InnerClasses,EnclosingMethod\n";
- private static final String KEEP_SIGNATURE_INNER_ENCLOSING =
- "-keepattributes Signature,InnerClasses,EnclosingMethod\n";
-
- abstract static class LambdaOrGroup {
- abstract boolean match(DexClass clazz);
- }
-
- static class Group extends LambdaOrGroup {
- final String pkg;
- final String capture;
- final int arity;
-
- private Group(String pkg, String capture, int arity) {
- this.pkg = pkg;
- this.capture = fixCapture(capture);
- this.arity = arity;
- }
-
- private String fixCapture(String capture) {
- capture += "I";
- char[] chars = capture.toCharArray();
- Arrays.sort(chars);
- return new String(chars);
- }
-
- @Override
- public String toString() {
- return "group class " +
- (pkg.length() == 0 ? "" : pkg + "/") +
- "-$$LambdaGroup$XXXX (arity: " + arity + ", capture: " + capture + ")";
- }
-
- @Override
- boolean match(DexClass clazz) {
- return clazz.type.getPackageDescriptor().equals(pkg) &&
- getLambdaGroupCapture(clazz).equals(capture) &&
- getLambdaArity(clazz) == arity;
- }
- }
-
- static class Lambda extends LambdaOrGroup {
- final String pkg;
- final String name;
- final int arity;
-
- private Lambda(String pkg, String name, int arity) {
- this.pkg = pkg;
- this.name = name;
- this.arity = arity;
- }
-
- @Override
- public String toString() {
- return "lambda class " +
- (pkg.length() == 0 ? "" : pkg + "/") +
- name + " (arity: " + arity + ")";
- }
-
- @Override
- boolean match(DexClass clazz) {
- return clazz.type.getPackageDescriptor().equals(pkg) &&
- clazz.type.getName().equals(name) &&
- getLambdaArity(clazz) == arity;
- }
- }
-
- static class Verifier {
- final DexInspector dexInspector;
- final List<DexClass> lambdas = new ArrayList<>();
- final List<DexClass> groups = new ArrayList<>();
-
- Verifier(AndroidApp app) throws IOException, ExecutionException {
- this.dexInspector = new DexInspector(app);
- dexInspector.forAllClasses(clazz -> {
- DexClass dexClass = clazz.getDexClass();
- if (extendsLambdaBase(dexClass)) {
- if (isLambdaGroupClass(dexClass)) {
- groups.add(dexClass);
- } else {
- lambdas.add(dexClass);
- }
- }
- });
- }
-
- void assertLambdaGroups(Group... groups) {
- assertLambdasOrGroups("Lambda group", this.groups, groups);
- }
-
- void assertLambdas(Lambda... lambdas) {
- assertLambdasOrGroups("Lambda", this.lambdas, lambdas);
- }
-
- @SafeVarargs
- private static <T extends LambdaOrGroup>
- void assertLambdasOrGroups(String what, List<DexClass> objects, T... checks) {
- ArrayList<DexClass> list = Lists.newArrayList(objects);
- for (int i = 0; i < checks.length; i++) {
- T check = checks[i];
- for (DexClass clazz : list) {
- if (check.match(clazz)) {
- list.remove(clazz);
- checks[i] = null;
- break;
- }
- }
- }
-
- int notFound = 0;
- for (T check : checks) {
- if (check != null) {
- System.err.println(what + " not found: " + check);
- notFound++;
- }
- }
-
- for (DexClass dexClass : list) {
- System.err.println(what + " unexpected: " +
- dexClass.type.descriptor.toString() +
- ", arity: " + getLambdaArity(dexClass) +
- ", capture: " + getLambdaGroupCapture(dexClass));
- notFound++;
- }
-
- assertTrue(what + "s match failed", 0 == notFound && 0 == list.size());
- }
- }
-
- private static int getLambdaArity(DexClass clazz) {
- for (DexType iface : clazz.interfaces.values) {
- String descr = iface.descriptor.toString();
- if (descr.startsWith(KOTLIN_FUNCTION_IFACE)) {
- return Integer.parseInt(
- descr.substring(KOTLIN_FUNCTION_IFACE.length(), descr.length() - 1));
- }
- }
- fail("Type " + clazz.type.descriptor.toString() +
- " does not implement functional interface.");
- throw new AssertionError();
- }
-
- private static boolean extendsLambdaBase(DexClass clazz) {
- return clazz.superType.descriptor.toString().equals("Lkotlin/jvm/internal/Lambda;");
- }
-
- private static boolean isLambdaGroupClass(DexClass clazz) {
- return clazz.type.getName().startsWith("-$$LambdaGroup$");
- }
-
- private static String getLambdaGroupCapture(DexClass clazz) {
- return CaptureSignature.getCaptureSignature(clazz.instanceFields());
- }
-
- @Test
- public void testTrivial() throws Exception {
- final String mainClassName = "lambdas.kstyle.trivial.MainKt";
- runTest("lambdas_kstyle_trivial", mainClassName, null, (app) -> {
- Verifier verifier = new Verifier(app);
- String pkg = "lambdas/kstyle/trivial";
-
- verifier.assertLambdaGroups(
- allowAccessModification ?
- new Group[]{
- new Group("", "", 0),
- new Group("", "", 1),
- new Group("", "", 2), // -\
- new Group("", "", 2), // - 3 groups different by main method
- new Group("", "", 2), // -/
- new Group("", "", 3),
- new Group("", "", 22)} :
- new Group[]{
- new Group(pkg, "", 0),
- new Group(pkg, "", 1),
- new Group(pkg, "", 2), // - 2 groups different by main method
- new Group(pkg, "", 2), // -/
- new Group(pkg, "", 3),
- new Group(pkg, "", 22),
- new Group(pkg + "/inner", "", 0),
- new Group(pkg + "/inner", "", 1)}
- );
-
- verifier.assertLambdas(
- allowAccessModification ?
- new Lambda[]{
- new Lambda(pkg, "MainKt$testStateless$6", 1) /* Banned for limited inlining */} :
- new Lambda[]{
- new Lambda(pkg, "MainKt$testStateless$6", 1), /* Banned for limited inlining */
- new Lambda(pkg, "MainKt$testStateless$8", 2),
- new Lambda(pkg + "/inner", "InnerKt$testInnerStateless$7", 2)}
-
- );
- });
- }
-
- @Test
- public void testCaptures() throws Exception {
- final String mainClassName = "lambdas.kstyle.captures.MainKt";
- runTest("lambdas_kstyle_captures", mainClassName, null, (app) -> {
- Verifier verifier = new Verifier(app);
- String pkg = "lambdas/kstyle/captures";
- String grpPkg = allowAccessModification ? "" : pkg;
-
- verifier.assertLambdaGroups(
- new Group(grpPkg, "LLL", 0),
- new Group(grpPkg, "ILL", 0),
- new Group(grpPkg, "III", 0),
- new Group(grpPkg, "BCDFIJLLLLSZ", 0),
- new Group(grpPkg, "BCDFIJLLSZ", 0)
- );
-
- verifier.assertLambdas(
- new Lambda(pkg, "MainKt$test1$15", 0),
- new Lambda(pkg, "MainKt$test2$10", 0),
- new Lambda(pkg, "MainKt$test2$11", 0),
- new Lambda(pkg, "MainKt$test2$9", 0)
- );
- });
- }
-
- @Test
- public void testGenericsNoSignature() throws Exception {
- final String mainClassName = "lambdas.kstyle.generics.MainKt";
- runTest("lambdas_kstyle_generics", mainClassName, null, (app) -> {
- Verifier verifier = new Verifier(app);
- String pkg = "lambdas/kstyle/generics";
- String grpPkg = allowAccessModification ? "" : pkg;
-
- verifier.assertLambdaGroups(
- new Group(grpPkg, "", 1), // Group for Any
- new Group(grpPkg, "L", 1), // Group for Beta
- new Group(grpPkg, "LS", 1), // Group for Gamma
- new Group(grpPkg, "", 1) // Group for int
- );
-
- verifier.assertLambdas(
- new Lambda(pkg, "MainKt$main$4", 1)
- );
- });
- }
-
- @Test
- public void testInnerClassesAndEnclosingMethods() throws Exception {
- final String mainClassName = "lambdas.kstyle.generics.MainKt";
- runTest("lambdas_kstyle_generics", mainClassName, KEEP_INNER_AND_ENCLOSING, (app) -> {
- Verifier verifier = new Verifier(app);
- String pkg = "lambdas/kstyle/generics";
- String grpPkg = allowAccessModification ? "" : pkg;
-
- verifier.assertLambdaGroups(
- new Group(grpPkg, "", 1), // Group for Any
- new Group(grpPkg, "L", 1), // Group for Beta // First
- new Group(grpPkg, "L", 1), // Group for Beta // Second
- new Group(grpPkg, "LS", 1), // Group for Gamma // First
- new Group(grpPkg, "LS", 1), // Group for Gamma // Second
- new Group(grpPkg, "", 1) // Group for int
- );
-
- verifier.assertLambdas(
- new Lambda(pkg, "MainKt$main$4", 1)
- );
- });
- }
-
- @Test
- public void testGenericsSignatureInnerEnclosing() throws Exception {
- final String mainClassName = "lambdas.kstyle.generics.MainKt";
- runTest("lambdas_kstyle_generics", mainClassName, KEEP_SIGNATURE_INNER_ENCLOSING, (app) -> {
- Verifier verifier = new Verifier(app);
- String pkg = "lambdas/kstyle/generics";
- String grpPkg = allowAccessModification ? "" : pkg;
-
- verifier.assertLambdaGroups(
- new Group(grpPkg, "", 1), // Group for Any
- new Group(grpPkg, "L", 1), // Group for Beta in First
- new Group(grpPkg, "L", 1), // Group for Beta in Second
- new Group(grpPkg, "LS", 1), // Group for Gamma<String> in First
- new Group(grpPkg, "LS", 1), // Group for Gamma<Integer> in First
- new Group(grpPkg, "LS", 1), // Group for Gamma<String> in Second
- new Group(grpPkg, "LS", 1), // Group for Gamma<Integer> in Second
- new Group(grpPkg, "", 1) // Group for int
- );
-
- verifier.assertLambdas(
- new Lambda(pkg, "MainKt$main$4", 1)
- );
- });
- }
-}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
new file mode 100644
index 0000000..65dc98f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -0,0 +1,451 @@
+// 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 junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.optimize.lambda.CaptureSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.Lists;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class KotlinLambdaMergingTest extends AbstractR8KotlinTestBase {
+ private static final String KOTLIN_FUNCTION_IFACE = "Lkotlin/jvm/functions/Function";
+ private static final String KOTLIN_FUNCTION_IFACE_STR = "kotlin.jvm.functions.Function";
+ private static final String KEEP_INNER_AND_ENCLOSING =
+ "-keepattributes InnerClasses,EnclosingMethod\n";
+ private static final String KEEP_SIGNATURE_INNER_ENCLOSING =
+ "-keepattributes Signature,InnerClasses,EnclosingMethod\n";
+
+ abstract static class LambdaOrGroup {
+ abstract boolean match(DexClass clazz);
+ }
+
+ private static class Group extends LambdaOrGroup {
+ final String pkg;
+ final String capture;
+ final int arity;
+ final String sam;
+ final int singletons;
+
+ private Group(String pkg, String capture, int arity, String sam, int singletons) {
+ this.pkg = pkg;
+ this.capture = fixCapture(capture);
+ this.arity = arity;
+ this.sam = sam;
+ this.singletons = singletons;
+ }
+
+ private String fixCapture(String capture) {
+ capture += "I";
+ char[] chars = capture.toCharArray();
+ Arrays.sort(chars);
+ return new String(chars);
+ }
+
+ @Override
+ public String toString() {
+ return "group class " +
+ (pkg.length() == 0 ? "" : pkg + "/") +
+ "-$$LambdaGroup$XXXX (arity: " + arity +
+ ", capture: " + capture + ", iface: " + sam + ", sing: " + singletons + ")";
+ }
+
+ @Override
+ boolean match(DexClass clazz) {
+ return clazz.type.getPackageDescriptor().equals(pkg) &&
+ getLambdaOrGroupCapture(clazz).equals(capture) &&
+ getLambdaSam(clazz).equals(sam) &&
+ getLambdaSingletons(clazz) == singletons &&
+ getLambdaOrGroupArity(clazz) == arity;
+ }
+ }
+
+ private Group kstyleImpl(String pkg, String capture, int arity, int singletons) {
+ assertEquals(capture.isEmpty(), singletons != 0);
+ return new Group(pkg, capture, arity, KOTLIN_FUNCTION_IFACE_STR + arity, singletons);
+ }
+
+ private Group kstyle(String pkg, int arity, int singletons) {
+ assertTrue(singletons != 0);
+ return kstyleImpl(pkg, "", arity, singletons);
+ }
+
+ private Group kstyle(String pkg, String capture, int arity) {
+ assertFalse(capture.isEmpty());
+ return kstyleImpl(pkg, capture, arity, 0);
+ }
+
+ private Group jstyleImpl(String pkg, String capture, int arity, String sam, int singletons) {
+ assertTrue(capture.isEmpty() || singletons == 0);
+ return new Group(pkg, capture, arity, sam, singletons);
+ }
+
+ private Group jstyle(String pkg, String capture, int arity, String sam) {
+ return jstyleImpl(pkg, capture, arity, sam, 0);
+ }
+
+ private Group jstyle(String pkg, int arity, String sam, int singletons) {
+ return jstyleImpl(pkg, "", arity, sam, singletons);
+ }
+
+ static class Lambda extends LambdaOrGroup {
+ final String pkg;
+ final String name;
+ final int arity;
+
+ private Lambda(String pkg, String name, int arity) {
+ this.pkg = pkg;
+ this.name = name;
+ this.arity = arity;
+ }
+
+ @Override
+ public String toString() {
+ return "lambda class " +
+ (pkg.length() == 0 ? "" : pkg + "/") +
+ name + " (arity: " + arity + ")";
+ }
+
+ @Override
+ boolean match(DexClass clazz) {
+ return clazz.type.getPackageDescriptor().equals(pkg) &&
+ clazz.type.getName().equals(name) &&
+ getLambdaOrGroupArity(clazz) == arity;
+ }
+ }
+
+ static class Verifier {
+ final DexInspector dexInspector;
+ final List<DexClass> lambdas = new ArrayList<>();
+ final List<DexClass> groups = new ArrayList<>();
+
+ Verifier(AndroidApp app) throws IOException, ExecutionException {
+ this.dexInspector = new DexInspector(app);
+ dexInspector.forAllClasses(clazz -> {
+ DexClass dexClass = clazz.getDexClass();
+ if (isLambdaOrGroup(dexClass)) {
+ if (isLambdaGroupClass(dexClass)) {
+ groups.add(dexClass);
+ } else {
+ lambdas.add(dexClass);
+ }
+ }
+ });
+ }
+
+ void assertLambdaGroups(Group... groups) {
+ assertLambdasOrGroups("Lambda group", this.groups, groups);
+ }
+
+ void assertLambdas(Lambda... lambdas) {
+ assertLambdasOrGroups("Lambda", this.lambdas, lambdas);
+ }
+
+ @SafeVarargs
+ private static <T extends LambdaOrGroup>
+ void assertLambdasOrGroups(String what, List<DexClass> objects, T... checks) {
+ ArrayList<DexClass> list = Lists.newArrayList(objects);
+ for (int i = 0; i < checks.length; i++) {
+ T check = checks[i];
+ for (DexClass clazz : list) {
+ if (check.match(clazz)) {
+ // Validate static initializer.
+ if (check instanceof Group) {
+ assertEquals(clazz.directMethods().length, ((Group) check).singletons == 0 ? 1 : 2);
+ }
+
+ list.remove(clazz);
+ checks[i] = null;
+ break;
+ }
+ }
+ }
+
+ int notFound = 0;
+ for (T check : checks) {
+ if (check != null) {
+ System.err.println(what + " not found: " + check);
+ notFound++;
+ }
+ }
+
+ for (DexClass dexClass : list) {
+ System.err.println(what + " unexpected: " +
+ dexClass.type.descriptor.toString() +
+ ", arity: " + getLambdaOrGroupArity(dexClass) +
+ ", capture: " + getLambdaOrGroupCapture(dexClass) +
+ ", sam: " + getLambdaSam(dexClass) +
+ ", sing: " + getLambdaSingletons(dexClass));
+ notFound++;
+ }
+
+ assertTrue(what + "s match failed", 0 == notFound && 0 == list.size());
+ }
+ }
+
+ private static int getLambdaOrGroupArity(DexClass clazz) {
+ if (isKStyleLambdaOrGroup(clazz)) {
+ for (DexType iface : clazz.interfaces.values) {
+ String descr = iface.descriptor.toString();
+ if (descr.startsWith(KOTLIN_FUNCTION_IFACE)) {
+ return Integer.parseInt(
+ descr.substring(KOTLIN_FUNCTION_IFACE.length(), descr.length() - 1));
+ }
+ }
+
+ } else {
+ assertTrue(isJStyleLambdaOrGroup(clazz));
+ // Taking the number of any virtual method parameters seems to be good enough.
+ assertTrue(clazz.virtualMethods().length > 0);
+ return clazz.virtualMethods()[0].method.proto.parameters.size();
+ }
+ fail("Failed to get arity for " + clazz.type.descriptor.toString());
+ throw new AssertionError();
+ }
+
+ private static String getLambdaSam(DexClass clazz) {
+ assertEquals(1, clazz.interfaces.size());
+ return clazz.interfaces.values[0].toSourceString();
+ }
+
+ private static int getLambdaSingletons(DexClass clazz) {
+ assertEquals(1, clazz.interfaces.size());
+ return clazz.staticFields().length;
+ }
+
+ private static boolean isLambdaOrGroup(DexClass clazz) {
+ return !clazz.type.getPackageDescriptor().startsWith("kotlin") &&
+ (isKStyleLambdaOrGroup(clazz) || isJStyleLambdaOrGroup(clazz));
+ }
+
+ private static boolean isKStyleLambdaOrGroup(DexClass clazz) {
+ return clazz.superType.descriptor.toString().equals("Lkotlin/jvm/internal/Lambda;");
+ }
+
+ private static boolean isJStyleLambdaOrGroup(DexClass clazz) {
+ return clazz.superType.descriptor.toString().equals("Ljava/lang/Object;") &&
+ clazz.interfaces.size() == 1;
+ }
+
+ private static boolean isLambdaGroupClass(DexClass clazz) {
+ return clazz.type.getName().startsWith("-$$LambdaGroup$");
+ }
+
+ private static String getLambdaOrGroupCapture(DexClass clazz) {
+ return CaptureSignature.getCaptureSignature(clazz.instanceFields());
+ }
+
+ @Test
+ public void testTrivialKs() throws Exception {
+ final String mainClassName = "lambdas.kstyle.trivial.MainKt";
+ runTest("lambdas_kstyle_trivial", mainClassName, null, (app) -> {
+ Verifier verifier = new Verifier(app);
+ String pkg = "lambdas/kstyle/trivial";
+
+ verifier.assertLambdaGroups(
+ allowAccessModification ?
+ new Group[]{
+ kstyle("", 0, 4),
+ kstyle("", 1, 8),
+ kstyle("", 2, 2), // -\
+ kstyle("", 2, 5), // - 3 groups different by main method
+ kstyle("", 2, 4), // -/
+ kstyle("", 3, 2),
+ kstyle("", 22, 2)} :
+ new Group[]{
+ kstyle(pkg, 0, 2),
+ kstyle(pkg, 1, 4),
+ kstyle(pkg, 2, 5), // - 2 groups different by main method
+ kstyle(pkg, 2, 4), // -/
+ kstyle(pkg, 3, 2),
+ kstyle(pkg, 22, 2),
+ kstyle(pkg + "/inner", 0, 2),
+ kstyle(pkg + "/inner", 1, 4)}
+ );
+
+ verifier.assertLambdas(
+ allowAccessModification ?
+ new Lambda[]{
+ new Lambda(pkg, "MainKt$testStateless$6", 1) /* Banned for limited inlining */} :
+ new Lambda[]{
+ new Lambda(pkg, "MainKt$testStateless$6", 1), /* Banned for limited inlining */
+ new Lambda(pkg, "MainKt$testStateless$8", 2),
+ new Lambda(pkg + "/inner", "InnerKt$testInnerStateless$7", 2)}
+
+ );
+ });
+ }
+
+ @Test
+ public void testCapturesKs() throws Exception {
+ final String mainClassName = "lambdas.kstyle.captures.MainKt";
+ runTest("lambdas_kstyle_captures", mainClassName, null, (app) -> {
+ Verifier verifier = new Verifier(app);
+ String pkg = "lambdas/kstyle/captures";
+ String grpPkg = allowAccessModification ? "" : pkg;
+
+ verifier.assertLambdaGroups(
+ kstyle(grpPkg, "LLL", 0),
+ kstyle(grpPkg, "ILL", 0),
+ kstyle(grpPkg, "III", 0),
+ kstyle(grpPkg, "BCDFIJLLLLSZ", 0),
+ kstyle(grpPkg, "BCDFIJLLSZ", 0)
+ );
+
+ verifier.assertLambdas(
+ new Lambda(pkg, "MainKt$test1$15", 0),
+ new Lambda(pkg, "MainKt$test2$10", 0),
+ new Lambda(pkg, "MainKt$test2$11", 0),
+ new Lambda(pkg, "MainKt$test2$9", 0)
+ );
+ });
+ }
+
+ @Test
+ public void testGenericsNoSignatureKs() throws Exception {
+ final String mainClassName = "lambdas.kstyle.generics.MainKt";
+ runTest("lambdas_kstyle_generics", mainClassName, null, (app) -> {
+ Verifier verifier = new Verifier(app);
+ String pkg = "lambdas/kstyle/generics";
+ String grpPkg = allowAccessModification ? "" : pkg;
+
+ verifier.assertLambdaGroups(
+ kstyle(grpPkg, 1, 3), // Group for Any
+ kstyle(grpPkg, "L", 1), // Group for Beta
+ kstyle(grpPkg, "LS", 1), // Group for Gamma
+ kstyle(grpPkg, 1, 2) // Group for int
+ );
+
+ verifier.assertLambdas(
+ new Lambda(pkg, "MainKt$main$4", 1)
+ );
+ });
+ }
+
+ @Test
+ public void testInnerClassesAndEnclosingMethodsKs() throws Exception {
+ final String mainClassName = "lambdas.kstyle.generics.MainKt";
+ runTest("lambdas_kstyle_generics", mainClassName, KEEP_INNER_AND_ENCLOSING, (app) -> {
+ Verifier verifier = new Verifier(app);
+ String pkg = "lambdas/kstyle/generics";
+ String grpPkg = allowAccessModification ? "" : pkg;
+
+ verifier.assertLambdaGroups(
+ kstyle(grpPkg, 1, 3), // Group for Any
+ kstyle(grpPkg, "L", 1), // Group for Beta // First
+ kstyle(grpPkg, "L", 1), // Group for Beta // Second
+ kstyle(grpPkg, "LS", 1), // Group for Gamma // First
+ kstyle(grpPkg, "LS", 1), // Group for Gamma // Second
+ kstyle(grpPkg, 1, 2) // Group for int
+ );
+
+ verifier.assertLambdas(
+ new Lambda(pkg, "MainKt$main$4", 1)
+ );
+ });
+ }
+
+ @Test
+ public void testGenericsSignatureInnerEnclosingKs() throws Exception {
+ final String mainClassName = "lambdas.kstyle.generics.MainKt";
+ runTest("lambdas_kstyle_generics", mainClassName, KEEP_SIGNATURE_INNER_ENCLOSING, (app) -> {
+ Verifier verifier = new Verifier(app);
+ String pkg = "lambdas/kstyle/generics";
+ String grpPkg = allowAccessModification ? "" : pkg;
+
+ verifier.assertLambdaGroups(
+ kstyle(grpPkg, 1, 3), // Group for Any
+ kstyle(grpPkg, "L", 1), // Group for Beta in First
+ kstyle(grpPkg, "L", 1), // Group for Beta in Second
+ kstyle(grpPkg, "LS", 1), // Group for Gamma<String> in First
+ kstyle(grpPkg, "LS", 1), // Group for Gamma<Integer> in First
+ kstyle(grpPkg, "LS", 1), // Group for Gamma<String> in Second
+ kstyle(grpPkg, "LS", 1), // Group for Gamma<Integer> in Second
+ kstyle(grpPkg, 1, 2) // Group for int
+ );
+
+ verifier.assertLambdas(
+ new Lambda(pkg, "MainKt$main$4", 1)
+ );
+ });
+ }
+
+ @Test
+ public void testTrivialJs() throws Exception {
+ final String mainClassName = "lambdas.jstyle.trivial.MainKt";
+ runTest("lambdas_jstyle_trivial", mainClassName, null, (app) -> {
+ Verifier verifier = new Verifier(app);
+ String pkg = "lambdas/jstyle/trivial";
+ String grp = allowAccessModification ? "" : pkg;
+
+ String supplier = "Lambdas$Supplier";
+ String intSupplier = "Lambdas$IntSupplier";
+ String consumer = "Lambdas$Consumer";
+ String intConsumer = "Lambdas$IntConsumer";
+ String multiFunction = "Lambdas$MultiFunction";
+
+ verifier.assertLambdaGroups(
+ jstyle(grp, 0, intSupplier, 2),
+ jstyle(grp, "L", 0, supplier),
+ jstyle(grp, "LL", 0, supplier),
+ jstyle(grp, "LLL", 0, supplier),
+ jstyle(grp, 1, intConsumer, allowAccessModification ? 3 : 2),
+ jstyle(grp, "I", 1, consumer),
+ jstyle(grp, "II", 1, consumer),
+ jstyle(grp, "III", 1, consumer),
+ jstyle(grp, "IIII", 1, consumer),
+ jstyle(grp, 3, multiFunction, 2),
+ jstyle(grp, 3, multiFunction, 2),
+ jstyle(grp, 3, multiFunction, 4),
+ jstyle(grp, 3, multiFunction, 6)
+ );
+
+ verifier.assertLambdas(
+ allowAccessModification ?
+ new Lambda[]{
+ new Lambda(pkg + "/inner", "InnerKt$testInner1$4", 1),
+ new Lambda(pkg + "/inner", "InnerKt$testInner1$5", 1)
+ } :
+ new Lambda[]{
+ new Lambda(pkg + "/inner", "InnerKt$testInner1$1", 1),
+ new Lambda(pkg + "/inner", "InnerKt$testInner1$2", 1),
+ new Lambda(pkg + "/inner", "InnerKt$testInner1$3", 1),
+ new Lambda(pkg + "/inner", "InnerKt$testInner1$4", 1),
+ new Lambda(pkg + "/inner", "InnerKt$testInner1$5", 1)
+ }
+
+ );
+ });
+ }
+
+ @Test
+ public void testSingleton() throws Exception {
+ final String mainClassName = "lambdas.singleton.MainKt";
+ runTest("lambdas_singleton", mainClassName, null, (app) -> {
+ Verifier verifier = new Verifier(app);
+ String pkg = "lambdas/singleton";
+ String grp = allowAccessModification ? "" : pkg;
+
+ verifier.assertLambdaGroups(
+ kstyle(grp, 1, 1 /* 1 out of 5 lambdas in the group */),
+ jstyle(grp, 2, "java.util.Comparator", 0 /* 0 out of 2 lambdas in the group */)
+ );
+
+ verifier.assertLambdas(/* None */);
+ });
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
index b96c17a..c82bbc9 100644
--- a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.utils.AndroidApp;
import com.google.common.collect.ImmutableList;
import java.util.List;
-import joptsimple.internal.Strings;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -41,7 +40,7 @@
AndroidApp app = readClassesAndAndriodJar(ImmutableList.of(
mainClass, Interface.class, Super.class, TestClass.class,
OtherPackageSuper.class, OtherPackageTestClass.class));
- app = compileWithR8(app, Strings.join(config, System.lineSeparator()));
+ app = compileWithR8(app, String.join(System.lineSeparator(), config));
assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
}
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 9a31c0d..004c31f 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
@@ -17,7 +17,6 @@
import java.io.File;
import java.nio.file.Path;
import java.util.List;
-import joptsimple.internal.Strings;
public class ProguardCompatabilityTestBase extends TestBase {
@@ -31,7 +30,7 @@
protected DexInspector runShrinker(
Shrinker mode, List<Class> programClasses, List<String> proguadConfigs) throws Exception {
- return runShrinker(mode, programClasses, Strings.join(proguadConfigs, System.lineSeparator()));
+ return runShrinker(mode, programClasses, String.join(System.lineSeparator(), proguadConfigs));
}
protected DexInspector runShrinker(
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/Lambdas.java b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/Lambdas.java
new file mode 100644
index 0000000..69e9a78
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/Lambdas.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.
+
+public class Lambdas {
+ public interface IntConsumer {
+ void put(int value);
+ }
+
+ public interface Consumer<T> {
+ void put(T t);
+ }
+
+ public interface Supplier<T> {
+ T get();
+ }
+
+ public interface IntSupplier {
+ int get();
+ }
+
+ public interface MultiFunction<R, P1, P2, P3> {
+ R get(P1 a, P2 b, P3 c);
+ }
+
+ public synchronized static void acceptStringSupplier(Supplier<String> s) {
+ System.out.println(s.get());
+ }
+
+ public synchronized static <T> void acceptGenericSupplier(Supplier<T> s) {
+ System.out.println(s.get());
+ }
+
+ public synchronized static void acceptIntSupplier(IntSupplier s) {
+ System.out.println(s.get());
+ }
+
+ public synchronized static void acceptStringConsumer(Consumer<String> s, String arg) {
+ s.put(arg);
+ }
+
+ public synchronized static <T> void acceptGenericConsumer(Consumer<T> s, T arg) {
+ s.put(arg);
+ }
+
+ public synchronized static void acceptIntConsumer(IntConsumer s, int arg) {
+ s.put(arg);
+ }
+
+ public synchronized static <R, P1, P2, P3>
+ void acceptMultiFunction(MultiFunction<R, P1, P2, P3> s, P1 a, P2 b, P3 c) {
+ System.out.println(s.get(a, b, c));
+ }
+}
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/inner.kt b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/inner.kt
new file mode 100644
index 0000000..5da8884
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/inner.kt
@@ -0,0 +1,26 @@
+// 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 lambdas.jstyle.trivial.inner
+
+import Lambdas
+import lambdas.jstyle.trivial.next
+import lambdas.jstyle.trivial.nextInt
+
+fun testInner() {
+ testInner1(nextInt(), nextInt(), nextInt(), nextInt())
+}
+
+private data class InnerLocal<out T>(val id: T)
+
+private fun testInner1(c0: Int, c1: Int, c2: Int, c3: Int) {
+ Lambdas.acceptIntConsumer({ println("{${next()}:$it}") }, 100)
+ Lambdas.acceptStringConsumer({ println("${next()}:{$it}:{$c0}") }, next())
+ Lambdas.acceptGenericConsumer({ println("${next()}:{$it}:{$c0}:{$c1}") }, next())
+ Lambdas.acceptGenericConsumer(
+ { println("${next()}:{$it}:{$c0}:{$c1}:{$c2}") }, InnerLocal(next()))
+ Lambdas.acceptGenericConsumer(
+ { println("${next()}:{$it}:{$c0}:{$c1}:{$c2}:{$c3") }, InnerLocal(InnerLocal(next())))
+}
+
diff --git a/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/main.kt b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/main.kt
new file mode 100644
index 0000000..39673af
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_jstyle_trivial/main.kt
@@ -0,0 +1,88 @@
+// 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 lambdas.jstyle.trivial
+
+import Lambdas
+import lambdas.jstyle.trivial.inner.testInner
+
+private var COUNT = 0
+
+fun nextInt() = COUNT++
+fun next() = "${nextInt()}".padStart(3, '0')
+
+fun main(args: Array<String>) {
+ test()
+ testInner()
+}
+
+private fun test() {
+ test1(nextInt(), nextInt(), nextInt(), nextInt())
+ test2(next(), next(), next())
+ test3a(next(), next(), next())
+ test3a(next(), Local(next()), Local(Local(next())))
+ test3b(next(), next(), next())
+ test3b(next(), Local(next()), Local(next()))
+}
+
+private data class Local<out T>(val id: T)
+
+private fun test1(c0: Int, c1: Int, c2: Int, c3: Int) {
+ Lambdas.acceptIntConsumer({ println("{${next()}:$it}") }, nextInt())
+ Lambdas.acceptIntConsumer({ println("{${next()}:$it}") }, nextInt())
+
+ Lambdas.acceptStringConsumer({ println("${next()}:{$it}:{$c0}") }, next())
+ Lambdas.acceptStringConsumer({ println("${next()}:{$it}:{$c0}") }, next())
+
+ Lambdas.acceptGenericConsumer({ println("${next()}:{$it}:{$c0}:{$c1}") }, next())
+ Lambdas.acceptGenericConsumer({ println("${next()}:{$it}:{$c0}:{$c1}") }, next())
+
+ Lambdas.acceptGenericConsumer({ println("${next()}:{$it}:{$c0}:{$c1}:{$c2}") }, Local(next()))
+ Lambdas.acceptGenericConsumer({ println("${next()}:{$it}:{$c0}:{$c1}:{$c2}") }, Local(next()))
+
+ Lambdas.acceptGenericConsumer(
+ { println("${next()}:{$it}:{$c0}:{$c1}:{$c2}:{$c3}") }, Local(Local(next())))
+ Lambdas.acceptGenericConsumer(
+ { println("${next()}:{$it}:{$c0}:{$c1}:{$c2}:{$c3}") }, Local(Local(next())))
+}
+
+private fun test2(c0: String, c1: String, c2: String) {
+ println(Lambdas.acceptIntSupplier { nextInt() })
+ println(Lambdas.acceptIntSupplier { nextInt() })
+
+ println(Lambdas.acceptStringSupplier { "${next()}:$c0" })
+ println(Lambdas.acceptStringSupplier { "${next()}:$c0" })
+
+ println(Lambdas.acceptGenericSupplier { "${next()}:$c0" })
+ println(Lambdas.acceptGenericSupplier { "${next()}:$c0" })
+
+ println(Lambdas.acceptGenericSupplier { "${Local(next())}:$c0:$c1" })
+ println(Lambdas.acceptGenericSupplier { "${Local(next())}:$c0:$c1" })
+
+ println(Lambdas.acceptGenericSupplier { "${Local(Local(next()))}:$c0:$c1:$c2" })
+ println(Lambdas.acceptGenericSupplier { "${Local(Local(next()))}:$c0:$c1:$c2" })
+}
+
+private fun <P1, P2, P3> test3a(a: P1, b: P2, c: P3) {
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, a, b, c)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, c, a, b)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, b, c, a)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, Local(a), b, c)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, Local(b), a, c)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, a, Local(b), c)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, a, Local(c), b)
+ Lambdas.acceptMultiFunction(
+ { x, y, z -> "$x:$y:$z" }, Local(Local(a)), Local(Local(b)), Local(Local(c)))
+ Lambdas.acceptMultiFunction(
+ { x, y, z -> "$x:$y:$z" }, Local(Local(c)), Local(Local(a)), Local(Local(b)))
+}
+
+private fun <P> test3b(a: P, b: P, c: P) {
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, a, b, c)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, c, a, b)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, b, c, a)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, Local(a), b, c)
+ Lambdas.acceptMultiFunction({ x, y, z -> "$x:$y:$z" }, Local(b), a, c)
+}
+
diff --git a/src/test/kotlinR8TestResources/lambdas_singleton/Lambdas.java b/src/test/kotlinR8TestResources/lambdas_singleton/Lambdas.java
new file mode 100644
index 0000000..4140293
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_singleton/Lambdas.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.
+
+public class Lambdas {
+ public interface Function<R, P1, P2> {
+ R get(P1 a, P2 b);
+ }
+
+ public synchronized static <R, P1, P2> void accept(Function<R, P1, P2> s, P1 a, P2 b) {
+ System.out.println(s.get(a, b));
+ }
+}
diff --git a/src/test/kotlinR8TestResources/lambdas_singleton/main.kt b/src/test/kotlinR8TestResources/lambdas_singleton/main.kt
new file mode 100644
index 0000000..8e34a0c
--- /dev/null
+++ b/src/test/kotlinR8TestResources/lambdas_singleton/main.kt
@@ -0,0 +1,46 @@
+// 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 lambdas.singleton
+
+private var COUNT = 0
+
+fun nextInt() = COUNT++
+fun next() = "${nextInt()}".padStart(3, '0')
+
+fun main(args: Array<String>) {
+ test()
+}
+
+private fun test() {
+ test2(listOf(next(), next(), next(), next(), next(), next(), next(), next(), next(), next()))
+}
+
+private fun Collection<String>.flatten() =
+ this.joinToString(prefix = "(*", postfix = "*)", separator = "*")
+
+private fun Array<String>.flatten() =
+ this.joinToString(prefix = "(*", postfix = "*)", separator = "*")
+
+private fun test2(args: Collection<String>) {
+ println(args.sortedByDescending { it.length }.flatten())
+ println(args.sortedByDescending { -it.length }.flatten())
+ process(::println)
+ process(::println)
+ val lambda: (Array<String>) -> Unit = {}
+}
+
+private inline fun process(crossinline f: (String) -> Unit) {
+ feed2 { f(it.flatten()) }
+ feed3 { f(it.flatten()) }
+}
+
+private fun feed3(f: (Array<String>) -> Unit) {
+ f(arrayOf(next(), next(), next()))
+}
+
+private fun feed2(f: (Array<String>) -> Unit) {
+ f(arrayOf(next(), next()))
+}
+