| // Copyright (c) 2019, 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.canonicalization; |
| |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assume.assumeTrue; |
| |
| import com.android.tools.r8.D8TestRunResult; |
| import com.android.tools.r8.NeverInline; |
| import com.android.tools.r8.NeverPropagateValue; |
| import com.android.tools.r8.R8TestRunResult; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestParametersCollection; |
| import com.android.tools.r8.TestRunResult; |
| import com.android.tools.r8.utils.StringUtils; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.InstructionOffsetSubject; |
| import com.android.tools.r8.utils.codeinspector.InstructionSubject; |
| import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode; |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; |
| import com.android.tools.r8.utils.codeinspector.RangeSubject; |
| import com.android.tools.r8.utils.codeinspector.TryCatchSubject; |
| import com.google.common.collect.Streams; |
| import java.util.Iterator; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| class StringInMonitorTestMain { |
| static final Object lock = new Object(); |
| |
| @NeverPropagateValue private static int foo = 0; |
| |
| @NeverInline |
| private static synchronized void sync() throws OutOfMemoryError { |
| String bar = foo == 0 ? "bar" : ""; |
| if (bar == "") { |
| System.out.println("bar"); |
| throw new OutOfMemoryError(); |
| } |
| } |
| |
| @NeverInline |
| private static void oom() throws OutOfMemoryError { |
| System.out.println("oom"); |
| if (System.currentTimeMillis() > 0) { |
| throw new OutOfMemoryError(); |
| } |
| System.out.println("oom"); |
| System.out.println("this-string-will-not-be-loaded."); |
| System.out.println("this-string-will-not-be-loaded."); |
| } |
| |
| public static void main(String[] args) { |
| try { |
| synchronized (lock) { |
| System.out.println("1st sync"); |
| sync(); |
| oom(); |
| System.out.println("1st sync"); |
| } |
| } catch (OutOfMemoryError oom) { |
| // Pretend to recover from OOM |
| synchronized (lock) { |
| System.out.println("2nd sync"); |
| } |
| } |
| } |
| } |
| |
| @RunWith(Parameterized.class) |
| public class StringInMonitorTest extends TestBase { |
| private static final Class<?> MAIN = StringInMonitorTestMain.class; |
| private static final String JAVA_OUTPUT = StringUtils.lines( |
| "1st sync", |
| "oom", |
| "2nd sync" |
| ); |
| |
| @Parameterized.Parameters(name = "{0}") |
| public static TestParametersCollection data() { |
| return getTestParameters().withAllRuntimes().build(); |
| } |
| |
| private final TestParameters parameters; |
| |
| public StringInMonitorTest(TestParameters parameters) { |
| this.parameters = parameters; |
| } |
| |
| @Test |
| public void testJVMOutput() throws Exception { |
| assumeTrue("Only run JVM reference on CF runtimes", parameters.isCfRuntime()); |
| testForJvm() |
| .addTestClasspath() |
| .run(parameters.getRuntime(), MAIN) |
| .assertSuccessWithOutput(JAVA_OUTPUT); |
| } |
| |
| private void test( |
| TestRunResult result, |
| int expectedConstStringCount1, |
| int expectedConstStringCount2, |
| int expectedConstStringCount3) throws Exception { |
| CodeInspector codeInspector = result.inspector(); |
| ClassSubject mainClass = codeInspector.clazz(MAIN); |
| MethodSubject mainMethod = mainClass.mainMethod(); |
| assertThat(mainMethod, isPresent()); |
| |
| long count = Streams.stream(mainMethod.iterateInstructions( |
| i -> i.isConstString("1st sync", JumboStringMode.ALLOW))).count(); |
| assertEquals(expectedConstStringCount1, count); |
| |
| // TODO(b/122302789): CfInstruction#getOffset() |
| if (parameters.isDexRuntime()) { |
| Iterator<InstructionSubject> constStringIterator = |
| mainMethod.iterateInstructions(i -> i.isConstString(JumboStringMode.ALLOW)); |
| // All const-string's in main(...) should be covered by try (or synthetic catch-all) region. |
| while (constStringIterator.hasNext()) { |
| InstructionSubject constString = constStringIterator.next(); |
| InstructionOffsetSubject offsetSubject = constString.getOffset(mainMethod); |
| // const-string of interest is indirectly covered. See b/122285813 for reference. |
| Iterator<TryCatchSubject> catchAllIterator = |
| mainMethod.iterateTryCatches(TryCatchSubject::hasCatchAll); |
| boolean covered = false; |
| while (catchAllIterator.hasNext()) { |
| RangeSubject tryRange = catchAllIterator.next().getRange(); |
| covered |= tryRange.includes(offsetSubject); |
| if (covered) { |
| break; |
| } |
| } |
| assertTrue(covered); |
| } |
| } |
| |
| MethodSubject sync = mainClass.uniqueMethodWithName("sync"); |
| assertThat(sync, isPresent()); |
| count = Streams.stream(sync.iterateInstructions( |
| i -> i.isConstString("", JumboStringMode.ALLOW))).count(); |
| assertEquals(expectedConstStringCount2, count); |
| |
| // In CF, we don't explicitly add monitor-{enter|exit} and catch-all for synchronized methods. |
| if (parameters.isDexRuntime()) { |
| Iterator<InstructionSubject> constStringIterator = |
| sync.iterateInstructions(i -> i.isConstString(JumboStringMode.ALLOW)); |
| // All const-string's in sync() should be covered by the synthetic catch-all regions. |
| while (constStringIterator.hasNext()) { |
| InstructionSubject constString = constStringIterator.next(); |
| InstructionOffsetSubject offsetSubject = constString.getOffset(mainMethod); |
| Iterator<TryCatchSubject> catchAllIterator = |
| sync.iterateTryCatches(TryCatchSubject::hasCatchAll); |
| boolean covered = false; |
| while (catchAllIterator.hasNext()) { |
| RangeSubject tryRange = catchAllIterator.next().getRange(); |
| covered |= tryRange.includes(offsetSubject); |
| if (covered) { |
| break; |
| } |
| } |
| assertTrue(covered); |
| } |
| } |
| |
| MethodSubject oom = mainClass.uniqueMethodWithName("oom"); |
| assertThat(oom, isPresent()); |
| count = Streams.stream(oom.iterateInstructions( |
| i -> i.isConstString("this-string-will-not-be-loaded.", JumboStringMode.ALLOW))).count(); |
| assertEquals(expectedConstStringCount3, count); |
| } |
| |
| @Test |
| public void testD8() throws Exception { |
| assumeTrue("Only run D8 for Dex backend", parameters.isDexRuntime()); |
| |
| D8TestRunResult result = |
| testForD8() |
| .debug() |
| .addProgramClasses(MAIN) |
| .setMinApi(parameters.getRuntime()) |
| .run(parameters.getRuntime(), MAIN) |
| .assertSuccessWithOutput(JAVA_OUTPUT); |
| test(result, 2, 2, 1); |
| |
| result = |
| testForD8() |
| .release() |
| .addProgramClasses(MAIN) |
| .setMinApi(parameters.getRuntime()) |
| .run(parameters.getRuntime(), MAIN) |
| .assertSuccessWithOutput(JAVA_OUTPUT); |
| test(result, 2, 2, 1); |
| } |
| |
| @Test |
| public void testR8() throws Exception { |
| R8TestRunResult result = |
| testForR8(parameters.getBackend()) |
| .addProgramClasses(MAIN) |
| .enableInliningAnnotations() |
| .enableMemberValuePropagationAnnotations() |
| .addKeepMainRule(MAIN) |
| .setMinApi(parameters.getRuntime()) |
| .run(parameters.getRuntime(), MAIN) |
| .assertSuccessWithOutput(JAVA_OUTPUT); |
| // Due to the different behavior regarding constant canonicalization. |
| int expectedConstStringCount3 = parameters.isCfRuntime() ? 2 : 1; |
| test(result, 2, 2, expectedConstStringCount3); |
| } |
| |
| } |