Debugger: implement compiling evaluator

This commit is contained in:
Natalia Ukhorskaya
2015-02-10 14:19:51 +03:00
parent 91e745704a
commit 7a4b291c85
24 changed files with 477 additions and 5 deletions
@@ -84,7 +84,13 @@ class KotlinEvaluateExpressionCache(val project: Project) {
}
}
data class CompiledDataDescriptor(val bytecodes: ByteArray, val sourcePosition: SourcePosition, val funName: String, val parameters: ParametersDescriptor)
data class CompiledDataDescriptor(
val bytecodes: ByteArray,
val additionalClasses: List<Pair<String, ByteArray>>,
val sourcePosition: SourcePosition,
val funName: String,
val parameters: ParametersDescriptor
)
class ParametersDescriptor : Iterable<Pair<String, JetType>> {
private val list = ArrayList<Pair<String, JetType>>()
@@ -78,6 +78,7 @@ import com.intellij.openapi.diagnostic.Attachment
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments
import com.sun.jdi.ClassType
import com.sun.jdi.InvocationException
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClasses
private val RECEIVER_NAME = "\$receiver"
private val THIS_NAME = "this"
@@ -169,19 +170,37 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
val classFileFactory = createClassFileFactory(codeFragment, extractedFunction)
// KT-4509
val outputFiles = (classFileFactory : OutputFileCollection).asList().filter { it.relativePath != "$packageInternalName.class" }
if (outputFiles.size() != 1) exception("Expression compiles to more than one class file. Note that lambdas, classes and objects are unsupported yet. List of files: ${outputFiles.joinToString(",")}")
val outputFiles = (classFileFactory : OutputFileCollection).asList()
.filter { it.relativePath != "$packageInternalName.class" }
.sortBy { it.relativePath.length() }
val funName = extractedFunction.getName()
if (funName == null) {
throw IllegalStateException("Extracted function should have a name: ${extractedFunction.getText()}")
}
return CompiledDataDescriptor(outputFiles.first().asByteArray(), sourcePosition, funName, extractedFunction.getParametersForDebugger())
val additionalFiles = if (outputFiles.size() < 2) emptyList()
else outputFiles.subList(1, outputFiles.size()).map { getClassName(it.relativePath) to it.asByteArray() }
return CompiledDataDescriptor(
outputFiles.first().asByteArray(),
additionalFiles,
sourcePosition,
funName,
extractedFunction.getParametersForDebugger())
}
private fun getClassName(fileName: String): String {
return fileName.substringBeforeLast(".class").replaceAll("/", ".")
}
private fun runEval4j(context: EvaluationContextImpl, compiledData: CompiledDataDescriptor): InterpreterResult {
val virtualMachine = context.getDebugProcess().getVirtualMachineProxy().getVirtualMachine()
if (compiledData.additionalClasses.isNotEmpty()) {
loadClasses(context, compiledData.additionalClasses)
}
var resultValue: InterpreterResult? = null
ClassReader(compiledData.bytecodes).accept(object : ClassVisitor(ASM5) {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
@@ -0,0 +1,87 @@
/*
* 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.evaluate.compilingEvaluator
import kotlin.properties.Delegates
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.DebugProcess
import com.sun.jdi.ClassLoaderReference
import com.intellij.openapi.projectRoots.JdkVersionUtil
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.engine.evaluation.EvaluationContext
import com.intellij.openapi.util.SystemInfo
import org.jetbrains.kotlin.idea.debugger.evaluate.CompilingEvaluatorUtils
public fun loadClasses(evaluationContext: EvaluationContextImpl, classes: Collection<Pair<String, ByteArray>>) {
val process = evaluationContext.getDebugProcess()
val classLoader: ClassLoaderReference
try {
classLoader = CompilingEvaluatorUtils.getClassLoader(evaluationContext, process)
}
catch (e: Exception) {
throw EvaluateException("Error creating evaluation class loader: " + e, e)
}
val version = (process.getVirtualMachineProxy()).version()
val sdkVersion = JdkVersionUtil.getVersion(version)
if (!SystemInfo.isJavaVersionAtLeast(sdkVersion.getDescription())) {
throw EvaluateException("Unable to compile for target level " + sdkVersion.getDescription() + ". Need to run IDEA on java version at least " + sdkVersion.getDescription() + ", currently running on " + SystemInfo.JAVA_RUNTIME_VERSION)
}
try {
defineClasses(classes, evaluationContext, process, classLoader)
}
catch (e: Exception) {
throw EvaluateException("Error during classes definition " + e, e)
}
evaluationContext.setClassLoader(classLoader)
}
private fun defineClasses(
classes: Collection<Pair<String, ByteArray>>,
context: EvaluationContext,
process: DebugProcess,
classLoader: ClassLoaderReference
) {
CompilingEvaluatorUtils.defineClass(FunctionImplBytes.name, FunctionImplBytes.bytes, context, process, classLoader)
for ((className, bytes) in classes) {
CompilingEvaluatorUtils.defineClass(className, bytes, context, process, classLoader)
}
}
private object FunctionImplBytes {
val bytes: ByteArray by Delegates.lazy {
(): ByteArray ->
val inputStream = this.javaClass.getClassLoader().getResourceAsStream("kotlin/jvm/internal/FunctionImpl.class")
if (inputStream != null) {
try {
return@lazy inputStream.readBytes()
}
finally {
inputStream.close()
}
}
throw EvaluateException("Couldn't find kotlin.jvm.internal.FunctionImpl class in current classloader")
}
val name = "kotlin.jvm.internal.FunctionImpl"
}
@@ -0,0 +1,9 @@
LineBreakpoint created at ceLambda.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 !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! ceLambda.CeLambdaPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
ceLambda.kt:6
Compile bytecode for foo { 1 }
Compile bytecode for foo { a }
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,16 @@
LineBreakpoint created at ceLocalClass.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 !APP_PATH!\classes;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! ceLocalClass.CeLocalClassPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
ceLocalClass.kt:6
Compile bytecode for class MyClass {
fun test() = 2
}
val a = MyClass()
a.test()
// RESULT: 2: I
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,16 @@
LineBreakpoint created at ceLocalClassMembers.kt:19
!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! ceLocalClassMembers.CeLocalClassMembersPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
ceLocalClassMembers.kt:19
Compile bytecode for class MyClass {
fun test() = publicFun() + publicVal + protectedFun() + protectedVal + privateFun() + privateFun()
}
val a = MyClass()
a.test()
// RESULT: 6: I
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,16 @@
LineBreakpoint created at ceLocalClassWithSuperClass.kt:19
!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! ceLocalClassWithSuperClass.CeLocalClassWithSuperClassPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
ceLocalClassWithSuperClass.kt:19
Compile bytecode for class MyClass: MySuperClass() {
fun test() = publicFun()
}
val a = MyClass()
a.test()
// RESULT: 1: I
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,13 @@
LineBreakpoint created at ceMembers.kt:19
!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! ceMembers.CeMembersPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
ceMembers.kt:19
Compile bytecode for foo { publicFun() }
Compile bytecode for foo { publicVal }
Compile bytecode for foo { protectedFun() }
Compile bytecode for foo { protectedVal }
Compile bytecode for foo { privateFun() }
Compile bytecode for foo { privateVal }
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,7 @@
LineBreakpoint created at ceObject.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! ceObject.CeObjectPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
ceObject.kt:5
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 ceSeveralLambdas.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! ceSeveralLambdas.CeSeveralLambdasPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
ceSeveralLambdas.kt:5
Compile bytecode for foo { 1 } + foo { 1 }
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,13 @@
LineBreakpoint created at ceSuperAccess.kt:20
!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! ceSuperAccess.CeSuperAccessPackage
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
ceSuperAccess.kt:20
Compile bytecode for foo { publicFun() }
Compile bytecode for foo { publicVal }
Compile bytecode for foo { protectedFun() }
Compile bytecode for foo { protectedVal }
Compile bytecode for foo { privateFun() }
Compile bytecode for foo { privateVal }
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
Process finished with exit code 0
@@ -0,0 +1,16 @@
package ceLambda
fun main(args: Array<String>) {
val a = 1
//Breakpoint!
args.size()
}
fun foo(p: () -> Int) = p()
// EXPRESSION: foo { 1 }
// RESULT: 1: I
// EXPRESSION: foo { a }
// RESULT: 1: I
@@ -0,0 +1,8 @@
package ceLocalClass
fun main(args: Array<String>) {
val a = 1
//Breakpoint!
args.size()
}
@@ -0,0 +1,8 @@
class MyClass {
fun test() = 2
}
val a = MyClass()
a.test()
// RESULT: 2: I
@@ -0,0 +1,25 @@
package ceLocalClassMembers
fun main(args: Array<String>) {
A().test()
}
class A {
public fun publicFun(): Int = 1
public val publicVal: Int = 1
protected fun protectedFun(): Int = 1
protected val protectedVal: Int = 1
private fun privateFun() = 1
private val privateVal = 1
fun test() {
//Breakpoint!
val a = 1
}
}
@@ -0,0 +1,8 @@
class MyClass {
fun test() = publicFun() + publicVal + protectedFun() + protectedVal + privateFun() + privateFun()
}
val a = MyClass()
a.test()
// RESULT: 6: I
@@ -0,0 +1,23 @@
package ceLocalClassWithSuperClass
fun main(args: Array<String>) {
A().test()
}
class A {
public fun publicFun(): Int = 1
public val publicVal: Int = 1
protected fun protectedFun(): Int = 1
protected val protectedVal: Int = 1
private fun privateFun() = 1
private val privateVal = 1
fun test() {
//Breakpoint!
val a = 1
}
}
open class MySuperClass
@@ -0,0 +1,8 @@
class MyClass: MySuperClass() {
fun test() = publicFun()
}
val a = MyClass()
a.test()
// RESULT: 1: I
@@ -0,0 +1,42 @@
package ceMembers
fun main(args: Array<String>) {
A().test()
}
class A {
public fun publicFun(): Int = 1
public val publicVal: Int = 1
protected fun protectedFun(): Int = 1
protected val protectedVal: Int = 1
private fun privateFun() = 1
private val privateVal = 1
fun test() {
//Breakpoint!
val a = 1
}
}
fun foo(p: () -> Int) = p()
// EXPRESSION: foo { publicFun() }
// RESULT: 1: I
// EXPRESSION: foo { publicVal }
// RESULT: 1: I
// EXPRESSION: foo { protectedFun() }
// RESULT: 1: I
// EXPRESSION: foo { protectedVal }
// RESULT: 1: I
// EXPRESSION: foo { privateFun() }
// RESULT: 1: I
// EXPRESSION: foo { privateVal }
// RESULT: 1: I
@@ -0,0 +1,13 @@
package ceObject
fun main(args: Array<String>) {
//Breakpoint!
args.size()
}
trait T {
fun test() = 1
}
//- EXPRESSION: (object: T {}).test()
//- RESULT: 1: I
@@ -0,0 +1,11 @@
package ceSeveralLambdas
fun main(args: Array<String>) {
//Breakpoint!
args.size()
}
fun foo(p: () -> Int) = p()
// EXPRESSION: foo { 1 } + foo { 1 }
// RESULT: 2: I
@@ -0,0 +1,43 @@
package ceSuperAccess
fun main(args: Array<String>) {
A().Inner().test()
}
class A {
public fun publicFun(): Int = 1
public val publicVal: Int = 1
protected fun protectedFun(): Int = 1
protected val protectedVal: Int = 1
private fun privateFun() = 1
private val privateVal = 1
inner class Inner {
fun test() {
//Breakpoint!
val a = publicFun()
}
}
}
fun foo(p: () -> Int) = p()
// EXPRESSION: foo { publicFun() }
// RESULT: 1: I
// EXPRESSION: foo { publicVal }
// RESULT: 1: I
// EXPRESSION: foo { protectedFun() }
// RESULT: 1: I
// EXPRESSION: foo { protectedVal }
// RESULT: 1: I
// EXPRESSION: foo { privateFun() }
// RESULT: 1: I
// EXPRESSION: foo { privateVal }
// RESULT: 1: I
@@ -33,7 +33,7 @@ import java.util.regex.Pattern;
public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluateExpressionTest {
@TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint")
@TestDataPath("$PROJECT_ROOT")
@InnerTestClasses({SingleBreakpoint.ExtraVariables.class, SingleBreakpoint.Frame.class, SingleBreakpoint.Lambdas.class, SingleBreakpoint.Renderer.class})
@InnerTestClasses({SingleBreakpoint.CompilingEvaluator.class, SingleBreakpoint.ExtraVariables.class, SingleBreakpoint.Frame.class, SingleBreakpoint.Lambdas.class, SingleBreakpoint.Renderer.class})
@RunWith(JUnit3RunnerWithInners.class)
public static class SingleBreakpoint extends AbstractKotlinEvaluateExpressionTest {
@TestMetadata("abstractFunCall.kt")
@@ -226,6 +226,63 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
doSingleBreakpointTest(fileName);
}
@TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class CompilingEvaluator extends AbstractKotlinEvaluateExpressionTest {
public void testAllFilesPresentInCompilingEvaluator() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("ceLambda.kt")
public void testCeLambda() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceLambda.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("ceLocalClass.kt")
public void testCeLocalClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceLocalClass.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("ceLocalClassMembers.kt")
public void testCeLocalClassMembers() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceLocalClassMembers.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("ceLocalClassWithSuperClass.kt")
public void testCeLocalClassWithSuperClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceLocalClassWithSuperClass.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("ceMembers.kt")
public void testCeMembers() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceMembers.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("ceObject.kt")
public void testCeObject() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceObject.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("ceSeveralLambdas.kt")
public void testCeSeveralLambdas() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceSeveralLambdas.kt");
doSingleBreakpointTest(fileName);
}
@TestMetadata("ceSuperAccess.kt")
public void testCeSuperAccess() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/compilingEvaluator/ceSuperAccess.kt");
doSingleBreakpointTest(fileName);
}
}
@TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/extraVariables")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)