Fix R8 support for Kotlin property syntheticMethodForDelegate
This CL adds full support for Kotlin property delegated methods
(`syntheticMethodForDelegate`) inside the R8 compiler's property model.
R8 was previously missing the tracking and parsing of delegated methods
in `KmPropertyProcessor`. Consequently, R8's tree shaker pruned these
synthetic delegate methods under minification and ignored updating their
signatures in the rewritten Kotlin metadata. This caused a metadata
resolution failure in `kotlin-reflect` >= 2.3.20 when using map delegation,
manifesting as a NullPointerException or KotlinReflectionInternalError at runtime.
Bug: b/510830974
Change-Id: I43efc3c98e964febf73d5fd774ce8c828ecc942e
diff --git a/src/main/java/com/android/tools/r8/kotlin/ConcreteKotlinPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/ConcreteKotlinPropertyInfo.java
index 8816001..78a6900 100644
--- a/src/main/java/com/android/tools/r8/kotlin/ConcreteKotlinPropertyInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/ConcreteKotlinPropertyInfo.java
@@ -113,7 +113,7 @@
@Override
public boolean rewriteNoBacking(Consumer<KmProperty> consumer, AppView<?> appView) {
- return rewrite(consumer, null, null, null, null, appView);
+ return rewrite(consumer, null, null, null, null, null, appView);
}
@Override
@@ -123,6 +123,7 @@
DexEncodedMethod getter,
DexEncodedMethod setter,
DexEncodedMethod syntheticMethodForAnnotationsMethod,
+ DexEncodedMethod syntheticMethodForDelegateMethod,
AppView<?> appView) {
KmProperty rewrittenKmProperty = new KmProperty(kmProperty.getName());
consumer.accept(rewrittenKmProperty);
@@ -190,13 +191,14 @@
syntheticMethodForAnnotationsMethod,
appView);
}
- rewritten |=
- rewriteIfNotNull(
- appView,
- syntheticMethodForDelegate,
- newMethod ->
- JvmExtensionsKt.setSyntheticMethodForDelegate(rewrittenKmProperty, newMethod),
- KotlinJvmMethodSignatureInfo::rewriteNoBacking);
+ if (syntheticMethodForDelegate != null) {
+ rewritten |=
+ syntheticMethodForDelegate.rewrite(
+ newSignature ->
+ JvmExtensionsKt.setSyntheticMethodForDelegate(rewrittenKmProperty, newSignature),
+ syntheticMethodForDelegateMethod,
+ appView);
+ }
return rewritten;
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
index 58eb8ed..b1b9e4c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinDeclarationContainerInfo.java
@@ -144,6 +144,19 @@
originalAssignmentTracker.add(method.getReference());
}
}
+ if (propertyProcessor.syntheticMethodForDelegateSignature() != null) {
+ DexEncodedMethod method =
+ methodSignatureMap.get(
+ propertyProcessor.syntheticMethodForDelegateSignature().toString());
+ if (method != null) {
+ hasBacking = true;
+ memberInfoConsumer.accept(
+ method,
+ new KotlinPropertyInfoDelegate(
+ kotlinPropertyInfo, PropertyType.SYNTHETIC_METHOD_FOR_DELEGATE));
+ originalAssignmentTracker.add(method.getReference());
+ }
+ }
if (!hasBacking) {
notBackedProperties.add(kotlinPropertyInfo);
}
@@ -256,6 +269,9 @@
case SYNTHETIC_METHOD_FOR_ANNOTATIONS:
kotlinPropertyGroup.setSyntheticMethodForAnnotations(method);
break;
+ case SYNTHETIC_METHOD_FOR_DELEGATE:
+ kotlinPropertyGroup.setSyntheticMethodForDelegate(method);
+ break;
default:
// Do nothing.
}
@@ -269,6 +285,7 @@
kotlinPropertyGroup.getter,
kotlinPropertyGroup.setter,
kotlinPropertyGroup.syntheticMethodForAnnotations,
+ kotlinPropertyGroup.syntheticMethodForDelegate,
appView);
}
// Add all not backed functions and properties.
@@ -300,6 +317,7 @@
private DexEncodedMethod setter = null;
private DexEncodedMethod getter = null;
private DexEncodedMethod syntheticMethodForAnnotations = null;
+ private DexEncodedMethod syntheticMethodForDelegate = null;
void setBackingField(DexEncodedField backingField) {
assert this.backingField == null;
@@ -320,5 +338,10 @@
assert this.syntheticMethodForAnnotations == null;
this.syntheticMethodForAnnotations = syntheticMethodForAnnotations;
}
+
+ public void setSyntheticMethodForDelegate(DexEncodedMethod syntheticMethodForDelegate) {
+ assert this.syntheticMethodForDelegate == null;
+ this.syntheticMethodForDelegate = syntheticMethodForDelegate;
+ }
}
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
index 8415811..237bd47 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataUtils.java
@@ -135,6 +135,7 @@
// Custom getter via @set:JvmName("..."). Otherwise, null.
private final JvmMethodSignature setterSignature;
private final JvmMethodSignature syntheticMethodForAnnotationsSignature;
+ private final JvmMethodSignature syntheticMethodForDelegateSignature;
KmPropertyProcessor(KmProperty kmProperty) {
fieldSignature = JvmExtensionsKt.getFieldSignature(kmProperty);
@@ -142,6 +143,8 @@
setterSignature = JvmExtensionsKt.getSetterSignature(kmProperty);
syntheticMethodForAnnotationsSignature =
JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty);
+ syntheticMethodForDelegateSignature =
+ JvmExtensionsKt.getSyntheticMethodForDelegate(kmProperty);
}
JvmFieldSignature fieldSignature() {
@@ -159,6 +162,10 @@
public JvmMethodSignature syntheticMethodForAnnotationsSignature() {
return syntheticMethodForAnnotationsSignature;
}
+
+ public JvmMethodSignature syntheticMethodForDelegateSignature() {
+ return syntheticMethodForDelegateSignature;
+ }
}
static boolean isValidMethodDescriptor(String methodDescriptor) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
index 237a877..9fa8a28 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -535,7 +535,7 @@
sb,
Objects.toString(syntheticMethodForAnnotations));
JvmMethodSignature syntheticMethodForDelegate =
- JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty);
+ JvmExtensionsKt.getSyntheticMethodForDelegate(kmProperty);
appendKeyValue(
newIndent,
"syntheticMethodForDelegate",
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
index 1e66fbd..f933324 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
@@ -45,5 +45,6 @@
DexEncodedMethod getter,
DexEncodedMethod setter,
DexEncodedMethod syntheticMethodForAnnotationsMethod,
+ DexEncodedMethod syntheticMethodForDelegateMethod,
AppView<?> appView);
}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfoDelegate.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfoDelegate.java
index fc11196..5b690d8 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfoDelegate.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfoDelegate.java
@@ -34,6 +34,7 @@
SETTER,
GETTER,
SYNTHETIC_METHOD_FOR_ANNOTATIONS,
+ SYNTHETIC_METHOD_FOR_DELEGATE,
UNKNOWN
}
@@ -64,9 +65,16 @@
DexEncodedMethod getter,
DexEncodedMethod setter,
DexEncodedMethod syntheticMethodForAnnotationsMethod,
+ DexEncodedMethod syntheticMethodForDelegateMethod,
AppView<?> appView) {
return delegate.rewrite(
- consumer, field, getter, setter, syntheticMethodForAnnotationsMethod, appView);
+ consumer,
+ field,
+ getter,
+ setter,
+ syntheticMethodForAnnotationsMethod,
+ syntheticMethodForDelegateMethod,
+ appView);
}
@Override
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
index 1e96f35..a1eb94b 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
@@ -15,7 +15,6 @@
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.kotlin.KotlinMetadataWriter;
import com.android.tools.r8.shaking.ProguardKeepAttributes;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -23,6 +22,10 @@
import com.android.tools.r8.utils.internal.StringUtils;
import java.nio.file.Path;
import java.util.Collection;
+import kotlin.metadata.KmClass;
+import kotlin.metadata.KmProperty;
+import kotlin.metadata.jvm.JvmExtensionsKt;
+import kotlin.metadata.jvm.JvmMethodSignature;
import kotlin.metadata.jvm.KotlinClassMetadata;
import org.junit.Assert;
import org.junit.Test;
@@ -147,7 +150,18 @@
assertThat(clazz, isPresent());
KotlinClassMetadata kotlinClassMetadata = clazz.getKotlinClassMetadata();
Assert.assertNotNull(kotlinClassMetadata);
- String metadataAsString = KotlinMetadataWriter.kotlinMetadataToString("", kotlinClassMetadata);
- assertThat(metadataAsString, containsString("syntheticMethodForDelegate:"));
+ Assert.assertTrue(kotlinClassMetadata instanceof KotlinClassMetadata.Class);
+ KmClass kmClass = ((KotlinClassMetadata.Class) kotlinClassMetadata).getKmClass();
+ KmProperty property =
+ kmClass.getProperties().stream()
+ .filter(p -> p.getName().equals("oldName"))
+ .findFirst()
+ .orElse(null);
+ Assert.assertNotNull(property);
+ JvmMethodSignature delegateSignature = JvmExtensionsKt.getSyntheticMethodForDelegate(property);
+ Assert.assertNotNull(delegateSignature);
+ Assert.assertEquals(
+ "getOldName$delegate(Lcom/android/tools/r8/kotlin/metadata/delegated_property_lib/MyDelegatedProperty;)Ljava/lang/Object;",
+ delegateSignature.toString());
}
}