Mitigate issues from Dalvik array field get verifier bug
Bug: 206891715
Change-Id: Id3f50f13242a73159cc82be9c3ada3309403c5a6
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java
new file mode 100644
index 0000000..eee1f34
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerFieldAccessAnalysis.java
@@ -0,0 +1,24 @@
+// 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.graph.analysis;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public interface EnqueuerFieldAccessAnalysis {
+
+ void traceInstanceFieldRead(
+ DexField field, FieldResolutionResult resolutionResult, ProgramMethod context);
+
+ void traceInstanceFieldWrite(
+ DexField field, FieldResolutionResult resolutionResult, ProgramMethod context);
+
+ void traceStaticFieldRead(
+ DexField field, FieldResolutionResult resolutionResult, ProgramMethod context);
+
+ void traceStaticFieldWrite(
+ DexField field, FieldResolutionResult resolutionResult, ProgramMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java b/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
new file mode 100644
index 0000000..1243e98
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/analysis/GetArrayOfMissingTypeVerifyErrorWorkaround.java
@@ -0,0 +1,122 @@
+// 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.graph.analysis;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.Enqueuer;
+import com.android.tools.r8.shaking.KeepInfo.Joiner;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+
+/**
+ * In Dalvik it is a verification error to read and use a field of type Missing[].
+ *
+ * <p>Example:
+ *
+ * <pre>
+ * Consumer<?>[] consumer = this.field;
+ * acceptConsumer(consumer); // acceptConsumer(Consumer[])
+ * </pre>
+ *
+ * <p>To avoid that the compiler moves such code into other contexts (e.g., as a result of inlining
+ * or class merging), and thereby causes new classes to no longer verify on Dalvik, we soft-pin
+ * methods that reads such fields.
+ */
+public class GetArrayOfMissingTypeVerifyErrorWorkaround implements EnqueuerFieldAccessAnalysis {
+
+ private final DexItemFactory dexItemFactory;
+ private final Enqueuer enqueuer;
+ private final Set<DexType> knownToBePresentOnDalvik;
+
+ public GetArrayOfMissingTypeVerifyErrorWorkaround(
+ AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+ this.dexItemFactory = appView.dexItemFactory();
+ this.enqueuer = enqueuer;
+ this.knownToBePresentOnDalvik =
+ ImmutableSet.<DexType>builder()
+ .add(dexItemFactory.boxedBooleanType)
+ .add(dexItemFactory.boxedByteType)
+ .add(dexItemFactory.boxedCharType)
+ .add(dexItemFactory.boxedDoubleType)
+ .add(dexItemFactory.boxedFloatType)
+ .add(dexItemFactory.boxedIntType)
+ .add(dexItemFactory.boxedLongType)
+ .add(dexItemFactory.boxedShortType)
+ .add(dexItemFactory.classType)
+ .add(dexItemFactory.objectType)
+ .add(dexItemFactory.enumType)
+ .add(dexItemFactory.stringType)
+ .build();
+ }
+
+ public static void register(
+ AppView<? extends AppInfoWithClassHierarchy> appView, Enqueuer enqueuer) {
+ if (!isNoop(appView)) {
+ enqueuer.registerFieldAccessAnalysis(
+ new GetArrayOfMissingTypeVerifyErrorWorkaround(appView, enqueuer));
+ }
+ }
+
+ private static boolean isNoop(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ InternalOptions options = appView.options();
+ return options.isGeneratingDex()
+ && options.getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L);
+ }
+
+ @Override
+ public void traceInstanceFieldRead(
+ DexField field, FieldResolutionResult resolutionResult, ProgramMethod context) {
+ if (isUnsafeToUseFieldOnDalvik(field, context)) {
+ enqueuer.getKeepInfo().joinMethod(context, Joiner::disallowOptimization);
+ }
+ }
+
+ @Override
+ public void traceStaticFieldRead(
+ DexField field, FieldResolutionResult resolutionResult, ProgramMethod context) {
+ if (isUnsafeToUseFieldOnDalvik(field, context)) {
+ enqueuer.getKeepInfo().joinMethod(context, Joiner::disallowOptimization);
+ }
+ }
+
+ private boolean isUnsafeToUseFieldOnDalvik(DexField field, ProgramMethod context) {
+ DexType fieldType = field.getType();
+ if (!fieldType.isArrayType()) {
+ return false;
+ }
+ DexType baseType = fieldType.toBaseType(dexItemFactory);
+ if (!baseType.isClassType()) {
+ return false;
+ }
+ if (knownToBePresentOnDalvik.contains(baseType)) {
+ return false;
+ }
+ // TODO(b/206891715): Use the API database to determine if the given type is introduced in API
+ // level L or later.
+ DexClass baseClass = enqueuer.definitionFor(baseType, context);
+ return baseClass != null && baseClass.isLibraryClass();
+ }
+
+ @Override
+ public void traceInstanceFieldWrite(
+ DexField field, FieldResolutionResult resolutionResult, ProgramMethod context) {
+ // Intentionally empty.
+ }
+
+ @Override
+ public void traceStaticFieldWrite(
+ DexField field, FieldResolutionResult resolutionResult, ProgramMethod context) {
+ // Intentionally empty.
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 02c18fc..6ca6a5c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -79,8 +79,10 @@
import com.android.tools.r8.graph.analysis.EnqueuerAnalysis;
import com.android.tools.r8.graph.analysis.EnqueuerCheckCastAnalysis;
import com.android.tools.r8.graph.analysis.EnqueuerExceptionGuardAnalysis;
+import com.android.tools.r8.graph.analysis.EnqueuerFieldAccessAnalysis;
import com.android.tools.r8.graph.analysis.EnqueuerInstanceOfAnalysis;
import com.android.tools.r8.graph.analysis.EnqueuerInvokeAnalysis;
+import com.android.tools.r8.graph.analysis.GetArrayOfMissingTypeVerifyErrorWorkaround;
import com.android.tools.r8.graph.analysis.InvokeVirtualToInterfaceVerifyErrorWorkaround;
import com.android.tools.r8.ir.analysis.proto.ProtoEnqueuerUseRegistry;
import com.android.tools.r8.ir.analysis.proto.schema.ProtoEnqueuerExtension;
@@ -232,11 +234,12 @@
private final boolean forceProguardCompatibility;
private final Mode mode;
- private Set<EnqueuerAnalysis> analyses = new LinkedHashSet<>();
- private Set<EnqueuerInvokeAnalysis> invokeAnalyses = new LinkedHashSet<>();
- private Set<EnqueuerInstanceOfAnalysis> instanceOfAnalyses = new LinkedHashSet<>();
- private Set<EnqueuerExceptionGuardAnalysis> exceptionGuardAnalyses = new LinkedHashSet<>();
- private Set<EnqueuerCheckCastAnalysis> checkCastAnalyses = new LinkedHashSet<>();
+ private final Set<EnqueuerAnalysis> analyses = new LinkedHashSet<>();
+ private final Set<EnqueuerFieldAccessAnalysis> fieldAccessAnalyses = new LinkedHashSet<>();
+ private final Set<EnqueuerInvokeAnalysis> invokeAnalyses = new LinkedHashSet<>();
+ private final Set<EnqueuerInstanceOfAnalysis> instanceOfAnalyses = new LinkedHashSet<>();
+ private final Set<EnqueuerExceptionGuardAnalysis> exceptionGuardAnalyses = new LinkedHashSet<>();
+ private final Set<EnqueuerCheckCastAnalysis> checkCastAnalyses = new LinkedHashSet<>();
// Don't hold a direct pointer to app info (use appView).
private AppInfoWithClassHierarchy appInfo;
@@ -471,6 +474,7 @@
: null;
if (mode.isInitialOrFinalTreeShaking()) {
+ GetArrayOfMissingTypeVerifyErrorWorkaround.register(appView, this);
InvokeVirtualToInterfaceVerifyErrorWorkaround.register(appView, this);
if (options.protoShrinking().enableGeneratedMessageLiteShrinking) {
registerAnalysis(new ProtoEnqueuerExtension(appView));
@@ -530,6 +534,11 @@
return this;
}
+ public Enqueuer registerFieldAccessAnalysis(EnqueuerFieldAccessAnalysis analysis) {
+ fieldAccessAnalyses.add(analysis);
+ return this;
+ }
+
public Enqueuer registerInvokeAnalysis(EnqueuerInvokeAnalysis analysis) {
invokeAnalyses.add(analysis);
return this;
@@ -1432,6 +1441,10 @@
}
FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+ fieldAccessAnalyses.forEach(
+ analysis ->
+ analysis.traceInstanceFieldRead(fieldReference, resolutionResult, currentMethod));
+
if (resolutionResult.isFailedOrUnknownResolution()) {
// Must trace the types from the field reference even if it does not exist.
traceFieldReference(fieldReference, resolutionResult, currentMethod);
@@ -1484,6 +1497,10 @@
}
FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+ fieldAccessAnalyses.forEach(
+ analysis ->
+ analysis.traceInstanceFieldWrite(fieldReference, resolutionResult, currentMethod));
+
if (resolutionResult.isFailedOrUnknownResolution()) {
// Must trace the types from the field reference even if it does not exist.
traceFieldReference(fieldReference, resolutionResult, currentMethod);
@@ -1536,6 +1553,9 @@
}
FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+ fieldAccessAnalyses.forEach(
+ analysis -> analysis.traceStaticFieldRead(fieldReference, resolutionResult, currentMethod));
+
if (resolutionResult.isFailedOrUnknownResolution()) {
// Must trace the types from the field reference even if it does not exist.
traceFieldReference(fieldReference, resolutionResult, currentMethod);
@@ -1604,6 +1624,10 @@
}
FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+ fieldAccessAnalyses.forEach(
+ analysis ->
+ analysis.traceStaticFieldWrite(fieldReference, resolutionResult, currentMethod));
+
if (resolutionResult.isFailedOrUnknownResolution()) {
// Must trace the types from the field reference even if it does not exist.
traceFieldReference(fieldReference, resolutionResult, currentMethod);
diff --git a/src/test/java/com/android/tools/r8/workaround/ArrayFieldGetWithMissingBaseTypeTest.java b/src/test/java/com/android/tools/r8/workaround/ArrayFieldGetWithMissingBaseTypeTest.java
index 87627bc..b28721d 100644
--- a/src/test/java/com/android/tools/r8/workaround/ArrayFieldGetWithMissingBaseTypeTest.java
+++ b/src/test/java/com/android/tools/r8/workaround/ArrayFieldGetWithMissingBaseTypeTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -39,21 +40,22 @@
.addKeepClassAndMembersRules(Utils.class)
.addKeepRules(
"-keep class " + Main.class.getTypeName() + " { void notUsedDuringLaunch(); }")
- // TODO(b/206891715): Should not allow merging when compiling for Dalvik.
.addHorizontallyMergedClassesInspector(
inspector ->
inspector
- .assertIsCompleteMergeGroup(UsedDuringLaunch.class, NotUsedDuringLaunch.class)
+ .applyIf(
+ parameters.isDexRuntime()
+ && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.L),
+ i ->
+ i.assertIsCompleteMergeGroup(
+ UsedDuringLaunch.class, NotUsedDuringLaunch.class))
.assertNoOtherClassesMerged())
.enableInliningAnnotations()
.enableNeverClassInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.compile()
.run(parameters.getRuntime(), Main.class)
- .applyIf(
- parameters.isCfRuntime() || !parameters.getDexRuntimeVersion().isDalvik(),
- runResult -> runResult.assertSuccessWithOutputLines("Hello world!"),
- runResult -> runResult.assertFailureWithErrorThatThrows(VerifyError.class));
+ .assertSuccessWithOutputLines("Hello world!");
}
static class Main {