Merge commit 'ad39dcb28b565277e9600ad0909e8f86cb79e95d' into dev-release
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
index 6c30ad3..bec1872 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfCheckCast.java
@@ -144,4 +144,14 @@
DexItemFactory dexItemFactory = appView.dexItemFactory();
return frame.popInitialized(appView, config, dexItemFactory.objectType).push(config, type);
}
+
+ @Override
+ public boolean isCheckCast() {
+ return true;
+ }
+
+ @Override
+ public CfCheckCast asCheckCast() {
+ return this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
index b137991..f21fc0c 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInstruction.java
@@ -412,4 +412,12 @@
public abstract CfFrameState evaluate(
CfFrameState frame, AppView<?> appView, CfAnalysisConfig config);
+
+ public boolean isCheckCast() {
+ return false;
+ }
+
+ public CfCheckCast asCheckCast() {
+ return null;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 43e7099..535a891 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfLabel;
import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfMonitor;
import com.android.tools.r8.cf.code.CfPosition;
import com.android.tools.r8.cf.code.CfReturnVoid;
import com.android.tools.r8.cf.code.CfTryCatch;
@@ -534,6 +535,16 @@
}
@Override
+ public boolean hasMonitorInstructions() {
+ for (CfInstruction instruction : getInstructions()) {
+ if (instruction instanceof CfMonitor) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
public IRCode buildIR(
ProgramMethod method,
AppView<?> appView,
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index 1077fb9..d0417b4 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -117,6 +117,10 @@
return false;
}
+ public boolean hasMonitorInstructions() {
+ return false;
+ }
+
public boolean isThrowExceptionCode() {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index 7e55f8e..09db76d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.dex.MixedSectionCollection;
import com.android.tools.r8.dex.code.CfOrDexInstruction;
import com.android.tools.r8.dex.code.DexInstruction;
+import com.android.tools.r8.dex.code.DexMonitorEnter;
import com.android.tools.r8.dex.code.DexReturnVoid;
import com.android.tools.r8.dex.code.DexSwitchPayload;
import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
@@ -377,6 +378,16 @@
}
@Override
+ public boolean hasMonitorInstructions() {
+ for (DexInstruction instruction : instructions) {
+ if (instruction instanceof DexMonitorEnter) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
public Code asCode() {
return this;
}
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index 3ab128b..3abc2e4 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -229,6 +229,11 @@
}
@Override
+ public boolean hasMonitorInstructions() {
+ return asCfCode().hasMonitorInstructions();
+ }
+
+ @Override
public int estimatedSizeForInlining() {
return asCfCode().estimatedSizeForInlining();
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
index b997f78..b6095d1 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyScheduler.java
@@ -46,6 +46,7 @@
import com.android.tools.r8.horizontalclassmerging.policies.NoVirtualMethodMerging;
import com.android.tools.r8.horizontalclassmerging.policies.NoWeakerAccessPrivileges;
import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
+import com.android.tools.r8.horizontalclassmerging.policies.NotTwoInitsWithMonitors;
import com.android.tools.r8.horizontalclassmerging.policies.OnlyClassesWithStaticDefinitionsAndNoClassInitializer;
import com.android.tools.r8.horizontalclassmerging.policies.OnlyDirectlyConnectedOrUnrelatedInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
@@ -292,6 +293,9 @@
new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
new NoWeakerAccessPrivileges(appView, immediateSubtypingInfo),
new PreventClassMethodAndDefaultMethodCollisions(appView, immediateSubtypingInfo));
+ if (appView.options().canHaveIssueWithInlinedMonitors()) {
+ builder.add(new NotTwoInitsWithMonitors());
+ }
}
private static void addMultiClassPoliciesForMergingNonSyntheticClasses(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotTwoInitsWithMonitors.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotTwoInitsWithMonitors.java
new file mode 100644
index 0000000..d5cebe2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NotTwoInitsWithMonitors.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2022, 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.horizontalclassmerging.policies;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.ProgramMethod;
+
+public class NotTwoInitsWithMonitors extends AtMostOneClassThatMatchesPolicy {
+
+ @Override
+ public boolean atMostOneOf(DexProgramClass clazz) {
+ for (ProgramMethod initializer : clazz.programInstanceInitializers()) {
+ DexEncodedMethod definition = initializer.getDefinition();
+ if (definition.isSynchronized() || definition.getCode().hasMonitorInstructions()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String getName() {
+ return "NotTwoInitsWithMonitors";
+ }
+}
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 3050bdc..4c3fc63 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,7 +8,9 @@
import com.android.tools.r8.androidapi.AndroidApiLevelCompute;
import com.android.tools.r8.androidapi.ComputedApiLevel;
+import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfFieldInstruction;
+import com.android.tools.r8.cf.code.CfInstanceOf;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
@@ -24,6 +26,7 @@
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.DexTypeList;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaring;
@@ -31,8 +34,10 @@
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.CheckCastSourceCode;
import com.android.tools.r8.ir.synthetic.FieldAccessorBuilder;
import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
+import com.android.tools.r8.ir.synthetic.InstanceOfSourceCode;
import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.TraversalContinuation;
@@ -51,9 +56,12 @@
private final AppView<?> appView;
private final AndroidApiLevelCompute apiLevelCompute;
+ private final DexTypeList objectParams;
+
public ApiInvokeOutlinerDesugaring(AppView<?> appView, AndroidApiLevelCompute apiLevelCompute) {
this.appView = appView;
this.apiLevelCompute = apiLevelCompute;
+ this.objectParams = DexTypeList.create(new DexType[] {appView.dexItemFactory().objectType});
}
@Override
@@ -67,7 +75,7 @@
CfInstructionDesugaringCollection desugaringCollection,
DexItemFactory dexItemFactory) {
ComputedApiLevel computedApiLevel =
- getComputedApiLevelInstructionOnHolderWithMinApi(instruction);
+ getComputedApiLevelInstructionOnHolderWithMinApi(instruction, context);
if (computedApiLevel.isGreaterThan(appView.computedMinApiLevel())) {
return desugarLibraryCall(
methodProcessingContext.createUniqueContext(),
@@ -82,16 +90,13 @@
@Override
public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
- if (context.getDefinition().isD8R8Synthesized()) {
- return false;
- }
- return getComputedApiLevelInstructionOnHolderWithMinApi(instruction)
+ return getComputedApiLevelInstructionOnHolderWithMinApi(instruction, context)
.isGreaterThan(appView.computedMinApiLevel());
}
private ComputedApiLevel getComputedApiLevelInstructionOnHolderWithMinApi(
- CfInstruction instruction) {
- if (!instruction.isInvoke() && !instruction.isFieldInstruction()) {
+ CfInstruction instruction, ProgramMethod context) {
+ if (context.getDefinition().isD8R8Synthesized()) {
return appView.computedMinApiLevel();
}
DexReference reference;
@@ -101,8 +106,14 @@
return appView.computedMinApiLevel();
}
reference = cfInvoke.getMethod();
- } else {
+ } else if (instruction.isFieldInstruction()) {
reference = instruction.asFieldInstruction().getField();
+ } else if (instruction.isCheckCast()) {
+ reference = instruction.asCheckCast().getType();
+ } else if (instruction.isInstanceOf()) {
+ reference = instruction.asInstanceOf().getType();
+ } else {
+ return appView.computedMinApiLevel();
}
if (!reference.getContextType().isClassType()) {
return appView.computedMinApiLevel();
@@ -119,7 +130,7 @@
return appView.computedMinApiLevel();
}
// Check for protected or package private access flags before outlining.
- if (holder.isInterface()) {
+ if (holder.isInterface() || instruction.isCheckCast() || instruction.isInstanceOf()) {
return referenceApiLevel;
} else {
DexEncodedMember<?, ?> definition =
@@ -167,7 +178,10 @@
DexItemFactory factory,
ApiInvokeOutlinerDesugaringEventConsumer eventConsumer,
ProgramMethod context) {
- assert instruction.isInvoke() || instruction.isFieldInstruction();
+ assert instruction.isInvoke()
+ || instruction.isFieldInstruction()
+ || instruction.isCheckCast()
+ || instruction.isInstanceOf();
ProgramMethod outlinedMethod =
ensureOutlineMethod(uniqueContext, instruction, computedApiLevel, factory, context);
eventConsumer.acceptOutlinedMethod(outlinedMethod, context);
@@ -199,6 +213,10 @@
.setApiLevelForCode(apiLevel);
if (instruction.isInvoke()) {
setCodeForInvoke(syntheticMethodBuilder, instruction.asInvoke(), factory);
+ } else if (instruction.isCheckCast()) {
+ setCodeForCheckCast(syntheticMethodBuilder, instruction.asCheckCast(), factory);
+ } else if (instruction.isInstanceOf()) {
+ setCodeForInstanceOf(syntheticMethodBuilder, instruction.asInstanceOf(), factory);
} else {
assert instruction.isCfInstruction();
setCodeForFieldInstruction(
@@ -273,6 +291,30 @@
.build());
}
+ private void setCodeForCheckCast(
+ SyntheticMethodBuilder methodBuilder, CfCheckCast instruction, DexItemFactory factory) {
+ DexClass target = appView.definitionFor(instruction.getType());
+ assert target != null;
+ methodBuilder
+ .setProto(factory.createProto(target.getType(), objectParams))
+ .setCode(
+ m ->
+ CheckCastSourceCode.create(appView, m.getHolderType(), target.getType())
+ .generateCfCode());
+ }
+
+ private void setCodeForInstanceOf(
+ SyntheticMethodBuilder methodBuilder, CfInstanceOf instruction, DexItemFactory factory) {
+ DexClass target = appView.definitionFor(instruction.getType());
+ assert target != null;
+ methodBuilder
+ .setProto(factory.createProto(factory.booleanType, objectParams))
+ .setCode(
+ m ->
+ InstanceOfSourceCode.create(appView, m.getHolderType(), target.getType())
+ .generateCfCode());
+ }
+
private boolean verifyLibraryHolderAndInvoke(
DexClass libraryHolder, DexMethod apiMethod, boolean isVirtualInvoke) {
DexEncodedMethod libraryApiMethodDefinition = libraryHolder.lookupMethod(apiMethod);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 69d51be..0db211d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
@@ -153,6 +154,10 @@
return false;
}
+ if (canHaveIssuesWithMonitors(singleTarget, method)) {
+ return false;
+ }
+
// We should never even try to inline something that is processed concurrently. It can lead
// to non-deterministic behaviour as the inlining IR could be built from either original output
// or optimized code. Right now this happens for the class class staticizer, as it just
@@ -199,6 +204,22 @@
return true;
}
+ private boolean canHaveIssuesWithMonitors(ProgramMethod singleTarget, ProgramMethod context) {
+ if (appView.options().canHaveIssueWithInlinedMonitors()) {
+ if (hasMonitorsOrIsSynchronized(singleTarget.getDefinition())) {
+ if (context.getOptimizationInfo().forceInline()
+ || hasMonitorsOrIsSynchronized(context.getDefinition())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static boolean hasMonitorsOrIsSynchronized(DexEncodedMethod definition) {
+ return definition.isSynchronized() || definition.getCode().hasMonitorInstructions();
+ }
+
public boolean satisfiesRequirementsForSimpleInlining(InvokeMethod invoke, ProgramMethod target) {
// Code size modified by inlining, so only read for non-concurrent methods.
boolean deterministic = !methodProcessor.isProcessedConcurrently(target);
@@ -303,6 +324,17 @@
return null;
}
+ // Ensure that we don't introduce several monitors in the same method on old device that can
+ // choke on this. If a context is forceinline, e.g., from class merging, don't ever inline
+ // monitors, since that may conflict with a similar other constructor.
+ if (appView.options().canHaveIssueWithInlinedMonitors()) {
+ if (hasMonitorsOrIsSynchronized(singleTarget.getDefinition())
+ && (context.getOptimizationInfo().forceInline()
+ || code.metadata().mayHaveMonitorInstruction())) {
+ return null;
+ }
+ }
+
InlineAction action =
invoke.computeInlining(
singleTarget, reason, this, classInitializationAnalysis, whyAreYouNotInliningReporter);
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/CheckCastSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/CheckCastSourceCode.java
new file mode 100644
index 0000000..32fe7c5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/CheckCastSourceCode.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, 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.ir.synthetic;
+
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+
+// Source code representing a simple call to CheckCast.
+public final class CheckCastSourceCode extends SyntheticCfCodeProvider {
+
+ private final DexType checkCastType;
+
+ private CheckCastSourceCode(AppView<?> appView, DexType holder, DexType checkCastType) {
+ super(appView, holder);
+ this.checkCastType = checkCastType;
+ }
+
+ public static CheckCastSourceCode create(
+ AppView<?> appView, DexType holder, DexType checkCastType) {
+ return new CheckCastSourceCode(appView, holder, checkCastType);
+ }
+
+ @Override
+ public CfCode generateCfCode() {
+ List<CfInstruction> instructions = new ArrayList<>();
+ instructions.add(new CfLoad(ValueType.OBJECT, 0));
+ instructions.add(new CfCheckCast(checkCastType));
+ instructions.add(new CfReturn(ValueType.OBJECT));
+ return standardCfCodeFromInstructions(instructions);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java
new file mode 100644
index 0000000..57257ff
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/InstanceOfSourceCode.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2022, 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.ir.synthetic;
+
+import com.android.tools.r8.cf.code.CfInstanceOf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+
+// Source code representing a simple call to CheckCast.
+public final class InstanceOfSourceCode extends SyntheticCfCodeProvider {
+
+ private final DexType instanceOfType;
+
+ private InstanceOfSourceCode(AppView<?> appView, DexType holder, DexType instanceOfType) {
+ super(appView, holder);
+ this.instanceOfType = instanceOfType;
+ }
+
+ public static InstanceOfSourceCode create(
+ AppView<?> appView, DexType holder, DexType checkCastType) {
+ return new InstanceOfSourceCode(appView, holder, checkCastType);
+ }
+
+ @Override
+ public CfCode generateCfCode() {
+ List<CfInstruction> instructions = new ArrayList<>();
+ instructions.add(new CfLoad(ValueType.OBJECT, 0));
+ instructions.add(new CfInstanceOf(instanceOfType));
+ instructions.add(new CfReturn(ValueType.INT));
+ return standardCfCodeFromInstructions(instructions);
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
index fcd894d..662f9f4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/shaking/ComputeApiLevelUseRegistry.java
@@ -118,12 +118,12 @@
@Override
public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
- // CheckCast are not causing soft verification issues
+ setMaxApiReferenceLevel(type);
}
@Override
public void registerSafeCheckCast(DexType type) {
- // CheckCast are not causing soft verification issues
+ setMaxApiReferenceLevel(type);
}
@Override
@@ -133,7 +133,7 @@
@Override
public void registerInstanceOf(DexType type) {
- // InstanceOf are not causing soft verification issues
+ setMaxApiReferenceLevel(type);
}
@Override
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 59c8d20..2aa20c2 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -423,7 +423,7 @@
return factory.createType(createDescriptor(separator, kind, externalSyntheticTypePrefix, id));
}
- private static String createDescriptor(
+ public static String createDescriptor(
String separator, SyntheticKind kind, String externalSyntheticTypePrefix, String id) {
return DescriptorUtils.getDescriptorFromClassBinaryName(
externalSyntheticTypePrefix + separator + kind.descriptor + id);
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index b4099ee..3b87985 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2824,4 +2824,10 @@
// TODO(b/246679983): Turned off while diagnosing b/246679983.
return false && isGeneratingDex() && minApiLevel.isGreaterThanOrEqualTo(AndroidApiLevel.L);
}
+
+ // b/238399429 Some art 6 vms have issues with multiple monitors in the same method
+ // Don't inline code with monitors into methods that already have monitors.
+ public boolean canHaveIssueWithInlinedMonitors() {
+ return canHaveBugPresentUntil(AndroidApiLevel.N);
+ }
}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
index f946a84..75e1da7 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelFieldTypeReferenceTest.java
@@ -51,14 +51,20 @@
.apply(
addTracedApiReferenceLevelCallBack(
(method, apiLevel) -> {
- if (Reference.methodFromMethod(readApiField).equals(method)
- || Reference.methodFromMethod(setApiField).equals(method)) {
+ if (Reference.methodFromMethod(readApiField).equals(method)) {
if (parameters.isCfRuntime()) {
assertEquals(AndroidApiLevel.B, apiLevel);
} else {
assertEquals(AndroidApiLevel.B.max(parameters.getApiLevel()), apiLevel);
}
}
+ if (Reference.methodFromMethod(setApiField).equals(method)) {
+ if (parameters.isCfRuntime()) {
+ assertEquals(AndroidApiLevel.L_MR1, apiLevel);
+ } else {
+ assertEquals(AndroidApiLevel.L_MR1.max(parameters.getApiLevel()), apiLevel);
+ }
+ }
}))
.compile()
.addRunClasspathClasses(Api.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
index 73132ae..2a0d763 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelInlineMethodWithApiTypeTest.java
@@ -5,8 +5,8 @@
package com.android.tools.r8.apimodel;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -38,7 +38,6 @@
@Test
public void testR8() throws Exception {
Method apiCallerApiLevel22 = ApiCaller.class.getDeclaredMethod("apiLevel22");
- Method apiCallerCallerApiLevel22 = ApiCallerCaller.class.getDeclaredMethod("apiLevel22");
testForR8(parameters.getBackend())
.addProgramClasses(ApiCaller.class, ApiCallerCaller.class, OtherCaller.class, Main.class)
.addLibraryClasses(ApiType.class)
@@ -55,8 +54,14 @@
.assertSuccessWithOutputLines(ApiType.class.getName())
.inspect(
inspector -> {
- verifyThat(inspector, parameters, apiCallerApiLevel22)
- .inlinedInto(apiCallerCallerApiLevel22);
+ assertThat(
+ inspector.method(apiCallerApiLevel22),
+ notIf(
+ isPresent(),
+ parameters.isDexRuntime()
+ && parameters
+ .getApiLevel()
+ .isGreaterThanOrEqualTo(AndroidApiLevel.L_MR1)));
assertThat(inspector.clazz(OtherCaller.class), not(isPresent()));
});
}
@@ -68,7 +73,7 @@
public static ApiType apiLevel22() throws Exception {
// The reflective call here is to ensure that the setting of A's api level is not based on
- // a method reference to `Api` and only because of the type reference in the field `api`.
+ // a method reference to `Api` and only because of the type reference in the checkcast.
Class<?> reflectiveCall =
Class.forName(
"com.android.tools.r8.apimodel.ApiModelInlineMethodWithApiTypeTest_ApiType"
@@ -82,7 +87,7 @@
public static void apiLevel22() throws Exception {
// This is referencing the proto of ApiCaller.foo and thus have a reference to ApiType. It is
- // therefore OK to inline ApiCaller.foo() into ApiCallerCaller.bar().
+ // therefore OK to inline ApiCaller.apiLevel22() into ApiCallerCaller.apiLevel22().
System.out.println(ApiCaller.apiLevel22().getClass().getName());
}
}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
index 53b4942..5df420d 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMethodTypeReferenceTest.java
@@ -51,14 +51,20 @@
.apply(
addTracedApiReferenceLevelCallBack(
(method, apiLevel) -> {
- if (Reference.methodFromMethod(readApi).equals(method)
- || Reference.methodFromMethod(setApi).equals(method)) {
+ if (Reference.methodFromMethod(readApi).equals(method)) {
if (parameters.isCfRuntime()) {
assertEquals(AndroidApiLevel.B, apiLevel);
} else {
assertEquals(AndroidApiLevel.B.max(parameters.getApiLevel()), apiLevel);
}
}
+ if (Reference.methodFromMethod(setApi).equals(method)) {
+ if (parameters.isCfRuntime()) {
+ assertEquals(AndroidApiLevel.L_MR1, apiLevel);
+ } else {
+ assertEquals(AndroidApiLevel.L_MR1.max(parameters.getApiLevel()), apiLevel);
+ }
+ }
}))
.compile()
.addRunClasspathClasses(Api.class)
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
index 22a7321..dec0a00 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelNoInliningOfHigherApiLevelInterfaceTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.apimodel;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
@@ -48,6 +49,7 @@
.enableConstantArgumentAnnotations()
.enableInliningAnnotations()
.enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForClass(Api.class, AndroidApiLevel.L_MR1))
.apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
.apply(ApiModelingTestHelper::enableApiCallerIdentification)
// We are testing that we do not inline/merge higher api-levels
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
new file mode 100644
index 0000000..663a649
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineCheckCastTest.java
@@ -0,0 +1,171 @@
+// Copyright (c) 2022, 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+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 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 ApiModelOutlineCheckCastTest extends TestBase {
+
+ private static final AndroidApiLevel classApiLevel = AndroidApiLevel.M;
+
+ private static final String[] EXPECTED = new String[] {"LibraryClass::foo"};
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+ testBuilder
+ .addLibraryClasses(LibraryClass.class, LibraryProvider.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .addProgramClasses(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .addAndroidBuildVersion(getApiLevelForRuntime())
+ .apply(setMockApiLevelForClass(LibraryProvider.class, AndroidApiLevel.B))
+ .apply(
+ setMockApiLevelForMethod(
+ LibraryProvider.class.getDeclaredMethod("getObject", boolean.class),
+ AndroidApiLevel.B))
+ .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+ .apply(setMockApiLevelForMethod(LibraryClass.class.getDeclaredMethod("foo"), classApiLevel))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+ .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+ }
+
+ public AndroidApiLevel getApiLevelForRuntime() {
+ return parameters.isCfRuntime()
+ ? AndroidApiLevel.B
+ : parameters.getRuntime().asDex().maxSupportedApiLevel();
+ }
+
+ public boolean addToBootClasspath() {
+ return getApiLevelForRuntime().isGreaterThanOrEqualTo(classApiLevel);
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+ testForJvm()
+ .addProgramClasses(Main.class)
+ .addAndroidBuildVersion(parameters.getApiLevel())
+ .addLibraryClasses(LibraryProvider.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testD8Debug() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .setMode(CompilationMode.DEBUG)
+ .apply(this::setupTestBuilder)
+ .compile()
+ .inspect(this::inspect)
+ .applyIf(
+ addToBootClasspath(),
+ b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+ b -> b.addBootClasspathClasses(LibraryProvider.class))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testD8Release() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .setMode(CompilationMode.RELEASE)
+ .apply(this::setupTestBuilder)
+ .compile()
+ .inspect(this::inspect)
+ .applyIf(
+ addToBootClasspath(),
+ b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+ b -> b.addBootClasspathClasses(LibraryProvider.class))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .apply(this::setupTestBuilder)
+ .addKeepMainRule(Main.class)
+ .compile()
+ .inspect(this::inspect)
+ .applyIf(
+ addToBootClasspath(),
+ b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+ b -> b.addBootClasspathClasses(LibraryProvider.class))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ private void inspect(CodeInspector inspector) throws Exception {
+ verifyThat(inspector, parameters, LibraryClass.class)
+ .hasCheckCastOutlinedFromUntil(Main.class.getMethod("main", String[].class), classApiLevel);
+ }
+
+ private void checkOutput(SingleTestRunResult<?> runResult) {
+ if (addToBootClasspath()) {
+ runResult.assertSuccessWithOutputLines(EXPECTED);
+ } else {
+ runResult.assertSuccessWithOutputLines("Not calling foo on object");
+ }
+ }
+
+ // Only present from api 23.
+ public static class LibraryClass {
+
+ public void foo() {
+ System.out.println("LibraryClass::foo");
+ }
+ }
+
+ public static class LibraryProvider {
+
+ public static Object getObject(boolean hasApiLevel) {
+ if (hasApiLevel) {
+ return new LibraryClass();
+ } else {
+ return new Object();
+ }
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Object object = LibraryProvider.getObject(AndroidBuildVersion.VERSION >= 23);
+ if (AndroidBuildVersion.VERSION >= 23) {
+ ((LibraryClass) object).foo();
+ } else {
+ System.out.println("Not calling foo on object");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceOfTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceOfTest.java
new file mode 100644
index 0000000..63e03a6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelOutlineInstanceOfTest.java
@@ -0,0 +1,166 @@
+// Copyright (c) 2022, 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.apimodel;
+
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+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 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 ApiModelOutlineInstanceOfTest extends TestBase {
+
+ private static final AndroidApiLevel classApiLevel = AndroidApiLevel.M;
+
+ private static final String[] EXPECTED = new String[] {"true"};
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+ testBuilder
+ .addLibraryClasses(LibraryClass.class, LibraryProvider.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .addProgramClasses(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .addAndroidBuildVersion(getApiLevelForRuntime())
+ .apply(setMockApiLevelForClass(LibraryProvider.class, AndroidApiLevel.B))
+ .apply(
+ setMockApiLevelForMethod(
+ LibraryProvider.class.getDeclaredMethod("getObject", boolean.class),
+ AndroidApiLevel.B))
+ .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .apply(ApiModelingTestHelper::enableOutliningOfMethods)
+ .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+ }
+
+ public AndroidApiLevel getApiLevelForRuntime() {
+ return parameters.isCfRuntime()
+ ? AndroidApiLevel.B
+ : parameters.getRuntime().asDex().maxSupportedApiLevel();
+ }
+
+ public boolean addToBootClasspath() {
+ return getApiLevelForRuntime().isGreaterThanOrEqualTo(classApiLevel);
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+ testForJvm()
+ .addProgramClasses(Main.class)
+ .addAndroidBuildVersion(parameters.getApiLevel())
+ .addLibraryClasses(LibraryProvider.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testD8Debug() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .setMode(CompilationMode.DEBUG)
+ .apply(this::setupTestBuilder)
+ .compile()
+ .inspect(this::inspect)
+ .applyIf(
+ addToBootClasspath(),
+ b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+ b -> b.addBootClasspathClasses(LibraryProvider.class))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testD8Release() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .setMode(CompilationMode.RELEASE)
+ .apply(this::setupTestBuilder)
+ .compile()
+ .inspect(this::inspect)
+ .applyIf(
+ addToBootClasspath(),
+ b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+ b -> b.addBootClasspathClasses(LibraryProvider.class))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .apply(this::setupTestBuilder)
+ .addKeepMainRule(Main.class)
+ .compile()
+ .inspect(this::inspect)
+ .applyIf(
+ addToBootClasspath(),
+ b -> b.addBootClasspathClasses(LibraryClass.class, LibraryProvider.class),
+ b -> b.addBootClasspathClasses(LibraryProvider.class))
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::checkOutput);
+ }
+
+ private void inspect(CodeInspector inspector) throws Exception {
+ verifyThat(inspector, parameters, LibraryClass.class)
+ .hasInstanceOfOutlinedFromUntil(
+ Main.class.getMethod("main", String[].class), classApiLevel);
+ }
+
+ private void checkOutput(SingleTestRunResult<?> runResult) {
+ if (addToBootClasspath()) {
+ runResult.assertSuccessWithOutputLines(EXPECTED);
+ } else {
+ runResult.assertSuccessWithOutputLines("Not checking instance of");
+ }
+ }
+
+ // Only present from api 23.
+ public static class LibraryClass {}
+
+ public static class LibraryProvider {
+
+ public static Object getObject(boolean hasApiLevel) {
+ if (hasApiLevel) {
+ return new LibraryClass();
+ } else {
+ return new Object();
+ }
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ Object object = LibraryProvider.getObject(AndroidBuildVersion.VERSION >= 23);
+ if (AndroidBuildVersion.VERSION >= 23) {
+ System.out.println(object instanceof LibraryClass);
+ } else {
+ System.out.println("Not checking instance of");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
index 79df3a4..8d59924 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelTypeReferenceInvokeTest.java
@@ -6,7 +6,8 @@
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
@@ -34,8 +35,6 @@
@Test
public void testR8() throws Exception {
Method apiMethod = LibraryClass.class.getDeclaredMethod("apiMethod");
- Method apiCaller = ApiHelper.class.getDeclaredMethod("apiCaller", LibraryClass.class);
- Method apiCallerCaller = Main.class.getDeclaredMethod("typeReference", Object.class);
boolean libraryClassOnBoot =
parameters.isDexRuntime()
&& parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.M);
@@ -53,10 +52,7 @@
.enableInliningAnnotations()
.addAndroidBuildVersion()
.compile()
- .inspect(
- inspector ->
- verifyThat(inspector, parameters, apiCaller)
- .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.M))
+ .inspect(inspector -> assertThat(inspector.clazz(ApiHelper.class), isAbsent()))
.addRunClasspathClasses(LibraryClass.class)
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithOutputLinesIf(
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 20d345a..e437943 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -5,6 +5,8 @@
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.containsCheckCast;
+import static com.android.tools.r8.utils.codeinspector.CodeMatchers.containsInstanceOf;
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithHolderAndName;
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethodWithName;
@@ -18,9 +20,11 @@
import com.android.tools.r8.TestCompilerBuilder;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.ThrowableConsumer;
+import com.android.tools.r8.references.ClassReference;
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.synthesis.SyntheticItemsTestUtils;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.CodeMatchers;
@@ -258,6 +262,86 @@
parameters.isCfRuntime()
|| parameters.getApiLevel().isGreaterThanOrEqualTo(finalApiLevel)));
}
+
+ void hasCheckCastOutlinedFromUntil(Method method, AndroidApiLevel apiLevel) {
+ if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(apiLevel)) {
+ hasCheckCastOutlinedFrom(method);
+ } else {
+ hasNotCheckCastOutlinedFrom(method);
+ }
+ }
+
+ public void hasCheckCastOutlinedFrom(Method method) {
+ // Check that we call is in a synthetic class with a check cast
+ ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
+ List<FoundMethodSubject> outlinedMethod =
+ inspector.allClasses().stream()
+ .filter(
+ clazz ->
+ clazz
+ .getOriginalName()
+ .startsWith(
+ SyntheticItemsTestUtils.syntheticApiOutlineClassPrefix(
+ method.getDeclaringClass())))
+ .flatMap(clazz -> clazz.allMethods().stream())
+ .filter(
+ methodSubject ->
+ methodSubject.isSynthetic()
+ && containsCheckCast(classOfInterestReference).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)));
+ }
+
+ public void hasNotCheckCastOutlinedFrom(Method method) {
+ ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
+ MethodSubject caller = inspector.method(method);
+ assertThat(caller, isPresent());
+ assertThat(caller, containsCheckCast(classOfInterestReference));
+ }
+
+ void hasInstanceOfOutlinedFromUntil(Method method, AndroidApiLevel apiLevel) {
+ if (parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(apiLevel)) {
+ hasInstanceOfOutlinedFrom(method);
+ } else {
+ hasNotInstanceOfOutlinedFrom(method);
+ }
+ }
+
+ public void hasInstanceOfOutlinedFrom(Method method) {
+ // Check that we call is in a synthetic class with an instance of.
+ ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
+ List<FoundMethodSubject> outlinedMethod =
+ inspector.allClasses().stream()
+ .filter(
+ clazz ->
+ clazz
+ .getOriginalName()
+ .startsWith(
+ SyntheticItemsTestUtils.syntheticApiOutlineClassPrefix(
+ method.getDeclaringClass())))
+ .flatMap(clazz -> clazz.allMethods().stream())
+ .filter(
+ methodSubject ->
+ methodSubject.isSynthetic()
+ && containsInstanceOf(classOfInterestReference).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)));
+ }
+
+ public void hasNotInstanceOfOutlinedFrom(Method method) {
+ ClassReference classOfInterestReference = Reference.classFromClass(classOfInterest);
+ MethodSubject caller = inspector.method(method);
+ assertThat(caller, isPresent());
+ assertThat(caller, containsInstanceOf(classOfInterestReference));
+ }
}
public static class ApiModelingFieldVerificationHelper {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineConstructorsWithMonitors.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineConstructorsWithMonitors.java
new file mode 100644
index 0000000..052398d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineConstructorsWithMonitors.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2022, 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.ir.optimize.inliner.sync;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.hasDefaultConstructor;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InlineConstructorsWithMonitors extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ public InlineConstructorsWithMonitors(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InlineConstructorsWithMonitors.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("monitor", "monitor2")
+ .inspect(this::inspect);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(TestClass.class);
+ assertThat(classSubject, isPresent());
+ ClassSubject fooClassSubject = inspector.clazz(Foo.class);
+ ClassSubject barClassSubject = inspector.clazz(Bar.class);
+ if (parameters.isCfRuntime()
+ || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M)) {
+ // On M and below we don't want to merge constructors when both have monitors. See b/238399429
+ assertThat(fooClassSubject, hasDefaultConstructor());
+ assertThat(barClassSubject, hasDefaultConstructor());
+ } else {
+ assertTrue(
+ fooClassSubject.uniqueInstanceInitializer().isPresent()
+ || barClassSubject.uniqueInstanceInitializer().isPresent());
+ assertFalse(
+ fooClassSubject.uniqueInstanceInitializer().isPresent()
+ && barClassSubject.uniqueInstanceInitializer().isPresent());
+ }
+ }
+
+ static class Foo {
+ public Foo() {
+ Object o = new Object();
+ synchronized (o) {
+ System.out.println("monitor");
+ }
+ }
+ }
+
+ static class Bar {
+ public Bar() {
+ Object o = new String("Foo");
+ synchronized (o) {
+ System.out.println("monitor2");
+ }
+ }
+ }
+
+ static class TestClass {
+ public static void main(String[] args) {
+ new Foo();
+ new Bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
index 1a20851..569652a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineStaticSynchronizedMethodTest.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.ir.optimize.inliner.sync.InlineStaticSynchronizedMethodTest.TestClass.RunnableImpl;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import java.lang.Thread.State;
@@ -53,8 +54,17 @@
private void verifySynchronizedMethodsAreInlined(CodeInspector inspector) {
ClassSubject classSubject = inspector.clazz(RunnableImpl.class);
assertThat(classSubject, isPresent());
- assertThat(classSubject.uniqueMethodWithOriginalName("m1"), not(isPresent()));
- assertThat(classSubject.uniqueMethodWithOriginalName("m2"), not(isPresent()));
+ // On M we are seeing issues when inlining code with monitors which will trip up some art
+ // vms. See issue b/238399429 for details.
+ if (parameters.isCfRuntime()
+ || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M)) {
+ assertThat(classSubject.uniqueMethodWithOriginalName("m1"), isPresent());
+ assertThat(classSubject.uniqueMethodWithOriginalName("m2"), not(isPresent()));
+
+ } else {
+ assertThat(classSubject.uniqueMethodWithOriginalName("m1"), not(isPresent()));
+ assertThat(classSubject.uniqueMethodWithOriginalName("m2"), not(isPresent()));
+ }
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineWithMonitorInConstructorInline.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineWithMonitorInConstructorInline.java
new file mode 100644
index 0000000..96120c6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlineWithMonitorInConstructorInline.java
@@ -0,0 +1,95 @@
+// Copyright (c) 2022, 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.ir.optimize.inliner.sync;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InlineWithMonitorInConstructorInline extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ public InlineWithMonitorInConstructorInline(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(InlineWithMonitorInConstructorInline.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), TestClass.class)
+ .inspect(this::inspect)
+ .assertSuccessWithOutputLines("foo", "monitor", "bar", "monitor2");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(TestClass.class);
+ assertThat(classSubject, isPresent());
+ ClassSubject utilClassSubject = inspector.clazz(Util.class);
+ if (parameters.isCfRuntime()
+ || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M)) {
+ // We disallow merging methods with monitors into constructors which will be class merged
+ // on pre M devices, see b/238399429
+ assertThat(utilClassSubject, isPresent());
+ } else {
+ assertThat(utilClassSubject, not(isPresent()));
+ }
+ }
+
+ static class Foo {
+ public Foo() {
+ System.out.println("foo");
+ Util.useMonitor(this);
+ }
+ }
+
+ static class Bar {
+ public Bar() {
+ System.out.println("bar");
+ Util.useMonitor2(this);
+ }
+ }
+
+ static class Util {
+ public static void useMonitor(Object object) {
+ synchronized (object) {
+ System.out.println("monitor");
+ }
+ }
+
+ public static void useMonitor2(Object object) {
+ synchronized (object) {
+ System.out.println("monitor2");
+ }
+ }
+ }
+
+ static class TestClass {
+ public static void main(String[] args) {
+ new Foo();
+ new Bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java
index d6c07ca..3ae6d3d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/sync/InlinerMonitorEnterValuesThresholdTest.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
@@ -44,9 +45,8 @@
.addOptionsModification(
options -> options.inlinerOptions().inliningMonitorEnterValuesAllowance = threshold)
.setMinApi(parameters.getApiLevel())
- .compile()
- .inspect(this::inspect)
.run(parameters.getRuntime(), TestClass.class)
+ .inspect(this::inspect)
.assertSuccessWithOutputLines("Hello world!");
}
@@ -54,13 +54,22 @@
ClassSubject classSubject = inspector.clazz(TestClass.class);
assertThat(classSubject, isPresent());
assertThat(classSubject.mainMethod(), isPresent());
- assertThat(classSubject.uniqueMethodWithOriginalName("m1"), not(isPresent()));
- assertThat(classSubject.uniqueMethodWithOriginalName("m2"), not(isPresent()));
- if (threshold == 2) {
+ // On M we are seeing issues when inlining code with monitors which will trip up some art
+ // vms. See issue b/238399429 for details.
+ if (parameters.isCfRuntime()
+ || parameters.getApiLevel().isLessThanOrEqualTo(AndroidApiLevel.M)) {
+ assertThat(classSubject.uniqueMethodWithOriginalName("m1"), isPresent());
+ assertThat(classSubject.uniqueMethodWithOriginalName("m2"), isPresent());
assertThat(classSubject.uniqueMethodWithOriginalName("m3"), isPresent());
} else {
- assert threshold == 3;
- assertThat(classSubject.uniqueMethodWithOriginalName("m3"), not(isPresent()));
+ assertThat(classSubject.uniqueMethodWithOriginalName("m1"), not(isPresent()));
+ assertThat(classSubject.uniqueMethodWithOriginalName("m2"), not(isPresent()));
+ if (threshold == 2) {
+ assertThat(classSubject.uniqueMethodWithOriginalName("m3"), isPresent());
+ } else {
+ assert threshold == 3;
+ assertThat(classSubject.uniqueMethodWithOriginalName("m3"), not(isPresent()));
+ }
}
}
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index c5bb96f..ff769a2 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.synthesis;
+import static com.android.tools.r8.synthesis.SyntheticNaming.EXTERNAL_SYNTHETIC_CLASS_SEPARATOR;
import static org.hamcrest.CoreMatchers.containsString;
import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting;
@@ -70,6 +71,12 @@
return syntheticClass(classReference, naming.API_MODEL_OUTLINE, id);
}
+ public static String syntheticApiOutlineClassPrefix(Class<?> clazz) {
+ return clazz.getTypeName()
+ + EXTERNAL_SYNTHETIC_CLASS_SEPARATOR
+ + naming.API_MODEL_OUTLINE.getDescriptor();
+ }
+
public static ClassReference syntheticBackportClass(Class<?> clazz, int id) {
return syntheticClass(clazz, naming.BACKPORT, id);
}
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 e4f68c1..70e7eab 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
@@ -5,6 +5,7 @@
package com.android.tools.r8.utils.codeinspector;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.utils.MethodReferenceUtils;
@@ -71,6 +72,56 @@
};
}
+ public static Matcher<MethodSubject> containsCheckCast(ClassReference classReference) {
+ return new TypeSafeMatcher<MethodSubject>() {
+ @Override
+ protected boolean matchesSafely(MethodSubject subject) {
+ return subject.isPresent()
+ && subject.getMethod().hasCode()
+ && subject
+ .streamInstructions()
+ .anyMatch(
+ instructionSubject ->
+ instructionSubject.isCheckCast(classReference.getTypeName()));
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("contains checkcast");
+ }
+
+ @Override
+ public void describeMismatchSafely(final MethodSubject subject, Description description) {
+ description.appendText("method did not");
+ }
+ };
+ }
+
+ public static Matcher<MethodSubject> containsInstanceOf(ClassReference classReference) {
+ return new TypeSafeMatcher<MethodSubject>() {
+ @Override
+ protected boolean matchesSafely(MethodSubject subject) {
+ return subject.isPresent()
+ && subject.getMethod().hasCode()
+ && subject
+ .streamInstructions()
+ .anyMatch(
+ instructionSubject ->
+ instructionSubject.isInstanceOf(classReference.getTypeName()));
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("contains instanceof");
+ }
+
+ @Override
+ public void describeMismatchSafely(final MethodSubject subject, Description description) {
+ description.appendText("method did not");
+ }
+ };
+ }
+
public static Matcher<MethodSubject> instantiatesClass(Class<?> clazz) {
return instantiatesClass(clazz.getTypeName());
}