Debugger: Fix breakpoint firing in '$suspendImpl' (KT-27645)
This commit is contained in:
+3
-1
@@ -498,8 +498,10 @@ fun InstructionAdapter.invokeInvokeSuspendWithUnit(thisName: String) {
|
||||
)
|
||||
}
|
||||
|
||||
const val SUSPEND_IMPL_NAME_SUFFIX = "\$suspendImpl"
|
||||
|
||||
fun Method.getImplForOpenMethod(ownerInternalName: String) =
|
||||
Method("$name\$suspendImpl", returnType, arrayOf(Type.getObjectType(ownerInternalName)) + argumentTypes)
|
||||
Method(name + SUSPEND_IMPL_NAME_SUFFIX, returnType, arrayOf(Type.getObjectType(ownerInternalName)) + argumentTypes)
|
||||
|
||||
fun FunctionDescriptor.isSuspendLambdaOrLocalFunction() = this.isSuspend && when (this) {
|
||||
is AnonymousFunctionDescriptor -> this.isSuspendLambda
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtPsiUtil
|
||||
|
||||
fun KtElement.containingNonLocalDeclaration(): KtDeclaration? {
|
||||
var container = this.containingDeclarationForPseudocode
|
||||
while (container != null && KtPsiUtil.isLocal(container)) {
|
||||
container = container.containingDeclarationForPseudocode
|
||||
}
|
||||
return container
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
package org.jetbrains.kotlin.idea.fir
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.containingDeclarationForPseudocode
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.FirReference
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
@@ -24,6 +23,7 @@ import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.util.containingNonLocalDeclaration
|
||||
|
||||
private val FirResolvePhase.stubMode: Boolean
|
||||
get() = this <= FirResolvePhase.DECLARATIONS
|
||||
@@ -145,14 +145,6 @@ private fun FirDeclaration.runResolve(
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtElement.containingNonLocalDeclaration(): KtDeclaration? {
|
||||
var container = this.containingDeclarationForPseudocode
|
||||
while (container != null && KtPsiUtil.isLocal(container)) {
|
||||
container = container.containingDeclarationForPseudocode
|
||||
}
|
||||
return container
|
||||
}
|
||||
|
||||
fun KtElement.getOrBuildFir(
|
||||
state: FirResolveState,
|
||||
phase: FirResolvePhase = FirResolvePhase.BODY_RESOLVE
|
||||
|
||||
+15
-119
@@ -1,131 +1,27 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.filter
|
||||
|
||||
import com.intellij.debugger.engine.SyntheticTypeComponentProvider
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.safeAllLineLocations
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import kotlin.jvm.internal.FunctionReference
|
||||
import kotlin.jvm.internal.PropertyReference
|
||||
import com.sun.jdi.Method
|
||||
import com.sun.jdi.TypeComponent
|
||||
import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_IMPL_NAME_SUFFIX
|
||||
import org.jetbrains.kotlin.idea.debugger.isInKotlinSources
|
||||
|
||||
class KotlinSyntheticTypeComponentProvider: SyntheticTypeComponentProvider {
|
||||
override fun isSynthetic(typeComponent: TypeComponent?): Boolean {
|
||||
if (typeComponent !is Method) return false
|
||||
|
||||
val containingType = typeComponent.declaringType()
|
||||
val typeName = containingType.name()
|
||||
if (!FqNameUnsafe.isValid(typeName)) return false
|
||||
|
||||
// TODO: this is most likely not necessary since KT-28453 is fixed, but still can be useful when debugging old compiled code
|
||||
if (containingType.isCallableReferenceSyntheticClass()) {
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeComponent.isDelegateToDefaultInterfaceImpl()) return true
|
||||
|
||||
if (typeComponent.location()?.lineNumber() != 1) return false
|
||||
|
||||
if (typeComponent.allLineLocations().any { it.lineNumber() != 1 }) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !typeComponent.declaringType().allLineLocations().any { it.lineNumber() != 1 }
|
||||
}
|
||||
catch(e: AbsentInformationException) {
|
||||
return false
|
||||
}
|
||||
catch(e: UnsupportedOperationException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private tailrec fun ReferenceType?.isCallableReferenceSyntheticClass(): Boolean {
|
||||
if (this !is ClassType) return false
|
||||
val superClass = this.superclass() ?: return false
|
||||
val superClassName = superClass.name()
|
||||
if (superClassName == PropertyReference::class.java.name || superClassName == FunctionReference::class.java.name) {
|
||||
return true
|
||||
}
|
||||
|
||||
// The direct supertype may be PropertyReference0 or something
|
||||
return if (superClassName.startsWith("kotlin.jvm.internal."))
|
||||
superClass.isCallableReferenceSyntheticClass()
|
||||
else
|
||||
false
|
||||
}
|
||||
|
||||
private fun Method.isDelegateToDefaultInterfaceImpl(): Boolean {
|
||||
if (safeAllLineLocations().size != 1) return false
|
||||
if (!virtualMachine().canGetBytecodes()) return false
|
||||
|
||||
if (!hasOnlyInvokeStatic(this)) return false
|
||||
|
||||
return hasInterfaceWithImplementation(this)
|
||||
}
|
||||
|
||||
private val LOAD_INSTRUCTIONS_WITH_INDEX = Opcodes.ILOAD.toByte()..Opcodes.ALOAD.toByte()
|
||||
private val LOAD_INSTRUCTIONS = (Opcodes.ALOAD + 1).toByte()..(Opcodes.IALOAD - 1).toByte()
|
||||
|
||||
private val RETURN_INSTRUCTIONS = Opcodes.IRETURN.toByte()..Opcodes.RETURN.toByte()
|
||||
|
||||
// Check that method contains only load and invokeStatic instructions. Note that if after load goes ldc instruction it could be checkParametersNotNull method invocation
|
||||
private fun hasOnlyInvokeStatic(m: Method): Boolean {
|
||||
val bytecodes = m.bytecodes()
|
||||
var i = 0
|
||||
var isALoad0BeforeStaticCall = false
|
||||
while (i < bytecodes.size) {
|
||||
val instr = bytecodes[i]
|
||||
when {
|
||||
instr == 42.toByte() /* ALOAD_0 */ -> {
|
||||
i += 1
|
||||
isALoad0BeforeStaticCall = true
|
||||
class KotlinSyntheticTypeComponentProvider : KotlinSyntheticTypeComponentProviderBase() {
|
||||
override fun isNotSynthetic(typeComponent: TypeComponent?): Boolean {
|
||||
if (typeComponent is Method && typeComponent.name().endsWith(SUSPEND_IMPL_NAME_SUFFIX)) {
|
||||
if (typeComponent.location()?.isInKotlinSources() == true) {
|
||||
val containingClass = typeComponent.declaringType()
|
||||
if (typeComponent.argumentTypeNames().firstOrNull() == containingClass.name()) {
|
||||
// Suspend wrapper for open method
|
||||
return true
|
||||
}
|
||||
instr in LOAD_INSTRUCTIONS_WITH_INDEX || instr in LOAD_INSTRUCTIONS -> {
|
||||
i += 1
|
||||
if (instr in LOAD_INSTRUCTIONS_WITH_INDEX) i += 1
|
||||
val nextInstr = bytecodes[i]
|
||||
if (nextInstr == Opcodes.LDC.toByte()) {
|
||||
i += 2
|
||||
isALoad0BeforeStaticCall = false
|
||||
}
|
||||
}
|
||||
instr == Opcodes.INVOKESTATIC.toByte() -> {
|
||||
i += 3
|
||||
if (isALoad0BeforeStaticCall && i == (bytecodes.size - 1)) {
|
||||
val nextInstr = bytecodes[i]
|
||||
return nextInstr in RETURN_INSTRUCTIONS
|
||||
}
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: class DefaultImpl can be not loaded
|
||||
private fun hasInterfaceWithImplementation(method: Method): Boolean {
|
||||
val declaringType = method.declaringType() as? ClassType ?: return false
|
||||
val interfaces = declaringType.allInterfaces()
|
||||
val vm = declaringType.virtualMachine()
|
||||
val traitImpls = interfaces.flatMap { vm.classesByName(it.name() + JvmAbi.DEFAULT_IMPLS_SUFFIX) }
|
||||
return traitImpls.any { !it.methodsByName(method.name()).isEmpty() }
|
||||
return super.isNotSynthetic(typeComponent)
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
* 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.filter
|
||||
|
||||
class KotlinSyntheticTypeComponentProvider : KotlinSyntheticTypeComponentProviderBase()
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.filter
|
||||
|
||||
import com.intellij.debugger.engine.SyntheticTypeComponentProvider
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.safeAllLineLocations
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import kotlin.jvm.internal.FunctionReference
|
||||
import kotlin.jvm.internal.PropertyReference
|
||||
|
||||
abstract class KotlinSyntheticTypeComponentProviderBase: SyntheticTypeComponentProvider {
|
||||
override fun isSynthetic(typeComponent: TypeComponent?): Boolean {
|
||||
if (typeComponent !is Method) return false
|
||||
|
||||
val containingType = typeComponent.declaringType()
|
||||
val typeName = containingType.name()
|
||||
if (!FqNameUnsafe.isValid(typeName)) return false
|
||||
|
||||
// TODO: this is most likely not necessary since KT-28453 is fixed, but still can be useful when debugging old compiled code
|
||||
if (containingType.isCallableReferenceSyntheticClass()) {
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeComponent.isDelegateToDefaultInterfaceImpl()) return true
|
||||
|
||||
if (typeComponent.location()?.lineNumber() != 1) return false
|
||||
|
||||
if (typeComponent.allLineLocations().any { it.lineNumber() != 1 }) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !typeComponent.declaringType().allLineLocations().any { it.lineNumber() != 1 }
|
||||
}
|
||||
catch(e: AbsentInformationException) {
|
||||
return false
|
||||
}
|
||||
catch(e: UnsupportedOperationException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private tailrec fun ReferenceType?.isCallableReferenceSyntheticClass(): Boolean {
|
||||
if (this !is ClassType) return false
|
||||
val superClass = this.superclass() ?: return false
|
||||
val superClassName = superClass.name()
|
||||
if (superClassName == PropertyReference::class.java.name || superClassName == FunctionReference::class.java.name) {
|
||||
return true
|
||||
}
|
||||
|
||||
// The direct supertype may be PropertyReference0 or something
|
||||
return if (superClassName.startsWith("kotlin.jvm.internal."))
|
||||
superClass.isCallableReferenceSyntheticClass()
|
||||
else
|
||||
false
|
||||
}
|
||||
|
||||
private fun Method.isDelegateToDefaultInterfaceImpl(): Boolean {
|
||||
if (safeAllLineLocations().size != 1) return false
|
||||
if (!virtualMachine().canGetBytecodes()) return false
|
||||
|
||||
if (!hasOnlyInvokeStatic(this)) return false
|
||||
|
||||
return hasInterfaceWithImplementation(this)
|
||||
}
|
||||
|
||||
private val LOAD_INSTRUCTIONS_WITH_INDEX = Opcodes.ILOAD.toByte()..Opcodes.ALOAD.toByte()
|
||||
private val LOAD_INSTRUCTIONS = (Opcodes.ALOAD + 1).toByte()..(Opcodes.IALOAD - 1).toByte()
|
||||
|
||||
private val RETURN_INSTRUCTIONS = Opcodes.IRETURN.toByte()..Opcodes.RETURN.toByte()
|
||||
|
||||
// Check that method contains only load and invokeStatic instructions. Note that if after load goes ldc instruction it could be checkParametersNotNull method invocation
|
||||
private fun hasOnlyInvokeStatic(m: Method): Boolean {
|
||||
val bytecodes = m.bytecodes()
|
||||
var i = 0
|
||||
var isALoad0BeforeStaticCall = false
|
||||
while (i < bytecodes.size) {
|
||||
val instr = bytecodes[i]
|
||||
when {
|
||||
instr == 42.toByte() /* ALOAD_0 */ -> {
|
||||
i += 1
|
||||
isALoad0BeforeStaticCall = true
|
||||
}
|
||||
instr in LOAD_INSTRUCTIONS_WITH_INDEX || instr in LOAD_INSTRUCTIONS -> {
|
||||
i += 1
|
||||
if (instr in LOAD_INSTRUCTIONS_WITH_INDEX) i += 1
|
||||
val nextInstr = bytecodes[i]
|
||||
if (nextInstr == Opcodes.LDC.toByte()) {
|
||||
i += 2
|
||||
isALoad0BeforeStaticCall = false
|
||||
}
|
||||
}
|
||||
instr == Opcodes.INVOKESTATIC.toByte() -> {
|
||||
i += 3
|
||||
if (isALoad0BeforeStaticCall && i == (bytecodes.size - 1)) {
|
||||
val nextInstr = bytecodes[i]
|
||||
return nextInstr in RETURN_INSTRUCTIONS
|
||||
}
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: class DefaultImpl can be not loaded
|
||||
private fun hasInterfaceWithImplementation(method: Method): Boolean {
|
||||
val declaringType = method.declaringType() as? ClassType ?: return false
|
||||
val interfaces = declaringType.allInterfaces()
|
||||
val vm = declaringType.virtualMachine()
|
||||
val traitImpls = interfaces.flatMap { vm.classesByName(it.name() + JvmAbi.DEFAULT_IMPLS_SUFFIX) }
|
||||
return traitImpls.any { !it.methodsByName(method.name()).isEmpty() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package suspendImpl
|
||||
|
||||
suspend fun main() {
|
||||
Foo().foo()
|
||||
}
|
||||
|
||||
open class Foo {
|
||||
open fun foo() {
|
||||
//Breakpoint!
|
||||
val a = 5
|
||||
val b = 6
|
||||
val c = 7
|
||||
}
|
||||
}
|
||||
|
||||
// STEP_OVER: 2
|
||||
@@ -0,0 +1,10 @@
|
||||
LineBreakpoint created at suspendImpl.kt:10
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
suspendImpl.kt:10
|
||||
suspendImpl.kt:11
|
||||
suspendImpl.kt:12
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
|
||||
+5
@@ -923,6 +923,11 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
|
||||
runTest("idea/testData/debugger/tinyApp/src/stepping/stepOver/stopInlineCallInLocalFunInSecondaryConstructor.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendImpl.kt")
|
||||
public void testSuspendImpl() throws Exception {
|
||||
runTest("idea/testData/debugger/tinyApp/src/stepping/stepOver/suspendImpl.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("whenWithoutExpression.kt")
|
||||
public void testWhenWithoutExpression() throws Exception {
|
||||
runTest("idea/testData/debugger/tinyApp/src/stepping/stepOver/whenWithoutExpression.kt");
|
||||
|
||||
Reference in New Issue
Block a user