Evaluate Expression: support local functions

This commit is contained in:
Natalia Ukhorskaya
2015-04-30 10:07:17 +03:00
parent 8c99183970
commit d7a301d698
8 changed files with 171 additions and 22 deletions
@@ -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
@@ -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()
}
@@ -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);
}
}
}
}