Evaluate Expression: support local functions
This commit is contained in:
@@ -47,7 +47,11 @@ class FrameVisitor(context: EvaluationContextImpl) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val localVariable = findLocalVariable(name, asmType, checkType)
|
val localVariable = if (isFunctionType(asmType))
|
||||||
|
findLocalVariableForLocalFun(name, asmType, checkType)
|
||||||
|
else
|
||||||
|
findLocalVariable(name, asmType, checkType)
|
||||||
|
|
||||||
if (localVariable != null) {
|
if (localVariable != null) {
|
||||||
return localVariable
|
return localVariable
|
||||||
}
|
}
|
||||||
@@ -60,16 +64,17 @@ class FrameVisitor(context: EvaluationContextImpl) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (!failIfNotFound)
|
return fail("Cannot find local variable: name = $name${if (checkType) ", type = " + asmType.toString() else ""}", failIfNotFound)
|
||||||
null
|
|
||||||
else
|
|
||||||
throw EvaluateExceptionUtil.createEvaluateException("Cannot find local variable: name = $name${if (checkType) ", type = " + asmType.toString() else ""}")
|
|
||||||
}
|
}
|
||||||
catch(e: InvalidStackFrameException) {
|
catch(e: InvalidStackFrameException) {
|
||||||
throw EvaluateExceptionUtil.createEvaluateException("Local variable $name is unavailable in current frame")
|
throw EvaluateExceptionUtil.createEvaluateException("Local variable $name is unavailable in current frame")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun fail(message: String, shouldFail: Boolean): Value? {
|
||||||
|
return if (shouldFail) throw EvaluateExceptionUtil.createEvaluateException(message) else null
|
||||||
|
}
|
||||||
|
|
||||||
private fun findThis(asmType: Type?): Value? {
|
private fun findThis(asmType: Type?): Value? {
|
||||||
val thisObject = frame!!.thisObject()
|
val thisObject = frame!!.thisObject()
|
||||||
if (thisObject != null) {
|
if (thisObject != null) {
|
||||||
@@ -89,6 +94,15 @@ class FrameVisitor(context: EvaluationContextImpl) {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun findLocalVariableForLocalFun(name: String, asmType: Type?, checkType: Boolean): Value? {
|
||||||
|
return findLocalVariable(name + "$", asmType, checkType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isFunctionType(type: Type?): Boolean {
|
||||||
|
if (type == null || AsmUtil.isPrimitive(type)) return false
|
||||||
|
return type.getInternalName().startsWith("kotlin/Function") || type.getInternalName().startsWith("kotlin/ExtensionFunction")
|
||||||
|
}
|
||||||
|
|
||||||
private fun findLocalVariable(name: String, asmType: Type?, checkType: Boolean): Value? {
|
private fun findLocalVariable(name: String, asmType: Type?, checkType: Boolean): Value? {
|
||||||
val localVariable = frame!!.visibleVariableByName(name)
|
val localVariable = frame!!.visibleVariableByName(name)
|
||||||
if (localVariable == null) return null
|
if (localVariable == null) return null
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ import org.jetbrains.kotlin.psi.codeFragmentUtil.debugTypeInfo
|
|||||||
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
|
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
|
||||||
import org.jetbrains.kotlin.resolve.AnalyzingUtils
|
import org.jetbrains.kotlin.resolve.AnalyzingUtils
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
import org.jetbrains.kotlin.types.Flexibility
|
import org.jetbrains.kotlin.types.Flexibility
|
||||||
import org.jetbrains.org.objectweb.asm.*
|
import org.jetbrains.org.objectweb.asm.*
|
||||||
@@ -167,9 +168,10 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
|||||||
if (extractionResult == null) {
|
if (extractionResult == null) {
|
||||||
throw IllegalStateException("Code fragment cannot be extracted to function")
|
throw IllegalStateException("Code fragment cannot be extracted to function")
|
||||||
}
|
}
|
||||||
|
val parametersDescriptor = extractionResult.getParametersForDebugger()
|
||||||
val extractedFunction = extractionResult.declaration as JetNamedFunction
|
val extractedFunction = extractionResult.declaration as JetNamedFunction
|
||||||
|
|
||||||
val classFileFactory = createClassFileFactory(codeFragment, extractedFunction, context)
|
val classFileFactory = createClassFileFactory(codeFragment, extractedFunction, context, parametersDescriptor)
|
||||||
|
|
||||||
val outputFiles = classFileFactory.asList()
|
val outputFiles = classFileFactory.asList()
|
||||||
.filter { it.relativePath != "$packageInternalName.class" }
|
.filter { it.relativePath != "$packageInternalName.class" }
|
||||||
@@ -188,7 +190,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
|||||||
additionalFiles,
|
additionalFiles,
|
||||||
sourcePosition,
|
sourcePosition,
|
||||||
funName,
|
funName,
|
||||||
extractionResult.getParametersForDebugger())
|
parametersDescriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getClassName(fileName: String): String {
|
private fun getClassName(fileName: String): String {
|
||||||
@@ -286,11 +288,10 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (param in config.descriptor.parameters) {
|
for (param in config.descriptor.parameters) {
|
||||||
val paramName = if (param.argumentText.contains("@")) {
|
val paramName = when {
|
||||||
param.argumentText.substringBefore("@")
|
param.argumentText.contains("@") -> param.argumentText.substringBefore("@")
|
||||||
}
|
param.argumentText.startsWith("::") -> param.argumentText.substring(2)
|
||||||
else {
|
else -> param.argumentText
|
||||||
param.argumentText
|
|
||||||
}
|
}
|
||||||
parameters.add(paramName, param.getParameterType(true))
|
parameters.add(paramName, param.getParameterType(true))
|
||||||
}
|
}
|
||||||
@@ -306,7 +307,8 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
|||||||
private fun createClassFileFactory(
|
private fun createClassFileFactory(
|
||||||
codeFragment: JetCodeFragment,
|
codeFragment: JetCodeFragment,
|
||||||
extractedFunction: JetNamedFunction,
|
extractedFunction: JetNamedFunction,
|
||||||
context: EvaluationContextImpl
|
context: EvaluationContextImpl,
|
||||||
|
parameters: ParametersDescriptor
|
||||||
): ClassFileFactory {
|
): ClassFileFactory {
|
||||||
return runReadAction {
|
return runReadAction {
|
||||||
val jetFile = createFileForDebugger(codeFragment, extractedFunction)
|
val jetFile = createFileForDebugger(codeFragment, extractedFunction)
|
||||||
@@ -337,21 +339,28 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
|||||||
val frameVisitor = FrameVisitor(context)
|
val frameVisitor = FrameVisitor(context)
|
||||||
|
|
||||||
extractedFunction.getReceiverTypeReference()?.let {
|
extractedFunction.getReceiverTypeReference()?.let {
|
||||||
state.recordAnonymousType(it, THIS_NAME, frameVisitor)
|
state.getBindingTrace().recordAnonymousType(it, THIS_NAME, frameVisitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (param in extractedFunction.getValueParameters()) {
|
val valueParameters = extractedFunction.getValueParameters()
|
||||||
val paramRef = param.getTypeReference()
|
var paramIndex = 0
|
||||||
val paramName = param.getName()
|
for (param in parameters) {
|
||||||
if (paramRef == null || paramName == null) {
|
val (callText, type) = param
|
||||||
logger.error("Each parameter for extracted function should have a name and a type reference",
|
|
||||||
|
if (callText.contains(THIS_NAME)) continue
|
||||||
|
|
||||||
|
val valueParameter = valueParameters[paramIndex++]
|
||||||
|
|
||||||
|
val paramRef = valueParameter.getTypeReference()
|
||||||
|
if (paramRef == null) {
|
||||||
|
logger.error("Each parameter for extracted function should have a type reference",
|
||||||
Attachment("codeFragment.txt", codeFragment.getText()),
|
Attachment("codeFragment.txt", codeFragment.getText()),
|
||||||
Attachment("extractedFunction.txt", extractedFunction.getText()))
|
Attachment("extractedFunction.txt", extractedFunction.getText()))
|
||||||
|
|
||||||
exception("An exception occurs during Evaluate Expression Action")
|
exception("An exception occurs during Evaluate Expression Action")
|
||||||
}
|
}
|
||||||
|
|
||||||
state.recordAnonymousType(paramRef, paramName, frameVisitor)
|
state.getBindingTrace().recordAnonymousType(paramRef, callText, frameVisitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION)
|
KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION)
|
||||||
@@ -360,7 +369,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun GenerationState.recordAnonymousType(typeReference: JetTypeReference, localVariableName: String, visitor: FrameVisitor) {
|
private fun BindingTrace.recordAnonymousType(typeReference: JetTypeReference, localVariableName: String, visitor: FrameVisitor) {
|
||||||
val paramAnonymousType = typeReference.debugTypeInfo
|
val paramAnonymousType = typeReference.debugTypeInfo
|
||||||
if (paramAnonymousType != null) {
|
if (paramAnonymousType != null) {
|
||||||
val declarationDescriptor = paramAnonymousType.getConstructor().getDeclarationDescriptor()
|
val declarationDescriptor = paramAnonymousType.getConstructor().getDeclarationDescriptor()
|
||||||
@@ -369,7 +378,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
|||||||
if (localVariable == null) {
|
if (localVariable == null) {
|
||||||
exception("Couldn't find local variable this in current frame to get classType for anonymous type ${paramAnonymousType}}")
|
exception("Couldn't find local variable this in current frame to get classType for anonymous type ${paramAnonymousType}}")
|
||||||
}
|
}
|
||||||
getBindingTrace().record(CodegenBinding.ASM_TYPE, declarationDescriptor, localVariable.asmType)
|
record(CodegenBinding.ASM_TYPE, declarationDescriptor, localVariable.asmType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package customLib.localFunInLibraryCustomLib
|
||||||
|
|
||||||
|
public fun localFunInLibraryCustomLibMainFun() {
|
||||||
|
fun localFun() = 1
|
||||||
|
val localFunInLibraryCustomLibProperty = 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
LineBreakpoint created at localFun.kt:9
|
||||||
|
LineBreakpoint created at localFun.kt:15
|
||||||
|
LineBreakpoint created at localFun.kt:21
|
||||||
|
LineBreakpoint created at localFun.kt:34
|
||||||
|
LineBreakpoint created at localFun.kt:43
|
||||||
|
LineBreakpoint created at localFun.kt:54
|
||||||
|
LineBreakpoint created at localFun.kt:61
|
||||||
|
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! localFun.LocalFunPackage
|
||||||
|
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
localFun.kt:9
|
||||||
|
Compile bytecode for myLocalFun1()
|
||||||
|
localFun.kt:15
|
||||||
|
Compile bytecode for myLocalFun2()
|
||||||
|
localFun.kt:21
|
||||||
|
Compile bytecode for myLocalFun1() + 1
|
||||||
|
localFun.kt:34
|
||||||
|
Compile bytecode for myLocalFun4()
|
||||||
|
localFun.kt:43
|
||||||
|
Compile bytecode for myLocalFun5(2)
|
||||||
|
localFun.kt:54
|
||||||
|
Compile bytecode for myLocalFun6()
|
||||||
|
localFun.kt:61
|
||||||
|
Compile bytecode for myLocalFun6() + 1
|
||||||
|
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 localFunCustomLib.kt:5
|
||||||
|
!JDK_HOME!\bin\java -agentlib:jdwp=transport=dt_socket,address=!HOST_NAME!:!HOST_PORT!,suspend=y,server=n -Dfile.encoding=!FILE_ENCODING! -classpath !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! localFunInLibrary.LocalFunInLibraryPackage
|
||||||
|
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
localFunCustomLib.kt:6
|
||||||
|
Compile bytecode for localFun()
|
||||||
|
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package localFunInLibrary
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
customLib.localFunInLibraryCustomLib.localFunInLibraryCustomLibMainFun()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADDITIONAL_BREAKPOINT: localFunCustomLib.kt:localFunInLibraryCustomLibProperty
|
||||||
|
// EXPRESSION: localFun()
|
||||||
|
// RESULT: 1: I
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package localFun
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
fun myLocalFun1() = 1
|
||||||
|
|
||||||
|
// EXPRESSION: myLocalFun1()
|
||||||
|
// RESULT: 1: I
|
||||||
|
//Breakpoint!
|
||||||
|
myLocalFun1()
|
||||||
|
|
||||||
|
fun myLocalFun2() = 2
|
||||||
|
// EXPRESSION: myLocalFun2()
|
||||||
|
// RESULT: 2: I
|
||||||
|
//Breakpoint!
|
||||||
|
myLocalFun2()
|
||||||
|
|
||||||
|
fun myLocalFun3() {
|
||||||
|
// EXPRESSION: myLocalFun1() + 1
|
||||||
|
// RESULT: Cannot find local variable: name = myLocalFun1
|
||||||
|
//Breakpoint!
|
||||||
|
myLocalFun1() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
myLocalFun3()
|
||||||
|
|
||||||
|
|
||||||
|
fun myLocalFun4(): Int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPRESSION: myLocalFun4()
|
||||||
|
// RESULT: 1: I
|
||||||
|
//Breakpoint!
|
||||||
|
myLocalFun4()
|
||||||
|
|
||||||
|
fun myLocalFun5(i: Int): Int {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPRESSION: myLocalFun5(2)
|
||||||
|
// RESULT: 2: I
|
||||||
|
//Breakpoint!
|
||||||
|
myLocalFun5(2)
|
||||||
|
|
||||||
|
var i = 1
|
||||||
|
fun myLocalFun6(): Int {
|
||||||
|
i++
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPRESSION: myLocalFun6()
|
||||||
|
// RESULT: 2: I
|
||||||
|
//Breakpoint!
|
||||||
|
myLocalFun6()
|
||||||
|
|
||||||
|
i = 1
|
||||||
|
fun myLocalFun7() {
|
||||||
|
// EXPRESSION: myLocalFun6() + 1
|
||||||
|
// RESULT: 3: I
|
||||||
|
//Breakpoint!
|
||||||
|
myLocalFun6() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
myLocalFun7()
|
||||||
|
}
|
||||||
+12
@@ -607,6 +607,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
|||||||
doMultipleBreakpointsTest(fileName);
|
doMultipleBreakpointsTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("localFun.kt")
|
||||||
|
public void testLocalFun() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/localFun.kt");
|
||||||
|
doMultipleBreakpointsTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("whenEntry.kt")
|
@TestMetadata("whenEntry.kt")
|
||||||
public void testWhenEntry() throws Exception {
|
public void testWhenEntry() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/whenEntry.kt");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/whenEntry.kt");
|
||||||
@@ -644,6 +650,12 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
|||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/library/customLibClassName.kt");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/library/customLibClassName.kt");
|
||||||
doMultipleBreakpointsTest(fileName);
|
doMultipleBreakpointsTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("localFunInLibrary.kt")
|
||||||
|
public void testLocalFunInLibrary() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/library/localFunInLibrary.kt");
|
||||||
|
doMultipleBreakpointsTest(fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user