| // Copyright (c) 2018, 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.debug; |
| |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.ToolHelper.DexVm.Version; |
| import com.android.tools.r8.VmTestRunner; |
| import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan; |
| import com.android.tools.r8.jasmin.JasminBuilder; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.android.tools.r8.utils.codeinspector.MethodSubject; |
| import com.google.common.collect.ImmutableList; |
| import java.nio.file.Path; |
| import java.util.Collections; |
| import java.util.List; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TemporaryFolder; |
| import org.junit.runner.RunWith; |
| |
| /** |
| * Test to check that locals that are introduced in a block that is not hit, still start in the |
| * block where they first become visible. |
| * |
| * <p>See b/75251251 or b/78617758 |
| */ |
| @RunWith(VmTestRunner.class) |
| public class LocalsLiveAtBlockEntryDebugTest extends DebugTestBase { |
| |
| final String className = "LocalsLiveAtEntry"; |
| final String sourcefile = className + ".j"; |
| final String methodName = "test"; |
| |
| @Rule |
| public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); |
| |
| @Test |
| public void testCF() throws Throwable { |
| JasminBuilder builder = getBuilderForTest(className, methodName); |
| Path outdir = temp.newFolder().toPath(); |
| builder.writeClassFiles(outdir); |
| CfDebugTestConfig config = new CfDebugTestConfig(); |
| config.addPaths(outdir); |
| runTest(config); |
| } |
| |
| @Test |
| @IgnoreIfVmOlderThan(Version.V7_0_0) |
| public void testD8() throws Throwable { |
| JasminBuilder builder = getBuilderForTest(className, methodName); |
| List<Path> outputs = builder.writeClassFiles(temp.newFolder().toPath()); |
| runTest(new D8DebugTestConfig().compileAndAdd(temp, outputs)); |
| } |
| |
| private void runTest(DebugTestConfig config) throws Throwable { |
| CodeInspector inspector = |
| new CodeInspector( |
| (config instanceof CfDebugTestConfig) |
| ? Collections.singletonList(config.getPaths().get(1).resolve(className + ".class")) |
| : config.getPaths()); |
| ClassSubject clazz = inspector.clazz(className); |
| MethodSubject method = clazz.method("void", methodName, ImmutableList.of("java.lang.Object")); |
| assertTrue(method.isPresent()); |
| runDebugTest( |
| config, |
| className, |
| breakpoint(className, methodName), |
| run(), |
| checkLine(sourcefile, 1), |
| checkNoLocal("obj"), |
| stepOver(), |
| checkLine(sourcefile, 3), |
| checkLocal("obj"), |
| stepOver(), |
| checkLine(sourcefile, 100), |
| run()); |
| } |
| |
| private JasminBuilder getBuilderForTest(String testClassName, String testMethodName) { |
| JasminBuilder builder = new JasminBuilder(); |
| JasminBuilder.ClassBuilder clazz = builder.addClass(testClassName); |
| |
| clazz.addStaticMethod( |
| testMethodName, |
| ImmutableList.of("Ljava/lang/Object;"), |
| "V", |
| ".limit stack 2", |
| ".limit locals 3", |
| ".var 0 is obj L" + testClassName + "; from L1 to L3", |
| "L0:", // Preamble code that does not have any live locals (eg, no formals too!). |
| ".line 1", |
| " ldc 42", |
| " lookupswitch", |
| " 0: L1", |
| " default: L2", |
| "L1:", // late introduction of formals. |
| ".line 2", |
| " aconst_null", |
| " pop", |
| "L2:", // target block with first visible location of locals. |
| ".line 3", |
| " aconst_null", |
| " pop", |
| " return", |
| "L3:"); |
| |
| clazz.addMainMethod( |
| ".limit stack 2", |
| ".limit locals 1", |
| ".line 100", |
| "aconst_null", |
| "invokestatic " + testClassName + "/" + testMethodName + "(Ljava/lang/Object;)V", |
| "return"); |
| |
| return builder; |
| } |
| } |