Write the Record attribute for both CF and DEX
This adds representation of all the information from the Record
attribute to the compiler.
Before the Record attribute was implicitly generated from the instance
fields of the record class. However, the Record attribute carry
this information separately in the class file where the annotations on the
field and the record component can be different based on the @Target
(@Target(ElementType.FIELD) vs. @Target(ElementType.RECORD_COMPONENT))
used when declaring the annotation used to annotate components of a record.
When writing to DEX a system annotation of type dalvik.annotation.Record
encodes the Record attribute information.
The shrinking and minification handling in R8 is not complete.
Bug: b/231930852
Bug: b/274888318
Change-Id: Ib9dd1876bca84e6aa735141b094d77f4a57de315
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 08b14b8..66db5e5 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -773,7 +773,8 @@
if (enclosingMethod == null
&& innerClasses.isEmpty()
&& clazz.getClassSignature().hasNoSignature()
- && !clazz.isInANest()) {
+ && !clazz.isInANest()
+ && !clazz.isRecord()) {
return;
}
@@ -853,6 +854,10 @@
clazz.getPermittedSubclassAttributes(), options.itemFactory));
}
+ if (clazz.isRecord() && options.canUseRecords()) {
+ annotations.add(DexAnnotation.createRecordAnnotation(clazz, appView));
+ }
+
if (!annotations.isEmpty()) {
// Append the annotations to annotations array of the class.
DexAnnotation[] copy =
@@ -868,6 +873,7 @@
clazz.clearInnerClasses();
clazz.clearClassSignature();
clazz.clearPermittedSubclasses();
+ clazz.clearRecordComponents();
}
private void insertAttributeAnnotationsForField(DexEncodedField field) {
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index 263acfc..2055d69 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -67,6 +67,7 @@
import com.android.tools.r8.graph.OffsetToObjectMapping;
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.PermittedSubclassAttribute;
+import com.android.tools.r8.graph.RecordComponentInfo;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.origin.PathOrigin;
import com.android.tools.r8.utils.InternalOptions;
@@ -898,6 +899,7 @@
attrs.nestHostAttribute,
attrs.nestMembersAttribute,
attrs.permittedSubclassesAttribute,
+ attrs.recordComponents,
attrs.getEnclosingMethodAttribute(),
attrs.getInnerClasses(),
attrs.classSignature,
@@ -1464,6 +1466,7 @@
private NestHostClassAttribute nestHostAttribute;
private List<NestMemberClassAttribute> nestMembersAttribute = Collections.emptyList();
private List<PermittedSubclassAttribute> permittedSubclassesAttribute = Collections.emptyList();
+ private List<RecordComponentInfo> recordComponents = Collections.emptyList();
public DexAnnotationSet getAnnotations() {
if (lazyAnnotations != null) {
@@ -1545,6 +1548,13 @@
permittedSubclassesAttribute =
ListUtils.map(permittedSubclasses, PermittedSubclassAttribute::new);
}
+ } else if (DexAnnotation.isRecordAnnotation(annotation, factory)) {
+ ensureAnnotations(i);
+ List<RecordComponentInfo> recordComponents =
+ DexAnnotation.getRecordComponentInfoFromAnnotation(type, annotation, factory, origin);
+ if (recordComponents != null) {
+ this.recordComponents = recordComponents;
+ }
} else {
copyAnnotation(annotation);
}
diff --git a/src/main/java/com/android/tools/r8/graph/ClassKind.java b/src/main/java/com/android/tools/r8/graph/ClassKind.java
index e878e0d..0e3e660 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassKind.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassKind.java
@@ -27,6 +27,7 @@
nestHost,
nestMembers,
permittedSubclasses,
+ recordComponents,
enclosingMember,
innerClasses,
classSignature,
@@ -49,6 +50,7 @@
nestHost,
nestMembers,
permittedSubclasses,
+ recordComponents,
enclosingMember,
innerClasses,
classSignature,
@@ -72,6 +74,7 @@
nestHost,
nestMembers,
permittedSubclasses,
+ recordComponents,
enclosingMember,
innerClasses,
classSignature,
@@ -94,6 +97,7 @@
nestHost,
nestMembers,
permittedSubclasses,
+ recordComponents,
enclosingMember,
innerClasses,
classSignature,
@@ -115,6 +119,7 @@
nestHost,
nestMembers,
permittedSubclasses,
+ recordComponents,
enclosingMember,
innerClasses,
classSignature,
@@ -137,6 +142,7 @@
nestHost,
nestMembers,
permittedSubclasses,
+ recordComponents,
enclosingMember,
innerClasses,
classSignature,
@@ -159,6 +165,7 @@
NestHostClassAttribute nestHost,
List<NestMemberClassAttribute> nestMembers,
List<PermittedSubclassAttribute> permittedSubclasses,
+ List<RecordComponentInfo> recordComponents,
EnclosingMethodAttribute enclosingMember,
List<InnerClassAttribute> innerClasses,
ClassSignature classSignature,
@@ -191,6 +198,7 @@
NestHostClassAttribute nestHost,
List<NestMemberClassAttribute> nestMembers,
List<PermittedSubclassAttribute> permittedSubclasses,
+ List<RecordComponentInfo> recordComponents,
EnclosingMethodAttribute enclosingMember,
List<InnerClassAttribute> innerClasses,
ClassSignature classSignature,
@@ -213,6 +221,7 @@
nestHost,
nestMembers,
permittedSubclasses,
+ recordComponents,
enclosingMember,
innerClasses,
classSignature,
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index f72877a..598adeb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -14,7 +14,9 @@
import com.android.tools.r8.graph.DexValue.DexValueNull;
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.DexValue.DexValueType;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
+import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -24,6 +26,7 @@
import com.android.tools.r8.utils.structural.StructuralMapping;
import com.android.tools.r8.utils.structural.StructuralSpecification;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.function.Function;
@@ -209,6 +212,10 @@
return annotation.annotation.type == factory.annotationPermittedSubclasses;
}
+ public static boolean isRecordAnnotation(DexAnnotation annotation, DexItemFactory factory) {
+ return annotation.getAnnotationType() == factory.annotationRecord;
+ }
+
public static DexAnnotation createInnerClassAnnotation(
DexString clazz, int access, DexItemFactory factory) {
return new DexAnnotation(
@@ -299,6 +306,108 @@
return getTypesFromAnnotation(factory.annotationPermittedSubclasses, annotation);
}
+ /** See {@link #createRecordAnnotation(DexProgramClass, AppView)} for the representation. */
+ public static List<RecordComponentInfo> getRecordComponentInfoFromAnnotation(
+ DexType type, DexAnnotation annotation, DexItemFactory factory, Origin origin) {
+ DexValue componentNamesValue =
+ getSystemValueAnnotationValueWithName(
+ factory.annotationRecord, annotation, factory.annotationRecordComponentNames);
+ DexValue componentTypesValue =
+ getSystemValueAnnotationValueWithName(
+ factory.annotationRecord, annotation, factory.annotationRecordComponentTypes);
+ DexValue componentSignaturesValue =
+ getSystemValueAnnotationValueWithName(
+ factory.annotationRecord, annotation, factory.annotationRecordComponentSignatures);
+ DexValue componentAnnotationVisibilitiesValue =
+ getSystemValueAnnotationValueWithName(
+ factory.annotationRecord, annotation, factory.annotationRecordComponentVisibilities);
+ DexValue componentAnnotationsValue =
+ getSystemValueAnnotationValueWithName(
+ factory.annotationRecord, annotation, factory.annotationRecordComponentAnnotations);
+
+ if (componentNamesValue == null
+ || componentTypesValue == null
+ || componentSignaturesValue == null
+ || componentAnnotationVisibilitiesValue == null
+ || componentAnnotationsValue == null) {
+ return null;
+ }
+ if (!componentNamesValue.isDexValueArray()
+ || !componentTypesValue.isDexValueArray()
+ || !componentSignaturesValue.isDexValueArray()
+ || !componentAnnotationVisibilitiesValue.isDexValueArray()
+ || !componentAnnotationsValue.isDexValueArray()) {
+ return null;
+ }
+ DexValueArray componentNamesValueArray = componentNamesValue.asDexValueArray();
+ DexValueArray componentTypesValueArray = componentTypesValue.asDexValueArray();
+ DexValueArray componentSignaturesValueArray = componentSignaturesValue.asDexValueArray();
+ DexValueArray componentAnnotationVisibilitiesValueArray =
+ componentAnnotationVisibilitiesValue.asDexValueArray();
+ DexValueArray componentAnnotationsValueArray = componentAnnotationsValue.asDexValueArray();
+ if (componentNamesValueArray.size() != componentTypesValueArray.size()
+ || componentNamesValueArray.size() != componentSignaturesValueArray.size()
+ || componentNamesValueArray.size() != componentAnnotationVisibilitiesValueArray.size()
+ || componentNamesValueArray.size() != componentAnnotationsValueArray.size()) {
+ return null;
+ }
+ List<RecordComponentInfo> result = new ArrayList<>(componentNamesValueArray.size());
+ for (int componentIndex = 0;
+ componentIndex < componentNamesValueArray.size();
+ componentIndex++) {
+ DexValue nameValue = componentNamesValueArray.getValue(componentIndex);
+ DexValue typeValue = componentTypesValueArray.getValue(componentIndex);
+ DexValue signatureValue = componentSignaturesValueArray.getValue(componentIndex);
+ DexValue visibilitiesValue =
+ componentAnnotationVisibilitiesValueArray.getValue(componentIndex);
+ DexValue annotationsValue = componentAnnotationsValueArray.getValue(componentIndex);
+ if (!nameValue.isDexValueString()
+ || !typeValue.isDexValueType()
+ || !(signatureValue.isDexValueAnnotation() || signatureValue.isDexValueNull())
+ || !visibilitiesValue.isDexValueArray()
+ || !annotationsValue.isDexValueArray()) {
+ return null;
+ }
+ DexValueArray visibilitiesValueArray = visibilitiesValue.asDexValueArray();
+ DexValueArray annotationsValueArray = annotationsValue.asDexValueArray();
+ if (visibilitiesValueArray.size() != annotationsValueArray.size()) {
+ return null;
+ }
+ List<DexAnnotation> componentAnnotations = Collections.emptyList();
+ if (annotationsValueArray.size() > 0) {
+ componentAnnotations = new ArrayList<>(annotationsValueArray.size());
+ for (int annotationIndex = 0;
+ annotationIndex < annotationsValueArray.size();
+ annotationIndex++) {
+ DexValue visibilityValue = visibilitiesValueArray.getValue(annotationIndex);
+ DexValue annotationValue = annotationsValueArray.getValue(annotationIndex);
+ if (!visibilityValue.isDexValueInt() || !annotationValue.isDexValueAnnotation()) {
+ return null;
+ }
+ componentAnnotations.add(
+ new DexAnnotation(
+ visibilityValue.asDexValueInt().getValue(),
+ annotationValue.asDexValueAnnotation().getValue()));
+ }
+ }
+ FieldTypeSignature componentSignature =
+ GenericSignature.parseFieldTypeSignature(
+ nameValue.asDexValueString().getValue().toString(),
+ signatureValue.isDexValueAnnotation()
+ ? getSignature(signatureValue.asDexValueAnnotation().getValue())
+ : null,
+ origin,
+ factory,
+ null);
+
+ DexType componentType = typeValue.asDexValueType().getValue();
+ DexString componentName = nameValue.asDexValueString().getValue();
+ DexField componentField = factory.createField(type, componentType, componentName);
+ result.add(new RecordComponentInfo(componentField, componentSignature, componentAnnotations));
+ }
+ return result;
+ }
+
public static DexAnnotation createSourceDebugExtensionAnnotation(DexValue value,
DexItemFactory factory) {
return new DexAnnotation(VISIBILITY_SYSTEM,
@@ -367,8 +476,143 @@
new DexValue.DexValueArray(list.toArray(DexValue.EMPTY_ARRAY)));
}
+ /**
+ * Record component information is written to DEX as a system annotation named <code>
+ * dalvik.annotation.Record</code> with the following content:
+ *
+ * <pre>
+ * componentAnnotationVisibilities int[][]
+ * componentAnnotations Annotation[][]
+ * componentNames String[]
+ * componentSignatures Annotation[] // Annotation dalvik.annotation.Signature or NULL
+ * componentTypes String[]
+ * </pre>
+ *
+ * Each of the arrays have one element for each component.
+ *
+ * <p>Example of a two component record with two annotations on the first component and one on the
+ * second and with a signature on the first component.
+ *
+ * <pre>
+ * .annotation system Ldalvik/annotation/Record;
+ * componentAnnotationVisibilities = {
+ * {
+ * 0x1,
+ * 0x1
+ * },
+ * {
+ * 0x1
+ * }
+ * }
+ * componentAnnotations = {
+ * {
+ * .subannotation LAnnotation1;
+ * value = "a"
+ * .end subannotation,
+ * .subannotation LAnnotation2;
+ * value = "c"
+ * .end subannotation
+ * },
+ * {
+ * .subannotation LAnnotation3;
+ * value = "z"
+ * .end subannotation
+ * }
+ * }
+ * componentNames = {
+ * "name",
+ * "age"
+ * }
+ * componentSignatures = {
+ * .subannotation Ldalvik/annotation/Signature;
+ * value = {
+ * "TX;"
+ * }
+ * .end subannotation,
+ * NULL
+ * }
+ * componentTypes = {
+ * Ljava/lang/CharSequence;,
+ * Ljava/lang/Object;
+ * }
+ * .end annotation
+ * </pre>
+ */
+ public static DexAnnotation createRecordAnnotation(DexProgramClass clazz, AppView<?> appView) {
+ DexItemFactory factory = appView.dexItemFactory();
+ int componentCount = clazz.getRecordComponents().size();
+ DexValueString[] componentNames = new DexValueString[componentCount];
+ DexValueType[] componentTypes = new DexValueType[componentCount];
+ DexValue[] componentSignatures = new DexValue[componentCount];
+ DexValueArray[] componentAnnotationVisibilities = new DexValueArray[componentCount];
+ DexValueArray[] componentAnnotations = new DexValueArray[componentCount];
+ for (int componentIndex = 0; componentIndex < componentCount; componentIndex++) {
+ RecordComponentInfo info = clazz.getRecordComponents().get(componentIndex);
+ componentNames[componentIndex] =
+ new DexValueString(appView.getNamingLens().lookupName(info.getField()));
+ componentTypes[componentIndex] = new DexValueType(info.getType());
+ if (info.getSignature().hasNoSignature()) {
+ componentSignatures[componentIndex] = DexValueNull.NULL;
+ } else {
+ componentSignatures[componentIndex] =
+ new DexValueAnnotation(
+ createSignatureAnnotation(info.getSignature().toString(), factory).annotation);
+ }
+ int annotationsSize = info.getAnnotations().size();
+ DexValueInt[] visibilities = new DexValueInt[annotationsSize];
+ DexValueAnnotation[] annotations = new DexValueAnnotation[annotationsSize];
+ componentAnnotationVisibilities[componentIndex] = new DexValueArray(visibilities);
+ componentAnnotations[componentIndex] = new DexValueArray(annotations);
+ for (int annotationIndex = 0; annotationIndex < annotationsSize; annotationIndex++) {
+ DexAnnotation annotation = info.getAnnotations().get(annotationIndex);
+ visibilities[annotationIndex] = DexValueInt.create(annotation.getVisibility());
+ annotations[annotationIndex] = new DexValueAnnotation(annotation.annotation);
+ }
+ }
+
+ if (appView.options().emitRecordAnnotationsExInDex) {
+ return new DexAnnotation(
+ VISIBILITY_SYSTEM,
+ new DexEncodedAnnotation(
+ factory.annotationRecord,
+ new DexAnnotationElement[] {
+ new DexAnnotationElement(
+ factory.annotationRecordComponentNames, new DexValueArray(componentNames)),
+ new DexAnnotationElement(
+ factory.annotationRecordComponentTypes, new DexValueArray(componentTypes)),
+ new DexAnnotationElement(
+ factory.annotationRecordComponentSignatures,
+ new DexValueArray(componentSignatures)),
+ new DexAnnotationElement(
+ factory.annotationRecordComponentVisibilities,
+ new DexValueArray(componentAnnotationVisibilities)),
+ new DexAnnotationElement(
+ factory.annotationRecordComponentAnnotations,
+ new DexValueArray(componentAnnotations))
+ }));
+ } else {
+ return new DexAnnotation(
+ VISIBILITY_SYSTEM,
+ new DexEncodedAnnotation(
+ factory.annotationRecord,
+ new DexAnnotationElement[] {
+ new DexAnnotationElement(
+ factory.annotationRecordComponentNames, new DexValueArray(componentNames)),
+ new DexAnnotationElement(
+ factory.annotationRecordComponentTypes, new DexValueArray(componentTypes))
+ }));
+ }
+ }
+
public static String getSignature(DexAnnotation signatureAnnotation) {
- DexValueArray elements = signatureAnnotation.annotation.elements[0].value.asDexValueArray();
+ return getSignature(signatureAnnotation.annotation);
+ }
+
+ public static String getSignature(DexEncodedAnnotation signatureAnnotation) {
+ return getSignature(signatureAnnotation.elements[0].value.asDexValueArray());
+ }
+
+ public static String getSignature(DexValueArray elements) {
StringBuilder signature = new StringBuilder();
for (DexValue element : elements.getValues()) {
signature.append(element.asDexValueString().value.toString());
@@ -403,6 +647,18 @@
: annotation.annotation.elements[0].value;
}
+ private static DexValue getSystemValueAnnotationValueWithName(
+ DexType type, DexAnnotation annotation, DexString name) {
+ assert annotation.visibility == VISIBILITY_SYSTEM;
+ assert annotation.getAnnotationType() == type;
+ for (DexAnnotationElement element : annotation.annotation.elements) {
+ if (element.name == name) {
+ return element.value;
+ }
+ }
+ return null;
+ }
+
public static boolean isThrowingAnnotation(DexAnnotation annotation,
DexItemFactory factory) {
return annotation.annotation.type == factory.annotationThrows;
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 59bff83..342a214 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -78,6 +78,8 @@
private List<PermittedSubclassAttribute> permittedSubclasses;
+ private List<RecordComponentInfo> recordComponents;
+
/** Generic signature information if the attribute is present in the input */
protected ClassSignature classSignature;
@@ -93,6 +95,7 @@
NestHostClassAttribute nestHost,
List<NestMemberClassAttribute> nestMembers,
List<PermittedSubclassAttribute> permittedSubclasses,
+ List<RecordComponentInfo> recordComponents,
EnclosingMethodAttribute enclosingMethod,
List<InnerClassAttribute> innerClasses,
ClassSignature classSignature,
@@ -113,6 +116,7 @@
this.nestMembers = nestMembers;
assert nestMembers != null;
this.permittedSubclasses = permittedSubclasses;
+ this.recordComponents = recordComponents;
assert permittedSubclasses != null;
this.enclosingMethod = enclosingMethod;
this.innerClasses = innerClasses;
@@ -1113,6 +1117,20 @@
return permittedSubclasses;
}
+ public List<RecordComponentInfo> getRecordComponents() {
+ return recordComponents;
+ }
+
+ public void clearRecordComponents() {
+ recordComponents.clear();
+ }
+
+ public void removeRecordComponents(Predicate<RecordComponentInfo> predicate) {
+ if (!recordComponents.isEmpty()) {
+ recordComponents.removeIf(predicate);
+ }
+ }
+
/** Returns kotlin class info if the class is synthesized by kotlin compiler. */
public abstract KotlinClassLevelInfo getKotlinInfo();
diff --git a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
index aca2a14..2937cd2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClasspathClass.java
@@ -36,6 +36,7 @@
NestHostClassAttribute nestHost,
List<NestMemberClassAttribute> nestMembers,
List<PermittedSubclassAttribute> permittedSubclasses,
+ List<RecordComponentInfo> recordComponents,
EnclosingMethodAttribute enclosingMember,
List<InnerClassAttribute> innerClasses,
ClassSignature classSignature,
@@ -56,6 +57,7 @@
nestHost,
nestMembers,
permittedSubclasses,
+ recordComponents,
enclosingMember,
innerClasses,
classSignature,
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 5c14069..418cdce 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -735,6 +735,14 @@
createStaticallyKnownType("Ldalvik/annotation/NestMembers;");
public final DexType annotationPermittedSubclasses =
createStaticallyKnownType("Ldalvik/annotation/PermittedSubclasses;");
+ public final DexType annotationRecord = createStaticallyKnownType("Ldalvik/annotation/Record;");
+ public final DexString annotationRecordComponentNames = createString("componentNames");
+ public final DexString annotationRecordComponentTypes = createString("componentTypes");
+ public final DexString annotationRecordComponentSignatures = createString("componentSignatures");
+ public final DexString annotationRecordComponentVisibilities =
+ createString("componentVisibilities");
+ public final DexString annotationRecordComponentAnnotations =
+ createString("componentAnnotations");
public final DexType annotationSourceDebugExtension =
createStaticallyKnownType("Ldalvik/annotation/SourceDebugExtension;");
public final DexType annotationThrows = createStaticallyKnownType("Ldalvik/annotation/Throws;");
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index 24091e4..a145cba 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -34,6 +34,7 @@
NestHostClassAttribute nestHost,
List<NestMemberClassAttribute> nestMembers,
List<PermittedSubclassAttribute> permittedSubclasses,
+ List<RecordComponentInfo> recordComponents,
EnclosingMethodAttribute enclosingMember,
List<InnerClassAttribute> innerClasses,
ClassSignature classSignature,
@@ -54,6 +55,7 @@
nestHost,
nestMembers,
permittedSubclasses,
+ recordComponents,
enclosingMember,
innerClasses,
classSignature,
@@ -174,6 +176,7 @@
private NestHostClassAttribute nestHost = null;
private List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
private List<PermittedSubclassAttribute> permittedSubclasses = Collections.emptyList();
+ private List<RecordComponentInfo> recordComponents = Collections.emptyList();
private EnclosingMethodAttribute enclosingMember = null;
private List<InnerClassAttribute> innerClasses = Collections.emptyList();
private ClassSignature classSignature = ClassSignature.noSignature();
@@ -217,6 +220,7 @@
nestHost,
nestMembers,
permittedSubclasses,
+ recordComponents,
enclosingMember,
innerClasses,
classSignature,
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 01fa0e0..a448af0 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -71,6 +71,7 @@
NestHostClassAttribute nestHost,
List<NestMemberClassAttribute> nestMembers,
List<PermittedSubclassAttribute> permittedSubclasses,
+ List<RecordComponentInfo> recordComponents,
EnclosingMethodAttribute enclosingMember,
List<InnerClassAttribute> innerClasses,
ClassSignature classSignature,
@@ -93,6 +94,7 @@
nestHost,
nestMembers,
permittedSubclasses,
+ recordComponents,
enclosingMember,
innerClasses,
classSignature,
@@ -117,6 +119,7 @@
NestHostClassAttribute nestHost,
List<NestMemberClassAttribute> nestMembers,
List<PermittedSubclassAttribute> permittedSubclasses,
+ List<RecordComponentInfo> recordComponents,
EnclosingMethodAttribute enclosingMember,
List<InnerClassAttribute> innerClasses,
ClassSignature classSignature,
@@ -137,6 +140,7 @@
nestHost,
nestMembers,
permittedSubclasses,
+ recordComponents,
enclosingMember,
innerClasses,
classSignature,
@@ -161,6 +165,7 @@
null,
Collections.emptyList(),
Collections.emptyList(),
+ Collections.emptyList(),
null,
Collections.emptyList(),
ClassSignature.noSignature(),
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 368ffea..d9158fc 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -48,9 +48,9 @@
import com.android.tools.r8.utils.StringUtils;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
import java.nio.ByteBuffer;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -203,7 +203,7 @@
int typeRef,
TypePath typePath) {
assert annotations != null;
- // Java 8 type annotations are not supported by Dex. Ignore them.
+ // Java 8 type annotations are not supported by DEX. Ignore them.
if (!application.options.isGeneratingClassFiles()) {
return null;
}
@@ -252,7 +252,7 @@
private NestHostClassAttribute nestHost = null;
private final List<NestMemberClassAttribute> nestMembers = new ArrayList<>();
private final List<PermittedSubclassAttribute> permittedSubclasses = new ArrayList<>();
- private final Set<DexField> recordComponents = Sets.newIdentityHashSet();
+ private final List<RecordComponentInfo> recordComponents = new ArrayList<>();
private EnclosingMethodAttribute enclosingMember = null;
private final List<InnerClassAttribute> innerClasses = new ArrayList<>();
private ClassSignature classSignature = ClassSignature.noSignature();
@@ -361,15 +361,7 @@
String name, String descriptor, String signature) {
assert name != null;
assert descriptor != null;
- // Javac generated record components are only the instance fields, so we just reuse the field
- // to avoid duplicating the field and field signature rewriting logic.
- DexField field =
- application
- .getFactory()
- .createField(
- type, application.getTypeFromDescriptor(descriptor), application.getString(name));
- recordComponents.add(field);
- return super.visitRecordComponent(name, descriptor, signature);
+ return new CreateRecordComponentVisitor(this, name, descriptor, signature);
}
@Override
@@ -502,6 +494,7 @@
nestHost,
nestMembers,
permittedSubclasses,
+ recordComponents,
enclosingMember,
innerClasses,
classSignature,
@@ -568,18 +561,12 @@
// so it's safe to compile even if there is a missmatch.
return;
}
- // TODO(b/169645628): Change this logic if we start stripping the record components.
+ // TODO(b/274888318): Change this logic if we start stripping the record components.
// Another approach would be to mark a bit in fields that are record components instead.
String message = "Records are expected to have one record component per instance field.";
if (recordComponents.size() != instanceFields.size()) {
throw new CompilationError(message, origin);
}
- for (DexEncodedField instanceField : instanceFields) {
- if (!recordComponents.contains(instanceField.getReference())) {
- throw new CompilationError(
- message + " Unmatched field " + instanceField.getReference() + ".", origin);
- }
- }
}
private ChecksumSupplier getChecksumSupplier(ClassKind<T> classKind) {
@@ -1172,6 +1159,66 @@
}
}
+ private static class CreateRecordComponentVisitor extends RecordComponentVisitor {
+ private final CreateDexClassVisitor<?> parent;
+ private final String name;
+ private final String descriptor;
+ private final DexField field;
+ private final FieldTypeSignature componentSignature;
+
+ public CreateRecordComponentVisitor(
+ CreateDexClassVisitor<?> parent, String name, String descriptor, String signature) {
+ super(ASM_VERSION);
+ this.field = parent.application.getField(parent.type, name, descriptor);
+ this.parent = parent;
+ this.name = name;
+ this.descriptor = descriptor;
+ this.componentSignature =
+ parent.application.options.parseSignatureAttribute()
+ ? GenericSignature.parseFieldTypeSignature(
+ name,
+ signature,
+ parent.origin,
+ parent.application.getFactory(),
+ parent.application.options.reporter)
+ : FieldTypeSignature.noSignature();
+ }
+
+ private List<DexAnnotation> annotations = null;
+
+ private List<DexAnnotation> getAnnotations() {
+ if (annotations == null) {
+ annotations = new ArrayList<>();
+ }
+ return annotations;
+ }
+
+ @Override
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ return createAnnotationVisitor(
+ desc, visible, getAnnotations(), parent.application, DexAnnotation::new);
+ }
+
+ @Override
+ public AnnotationVisitor visitTypeAnnotation(
+ int typeRef, TypePath typePath, String desc, boolean visible) {
+ // Java 8 type annotations are not supported by DEX, thus ignore them.
+ return null;
+ }
+
+ @Override
+ public void visitEnd() {
+ assert annotations == null || !annotations.isEmpty();
+ parent.recordComponents.add(
+ new RecordComponentInfo(
+ field,
+ componentSignature,
+ annotations != null && !annotations.isEmpty()
+ ? annotations
+ : Collections.emptyList()));
+ }
+ }
+
public static class ReparseContext {
// This will hold the content of the whole class. Once all the methods of the class are swapped
diff --git a/src/main/java/com/android/tools/r8/graph/RecordComponentInfo.java b/src/main/java/com/android/tools/r8/graph/RecordComponentInfo.java
new file mode 100644
index 0000000..c6ac18f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/RecordComponentInfo.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2023, 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.graph;
+
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.structural.StructuralItem;
+import com.android.tools.r8.utils.structural.StructuralMapping;
+import com.android.tools.r8.utils.structural.StructuralSpecification;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.BiConsumer;
+import java.util.function.Predicate;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.RecordComponentVisitor;
+
+public class RecordComponentInfo implements StructuralItem<RecordComponentInfo> {
+
+ private final DexField field;
+ private final FieldTypeSignature signature;
+ private final List<DexAnnotation> annotations;
+
+ private static void specify(StructuralSpecification<RecordComponentInfo, ?> spec) {
+ spec.withItem(RecordComponentInfo::getName).withItem(RecordComponentInfo::getType);
+ }
+
+ public RecordComponentInfo(
+ DexField field, FieldTypeSignature signature, List<DexAnnotation> annotations) {
+ assert field != null;
+ assert signature != null;
+ assert annotations != null;
+ this.field = field;
+ this.signature = signature;
+ this.annotations = annotations;
+ }
+
+ public static List<RecordComponentInfo> emptyList() {
+ return Collections.emptyList();
+ }
+
+ public DexField getField() {
+ return field;
+ }
+
+ public DexString getName() {
+ return field.getName();
+ }
+
+ public DexType getType() {
+ return field.getType();
+ }
+
+ public FieldTypeSignature getSignature() {
+ return signature;
+ }
+
+ public List<DexAnnotation> getAnnotations() {
+ return annotations;
+ }
+
+ public void write(
+ ClassWriter writer,
+ NamingLens lens,
+ Predicate<DexType> isTypeMissing,
+ BiConsumer<AnnotationVisitor, DexEncodedAnnotation> annotationWriter) {
+ RecordComponentVisitor v =
+ writer.visitRecordComponent(
+ lens.lookupName(field).toString(),
+ lens.lookupDescriptor(getType()).toString(),
+ signature.toRenamedString(lens, isTypeMissing));
+ for (DexAnnotation annotation : annotations) {
+ if (annotation.visibility == DexAnnotation.VISIBILITY_SYSTEM) {
+ // Annotations with VISIBILITY_SYSTEM are not annotations in CF, but are special
+ // annotations in DEX, i.e. default, enclosing class, enclosing method, member classes,
+ // signature, throws.
+ continue;
+ }
+ String desc = lens.lookupDescriptor(annotation.getAnnotationType()).toString();
+ boolean visible = annotation.visibility == DexAnnotation.VISIBILITY_RUNTIME;
+ AnnotationVisitor av = v.visitAnnotation(desc, visible);
+ if (av != null) {
+ annotationWriter.accept(av, annotation.annotation);
+ av.visitEnd();
+ }
+ }
+ v.visitEnd();
+ }
+
+ @Override
+ public RecordComponentInfo self() {
+ return this;
+ }
+
+ @Override
+ public StructuralMapping<RecordComponentInfo> getStructuralMapping() {
+ return RecordComponentInfo::specify;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
index 7a6a5fc..f3cc3e9 100644
--- a/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/TreeFixerBase.java
@@ -108,6 +108,7 @@
fixupNestHost(clazz.getNestHostClassAttribute()),
fixupNestMemberAttributes(clazz.getNestMembersClassAttributes()),
fixupPermittedSubclassAttribute(clazz.getPermittedSubclassAttributes()),
+ fixupRecordComponents(clazz.getRecordComponents()),
fixupEnclosingMethodAttribute(clazz.getEnclosingMethodAttribute()),
fixupInnerClassAttributes(clazz.getInnerClasses()),
clazz.getClassSignature(),
@@ -289,6 +290,24 @@
return changed ? newPermittedSubclassAttributes : permittedSubclassAttributes;
}
+ protected List<RecordComponentInfo> fixupRecordComponents(
+ List<RecordComponentInfo> recordComponents) {
+ if (recordComponents.isEmpty()) {
+ return recordComponents;
+ }
+ // TODO(b/274888318): Check this.
+ boolean changed = false;
+ List<RecordComponentInfo> newRecordComponents = new ArrayList<>(recordComponents.size());
+ for (RecordComponentInfo info : recordComponents) {
+ DexField field = info.getField();
+ DexField newField = fixupFieldReference(field);
+ newRecordComponents.add(
+ new RecordComponentInfo(newField, info.getSignature(), info.getAnnotations()));
+ changed |= newField != field;
+ }
+ return changed ? newRecordComponents : recordComponents;
+ }
+
/** Fixup a proto descriptor. */
public DexProto fixupProto(DexProto proto) {
DexProto result = protoFixupCache.get(proto);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
index e1e72d8..de9f350 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/lint/GenerateLintFiles.java
@@ -131,6 +131,7 @@
null,
Collections.emptyList(),
Collections.emptyList(),
+ Collections.emptyList(),
null,
Collections.emptyList(),
ClassSignature.noSignature(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
index fd607ee..47a6d30 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/EmulatedInterfaceApplicationRewriter.java
@@ -79,6 +79,7 @@
null,
Collections.emptyList(),
Collections.emptyList(),
+ Collections.emptyList(),
null, // Note that we clear the enclosing and inner class attributes.
Collections.emptyList(),
emulatedInterface.getClassSignature(),
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index f10a6cf..5c92ac9 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -41,6 +41,7 @@
import com.android.tools.r8.graph.ParameterAnnotationsList;
import com.android.tools.r8.graph.PermittedSubclassAttribute;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.RecordComponentInfo;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapId;
@@ -293,14 +294,9 @@
}
if (clazz.isRecord()) {
- // TODO(b/169645628): Strip record components if not kept.
- for (DexEncodedField instanceField : clazz.instanceFields()) {
- String componentName = getNamingLens().lookupName(instanceField.getReference()).toString();
- String componentDescriptor =
- getNamingLens().lookupDescriptor(instanceField.getReference().type).toString();
- String componentSignature =
- instanceField.getGenericSignature().toRenamedString(getNamingLens(), isTypeMissing);
- writer.visitRecordComponent(componentName, componentDescriptor, componentSignature);
+ // TODO(b/274888318): Strip record components if not kept.
+ for (RecordComponentInfo info : clazz.getRecordComponents()) {
+ info.write(writer, getNamingLens(), isTypeMissing, this::writeAnnotation);
}
}
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index 7b5a3ef..ad239c5 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.graph.GenericSignatureContextBuilder;
import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
import com.android.tools.r8.graph.GenericSignatureTypeRewriter;
+import com.android.tools.r8.graph.RecordComponentInfo;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.ThreadUtils;
import java.util.Collection;
@@ -108,6 +109,10 @@
.visitMethodSignature(method.getGenericSignature())
: method.getGenericSignature()));
});
+ // TODO(b/274888318): Check this.
+ for (RecordComponentInfo recordComponent : clazz.getRecordComponents()) {
+ genericSignatureTypeRewriter.rewrite(recordComponent.getSignature());
+ }
},
executorService);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 401d9af..6124e82 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.shaking;
+import static com.google.common.base.Predicates.alwaysFalse;
+
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DefaultInstanceInitializerCode;
import com.android.tools.r8.graph.DexClass;
@@ -21,15 +23,18 @@
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.graph.RecordComponentInfo;
import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.MutableMethodOptimizationInfo;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
+import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.ExceptionUtils;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.PredicateUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Sets;
import java.util.ArrayList;
@@ -219,6 +224,13 @@
clazz.removeInnerClasses(this::isAttributeReferencingMissingOrPrunedType);
clazz.removeEnclosingMethodAttribute(this::isAttributeReferencingPrunedItem);
rewriteNestAttributes(clazz, this::isTypeLive, appView::definitionFor);
+ // TODO(b/274888318): Check this.
+ if (reachableInstanceFields != null) {
+ if (!clazz.getRecordComponents().isEmpty()) {
+ clazz.removeRecordComponents(
+ PredicateUtils.not(isReachableInstanceField(reachableInstanceFields)));
+ }
+ }
unusedItemsPrinter.visited();
assert verifyNoDeadFields(clazz);
}
@@ -238,6 +250,24 @@
}
}
+ public static Predicate<RecordComponentInfo> isReachableInstanceField(
+ DexEncodedField[] reachableInstanceFields) {
+ final int ARRAY_LOOKUP_THRESHOLD = 10;
+ if (reachableInstanceFields.length == 0) {
+ return alwaysFalse();
+ } else if (reachableInstanceFields.length < ARRAY_LOOKUP_THRESHOLD) {
+ return info ->
+ ArrayUtils.contains(
+ reachableInstanceFields, DexEncodedField::getReference, info.getField());
+ } else {
+ Set<DexField> reachableInstanceFieldSet = Sets.newIdentityHashSet();
+ for (DexEncodedField reachableInstanceField : reachableInstanceFields) {
+ reachableInstanceFieldSet.add(reachableInstanceField.getReference());
+ }
+ return info -> reachableInstanceFieldSet.contains(info.getField());
+ }
+ }
+
private boolean isTypeMissing(DexType type) {
return appView.appInfo().getMissingClasses().contains(type);
}
diff --git a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerFactory.java b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerFactory.java
index 1c5dd2b..d63bcf5 100644
--- a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerFactory.java
+++ b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerFactory.java
@@ -48,6 +48,7 @@
NestHostClassAttribute.none(),
Collections.emptyList(),
Collections.emptyList(),
+ Collections.emptyList(),
EnclosingMethodAttribute.none(),
Collections.emptyList(),
ClassSignature.noSignature(),
diff --git a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
index e971964..e9d64ab 100644
--- a/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
+++ b/src/main/java/com/android/tools/r8/startup/generated/InstrumentationServerImplFactory.java
@@ -70,6 +70,7 @@
NestHostClassAttribute.none(),
Collections.emptyList(),
Collections.emptyList(),
+ Collections.emptyList(),
EnclosingMethodAttribute.none(),
Collections.emptyList(),
ClassSignature.noSignature(),
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
index e9951c3..da83f1e 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticClassBuilder.java
@@ -22,6 +22,7 @@
import com.android.tools.r8.graph.NestHostClassAttribute;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.graph.PermittedSubclassAttribute;
+import com.android.tools.r8.graph.RecordComponentInfo;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import java.util.ArrayList;
@@ -173,6 +174,7 @@
NestHostClassAttribute nestHost = null;
List<NestMemberClassAttribute> nestMembers = Collections.emptyList();
List<PermittedSubclassAttribute> permittedSubclasses = Collections.emptyList();
+ List<RecordComponentInfo> recordComponents = Collections.emptyList();
EnclosingMethodAttribute enclosingMembers = null;
List<InnerClassAttribute> innerClasses = Collections.emptyList();
for (SyntheticMethodBuilder builder : methods) {
@@ -201,6 +203,7 @@
nestHost,
nestMembers,
permittedSubclasses,
+ recordComponents,
enclosingMembers,
innerClasses,
signature,
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index e1741af..b8d37cb 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -144,6 +144,16 @@
return false;
}
+ public static <T, U> boolean contains(
+ T[] elements, Function<T, U> elementMap, U mappedElementToLookFor) {
+ for (T element : elements) {
+ if (elementMap.apply(element).equals(mappedElementToLookFor)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public static int[] fromPredicate(IntPredicate predicate, int size) {
int[] result = new int[size];
for (int i = 0; i < size; i++) {
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 8ce6323..dbbc1e0 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -449,6 +449,8 @@
// Flag to allow record annotations in DEX. See b/231930852 for context.
public boolean emitRecordAnnotationsInDex =
System.getProperty("com.android.tools.r8.emitRecordAnnotationsInDex") != null;
+ public boolean emitRecordAnnotationsExInDex =
+ System.getProperty("com.android.tools.r8.emitRecordAnnotationsExInDex") != null;
// Flag to allow nest annotations in DEX. See b/231930852 for context.
public boolean emitNestAnnotationsInDex =
diff --git a/src/test/examplesJava17/records/RecordWithAnnotations.java b/src/test/examplesJava17/records/RecordWithAnnotations.java
new file mode 100644
index 0000000..d1648b6
--- /dev/null
+++ b/src/test/examplesJava17/records/RecordWithAnnotations.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2023, 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 records;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.reflect.Field;
+import java.lang.reflect.RecordComponent;
+
+public class RecordWithAnnotations {
+
+ @Target({ElementType.FIELD, ElementType.RECORD_COMPONENT})
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface Annotation {
+ String value();
+ }
+
+ @Target(ElementType.FIELD)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface AnnotationFieldOnly {
+ String value();
+ }
+
+ @Target(ElementType.RECORD_COMPONENT)
+ @Retention(RetentionPolicy.RUNTIME)
+ @interface AnnotationRecordComponentOnly {
+ String value();
+ }
+
+ record Person(
+ @Annotation("a") @AnnotationFieldOnly("b") @AnnotationRecordComponentOnly("c") String name,
+ @Annotation("x") @AnnotationFieldOnly("y") @AnnotationRecordComponentOnly("z") int age) {}
+
+ public static void main(String[] args) {
+ Person janeDoe = new Person("Jane Doe", 42);
+ System.out.println(janeDoe.name);
+ System.out.println(janeDoe.age);
+ System.out.println(janeDoe.name());
+ System.out.println(janeDoe.age());
+ try {
+ Class.class.getDeclaredMethod("isRecord");
+ } catch (NoSuchMethodException e) {
+ System.out.println("Class.isRecord not present");
+ return;
+ }
+ System.out.println(Person.class.isRecord());
+ if (Person.class.isRecord()) {
+ System.out.println(Person.class.getRecordComponents().length);
+ for (int i = 0; i < Person.class.getRecordComponents().length; i++) {
+ RecordComponent c = Person.class.getRecordComponents()[i];
+ System.out.println(c.getName());
+ System.out.println(c.getType().getName());
+ System.out.println(c.getGenericSignature() == null);
+ System.out.println(c.getAnnotations().length);
+ for (int j = 0; j < c.getAnnotations().length; j++) {
+ System.out.println(c.getAnnotations()[j]);
+ }
+ }
+ System.out.println(Person.class.getDeclaredFields().length);
+ for (int i = 0; i < Person.class.getDeclaredFields().length; i++) {
+ Field f = Person.class.getDeclaredFields()[i];
+ System.out.println(f.getDeclaredAnnotations().length);
+ for (int j = 0; j < f.getDeclaredAnnotations().length; j++) {
+ System.out.println(f.getAnnotations()[j]);
+ }
+ }
+ }
+ }
+}
diff --git a/src/test/examplesJava17/records/RecordWithSignature.java b/src/test/examplesJava17/records/RecordWithSignature.java
new file mode 100644
index 0000000..be31024
--- /dev/null
+++ b/src/test/examplesJava17/records/RecordWithSignature.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2023, 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 records;
+
+import java.lang.reflect.RecordComponent;
+
+public class RecordWithSignature {
+
+ record Person<N extends CharSequence, A>(N name, A age) {}
+
+ public static void main(String[] args) {
+ Person<String, Integer> janeDoe = new Person<>("Jane Doe", 42);
+ System.out.println(janeDoe.name);
+ System.out.println(janeDoe.age);
+ System.out.println(janeDoe.name());
+ System.out.println(janeDoe.age());
+ try {
+ Class.class.getDeclaredMethod("isRecord");
+ } catch (NoSuchMethodException e) {
+ System.out.println("Class.isRecord not present");
+ return;
+ }
+ System.out.println(Person.class.isRecord());
+ if (Person.class.isRecord()) {
+ System.out.println(Person.class.getRecordComponents().length);
+ for (int i = 0; i < Person.class.getRecordComponents().length; i++) {
+ RecordComponent c = Person.class.getRecordComponents()[i];
+ System.out.println(c.getName());
+ System.out.println(c.getType().getName());
+ System.out.println(c.getGenericSignature());
+ System.out.println(c.getAnnotations().length);
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordComponentAnnotationsTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordComponentAnnotationsTest.java
new file mode 100644
index 0000000..dc3ef4e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordComponentAnnotationsTest.java
@@ -0,0 +1,332 @@
+// Copyright (c) 2023, 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.desugar.records;
+
+import static com.android.tools.r8.utils.codeinspector.AnnotationMatchers.hasAnnotationTypes;
+import static com.android.tools.r8.utils.codeinspector.AnnotationMatchers.hasElements;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RecordComponentAnnotationsTest extends TestBase {
+
+ private static final String RECORD_NAME = "RecordWithAnnotations";
+ private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+ private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "Jane Doe",
+ "42",
+ "Jane Doe",
+ "42",
+ "true",
+ "2",
+ "name",
+ "java.lang.String",
+ "true",
+ "2",
+ "@records.RecordWithAnnotations$Annotation(\"a\")",
+ "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(\"c\")",
+ "age",
+ "int",
+ "true",
+ "2",
+ "@records.RecordWithAnnotations$Annotation(\"x\")",
+ "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(\"z\")",
+ "2",
+ "2",
+ "@records.RecordWithAnnotations$Annotation(\"a\")",
+ "@records.RecordWithAnnotations$AnnotationFieldOnly(\"b\")",
+ "2",
+ "@records.RecordWithAnnotations$Annotation(\"x\")",
+ "@records.RecordWithAnnotations$AnnotationFieldOnly(\"y\")");
+ private static final String EXPECTED_RESULT_R8 =
+ StringUtils.lines(
+ "Jane Doe",
+ "42",
+ "Jane Doe",
+ "42",
+ "true",
+ "2",
+ "a",
+ "java.lang.String",
+ "true",
+ "2",
+ "@records.RecordWithAnnotations$Annotation(\"a\")",
+ "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(\"c\")",
+ "b",
+ "int",
+ "true",
+ "2",
+ "@records.RecordWithAnnotations$Annotation(\"x\")",
+ "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(\"z\")",
+ "2",
+ "2",
+ "@records.RecordWithAnnotations$Annotation(\"a\")",
+ "@records.RecordWithAnnotations$AnnotationFieldOnly(\"b\")",
+ "2",
+ "@records.RecordWithAnnotations$Annotation(\"x\")",
+ "@records.RecordWithAnnotations$AnnotationFieldOnly(\"y\")");
+ private static final String EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS =
+ StringUtils.lines(
+ "Jane Doe",
+ "42",
+ "Jane Doe",
+ "42",
+ "true",
+ "2",
+ "a",
+ "java.lang.String",
+ "true",
+ "2",
+ "@records.RecordWithAnnotations$Annotation(\"a\")",
+ "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(\"c\")",
+ "b",
+ "int",
+ "true",
+ "2",
+ "@records.RecordWithAnnotations$Annotation(\"x\")",
+ "@records.RecordWithAnnotations$AnnotationRecordComponentOnly(\"z\")",
+ "2",
+ "0",
+ "0");
+ private static final String EXPECTED_RESULT_DESUGARED =
+ StringUtils.lines("Jane Doe", "42", "Jane Doe", "42", "Class.isRecord not present");
+ private static final String EXPECTED_RESULT_DESUGARED_JVM17 =
+ StringUtils.lines("Jane Doe", "42", "Jane Doe", "42", "false");
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public Boolean keepAnnotations;
+
+ @Parameters(name = "{0}, keepAnnotations: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters()
+ .withDexRuntimesAndAllApiLevels()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
+ .withAllApiLevelsAlsoForCf()
+ .build(),
+ BooleanUtils.values());
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeJvmTestParameters();
+ assumeTrue(keepAnnotations);
+ testForJvm(parameters)
+ .addProgramClassFileData(PROGRAM_DATA)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testDesugaring() throws Exception {
+ parameters.assumeDexRuntime();
+ assumeTrue(keepAnnotations);
+ // Android U will support records.
+ boolean compilingForNativeRecordSupport =
+ parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U);
+ testForDesugaring(
+ parameters,
+ options -> {
+ if (compilingForNativeRecordSupport) {
+ // TODO(b/231930852): When Art 14 support records this will be controlled by API
+ // level.
+ options.emitRecordAnnotationsInDex = true;
+ options.emitRecordAnnotationsExInDex = true;
+ }
+ })
+ .addProgramClassFileData(PROGRAM_DATA)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .applyIf(
+ parameters.isDexRuntime() && compilingForNativeRecordSupport,
+ // Current Art 14 build does not support the java.lang.Record class.
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+ compilingForNativeRecordSupport,
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
+ r ->
+ r.assertSuccessWithOutput(
+ !parameters.isCfRuntime()
+ ? EXPECTED_RESULT_DESUGARED
+ : EXPECTED_RESULT_DESUGARED_JVM17)
+ .inspect(
+ inspector -> {
+ ClassSubject person =
+ inspector.clazz("records.RecordWithAnnotations$Person");
+ FieldSubject name = person.uniqueFieldWithOriginalName("name");
+ assertThat(name, isPresentAndNotRenamed());
+ FieldSubject age = person.uniqueFieldWithOriginalName("age");
+ assertThat(age, isPresentAndNotRenamed());
+ if (compilingForNativeRecordSupport) {
+ assertEquals(2, person.getFinalRecordComponents().size());
+
+ assertEquals(
+ name.getFinalName(),
+ person.getFinalRecordComponents().get(0).getName());
+ assertTrue(
+ person
+ .getFinalRecordComponents()
+ .get(0)
+ .getType()
+ .is("java.lang.String"));
+ assertNull(person.getFinalRecordComponents().get(0).getSignature());
+ assertEquals(
+ 2,
+ person.getFinalRecordComponents().get(0).getAnnotations().size());
+ assertThat(
+ person.getFinalRecordComponents().get(0).getAnnotations(),
+ hasAnnotationTypes(
+ inspector.getTypeSubject(
+ "records.RecordWithAnnotations$Annotation"),
+ inspector.getTypeSubject(
+ "records.RecordWithAnnotations$AnnotationRecordComponentOnly")));
+ assertThat(
+ person.getFinalRecordComponents().get(0).getAnnotations().get(0),
+ hasElements(new Pair<>("value", "a")));
+ assertThat(
+ person.getFinalRecordComponents().get(0).getAnnotations().get(1),
+ hasElements(new Pair<>("value", "c")));
+
+ assertEquals(
+ age.getFinalName(),
+ person.getFinalRecordComponents().get(1).getName());
+ assertTrue(
+ person.getFinalRecordComponents().get(1).getType().is("int"));
+ assertNull(person.getFinalRecordComponents().get(1).getSignature());
+ assertEquals(
+ 2,
+ person.getFinalRecordComponents().get(1).getAnnotations().size());
+ assertThat(
+ person.getFinalRecordComponents().get(1).getAnnotations(),
+ hasAnnotationTypes(
+ inspector.getTypeSubject(
+ "records.RecordWithAnnotations$Annotation"),
+ inspector.getTypeSubject(
+ "records.RecordWithAnnotations$AnnotationRecordComponentOnly")));
+ assertThat(
+ person.getFinalRecordComponents().get(1).getAnnotations().get(0),
+ hasElements(new Pair<>("value", "x")));
+ assertThat(
+ person.getFinalRecordComponents().get(1).getAnnotations().get(1),
+ hasElements(new Pair<>("value", "z")));
+ } else {
+ assertEquals(0, person.getFinalRecordComponents().size());
+ }
+ }));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ // Android U will support records.
+ boolean compilingForNativeRecordSupport =
+ parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U);
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ // TODO(b/231930852): Change to android.jar for Androud U when that contains
+ // java.lang.Record.
+ .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .addKeepMainRule(MAIN_TYPE)
+ .addKeepClassAndMembersRulesWithAllowObfuscation("records.RecordWithAnnotations$Person")
+ .addKeepClassAndMembersRules(
+ "records.RecordWithAnnotations$Annotation",
+ "records.RecordWithAnnotations$AnnotationFieldOnly",
+ "records.RecordWithAnnotations$AnnotationRecordComponentOnly")
+ .applyIf(keepAnnotations, TestShrinkerBuilder::addKeepRuntimeVisibleAnnotations)
+ .setMinApi(parameters)
+ .applyIf(
+ compilingForNativeRecordSupport,
+ // TODO(b/231930852): When Art 14 support records this will be controlled by API level.
+ b ->
+ b.addOptionsModification(options -> options.emitRecordAnnotationsInDex = true)
+ .addOptionsModification(options -> options.emitRecordAnnotationsExInDex = true))
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject person = inspector.clazz("records.RecordWithAnnotations$Person");
+ FieldSubject name = person.uniqueFieldWithOriginalName("name");
+ FieldSubject age = person.uniqueFieldWithOriginalName("age");
+ if (compilingForNativeRecordSupport) {
+ assertEquals(2, person.getFinalRecordComponents().size());
+
+ assertEquals(
+ name.getFinalName(), person.getFinalRecordComponents().get(0).getName());
+ assertTrue(
+ person.getFinalRecordComponents().get(0).getType().is("java.lang.String"));
+ assertNull(person.getFinalRecordComponents().get(0).getSignature());
+ assertEquals(2, person.getFinalRecordComponents().get(0).getAnnotations().size());
+ assertThat(
+ person.getFinalRecordComponents().get(0).getAnnotations(),
+ hasAnnotationTypes(
+ inspector.getTypeSubject("records.RecordWithAnnotations$Annotation"),
+ inspector.getTypeSubject(
+ "records.RecordWithAnnotations$AnnotationRecordComponentOnly")));
+ assertThat(
+ person.getFinalRecordComponents().get(0).getAnnotations().get(0),
+ hasElements(new Pair<>("value", "a")));
+ assertThat(
+ person.getFinalRecordComponents().get(0).getAnnotations().get(1),
+ hasElements(new Pair<>("value", "c")));
+
+ assertEquals(
+ age.getFinalName(), person.getFinalRecordComponents().get(1).getName());
+ assertTrue(person.getFinalRecordComponents().get(1).getType().is("int"));
+ assertNull(person.getFinalRecordComponents().get(1).getSignature());
+ assertEquals(2, person.getFinalRecordComponents().get(1).getAnnotations().size());
+ assertThat(
+ person.getFinalRecordComponents().get(1).getAnnotations(),
+ hasAnnotationTypes(
+ inspector.getTypeSubject("records.RecordWithAnnotations$Annotation"),
+ inspector.getTypeSubject(
+ "records.RecordWithAnnotations$AnnotationRecordComponentOnly")));
+ assertThat(
+ person.getFinalRecordComponents().get(1).getAnnotations().get(0),
+ hasElements(new Pair<>("value", "x")));
+ assertThat(
+ person.getFinalRecordComponents().get(1).getAnnotations().get(1),
+ hasElements(new Pair<>("value", "z")));
+ } else {
+ assertEquals(0, person.getFinalRecordComponents().size());
+ }
+ })
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .applyIf(
+ parameters.isCfRuntime(),
+ // TODO(b/274888318): EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS still has component
+ // annotations.
+ r ->
+ r.assertSuccessWithOutput(
+ keepAnnotations ? EXPECTED_RESULT_R8 : EXPECTED_RESULT_R8_NO_KEEP_ANNOTATIONS),
+ // r -> r.assertSuccessWithOutput(EXPECTED_RESULT_R8),
+ compilingForNativeRecordSupport,
+ // Current Art 14 build does not support the java.lang.Record class.
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT_DESUGARED));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/RecordComponentSignatureTest.java b/src/test/java/com/android/tools/r8/desugar/records/RecordComponentSignatureTest.java
new file mode 100644
index 0000000..5ce845f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/RecordComponentSignatureTest.java
@@ -0,0 +1,205 @@
+// Copyright (c) 2023, 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.desugar.records;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestShrinkerBuilder;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RecordComponentSignatureTest extends TestBase {
+
+ private static final String RECORD_NAME = "RecordWithSignature";
+ private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
+ private static final String MAIN_TYPE = RecordTestUtils.getMainType(RECORD_NAME);
+ private static final String EXPECTED_RESULT =
+ StringUtils.lines(
+ "Jane Doe",
+ "42",
+ "Jane Doe",
+ "42",
+ "true",
+ "2",
+ "name",
+ "java.lang.CharSequence",
+ "TN;",
+ "0",
+ "age",
+ "java.lang.Object",
+ "TA;",
+ "0");
+ private static final String EXPECTED_RESULT_R8 =
+ StringUtils.lines(
+ "Jane Doe", "42", "Jane Doe", "42", "true", "1", "a", "java.lang.Object", "TA;", "0");
+ private static final String EXPECTED_RESULT_R8_NO_KEEP_SIGNATURE =
+ StringUtils.lines(
+ "Jane Doe", "42", "Jane Doe", "42", "true", "1", "a", "java.lang.Object", "null", "0");
+ private static final String EXPECTED_RESULT_DESUGARED =
+ StringUtils.lines("Jane Doe", "42", "Jane Doe", "42", "Class.isRecord not present");
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public Boolean keepSignatures;
+
+ @Parameters(name = "{0}, keepSignatures: {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters()
+ .withDexRuntimesAndAllApiLevels()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
+ .withAllApiLevelsAlsoForCf()
+ .build(),
+ BooleanUtils.values());
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeJvmTestParameters();
+ assumeTrue(keepSignatures);
+ testForJvm(parameters)
+ .addProgramClassFileData(PROGRAM_DATA)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testDesugaring() throws Exception {
+ parameters.assumeDexRuntime();
+ assumeTrue(keepSignatures);
+ // Android U will support records.
+ boolean compilingForNativeRecordSupport =
+ parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U);
+ testForDesugaring(
+ parameters,
+ options -> {
+ if (compilingForNativeRecordSupport) {
+ // TODO(b/231930852): When Art 14 support records this will be controlled by API
+ // level.
+ options.emitRecordAnnotationsInDex = true;
+ options.emitRecordAnnotationsExInDex = true;
+ }
+ })
+ .addProgramClassFileData(PROGRAM_DATA)
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .applyIf(
+ compilingForNativeRecordSupport,
+ // Current Art 14 build does not support the java.lang.Record class.
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+ r ->
+ r.assertSuccessWithOutput(EXPECTED_RESULT_DESUGARED)
+ .inspect(
+ inspector -> {
+ ClassSubject person =
+ inspector.clazz("records.RecordWithSignature$Person");
+ if (compilingForNativeRecordSupport) {
+ assertEquals(2, person.getFinalRecordComponents().size());
+
+ assertEquals(
+ "name", person.getFinalRecordComponents().get(0).getName());
+ assertTrue(
+ person
+ .getFinalRecordComponents()
+ .get(0)
+ .getType()
+ .is("java.lang.CharSequence"));
+ assertEquals(
+ "TN;", person.getFinalRecordComponents().get(0).getSignature());
+ assertEquals(
+ 0,
+ person.getFinalRecordComponents().get(0).getAnnotations().size());
+
+ assertEquals("age", person.getFinalRecordComponents().get(1).getName());
+ assertTrue(
+ person
+ .getFinalRecordComponents()
+ .get(1)
+ .getType()
+ .is("java.lang.Object"));
+ assertEquals(
+ "TA;", person.getFinalRecordComponents().get(1).getSignature());
+ assertEquals(
+ 0,
+ person.getFinalRecordComponents().get(1).getAnnotations().size());
+ } else {
+ assertEquals(0, person.getFinalRecordComponents().size());
+ }
+ }));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ boolean compilingForNativeRecordSupport =
+ parameters.isCfRuntime()
+ || parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.U);
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ // TODO(b/231930852): Change to android.jar for Androud U when that contains
+ // java.lang.Record.
+ .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .addKeepMainRule(MAIN_TYPE)
+ .applyIf(keepSignatures, TestShrinkerBuilder::addKeepAttributeSignature)
+ .setMinApi(parameters)
+ .applyIf(
+ compilingForNativeRecordSupport,
+ // TODO(b/231930852): When Art 14 support records this will be controlled by API level.
+ b ->
+ b.addOptionsModification(options -> options.emitRecordAnnotationsInDex = true)
+ .addOptionsModification(options -> options.emitRecordAnnotationsExInDex = true))
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject person = inspector.clazz("records.RecordWithSignature$Person");
+ FieldSubject age = person.uniqueFieldWithOriginalName("age");
+ assertThat(age, isPresentAndRenamed());
+ if (compilingForNativeRecordSupport) {
+ assertEquals(1, person.getFinalRecordComponents().size());
+ assertEquals(
+ age.getFinalName(), person.getFinalRecordComponents().get(0).getName());
+ assertTrue(
+ person.getFinalRecordComponents().get(0).getType().is("java.lang.Object"));
+ if (keepSignatures) {
+ assertEquals("TA;", person.getFinalRecordComponents().get(0).getSignature());
+ } else {
+ assertNull(person.getFinalRecordComponents().get(0).getSignature());
+ }
+ assertEquals(0, person.getFinalRecordComponents().get(0).getAnnotations().size());
+ } else {
+ assertEquals(0, person.getFinalRecordComponents().size());
+ }
+ })
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ // No Art VM actually supports the java.lang.Record class.
+ .applyIf(
+ parameters.isCfRuntime(),
+ r ->
+ r.assertSuccessWithOutput(
+ keepSignatures ? EXPECTED_RESULT_R8 : EXPECTED_RESULT_R8_NO_KEEP_SIGNATURE),
+ compilingForNativeRecordSupport,
+ // Current Art 14 build does not support the java.lang.Record class.
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+ r -> r.assertSuccessWithOutput(EXPECTED_RESULT_DESUGARED));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
index d5f5e91..c7c00b8 100644
--- a/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/conversion/CallGraphTestBase.java
@@ -35,6 +35,7 @@
null,
Collections.emptyList(),
Collections.emptyList(),
+ Collections.emptyList(),
null,
Collections.emptyList(),
ClassSignature.noSignature(),
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 0d8f6d0..39e3174 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -235,6 +235,11 @@
}
@Override
+ public List<RecordComponentSubject> getFinalRecordComponents() {
+ throw new Unreachable("Cannot determine RecordComponents attribute of an absent class");
+ }
+
+ @Override
public KmClassSubject getKmClass() {
return null;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationMatchers.java
index bb1c57a..4a0f1ac 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AnnotationMatchers.java
@@ -4,6 +4,8 @@
package com.android.tools.r8.utils.codeinspector;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.StringUtils;
import java.util.Arrays;
import java.util.List;
@@ -101,4 +103,50 @@
}
};
}
+
+ @SafeVarargs
+ public static Matcher<FoundAnnotationSubject> hasElements(
+ Pair<String, String>... expectedElements) {
+ return new TypeSafeMatcher<FoundAnnotationSubject>() {
+
+ @Override
+ protected boolean matchesSafely(FoundAnnotationSubject subject) {
+ if (expectedElements.length != subject.getAnnotation().elements.length) {
+ return false;
+ }
+ for (int i = 0; i < expectedElements.length; i++) {
+ DexAnnotationElement element = subject.getAnnotation().elements[i];
+ if (!element.name.toString().equals(expectedElements[i].getFirst())
+ || !element.value.isDexValueString()
+ || !element
+ .value
+ .asDexValueString()
+ .getValue()
+ .toString()
+ .equals(expectedElements[i].getSecond())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ StringBuilder builder = new StringBuilder("has elements ");
+ for (Pair<String, String> expectedElement : expectedElements) {
+ builder
+ .append(expectedElement.getFirst())
+ .append(" = ")
+ .append(expectedElement.getSecond())
+ .append(", ");
+ }
+ description.appendText(builder.toString());
+ }
+
+ @Override
+ public void describeMismatchSafely(FoundAnnotationSubject subject, Description description) {
+ description.appendText("annotation did not");
+ }
+ };
+ }
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 1aebbdd..99dd879 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -250,6 +250,8 @@
public abstract List<TypeSubject> getFinalPermittedSubclassAttributes();
+ public abstract List<RecordComponentSubject> getFinalRecordComponents();
+
public abstract KmClassSubject getKmClass();
public abstract KmPackageSubject getKmPackage();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index fcfe160..457e4a2 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -525,6 +525,15 @@
}
@Override
+ public List<RecordComponentSubject> getFinalRecordComponents() {
+ List<RecordComponentSubject> result = new ArrayList<>(dexClass.getRecordComponents().size());
+ for (int i = 0; i < dexClass.getRecordComponents().size(); i++) {
+ result.add(new RecordComponentSubject(codeInspector, dexClass, i));
+ }
+ return result;
+ }
+
+ @Override
public int hashCode() {
int result = codeInspector.hashCode();
result = 31 * result + dexClass.hashCode();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/RecordComponentSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/RecordComponentSubject.java
new file mode 100644
index 0000000..87b36df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/RecordComponentSubject.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2023, 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.utils.codeinspector;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexAnnotation;
+import com.android.tools.r8.graph.DexClass;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class RecordComponentSubject extends Subject {
+
+ private final CodeInspector codeInspector;
+ private final DexClass clazz;
+ private final int index;
+
+ public RecordComponentSubject(CodeInspector codeInspector, DexClass clazz, int index) {
+ this.codeInspector = codeInspector;
+ this.clazz = clazz;
+ this.index = index;
+ }
+
+ public int size() {
+ return clazz.getRecordComponents().size();
+ }
+
+ public String getName() {
+ return clazz.getRecordComponents().get(index).getName().toString();
+ }
+
+ public TypeSubject getType() {
+ return new TypeSubject(codeInspector, clazz.getRecordComponents().get(index).getType());
+ }
+
+ public String getSignature() {
+ return clazz.getRecordComponents().get(index).getSignature().toString();
+ }
+
+ public List<FoundAnnotationSubject> getAnnotations() {
+ int size = clazz.getRecordComponents().get(index).getAnnotations().size();
+ if (size == 0) {
+ return Collections.emptyList();
+ }
+ List<FoundAnnotationSubject> result = new ArrayList<>(size);
+ for (DexAnnotation annotation : clazz.getRecordComponents().get(index).getAnnotations()) {
+ result.add(new FoundAnnotationSubject(annotation, codeInspector));
+ }
+ return result;
+ }
+
+ @Override
+ public boolean isPresent() {
+ return true;
+ }
+
+ @Override
+ public boolean isRenamed() {
+ throw new Unreachable("Cannot determine if a record component is renamed");
+ }
+
+ @Override
+ public boolean isSynthetic() {
+ throw new Unreachable("Cannot determine if a record component is synthetic");
+ }
+}