blob: 25cc41b4f7b1fd436c33974b24d9fb31e139dddd [file]
// Copyright (c) 2026, 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.assistant;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import android.view.MockLibrary;
import com.android.tools.r8.GlobalSyntheticsGeneratorCommand;
import com.android.tools.r8.GlobalSyntheticsTestingConsumer;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.CodeMatchers;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class D8AssistantInstrumentationTest extends TestBase {
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withDexRuntimes().withAllApiLevels().build();
}
public D8AssistantInstrumentationTest(TestParameters parameters) {
this.parameters = parameters;
}
@Test
public void test() throws Exception {
Path jsonLog = temp.newFile().toPath();
GlobalSyntheticsTestingConsumer globalsConsumer = new GlobalSyntheticsTestingConsumer();
GlobalSyntheticsGeneratorCommand command =
GlobalSyntheticsGeneratorCommand.builder()
.addLibraryFiles(ToolHelper.getMostRecentAndroidJar())
.setMinApiLevel(parameters.getApiLevel().getLevel())
.setGlobalSyntheticsConsumer(globalsConsumer)
.build();
runGlobalSyntheticsGenerator(
command, options -> options.getAssistantOptions().enableAssistantInstrumentation = true);
testForD8(parameters.getBackend())
.addProgramClasses(TestClass.class, MockLibrary.class)
.apply(
b ->
b.getBuilder().addGlobalSyntheticsResourceProviders(globalsConsumer.getProviders()))
.setMinApi(parameters)
.addOptionsModification(
o -> {
o.getAssistantOptions().enableAssistantInstrumentation = true;
o.testing.globalSyntheticCreatedCallback = null;
})
.compile()
.inspect(this::inspect)
.addVmArguments("-Dcom.android.tools.r8.reflectiveJsonLogger=" + jsonLog)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutputThatMatches(containsString("Hello, world!"));
String logContent = new String(Files.readAllBytes(jsonLog), StandardCharsets.UTF_8);
assertThat(logContent, containsString("CLASS_FOR_NAME"));
assertThat(
logContent,
containsString("com.android.tools.r8.assistant.D8AssistantInstrumentationTest$TestClass"));
assertThat(logContent, containsString("CLASS_GET_NAME"));
assertThat(logContent, containsString("NAME"));
assertThat(logContent, containsString("CLASS_GET_DECLARED_METHOD"));
assertThat(logContent, containsString("CLASS_GET_FIELDS"));
assertThat(logContent, containsString("CLASS_GET_CONSTRUCTORS"));
assertThat(logContent, containsString("CLASS_GET_SUPERCLASS"));
assertThat(logContent, containsString("CLASS_GET_PACKAGE"));
assertThat(logContent, containsString("CLASS_FLAG"));
assertThat(logContent, containsString("CLASS_GET_COMPONENT_TYPE"));
// Verify reflection ON android.* class is IGNORED.
assertThat(logContent, not(containsString("android.view.MockLibrary")));
// Verify reflection FROM android.* class is IGNORED.
assertThat(logContent, not(containsString("android.view.MockLibrary.doReflection")));
// Verify java.* target is IGNORED.
assertThat(logContent, not(containsString("java.lang.Object")));
}
private void inspect(CodeInspector inspector) {
// Check that assistant runtime classes are present.
assertThat(
inspector.clazz("com.android.tools.r8.assistant.runtime.ReflectiveOracle"), isPresent());
assertThat(
inspector.clazz("com.android.tools.r8.assistant.runtime.ReflectiveOracle$Stack"),
isPresent());
assertThat(
inspector
.clazz("com.android.tools.r8.assistant.runtime.ReflectiveOracle$Stack")
.method("com.android.tools.r8.assistant.runtime.ReflectiveOracle$Stack", "createStack"),
isPresent());
assertThat(
inspector.clazz("com.android.tools.r8.assistant.runtime.ReflectiveOperationJsonLogger"),
isPresent());
assertThat(
inspector.clazz(
"com.android.tools.r8.assistant.runtime.ReflectiveOperationReceiver$ClassFlag"),
isPresent());
assertThat(
inspector.clazz(
"com.android.tools.r8.assistant.runtime.ReflectiveOperationReceiver$NameLookupType"),
isPresent());
// Check that TestClass is instrumented.
// ReflectiveOracle.onClassForNameDefault(String) should be called.
assertThat(
inspector.clazz(TestClass.class).mainMethod(),
CodeMatchers.invokesMethodWithName("onClassForNameDefault"));
}
static class TestClass {
public static void main(String[] args) throws Exception {
Class<?> clazz =
Class.forName("com.android.tools.r8.assistant.D8AssistantInstrumentationTest$TestClass");
System.out.println("Hello, world!");
clazz.getName();
clazz.getDeclaredMethod("main", String[].class);
clazz.getFields();
clazz.getConstructors();
clazz.getSuperclass();
clazz.getPackage();
clazz.isInterface();
clazz.getComponentType();
// Reflection ON android.* class from app code.
// MockLibrary is in android.test package.
Class.forName("android.view.MockLibrary").getName();
// Reflection FROM android.* class.
MockLibrary.doReflection();
// Reflection ON java.* class.
Object.class.getName();
}
}
}