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)