| // Copyright (c) 2017, 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 com.android.tools.r8.naming.ClassNameMapper.MissingFileAction.MISSING_FILE_IS_EMPTY_MAP; | |
| import com.android.tools.r8.CompilationMode; | |
| import com.android.tools.r8.OutputMode; | |
| import com.android.tools.r8.R8Command; | |
| import com.android.tools.r8.ToolHelper; | |
| import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command; | |
| import com.android.tools.r8.debug.DebugTestConfig.RuntimeKind; | |
| import com.android.tools.r8.origin.Origin; | |
| import com.android.tools.r8.utils.AndroidApiLevel; | |
| import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization; | |
| import com.google.common.collect.ImmutableList; | |
| import java.nio.file.Path; | |
| import java.util.ArrayList; | |
| import java.util.Arrays; | |
| import java.util.Collection; | |
| import java.util.List; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.Parameterized; | |
| import org.junit.runners.Parameterized.Parameters; | |
| /** Tests source file and line numbers on inlined methods. */ | |
| @RunWith(Parameterized.class) | |
| public class DebugInfoWhenInliningTest extends DebugTestBase { | |
| public enum Config { | |
| CF, | |
| DEX_NO_FORCE_JUMBO, | |
| DEX_FORCE_JUMBO | |
| }; | |
| private static final String CLASS_NAME = "Inlining1"; | |
| private static final String SOURCE_FILE = "Inlining1.java"; | |
| private DebugTestConfig makeConfig( | |
| LineNumberOptimization lineNumberOptimization, | |
| boolean writeProguardMap, | |
| RuntimeKind runtimeKind) | |
| throws Exception { | |
| DebugTestConfig config = null; | |
| Path outdir = temp.newFolder().toPath(); | |
| Path outjar = outdir.resolve("r8_compiled.jar"); | |
| R8Command.Builder builder = | |
| R8Command.builder() | |
| .addProgramFiles(DEBUGGEE_JAR) | |
| .setMode(CompilationMode.RELEASE) | |
| .addProguardConfiguration( | |
| ImmutableList.of( | |
| "-keep class " | |
| + CLASS_NAME | |
| + " { public static void main(java.lang.String[]); }"), | |
| Origin.unknown()) | |
| .setDisableMinification(true) | |
| .addProguardConfiguration( | |
| ImmutableList.of("-keepattributes SourceFile,LineNumberTable"), Origin.unknown()); | |
| if (runtimeKind == RuntimeKind.DEX) { | |
| AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm(); | |
| builder | |
| .setMinApiLevel(minSdk.getLevel()) | |
| .addLibraryFiles(ToolHelper.getAndroidJar(minSdk)) | |
| .setOutput(outjar, OutputMode.DexIndexed); | |
| config = new DexDebugTestConfig(outjar); | |
| } else { | |
| assert (runtimeKind == RuntimeKind.CF); | |
| builder | |
| .setOutput(outjar, OutputMode.ClassFile) | |
| .addLibraryFiles(ToolHelper.getJava8RuntimeJar()); | |
| config = new CfDebugTestConfig(outjar); | |
| } | |
| if (writeProguardMap) { | |
| Path proguardMapPath = outdir.resolve("proguard.map"); | |
| builder.setProguardMapOutputPath(proguardMapPath); | |
| config.setProguardMap(proguardMapPath, MISSING_FILE_IS_EMPTY_MAP); | |
| } | |
| ToolHelper.runR8( | |
| builder.build(), options -> { | |
| options.lineNumberOptimization = lineNumberOptimization; | |
| options.testing.forceJumboStringProcessing = forceJumboStringProcessing; | |
| // TODO(b/117848700): Can we make these tests neutral to inlining threshold? | |
| // Also CF needs improvements here. | |
| options.inliningInstructionLimit = runtimeKind == RuntimeKind.CF ? 5 : 4; | |
| }); | |
| return config; | |
| } | |
| private boolean forceJumboStringProcessing; | |
| private RuntimeKind runtimeKind; | |
| @Parameters(name = "config: {0}") | |
| public static Collection<Config> data() { | |
| return Arrays.asList(Config.values()); | |
| } | |
| public DebugInfoWhenInliningTest(Config config) { | |
| this.forceJumboStringProcessing = config == Config.DEX_FORCE_JUMBO; | |
| this.runtimeKind = config == Config.CF ? RuntimeKind.CF : RuntimeKind.DEX; | |
| } | |
| @Test | |
| public void testEachLineNotOptimized() throws Throwable { | |
| // The reason why the not-optimized test contains half as many line numbers as the optimized | |
| // one: | |
| // | |
| // In the Java source (Inlining1) each call is duplicated. Since they end up on the same line | |
| // (innermost callee) the line numbers are actually 7, 7, 32, 32, ... but even if the positions | |
| // are emitted duplicated in the dex code, the debugger stops only when there's a change. | |
| int[] lineNumbers = {7, 32, 11, 7}; | |
| testEachLine(makeConfig(LineNumberOptimization.OFF, false, runtimeKind), lineNumbers); | |
| } | |
| @Test | |
| public void testEachLineNotOptimizedWithMap() throws Throwable { | |
| // The reason why the not-optimized test contains half as many line numbers as the optimized | |
| // one: | |
| // | |
| // In the Java source (Inlining1) each call is duplicated. Since they end up on the same line | |
| // (innermost callee) the line numbers are actually 7, 7, 32, 32, ... but even if the positions | |
| // are emitted duplicated in the dex code, the debugger stops only when there's a change. | |
| int[] lineNumbers = {7, 32, 11, 7}; | |
| testEachLine(makeConfig(LineNumberOptimization.OFF, true, runtimeKind), lineNumbers); | |
| } | |
| @Test | |
| public void testEachLineOptimized() throws Throwable { | |
| int[] lineNumbers = {1, 2, 3, 4, 5, 6, 7, 8}; | |
| testEachLine(makeConfig(LineNumberOptimization.ON, false, runtimeKind), lineNumbers); | |
| } | |
| @Test | |
| public void testEachLineOptimizedWithMap() throws Throwable { | |
| int[] lineNumbers = {7, 7, 32, 32, 11, 11, 7, 7}; | |
| List<List<SignatureAndLine>> inlineFramesList = | |
| ImmutableList.of( | |
| ImmutableList.of( | |
| new SignatureAndLine("void inlineThisFromSameFile()", 7), | |
| new SignatureAndLine("void main(java.lang.String[])", 19)), | |
| ImmutableList.of( | |
| new SignatureAndLine("void inlineThisFromSameFile()", 7), | |
| new SignatureAndLine("void main(java.lang.String[])", 20)), | |
| ImmutableList.of( | |
| new SignatureAndLine("void Inlining2.inlineThisFromAnotherFile()", 32), | |
| new SignatureAndLine("void main(java.lang.String[])", 21)), | |
| ImmutableList.of( | |
| new SignatureAndLine("void Inlining2.inlineThisFromAnotherFile()", 32), | |
| new SignatureAndLine("void main(java.lang.String[])", 22)), | |
| ImmutableList.of( | |
| new SignatureAndLine("void sameFileMultilevelInliningLevel2()", 11), | |
| new SignatureAndLine("void sameFileMultilevelInliningLevel1()", 15), | |
| new SignatureAndLine("void main(java.lang.String[])", 23)), | |
| ImmutableList.of( | |
| new SignatureAndLine("void sameFileMultilevelInliningLevel2()", 11), | |
| new SignatureAndLine("void sameFileMultilevelInliningLevel1()", 15), | |
| new SignatureAndLine("void main(java.lang.String[])", 24)), | |
| ImmutableList.of( | |
| new SignatureAndLine("void Inlining3.differentFileMultilevelInliningLevel2()", 7), | |
| new SignatureAndLine("void Inlining2.differentFileMultilevelInliningLevel1()", 36), | |
| new SignatureAndLine("void main(java.lang.String[])", 25)), | |
| ImmutableList.of( | |
| new SignatureAndLine("void Inlining3.differentFileMultilevelInliningLevel2()", 7), | |
| new SignatureAndLine("void Inlining2.differentFileMultilevelInliningLevel1()", 36), | |
| new SignatureAndLine("void main(java.lang.String[])", 26))); | |
| testEachLine( | |
| makeConfig(LineNumberOptimization.ON, true, runtimeKind), lineNumbers, inlineFramesList); | |
| } | |
| private void testEachLine(DebugTestConfig config, int[] lineNumbers) throws Throwable { | |
| testEachLine(config, lineNumbers, null); | |
| } | |
| private void testEachLine( | |
| DebugTestConfig config, int[] lineNumbers, List<List<SignatureAndLine>> inlineFramesList) | |
| throws Throwable { | |
| final String mainSignature = "([Ljava/lang/String;)V"; | |
| List<Command> commands = new ArrayList<Command>(); | |
| commands.add(breakpoint(CLASS_NAME, "main", mainSignature)); | |
| commands.add(run()); | |
| boolean first = true; | |
| assert inlineFramesList == null || inlineFramesList.size() == lineNumbers.length; | |
| for (int idx = 0; idx < lineNumbers.length; ++idx) { | |
| if (first) { | |
| first = false; | |
| } else { | |
| commands.add(stepOver()); | |
| } | |
| commands.add(checkMethod(CLASS_NAME, "main", mainSignature)); | |
| commands.add(checkLine(SOURCE_FILE, lineNumbers[idx])); | |
| if (inlineFramesList != null) { | |
| commands.add(checkInlineFrames(inlineFramesList.get(idx))); | |
| } | |
| } | |
| commands.add(run()); | |
| runDebugTest(config, CLASS_NAME, commands); | |
| } | |
| } |