Retain signature of native methods in excluded classes
Bug: b/415977217
Change-Id: I66d8b401ff4475e67048b28911ce8f99d63e2611
diff --git a/src/main/java/com/android/tools/r8/partial/R8PartialUseCollector.java b/src/main/java/com/android/tools/r8/partial/R8PartialUseCollector.java
index 71ad575..6e30e4ee 100644
--- a/src/main/java/com/android/tools/r8/partial/R8PartialUseCollector.java
+++ b/src/main/java/com/android/tools/r8/partial/R8PartialUseCollector.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
@@ -27,6 +28,7 @@
import com.android.tools.r8.shaking.reflectiveidentification.ReflectiveIdentification;
import com.android.tools.r8.tracereferences.TraceReferencesConsumer;
import com.android.tools.r8.tracereferences.UseCollector;
+import com.android.tools.r8.tracereferences.UseCollectorEventConsumer;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.NopDiagnosticsHandler;
import com.android.tools.r8.utils.timing.Timing;
@@ -55,6 +57,11 @@
appView, new KeepAllReflectiveIdentificationEventConsumer(this));
}
+ @Override
+ protected UseCollectorEventConsumer getEventConsumerForNativeMethod() {
+ return new KeepNativeMethodSignatureEventConsumer();
+ }
+
public static Predicate<DexType> getTargetPredicate(
AppView<? extends AppInfoWithClassHierarchy> appView) {
return type -> appView.definitionFor(type) != null;
@@ -94,36 +101,40 @@
@Override
public void notifyPresentClass(DexClass clazz, DefinitionContext referencedFrom) {
- notifyPresentItem(clazz, referencedFrom);
+ keepAllowObfuscation(clazz, referencedFrom);
}
@Override
public void notifyPresentField(DexClassAndField field, DefinitionContext referencedFrom) {
- notifyPresentItem(field, referencedFrom);
+ keepAllowObfuscation(field, referencedFrom);
}
@Override
public void notifyPresentMethod(DexClassAndMethod method, DefinitionContext referencedFrom) {
- notifyPresentItem(method, referencedFrom);
+ keepAllowObfuscation(method, referencedFrom);
}
@Override
public void notifyPresentMethod(
DexClassAndMethod method, DefinitionContext referencedFrom, DexMethod reference) {
- notifyPresentItem(method, referencedFrom);
+ keepAllowObfuscation(method, referencedFrom);
}
@Override
public void notifyPresentMethodOverride(
DexClassAndMethod method, ProgramMethod override, DefinitionContext referencedFrom) {
- if (seenDisallowObfuscation.add(method.getReference())) {
- keep(method, referencedFrom, false);
+ keepDisallowObfuscation(method, referencedFrom);
+ }
+
+ private void keepAllowObfuscation(Definition definition, DefinitionContext referencedFrom) {
+ if (seenAllowObfuscation.add(definition.getReference())) {
+ keep(definition, referencedFrom, true);
}
}
- private void notifyPresentItem(Definition definition, DefinitionContext referencedFrom) {
- if (seenAllowObfuscation.add(definition.getReference())) {
- keep(definition, referencedFrom, true);
+ private void keepDisallowObfuscation(Definition definition, DefinitionContext referencedFrom) {
+ if (seenDisallowObfuscation.add(definition.getReference())) {
+ keep(definition, referencedFrom, false);
}
}
@@ -137,6 +148,56 @@
reflectiveIdentification.scanInvoke(invokedMethod, method);
}
+ private class KeepNativeMethodSignatureEventConsumer implements UseCollectorEventConsumer {
+
+ @Override
+ public void notifyPresentClass(DexClass clazz, DefinitionContext referencedFrom) {
+ keepDisallowObfuscation(clazz, referencedFrom);
+ }
+
+ @Override
+ public void notifyMissingClass(DexType type, DefinitionContext referencedFrom) {
+ R8PartialUseCollector.this.notifyMissingClass(type, referencedFrom);
+ }
+
+ @Override
+ public void notifyPackageOf(Definition definition) {
+ R8PartialUseCollector.this.notifyPackageOf(definition);
+ }
+
+ @Override
+ public void notifyPresentField(DexClassAndField field, DefinitionContext referencedFrom) {
+ assert false;
+ }
+
+ @Override
+ public void notifyMissingField(DexField field, DefinitionContext referencedFrom) {
+ assert false;
+ }
+
+ @Override
+ public void notifyPresentMethod(DexClassAndMethod method, DefinitionContext referencedFrom) {
+ assert false;
+ }
+
+ @Override
+ public void notifyPresentMethod(
+ DexClassAndMethod method, DefinitionContext referencedFrom, DexMethod reference) {
+ assert false;
+ }
+
+ @Override
+ public void notifyPresentMethodOverride(
+ DexClassAndMethod method, ProgramMethod override, DefinitionContext referencedFrom) {
+ assert false;
+ }
+
+ @Override
+ public void notifyMissingMethod(DexMethod method, DefinitionContext referencedFrom) {
+ assert false;
+ }
+ }
+
private static class MissingReferencesConsumer implements TraceReferencesConsumer {
@Override
diff --git a/src/main/java/com/android/tools/r8/tracereferences/UseCollector.java b/src/main/java/com/android/tools/r8/tracereferences/UseCollector.java
index 2171eac..2152274 100644
--- a/src/main/java/com/android/tools/r8/tracereferences/UseCollector.java
+++ b/src/main/java/com/android/tools/r8/tracereferences/UseCollector.java
@@ -101,6 +101,10 @@
return this;
}
+ protected UseCollectorEventConsumer getEventConsumerForNativeMethod() {
+ return getDefaultEventConsumer();
+ }
+
protected void notifyReflectiveIdentification(DexMethod invokedMethod, ProgramMethod method) {
// Intentionally empty. Overridden in R8PartialUseCollector.
}
@@ -429,8 +433,10 @@
private void registerMethod(ProgramMethod method, UseCollectorEventConsumer eventConsumer) {
DefinitionContext referencedFrom = DefinitionContextUtils.create(method);
- addTypes(method.getParameters(), referencedFrom, eventConsumer);
- addType(method.getReturnType(), referencedFrom, eventConsumer);
+ UseCollectorEventConsumer signatureEventConsumer =
+ method.getAccessFlags().isNative() ? getEventConsumerForNativeMethod() : eventConsumer;
+ addTypes(method.getParameters(), referencedFrom, signatureEventConsumer);
+ addType(method.getReturnType(), referencedFrom, signatureEventConsumer);
method
.getAnnotations()
.forEach(
diff --git a/src/test/java/com/android/tools/r8/partial/PartialCompilationNativeMethodTest.java b/src/test/java/com/android/tools/r8/partial/PartialCompilationNativeMethodTest.java
new file mode 100644
index 0000000..2a98f55
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/partial/PartialCompilationNativeMethodTest.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2025, 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.partial;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static junit.framework.TestCase.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+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 PartialCompilationNativeMethodTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8Partial(parameters)
+ .addR8IncludedClasses(IncludedClass.class)
+ .addR8ExcludedClasses(ExcludedClass.class)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject includedClassSubject = inspector.clazz(IncludedClass.class);
+ assertThat(includedClassSubject, isPresentAndNotRenamed());
+
+ MethodSubject nativeMethodSubject =
+ inspector.clazz(ExcludedClass.class).uniqueMethodWithOriginalName("m");
+ assertThat(nativeMethodSubject, isPresent());
+ assertEquals(
+ includedClassSubject.asTypeSubject(), nativeMethodSubject.getParameter(0));
+ });
+ }
+
+ static class ExcludedClass {
+
+ public static native void m(IncludedClass includedClass);
+ }
+
+ static class IncludedClass {}
+}