From 6a04cd39b21bcbc8abeb3e9712b08847e94ab959 Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Mon, 1 Aug 2016 18:34:24 +0300 Subject: [PATCH] Debugger: allow to evaluate kotlin expressions in java files #KT-7549 Fixed If the context is inside PsiJavaFile, get list of all local variables available in current frame (this logic was removed in 29778311e8e151a2a7aaff4c9c47adb54e70f97c 01.12.2015 Drop unnecesary logic about additional context for lambda in debugger). For each variable create KtProperty inside top-level KtFunction in KtFile and set it as contextElement for KtCodeFragment. This file should have all imports from PsiJavaFile. We do not create properties on top-level because they will be highlighted as top-level vals/vars. --- ChangeLog.md | 1 + .../jetbrains/kotlin/psi/KtCodeFragment.kt | 20 ++- .../evaluate/KotlinCodeFragmentFactory.kt | 126 ++++++++++++++++-- .../evaluate/KotlinEvaluationBuilder.kt | 18 ++- .../extractFunctionForDebuggerUtil.kt | 3 +- .../outs/allFilesPresentInJavaContext.out | 0 .../debugger/tinyApp/outs/jcBlock.out | 12 ++ .../debugger/tinyApp/outs/jcImports.out | 10 ++ .../debugger/tinyApp/outs/jcLocalVariable.out | 10 ++ .../debugger/tinyApp/outs/jcMarkedObject.out | 11 ++ .../debugger/tinyApp/outs/jcSimple.out | 9 ++ .../singleBreakpoint/javaContext/jcBlock.kt | 19 +++ .../singleBreakpoint/javaContext/jcImports.kt | 13 ++ .../javaContext/jcLocalVariable.kt | 13 ++ .../javaContext/jcMarkedObject.kt | 18 +++ .../singleBreakpoint/javaContext/jcSimple.kt | 12 ++ .../src/forTests/javaContext/JavaClass.java | 42 ++++++ .../idea/debugger/KotlinDebuggerTestCase.java | 2 +- ...KotlinEvaluateExpressionTestGenerated.java | 39 ++++++ 19 files changed, 352 insertions(+), 26 deletions(-) create mode 100644 idea/testData/debugger/tinyApp/outs/allFilesPresentInJavaContext.out create mode 100644 idea/testData/debugger/tinyApp/outs/jcBlock.out create mode 100644 idea/testData/debugger/tinyApp/outs/jcImports.out create mode 100644 idea/testData/debugger/tinyApp/outs/jcLocalVariable.out create mode 100644 idea/testData/debugger/tinyApp/outs/jcMarkedObject.out create mode 100644 idea/testData/debugger/tinyApp/outs/jcSimple.out create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcBlock.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcImports.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcLocalVariable.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcMarkedObject.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcSimple.kt create mode 100644 idea/testData/debugger/tinyApp/src/forTests/javaContext/JavaClass.java diff --git a/ChangeLog.md b/ChangeLog.md index 2e6d926de01..a40af8f6258 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -245,6 +245,7 @@ Using 'this' as function argument in constructor of non-final class - [`KT-13059`](https://youtrack.jetbrains.com/issue/KT-13059) Fix error stepping on *Step Over* action in the end of while block - [`KT-13037`](https://youtrack.jetbrains.com/issue/KT-13037) Fix possible deadlock in debugger in 2016.1 and exception in 2016.2 - [`KT-12651`](https://youtrack.jetbrains.com/issue/KT-12651) Fix exception in evaluate expression when bad identifier is used for marking object +- [`KT-7549`](https://youtrack.jetbrains.com/issue/KT-13037) Allow to evaluate kotlin expressions in Java files ### JS diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtCodeFragment.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtCodeFragment.kt index 14fc562d0f1..01fb2814aaa 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtCodeFragment.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtCodeFragment.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.psi +import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project import com.intellij.openapi.util.Key import com.intellij.psi.* @@ -25,6 +26,7 @@ import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.tree.IElementType import com.intellij.testFramework.LightVirtualFile import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.utils.addToStdlib.check import java.util.* @@ -41,6 +43,10 @@ abstract class KtCodeFragment( private var viewProvider = super.getViewProvider() as SingleRootFileViewProvider private var imports = LinkedHashSet() + private val fakeContextForJavaFile: PsiElement? by lazy { + this.getCopyableUserData(FAKE_CONTEXT_FOR_JAVA_FILE)?.invoke() + } + init { getViewProvider().forceCachedPsi(this) init(TokenType.CODE_FRAGMENT, elementType) @@ -67,7 +73,15 @@ abstract class KtCodeFragment( override fun isValid() = true - override fun getContext() = context + override fun getContext(): PsiElement? { + if (fakeContextForJavaFile != null) return fakeContextForJavaFile + if (context !is KtElement) { + LOG.warn("CodeFragment with non-kotlin context should have fakeContextForJavaFile set: \noriginalContext = ${context?.getElementTextWithContext()}") + return null + } + + return context + } override fun getResolveScope() = context?.resolveScope ?: super.getResolveScope() @@ -171,6 +185,8 @@ abstract class KtCodeFragment( companion object { val IMPORT_SEPARATOR: String = "," val RUNTIME_TYPE_EVALUATOR: Key> = Key.create("RUNTIME_TYPE_EVALUATOR") - val ADDITIONAL_CONTEXT_FOR_LAMBDA: Key> = Key.create("ADDITIONAL_CONTEXT_FOR_LAMBDA") + val FAKE_CONTEXT_FOR_JAVA_FILE: Key> = Key.create("FAKE_CONTEXT_FOR_JAVA_FILE") + + private val LOG = Logger.getInstance(KtCodeFragment::class.java) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt index 6138385bde4..5320725423e 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt @@ -20,6 +20,7 @@ import com.intellij.debugger.DebuggerManagerEx import com.intellij.debugger.engine.evaluation.CodeFragmentFactory import com.intellij.debugger.engine.evaluation.CodeFragmentKind import com.intellij.debugger.engine.evaluation.TextWithImports +import com.intellij.debugger.engine.events.DebuggerCommandImpl import com.intellij.debugger.impl.DebuggerContextImpl import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl import com.intellij.ide.highlighter.JavaFileType @@ -37,9 +38,7 @@ import com.intellij.util.concurrency.Semaphore import com.intellij.xdebugger.XDebuggerManager import com.intellij.xdebugger.impl.XDebugSessionImpl import com.intellij.xdebugger.impl.ui.tree.ValueMarkup -import com.sun.jdi.ArrayReference -import com.sun.jdi.PrimitiveValue -import com.sun.jdi.Value +import com.sun.jdi.* import org.jetbrains.annotations.TestOnly import org.jetbrains.eval4j.jdi.asValue import org.jetbrains.kotlin.asJava.classes.KtLightClass @@ -51,6 +50,7 @@ import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor import org.jetbrains.kotlin.idea.refactoring.j2kText import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.idea.versions.getKotlinJvmRuntimeMarkerClass import org.jetbrains.kotlin.j2k.AfterConversionPass import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext @@ -117,9 +117,86 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { } }) + if (contextElement != null && contextElement !is KtElement) { + codeFragment.putCopyableUserData(KtCodeFragment.FAKE_CONTEXT_FOR_JAVA_FILE, { + val emptyFile = createFakeFileWithJavaContextElement("", contextElement) + + val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context + val debuggerSession = debuggerContext.debuggerSession + if ((debuggerSession == null || debuggerContext.suspendContext == null) && !ApplicationManager.getApplication().isUnitTestMode) { + LOG.warn("Couldn't create fake context element for java file, debugger isn't paused on breakpoint") + return@putCopyableUserData emptyFile + } + + // TODO: 'this' is unavailable + val visibleVariables = getVisibleLocalVariables(contextElement, debuggerContext) + if (visibleVariables == null) { + LOG.warn("Couldn't get a list of local variables for ${debuggerContext.sourcePosition.file.name}:${debuggerContext.sourcePosition.line}") + return@putCopyableUserData emptyFile + } + + val fakeFunctionText = StringBuilder().apply { + append("fun _java_locals_debug_fun_() {\n") + + val psiNameHelper = PsiNameHelper.getInstance(project) + visibleVariables.forEach { + val variable = it.key + val variableName = variable.name() + if (!psiNameHelper.isIdentifier(variableName)) return@forEach + + val kotlinProperty = createKotlinProperty(project, variableName, variable.type().name(), it.value) ?: return@forEach + append("$kotlinProperty\n") + } + + append("val _debug_context_val = 1\n") + append("}") + }.toString() + + val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement) + val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction + val fakeContext = (fakeFunction?.bodyExpression as? KtBlockExpression)?.statements?.lastOrNull() + + return@putCopyableUserData wrapContextIfNeeded(project, contextElement, fakeContext) ?: emptyFile + }) + } + return codeFragment } + private fun getVisibleLocalVariables(contextElement: PsiElement?, debuggerContext: DebuggerContextImpl): Map? { + val semaphore = Semaphore() + semaphore.down() + + var visibleVariables: Map? = null + + val worker = object : DebuggerCommandImpl() { + override fun action() { + try { + val frame = if (ApplicationManager.getApplication().isUnitTestMode) + contextElement?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.frameProxy?.stackFrame + else + debuggerContext.frameProxy?.stackFrame + + visibleVariables = frame?.let { it.getValues(it.visibleVariables()) } ?: emptyMap() + } + catch(ignored: AbsentInformationException) { + // Debug info unavailable + } + finally { + semaphore.up() + } + } + } + + debuggerContext.debugProcess?.managerThread?.invoke(worker) + + for (i in 0..50) { + if (semaphore.waitFor(20)) break + } + + return visibleVariables + } + private fun initImports(imports: String?): String? { if (imports != null && !imports.isEmpty()) { return imports.split(KtCodeFragment.IMPORT_SEPARATOR) @@ -142,8 +219,11 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { return import } - private fun getWrappedContextElement(project: Project, context: PsiElement?) - = wrapContextIfNeeded(project, context, getContextElement(context)) + private fun getWrappedContextElement(project: Project, context: PsiElement?): PsiElement? { + val newContext = getContextElement(context) + if (newContext !is KtElement) return newContext + return wrapContextIfNeeded(project, context, newContext) + } override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment { val kotlinCodeFragment = createCodeFragment(item, context, project) @@ -190,11 +270,16 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { } override fun isContextAccepted(contextElement: PsiElement?): Boolean { - if (contextElement is PsiCodeBlock) { - // PsiCodeBlock -> DummyHolder -> originalElement - return isContextAccepted(contextElement.context?.context) + return when { + // PsiCodeBlock -> DummyHolder -> originalElement + contextElement is PsiCodeBlock -> isContextAccepted(contextElement.context?.context) + contextElement == null -> false + contextElement.language == KotlinFileType.INSTANCE.language -> true + contextElement.language == JavaFileType.INSTANCE.language -> { + getKotlinJvmRuntimeMarkerClass(contextElement.project, contextElement.resolveScope) != null + } + else -> false } - return contextElement?.language == KotlinFileType.INSTANCE.language } override fun getFileType() = KotlinFileType.INSTANCE @@ -206,7 +291,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { val DEBUG_LABEL_SUFFIX: String = "_DebugLabel" @TestOnly val DEBUG_CONTEXT_FOR_TESTS: Key = Key.create("DEBUG_CONTEXT_FOR_TESTS") - fun getContextElement(elementAt: PsiElement?): KtElement? { + fun getContextElement(elementAt: PsiElement?): PsiElement? { if (elementAt == null) return null if (elementAt is PsiCodeBlock) { @@ -218,6 +303,7 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { } val containingFile = elementAt.containingFile + if (containingFile is PsiJavaFile) return elementAt if (containingFile !is KtFile) return null // elementAt can be PsiWhiteSpace when codeFragment is created from line start offset (in case of first opening EE window) @@ -300,11 +386,27 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { return createWrappingContext(text, labels, newContext, project) } + private fun createFakeFileWithJavaContextElement(funWithLocalVariables: String, javaContext: PsiElement): KtFile { + val javaFile = javaContext.containingFile as? PsiJavaFile + + val sb = StringBuilder() + + javaFile?.packageName?.check { !it.isBlank() }?.let { + sb.append("package ").append(it.quoteIfNeeded()).append("\n") + } + + javaFile?.importList?.let { sb.append(it.text).append("\n") } + + sb.append(funWithLocalVariables) + + return KtPsiFactory(javaContext.project).createAnalyzableFile("fakeFileForJavaContextInDebugger.kt", sb.toString(), javaContext) + } + // internal for test fun createWrappingContext( newFragmentText: String, labels: Map, - originalContext: PsiElement?, + originalContext: KtElement?, project: Project ): KtElement? { val codeFragment = KtPsiFactory(project).createBlockCodeFragment(newFragmentText, originalContext) @@ -318,6 +420,6 @@ class KotlinCodeFragmentFactory: CodeFragmentFactory() { } }) - return getContextElement(codeFragment.findElementAt(codeFragment.text.length - 1)) + return getContextElement(codeFragment.findElementAt(codeFragment.text.length - 1)) as? KtElement } } 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 60fd76be368..ceb6ab42839 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt @@ -92,20 +92,18 @@ object KotlinEvaluationBuilder: EvaluatorBuilder { return EvaluatorBuilderImpl.getInstance()!!.build(codeFragment, position) } - val file = position.file - if (file !is KtFile) { - throw EvaluateExceptionUtil.createEvaluateException("Couldn't evaluate kotlin expression in non-kotlin context") - } - if (position.line < 0) { throw EvaluateExceptionUtil.createEvaluateException("Couldn't evaluate kotlin expression at $position") } - val document = PsiDocumentManager.getInstance(file.project).getDocument(file) - if (document == null || document.lineCount < position.line) { - throw EvaluateExceptionUtil.createEvaluateException( - "Couldn't evaluate kotlin expression: breakpoint is placed outside the file. " + - "It may happen when you've changed source file after starting a debug process.") + val file = position.file + if (file is KtFile) { + val document = PsiDocumentManager.getInstance(file.project).getDocument(file) + if (document == null || document.lineCount < position.line) { + throw EvaluateExceptionUtil.createEvaluateException( + "Couldn't evaluate kotlin expression: breakpoint is placed outside the file. " + + "It may happen when you've changed source file after starting a debug process.") + } } if (codeFragment.context !is KtElement) { diff --git a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt index 25f0cea5ae4..bd91d3ec9ea 100644 --- a/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt @@ -82,7 +82,7 @@ fun getFunctionForExtractedFragment( } fun generateFunction(): ExtractionResult? { - val originalFile = breakpointFile as KtFile + val originalFile = codeFragment.getContextContainingFile() ?: return null val newDebugExpressions = addDebugExpressionIntoTmpFileForExtractFunction(originalFile, codeFragment, breakpointLine) if (newDebugExpressions.isEmpty()) return null @@ -124,6 +124,7 @@ fun addDebugExpressionIntoTmpFileForExtractFunction(originalFile: KtFile, codeFr val tmpFile = originalFile.copy() as KtFile tmpFile.suppressDiagnosticsInDebugMode = true + tmpFile.analysisContext = originalFile.analysisContext val contextElement = getExpressionToAddDebugExpressionBefore(tmpFile, codeFragment.getOriginalContext(), line) ?: return emptyList() diff --git a/idea/testData/debugger/tinyApp/outs/allFilesPresentInJavaContext.out b/idea/testData/debugger/tinyApp/outs/allFilesPresentInJavaContext.out new file mode 100644 index 00000000000..e69de29bb2d diff --git a/idea/testData/debugger/tinyApp/outs/jcBlock.out b/idea/testData/debugger/tinyApp/outs/jcBlock.out new file mode 100644 index 00000000000..7e6d74f1155 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/jcBlock.out @@ -0,0 +1,12 @@ +LineBreakpoint created at jcBlock.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 !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! jcBlock.JcBlockKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +jcBlock.kt:6 +JavaClass.java:16 +JavaClass.java:18 +JavaClass.java:19 +Compile bytecode for bodyVal +Compile bytecode for thenVal +Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/outs/jcImports.out b/idea/testData/debugger/tinyApp/outs/jcImports.out new file mode 100644 index 00000000000..6a328de018c --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/jcImports.out @@ -0,0 +1,10 @@ +LineBreakpoint created at jcImports.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 !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! jcImports.JcImportsKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +jcImports.kt:6 +JavaClass.java:27 +JavaClass.java:28 +Compile bytecode for list.filter { it == 1 }.size +Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/outs/jcLocalVariable.out b/idea/testData/debugger/tinyApp/outs/jcLocalVariable.out new file mode 100644 index 00000000000..279e6f8db51 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/jcLocalVariable.out @@ -0,0 +1,10 @@ +LineBreakpoint created at jcLocalVariable.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 !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! jcLocalVariable.JcLocalVariableKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +jcLocalVariable.kt:6 +JavaClass.java:11 +JavaClass.java:12 +Compile bytecode for i +Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/outs/jcMarkedObject.out b/idea/testData/debugger/tinyApp/outs/jcMarkedObject.out new file mode 100644 index 00000000000..2cd9d3e8d34 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/jcMarkedObject.out @@ -0,0 +1,11 @@ +LineBreakpoint created at jcMarkedObject.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 !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! jcMarkedObject.JcMarkedObjectKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +jcMarkedObject.kt:6 +JavaClass.java:39 +JavaClass.java:40 +Compile bytecode for i +Compile bytecode for i_DebugLabel +Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/outs/jcSimple.out b/idea/testData/debugger/tinyApp/outs/jcSimple.out new file mode 100644 index 00000000000..7ce1e0a7453 --- /dev/null +++ b/idea/testData/debugger/tinyApp/outs/jcSimple.out @@ -0,0 +1,9 @@ +LineBreakpoint created at jcSimple.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 !OUTPUT_PATH!;!KOTLIN_RUNTIME!;!CUSTOM_LIBRARY!;!RT_JAR! jcSimple.JcSimpleKt +Connected to the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' +jcSimple.kt:6 +JavaClass.java:7 +Compile bytecode for 1 + 1 +Disconnected from the target VM, address: '!HOST_NAME!:PORT_NAME!', transport: 'socket' + +Process finished with exit code 0 diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcBlock.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcBlock.kt new file mode 100644 index 00000000000..7419cf1a626 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcBlock.kt @@ -0,0 +1,19 @@ +package jcBlock + +fun main(args: Array) { + val javaClass = forTests.javaContext.JavaClass() + //Breakpoint! + javaClass.block() +} + +// STEP_INTO: 1 +// STEP_OVER: 2 + +// EXPRESSION: bodyVal +// RESULT: 1: I + +// EXPRESSION: thenVal +// RESULT: 1: I + +// EXPRESSION: elseVal +// RESULT: Unresolved reference: elseVal \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcImports.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcImports.kt new file mode 100644 index 00000000000..926cdfdb2b3 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcImports.kt @@ -0,0 +1,13 @@ +package jcImports + +fun main(args: Array) { + val javaClass = forTests.javaContext.JavaClass() + //Breakpoint! + javaClass.imports() +} + +// STEP_INTO: 1 +// STEP_OVER: 1 + +// EXPRESSION: list.filter { it == 1 }.size +// RESULT: 1: I \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcLocalVariable.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcLocalVariable.kt new file mode 100644 index 00000000000..40281433261 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcLocalVariable.kt @@ -0,0 +1,13 @@ +package jcLocalVariable + +fun main(args: Array) { + val javaClass = forTests.javaContext.JavaClass() + //Breakpoint! + javaClass.localVariable() +} + +// STEP_INTO: 1 +// STEP_OVER: 1 + +// EXPRESSION: i +// RESULT: 1: I \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcMarkedObject.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcMarkedObject.kt new file mode 100644 index 00000000000..f8d9aeb7228 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcMarkedObject.kt @@ -0,0 +1,18 @@ +package jcMarkedObject + +fun main(args: Array) { + val javaClass = forTests.javaContext.JavaClass() + //Breakpoint! + javaClass.markObject() +} + +// STEP_INTO: 1 +// STEP_OVER: 1 + +// EXPRESSION: i +// RESULT: instance of java.lang.Integer(id=ID): Ljava/lang/Integer; + +// EXPRESSION: i_DebugLabel +// RESULT: instance of java.lang.Integer(id=ID): Ljava/lang/Integer; + +// DEBUG_LABEL: i = i \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcSimple.kt b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcSimple.kt new file mode 100644 index 00000000000..9ada270154d --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcSimple.kt @@ -0,0 +1,12 @@ +package jcSimple + +fun main(args: Array) { + val javaClass = forTests.javaContext.JavaClass() + //Breakpoint! + javaClass.simple() +} + +// STEP_INTO: 1 + +// EXPRESSION: 1 + 1 +// RESULT: 2: I \ No newline at end of file diff --git a/idea/testData/debugger/tinyApp/src/forTests/javaContext/JavaClass.java b/idea/testData/debugger/tinyApp/src/forTests/javaContext/JavaClass.java new file mode 100644 index 00000000000..110a98dcfe9 --- /dev/null +++ b/idea/testData/debugger/tinyApp/src/forTests/javaContext/JavaClass.java @@ -0,0 +1,42 @@ +package forTests.javaContext; + +import java.util.ArrayList; + +public class JavaClass { + public void simple() { + int breakpoint = 1; + } + + public void localVariable() { + int i = 1; + int breakpoint = 1; + } + + public void block() { + int bodyVal = 1; + if (true) { + int thenVal = 1; + int breakpoint = 1; + } + else { + int elseVal = 1; + } + } + + public void imports() { + ArrayList list = createList(); + int breakpoint = 1; + } + + private ArrayList createList() { + ArrayList list = new ArrayList(); + list.add(1); + list.add(2); + return list; + } + + public void markObject() { + Integer i = 1; + int breakpoint = 1; + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestCase.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestCase.java index 91ecc34ed0b..ad48dad9a18 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestCase.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/KotlinDebuggerTestCase.java @@ -137,7 +137,7 @@ public abstract class KotlinDebuggerTestCase extends DescriptorTestCase { MockLibraryUtil.compileKotlin(sourcesDir, outDir, CUSTOM_LIBRARY_JAR.getPath()); - List options = Arrays.asList("-d", outputDirPath, "-classpath", ForTestCompileRuntime.runtimeJarForTests().getPath()); + List options = Arrays.asList("-d", outputDirPath, "-classpath", ForTestCompileRuntime.runtimeJarForTests().getPath(), "-g"); try { KotlinTestUtils.compileJavaFiles(findJavaFiles(new File(sourcesDir)), options); } diff --git a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java index e6058533490..5c896f74be8 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluateExpressionTestGenerated.java @@ -697,6 +697,45 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat } } + @TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class JavaContext extends AbstractKotlinEvaluateExpressionTest { + public void testAllFilesPresentInJavaContext() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("jcBlock.kt") + public void testJcBlock() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcBlock.kt"); + doSingleBreakpointTest(fileName); + } + + @TestMetadata("jcImports.kt") + public void testJcImports() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcImports.kt"); + doSingleBreakpointTest(fileName); + } + + @TestMetadata("jcLocalVariable.kt") + public void testJcLocalVariable() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcLocalVariable.kt"); + doSingleBreakpointTest(fileName); + } + + @TestMetadata("jcMarkedObject.kt") + public void testJcMarkedObject() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcMarkedObject.kt"); + doSingleBreakpointTest(fileName); + } + + @TestMetadata("jcSimple.kt") + public void testJcSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/javaContext/jcSimple.kt"); + doSingleBreakpointTest(fileName); + } + } + @TestMetadata("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/labels") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)