blob: cb6c812960561478525eb52bb37acc68d40b9bec [file] [log] [blame]
// 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.cf.varhandle;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.JdkClassFileProvider;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.examples.jdk9.VarHandle;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
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.IntBox;
import com.android.tools.r8.utils.ZipUtils;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
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 abstract class VarHandleDesugaringTestBase extends TestBase {
@Parameter(0)
public TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters()
.withCfRuntimesStartingFromIncluding(CfVm.JDK9)
// Running on 4.0.4 and 4.4.4 needs to be checked. Output seems correct, but at the
// same time there are VFY errors on stderr.
.withDexRuntimesStartingFromExcluding(Version.V4_4_4)
.withAllApiLevels()
.build();
}
protected abstract String getMainClass();
protected List<String> getKeepRules() {
return ImmutableList.of("");
}
protected abstract List<String> getJarEntries();
protected abstract String getExpectedOutputForReferenceImplementation();
protected String getExpectedOutputForDesugaringImplementation() {
return getExpectedOutputForReferenceImplementation();
}
protected String getExpectedOutputForArtImplementation() {
return getExpectedOutputForReferenceImplementation();
}
@Test
public void testReference() throws Throwable {
// The tests for weakCompareAndSet might fail on the JVM, as the tests do not account for
// possible spurious failures but expect it to behave like compareAndSet (which is what the
// desugared implementation does.
assumeTrue(parameters.isCfRuntime() && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK9));
testForJvm()
.addProgramFiles(VarHandle.jar())
.run(parameters.getRuntime(), getMainClass())
.assertSuccessWithOutput(getExpectedOutputForReferenceImplementation());
}
private List<byte[]> getProgramClassFileData() {
return getJarEntries().stream()
.map(
entry -> {
try {
return ZipUtils.readSingleEntry(VarHandle.jar(), entry);
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
}
private boolean willDesugarVarHandle() {
return parameters.isDexRuntime() && parameters.getApiLevel().isLessThan(AndroidApiLevel.T);
}
private void inspect(CodeInspector inspector) {
IntBox unsafeCompareAndSwapInt = new IntBox();
IntBox unsafeCompareAndSwapLong = new IntBox();
IntBox unsafeCompareAndSwapObject = new IntBox();
DexString compareAndSwapInt = inspector.getFactory().createString("compareAndSwapInt");
DexString compareAndSwapLong = inspector.getFactory().createString("compareAndSwapLong");
DexString compareAndSwapObject = inspector.getFactory().createString("compareAndSwapObject");
// Right now we only expect one backport coming out of DesugarVarHandle - the backport with
// forwarding of Unsafe.compareAndSwapObject.
MethodReference firstBackportFromDesugarVarHandle =
SyntheticItemsTestUtils.syntheticBackportWithForwardingMethod(
Reference.classFromDescriptor("Lcom/android/tools/r8/DesugarVarHandle;"),
0,
Reference.method(
Reference.classFromDescriptor("Lsun/misc/Unsafe;"),
"compareAndSwapObject",
ImmutableList.of(
Reference.typeFromDescriptor("Ljava/lang/Object;"),
Reference.LONG,
Reference.typeFromDescriptor("Ljava/lang/Object;"),
Reference.typeFromDescriptor("Ljava/lang/Object;")),
Reference.BOOL));
inspector.forAllClasses(
clazz -> {
clazz.forAllMethods(
method -> {
method
.instructions()
.forEach(
instruction -> {
if (instruction.isInvoke()) {
DexMethod target = instruction.getMethod();
if (target.getHolderType() == inspector.getFactory().unsafeType) {
if (target.getName() == compareAndSwapInt
|| target.getName() == compareAndSwapLong) {
// All compareAndSwapInt and compareAndSwapLong stay on
// DesugarVarHandle.
assertSame(
clazz.getDexProgramClass().getType(),
inspector.getFactory().desugarVarHandleType);
unsafeCompareAndSwapInt.incrementIf(
target.getName() == compareAndSwapInt);
unsafeCompareAndSwapLong.incrementIf(
target.getName() == compareAndSwapLong);
} else if (target.getName() == compareAndSwapObject) {
// compareAndSwapObject is not on DesugarVarHandle - it must be
// backported.
assertNotSame(
clazz.getDexProgramClass().getType(),
inspector.getFactory().desugarVarHandleType);
assertEquals(
clazz.getFinalReference(),
firstBackportFromDesugarVarHandle.getHolderClass());
assertEquals(1, clazz.allMethods().size());
assertEquals(
firstBackportFromDesugarVarHandle,
clazz.allMethods().iterator().next().getFinalReference());
unsafeCompareAndSwapObject.increment();
}
}
}
});
});
});
if (willDesugarVarHandle()) {
assertEquals(8, unsafeCompareAndSwapInt.get());
assertEquals(10, unsafeCompareAndSwapLong.get());
assertEquals(1, unsafeCompareAndSwapObject.get());
} else {
assertEquals(0, unsafeCompareAndSwapInt.get());
assertEquals(0, unsafeCompareAndSwapLong.get());
assertEquals(0, unsafeCompareAndSwapObject.get());
}
}
// TODO(b/247076137: Also turn on VarHandle desugaring for R8 tests.
@Test
public void testD8() throws Throwable {
assumeTrue(parameters.isDexRuntime());
testForD8(parameters.getBackend())
.addProgramClassFileData(getProgramClassFileData())
.setMinApi(parameters.getApiLevel())
.addOptionsModification(options -> options.enableVarHandleDesugaring = true)
.run(parameters.getRuntime(), getMainClass())
.applyIf(
parameters.isDexRuntime()
&& parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V4_4_4),
// TODO(b/247076137): Running on 4.0.4 and 4.4.4 needs to be checked. Output seems
// correct, but at the same time there are VFY errors on stderr.
r -> r.assertFailureWithErrorThatThrows(NoSuchFieldException.class),
r ->
r.assertSuccessWithOutput(
parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.T)
&& parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V13_0_0)
? getExpectedOutputForArtImplementation()
: getExpectedOutputForDesugaringImplementation()))
.inspect(this::inspect);
}
@Test
public void testR8() throws Throwable {
testForR8(parameters.getBackend())
.applyIf(
parameters.isDexRuntime(),
// Use android.jar from Android T to get the VarHandle type.
b -> b.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.T)),
// Use system JDK to have references types including StringConcatFactory.
b -> b.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()))
.addProgramClassFileData(getProgramClassFileData())
.addOptionsModification(options -> options.enableVarHandleDesugaring = true)
.setMinApi(parameters.getApiLevel())
.addKeepMainRule(getMainClass())
.addKeepRules(getKeepRules())
.run(parameters.getRuntime(), getMainClass())
.applyIf(
parameters.isDexRuntime()
&& parameters.asDexRuntime().getVersion().isOlderThanOrEqual(Version.V4_4_4),
// TODO(b/247076137): Running on 4.0.4 and 4.4.4 needs to be checked. Output seems
// correct, but at the same time there are VFY errors on stderr.
r -> r.assertFailureWithErrorThatThrows(NoSuchFieldException.class),
r ->
r.assertSuccessWithOutput(
parameters.isDexRuntime()
&& parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.T)
&& parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V13_0_0)
? getExpectedOutputForArtImplementation()
: (parameters.isDexRuntime()
? getExpectedOutputForDesugaringImplementation()
: getExpectedOutputForReferenceImplementation())));
}
}