Implement 'Skip simple getters' for debugger

This commit is contained in:
Natalia Ukhorskaya
2015-12-09 16:05:02 +03:00
parent b950bf0e6e
commit ea8de883ac
12 changed files with 283 additions and 72 deletions
+1
View File
@@ -515,6 +515,7 @@
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointHandlerFactory"/>
<debugger.javaBreakpointHandlerFactory implementation="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointHandlerFactory"/>
<debugger.jvmSteppingCommandProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingCommandProvider"/>
<debugger.simplePropertyGetterProvider implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSimpleGetterProvider"/>
<codeInsight.implementMethod language="kotlin" implementationClass="org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMembersHandler"/>
<codeInsight.overrideMethod language="kotlin" implementationClass="org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMembersHandler"/>
@@ -53,12 +53,15 @@ import org.jetbrains.kotlin.fileClasses.internalNameWithoutInnerClasses
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.core.refactoring.getLineStartOffset
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory
import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
import org.jetbrains.kotlin.idea.util.DebuggerUtils
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
@@ -125,12 +128,30 @@ public class KotlinPositionManager(private val myDebugProcess: DebugProcess) : M
if (lambdaOrFunIfInside != null) {
return SourcePosition.createFromElement(lambdaOrFunIfInside.bodyExpression!!)
}
val property = getParameterIfInConstructor(location, psiFile, lineNumber)
if (property != null) {
return SourcePosition.createFromElement(property)
}
return SourcePosition.createFromLine(psiFile, lineNumber)
}
throw NoDataException.INSTANCE
}
private fun getParameterIfInConstructor(location: Location, file: KtFile, lineNumber: Int): KtParameter? {
val lineStartOffset = file.getLineStartOffset(lineNumber) ?: return null
val elementAt = file.findElementAt(lineStartOffset)
val contextElement = KotlinCodeFragmentFactory.getContextElement(elementAt)
val methodName = location.method().name()
if (contextElement is KtClass && JvmAbi.isGetterName(methodName)) {
val parameterForGetter = contextElement.getPrimaryConstructor()?.valueParameters?.firstOrNull() {
it.hasValOrVar() && it.name != null && JvmAbi.getterName(it.name!!) == methodName
} ?: return null
return parameterForGetter
}
return null
}
private fun getLambdaOrFunIfInside(location: Location, file: KtFile, lineNumber: Int): KtFunction? {
val currentLocationFqName = location.declaringType().name()
if (currentLocationFqName == null) return null
@@ -180,81 +180,13 @@ private fun getExpressionToAddDebugExpressionBefore(tmpFile: KtFile, contextElem
}
private fun addDebugExpressionBeforeContextElement(codeFragment: KtCodeFragment, contextElement: PsiElement): List<KtExpression> {
val psiFactory = KtPsiFactory(codeFragment)
fun insertNewInitializer(classBody: KtClassBody): PsiElement? {
val initializer = psiFactory.createAnonymousInitializer()
val newInitializer = (classBody.addAfter(initializer, classBody.firstChild) as KtAnonymousInitializer)
val block = newInitializer.body as KtBlockExpression?
return block?.lastChild
}
val elementBefore = when {
contextElement is KtFile -> {
val fakeFunction = psiFactory.createFunction("fun _debug_fun_() {}")
contextElement.add(psiFactory.createNewLine())
val newFakeFun = contextElement.add(fakeFunction) as KtNamedFunction
newFakeFun.bodyExpression!!.lastChild
}
contextElement is KtProperty && !contextElement.isLocal -> {
val delegateExpressionOrInitializer = contextElement.delegateExpressionOrInitializer
if (delegateExpressionOrInitializer != null) {
wrapInRunFun(delegateExpressionOrInitializer)
}
else {
val getter = contextElement.getter!!
if (!getter.hasBlockBody()) {
wrapInRunFun(getter.bodyExpression!!)
}
else {
(getter.bodyExpression as KtBlockExpression).statements.first()
}
}
}
contextElement is KtPrimaryConstructor -> {
val classOrObject = contextElement.getContainingClassOrObject()
insertNewInitializer(classOrObject.getOrCreateBody())
}
contextElement is KtClassOrObject -> {
insertNewInitializer(contextElement.getOrCreateBody())
}
contextElement is KtFunctionLiteral -> {
val block = contextElement.bodyExpression!!
block.statements.firstOrNull() ?: block.lastChild
}
contextElement is KtDeclarationWithBody && !contextElement.hasBody()-> {
val block = psiFactory.createBlock("")
val newBlock = contextElement.add(block) as KtBlockExpression
newBlock.rBrace
}
contextElement is KtDeclarationWithBody && !contextElement.hasBlockBody()-> {
wrapInRunFun(contextElement.bodyExpression!!)
}
contextElement is KtDeclarationWithBody && contextElement.hasBlockBody()-> {
val block = contextElement.bodyExpression as KtBlockExpression
val last = block.statements.lastOrNull()
if (last is KtReturnExpression)
last
else
block.rBrace
}
contextElement is KtWhenEntry -> {
val entryExpression = contextElement.expression
if (entryExpression is KtBlockExpression) {
entryExpression.statements.firstOrNull() ?: entryExpression.lastChild
}
else {
wrapInRunFun(entryExpression!!)
}
}
else -> {
contextElement
}
}
val elementBefore = findElementBefore(contextElement)
val parent = elementBefore?.parent
if (parent == null || elementBefore == null) return emptyList()
val psiFactory = KtPsiFactory(codeFragment)
parent.addBefore(psiFactory.createNewLine(), elementBefore)
fun insertExpression(expr: KtElement?): List<KtExpression> {
@@ -282,6 +214,84 @@ private fun addDebugExpressionBeforeContextElement(codeFragment: KtCodeFragment,
return insertExpression(debugExpression)
}
private fun findElementBefore(contextElement: PsiElement): PsiElement? {
val psiFactory = KtPsiFactory(contextElement)
fun insertNewInitializer(classBody: KtClassBody): PsiElement? {
val initializer = psiFactory.createAnonymousInitializer()
val newInitializer = (classBody.addAfter(initializer, classBody.firstChild) as KtAnonymousInitializer)
val block = newInitializer.body as KtBlockExpression?
return block?.lastChild
}
return when {
contextElement is KtFile -> {
val fakeFunction = psiFactory.createFunction("fun _debug_fun_() {}")
contextElement.add(psiFactory.createNewLine())
val newFakeFun = contextElement.add(fakeFunction) as KtNamedFunction
newFakeFun.bodyExpression!!.lastChild
}
contextElement is KtProperty && !contextElement.isLocal -> {
val delegateExpressionOrInitializer = contextElement.delegateExpressionOrInitializer
if (delegateExpressionOrInitializer != null) {
wrapInRunFun(delegateExpressionOrInitializer)
}
else {
val getter = contextElement.getter!!
if (!getter.hasBlockBody()) {
wrapInRunFun(getter.bodyExpression!!)
}
else {
(getter.bodyExpression as KtBlockExpression).statements.first()
}
}
}
contextElement is KtParameter -> {
val ownerFunction = contextElement.ownerFunction!!
findElementBefore(ownerFunction)
}
contextElement is KtPrimaryConstructor -> {
val classOrObject = contextElement.getContainingClassOrObject()
insertNewInitializer(classOrObject.getOrCreateBody())
}
contextElement is KtClassOrObject -> {
insertNewInitializer(contextElement.getOrCreateBody())
}
contextElement is KtFunctionLiteral -> {
val block = contextElement.bodyExpression!!
block.statements.firstOrNull() ?: block.lastChild
}
contextElement is KtDeclarationWithBody && !contextElement.hasBody() -> {
val block = psiFactory.createBlock("")
val newBlock = contextElement.add(block) as KtBlockExpression
newBlock.rBrace
}
contextElement is KtDeclarationWithBody && !contextElement.hasBlockBody() -> {
wrapInRunFun(contextElement.bodyExpression!!)
}
contextElement is KtDeclarationWithBody && contextElement.hasBlockBody() -> {
val block = contextElement.bodyExpression as KtBlockExpression
val last = block.statements.lastOrNull()
if (last is KtReturnExpression)
last
else
block.rBrace
}
contextElement is KtWhenEntry -> {
val entryExpression = contextElement.expression
if (entryExpression is KtBlockExpression) {
entryExpression.statements.firstOrNull() ?: entryExpression.lastChild
}
else {
wrapInRunFun(entryExpression!!)
}
}
else -> {
contextElement
}
}
}
private fun replaceByRunFunction(expression: KtExpression): KtCallExpression {
val callExpression = KtPsiFactory(expression).createExpression("run { \n${expression.text} \n}") as KtCallExpression
val replaced = expression.replaced(callExpression)
@@ -0,0 +1,54 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.debugger.stepping
import com.intellij.debugger.engine.SimplePropertyGetterProvider
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.psi.*
class KotlinSimpleGetterProvider : SimplePropertyGetterProvider {
override fun isInsideSimpleGetter(element: PsiElement): Boolean {
// class A(val a: Int)
if (element is KtParameter) {
return true
}
val accessor = PsiTreeUtil.getParentOfType(element, KtPropertyAccessor::class.java)
if (accessor != null && accessor.isGetter) {
val body = accessor.bodyExpression
return when (body) {
// val a: Int get() { return field }
is KtBlockExpression -> {
val returnedExpression = (body.statements.singleOrNull() as? KtReturnExpression)?.returnedExpression ?: return false
returnedExpression.textMatches("field")
}
// val a: Int get() = field
is KtExpression -> body.textMatches("field")
else -> false
}
}
val property = PsiTreeUtil.getParentOfType(element, KtProperty::class.java)
// val a = foo()
if (property != null) {
return property.getter == null
}
return false
}
}
+9
View File
@@ -0,0 +1,9 @@
LineBreakpoint created at onGetter.kt:9
!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! onGetter.OnGetterKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
onGetter.kt:9
onGetter.kt:12
Compile bytecode for prop
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,8 @@
LineBreakpoint created at skipSimpleGetter.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! skipSimpleGetter.SkipSimpleGetterKt
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
skipSimpleGetter.kt:6
skipSimpleGetter.kt:7
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,12 @@
package onGetter
fun main(args: Array<String>) {
val a = A(1)
// EXPRESSION: prop
// RESULT: 1: I
// STEP_INTO: 1
//Breakpoint!
a.prop
}
class A(val prop: Int)
@@ -0,0 +1,25 @@
package skipSimpleGetter
fun main(args: Array<String>) {
val a = A(1)
//Breakpoint!
a.a1 + a.a2 + a.a3 + a.a4
}
class A(val a4: Int) {
// only init
val a1 = 1
// simple get, expression body
val a2: Int = 1
get() = field
// simple get, block body
val a3: Int = 1
get() {
return field
}
}
// STEP_INTO: 21
// SKIP_GETTERS: true
@@ -241,6 +241,12 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
doStepIntoTest(fileName);
}
@TestMetadata("skipSimpleGetter.kt")
public void testSkipSimpleGetter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepInto/skipSimpleGetter.kt");
doStepIntoTest(fileName);
}
@TestMetadata("stepIntoFromInlineFun.kt")
public void testStepIntoFromInlineFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/stepInto/stepIntoFromInlineFun.kt");
@@ -48,7 +48,7 @@ public class MockLocation implements Location {
@Override
public Method method() {
throw new UnsupportedOperationException();
return new MockMethod();
}
@Override
@@ -0,0 +1,59 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.debugger
import com.sun.jdi.Method
class MockMethod : Method {
override fun name() = ""
override fun isSynthetic() = throw UnsupportedOperationException()
override fun isFinal() = throw UnsupportedOperationException()
override fun isStatic() = throw UnsupportedOperationException()
override fun declaringType() = throw UnsupportedOperationException()
override fun signature() = throw UnsupportedOperationException()
override fun genericSignature() = throw UnsupportedOperationException()
override fun variables() = throw UnsupportedOperationException()
override fun variablesByName(name: String?) = throw UnsupportedOperationException()
override fun bytecodes() = throw UnsupportedOperationException()
override fun isBridge() = throw UnsupportedOperationException()
override fun isObsolete() = throw UnsupportedOperationException()
override fun isSynchronized() = throw UnsupportedOperationException()
override fun allLineLocations() = throw UnsupportedOperationException()
override fun allLineLocations(stratum: String?, sourceName: String?) = throw UnsupportedOperationException()
override fun isNative() = throw UnsupportedOperationException()
override fun locationOfCodeIndex(codeIndex: Long) = throw UnsupportedOperationException()
override fun arguments() = throw UnsupportedOperationException()
override fun isAbstract() = throw UnsupportedOperationException()
override fun isVarArgs() = throw UnsupportedOperationException()
override fun returnTypeName() = throw UnsupportedOperationException()
override fun argumentTypes( ) = throw UnsupportedOperationException()
override fun isConstructor() = throw UnsupportedOperationException()
override fun locationsOfLine(lineNumber: Int) = throw UnsupportedOperationException()
override fun locationsOfLine(stratum: String?, sourceName: String?, lineNumber: Int) = throw UnsupportedOperationException()
override fun argumentTypeNames() = throw UnsupportedOperationException()
override fun returnType() = throw UnsupportedOperationException()
override fun isStaticInitializer() = throw UnsupportedOperationException()
override fun location() = throw UnsupportedOperationException()
override fun compareTo(other: Method?) = throw UnsupportedOperationException()
override fun isPackagePrivate() = throw UnsupportedOperationException()
override fun isPrivate() = throw UnsupportedOperationException()
override fun isProtected() = throw UnsupportedOperationException()
override fun isPublic() = throw UnsupportedOperationException()
override fun modifiers() = throw UnsupportedOperationException()
override fun virtualMachine() = throw UnsupportedOperationException()
}
@@ -223,6 +223,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
doSingleBreakpointTest(fileName);
}
@TestMetadata("onGetter.kt")
public void testOnGetter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/onGetter.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("onObjectHeader.kt")
public void testOnObjectHeader() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/onObjectHeader.kt");