From 129ca7f2d8a9aa00c50324eaa88dc8340190997f Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Wed, 3 Jul 2019 18:27:47 +0900 Subject: [PATCH] Debugger: Fix breakpoint applicability (KT-10984) Ensure that breakpoints of each type can be placed only on lines where it makes sense to place a breakpoint. Here is a quick summary of the rules: 1. Method breakpoints are available for functions, property accessors, constructors; 2. Line breakpoints are available on any line with an expression, excluding some cases like 'const' property initializers or annotations; 3. Line breakpoints should be available on a '}' in functions and lambdas; 4. Line breakpoints are not suggested for one-liners; 5. Lambda breakpoints should be shown for single-line lambdas. --- .../kotlin/idea/KotlinBundle.properties | 1 + .../breakpoints/KotlinFieldBreakpointType.kt | 9 +- .../breakpoints/KotlinLineBreakpointType.java | 79 ++++++- .../LineBreakpointExpressionVisitor.kt | 217 ++++++++++++++++++ .../breakpoints/breakpointTypeUtils.kt | 111 +++------ .../multipleBreakpoints/constructors.kt | 24 +- .../multipleBreakpoints/constructors.out | 35 +-- .../withoutBodyFunctions.kt | 12 +- .../withoutBodyFunctions.out | 12 +- .../withoutBodyTypeParameters.kt | 2 +- .../withoutBodyTypeParameters.out | 2 +- .../extraVariables/evFunctionDeclaration.kt | 2 +- .../extraVariables/evFunctionDeclaration.out | 2 +- .../src/evaluate/singleBreakpoint/kt15259.kt | 5 +- .../src/evaluate/singleBreakpoint/kt15259.out | 1 + .../evaluate/singleBreakpoint/staticField.kt | 2 +- .../evaluate/singleBreakpoint/staticField.out | 2 +- .../idea/debugger/KotlinDebuggerTestBase.kt | 15 +- 18 files changed, 407 insertions(+), 126 deletions(-) create mode 100644 idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/LineBreakpointExpressionVisitor.kt diff --git a/idea/idea-analysis/resources/org/jetbrains/kotlin/idea/KotlinBundle.properties b/idea/idea-analysis/resources/org/jetbrains/kotlin/idea/KotlinBundle.properties index 5d1d0fded35..b7b9d11cd18 100644 --- a/idea/idea-analysis/resources/org/jetbrains/kotlin/idea/KotlinBundle.properties +++ b/idea/idea-analysis/resources/org/jetbrains/kotlin/idea/KotlinBundle.properties @@ -243,6 +243,7 @@ debugger.filter.ignore.internal.classes=Do not step into Kotlin runtime library debugger.data.view.delegated.properties=Calculate values of delegated properties (may affect program execution) debugger.field.watchpoints.tab.title=Kotlin Field Watchpoints debugger.function.breakpoints.tab.title=Kotlin Function Breakpoints +debugger.line.breakpoints.tab.title=Kotlin Line Breakpoints debugger.field.watchpoints.properties.panel.field.access.label=Field &access debugger.field.watchpoints.properties.panel.field.modification.label=Field &modification debugger.field.watchpoints.properties.panel.field.initialization.label=Field &initialization diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinFieldBreakpointType.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinFieldBreakpointType.kt index 1dd5efc1970..f4060d28dd7 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinFieldBreakpointType.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinFieldBreakpointType.kt @@ -41,6 +41,7 @@ import org.jetbrains.kotlin.idea.debugger.breakpoints.dialog.AddFieldBreakpointD import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.psi.KtDeclarationContainer import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.psi.KtProperty import javax.swing.JComponent @@ -52,7 +53,13 @@ class KotlinFieldBreakpointType : JavaBreakpointType + when (element) { + is KtProperty -> ApplicabilityResult.definitely(!element.isLocal) + is KtParameter -> ApplicabilityResult.definitely(element.hasValOrVar()) + else -> ApplicabilityResult.UNKNOWN + } + } } override fun getPriority() = 120 diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java index 080c811f338..3fb8f538de4 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/KotlinLineBreakpointType.java @@ -29,6 +29,7 @@ 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.impl.source.tree.LeafPsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.util.PsiTreeUtil; @@ -40,16 +41,19 @@ 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.KotlinBundle; import org.jetbrains.kotlin.idea.debugger.KotlinPositionManager; -import org.jetbrains.kotlin.psi.KtClassInitializer; +import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.psi.KtElement; -import org.jetbrains.kotlin.psi.KtFunction; +import org.jetbrains.kotlin.psi.*; import java.util.List; +import static org.jetbrains.kotlin.idea.debugger.breakpoints.BreakpointTypeUtilsKt.isBreakpointApplicable; + public class KotlinLineBreakpointType extends JavaLineBreakpointType { public KotlinLineBreakpointType() { - super("kotlin-line", "Kotlin Line Breakpoints"); + super("kotlin-line", KotlinBundle.message("debugger.line.breakpoints.tab.title")); } @NotNull @@ -126,13 +130,50 @@ public class KotlinLineBreakpointType extends JavaLineBreakpointType { @Override public boolean canPutAt(@NotNull VirtualFile file, int line, @NotNull Project project) { - return BreakpointTypeUtilsKt.canPutAt(file, line, project, getClass()); + return isBreakpointApplicable(file, line, project, element -> { + if (element instanceof KtDestructuringDeclaration) { + return ApplicabilityResult.MAYBE_YES; + } + + if (isClosingBraceInMethod(element)) { + return ApplicabilityResult.MAYBE_YES; + } + + if (element instanceof KtElement) { + LineBreakpointExpressionVisitor visitor = LineBreakpointExpressionVisitor.of(file, line); + if (visitor != null) { + ApplicabilityResult result = ((KtElement) element).accept(visitor, null); + if (result == null) { + return ApplicabilityResult.UNKNOWN; + } + return result; + } + } + + return ApplicabilityResult.UNKNOWN; + }); + } + + private static boolean isClosingBraceInMethod(PsiElement element) { + if (element instanceof LeafPsiElement && element.getNode().getElementType() == KtTokens.RBRACE) { + PsiElement blockExpression = element.getParent(); + if (blockExpression instanceof KtFunctionLiteral) { + return true; + } + if (blockExpression instanceof KtBlockExpression) { + PsiElement owner = blockExpression.getParent(); + if (owner instanceof KtFunction || owner instanceof KtClassInitializer) { + return true; + } + } + } + return false; } @NotNull @Override public List computeVariants(@NotNull Project project, @NotNull XSourcePosition position) { - return BreakpointTypeUtilsKt.computeVariants(project, position, this); + return BreakpointTypeUtilsKt.computeLineBreakpointVariants(project, position, this); } @Nullable @@ -195,4 +236,32 @@ public class KotlinLineBreakpointType extends JavaLineBreakpointType { return super.getSourcePosition(breakpoint); } + + public class LineKotlinBreakpointVariant extends LineJavaBreakpointVariant { + public LineKotlinBreakpointVariant(@NotNull XSourcePosition position, @Nullable PsiElement element, Integer lambdaOrdinal) { + super(position, element, lambdaOrdinal); + } + + @NotNull + @Override + public String getText() { + return "Line Breakpoint"; + } + } + + public class KotlinBreakpointVariant extends JavaBreakpointVariant { + private final int lambdaCount; + + public KotlinBreakpointVariant(@NotNull XSourcePosition position, int lambdaCount) { + super(position); + this.lambdaCount = lambdaCount; + } + + @NotNull + @Override + public String getText() { + String lambdas = lambdaCount > 1 ? "Lambdas" : "Lambda"; + return "Line and " + lambdas + " Breakpoints"; + } + } } diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/LineBreakpointExpressionVisitor.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/LineBreakpointExpressionVisitor.kt new file mode 100644 index 00000000000..6b96cf92c1b --- /dev/null +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/LineBreakpointExpressionVisitor.kt @@ -0,0 +1,217 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.debugger.breakpoints + +import com.intellij.openapi.editor.Document +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.startOffset + +class LineBreakpointExpressionVisitor private constructor( + private val document: Document, + private val mainLine: Int +) : KtVisitor() { + companion object { + @JvmStatic + fun of(file: VirtualFile, line: Int): LineBreakpointExpressionVisitor? { + val document = FileDocumentManager.getInstance().getDocument(file) ?: return null + return LineBreakpointExpressionVisitor(document, line) + } + } + + override fun visitKtElement(element: KtElement, data: Unit?): ApplicabilityResult { + return visitChildren(element.children.asList(), data) + } + + private fun visitChildren(children: List, data: Unit?): ApplicabilityResult { + if (children.isEmpty() || !children.first().isSameLine()) { + return ApplicabilityResult.UNKNOWN + } + + var isApplicable = false + + for (child in children) { + val ktElement = child as? KtElement ?: continue + val result = ktElement.accept(this, data) + if (result.shouldStop) { + return result + } + + isApplicable = isApplicable or result.isApplicable + } + + return ApplicabilityResult.maybe(isApplicable) + } + + override fun visitExpression(expression: KtExpression, data: Unit?): ApplicabilityResult { + if (expression.isSameLine()) { + val superResult = super.visitExpression(expression, data) + if (superResult.shouldStop) { + return superResult + } + + return ApplicabilityResult.MAYBE_YES + } + + return ApplicabilityResult.UNKNOWN + } + + override fun visitClass(klass: KtClass, data: Unit?): ApplicabilityResult { + visitChildren(klass.primaryConstructorParameters, data).handle()?.let { return it } + return klass.body?.accept(this, data) ?: ApplicabilityResult.UNKNOWN + } + + override fun visitObjectDeclaration(declaration: KtObjectDeclaration, data: Unit?): ApplicabilityResult { + return declaration.body?.accept(this, data) ?: ApplicabilityResult.UNKNOWN + } + + override fun visitSecondaryConstructor(constructor: KtSecondaryConstructor, data: Unit?): ApplicabilityResult { + return constructor.bodyExpression?.accept(this, data) + ?.acceptIfMultiLineParent(constructor) + ?: ApplicabilityResult.UNKNOWN + } + + override fun visitParameter(parameter: KtParameter, data: Unit?): ApplicabilityResult { + val defaultValue = parameter.defaultValue + if (defaultValue.isSameLine()) { + return ApplicabilityResult.DEFINITELY_YES + } + + return ApplicabilityResult.UNKNOWN + } + + override fun visitCatchSection(catchClause: KtCatchClause, data: Unit?): ApplicabilityResult { + return ApplicabilityResult.MAYBE_YES + } + + override fun visitAnnotationEntry(annotationEntry: KtAnnotationEntry, data: Unit?): ApplicabilityResult { + return ApplicabilityResult.UNKNOWN + } + + override fun visitProperty(property: KtProperty, data: Unit?): ApplicabilityResult { + if (property.hasModifier(KtTokens.CONST_KEYWORD)) { + return ApplicabilityResult.UNKNOWN + } + + return super.visitProperty(property, data) + } + + override fun visitNamedFunction(function: KtNamedFunction, data: Unit?): ApplicabilityResult { + if (function.isSameLine() && KtPsiUtil.isLocal(function)) { + return ApplicabilityResult.MAYBE_YES + } + + visitChildren(function.valueParameters, data).handle()?.let { return it } + + return function.bodyExpression?.accept(this, data) + ?.acceptIfMultiLineParent(function) + ?: ApplicabilityResult.UNKNOWN + } + + override fun visitPropertyAccessor(accessor: KtPropertyAccessor, data: Unit?): ApplicabilityResult { + return accessor.bodyExpression?.accept(this, data) + ?.acceptIfMultiLineParent(accessor) + ?: ApplicabilityResult.UNKNOWN + } + + override fun visitPropertyDelegate(delegate: KtPropertyDelegate, data: Unit?): ApplicabilityResult { + return ApplicabilityResult.maybe(delegate.isSameLine()) + } + + override fun visitLambdaExpression(expression: KtLambdaExpression, data: Unit?): ApplicabilityResult { + if (expression.isSameLine()) { + return ApplicabilityResult.DEFINITELY_YES + } + + return ApplicabilityResult.UNKNOWN + } + + override fun visitWhenEntry(jetWhenEntry: KtWhenEntry, data: Unit?): ApplicabilityResult { + jetWhenEntry.expression?.accept(this, data)?.handle()?.let { return it } + return ApplicabilityResult.maybe(jetWhenEntry.conditions.isNotEmpty() || jetWhenEntry.isElse) + } + + override fun visitDestructuringDeclaration(multiDeclaration: KtDestructuringDeclaration, data: Unit?): ApplicabilityResult { + return ApplicabilityResult.maybe(multiDeclaration.isSameLine()) + } + + override fun visitBlockExpression(expression: KtBlockExpression, data: Unit?): ApplicabilityResult { + return visitChildren(expression.statements, data) + } + + override fun visitParenthesizedExpression(expression: KtParenthesizedExpression, data: Unit?): ApplicabilityResult { + val parenthesized = expression.expression ?: return ApplicabilityResult.UNKNOWN + val lines = parenthesized.getLines() + + if (lines.start > mainLine) { + return ApplicabilityResult.UNKNOWN + } + + if (lines.isMultiLine) { + return parenthesized.accept(this, data) + } + + return ApplicabilityResult.UNKNOWN + } + + private fun PsiElement?.isSameLine(): Boolean { + val lines = getLines() + return lines.start == mainLine + } + + private fun PsiElement?.getLines(): Lines { + if (this == null) { + return Lines.EMPTY + } + + val startOffset = this.startOffset + val endOffset = this.endOffset + + if (startOffset < 0 || endOffset < 0) { + return Lines.EMPTY + } + + val startLine = document.getLineNumber(startOffset) + val endLine = document.getLineNumber(endOffset) + return Lines(startLine, endLine) + } + + private fun ApplicabilityResult.acceptIfMultiLineParent(parent: KtExpression): ApplicabilityResult? { + if (this.shouldStop) { + return this + } + + if (parent.getLines().isSingleLine) { + return null + } + + return this + } +} + +private fun ApplicabilityResult.handle(): ApplicabilityResult? { + if (this.shouldStop || this.isApplicable) { + return this + } + + return null +} + +private class Lines(val start: Int, val end: Int) { + companion object { + val EMPTY = Lines(-1, -1) + } + + val isSingleLine: Boolean + get() = start == end && start >= 0 + + val isMultiLine: Boolean + get() = start != end && start >= 0 && end >= 0 +} \ No newline at end of file diff --git a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/breakpointTypeUtils.kt b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/breakpointTypeUtils.kt index de0990f9ce4..ba9fc1acf08 100644 --- a/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/breakpointTypeUtils.kt +++ b/idea/jvm-debugger/jvm-debugger-core/src/org/jetbrains/kotlin/idea/debugger/breakpoints/breakpointTypeUtils.kt @@ -39,51 +39,6 @@ import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance import java.util.* -fun canPutAt(file: VirtualFile, line: Int, project: Project, breakpointTypeClass: Class<*>): Boolean { - val psiFile = PsiManager.getInstance(project).findFile(file) - - if (psiFile == null || psiFile.virtualFile?.fileType != KotlinFileType.INSTANCE) { - return false - } - - val document = FileDocumentManager.getInstance().getDocument(file) ?: return false - - var result: Class<*>? = null - XDebuggerUtil.getInstance().iterateLine(project, document, line, fun (el: PsiElement): Boolean { - // avoid comments - if (el is PsiWhiteSpace || PsiTreeUtil.getParentOfType(el, PsiComment::class.java, false) != null) { - return true - } - - var element = el - var parent = element.parent - while (parent != null) { - val offset = parent.textOffset - if (offset >= 0 && document.getLineNumber(offset) != line) break - - element = parent - parent = element.parent - } - - if (element is KtProperty || element is KtParameter) { - result = if ((element is KtParameter && element.hasValOrVar()) || (element is KtProperty && !element.isLocal)) { - KotlinFieldBreakpointType::class.java - } - else { - KotlinLineBreakpointType::class.java - } - return false - } - else { - result = KotlinLineBreakpointType::class.java - } - - return true - }) - - return result == breakpointTypeClass -} - class ApplicabilityResult(val isApplicable: Boolean, val shouldStop: Boolean) { companion object { @JvmStatic @@ -111,32 +66,34 @@ fun isBreakpointApplicable(file: VirtualFile, line: Int, project: Project, check } val document = FileDocumentManager.getInstance().getDocument(file) ?: return false - var isApplicable = false - val checked = HashSet() + return runReadAction { + var isApplicable = false + val checked = HashSet() - XDebuggerUtil.getInstance().iterateLine(project, document, line, fun(element: PsiElement): Boolean { - if (element is PsiWhiteSpace || element.getParentOfType(false) != null) { - return true - } + XDebuggerUtil.getInstance().iterateLine(project, document, line, fun(element: PsiElement): Boolean { + if (element is PsiWhiteSpace || element.getParentOfType(false) != null) { + return true + } - val parent = getTopmostParentOnLineOrSelf(element, document, line) - if (!checked.add(parent)) { - return true - } + val parent = getTopmostParentOnLineOrSelf(element, document, line) + if (!checked.add(parent)) { + return true + } - val result = checker(parent) + val result = checker(parent) - if (result.shouldStop && !result.isApplicable) { - isApplicable = false - return false - } + if (result.shouldStop && !result.isApplicable) { + isApplicable = false + return false + } - isApplicable = isApplicable or result.isApplicable - return !result.shouldStop - }) + isApplicable = isApplicable or result.isApplicable + return !result.shouldStop + }) - return isApplicable + return@runReadAction isApplicable + } } private fun getTopmostParentOnLineOrSelf(element: PsiElement, document: Document, line: Int): PsiElement { @@ -153,7 +110,7 @@ private fun getTopmostParentOnLineOrSelf(element: PsiElement, document: Document return current } -fun computeVariants( +fun computeLineBreakpointVariants( project: Project, position: XSourcePosition, kotlinBreakpointType: KotlinLineBreakpointType @@ -167,15 +124,19 @@ fun computeVariants( val result = LinkedList() val elementAt = pos.elementAt.parentsWithSelf.firstIsInstance() - val mainMethod = KotlinLineBreakpointType.getContainingMethod(elementAt) + val mainMethod = PsiTreeUtil.getParentOfType(elementAt, KtFunction::class.java, false) + + var mainMethodAdded = false + if (mainMethod != null) { - result.add( - kotlinBreakpointType.LineJavaBreakpointVariant( - position, - CodeInsightUtils.getTopmostElementAtOffset(elementAt, pos.offset), - -1 - ) - ) + val bodyExpression = mainMethod.bodyExpression + val isLambdaResult = bodyExpression is KtLambdaExpression && bodyExpression.functionLiteral in lambdas + + if (!isLambdaResult) { + val variantElement = CodeInsightUtils.getTopmostElementAtOffset(elementAt, pos.offset) + result.add(kotlinBreakpointType.LineKotlinBreakpointVariant(position, variantElement, -1)) + mainMethodAdded = true + } } lambdas.forEachIndexed { ordinal, lambda -> @@ -186,7 +147,9 @@ fun computeVariants( } } - result.add(kotlinBreakpointType.JavaBreakpointVariant(position)) + if (mainMethodAdded && result.size > 1) { + result.add(kotlinBreakpointType.KotlinBreakpointVariant(position, lambdas.size)) + } return result } diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.kt index b03f26a97aa..726006b7f52 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.kt @@ -5,7 +5,7 @@ class Derived2(): Base(1) { // constructor with body // EXPRESSION: p // RESULT: 1: I - //Breakpoint! + //FunctionBreakpoint! constructor(p: Int): this() { // EXPRESSION: p + 1 // RESULT: 2: I @@ -16,7 +16,7 @@ class Derived2(): Base(1) { // constructor without body // EXPRESSION: p1 + p2 // RESULT: 2: I - //Breakpoint! + //FunctionBreakpoint! constructor(p1: Int, p2: Int): this() } @@ -36,6 +36,7 @@ fun main(args: Array) { Derived1(1) A() + AA() B() C(1) D() @@ -45,33 +46,40 @@ fun main(args: Array) { // EXPRESSION: 1 + 1 // RESULT: 2: I -//Breakpoint! +//FunctionBreakpoint! class A +// EXPRESSION: 1 + 3 +// RESULT: 4: I +//FunctionBreakpoint! +class AA { + +} + // EXPRESSION: 1 + 2 // RESULT: 3: I -//Breakpoint! +//FunctionBreakpoint! class B() // EXPRESSION: a // RESULT: 1: I -//Breakpoint! +//FunctionBreakpoint! class C(val a: Int) class D { // EXPRESSION: 1 + 3 // RESULT: 4: I - //Breakpoint! + //FunctionBreakpoint! constructor() } class E { // EXPRESSION: i // RESULT: 1: I - //Breakpoint! + //FunctionBreakpoint! constructor(i: Int) } // EXPRESSION: a // RESULT: "foo": Ljava/lang/String; -//Breakpoint! +//FunctionBreakpoint! class F(val a: String) \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.out b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.out index cebd30663e2..eab76fd268d 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/constructors.out @@ -1,13 +1,14 @@ -LineBreakpoint created at constructors.kt:9 +FunctionBreakpoint created at constructors.kt:9 LineBreakpoint created at constructors.kt:13 -LineBreakpoint created at constructors.kt:20 +FunctionBreakpoint created at constructors.kt:20 LineBreakpoint created at constructors.kt:28 -LineBreakpoint created at constructors.kt:49 -LineBreakpoint created at constructors.kt:54 -LineBreakpoint created at constructors.kt:59 -LineBreakpoint created at constructors.kt:65 -LineBreakpoint created at constructors.kt:71 -LineBreakpoint created at constructors.kt:77 +FunctionBreakpoint created at constructors.kt:50 +FunctionBreakpoint created at constructors.kt:55 +FunctionBreakpoint created at constructors.kt:62 +FunctionBreakpoint created at constructors.kt:67 +FunctionBreakpoint created at constructors.kt:73 +FunctionBreakpoint created at constructors.kt:79 +FunctionBreakpoint created at constructors.kt:85 Run Java Connected to the target VM constructors.kt:9 @@ -18,17 +19,19 @@ constructors.kt:20 Compile bytecode for p1 + p2 constructors.kt:28 Compile bytecode for i1 -constructors.kt:49 +constructors.kt:50 Compile bytecode for 1 + 1 -constructors.kt:54 -Compile bytecode for 1 + 2 -constructors.kt:59 -Compile bytecode for a -constructors.kt:65 +constructors.kt:55 Compile bytecode for 1 + 3 -constructors.kt:71 +constructors.kt:62 +Compile bytecode for 1 + 2 +constructors.kt:67 +Compile bytecode for a +constructors.kt:73 +Compile bytecode for 1 + 3 +constructors.kt:79 Compile bytecode for i -constructors.kt:77 +constructors.kt:85 Compile bytecode for a Disconnected from the target VM diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyFunctions.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyFunctions.kt index 5156a91c951..9a27893b6fb 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyFunctions.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyFunctions.kt @@ -5,13 +5,13 @@ import kotlin.properties.Delegates // EXPRESSION: 1 + 1 // RESULT: 2: I val aGet: Int - //Breakpoint! + //FunctionBreakpoint! get() = 1 // EXPRESSION: 1 + 2 // RESULT: 3: I val aGet2: Int - //Breakpoint! + //FunctionBreakpoint! get() { return 1 } fun fooWithBody(i: Int): Int { @@ -23,23 +23,23 @@ fun fooWithBody(i: Int): Int { // EXPRESSION: i // RESULT: 2: I -//Breakpoint! +//FunctionBreakpoint! fun foo(i: Int) = i // EXPRESSION: i // RESULT: 2: I -//Breakpoint! +//FunctionBreakpoint! fun fooOneLine(i: Int): Int { return 1 } // EXPRESSION: i // RESULT: 2: I -//Breakpoint! +//FunctionBreakpoint! fun fooEmpty(i: Int) {} object A { // EXPRESSION: test2() // RESULT: 2: I - //Breakpoint! + //FunctionBreakpoint! @JvmStatic fun fooWithoutBodyInsideObject() = test2() fun test2() = 2 } diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyFunctions.out b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyFunctions.out index 7a8c18b7d2e..97e474de7e7 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyFunctions.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyFunctions.out @@ -1,10 +1,10 @@ -LineBreakpoint created at withoutBodyFunctions.kt:9 -LineBreakpoint created at withoutBodyFunctions.kt:15 +FunctionBreakpoint created at withoutBodyFunctions.kt:9 +FunctionBreakpoint created at withoutBodyFunctions.kt:15 LineBreakpoint created at withoutBodyFunctions.kt:21 -LineBreakpoint created at withoutBodyFunctions.kt:27 -LineBreakpoint created at withoutBodyFunctions.kt:32 -LineBreakpoint created at withoutBodyFunctions.kt:37 -LineBreakpoint created at withoutBodyFunctions.kt:43 +FunctionBreakpoint created at withoutBodyFunctions.kt:27 +FunctionBreakpoint created at withoutBodyFunctions.kt:32 +FunctionBreakpoint created at withoutBodyFunctions.kt:37 +FunctionBreakpoint created at withoutBodyFunctions.kt:43 Run Java Connected to the target VM withoutBodyFunctions.kt:9 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.kt b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.kt index af0d05f3483..1de2ed82af5 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.kt @@ -4,7 +4,7 @@ import kotlin.properties.Delegates // EXPRESSION: i // RESULT: instance of java.lang.Integer(id=ID): Ljava/lang/Integer; -//Breakpoint! +//FunctionBreakpoint! fun foo(i: T) = i fun run(i: () -> Int) = 1 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.out b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.out index d155289507b..a217edbeac8 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/withoutBodyTypeParameters.out @@ -1,4 +1,4 @@ -LineBreakpoint created at withoutBodyTypeParameters.kt:8 +FunctionBreakpoint created at withoutBodyTypeParameters.kt:8 Run Java Connected to the target VM withoutBodyTypeParameters.kt:8 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/extraVariables/evFunctionDeclaration.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/extraVariables/evFunctionDeclaration.kt index 221942f10ee..6a8fd3651b8 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/extraVariables/evFunctionDeclaration.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/extraVariables/evFunctionDeclaration.kt @@ -1,7 +1,7 @@ package evFunctionDeclaration class A(val a: Int) { - //Breakpoint! + //FunctionBreakpoint! fun foo() = a } diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/extraVariables/evFunctionDeclaration.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/extraVariables/evFunctionDeclaration.out index 538ca1e7c01..11658163e51 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/extraVariables/evFunctionDeclaration.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/extraVariables/evFunctionDeclaration.out @@ -1,4 +1,4 @@ -LineBreakpoint created at evFunctionDeclaration.kt:5 +FunctionBreakpoint created at evFunctionDeclaration.kt:5 Run Java Connected to the target VM evFunctionDeclaration.kt:5 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt15259.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt15259.kt index 5319a43b206..ffdda337f81 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt15259.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt15259.kt @@ -3,12 +3,15 @@ package kt15259 interface ObjectFace private fun makeFace() = object : ObjectFace { -//Breakpoint! + //Breakpoint! + init { 5 } } fun main() { makeFace() } +// STEP_OVER: 1 + // EXPRESSION: this // RESULT: 'this' is not defined in this context \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt15259.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt15259.out index a5126d6f8da..c8b578ab088 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt15259.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt15259.out @@ -2,6 +2,7 @@ LineBreakpoint created at kt15259.kt:7 Run Java Connected to the target VM kt15259.kt:7 +kt15259.kt:8 Disconnected from the target VM Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/staticField.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/staticField.kt index 5c5f43830bb..3b6520f90a5 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/staticField.kt +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/staticField.kt @@ -5,7 +5,7 @@ fun main() { } val x: String = "x" - //Breakpoint! + //FunctionBreakpoint! get() = "foo" + field // EXPRESSION: field diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/staticField.out b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/staticField.out index f202d69cc71..6c869c2bcae 100644 --- a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/staticField.out +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/staticField.out @@ -1,4 +1,4 @@ -LineBreakpoint created at staticField.kt:9 +FunctionBreakpoint created at staticField.kt:9 Run Java Connected to the target VM staticField.kt:9 diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt index 8a184647cb2..2697615a726 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestBase.kt @@ -42,9 +42,7 @@ import com.intellij.xdebugger.breakpoints.XLineBreakpointType import com.sun.jdi.request.StepRequest import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties -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.breakpoints.* import org.jetbrains.kotlin.idea.debugger.stepping.* import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runWriteAction @@ -326,6 +324,9 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() { val condition = getPropertyFromComment(comment, "condition") createLineBreakpoint(breakpointManager, file, lineIndex, ordinal, condition) } + else if (comment.startsWith("//FunctionBreakpoint!")) { + createFunctionBreakpoint(breakpointManager, file, lineIndex) + } else { throw AssertionError("Cannot create breakpoint at line ${lineIndex + 1}") } @@ -353,6 +354,14 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() { return null } + private fun createFunctionBreakpoint(breakpointManager: XBreakpointManager, file: PsiFile, lineIndex: Int) { + val breakpointType = findBreakpointType(KotlinFunctionBreakpointType::class.java) + val breakpoint = createBreakpointOfType(breakpointManager, breakpointType, lineIndex, file.virtualFile) + if (breakpoint is KotlinFunctionBreakpoint) { + println("FunctionBreakpoint created at ${file.virtualFile.name}:${lineIndex + 1}", ProcessOutputTypes.SYSTEM) + } + } + private fun createLineBreakpoint( breakpointManager: XBreakpointManager, file: PsiFile,