Support unused records
Bug: 199360923
Change-Id: Ic43632d34cd955ca428a6dd15f7e893e662cd7fa
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index 4050961..08e18ca4 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -331,7 +331,6 @@
// as there was a dex resource.
private boolean hasReadProgramResourceFromCf = false;
private boolean hasReadProgramResourceFromDex = false;
- private boolean hasReadProgramRecord = false;
ClassReader(ExecutorService executorService, List<Future<?>> futures) {
this.executorService = executorService;
@@ -340,7 +339,9 @@
public DexApplicationReadFlags getDexApplicationReadFlags() {
return new DexApplicationReadFlags(
- hasReadProgramResourceFromDex, hasReadProgramResourceFromCf, hasReadProgramRecord);
+ hasReadProgramResourceFromDex,
+ hasReadProgramResourceFromCf,
+ application.hasReadRecordReferenceFromProgramClass());
}
private void readDexSources(List<ProgramResource> dexSources, Queue<DexProgramClass> classes)
@@ -382,15 +383,7 @@
}
hasReadProgramResourceFromCf = true;
JarClassFileReader<DexProgramClass> reader =
- new JarClassFileReader<>(
- application,
- clazz -> {
- classes.add(clazz);
- if (clazz.isRecord()) {
- hasReadProgramRecord = true;
- }
- },
- PROGRAM);
+ new JarClassFileReader<>(application, classes::add, PROGRAM);
// Read classes in parallel.
for (ProgramResource input : classSources) {
futures.add(
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java b/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java
index d5089a7..bb3bcf8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplicationReadFlags.java
@@ -9,15 +9,15 @@
private final boolean hasReadProgramClassFromDex;
private final boolean hasReadProgramClassFromCf;
- private final boolean hasReadProgramRecord;
+ private final boolean hasReadRecordReferenceFromProgramClass;
public DexApplicationReadFlags(
boolean hasReadProgramClassFromDex,
boolean hasReadProgramClassFromCf,
- boolean hasReadProgramRecord) {
+ boolean hasReadRecordReferenceFromProgramClass) {
this.hasReadProgramClassFromDex = hasReadProgramClassFromDex;
this.hasReadProgramClassFromCf = hasReadProgramClassFromCf;
- this.hasReadProgramRecord = hasReadProgramRecord;
+ this.hasReadRecordReferenceFromProgramClass = hasReadRecordReferenceFromProgramClass;
}
public boolean hasReadProgramClassFromCf() {
@@ -28,7 +28,7 @@
return hasReadProgramClassFromDex;
}
- public boolean hasReadProgramRecord() {
- return hasReadProgramRecord;
+ public boolean hasReadRecordReferenceFromProgramClass() {
+ return hasReadRecordReferenceFromProgramClass;
}
}
diff --git a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
index 67e7b5b..047be58 100644
--- a/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarApplicationReader.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.ir.desugar.records.RecordRewriter;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
import java.util.List;
@@ -26,6 +27,8 @@
private final ConcurrentHashMap<String, DexString> stringCache = new ConcurrentHashMap<>();
private final Map<String, String> typeDescriptorMap;
+ private boolean hasReadRecordReferenceFromProgramClass = false;
+
public JarApplicationReader(InternalOptions options) {
this.options = options;
typeDescriptorMap = ApplicationReaderMap.getDescriptorMap(options);
@@ -149,4 +152,24 @@
public Type getReturnType(final String methodDescriptor) {
return getAsmType(DescriptorUtils.getReturnTypeDescriptor(methodDescriptor));
}
+
+ public void setHasReadRecordReferenceFromProgramClass() {
+ hasReadRecordReferenceFromProgramClass = true;
+ }
+
+ public boolean hasReadRecordReferenceFromProgramClass() {
+ return hasReadRecordReferenceFromProgramClass;
+ }
+
+ public void checkFieldForRecord(DexField dexField) {
+ if (options.shouldDesugarRecords() && RecordRewriter.refersToRecord(dexField, getFactory())) {
+ setHasReadRecordReferenceFromProgramClass();
+ }
+ }
+
+ public void checkMethodForRecord(DexMethod dexMethod) {
+ if (options.shouldDesugarRecords() && RecordRewriter.refersToRecord(dexMethod, getFactory())) {
+ setHasReadRecordReferenceFromProgramClass();
+ }
+ }
}
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 4c6c165..296f792 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -512,9 +512,13 @@
}
private void checkRecord() {
+ if (!application.options.shouldDesugarRecords()) {
+ return;
+ }
if (!accessFlags.isRecord()) {
return;
}
+ application.setHasReadRecordReferenceFromProgramClass();
// TODO(b/169645628): 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.";
@@ -661,6 +665,7 @@
public void visitEnd() {
FieldAccessFlags flags = createFieldAccessFlags(access);
DexField dexField = parent.application.getField(parent.type, name, desc);
+ parent.application.checkFieldForRecord(dexField);
Wrapper<DexField> signature = FieldSignatureEquivalence.get().wrap(dexField);
if (parent.fieldSignatures.add(signature)) {
DexAnnotationSet annotationSet =
@@ -878,6 +883,7 @@
@Override
public void visitEnd() {
InternalOptions options = parent.application.options;
+ parent.application.checkMethodForRecord(method);
if (!flags.isAbstract() && !flags.isNative() && classRequiresCode()) {
code = new LazyCfCode(method, parent.origin, parent.context, parent.application);
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
index 7fa0410..1f16c1b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordRewriter.java
@@ -137,21 +137,21 @@
assert !instruction.isInitClass();
if (instruction.isInvoke()) {
CfInvoke cfInvoke = instruction.asInvoke();
- if (refersToRecord(cfInvoke.getMethod())) {
+ if (refersToRecord(cfInvoke.getMethod(), factory)) {
ensureRecordClass(eventConsumer);
}
return;
}
if (instruction.isFieldInstruction()) {
CfFieldInstruction fieldInstruction = instruction.asFieldInstruction();
- if (refersToRecord(fieldInstruction.getField())) {
+ if (refersToRecord(fieldInstruction.getField(), factory)) {
ensureRecordClass(eventConsumer);
}
return;
}
if (instruction.isTypeInstruction()) {
CfTypeInstruction typeInstruction = instruction.asTypeInstruction();
- if (refersToRecord(typeInstruction.getType())) {
+ if (refersToRecord(typeInstruction.getType(), factory)) {
ensureRecordClass(eventConsumer);
}
return;
@@ -452,35 +452,35 @@
}
}
- private boolean refersToRecord(DexField field) {
- assert !refersToRecord(field.holder) : "The java.lang.Record class has no fields.";
- return refersToRecord(field.type);
+ public static boolean refersToRecord(DexField field, DexItemFactory factory) {
+ assert !refersToRecord(field.holder, factory) : "The java.lang.Record class has no fields.";
+ return refersToRecord(field.type, factory);
}
- private boolean refersToRecord(DexMethod method) {
- if (refersToRecord(method.holder)) {
+ public static boolean refersToRecord(DexMethod method, DexItemFactory factory) {
+ if (refersToRecord(method.holder, factory)) {
return true;
}
- return refersToRecord(method.proto);
+ return refersToRecord(method.proto, factory);
}
- private boolean refersToRecord(DexProto proto) {
- if (refersToRecord(proto.returnType)) {
+ private static boolean refersToRecord(DexProto proto, DexItemFactory factory) {
+ if (refersToRecord(proto.returnType, factory)) {
return true;
}
- return refersToRecord(proto.parameters.values);
+ return refersToRecord(proto.parameters.values, factory);
}
- private boolean refersToRecord(DexType[] types) {
+ private static boolean refersToRecord(DexType[] types, DexItemFactory factory) {
for (DexType type : types) {
- if (refersToRecord(type)) {
+ if (refersToRecord(type, factory)) {
return true;
}
}
return false;
}
- private boolean refersToRecord(DexType type) {
+ private static boolean refersToRecord(DexType type, DexItemFactory factory) {
return type == factory.recordType;
}
@@ -600,7 +600,7 @@
@Override
public void synthesizeClasses(CfClassSynthesizerDesugaringEventConsumer eventConsumer) {
- if (appView.appInfo().app().getFlags().hasReadProgramRecord()) {
+ if (appView.appInfo().app().getFlags().hasReadRecordReferenceFromProgramClass()) {
ensureRecordClass(eventConsumer);
}
}
diff --git a/src/test/examplesJava16/records/UnusedRecordField.java b/src/test/examplesJava16/records/UnusedRecordField.java
new file mode 100644
index 0000000..7a21412
--- /dev/null
+++ b/src/test/examplesJava16/records/UnusedRecordField.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2020, 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;
+
+public class UnusedRecordField {
+
+ Record unusedInstanceField;
+
+ void printHello() {
+ System.out.println("Hello!");
+ }
+
+ public static void main(String[] args) {
+ new UnusedRecordField().printHello();
+ }
+}
diff --git a/src/test/examplesJava16/records/UnusedRecordMethod.java b/src/test/examplesJava16/records/UnusedRecordMethod.java
new file mode 100644
index 0000000..31a044f
--- /dev/null
+++ b/src/test/examplesJava16/records/UnusedRecordMethod.java
@@ -0,0 +1,20 @@
+// Copyright (c) 2020, 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;
+
+public class UnusedRecordMethod {
+
+ Record unusedInstanceMethod(Record unused) {
+ return null;
+ }
+
+ void printHello() {
+ System.out.println("Hello!");
+ }
+
+ public static void main(String[] args) {
+ new UnusedRecordMethod().printHello();
+ }
+}
diff --git a/src/test/examplesJava16/records/UnusedRecordReflection.java b/src/test/examplesJava16/records/UnusedRecordReflection.java
new file mode 100644
index 0000000..f99cc52
--- /dev/null
+++ b/src/test/examplesJava16/records/UnusedRecordReflection.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2020, 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.Method;
+
+public class UnusedRecordReflection {
+
+ Record instanceField;
+
+ Record method(int i, Record unused, int j) {
+ return null;
+ }
+
+ Object reflectiveGetField() {
+ try {
+ return this.getClass().getDeclaredField("instanceField").get(this);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ Object reflectiveCallMethod() {
+ try {
+ for (Method declaredMethod : this.getClass().getDeclaredMethods()) {
+ if (declaredMethod.getName().equals("method")) {
+ return declaredMethod.invoke(this, 0, null, 1);
+ }
+ }
+ throw new RuntimeException("Unreachable");
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(new UnusedRecordReflection().reflectiveGetField());
+ System.out.println(new UnusedRecordReflection().reflectiveCallMethod());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java
new file mode 100644
index 0000000..bb1fa84
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordFieldTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2021, 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.InternalOptions.TestingOptions;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnusedRecordFieldTest extends TestBase {
+
+ private static final String RECORD_NAME = "UnusedRecordField";
+ 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("Hello!");
+
+ private final TestParameters parameters;
+
+ public UnusedRecordFieldTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ return buildParameters(
+ getTestParameters()
+ .withCustomRuntime(CfRuntime.getCheckedInJdk16())
+ .withDexRuntimes()
+ .withAllApiLevelsAlsoForCf()
+ .build());
+ }
+
+ @Test
+ public void testD8AndJvm() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .enablePreview()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules("-keep class records.UnusedRecordField { *; }")
+ .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .inspect(RecordTestUtils::assertRecordsAreRecords)
+ .enableJVMPreview()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ return;
+ }
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules("-keep class records.UnusedRecordField { *; }")
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java
new file mode 100644
index 0000000..9edf909
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordMethodTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2021, 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.InternalOptions.TestingOptions;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnusedRecordMethodTest extends TestBase {
+
+ private static final String RECORD_NAME = "UnusedRecordMethod";
+ 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("Hello!");
+
+ private final TestParameters parameters;
+
+ public UnusedRecordMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ return buildParameters(
+ getTestParameters()
+ .withCustomRuntime(CfRuntime.getCheckedInJdk16())
+ .withDexRuntimes()
+ .withAllApiLevelsAlsoForCf()
+ .build());
+ }
+
+ @Test
+ public void testD8AndJvm() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .enablePreview()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules("-keep class records.UnusedRecordMethod { *; }")
+ .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .inspect(RecordTestUtils::assertRecordsAreRecords)
+ .enableJVMPreview()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ return;
+ }
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules("-keep class records.UnusedRecordMethod { *; }")
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java
new file mode 100644
index 0000000..a3dc24e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/records/UnusedRecordReflectionTest.java
@@ -0,0 +1,87 @@
+// Copyright (c) 2021, 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.InternalOptions.TestingOptions;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class UnusedRecordReflectionTest extends TestBase {
+
+ private static final String RECORD_NAME = "UnusedRecordReflection";
+ 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("null", "null");
+
+ private final TestParameters parameters;
+
+ public UnusedRecordReflectionTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ // TODO(b/174431251): This should be replaced with .withCfRuntimes(start = jdk16).
+ return buildParameters(
+ getTestParameters()
+ .withCustomRuntime(CfRuntime.getCheckedInJdk16())
+ .withDexRuntimes()
+ .withAllApiLevelsAlsoForCf()
+ .build());
+ }
+
+ @Test
+ public void testD8AndJvm() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForJvm()
+ .addProgramClassFileData(PROGRAM_DATA)
+ .enablePreview()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ if (parameters.isCfRuntime()) {
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules("-keep class records.UnusedRecordReflection { *; }")
+ .addLibraryFiles(RecordTestUtils.getJdk15LibraryFiles(temp))
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .inspect(RecordTestUtils::assertRecordsAreRecords)
+ .enableJVMPreview()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ return;
+ }
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(PROGRAM_DATA)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepRules("-keep class records.UnusedRecordReflection { *; }")
+ .addKeepMainRule(MAIN_TYPE)
+ .addOptionsModification(TestingOptions::allowExperimentClassFileVersion)
+ .compile()
+ .run(parameters.getRuntime(), MAIN_TYPE)
+ .assertSuccessWithOutput(EXPECTED_RESULT);
+ }
+}