From 64979ae190351eac87200daa27d900ea4d93cc5f Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Thu, 21 Jul 2016 19:00:16 +0300 Subject: [PATCH] Allow to step into inline functions while debugging Android app (KT-12896) #KT-12896 In Progress --- .../idea/debugger/KotlinPositionManager.kt | 25 ++++++++++++++++--- .../kotlin/idea/debugger/smapUtil.kt | 14 +++++++++++ .../debugger/tinyApp/outs/dexInline.out | 8 ++++++ .../src/stepping/stepInto/dexInline.kt | 10 ++++++++ .../idea/debugger/KotlinDebuggerTestCase.java | 7 ++++++ .../debugger/KotlinSteppingTestGenerated.java | 6 +++++ 6 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 idea/testData/debugger/tinyApp/outs/dexInline.out create mode 100644 idea/testData/debugger/tinyApp/src/stepping/stepInto/dexInline.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt index 80670a15a72..3331c39d735 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/KotlinPositionManager.kt @@ -24,7 +24,9 @@ import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.PositionManagerEx import com.intellij.debugger.engine.evaluation.EvaluationContext import com.intellij.debugger.jdi.StackFrameProxyImpl +import com.intellij.debugger.jdi.VirtualMachineProxyImpl import com.intellij.debugger.requests.ClassPrepareRequestor +import com.intellij.openapi.application.ApplicationManager import com.intellij.psi.PsiFile import com.intellij.psi.impl.compiled.ClsFileImpl import com.intellij.psi.search.GlobalSearchScope @@ -42,6 +44,7 @@ import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile +import org.jetbrains.kotlin.idea.refactoring.getLineCount import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope import org.jetbrains.kotlin.idea.util.ProjectRootsUtil @@ -76,9 +79,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq } override fun getSourcePosition(location: Location?): SourcePosition? { - if (location == null) { - throw NoDataException.INSTANCE - } + if (location == null) throw NoDataException.INSTANCE val psiFile = getPsiFileByLocation(location) if (psiFile == null) { @@ -121,6 +122,17 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq if (property != null) { return SourcePosition.createFromElement(property) } + + if (lineNumber > psiFile.getLineCount() && myDebugProcess.isDexDebug()) { + val inlinePosition = inlineLineAndFileByPosition( + location.lineNumber(), FqName(location.declaringType().name()), location.sourceName(), + myDebugProcess.project, GlobalSearchScope.allScope(myDebugProcess.project)) + + if (inlinePosition != null) { + return SourcePosition.createFromLine(inlinePosition.first, inlinePosition.second) + } + } + return SourcePosition.createFromLine(psiFile, lineNumber) } @@ -181,7 +193,6 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq return null } - val referenceInternalName: String try { if (location.declaringType().containsKotlinStrata()) { @@ -280,3 +291,9 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq inline fun U.readAction(crossinline f: (U) -> V): V { return runReadAction { f(this) } } + +@Volatile var emulateDexDebugInTests: Boolean = false + +private fun DebugProcess.isDexDebug() = + (emulateDexDebugInTests && ApplicationManager.getApplication ().isUnitTestMode) || + (this.virtualMachineProxy as? VirtualMachineProxyImpl)?.virtualMachine?.name() == "Dalvik" // TODO: check other machine name diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/smapUtil.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/smapUtil.kt index c4dda0be59b..14ee0b6171d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/smapUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/smapUtil.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.idea.refactoring.toPsiFile import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.tail import org.jetbrains.kotlin.psi.KtFile @@ -39,6 +40,19 @@ import org.jetbrains.org.objectweb.asm.ClassReader import org.jetbrains.org.objectweb.asm.ClassVisitor import java.io.File +fun inlineLineAndFileByPosition(lineNumber: Int, fqName: FqName, fileName: String, project: Project, searchScope: GlobalSearchScope): Pair? { + val internalName = fqName.asString().replace('.', '/') + val jvmClassName = JvmClassName.byInternalName(internalName) + + val file = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, jvmClassName, fileName) ?: return null + + val virtualFile = file.virtualFile ?: return null + + val bytes = readClassFile(project, jvmClassName, virtualFile, { isInlineFunctionLineNumber(it, lineNumber, project) }) ?: return null + val smapData = readDebugInfo(bytes) ?: return null + return mapStacktraceLineToSource(smapData, lineNumber, project, SourceLineKind.EXECUTED_LINE, searchScope) +} + fun isInlineFunctionLineNumber(file: VirtualFile, lineNumber: Int, project: Project): Boolean { val linesInFile = file.toPsiFile(project)?.getLineCount() ?: return false return lineNumber > linesInFile diff --git a/idea/testData/debugger/tinyApp/outs/dexInline.out b/idea/testData/debugger/tinyApp/outs/dexInline.out new file mode 100644 index 00000000000..dafecd3dd5f --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/dexInline.out @@ -0,0 +1,8 @@ +LineBreakpoint created at dexInline.kt:5 +!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! dexInline.DexInlineKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +dexInline.kt:5 +dexInline.kt:9 +Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/stepping/stepInto/dexInline.kt b/idea/testData/debugger/tinyApp/src/stepping/stepInto/dexInline.kt new file mode 100644 index 00000000000..21f76adce24 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/stepping/stepInto/dexInline.kt @@ -0,0 +1,10 @@ +package dexInline + +fun main(args: Array) { + //Breakpoint! + myPrint("OK") +} + +inline fun myPrint(s: String) { + val z = s; +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestCase.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestCase.java index 89b76316273..8a66015f671 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestCase.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestCase.java @@ -83,6 +83,9 @@ public abstract class KotlinDebuggerTestCase extends DescriptorTestCase { @Override protected void setUp() throws Exception { VfsRootAccess.allowRootAccess(KotlinTestUtils.getHomeDirectory()); + if (getTestName(true).startsWith("dex")) { + KotlinPositionManagerKt.setEmulateDexDebugInTests(true); + } super.setUp(); } @@ -98,6 +101,10 @@ public abstract class KotlinDebuggerTestCase extends DescriptorTestCase { @Override protected void tearDown() throws Exception { + if (getTestName(true).startsWith("dex")) { + KotlinPositionManagerKt.setEmulateDexDebugInTests(false); + } + EdtTestUtil.runInEdtAndWait(new ThrowableRunnable() { @Override public void run() throws Throwable { diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java index 0a12ac00779..c7229aa7355 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinSteppingTestGenerated.java @@ -229,6 +229,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest { doStepIntoTest(fileName); } + @TestMetadata("dexInline.kt") + public void testDexInline() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepInto/dexInline.kt"); + doStepIntoTest(fileName); + } + @TestMetadata("forLoop.kt") public void testForLoop() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepInto/forLoop.kt");