blob: 6d037042bc571cd0e999221d73d675fcecfec0c4 [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 com.android.tools.r8.profile.art.completeness;
import static com.android.tools.r8.ir.desugar.records.RecordDesugaring.EQUALS_RECORD_METHOD_NAME;
import static com.android.tools.r8.ir.desugar.records.RecordDesugaring.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.isPresent;
import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
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.R8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.desugar.records.RecordTestUtils;
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 RECORD_NAME = "SimpleRecord";
private static final byte[][] PROGRAM_DATA = RecordTestUtils.getProgramData(RECORD_NAME);
private static final String EXPECTED_RESULT =
StringUtils.lines("Jane Doe", "42", "Jane Doe", "42");
private static final ClassReference MAIN_REFERENCE =
Reference.classFromTypeName(RecordTestUtils.getMainType(RECORD_NAME));
private static final ClassReference PERSON_REFERENCE =
Reference.classFromTypeName(MAIN_REFERENCE.getTypeName() + "$Person");
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(parameters.canUseRecords());
testForJvm(parameters)
.addProgramClassFileData(PROGRAM_DATA)
.run(parameters.getRuntime(), MAIN_REFERENCE.getTypeName())
.assertSuccessWithOutput(EXPECTED_RESULT);
}
@Test
public void testD8() throws Exception {
D8TestCompileResult compileResult =
testForD8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA)
.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())
.assertSuccessWithOutput(EXPECTED_RESULT);
}
@Test
public void testR8() throws Exception {
parameters.assumeR8TestParameters();
assumeTrue(parameters.canUseRecords() || parameters.isDexRuntime());
R8TestCompileResult compileResult =
testForR8(parameters.getBackend())
.addProgramClassFileData(PROGRAM_DATA)
.addKeepMainRule(MAIN_REFERENCE.getTypeName())
.addKeepRules(
"-neverpropagatevalue class " + PERSON_REFERENCE.getTypeName() + " { <fields>; }")
.addArtProfileForRewriting(getArtProfile())
.addOptionsModification(InlinerOptions::disableInlining)
.applyIf(
parameters.isCfRuntime(),
testBuilder ->
testBuilder.addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp)))
.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,
parameters.canUseNestBasedAccessesWhenDesugaring(),
parameters.canUseRecordsWhenDesugaring());
}
private void inspectR8(ArtProfileInspector profileInspector, CodeInspector inspector) {
inspect(
profileInspector,
inspector,
RECORD_REFERENCE,
parameters.canHaveNonReboundConstructorInvoke(),
parameters.canUseNestBasedAccesses(),
parameters.canUseRecords());
}
private void inspect(
ArtProfileInspector profileInspector,
CodeInspector inspector,
ClassReference recordClassReference,
boolean canHaveNonReboundConstructorInvoke,
boolean canUseNestBasedAccesses,
boolean canUseRecords) {
ClassSubject mainClassSubject = inspector.clazz(MAIN_REFERENCE);
assertThat(mainClassSubject, isPresent());
MethodSubject mainMethodSubject = mainClassSubject.mainMethod();
assertThat(mainMethodSubject, isPresent());
ClassSubject recordTagClassSubject = inspector.clazz(recordClassReference);
assertThat(recordTagClassSubject, notIf(isPresent(), canUseRecords));
if (!canUseRecords) {
assertEquals(
canHaveNonReboundConstructorInvoke ? 0 : 1, recordTagClassSubject.allMethods().size());
}
MethodSubject recordTagInstanceInitializerSubject = recordTagClassSubject.init();
assertThat(
recordTagInstanceInitializerSubject,
notIf(isPresent(), canHaveNonReboundConstructorInvoke || canUseRecords));
ClassSubject personRecordClassSubject = inspector.clazz(PERSON_REFERENCE);
assertThat(personRecordClassSubject, isPresent());
assertEquals(
canUseRecords
? inspector.getTypeSubject(RECORD_REFERENCE.getTypeName())
: recordTagClassSubject.asTypeSubject(),
personRecordClassSubject.getSuperType());
assertEquals(canUseRecords ? 6 : 10, personRecordClassSubject.allMethods().size());
MethodSubject personInstanceInitializerSubject =
personRecordClassSubject.uniqueInstanceInitializer();
assertThat(personInstanceInitializerSubject, isPresent());
// Name getters.
MethodSubject nameMethodSubject = personRecordClassSubject.uniqueMethodWithOriginalName("name");
assertThat(nameMethodSubject, isPresent());
MethodSubject nameNestAccessorMethodSubject =
personRecordClassSubject.uniqueMethodWithOriginalName(
SyntheticItemsTestUtils.syntheticNestInstanceFieldGetter(
Reference.field(PERSON_REFERENCE, "name", STRING_REFERENCE))
.getMethodName());
assertThat(nameNestAccessorMethodSubject, notIf(isPresent(), canUseNestBasedAccesses));
// Age getters.
MethodSubject ageMethodSubject = personRecordClassSubject.uniqueMethodWithOriginalName("age");
assertThat(ageMethodSubject, isPresent());
MethodSubject ageNestAccessorMethodSubject =
personRecordClassSubject.uniqueMethodWithOriginalName(
SyntheticItemsTestUtils.syntheticNestInstanceFieldGetter(
Reference.field(PERSON_REFERENCE, "age", Reference.INT))
.getMethodName());
assertThat(ageNestAccessorMethodSubject, notIf(isPresent(), canUseNestBasedAccesses));
// boolean equals(Object)
MethodSubject getFieldsAsObjectsMethodSubject =
personRecordClassSubject.uniqueMethodWithOriginalName(GET_FIELDS_AS_OBJECTS_METHOD_NAME);
assertThat(getFieldsAsObjectsMethodSubject, notIf(isPresent(), canUseRecords));
MethodSubject equalsHelperMethodSubject =
personRecordClassSubject.uniqueMethodWithOriginalName(EQUALS_RECORD_METHOD_NAME);
assertThat(equalsHelperMethodSubject, notIf(isPresent(), canUseRecords));
MethodSubject equalsMethodSubject =
personRecordClassSubject.uniqueMethodWithOriginalName("equals");
assertThat(equalsMethodSubject, isPresent());
assertThat(
equalsMethodSubject, ifThen(!canUseRecords, invokesMethod(equalsHelperMethodSubject)));
// int hashCode()
ClassSubject hashCodeHelperClassSubject =
inspector.clazz(SyntheticItemsTestUtils.syntheticRecordHelperClass(PERSON_REFERENCE, 0));
assertThat(hashCodeHelperClassSubject, notIf(isPresent(), canUseRecords));
MethodSubject hashCodeHelperMethodSubject = hashCodeHelperClassSubject.uniqueMethod();
assertThat(hashCodeHelperMethodSubject, notIf(isPresent(), canUseRecords));
MethodSubject hashCodeMethodSubject =
personRecordClassSubject.uniqueMethodWithOriginalName("hashCode");
assertThat(hashCodeMethodSubject, isPresent());
assertThat(
hashCodeMethodSubject,
ifThen(!canUseRecords, invokesMethod(getFieldsAsObjectsMethodSubject)));
assertThat(
hashCodeMethodSubject, ifThen(!canUseRecords, invokesMethod(hashCodeHelperMethodSubject)));
// String toString()
ClassSubject toStringHelperClassSubject =
inspector.clazz(SyntheticItemsTestUtils.syntheticRecordHelperClass(PERSON_REFERENCE, 1));
assertThat(toStringHelperClassSubject, notIf(isPresent(), canUseRecords));
MethodSubject toStringHelperMethodSubject = toStringHelperClassSubject.uniqueMethod();
assertThat(toStringHelperMethodSubject, notIf(isPresent(), canUseRecords));
MethodSubject toStringMethodSubject =
personRecordClassSubject.uniqueMethodWithOriginalName("toString");
assertThat(toStringMethodSubject, isPresent());
assertThat(
toStringMethodSubject,
ifThen(!canUseRecords, invokesMethod(getFieldsAsObjectsMethodSubject)));
assertThat(
toStringMethodSubject, ifThen(!canUseRecords, invokesMethod(toStringHelperMethodSubject)));
profileInspector
.assertContainsClassRule(personRecordClassSubject)
.assertContainsMethodRules(
mainMethodSubject,
personInstanceInitializerSubject,
nameMethodSubject,
ageMethodSubject,
equalsMethodSubject,
hashCodeMethodSubject,
toStringMethodSubject)
.applyIf(
!canUseNestBasedAccesses,
i ->
i.assertContainsMethodRules(
nameNestAccessorMethodSubject, ageNestAccessorMethodSubject))
.applyIf(
!canUseRecords,
i ->
i.assertContainsClassRules(
recordTagClassSubject,
hashCodeHelperClassSubject,
toStringHelperClassSubject)
.assertContainsMethodRules(
equalsHelperMethodSubject,
getFieldsAsObjectsMethodSubject,
hashCodeHelperMethodSubject,
toStringHelperMethodSubject)
.applyIf(
!canHaveNonReboundConstructorInvoke,
j -> j.assertContainsMethodRule(recordTagInstanceInitializerSubject)))
.assertContainsNoOtherRules();
}
}