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.
This commit is contained in:
Yan Zhulanow
2019-07-03 18:27:47 +09:00
parent 22c18ffaa9
commit 129ca7f2d8
18 changed files with 407 additions and 126 deletions
@@ -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
@@ -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<KotlinPropertyBreakpointPro
}
override fun canPutAt(file: VirtualFile, line: Int, project: Project): Boolean {
return canPutAt(file, line, project, this::class.java)
return isBreakpointApplicable(file, line, project) { element ->
when (element) {
is KtProperty -> ApplicabilityResult.definitely(!element.isLocal)
is KtParameter -> ApplicabilityResult.definitely(element.hasValOrVar())
else -> ApplicabilityResult.UNKNOWN
}
}
}
override fun getPriority() = 120
@@ -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<JavaBreakpointVariant> 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";
}
}
}
@@ -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<ApplicabilityResult, Unit?>() {
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<PsiElement>, 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
}
@@ -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<PsiElement>()
return runReadAction {
var isApplicable = false
val checked = HashSet<PsiElement>()
XDebuggerUtil.getInstance().iterateLine(project, document, line, fun(element: PsiElement): Boolean {
if (element is PsiWhiteSpace || element.getParentOfType<PsiComment>(false) != null) {
return true
}
XDebuggerUtil.getInstance().iterateLine(project, document, line, fun(element: PsiElement): Boolean {
if (element is PsiWhiteSpace || element.getParentOfType<PsiComment>(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<JavaLineBreakpointType.JavaBreakpointVariant>()
val elementAt = pos.elementAt.parentsWithSelf.firstIsInstance<KtElement>()
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
}
@@ -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<String>) {
Derived1(1)
A()
AA()
B()
C(1)
D()
@@ -45,33 +46,40 @@ fun main(args: Array<String>) {
// 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)
@@ -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
@@ -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
}
@@ -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
@@ -4,7 +4,7 @@ import kotlin.properties.Delegates
// EXPRESSION: i
// RESULT: instance of java.lang.Integer(id=ID): Ljava/lang/Integer;
//Breakpoint!
//FunctionBreakpoint!
fun <T> foo(i: T) = i
fun run(i: () -> Int) = 1
@@ -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
@@ -1,7 +1,7 @@
package evFunctionDeclaration
class A(val a: Int) {
//Breakpoint!
//FunctionBreakpoint!
fun foo() = a
}
@@ -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
@@ -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
@@ -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
@@ -5,7 +5,7 @@ fun main() {
}
val x: String = "x"
//Breakpoint!
//FunctionBreakpoint!
get() = "foo" + field
// EXPRESSION: field
@@ -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
@@ -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,