Allow to step into inline functions while debugging Android app (KT-12896)
#KT-12896 In Progress
This commit is contained in:
@@ -24,7 +24,9 @@ import com.intellij.debugger.engine.DebugProcessImpl
|
|||||||
import com.intellij.debugger.engine.PositionManagerEx
|
import com.intellij.debugger.engine.PositionManagerEx
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
|
import com.intellij.debugger.jdi.VirtualMachineProxyImpl
|
||||||
import com.intellij.debugger.requests.ClassPrepareRequestor
|
import com.intellij.debugger.requests.ClassPrepareRequestor
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.psi.PsiFile
|
import com.intellij.psi.PsiFile
|
||||||
import com.intellij.psi.impl.compiled.ClsFileImpl
|
import com.intellij.psi.impl.compiled.ClsFileImpl
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
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.KotlinCodeFragmentFactory
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||||
import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile
|
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.refactoring.getLineStartOffset
|
||||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||||
@@ -76,9 +79,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getSourcePosition(location: Location?): SourcePosition? {
|
override fun getSourcePosition(location: Location?): SourcePosition? {
|
||||||
if (location == null) {
|
if (location == null) throw NoDataException.INSTANCE
|
||||||
throw NoDataException.INSTANCE
|
|
||||||
}
|
|
||||||
|
|
||||||
val psiFile = getPsiFileByLocation(location)
|
val psiFile = getPsiFileByLocation(location)
|
||||||
if (psiFile == null) {
|
if (psiFile == null) {
|
||||||
@@ -121,6 +122,17 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
if (property != null) {
|
if (property != null) {
|
||||||
return SourcePosition.createFromElement(property)
|
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)
|
return SourcePosition.createFromLine(psiFile, lineNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +193,6 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val referenceInternalName: String
|
val referenceInternalName: String
|
||||||
try {
|
try {
|
||||||
if (location.declaringType().containsKotlinStrata()) {
|
if (location.declaringType().containsKotlinStrata()) {
|
||||||
@@ -280,3 +291,9 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
inline fun <U, V> U.readAction(crossinline f: (U) -> V): V {
|
inline fun <U, V> U.readAction(crossinline f: (U) -> V): V {
|
||||||
return runReadAction { f(this) }
|
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
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.idea.refactoring.toPsiFile
|
|||||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||||
import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder
|
import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder
|
||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.name.tail
|
import org.jetbrains.kotlin.name.tail
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
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 org.jetbrains.org.objectweb.asm.ClassVisitor
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
fun inlineLineAndFileByPosition(lineNumber: Int, fqName: FqName, fileName: String, project: Project, searchScope: GlobalSearchScope): Pair<KtFile, Int>? {
|
||||||
|
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 {
|
fun isInlineFunctionLineNumber(file: VirtualFile, lineNumber: Int, project: Project): Boolean {
|
||||||
val linesInFile = file.toPsiFile(project)?.getLineCount() ?: return false
|
val linesInFile = file.toPsiFile(project)?.getLineCount() ?: return false
|
||||||
return lineNumber > linesInFile
|
return lineNumber > linesInFile
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package dexInline
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
//Breakpoint!
|
||||||
|
myPrint("OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun myPrint(s: String) {
|
||||||
|
val z = s;
|
||||||
|
}
|
||||||
@@ -83,6 +83,9 @@ public abstract class KotlinDebuggerTestCase extends DescriptorTestCase {
|
|||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
VfsRootAccess.allowRootAccess(KotlinTestUtils.getHomeDirectory());
|
VfsRootAccess.allowRootAccess(KotlinTestUtils.getHomeDirectory());
|
||||||
|
if (getTestName(true).startsWith("dex")) {
|
||||||
|
KotlinPositionManagerKt.setEmulateDexDebugInTests(true);
|
||||||
|
}
|
||||||
super.setUp();
|
super.setUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +101,10 @@ public abstract class KotlinDebuggerTestCase extends DescriptorTestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void tearDown() throws Exception {
|
protected void tearDown() throws Exception {
|
||||||
|
if (getTestName(true).startsWith("dex")) {
|
||||||
|
KotlinPositionManagerKt.setEmulateDexDebugInTests(false);
|
||||||
|
}
|
||||||
|
|
||||||
EdtTestUtil.runInEdtAndWait(new ThrowableRunnable<Throwable>() {
|
EdtTestUtil.runInEdtAndWait(new ThrowableRunnable<Throwable>() {
|
||||||
@Override
|
@Override
|
||||||
public void run() throws Throwable {
|
public void run() throws Throwable {
|
||||||
|
|||||||
@@ -229,6 +229,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
|
|||||||
doStepIntoTest(fileName);
|
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")
|
@TestMetadata("forLoop.kt")
|
||||||
public void testForLoop() throws Exception {
|
public void testForLoop() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepInto/forLoop.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepInto/forLoop.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user