blob: 13c487d3abb90f94bd1cfdcdc9c2be5552118221 [file] [log] [blame]
// 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 static com.android.tools.r8.ir.desugar.records.RecordFullInstructionDesugaring.EQUALS_RECORD_METHOD_NAME;
import static com.android.tools.r8.ir.desugar.records.RecordFullInstructionDesugaring.GET_FIELDS_AS_OBJECTS_METHOD_NAME;
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
import static com.android.tools.r8.utils.codeinspector.Matchers.ifThen;
import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsentIf;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentIf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.D8TestCompileResult;
import com.android.tools.r8.JdkClassFileProvider;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.profile.art.model.ExternalArtProfile;
import com.android.tools.r8.profile.art.utils.ArtProfileInspector;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.InternalOptions.InlinerOptions;
import com.android.tools.r8.utils.MethodReferenceUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.Collections;
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 RecordProfileRewritingTest extends TestBase {
private static final String EXPECTED_RESULT =
StringUtils.lines(
"Jane Doe", "42", "true", "true", "true", "false", "false", "false", "false");
private static final ClassReference MAIN_REFERENCE = Reference.classFromClass(SimpleRecord.class);
private static final ClassReference PERSON_REFERENCE = Reference.classFromClass(Person.class);
private static final ClassReference RECORD_REFERENCE =
Reference.classFromTypeName("java.lang.Record");
private static final ClassReference OBJECT_REFERENCE = Reference.classFromClass(Object.class);
private static final ClassReference STRING_REFERENCE = Reference.classFromClass(String.class);
@Parameter(0)
public TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
}
@Test
public void testReference() throws Exception {
parameters.assumeJvmTestParameters();
assumeTrue(runtimeWithRecordsSupport(parameters.getRuntime()));
testForJvm(parameters)
.addInnerClassesAndStrippedOuter(getClass())
.run(parameters.getRuntime(), MAIN_REFERENCE.getTypeName())
.assertSuccessWithOutput(EXPECTED_RESULT);
}
@Test
public void testD8() throws Exception {
D8TestCompileResult compileResult =
testForD8(parameters.getBackend())
.addInnerClassesAndStrippedOuter(getClass())
.addArtProfileForRewriting(getArtProfile())
.setMinApi(parameters)
.compile();
compileResult
.inspectResidualArtProfile(
profileInspector ->
compileResult.inspectWithOptions(
inspector -> inspectD8(profileInspector, inspector),
options -> options.testing.disableRecordApplicationReaderMap = true))
.run(parameters.getRuntime(), MAIN_REFERENCE.getTypeName())
.applyIf(
isRecordsFullyDesugaredForD8(parameters)
|| runtimeWithRecordsSupport(parameters.getRuntime()),
r -> r.assertSuccessWithOutput(EXPECTED_RESULT),
r -> r.assertFailureWithErrorThatThrows(ClassNotFoundException.class));
}
@Test
public void testR8() throws Exception {
parameters.assumeR8TestParameters();
assumeTrue(runtimeWithRecordsSupport(parameters.getRuntime()) || parameters.isDexRuntime());
R8TestCompileResult compileResult =
testForR8(parameters.getBackend())
.addInnerClassesAndStrippedOuter(getClass())
.addKeepMainRule(MAIN_REFERENCE.getTypeName())
.addKeepRules(
"-neverpropagatevalue class " + PERSON_REFERENCE.getTypeName() + " { <fields>; }")
.addArtProfileForRewriting(getArtProfile())
.addOptionsModification(InlinerOptions::disableInlining)
.applyIf(
parameters.isCfRuntime(),
testBuilder -> testBuilder.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()))
.enableProguardTestOptions()
.noHorizontalClassMergingOfSynthetics()
.setMinApi(parameters)
.compile();
compileResult
.inspectResidualArtProfile(
profileInspector ->
compileResult.inspectWithOptions(
inspector -> inspectR8(profileInspector, inspector),
options -> options.testing.disableRecordApplicationReaderMap = true))
.run(parameters.getRuntime(), MAIN_REFERENCE.getTypeName())
.assertSuccessWithOutput(EXPECTED_RESULT);
}
private ExternalArtProfile getArtProfile() {
return ExternalArtProfile.builder()
.addMethodRule(MethodReferenceUtils.mainMethod(MAIN_REFERENCE))
.addClassRule(PERSON_REFERENCE)
.addMethodRule(
MethodReferenceUtils.instanceConstructor(
PERSON_REFERENCE, STRING_REFERENCE, Reference.INT))
.addMethodRule(
Reference.method(PERSON_REFERENCE, "name", Collections.emptyList(), STRING_REFERENCE))
.addMethodRule(
Reference.method(PERSON_REFERENCE, "age", Collections.emptyList(), Reference.INT))
.addMethodRule(
Reference.method(
PERSON_REFERENCE, "equals", ImmutableList.of(OBJECT_REFERENCE), Reference.BOOL))
.addMethodRule(
Reference.method(PERSON_REFERENCE, "hashCode", Collections.emptyList(), Reference.INT))
.addMethodRule(
Reference.method(
PERSON_REFERENCE, "toString", Collections.emptyList(), STRING_REFERENCE))
.build();
}
private void inspectD8(ArtProfileInspector profileInspector, CodeInspector inspector) {
inspect(
profileInspector,
inspector,
SyntheticItemsTestUtils.syntheticRecordTagClass(),
false,
false,
parameters.canUseNestBasedAccessesWhenDesugaring(),
!isRecordsFullyDesugaredForD8(parameters),
false);
}
private void inspectR8(ArtProfileInspector profileInspector, CodeInspector inspector) {
inspect(
profileInspector,
inspector,
RECORD_REFERENCE,
parameters.canHaveNonReboundConstructorInvoke(),
true,
parameters.canUseNestBasedAccesses(),
!isRecordsFullyDesugaredForR8(parameters),
parameters.isCfRuntime());
}
private void inspect(
ArtProfileInspector profileInspector,
CodeInspector inspector,
ClassReference recordClassReference,
boolean canHaveNonReboundConstructorInvoke,
boolean canMergeRecordTag,
boolean canUseNestBasedAccesses,
boolean partialDesugaring,
boolean recordDesugaringIsOff) { // Record desugaring is partial or full.
ClassSubject mainClassSubject = inspector.clazz(MAIN_REFERENCE);
assertThat(mainClassSubject, isPresent());
MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
assertThat(mainMethodSubject, isPresent());
ClassSubject recordTagClassSubject = inspector.clazz(recordClassReference);
assertThat(recordTagClassSubject, isAbsentIf(canMergeRecordTag || partialDesugaring));
if (recordTagClassSubject.isPresent()) {
assertEquals(1, recordTagClassSubject.allMethods().size());
}
MethodSubject recordTagInstanceInitializerSubject = recordTagClassSubject.init();
assertThat(recordTagInstanceInitializerSubject, isPresentIf(recordTagClassSubject.isPresent()));
ClassSubject personRecordClassSubject = inspector.clazz(PERSON_REFERENCE);
assertThat(personRecordClassSubject, isPresent());
assertEquals(
partialDesugaring
? inspector.getTypeSubject(RECORD_REFERENCE.getTypeName())
: canMergeRecordTag
? inspector.getTypeSubject(Object.class.getTypeName())
: recordTagClassSubject.asTypeSubject(),
personRecordClassSubject.getSuperType());
assertEquals(
recordDesugaringIsOff ? 6 : (canMergeRecordTag && !partialDesugaring) ? 9 : 8,
personRecordClassSubject.allMethods().size());
MethodSubject personDefaultInstanceInitializerSubject = personRecordClassSubject.init();
assertThat(
personDefaultInstanceInitializerSubject,
isPresentIf(canMergeRecordTag && !partialDesugaring));
MethodSubject personInstanceInitializerSubject =
canMergeRecordTag
? personRecordClassSubject.init(String.class.getTypeName())
: personRecordClassSubject.init(String.class.getTypeName(), "int");
assertThat(personInstanceInitializerSubject, isPresent());
// Name getters.
MethodSubject nameMethodSubject = personRecordClassSubject.uniqueMethodWithOriginalName("name");
assertThat(nameMethodSubject, isPresent());
// Age getters.
MethodSubject ageMethodSubject = personRecordClassSubject.uniqueMethodWithOriginalName("age");
assertThat(ageMethodSubject, isPresent());
// boolean equals(Object)
MethodSubject getFieldsAsObjectsMethodSubject =
personRecordClassSubject.uniqueMethodWithOriginalName(GET_FIELDS_AS_OBJECTS_METHOD_NAME);
assertThat(getFieldsAsObjectsMethodSubject, isAbsentIf(recordDesugaringIsOff));
MethodSubject equalsHelperMethodSubject =
personRecordClassSubject.uniqueMethodWithOriginalName(EQUALS_RECORD_METHOD_NAME);
assertThat(equalsHelperMethodSubject, isAbsentIf(recordDesugaringIsOff));
MethodSubject equalsMethodSubject =
personRecordClassSubject.uniqueMethodWithOriginalName("equals");
assertThat(equalsMethodSubject, isPresent());
assertThat(
equalsMethodSubject,
ifThen(!recordDesugaringIsOff, invokesMethod(equalsHelperMethodSubject)));
// Objects.hashCode(Object) (used by helper generated for record hashCode).
ClassSubject objectsHashCodeClassSubject =
inspector.clazz(SyntheticItemsTestUtils.syntheticBackportClass(PERSON_REFERENCE, 0));
assertThat(
objectsHashCodeClassSubject,
isAbsentIf(recordDesugaringIsOff || canUseJavaUtilObjects(parameters)));
MethodSubject objectsHashCodeMethodSubject = objectsHashCodeClassSubject.uniqueMethod();
assertThat(objectsHashCodeMethodSubject, isAbsentIf(objectsHashCodeClassSubject.isAbsent()));
// Objects.equals(Object, Object) (used by helper generated for record compare).
ClassSubject objectsEqualsClassSubject =
inspector.clazz(SyntheticItemsTestUtils.syntheticBackportClass(PERSON_REFERENCE, 1));
assertThat(
objectsEqualsClassSubject,
isAbsentIf(recordDesugaringIsOff || canUseJavaUtilObjects(parameters)));
MethodSubject objectsEqualsMethodSubject = objectsEqualsClassSubject.uniqueMethod();
assertThat(objectsEqualsMethodSubject, isAbsentIf(objectsEqualsClassSubject.isAbsent()));
assertEquals(objectsHashCodeClassSubject.isPresent(), objectsEqualsClassSubject.isPresent());
// int hashCode()
ClassSubject hashCodeHelperClassSubject =
inspector.clazz(
SyntheticItemsTestUtils.syntheticRecordHelperClass(
PERSON_REFERENCE, objectsHashCodeClassSubject.isAbsent() ? 0 : 2));
assertThat(hashCodeHelperClassSubject, isAbsentIf(recordDesugaringIsOff));
MethodSubject hashCodeHelperMethodSubject = hashCodeHelperClassSubject.uniqueMethod();
assertThat(hashCodeHelperMethodSubject, isAbsentIf(recordDesugaringIsOff));
MethodSubject hashCodeMethodSubject =
personRecordClassSubject.uniqueMethodWithOriginalName("hashCode");
assertThat(hashCodeMethodSubject, isPresent());
assertThat(
hashCodeMethodSubject,
ifThen(!recordDesugaringIsOff, invokesMethod(hashCodeHelperMethodSubject)));
// String toString()
ClassSubject toStringHelperClassSubject =
inspector.clazz(
SyntheticItemsTestUtils.syntheticRecordHelperClass(
PERSON_REFERENCE, objectsHashCodeClassSubject.isAbsent() ? 1 : 3));
assertThat(toStringHelperClassSubject, isAbsentIf(recordDesugaringIsOff));
MethodSubject toStringHelperMethodSubject = toStringHelperClassSubject.uniqueMethod();
assertThat(toStringHelperMethodSubject, isAbsentIf(recordDesugaringIsOff));
MethodSubject toStringMethodSubject =
personRecordClassSubject.uniqueMethodWithOriginalName("toString");
assertThat(toStringMethodSubject, isPresent());
assertThat(
toStringMethodSubject,
ifThen(!recordDesugaringIsOff, invokesMethod(getFieldsAsObjectsMethodSubject)));
assertThat(
toStringMethodSubject,
ifThen(!recordDesugaringIsOff, invokesMethod(toStringHelperMethodSubject)));
profileInspector
.assertContainsClassRules(mainClassSubject, personRecordClassSubject)
.assertContainsMethodRules(
mainMethodSubject,
personInstanceInitializerSubject,
nameMethodSubject,
ageMethodSubject,
equalsMethodSubject,
hashCodeMethodSubject,
toStringMethodSubject)
.applyIf(
!recordDesugaringIsOff && !canUseJavaUtilObjects(parameters),
i ->
i.assertContainsClassRules(objectsHashCodeClassSubject, objectsEqualsClassSubject)
.assertContainsMethodRules(
objectsHashCodeMethodSubject, objectsEqualsMethodSubject))
.applyIf(
canMergeRecordTag && !partialDesugaring,
i -> i.assertContainsMethodRule(personDefaultInstanceInitializerSubject))
.applyIf(
!canMergeRecordTag && !partialDesugaring,
j ->
j.assertContainsClassRules(recordTagClassSubject)
.assertContainsMethodRule(recordTagInstanceInitializerSubject))
.applyIf(
!recordDesugaringIsOff,
i ->
i.assertContainsClassRules(hashCodeHelperClassSubject, toStringHelperClassSubject)
.assertContainsMethodRules(
equalsHelperMethodSubject,
getFieldsAsObjectsMethodSubject,
hashCodeHelperMethodSubject,
toStringHelperMethodSubject))
.assertContainsNoOtherRules();
}
record Person(String name, int age) {}
public class SimpleRecord {
public static void main(String[] args) {
Person janeDoe = new Person("Jane Doe", 42);
System.out.println(janeDoe.name());
System.out.println(janeDoe.age());
// Test equals with self.
System.out.println(janeDoe.equals(janeDoe));
// Test equals with structurally equals Person.
Person otherJaneDoe = new Person("Jane Doe", 42);
System.out.println(janeDoe.equals(otherJaneDoe));
System.out.println(otherJaneDoe.equals(janeDoe));
// Test equals with not-structually equals Person.
Person johnDoe = new Person("John Doe", 42);
System.out.println(janeDoe.equals(johnDoe));
System.out.println(johnDoe.equals(janeDoe));
// Test equals with Object and null.
System.out.println(janeDoe.equals(new Object()));
System.out.println(janeDoe.equals(null));
}
}
}