From 347e6d905898295bdf11d8eeaa2d5ac5399ddc04 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Wed, 9 Apr 2014 17:25:45 +0400 Subject: [PATCH] Debugger: Implement EvaluatorBuilder for evaluate expression for kotlin files --- .../com/intellij/debugger/annotations.xml | 3 + .../intellij/debugger/engine/annotations.xml | 3 + .../engine/evaluation/annotations.xml | 83 ++++++++++ .../evaluation/expression/annotations.xml | 29 ++++ .../intellij/debugger/impl/annotations.xml | 8 + .../com/intellij/debugger/jdi/annotations.xml | 5 + .../jet/codegen/ClassBuilderFactories.java | 2 + .../evaluate/KotlinCodeFragmentFactory.kt | 2 +- .../evaluate/KotlinEvaluationBuilder.kt | 154 ++++++++++++++++++ 9 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 annotations/com/intellij/debugger/engine/evaluation/annotations.xml create mode 100644 annotations/com/intellij/debugger/engine/evaluation/expression/annotations.xml create mode 100644 annotations/com/intellij/debugger/jdi/annotations.xml create mode 100644 idea/src/org/jetbrains/jet/plugin/debugger/evaluate/KotlinEvaluationBuilder.kt diff --git a/annotations/com/intellij/debugger/annotations.xml b/annotations/com/intellij/debugger/annotations.xml index 8d2379bc615..74b1dd9d97d 100644 --- a/annotations/com/intellij/debugger/annotations.xml +++ b/annotations/com/intellij/debugger/annotations.xml @@ -3,4 +3,7 @@ name='com.intellij.debugger.ExecutionWithDebuggerToolsTestCase void onBreakpoint(com.intellij.debugger.engine.SuspendContextRunnable) 0'> + + + \ No newline at end of file diff --git a/annotations/com/intellij/debugger/engine/annotations.xml b/annotations/com/intellij/debugger/engine/annotations.xml index f8270efeebf..709e447b547 100644 --- a/annotations/com/intellij/debugger/engine/annotations.xml +++ b/annotations/com/intellij/debugger/engine/annotations.xml @@ -1,4 +1,7 @@ + + + diff --git a/annotations/com/intellij/debugger/engine/evaluation/annotations.xml b/annotations/com/intellij/debugger/engine/evaluation/annotations.xml new file mode 100644 index 00000000000..92f8c857035 --- /dev/null +++ b/annotations/com/intellij/debugger/engine/evaluation/annotations.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/annotations/com/intellij/debugger/engine/evaluation/expression/annotations.xml b/annotations/com/intellij/debugger/engine/evaluation/expression/annotations.xml new file mode 100644 index 00000000000..fd2ca89dc15 --- /dev/null +++ b/annotations/com/intellij/debugger/engine/evaluation/expression/annotations.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/annotations/com/intellij/debugger/impl/annotations.xml b/annotations/com/intellij/debugger/impl/annotations.xml index 45042976341..f9c635bf199 100644 --- a/annotations/com/intellij/debugger/impl/annotations.xml +++ b/annotations/com/intellij/debugger/impl/annotations.xml @@ -1,4 +1,12 @@ + + + + + + diff --git a/annotations/com/intellij/debugger/jdi/annotations.xml b/annotations/com/intellij/debugger/jdi/annotations.xml new file mode 100644 index 00000000000..664afea9e97 --- /dev/null +++ b/annotations/com/intellij/debugger/jdi/annotations.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/ClassBuilderFactories.java b/compiler/backend/src/org/jetbrains/jet/codegen/ClassBuilderFactories.java index d9b9113663b..e251d1e07cc 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/ClassBuilderFactories.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/ClassBuilderFactories.java @@ -26,6 +26,7 @@ import java.io.StringWriter; @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") public class ClassBuilderFactories { + @NotNull public static ClassBuilderFactory TEST = new ClassBuilderFactory() { @NotNull @Override @@ -54,6 +55,7 @@ public class ClassBuilderFactories { } }; + @NotNull public static ClassBuilderFactory BINARIES = new ClassBuilderFactory() { @NotNull @Override diff --git a/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/KotlinCodeFragmentFactory.kt b/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/KotlinCodeFragmentFactory.kt index 229fd9a513b..03c4f8ea3fe 100644 --- a/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/KotlinCodeFragmentFactory.kt +++ b/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/KotlinCodeFragmentFactory.kt @@ -46,5 +46,5 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { override fun getFileType() = JetFileType.INSTANCE - override fun getEvaluatorBuilder() = null + override fun getEvaluatorBuilder() = KotlinEvaluationBuilder } diff --git a/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/KotlinEvaluationBuilder.kt b/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/KotlinEvaluationBuilder.kt new file mode 100644 index 00000000000..d0e6bfb36fc --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/debugger/evaluate/KotlinEvaluationBuilder.kt @@ -0,0 +1,154 @@ +/* + * 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 org.jetbrains.jet.plugin.debugger.evaluate.* +import com.intellij.psi.PsiElement +import com.intellij.debugger.SourcePosition +import com.intellij.debugger.engine.evaluation.* +import com.intellij.debugger.engine.evaluation.expression.* +import org.jetbrains.jet.lang.resolve.java.AnalyzerFacadeForJVM +import com.google.common.base.Predicates +import org.jetbrains.jet.lang.resolve.AnalyzingUtils +import org.jetbrains.jet.codegen.state.GenerationState +import org.jetbrains.jet.codegen.ClassBuilderFactories +import java.util.Collections +import com.intellij.psi.PsiFile +import org.jetbrains.jet.codegen.state.Progress +import org.jetbrains.jet.codegen.CompilationErrorHandler +import org.jetbrains.jet.codegen.KotlinCodegenFacade +import com.intellij.openapi.application.ApplicationManager +import com.intellij.testFramework.LightVirtualFile +import org.jetbrains.jet.plugin.JetLanguage +import org.jetbrains.jet.lang.psi.JetFile +import com.intellij.psi.impl.PsiFileFactoryImpl +import com.intellij.psi.PsiFileFactory +import com.intellij.openapi.vfs.CharsetToolkit +import org.jetbrains.org.objectweb.asm.tree.MethodNode +import org.jetbrains.org.objectweb.asm.Opcodes.ASM5 +import org.jetbrains.org.objectweb.asm.* +import com.intellij.openapi.util.Computable +import org.jetbrains.eval4j.* +import org.jetbrains.eval4j.jdi.JDIEval +import org.jetbrains.eval4j.jdi.asJdiValue +import org.jetbrains.eval4j.jdi.makeInitialFrame +import org.jetbrains.jet.lang.resolve.java.PackageClassUtils +import org.jetbrains.jet.lang.resolve.name.FqName +import org.jetbrains.jet.plugin.caches.resolve.KotlinDeclarationsCache +import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache +import org.jetbrains.jet.OutputFileCollection + +object KotlinEvaluationBuilder: EvaluatorBuilder { + override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator { + return ExpressionEvaluatorImpl(KotlinEvaluator(codeFragment)) + } +} + +class KotlinEvaluator(val codeFragment: PsiElement) : Evaluator { + override fun evaluate(context: EvaluationContextImpl): Any? { + return ApplicationManager.getApplication()?.runReadAction(object: Computable { + override fun compute(): Any? { + val fileText = template.replace("!EXPRESSION!", codeFragment.getText()) + val virtualFile = LightVirtualFile("debugFile.kt", JetLanguage.INSTANCE, fileText) + virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET) + val file = (PsiFileFactory.getInstance(codeFragment.getProject()) as PsiFileFactoryImpl).trySetupPsiForFile(virtualFile, JetLanguage.INSTANCE, true, false) as JetFile + + val analyzeExhaust = AnalyzerFacadeForJVM.analyzeFilesWithJavaIntegrationAndCheckForErrors( + file.getProject(), + Collections.singletonList(file), + Predicates.alwaysTrue()) + //val analyzeExhaust = AnalyzerFacadeWithCache.analyzeFileWithCache(file) + //analyzeExhaust.forceResolveAll() + + val bindingContext = analyzeExhaust.getBindingContext() + try { + analyzeExhaust.throwIfError() + AnalyzingUtils.throwExceptionOnErrors(bindingContext) + } + catch (e: IllegalStateException) { + throw EvaluateExceptionUtil.createEvaluateException(e.getMessage()) + } + + val state = GenerationState( + file.getProject(), + ClassBuilderFactories.BINARIES, + bindingContext, + Collections.singletonList(file), + true) + + KotlinCodegenFacade.compileCorrectFiles(state) { + e, msg -> + exception("$msg\n${e?.getMessage() ?: ""}") + } + + // KT-4509 + val outputFiles = (state.getFactory() : OutputFileCollection).asList().filter { it.relativePath != "$packageInternalName.class" } + if (outputFiles.size() != 1) exception("More than one class file found. Note that lambdas, classes and objects are unsupported yet.\n${outputFiles.makeString("\n")}") + + var resultValue: Value? = null + + val virtualMachine = context.getDebugProcess().getVirtualMachineProxy().getVirtualMachine() + + ClassReader(outputFiles.first().asByteArray()).accept(object : ClassVisitor(ASM5) { + override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array?): MethodVisitor? { + if (name == "debugFun") { + return object : MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions) { + override fun visitEnd() { + val value = interpreterLoop( + this, + makeInitialFrame(this, listOf()), + JDIEval(virtualMachine, + context.getClassLoader()!!, + context.getSuspendContext().getThread()?.getThreadReference()!!) + ) + + 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) exception("Cannot evaluate expression") + + return resultValue!!.asJdiValue(virtualMachine, resultValue!!.asmType) + } + }) + } + + override fun getModifier(): Modifier? { + return null + } + + private fun exception(msg: String) = throw EvaluateExceptionUtil.createEvaluateException(msg) +} + +private val template = """ +package packageForDebugger + +fun debugFun() = run { + !EXPRESSION! +} +""" + +private val packageInternalName = PackageClassUtils.getPackageClassFqName(FqName("packageForDebugger")).asString().replace(".", "/") \ No newline at end of file