diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt index 8b8f4201df4..93eefc61a4e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/JetPositionManager.kt @@ -54,6 +54,7 @@ import org.jetbrains.kotlin.fileClasses.getInternalName import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils +import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny import org.jetbrains.kotlin.idea.decompiler.JetClsFile import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage import org.jetbrains.kotlin.idea.util.DebuggerUtils @@ -64,6 +65,7 @@ import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.inline.InlineUtil import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import org.jetbrains.kotlin.utils.toReadOnlyList import java.util.* import com.intellij.debugger.engine.DebuggerUtils as JDebuggerUtils @@ -135,10 +137,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult val end = CodeInsightUtils.getEndLineOffset(file, lineNumber) if (start == null || end == null) return null - val literalsOrFunctions = CodeInsightUtils. - findElementsOfClassInRange(file, start, end, KtFunctionLiteral::class.java, KtNamedFunction::class.java). - filter { KtPsiUtil.getParentCallIfPresent(it as KtExpression) != null } - + val literalsOrFunctions = getLambdasAtLineIfAny(file, lineNumber) if (literalsOrFunctions.isEmpty()) return null; val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null @@ -149,17 +148,16 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult val currentLocationClassName = JvmClassName.byFqNameWithoutInnerClasses(FqName(currentLocationFqName)).internalName for (literal in literalsOrFunctions) { - val functionLiteral = literal as KtFunction - if (isInlinedLambda(functionLiteral, typeMapper.bindingContext)) { - if (isInsideInlineArgument(functionLiteral, location, myDebugProcess as DebugProcessImpl)) { - return functionLiteral + if (isInlinedLambda(literal, typeMapper.bindingContext)) { + if (isInsideInlineArgument(literal, location, myDebugProcess as DebugProcessImpl)) { + return literal } continue } val internalClassName = getInternalClassNameForElement(literal.firstChild, typeMapper, file, isInLibrary).className if (internalClassName == currentLocationClassName) { - return functionLiteral + return literal } } @@ -232,7 +230,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult } private fun classNameForPositionAndInlinedOnes(sourcePosition: SourcePosition): List { - val result = arrayListOf() + val result = hashSetOf() val name = classNameForPosition(sourcePosition) if (name != null) { result.add(name) @@ -240,7 +238,22 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult val list = findInlinedCalls(sourcePosition.elementAt, sourcePosition.file) result.addAll(list) - return result; + val lambdas = findLambdas(sourcePosition) + result.addAll(lambdas) + + return result.toReadOnlyList(); + } + + private fun findLambdas(sourcePosition: SourcePosition): List { + return runReadAction { + val lambdas = getLambdasAtLineIfAny(sourcePosition) + val file = sourcePosition.file.containingFile as KtFile + val isInLibrary = LibraryUtil.findLibraryEntry(file.virtualFile, file.project) != null + lambdas.map { + val typeMapper = if (!isInLibrary) prepareTypeMapper(file) else createTypeMapperForLibraryFile(it, file) + getInternalClassNameForElement(it, typeMapper, file, isInLibrary).className + }.filterNotNull() + } } public fun classNameForPosition(sourcePosition: SourcePosition): String? { diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java b/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java index 971e7107626..8e6683d8f2e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java @@ -17,17 +17,29 @@ package org.jetbrains.kotlin.idea.debugger.breakpoints; import com.intellij.debugger.SourcePosition; +import com.intellij.debugger.ui.breakpoints.Breakpoint; +import com.intellij.debugger.ui.breakpoints.BreakpointManager; import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType; import com.intellij.debugger.ui.breakpoints.LineBreakpoint; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.xdebugger.XSourcePosition; +import com.intellij.xdebugger.breakpoints.XBreakpoint; +import com.intellij.xdebugger.breakpoints.XLineBreakpoint; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties; +import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties; +import org.jetbrains.kotlin.idea.KotlinIcons; import org.jetbrains.kotlin.psi.KtFunction; +import javax.swing.*; +import java.util.List; + public class KotlinLineBreakpointType extends JavaLineBreakpointType { public KotlinLineBreakpointType() { super("kotlin-line", "Kotlin Line Breakpoints"); @@ -35,11 +47,16 @@ public class KotlinLineBreakpointType extends JavaLineBreakpointType { @Override public boolean matchesPosition(@NotNull LineBreakpoint breakpoint, @NotNull SourcePosition position) { - if (super.matchesPosition(breakpoint, position)) return true; + JavaBreakpointProperties properties = getProperties(breakpoint); + if (properties == null || properties instanceof JavaLineBreakpointProperties) { + if (properties != null && ((JavaLineBreakpointProperties)properties).getLambdaOrdinal() == null) return true; - PsiElement containingMethod = getContainingMethod(breakpoint); - if (containingMethod == null) return false; - return inTheMethod(position, containingMethod); + PsiElement containingMethod = getContainingMethod(breakpoint); + if (containingMethod == null) return false; + return inTheMethod(position, containingMethod); + } + + return true; } @Override @@ -48,9 +65,34 @@ public class KotlinLineBreakpointType extends JavaLineBreakpointType { SourcePosition position = breakpoint.getSourcePosition(); if (position == null) return null; + JavaBreakpointProperties properties = getProperties(breakpoint); + if (properties instanceof JavaLineBreakpointProperties) { + Integer ordinal = ((JavaLineBreakpointProperties) properties).getLambdaOrdinal(); + PsiElement lambda = getLambdaByOrdinal(position, ordinal); + if (lambda != null) return lambda; + } + return getContainingMethod(position.getElementAt()); } + + @Nullable + private static JavaBreakpointProperties getProperties(@NotNull LineBreakpoint breakpoint) { + XBreakpoint xBreakpoint = breakpoint.getXBreakpoint(); + return xBreakpoint != null ? (JavaBreakpointProperties) xBreakpoint.getProperties() : null; + } + + @Nullable + private static KtFunction getLambdaByOrdinal(SourcePosition position, Integer ordinal) { + if (ordinal != null && ordinal >= 0) { + List lambdas = BreakpointTypeUtilsKt.getLambdasAtLineIfAny(position); + if (lambdas.size() >= ordinal) { + return lambdas.get(ordinal); + } + } + return null; + } + @Nullable public static PsiElement getContainingMethod(@Nullable PsiElement elem) { //noinspection unchecked @@ -68,5 +110,42 @@ public class KotlinLineBreakpointType extends JavaLineBreakpointType { return BreakpointTypeUtilsKt.canPutAt(file, line, project, getClass()); } + @NotNull + @Override + public List computeVariants(@NotNull Project project, @NotNull XSourcePosition position) { + return BreakpointTypeUtilsKt.computeVariants(project, position, this); + } + @Nullable + @Override + public TextRange getHighlightRange(XLineBreakpoint breakpoint) { + JavaLineBreakpointProperties properties = breakpoint.getProperties(); + if (properties != null) { + Integer ordinal = properties.getLambdaOrdinal(); + if (ordinal != null) { + Breakpoint javaBreakpoint = BreakpointManager.getJavaBreakpoint(breakpoint); + if (javaBreakpoint instanceof LineBreakpoint) { + SourcePosition position = ((LineBreakpoint) javaBreakpoint).getSourcePosition(); + if (position != null) { + KtFunction lambda = getLambdaByOrdinal(position, ordinal); + if (lambda != null) { + return lambda.getTextRange(); + } + } + } + } + } + return null; + } + + public class ExactKotlinJavaBreakpointVariant extends ExactJavaBreakpointVariant { + public ExactKotlinJavaBreakpointVariant(XSourcePosition position, KtFunction function, Integer lambdaOrdinal) { + super(position, function, lambdaOrdinal); + } + + @Override + public Icon getIcon() { + return KotlinIcons.LAMBDA; + } + } } diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/breakpointTypeUtils.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/breakpointTypeUtils.kt index 2bfedc5e898..f69a9f27ce9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/breakpointTypeUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/breakpoints/breakpointTypeUtils.kt @@ -16,19 +16,32 @@ package org.jetbrains.kotlin.idea.debugger.breakpoints +import com.intellij.debugger.SourcePosition import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.* +import com.intellij.psi.PsiComment +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiManager +import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.util.PsiTreeUtil import com.intellij.xdebugger.XDebuggerUtil +import com.intellij.xdebugger.XSourcePosition +import com.intellij.xdebugger.impl.XSourcePositionImpl import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.idea.caches.resolve.analyze +import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils +import org.jetbrains.kotlin.idea.core.refactoring.getLineEndOffset +import org.jetbrains.kotlin.idea.core.refactoring.getLineNumber +import org.jetbrains.kotlin.idea.core.refactoring.getLineStartOffset import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.resolve.BindingContext +import java.util.* fun canPutAt(file: VirtualFile, line: Int, project: Project, breakpointTypeClass: Class<*>): Boolean { val psiFile = PsiManager.getInstance(project).findFile(file) @@ -80,4 +93,73 @@ fun canPutAt(file: VirtualFile, line: Int, project: Project, breakpointTypeClass return result == breakpointTypeClass } +fun computeVariants( + project: Project, position: XSourcePosition, + kotlinBreakpointType: KotlinLineBreakpointType +): List { + val file = PsiManager.getInstance(project).findFile(position.file) as? KtFile ?: return emptyList() + + val pos = SourcePosition.createFromLine(file, position.line) + val lambdas = getLambdasAtLineIfAny(pos) + if (lambdas.isEmpty()) return emptyList() + + val result = LinkedList() + + val mainMethod = KotlinLineBreakpointType.getContainingMethod(pos.elementAt) + if (mainMethod != null) { + result.add((kotlinBreakpointType as JavaLineBreakpointType).ExactJavaBreakpointVariant( + XSourcePositionImpl.createByElement(mainMethod), + mainMethod, -1)) + } + + lambdas.forEachIndexed { ordinal, lambda -> + result.add(kotlinBreakpointType.ExactKotlinJavaBreakpointVariant( + XSourcePositionImpl.createByElement(lambda.bodyExpression), lambda, ordinal)) + } + + if (result.size > 1) { + val allBreakpoint = (kotlinBreakpointType as JavaLineBreakpointType).JavaBreakpointVariant(position) + result.addFirst(allBreakpoint) + } + + return result +} + +fun getLambdasAtLineIfAny(sourcePosition: SourcePosition): List { + val file = sourcePosition.file as? KtFile ?: return emptyList() + val lineNumber = sourcePosition.line + return getLambdasAtLineIfAny(file, lineNumber) +} + +fun getLambdasAtLineIfAny(file: KtFile, line: Int): List { + var lineStartOffset = file.getLineStartOffset(line) ?: return emptyList() + var lineEndOffset = file.getLineEndOffset(line) ?: return emptyList() + + var topMostElement: PsiElement? = null + var elementAt: PsiElement? + while (topMostElement !is KtElement && lineStartOffset < lineEndOffset) { + elementAt = file.findElementAt(lineStartOffset) + if (elementAt != null) { + topMostElement = CodeInsightUtils.getTopmostElementAtOffset(elementAt, lineStartOffset) + } + lineStartOffset++ + } + + if (topMostElement !is KtElement) return emptyList() + + val start = topMostElement.startOffset + val end = topMostElement.endOffset + + val allInlineFunctionCalls = CodeInsightUtils. + findElementsOfClassInRange(file, start, end, KtFunction::class.java) + .filter { KtPsiUtil.getParentCallIfPresent(it as KtExpression) != null } + .filterIsInstance() + .toSet() + + return allInlineFunctionCalls.filter { + val statement = (it.bodyExpression as? KtBlockExpression)?.statements?.firstOrNull() ?: it + statement.getLineNumber() == line && statement.getLineNumber(false) == line + } +} + diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinLambdaMethodFilter.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinLambdaMethodFilter.kt index c2a26ebf396..ccd166f261d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinLambdaMethodFilter.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/stepping/KotlinLambdaMethodFilter.kt @@ -36,11 +36,11 @@ public class KotlinLambdaMethodFilter( private val myLastStatementLine: Int init { - if (lambda.isMultiLine()) { + val body = lambda.bodyExpression + if (body != null && lambda.isMultiLine()) { var firstStatementPosition: SourcePosition? = null var lastStatementPosition: SourcePosition? = null - val body = lambda.bodyExpression as KtBlockExpression - val statements = body.statements + val statements = (body as? KtBlockExpression)?.statements ?: listOf(body) if (statements.isNotEmpty()) { firstStatementPosition = SourcePosition.createFromElement(statements.first()) if (firstStatementPosition != null) { diff --git a/idea/testData/debugger/tinyApp/outs/ceAnonymousObjectCapturedInClosure.out b/idea/testData/debugger/tinyApp/outs/ceAnonymousObjectCapturedInClosure.out index d2994ac91c6..9b53beb9883 100644 --- a/idea/testData/debugger/tinyApp/outs/ceAnonymousObjectCapturedInClosure.out +++ b/idea/testData/debugger/tinyApp/outs/ceAnonymousObjectCapturedInClosure.out @@ -1,4 +1,4 @@ -LineBreakpoint created at ceAnonymousObjectCapturedInClosure.kt:7 +LineBreakpoint created at ceAnonymousObjectCapturedInClosure.kt:7 lambdaOrdinal = -1 !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! ceAnonymousObjectCapturedInClosure.CeAnonymousObjectCapturedInClosureKt Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' ceAnonymousObjectCapturedInClosure.kt:7 diff --git a/idea/testData/debugger/tinyApp/outs/multipleBreakpointsAtLine.out b/idea/testData/debugger/tinyApp/outs/multipleBreakpointsAtLine.out new file mode 100644 index 00000000000..ae7edd16ee4 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/multipleBreakpointsAtLine.out @@ -0,0 +1,38 @@ +LineBreakpoint created at multipleBreakpointsAtLine.kt:9 lambdaOrdinal = -1 +LineBreakpoint created at multipleBreakpointsAtLine.kt:14 lambdaOrdinal = 0 +LineBreakpoint created at multipleBreakpointsAtLine.kt:19 lambdaOrdinal = 1 +LineBreakpoint created at multipleBreakpointsAtLine.kt:30 +LineBreakpoint created at multipleBreakpointsAtLine.kt:38 lambdaOrdinal = -1 +LineBreakpoint created at multipleBreakpointsAtLine.kt:43 lambdaOrdinal = 0 +LineBreakpoint created at multipleBreakpointsAtLine.kt:48 lambdaOrdinal = 1 +LineBreakpoint created at multipleBreakpointsAtLine.kt:62 +LineBreakpoint created at multipleBreakpointsAtLine.kt:67 lambdaOrdinal = 1 +!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! multipleBreakpointsAtLine.MultipleBreakpointsAtLineKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +multipleBreakpointsAtLine.kt:9 +multipleBreakpointsAtLine.kt:14 +Compile bytecode for it + 2 +multipleBreakpointsAtLine.kt:19 +Compile bytecode for it + 3 +multipleBreakpointsAtLine.kt:30 +multipleBreakpointsAtLine.kt:30 +Compile bytecode for it + 5 +multipleBreakpointsAtLine.kt:30 +Compile bytecode for it + 6 +multipleBreakpointsAtLine.kt:38 +multipleBreakpointsAtLine.kt:38 +multipleBreakpointsAtLine.kt:43 +Compile bytecode for it + 9 +multipleBreakpointsAtLine.kt:48 +Compile bytecode for it + 10 +multipleBreakpointsAtLine.kt:62 +multipleBreakpointsAtLine.kt:62 +Compile bytecode for it + 12 +multipleBreakpointsAtLine.kt:62 +multipleBreakpointsAtLine.kt:62 +Compile bytecode for it + 14 +multipleBreakpointsAtLine.kt:67 +Compile bytecode for it + 15 +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/outs/severalFunLiterals.out b/idea/testData/debugger/tinyApp/outs/severalFunLiterals.out index 08bdb72f63e..5eb1945ff6b 100644 --- a/idea/testData/debugger/tinyApp/outs/severalFunLiterals.out +++ b/idea/testData/debugger/tinyApp/outs/severalFunLiterals.out @@ -1,4 +1,4 @@ -LineBreakpoint created at severalFunLiterals.kt:6 +LineBreakpoint created at severalFunLiterals.kt:6 lambdaOrdinal = -1 !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! severalFunLiterals.SeveralFunLiteralsKt Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' severalFunLiterals.kt:6 diff --git a/idea/testData/debugger/tinyApp/outs/severalFunLiteralsInClass.out b/idea/testData/debugger/tinyApp/outs/severalFunLiteralsInClass.out index 79af24c3e08..4fd8001dc7d 100644 --- a/idea/testData/debugger/tinyApp/outs/severalFunLiteralsInClass.out +++ b/idea/testData/debugger/tinyApp/outs/severalFunLiteralsInClass.out @@ -1,4 +1,4 @@ -LineBreakpoint created at severalFunLiteralsInClass.kt:20 +LineBreakpoint created at severalFunLiteralsInClass.kt:20 lambdaOrdinal = -1 !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! severalFunLiteralsInClass.SeveralFunLiteralsInClassKt Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' severalFunLiteralsInClass.kt:20 diff --git a/idea/testData/debugger/tinyApp/outs/smartStepIntoInlinedFunLiteral.out b/idea/testData/debugger/tinyApp/outs/smartStepIntoInlinedFunLiteral.out index 62f5592e325..34aa59e6e58 100644 --- a/idea/testData/debugger/tinyApp/outs/smartStepIntoInlinedFunLiteral.out +++ b/idea/testData/debugger/tinyApp/outs/smartStepIntoInlinedFunLiteral.out @@ -2,12 +2,14 @@ LineBreakpoint created at smartStepIntoInlinedFunLiteral.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! smartStepIntoInlinedFunLiteral.SmartStepIntoInlinedFunLiteralKt Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' smartStepIntoInlinedFunLiteral.kt:6 -smartStepIntoInlinedFunLiteral.kt:9 -smartStepIntoInlinedFunLiteral.kt:10 -smartStepIntoInlinedFunLiteral.kt:13 -smartStepIntoInlinedFunLiteral.kt:14 +smartStepIntoInlinedFunLiteral.kt:11 +smartStepIntoInlinedFunLiteral.kt:12 +smartStepIntoInlinedFunLiteral.kt:17 smartStepIntoInlinedFunLiteral.kt:18 -smartStepIntoInlinedFunLiteral.kt:20 +smartStepIntoInlinedFunLiteral.kt:24 +smartStepIntoInlinedFunLiteral.kt:26 +smartStepIntoInlinedFunLiteral.kt:32 +smartStepIntoInlinedFunLiteral.kt:32 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/outs/smartStepIntoInlinedFunctionalExpression.out b/idea/testData/debugger/tinyApp/outs/smartStepIntoInlinedFunctionalExpression.out index 4b4042927c3..c4ece0f490a 100644 --- a/idea/testData/debugger/tinyApp/outs/smartStepIntoInlinedFunctionalExpression.out +++ b/idea/testData/debugger/tinyApp/outs/smartStepIntoInlinedFunctionalExpression.out @@ -2,12 +2,14 @@ LineBreakpoint created at smartStepIntoInlinedFunctionalExpression.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! smartStepIntoInlinedFunctionalExpression.SmartStepIntoInlinedFunctionalExpressionKt Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' smartStepIntoInlinedFunctionalExpression.kt:6 -smartStepIntoInlinedFunctionalExpression.kt:9 -smartStepIntoInlinedFunctionalExpression.kt:10 -smartStepIntoInlinedFunctionalExpression.kt:13 -smartStepIntoInlinedFunctionalExpression.kt:14 +smartStepIntoInlinedFunctionalExpression.kt:11 +smartStepIntoInlinedFunctionalExpression.kt:12 +smartStepIntoInlinedFunctionalExpression.kt:17 smartStepIntoInlinedFunctionalExpression.kt:18 -smartStepIntoInlinedFunctionalExpression.kt:20 +smartStepIntoInlinedFunctionalExpression.kt:24 +smartStepIntoInlinedFunctionalExpression.kt:26 +smartStepIntoInlinedFunctionalExpression.kt:31 +smartStepIntoInlinedFunctionalExpression.kt:31 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/outs/twoLambdasOnOneLineFirst.out b/idea/testData/debugger/tinyApp/outs/twoLambdasOnOneLineFirst.out index 8ec369f657f..1bcc9fc34b0 100644 --- a/idea/testData/debugger/tinyApp/outs/twoLambdasOnOneLineFirst.out +++ b/idea/testData/debugger/tinyApp/outs/twoLambdasOnOneLineFirst.out @@ -1,4 +1,4 @@ -LineBreakpoint created at twoLambdasOnOneLineFirst.kt:9 +LineBreakpoint created at twoLambdasOnOneLineFirst.kt:9 lambdaOrdinal = -1 !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! twoLambdasOnOneLineFirst.TwoLambdasOnOneLineFirstKt Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' twoLambdasOnOneLineFirst.kt:9 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.kt new file mode 100644 index 00000000000..ff77077f7fa --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.kt @@ -0,0 +1,82 @@ +package multipleBreakpointsAtLine + +fun main(args: Array) { + val a = A() + + // EXPRESSION: it + 1 + // RESULT: Unresolved reference: it + //Breakpoint! (-1) + a.foo(1) { 1 }.foo(2) { 1 } + + // EXPRESSION: it + 2 + // RESULT: 3: I + //Breakpoint! (0) + a.foo(1) { 1 }.foo(2) { 1 } + + // EXPRESSION: it + 3 + // RESULT: 5: I + //Breakpoint! (1) + a.foo(1) { 1 }.foo(2) { 1 } + + // EXPRESSION: it + 4 + // RESULT: Unresolved reference: it + + // EXPRESSION: it + 5 + // RESULT: 6: I + + // EXPRESSION: it + 6 + // RESULT: 8: I + //Breakpoint! + a.foo(1) { 1 }.foo(2) { 1 } + + // EXPRESSION: it + 7 + // RESULT: Unresolved reference: it + + // EXPRESSION: it + 8 + // RESULT: Unresolved reference: it + //Breakpoint! (-1) + a.bar(1) { 1 }.bar(2) { 1 } + + // EXPRESSION: it + 9 + // RESULT: 10: I + //Breakpoint! (0) + a.bar(1) { 1 }.bar(2) { 1 } + + // EXPRESSION: it + 10 + // RESULT: 12: I + //Breakpoint! (1) + a.bar(1) { 1 }.bar(2) { 1 } + + // EXPRESSION: it + 11 + // RESULT: Unresolved reference: it + + // EXPRESSION: it + 12 + // RESULT: 13: I + + // EXPRESSION: it + 13 + // RESULT: Unresolved reference: it + + // EXPRESSION: it + 14 + // RESULT: 16: I + //Breakpoint! + a.bar(1) { 1 }.bar(2) { 1 } + + // EXPRESSION: it + 15 + // RESULT: 17: I + //Breakpoint! (1) + a.bar(1) { 1 }.bar(2) { 1 + 1 + 1 + 1 + } +} + +class A { + fun foo(i: Int, f: (i: Int) -> Int): A { + f(i) + return this + } + + inline fun bar(i: Int, f: (i: Int) -> Int): A { + f(i) + return this + } +} diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceAnonymousObjectCapturedInClosure.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceAnonymousObjectCapturedInClosure.kt index 6870af5b319..f0b20979ae6 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceAnonymousObjectCapturedInClosure.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceAnonymousObjectCapturedInClosure.kt @@ -3,7 +3,7 @@ package ceAnonymousObjectCapturedInClosure fun main(args: Array) { val localObject = object { fun test() = 1 } var localObjectVar = object { fun test() = 1 } - //Breakpoint! + //Breakpoint! (-1) lambda { localObjectVar.test() } } diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/twoLambdasOnOneLineFirst.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/twoLambdasOnOneLineFirst.kt index 1fa7e93a728..a7ce4515cb3 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/twoLambdasOnOneLineFirst.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/lambdas/twoLambdasOnOneLineFirst.kt @@ -5,7 +5,7 @@ fun main(args: Array) { // EXPRESSION: it // RESULT: 1: I // STEP_INTO: 2 - //Breakpoint! + //Breakpoint! (-1) a.foo { a }.foo { a } } diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/severalFunLiterals.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/severalFunLiterals.kt index b12cfc1b7a3..9cef982aaab 100644 --- a/idea/testData/debugger/tinyApp/src/stepping/custom/severalFunLiterals.kt +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/severalFunLiterals.kt @@ -2,7 +2,7 @@ package severalFunLiterals fun main(args: Array) { val myClass = MyClass() - //Breakpoint! + //Breakpoint! (-1) myClass.f1 { test() }.f2 { test() } @@ -22,4 +22,4 @@ class MyClass { fun test() {} -// SMART_STEP_INTO_BY_INDEX: 4 \ No newline at end of file +// SMART_STEP_INTO_BY_INDEX: 4 diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/severalFunLiteralsInClass.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/severalFunLiteralsInClass.kt index aa4aa2f7fd9..912b6638d78 100644 --- a/idea/testData/debugger/tinyApp/src/stepping/custom/severalFunLiteralsInClass.kt +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/severalFunLiteralsInClass.kt @@ -16,7 +16,7 @@ class MyClass { } fun inClass() { - //Breakpoint! + //Breakpoint! (-1) f1 { test() }.f2 { test() } @@ -25,4 +25,4 @@ class MyClass { fun test() {} -// SMART_STEP_INTO_BY_INDEX: 4 \ No newline at end of file +// SMART_STEP_INTO_BY_INDEX: 4 diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInlinedFunLiteral.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInlinedFunLiteral.kt index b33c3c491b9..2239166757f 100644 --- a/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInlinedFunLiteral.kt +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInlinedFunLiteral.kt @@ -5,20 +5,31 @@ fun main(args: Array) { //Breakpoint! val myClass = MyClass() + // STEP_OVER: 1 + // SMART_STEP_INTO_BY_INDEX: 4 // smart step into f2.invoke(), one-line lambda myClass.f1 { test() } .f2 { test() } + // STEP_OVER: 1 + // SMART_STEP_INTO_BY_INDEX: 2 // smart step into map.invoke(), multiline lambda array.map { it *2 } + // STEP_OVER: 1 + // SMART_STEP_INTO_BY_INDEX: 4 // smart step into filter.invoke() array.map { it * 2 } .filter { it > 2 } + + // STEP_OVER: 1 + // SMART_STEP_INTO_BY_INDEX: 4 + // smart step into f2.invoke(), one-line lambda + myClass.f1 { test() }.f2 { test() } } class MyClass { @@ -35,11 +46,4 @@ class MyClass { } } -fun test() {} - -// STEP_OVER: 1 -// SMART_STEP_INTO_BY_INDEX: 4 -// STEP_OVER: 1 -// SMART_STEP_INTO_BY_INDEX: 2 -// STEP_OVER: 1 -// SMART_STEP_INTO_BY_INDEX: 4 \ No newline at end of file +fun test() {} \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInlinedFunctionalExpression.kt b/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInlinedFunctionalExpression.kt index 2522e4afd04..c4aaebcb782 100644 --- a/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInlinedFunctionalExpression.kt +++ b/idea/testData/debugger/tinyApp/src/stepping/custom/smartStepIntoInlinedFunctionalExpression.kt @@ -5,20 +5,32 @@ fun main(args: Array) { //Breakpoint! val myClass = MyClass() + // STEP_OVER: 1 + // SMART_STEP_INTO_BY_INDEX: 4 // smart step into f2.invoke(), one-line lambda myClass.f1(fun () { test() }) .f2(fun () { test() }) + // STEP_OVER: 1 + // SMART_STEP_INTO_BY_INDEX: 2 // smart step into map.invoke(), multiline lambda array.map(fun (it): Int { return it * 2 }) + // STEP_OVER: 1 + // SMART_STEP_INTO_BY_INDEX: 4 // smart step into filter.invoke() array.map(fun (it): Int { return it * 2 }) .filter(fun (it): Boolean { return it > 2 }) + + // STEP_OVER: 1 + // SMART_STEP_INTO_BY_INDEX: 2 + myClass.f3(fun () = myClass.f3 { + println() + }) } class MyClass { @@ -33,13 +45,11 @@ class MyClass { f1Param() return this } + + inline fun f3(f1Param: () -> Unit): Unit { + test() + f1Param() + } } -fun test() {} - -// STEP_OVER: 1 -// SMART_STEP_INTO_BY_INDEX: 4 -// STEP_OVER: 1 -// SMART_STEP_INTO_BY_INDEX: 2 -// STEP_OVER: 1 -// SMART_STEP_INTO_BY_INDEX: 4 \ No newline at end of file +fun test() {} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt index 743132e0098..ad6e5f4c995 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt @@ -26,25 +26,28 @@ import com.intellij.debugger.engine.evaluation.EvaluationContextImpl import com.intellij.debugger.impl.DebuggerContextImpl import com.intellij.debugger.impl.PositionUtil import com.intellij.debugger.settings.DebuggerSettings +import com.intellij.debugger.ui.breakpoints.Breakpoint import com.intellij.debugger.ui.breakpoints.BreakpointManager +import com.intellij.debugger.ui.breakpoints.LineBreakpoint import com.intellij.execution.process.ProcessOutputTypes import com.intellij.openapi.application.ModalityState import com.intellij.openapi.roots.JdkOrderEntry import com.intellij.openapi.roots.libraries.LibraryUtil import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import com.intellij.psi.search.FilenameIndex import com.intellij.xdebugger.XDebuggerManager import com.intellij.xdebugger.XDebuggerUtil -import com.intellij.xdebugger.breakpoints.XBreakpoint -import com.intellij.xdebugger.breakpoints.XBreakpointProperties -import com.intellij.xdebugger.breakpoints.XBreakpointType -import com.intellij.xdebugger.breakpoints.XLineBreakpointType +import com.intellij.xdebugger.breakpoints.* +import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties +import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties import org.jetbrains.kotlin.idea.core.refactoring.getLineNumber import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpoint import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType +import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType import org.jetbrains.kotlin.idea.debugger.stepping.* import org.jetbrains.kotlin.idea.test.JetJdkAndLibraryProjectDescriptor import org.jetbrains.kotlin.idea.util.application.runReadAction @@ -261,49 +264,68 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() { } override fun createBreakpoints(file: PsiFile?) { - super.createBreakpoints(file) - if (file == null) return - val document = PsiDocumentManager.getInstance(myProject).getDocument(file) ?: return + val document = runReadAction { PsiDocumentManager.getInstance(myProject).getDocument(file) } ?: return val breakpointManager = XDebuggerManager.getInstance(myProject).getBreakpointManager() - val breakpointType = javaClass() as Class>, XBreakpointProperties<*>>> - val type = XDebuggerUtil.getInstance().findBreakpointType>>(breakpointType) as KotlinFieldBreakpointType + val kotlinFieldBreakpointType = findBreakpointType(KotlinFieldBreakpointType::class.java) + val kotlinLineBreakpointType = findBreakpointType(KotlinLineBreakpointType::class.java) val virtualFile = file.getVirtualFile() val runnable = { var offset = -1; while (true) { val fileText = document.getText() - offset = fileText.indexOf("FieldWatchpoint!", offset + 1) + offset = fileText.indexOf("point!", offset + 1) if (offset == -1) break val commentLine = document.getLineNumber(offset) - val comment = fileText.substring(document.getLineStartOffset(commentLine), document.getLineEndOffset(commentLine)) + val comment = fileText.substring(document.getLineStartOffset(commentLine), document.getLineEndOffset(commentLine)).trim() val lineIndex = commentLine + 1 - val fieldName = comment.substringAfter("//FieldWatchpoint! (").substringBefore(")") - if (!type.canPutAt(virtualFile, lineIndex, myProject)) continue - - val xBreakpoint = runWriteAction { - breakpointManager.addLineBreakpoint( - type as XLineBreakpointType>, - virtualFile.getUrl(), + if (comment.startsWith("//FieldWatchpoint!")) { + val javaBreakpoint = createBreakpointOfType( + breakpointManager, + kotlinFieldBreakpointType as XLineBreakpointType>, lineIndex, - type.createBreakpointProperties(virtualFile, lineIndex) - ) + virtualFile) + if (javaBreakpoint is KotlinFieldBreakpoint) { + val fieldName = comment.substringAfter("//FieldWatchpoint! (").substringBefore(")") + javaBreakpoint.setFieldName(fieldName) + javaBreakpoint.setWatchAccess(fileText.getValueForSetting("WATCH_FIELD_ACCESS", true)) + javaBreakpoint.setWatchModification(fileText.getValueForSetting("WATCH_FIELD_MODIFICATION", true)) + javaBreakpoint.setWatchInitialization(fileText.getValueForSetting("WATCH_FIELD_INITIALISATION", false)) + BreakpointManager.addBreakpoint(javaBreakpoint) + println("KotlinFieldBreakpoint created at ${file.virtualFile.name}:${lineIndex + 1}", ProcessOutputTypes.SYSTEM) + } } + else if (comment.startsWith("//Breakpoint!")) { + val javaBreakpoint = createBreakpointOfType( + breakpointManager, + kotlinLineBreakpointType as XLineBreakpointType>, + lineIndex, + virtualFile) + if (javaBreakpoint is LineBreakpoint<*>) { - val javaBreakpoint = BreakpointManager.getJavaBreakpoint(xBreakpoint) - if (javaBreakpoint is KotlinFieldBreakpoint) { - javaBreakpoint.setFieldName(fieldName) - javaBreakpoint.setWatchAccess(fileText.getValueForSetting("WATCH_FIELD_ACCESS", true)) - javaBreakpoint.setWatchModification(fileText.getValueForSetting("WATCH_FIELD_MODIFICATION", true)) - javaBreakpoint.setWatchInitialization(fileText.getValueForSetting("WATCH_FIELD_INITIALISATION", false)) - BreakpointManager.addBreakpoint(javaBreakpoint) - println("KotlinFieldBreakpoint created at ${file.getVirtualFile().getName()}:${lineIndex + 1}", ProcessOutputTypes.SYSTEM) + if (comment.contains("(")) { + val ordinal = comment.substringAfter("//Breakpoint! (").substringBefore(")").toInt() + + val properties = javaBreakpoint.xBreakpoint.properties as? JavaLineBreakpointProperties ?: continue + properties.lambdaOrdinal = ordinal + + BreakpointManager.addBreakpoint(javaBreakpoint) + println("LineBreakpoint created at ${file.virtualFile.name}:${lineIndex + 1} lambdaOrdinal = $ordinal", ProcessOutputTypes.SYSTEM) + } + else { + BreakpointManager.addBreakpoint(javaBreakpoint) + println("LineBreakpoint created at ${file.virtualFile.name}:${lineIndex + 1}", ProcessOutputTypes.SYSTEM) + } + } + } + else { + throw AssertionError("Cannot create breakpoint at line ${lineIndex + 1}") } } } @@ -316,6 +338,29 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() { } } + private fun createBreakpointOfType( + breakpointManager: XBreakpointManager, + breakpointType: XLineBreakpointType>, + lineIndex: Int, + virtualFile: VirtualFile + ): Breakpoint>? { + if (!breakpointType.canPutAt(virtualFile, lineIndex, myProject)) return null + val xBreakpoint = runWriteAction { + breakpointManager.addLineBreakpoint( + breakpointType, + virtualFile.url, + lineIndex, + breakpointType.createBreakpointProperties(virtualFile, lineIndex) + ) + } + return BreakpointManager.getJavaBreakpoint(xBreakpoint) + } + + private inline fun findBreakpointType(javaClass: Class): T { + val kotlinFieldBreakpointTypeClass = javaClass as Class>, XBreakpointProperties<*>>> + return XDebuggerUtil.getInstance().findBreakpointType>>(kotlinFieldBreakpointTypeClass) as T + } + protected fun createAdditionalBreakpoints(fileText: String) { val breakpoints = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileText, "// ADDITIONAL_BREAKPOINT: ") for (breakpoint in breakpoints) { diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java index 13901a948fb..ff178ca65c5 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -688,6 +688,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat doMultipleBreakpointsTest(fileName); } + @TestMetadata("multipleBreakpointsAtLine.kt") + public void testMultipleBreakpointsAtLine() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/multipleBreakpointsAtLine.kt"); + doMultipleBreakpointsTest(fileName); + } + @TestMetadata("whenEntry.kt") public void testWhenEntry() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/whenEntry.kt"); @@ -706,6 +712,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat doMultipleBreakpointsTest(fileName); } + @TestMetadata("withoutBodyProperties2.kt") + public void testWithoutBodyProperties2() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyProperties2.kt"); + doMultipleBreakpointsTest(fileName); + } + @TestMetadata("withoutBodyTypeParameters.kt") public void testWithoutBodyTypeParameters() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.kt");