| // 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 twr.twrcloseresourceduplication; | |
| import static org.junit.Assert.assertEquals; | |
| 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.references.Reference; | |
| import com.android.tools.r8.utils.AndroidApiLevel; | |
| import com.android.tools.r8.utils.InternalOptions; | |
| import com.android.tools.r8.utils.StringUtils; | |
| import com.android.tools.r8.utils.ZipUtils; | |
| import com.android.tools.r8.utils.codeinspector.FoundClassSubject; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableSet; | |
| import java.io.IOException; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Set; | |
| 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; | |
| import twr.twrcloseresourceduplication.asm.TwrCloseResourceDuplication$BarDump; | |
| import twr.twrcloseresourceduplication.asm.TwrCloseResourceDuplication$FooDump; | |
| import twr.twrcloseresourceduplication.asm.TwrCloseResourceDuplicationDump; | |
| @RunWith(Parameterized.class) | |
| public class TwrCloseResourceDuplicationTest extends TestBase { | |
| protected static final String MAIN = | |
| "twr.twrcloseresourceduplication.TwrCloseResourceDuplication"; | |
| protected static final String FOO = | |
| "twr.twrcloseresourceduplication.TwrCloseResourceDuplication$Foo"; | |
| protected static final String BAR = | |
| "twr.twrcloseresourceduplication.TwrCloseResourceDuplication$Bar"; | |
| static final int INPUT_CLASSES = 3; | |
| protected static final String EXPECTED = | |
| StringUtils.lines( | |
| "foo opened 1", | |
| "foo post close 1", | |
| "foo opened 2", | |
| "foo caught from 2: RuntimeException", | |
| "foo post close 2", | |
| "bar opened 1", | |
| "bar post close 1", | |
| "bar opened 2", | |
| "bar caught from 2: RuntimeException", | |
| "bar post close 2"); | |
| @Parameter(0) | |
| public TestParameters parameters; | |
| @Parameters(name = "{0}") | |
| public static TestParametersCollection data() { | |
| return getTestParameters() | |
| .withCfRuntimesStartingFromIncluding(CfVm.JDK9) | |
| .withDexRuntimes() | |
| .withAllApiLevelsAlsoForCf() | |
| .build(); | |
| } | |
| protected boolean hasTwrCloseResourceSupport(boolean isDesugaring) { | |
| return !isDesugaring | |
| || parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelWithTwrCloseResourceSupport()); | |
| } | |
| protected boolean hasTwrCloseResourceApiOutlines() { | |
| return parameters.isDexRuntime() | |
| && parameters.getApiLevel().isLessThan(apiLevelWithTwrCloseResourceSupport()); | |
| } | |
| protected String getZipFile() throws IOException { | |
| return ZipUtils.ZipBuilder.builder(temp.newFile("file.zip").toPath()) | |
| // DEX VMs from 4.4 up-to 9.0 including, will fail if no entry is added. | |
| .addBytes("entry", new byte[1]) | |
| .build() | |
| .toString(); | |
| } | |
| protected static List<byte[]> getProgramInputs() throws Exception { | |
| return ImmutableList.of( | |
| TwrCloseResourceDuplicationDump.dump(), | |
| TwrCloseResourceDuplication$FooDump.dump(), | |
| TwrCloseResourceDuplication$BarDump.dump()); | |
| } | |
| @Test | |
| public void testJvm() throws Exception { | |
| parameters.assumeJvmTestParameters(); | |
| testForJvm(parameters) | |
| .addProgramClassFileData(getProgramInputs()) | |
| .run(parameters.getRuntime(), MAIN, getZipFile()) | |
| .assertSuccessWithOutput(EXPECTED); | |
| } | |
| @Test | |
| public void testD8() throws Exception { | |
| testForD8(parameters.getBackend()) | |
| .addProgramClassFileData(getProgramInputs()) | |
| .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST)) | |
| .setMinApi(parameters) | |
| .run(parameters.getRuntime(), MAIN, getZipFile()) | |
| .assertSuccessWithOutput(EXPECTED) | |
| .inspect( | |
| inspector -> { | |
| // NOTE: The $closeResource helper is _only_ generated by the JDK-9 compiler. | |
| // | |
| // There should be two synthetic classes besides the three program classes. | |
| // One for the desugar version of TWR $closeResource and one for the | |
| // Throwable.addSuppressed that is still present in the original $closeResource. | |
| // TODO(b/214329923): If the original $closeResource is pruned this will decrease. | |
| // TODO(b/168568827): Once we support a nested addSuppressed this will increase. | |
| int expectedSynthetics = 0; | |
| if (!hasTwrCloseResourceSupport(true)) { | |
| expectedSynthetics += 2; | |
| } | |
| if (hasTwrCloseResourceApiOutlines()) { | |
| expectedSynthetics += 1; | |
| } | |
| InternalOptions options = inspector.getApplication().options; | |
| options.setMinApiLevel(parameters.getApiLevel()); | |
| if (options.shouldDesugarAutoCloseable()) { | |
| expectedSynthetics += 3; | |
| } | |
| assertEquals(INPUT_CLASSES + expectedSynthetics, inspector.allClasses().size()); | |
| }); | |
| } | |
| @Test | |
| public void testR8() throws Exception { | |
| parameters.assumeDexRuntime(); | |
| testForR8(parameters.getBackend()) | |
| .addProgramClassFileData(getProgramInputs()) | |
| .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST)) | |
| .addKeepMainRule(MAIN) | |
| .addKeepClassAndMembersRules(FOO, BAR) | |
| .collectSyntheticItems() | |
| .setMinApi(parameters) | |
| .addDontObfuscate() | |
| .compile() | |
| .inspectWithSyntheticItems( | |
| (inspector, syntheticItems) -> { | |
| List<FoundClassSubject> foundClassSubjects = inspector.allClasses(); | |
| Set<String> foundClasses = | |
| foundClassSubjects.stream() | |
| .map(FoundClassSubject::getFinalName) | |
| .collect(Collectors.toSet()); | |
| // R8 will optimize the generated methods for the two cases below where the thrown | |
| // exception is known or not, thus the synthetic methods will be 2. | |
| Set<String> nonSyntheticClassOutput = ImmutableSet.of(FOO, BAR, MAIN); | |
| if (!hasTwrCloseResourceSupport(parameters.isDexRuntime())) { | |
| Set<String> classOutputWithSynthetics = new HashSet<>(nonSyntheticClassOutput); | |
| classOutputWithSynthetics.add( | |
| syntheticItems | |
| .syntheticApiOutlineClass(Reference.classFromTypeName(BAR), 0) | |
| .getTypeName()); | |
| assertEquals(classOutputWithSynthetics, foundClasses); | |
| } else { | |
| Set<String> classOutputWithSynthetics = new HashSet<>(nonSyntheticClassOutput); | |
| if (parameters.getApiLevel().isLessThan(AndroidApiLevel.N)) { | |
| // Above N, the forwarder is inlined in the dispatcher. | |
| classOutputWithSynthetics.add( | |
| syntheticItems | |
| .syntheticAutoCloseableForwarderClass(Reference.classFromTypeName(BAR), 1) | |
| .getTypeName()); | |
| } | |
| if (!parameters.corelibWithExecutorServiceImplementingAutoClosable()) { | |
| classOutputWithSynthetics.add( | |
| syntheticItems | |
| .syntheticAutoCloseableDispatcherClass( | |
| Reference.classFromTypeName(BAR), 0) | |
| .getTypeName()); | |
| } | |
| assertEquals(classOutputWithSynthetics, foundClasses); | |
| } | |
| }) | |
| .run(parameters.getRuntime(), MAIN, getZipFile()) | |
| .assertSuccessWithOutput(EXPECTED); | |
| } | |
| } |