Collect non-null parameter hints from Kotlin metadata.
Test:
$ tools/test.py --tool=r8 *kotlin*
Bug: 110879661
Change-Id: I59616bd5aabf1285861d0c0254ef8d6b3f3b92ec
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 33531da..58e22b7 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.graph.DexAnnotation;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexApplication.Builder;
+import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
@@ -51,6 +52,7 @@
import com.android.tools.r8.ir.optimize.lambda.LambdaMerger;
import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.kotlin.KotlinInfo;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.IdentifierNameStringMarker;
import com.android.tools.r8.shaking.protolite.ProtoLitePruner;
@@ -647,9 +649,10 @@
printC1VisualizerHeader(method);
printMethod(code, "Initial IR (SSA)");
- if (method.getCode() != null && method.getCode().isJarCode() &&
- appInfo.definitionFor(method.method.holder).hasKotlinInfo()) {
- computeKotlinNotNullParamHints(feedback, method, code);
+ DexClass holder = appInfo.definitionFor(method.method.holder);
+ if (method.getCode() != null && method.getCode().isJarCode()
+ && holder.hasKotlinInfo()) {
+ computeKotlinNotNullParamHints(feedback, holder.getKotlinInfo(), method, code);
}
if (options.canHaveArtStringNewInitBug()) {
@@ -807,8 +810,19 @@
}
private void computeKotlinNotNullParamHints(
- OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
- // Try to infer Kotlin non-null parameter check to use it as a hint.
+ OptimizationFeedback feedback, KotlinInfo kotlinInfo, DexEncodedMethod method, IRCode code) {
+ // Use non-null parameter hints in Kotlin metadata if available.
+ if (kotlinInfo.hasNonNullParameterHints()) {
+ BitSet hintFromMetadata = kotlinInfo.lookupNonNullParameterHint(
+ method.method.name.toString(), method.method.proto.toDescriptorString());
+ if (hintFromMetadata != null) {
+ if (hintFromMetadata.length() > 0) {
+ feedback.setKotlinNotNullParamHints(method, hintFromMetadata);
+ }
+ return;
+ }
+ }
+ // Otherwise, fall back to inspecting the code.
List<Value> arguments = code.collectArguments(true);
BitSet paramsCheckedForNull = new BitSet();
DexMethod checkParameterIsNotNull =
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index 74184a5..d15ed5a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -4,7 +4,12 @@
package com.android.tools.r8.kotlin;
+import static kotlinx.metadata.Flag.Property.IS_VAR;
+
import kotlinx.metadata.KmClassVisitor;
+import kotlinx.metadata.KmConstructorVisitor;
+import kotlinx.metadata.KmFunctionVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public class KotlinClass extends KotlinInfo<KotlinClassMetadata.Class> {
@@ -20,13 +25,30 @@
}
@Override
- void validateMetadata(KotlinClassMetadata.Class metadata) {
- ClassMetadataVisitor visitor = new ClassMetadataVisitor();
+ void processMetadata(KotlinClassMetadata.Class metadata) {
// To avoid lazy parsing/verifying metadata.
- metadata.accept(visitor);
+ metadata.accept(new ClassVisitorForNonNullParameterHints());
}
- private static class ClassMetadataVisitor extends KmClassVisitor {
+ private class ClassVisitorForNonNullParameterHints extends KmClassVisitor {
+ @Override
+ public KmFunctionVisitor visitFunction(int functionFlags, String functionName) {
+ return new NonNullParameterHintCollector.FunctionVisitor(nonNullparamHints);
+ }
+
+ @Override
+ public KmConstructorVisitor visitConstructor(int ctorFlags) {
+ return new NonNullParameterHintCollector.ConstructorVisitor(nonNullparamHints);
+ }
+
+ @Override
+ public KmPropertyVisitor visitProperty(
+ int propertyFlags, String name, int getterFlags, int setterFlags) {
+ if (IS_VAR.invoke(propertyFlags)) {
+ return new NonNullParameterHintCollector.PropertyVisitor(nonNullparamHints);
+ }
+ return null;
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index 62db3d1..de8a12a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -20,7 +20,7 @@
}
@Override
- void validateMetadata(KotlinClassMetadata.MultiFileClassFacade metadata) {
+ void processMetadata(KotlinClassMetadata.MultiFileClassFacade metadata) {
// No worries about lazy parsing/verifying, since no API to explore metadata details.
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index da66e6c..225e63a 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,7 +4,11 @@
package com.android.tools.r8.kotlin;
+import static kotlinx.metadata.Flag.Property.IS_VAR;
+
+import kotlinx.metadata.KmFunctionVisitor;
import kotlinx.metadata.KmPackageVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public final class KotlinClassPart extends KotlinInfo<KotlinClassMetadata.MultiFileClassPart> {
@@ -21,12 +25,25 @@
}
@Override
- void validateMetadata(KotlinClassMetadata.MultiFileClassPart metadata) {
+ void processMetadata(KotlinClassMetadata.MultiFileClassPart metadata) {
// To avoid lazy parsing/verifying metadata.
- metadata.accept(new MultiFileClassPartMetadataVisitor());
+ metadata.accept(new PackageVisitorForNonNullParameterHints());
}
- private static class MultiFileClassPartMetadataVisitor extends KmPackageVisitor {
+ private class PackageVisitorForNonNullParameterHints extends KmPackageVisitor {
+ @Override
+ public KmFunctionVisitor visitFunction(int functionFlags, String functionName) {
+ return new NonNullParameterHintCollector.FunctionVisitor(nonNullparamHints);
+ }
+
+ @Override
+ public KmPropertyVisitor visitProperty(
+ int propertyFlags, String name, int getterFlags, int setterFlags) {
+ if (IS_VAR.invoke(propertyFlags)) {
+ return new NonNullParameterHintCollector.PropertyVisitor(nonNullparamHints);
+ }
+ return null;
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index bcb70ed..2f9477e 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -4,7 +4,11 @@
package com.android.tools.r8.kotlin;
+import static kotlinx.metadata.Flag.Property.IS_VAR;
+
+import kotlinx.metadata.KmFunctionVisitor;
import kotlinx.metadata.KmPackageVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
import kotlinx.metadata.jvm.KotlinClassMetadata;
public final class KotlinFile extends KotlinInfo<KotlinClassMetadata.FileFacade> {
@@ -21,12 +25,25 @@
}
@Override
- void validateMetadata(KotlinClassMetadata.FileFacade metadata) {
+ void processMetadata(KotlinClassMetadata.FileFacade metadata) {
// To avoid lazy parsing/verifying metadata.
- metadata.accept(new FileFacadeMetadataVisitor());
+ metadata.accept(new PackageVisitorForNonNullParameterHints());
}
- private static class FileFacadeMetadataVisitor extends KmPackageVisitor {
+ private class PackageVisitorForNonNullParameterHints extends KmPackageVisitor {
+ @Override
+ public KmFunctionVisitor visitFunction(int functionFlags, String functionName) {
+ return new NonNullParameterHintCollector.FunctionVisitor(nonNullparamHints);
+ }
+
+ @Override
+ public KmPropertyVisitor visitProperty(
+ int propertyFlags, String name, int getterFlags, int setterFlags) {
+ if (IS_VAR.invoke(propertyFlags)) {
+ return new NonNullParameterHintCollector.PropertyVisitor(nonNullparamHints);
+ }
+ return null;
+ }
}
@Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index 702e0eb..7cc8aa1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -4,22 +4,25 @@
package com.android.tools.r8.kotlin;
+import com.google.common.collect.HashBasedTable;
+import java.util.BitSet;
import kotlinx.metadata.jvm.KotlinClassMetadata;
// Provides access to kotlin information.
public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> {
MetadataKind metadata;
+ final HashBasedTable<String, String, BitSet> nonNullparamHints = HashBasedTable.create();
KotlinInfo() {
}
KotlinInfo(MetadataKind metadata) {
- validateMetadata(metadata);
+ processMetadata(metadata);
this.metadata = metadata;
}
- // Subtypes will define how to validate the given metadata.
- abstract void validateMetadata(MetadataKind metadata);
+ // Subtypes will define how to process the given metadata.
+ abstract void processMetadata(MetadataKind metadata);
public enum Kind {
Class, File, Synthetic, Part, Facade
@@ -66,4 +69,12 @@
public KotlinClassFacade asClassFacade() {
return null;
}
+
+ public boolean hasNonNullParameterHints() {
+ return !nonNullparamHints.isEmpty();
+ }
+
+ public BitSet lookupNonNullParameterHint(String name, String descriptor) {
+ return nonNullparamHints.get(name, descriptor);
+ }
}
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 4660800..fc11f8c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.kotlin;
import com.android.tools.r8.graph.DexClass;
+import kotlinx.metadata.KmFunctionVisitor;
import kotlinx.metadata.KmLambdaVisitor;
import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -32,21 +33,23 @@
}
private KotlinSyntheticClass(Flavour flavour, KotlinClassMetadata.SyntheticClass metadata) {
+ super(metadata);
this.flavour = flavour;
- validateMetadata(metadata);
- this.metadata = metadata;
}
@Override
- void validateMetadata(KotlinClassMetadata.SyntheticClass metadata) {
+ void processMetadata(KotlinClassMetadata.SyntheticClass metadata) {
if (metadata.isLambda()) {
- SyntheticClassMetadataVisitor visitor = new SyntheticClassMetadataVisitor();
// To avoid lazy parsing/verifying metadata.
- metadata.accept(visitor);
+ metadata.accept(new LambdaVisitorForNonNullParameterHints());
}
}
- private static class SyntheticClassMetadataVisitor extends KmLambdaVisitor {
+ private class LambdaVisitorForNonNullParameterHints extends KmLambdaVisitor {
+ @Override
+ public KmFunctionVisitor visitFunction(int functionFlags, String functionName) {
+ return new NonNullParameterHintCollector.FunctionVisitor(nonNullparamHints);
+ }
}
public boolean isLambda() {
diff --git a/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java b/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java
new file mode 100644
index 0000000..21b9409
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/NonNullParameterHintCollector.java
@@ -0,0 +1,184 @@
+// 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 kotlinx.metadata.Flag.Type.IS_NULLABLE;
+
+import com.google.common.collect.HashBasedTable;
+import java.util.BitSet;
+import kotlinx.metadata.KmConstructorExtensionVisitor;
+import kotlinx.metadata.KmConstructorVisitor;
+import kotlinx.metadata.KmExtensionType;
+import kotlinx.metadata.KmFunctionExtensionVisitor;
+import kotlinx.metadata.KmFunctionVisitor;
+import kotlinx.metadata.KmPropertyExtensionVisitor;
+import kotlinx.metadata.KmPropertyVisitor;
+import kotlinx.metadata.KmTypeVisitor;
+import kotlinx.metadata.KmValueParameterVisitor;
+import kotlinx.metadata.jvm.JvmConstructorExtensionVisitor;
+import kotlinx.metadata.jvm.JvmFieldSignature;
+import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor;
+import kotlinx.metadata.jvm.JvmMethodSignature;
+import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor;
+
+class NonNullParameterHintCollector {
+
+ static class FunctionVisitor extends KmFunctionVisitor {
+
+ private final HashBasedTable<String, String, BitSet> paramHints;
+
+ private BitSet paramHint = new BitSet();
+ private int paramIndex = 0;
+ private String name = "";
+ private String descriptor = "";
+
+ FunctionVisitor(HashBasedTable<String, String, BitSet> paramHints) {
+ this.paramHints = paramHints;
+ }
+
+ @Override
+ public KmTypeVisitor visitReceiverParameterType(int typeFlags) {
+ if (!IS_NULLABLE.invoke(typeFlags)) {
+ paramHint.set(paramIndex);
+ }
+ paramIndex++;
+ return null;
+ }
+
+ @Override
+ public KmValueParameterVisitor visitValueParameter(int paramFlags, String paramName) {
+ return new KmValueParameterVisitor() {
+ @Override
+ public KmTypeVisitor visitType(int typeFlags) {
+ if (!IS_NULLABLE.invoke(typeFlags)) {
+ paramHint.set(paramIndex);
+ }
+ paramIndex++;
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public KmFunctionExtensionVisitor visitExtensions(KmExtensionType type) {
+ if (type != JvmFunctionExtensionVisitor.TYPE) {
+ return null;
+ }
+ return new JvmFunctionExtensionVisitor() {
+ @Override
+ public void visit(JvmMethodSignature desc) {
+ name = desc.getName();
+ descriptor = desc.getDesc();
+ }
+ };
+ }
+
+ @Override
+ public void visitEnd() {
+ if (name.isEmpty() || descriptor.isEmpty()) {
+ return;
+ }
+ paramHints.put(name, descriptor, paramHint);
+ }
+ }
+
+ static class ConstructorVisitor extends KmConstructorVisitor {
+ private final HashBasedTable<String, String, BitSet> paramHints;
+
+ private BitSet paramHint = new BitSet();
+ private int paramIndex = 0;
+ private final String name = "<init>";
+ private String descriptor = "";
+
+ ConstructorVisitor(HashBasedTable<String, String, BitSet> paramHints) {
+ this.paramHints = paramHints;
+ }
+
+ @Override
+ public KmValueParameterVisitor visitValueParameter(int paramFlags, String paramName) {
+ return new KmValueParameterVisitor() {
+ @Override
+ public KmTypeVisitor visitType(int typeFlags) {
+ if (!IS_NULLABLE.invoke(typeFlags)) {
+ paramHint.set(paramIndex);
+ }
+ paramIndex++;
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public KmConstructorExtensionVisitor visitExtensions(KmExtensionType type) {
+ if (type != JvmConstructorExtensionVisitor.TYPE) {
+ return null;
+ }
+ return new JvmConstructorExtensionVisitor() {
+ @Override
+ public void visit(JvmMethodSignature desc) {
+ assert name.equals(desc.getName());
+ descriptor = desc.getDesc();
+ }
+ };
+ }
+
+ @Override
+ public void visitEnd() {
+ if (descriptor.isEmpty()) {
+ return;
+ }
+ paramHints.put(name, descriptor, paramHint);
+ }
+ }
+
+ static class PropertyVisitor extends KmPropertyVisitor {
+ private final HashBasedTable<String, String, BitSet> paramHints;
+
+ private BitSet paramHint = new BitSet();
+ private int paramIndex = 0;
+ private String name = "";
+ private String descriptor = "";
+
+ PropertyVisitor(HashBasedTable<String, String, BitSet> paramHints) {
+ this.paramHints = paramHints;
+ }
+
+ @Override
+ public KmTypeVisitor visitReturnType(int typeFlags) {
+ if (!IS_NULLABLE.invoke(typeFlags)) {
+ paramHint.set(paramIndex);
+ }
+ paramIndex++;
+ return null;
+ }
+
+ @Override
+ public KmPropertyExtensionVisitor visitExtensions(KmExtensionType type) {
+ if (type != JvmPropertyExtensionVisitor.TYPE) {
+ return null;
+ }
+ return new JvmPropertyExtensionVisitor() {
+ @Override
+ public void visit(
+ JvmFieldSignature fieldDesc,
+ JvmMethodSignature getterDesc,
+ JvmMethodSignature setterDesc) {
+ if (setterDesc != null) {
+ name = setterDesc.getName();
+ descriptor = setterDesc.getDesc();
+ }
+ }
+ };
+ }
+
+ @Override
+ public void visitEnd() {
+ if (name.isEmpty() || descriptor.isEmpty()) {
+ return;
+ }
+ paramHints.put(name, descriptor, paramHint);
+ }
+ }
+}
\ No newline at end of file