Test for adding record synthetics to profiles
Bug: b/265729283
Change-Id: Ie925ce80e6af98cdcac9774d9354a1da21268f0a
diff --git a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
index 251edcc..558ea43 100644
--- a/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MethodReferenceUtils.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.references.TypeReference;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
@@ -74,6 +75,11 @@
return Reference.method(type, "<init>", Collections.emptyList(), null);
}
+ public static MethodReference instanceConstructor(
+ ClassReference type, TypeReference... formalTypes) {
+ return Reference.method(type, "<init>", Arrays.asList(formalTypes), null);
+ }
+
public static int compare(MethodReference methodReference, ClassReference other) {
return ClassReferenceUtils.compare(other, methodReference) * -1;
}
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
index 679584b..1ca26f0 100644
--- a/src/test/java/com/android/tools/r8/R8TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.shaking.ProguardConfigurationRule;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThrowingBiConsumer;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -98,7 +99,12 @@
@Override
public CodeInspector inspector() throws IOException {
- return new CodeInspector(app, proguardMap);
+ return inspector(null);
+ }
+
+ public CodeInspector inspector(Consumer<InternalOptions> debugOptionsConsumer)
+ throws IOException {
+ return new CodeInspector(app, proguardMap, debugOptionsConsumer);
}
private CodeInspector featureInspector(Path feature) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index 1074507..aabdd99 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -94,6 +94,17 @@
return false;
}
+ public boolean canUseRecords() {
+ assert isCfRuntime() || isDexRuntime();
+ return isCfRuntime() && asCfRuntime().isNewerThanOrEqual(CfVm.JDK14);
+ }
+
+ public boolean canUseRecordsWhenDesugaring() {
+ assert isCfRuntime() || isDexRuntime();
+ assert apiLevel != null;
+ return false;
+ }
+
// Convenience predicates.
public boolean isDexRuntime() {
return runtime.isDex();
@@ -182,6 +193,15 @@
return this;
}
+ public TestParameters assumeJvmTestParameters() {
+ assertFalse(
+ "No need to use assumeR8TestParameters() when not using api levels for CF",
+ apiLevel == null);
+ assumeCfRuntime();
+ assumeTrue(representativeApiLevelForRuntime);
+ return this;
+ }
+
public TestParameters assumeR8TestParameters() {
assertFalse(
"No need to use assumeR8TestParameters() when not using api levels for CF",
diff --git a/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
new file mode 100644
index 0000000..dcf365b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/profile/art/completeness/RecordProfileRewritingTest.java
@@ -0,0 +1,289 @@
+// 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()
+ .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.getApiLevel())
+ .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.getApiLevel())
+ .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(),
+ parameters.canUseNestBasedAccessesWhenDesugaring(),
+ parameters.canUseRecordsWhenDesugaring());
+ }
+
+ private void inspectR8(ArtProfileInspector profileInspector, CodeInspector inspector) {
+ inspect(
+ profileInspector,
+ inspector,
+ RECORD_REFERENCE,
+ parameters.canUseNestBasedAccesses(),
+ parameters.canUseRecords());
+ }
+
+ private void inspect(
+ ArtProfileInspector profileInspector,
+ CodeInspector inspector,
+ ClassReference recordClassReference,
+ 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(1, recordTagClassSubject.allMethods().size());
+ }
+
+ MethodSubject recordTagInstanceInitializerSubject = recordTagClassSubject.init();
+ assertThat(recordTagInstanceInitializerSubject, notIf(isPresent(), 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)));
+
+ // TODO(b/265729283): Should include all the synthetics from above when there is no native
+ // support.
+ profileInspector
+ .assertContainsClassRule(personRecordClassSubject)
+ .assertContainsMethodRules(
+ mainMethodSubject,
+ personInstanceInitializerSubject,
+ nameMethodSubject,
+ ageMethodSubject,
+ equalsMethodSubject,
+ hashCodeMethodSubject,
+ toStringMethodSubject)
+ .applyIf(
+ !canUseNestBasedAccesses,
+ i ->
+ i.assertContainsMethodRules(
+ nameNestAccessorMethodSubject, ageNestAccessorMethodSubject))
+ .assertContainsNoOtherRules();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index d7df00b..478af32 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -13,6 +13,7 @@
import static com.android.tools.r8.synthesis.SyntheticNaming.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
import static org.hamcrest.CoreMatchers.containsString;
+import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.ir.desugar.invokespecial.InvokeSpecialToSelfDesugaring;
import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting;
import com.android.tools.r8.references.ClassReference;
@@ -130,6 +131,14 @@
return syntheticClass(classReference, naming.BACKPORT_WITH_FORWARDING, id);
}
+ public static ClassReference syntheticRecordTagClass() {
+ return Reference.classFromDescriptor(DexItemFactory.recordTagDescriptorString);
+ }
+
+ public static ClassReference syntheticRecordHelperClass(ClassReference reference, int id) {
+ return syntheticClass(reference, naming.RECORD_HELPER, id);
+ }
+
public static ClassReference syntheticTwrCloseResourceClass(Class<?> clazz, int id) {
return syntheticClass(clazz, naming.TWR_CLOSE_RESOURCE, id);
}
@@ -155,10 +164,13 @@
}
public static MethodReference syntheticNestInstanceFieldGetter(Field field) {
- FieldReference fieldReference = Reference.fieldFromField(field);
+ return syntheticNestInstanceFieldGetter(Reference.fieldFromField(field));
+ }
+
+ public static MethodReference syntheticNestInstanceFieldGetter(FieldReference fieldReference) {
return Reference.method(
fieldReference.getHolderClass(),
- NEST_ACCESS_FIELD_GET_NAME_PREFIX + field.getName(),
+ NEST_ACCESS_FIELD_GET_NAME_PREFIX + fieldReference.getFieldName(),
Collections.emptyList(),
fieldReference.getFieldType());
}
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 ba1898d..7596ee6 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
@@ -130,6 +130,11 @@
}
@Override
+ public TypeSubject getSuperType() {
+ throw new Unreachable("Absent class has no super type");
+ }
+
+ @Override
public boolean isInterface() {
throw new Unreachable("Cannot determine if an absent class is an interface");
}
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 58dbf1d..1aebbdd 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
@@ -187,6 +187,8 @@
@Override
public abstract ClassAccessFlags getAccessFlags();
+ public abstract TypeSubject getSuperType();
+
public abstract boolean isInterface();
public abstract boolean isAbstract();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index b973782..81c7400 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.utils.codeinspector;
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
import com.android.tools.r8.DexIndexedConsumer;
import com.android.tools.r8.StringResource;
import com.android.tools.r8.TestDiagnosticMessagesImpl;
@@ -159,10 +161,15 @@
}
public CodeInspector(AndroidApp app, String proguardMapContent) throws IOException {
+ this(app, proguardMapContent, emptyConsumer());
+ }
+
+ public CodeInspector(
+ AndroidApp app, String proguardMapContent, Consumer<InternalOptions> optionsConsumer)
+ throws IOException {
this(
- new ApplicationReader(app, runOptionsConsumer(null), Timing.empty())
- .read(
- StringResource.fromString(proguardMapContent, Origin.unknown())));
+ new ApplicationReader(app, runOptionsConsumer(optionsConsumer), Timing.empty())
+ .read(StringResource.fromString(proguardMapContent, Origin.unknown())));
}
public CodeInspector(DexApplication application) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index 1f9b372..d711ac1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -4,6 +4,9 @@
package com.android.tools.r8.utils.codeinspector;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
@@ -12,6 +15,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
@@ -179,14 +183,21 @@
}
public static Matcher<MethodSubject> invokesMethod(MethodSubject targetSubject) {
- if (!targetSubject.isPresent()) {
- throw new IllegalArgumentException();
- }
- return invokesMethod(targetSubject.getFinalReference());
+ return invokesMethod(
+ () -> {
+ assertThat(targetSubject, isPresent());
+ return targetSubject.getFinalReference();
+ });
}
public static Matcher<MethodSubject> invokesMethod(MethodReference targetReference) {
+ return invokesMethod(() -> targetReference);
+ }
+
+ public static Matcher<MethodSubject> invokesMethod(
+ Supplier<MethodReference> targetReferenceSupplier) {
return new TypeSafeMatcher<MethodSubject>() {
+
@Override
protected boolean matchesSafely(MethodSubject subject) {
if (!subject.isPresent()) {
@@ -195,11 +206,13 @@
if (!subject.getMethod().hasCode()) {
return false;
}
+ MethodReference targetReference = targetReferenceSupplier.get();
return subject.streamInstructions().anyMatch(isInvokeWithTarget(targetReference));
}
@Override
public void describeTo(Description description) {
+ MethodReference targetReference = targetReferenceSupplier.get();
description.appendText(
"invokes method `" + MethodReferenceUtils.toSourceString(targetReference) + "`");
}
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 738507a..fcfe160 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
@@ -388,6 +388,11 @@
}
@Override
+ public TypeSubject getSuperType() {
+ return new TypeSubject(codeInspector, dexClass.getSuperType());
+ }
+
+ @Override
public String getOriginalName() {
if (getNaming() != null) {
return getNaming().originalName;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index 4a3d5f9..82eaa9d 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.utils.codeinspector;
+import static org.hamcrest.CoreMatchers.anything;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -846,6 +847,14 @@
}
}
+ @SuppressWarnings("unchecked")
+ public static <T> Matcher<T> ifThen(boolean condition, Matcher<T> matcher) {
+ if (condition) {
+ return matcher;
+ }
+ return (Matcher<T>) anything();
+ }
+
public static <T> Matcher<T> onlyIf(boolean condition, Matcher<T> matcher) {
return notIf(matcher, !condition);
}