Evaluate Expression: support local functions
This commit is contained in:
@@ -47,7 +47,11 @@ class FrameVisitor(context: EvaluationContextImpl) {
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val localVariable = findLocalVariable(name, asmType, checkType)
|
||||
val localVariable = if (isFunctionType(asmType))
|
||||
findLocalVariableForLocalFun(name, asmType, checkType)
|
||||
else
|
||||
findLocalVariable(name, asmType, checkType)
|
||||
|
||||
if (localVariable != null) {
|
||||
return localVariable
|
||||
}
|
||||
@@ -60,16 +64,17 @@ class FrameVisitor(context: EvaluationContextImpl) {
|
||||
}
|
||||
}
|
||||
|
||||
return if (!failIfNotFound)
|
||||
null
|
||||
else
|
||||
throw EvaluateExceptionUtil.createEvaluateException("Cannot find local variable: name = $name${if (checkType) ", type = " + asmType.toString() else ""}")
|
||||
return fail("Cannot find local variable: name = $name${if (checkType) ", type = " + asmType.toString() else ""}", failIfNotFound)
|
||||
}
|
||||
catch(e: InvalidStackFrameException) {
|
||||
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? {
|
||||
val thisObject = frame!!.thisObject()
|
||||
if (thisObject != null) {
|
||||
@@ -89,6 +94,15 @@ class FrameVisitor(context: EvaluationContextImpl) {
|
||||
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? {
|
||||
val localVariable = frame!!.visibleVariableByName(name)
|
||||
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.resolve.AnalyzingUtils
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.types.Flexibility
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
@@ -167,9 +168,10 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
||||
if (extractionResult == null) {
|
||||
throw IllegalStateException("Code fragment cannot be extracted to function")
|
||||
}
|
||||
val parametersDescriptor = extractionResult.getParametersForDebugger()
|
||||
val extractedFunction = extractionResult.declaration as JetNamedFunction
|
||||
|
||||
val classFileFactory = createClassFileFactory(codeFragment, extractedFunction, context)
|
||||
val classFileFactory = createClassFileFactory(codeFragment, extractedFunction, context, parametersDescriptor)
|
||||
|
||||
val outputFiles = classFileFactory.asList()
|
||||
.filter { it.relativePath != "$packageInternalName.class" }
|
||||
@@ -188,7 +190,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
||||
additionalFiles,
|
||||
sourcePosition,
|
||||
funName,
|
||||
extractionResult.getParametersForDebugger())
|
||||
parametersDescriptor)
|
||||
}
|
||||
|
||||
private fun getClassName(fileName: String): String {
|
||||
@@ -286,11 +288,10 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
||||
}
|
||||
|
||||
for (param in config.descriptor.parameters) {
|
||||
val paramName = if (param.argumentText.contains("@")) {
|
||||
param.argumentText.substringBefore("@")
|
||||
}
|
||||
else {
|
||||
param.argumentText
|
||||
val paramName = when {
|
||||
param.argumentText.contains("@") -> param.argumentText.substringBefore("@")
|
||||
param.argumentText.startsWith("::") -> param.argumentText.substring(2)
|
||||
else -> param.argumentText
|
||||
}
|
||||
parameters.add(paramName, param.getParameterType(true))
|
||||
}
|
||||
@@ -306,7 +307,8 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
||||
private fun createClassFileFactory(
|
||||
codeFragment: JetCodeFragment,
|
||||
extractedFunction: JetNamedFunction,
|
||||
context: EvaluationContextImpl
|
||||
context: EvaluationContextImpl,
|
||||
parameters: ParametersDescriptor
|
||||
): ClassFileFactory {
|
||||
return runReadAction {
|
||||
val jetFile = createFileForDebugger(codeFragment, extractedFunction)
|
||||
@@ -337,21 +339,28 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
||||
val frameVisitor = FrameVisitor(context)
|
||||
|
||||
extractedFunction.getReceiverTypeReference()?.let {
|
||||
state.recordAnonymousType(it, THIS_NAME, frameVisitor)
|
||||
state.getBindingTrace().recordAnonymousType(it, THIS_NAME, frameVisitor)
|
||||
}
|
||||
|
||||
for (param in extractedFunction.getValueParameters()) {
|
||||
val paramRef = param.getTypeReference()
|
||||
val paramName = param.getName()
|
||||
if (paramRef == null || paramName == null) {
|
||||
logger.error("Each parameter for extracted function should have a name and a type reference",
|
||||
val valueParameters = extractedFunction.getValueParameters()
|
||||
var paramIndex = 0
|
||||
for (param in parameters) {
|
||||
val (callText, type) = param
|
||||
|
||||
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("extractedFunction.txt", extractedFunction.getText()))
|
||||
|
||||
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)
|
||||
@@ -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
|
||||
if (paramAnonymousType != null) {
|
||||
val declarationDescriptor = paramAnonymousType.getConstructor().getDeclarationDescriptor()
|
||||
@@ -369,7 +378,7 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
||||
if (localVariable == null) {
|
||||
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);
|
||||
}
|
||||
|
||||
@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")
|
||||
public void testWhenEntry() throws Exception {
|
||||
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");
|
||||
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