From 0657a3d3994fffe532c4cd44fb07c6489ca95062 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Wed, 30 Jan 2019 02:35:10 +0300 Subject: [PATCH] New evaluator that doesn't depend on the 'extract function' refactoring (KT-28192, KT-25220, KT-25222, KT-21650) --- .../kotlin/codegen/CodeFragmentCodegen.kt | 279 +++++++ .../kotlin/codegen/ExpressionCodegen.java | 51 +- .../kotlin/codegen/JvmCodegenUtil.java | 17 +- .../kotlin/codegen/PackageCodegenImpl.java | 49 +- .../binding/CodegenAnnotatingVisitor.java | 10 +- .../codegen/binding/CodegenBinding.java | 11 + .../kotlin/codegen/classFileUtils.kt | 2 +- .../kotlin/codegen/context/ScriptContext.kt | 7 +- .../codegen/context/ScriptLikeContext.kt | 25 + .../kotlin/codegen/state/GenerationState.kt | 6 +- .../asJava/builder/LightClassDataProvider.kt | 8 +- .../KtLightClassForSourceDeclaration.kt | 6 + .../jetbrains/kotlin/psi/KtCodeFragment.kt | 2 + .../jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt | 3 + .../kotlin/generators/tests/GenerateTests.kt | 4 - .../generators/tests/GenerateTests.kt.as32 | 9 +- .../generators/tests/GenerateTests.kt.as33 | 9 +- .../generators/tests/GenerateTests.kt.as34 | 4 - .../caches/resolve/CodeFragmentAnalyzer.kt | 193 +++-- .../caches/resolve/PerFileAnalysisCache.kt | 19 +- .../idea/project/ResolveElementCache.kt | 4 +- ...KotlinCoroutinesAsyncStackTraceProvider.kt | 2 +- .../kotlin/idea/debugger/debuggerUtil.kt | 11 - .../debugger/evaluate/ExecutionContext.kt | 49 ++ .../evaluate/KotlinCodeFragmentFactory.kt | 99 +-- .../debugger/evaluate/KotlinDebuggerCaches.kt | 67 +- .../evaluate/KotlinEvaluationBuilder.kt | 789 +++++------------- .../evaluate/ScopeCheckerForEvaluator.kt | 72 -- .../classLoading/ClassLoadingAdapter.kt | 7 +- .../evaluate/classLoading/ClassToLoad.kt | 3 +- .../compilation/CodeFragmentCompiler.kt | 277 ++++++ .../CodeFragmentParameterAnalyzer.kt | 349 ++++++++ .../compilation/CompiledDataDescriptor.kt | 48 ++ .../DebugLabelPropertyDescriptorProvider.kt | 162 ++++ .../extractFunctionForDebuggerUtil.kt | 374 --------- .../variables/EvaluatorValueConverter.kt | 266 ++++++ .../{ => variables}/VariableFinder.kt | 319 ++++--- .../kotlin/idea/debugger/safeUtil.kt | 10 + .../idea/internal/KotlinBytecodeToolWindow.kt | 3 + .../scratch/compile/KtCompilingExecutor.kt | 6 +- .../privateFunArgumentsResolve.kt.fragment | 1 + .../privateFunTypeArguments.kt.fragment | 1 + .../codeFragments/privateMember.kt.fragment | 3 +- .../codeFragments/protectedMember.kt.fragment | 3 +- .../secondaryConstructor.kt.fragment | 2 +- .../multipleBreakpoints/clearCache.out | 6 + .../multipleBreakpoints/exceptions.kt | 8 +- .../multipleBreakpoints/exceptions.out | 3 + .../extensionMemberFunction.out | 2 + .../extensionMemberProperty.out | 2 + .../multipleBreakpoints/fieldVariable.kt | 2 +- .../funFromOuterClassInLamdba.kt | 2 +- .../multipleBreakpoints/smartcasts.kt | 2 +- .../multipleBreakpoints/thisLabels.kt | 4 +- .../evaluate/singleBreakpoint/callableBug.kt | 4 +- .../evaluate/singleBreakpoint/callableBug.out | 3 +- .../compilingEvaluator/ceObject.kt | 2 +- .../src/evaluate/singleBreakpoint/errors.kt | 2 +- .../singleBreakpoint/errors.kt.fragment | 2 +- .../src/evaluate/singleBreakpoint/errors.out | 2 +- .../frame/suspendContinuation.out | 23 +- .../src/evaluate/singleBreakpoint/kt25220.kt | 6 + .../singleBreakpoint/kt25220.kt.fragment | 9 + .../src/evaluate/singleBreakpoint/kt25220.out | 16 + .../src/evaluate/singleBreakpoint/kt25222.kt | 20 + .../src/evaluate/singleBreakpoint/kt25222.out | 8 + .../singleBreakpoint/nestedInlineArguments.kt | 16 +- ...rivatePropertyWithExplicitDefaultGetter.kt | 2 +- ...KotlinEvaluateExpressionTestGenerated.java | 10 + 69 files changed, 2300 insertions(+), 1497 deletions(-) create mode 100644 compiler/backend/src/org/jetbrains/kotlin/codegen/CodeFragmentCodegen.kt create mode 100644 compiler/backend/src/org/jetbrains/kotlin/codegen/context/ScriptLikeContext.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/ExecutionContext.kt delete mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/ScopeCheckerForEvaluator.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentCompiler.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CodeFragmentParameterAnalyzer.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/CompiledDataDescriptor.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/compilation/DebugLabelPropertyDescriptorProvider.kt delete mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/extractFunctionForDebuggerUtil.kt create mode 100644 idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/variables/EvaluatorValueConverter.kt rename idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/{ => variables}/VariableFinder.kt (54%) create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt25220.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt25220.kt.fragment create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt25220.out create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt25222.kt create mode 100644 idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt25222.out diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/CodeFragmentCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/CodeFragmentCodegen.kt new file mode 100644 index 00000000000..746274458fb --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/CodeFragmentCodegen.kt @@ -0,0 +1,279 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.codegen + +import com.intellij.openapi.util.Key +import org.jetbrains.kotlin.codegen.CalculatedCodeFragmentCodegenInfo.CalculatedParameter +import org.jetbrains.kotlin.codegen.CodeFragmentCodegenInfo.IParameter +import org.jetbrains.kotlin.codegen.binding.MutableClosure +import org.jetbrains.kotlin.codegen.context.* +import org.jetbrains.kotlin.codegen.context.LocalLookup.* +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin.Companion.NO_ORIGIN +import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.org.objectweb.asm.Opcodes.* +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter +import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension.Context as InCo + +class CodeFragmentCodegenInfo( + val classDescriptor: ClassDescriptor, + val methodDescriptor: FunctionDescriptor, + val parameters: List +) { + val classType: Type = Type.getObjectType(classDescriptor.name.asString()) + + interface IParameter { + val targetDescriptor: DeclarationDescriptor + val targetType: KotlinType + } +} + +class CodeFragmentCodegen private constructor( + private val codeFragment: KtCodeFragment, + private val info: CodeFragmentCodegenInfo, + private val calculatedInfo: CalculatedCodeFragmentCodegenInfo, + private val classContext: CodeFragmentContext, + state: GenerationState, + builder: ClassBuilder +) : MemberCodegen(state, null, classContext, codeFragment, builder) { + private val methodDescriptor = info.methodDescriptor + + override fun generateDeclaration() { + v.defineClass( + codeFragment, + state.classFileVersion, + ACC_PUBLIC or ACC_SUPER, + info.classType.internalName, + null, + "java/lang/Object", + emptyArray() + ) + } + + override fun generateBody() { + genConstructor() + genMethod(classContext.intoFunction(methodDescriptor)) + } + + override fun generateKotlinMetadataAnnotation() { + writeSyntheticClassMetadata(v, state) + } + + private fun genConstructor() { + val mv = v.newMethod(NO_ORIGIN, ACC_PUBLIC, "", "()V", null, null) + + if (state.classBuilderMode.generateBodies) { + mv.visitCode() + + val iv = InstructionAdapter(mv) + iv.load(0, info.classType) + iv.invokespecial("java/lang/Object", "", "()V", false) + iv.areturn(Type.VOID_TYPE) + } + + mv.visitMaxs(-1, -1) + mv.visitEnd() + } + + private fun genMethod(methodContext: MethodContext) { + val returnType = calculatedInfo.returnAsmType + val parameters = calculatedInfo.parameters + + val methodDesc = Type.getMethodDescriptor(returnType, *parameters.map { it.asmType }.toTypedArray()) + + val mv = v.newMethod( + OtherOrigin(codeFragment, methodContext.functionDescriptor), + ACC_PUBLIC or ACC_STATIC, + methodDescriptor.name.asString(), methodDesc, + null, null + ) + + if (state.classBuilderMode.generateBodies) { + mv.visitCode() + + val frameMap = FrameMap() + parameters.forEach { frameMap.enter(it.targetDescriptor, it.asmType) } + + val codegen = object : ExpressionCodegen(mv, frameMap, returnType, methodContext, state, this) { + override fun findCapturedValue(descriptor: DeclarationDescriptor): StackValue? { + val parameter = calculatedInfo.findParameter(descriptor) ?: return super.findCapturedValue(descriptor) + return parameter.stackValue + } + + override fun generateThisOrOuter(calleeContainingClass: ClassDescriptor, isSuper: Boolean): StackValue { + findCapturedValue(calleeContainingClass)?.let { return it } + return super.generateThisOrOuter(calleeContainingClass, isSuper) + } + + override fun generateExtensionReceiver(descriptor: CallableDescriptor): StackValue { + val receiverParameter = descriptor.extensionReceiverParameter + if (receiverParameter != null) { + findCapturedValue(receiverParameter)?.let { return it } + } + + return super.generateExtensionReceiver(descriptor) + } + + override fun generateNonIntrinsicSimpleNameExpression( + expression: KtSimpleNameExpression, receiver: StackValue, + descriptor: DeclarationDescriptor, resolvedCall: ResolvedCall<*>?, isSyntheticField: Boolean + ): StackValue { + val resultingDescriptor = resolvedCall?.resultingDescriptor + if (resultingDescriptor != null) { + findCapturedValue(resultingDescriptor)?.let { return it } + } + return super.generateNonIntrinsicSimpleNameExpression(expression, receiver, descriptor, resolvedCall, isSyntheticField) + } + + override fun visitThisExpression(expression: KtThisExpression, receiver: StackValue?): StackValue { + val instanceReference = expression.instanceReference + val target = bindingContext[BindingContext.REFERENCE_TARGET, instanceReference] + if (target != null) { + findCapturedValue(target)?.let { return it } + } + + return super.visitThisExpression(expression, receiver) + } + } + + codegen.gen(codeFragment.getContentElement(), returnType) + codegen.v.areturn(returnType) + + parameters.asReversed().forEach { frameMap.leave(it.targetDescriptor) } + } + + mv.visitMaxs(-1, -1) + mv.visitEnd() + } + + companion object { + private val INFO_USERDATA_KEY = Key.create("CODE_FRAGMENT_CODEGEN_INFO") + + fun setCodeFragmentInfo(codeFragment: KtCodeFragment, info: CodeFragmentCodegenInfo) { + codeFragment.putUserData(INFO_USERDATA_KEY, info) + } + + @JvmStatic + fun getCodeFragmentInfo(codeFragment: KtCodeFragment): CodeFragmentCodegenInfo { + return codeFragment.getUserData(INFO_USERDATA_KEY) ?: error("Codegen info user data is not set") + } + + @JvmStatic + fun createCodegen( + declaration: KtCodeFragment, + state: GenerationState, + parentContext: CodegenContext<*> + ): CodeFragmentCodegen { + val info = getCodeFragmentInfo(declaration) + val classDescriptor = info.classDescriptor + val builder = state.factory.newVisitor(OtherOrigin(declaration, classDescriptor), info.classType, declaration.containingFile) + val calculatedInfo = calculateInfo(info, state.typeMapper) + val classContext = CodeFragmentContext(state.typeMapper, classDescriptor, parentContext, calculatedInfo) + return CodeFragmentCodegen(declaration, info, calculatedInfo, classContext, state, builder) + } + + private fun calculateInfo(info: CodeFragmentCodegenInfo, typeMapper: KotlinTypeMapper): CalculatedCodeFragmentCodegenInfo { + val methodSignature = typeMapper.mapSignatureSkipGeneric(info.methodDescriptor) + require(info.parameters.size == methodSignature.valueParameters.size) + require(info.parameters.size == info.methodDescriptor.valueParameters.size) + + var stackIndex = 0 + val parameters = mutableListOf() + + for (parameterIndex in 0 until info.parameters.size) { + val parameter = info.parameters[parameterIndex] + val asmParameter = methodSignature.valueParameters[parameterIndex] + val parameterDescriptor = info.methodDescriptor.valueParameters[parameterIndex] + + val asmType: Type + val stackValue: StackValue + + val sharedAsmType = getSharedTypeIfApplicable(parameter.targetDescriptor, typeMapper) + if (sharedAsmType != null) { + asmType = sharedAsmType + val unwrappedType = typeMapper.mapType(parameter.targetType) + stackValue = StackValue.shared(stackIndex, unwrappedType) + } else { + asmType = asmParameter.asmType + stackValue = StackValue.local(stackIndex, asmType) + } + + val calculatedParameter = CalculatedParameter(parameter, asmType, stackValue, parameterDescriptor) + parameters += calculatedParameter + + stackIndex += if (asmType == Type.DOUBLE_TYPE || asmType == Type.LONG_TYPE) 2 else 1 + } + + return CalculatedCodeFragmentCodegenInfo(parameters, methodSignature.returnType) + } + + fun getSharedTypeIfApplicable(descriptor: DeclarationDescriptor, typeMapper: KotlinTypeMapper): Type? { + return when (descriptor) { + is LocalVariableDescriptor -> typeMapper.getSharedVarType(descriptor) + else -> null + } + } + } +} + +private class CalculatedCodeFragmentCodegenInfo(val parameters: List, val returnAsmType: Type) { + class CalculatedParameter( + parameter: IParameter, + val asmType: Type, + val stackValue: StackValue, + val parameterDescriptor: ValueParameterDescriptor + ) : IParameter by parameter + + fun findParameter(target: DeclarationDescriptor): CalculatedParameter? { + for (parameter in parameters) { + if (parameter.targetDescriptor == target || parameter.parameterDescriptor == target) { + return parameter + } + } + + return null + } +} + +private class CodeFragmentContext( + typeMapper: KotlinTypeMapper, + contextDescriptor: ClassDescriptor, + parentContext: CodegenContext<*>?, + private val calculatedInfo: CalculatedCodeFragmentCodegenInfo +) : ScriptLikeContext(typeMapper, contextDescriptor, parentContext) { + private val localLookup = object : LocalLookup { + override fun isLocal(descriptor: DeclarationDescriptor?): Boolean { + return calculatedInfo.parameters.any { descriptor == it.targetDescriptor || descriptor == it.parameterDescriptor } + } + } + + override fun getOuterReceiverExpression(prefix: StackValue?, thisOrOuterClass: ClassDescriptor): StackValue { + val parameter = calculatedInfo.findParameter(thisOrOuterClass) + ?: throw IllegalStateException("Can not generate outer receiver value for $thisOrOuterClass") + + return parameter.stackValue + } + + override fun captureVariable(closure: MutableClosure, target: DeclarationDescriptor): StackValue? { + val parameter = calculatedInfo.findParameter(target) ?: return null + val parameterDescriptor = parameter.parameterDescriptor + + // Value is already captured + closure.captureVariables[parameterDescriptor]?.let { return it.innerValue } + + // Capture new value + val closureAsmType = typeMapper.mapType(closure.closureClass) + return LocalLookupCase.VAR.innerValue(parameterDescriptor, localLookup, state, closure, closureAsmType) + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index 6d2110413c4..d46dfc729ac 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.backend.common.CodegenUtil; import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.codegen.binding.CalculatedClosure; import org.jetbrains.kotlin.codegen.binding.CodegenBinding; +import org.jetbrains.kotlin.codegen.binding.MutableClosure; import org.jetbrains.kotlin.codegen.context.*; import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenForLambda; import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt; @@ -1759,6 +1760,16 @@ public class ExpressionCodegen extends KtVisitor impleme StackValue intrinsicResult = applyIntrinsic(descriptor, IntrinsicPropertyGetter.class, resolvedCall, receiver); if (intrinsicResult != null) return intrinsicResult; + return generateNonIntrinsicSimpleNameExpression(expression, receiver, descriptor, resolvedCall, isSyntheticField); + } + + public StackValue generateNonIntrinsicSimpleNameExpression( + @NotNull KtSimpleNameExpression expression, + @NotNull StackValue receiver, + @NotNull DeclarationDescriptor descriptor, + @Nullable ResolvedCall resolvedCall, + boolean isSyntheticField + ) { if (descriptor instanceof PropertyDescriptor) { PropertyDescriptor propertyDescriptor = (PropertyDescriptor) descriptor; @@ -2132,6 +2143,14 @@ public class ExpressionCodegen extends KtVisitor impleme } else { skipPropertyAccessors = forceField; + + if (JvmCodegenUtil.isDebuggerContext(context) + && Visibilities.isPrivate(propertyDescriptor.getVisibility()) + && bindingContext.get(BACKING_FIELD_REQUIRED, propertyDescriptor) == Boolean.TRUE + ) { + skipPropertyAccessors = true; + } + ownerDescriptor = isBackingFieldMovedFromCompanion ? containingDeclaration : propertyDescriptor; } @@ -2772,7 +2791,7 @@ public class ExpressionCodegen extends KtVisitor impleme } @NotNull - private StackValue generateExtensionReceiver(@NotNull CallableDescriptor descriptor) { + public StackValue generateExtensionReceiver(@NotNull CallableDescriptor descriptor) { ReceiverParameterDescriptor parameter = descriptor.getExtensionReceiverParameter(); if (myFrameMap.getIndex(parameter) != -1) { KotlinType type = parameter.getReturnType(); @@ -2891,24 +2910,40 @@ public class ExpressionCodegen extends KtVisitor impleme forceOuter = false; + CodegenContext enclosingContext; + //for constructor super call we should access to outer instance through parameter in locals, in other cases through field for captured outer if (inStartConstructorContext) { result = cur.getOuterExpression(result, false); cur = getNotNullParentContextForMethod(cur); + enclosingContext = cur.getEnclosingClassContext(); inStartConstructorContext = false; } else { cur = getNotNullParentContextForMethod(cur); - // for now the script codegen only passes this branch, since the method context for script constructor is defined using function context - if (cur instanceof ScriptContext && !(thisOrOuterClass instanceof ScriptDescriptor)) { - return ((ScriptContext) cur).getOuterReceiverExpression(result, thisOrOuterClass); - } - else { - result = cur.getOuterExpression(result, false); + enclosingContext = cur.getEnclosingClassContext(); + + if (!(thisOrOuterClass instanceof ScriptDescriptor)) { + if (cur instanceof ScriptLikeContext) { + /* for now the script codegen only passes this branch, + since the method context for script constructor is defined using function context */ + + return ((ScriptLikeContext) cur).getOuterReceiverExpression(result, thisOrOuterClass); + } else if (enclosingContext instanceof ScriptLikeContext) { + MutableClosure closure = cur.closure; + if (closure != null) { + StackValue captured = ((ScriptLikeContext) enclosingContext).captureVariable(closure, thisOrOuterClass); + if (captured != null) { + return captured; + } + } + } } + + result = cur.getOuterExpression(result, false); } - cur = cur.getEnclosingClassContext(); + cur = enclosingContext; } throw new UnsupportedOperationException(); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java index 542c9876ace..bab3e580d03 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java @@ -44,6 +44,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; import org.jetbrains.kotlin.resolve.inline.InlineUtil; import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue; import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver; +import org.jetbrains.kotlin.resolve.source.PsiSourceElement; import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor; import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.util.OperatorNameConventions; @@ -242,8 +243,20 @@ public class JvmCodegenUtil { } public static boolean isDebuggerContext(@NotNull CodegenContext context) { - KtFile file = DescriptorToSourceUtils.getContainingFile(context.getContextDescriptor()); - return file != null && CodeFragmentUtilKt.getSuppressDiagnosticsInDebugMode(file); + PsiFile file = null; + + DeclarationDescriptor contextDescriptor = context.getContextDescriptor(); + if (contextDescriptor instanceof DeclarationDescriptorWithSource) { + SourceElement sourceElement = ((DeclarationDescriptorWithSource) contextDescriptor).getSource(); + if (sourceElement instanceof PsiSourceElement) { + PsiElement psi = ((PsiSourceElement) sourceElement).getPsi(); + if (psi != null) { + file = psi.getContainingFile(); + } + } + } + + return file instanceof KtFile && CodeFragmentUtilKt.getSuppressDiagnosticsInDebugMode((KtFile) file); } @Nullable diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/PackageCodegenImpl.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/PackageCodegenImpl.java index 805be14c9c7..3e543b3a613 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/PackageCodegenImpl.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/PackageCodegenImpl.java @@ -32,10 +32,7 @@ import org.jetbrains.kotlin.fileClasses.JvmFileClassInfo; import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus; -import org.jetbrains.kotlin.psi.KtClassOrObject; -import org.jetbrains.kotlin.psi.KtDeclaration; -import org.jetbrains.kotlin.psi.KtFile; -import org.jetbrains.kotlin.psi.KtScript; +import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker; @@ -98,30 +95,40 @@ public class PackageCodegenImpl implements PackageCodegen { Type fileClassType = AsmUtil.asmTypeByFqNameWithoutInnerClasses(fileClassInfo.getFileClassFqName()); PackageContext packagePartContext = state.getRootContext().intoPackagePart(packageFragment, fileClassType, file); - List classOrObjects = new ArrayList<>(); + if (file instanceof KtCodeFragment) { + // Avoid generating light classes for code fragments + if (state.getClassBuilderMode().generateBodies + && state.getGenerateDeclaredClassFilter().shouldGenerateCodeFragment((KtCodeFragment) file) + ) { + CodeFragmentCodegen.createCodegen((KtCodeFragment) file, state, packagePartContext).generate(); + } + } else { + List classOrObjects = new ArrayList<>(); - for (KtDeclaration declaration : file.getDeclarations()) { - if (declaration instanceof KtClassOrObject) { - ClassDescriptor descriptor = state.getBindingContext().get(BindingContext.CLASS, declaration); - if (PsiUtilsKt.hasExpectModifier(declaration) && - (descriptor == null || !ExpectedActualDeclarationChecker.shouldGenerateExpectClass(descriptor))) { - continue; + for (KtDeclaration declaration : file.getDeclarations()) { + if (declaration instanceof KtClassOrObject) { + ClassDescriptor descriptor = state.getBindingContext().get(BindingContext.CLASS, declaration); + if (PsiUtilsKt.hasExpectModifier(declaration) && + (descriptor == null || !ExpectedActualDeclarationChecker.shouldGenerateExpectClass(descriptor))) { + continue; + } + + KtClassOrObject classOrObject = (KtClassOrObject) declaration; + if (state.getGenerateDeclaredClassFilter().shouldGenerateClass(classOrObject)) { + classOrObjects.add(classOrObject); + } } + else if (declaration instanceof KtScript) { + KtScript script = (KtScript) declaration; - KtClassOrObject classOrObject = (KtClassOrObject) declaration; - if (state.getGenerateDeclaredClassFilter().shouldGenerateClass(classOrObject)) { - classOrObjects.add(classOrObject); + if (state.getGenerateDeclaredClassFilter().shouldGenerateScript(script)) { + ScriptCodegen.createScriptCodegen(script, state, packagePartContext).generate(); + } } } - else if (declaration instanceof KtScript) { - KtScript script = (KtScript) declaration; - if (state.getGenerateDeclaredClassFilter().shouldGenerateScript(script)) { - ScriptCodegen.createScriptCodegen(script, state, packagePartContext).generate(); - } - } + generateClassesAndObjectsInFile(classOrObjects, packagePartContext); } - generateClassesAndObjectsInFile(classOrObjects, packagePartContext); if (!state.getGenerateDeclaredClassFilter().shouldGeneratePackagePart(file)) return; diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java index 9adfe8cb09c..40775ef0062 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java @@ -222,7 +222,15 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid { @Override public void visitKtFile(@NotNull KtFile file) { - nameStack.push(AsmUtil.internalNameByFqNameWithoutInnerClasses(file.getPackageFqName())); + String name; + if (file instanceof KtCodeFragment) { + CodeFragmentCodegenInfo info = CodeFragmentCodegen.getCodeFragmentInfo((KtCodeFragment) file); + name = info.getClassDescriptor().getName().asString() + "$" + info.getMethodDescriptor().getName().asString(); + } else { + name = AsmUtil.internalNameByFqNameWithoutInnerClasses(file.getPackageFqName()); + } + + nameStack.push(name); visitKtElement(file); nameStack.pop(); } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenBinding.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenBinding.java index 83bdca4865d..ebd34806532 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenBinding.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenBinding.java @@ -6,6 +6,8 @@ package org.jetbrains.kotlin.codegen.binding; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; import kotlin.collections.CollectionsKt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -90,6 +92,15 @@ public class CodegenBinding { CodegenAnnotatingVisitor visitor = new CodegenAnnotatingVisitor(state); for (KtFile file : allFilesInPackages(state.getBindingContext(), state.getFiles())) { file.accept(visitor); + if (file instanceof KtCodeFragment) { + PsiElement context = file.getContext(); + if (context != null) { + PsiFile contextFile = context.getContainingFile(); + if (contextFile != null) { + contextFile.accept(visitor); + } + } + } } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/classFileUtils.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/classFileUtils.kt index 744993ebc49..ead6ba6a55a 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/classFileUtils.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/classFileUtils.kt @@ -27,7 +27,7 @@ fun ClassFileFactory.getClassFiles(): Iterable { return asList().filterClassFiles() } -fun List.filterClassFiles(): Iterable { +fun List.filterClassFiles(): List { return filter { it.relativePath.endsWith(".class") } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/context/ScriptContext.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/ScriptContext.kt index 453fbc1aa2c..1839984289e 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/context/ScriptContext.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/ScriptContext.kt @@ -17,7 +17,6 @@ package org.jetbrains.kotlin.codegen.context import org.jetbrains.kotlin.codegen.FieldInfo -import org.jetbrains.kotlin.codegen.OwnerKind import org.jetbrains.kotlin.codegen.StackValue import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.descriptors.ClassDescriptor @@ -35,12 +34,12 @@ import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.org.objectweb.asm.Type class ScriptContext( - val typeMapper: KotlinTypeMapper, + typeMapper: KotlinTypeMapper, val scriptDescriptor: ScriptDescriptor, val earlierScripts: List, contextDescriptor: ClassDescriptor, parentContext: CodegenContext<*>? -) : ClassContext(typeMapper, contextDescriptor, OwnerKind.IMPLEMENTATION, parentContext, null) { +) : ScriptLikeContext(typeMapper, contextDescriptor, parentContext) { val lastStatement: KtExpression? val resultFieldInfo: FieldInfo @@ -89,7 +88,7 @@ class ScriptContext( fun getProvidedPropertyType(index: Int): Type = typeMapper.mapType(scriptDescriptor.scriptProvidedProperties[index].type) - fun getOuterReceiverExpression(prefix: StackValue?, thisOrOuterClass: ClassDescriptor): StackValue { + override fun getOuterReceiverExpression(prefix: StackValue?, thisOrOuterClass: ClassDescriptor): StackValue { if (thisOrOuterClass.containingDeclaration == scriptDescriptor) { return prefix ?: StackValue.LOCAL_0 } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/context/ScriptLikeContext.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/ScriptLikeContext.kt new file mode 100644 index 00000000000..f99bc39b286 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/context/ScriptLikeContext.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.codegen.context + +import org.jetbrains.kotlin.codegen.OwnerKind +import org.jetbrains.kotlin.codegen.StackValue +import org.jetbrains.kotlin.codegen.binding.MutableClosure +import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor + +abstract class ScriptLikeContext( + val typeMapper: KotlinTypeMapper, + contextDescriptor: ClassDescriptor, + parentContext: CodegenContext<*>? +) : ClassContext(typeMapper, contextDescriptor, OwnerKind.IMPLEMENTATION, parentContext, null) { + abstract fun getOuterReceiverExpression(prefix: StackValue?, thisOrOuterClass: ClassDescriptor): StackValue + + open fun captureVariable(closure: MutableClosure, target: DeclarationDescriptor): StackValue? { + return null + } +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt index 16566c7aad3..c9724ad61cb 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -30,6 +30,7 @@ import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion import org.jetbrains.kotlin.modules.TargetId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtCodeFragment import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.resolve.* @@ -120,18 +121,17 @@ class GenerationState private constructor( abstract fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean abstract fun shouldGeneratePackagePart(ktFile: KtFile): Boolean abstract fun shouldGenerateScript(script: KtScript): Boolean + abstract fun shouldGenerateCodeFragment(script: KtCodeFragment): Boolean open fun shouldGenerateClassMembers(processingClassOrObject: KtClassOrObject) = shouldGenerateClass(processingClassOrObject) companion object { @JvmField val GENERATE_ALL: GenerateClassFilter = object : GenerateClassFilter() { override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject): Boolean = true - override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean = true - override fun shouldGenerateScript(script: KtScript): Boolean = true - override fun shouldGeneratePackagePart(ktFile: KtFile): Boolean = true + override fun shouldGenerateCodeFragment(script: KtCodeFragment) = true } } } diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/builder/LightClassDataProvider.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/builder/LightClassDataProvider.kt index 72e07be83dc..2df96b5d547 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/builder/LightClassDataProvider.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/builder/LightClassDataProvider.kt @@ -30,10 +30,7 @@ import org.jetbrains.kotlin.codegen.CompilationErrorHandler import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.psi.KtClassOrObject -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtPsiUtil -import org.jetbrains.kotlin.psi.KtScript +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.org.objectweb.asm.Type @@ -199,6 +196,7 @@ private class ClassFilterForClassOrObject(private val classOrObject: KtClassOrOb = shouldGenerateClassMembers(processingClassOrObject) || processingClassOrObject.isAncestor(classOrObject, true) override fun shouldGenerateScript(script: KtScript) = PsiTreeUtil.isAncestor(script, classOrObject, false) + override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false } object ClassFilterForFacade : GenerationState.GenerateClassFilter() { @@ -206,6 +204,7 @@ object ClassFilterForFacade : GenerationState.GenerateClassFilter() { override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = KtPsiUtil.isLocal(processingClassOrObject) override fun shouldGeneratePackagePart(ktFile: KtFile) = true override fun shouldGenerateScript(script: KtScript) = false + override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false } private class ClassFilterForScript(val script: KtScript) : GenerationState.GenerateClassFilter() { @@ -220,4 +219,5 @@ private class ClassFilterForScript(val script: KtScript) : GenerationState.Gener override fun shouldGeneratePackagePart(ktFile: KtFile): Boolean = script.containingKtFile === ktFile override fun shouldGenerateScript(script: KtScript): Boolean = this.script === script + override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false } diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassForSourceDeclaration.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassForSourceDeclaration.kt index f29adac5bb9..5689f7c9e16 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassForSourceDeclaration.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/KtLightClassForSourceDeclaration.kt @@ -337,6 +337,12 @@ abstract class KtLightClassForSourceDeclaration(protected val classOrObject: KtC } fun createNoCache(classOrObject: KtClassOrObject): KtLightClassForSourceDeclaration? { + val containingFile = classOrObject.containingFile + if (containingFile is KtCodeFragment) { + // Avoid building light classes for code fragments + return null + } + if (classOrObject.shouldNotBeVisibleAsLightClass()) { return null } diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/KtCodeFragment.kt b/compiler/psi/src/org/jetbrains/kotlin/psi/KtCodeFragment.kt index 015997eab58..2d673de34d9 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/psi/KtCodeFragment.kt +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/KtCodeFragment.kt @@ -199,3 +199,5 @@ abstract class KtCodeFragment( private val LOG = Logger.getInstance(KtCodeFragment::class.java) } } + +var KtCodeFragment.externalDescriptors: List? by CopyablePsiUserDataProperty(Key.create("EXTERNAL_DESCRIPTORS")) \ No newline at end of file diff --git a/compiler/psi/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt b/compiler/psi/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt index 20bd1b207ba..f9aa8f36afb 100644 --- a/compiler/psi/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt +++ b/compiler/psi/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt @@ -153,6 +153,9 @@ fun KtExpression.getQualifiedExpressionForReceiverOrThis(): KtExpression { fun KtExpression.isDotReceiver(): Boolean = (parent as? KtDotQualifiedExpression)?.receiverExpression == this +fun KtExpression.isDotSelector(): Boolean = + (parent as? KtDotQualifiedExpression)?.selectorExpression == this + fun KtExpression.getPossiblyQualifiedCallExpression(): KtCallExpression? = ((this as? KtQualifiedExpression)?.selectorExpression ?: this) as? KtCallExpression diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 563fa1b5a81..b802d3f6b15 100755 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -636,10 +636,6 @@ fun main(args: Array) { model("debugger/smartStepInto") } - testClass { - model("debugger/insertBeforeExtractFunction", extension = "kt") - } - testClass { model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest", testClassName = "StepInto") model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto") diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as32 b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as32 index 924dcf09bf6..25e9b4eca07 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as32 +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as32 @@ -75,10 +75,7 @@ import org.jetbrains.kotlin.idea.conversion.copy.AbstractLiteralKotlinToKotlinCo import org.jetbrains.kotlin.idea.conversion.copy.AbstractLiteralTextToKotlinCopyPasteTest import org.jetbrains.kotlin.idea.conversion.copy.AbstractTextJavaToKotlinCopyPasteConversionTest import org.jetbrains.kotlin.idea.coverage.AbstractKotlinCoverageOutputFilesTest -import org.jetbrains.kotlin.idea.debugger.AbstractBeforeExtractFunctionInsertionTest -import org.jetbrains.kotlin.idea.debugger.AbstractKotlinSteppingTest -import org.jetbrains.kotlin.idea.debugger.AbstractPositionManagerTest -import org.jetbrains.kotlin.idea.debugger.AbstractSmartStepIntoTest +import org.jetbrains.kotlin.idea.debugger.* import org.jetbrains.kotlin.idea.debugger.evaluate.* import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToDecompiledLibraryTest import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToLibrarySourceTest @@ -627,10 +624,6 @@ fun main(args: Array) { model("debugger/smartStepInto") } - testClass { - model("debugger/insertBeforeExtractFunction", extension = "kt") - } - testClass { model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest", testClassName = "StepInto") model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto") diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as33 b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as33 index 38dc4f50554..ceadfe00ba2 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as33 +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as33 @@ -67,10 +67,7 @@ import org.jetbrains.kotlin.idea.conversion.copy.AbstractLiteralKotlinToKotlinCo import org.jetbrains.kotlin.idea.conversion.copy.AbstractLiteralTextToKotlinCopyPasteTest import org.jetbrains.kotlin.idea.conversion.copy.AbstractTextJavaToKotlinCopyPasteConversionTest import org.jetbrains.kotlin.idea.coverage.AbstractKotlinCoverageOutputFilesTest -import org.jetbrains.kotlin.idea.debugger.AbstractBeforeExtractFunctionInsertionTest -import org.jetbrains.kotlin.idea.debugger.AbstractKotlinSteppingTest -import org.jetbrains.kotlin.idea.debugger.AbstractPositionManagerTest -import org.jetbrains.kotlin.idea.debugger.AbstractSmartStepIntoTest +import org.jetbrains.kotlin.idea.debugger.* import org.jetbrains.kotlin.idea.debugger.evaluate.* import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToDecompiledLibraryTest import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToLibrarySourceTest @@ -619,10 +616,6 @@ fun main(args: Array) { model("debugger/smartStepInto") } - testClass { - model("debugger/insertBeforeExtractFunction", extension = "kt") - } - testClass { model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest", testClassName = "StepInto") model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto") diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as34 b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as34 index a4955ad215e..f6975177d93 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as34 +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt.as34 @@ -616,10 +616,6 @@ fun main(args: Array) { model("debugger/smartStepInto") } - testClass { - model("debugger/insertBeforeExtractFunction", extension = "kt") - } - testClass { model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest", testClassName = "StepInto") model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto") diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/CodeFragmentAnalyzer.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/CodeFragmentAnalyzer.kt index 5b3070e0a09..4771f695099 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/CodeFragmentAnalyzer.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/CodeFragmentAnalyzer.kt @@ -16,18 +16,23 @@ package org.jetbrains.kotlin.idea.caches.resolve +import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes import org.jetbrains.kotlin.idea.project.ResolveElementCache import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode +import org.jetbrains.kotlin.psi.psiUtil.getParentOfType import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoAfter import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.ResolveSession +import org.jetbrains.kotlin.resolve.scopes.ExplicitImportsScope +import org.jetbrains.kotlin.resolve.scopes.ImportingScope import org.jetbrains.kotlin.resolve.scopes.LexicalScope +import org.jetbrains.kotlin.resolve.scopes.utils.ErrorLexicalScope import org.jetbrains.kotlin.resolve.scopes.utils.addImportingScopes import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices @@ -43,132 +48,154 @@ class CodeFragmentAnalyzer( @set:Inject // component dependency cycle lateinit var resolveElementCache: ResolveElementCache - fun analyzeCodeFragment(codeFragment: KtCodeFragment, trace: BindingTrace, bodyResolveMode: BodyResolveMode): BindingTrace { - val codeFragmentElement = codeFragment.getContentElement() + fun analyzeCodeFragment(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): BindingTrace { + val contextAnalysisResult = analyzeCodeFragmentContext(codeFragment, bodyResolveMode) + return doAnalyzeCodeFragment(codeFragment, contextAnalysisResult) + } - val (scopeForContextElement, dataFlowInfo, newBindingContext) = doAnalyzeCoreFragment(codeFragment) { - resolveElementCache!!.resolveToElements(listOf(it), bodyResolveMode) - } ?: return trace + private fun doAnalyzeCodeFragment(codeFragment: KtCodeFragment, contextAnalysisResult: ContextAnalysisResult): BindingTrace { + val (bindingContext, scope, dataFlowInfo) = contextAnalysisResult + val bindingTrace = DelegatingBindingTrace(bindingContext, "For code fragment analysis") - val newBindingTrace = DelegatingBindingTrace(newBindingContext, "For code fragment analysis") - - when (codeFragmentElement) { + when (val contentElement = codeFragment.getContentElement()) { is KtExpression -> { PreliminaryDeclarationVisitor.createForExpression( - codeFragmentElement, newBindingTrace, + contentElement, bindingTrace, expressionTypingServices.languageVersionSettings ) + expressionTypingServices.getTypeInfo( - scopeForContextElement, - codeFragmentElement, - TypeUtils.NO_EXPECTED_TYPE, - dataFlowInfo, - newBindingTrace, - false + scope, contentElement, TypeUtils.NO_EXPECTED_TYPE, + dataFlowInfo, bindingTrace, false ) } is KtTypeReference -> { val context = TypeResolutionContext( - scopeForContextElement, - newBindingTrace, - true, - true, - codeFragment.suppressDiagnosticsInDebugMode() + scope, bindingTrace, + true, true, codeFragment.suppressDiagnosticsInDebugMode() ).noBareTypes() - typeResolver.resolvePossiblyBareType(context, codeFragmentElement) + + typeResolver.resolvePossiblyBareType(context, contentElement) } } - return newBindingTrace + return bindingTrace } - //TODO: this code should be moved into debugger which should set correct context for its code fragment - private fun KtElement.correctContextForElement(): KtElement { - return when (this) { - is KtProperty -> this.delegateExpressionOrInitializer - is KtFunctionLiteral -> this.bodyExpression?.statements?.lastOrNull() - is KtDeclarationWithBody -> this.bodyExpression - is KtBlockExpression -> this.statements.lastOrNull() - else -> null - } ?: this - } - - private fun doAnalyzeCoreFragment( - codeFragment: KtCodeFragment, - resolveToElement: (KtElement) -> BindingContext - ): Triple? { - val context = codeFragment.context - - val scopeForContextElement: LexicalScope? + private data class ContextAnalysisResult( + val bindingContext: BindingContext, + val scope: LexicalScope, val dataFlowInfo: DataFlowInfo + ) - fun getClassDescriptor(classOrObject: KtClassOrObject): Pair? { - val bindingContext: BindingContext - val classDescriptor: ClassDescriptor? - - if (!KtPsiUtil.isLocal(classOrObject)) { - bindingContext = resolveSession.bindingContext - classDescriptor = resolveSession.getClassDescriptor(classOrObject, NoLookupLocation.FROM_IDE) - } else { - bindingContext = resolveToElement(classOrObject) - classDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, classOrObject] as ClassDescriptor? - } - - return (classDescriptor as? ClassDescriptorWithResolutionScopes)?.let { Pair(bindingContext, it) } + private fun analyzeCodeFragmentContext(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): ContextAnalysisResult { + fun resolutionFactory(element: KtElement): BindingContext { + return resolveElementCache.resolveToElements(listOf(element), bodyResolveMode) } - val bindingContextForContext: BindingContext + val context = refineContextElement(codeFragment.context) + + var bindingContext: BindingContext = BindingContext.EMPTY + var dataFlowInfo: DataFlowInfo = DataFlowInfo.EMPTY + var scope: LexicalScope? = null + when (context) { is KtPrimaryConstructor -> { - val (bindingContext, classDescriptor) = getClassDescriptor(context.getContainingClassOrObject()) ?: return null - - scopeForContextElement = classDescriptor.scopeForInitializerResolution - dataFlowInfo = DataFlowInfo.EMPTY - bindingContextForContext = bindingContext + val containingClass = context.getContainingClassOrObject() + val resolutionResult = getClassDescriptor(containingClass, ::resolutionFactory) + if (resolutionResult != null) { + bindingContext = resolutionResult.bindingContext + scope = resolutionResult.descriptor.scopeForInitializerResolution + } } is KtSecondaryConstructor -> { - val correctedContext = context.getDelegationCall().calleeExpression!! - bindingContextForContext = resolveToElement(correctedContext) - - scopeForContextElement = bindingContextForContext[BindingContext.LEXICAL_SCOPE, correctedContext] - dataFlowInfo = DataFlowInfo.EMPTY + val expression = context.bodyExpression ?: context.getDelegationCall().calleeExpression + if (expression != null) { + bindingContext = resolutionFactory(expression) + scope = bindingContext[BindingContext.LEXICAL_SCOPE, expression] + } } is KtClassOrObject -> { - val (bindingContext, classDescriptor) = getClassDescriptor(context) ?: return null - scopeForContextElement = classDescriptor.scopeForMemberDeclarationResolution - dataFlowInfo = DataFlowInfo.EMPTY - bindingContextForContext = bindingContext + val resolutionResult = getClassDescriptor(context, ::resolutionFactory) + if (resolutionResult != null) { + bindingContext = resolutionResult.bindingContext + scope = resolutionResult.descriptor.scopeForMemberDeclarationResolution + } } is KtFile -> { - scopeForContextElement = resolveSession.fileScopeProvider.getFileResolutionScope(context) - dataFlowInfo = DataFlowInfo.EMPTY - bindingContextForContext = BindingContext.EMPTY + bindingContext = resolveSession.bindingContext + scope = resolveSession.fileScopeProvider.getFileResolutionScope(context) } is KtElement -> { - val correctedContext = context.correctContextForElement() - bindingContextForContext = resolveToElement(correctedContext) - - scopeForContextElement = bindingContextForContext[BindingContext.LEXICAL_SCOPE, correctedContext] - dataFlowInfo = bindingContextForContext.getDataFlowInfoAfter(correctedContext) + bindingContext = resolutionFactory(context) + scope = bindingContext[BindingContext.LEXICAL_SCOPE, context] + dataFlowInfo = bindingContext.getDataFlowInfoAfter(context) } - else -> return null } - if (scopeForContextElement == null) return null + val scopeWithImports = enrichScopeWithImports(scope ?: ErrorLexicalScope(), codeFragment) + return ContextAnalysisResult(bindingContext, scopeWithImports, dataFlowInfo) + } + + private data class ClassResolutionResult(val bindingContext: BindingContext, val descriptor: ClassDescriptorWithResolutionScopes) + + private fun getClassDescriptor( + classOrObject: KtClassOrObject, + resolutionFactory: (KtElement) -> BindingContext + ): ClassResolutionResult? { + val bindingContext: BindingContext + val classDescriptor: ClassDescriptor? + + if (!KtPsiUtil.isLocal(classOrObject)) { + bindingContext = resolveSession.bindingContext + classDescriptor = resolveSession.getClassDescriptor(classOrObject, NoLookupLocation.FROM_IDE) + } else { + bindingContext = resolutionFactory(classOrObject) + classDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, classOrObject] as ClassDescriptor? + } + + return (classDescriptor as? ClassDescriptorWithResolutionScopes)?.let { ClassResolutionResult(bindingContext, it) } + } + + private fun refineContextElement(context: PsiElement?): KtElement? { + return when (context) { + is KtParameter -> context.getParentOfType(true) + is KtProperty -> context.delegateExpressionOrInitializer + is KtConstructor<*> -> context + is KtFunctionLiteral -> context.bodyExpression?.statements?.lastOrNull() + is KtDeclarationWithBody -> context.bodyExpression + is KtBlockExpression -> context.statements.lastOrNull() + else -> null + } ?: context as? KtElement + } + + private fun enrichScopeWithImports(scope: LexicalScope, codeFragment: KtCodeFragment): LexicalScope { + val additionalImportingScopes = mutableListOf() + + val externalDescriptors = codeFragment.externalDescriptors ?: emptyList() + if (externalDescriptors.isNotEmpty()) { + additionalImportingScopes += ExplicitImportsScope(externalDescriptors) + } val importList = codeFragment.importsAsImportList() - if (importList == null || importList.imports.isEmpty()) { - return Triple(scopeForContextElement, dataFlowInfo, bindingContextForContext) + if (importList != null && importList.imports.isNotEmpty()) { + additionalImportingScopes += createImportScopes(importList) } - val importScopes = importList.imports.mapNotNull { + if (additionalImportingScopes.isNotEmpty()) { + return scope.addImportingScopes(additionalImportingScopes) + } + + return scope + } + + private fun createImportScopes(importList: KtImportList): List { + return importList.imports.mapNotNull { qualifierResolver.processImportReference( it, resolveSession.moduleDescriptor, resolveSession.trace, excludedImportNames = emptyList(), packageFragmentForVisibilityCheck = null ) } - - return Triple(scopeForContextElement.addImportingScopes(importScopes), dataFlowInfo, bindingContextForContext) } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/PerFileAnalysisCache.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/PerFileAnalysisCache.kt index 1f9d4501ab5..639d3378735 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/PerFileAnalysisCache.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/PerFileAnalysisCache.kt @@ -176,7 +176,9 @@ private object KotlinResolveDataProvider { ): AnalysisResult { try { if (analyzableElement is KtCodeFragment) { - return AnalysisResult.success(analyzeExpressionCodeFragment(codeFragmentAnalyzer, analyzableElement), moduleDescriptor) + val bodyResolveMode = BodyResolveMode.PARTIAL_FOR_COMPLETION + val bindingContext = codeFragmentAnalyzer.analyzeCodeFragment(analyzableElement, bodyResolveMode).bindingContext + return AnalysisResult.success(bindingContext, moduleDescriptor) } val trace = DelegatingBindingTrace( @@ -217,19 +219,4 @@ private object KotlinResolveDataProvider { return AnalysisResult.internalError(BindingContext.EMPTY, e) } } - - private fun analyzeExpressionCodeFragment(codeFragmentAnalyzer: CodeFragmentAnalyzer, codeFragment: KtCodeFragment): BindingContext { - val contextElement = codeFragment.getContentElement() - val trace = if (contextElement != null) { - DelegatingBindingTrace(contextElement.analyze(BodyResolveMode.PARTIAL_FOR_COMPLETION), "Trace for code fragment resolution") - } else { - BindingTraceContext() - } - - return codeFragmentAnalyzer.analyzeCodeFragment( - codeFragment, - trace, - BodyResolveMode.PARTIAL_FOR_COMPLETION //TODO: discuss it - ).bindingContext - } } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ResolveElementCache.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ResolveElementCache.kt index 276eee13e96..208906aa834 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ResolveElementCache.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/ResolveElementCache.kt @@ -457,14 +457,12 @@ class ResolveElementCache( } private fun codeFragmentAdditionalResolve(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): BindingTrace { - val trace = createDelegatingTrace(codeFragment, bodyResolveMode.bindingTraceFilter) - val contextResolveMode = if (bodyResolveMode == BodyResolveMode.PARTIAL) BodyResolveMode.PARTIAL_FOR_COMPLETION else bodyResolveMode - return codeFragmentAnalyzer.analyzeCodeFragment(codeFragment, trace, contextResolveMode) + return codeFragmentAnalyzer.analyzeCodeFragment(codeFragment, contextResolveMode) } private fun annotationAdditionalResolve(resolveSession: ResolveSession, ktAnnotationEntry: KtAnnotationEntry): BindingTrace { diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinCoroutinesAsyncStackTraceProvider.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinCoroutinesAsyncStackTraceProvider.kt index 2a922155dc5..3e350150480 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinCoroutinesAsyncStackTraceProvider.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/KotlinCoroutinesAsyncStackTraceProvider.kt @@ -20,7 +20,7 @@ import com.intellij.xdebugger.frame.XNamedValue import com.sun.jdi.* import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME import org.jetbrains.kotlin.idea.debugger.evaluate.LOG -import org.jetbrains.kotlin.idea.debugger.evaluate.VariableFinder.Companion.SUSPEND_LAMBDA_CLASSES +import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder.Companion.SUSPEND_LAMBDA_CLASSES import org.jetbrains.kotlin.idea.debugger.evaluate.getInvokePolicy import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt index a6377935611..7b72312cd10 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/debuggerUtil.kt @@ -17,7 +17,6 @@ import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousCl import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME import org.jetbrains.kotlin.codegen.coroutines.continuationAsmTypes -import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches import org.jetbrains.kotlin.idea.refactoring.getLineEndOffset @@ -27,7 +26,6 @@ import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.inline.InlineUtil import org.jetbrains.org.objectweb.asm.Type as AsmType import java.util.* @@ -241,15 +239,6 @@ fun findCallByEndToken(element: PsiElement): KtCallExpression? { } } -fun PropertyDescriptor.getBackingFieldName(): String? { - if (backingField == null) { - return null - } - - val jvmNameAnnotation = DescriptorUtils.findJvmNameAnnotation(this) ?: return name.asString() - return jvmNameAnnotation.allValueArguments.values.singleOrNull()?.toString() -} - fun Type.isSubtype(className: String): Boolean = isSubtype(AsmType.getObjectType(className)) fun Type.isSubtype(type: AsmType): Boolean { diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/ExecutionContext.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/ExecutionContext.kt new file mode 100644 index 00000000000..70ae61c21b7 --- /dev/null +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/ExecutionContext.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.debugger.evaluate + +import com.intellij.debugger.engine.evaluation.EvaluationContextImpl +import com.sun.jdi.* +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import org.jetbrains.org.objectweb.asm.Type + +class ExecutionContext(val evaluationContext: EvaluationContextImpl, val thread: ThreadReference, val invokePolicy: Int) { + val vm: VirtualMachine + get() = thread.virtualMachine() + + fun loadClassType(asmType: Type, classLoader: ClassLoaderReference? = null): ReferenceType? { + if (asmType.sort == Type.ARRAY) { + return loadClassType(asmType.elementType, classLoader) + } + + if (asmType.sort != Type.OBJECT) { + return null + } + + val vm = thread.virtualMachine() + val className = asmType.className + + val classClass = vm.classesByName(Class::class.java.name).firstIsInstanceOrNull() ?: return null + + val method: Method? + val args: List + + if (classLoader != null) { + method = classClass.methodsByName("forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;").firstOrNull() + args = listOf(vm.mirrorOf(className), vm.mirrorOf(true), classLoader) + } else { + method = classClass.methodsByName("forName", "(Ljava/lang/String;)Ljava/lang/Class;").firstOrNull() + args = listOf(vm.mirrorOf(className)) + } + + if (method == null) { + return null + } + + return (classClass.invokeMethod(thread, method, args, invokePolicy) as? ClassObjectReference)?.reflectedType() + } + +} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt index da7a0d11109..f849ecb0f16 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinCodeFragmentFactory.kt @@ -22,7 +22,6 @@ 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 import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diagnostic.Logger @@ -35,9 +34,6 @@ import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTypesUtil import com.intellij.util.IncorrectOperationException 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.* import org.jetbrains.annotations.TestOnly import org.jetbrains.eval4j.jdi.asValue @@ -55,7 +51,6 @@ import org.jetbrains.kotlin.idea.util.application.runReadAction 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 import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.typeUtil.makeNullable @@ -63,28 +58,16 @@ import java.util.concurrent.atomic.AtomicReference class KotlinCodeFragmentFactory : CodeFragmentFactory() { override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment { - val contextElement = getWrappedContextElement(project, context) - if (contextElement == null) { - LOG.warn("CodeFragment with null context created:\noriginalContext = ${context?.getElementTextWithContext()}") - } - val codeFragment = if (item.kind == CodeFragmentKind.EXPRESSION) { - KtExpressionCodeFragment( - project, - "fragment.kt", - item.text, - initImports(item.imports), - contextElement - ) - } else { - KtBlockCodeFragment( - project, - "fragment.kt", - item.text, - initImports(item.imports), - contextElement - ) + val contextElement = getContextElement(context) + + val constructor = when (item.kind) { + null -> error("Code fragment kind should be set") + CodeFragmentKind.EXPRESSION -> ::KtExpressionCodeFragment + CodeFragmentKind.CODE_BLOCK -> ::KtBlockCodeFragment } + val codeFragment = constructor(project, "fragment.kt", item.text, initImports(item.imports), contextElement) + codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR, { expression: KtExpression -> val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context val debuggerSession = debuggerContext.debuggerSession @@ -135,19 +118,19 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() { } val receiverTypeReference = - frameDescriptor.thisObject?.let { createKotlinProperty(project, "this_0", it.type().name(), it) }?.typeReference + frameDescriptor.thisObject?.let { createKotlinProperty(project, FAKE_JAVA_THIS_NAME, it.type().name(), it) }?.typeReference val receiverTypeText = receiverTypeReference?.let { "${it.text}." } ?: "" val kotlinVariablesText = frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project) - val fakeFunctionText = "fun ${receiverTypeText}_java_locals_debug_fun_() {\n$kotlinVariablesText\n}" + val fakeFunctionText = "fun ${receiverTypeText}$FAKE_JAVA_CONTEXT_FUNCTION_NAME() {\n$kotlinVariablesText\n}" val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement) val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction val fakeContext = fakeFunction?.bodyBlockExpression?.statements?.lastOrNull() - return@putCopyableUserData wrapContextIfNeeded(project, contextElement, fakeContext) ?: emptyFile + return@putCopyableUserData fakeContext ?: emptyFile }) } @@ -219,12 +202,6 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() { return import } - 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) if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtExpressionCodeFragment) { @@ -292,13 +269,12 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() { companion object { private val LOG = Logger.getInstance(this::class.java) - val LABEL_VARIABLE_VALUE_KEY: Key = Key.create("_label_variable_value_key_") - - private const val DEBUG_LABEL_SUFFIX: String = "_DebugLabel" - @TestOnly val DEBUG_CONTEXT_FOR_TESTS: Key = Key.create("DEBUG_CONTEXT_FOR_TESTS") + const val FAKE_JAVA_CONTEXT_FUNCTION_NAME = "_java_locals_debug_fun_" + const val FAKE_JAVA_THIS_NAME = "\$this\$_java_locals_debug_fun_" + fun getContextElement(elementAt: PsiElement?): PsiElement? { if (elementAt == null) return null @@ -335,17 +311,6 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() { return containingFile } - //internal for tests - fun createCodeFragmentForLabeledObjects(project: Project, markupMap: Map<*, ValueMarkup>): Pair> { - @Suppress("UNCHECKED_CAST") - val variables = markupMap.entries.associate { - val (value, markup) = it - "${markup.text}$DEBUG_LABEL_SUFFIX" to value as? Value - }.filterValues { it != null } as Map - - return variables.kotlinVariablesAsText(project) to variables - } - private fun Map.kotlinVariablesAsText(project: Project): String { val sb = StringBuilder() @@ -389,21 +354,6 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() { } } - private fun wrapContextIfNeeded(project: Project, originalContext: PsiElement?, newContext: KtElement?): KtElement? { - val markupMap: Map<*, ValueMarkup>? = - if (ApplicationManager.getApplication().isUnitTestMode) - NodeDescriptorImpl.getMarkupMap(originalContext?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.debugProcess) - else - (XDebuggerManager.getInstance(project).currentSession as? XDebugSessionImpl)?.valueMarkers?.allMarkers - - if (markupMap == null || markupMap.isEmpty()) return newContext - - val (text, labels) = createCodeFragmentForLabeledObjects(project, markupMap) - if (text.isEmpty()) return newContext - - return createWrappingContext(text, labels, newContext, project) - } - private fun createFakeFileWithJavaContextElement(funWithLocalVariables: String, javaContext: PsiElement): KtFile { val javaFile = javaContext.containingFile as? PsiJavaFile @@ -419,25 +369,4 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() { return KtPsiFactory(javaContext.project).createAnalyzableFile("fakeFileForJavaContextInDebugger.kt", sb.toString(), javaContext) } - - // internal for test - private fun createWrappingContext( - newFragmentText: String, - labels: Map, - originalContext: KtElement?, - project: Project - ): KtElement? { - val codeFragment = KtPsiFactory(project).createBlockCodeFragment(newFragmentText, originalContext) - - codeFragment.accept(object : KtTreeVisitorVoid() { - override fun visitProperty(property: KtProperty) { - val reference = labels[property.name] - if (reference != null) { - property.putUserData(LABEL_VARIABLE_VALUE_KEY, reference) - } - } - }) - - return codeFragment.getContentElement().statements.lastOrNull() - } } diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt index 47c6fea3d79..2ced4a4d512 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt @@ -18,7 +18,6 @@ package org.jetbrains.kotlin.idea.debugger.evaluate import com.intellij.debugger.SourcePosition import com.intellij.debugger.engine.evaluation.EvaluateException -import com.intellij.debugger.engine.evaluation.EvaluationContextImpl import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.project.Project import com.intellij.openapi.roots.libraries.LibraryUtil @@ -29,35 +28,31 @@ import com.intellij.psi.util.CachedValuesManager import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.containers.MultiMap -import org.apache.log4j.Logger import org.jetbrains.annotations.TestOnly +import org.apache.log4j.Logger import org.jetbrains.eval4j.Value -import org.jetbrains.eval4j.jdi.asValue import org.jetbrains.kotlin.analyzer.AnalysisResult import org.jetbrains.kotlin.codegen.ClassBuilderFactories import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.config.CompilerConfiguration -import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks import org.jetbrains.kotlin.idea.debugger.BinaryCacheKey import org.jetbrains.kotlin.idea.debugger.BytecodeDebugInfo import org.jetbrains.kotlin.idea.debugger.createWeakBytecodeDebugInfoStorage -import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad +import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CompiledDataDescriptor import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.KtCodeFragment import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.types.KotlinType import java.util.* import java.util.concurrent.ConcurrentHashMap class KotlinDebuggerCaches(project: Project) { - private val cachedCompiledData = CachedValuesManager.getManager(project).createCachedValue( { CachedValueProvider.Result>( @@ -98,37 +93,39 @@ class KotlinDebuggerCaches(project: Project) { fun getInstance(project: Project) = ServiceManager.getService(project, KotlinDebuggerCaches::class.java)!! - fun getOrCreateCompiledData( + fun compileCodeFragmentCacheAware( codeFragment: KtCodeFragment, sourcePosition: SourcePosition, - evaluationContext: EvaluationContextImpl, - create: (KtCodeFragment, SourcePosition) -> CompiledDataDescriptor - ): CompiledDataDescriptor { + compileCode: () -> CompiledDataDescriptor, + force: Boolean = false + ): Pair { val evaluateExpressionCache = getInstance(codeFragment.project) val text = "${codeFragment.importsToString()}\n${codeFragment.text}" - val cached = synchronized>(evaluateExpressionCache.cachedCompiledData) { - val cache = evaluateExpressionCache.cachedCompiledData.value!! - - cache[text] + val cachedResults = synchronized>(evaluateExpressionCache.cachedCompiledData) { + evaluateExpressionCache.cachedCompiledData.value[text] } - val answer = cached.firstOrNull { - it.sourcePosition == sourcePosition || evaluateExpressionCache.canBeEvaluatedInThisContext(it, evaluationContext) - } - if (answer != null) { - return answer + val existingResult = cachedResults.firstOrNull { it.sourcePosition == sourcePosition } + if (existingResult != null) { + if (force) { + synchronized(evaluateExpressionCache.cachedCompiledData) { + evaluateExpressionCache.cachedCompiledData.value.remove(text, existingResult) + } + } else { + return Pair(existingResult, true) + } } - val newCompiledData = create(codeFragment, sourcePosition) + val newCompiledData = compileCode() LOG.debug("Compile bytecode for ${codeFragment.text}") synchronized(evaluateExpressionCache.cachedCompiledData) { evaluateExpressionCache.cachedCompiledData.value.putValue(text, newCompiledData) } - return newCompiledData + return Pair(newCompiledData, false) } fun getOrComputeClassNames(psiElement: T?, create: (T) -> ComputedClassNames): List { @@ -215,32 +212,6 @@ class KotlinDebuggerCaches(project: Project) { } } - private fun canBeEvaluatedInThisContext(compiledData: CompiledDataDescriptor, context: EvaluationContextImpl): Boolean { - val variableFinder = VariableFinder.instance(context) ?: return false - - return compiledData.parameters.all { p -> - val (name, jetType) = p - val lookupResult = variableFinder.find(name, null) ?: return@all false - val value = lookupResult.value.asValue() - - val thisDescriptor = value.asmType.getClassDescriptor(context.debugProcess.searchScope) - val superClassDescriptor = jetType.constructor.declarationDescriptor as? ClassDescriptor - return@all thisDescriptor != null && superClassDescriptor != null && runReadAction { - DescriptorUtils.isSubclass( - thisDescriptor, - superClassDescriptor - ) - } - } - } - - data class CompiledDataDescriptor( - val classes: List, - val sourcePosition: SourcePosition, - val parameters: List, - val variablesCrossingInlineBounds: Set - ) - data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null, val error: EvaluateException? = null) class ComputedClassNames(val classNames: List, val shouldBeCached: Boolean) { diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt index 396b6486d87..bc07e2be07c 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluationBuilder.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.idea.debugger.evaluate import com.intellij.debugger.SourcePosition +import com.intellij.debugger.engine.DebugProcessImpl import com.intellij.debugger.engine.SuspendContext import com.intellij.debugger.engine.evaluation.EvaluateException import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil @@ -27,21 +28,17 @@ import com.intellij.openapi.diagnostic.Attachment import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.project.DumbService -import com.intellij.openapi.util.Disposer -import com.intellij.openapi.vfs.CharsetToolkit import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFileFactory -import com.intellij.psi.impl.PsiFileFactoryImpl import com.intellij.psi.search.GlobalSearchScope -import com.intellij.testFramework.LightVirtualFile import com.intellij.testFramework.runInEdtAndWait import com.intellij.util.ExceptionUtil import com.sun.jdi.* +import com.sun.jdi.Value import com.sun.jdi.request.EventRequest import org.jetbrains.eval4j.* -import org.jetbrains.eval4j.Value +import org.jetbrains.eval4j.Value as Eval4JValue import org.jetbrains.eval4j.jdi.JDIEval import org.jetbrains.eval4j.jdi.asJdiValue import org.jetbrains.eval4j.jdi.asValue @@ -50,57 +47,39 @@ import org.jetbrains.kotlin.builtins.DefaultBuiltIns import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap import org.jetbrains.kotlin.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.codegen.* -import org.jetbrains.kotlin.codegen.AsmUtil.LABELED_THIS_PARAMETER -import org.jetbrains.kotlin.codegen.AsmUtil.RECEIVER_PARAMETER_NAME -import org.jetbrains.kotlin.codegen.binding.CodegenBinding -import org.jetbrains.kotlin.codegen.state.GenerationState -import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies -import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor import org.jetbrains.kotlin.diagnostics.DiagnosticFactory import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Severity import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages -import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaClassDescriptor -import org.jetbrains.kotlin.idea.core.quoteSegmentsIfNeeded import org.jetbrains.kotlin.idea.debugger.DebuggerUtils -import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.* -import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad +import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companion.compileCodeFragmentCacheAware +import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.* import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely -import org.jetbrains.kotlin.idea.debugger.getBackingFieldName -import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionResult +import org.jetbrains.kotlin.idea.debugger.evaluate.variables.EvaluatorValueConverter +import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.attachment.attachmentByPsiFile import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.codeFragmentUtil.debugTypeInfo -import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode -import org.jetbrains.kotlin.psi.psiUtil.getParentOfType -import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded import org.jetbrains.kotlin.resolve.AnalyzingUtils import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.BindingTrace -import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns import org.jetbrains.kotlin.resolve.isInlineClassType import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.org.objectweb.asm.* -import org.jetbrains.org.objectweb.asm.Opcodes.API_VERSION 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.* internal val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluator") internal const val GENERATED_FUNCTION_NAME = "generated_for_debugger_fun" internal const val GENERATED_CLASS_NAME = "Generated_for_debugger_class" -private const val DEBUG_MODE = false - object KotlinEvaluationBuilder : EvaluatorBuilder { override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator { if (codeFragment !is KtCodeFragment || position == null) { @@ -108,14 +87,14 @@ object KotlinEvaluationBuilder : EvaluatorBuilder { } if (position.line < 0) { - throw EvaluateExceptionUtil.createEvaluateException("Couldn't evaluate kotlin expression at $position") + evaluationException("Couldn't evaluate kotlin expression at $position") } 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( + evaluationException( "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." ) @@ -133,7 +112,7 @@ object KotlinEvaluationBuilder : EvaluatorBuilder { "Trying to evaluate ${codeFragment::class.java} with context ${codeFragment.context?.javaClass}", mergeAttachments(*attachments) ) - throw EvaluateExceptionUtil.createEvaluateException("Couldn't evaluate kotlin expression in this context") + evaluationException("Couldn't evaluate kotlin expression in this context") } return ExpressionEvaluatorImpl(KotlinEvaluator(codeFragment, position)) @@ -147,44 +126,21 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour } if (DumbService.getInstance(codeFragment.project).isDumb) { - throw EvaluateExceptionUtil.createEvaluateException("Code fragment evaluation is not available in the dumb mode") + evaluationException("Code fragment evaluation is not available in the dumb mode") } - var isCompiledDataFromCache = true try { - val compiledData = KotlinDebuggerCaches.getOrCreateCompiledData(codeFragment, sourcePosition, context) { fragment, position -> - isCompiledDataFromCache = false - extractAndCompile(fragment, position, context) - } - - val classLoaderRef = loadClassesSafely(context, compiledData.classes) - - val result = if (classLoaderRef != null) { - evaluateWithCompilation(context, compiledData, classLoaderRef) ?: runEval4j(context, compiledData, classLoaderRef) - } else { - runEval4j(context, compiledData, classLoaderRef) - } - - // 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), classLoaderRef).toJdiValue(context) - } - - return if (result is InterpreterResult) { - result.toJdiValue(context) - } else { - result - } + return evaluateSafe(context) } catch (e: EvaluateException) { throw e } catch (e: ProcessCanceledException) { - exception(e) + evaluationException(e) } catch (e: Eval4JInterpretingException) { - exception(e.cause) + evaluationException(e.cause) } catch (e: Exception) { val isSpecialException = isSpecialException(e) if (isSpecialException) { - exception(e) + evaluationException(e) } val text = runReadAction { codeFragment.context?.text ?: "null" } @@ -196,6 +152,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour ) LOG.error( + @Suppress("DEPRECATION") LogMessageEx.createEvent( "Couldn't evaluate expression", ExceptionUtil.getThrowableText(e), @@ -204,570 +161,254 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour ) val cause = if (e.message != null) ": ${e.message}" else "" - exception("An exception occurs during Evaluate Expression Action $cause") + evaluationException("An exception occurs during Evaluate Expression Action $cause") } } - private fun isSpecialException(th: Throwable): Boolean { - return when (th) { - is ClassNotPreparedException, - is InternalException, - is AbsentInformationException, - is ClassNotLoadedException, - is IncompatibleThreadStateException, - is InconsistentDebugInfoException, - is ObjectCollectedException, - is VMDisconnectedException -> true - else -> false + private fun evaluateSafe(context: EvaluationContextImpl): Any? { + fun compilerFactory(): CompiledDataDescriptor = compileCodeFragment(context.debugProcess) + + val (compiledData, isCompiledDataFromCache) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory) + val classLoaderRef = loadClassesSafely(context, compiledData.classes) + + val thread = context.suspendContext.thread?.threadReference ?: error("Can not find a thread to run evaluation on") + val invokePolicy = context.suspendContext.getInvokePolicy() + val executionContext = ExecutionContext(context, thread, invokePolicy) + + val result = if (classLoaderRef != null) { + evaluateWithCompilation(executionContext, compiledData, classLoaderRef) + ?: evaluateWithEval4J(executionContext, compiledData, classLoaderRef) + } else { + evaluateWithEval4J(executionContext, compiledData, classLoaderRef) + } + + // 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) { + val (recompiledData, _) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory, force = true) + return evaluateWithEval4J(executionContext, recompiledData, classLoaderRef).toJdiValue(executionContext) + } + + return when (result) { + is InterpreterResult -> result.toJdiValue(executionContext) + else -> result } } - override fun getModifier(): Modifier? { - return null - } + private fun compileCodeFragment(debugProcess: DebugProcessImpl): CompiledDataDescriptor { + var analysisResult = checkForErrors(codeFragment, debugProcess) - companion object { - private fun extractAndCompile( - codeFragment: KtCodeFragment, - sourcePosition: SourcePosition, - context: EvaluationContextImpl - ): CompiledDataDescriptor { - var bindingContext = codeFragment.checkForErrors().bindingContext + if (codeFragment.wrapToStringIfNeeded(analysisResult.bindingContext)) { + // Repeat analysis with toString() added + analysisResult = checkForErrors(codeFragment, debugProcess) + } - if (codeFragment.wrapToStringIfNeeded(bindingContext)) { - // Repeat analysis with toString() added - bindingContext = codeFragment.checkForErrors().bindingContext - } - - val variablesCrossingInlineBounds = ScopeCheckerForEvaluator.checkScopes(bindingContext, codeFragment) - - val extractionResult = getFunctionForExtractedFragment(codeFragment, sourcePosition.file, sourcePosition.line) - ?: throw IllegalStateException("Code fragment cannot be extracted to function: ${codeFragment.text}") - val (parametersDescriptor, extractedFunction) = try { - extractionResult.getParametersForDebugger(codeFragment, context) to extractionResult.declaration as KtNamedFunction - } finally { - Disposer.dispose(extractionResult) - } - - if (LOG.isDebugEnabled) { - LOG.debug("Extracted function:\n" + runReadAction { extractedFunction.text }) - } - - val classFileFactory = createClassFileFactory(codeFragment, extractedFunction, context, parametersDescriptor) - - val outputFiles = classFileFactory.asList().filterClassFiles() - - for (file in outputFiles) { - if (LOG.isDebugEnabled) { - LOG.debug("Output file generated: ${file.relativePath}") - } - @Suppress("ConstantConditionIf") - if (DEBUG_MODE) { - println(file.asText()) - } - } - - val additionalFiles = outputFiles.map { ClassToLoad(getClassName(it.relativePath), it.relativePath, it.asByteArray()) } - - return CompiledDataDescriptor( - additionalFiles, - sourcePosition, - parametersDescriptor, - variablesCrossingInlineBounds + val (bindingContext) = runReadAction { + DebuggerUtils.analyzeInlinedFunctions( + KotlinCacheService.getInstance(codeFragment.project).getResolutionFacade(listOf(codeFragment)), + codeFragment, false, analysisResult.bindingContext ) } - private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean { - if (this !is KtExpressionCodeFragment) { - return false - } + val moduleDescriptor = analysisResult.moduleDescriptor - val contentElement = runReadAction { getContentElement() } - val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, contentElement]?.type - if (contentElement != null && expressionType?.isInlineClassType() == true) { - val newExpression = runReadAction { - val expressionText = contentElement.text - KtPsiFactory(project).createExpression("($expressionText).toString()") - } - runInEdtAndWait { - project.executeWriteCommand("Wrap with 'toString()'") { - contentElement.replace(newExpression) - } - } - return true - } + val result = CodeFragmentCompiler.compile(codeFragment, bindingContext, moduleDescriptor) + return CompiledDataDescriptor.from(result, sourcePosition) + } + private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean { + if (this !is KtExpressionCodeFragment) { return false } - private fun getClassName(fileName: String): String { - return fileName.substringBeforeLast(".class").replace("/", ".") + val contentElement = runReadAction { getContentElement() } + val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, contentElement]?.type + if (contentElement != null && expressionType?.isInlineClassType() == true) { + val newExpression = runReadAction { + val expressionText = contentElement.text + KtPsiFactory(project).createExpression("($expressionText).toString()") + } + runInEdtAndWait { + project.executeWriteCommand("Wrap with 'toString()'") { + contentElement.replace(newExpression) + } + } + return true } - private val CompiledDataDescriptor.mainClass - get() = classes.firstOrNull { it.isMainClass() } ?: error( - "Can't find main class for " + sourcePosition.elementAt.getParentOfType(strict = false) - ) + return false + } - private fun evaluateWithCompilation( - context: EvaluationContextImpl, - compiledData: CompiledDataDescriptor, - classLoader: ClassLoaderReference - ): Any? { - val vm = context.debugProcess.virtualMachineProxy.virtualMachine - val mainClassBytecode = compiledData.mainClass.bytes + private data class ErrorCheckingResult( + val bindingContext: BindingContext, + val moduleDescriptor: ModuleDescriptor, + val files: List + ) + private fun checkForErrors(codeFragment: KtCodeFragment, debugProcess: DebugProcessImpl): ErrorCheckingResult { + return runInReadActionWithWriteActionPriorityWithPCE { try { - val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, ClassReader.SKIP_CODE) } - assert(mainClassAsmNode.methods.size == 1) - - val methodToInvoke = mainClassAsmNode.methods[0] - assert(methodToInvoke.parameters == null || methodToInvoke.parameters.isEmpty()) - - val thread = context.suspendContext.thread?.threadReference!! - val invokePolicy = context.suspendContext.getInvokePolicy() - val eval = JDIEval(vm, classLoader, thread, invokePolicy) - - val mainClassValue = (eval.loadClass(Type.getObjectType(mainClassAsmNode.name), classLoader) as? ObjectValue) - val mainClass = (mainClassValue?.value as? ClassObjectReference)?.reflectedType() as? ClassType ?: return null - - // Preload all classes - compiledData.classes.asSequence() - .filter { !it.isMainClass() } - .forEach { eval.loadClass(Type.getObjectType(it.className), classLoader) } - - return vm.executeWithBreakpointsDisabled { - // Prepare the main class - - val argumentTypes = Type.getArgumentTypes(methodToInvoke.desc) - val args = context.getArgumentsForEvaluation(compiledData.parameters, argumentTypes, compiledData) - .zip(argumentTypes) - .map { (value, type) -> - // Make argument type classes prepared for sure - eval.loadClassByName(type.className, classLoader) - boxOrUnboxArgumentIfNeeded(eval, value, type).asJdiValue(vm, type) - } - - - mainClass.invokeMethod(thread, mainClass.methods().single(), args, invokePolicy) - } - } catch (e: Throwable) { - LOG.error("Unable to evaluate expression with compilation", e) - return null + AnalyzingUtils.checkForSyntacticErrors(codeFragment) + } catch (e: IllegalArgumentException) { + evaluationException(e.message ?: e.toString()) } - } - private fun runEval4j( - context: EvaluationContextImpl, - compiledData: CompiledDataDescriptor, - classLoader: ClassLoaderReference? - ): InterpreterResult { - val virtualMachine = context.debugProcess.virtualMachineProxy.virtualMachine - var resultValue: InterpreterResult? = null + val filesToAnalyze = listOf(codeFragment) + val resolutionFacade = KotlinCacheService.getInstance(codeFragment.project).getResolutionFacade(filesToAnalyze) - // assert [0] with some context - val mainClassBytecode = compiledData.mainClass.bytes + DebugLabelPropertyDescriptorProvider(codeFragment, resolutionFacade.moduleDescriptor, debugProcess).supplyDebugLabels() - ClassReader(mainClassBytecode).accept(object : ClassVisitor(API_VERSION) { - override fun visitMethod( - access: Int, - name: String, - desc: String, - signature: String?, - exceptions: Array? - ): MethodVisitor? { - // Maybe just take the single method from the class, as it is done in 'evaluateWithCompilation' - @Suppress("ConvertToStringTemplate") - if (name == GENERATED_FUNCTION_NAME || name.startsWith(GENERATED_FUNCTION_NAME + "-")) { - val argumentTypes = Type.getArgumentTypes(desc) - val args = context.getArgumentsForEvaluation(compiledData.parameters, argumentTypes, compiledData) + val analysisResult = resolutionFacade.analyzeWithAllCompilerChecks(filesToAnalyze) - return object : MethodNode(Opcodes.API_VERSION, access, name, desc, signature, exceptions) { - override fun visitEnd() { - virtualMachine.executeWithBreakpointsDisabled { - val eval = JDIEval( - virtualMachine, - classLoader ?: 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 - ) - } - } - } - } - - return super.visitMethod(access, name, desc, signature, exceptions) - } - }, 0) - - 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() } + if (analysisResult.isError()) { + evaluationException(analysisResult.error) } + + val bindingContext = analysisResult.bindingContext + + bindingContext.diagnostics + .filter { it.factory !in IGNORED_DIAGNOSTICS } + .firstOrNull { it.severity == Severity.ERROR && it.psiElement.containingFile == codeFragment } + ?.let { evaluationException(DefaultErrorMessages.render(it)) } + + ErrorCheckingResult(bindingContext, analysisResult.moduleDescriptor, Collections.singletonList(codeFragment)) } + } - private fun boxOrUnboxArgumentIfNeeded(eval: JDIEval, argumentValue: Value, parameterType: Type): Value { - val argumentType = argumentValue.asmType + private fun evaluateWithCompilation( + context: ExecutionContext, + compiledData: CompiledDataDescriptor, + classLoader: ClassLoaderReference + ): Value? { + return try { + runEvaluation(context, compiledData, classLoader) { args -> + val mainClassType = context.loadClassType(Type.getObjectType(GENERATED_CLASS_NAME), classLoader) as? ClassType + ?: error("Can not find class \"$GENERATED_CLASS_NAME\"") + val mainMethod = mainClassType.methods().single { it.name() == GENERATED_FUNCTION_NAME } + val returnValue = mainClassType.invokeMethod(context.thread, mainMethod, args, context.invokePolicy) + EvaluatorValueConverter(context).unref(returnValue) + } + } catch (e: Throwable) { + LOG.error("Unable to evaluate expression with compilation", e) + return null + } + } - if (AsmUtil.isPrimitive(parameterType) && !AsmUtil.isPrimitive(argumentType)) { - try { - val unboxedType = AsmUtil.unboxType(argumentType) - if (parameterType == unboxedType) { - return eval.unboxType(argumentValue, parameterType) - } - } catch (ignored: UnsupportedOperationException) { + private fun evaluateWithEval4J( + context: ExecutionContext, + compiledData: CompiledDataDescriptor, + classLoader: ClassLoaderReference? + ): InterpreterResult { + val mainClassBytecode = compiledData.mainClass.bytes + val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, 0) } + val mainMethod = mainClassAsmNode.methods.first { it.name == GENERATED_FUNCTION_NAME } + + return runEvaluation(context, compiledData, classLoader ?: context.evaluationContext.classLoader) { args -> + val eval = JDIEval(context.vm, classLoader, context.thread, context.invokePolicy) + interpreterLoop(mainMethod, makeInitialFrame(mainMethod, args.map { it.asValue() }), eval) + } + } + + private fun runEvaluation( + context: ExecutionContext, + compiledData: CompiledDataDescriptor, + classLoader: ClassLoaderReference?, + block: (List) -> T + ): T { + // Preload additional classes + compiledData.classes + .filter { !it.isMainClass } + .forEach { context.loadClassType(Type.getObjectType(it.className), classLoader) } + + return context.vm.executeWithBreakpointsDisabled { + for (parameterType in compiledData.mainMethodSignature.parameterTypes) { + context.loadClassType(parameterType, classLoader) + } + val args = context.calculateMainMethodCallArguments(compiledData) + block(args) + } + } + + private fun ExecutionContext.calculateMainMethodCallArguments(compiledData: CompiledDataDescriptor): List { + val asmValueParameters = compiledData.mainMethodSignature.parameterTypes + val valueParameters = compiledData.parameters + require(asmValueParameters.size == valueParameters.size) + + val args = valueParameters.zip(asmValueParameters) + val variableFinder = VariableFinder.instance(this) ?: error("Frame map is not available") + + return args.map { (parameter, asmType) -> + val result = variableFinder.find(parameter, asmType) + + if (result == null) { + val name = parameter.debugString + + if (parameter in compiledData.crossingBounds) { + evaluationException("'$name' is not captured") + } else if (parameter.kind == CodeFragmentParameter.Kind.FIELD_VAR) { + evaluationException("Cannot find the backing field '${parameter.name}'") + } else { + throw VariableFinder.variableNotFound(evaluationContext, buildString { + append("Cannot find local variable: name = '").append(name).append("', type = ").append(asmType.className) + }) } } - if (!AsmUtil.isPrimitive(parameterType) && AsmUtil.isPrimitive(argumentType)) { - if (parameterType.descriptor == "Ljava/lang/Object;" || parameterType == AsmUtil.boxType(argumentType)) { - return eval.boxType(argumentValue) - } - } - - return argumentValue + result.value } + } - private fun InterpreterResult.toJdiValue(context: EvaluationContextImpl): com.sun.jdi.Value? { + override fun getModifier() = null + + companion object { + private val IGNORED_DIAGNOSTICS: Set> = Errors.INVISIBLE_REFERENCE_DIAGNOSTICS + + private fun InterpreterResult.toJdiValue(context: ExecutionContext): com.sun.jdi.Value? { val jdiValue = when (this) { is ValueReturned -> result is ExceptionThrown -> { when { this.kind == ExceptionThrown.ExceptionKind.FROM_EVALUATED_CODE -> - exception(InvocationException(this.exception.value as ObjectReference)) + evaluationException(InvocationException(this.exception.value as ObjectReference)) this.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE -> throw exception.value as Throwable else -> - exception(exception.toString()) + evaluationException(exception.toString()) } } - is AbnormalTermination -> exception(message) + is AbnormalTermination -> evaluationException(message) else -> throw IllegalStateException("Unknown result value produced by eval4j") } - val vm = context.debugProcess.virtualMachineProxy.virtualMachine - - val sharedVar = getValueIfSharedVar(jdiValue) - return sharedVar?.value ?: jdiValue.asJdiValue(vm, jdiValue.asmType) + val sharedVar = if ((jdiValue is AbstractValue<*>)) getValueIfSharedVar(jdiValue, context) else null + return sharedVar?.value ?: jdiValue.asJdiValue(context.vm, jdiValue.asmType) } - private fun getValueIfSharedVar(value: Value): VariableFinder.Result? { + private fun getValueIfSharedVar(value: Eval4JValue, context: ExecutionContext): VariableFinder.Result? { val obj = value.obj(value.asmType) as? ObjectReference ?: return null - return VariableFinder.Result(VariableFinder.unwrapRefValue(obj)) + return VariableFinder.Result(EvaluatorValueConverter(context).unref(obj)) } - - private fun ExtractionResult.getParametersForDebugger( - fragment: KtCodeFragment, - context: EvaluationContextImpl - ): List { - return runReadAction { - val valuesForLabels = HashMap() - - val contextElementFile = fragment.context?.containingFile - if (contextElementFile is KtCodeFragment) { - contextElementFile.accept(object : KtTreeVisitorVoid() { - override fun visitProperty(property: KtProperty) { - val value = property.getUserData(KotlinCodeFragmentFactory.LABEL_VARIABLE_VALUE_KEY) - if (value != null) { - valuesForLabels[property.name?.quoteIfNeeded()!!] = value.asValue() - } - } - }) - } - - val parameters = mutableListOf() - val receiver = config.descriptor.receiverParameter - if (receiver != null) { - parameters += Parameter(AsmUtil.THIS + "@" + config.descriptor.name, receiver.getParameterType(true)) - } - - for (param in config.descriptor.parameters) { - val argument = param.argumentText - val paramName = if (argument.startsWith("::")) argument.substring(2) else argument - - val paramDescriptor = param.originalDescriptor - if (paramDescriptor is SyntheticFieldDescriptor) { - val backingFieldName = paramDescriptor.propertyDescriptor.getBackingFieldName() - if (backingFieldName != null) { - val thisObject = context.suspendContext.frameProxy?.thisObject() - val field = thisObject?.referenceType()?.fieldByName(backingFieldName) - - val parameter = if (thisObject != null && field != null) { - Parameter(backingFieldName, param.getParameterType(true), thisObject.getValue(field).asValue()) - } else { - Parameter( - backingFieldName, paramDescriptor.builtIns.unitType, - error = EvaluateException("Can't find a backing field for property ${paramDescriptor.name}") - ) - } - - parameters += parameter - continue - } - } - - parameters += Parameter(paramName, param.getParameterType(true), valuesForLabels[paramName]) - } - - parameters - } - } - - private fun EvaluationContextImpl.getArgumentsForEvaluation( - parameters: List, - parameterTypes: Array, - compiledData: CompiledDataDescriptor - ): List { - val variableFinder = VariableFinder.instance(this) ?: error("No stack frame available") - return parameters.zip(parameterTypes).map { (parameter, type) -> - parameter.error?.let { throw it } - parameter.value?.let { return@map it } - - val name = parameter.callText - val result = variableFinder.find(name, type) - - if (result == null) { - if (name in compiledData.variablesCrossingInlineBounds) { - throw EvaluateExceptionUtil.createEvaluateException("'$name' is not captured") - } else { - throw VariableFinder.variableNotFound(this, buildString { - append("Cannot find local variable: name = '").append(name).append("', type = ").append(type.className) - }) - } - } else { - return@map result.value.asValue() - } - } - } - - private fun createClassFileFactory( - codeFragment: KtCodeFragment, - extractedFunction: KtNamedFunction, - context: EvaluationContextImpl, - parameters: List - ): ClassFileFactory { - return runReadAction { - val fileForDebugger = createFileForDebugger(codeFragment, extractedFunction) - if (LOG.isDebugEnabled) { - LOG.debug("File for eval4j:\n${runReadAction { fileForDebugger.text }}") - } - - val (bindingContext, moduleDescriptor, files) = fileForDebugger.checkForErrors( - true, - codeFragment.getContextContainingFile() - ) - - val generateClassFilter = object : GenerationState.GenerateClassFilter() { - override fun shouldGeneratePackagePart(ktFile: KtFile) = ktFile == fileForDebugger - override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject) = true - override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = - processingClassOrObject.containingKtFile == fileForDebugger - - override fun shouldGenerateScript(script: KtScript) = false - } - - @Suppress("ConstantConditionIf") - val state = GenerationState.Builder( - fileForDebugger.project, - if (!DEBUG_MODE) ClassBuilderFactories.BINARIES else ClassBuilderFactories.TEST, - moduleDescriptor, - bindingContext, - files, - CompilerConfiguration.EMPTY - ).generateDeclaredClassFilter(generateClassFilter).build() - - val variableFinder = VariableFinder.instance(context) ?: error("No stack frame available") - - val extractedFunctionName = extractedFunction.name - ?: error("Extracted function has an empty name: ${extractedFunction.text}") - - extractedFunction.receiverTypeReference?.let { - val name = AsmUtil.getLabeledThisName(extractedFunctionName, LABELED_THIS_PARAMETER, RECEIVER_PARAMETER_NAME) - state.bindingTrace.recordAnonymousType(it, name, variableFinder) - } - - val valueParameters = extractedFunction.valueParameters - for ((paramIndex, param) in parameters.withIndex()) { - val valueParameter = valueParameters[paramIndex] - - val paramRef = valueParameter.typeReference - if (paramRef == null) { - LOG.error( - "Each parameter for extracted function should have a type reference", - Attachment("codeFragment.txt", codeFragment.text), - Attachment("extractedFunction.txt", extractedFunction.text) - ) - - exception("An exception occurs during Evaluate Expression Action") - } - - state.bindingTrace.recordAnonymousType(paramRef, param.callText, variableFinder) - } - - KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION) - - state.factory - } - } - - private fun BindingTrace.recordAnonymousType( - typeReference: KtTypeReference, - localVariableName: String, - variableFinder: VariableFinder - ) { - val paramAnonymousType = typeReference.debugTypeInfo - if (paramAnonymousType != null) { - val declarationDescriptor = paramAnonymousType.constructor.declarationDescriptor - if (declarationDescriptor is ClassDescriptor) { - val lookupResult = variableFinder.find(localVariableName, null) - ?: exception("Couldn't find local variable this in current frame to get classType for anonymous type $paramAnonymousType}") - val localVariable = lookupResult.value.asValue() - - record(CodegenBinding.ASM_TYPE, declarationDescriptor, localVariable.asmType) - if (LOG.isDebugEnabled) { - LOG.debug("Asm type ${localVariable.asmType.className} was recorded for ${declarationDescriptor.name}") - } - } - } - } - - private fun exception(msg: String): Nothing = throw EvaluateExceptionUtil.createEvaluateException(msg) - - private fun exception(e: Throwable): Nothing = throw EvaluateExceptionUtil.createEvaluateException(e) - - private val IGNORED_DIAGNOSTICS: Set> = Errors.INVISIBLE_REFERENCE_DIAGNOSTICS - - // contextFile must be NotNull when analyzeInlineFunctions = true - private fun KtFile.checkForErrors(analyzeInlineFunctions: Boolean = false, contextFile: KtFile? = null): ExtendedAnalysisResult { - return runInReadActionWithWriteActionPriorityWithPCE { - try { - AnalyzingUtils.checkForSyntacticErrors(this) - } catch (e: IllegalArgumentException) { - throw EvaluateExceptionUtil.createEvaluateException(e.message) - } - - val filesToAnalyze = if (contextFile == null) listOf(this) else listOf(this, contextFile) - val resolutionFacade = KotlinCacheService.getInstance(project).getResolutionFacade(filesToAnalyze) - val analysisResult = resolutionFacade.analyzeWithAllCompilerChecks(filesToAnalyze) - - if (analysisResult.isError()) { - exception(analysisResult.error) - } - - val bindingContext = analysisResult.bindingContext - val filteredDiagnostics = bindingContext.diagnostics.filter { it.factory !in IGNORED_DIAGNOSTICS } - filteredDiagnostics.firstOrNull { it.severity == Severity.ERROR }?.let { - if (it.psiElement.containingFile == this) { - exception(DefaultErrorMessages.render(it)) - } - } - - if (analyzeInlineFunctions) { - val (newBindingContext, files) = DebuggerUtils.analyzeInlinedFunctions(resolutionFacade, this, false) - ExtendedAnalysisResult(newBindingContext, analysisResult.moduleDescriptor, files) - } else { - ExtendedAnalysisResult(bindingContext, analysisResult.moduleDescriptor, Collections.singletonList(this)) - } - } - } - - private data class ExtendedAnalysisResult( - val bindingContext: BindingContext, - val moduleDescriptor: ModuleDescriptor, - val files: List - ) } } -private const val template = """ -@file:kotlin.jvm.JvmName("$GENERATED_CLASS_NAME") -!PACKAGE! - -!IMPORT_LIST! - -!FUNCTION! -""" - -private fun createFileForDebugger( - codeFragment: KtCodeFragment, - extractedFunction: KtNamedFunction -): KtFile { - val containingContextFile = codeFragment.getContextContainingFile() - val importsFromContextFile = containingContextFile?.importList?.let { it.text + "\n" } ?: "" - - var fileText = template.replace( - "!IMPORT_LIST!", - importsFromContextFile + codeFragment.importsToString().split(KtCodeFragment.IMPORT_SEPARATOR).joinToString("\n") - ) - - val packageFromContextFile = containingContextFile?.packageFqName?.let { - if (!it.isRoot) "package ${it.quoteSegmentsIfNeeded()}" else "" - } ?: "" - fileText = fileText.replace("!PACKAGE!", packageFromContextFile) - fileText = fileText.replace("!FUNCTION!", extractedFunction.text!!) - - val jetFile = codeFragment.createKtFile("debugFile.kt", fileText) - jetFile.suppressDiagnosticsInDebugMode = true - - val list = jetFile.declarations - val function = list[0] as KtNamedFunction - - function.receiverTypeReference?.debugTypeInfo = extractedFunction.receiverTypeReference?.debugTypeInfo - - for ((newParam, oldParam) in function.valueParameters.zip(extractedFunction.valueParameters)) { - newParam.typeReference?.debugTypeInfo = oldParam.typeReference?.debugTypeInfo - } - - function.typeReference?.debugTypeInfo = extractedFunction.typeReference?.debugTypeInfo - - return jetFile -} - -private fun PsiElement.createKtFile(fileName: String, fileText: String): KtFile { - // Not using KtPsiFactory because we need a virtual file attached to the KtFile - val virtualFile = LightVirtualFile(fileName, KotlinLanguage.INSTANCE, fileText) - virtualFile.charset = CharsetToolkit.UTF8_CHARSET - val jetFile = (PsiFileFactory.getInstance(project) as PsiFileFactoryImpl) - .trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile - jetFile.analysisContext = this - return jetFile -} - internal fun SuspendContext.getInvokePolicy(): Int { return if (suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD) ObjectReference.INVOKE_SINGLE_THREADED else 0 } -fun Type.getClassDescriptor(scope: GlobalSearchScope): ClassDescriptor? { +fun Type.getClassDescriptor( + scope: GlobalSearchScope, + mapBuiltIns: Boolean = true, + moduleDescriptor: ModuleDescriptor = DefaultBuiltIns.Instance.builtInsModule +): ClassDescriptor? { if (AsmUtil.isPrimitive(this)) return null val jvmName = JvmClassName.byInternalName(internalName).fqNameForClassNameWithoutDollars - // TODO: use the correct built-ins from the module instead of DefaultBuiltIns here - JavaToKotlinClassMap.mapJavaToKotlin(jvmName)?.let( - DefaultBuiltIns.Instance.builtInsModule::findClassAcrossModuleDependencies - )?.let { return it } + if (mapBuiltIns) { + val mappedName = JavaToKotlinClassMap.mapJavaToKotlin(jvmName) + if (mappedName != null) { + moduleDescriptor.findClassAcrossModuleDependencies(mappedName)?.let { return it } + } + } return runReadAction { val classes = JavaPsiFacade.getInstance(scope.project).findClasses(jvmName.asString(), scope) @@ -777,3 +418,31 @@ fun Type.getClassDescriptor(scope: GlobalSearchScope): ClassDescriptor? { } } } + +private 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 isSpecialException(th: Throwable): Boolean { + return when (th) { + is ClassNotPreparedException, + is InternalException, + is AbsentInformationException, + is ClassNotLoadedException, + is IncompatibleThreadStateException, + is InconsistentDebugInfoException, + is ObjectCollectedException, + is VMDisconnectedException -> true + else -> false + } +} + +private fun evaluationException(msg: String): Nothing = throw EvaluateExceptionUtil.createEvaluateException(msg) +private fun evaluationException(e: Throwable): Nothing = throw EvaluateExceptionUtil.createEvaluateException(e) \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/ScopeCheckerForEvaluator.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/ScopeCheckerForEvaluator.kt deleted file mode 100644 index 20826ec4c37..00000000000 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/ScopeCheckerForEvaluator.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license - * that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.idea.debugger.evaluate - -import com.intellij.psi.PsiElement -import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility -import org.jetbrains.kotlin.descriptors.Visibilities -import org.jetbrains.kotlin.idea.util.application.runReadAction -import org.jetbrains.kotlin.load.kotlin.toSourceElement -import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.isAncestor -import org.jetbrains.kotlin.resolve.BindingContext -import org.jetbrains.kotlin.resolve.source.getPsi - -object ScopeCheckerForEvaluator { - fun checkScopes(bindingContext: BindingContext, codeFragment: KtCodeFragment): Set { - val result = hashSetOf() - - codeFragment.accept(object : KtTreeVisitor() { - override fun visitSimpleNameExpression(expression: KtSimpleNameExpression, data: Unit?): Void? { - val target = bindingContext[BindingContext.REFERENCE_TARGET, expression] - if (target is DeclarationDescriptorWithVisibility && target.visibility == Visibilities.LOCAL) { - val declarationPsiElement = target.toSourceElement.getPsi() - if (declarationPsiElement != null) { - runReadAction { - if (doesCrossInlineBounds(bindingContext, expression, declarationPsiElement)) { - result.add(expression.getReferencedName()) - } - } - } - } - - return null - } - }, Unit) - - return result - } - - private fun doesCrossInlineBounds(bindingContext: BindingContext, reference: KtSimpleNameExpression, declaration: PsiElement): Boolean { - val declarationParent = declaration.parent ?: return false - var currentParent: PsiElement? = reference.parent?.takeIf { it.isInside(declarationParent) } ?: return false - - while (currentParent != null && currentParent != declarationParent) { - if (currentParent is KtFunctionLiteral) { - val functionDescriptor = bindingContext[BindingContext.FUNCTION, currentParent] - if (functionDescriptor != null && !functionDescriptor.isInline) { - return true - } - } - - currentParent = when (currentParent) { - is KtCodeFragment -> currentParent.context - else -> currentParent.parent - } - } - - return false - } - - private tailrec fun PsiElement.isInside(parent: PsiElement): Boolean { - if (parent.isAncestor(this)) { - return true - } - - val context = (this.containingFile as? KtCodeFragment)?.context ?: return false - return context.isInside(parent) - } -} \ No newline at end of file diff --git a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/ClassLoadingAdapter.kt b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/ClassLoadingAdapter.kt index 8f73c643837..c4b6d450bf7 100644 --- a/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/ClassLoadingAdapter.kt +++ b/idea/idea-jvm/src/org/jetbrains/kotlin/idea/debugger/evaluate/classLoading/ClassLoadingAdapter.kt @@ -23,6 +23,7 @@ import com.sun.jdi.ArrayReference import com.sun.jdi.ArrayType import com.sun.jdi.ClassLoaderReference import com.sun.jdi.Value +import org.jetbrains.kotlin.idea.debugger.evaluate.GENERATED_FUNCTION_NAME import org.jetbrains.org.objectweb.asm.ClassReader import org.jetbrains.org.objectweb.asm.Label import org.jetbrains.org.objectweb.asm.tree.* @@ -38,7 +39,7 @@ interface ClassLoadingAdapter { ) fun loadClasses(context: EvaluationContextImpl, classes: Collection): ClassLoaderReference? { - val mainClass = classes.firstOrNull { it.isMainClass() } ?: return null + val mainClass = classes.firstOrNull { it.isMainClass } ?: return null var info = ClassInfoForEvaluator(containsAdditionalClasses = classes.size > 1) if (!info.containsAdditionalClasses) { @@ -64,8 +65,8 @@ interface ClassLoadingAdapter { } private fun analyzeClass(classToLoad: ClassToLoad, info: ClassInfoForEvaluator): ClassInfoForEvaluator { - val classNode = ClassNode().apply { ClassReader(classToLoad.bytes).accept(this, ClassReader.EXPAND_FRAMES) } - val methodToRun = classNode.methods.single() + val classNode = ClassNode().apply { ClassReader(classToLoad.bytes).accept(this, 0) } + val methodToRun = classNode.methods.single { it.name == GENERATED_FUNCTION_NAME } val visitedLabels = hashSetOf