Cache bytecodes for JetCodeFragment in Evaluate Expression
This commit is contained in:
@@ -612,7 +612,8 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
testClass(javaClass<AbstractKotlinEvaluateExpressionTest>()) {
|
||||
model("debugger/tinyApp/src/evaluate")
|
||||
model("debugger/tinyApp/src/evaluate/singleBreakpoint", testMethod = "doSingleBreakpointTest")
|
||||
model("debugger/tinyApp/src/evaluate/multipleBreakpoints", testMethod = "doMultipleBreakpointsTest")
|
||||
}
|
||||
|
||||
testClass(javaClass<AbstractStubBuilderTest>()) {
|
||||
|
||||
@@ -155,6 +155,9 @@
|
||||
<projectService serviceInterface="org.jetbrains.jet.asJava.KotlinLightClassForPackage$FileStubCache"
|
||||
serviceImplementation="org.jetbrains.jet.asJava.KotlinLightClassForPackage$FileStubCache"/>
|
||||
|
||||
<projectService serviceInterface="org.jetbrains.jet.plugin.debugger.evaluate.KotlinEvaluateExpressionCache"
|
||||
serviceImplementation="org.jetbrains.jet.plugin.debugger.evaluate.KotlinEvaluateExpressionCache"/>
|
||||
|
||||
<errorHandler implementation="org.jetbrains.jet.plugin.reporter.KotlinReportSubmitter"/>
|
||||
|
||||
<internalFileTemplate name="Kotlin File"/>
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2010-2014 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.jet.plugin.debugger.evaluate
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.util.CachedValuesManager
|
||||
import com.intellij.psi.util.CachedValueProvider
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import org.jetbrains.jet.lang.psi.JetCodeFragment
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import java.util.ArrayList
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.jet.lang.types.JetType
|
||||
import org.jetbrains.jet.lang.resolve.java.mapping.JavaToKotlinClassMap
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import org.jetbrains.jet.lang.psi.JetFile
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import org.jetbrains.jet.lang.resolve.DescriptorUtils
|
||||
import org.jetbrains.jet.lang.descriptors.ClassDescriptor
|
||||
import org.jetbrains.jet.plugin.caches.resolve.JavaResolveExtension
|
||||
import org.jetbrains.jet.lang.resolve.java.structure.impl.JavaClassImpl
|
||||
import org.jetbrains.jet.lang.resolve.java.JvmClassName
|
||||
import org.jetbrains.jet.codegen.AsmUtil
|
||||
|
||||
class KotlinEvaluateExpressionCache(val project: Project) {
|
||||
|
||||
private val cachedCompiledData = CachedValuesManager.getManager(project).createCachedValue(
|
||||
{
|
||||
CachedValueProvider.Result<MultiMap<String, CompiledDataDescriptor>>(
|
||||
MultiMap.create(), PsiModificationTracker.MODIFICATION_COUNT)
|
||||
}, false)
|
||||
|
||||
class object {
|
||||
fun getInstance(project: Project) = ServiceManager.getService(project, javaClass<KotlinEvaluateExpressionCache>())!!
|
||||
|
||||
fun getOrCreateCompiledData(
|
||||
codeFragment: JetCodeFragment,
|
||||
sourcePosition: SourcePosition,
|
||||
evaluationContext: EvaluationContextImpl,
|
||||
create: (JetCodeFragment, SourcePosition) -> CompiledDataDescriptor
|
||||
): CompiledDataDescriptor {
|
||||
val evaluateExpressionCache = getInstance(codeFragment.getProject())
|
||||
|
||||
return synchronized(evaluateExpressionCache.cachedCompiledData) {
|
||||
(): CompiledDataDescriptor ->
|
||||
val cache = evaluateExpressionCache.cachedCompiledData.getValue()!!
|
||||
val text = "${codeFragment.importsToString()}\n${codeFragment.getText()}"
|
||||
|
||||
val answer = cache[text].firstOrNull {
|
||||
it.sourcePosition == sourcePosition || evaluateExpressionCache.canBeEvaluatedInThisContext(it, evaluationContext)
|
||||
}
|
||||
if (answer != null) return@synchronized answer
|
||||
|
||||
val newCompiledData = create(codeFragment, sourcePosition)
|
||||
cache.putValue(text, newCompiledData)
|
||||
|
||||
return@synchronized newCompiledData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun canBeEvaluatedInThisContext(compiledData: CompiledDataDescriptor, context: EvaluationContextImpl): Boolean {
|
||||
return compiledData.parameters.all { (p): Boolean ->
|
||||
val (name, jetType) = p
|
||||
val value = context.getFrameProxy()?.getStackFrame()?.findLocalVariable(name, failIfNotFound = false)
|
||||
if (value == null) return@all false
|
||||
|
||||
val thisDescriptor = value.asmType.getClassDescriptor()
|
||||
val superClassDescriptor = jetType.getConstructor().getDeclarationDescriptor() as? ClassDescriptor
|
||||
return@all thisDescriptor != null && superClassDescriptor != null && DescriptorUtils.isSubclass(thisDescriptor, superClassDescriptor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Type.getClassDescriptor(): ClassDescriptor? {
|
||||
if (AsmUtil.isPrimitive(this)) return null
|
||||
|
||||
val jvmName = JvmClassName.byInternalName(getInternalName()).getFqNameForClassNameWithoutDollars()
|
||||
|
||||
val platformClasses = JavaToKotlinClassMap.getInstance().mapPlatformClass(jvmName)
|
||||
if (platformClasses.notEmpty) return platformClasses.first()
|
||||
|
||||
return ApplicationManager.getApplication()?.runReadAction<ClassDescriptor> {
|
||||
val classes = JavaPsiFacade.getInstance(project).findClasses(jvmName.asString(), GlobalSearchScope.allScope(project))
|
||||
if (classes.isEmpty()) null else JavaResolveExtension[project].resolveClass(JavaClassImpl(classes.first()))
|
||||
}
|
||||
}
|
||||
|
||||
data class CompiledDataDescriptor(val bytecodes: ByteArray, val sourcePosition: SourcePosition, val funName: String, val parameters: ParametersDescriptor)
|
||||
|
||||
class ParametersDescriptor : Iterable<Pair<String, JetType>> {
|
||||
private val list = ArrayList<Pair<String, JetType>>()
|
||||
|
||||
fun add(name: String, jetType: JetType) {
|
||||
list.add(name to jetType)
|
||||
}
|
||||
|
||||
fun getParameterNames() = list.map { it.first }
|
||||
|
||||
override fun iterator() = list.iterator()
|
||||
}
|
||||
}
|
||||
@@ -49,10 +49,7 @@ import org.jetbrains.jet.codegen.ClassFileFactory
|
||||
import org.jetbrains.jet.OutputFileCollection
|
||||
import org.jetbrains.jet.plugin.caches.resolve.getAnalysisResults
|
||||
import org.jetbrains.jet.lang.psi.JetCodeFragment
|
||||
import org.jetbrains.jet.lang.psi.JetImportList
|
||||
import org.jetbrains.jet.lang.psi.codeFragmentUtil.skipVisibilityCheck
|
||||
import org.jetbrains.jet.lang.psi.JetExpression
|
||||
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.Status
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import org.jetbrains.jet.codegen.CompilationErrorHandler
|
||||
import org.jetbrains.jet.lang.diagnostics.Severity
|
||||
@@ -60,6 +57,10 @@ import org.jetbrains.jet.lang.diagnostics.rendering.DefaultErrorMessages
|
||||
import com.sun.jdi.request.EventRequest
|
||||
import com.sun.jdi.ObjectReference
|
||||
import com.intellij.debugger.engine.SuspendContext
|
||||
import org.jetbrains.jet.plugin.debugger.evaluate.KotlinEvaluateExpressionCache.*
|
||||
import org.jetbrains.jet.lang.resolve.BindingContext
|
||||
import com.sun.jdi.StackFrame
|
||||
import com.sun.jdi.VirtualMachine
|
||||
|
||||
object KotlinEvaluationBuilder: EvaluatorBuilder {
|
||||
override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator {
|
||||
@@ -86,9 +87,24 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
||||
val sourcePosition: SourcePosition
|
||||
) : Evaluator {
|
||||
override fun evaluate(context: EvaluationContextImpl): Any? {
|
||||
var isCompiledDataFromCache = true
|
||||
try {
|
||||
val compiledData = extractAndCompile(codeFragment, sourcePosition)
|
||||
return runEval4j(context, compiledData)
|
||||
val compiledData = KotlinEvaluateExpressionCache.getOrCreateCompiledData(codeFragment, sourcePosition, context) {
|
||||
fragment, position ->
|
||||
isCompiledDataFromCache = false
|
||||
extractAndCompile(fragment, position)
|
||||
}
|
||||
val args = context.getArgumentsByNames(compiledData.parameters.getParameterNames())
|
||||
val result = runEval4j(context, compiledData, args)
|
||||
|
||||
val virtualMachine = context.getDebugProcess().getVirtualMachineProxy().getVirtualMachine()
|
||||
|
||||
// If bytecode was taken from cache and exception was thrown - recompile bytecode and run eval4j again
|
||||
if (result is ExceptionThrown && isCompiledDataFromCache) {
|
||||
return runEval4j(context, extractAndCompile(codeFragment, sourcePosition), args).toJdiValue(virtualMachine)
|
||||
}
|
||||
|
||||
return result.toJdiValue(virtualMachine)
|
||||
}
|
||||
catch(e: EvaluateException) {
|
||||
throw e
|
||||
@@ -121,77 +137,79 @@ class KotlinEvaluator(val codeFragment: JetCodeFragment,
|
||||
if (funName == null) {
|
||||
throw IllegalStateException("Extracted function should have a name: ${extractedFunction.getText()}")
|
||||
}
|
||||
return CompiledDataDescriptor(outputFiles.first().asByteArray(), funName, extractedFunction.getParameterNamesForDebugger())
|
||||
return CompiledDataDescriptor(outputFiles.first().asByteArray(), sourcePosition, funName, extractedFunction.getParametersForDebugger())
|
||||
}
|
||||
|
||||
private fun runEval4j(context: EvaluationContextImpl, compiledData: CompiledDataDescriptor): Any? {
|
||||
private fun runEval4j(
|
||||
context: EvaluationContextImpl,
|
||||
compiledData: CompiledDataDescriptor,
|
||||
args: List<Value>
|
||||
): InterpreterResult {
|
||||
val virtualMachine = context.getDebugProcess().getVirtualMachineProxy().getVirtualMachine()
|
||||
|
||||
var resultValue: Value? = null
|
||||
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? {
|
||||
if (name == compiledData.funName) {
|
||||
return object : MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions) {
|
||||
override fun visitEnd() {
|
||||
val value = interpreterLoop(
|
||||
resultValue = interpreterLoop(
|
||||
this,
|
||||
makeInitialFrame(this, context.getArgumentsByNames(compiledData.parameterNames)),
|
||||
makeInitialFrame(this, args),
|
||||
JDIEval(virtualMachine,
|
||||
context.getClassLoader()!!,
|
||||
context.getSuspendContext().getThread()?.getThreadReference()!!, context.getSuspendContext().getInvokePolicy())
|
||||
context.getSuspendContext().getThread()?.getThreadReference()!!,
|
||||
context.getSuspendContext().getInvokePolicy())
|
||||
)
|
||||
|
||||
resultValue = when (value) {
|
||||
is ValueReturned -> value.result
|
||||
is ExceptionThrown -> exception(value.exception.toString())
|
||||
is AbnormalTermination -> exception(value.message)
|
||||
else -> throw IllegalStateException("Unknown result value produced by eval4j")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.visitMethod(access, name, desc, signature, exceptions)
|
||||
}
|
||||
}, 0)
|
||||
|
||||
if (resultValue == null) {
|
||||
throw IllegalStateException("resultValue is null: cannot find method ${compiledData.funName}")
|
||||
}
|
||||
return resultValue ?: throw IllegalStateException("resultValue is null: cannot find method ${compiledData.funName}")
|
||||
}
|
||||
|
||||
return resultValue!!.asJdiValue(virtualMachine, resultValue!!.asmType)
|
||||
private fun InterpreterResult.toJdiValue(vm: VirtualMachine): com.sun.jdi.Value? {
|
||||
val jdiValue = when (this) {
|
||||
is ValueReturned -> result
|
||||
is ExceptionThrown -> exception(exception.toString())
|
||||
is AbnormalTermination -> exception(message)
|
||||
else -> throw IllegalStateException("Unknown result value produced by eval4j")
|
||||
}
|
||||
return jdiValue.asJdiValue(vm, jdiValue.asmType)
|
||||
}
|
||||
|
||||
private fun SuspendContext.getInvokePolicy(): Int {
|
||||
return if (getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD) ObjectReference.INVOKE_SINGLE_THREADED else 0
|
||||
}
|
||||
|
||||
private fun JetNamedFunction.getParameterNamesForDebugger(): List<String> {
|
||||
val result = arrayListOf<String>()
|
||||
if (getReceiverTypeRef() != null) {
|
||||
result.add("this")
|
||||
}
|
||||
for (param in getValueParameters()) {
|
||||
result.add(param.getName()!!)
|
||||
}
|
||||
return result
|
||||
private fun JetNamedFunction.getParametersForDebugger(): ParametersDescriptor {
|
||||
return ApplicationManager.getApplication()?.runReadAction(Computable {
|
||||
val parameters = ParametersDescriptor()
|
||||
val bindingContext = getAnalysisResults().getBindingContext()
|
||||
val descriptor = bindingContext[BindingContext.FUNCTION, this]
|
||||
if (descriptor != null) {
|
||||
val receiver = descriptor.getReceiverParameter()
|
||||
if (receiver != null) {
|
||||
parameters.add("this", receiver.getType())
|
||||
}
|
||||
|
||||
descriptor.getValueParameters().forEach {
|
||||
param ->
|
||||
parameters.add(param.getName().asString(), param.getType())
|
||||
}
|
||||
}
|
||||
parameters
|
||||
})!!
|
||||
}
|
||||
|
||||
private fun EvaluationContextImpl.getArgumentsByNames(parameterNames: List<String>): List<Value> {
|
||||
val frames = getFrameProxy()?.getStackFrame()
|
||||
if (frames != null) {
|
||||
fun getValue(name: String): Value {
|
||||
return try {
|
||||
when (name) {
|
||||
"this" -> frames.thisObject().asValue()
|
||||
else -> frames.getValue(frames.visibleVariableByName(name)).asValue()
|
||||
}
|
||||
}
|
||||
catch(e: Exception) {
|
||||
exception("Cannot get parameter value from local variables table: parameterName = ${name}. Note that captured parameters are unsupported yet.")
|
||||
}
|
||||
}
|
||||
|
||||
return parameterNames.map { getValue(it) }
|
||||
return parameterNames.map { frames.findLocalVariable(it)!! }
|
||||
}
|
||||
return Collections.emptyList()
|
||||
}
|
||||
@@ -283,6 +301,22 @@ fun checkForSyntacticErrors(file: JetFile) {
|
||||
}
|
||||
}
|
||||
|
||||
data class CompiledDataDescriptor(val bytecodes: ByteArray, val funName: String, val parameterNames: List<String>)
|
||||
fun StackFrame.findLocalVariable(name: String, failIfNotFound: Boolean = true): Value? {
|
||||
return try {
|
||||
when (name) {
|
||||
"this" -> thisObject().asValue()
|
||||
else -> getValue(visibleVariableByName(name)).asValue()
|
||||
}
|
||||
}
|
||||
catch(e: Exception) {
|
||||
if (failIfNotFound) {
|
||||
throw EvaluateExceptionUtil.createEvaluateException(
|
||||
"Cannot find local variable: name = ${name}. Note that captured variables are unsupported yet.")
|
||||
}
|
||||
else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
LineBreakpoint created at clearCache.kt:12
|
||||
LineBreakpoint created at clearCache.kt:20
|
||||
LineBreakpoint created at clearCache.kt:31
|
||||
LineBreakpoint created at clearCache.kt:42
|
||||
LineBreakpoint created at clearCache.kt:52
|
||||
LineBreakpoint created at clearCache.kt:60
|
||||
LineBreakpoint created at clearCache.kt:78
|
||||
LineBreakpoint created at clearCache.kt:86
|
||||
LineBreakpoint created at clearCache.kt:95
|
||||
LineBreakpoint created at clearCache.kt:105
|
||||
LineBreakpoint created at clearCache.kt:113
|
||||
LineBreakpoint created at clearCache.kt:124
|
||||
LineBreakpoint created at clearCache.kt:133
|
||||
LineBreakpoint created at clearCache.kt:143
|
||||
LineBreakpoint created at clearCache.kt:151
|
||||
LineBreakpoint created at clearCache.kt:169
|
||||
LineBreakpoint created at clearCache.kt:174
|
||||
LineBreakpoint created at clearCache.kt:181
|
||||
LineBreakpoint created at clearCache.kt:189
|
||||
!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!;!RT_JAR! clearCache.ClearCachePackage
|
||||
Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||
clearCache.kt:11
|
||||
clearCache.kt:19
|
||||
clearCache.kt:30
|
||||
clearCache.kt:30
|
||||
clearCache.kt:41
|
||||
clearCache.kt:41
|
||||
clearCache.kt:51
|
||||
clearCache.kt:59
|
||||
clearCache.kt:77
|
||||
clearCache.kt:85
|
||||
clearCache.kt:94
|
||||
clearCache.kt:104
|
||||
clearCache.kt:112
|
||||
clearCache.kt:123
|
||||
clearCache.kt:132
|
||||
clearCache.kt:142
|
||||
clearCache.kt:150
|
||||
clearCache.kt:168
|
||||
clearCache.kt:173
|
||||
clearCache.kt:180
|
||||
clearCache.kt:188
|
||||
Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket'
|
||||
|
||||
Process finished with exit code 0
|
||||
@@ -0,0 +1,206 @@
|
||||
package clearCache
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.HashSet
|
||||
|
||||
fun primitiveTypes() {
|
||||
if (true) {
|
||||
val a: Any = 0
|
||||
// EXPRESSION: a
|
||||
// RESULT: instance of java.lang.Integer(id=ID): Ljava/lang/Integer;
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
|
||||
if (true) {
|
||||
val a = 1
|
||||
// EXPRESSION: a
|
||||
// RESULT: 1: I
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
|
||||
for (i in 1..2) {
|
||||
// EXPRESSION: i
|
||||
// RESULT: 1: I
|
||||
|
||||
// EXPRESSION: i
|
||||
// RESULT: 2: I
|
||||
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
|
||||
for (i in 5.0..6.0) {
|
||||
// EXPRESSION: i
|
||||
// RESULT: 5.0: D
|
||||
|
||||
// EXPRESSION: i
|
||||
// RESULT: 6.0: D
|
||||
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
}
|
||||
|
||||
fun subType() {
|
||||
if (true) {
|
||||
val o = Base()
|
||||
// EXPRESSION: o.test()
|
||||
// RESULT: 100: I
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
|
||||
if (true) {
|
||||
val o = Derived()
|
||||
// EXPRESSION: o.test()
|
||||
// RESULT: 200: I
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
}
|
||||
|
||||
open class Base {
|
||||
open fun test() = 100
|
||||
}
|
||||
|
||||
class Derived: Base() {
|
||||
override fun test() = 200
|
||||
}
|
||||
|
||||
fun subTypePlatform() {
|
||||
if (true) {
|
||||
val c: MutableList<String> = ArrayList<String>()
|
||||
// EXPRESSION: c.size()
|
||||
// RESULT: 0: I
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
|
||||
if (true) {
|
||||
val c: List<String> = ArrayList<String>()
|
||||
// EXPRESSION: c.size()
|
||||
// RESULT: 0: I
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
|
||||
if (true) {
|
||||
val c = ArrayList<String>()
|
||||
c.add("a")
|
||||
// EXPRESSION: c.size()
|
||||
// RESULT: 1: I
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
|
||||
if (true) {
|
||||
val c = ArrayList<Int>()
|
||||
c.add(1)
|
||||
c.add(2)
|
||||
// EXPRESSION: c.size()
|
||||
// RESULT: 2: I
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
|
||||
if (true) {
|
||||
val c = HashSet<Int>()
|
||||
// EXPRESSION: c.size()
|
||||
// RESULT: 0: I
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
}
|
||||
|
||||
fun genericRerun() {
|
||||
if (true) {
|
||||
val c = ArrayList<Int>()
|
||||
c.add(1)
|
||||
// EXPRESSION: c.get(0)
|
||||
// RESULT: 1: I
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
|
||||
if (true) {
|
||||
val c = ArrayList<String>()
|
||||
c.add("a")
|
||||
// EXPRESSION: c.get(0)
|
||||
// RESULT: "a": Ljava/lang/String;
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
}
|
||||
|
||||
fun innerClass() {
|
||||
if (true) {
|
||||
val o = TestInnerClasses.Base()
|
||||
// EXPRESSION: o.test()
|
||||
// RESULT: 100: I
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
|
||||
if (true) {
|
||||
val o = TestInnerClasses.Derived()
|
||||
// EXPRESSION: o.test()
|
||||
// RESULT: 200: I
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
}
|
||||
|
||||
class TestInnerClasses {
|
||||
open class Base {
|
||||
open fun test() = 100
|
||||
}
|
||||
|
||||
class Derived: Base() {
|
||||
override fun test() = 200
|
||||
}
|
||||
}
|
||||
|
||||
fun objects() {
|
||||
// EXPRESSION: obj.test()
|
||||
// RESULT: 1: I
|
||||
//Breakpoint!
|
||||
val a1 = 1
|
||||
|
||||
// EXPRESSION: obj.test()
|
||||
// RESULT: 1: I
|
||||
//Breakpoint!
|
||||
val a2 = 1
|
||||
|
||||
if (true) {
|
||||
val o: BaseObject = obj
|
||||
// EXPRESSION: o.test()
|
||||
// RESULT: 1: I
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
|
||||
if (true) {
|
||||
val o = obj
|
||||
// EXPRESSION: o.test()
|
||||
// RESULT: 1: I
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
}
|
||||
|
||||
val obj = object: BaseObject() { }
|
||||
|
||||
open class BaseObject {
|
||||
fun test() = 1
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
primitiveTypes()
|
||||
subType()
|
||||
subTypePlatform()
|
||||
genericRerun()
|
||||
innerClass()
|
||||
objects()
|
||||
}
|
||||
+52
-21
@@ -37,12 +37,9 @@ import java.util.Collections
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
|
||||
public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestCase() {
|
||||
fun doTest(path: String) {
|
||||
fun doSingleBreakpointTest(path: String) {
|
||||
val file = File(path)
|
||||
val fileContent = FileUtil.loadFile(file, true)
|
||||
val expressions = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileContent, "// EXPRESSION: ")
|
||||
val expectedExpressionResults = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileContent, "// RESULT: ")
|
||||
assert(expressions.size == expectedExpressionResults.size, "Sizes of test directives are different")
|
||||
val expressions = loadTestDirectivesPairs(file, "// EXPRESSION: ", "// RESULT: ")
|
||||
|
||||
val blocks = findFilesWithBlocks(file).map { FileUtil.loadFile(it, true) }
|
||||
val expectedBlockResults = blocks.map { InTextDirectivesUtils.findLinesWithPrefixesRemoved(it, "// RESULT: ").makeString("\n") }
|
||||
@@ -51,34 +48,68 @@ public abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestC
|
||||
|
||||
onBreakpoint {
|
||||
val exceptions = linkedMapOf<String, Throwable>()
|
||||
for ((i, expression) in expressions.withIndices()) {
|
||||
try {
|
||||
evaluate(expression, CodeFragmentKind.EXPRESSION, expectedExpressionResults[i])
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
exceptions.put(expression, e)
|
||||
for ((expression, expected) in expressions) {
|
||||
mayThrow(exceptions, expression) {
|
||||
evaluate(expression, CodeFragmentKind.EXPRESSION, expected)
|
||||
}
|
||||
}
|
||||
|
||||
for ((i, block) in blocks.withIndices()) {
|
||||
try {
|
||||
mayThrow(exceptions, block) {
|
||||
evaluate(block, CodeFragmentKind.CODE_BLOCK, expectedBlockResults[i])
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
exceptions.put(block, e)
|
||||
}
|
||||
}
|
||||
|
||||
if (!exceptions.empty) {
|
||||
for (exc in exceptions.values()) {
|
||||
exc.printStackTrace()
|
||||
}
|
||||
throw AssertionError("Test failed:\n" + exceptions.map { "expression: ${it.key}, exception: ${it.value.getMessage()}" }.makeString("\n"))
|
||||
}
|
||||
checkExceptions(exceptions)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
fun doMultipleBreakpointsTest(path: String) {
|
||||
val expressions = loadTestDirectivesPairs(File(path), "// EXPRESSION: ", "// RESULT: ")
|
||||
|
||||
createDebugProcess(path)
|
||||
|
||||
val exceptions = linkedMapOf<String, Throwable>()
|
||||
for ((expression, expected) in expressions) {
|
||||
mayThrow(exceptions, expression) {
|
||||
onBreakpoint {
|
||||
evaluate(expression, CodeFragmentKind.EXPRESSION, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkExceptions(exceptions)
|
||||
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun checkExceptions(exceptions: MutableMap<String, Throwable>) {
|
||||
if (!exceptions.empty) {
|
||||
for (exc in exceptions.values()) {
|
||||
exc.printStackTrace()
|
||||
}
|
||||
throw AssertionError("Test failed:\n" + exceptions.map { "expression: ${it.key}, exception: ${it.value.getMessage()}" }.makeString("\n"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun mayThrow(map: MutableMap<String, Throwable>, expression: String, f: () -> Unit) {
|
||||
try {
|
||||
f()
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
map.put(expression, e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadTestDirectivesPairs(file: File, directivePrefix: String, expectedPrefix: String): List<Pair<String, String>> {
|
||||
val fileContent = FileUtil.loadFile(file, true)
|
||||
val directives = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileContent, directivePrefix)
|
||||
val expected = InTextDirectivesUtils.findLinesWithPrefixesRemoved(fileContent, expectedPrefix)
|
||||
assert(directives.size == expected.size, "Sizes of test directives are different")
|
||||
return directives.zip(expected)
|
||||
}
|
||||
|
||||
private fun findFilesWithBlocks(mainFile: File): List<File> {
|
||||
val mainFileName = mainFile.getName()
|
||||
return mainFile.getParentFile()?.listFiles()?.filter { it.name.startsWith(mainFileName) && it.name != mainFileName } ?: Collections.emptyList()
|
||||
|
||||
+123
-100
@@ -30,110 +30,133 @@ import org.jetbrains.jet.plugin.debugger.evaluate.AbstractKotlinEvaluateExpressi
|
||||
|
||||
/** This class is generated by {@link org.jetbrains.jet.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||
@SuppressWarnings("all")
|
||||
@TestMetadata("idea/testData/debugger/tinyApp/src/evaluate")
|
||||
@InnerTestClasses({KotlinEvaluateExpressionTestGenerated.SingleBreakpoint.class, KotlinEvaluateExpressionTestGenerated.MultipleBreakpoints.class})
|
||||
public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluateExpressionTest {
|
||||
@TestMetadata("abstractFunCall.kt")
|
||||
public void testAbstractFunCall() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/abstractFunCall.kt");
|
||||
@TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint")
|
||||
public static class SingleBreakpoint extends AbstractKotlinEvaluateExpressionTest {
|
||||
@TestMetadata("abstractFunCall.kt")
|
||||
public void testAbstractFunCall() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/abstractFunCall.kt");
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInSingleBreakpoint() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("arrays.kt")
|
||||
public void testArrays() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/arrays.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("classFromAnotherPackage.kt")
|
||||
public void testClassFromAnotherPackage() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/classFromAnotherPackage.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("classObjectVal.kt")
|
||||
public void testClassObjectVal() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/classObjectVal.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("collections.kt")
|
||||
public void testCollections() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/collections.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("dependentOnFile.kt")
|
||||
public void testDependentOnFile() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/dependentOnFile.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("doubles.kt")
|
||||
public void testDoubles() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/doubles.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("enums.kt")
|
||||
public void testEnums() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/enums.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("errors.kt")
|
||||
public void testErrors() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/errors.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("extractLocalVariables.kt")
|
||||
public void testExtractLocalVariables() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/extractLocalVariables.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("extractThis.kt")
|
||||
public void testExtractThis() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/extractThis.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("extractVariablesFromCall.kt")
|
||||
public void testExtractVariablesFromCall() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/extractVariablesFromCall.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("imports.kt")
|
||||
public void testImports() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/imports.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("insertInBlock.kt")
|
||||
public void testInsertInBlock() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/insertInBlock.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("multilineExpressionAtBreakpoint.kt")
|
||||
public void testMultilineExpressionAtBreakpoint() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/multilineExpressionAtBreakpoint.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("privateMember.kt")
|
||||
public void testPrivateMember() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/privateMember.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("protectedMember.kt")
|
||||
public void testProtectedMember() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/protectedMember.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/simple.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stdlib.kt")
|
||||
public void testStdlib() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/stdlib.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("vars.kt")
|
||||
public void testVars() throws Exception {
|
||||
doSingleBreakpointTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/vars.kt");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInEvaluate() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/debugger/tinyApp/src/evaluate"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
@TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints")
|
||||
public static class MultipleBreakpoints extends AbstractKotlinEvaluateExpressionTest {
|
||||
public void testAllFilesPresentInMultipleBreakpoints() throws Exception {
|
||||
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("clearCache.kt")
|
||||
public void testClearCache() throws Exception {
|
||||
doMultipleBreakpointsTest("idea/testData/debugger/tinyApp/src/evaluate/multipleBreakpoints/clearCache.kt");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@TestMetadata("arrays.kt")
|
||||
public void testArrays() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/arrays.kt");
|
||||
public static Test suite() {
|
||||
TestSuite suite = new TestSuite("KotlinEvaluateExpressionTestGenerated");
|
||||
suite.addTestSuite(SingleBreakpoint.class);
|
||||
suite.addTestSuite(MultipleBreakpoints.class);
|
||||
return suite;
|
||||
}
|
||||
|
||||
@TestMetadata("classFromAnotherPackage.kt")
|
||||
public void testClassFromAnotherPackage() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/classFromAnotherPackage.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("classObjectVal.kt")
|
||||
public void testClassObjectVal() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/classObjectVal.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("collections.kt")
|
||||
public void testCollections() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/collections.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("dependentOnFile.kt")
|
||||
public void testDependentOnFile() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/dependentOnFile.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("doubles.kt")
|
||||
public void testDoubles() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/doubles.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("enums.kt")
|
||||
public void testEnums() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/enums.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("errors.kt")
|
||||
public void testErrors() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/errors.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("extractLocalVariables.kt")
|
||||
public void testExtractLocalVariables() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/extractLocalVariables.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("extractThis.kt")
|
||||
public void testExtractThis() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/extractThis.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("extractVariablesFromCall.kt")
|
||||
public void testExtractVariablesFromCall() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/extractVariablesFromCall.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("imports.kt")
|
||||
public void testImports() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/imports.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("insertInBlock.kt")
|
||||
public void testInsertInBlock() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/insertInBlock.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("multilineExpressionAtBreakpoint.kt")
|
||||
public void testMultilineExpressionAtBreakpoint() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/multilineExpressionAtBreakpoint.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("privateMember.kt")
|
||||
public void testPrivateMember() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/privateMember.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("protectedMember.kt")
|
||||
public void testProtectedMember() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/protectedMember.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/simple.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("stdlib.kt")
|
||||
public void testStdlib() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/stdlib.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("vars.kt")
|
||||
public void testVars() throws Exception {
|
||||
doTest("idea/testData/debugger/tinyApp/src/evaluate/vars.kt");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user