Debugger: support stepping out inline function

This commit is contained in:
Natalia Ukhorskaya
2015-09-02 12:22:06 +03:00
parent ba686c701e
commit 060b6e6b15
16 changed files with 348 additions and 9 deletions
@@ -24,14 +24,20 @@ import com.intellij.debugger.engine.events.DebuggerCommandImpl
import com.intellij.debugger.impl.JvmSteppingCommandProvider
import com.intellij.util.concurrency.Semaphore
import com.intellij.xdebugger.impl.XSourcePositionImpl
import com.sun.jdi.Location
import com.sun.jdi.ReferenceType
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.core.refactoring.getLineCount
import org.jetbrains.kotlin.idea.core.refactoring.getLineStartOffset
import org.jetbrains.kotlin.idea.util.DebuggerUtils
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
@@ -44,16 +50,13 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
): DebugProcessImpl.ResumeCommand? {
if (suspendContext == null) return null
if (!isKotlinStrataPresent(suspendContext)) return null
val result: Pair<SourcePosition, List<ReferenceType>>? = computeInManagerThread(suspendContext) {
val result: Pair<SourcePosition, ReferenceType>? = computeInManagerThread(suspendContext) {
val sp = runReadAction { ContextUtil.getSourcePosition(it) } ?: return@computeInManagerThread null
val cl = it.debugProcess.positionManager.getAllClasses(sp)
val cl = it.frameProxy?.location()?.declaringType() ?: return@computeInManagerThread null
sp to cl
}
val (sourcePosition, allClasses) = result ?: return null
val computedReferenceType = allClasses.firstOrNull() ?: return null
val (sourcePosition, computedReferenceType) = result ?: return null
val file = sourcePosition.file as? JetFile ?: return null
@@ -80,7 +83,107 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
return null
}
private fun isKotlinStrataPresent(suspendContext: SuspendContextImpl): Boolean {
override fun getStepOutCommand(suspendContext: SuspendContextImpl?, stepSize: Int): DebugProcessImpl.ResumeCommand? {
if (suspendContext == null) return null
val location = computeInManagerThread(suspendContext) {
it.frameProxy?.location()
} ?: return null
val sourcePosition = suspendContext.debugProcess.positionManager.getSourcePosition(location) ?: return null
val computedReferenceType = location.declaringType() ?: return null
val locations = computedReferenceType.allLineLocations()
val file = sourcePosition.file as? JetFile ?: return null
val lineStartOffset = file.getLineStartOffset(sourcePosition.line) ?: return null
val nextLineLocations = locations.dropWhile { it.lineNumber() != location.lineNumber() }.filter { it.method() == location.method() }
val inlineFunction = getInlineFunctionsIfAny(file, lineStartOffset)
if (inlineFunction != null) {
val xPosition = suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunction) ?: return null
return suspendContext.debugProcess.createRunToCursorCommand(suspendContext, xPosition, true)
}
val inlinedArgument = getInlineArgumentIfAny(file, lineStartOffset)
if (inlinedArgument != null) {
val xPosition = suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument) ?: return null
return suspendContext.debugProcess.createRunToCursorCommand(suspendContext, xPosition, true)
}
return null
}
private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction(
locations: List<Location>,
inlineFunctionsToSkip: List<JetNamedFunction>
): XSourcePositionImpl? {
return getNextPositionWithFilter(locations) {
file, offset ->
if (inlineFunctionsToSkip.any { it.textRange.contains(offset) }) {
return@getNextPositionWithFilter true
}
val inlinedArgument = getInlineArgumentIfAny(file, offset)
inlinedArgument != null && inlinedArgument.textRange.contains(offset)
}
}
private fun SuspendContextImpl.getXPositionForStepOutFromInlinedArgument(
locations: List<Location>,
inlinedArgumentToSkip: JetFunctionLiteral
): XSourcePositionImpl? {
return getNextPositionWithFilter(locations) {
file, offset ->
inlinedArgumentToSkip.textRange.contains(offset)
}
}
private fun SuspendContextImpl.getNextPositionWithFilter(
locations: List<Location>,
skip: (JetFile, Int) -> Boolean
): XSourcePositionImpl? {
for (location in locations) {
val file = this.debugProcess.positionManager.getSourcePosition(location)?.file as? JetFile ?: continue
val currentLine = location.lineNumber() - 1
val lineStartOffset = file.getLineStartOffset(currentLine) ?: continue
if (skip(file, lineStartOffset)) continue
val elementAt = file.findElementAt(lineStartOffset) ?: continue
return XSourcePositionImpl.createByElement(elementAt)
}
return null
}
private fun getInlineFunctionsIfAny(file: JetFile, offset: Int): List<JetNamedFunction>? {
val elementAt = file.findElementAt(offset) ?: return null
val containingFunction = elementAt.getParentOfType<JetNamedFunction>(false) ?: return null
val descriptor = containingFunction.resolveToDescriptor()
if (!InlineUtil.isInline(descriptor)) return null
val inlineFunctionsCalls = DebuggerUtils.analyzeElementWithInline(
containingFunction.getResolutionFacade(),
containingFunction.analyzeFully(),
containingFunction,
false
).filterIsInstance<JetNamedFunction>()
return inlineFunctionsCalls
}
private fun getInlineArgumentIfAny(file: JetFile, offset: Int): JetFunctionLiteral? {
val elementAt = file.findElementAt(offset) ?: return null
val functionLiteralExpression = elementAt.getParentOfType<JetFunctionLiteralExpression>(false) ?: return null
val context = functionLiteralExpression.analyze(BodyResolveMode.PARTIAL)
if (!InlineUtil.isInlinedArgument(functionLiteralExpression.functionLiteral, context, false)) return null
return functionLiteralExpression.functionLiteral
}
private fun isKotlinStrataAvailable(suspendContext: SuspendContextImpl): Boolean {
val availableStrata = suspendContext.frameProxy?.location()?.declaringType()?.availableStrata() ?: return false
return availableStrata.contains("Kotlin")
}
@@ -93,7 +196,9 @@ public class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
val worker = object : DebuggerCommandImpl() {
override fun action() {
try {
result = action(suspendContext)
if (isKotlinStrataAvailable(suspendContext)) {
result = action(suspendContext)
}
}
finally {
semaphore.up()
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.idea.resolve.ResolutionFacade;
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils;
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde;
import org.jetbrains.kotlin.idea.debugger.DebuggerPackage;
import org.jetbrains.kotlin.idea.resolve.ResolvePackage;
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils;
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils;
import org.jetbrains.kotlin.psi.*;
@@ -177,6 +178,18 @@ public class DebuggerUtils {
return new Pair<BindingContext, List<JetFile>>(context, new ArrayList<JetFile>(toProcess));
}
@NotNull
public static Collection<JetElement> analyzeElementWithInline(
@NotNull ResolutionFacade resolutionFacade,
@NotNull BindingContext bindingContext,
@NotNull JetNamedFunction function,
boolean analyzeInlineFunctions
) {
Set<JetElement> analyzedElements = new HashSet<JetElement>();
analyzeElementWithInline(resolutionFacade, bindingContext, function, 1, analyzedElements, !analyzeInlineFunctions);
return analyzedElements;
}
@NotNull
private static BindingContext analyzeElementWithInline(
@NotNull ResolutionFacade resolutionFacade,
@@ -0,0 +1,8 @@
LineBreakpoint created at stepOutInlineFunction.kt:12
!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! stepOutInlineFunction.StepOutInlineFunctionPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stepOutInlineFunction.kt:12
stepOutInlineFunction.kt:7
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,9 @@
LineBreakpoint created at stepOutInlineFunctionStdlib.kt:6
!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! stepOutInlineFunctionStdlib.StepOutInlineFunctionStdlibPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stepOutInlineFunctionStdlib.kt:6
_Elements.!EXT!
stepOutInlineFunctionStdlib.kt:9
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,9 @@
LineBreakpoint created at stepOutInlinedLambdaArgument.kt:6
!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! stepOutInlinedLambdaArgument.StepOutInlinedLambdaArgumentPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stepOutInlinedLambdaArgument.kt:6
stepOutInlinedLambdaArgument.kt:15
stepOutInlinedLambdaArgument.kt:9
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,10 @@
LineBreakpoint created at stepOutSeveralInlineArgumentDeepest.kt:6
!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! stepOutSeveralInlineArgumentDeepest.StepOutSeveralInlineArgumentDeepestPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stepOutSeveralInlineArgumentDeepest.kt:6
stepOutSeveralInlineArgumentDeepest.kt:22
stepOutSeveralInlineArgumentDeepest.kt:16
stepOutSeveralInlineArgumentDeepest.kt:8
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,8 @@
LineBreakpoint created at stepOutSeveralInlineFunctions.kt:12
!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! stepOutSeveralInlineFunctions.StepOutSeveralInlineFunctionsPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stepOutSeveralInlineFunctions.kt:12
stepOutSeveralInlineFunctions.kt:7
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,8 @@
LineBreakpoint created at stepOutSeveralInlineFunctionsDeepest.kt:20
!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! stepOutSeveralInlineFunctionsDeepest.StepOutSeveralInlineFunctionsDeepestPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
stepOutSeveralInlineFunctionsDeepest.kt:20
stepOutSeveralInlineFunctionsDeepest.kt:15
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,15 @@
package stepOutInlineFunctionStdlib
fun main(args: Array<String>) {
val a = listOf(1, 2, 3)
//Breakpoint!
a.firstOrNull {
it > 1
}
}
fun test(i: Int) = 1
// TRACING_FILTERS_ENABLED: false
// STEP_INTO: 1
// STEP_OUT: 1
@@ -0,0 +1,19 @@
package stepOutInlineFunction
fun main(args: Array<String>) {
foo {
test(2)
}
test(3)
}
inline fun foo(f: () -> Unit) {
//Breakpoint!
val a = 1
f()
val b = 2
}
fun test(i: Int) = 1
// STEP_OUT: 2
@@ -0,0 +1,20 @@
package stepOutInlinedLambdaArgument
fun main(args: Array<String>) {
foo {
//Breakpoint!
test(1)
test(2)
}
test(3)
}
inline fun foo(f: () -> Unit) {
val a = 1
f()
val b = 2
}
fun test(i: Int) = 1
// STEP_OUT: 2
@@ -0,0 +1,27 @@
package stepOutSeveralInlineArgumentDeepest
fun main(args: Array<String>) {
f1 {
//Breakpoint!
test(2)
}
test(3)
}
inline fun f1(f: () -> Unit) {
val a = 1
f2 {
f()
}
val b = 2
}
inline fun f2(f: () -> Unit) {
val a = 1
f()
val b = 2
}
fun test(i: Int) = 1
// STEP_OUT: 4
@@ -0,0 +1,25 @@
package stepOutSeveralInlineFunctions
fun main(args: Array<String>) {
f1 {
test(2)
}
test(3)
}
inline fun f1(f: () -> Unit) {
//Breakpoint!
val a = 1
f2 {
f()
}
val b = 2
}
inline fun f2(f: () -> Unit) {
val a = 1
f()
val b = 2
}
fun test(i: Int) = 1
@@ -0,0 +1,25 @@
package stepOutSeveralInlineFunctionsDeepest
fun main(args: Array<String>) {
f1 {
test(2)
}
test(3)
}
inline fun f1(f: () -> Unit) {
val a = 1
f2 {
f()
}
val b = 2
}
inline fun f2(f: () -> Unit) {
//Breakpoint!
val a = 1
f()
val b = 2
}
fun test(i: Int) = 1
@@ -136,7 +136,9 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
}
protected fun SuspendContextImpl.stepOut() {
dp.getManagerThread()!!.schedule(dp.createStepOutCommand(this))
val stepOutCommand = runReadAction { KotlinSteppingCommandProvider().getStepOutCommand(this, StepRequest.STEP_LINE) }
?: dp.createStepOutCommand(this)
dp.getManagerThread()!!.schedule(stepOutCommand)
}
protected fun SuspendContextImpl.stepOver() {
@@ -297,6 +297,36 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOut/inapplicableFieldWatchpoints.kt");
doStepOutTest(fileName);
}
@TestMetadata("stepOutInlineFunction.kt")
public void testStepOutInlineFunction() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOut/stepOutInlineFunction.kt");
doStepOutTest(fileName);
}
@TestMetadata("stepOutInlinedLambdaArgument.kt")
public void testStepOutInlinedLambdaArgument() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOut/stepOutInlinedLambdaArgument.kt");
doStepOutTest(fileName);
}
@TestMetadata("stepOutSeveralInlineArgumentDeepest.kt")
public void testStepOutSeveralInlineArgumentDeepest() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOut/stepOutSeveralInlineArgumentDeepest.kt");
doStepOutTest(fileName);
}
@TestMetadata("stepOutSeveralInlineFunctions.kt")
public void testStepOutSeveralInlineFunctions() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOut/stepOutSeveralInlineFunctions.kt");
doStepOutTest(fileName);
}
@TestMetadata("stepOutSeveralInlineFunctionsDeepest.kt")
public void testStepOutSeveralInlineFunctionsDeepest() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOut/stepOutSeveralInlineFunctionsDeepest.kt");
doStepOutTest(fileName);
}
}
@TestMetadata("idea/testData/debugger/tinyApp/src/stepping/stepOver")
@@ -444,5 +474,11 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/stepIntoStdlibInlineFun2step.kt");
doCustomTest(fileName);
}
@TestMetadata("stepOutInlineFunctionStdlib.kt")
public void testStepOutInlineFunctionStdlib() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/stepOutInlineFunctionStdlib.kt");
doCustomTest(fileName);
}
}
}