[ApiModel] Outline static and instance field calls
Bug: b/242078642
Change-Id: I9d5a618baac7e9c6b508e020939f8a6bb42f20a7
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 1aa41dc..f6edecd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -221,10 +221,6 @@
return accessFlags.isProtected();
}
- public boolean isPublic() {
- return accessFlags.isPublic();
- }
-
@Override
public boolean isStaticMember() {
return isStatic();
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
index 7f42e49..806b7a7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMember.java
@@ -73,6 +73,10 @@
return getAccessFlags().isPrivate();
}
+ public final boolean isPublic() {
+ return getAccessFlags().isPublic();
+ }
+
public abstract ProgramMember<D, R> asProgramMember(DexDefinitionSupplier definitions);
public abstract <T> T apply(
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index ba712ee..05b5030 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -443,10 +443,6 @@
return accessFlags.isNative();
}
- public boolean isPublic() {
- return accessFlags.isPublic();
- }
-
public boolean isSynchronized() {
return accessFlags.isSynchronized();
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index 3c0046f..3050bdc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -8,17 +8,21 @@
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.cf.code.CfFieldInstruction;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedMember;
import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
@@ -27,11 +31,16 @@
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.FreshLocalProvider;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
+import com.android.tools.r8.ir.synthetic.FieldAccessorBuilder;
import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
+import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.TraversalContinuation;
import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
/**
* This desugaring will outline calls to library methods that are introduced after the min-api
@@ -57,11 +66,12 @@
MethodProcessingContext methodProcessingContext,
CfInstructionDesugaringCollection desugaringCollection,
DexItemFactory dexItemFactory) {
- ComputedApiLevel computedApiLevel = getComputedApiLevelForMethodOnHolderWithMinApi(instruction);
+ ComputedApiLevel computedApiLevel =
+ getComputedApiLevelInstructionOnHolderWithMinApi(instruction);
if (computedApiLevel.isGreaterThan(appView.computedMinApiLevel())) {
return desugarLibraryCall(
methodProcessingContext.createUniqueContext(),
- instruction.asInvoke(),
+ instruction,
computedApiLevel,
dexItemFactory,
eventConsumer,
@@ -75,59 +85,68 @@
if (context.getDefinition().isD8R8Synthesized()) {
return false;
}
- return getComputedApiLevelForMethodOnHolderWithMinApi(instruction)
+ return getComputedApiLevelInstructionOnHolderWithMinApi(instruction)
.isGreaterThan(appView.computedMinApiLevel());
}
- private ComputedApiLevel getComputedApiLevelForMethodOnHolderWithMinApi(
+ private ComputedApiLevel getComputedApiLevelInstructionOnHolderWithMinApi(
CfInstruction instruction) {
- if (!instruction.isInvoke()) {
+ if (!instruction.isInvoke() && !instruction.isFieldInstruction()) {
return appView.computedMinApiLevel();
}
- CfInvoke cfInvoke = instruction.asInvoke();
- if (cfInvoke.isInvokeSpecial()) {
+ DexReference reference;
+ if (instruction.isInvoke()) {
+ CfInvoke cfInvoke = instruction.asInvoke();
+ if (cfInvoke.isInvokeSpecial()) {
+ return appView.computedMinApiLevel();
+ }
+ reference = cfInvoke.getMethod();
+ } else {
+ reference = instruction.asFieldInstruction().getField();
+ }
+ if (!reference.getContextType().isClassType()) {
return appView.computedMinApiLevel();
}
- DexType holderType = cfInvoke.getMethod().getHolderType();
- if (!holderType.isClassType()) {
- return appView.computedMinApiLevel();
- }
- DexClass holder = appView.definitionFor(holderType);
+ DexClass holder = appView.definitionFor(reference.getContextType());
if (holder == null || !holder.isLibraryClass()) {
return appView.computedMinApiLevel();
}
- ComputedApiLevel methodApiLevel =
- apiLevelCompute.computeApiLevelForLibraryReference(
- cfInvoke.getMethod(), ComputedApiLevel.unknown());
- if (appView.computedMinApiLevel().isGreaterThanOrEqualTo(methodApiLevel)
- || isApiLevelLessThanOrEqualTo9(methodApiLevel)
- || methodApiLevel.isUnknownApiLevel()) {
+ ComputedApiLevel referenceApiLevel =
+ apiLevelCompute.computeApiLevelForLibraryReference(reference, ComputedApiLevel.unknown());
+ if (appView.computedMinApiLevel().isGreaterThanOrEqualTo(referenceApiLevel)
+ || isApiLevelLessThanOrEqualTo9(referenceApiLevel)
+ || referenceApiLevel.isUnknownApiLevel()) {
return appView.computedMinApiLevel();
}
// Check for protected or package private access flags before outlining.
if (holder.isInterface()) {
- return methodApiLevel;
+ return referenceApiLevel;
} else {
- DexEncodedMethod methodDefinition =
- simpleLookupInClassHierarchy(holder.asLibraryClass(), cfInvoke.getMethod());
- return methodDefinition != null && methodDefinition.isPublic()
- ? methodApiLevel
+ DexEncodedMember<?, ?> definition =
+ simpleLookupInClassHierarchy(
+ holder.asLibraryClass(),
+ reference.isDexMethod()
+ ? x -> x.lookupMethod(reference.asDexMethod())
+ : x -> x.lookupField(reference.asDexField()));
+ return definition != null && definition.isPublic()
+ ? referenceApiLevel
: appView.computedMinApiLevel();
}
}
- private DexEncodedMethod simpleLookupInClassHierarchy(DexLibraryClass holder, DexMethod method) {
- DexEncodedMethod result = holder.lookupMethod(method);
+ private DexEncodedMember<?, ?> simpleLookupInClassHierarchy(
+ DexLibraryClass holder, Function<DexClass, DexEncodedMember<?, ?>> lookup) {
+ DexEncodedMember<?, ?> result = lookup.apply(holder);
if (result != null) {
return result;
}
- TraversalContinuation<DexEncodedMethod, ?> traversalResult =
+ TraversalContinuation<DexEncodedMember<?, ?>, ?> traversalResult =
appView
.appInfoForDesugaring()
.traverseSuperClasses(
holder,
(ignored, superClass, ignored_) -> {
- DexEncodedMethod definition = superClass.lookupMethod(method);
+ DexEncodedMember<?, ?> definition = lookup.apply(superClass);
if (definition != null) {
return TraversalContinuation.doBreak(definition);
}
@@ -143,29 +162,24 @@
private Collection<CfInstruction> desugarLibraryCall(
UniqueContext uniqueContext,
- CfInvoke invoke,
+ CfInstruction instruction,
ComputedApiLevel computedApiLevel,
DexItemFactory factory,
ApiInvokeOutlinerDesugaringEventConsumer eventConsumer,
ProgramMethod context) {
- DexMethod method = invoke.getMethod();
+ assert instruction.isInvoke() || instruction.isFieldInstruction();
ProgramMethod outlinedMethod =
- ensureOutlineMethod(uniqueContext, method, computedApiLevel, factory, invoke);
+ ensureOutlineMethod(uniqueContext, instruction, computedApiLevel, factory, context);
eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
return ImmutableList.of(new CfInvoke(INVOKESTATIC, outlinedMethod.getReference(), false));
}
private ProgramMethod ensureOutlineMethod(
UniqueContext context,
- DexMethod apiMethod,
+ CfInstruction instruction,
ComputedApiLevel apiLevel,
DexItemFactory factory,
- CfInvoke invoke) {
- DexClass libraryHolder = appView.definitionFor(apiMethod.getHolderType());
- assert libraryHolder != null;
- boolean isVirtualMethod = invoke.isInvokeVirtual() || invoke.isInvokeInterface();
- assert verifyLibraryHolderAndInvoke(libraryHolder, apiMethod, isVirtualMethod);
- DexProto proto = factory.prependHolderToProtoIf(apiMethod, isVirtualMethod);
+ ProgramMethod programContext) {
return appView
.getSyntheticItems()
.createMethod(
@@ -174,7 +188,6 @@
appView,
syntheticMethodBuilder -> {
syntheticMethodBuilder
- .setProto(proto)
.setAccessFlags(
MethodAccessFlags.builder()
.setPublic()
@@ -183,24 +196,83 @@
.setBridge()
.build())
.setApiLevelForDefinition(apiLevel)
- .setApiLevelForCode(apiLevel)
- .setCode(
- m -> {
- if (isVirtualMethod) {
- return ForwardMethodBuilder.builder(factory)
- .setVirtualTarget(apiMethod, libraryHolder.isInterface())
- .setNonStaticSource(apiMethod)
- .build();
- } else {
- return ForwardMethodBuilder.builder(factory)
- .setStaticTarget(apiMethod, libraryHolder.isInterface())
- .setStaticSource(apiMethod)
- .build();
- }
- });
+ .setApiLevelForCode(apiLevel);
+ if (instruction.isInvoke()) {
+ setCodeForInvoke(syntheticMethodBuilder, instruction.asInvoke(), factory);
+ } else {
+ assert instruction.isCfInstruction();
+ setCodeForFieldInstruction(
+ syntheticMethodBuilder,
+ instruction.asFieldInstruction(),
+ factory,
+ programContext);
+ }
});
}
+ private void setCodeForInvoke(
+ SyntheticMethodBuilder methodBuilder, CfInvoke invoke, DexItemFactory factory) {
+ DexMethod method = invoke.getMethod();
+ DexClass libraryHolder = appView.definitionFor(method.getHolderType());
+ assert libraryHolder != null;
+ boolean isVirtualMethod = invoke.isInvokeVirtual() || invoke.isInvokeInterface();
+ assert verifyLibraryHolderAndInvoke(libraryHolder, method, isVirtualMethod);
+ DexProto proto = factory.prependHolderToProtoIf(method, isVirtualMethod);
+ methodBuilder
+ .setProto(proto)
+ .setCode(
+ m -> {
+ if (isVirtualMethod) {
+ return ForwardMethodBuilder.builder(factory)
+ .setVirtualTarget(method, libraryHolder.isInterface())
+ .setNonStaticSource(method)
+ .build();
+ } else {
+ return ForwardMethodBuilder.builder(factory)
+ .setStaticTarget(method, libraryHolder.isInterface())
+ .setStaticSource(method)
+ .build();
+ }
+ });
+ }
+
+ private void setCodeForFieldInstruction(
+ SyntheticMethodBuilder methodBuilder,
+ CfFieldInstruction fieldInstruction,
+ DexItemFactory factory,
+ ProgramMethod programContext) {
+ DexField field = fieldInstruction.getField();
+ DexClass libraryHolder = appView.definitionFor(field.getHolderType());
+ assert libraryHolder != null;
+ boolean isInstance =
+ fieldInstruction.isInstanceFieldPut() || fieldInstruction.isInstanceFieldGet();
+ // Outlined field references will return a value if getter and only takes arguments if
+ // instance or if put or two arguments if both.
+ DexType returnType = fieldInstruction.isFieldGet() ? field.getType() : factory.voidType;
+ List<DexType> parameters = new ArrayList<>();
+ if (isInstance) {
+ parameters.add(libraryHolder.getType());
+ }
+ if (fieldInstruction.isFieldPut()) {
+ parameters.add(field.getType());
+ }
+ methodBuilder
+ .setProto(factory.createProto(returnType, parameters))
+ .setCode(
+ m ->
+ FieldAccessorBuilder.builder()
+ .applyIf(
+ isInstance,
+ thenConsumer -> thenConsumer.setInstanceField(field),
+ elseConsumer -> elseConsumer.setStaticField(field))
+ .applyIf(
+ fieldInstruction.isFieldGet(),
+ FieldAccessorBuilder::setGetter,
+ FieldAccessorBuilder::setSetter)
+ .setSourceMethod(programContext.getReference())
+ .build());
+ }
+
private boolean verifyLibraryHolderAndInvoke(
DexClass libraryHolder, DexMethod apiMethod, boolean isVirtualInvoke) {
DexEncodedMethod libraryApiMethodDefinition = libraryHolder.lookupMethod(apiMethod);
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceFieldTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceFieldTest.java
index a5832ff..2d9de46 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceFieldTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceFieldTest.java
@@ -7,6 +7,7 @@
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForField;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationMode;
@@ -17,6 +18,8 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.reflect.Field;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -29,7 +32,8 @@
private static final AndroidApiLevel classApiLevel = AndroidApiLevel.M;
private static final AndroidApiLevel fieldApiLevel = AndroidApiLevel.P;
- private static final String EXPECTED = "LibraryClass::field";
+ private static final String[] EXPECTED =
+ new String[] {"LibraryClass::getField", "LibraryClass::putField"};
@Parameter public TestParameters parameters;
@@ -47,7 +51,8 @@
.addAndroidBuildVersion()
.apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
.apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, classApiLevel))
- .apply(setMockApiLevelForField(LibraryClass.class.getField("field"), fieldApiLevel))
+ .apply(setMockApiLevelForField(LibraryClass.class.getField("getField"), fieldApiLevel))
+ .apply(setMockApiLevelForField(LibraryClass.class.getField("putField"), fieldApiLevel))
.apply(ApiModelingTestHelper::enableApiCallerIdentification)
.apply(ApiModelingTestHelper::enableOutliningOfMethods)
.apply(ApiModelingTestHelper::disableStubbingOfClasses);
@@ -65,8 +70,7 @@
.setMode(CompilationMode.DEBUG)
.apply(this::setupTestBuilder)
.compile()
- // TODO(b/242078642): We should outline field references to newer api levels.
- .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+ .inspect(this::inspect)
.applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
.run(parameters.getRuntime(), Main.class)
.apply(this::checkOutput);
@@ -79,8 +83,7 @@
.setMode(CompilationMode.RELEASE)
.apply(this::setupTestBuilder)
.compile()
- // TODO(b/242078642): We should outline field references to newer api levels.
- .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+ .inspect(this::inspect)
.applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
.run(parameters.getRuntime(), Main.class)
.apply(this::checkOutput);
@@ -92,13 +95,23 @@
.apply(this::setupTestBuilder)
.addKeepMainRule(Main.class)
.compile()
- // TODO(b/242078642): We should outline field references to newer api levels.
- .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+ .inspect(this::inspect)
.applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
.run(parameters.getRuntime(), Main.class)
.apply(this::checkOutput);
}
+ private void inspect(CodeInspector inspector) throws Exception {
+ Field[] fields =
+ new Field[] {
+ LibraryClass.class.getField("getField"), LibraryClass.class.getField("putField")
+ };
+ for (Field field : fields) {
+ verifyThat(inspector, parameters, field)
+ .isOutlinedFromUntil(Main.class.getMethod("main", String[].class), fieldApiLevel);
+ }
+ }
+
private void checkOutput(SingleTestRunResult<?> runResult) {
if (parameters.isDexRuntime()
&& parameters.getApiLevel().isGreaterThanOrEqualTo(classApiLevel)) {
@@ -111,7 +124,9 @@
// Only present from api level 23.
public static class LibraryClass {
- public String field = "LibraryClass::field";
+ public String getField = "LibraryClass::getField";
+
+ public String putField;
}
public static class Main {
@@ -119,7 +134,9 @@
public static void main(String[] args) {
if (AndroidBuildVersion.VERSION >= 23) {
LibraryClass libraryClass = new LibraryClass();
- System.out.println(libraryClass.field);
+ System.out.println(libraryClass.getField);
+ libraryClass.putField = "LibraryClass::putField";
+ System.out.println(libraryClass.putField);
} else {
System.out.println("Not calling API");
}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineStaticFieldTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineStaticFieldTest.java
index d00685f..79b2cb8 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineStaticFieldTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineStaticFieldTest.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForField;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationMode;
@@ -16,6 +17,8 @@
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.testing.AndroidBuildVersion;
import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.lang.reflect.Field;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -28,7 +31,8 @@
private static final AndroidApiLevel classApiLevel = AndroidApiLevel.M;
private static final AndroidApiLevel fieldApiLevel = AndroidApiLevel.P;
- private static final String EXPECTED = "LibraryClass::field";
+ private static final String[] EXPECTED =
+ new String[] {"LibraryClass::getField", "LibraryClass::putField"};
@Parameter public TestParameters parameters;
@@ -45,7 +49,8 @@
.setMinApi(parameters.getApiLevel())
.addAndroidBuildVersion()
.apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
- .apply(setMockApiLevelForField(LibraryClass.class.getField("field"), fieldApiLevel))
+ .apply(setMockApiLevelForField(LibraryClass.class.getField("getField"), fieldApiLevel))
+ .apply(setMockApiLevelForField(LibraryClass.class.getField("putField"), fieldApiLevel))
.apply(ApiModelingTestHelper::enableApiCallerIdentification)
.apply(ApiModelingTestHelper::enableOutliningOfMethods)
.apply(ApiModelingTestHelper::disableStubbingOfClasses);
@@ -63,8 +68,7 @@
.setMode(CompilationMode.DEBUG)
.apply(this::setupTestBuilder)
.compile()
- // TODO(b/242078642): We should outline field references to newer api levels.
- .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+ .inspect(this::inspect)
.applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
.run(parameters.getRuntime(), Main.class)
.apply(this::checkOutput);
@@ -77,8 +81,7 @@
.setMode(CompilationMode.RELEASE)
.apply(this::setupTestBuilder)
.compile()
- // TODO(b/242078642): We should outline field references to newer api levels.
- .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+ .inspect(this::inspect)
.applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
.run(parameters.getRuntime(), Main.class)
.apply(this::checkOutput);
@@ -90,13 +93,23 @@
.apply(this::setupTestBuilder)
.addKeepMainRule(Main.class)
.compile()
- // TODO(b/242078642): We should outline field references to newer api levels.
- .inspect(ApiModelingTestHelper::assertNoSynthesizedClasses)
+ .inspect(this::inspect)
.applyIf(addToBootClasspath(), b -> b.addBootClasspathClasses(LibraryClass.class))
.run(parameters.getRuntime(), Main.class)
.apply(this::checkOutput);
}
+ private void inspect(CodeInspector inspector) throws Exception {
+ Field[] fields =
+ new Field[] {
+ LibraryClass.class.getField("getField"), LibraryClass.class.getField("putField")
+ };
+ for (Field field : fields) {
+ verifyThat(inspector, parameters, field)
+ .isOutlinedFromUntil(Main.class.getMethod("main", String[].class), fieldApiLevel);
+ }
+ }
+
private void checkOutput(SingleTestRunResult<?> runResult) {
if (parameters.isDexRuntime()
&& parameters.getApiLevel().isGreaterThanOrEqualTo(classApiLevel)) {
@@ -109,14 +122,18 @@
// Only present from api level 23.
public static class LibraryClass {
- public static String field = "LibraryClass::field";
+ public static String getField = "LibraryClass::getField";
+
+ public static String putField;
}
public static class Main {
public static void main(String[] args) {
if (AndroidBuildVersion.VERSION >= 23) {
- System.out.println(LibraryClass.field);
+ System.out.println(LibraryClass.getField);
+ LibraryClass.putField = "LibraryClass::putField";
+ System.out.println(LibraryClass.putField);
} else {
System.out.println("Not calling API");
}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index ed2343a..4166e5e 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.apimodel;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.accessesField;
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
@@ -11,10 +12,12 @@
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import com.android.tools.r8.TestCompilerBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
import com.android.tools.r8.utils.AndroidApiLevel;
@@ -193,6 +196,12 @@
inspector, parameters, Reference.methodFromMethod(method));
}
+ static ApiModelingFieldVerificationHelper verifyThat(
+ CodeInspector inspector, TestParameters parameters, Field field) {
+ return new ApiModelingFieldVerificationHelper(
+ inspector, parameters, Reference.fieldFromField(field));
+ }
+
public static void assertNoSynthesizedClasses(CodeInspector inspector) {
assertEquals(
Collections.emptySet(),
@@ -224,6 +233,51 @@
}
}
+ public static class ApiModelingFieldVerificationHelper {
+
+ private final CodeInspector inspector;
+ private final FieldReference fieldOfInterest;
+ private final TestParameters parameters;
+
+ private ApiModelingFieldVerificationHelper(
+ CodeInspector inspector, TestParameters parameters, FieldReference fieldOfInterest) {
+ this.inspector = inspector;
+ this.fieldOfInterest = fieldOfInterest;
+ this.parameters = parameters;
+ }
+
+ void isOutlinedFromUntil(Method method, AndroidApiLevel apiLevel) {
+ if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(apiLevel)) {
+ isOutlinedFrom(method);
+ } else {
+ isNotOutlinedFrom(method);
+ }
+ }
+
+ void isOutlinedFrom(Method method) {
+ // Check that the call is in a synthetic class.
+ List<FoundMethodSubject> outlinedMethod =
+ inspector.allClasses().stream()
+ .flatMap(clazz -> clazz.allMethods().stream())
+ .filter(
+ methodSubject ->
+ methodSubject.isSynthetic()
+ && accessesField(fieldOfInterest).matches(methodSubject))
+ .collect(Collectors.toList());
+ assertFalse(outlinedMethod.isEmpty());
+ // Assert that method invokes the outline
+ MethodSubject caller = inspector.method(method);
+ assertThat(caller, isPresent());
+ assertThat(caller, invokesMethod(outlinedMethod.get(0)));
+ }
+
+ void isNotOutlinedFrom(Method method) {
+ MethodSubject caller = inspector.method(method);
+ assertThat(caller, isPresent());
+ assertThat(caller, accessesField(fieldOfInterest));
+ }
+ }
+
public static class ApiModelingMethodVerificationHelper {
private final CodeInspector inspector;
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 50a77fe..db36bbc 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,8 +4,8 @@
package com.android.tools.r8.utils.codeinspector;
-import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.references.FieldReference;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
@@ -20,7 +20,10 @@
if (!targetSubject.isPresent()) {
throw new IllegalArgumentException();
}
- DexField target = targetSubject.getField().getReference();
+ return accessesField(targetSubject.getField().getReference().asFieldReference());
+ }
+
+ public static Matcher<MethodSubject> accessesField(FieldReference target) {
return new TypeSafeMatcher<MethodSubject>() {
@Override
protected boolean matchesSafely(MethodSubject subject) {
@@ -35,7 +38,7 @@
@Override
public void describeTo(Description description) {
- description.appendText("accesses field `" + target.toSourceString() + "`");
+ description.appendText("accesses field `" + target.toString() + "`");
}
@Override
@@ -232,7 +235,8 @@
};
}
- public static Predicate<InstructionSubject> isFieldAccessWithTarget(DexField target) {
- return instruction -> instruction.isFieldAccess() && instruction.getField() == target;
+ public static Predicate<InstructionSubject> isFieldAccessWithTarget(FieldReference target) {
+ return instruction ->
+ instruction.isFieldAccess() && instruction.getField().asFieldReference().equals(target);
}
}