diff --git a/idea/idea-android/idea-android.iml b/idea/idea-android/idea-android.iml
index 2a5b3985a11..d7e839afa2d 100644
--- a/idea/idea-android/idea-android.iml
+++ b/idea/idea-android/idea-android.iml
@@ -24,5 +24,6 @@
+
\ No newline at end of file
diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/debugger/AndroidDexWrapper.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/debugger/AndroidDexWrapper.kt
new file mode 100644
index 00000000000..9c1bff78ce7
--- /dev/null
+++ b/idea/idea-android/src/org/jetbrains/kotlin/android/debugger/AndroidDexWrapper.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2010-2017 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.android.debugger
+
+import com.android.dx.cf.direct.DirectClassFile
+import com.android.dx.cf.direct.StdAttributeFactory
+import com.android.dx.command.dexer.Main
+import com.android.dx.dex.cf.CfTranslator
+import com.android.dx.dex.file.DexFile
+import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
+
+class AndroidDexWrapper {
+ @Suppress("unused") // Used in AndroidOClassLoadingAdapter#dex
+ fun dex(classes: Collection): ByteArray? {
+ val dexArguments = Main.Arguments().apply { parse(arrayOf("testArgs")) }
+
+ val dexFile = DexFile(dexArguments.dexOptions)
+
+ for ((_, relativeFileName, bytes) in classes) {
+ val cf = DirectClassFile(bytes, relativeFileName, true)
+ cf.setAttributeFactory(StdAttributeFactory.THE_ONE)
+ val classDef = CfTranslator.translate(
+ cf,
+ bytes,
+ dexArguments.cfOptions,
+ dexArguments.dexOptions,
+ dexFile
+ )
+
+ dexFile.add(classDef)
+ }
+
+ return dexFile.toDex(null, false)
+ }
+}
\ No newline at end of file
diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/debugger/AndroidDexerImpl.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/debugger/AndroidDexerImpl.kt
new file mode 100644
index 00000000000..0cafa358089
--- /dev/null
+++ b/idea/idea-android/src/org/jetbrains/kotlin/android/debugger/AndroidDexerImpl.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010-2017 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.android.debugger
+
+import com.intellij.openapi.module.ModuleManager
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.roots.ProjectRootModificationTracker
+import com.intellij.psi.util.CachedValueProvider
+import com.intellij.psi.util.CachedValuesManager
+import org.jetbrains.android.facet.AndroidFacet
+import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.AndroidDexer
+import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
+import java.io.File
+import java.net.URLClassLoader
+import java.security.ProtectionDomain
+
+class AndroidDexerImpl(val project: Project) : AndroidDexer {
+ private val cachedDexWrapper = CachedValuesManager.getManager(project).createCachedValue({
+ val dexWrapper = doGetAndroidDexFile()?.let { dexJarFile ->
+ val androidDexWrapperName = AndroidDexWrapper::class.java.canonicalName
+ val classBytes = this.javaClass.classLoader.getResource(
+ androidDexWrapperName.replace('.', '/') + ".class").readBytes()
+
+ val dexClassLoader = object : URLClassLoader(arrayOf(dexJarFile.toURI().toURL()), this::class.java.classLoader) {
+ init {
+ defineClass(androidDexWrapperName, classBytes, 0, classBytes.size, null as ProtectionDomain?)
+ }
+ }
+
+ Class.forName(androidDexWrapperName, true, dexClassLoader).newInstance()
+ }
+
+ CachedValueProvider.Result.createSingleDependency(dexWrapper, ProjectRootModificationTracker.getInstance(project))
+ }, /* trackValue = */ false)
+
+ override fun dex(classes: Collection): ByteArray? {
+ val dexWrapper = cachedDexWrapper.value
+ val dexMethod = dexWrapper::class.java.methods.firstOrNull { it.name == "dex" } ?: return null
+ return dexMethod.invoke(dexWrapper, classes) as? ByteArray ?: return null
+ }
+
+ private fun doGetAndroidDexFile(): File? {
+ for (module in ModuleManager.getInstance(project).modules) {
+ val androidFacet = AndroidFacet.getInstance(module) ?: continue
+ val sdkData = androidFacet.sdkData ?: continue
+ val latestBuildTool = sdkData.getLatestBuildTool(/* allowPreview = */ false)
+ ?: sdkData.getLatestBuildTool(/* allowPreview = */ true)
+ ?: continue
+
+ val dxJar = File(latestBuildTool.location, "lib/dx.jar")
+ if (dxJar.exists()) {
+ return dxJar
+ }
+ }
+
+ return null
+ }
+}
\ No newline at end of file
diff --git a/idea/src/META-INF/android.xml b/idea/src/META-INF/android.xml
index 77ae4f2fa91..1b5e34fe10a 100644
--- a/idea/src/META-INF/android.xml
+++ b/idea/src/META-INF/android.xml
@@ -84,6 +84,8 @@
+
+
diff --git a/idea/src/META-INF/extensions/ide.xml b/idea/src/META-INF/extensions/ide.xml
index 907aca56000..d83cc27d7ce 100644
--- a/idea/src/META-INF/extensions/ide.xml
+++ b/idea/src/META-INF/extensions/ide.xml
@@ -6,6 +6,10 @@
+
+
diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt
index d01efd09c47..f4fc6fac609 100644
--- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt
@@ -42,6 +42,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
import org.jetbrains.kotlin.idea.debugger.BinaryCacheKey
import org.jetbrains.kotlin.idea.debugger.BytecodeDebugInfo
import org.jetbrains.kotlin.idea.debugger.WeakBytecodeDebugInfoStorage
+import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriority
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.KtCodeFragment
@@ -217,8 +218,7 @@ class KotlinDebuggerCaches(project: Project) {
}
data class CompiledDataDescriptor(
- val bytecodes: ByteArray,
- val additionalClasses: List>,
+ val classes: List,
val sourcePosition: SourcePosition,
val parameters: ParametersDescriptor
)
diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt
index c1ea26f6290..bd96a5df65f 100644
--- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt
@@ -61,7 +61,9 @@ import org.jetbrains.kotlin.idea.core.quoteSegmentsIfNeeded
import org.jetbrains.kotlin.idea.debugger.DebuggerUtils
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.CompiledDataDescriptor
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ParametersDescriptor
+import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClasses
+import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionResult
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriority
import org.jetbrains.kotlin.idea.util.application.runReadAction
@@ -78,6 +80,7 @@ import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.Opcodes.ASM5
import org.jetbrains.org.objectweb.asm.Type
+import org.jetbrains.org.objectweb.asm.tree.ClassNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import java.util.*
@@ -134,14 +137,37 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
isCompiledDataFromCache = false
extractAndCompile(fragment, position, context)
}
- val result = runEval4j(context, compiledData)
+
+ val classLoaderHandler = loadClassesSafely(context, compiledData.classes)
+
+ val result = if (classLoaderHandler != null) {
+ try {
+ evaluateWithCompilation(context, compiledData) ?: runEval4j(context, compiledData)
+ } finally {
+ classLoaderHandler.dispose()
+ }
+ }
+ else {
+ runEval4j(context, compiledData)
+ }
// If bytecode was taken from cache and exception was thrown - recompile bytecode and run eval4j again
if (isCompiledDataFromCache && result is ExceptionThrown && result.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE) {
- return runEval4j(context, extractAndCompile(codeFragment, sourcePosition, context)).toJdiValue(context)
+ // We need only lambda classes here cause we using only eval4j evaluation method
+ val classLoaderHandler = loadClasses(context, compiledData.classes.drop(1))
+
+ try {
+ return runEval4j(context, extractAndCompile(codeFragment, sourcePosition, context)).toJdiValue(context)
+ } finally {
+ classLoaderHandler.dispose()
+ }
}
- return result.toJdiValue(context)
+ return if (result is InterpreterResult) {
+ result.toJdiValue(context)
+ } else {
+ result
+ }
}
catch(e: EvaluateException) {
throw e
@@ -217,10 +243,9 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
}
}
- val additionalFiles = outputFiles.drop(1).map { getClassName(it.relativePath) to it.asByteArray() }
+ val additionalFiles = outputFiles.map { ClassToLoad(getClassName(it.relativePath), it.relativePath, it.asByteArray()) }
return CompiledDataDescriptor(
- outputFiles.first().asByteArray(),
additionalFiles,
sourcePosition,
parametersDescriptor)
@@ -230,15 +255,66 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
return fileName.substringBeforeLast(".class").replace("/", ".")
}
+ private fun evaluateWithCompilation(context: EvaluationContextImpl, compiledData: CompiledDataDescriptor): Any? {
+ val vm = context.debugProcess.virtualMachineProxy.virtualMachine
+ val classLoader = context.classLoader ?: return null
+ val mainClassBytecode = compiledData.classes[0].bytes
+
+ try {
+ val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, ClassReader.EXPAND_FRAMES) }
+ val mainClassJdiName = mainClassAsmNode.name.replace('/', '.')
+ assert(mainClassAsmNode.methods.size == 1)
+
+ val methodToInvoke = mainClassAsmNode.methods[0]
+ assert(methodToInvoke.parameters == null || methodToInvoke.parameters.isEmpty())
+
+ val mainClass = classByName(context, mainClassJdiName, classLoader).reflectedType() as ClassType
+
+ val thread = context.suspendContext.thread?.threadReference!!
+ val invokePolicy = context.suspendContext.getInvokePolicy()
+ val eval = JDIEval(vm, classLoader, thread, invokePolicy)
+
+ return vm.executeWithBreakpointsDisabled {
+ // Prepare the main class
+ eval.loadClass(Type.getObjectType(mainClassAsmNode.name), classLoader)
+
+ val argumentTypes = Type.getArgumentTypes(methodToInvoke.desc)
+ val args = context.getArgumentsForEval4j(compiledData.parameters, argumentTypes)
+ .zip(argumentTypes)
+ .map { (value, type) ->
+ // Make argument type classes prepared for sure
+ eval.loadClass(type, classLoader)
+ boxOrUnboxArgumentIfNeeded(eval, value, type).asJdiValue(vm, type)
+ }
+
+
+ mainClass.invokeMethod(thread, mainClass.methods().single(), args, invokePolicy)
+ }
+ } catch (e: Throwable) {
+ LOG.debug("Unable to evaluate expression with compilation", e)
+ return null
+ }
+ }
+
+ private fun classByName(context: EvaluationContextImpl, className: String, classLoader: ClassLoaderReference): ClassObjectReference {
+ val process = context.debugProcess
+ val vm = process.virtualMachineProxy
+ val classClass = process.findClass(context, Class::class.java.canonicalName, classLoader) as ClassType
+ val forNameMethod = classClass.concreteMethodByName(
+ "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;") ?: error("'forName' method not found")
+ return process.invokeMethod(context, classClass, forNameMethod, listOf(
+ vm.mirrorOf(className),
+ vm.mirrorOf(true),
+ classLoader)) as ClassObjectReference
+ }
+
private fun runEval4j(context: EvaluationContextImpl, compiledData: CompiledDataDescriptor): InterpreterResult {
val virtualMachine = context.debugProcess.virtualMachineProxy.virtualMachine
-
- if (compiledData.additionalClasses.isNotEmpty()) {
- loadClasses(context, compiledData.additionalClasses)
- }
-
var resultValue: InterpreterResult? = null
- ClassReader(compiledData.bytecodes).accept(object : ClassVisitor(ASM5) {
+
+ val mainClassBytecode = compiledData.classes[0].bytes
+
+ ClassReader(mainClassBytecode).accept(object : ClassVisitor(ASM5) {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array?): MethodVisitor? {
if (name == GENERATED_FUNCTION_NAME) {
val argumentTypes = Type.getArgumentTypes(desc)
@@ -246,22 +322,18 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
return object : MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions) {
override fun visitEnd() {
- val allRequests = virtualMachine.eventRequestManager().breakpointRequests() +
- virtualMachine.eventRequestManager().classPrepareRequests()
- allRequests.forEach { it.disable() }
+ virtualMachine.executeWithBreakpointsDisabled {
+ val eval = JDIEval(virtualMachine,
+ context.classLoader,
+ context.suspendContext.thread?.threadReference!!,
+ context.suspendContext.getInvokePolicy())
- val eval = JDIEval(virtualMachine,
- context.classLoader,
- context.suspendContext.thread?.threadReference!!,
- context.suspendContext.getInvokePolicy())
-
- resultValue = interpreterLoop(
- this,
- makeInitialFrame(this, args.zip(argumentTypes).map { boxOrUnboxArgumentIfNeeded(eval, it.first, it.second) }),
- eval
- )
-
- allRequests.forEach { it.enable() }
+ resultValue = interpreterLoop(
+ this,
+ makeInitialFrame(this, args.zip(argumentTypes).map { boxOrUnboxArgumentIfNeeded(eval, it.first, it.second) }),
+ eval
+ )
+ }
}
}
}
@@ -273,6 +345,17 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
return resultValue ?: throw IllegalStateException("resultValue is null: cannot find method " + GENERATED_FUNCTION_NAME)
}
+ private inline fun VirtualMachine.executeWithBreakpointsDisabled(block: () -> T): T {
+ val allRequests = eventRequestManager().breakpointRequests() + eventRequestManager().classPrepareRequests()
+
+ try {
+ allRequests.forEach { it.disable() }
+ return block()
+ } finally {
+ allRequests.forEach { it.enable() }
+ }
+ }
+
private fun boxOrUnboxArgumentIfNeeded(eval: JDIEval, argumentValue: Value, parameterType: Type): Value {
val argumentType = argumentValue.asmType
@@ -541,7 +624,7 @@ private fun PsiElement.createKtFile(fileName: String, fileText: String): KtFile
return jetFile
}
-private fun SuspendContext.getInvokePolicy(): Int {
+internal fun SuspendContext.getInvokePolicy(): Int {
return if (suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD) ObjectReference.INVOKE_SINGLE_THREADED else 0
}
diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/AbstractAndroidClassLoadingAdapter.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/AbstractAndroidClassLoadingAdapter.kt
new file mode 100644
index 00000000000..7d648a386ff
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/AbstractAndroidClassLoadingAdapter.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2010-2017 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.classLoading
+
+import com.intellij.debugger.engine.DebugProcessImpl
+import com.intellij.debugger.engine.evaluation.EvaluationContext
+import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
+import com.intellij.debugger.impl.DebuggerUtilsEx
+import com.sun.jdi.*
+
+abstract class AbstractAndroidClassLoadingAdapter : ClassLoadingAdapter {
+ protected fun dex(context: EvaluationContextImpl, classes: Collection): ByteArray? {
+ return AndroidDexer.getInstances(context.project).single().dex(classes)
+ }
+
+ protected fun mirrorOfByteArray(bytes: ByteArray, context: EvaluationContextImpl, process: DebugProcessImpl): ArrayReference {
+ val arrayClass = process.findClass(context, "byte[]", context.classLoader) as ArrayType
+ val reference = process.newInstance(arrayClass, bytes.size)
+ DebuggerUtilsEx.keep(reference, context)
+
+ for (i in 0..bytes.lastIndex) {
+ reference.setValue(i, process.virtualMachineProxy.mirrorOf(bytes[i]))
+ }
+
+ return reference
+ }
+
+ protected fun wrapToByteBuffer(bytes: ArrayReference, context: EvaluationContext, process: DebugProcessImpl): ObjectReference {
+ val byteBufferClass = process.findClass(context, "java.nio.ByteBuffer", context.classLoader) as ClassType
+ val wrapMethod = byteBufferClass.concreteMethodByName("wrap", "([B)Ljava/nio/ByteBuffer;")
+ ?: error("'wrap' method not found")
+
+ return process.invokeMethod(context, byteBufferClass, wrapMethod, listOf(bytes)) as ObjectReference
+ }
+
+ protected fun DebugProcessImpl.tryLoadClass(
+ context: EvaluationContextImpl,
+ fqName: String,
+ classLoader: ClassLoaderReference?
+ ): ReferenceType? {
+ return try {
+ loadClass(context, fqName, classLoader)
+ } catch (e: Throwable) {
+ null
+ }
+ }
+}
+
+internal val ClassLoaderReference.isDalvikClassLoader: Boolean
+ get() = referenceType()?.name()?.startsWith("dalvik.system.") ?: false
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/AndroidDexer.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/AndroidDexer.kt
new file mode 100644
index 00000000000..032726ac12a
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/AndroidDexer.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2010-2017 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.classLoading
+
+import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
+
+interface AndroidDexer {
+ companion object : ProjectExtensionDescriptor(
+ "org.jetbrains.kotlin.androidDexer", AndroidDexer::class.java)
+
+ fun dex(classes: Collection): ByteArray?
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/AndroidOClassLoadingAdapter.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/AndroidOClassLoadingAdapter.kt
new file mode 100644
index 00000000000..a06fae047d9
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/AndroidOClassLoadingAdapter.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2010-2017 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.classLoading
+
+import com.intellij.debugger.engine.JVMNameUtil
+import com.intellij.debugger.engine.evaluation.EvaluateException
+import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
+import com.intellij.debugger.impl.DebuggerUtilsEx
+import com.sun.jdi.*
+
+class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() {
+ override fun isApplicable(context: EvaluationContextImpl, classes: Collection): Boolean {
+ if (classes.size <= 1) {
+ // Dex takes significant amount of time so we load classes only if we have some non-inline lambdas
+ return false
+ }
+
+ return context.classLoader?.isDalvikClassLoader ?: false
+ }
+
+ private fun resolveClassLoaderClass(context: EvaluationContextImpl): ClassType? {
+ try {
+ return context.debugProcess.tryLoadClass(
+ context, "dalvik.system.InMemoryDexClassLoader", context.classLoader) as? ClassType
+ } catch (e: EvaluateException) {
+ return null
+ }
+ }
+
+ override fun loadClasses(context: EvaluationContextImpl, classes: Collection): ClassLoaderHandler {
+ val process = context.debugProcess
+ val inMemoryClassLoaderClass = resolveClassLoaderClass(context) ?: error("InMemoryDexClassLoader class not found")
+ val constructorMethod = inMemoryClassLoaderClass.concreteMethodByName(
+ JVMNameUtil.CONSTRUCTOR_NAME, "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V") ?: error("Constructor method not found")
+
+ val dexBytes = dex(context, classes) ?: error("Can't dex classes")
+ val dexBytesMirror = mirrorOfByteArray(dexBytes, context, process)
+ val dexByteBuffer = wrapToByteBuffer(dexBytesMirror, context, process)
+
+ val newClassLoader = process.newInstance(context, inMemoryClassLoaderClass, constructorMethod,
+ listOf(dexByteBuffer, context.classLoader))
+
+ DebuggerUtilsEx.keep(newClassLoader, context)
+
+ return ClassLoaderHandler(newClassLoader as ClassLoaderReference)
+ }
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/ClassLoaderHandler.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/ClassLoaderHandler.kt
new file mode 100644
index 00000000000..94f4eb18de4
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/ClassLoaderHandler.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010-2017 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.classLoading
+
+import com.intellij.openapi.Disposable
+import com.sun.jdi.ClassLoaderReference
+
+open class ClassLoaderHandler(val reference: ClassLoaderReference?) : Disposable {
+ override fun dispose() {}
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/ClassLoadingAdapter.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/ClassLoadingAdapter.kt
new file mode 100644
index 00000000000..578d42eef75
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/ClassLoadingAdapter.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2010-2017 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.classLoading
+
+import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
+import com.intellij.psi.PsiFile
+import com.sun.jdi.ClassLoaderReference
+
+interface ClassLoadingAdapter {
+ companion object {
+ private val ADAPTERS = listOf(
+ AndroidOClassLoadingAdapter(),
+ OrdinaryClassLoadingAdapter())
+
+ fun loadClasses(context: EvaluationContextImpl, classes: Collection): ClassLoaderHandler {
+ for (adapter in ADAPTERS) {
+ if (adapter.isApplicable(context, classes)) {
+ return adapter.loadClasses(context, classes)
+ }
+ }
+
+ // Should never happen because OrdinaryClassLoadingAdapter is always applicable
+ throw IllegalStateException("Class loading adapter not found for $context")
+ }
+ }
+
+ fun isApplicable(context: EvaluationContextImpl, classes: Collection): Boolean
+
+ fun loadClasses(context: EvaluationContextImpl, classes: Collection): ClassLoaderHandler
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/ClassToLoad.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/ClassToLoad.kt
new file mode 100644
index 00000000000..571d242ffd3
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/ClassToLoad.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2010-2017 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.classLoading
+
+@Suppress("ArrayInDataClass")
+data class ClassToLoad(val className: String, val relativeFileName: String, val bytes: ByteArray)
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/OrdinaryClassLoadingAdapter.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/OrdinaryClassLoadingAdapter.kt
new file mode 100644
index 00000000000..35aa320040b
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/OrdinaryClassLoadingAdapter.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2010-2017 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.classLoading
+
+import com.intellij.debugger.engine.DebugProcess
+import com.intellij.debugger.engine.evaluation.EvaluateException
+import com.intellij.debugger.engine.evaluation.EvaluationContext
+import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
+import com.intellij.debugger.impl.ClassLoadingUtils
+import com.intellij.openapi.projectRoots.JdkVersionUtil
+import com.intellij.openapi.util.SystemInfo
+import com.sun.jdi.ClassLoaderReference
+import org.jetbrains.kotlin.idea.debugger.evaluate.CompilingEvaluatorUtils
+
+class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
+ private companion object {
+ // This list should contain all superclasses of lambda classes.
+ // The order is relevant here: if we load Lambda first instead, during the definition of Lambda the class loader will try
+ // to load its superclass. It will succeed, probably with the help of some parent class loader, and the subsequent attempt to define
+ // the patched version of that superclass will fail with LinkageError (cannot redefine class)
+ private val LAMBDA_SUPERCLASSES = listOf(ClassBytes("kotlin.jvm.internal.Lambda"))
+ }
+
+ override fun isApplicable(context: EvaluationContextImpl, classes: Collection): Boolean {
+ val classLoader = context.classLoader
+ return classLoader != null && !classLoader.isDalvikClassLoader
+ }
+
+ override fun loadClasses(context: EvaluationContextImpl, classes: Collection): ClassLoaderHandler {
+ val process = context.debugProcess
+
+ val classLoader = try {
+ ClassLoadingUtils.getClassLoader(context, process)
+ }
+ catch (e: Exception) {
+ throw EvaluateException("Error creating evaluation class loader: " + e, e)
+ }
+
+ val version = process.virtualMachineProxy.version()
+ val sdkVersion = JdkVersionUtil.getVersion(version)
+
+ if (!SystemInfo.isJavaVersionAtLeast(sdkVersion.description)) {
+ throw EvaluateException(
+ "Unable to compile for target level ${sdkVersion.description}. " +
+ "Need to run IDEA on java version at least ${sdkVersion.description}, " +
+ "currently running on ${SystemInfo.JAVA_RUNTIME_VERSION}")
+ }
+
+ try {
+ defineClasses(classes, context, process, classLoader)
+ }
+ catch (e: Exception) {
+ throw EvaluateException("Error during classes definition " + e, e)
+ }
+
+ return ClassLoaderHandler(classLoader)
+ }
+
+ private fun defineClasses(
+ classes: Collection,
+ context: EvaluationContext,
+ process: DebugProcess,
+ classLoader: ClassLoaderReference
+ ) {
+ val classesToLoad = if (classes.size == 1) {
+ // No need in loading lambda superclass if there're no lambdas
+ classes
+ }
+ else {
+ val lambdaSuperclasses = LAMBDA_SUPERCLASSES.map {
+ ClassToLoad(it.name, it.name.replace('.', '/') + ".class", it.bytes)
+ }
+ lambdaSuperclasses + classes
+ }
+
+ for ((className, _, bytes) in classesToLoad) {
+ val patchedBytes = CompilingEvaluatorUtils.changeSuperToMagicAccessor(bytes)
+ ClassLoadingUtils.defineClass(className, patchedBytes, context, process, classLoader)
+ }
+ }
+
+ private class ClassBytes(val name: String) {
+ val bytes: ByteArray by lazy {
+ val inputStream = this::class.java.classLoader.getResourceAsStream(name.replace('.', '/') + ".class")
+ ?: throw EvaluateException("Couldn't find $name class in current class loader")
+
+ inputStream.use {
+ it.readBytes()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilingEvaluator.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilingEvaluator.kt
index 6ae3644140e..e0064beffe8 100644
--- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilingEvaluator.kt
+++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilingEvaluator.kt
@@ -16,72 +16,23 @@
package org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator
-import com.intellij.debugger.engine.DebugProcess
-import com.intellij.debugger.engine.evaluation.EvaluateException
-import com.intellij.debugger.engine.evaluation.EvaluationContext
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
-import com.intellij.debugger.impl.ClassLoadingUtils
-import com.intellij.openapi.projectRoots.JdkVersionUtil
-import com.intellij.openapi.util.SystemInfo
-import com.sun.jdi.ClassLoaderReference
-import org.jetbrains.kotlin.idea.debugger.evaluate.CompilingEvaluatorUtils
+import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassLoaderHandler
+import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassLoadingAdapter
+import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
-fun loadClasses(evaluationContext: EvaluationContextImpl, classes: Collection>) {
- val process = evaluationContext.debugProcess
-
- val classLoader: ClassLoaderReference
+fun loadClassesSafely(evaluationContext: EvaluationContextImpl, classes: Collection): ClassLoaderHandler? {
try {
- classLoader = ClassLoadingUtils.getClassLoader(evaluationContext, process)
- }
- catch (e: Exception) {
- throw EvaluateException("Error creating evaluation class loader: " + e, e)
- }
-
- val version = (process.virtualMachineProxy).version()
- val sdkVersion = JdkVersionUtil.getVersion(version)
-
- if (!SystemInfo.isJavaVersionAtLeast(sdkVersion.description)) {
- throw EvaluateException("Unable to compile for target level " + sdkVersion.description + ". Need to run IDEA on java version at least " + sdkVersion.description + ", 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.classLoader = classLoader
-}
-
-private fun defineClasses(
- classes: Collection>,
- context: EvaluationContext,
- process: DebugProcess,
- classLoader: ClassLoaderReference
-) {
- val lambdaSuperclasses = LAMBDA_SUPERCLASSES.map { it.name to it.bytes }
- for ((className, bytes) in lambdaSuperclasses + classes) {
- val patchedBytes = CompilingEvaluatorUtils.changeSuperToMagicAccessor(bytes)
- ClassLoadingUtils.defineClass(className, patchedBytes, context, process, classLoader)
+ return loadClasses(evaluationContext, classes)
+ } catch (e: Throwable) {
+ return null
}
}
-// This list should contain all superclasses of lambda classes.
-// The order is relevant here: if we load Lambda first instead, during the definition of Lambda the class loader will try
-// to load its superclass. It will succeed, probably with the help of some parent class loader, and the subsequent attempt to define
-// the patched version of that superclass will fail with LinkageError (cannot redefine class)
-private val LAMBDA_SUPERCLASSES = listOf(
- ClassBytes("kotlin.jvm.internal.Lambda")
-)
+fun loadClasses(evaluationContext: EvaluationContextImpl, classes: Collection): ClassLoaderHandler {
+ if (classes.isEmpty()) return ClassLoaderHandler(evaluationContext.classLoader)
-private class ClassBytes(val name: String) {
- val bytes: ByteArray by lazy {
- val inputStream = this::class.java.classLoader.getResourceAsStream(name.replace('.', '/') + ".class")
- ?: throw EvaluateException("Couldn't find $name class in current class loader")
-
- inputStream.use {
- it.readBytes()
- }
+ return ClassLoadingAdapter.loadClasses(evaluationContext, classes).apply {
+ evaluationContext.classLoader = this.reference
}
-}
+}
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt
index dba5591b0f3..18319ad4b78 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt
+++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/AbstractKotlinEvaluateExpressionTest.kt
@@ -23,6 +23,7 @@ import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.TextWithImports
import com.intellij.debugger.engine.evaluation.TextWithImportsImpl
import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilderImpl
+import com.intellij.debugger.engine.events.SuspendContextCommandImpl
import com.intellij.debugger.settings.NodeRendererSettings
import com.intellij.debugger.ui.impl.watch.*
import com.intellij.debugger.ui.tree.*
@@ -454,26 +455,39 @@ abstract class AbstractKotlinEvaluateExpressionTest : KotlinDebuggerTestBase() {
contextElement.putCopyableUserData(KotlinCodeFragmentFactory.DEBUG_CONTEXT_FOR_TESTS, this@AbstractKotlinEvaluateExpressionTest.debuggerContext)
- try {
- val evaluator =
- EvaluatorBuilderImpl.build(item,
- contextElement,
- sourcePosition,
- project)
+ runActionInSuspendCommand {
+ try {
+ val evaluator = EvaluatorBuilderImpl.build(item, contextElement, sourcePosition, project)
+ ?: throw AssertionError("Cannot create an Evaluator for Evaluate Expression")
-
- if (evaluator == null) throw AssertionError("Cannot create an Evaluator for Evaluate Expression")
-
- val value = evaluator.evaluate(this@AbstractKotlinEvaluateExpressionTest.evaluationContext)
- val actualResult = value.asValue().asString()
- if (expectedResult != null) {
- Assert.assertTrue("Evaluate expression returns wrong result for ${item.text}:\nexpected = $expectedResult\nactual = $actualResult\n", expectedResult == actualResult)
+ val value = evaluator.evaluate(this@AbstractKotlinEvaluateExpressionTest.evaluationContext)
+ val actualResult = value.asValue().asString()
+ if (expectedResult != null) {
+ Assert.assertTrue("Evaluate expression returns wrong result for ${item.text}:" +
+ "\nexpected = $expectedResult\nactual = $actualResult\n", expectedResult == actualResult)
+ }
+ }
+ catch (e: EvaluateException) {
+ Assert.assertTrue("Evaluate expression throws wrong exception for ${item.text}:" +
+ "\nexpected = $expectedResult\nactual = ${e.message}\n", expectedResult == e.message?.replaceFirst(ID_PART_REGEX, "id=ID"))
}
}
- catch (e: EvaluateException) {
- Assert.assertTrue("Evaluate expression throws wrong exception for ${item.text}:\nexpected = $expectedResult\nactual = ${e.message}\n", expectedResult == e.message?.replaceFirst(ID_PART_REGEX, "id=ID"))
+ }
+ }
+
+ private fun SuspendContextImpl.runActionInSuspendCommand(action: SuspendContextImpl.() -> Unit) {
+ if (myInProgress) {
+ action()
+ } else {
+ val command = object : SuspendContextCommandImpl(this) {
+ override fun contextAction(suspendContext: SuspendContextImpl) {
+ action(suspendContext)
+ }
}
+
+ // Try to execute the action inside a command if we aren't already inside it.
+ debuggerContext.debugProcess?.managerThread?.invoke(command) ?: command.contextAction(this)
}
}