New evaluator that doesn't depend on the 'extract function' refactoring (KT-28192, KT-25220, KT-25222, KT-21650)
This commit is contained in:
@@ -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<IParameter>
|
||||||
|
) {
|
||||||
|
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<KtCodeFragment>(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, "<init>", "()V", null, null)
|
||||||
|
|
||||||
|
if (state.classBuilderMode.generateBodies) {
|
||||||
|
mv.visitCode()
|
||||||
|
|
||||||
|
val iv = InstructionAdapter(mv)
|
||||||
|
iv.load(0, info.classType)
|
||||||
|
iv.invokespecial("java/lang/Object", "<init>", "()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<CodeFragmentCodegenInfo>("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<CalculatedParameter>()
|
||||||
|
|
||||||
|
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<CalculatedParameter>, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.backend.common.CodegenUtil;
|
|||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
||||||
import org.jetbrains.kotlin.codegen.binding.CalculatedClosure;
|
import org.jetbrains.kotlin.codegen.binding.CalculatedClosure;
|
||||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
|
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.context.*;
|
||||||
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenForLambda;
|
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenForLambda;
|
||||||
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt;
|
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt;
|
||||||
@@ -1759,6 +1760,16 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
|||||||
StackValue intrinsicResult = applyIntrinsic(descriptor, IntrinsicPropertyGetter.class, resolvedCall, receiver);
|
StackValue intrinsicResult = applyIntrinsic(descriptor, IntrinsicPropertyGetter.class, resolvedCall, receiver);
|
||||||
if (intrinsicResult != null) return intrinsicResult;
|
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) {
|
if (descriptor instanceof PropertyDescriptor) {
|
||||||
PropertyDescriptor propertyDescriptor = (PropertyDescriptor) descriptor;
|
PropertyDescriptor propertyDescriptor = (PropertyDescriptor) descriptor;
|
||||||
|
|
||||||
@@ -2132,6 +2143,14 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
skipPropertyAccessors = forceField;
|
skipPropertyAccessors = forceField;
|
||||||
|
|
||||||
|
if (JvmCodegenUtil.isDebuggerContext(context)
|
||||||
|
&& Visibilities.isPrivate(propertyDescriptor.getVisibility())
|
||||||
|
&& bindingContext.get(BACKING_FIELD_REQUIRED, propertyDescriptor) == Boolean.TRUE
|
||||||
|
) {
|
||||||
|
skipPropertyAccessors = true;
|
||||||
|
}
|
||||||
|
|
||||||
ownerDescriptor = isBackingFieldMovedFromCompanion ? containingDeclaration : propertyDescriptor;
|
ownerDescriptor = isBackingFieldMovedFromCompanion ? containingDeclaration : propertyDescriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2772,7 +2791,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private StackValue generateExtensionReceiver(@NotNull CallableDescriptor descriptor) {
|
public StackValue generateExtensionReceiver(@NotNull CallableDescriptor descriptor) {
|
||||||
ReceiverParameterDescriptor parameter = descriptor.getExtensionReceiverParameter();
|
ReceiverParameterDescriptor parameter = descriptor.getExtensionReceiverParameter();
|
||||||
if (myFrameMap.getIndex(parameter) != -1) {
|
if (myFrameMap.getIndex(parameter) != -1) {
|
||||||
KotlinType type = parameter.getReturnType();
|
KotlinType type = parameter.getReturnType();
|
||||||
@@ -2891,24 +2910,40 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
|||||||
|
|
||||||
forceOuter = false;
|
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
|
//for constructor super call we should access to outer instance through parameter in locals, in other cases through field for captured outer
|
||||||
if (inStartConstructorContext) {
|
if (inStartConstructorContext) {
|
||||||
result = cur.getOuterExpression(result, false);
|
result = cur.getOuterExpression(result, false);
|
||||||
cur = getNotNullParentContextForMethod(cur);
|
cur = getNotNullParentContextForMethod(cur);
|
||||||
|
enclosingContext = cur.getEnclosingClassContext();
|
||||||
inStartConstructorContext = false;
|
inStartConstructorContext = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
cur = getNotNullParentContextForMethod(cur);
|
cur = getNotNullParentContextForMethod(cur);
|
||||||
// for now the script codegen only passes this branch, since the method context for script constructor is defined using function context
|
enclosingContext = cur.getEnclosingClassContext();
|
||||||
if (cur instanceof ScriptContext && !(thisOrOuterClass instanceof ScriptDescriptor)) {
|
|
||||||
return ((ScriptContext) cur).getOuterReceiverExpression(result, thisOrOuterClass);
|
if (!(thisOrOuterClass instanceof ScriptDescriptor)) {
|
||||||
}
|
if (cur instanceof ScriptLikeContext) {
|
||||||
else {
|
/* for now the script codegen only passes this branch,
|
||||||
result = cur.getOuterExpression(result, false);
|
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();
|
throw new UnsupportedOperationException();
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
|
|||||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
|
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
|
||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
|
||||||
import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver;
|
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.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor;
|
||||||
import org.jetbrains.kotlin.types.KotlinType;
|
import org.jetbrains.kotlin.types.KotlinType;
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions;
|
import org.jetbrains.kotlin.util.OperatorNameConventions;
|
||||||
@@ -242,8 +243,20 @@ public class JvmCodegenUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isDebuggerContext(@NotNull CodegenContext context) {
|
public static boolean isDebuggerContext(@NotNull CodegenContext context) {
|
||||||
KtFile file = DescriptorToSourceUtils.getContainingFile(context.getContextDescriptor());
|
PsiFile file = null;
|
||||||
return file != null && CodeFragmentUtilKt.getSuppressDiagnosticsInDebugMode(file);
|
|
||||||
|
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
|
@Nullable
|
||||||
|
|||||||
@@ -32,10 +32,7 @@ import org.jetbrains.kotlin.fileClasses.JvmFileClassInfo;
|
|||||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil;
|
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil;
|
||||||
import org.jetbrains.kotlin.name.FqName;
|
import org.jetbrains.kotlin.name.FqName;
|
||||||
import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
|
import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
|
||||||
import org.jetbrains.kotlin.psi.KtClassOrObject;
|
import org.jetbrains.kotlin.psi.*;
|
||||||
import org.jetbrains.kotlin.psi.KtDeclaration;
|
|
||||||
import org.jetbrains.kotlin.psi.KtFile;
|
|
||||||
import org.jetbrains.kotlin.psi.KtScript;
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
|
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||||
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker;
|
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker;
|
||||||
@@ -98,30 +95,40 @@ public class PackageCodegenImpl implements PackageCodegen {
|
|||||||
Type fileClassType = AsmUtil.asmTypeByFqNameWithoutInnerClasses(fileClassInfo.getFileClassFqName());
|
Type fileClassType = AsmUtil.asmTypeByFqNameWithoutInnerClasses(fileClassInfo.getFileClassFqName());
|
||||||
PackageContext packagePartContext = state.getRootContext().intoPackagePart(packageFragment, fileClassType, file);
|
PackageContext packagePartContext = state.getRootContext().intoPackagePart(packageFragment, fileClassType, file);
|
||||||
|
|
||||||
List<KtClassOrObject> 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<KtClassOrObject> classOrObjects = new ArrayList<>();
|
||||||
|
|
||||||
for (KtDeclaration declaration : file.getDeclarations()) {
|
for (KtDeclaration declaration : file.getDeclarations()) {
|
||||||
if (declaration instanceof KtClassOrObject) {
|
if (declaration instanceof KtClassOrObject) {
|
||||||
ClassDescriptor descriptor = state.getBindingContext().get(BindingContext.CLASS, declaration);
|
ClassDescriptor descriptor = state.getBindingContext().get(BindingContext.CLASS, declaration);
|
||||||
if (PsiUtilsKt.hasExpectModifier(declaration) &&
|
if (PsiUtilsKt.hasExpectModifier(declaration) &&
|
||||||
(descriptor == null || !ExpectedActualDeclarationChecker.shouldGenerateExpectClass(descriptor))) {
|
(descriptor == null || !ExpectedActualDeclarationChecker.shouldGenerateExpectClass(descriptor))) {
|
||||||
continue;
|
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().shouldGenerateScript(script)) {
|
||||||
if (state.getGenerateDeclaredClassFilter().shouldGenerateClass(classOrObject)) {
|
ScriptCodegen.createScriptCodegen(script, state, packagePartContext).generate();
|
||||||
classOrObjects.add(classOrObject);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (declaration instanceof KtScript) {
|
|
||||||
KtScript script = (KtScript) declaration;
|
|
||||||
|
|
||||||
if (state.getGenerateDeclaredClassFilter().shouldGenerateScript(script)) {
|
generateClassesAndObjectsInFile(classOrObjects, packagePartContext);
|
||||||
ScriptCodegen.createScriptCodegen(script, state, packagePartContext).generate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
generateClassesAndObjectsInFile(classOrObjects, packagePartContext);
|
|
||||||
|
|
||||||
if (!state.getGenerateDeclaredClassFilter().shouldGeneratePackagePart(file)) return;
|
if (!state.getGenerateDeclaredClassFilter().shouldGeneratePackagePart(file)) return;
|
||||||
|
|
||||||
|
|||||||
+9
-1
@@ -222,7 +222,15 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visitKtFile(@NotNull KtFile file) {
|
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);
|
visitKtElement(file);
|
||||||
nameStack.pop();
|
nameStack.pop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
package org.jetbrains.kotlin.codegen.binding;
|
package org.jetbrains.kotlin.codegen.binding;
|
||||||
|
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
|
import com.intellij.psi.PsiElement;
|
||||||
|
import com.intellij.psi.PsiFile;
|
||||||
import kotlin.collections.CollectionsKt;
|
import kotlin.collections.CollectionsKt;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
@@ -90,6 +92,15 @@ public class CodegenBinding {
|
|||||||
CodegenAnnotatingVisitor visitor = new CodegenAnnotatingVisitor(state);
|
CodegenAnnotatingVisitor visitor = new CodegenAnnotatingVisitor(state);
|
||||||
for (KtFile file : allFilesInPackages(state.getBindingContext(), state.getFiles())) {
|
for (KtFile file : allFilesInPackages(state.getBindingContext(), state.getFiles())) {
|
||||||
file.accept(visitor);
|
file.accept(visitor);
|
||||||
|
if (file instanceof KtCodeFragment) {
|
||||||
|
PsiElement context = file.getContext();
|
||||||
|
if (context != null) {
|
||||||
|
PsiFile contextFile = context.getContainingFile();
|
||||||
|
if (contextFile != null) {
|
||||||
|
contextFile.accept(visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ fun ClassFileFactory.getClassFiles(): Iterable<OutputFile> {
|
|||||||
return asList().filterClassFiles()
|
return asList().filterClassFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun List<OutputFile>.filterClassFiles(): Iterable<OutputFile> {
|
fun List<OutputFile>.filterClassFiles(): List<OutputFile> {
|
||||||
return filter { it.relativePath.endsWith(".class") }
|
return filter { it.relativePath.endsWith(".class") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
package org.jetbrains.kotlin.codegen.context
|
package org.jetbrains.kotlin.codegen.context
|
||||||
|
|
||||||
import org.jetbrains.kotlin.codegen.FieldInfo
|
import org.jetbrains.kotlin.codegen.FieldInfo
|
||||||
import org.jetbrains.kotlin.codegen.OwnerKind
|
|
||||||
import org.jetbrains.kotlin.codegen.StackValue
|
import org.jetbrains.kotlin.codegen.StackValue
|
||||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
@@ -35,12 +34,12 @@ import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
|||||||
import org.jetbrains.org.objectweb.asm.Type
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
|
|
||||||
class ScriptContext(
|
class ScriptContext(
|
||||||
val typeMapper: KotlinTypeMapper,
|
typeMapper: KotlinTypeMapper,
|
||||||
val scriptDescriptor: ScriptDescriptor,
|
val scriptDescriptor: ScriptDescriptor,
|
||||||
val earlierScripts: List<ScriptDescriptor>,
|
val earlierScripts: List<ScriptDescriptor>,
|
||||||
contextDescriptor: ClassDescriptor,
|
contextDescriptor: ClassDescriptor,
|
||||||
parentContext: CodegenContext<*>?
|
parentContext: CodegenContext<*>?
|
||||||
) : ClassContext(typeMapper, contextDescriptor, OwnerKind.IMPLEMENTATION, parentContext, null) {
|
) : ScriptLikeContext(typeMapper, contextDescriptor, parentContext) {
|
||||||
val lastStatement: KtExpression?
|
val lastStatement: KtExpression?
|
||||||
|
|
||||||
val resultFieldInfo: FieldInfo
|
val resultFieldInfo: FieldInfo
|
||||||
@@ -89,7 +88,7 @@ class ScriptContext(
|
|||||||
|
|
||||||
fun getProvidedPropertyType(index: Int): Type = typeMapper.mapType(scriptDescriptor.scriptProvidedProperties[index].type)
|
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) {
|
if (thisOrOuterClass.containingDeclaration == scriptDescriptor) {
|
||||||
return prefix ?: StackValue.LOCAL_0
|
return prefix ?: StackValue.LOCAL_0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
|
|||||||
import org.jetbrains.kotlin.modules.TargetId
|
import org.jetbrains.kotlin.modules.TargetId
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
|
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.psi.KtScript
|
import org.jetbrains.kotlin.psi.KtScript
|
||||||
import org.jetbrains.kotlin.resolve.*
|
import org.jetbrains.kotlin.resolve.*
|
||||||
@@ -120,18 +121,17 @@ class GenerationState private constructor(
|
|||||||
abstract fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean
|
abstract fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean
|
||||||
abstract fun shouldGeneratePackagePart(ktFile: KtFile): Boolean
|
abstract fun shouldGeneratePackagePart(ktFile: KtFile): Boolean
|
||||||
abstract fun shouldGenerateScript(script: KtScript): Boolean
|
abstract fun shouldGenerateScript(script: KtScript): Boolean
|
||||||
|
abstract fun shouldGenerateCodeFragment(script: KtCodeFragment): Boolean
|
||||||
open fun shouldGenerateClassMembers(processingClassOrObject: KtClassOrObject) = shouldGenerateClass(processingClassOrObject)
|
open fun shouldGenerateClassMembers(processingClassOrObject: KtClassOrObject) = shouldGenerateClass(processingClassOrObject)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmField
|
@JvmField
|
||||||
val GENERATE_ALL: GenerateClassFilter = object : GenerateClassFilter() {
|
val GENERATE_ALL: GenerateClassFilter = object : GenerateClassFilter() {
|
||||||
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject): Boolean = true
|
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject): Boolean = true
|
||||||
|
|
||||||
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean = true
|
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean = true
|
||||||
|
|
||||||
override fun shouldGenerateScript(script: KtScript): Boolean = true
|
override fun shouldGenerateScript(script: KtScript): Boolean = true
|
||||||
|
|
||||||
override fun shouldGeneratePackagePart(ktFile: KtFile): Boolean = true
|
override fun shouldGeneratePackagePart(ktFile: KtFile): Boolean = true
|
||||||
|
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -30,10 +30,7 @@ import org.jetbrains.kotlin.codegen.CompilationErrorHandler
|
|||||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
|
||||||
import org.jetbrains.kotlin.psi.KtPsiUtil
|
|
||||||
import org.jetbrains.kotlin.psi.KtScript
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
|
|
||||||
@@ -199,6 +196,7 @@ private class ClassFilterForClassOrObject(private val classOrObject: KtClassOrOb
|
|||||||
= shouldGenerateClassMembers(processingClassOrObject) || processingClassOrObject.isAncestor(classOrObject, true)
|
= shouldGenerateClassMembers(processingClassOrObject) || processingClassOrObject.isAncestor(classOrObject, true)
|
||||||
|
|
||||||
override fun shouldGenerateScript(script: KtScript) = PsiTreeUtil.isAncestor(script, classOrObject, false)
|
override fun shouldGenerateScript(script: KtScript) = PsiTreeUtil.isAncestor(script, classOrObject, false)
|
||||||
|
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
|
||||||
}
|
}
|
||||||
|
|
||||||
object ClassFilterForFacade : GenerationState.GenerateClassFilter() {
|
object ClassFilterForFacade : GenerationState.GenerateClassFilter() {
|
||||||
@@ -206,6 +204,7 @@ object ClassFilterForFacade : GenerationState.GenerateClassFilter() {
|
|||||||
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = KtPsiUtil.isLocal(processingClassOrObject)
|
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = KtPsiUtil.isLocal(processingClassOrObject)
|
||||||
override fun shouldGeneratePackagePart(ktFile: KtFile) = true
|
override fun shouldGeneratePackagePart(ktFile: KtFile) = true
|
||||||
override fun shouldGenerateScript(script: KtScript) = false
|
override fun shouldGenerateScript(script: KtScript) = false
|
||||||
|
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ClassFilterForScript(val script: KtScript) : GenerationState.GenerateClassFilter() {
|
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 shouldGeneratePackagePart(ktFile: KtFile): Boolean = script.containingKtFile === ktFile
|
||||||
|
|
||||||
override fun shouldGenerateScript(script: KtScript): Boolean = this.script === script
|
override fun shouldGenerateScript(script: KtScript): Boolean = this.script === script
|
||||||
|
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -337,6 +337,12 @@ abstract class KtLightClassForSourceDeclaration(protected val classOrObject: KtC
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createNoCache(classOrObject: KtClassOrObject): KtLightClassForSourceDeclaration? {
|
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()) {
|
if (classOrObject.shouldNotBeVisibleAsLightClass()) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,3 +199,5 @@ abstract class KtCodeFragment(
|
|||||||
private val LOG = Logger.getInstance(KtCodeFragment::class.java)
|
private val LOG = Logger.getInstance(KtCodeFragment::class.java)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var KtCodeFragment.externalDescriptors: List<DeclarationDescriptor>? by CopyablePsiUserDataProperty(Key.create("EXTERNAL_DESCRIPTORS"))
|
||||||
@@ -153,6 +153,9 @@ fun KtExpression.getQualifiedExpressionForReceiverOrThis(): KtExpression {
|
|||||||
fun KtExpression.isDotReceiver(): Boolean =
|
fun KtExpression.isDotReceiver(): Boolean =
|
||||||
(parent as? KtDotQualifiedExpression)?.receiverExpression == this
|
(parent as? KtDotQualifiedExpression)?.receiverExpression == this
|
||||||
|
|
||||||
|
fun KtExpression.isDotSelector(): Boolean =
|
||||||
|
(parent as? KtDotQualifiedExpression)?.selectorExpression == this
|
||||||
|
|
||||||
fun KtExpression.getPossiblyQualifiedCallExpression(): KtCallExpression? =
|
fun KtExpression.getPossiblyQualifiedCallExpression(): KtCallExpression? =
|
||||||
((this as? KtQualifiedExpression)?.selectorExpression ?: this) as? KtCallExpression
|
((this as? KtQualifiedExpression)?.selectorExpression ?: this) as? KtCallExpression
|
||||||
|
|
||||||
|
|||||||
@@ -636,10 +636,6 @@ fun main(args: Array<String>) {
|
|||||||
model("debugger/smartStepInto")
|
model("debugger/smartStepInto")
|
||||||
}
|
}
|
||||||
|
|
||||||
testClass<AbstractBeforeExtractFunctionInsertionTest> {
|
|
||||||
model("debugger/insertBeforeExtractFunction", extension = "kt")
|
|
||||||
}
|
|
||||||
|
|
||||||
testClass<AbstractKotlinSteppingTest> {
|
testClass<AbstractKotlinSteppingTest> {
|
||||||
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 = "doStepIntoTest", testClassName = "StepInto")
|
||||||
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto")
|
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto")
|
||||||
|
|||||||
@@ -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.AbstractLiteralTextToKotlinCopyPasteTest
|
||||||
import org.jetbrains.kotlin.idea.conversion.copy.AbstractTextJavaToKotlinCopyPasteConversionTest
|
import org.jetbrains.kotlin.idea.conversion.copy.AbstractTextJavaToKotlinCopyPasteConversionTest
|
||||||
import org.jetbrains.kotlin.idea.coverage.AbstractKotlinCoverageOutputFilesTest
|
import org.jetbrains.kotlin.idea.coverage.AbstractKotlinCoverageOutputFilesTest
|
||||||
import org.jetbrains.kotlin.idea.debugger.AbstractBeforeExtractFunctionInsertionTest
|
import org.jetbrains.kotlin.idea.debugger.*
|
||||||
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.evaluate.*
|
import org.jetbrains.kotlin.idea.debugger.evaluate.*
|
||||||
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToDecompiledLibraryTest
|
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToDecompiledLibraryTest
|
||||||
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToLibrarySourceTest
|
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToLibrarySourceTest
|
||||||
@@ -627,10 +624,6 @@ fun main(args: Array<String>) {
|
|||||||
model("debugger/smartStepInto")
|
model("debugger/smartStepInto")
|
||||||
}
|
}
|
||||||
|
|
||||||
testClass<AbstractBeforeExtractFunctionInsertionTest> {
|
|
||||||
model("debugger/insertBeforeExtractFunction", extension = "kt")
|
|
||||||
}
|
|
||||||
|
|
||||||
testClass<AbstractKotlinSteppingTest> {
|
testClass<AbstractKotlinSteppingTest> {
|
||||||
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 = "doStepIntoTest", testClassName = "StepInto")
|
||||||
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto")
|
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto")
|
||||||
|
|||||||
@@ -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.AbstractLiteralTextToKotlinCopyPasteTest
|
||||||
import org.jetbrains.kotlin.idea.conversion.copy.AbstractTextJavaToKotlinCopyPasteConversionTest
|
import org.jetbrains.kotlin.idea.conversion.copy.AbstractTextJavaToKotlinCopyPasteConversionTest
|
||||||
import org.jetbrains.kotlin.idea.coverage.AbstractKotlinCoverageOutputFilesTest
|
import org.jetbrains.kotlin.idea.coverage.AbstractKotlinCoverageOutputFilesTest
|
||||||
import org.jetbrains.kotlin.idea.debugger.AbstractBeforeExtractFunctionInsertionTest
|
import org.jetbrains.kotlin.idea.debugger.*
|
||||||
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.evaluate.*
|
import org.jetbrains.kotlin.idea.debugger.evaluate.*
|
||||||
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToDecompiledLibraryTest
|
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToDecompiledLibraryTest
|
||||||
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToLibrarySourceTest
|
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToLibrarySourceTest
|
||||||
@@ -619,10 +616,6 @@ fun main(args: Array<String>) {
|
|||||||
model("debugger/smartStepInto")
|
model("debugger/smartStepInto")
|
||||||
}
|
}
|
||||||
|
|
||||||
testClass<AbstractBeforeExtractFunctionInsertionTest> {
|
|
||||||
model("debugger/insertBeforeExtractFunction", extension = "kt")
|
|
||||||
}
|
|
||||||
|
|
||||||
testClass<AbstractKotlinSteppingTest> {
|
testClass<AbstractKotlinSteppingTest> {
|
||||||
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 = "doStepIntoTest", testClassName = "StepInto")
|
||||||
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto")
|
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto")
|
||||||
|
|||||||
@@ -616,10 +616,6 @@ fun main(args: Array<String>) {
|
|||||||
model("debugger/smartStepInto")
|
model("debugger/smartStepInto")
|
||||||
}
|
}
|
||||||
|
|
||||||
testClass<AbstractBeforeExtractFunctionInsertionTest> {
|
|
||||||
model("debugger/insertBeforeExtractFunction", extension = "kt")
|
|
||||||
}
|
|
||||||
|
|
||||||
testClass<AbstractKotlinSteppingTest> {
|
testClass<AbstractKotlinSteppingTest> {
|
||||||
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 = "doStepIntoTest", testClassName = "StepInto")
|
||||||
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto")
|
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto")
|
||||||
|
|||||||
+110
-83
@@ -16,18 +16,23 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.caches.resolve
|
package org.jetbrains.kotlin.idea.caches.resolve
|
||||||
|
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
|
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
|
||||||
import org.jetbrains.kotlin.idea.project.ResolveElementCache
|
import org.jetbrains.kotlin.idea.project.ResolveElementCache
|
||||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
|
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||||
import org.jetbrains.kotlin.resolve.*
|
import org.jetbrains.kotlin.resolve.*
|
||||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoAfter
|
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoAfter
|
||||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
|
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
|
||||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
|
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.LexicalScope
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.utils.ErrorLexicalScope
|
||||||
import org.jetbrains.kotlin.resolve.scopes.utils.addImportingScopes
|
import org.jetbrains.kotlin.resolve.scopes.utils.addImportingScopes
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices
|
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices
|
||||||
@@ -43,132 +48,154 @@ class CodeFragmentAnalyzer(
|
|||||||
@set:Inject // component dependency cycle
|
@set:Inject // component dependency cycle
|
||||||
lateinit var resolveElementCache: ResolveElementCache
|
lateinit var resolveElementCache: ResolveElementCache
|
||||||
|
|
||||||
fun analyzeCodeFragment(codeFragment: KtCodeFragment, trace: BindingTrace, bodyResolveMode: BodyResolveMode): BindingTrace {
|
fun analyzeCodeFragment(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): BindingTrace {
|
||||||
val codeFragmentElement = codeFragment.getContentElement()
|
val contextAnalysisResult = analyzeCodeFragmentContext(codeFragment, bodyResolveMode)
|
||||||
|
return doAnalyzeCodeFragment(codeFragment, contextAnalysisResult)
|
||||||
|
}
|
||||||
|
|
||||||
val (scopeForContextElement, dataFlowInfo, newBindingContext) = doAnalyzeCoreFragment(codeFragment) {
|
private fun doAnalyzeCodeFragment(codeFragment: KtCodeFragment, contextAnalysisResult: ContextAnalysisResult): BindingTrace {
|
||||||
resolveElementCache!!.resolveToElements(listOf(it), bodyResolveMode)
|
val (bindingContext, scope, dataFlowInfo) = contextAnalysisResult
|
||||||
} ?: return trace
|
val bindingTrace = DelegatingBindingTrace(bindingContext, "For code fragment analysis")
|
||||||
|
|
||||||
val newBindingTrace = DelegatingBindingTrace(newBindingContext, "For code fragment analysis")
|
when (val contentElement = codeFragment.getContentElement()) {
|
||||||
|
|
||||||
when (codeFragmentElement) {
|
|
||||||
is KtExpression -> {
|
is KtExpression -> {
|
||||||
PreliminaryDeclarationVisitor.createForExpression(
|
PreliminaryDeclarationVisitor.createForExpression(
|
||||||
codeFragmentElement, newBindingTrace,
|
contentElement, bindingTrace,
|
||||||
expressionTypingServices.languageVersionSettings
|
expressionTypingServices.languageVersionSettings
|
||||||
)
|
)
|
||||||
|
|
||||||
expressionTypingServices.getTypeInfo(
|
expressionTypingServices.getTypeInfo(
|
||||||
scopeForContextElement,
|
scope, contentElement, TypeUtils.NO_EXPECTED_TYPE,
|
||||||
codeFragmentElement,
|
dataFlowInfo, bindingTrace, false
|
||||||
TypeUtils.NO_EXPECTED_TYPE,
|
|
||||||
dataFlowInfo,
|
|
||||||
newBindingTrace,
|
|
||||||
false
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is KtTypeReference -> {
|
is KtTypeReference -> {
|
||||||
val context = TypeResolutionContext(
|
val context = TypeResolutionContext(
|
||||||
scopeForContextElement,
|
scope, bindingTrace,
|
||||||
newBindingTrace,
|
true, true, codeFragment.suppressDiagnosticsInDebugMode()
|
||||||
true,
|
|
||||||
true,
|
|
||||||
codeFragment.suppressDiagnosticsInDebugMode()
|
|
||||||
).noBareTypes()
|
).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 data class ContextAnalysisResult(
|
||||||
private fun KtElement.correctContextForElement(): KtElement {
|
val bindingContext: BindingContext,
|
||||||
return when (this) {
|
val scope: LexicalScope,
|
||||||
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<LexicalScope, DataFlowInfo, BindingContext>? {
|
|
||||||
val context = codeFragment.context
|
|
||||||
|
|
||||||
val scopeForContextElement: LexicalScope?
|
|
||||||
val dataFlowInfo: DataFlowInfo
|
val dataFlowInfo: DataFlowInfo
|
||||||
|
)
|
||||||
|
|
||||||
fun getClassDescriptor(classOrObject: KtClassOrObject): Pair<BindingContext, ClassDescriptorWithResolutionScopes>? {
|
private fun analyzeCodeFragmentContext(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): ContextAnalysisResult {
|
||||||
val bindingContext: BindingContext
|
fun resolutionFactory(element: KtElement): BindingContext {
|
||||||
val classDescriptor: ClassDescriptor?
|
return resolveElementCache.resolveToElements(listOf(element), bodyResolveMode)
|
||||||
|
|
||||||
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) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val bindingContextForContext: BindingContext
|
val context = refineContextElement(codeFragment.context)
|
||||||
|
|
||||||
|
var bindingContext: BindingContext = BindingContext.EMPTY
|
||||||
|
var dataFlowInfo: DataFlowInfo = DataFlowInfo.EMPTY
|
||||||
|
var scope: LexicalScope? = null
|
||||||
|
|
||||||
when (context) {
|
when (context) {
|
||||||
is KtPrimaryConstructor -> {
|
is KtPrimaryConstructor -> {
|
||||||
val (bindingContext, classDescriptor) = getClassDescriptor(context.getContainingClassOrObject()) ?: return null
|
val containingClass = context.getContainingClassOrObject()
|
||||||
|
val resolutionResult = getClassDescriptor(containingClass, ::resolutionFactory)
|
||||||
scopeForContextElement = classDescriptor.scopeForInitializerResolution
|
if (resolutionResult != null) {
|
||||||
dataFlowInfo = DataFlowInfo.EMPTY
|
bindingContext = resolutionResult.bindingContext
|
||||||
bindingContextForContext = bindingContext
|
scope = resolutionResult.descriptor.scopeForInitializerResolution
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is KtSecondaryConstructor -> {
|
is KtSecondaryConstructor -> {
|
||||||
val correctedContext = context.getDelegationCall().calleeExpression!!
|
val expression = context.bodyExpression ?: context.getDelegationCall().calleeExpression
|
||||||
bindingContextForContext = resolveToElement(correctedContext)
|
if (expression != null) {
|
||||||
|
bindingContext = resolutionFactory(expression)
|
||||||
scopeForContextElement = bindingContextForContext[BindingContext.LEXICAL_SCOPE, correctedContext]
|
scope = bindingContext[BindingContext.LEXICAL_SCOPE, expression]
|
||||||
dataFlowInfo = DataFlowInfo.EMPTY
|
}
|
||||||
}
|
}
|
||||||
is KtClassOrObject -> {
|
is KtClassOrObject -> {
|
||||||
val (bindingContext, classDescriptor) = getClassDescriptor(context) ?: return null
|
val resolutionResult = getClassDescriptor(context, ::resolutionFactory)
|
||||||
scopeForContextElement = classDescriptor.scopeForMemberDeclarationResolution
|
if (resolutionResult != null) {
|
||||||
dataFlowInfo = DataFlowInfo.EMPTY
|
bindingContext = resolutionResult.bindingContext
|
||||||
bindingContextForContext = bindingContext
|
scope = resolutionResult.descriptor.scopeForMemberDeclarationResolution
|
||||||
|
}
|
||||||
}
|
}
|
||||||
is KtFile -> {
|
is KtFile -> {
|
||||||
scopeForContextElement = resolveSession.fileScopeProvider.getFileResolutionScope(context)
|
bindingContext = resolveSession.bindingContext
|
||||||
dataFlowInfo = DataFlowInfo.EMPTY
|
scope = resolveSession.fileScopeProvider.getFileResolutionScope(context)
|
||||||
bindingContextForContext = BindingContext.EMPTY
|
|
||||||
}
|
}
|
||||||
is KtElement -> {
|
is KtElement -> {
|
||||||
val correctedContext = context.correctContextForElement()
|
bindingContext = resolutionFactory(context)
|
||||||
bindingContextForContext = resolveToElement(correctedContext)
|
scope = bindingContext[BindingContext.LEXICAL_SCOPE, context]
|
||||||
|
dataFlowInfo = bindingContext.getDataFlowInfoAfter(context)
|
||||||
scopeForContextElement = bindingContextForContext[BindingContext.LEXICAL_SCOPE, correctedContext]
|
|
||||||
dataFlowInfo = bindingContextForContext.getDataFlowInfoAfter(correctedContext)
|
|
||||||
}
|
}
|
||||||
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<KtFunction>(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<ImportingScope>()
|
||||||
|
|
||||||
|
val externalDescriptors = codeFragment.externalDescriptors ?: emptyList()
|
||||||
|
if (externalDescriptors.isNotEmpty()) {
|
||||||
|
additionalImportingScopes += ExplicitImportsScope(externalDescriptors)
|
||||||
|
}
|
||||||
|
|
||||||
val importList = codeFragment.importsAsImportList()
|
val importList = codeFragment.importsAsImportList()
|
||||||
if (importList == null || importList.imports.isEmpty()) {
|
if (importList != null && importList.imports.isNotEmpty()) {
|
||||||
return Triple(scopeForContextElement, dataFlowInfo, bindingContextForContext)
|
additionalImportingScopes += createImportScopes(importList)
|
||||||
}
|
}
|
||||||
|
|
||||||
val importScopes = importList.imports.mapNotNull {
|
if (additionalImportingScopes.isNotEmpty()) {
|
||||||
|
return scope.addImportingScopes(additionalImportingScopes)
|
||||||
|
}
|
||||||
|
|
||||||
|
return scope
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createImportScopes(importList: KtImportList): List<ImportingScope> {
|
||||||
|
return importList.imports.mapNotNull {
|
||||||
qualifierResolver.processImportReference(
|
qualifierResolver.processImportReference(
|
||||||
it, resolveSession.moduleDescriptor, resolveSession.trace,
|
it, resolveSession.moduleDescriptor, resolveSession.trace,
|
||||||
excludedImportNames = emptyList(), packageFragmentForVisibilityCheck = null
|
excludedImportNames = emptyList(), packageFragmentForVisibilityCheck = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Triple(scopeForContextElement.addImportingScopes(importScopes), dataFlowInfo, bindingContextForContext)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-16
@@ -176,7 +176,9 @@ private object KotlinResolveDataProvider {
|
|||||||
): AnalysisResult {
|
): AnalysisResult {
|
||||||
try {
|
try {
|
||||||
if (analyzableElement is KtCodeFragment) {
|
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(
|
val trace = DelegatingBindingTrace(
|
||||||
@@ -217,19 +219,4 @@ private object KotlinResolveDataProvider {
|
|||||||
return AnalysisResult.internalError(BindingContext.EMPTY, e)
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -457,14 +457,12 @@ class ResolveElementCache(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun codeFragmentAdditionalResolve(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): BindingTrace {
|
private fun codeFragmentAdditionalResolve(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): BindingTrace {
|
||||||
val trace = createDelegatingTrace(codeFragment, bodyResolveMode.bindingTraceFilter)
|
|
||||||
|
|
||||||
val contextResolveMode = if (bodyResolveMode == BodyResolveMode.PARTIAL)
|
val contextResolveMode = if (bodyResolveMode == BodyResolveMode.PARTIAL)
|
||||||
BodyResolveMode.PARTIAL_FOR_COMPLETION
|
BodyResolveMode.PARTIAL_FOR_COMPLETION
|
||||||
else
|
else
|
||||||
bodyResolveMode
|
bodyResolveMode
|
||||||
|
|
||||||
return codeFragmentAnalyzer.analyzeCodeFragment(codeFragment, trace, contextResolveMode)
|
return codeFragmentAnalyzer.analyzeCodeFragment(codeFragment, contextResolveMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun annotationAdditionalResolve(resolveSession: ResolveSession, ktAnnotationEntry: KtAnnotationEntry): BindingTrace {
|
private fun annotationAdditionalResolve(resolveSession: ResolveSession, ktAnnotationEntry: KtAnnotationEntry): BindingTrace {
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ import com.intellij.xdebugger.frame.XNamedValue
|
|||||||
import com.sun.jdi.*
|
import com.sun.jdi.*
|
||||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
|
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.idea.debugger.evaluate.getInvokePolicy
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
|
|
||||||
|
|||||||
@@ -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.DO_RESUME_METHOD_NAME
|
||||||
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
|
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
|
||||||
import org.jetbrains.kotlin.codegen.coroutines.continuationAsmTypes
|
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.codeInsight.CodeInsightUtils
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||||
import org.jetbrains.kotlin.idea.refactoring.getLineEndOffset
|
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.load.java.JvmAbi
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
|
||||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||||
import java.util.*
|
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(className: String): Boolean = isSubtype(AsmType.getObjectType(className))
|
||||||
|
|
||||||
fun Type.isSubtype(type: AsmType): Boolean {
|
fun Type.isSubtype(type: AsmType): Boolean {
|
||||||
|
|||||||
@@ -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<ClassType>() ?: return null
|
||||||
|
|
||||||
|
val method: Method?
|
||||||
|
val args: List<Value?>
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+14
-85
@@ -22,7 +22,6 @@ import com.intellij.debugger.engine.evaluation.CodeFragmentKind
|
|||||||
import com.intellij.debugger.engine.evaluation.TextWithImports
|
import com.intellij.debugger.engine.evaluation.TextWithImports
|
||||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||||
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl
|
|
||||||
import com.intellij.ide.highlighter.JavaFileType
|
import com.intellij.ide.highlighter.JavaFileType
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.diagnostic.Logger
|
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.psi.util.PsiTypesUtil
|
||||||
import com.intellij.util.IncorrectOperationException
|
import com.intellij.util.IncorrectOperationException
|
||||||
import com.intellij.util.concurrency.Semaphore
|
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 com.sun.jdi.*
|
||||||
import org.jetbrains.annotations.TestOnly
|
import org.jetbrains.annotations.TestOnly
|
||||||
import org.jetbrains.eval4j.jdi.asValue
|
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.idea.versions.getKotlinJvmRuntimeMarkerClass
|
||||||
import org.jetbrains.kotlin.j2k.AfterConversionPass
|
import org.jetbrains.kotlin.j2k.AfterConversionPass
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
|
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.typeUtil.makeNullable
|
import org.jetbrains.kotlin.types.typeUtil.makeNullable
|
||||||
@@ -63,28 +58,16 @@ import java.util.concurrent.atomic.AtomicReference
|
|||||||
|
|
||||||
class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
||||||
override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
||||||
val contextElement = getWrappedContextElement(project, context)
|
val contextElement = getContextElement(context)
|
||||||
if (contextElement == null) {
|
|
||||||
LOG.warn("CodeFragment with null context created:\noriginalContext = ${context?.getElementTextWithContext()}")
|
val constructor = when (item.kind) {
|
||||||
}
|
null -> error("Code fragment kind should be set")
|
||||||
val codeFragment = if (item.kind == CodeFragmentKind.EXPRESSION) {
|
CodeFragmentKind.EXPRESSION -> ::KtExpressionCodeFragment
|
||||||
KtExpressionCodeFragment(
|
CodeFragmentKind.CODE_BLOCK -> ::KtBlockCodeFragment
|
||||||
project,
|
|
||||||
"fragment.kt",
|
|
||||||
item.text,
|
|
||||||
initImports(item.imports),
|
|
||||||
contextElement
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
KtBlockCodeFragment(
|
|
||||||
project,
|
|
||||||
"fragment.kt",
|
|
||||||
item.text,
|
|
||||||
initImports(item.imports),
|
|
||||||
contextElement
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val codeFragment = constructor(project, "fragment.kt", item.text, initImports(item.imports), contextElement)
|
||||||
|
|
||||||
codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR, { expression: KtExpression ->
|
codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR, { expression: KtExpression ->
|
||||||
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
|
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
|
||||||
val debuggerSession = debuggerContext.debuggerSession
|
val debuggerSession = debuggerContext.debuggerSession
|
||||||
@@ -135,19 +118,19 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val receiverTypeReference =
|
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 receiverTypeText = receiverTypeReference?.let { "${it.text}." } ?: ""
|
||||||
|
|
||||||
val kotlinVariablesText =
|
val kotlinVariablesText =
|
||||||
frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project)
|
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 fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement)
|
||||||
val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction
|
val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction
|
||||||
val fakeContext = fakeFunction?.bodyBlockExpression?.statements?.lastOrNull()
|
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
|
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 {
|
override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
||||||
val kotlinCodeFragment = createCodeFragment(item, context, project)
|
val kotlinCodeFragment = createCodeFragment(item, context, project)
|
||||||
if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtExpressionCodeFragment) {
|
if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtExpressionCodeFragment) {
|
||||||
@@ -292,13 +269,12 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
|||||||
companion object {
|
companion object {
|
||||||
private val LOG = Logger.getInstance(this::class.java)
|
private val LOG = Logger.getInstance(this::class.java)
|
||||||
|
|
||||||
val LABEL_VARIABLE_VALUE_KEY: Key<Value> = Key.create<Value>("_label_variable_value_key_")
|
|
||||||
|
|
||||||
private const val DEBUG_LABEL_SUFFIX: String = "_DebugLabel"
|
|
||||||
|
|
||||||
@TestOnly
|
@TestOnly
|
||||||
val DEBUG_CONTEXT_FOR_TESTS: Key<DebuggerContextImpl> = Key.create("DEBUG_CONTEXT_FOR_TESTS")
|
val DEBUG_CONTEXT_FOR_TESTS: Key<DebuggerContextImpl> = 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? {
|
fun getContextElement(elementAt: PsiElement?): PsiElement? {
|
||||||
if (elementAt == null) return null
|
if (elementAt == null) return null
|
||||||
|
|
||||||
@@ -335,17 +311,6 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
|||||||
return containingFile
|
return containingFile
|
||||||
}
|
}
|
||||||
|
|
||||||
//internal for tests
|
|
||||||
fun createCodeFragmentForLabeledObjects(project: Project, markupMap: Map<*, ValueMarkup>): Pair<String, Map<String, Value>> {
|
|
||||||
@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<String, Value>
|
|
||||||
|
|
||||||
return variables.kotlinVariablesAsText(project) to variables
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Map<String, Value>.kotlinVariablesAsText(project: Project): String {
|
private fun Map<String, Value>.kotlinVariablesAsText(project: Project): String {
|
||||||
val sb = StringBuilder()
|
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 {
|
private fun createFakeFileWithJavaContextElement(funWithLocalVariables: String, javaContext: PsiElement): KtFile {
|
||||||
val javaFile = javaContext.containingFile as? PsiJavaFile
|
val javaFile = javaContext.containingFile as? PsiJavaFile
|
||||||
|
|
||||||
@@ -419,25 +369,4 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
|||||||
|
|
||||||
return KtPsiFactory(javaContext.project).createAnalyzableFile("fakeFileForJavaContextInDebugger.kt", sb.toString(), javaContext)
|
return KtPsiFactory(javaContext.project).createAnalyzableFile("fakeFileForJavaContextInDebugger.kt", sb.toString(), javaContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal for test
|
|
||||||
private fun createWrappingContext(
|
|
||||||
newFragmentText: String,
|
|
||||||
labels: Map<String, Value>,
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-48
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.idea.debugger.evaluate
|
|||||||
|
|
||||||
import com.intellij.debugger.SourcePosition
|
import com.intellij.debugger.SourcePosition
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
|
||||||
import com.intellij.openapi.components.ServiceManager
|
import com.intellij.openapi.components.ServiceManager
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.roots.libraries.LibraryUtil
|
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.PsiModificationTracker
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
import com.intellij.util.containers.MultiMap
|
import com.intellij.util.containers.MultiMap
|
||||||
import org.apache.log4j.Logger
|
|
||||||
import org.jetbrains.annotations.TestOnly
|
import org.jetbrains.annotations.TestOnly
|
||||||
|
import org.apache.log4j.Logger
|
||||||
import org.jetbrains.eval4j.Value
|
import org.jetbrains.eval4j.Value
|
||||||
import org.jetbrains.eval4j.jdi.asValue
|
|
||||||
import org.jetbrains.kotlin.analyzer.AnalysisResult
|
import org.jetbrains.kotlin.analyzer.AnalysisResult
|
||||||
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
|
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
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.analyzeAndGetResult
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
||||||
import org.jetbrains.kotlin.idea.debugger.BinaryCacheKey
|
import org.jetbrains.kotlin.idea.debugger.BinaryCacheKey
|
||||||
import org.jetbrains.kotlin.idea.debugger.BytecodeDebugInfo
|
import org.jetbrains.kotlin.idea.debugger.BytecodeDebugInfo
|
||||||
import org.jetbrains.kotlin.idea.debugger.createWeakBytecodeDebugInfoStorage
|
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.runInReadActionWithWriteActionPriorityWithPCE
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||||
import org.jetbrains.kotlin.psi.KtElement
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
class KotlinDebuggerCaches(project: Project) {
|
class KotlinDebuggerCaches(project: Project) {
|
||||||
|
|
||||||
private val cachedCompiledData = CachedValuesManager.getManager(project).createCachedValue(
|
private val cachedCompiledData = CachedValuesManager.getManager(project).createCachedValue(
|
||||||
{
|
{
|
||||||
CachedValueProvider.Result<MultiMap<String, CompiledDataDescriptor>>(
|
CachedValueProvider.Result<MultiMap<String, CompiledDataDescriptor>>(
|
||||||
@@ -98,37 +93,39 @@ class KotlinDebuggerCaches(project: Project) {
|
|||||||
|
|
||||||
fun getInstance(project: Project) = ServiceManager.getService(project, KotlinDebuggerCaches::class.java)!!
|
fun getInstance(project: Project) = ServiceManager.getService(project, KotlinDebuggerCaches::class.java)!!
|
||||||
|
|
||||||
fun getOrCreateCompiledData(
|
fun compileCodeFragmentCacheAware(
|
||||||
codeFragment: KtCodeFragment,
|
codeFragment: KtCodeFragment,
|
||||||
sourcePosition: SourcePosition,
|
sourcePosition: SourcePosition,
|
||||||
evaluationContext: EvaluationContextImpl,
|
compileCode: () -> CompiledDataDescriptor,
|
||||||
create: (KtCodeFragment, SourcePosition) -> CompiledDataDescriptor
|
force: Boolean = false
|
||||||
): CompiledDataDescriptor {
|
): Pair<CompiledDataDescriptor, Boolean> {
|
||||||
val evaluateExpressionCache = getInstance(codeFragment.project)
|
val evaluateExpressionCache = getInstance(codeFragment.project)
|
||||||
|
|
||||||
val text = "${codeFragment.importsToString()}\n${codeFragment.text}"
|
val text = "${codeFragment.importsToString()}\n${codeFragment.text}"
|
||||||
|
|
||||||
val cached = synchronized<Collection<CompiledDataDescriptor>>(evaluateExpressionCache.cachedCompiledData) {
|
val cachedResults = synchronized<Collection<CompiledDataDescriptor>>(evaluateExpressionCache.cachedCompiledData) {
|
||||||
val cache = evaluateExpressionCache.cachedCompiledData.value!!
|
evaluateExpressionCache.cachedCompiledData.value[text]
|
||||||
|
|
||||||
cache[text]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val answer = cached.firstOrNull {
|
val existingResult = cachedResults.firstOrNull { it.sourcePosition == sourcePosition }
|
||||||
it.sourcePosition == sourcePosition || evaluateExpressionCache.canBeEvaluatedInThisContext(it, evaluationContext)
|
if (existingResult != null) {
|
||||||
}
|
if (force) {
|
||||||
if (answer != null) {
|
synchronized(evaluateExpressionCache.cachedCompiledData) {
|
||||||
return answer
|
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}")
|
LOG.debug("Compile bytecode for ${codeFragment.text}")
|
||||||
|
|
||||||
synchronized(evaluateExpressionCache.cachedCompiledData) {
|
synchronized(evaluateExpressionCache.cachedCompiledData) {
|
||||||
evaluateExpressionCache.cachedCompiledData.value.putValue(text, newCompiledData)
|
evaluateExpressionCache.cachedCompiledData.value.putValue(text, newCompiledData)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newCompiledData
|
return Pair(newCompiledData, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : PsiElement> getOrComputeClassNames(psiElement: T?, create: (T) -> ComputedClassNames): List<String> {
|
fun <T : PsiElement> getOrComputeClassNames(psiElement: T?, create: (T) -> ComputedClassNames): List<String> {
|
||||||
@@ -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<ClassToLoad>,
|
|
||||||
val sourcePosition: SourcePosition,
|
|
||||||
val parameters: List<Parameter>,
|
|
||||||
val variablesCrossingInlineBounds: Set<String>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null, val error: EvaluateException? = null)
|
data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null, val error: EvaluateException? = null)
|
||||||
|
|
||||||
class ComputedClassNames(val classNames: List<String>, val shouldBeCached: Boolean) {
|
class ComputedClassNames(val classNames: List<String>, val shouldBeCached: Boolean) {
|
||||||
|
|||||||
+229
-560
@@ -17,6 +17,7 @@
|
|||||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||||
|
|
||||||
import com.intellij.debugger.SourcePosition
|
import com.intellij.debugger.SourcePosition
|
||||||
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
import com.intellij.debugger.engine.SuspendContext
|
import com.intellij.debugger.engine.SuspendContext
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
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.diagnostic.Logger
|
||||||
import com.intellij.openapi.progress.ProcessCanceledException
|
import com.intellij.openapi.progress.ProcessCanceledException
|
||||||
import com.intellij.openapi.project.DumbService
|
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.JavaPsiFacade
|
||||||
import com.intellij.psi.PsiDocumentManager
|
import com.intellij.psi.PsiDocumentManager
|
||||||
import com.intellij.psi.PsiElement
|
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.psi.search.GlobalSearchScope
|
||||||
import com.intellij.testFramework.LightVirtualFile
|
|
||||||
import com.intellij.testFramework.runInEdtAndWait
|
import com.intellij.testFramework.runInEdtAndWait
|
||||||
import com.intellij.util.ExceptionUtil
|
import com.intellij.util.ExceptionUtil
|
||||||
import com.sun.jdi.*
|
import com.sun.jdi.*
|
||||||
|
import com.sun.jdi.Value
|
||||||
import com.sun.jdi.request.EventRequest
|
import com.sun.jdi.request.EventRequest
|
||||||
import org.jetbrains.eval4j.*
|
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.JDIEval
|
||||||
import org.jetbrains.eval4j.jdi.asJdiValue
|
import org.jetbrains.eval4j.jdi.asJdiValue
|
||||||
import org.jetbrains.eval4j.jdi.asValue
|
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.builtins.jvm.JavaToKotlinClassMap
|
||||||
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
|
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
|
||||||
import org.jetbrains.kotlin.codegen.*
|
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.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
|
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
|
||||||
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
|
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||||
import org.jetbrains.kotlin.diagnostics.Errors
|
import org.jetbrains.kotlin.diagnostics.Errors
|
||||||
import org.jetbrains.kotlin.diagnostics.Severity
|
import org.jetbrains.kotlin.diagnostics.Severity
|
||||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
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.caches.resolve.util.getJavaClassDescriptor
|
||||||
import org.jetbrains.kotlin.idea.core.quoteSegmentsIfNeeded
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.DebuggerUtils
|
import org.jetbrains.kotlin.idea.debugger.DebuggerUtils
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.*
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companion.compileCodeFragmentCacheAware
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely
|
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely
|
||||||
import org.jetbrains.kotlin.idea.debugger.getBackingFieldName
|
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.EvaluatorValueConverter
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionResult
|
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder
|
||||||
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
|
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
|
||||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.idea.util.attachment.attachmentByPsiFile
|
import org.jetbrains.kotlin.idea.util.attachment.attachmentByPsiFile
|
||||||
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments
|
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments
|
||||||
import org.jetbrains.kotlin.psi.*
|
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.AnalyzingUtils
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
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.isInlineClassType
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
import org.jetbrains.org.objectweb.asm.*
|
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.Type
|
||||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
internal val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluator")
|
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_FUNCTION_NAME = "generated_for_debugger_fun"
|
||||||
internal const val GENERATED_CLASS_NAME = "Generated_for_debugger_class"
|
internal const val GENERATED_CLASS_NAME = "Generated_for_debugger_class"
|
||||||
|
|
||||||
private const val DEBUG_MODE = false
|
|
||||||
|
|
||||||
object KotlinEvaluationBuilder : EvaluatorBuilder {
|
object KotlinEvaluationBuilder : EvaluatorBuilder {
|
||||||
override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator {
|
override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator {
|
||||||
if (codeFragment !is KtCodeFragment || position == null) {
|
if (codeFragment !is KtCodeFragment || position == null) {
|
||||||
@@ -108,14 +87,14 @@ object KotlinEvaluationBuilder : EvaluatorBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (position.line < 0) {
|
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
|
val file = position.file
|
||||||
if (file is KtFile) {
|
if (file is KtFile) {
|
||||||
val document = PsiDocumentManager.getInstance(file.project).getDocument(file)
|
val document = PsiDocumentManager.getInstance(file.project).getDocument(file)
|
||||||
if (document == null || document.lineCount < position.line) {
|
if (document == null || document.lineCount < position.line) {
|
||||||
throw EvaluateExceptionUtil.createEvaluateException(
|
evaluationException(
|
||||||
"Couldn't evaluate kotlin expression: breakpoint is placed outside the file. " +
|
"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."
|
"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}",
|
"Trying to evaluate ${codeFragment::class.java} with context ${codeFragment.context?.javaClass}",
|
||||||
mergeAttachments(*attachments)
|
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))
|
return ExpressionEvaluatorImpl(KotlinEvaluator(codeFragment, position))
|
||||||
@@ -147,44 +126,21 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (DumbService.getInstance(codeFragment.project).isDumb) {
|
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 {
|
try {
|
||||||
val compiledData = KotlinDebuggerCaches.getOrCreateCompiledData(codeFragment, sourcePosition, context) { fragment, position ->
|
return evaluateSafe(context)
|
||||||
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
|
|
||||||
}
|
|
||||||
} catch (e: EvaluateException) {
|
} catch (e: EvaluateException) {
|
||||||
throw e
|
throw e
|
||||||
} catch (e: ProcessCanceledException) {
|
} catch (e: ProcessCanceledException) {
|
||||||
exception(e)
|
evaluationException(e)
|
||||||
} catch (e: Eval4JInterpretingException) {
|
} catch (e: Eval4JInterpretingException) {
|
||||||
exception(e.cause)
|
evaluationException(e.cause)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val isSpecialException = isSpecialException(e)
|
val isSpecialException = isSpecialException(e)
|
||||||
if (isSpecialException) {
|
if (isSpecialException) {
|
||||||
exception(e)
|
evaluationException(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
val text = runReadAction { codeFragment.context?.text ?: "null" }
|
val text = runReadAction { codeFragment.context?.text ?: "null" }
|
||||||
@@ -196,6 +152,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
)
|
)
|
||||||
|
|
||||||
LOG.error(
|
LOG.error(
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
LogMessageEx.createEvent(
|
LogMessageEx.createEvent(
|
||||||
"Couldn't evaluate expression",
|
"Couldn't evaluate expression",
|
||||||
ExceptionUtil.getThrowableText(e),
|
ExceptionUtil.getThrowableText(e),
|
||||||
@@ -204,570 +161,254 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
|||||||
)
|
)
|
||||||
|
|
||||||
val cause = if (e.message != null) ": ${e.message}" else ""
|
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 {
|
private fun evaluateSafe(context: EvaluationContextImpl): Any? {
|
||||||
return when (th) {
|
fun compilerFactory(): CompiledDataDescriptor = compileCodeFragment(context.debugProcess)
|
||||||
is ClassNotPreparedException,
|
|
||||||
is InternalException,
|
val (compiledData, isCompiledDataFromCache) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory)
|
||||||
is AbsentInformationException,
|
val classLoaderRef = loadClassesSafely(context, compiledData.classes)
|
||||||
is ClassNotLoadedException,
|
|
||||||
is IncompatibleThreadStateException,
|
val thread = context.suspendContext.thread?.threadReference ?: error("Can not find a thread to run evaluation on")
|
||||||
is InconsistentDebugInfoException,
|
val invokePolicy = context.suspendContext.getInvokePolicy()
|
||||||
is ObjectCollectedException,
|
val executionContext = ExecutionContext(context, thread, invokePolicy)
|
||||||
is VMDisconnectedException -> true
|
|
||||||
else -> false
|
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? {
|
private fun compileCodeFragment(debugProcess: DebugProcessImpl): CompiledDataDescriptor {
|
||||||
return null
|
var analysisResult = checkForErrors(codeFragment, debugProcess)
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
if (codeFragment.wrapToStringIfNeeded(analysisResult.bindingContext)) {
|
||||||
private fun extractAndCompile(
|
// Repeat analysis with toString() added
|
||||||
codeFragment: KtCodeFragment,
|
analysisResult = checkForErrors(codeFragment, debugProcess)
|
||||||
sourcePosition: SourcePosition,
|
}
|
||||||
context: EvaluationContextImpl
|
|
||||||
): CompiledDataDescriptor {
|
|
||||||
var bindingContext = codeFragment.checkForErrors().bindingContext
|
|
||||||
|
|
||||||
if (codeFragment.wrapToStringIfNeeded(bindingContext)) {
|
val (bindingContext) = runReadAction {
|
||||||
// Repeat analysis with toString() added
|
DebuggerUtils.analyzeInlinedFunctions(
|
||||||
bindingContext = codeFragment.checkForErrors().bindingContext
|
KotlinCacheService.getInstance(codeFragment.project).getResolutionFacade(listOf(codeFragment)),
|
||||||
}
|
codeFragment, false, analysisResult.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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean {
|
val moduleDescriptor = analysisResult.moduleDescriptor
|
||||||
if (this !is KtExpressionCodeFragment) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val contentElement = runReadAction { getContentElement() }
|
val result = CodeFragmentCompiler.compile(codeFragment, bindingContext, moduleDescriptor)
|
||||||
val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, contentElement]?.type
|
return CompiledDataDescriptor.from(result, sourcePosition)
|
||||||
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 fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean {
|
||||||
|
if (this !is KtExpressionCodeFragment) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getClassName(fileName: String): String {
|
val contentElement = runReadAction { getContentElement() }
|
||||||
return fileName.substringBeforeLast(".class").replace("/", ".")
|
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
|
return false
|
||||||
get() = classes.firstOrNull { it.isMainClass() } ?: error(
|
}
|
||||||
"Can't find main class for " + sourcePosition.elementAt.getParentOfType<KtDeclaration>(strict = false)
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun evaluateWithCompilation(
|
private data class ErrorCheckingResult(
|
||||||
context: EvaluationContextImpl,
|
val bindingContext: BindingContext,
|
||||||
compiledData: CompiledDataDescriptor,
|
val moduleDescriptor: ModuleDescriptor,
|
||||||
classLoader: ClassLoaderReference
|
val files: List<KtFile>
|
||||||
): Any? {
|
)
|
||||||
val vm = context.debugProcess.virtualMachineProxy.virtualMachine
|
|
||||||
val mainClassBytecode = compiledData.mainClass.bytes
|
|
||||||
|
|
||||||
|
private fun checkForErrors(codeFragment: KtCodeFragment, debugProcess: DebugProcessImpl): ErrorCheckingResult {
|
||||||
|
return runInReadActionWithWriteActionPriorityWithPCE {
|
||||||
try {
|
try {
|
||||||
val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, ClassReader.SKIP_CODE) }
|
AnalyzingUtils.checkForSyntacticErrors(codeFragment)
|
||||||
assert(mainClassAsmNode.methods.size == 1)
|
} catch (e: IllegalArgumentException) {
|
||||||
|
evaluationException(e.message ?: e.toString())
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun runEval4j(
|
val filesToAnalyze = listOf(codeFragment)
|
||||||
context: EvaluationContextImpl,
|
val resolutionFacade = KotlinCacheService.getInstance(codeFragment.project).getResolutionFacade(filesToAnalyze)
|
||||||
compiledData: CompiledDataDescriptor,
|
|
||||||
classLoader: ClassLoaderReference?
|
|
||||||
): InterpreterResult {
|
|
||||||
val virtualMachine = context.debugProcess.virtualMachineProxy.virtualMachine
|
|
||||||
var resultValue: InterpreterResult? = null
|
|
||||||
|
|
||||||
// assert [0] with some context
|
DebugLabelPropertyDescriptorProvider(codeFragment, resolutionFacade.moduleDescriptor, debugProcess).supplyDebugLabels()
|
||||||
val mainClassBytecode = compiledData.mainClass.bytes
|
|
||||||
|
|
||||||
ClassReader(mainClassBytecode).accept(object : ClassVisitor(API_VERSION) {
|
val analysisResult = resolutionFacade.analyzeWithAllCompilerChecks(filesToAnalyze)
|
||||||
override fun visitMethod(
|
|
||||||
access: Int,
|
|
||||||
name: String,
|
|
||||||
desc: String,
|
|
||||||
signature: String?,
|
|
||||||
exceptions: Array<out String>?
|
|
||||||
): 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)
|
|
||||||
|
|
||||||
return object : MethodNode(Opcodes.API_VERSION, access, name, desc, signature, exceptions) {
|
if (analysisResult.isError()) {
|
||||||
override fun visitEnd() {
|
evaluationException(analysisResult.error)
|
||||||
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 <T> VirtualMachine.executeWithBreakpointsDisabled(block: () -> T): T {
|
|
||||||
val allRequests = eventRequestManager().breakpointRequests() + eventRequestManager().classPrepareRequests()
|
|
||||||
|
|
||||||
try {
|
|
||||||
allRequests.forEach { it.disable() }
|
|
||||||
return block()
|
|
||||||
} finally {
|
|
||||||
allRequests.forEach { it.enable() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
private fun evaluateWithCompilation(
|
||||||
val argumentType = argumentValue.asmType
|
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)) {
|
private fun evaluateWithEval4J(
|
||||||
try {
|
context: ExecutionContext,
|
||||||
val unboxedType = AsmUtil.unboxType(argumentType)
|
compiledData: CompiledDataDescriptor,
|
||||||
if (parameterType == unboxedType) {
|
classLoader: ClassLoaderReference?
|
||||||
return eval.unboxType(argumentValue, parameterType)
|
): InterpreterResult {
|
||||||
}
|
val mainClassBytecode = compiledData.mainClass.bytes
|
||||||
} catch (ignored: UnsupportedOperationException) {
|
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 <T> runEvaluation(
|
||||||
|
context: ExecutionContext,
|
||||||
|
compiledData: CompiledDataDescriptor,
|
||||||
|
classLoader: ClassLoaderReference?,
|
||||||
|
block: (List<Value?>) -> 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<Value?> {
|
||||||
|
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)) {
|
result.value
|
||||||
if (parameterType.descriptor == "Ljava/lang/Object;" || parameterType == AsmUtil.boxType(argumentType)) {
|
|
||||||
return eval.boxType(argumentValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return argumentValue
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun InterpreterResult.toJdiValue(context: EvaluationContextImpl): com.sun.jdi.Value? {
|
override fun getModifier() = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val IGNORED_DIAGNOSTICS: Set<DiagnosticFactory<*>> = Errors.INVISIBLE_REFERENCE_DIAGNOSTICS
|
||||||
|
|
||||||
|
private fun InterpreterResult.toJdiValue(context: ExecutionContext): com.sun.jdi.Value? {
|
||||||
val jdiValue = when (this) {
|
val jdiValue = when (this) {
|
||||||
is ValueReturned -> result
|
is ValueReturned -> result
|
||||||
is ExceptionThrown -> {
|
is ExceptionThrown -> {
|
||||||
when {
|
when {
|
||||||
this.kind == ExceptionThrown.ExceptionKind.FROM_EVALUATED_CODE ->
|
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 ->
|
this.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE ->
|
||||||
throw exception.value as Throwable
|
throw exception.value as Throwable
|
||||||
else ->
|
else ->
|
||||||
exception(exception.toString())
|
evaluationException(exception.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is AbnormalTermination -> exception(message)
|
is AbnormalTermination -> evaluationException(message)
|
||||||
else -> throw IllegalStateException("Unknown result value produced by eval4j")
|
else -> throw IllegalStateException("Unknown result value produced by eval4j")
|
||||||
}
|
}
|
||||||
|
|
||||||
val vm = context.debugProcess.virtualMachineProxy.virtualMachine
|
val sharedVar = if ((jdiValue is AbstractValue<*>)) getValueIfSharedVar(jdiValue, context) else null
|
||||||
|
return sharedVar?.value ?: jdiValue.asJdiValue(context.vm, jdiValue.asmType)
|
||||||
val sharedVar = getValueIfSharedVar(jdiValue)
|
|
||||||
return sharedVar?.value ?: jdiValue.asJdiValue(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
|
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<Parameter> {
|
|
||||||
return runReadAction {
|
|
||||||
val valuesForLabels = HashMap<String, Value>()
|
|
||||||
|
|
||||||
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<Parameter>()
|
|
||||||
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<Parameter>,
|
|
||||||
parameterTypes: Array<Type>,
|
|
||||||
compiledData: CompiledDataDescriptor
|
|
||||||
): List<Value> {
|
|
||||||
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<Parameter>
|
|
||||||
): 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<DiagnosticFactory<*>> = 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<KtFile>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
internal fun SuspendContext.getInvokePolicy(): Int {
|
||||||
return if (suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD) ObjectReference.INVOKE_SINGLE_THREADED else 0
|
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
|
if (AsmUtil.isPrimitive(this)) return null
|
||||||
|
|
||||||
val jvmName = JvmClassName.byInternalName(internalName).fqNameForClassNameWithoutDollars
|
val jvmName = JvmClassName.byInternalName(internalName).fqNameForClassNameWithoutDollars
|
||||||
|
|
||||||
// TODO: use the correct built-ins from the module instead of DefaultBuiltIns here
|
if (mapBuiltIns) {
|
||||||
JavaToKotlinClassMap.mapJavaToKotlin(jvmName)?.let(
|
val mappedName = JavaToKotlinClassMap.mapJavaToKotlin(jvmName)
|
||||||
DefaultBuiltIns.Instance.builtInsModule::findClassAcrossModuleDependencies
|
if (mappedName != null) {
|
||||||
)?.let { return it }
|
moduleDescriptor.findClassAcrossModuleDependencies(mappedName)?.let { return it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return runReadAction {
|
return runReadAction {
|
||||||
val classes = JavaPsiFacade.getInstance(scope.project).findClasses(jvmName.asString(), scope)
|
val classes = JavaPsiFacade.getInstance(scope.project).findClasses(jvmName.asString(), scope)
|
||||||
@@ -777,3 +418,31 @@ fun Type.getClassDescriptor(scope: GlobalSearchScope): ClassDescriptor? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun <T> 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)
|
||||||
-72
@@ -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<String> {
|
|
||||||
val result = hashSetOf<String>()
|
|
||||||
|
|
||||||
codeFragment.accept(object : KtTreeVisitor<Unit>() {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+4
-3
@@ -23,6 +23,7 @@ import com.sun.jdi.ArrayReference
|
|||||||
import com.sun.jdi.ArrayType
|
import com.sun.jdi.ArrayType
|
||||||
import com.sun.jdi.ClassLoaderReference
|
import com.sun.jdi.ClassLoaderReference
|
||||||
import com.sun.jdi.Value
|
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.ClassReader
|
||||||
import org.jetbrains.org.objectweb.asm.Label
|
import org.jetbrains.org.objectweb.asm.Label
|
||||||
import org.jetbrains.org.objectweb.asm.tree.*
|
import org.jetbrains.org.objectweb.asm.tree.*
|
||||||
@@ -38,7 +39,7 @@ interface ClassLoadingAdapter {
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||||
val mainClass = classes.firstOrNull { it.isMainClass() } ?: return null
|
val mainClass = classes.firstOrNull { it.isMainClass } ?: return null
|
||||||
|
|
||||||
var info = ClassInfoForEvaluator(containsAdditionalClasses = classes.size > 1)
|
var info = ClassInfoForEvaluator(containsAdditionalClasses = classes.size > 1)
|
||||||
if (!info.containsAdditionalClasses) {
|
if (!info.containsAdditionalClasses) {
|
||||||
@@ -64,8 +65,8 @@ interface ClassLoadingAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun analyzeClass(classToLoad: ClassToLoad, info: ClassInfoForEvaluator): ClassInfoForEvaluator {
|
private fun analyzeClass(classToLoad: ClassToLoad, info: ClassInfoForEvaluator): ClassInfoForEvaluator {
|
||||||
val classNode = ClassNode().apply { ClassReader(classToLoad.bytes).accept(this, ClassReader.EXPAND_FRAMES) }
|
val classNode = ClassNode().apply { ClassReader(classToLoad.bytes).accept(this, 0) }
|
||||||
val methodToRun = classNode.methods.single()
|
val methodToRun = classNode.methods.single { it.name == GENERATED_FUNCTION_NAME }
|
||||||
|
|
||||||
val visitedLabels = hashSetOf<Label>()
|
val visitedLabels = hashSetOf<Label>()
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -20,5 +20,6 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.GENERATED_CLASS_NAME
|
|||||||
|
|
||||||
@Suppress("ArrayInDataClass")
|
@Suppress("ArrayInDataClass")
|
||||||
data class ClassToLoad(val className: String, val relativeFileName: String, val bytes: ByteArray) {
|
data class ClassToLoad(val className: String, val relativeFileName: String, val bytes: ByteArray) {
|
||||||
fun isMainClass() = className.endsWith(GENERATED_CLASS_NAME)
|
val isMainClass: Boolean
|
||||||
|
get() = className == GENERATED_CLASS_NAME
|
||||||
}
|
}
|
||||||
+277
@@ -0,0 +1,277 @@
|
|||||||
|
/*
|
||||||
|
* 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.compilation
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.output.OutputFile
|
||||||
|
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
|
||||||
|
import org.jetbrains.kotlin.codegen.*
|
||||||
|
import org.jetbrains.kotlin.codegen.CodeFragmentCodegen.Companion.getSharedTypeIfApplicable
|
||||||
|
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension.Context as InCo
|
||||||
|
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||||
|
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||||
|
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||||
|
import org.jetbrains.kotlin.config.languageVersionSettings
|
||||||
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
|
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||||
|
import org.jetbrains.kotlin.descriptors.impl.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||||
|
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
|
import org.jetbrains.kotlin.incremental.components.LookupLocation
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.data.KtClassOrObjectInfo
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.data.KtScriptInfo
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.declarations.PackageMemberDeclarationProvider
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyPackageDescriptor
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.ChainedMemberScope
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl
|
||||||
|
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
|
||||||
|
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.types.SimpleType
|
||||||
|
import org.jetbrains.kotlin.utils.Printer
|
||||||
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
|
|
||||||
|
object CodeFragmentCompiler {
|
||||||
|
data class CompilationResult(
|
||||||
|
val classes: List<ClassToLoad>,
|
||||||
|
val parameterInfo: CodeFragmentParameterInfo,
|
||||||
|
val localFunctionSuffixes: Map<CodeFragmentParameter.Dumb, String>,
|
||||||
|
val mainMethodSignature: MethodSignature
|
||||||
|
)
|
||||||
|
|
||||||
|
data class MethodSignature(val parameterTypes: List<Type>, val returnType: Type)
|
||||||
|
|
||||||
|
fun compile(codeFragment: KtCodeFragment, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor): CompilationResult {
|
||||||
|
return runReadAction { doCompile(codeFragment, bindingContext, moduleDescriptor) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doCompile(codeFragment: KtCodeFragment, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor): CompilationResult {
|
||||||
|
require(codeFragment is KtBlockCodeFragment || codeFragment is KtExpressionCodeFragment) {
|
||||||
|
"Unsupported code fragment type: $codeFragment"
|
||||||
|
}
|
||||||
|
|
||||||
|
val project = codeFragment.project
|
||||||
|
val resolutionFacade = KotlinCacheService.getInstance(project).getResolutionFacade(listOf(codeFragment))
|
||||||
|
val resolveSession = resolutionFacade.getFrontendService(ResolveSession::class.java)
|
||||||
|
val moduleDescriptorWrapper = EvaluatorModuleDescriptor(codeFragment, moduleDescriptor, resolveSession)
|
||||||
|
|
||||||
|
val defaultReturnType = moduleDescriptor.builtIns.unitType
|
||||||
|
val returnType = getReturnType(codeFragment, bindingContext, defaultReturnType)
|
||||||
|
|
||||||
|
val compilerConfiguration = CompilerConfiguration()
|
||||||
|
compilerConfiguration.languageVersionSettings = codeFragment.languageVersionSettings
|
||||||
|
|
||||||
|
val generationState = GenerationState.Builder(
|
||||||
|
project, ClassBuilderFactories.BINARIES, moduleDescriptorWrapper,
|
||||||
|
bindingContext, listOf(codeFragment), compilerConfiguration
|
||||||
|
).build()
|
||||||
|
|
||||||
|
val parameterInfo = CodeFragmentParameterAnalyzer(codeFragment, bindingContext).analyze()
|
||||||
|
val (classDescriptor, methodDescriptor) = createDescriptorsForCodeFragment(
|
||||||
|
codeFragment, Name.identifier(GENERATED_CLASS_NAME), Name.identifier(GENERATED_FUNCTION_NAME),
|
||||||
|
parameterInfo, returnType, moduleDescriptorWrapper.packageFragmentForEvaluator
|
||||||
|
)
|
||||||
|
|
||||||
|
val codegenInfo = CodeFragmentCodegenInfo(classDescriptor, methodDescriptor, parameterInfo.parameters)
|
||||||
|
CodeFragmentCodegen.setCodeFragmentInfo(codeFragment, codegenInfo)
|
||||||
|
|
||||||
|
KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION)
|
||||||
|
|
||||||
|
val classes = generationState.factory.asList().filterClassFiles()
|
||||||
|
.map { ClassToLoad(it.internalClassName, it.relativePath, it.asByteArray()) }
|
||||||
|
|
||||||
|
val methodSignature = getMethodSignature(methodDescriptor, parameterInfo.parameters, generationState)
|
||||||
|
val functionSuffixes = getLocalFunctionSuffixes(parameterInfo.parameters, generationState.typeMapper)
|
||||||
|
|
||||||
|
generationState.destroy()
|
||||||
|
|
||||||
|
return CompilationResult(classes, parameterInfo, functionSuffixes, methodSignature)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLocalFunctionSuffixes(
|
||||||
|
parameters: List<CodeFragmentParameter.Smart>,
|
||||||
|
typeMapper: KotlinTypeMapper
|
||||||
|
): Map<CodeFragmentParameter.Dumb, String> {
|
||||||
|
val result = mutableMapOf<CodeFragmentParameter.Dumb, String>()
|
||||||
|
|
||||||
|
for (parameter in parameters) {
|
||||||
|
if (parameter.kind != CodeFragmentParameter.Kind.LOCAL_FUNCTION) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val ownerClassName = typeMapper.mapOwner(parameter.targetDescriptor).internalName
|
||||||
|
val lastDollarIndex = ownerClassName.lastIndexOf('$').takeIf { it >= 0} ?: continue
|
||||||
|
result[parameter.dumb] = ownerClassName.drop(lastDollarIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMethodSignature(
|
||||||
|
methodDescriptor: FunctionDescriptor,
|
||||||
|
parameters: List<CodeFragmentParameter.Smart>,
|
||||||
|
state: GenerationState
|
||||||
|
): MethodSignature {
|
||||||
|
val typeMapper = state.typeMapper
|
||||||
|
val asmSignature = typeMapper.mapSignatureSkipGeneric(methodDescriptor)
|
||||||
|
val asmParameters = parameters.zip(asmSignature.valueParameters).map { (param, sigParam) ->
|
||||||
|
getSharedTypeIfApplicable(param.targetDescriptor, typeMapper) ?: sigParam.asmType
|
||||||
|
}
|
||||||
|
|
||||||
|
return MethodSignature(asmParameters, asmSignature.returnType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getReturnType(
|
||||||
|
codeFragment: KtCodeFragment,
|
||||||
|
bindingContext: BindingContext,
|
||||||
|
defaultReturnType: SimpleType
|
||||||
|
): KotlinType {
|
||||||
|
return when (codeFragment) {
|
||||||
|
is KtExpressionCodeFragment -> {
|
||||||
|
val typeInfo = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, codeFragment.getContentElement()]
|
||||||
|
typeInfo?.type ?: defaultReturnType
|
||||||
|
}
|
||||||
|
is KtBlockCodeFragment -> {
|
||||||
|
val blockExpression = codeFragment.getContentElement()
|
||||||
|
val lastStatement = blockExpression.statements.lastOrNull() ?: return defaultReturnType
|
||||||
|
val typeInfo = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, lastStatement]
|
||||||
|
typeInfo?.type ?: defaultReturnType
|
||||||
|
}
|
||||||
|
else -> defaultReturnType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDescriptorsForCodeFragment(
|
||||||
|
declaration: KtCodeFragment,
|
||||||
|
className: Name,
|
||||||
|
methodName: Name,
|
||||||
|
parameterInfo: CodeFragmentParameterInfo,
|
||||||
|
returnType: KotlinType,
|
||||||
|
packageFragmentDescriptor: PackageFragmentDescriptor
|
||||||
|
): Pair<ClassDescriptor, FunctionDescriptor> {
|
||||||
|
val classDescriptor = ClassDescriptorImpl(
|
||||||
|
packageFragmentDescriptor, className, Modality.FINAL, ClassKind.OBJECT,
|
||||||
|
emptyList(),
|
||||||
|
KotlinSourceElement(declaration),
|
||||||
|
false,
|
||||||
|
LockBasedStorageManager.NO_LOCKS
|
||||||
|
)
|
||||||
|
|
||||||
|
val methodDescriptor = SimpleFunctionDescriptorImpl.create(
|
||||||
|
classDescriptor, Annotations.EMPTY, methodName,
|
||||||
|
CallableMemberDescriptor.Kind.SYNTHESIZED, classDescriptor.source
|
||||||
|
)
|
||||||
|
|
||||||
|
val parameters = parameterInfo.parameters.mapIndexed { index, parameter ->
|
||||||
|
ValueParameterDescriptorImpl(
|
||||||
|
methodDescriptor, null, index, Annotations.EMPTY, Name.identifier("p$index"),
|
||||||
|
parameter.targetType, false, false, false, null, SourceElement.NO_SOURCE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
methodDescriptor.initialize(
|
||||||
|
null, classDescriptor.thisAsReceiverParameter, emptyList(),
|
||||||
|
parameters, returnType, Modality.FINAL, Visibilities.PUBLIC
|
||||||
|
)
|
||||||
|
|
||||||
|
val memberScope = EvaluatorMemberScopeForMethod(methodDescriptor)
|
||||||
|
|
||||||
|
val constructor = ClassConstructorDescriptorImpl.create(classDescriptor, Annotations.EMPTY, true, classDescriptor.source)
|
||||||
|
classDescriptor.initialize(memberScope, setOf(constructor), constructor)
|
||||||
|
|
||||||
|
return Pair(classDescriptor, methodDescriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EvaluatorMemberScopeForMethod(private val methodDescriptor: SimpleFunctionDescriptor) : MemberScopeImpl() {
|
||||||
|
override fun getContributedFunctions(name: Name, location: LookupLocation): Collection<SimpleFunctionDescriptor> {
|
||||||
|
return if (name == methodDescriptor.name) {
|
||||||
|
listOf(methodDescriptor)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getContributedDescriptors(
|
||||||
|
kindFilter: DescriptorKindFilter,
|
||||||
|
nameFilter: (Name) -> Boolean
|
||||||
|
): Collection<DeclarationDescriptor> {
|
||||||
|
return if (kindFilter.accepts(methodDescriptor) && nameFilter(methodDescriptor.name)) {
|
||||||
|
listOf(methodDescriptor)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFunctionNames() = setOf(methodDescriptor.name)
|
||||||
|
|
||||||
|
override fun printScopeStructure(p: Printer) {
|
||||||
|
p.println(this::class.java.simpleName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EvaluatorModuleDescriptor(
|
||||||
|
val codeFragment: KtCodeFragment,
|
||||||
|
val moduleDescriptor: ModuleDescriptor,
|
||||||
|
resolveSession: ResolveSession
|
||||||
|
) : ModuleDescriptor by moduleDescriptor {
|
||||||
|
private val declarationProvider = object : PackageMemberDeclarationProvider {
|
||||||
|
override fun getPackageFiles() = listOf(codeFragment)
|
||||||
|
override fun containsFile(file: KtFile) = file == codeFragment
|
||||||
|
|
||||||
|
override fun getDeclarationNames() = emptySet<Name>()
|
||||||
|
override fun getDeclarations(kindFilter: DescriptorKindFilter, nameFilter: (Name) -> Boolean) = emptyList<KtDeclaration>()
|
||||||
|
override fun getClassOrObjectDeclarations(name: Name) = emptyList<KtClassOrObjectInfo<*>>()
|
||||||
|
override fun getAllDeclaredSubPackages(nameFilter: (Name) -> Boolean) = emptyList<FqName>()
|
||||||
|
override fun getFunctionDeclarations(name: Name) = emptyList<KtNamedFunction>()
|
||||||
|
override fun getPropertyDeclarations(name: Name) = emptyList<KtProperty>()
|
||||||
|
override fun getTypeAliasDeclarations(name: Name) = emptyList<KtTypeAlias>()
|
||||||
|
override fun getDestructuringDeclarationsEntries(name: Name) = emptyList<KtDestructuringDeclarationEntry>()
|
||||||
|
override fun getScriptDeclarations(name: Name) = emptyList<KtScriptInfo>()
|
||||||
|
}
|
||||||
|
|
||||||
|
val packageFragmentForEvaluator = LazyPackageDescriptor(this, FqName.ROOT, resolveSession, declarationProvider)
|
||||||
|
|
||||||
|
override fun getPackage(fqName: FqName): PackageViewDescriptor {
|
||||||
|
val originalPackageDescriptor = moduleDescriptor.getPackage(fqName)
|
||||||
|
if (fqName != FqName.ROOT) {
|
||||||
|
return originalPackageDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
return object : DeclarationDescriptorImpl(Annotations.EMPTY, fqName.shortNameOrSpecial()), PackageViewDescriptor {
|
||||||
|
override fun getContainingDeclaration() = originalPackageDescriptor.containingDeclaration
|
||||||
|
|
||||||
|
override val fqName get() = originalPackageDescriptor.fqName
|
||||||
|
override val module get() = this@EvaluatorModuleDescriptor
|
||||||
|
|
||||||
|
override val memberScope by lazy {
|
||||||
|
if (fragments.isEmpty()) {
|
||||||
|
MemberScope.Empty
|
||||||
|
} else {
|
||||||
|
val scopes = fragments.map { it.getMemberScope() } + SubpackagesScope(module, fqName)
|
||||||
|
ChainedMemberScope("package view scope for $fqName in ${module.name}", scopes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val fragments by lazy { originalPackageDescriptor.fragments + packageFragmentForEvaluator }
|
||||||
|
|
||||||
|
override fun <R, D> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D): R {
|
||||||
|
return visitor.visitPackageViewDescriptor(this, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val OutputFile.internalClassName: String
|
||||||
|
get() = relativePath.removeSuffix(".class").replace('/', '.')
|
||||||
+349
@@ -0,0 +1,349 @@
|
|||||||
|
/*
|
||||||
|
* 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.compilation
|
||||||
|
|
||||||
|
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||||
|
import org.jetbrains.kotlin.codegen.CodeFragmentCodegenInfo
|
||||||
|
import org.jetbrains.kotlin.codegen.getCallLabelForLambdaArgument
|
||||||
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
|
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldPropertyDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory.Companion.FAKE_JAVA_CONTEXT_FUNCTION_NAME
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
|
import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_3_FQ_NAME
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
||||||
|
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
|
||||||
|
interface CodeFragmentParameter {
|
||||||
|
val kind: Kind
|
||||||
|
val name: String
|
||||||
|
val debugString: String
|
||||||
|
|
||||||
|
enum class Kind {
|
||||||
|
ORDINARY, EXTENSION_RECEIVER, DISPATCH_RECEIVER, COROUTINE_CONTEXT, LOCAL_FUNCTION,
|
||||||
|
FAKE_JAVA_OUTER_CLASS, FIELD_VAR, DEBUG_LABEL
|
||||||
|
}
|
||||||
|
|
||||||
|
class Smart(
|
||||||
|
val dumb: Dumb,
|
||||||
|
override val targetType: KotlinType,
|
||||||
|
override val targetDescriptor: DeclarationDescriptor
|
||||||
|
) : CodeFragmentParameter by dumb, CodeFragmentCodegenInfo.IParameter
|
||||||
|
|
||||||
|
data class Dumb(
|
||||||
|
override val kind: Kind,
|
||||||
|
override val name: String,
|
||||||
|
override val debugString: String = name
|
||||||
|
) : CodeFragmentParameter
|
||||||
|
}
|
||||||
|
|
||||||
|
class CodeFragmentParameterInfo(
|
||||||
|
val parameters: List<CodeFragmentParameter.Smart>,
|
||||||
|
val crossingBounds: Set<CodeFragmentParameter.Dumb>
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
The purpose of this class is to figure out what parameters the received code fragment captures.
|
||||||
|
It handles both directly mentioned names such as local variables or parameters and implicit values (dispatch/extension receivers).
|
||||||
|
*/
|
||||||
|
class CodeFragmentParameterAnalyzer(private val codeFragment: KtCodeFragment, private val bindingContext: BindingContext) {
|
||||||
|
private val parameters = LinkedHashMap<DeclarationDescriptor, Smart>()
|
||||||
|
private val crossingBounds = mutableSetOf<Dumb>()
|
||||||
|
|
||||||
|
private val onceUsedChecker = OnceUsedChecker(CodeFragmentParameterAnalyzer::class.java)
|
||||||
|
|
||||||
|
fun analyze(): CodeFragmentParameterInfo {
|
||||||
|
onceUsedChecker.trigger()
|
||||||
|
|
||||||
|
codeFragment.accept(object : KtTreeVisitor<Unit>() {
|
||||||
|
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression, data: Unit?): Void? {
|
||||||
|
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return null
|
||||||
|
|
||||||
|
// Capture dispatch receiver for the extension callable
|
||||||
|
run {
|
||||||
|
val descriptor = resolvedCall.resultingDescriptor as? CallableDescriptor
|
||||||
|
val containingClass = descriptor?.containingDeclaration as? ClassDescriptor
|
||||||
|
val extensionParameter = descriptor?.extensionReceiverParameter
|
||||||
|
if (descriptor != null && descriptor !is DebuggerFieldPropertyDescriptor
|
||||||
|
&& extensionParameter != null && containingClass != null
|
||||||
|
) {
|
||||||
|
if (containingClass.kind != ClassKind.OBJECT) {
|
||||||
|
processDispatchReceiver(containingClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runReadAction { expression.isDotSelector() }) {
|
||||||
|
// The receiver expression is already captured for this reference
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCodeFragmentDeclaration(resolvedCall.resultingDescriptor)) {
|
||||||
|
// The reference is from the code fragment we analyze, no need to capture
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
var processed = false
|
||||||
|
|
||||||
|
val extensionReceiver = resolvedCall.extensionReceiver
|
||||||
|
if (extensionReceiver is ImplicitReceiver) {
|
||||||
|
val descriptor = extensionReceiver.declarationDescriptor
|
||||||
|
val parameter = processReceiver(extensionReceiver)
|
||||||
|
checkBounds(descriptor, expression, parameter)
|
||||||
|
processed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val dispatchReceiver = resolvedCall.dispatchReceiver
|
||||||
|
if (dispatchReceiver is ImplicitReceiver) {
|
||||||
|
val descriptor = dispatchReceiver.declarationDescriptor
|
||||||
|
val parameter = processReceiver(dispatchReceiver)
|
||||||
|
checkBounds(descriptor, expression, parameter)
|
||||||
|
processed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!processed && resolvedCall.resultingDescriptor is SyntheticFieldDescriptor) {
|
||||||
|
val descriptor = resolvedCall.resultingDescriptor as SyntheticFieldDescriptor
|
||||||
|
val parameter = processSyntheticFieldVariable(descriptor)
|
||||||
|
checkBounds(descriptor, expression, parameter)
|
||||||
|
processed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a reference has receivers, we can calculate its value using them, no need to capture
|
||||||
|
if (!processed) {
|
||||||
|
val descriptor = resolvedCall.resultingDescriptor
|
||||||
|
val parameter = processDebugLabel(descriptor)
|
||||||
|
?: processCoroutineContextCall(descriptor)
|
||||||
|
?: processSimpleNameExpression(descriptor)
|
||||||
|
checkBounds(descriptor, expression, parameter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitThisExpression(expression: KtThisExpression, data: Unit?): Void? {
|
||||||
|
val instanceReference = runReadAction { expression.instanceReference }
|
||||||
|
val target = bindingContext[BindingContext.REFERENCE_TARGET, instanceReference]
|
||||||
|
|
||||||
|
if (isCodeFragmentDeclaration(target)) {
|
||||||
|
// The reference is from the code fragment we analyze, no need to capture
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val parameter = when (target) {
|
||||||
|
is ClassDescriptor -> processDispatchReceiver(target)
|
||||||
|
is CallableDescriptor -> {
|
||||||
|
val type = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, expression]?.type
|
||||||
|
type?.let { processExtensionReceiver(target, type, expression.getLabelName()) }
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameter != null) {
|
||||||
|
checkBounds(target, expression, parameter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitSuperExpression(expression: KtSuperExpression, data: Unit?): Void {
|
||||||
|
throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'super' call expression is not supported")
|
||||||
|
}
|
||||||
|
}, Unit)
|
||||||
|
|
||||||
|
return CodeFragmentParameterInfo(parameters.values.toList(), crossingBounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processReceiver(receiver: ImplicitReceiver): Smart? {
|
||||||
|
return when (receiver) {
|
||||||
|
is ImplicitClassReceiver -> processDispatchReceiver(receiver.classDescriptor)
|
||||||
|
is ExtensionReceiver -> processExtensionReceiver(receiver.declarationDescriptor, receiver.type, null)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processDispatchReceiver(descriptor: ClassDescriptor): Smart? {
|
||||||
|
if (descriptor.kind == ClassKind.OBJECT) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val type = descriptor.defaultType
|
||||||
|
return parameters.getOrPut(descriptor) {
|
||||||
|
Smart(Dumb(Kind.DISPATCH_RECEIVER, "", AsmUtil.THIS + "@" + descriptor.name.asString()), type, descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processExtensionReceiver(descriptor: CallableDescriptor, receiverType: KotlinType, label: String?): Smart? {
|
||||||
|
if (isFakeFunctionForJavaContext(descriptor)) {
|
||||||
|
return processFakeJavaCodeReceiver(descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
val actualLabel = label ?: getLabel(descriptor) ?: return null
|
||||||
|
val receiverParameter = descriptor.extensionReceiverParameter ?: return null
|
||||||
|
|
||||||
|
return parameters.getOrPut(descriptor) {
|
||||||
|
Smart(Dumb(Kind.EXTENSION_RECEIVER, actualLabel, AsmUtil.THIS + "@" + actualLabel), receiverType, receiverParameter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLabel(callableDescriptor: CallableDescriptor): String? {
|
||||||
|
val source = callableDescriptor.source.getPsi()
|
||||||
|
|
||||||
|
if (source is KtFunctionLiteral) {
|
||||||
|
getCallLabelForLambdaArgument(source, bindingContext)?.let { return it }
|
||||||
|
}
|
||||||
|
|
||||||
|
return callableDescriptor.name.takeIf { !it.isSpecial }?.asString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isFakeFunctionForJavaContext(descriptor: CallableDescriptor): Boolean {
|
||||||
|
return descriptor is FunctionDescriptor
|
||||||
|
&& descriptor.name.asString() == FAKE_JAVA_CONTEXT_FUNCTION_NAME
|
||||||
|
&& codeFragment.getCopyableUserData(KtCodeFragment.FAKE_CONTEXT_FOR_JAVA_FILE) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processFakeJavaCodeReceiver(descriptor: CallableDescriptor): Smart? {
|
||||||
|
val receiverParameter = descriptor
|
||||||
|
.takeIf { descriptor is FunctionDescriptor }
|
||||||
|
?.extensionReceiverParameter
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
val label = FAKE_JAVA_CONTEXT_FUNCTION_NAME
|
||||||
|
val type = receiverParameter.type
|
||||||
|
return parameters.getOrPut(descriptor) {
|
||||||
|
Smart(Dumb(Kind.FAKE_JAVA_OUTER_CLASS, label, AsmUtil.THIS), type, receiverParameter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processSyntheticFieldVariable(descriptor: SyntheticFieldDescriptor): Smart? {
|
||||||
|
val propertyDescriptor = descriptor.propertyDescriptor
|
||||||
|
val fieldName = propertyDescriptor.name.asString()
|
||||||
|
val type = propertyDescriptor.type
|
||||||
|
return parameters.getOrPut(descriptor) {
|
||||||
|
Smart(Dumb(Kind.FIELD_VAR, fieldName, "field"), type, descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processSimpleNameExpression(target: DeclarationDescriptor): Smart? {
|
||||||
|
if ((target as? DeclarationDescriptorWithVisibility)?.visibility != Visibilities.LOCAL) {
|
||||||
|
// No need to capture non-local declarations
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (target) {
|
||||||
|
is FunctionDescriptor -> {
|
||||||
|
val type = SingleAbstractMethodUtils.getFunctionTypeForAbstractMethod(target, false)
|
||||||
|
parameters.getOrPut(target) {
|
||||||
|
Smart(Dumb(Kind.LOCAL_FUNCTION, target.name.asString()), type, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ValueDescriptor -> {
|
||||||
|
parameters.getOrPut(target) {
|
||||||
|
val type = target.type
|
||||||
|
Smart(Dumb(Kind.ORDINARY, target.name.asString()), type, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processCoroutineContextCall(target: DeclarationDescriptor): Smart? {
|
||||||
|
if (target is PropertyDescriptor && target.fqNameSafe == COROUTINE_CONTEXT_1_3_FQ_NAME) {
|
||||||
|
return parameters.getOrPut(target) {
|
||||||
|
Smart(Dumb(Kind.COROUTINE_CONTEXT, ""), target.type, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processDebugLabel(target: DeclarationDescriptor): Smart? {
|
||||||
|
val debugLabelPropertyDescriptor = target as? DebugLabelPropertyDescriptor ?: return null
|
||||||
|
val labelName = debugLabelPropertyDescriptor.labelName
|
||||||
|
val debugString = debugLabelPropertyDescriptor.name.asString()
|
||||||
|
|
||||||
|
return parameters.getOrPut(target) {
|
||||||
|
val type = debugLabelPropertyDescriptor.type
|
||||||
|
Smart(Dumb(Kind.DEBUG_LABEL, labelName, debugString), type, debugLabelPropertyDescriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkBounds(descriptor: DeclarationDescriptor?, expression: KtExpression, parameter: Smart?) {
|
||||||
|
if (parameter == null || descriptor !is DeclarationDescriptorWithSource) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val targetPsi = descriptor.source.getPsi()
|
||||||
|
if (targetPsi != null && doesCrossInlineBounds(expression, targetPsi)) {
|
||||||
|
crossingBounds += parameter.dumb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doesCrossInlineBounds(expression: PsiElement, declaration: PsiElement): Boolean {
|
||||||
|
val declarationParent = declaration.parent ?: return false
|
||||||
|
var currentParent: PsiElement? = expression.parent?.takeIf { it.isInside(declarationParent) } ?: return false
|
||||||
|
|
||||||
|
while (currentParent != null && currentParent != declarationParent) {
|
||||||
|
if (currentParent is KtFunction) {
|
||||||
|
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 fun isCodeFragmentDeclaration(descriptor: DeclarationDescriptor?): Boolean {
|
||||||
|
if (descriptor is ValueParameterDescriptor && isCodeFragmentDeclaration(descriptor.containingDeclaration)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (descriptor !is DeclarationDescriptorWithSource) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptor.source.getPsi()?.containingFile is KtCodeFragment
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OnceUsedChecker(private val clazz: Class<*>) {
|
||||||
|
private var used = false
|
||||||
|
|
||||||
|
fun trigger() {
|
||||||
|
if (used) {
|
||||||
|
error(clazz.name + " may be only used once")
|
||||||
|
}
|
||||||
|
|
||||||
|
used = true
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.compilation
|
||||||
|
|
||||||
|
import com.intellij.debugger.SourcePosition
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||||
|
|
||||||
|
data class CompiledDataDescriptor(
|
||||||
|
val classes: List<ClassToLoad>,
|
||||||
|
val parameters: List<CodeFragmentParameter.Dumb>,
|
||||||
|
val crossingBounds: Set<CodeFragmentParameter.Dumb>,
|
||||||
|
val mainMethodSignature: CodeFragmentCompiler.MethodSignature,
|
||||||
|
val sourcePosition: SourcePosition
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun from(result: CodeFragmentCompiler.CompilationResult, sourcePosition: SourcePosition): CompiledDataDescriptor {
|
||||||
|
val localFunctionSuffixes = result.localFunctionSuffixes
|
||||||
|
|
||||||
|
val dumbParameters = ArrayList<CodeFragmentParameter.Dumb>(result.parameterInfo.parameters.size)
|
||||||
|
for (parameter in result.parameterInfo.parameters) {
|
||||||
|
val dumb = parameter.dumb
|
||||||
|
if (dumb.kind == CodeFragmentParameter.Kind.LOCAL_FUNCTION) {
|
||||||
|
val suffix = localFunctionSuffixes[dumb]
|
||||||
|
if (suffix != null) {
|
||||||
|
dumbParameters += dumb.copy(name = dumb.name + suffix)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dumbParameters += dumb
|
||||||
|
}
|
||||||
|
|
||||||
|
return CompiledDataDescriptor(
|
||||||
|
result.classes,
|
||||||
|
dumbParameters,
|
||||||
|
result.parameterInfo.crossingBounds,
|
||||||
|
result.mainMethodSignature,
|
||||||
|
sourcePosition
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val CompiledDataDescriptor.mainClass: ClassToLoad
|
||||||
|
get() = classes.first { it.isMainClass }
|
||||||
+162
@@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* 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.compilation
|
||||||
|
|
||||||
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
|
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||||
|
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
|
||||||
|
import org.jetbrains.kotlin.builtins.PrimitiveType
|
||||||
|
import com.sun.jdi.*
|
||||||
|
import com.sun.jdi.Type as JdiType
|
||||||
|
import org.jetbrains.kotlin.backend.common.lower.SimpleMemberScope
|
||||||
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
|
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||||
|
import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl
|
||||||
|
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
|
||||||
|
import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.getClassDescriptor
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||||
|
import org.jetbrains.kotlin.psi.externalDescriptors
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.types.Variance
|
||||||
|
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||||
|
|
||||||
|
class DebugLabelPropertyDescriptorProvider(
|
||||||
|
val codeFragment: KtCodeFragment,
|
||||||
|
val moduleDescriptor: ModuleDescriptor,
|
||||||
|
val debugProcess: DebugProcessImpl
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun getMarkupMap(debugProcess: DebugProcessImpl) = doGetMarkupMap(debugProcess) ?: emptyMap()
|
||||||
|
|
||||||
|
private fun doGetMarkupMap(debugProcess: DebugProcessImpl): Map<out Value?, ValueMarkup>? {
|
||||||
|
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
return NodeDescriptorImpl.getMarkupMap(debugProcess)
|
||||||
|
}
|
||||||
|
|
||||||
|
val debugSession = debugProcess.session.xDebugSession as? XDebugSessionImpl
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return debugSession?.valueMarkers?.allMarkers?.filterKeys { it is Value? } as Map<out Value?, ValueMarkup>?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun supplyDebugLabels() {
|
||||||
|
val packageFragment = object : PackageFragmentDescriptorImpl(moduleDescriptor, FqName.ROOT) {
|
||||||
|
val properties = createDebugLabelDescriptors(this)
|
||||||
|
override fun getMemberScope() = SimpleMemberScope(properties)
|
||||||
|
}
|
||||||
|
|
||||||
|
codeFragment.externalDescriptors = packageFragment.properties
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDebugLabelDescriptors(containingDeclaration: PackageFragmentDescriptor): List<PropertyDescriptor> {
|
||||||
|
val markupMap = getMarkupMap(debugProcess)
|
||||||
|
|
||||||
|
val result = ArrayList<PropertyDescriptor>(markupMap.size)
|
||||||
|
|
||||||
|
nextValue@ for ((value, markup) in markupMap) {
|
||||||
|
val labelName = markup.text
|
||||||
|
val kotlinType = value?.type()?.let { convertType(it) } ?: moduleDescriptor.builtIns.nullableAnyType
|
||||||
|
result += createDebugLabelDescriptor(labelName, kotlinType, containingDeclaration)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDebugLabelDescriptor(
|
||||||
|
labelName: String,
|
||||||
|
type: KotlinType,
|
||||||
|
containingDeclaration: PackageFragmentDescriptor
|
||||||
|
): PropertyDescriptor {
|
||||||
|
val propertyDescriptor = DebugLabelPropertyDescriptor(containingDeclaration, labelName)
|
||||||
|
propertyDescriptor.setType(type, emptyList(), null, null)
|
||||||
|
|
||||||
|
val getterDescriptor = PropertyGetterDescriptorImpl(
|
||||||
|
propertyDescriptor,
|
||||||
|
Annotations.EMPTY,
|
||||||
|
Modality.FINAL,
|
||||||
|
Visibilities.PUBLIC,
|
||||||
|
/* isDefault = */ false,
|
||||||
|
/* isExternal = */ false,
|
||||||
|
/* isInline = */ false,
|
||||||
|
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||||
|
/* original = */ null,
|
||||||
|
SourceElement.NO_SOURCE
|
||||||
|
).apply { initialize(type) }
|
||||||
|
|
||||||
|
propertyDescriptor.initialize(getterDescriptor, null)
|
||||||
|
return propertyDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convertType(type: JdiType): KotlinType {
|
||||||
|
val builtIns = moduleDescriptor.builtIns
|
||||||
|
|
||||||
|
return when (type) {
|
||||||
|
is VoidType -> builtIns.unitType
|
||||||
|
is LongType -> builtIns.longType
|
||||||
|
is DoubleType -> builtIns.doubleType
|
||||||
|
is CharType -> builtIns.charType
|
||||||
|
is FloatType -> builtIns.floatType
|
||||||
|
is ByteType -> builtIns.byteType
|
||||||
|
is IntegerType -> builtIns.intType
|
||||||
|
is BooleanType -> builtIns.booleanType
|
||||||
|
is ShortType -> builtIns.shortType
|
||||||
|
is ArrayType -> {
|
||||||
|
when (val componentType = type.componentType()) {
|
||||||
|
is VoidType -> builtIns.getArrayType(Variance.INVARIANT, builtIns.unitType)
|
||||||
|
is LongType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.LONG)
|
||||||
|
is DoubleType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.DOUBLE)
|
||||||
|
is CharType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.CHAR)
|
||||||
|
is FloatType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.FLOAT)
|
||||||
|
is ByteType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.BYTE)
|
||||||
|
is IntegerType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.INT)
|
||||||
|
is BooleanType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.BOOLEAN)
|
||||||
|
is ShortType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.SHORT)
|
||||||
|
else -> builtIns.getArrayType(Variance.INVARIANT, convertReferenceType(componentType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ReferenceType -> convertReferenceType(type)
|
||||||
|
else -> builtIns.anyType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convertReferenceType(type: JdiType): KotlinType {
|
||||||
|
require(type is ClassType || type is InterfaceType)
|
||||||
|
|
||||||
|
val asmType = AsmType.getType(type.signature())
|
||||||
|
val project = codeFragment.project
|
||||||
|
val classDescriptor = asmType.getClassDescriptor(GlobalSearchScope.allScope(project), mapBuiltIns = false)
|
||||||
|
?: return moduleDescriptor.builtIns.nullableAnyType
|
||||||
|
return classDescriptor.defaultType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DebugLabelPropertyDescriptor(
|
||||||
|
containingDeclaration: DeclarationDescriptor,
|
||||||
|
val labelName: String
|
||||||
|
) : PropertyDescriptorImpl(
|
||||||
|
containingDeclaration,
|
||||||
|
null,
|
||||||
|
Annotations.EMPTY,
|
||||||
|
Modality.FINAL,
|
||||||
|
Visibilities.PUBLIC,
|
||||||
|
/*isVar = */false,
|
||||||
|
Name.identifier(labelName + "_DebugLabel"),
|
||||||
|
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||||
|
SourceElement.NO_SOURCE,
|
||||||
|
/*lateInit = */false,
|
||||||
|
/*isConst = */false,
|
||||||
|
/*isExpect = */false,
|
||||||
|
/*isActual = */false,
|
||||||
|
/*isExternal = */false,
|
||||||
|
/*isDelegated = */false
|
||||||
|
)
|
||||||
-374
@@ -1,374 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2010-2015 JetBrains s.r.o.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
|
||||||
|
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
|
||||||
import com.intellij.diagnostic.LogMessageEx
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
|
||||||
import com.intellij.openapi.diagnostic.Attachment
|
|
||||||
import com.intellij.openapi.util.Disposer
|
|
||||||
import com.intellij.openapi.util.Key
|
|
||||||
import com.intellij.psi.PsiElement
|
|
||||||
import com.intellij.psi.PsiFile
|
|
||||||
import com.intellij.util.ExceptionUtil
|
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithContent
|
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
|
||||||
import org.jetbrains.kotlin.idea.core.replaced
|
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
|
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.ErrorMessage
|
|
||||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.Status
|
|
||||||
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
|
|
||||||
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.idea.util.psi.patternMatching.toRange
|
|
||||||
import org.jetbrains.kotlin.psi.*
|
|
||||||
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
|
|
||||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext.SMARTCAST
|
|
||||||
|
|
||||||
fun getFunctionForExtractedFragment(
|
|
||||||
codeFragment: KtCodeFragment,
|
|
||||||
breakpointFile: PsiFile,
|
|
||||||
breakpointLine: Int
|
|
||||||
): ExtractionResult? {
|
|
||||||
|
|
||||||
fun getErrorMessageForExtractFunctionResult(analysisResult: AnalysisResult, tmpFile: KtFile): String {
|
|
||||||
if (ApplicationManager.getApplication().isInternal) {
|
|
||||||
val attachments = arrayOf(
|
|
||||||
attachmentByPsiFile(tmpFile),
|
|
||||||
attachmentByPsiFile(breakpointFile),
|
|
||||||
attachmentByPsiFile(codeFragment),
|
|
||||||
Attachment("breakpoint.info", "line: $breakpointLine"),
|
|
||||||
Attachment("context.info", codeFragment.context?.text ?: "null"),
|
|
||||||
Attachment("errors.info", analysisResult.messages.joinToString("\n") { "$it: ${it.renderMessage()}" })
|
|
||||||
)
|
|
||||||
LOG.error(
|
|
||||||
LogMessageEx.createEvent(
|
|
||||||
"Internal error during evaluate expression",
|
|
||||||
ExceptionUtil.getThrowableText(Throwable("Extract function fails with ${analysisResult.messages.joinToString { it.name }}")),
|
|
||||||
mergeAttachments(*attachments)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return analysisResult.messages.joinToString(", ") { errorMessage ->
|
|
||||||
val message = when (errorMessage) {
|
|
||||||
ErrorMessage.NO_EXPRESSION -> "Cannot perform an action without an expression"
|
|
||||||
ErrorMessage.NO_CONTAINER -> "Cannot perform an action at this breakpoint ${breakpointFile.name}:$breakpointLine"
|
|
||||||
ErrorMessage.SYNTAX_ERRORS -> "Cannot perform an action due to erroneous code"
|
|
||||||
ErrorMessage.SUPER_CALL -> "Cannot perform an action for expression with super call"
|
|
||||||
ErrorMessage.DENOTABLE_TYPES -> "Cannot perform an action because following types are unavailable from debugger scope"
|
|
||||||
ErrorMessage.ERROR_TYPES -> "Cannot perform an action because this code fragment contains erroneous types"
|
|
||||||
ErrorMessage.MULTIPLE_EXIT_POINTS,
|
|
||||||
ErrorMessage.DECLARATIONS_OUT_OF_SCOPE,
|
|
||||||
ErrorMessage.OUTPUT_AND_EXIT_POINT,
|
|
||||||
ErrorMessage.DECLARATIONS_ARE_USED_OUTSIDE -> "Cannot perform an action for this expression"
|
|
||||||
ErrorMessage.MULTIPLE_OUTPUT -> throw AssertionError("Unexpected error: $errorMessage")
|
|
||||||
}
|
|
||||||
errorMessage.additionalInfo?.let { "$message: ${it.joinToString(", ")}" } ?: message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun generateFunction(): ExtractionResult? {
|
|
||||||
val originalFile = codeFragment.getContextContainingFile() ?: return null
|
|
||||||
|
|
||||||
val newDebugExpressions = addDebugExpressionIntoTmpFileForExtractFunction(originalFile, codeFragment, breakpointLine)
|
|
||||||
if (newDebugExpressions.isEmpty()) return null
|
|
||||||
val tmpFile = newDebugExpressions.first().containingKtFile
|
|
||||||
|
|
||||||
if (LOG.isDebugEnabled) {
|
|
||||||
LOG.debug("TMP_FILE:\n${runReadAction { tmpFile.text }}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val targetSibling = tmpFile.declarations.firstOrNull() ?: return null
|
|
||||||
|
|
||||||
val options = ExtractionOptions(
|
|
||||||
inferUnitTypeForUnusedValues = false,
|
|
||||||
enableListBoxing = true,
|
|
||||||
allowSpecialClassNames = true,
|
|
||||||
captureLocalFunctions = true,
|
|
||||||
canWrapInWith = true
|
|
||||||
)
|
|
||||||
val extractionData = ExtractionData(tmpFile, newDebugExpressions.toRange(), targetSibling, null, options)
|
|
||||||
try {
|
|
||||||
val analysisResult = extractionData.performAnalysis()
|
|
||||||
if (analysisResult.status != Status.SUCCESS) {
|
|
||||||
throw EvaluateExceptionUtil.createEvaluateException(getErrorMessageForExtractFunctionResult(analysisResult, tmpFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
val validationResult = analysisResult.descriptor!!.validate()
|
|
||||||
if (!validationResult.conflicts.isEmpty) {
|
|
||||||
throw EvaluateExceptionUtil.createEvaluateException(
|
|
||||||
"Following declarations are unavailable in debug scope: ${validationResult.conflicts.keySet().joinToString(
|
|
||||||
","
|
|
||||||
) { it.text }}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val generatorOptions = ExtractionGeneratorOptions(
|
|
||||||
inTempFile = true,
|
|
||||||
dummyName = GENERATED_FUNCTION_NAME,
|
|
||||||
allowExpressionBody = false
|
|
||||||
)
|
|
||||||
return ExtractionGeneratorConfiguration(validationResult.descriptor, generatorOptions).generateDeclaration()
|
|
||||||
} finally {
|
|
||||||
Disposer.dispose(extractionData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return runReadAction { generateFunction() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addDebugExpressionIntoTmpFileForExtractFunction(originalFile: KtFile, codeFragment: KtCodeFragment, line: Int): List<KtExpression> {
|
|
||||||
codeFragment.markContextElement()
|
|
||||||
codeFragment.markSmartCasts()
|
|
||||||
|
|
||||||
val tmpFile = originalFile.copy() as KtFile
|
|
||||||
tmpFile.suppressDiagnosticsInDebugMode = true
|
|
||||||
tmpFile.analysisContext = originalFile.analysisContext
|
|
||||||
|
|
||||||
val contextElement = getExpressionToAddDebugExpressionBefore(tmpFile, codeFragment.getOriginalContext(), line) ?: return emptyList()
|
|
||||||
|
|
||||||
addImportsToFile(codeFragment.importsAsImportList(), tmpFile)
|
|
||||||
|
|
||||||
val contentElementsInTmpFile = addDebugExpressionBeforeContextElement(codeFragment, contextElement)
|
|
||||||
contentElementsInTmpFile.forEach { it.insertSmartCasts() }
|
|
||||||
|
|
||||||
codeFragment.clearContextElement()
|
|
||||||
codeFragment.clearSmartCasts()
|
|
||||||
|
|
||||||
return contentElementsInTmpFile
|
|
||||||
}
|
|
||||||
|
|
||||||
private var PsiElement.IS_CONTEXT_ELEMENT: Boolean by NotNullablePsiCopyableUserDataProperty(Key.create("IS_CONTEXT_ELEMENT"), false)
|
|
||||||
|
|
||||||
private fun KtCodeFragment.markContextElement() {
|
|
||||||
getOriginalContext()?.IS_CONTEXT_ELEMENT = true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun KtCodeFragment.clearContextElement() {
|
|
||||||
getOriginalContext()?.IS_CONTEXT_ELEMENT = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun KtFile.findContextElement(): KtElement? {
|
|
||||||
return this.findDescendantOfType { it.IS_CONTEXT_ELEMENT }
|
|
||||||
}
|
|
||||||
|
|
||||||
private var PsiElement.DEBUG_SMART_CAST: PsiElement? by CopyablePsiUserDataProperty(Key.create("DEBUG_SMART_CAST"))
|
|
||||||
|
|
||||||
private fun KtCodeFragment.markSmartCasts() {
|
|
||||||
val bindingContext = runInReadActionWithWriteActionPriorityWithPCE { analyzeWithContent() }
|
|
||||||
val factory = KtPsiFactory(project)
|
|
||||||
|
|
||||||
getContentElement()?.forEachDescendantOfType<KtExpression> { expression ->
|
|
||||||
val smartCast = bindingContext.get(SMARTCAST, expression)?.defaultType
|
|
||||||
if (smartCast != null) {
|
|
||||||
val smartCastedExpression = factory.createExpressionByPattern(
|
|
||||||
"($0 as ${DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(smartCast)})",
|
|
||||||
expression
|
|
||||||
) as KtParenthesizedExpression
|
|
||||||
|
|
||||||
expression.DEBUG_SMART_CAST = smartCastedExpression
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun KtExpression.insertSmartCasts() {
|
|
||||||
forEachDescendantOfType<KtExpression> {
|
|
||||||
val replacement = it.DEBUG_SMART_CAST
|
|
||||||
if (replacement != null) runReadAction { it.replace(replacement) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun KtCodeFragment.clearSmartCasts() {
|
|
||||||
getContentElement()?.forEachDescendantOfType<KtExpression> { it.DEBUG_SMART_CAST = null }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addImportsToFile(newImportList: KtImportList?, tmpFile: KtFile) {
|
|
||||||
if (newImportList != null && newImportList.imports.isNotEmpty()) {
|
|
||||||
val tmpFileImportList = tmpFile.importList
|
|
||||||
val psiFactory = KtPsiFactory(tmpFile)
|
|
||||||
if (tmpFileImportList == null) {
|
|
||||||
val packageDirective = tmpFile.packageDirective
|
|
||||||
tmpFile.addAfter(psiFactory.createNewLine(), packageDirective)
|
|
||||||
tmpFile.addAfter(newImportList, tmpFile.packageDirective)
|
|
||||||
} else {
|
|
||||||
newImportList.imports.forEach {
|
|
||||||
tmpFileImportList.add(psiFactory.createNewLine())
|
|
||||||
tmpFileImportList.add(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpFileImportList.add(psiFactory.createNewLine())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getExpressionToAddDebugExpressionBefore(tmpFile: KtFile, contextElement: PsiElement?, line: Int): PsiElement? {
|
|
||||||
if (contextElement == null) {
|
|
||||||
val lineStart = CodeInsightUtils.getStartLineOffset(tmpFile, line) ?: return null
|
|
||||||
|
|
||||||
val elementAtOffset = tmpFile.findElementAt(lineStart) ?: return null
|
|
||||||
|
|
||||||
return CodeInsightUtils.getTopmostElementAtOffset(elementAtOffset, lineStart)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shouldStop(el: PsiElement?, p: PsiElement?) = p is KtBlockExpression || el is KtDeclaration || el is KtFile
|
|
||||||
|
|
||||||
val elementAt = tmpFile.findContextElement()
|
|
||||||
|
|
||||||
var parent = elementAt?.parent
|
|
||||||
if (shouldStop(elementAt, parent)) {
|
|
||||||
return elementAt
|
|
||||||
}
|
|
||||||
|
|
||||||
var parentOfParent = parent?.parent
|
|
||||||
|
|
||||||
while (parent != null && parentOfParent != null) {
|
|
||||||
if (shouldStop(parent, parentOfParent)) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
parent = parent.parent
|
|
||||||
parentOfParent = parent?.parent
|
|
||||||
}
|
|
||||||
|
|
||||||
return parent
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addDebugExpressionBeforeContextElement(codeFragment: KtCodeFragment, contextElement: PsiElement): List<KtExpression> {
|
|
||||||
val elementBefore = findElementBefore(contextElement)
|
|
||||||
|
|
||||||
val parent = elementBefore?.parent ?: return emptyList()
|
|
||||||
|
|
||||||
val psiFactory = KtPsiFactory(codeFragment)
|
|
||||||
|
|
||||||
parent.addBefore(psiFactory.createNewLine(), elementBefore)
|
|
||||||
|
|
||||||
fun insertExpression(expr: KtElement?): List<KtExpression> {
|
|
||||||
when (expr) {
|
|
||||||
is KtBlockExpression -> return expr.statements.flatMap(::insertExpression)
|
|
||||||
is KtExpression -> {
|
|
||||||
val newDebugExpression = parent.addBefore(expr, elementBefore)
|
|
||||||
if (newDebugExpression == null) {
|
|
||||||
LOG.error("Couldn't insert debug expression ${expr.text} to context file before ${elementBefore.text}")
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
parent.addBefore(psiFactory.createNewLine(), elementBefore)
|
|
||||||
return listOf(newDebugExpression as KtExpression)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
val containingFile = codeFragment.context?.containingFile
|
|
||||||
if (containingFile is KtCodeFragment) {
|
|
||||||
insertExpression(containingFile.getContentElement() as? KtExpression)
|
|
||||||
}
|
|
||||||
|
|
||||||
val debugExpression = codeFragment.getContentElement() ?: return emptyList()
|
|
||||||
return insertExpression(debugExpression)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findElementBefore(contextElement: PsiElement): PsiElement? {
|
|
||||||
val psiFactory = KtPsiFactory(contextElement)
|
|
||||||
|
|
||||||
fun insertNewInitializer(classBody: KtClassBody): PsiElement? {
|
|
||||||
val initializer = psiFactory.createAnonymousInitializer()
|
|
||||||
val newInitializer = (classBody.addAfter(initializer, classBody.firstChild) as KtAnonymousInitializer)
|
|
||||||
val block = newInitializer.body as KtBlockExpression?
|
|
||||||
return block?.lastChild
|
|
||||||
}
|
|
||||||
|
|
||||||
return when {
|
|
||||||
contextElement is KtFile -> {
|
|
||||||
val fakeFunction = psiFactory.createFunction("fun _debug_fun_() {}")
|
|
||||||
contextElement.add(psiFactory.createNewLine())
|
|
||||||
val newFakeFun = contextElement.add(fakeFunction) as KtNamedFunction
|
|
||||||
newFakeFun.bodyExpression!!.lastChild
|
|
||||||
}
|
|
||||||
contextElement is KtProperty && !contextElement.isLocal -> {
|
|
||||||
val delegateExpressionOrInitializer = contextElement.delegateExpressionOrInitializer
|
|
||||||
if (delegateExpressionOrInitializer != null) {
|
|
||||||
wrapInLambdaCall(delegateExpressionOrInitializer)
|
|
||||||
} else {
|
|
||||||
val getter = contextElement.getter
|
|
||||||
val bodyExpression = getter?.bodyExpression
|
|
||||||
|
|
||||||
if (getter != null && bodyExpression != null) {
|
|
||||||
if (!getter.hasBlockBody()) {
|
|
||||||
wrapInLambdaCall(bodyExpression)
|
|
||||||
} else {
|
|
||||||
(bodyExpression as KtBlockExpression).statements.first()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
contextElement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
contextElement is KtParameter -> {
|
|
||||||
val ownerFunction = contextElement.ownerFunction!!
|
|
||||||
findElementBefore(ownerFunction)
|
|
||||||
}
|
|
||||||
contextElement is KtPrimaryConstructor -> {
|
|
||||||
val classOrObject = contextElement.getContainingClassOrObject()
|
|
||||||
insertNewInitializer(classOrObject.getOrCreateBody())
|
|
||||||
}
|
|
||||||
contextElement is KtClassOrObject -> {
|
|
||||||
insertNewInitializer(contextElement.getOrCreateBody())
|
|
||||||
}
|
|
||||||
contextElement is KtFunctionLiteral -> {
|
|
||||||
val block = contextElement.bodyExpression!!
|
|
||||||
block.statements.firstOrNull() ?: block.lastChild
|
|
||||||
}
|
|
||||||
contextElement is KtDeclarationWithBody && !contextElement.hasBody() -> {
|
|
||||||
val block = psiFactory.createBlock("")
|
|
||||||
val newBlock = contextElement.add(block) as KtBlockExpression
|
|
||||||
newBlock.rBrace
|
|
||||||
}
|
|
||||||
contextElement is KtDeclarationWithBody && !contextElement.hasBlockBody() -> {
|
|
||||||
wrapInLambdaCall(contextElement.bodyExpression!!)
|
|
||||||
}
|
|
||||||
contextElement is KtDeclarationWithBody && contextElement.hasBlockBody() -> {
|
|
||||||
val block = contextElement.bodyBlockExpression!!
|
|
||||||
val last = block.statements.lastOrNull()
|
|
||||||
last as? KtReturnExpression ?: block.rBrace
|
|
||||||
}
|
|
||||||
contextElement is KtWhenEntry -> {
|
|
||||||
val entryExpression = contextElement.expression
|
|
||||||
if (entryExpression is KtBlockExpression) {
|
|
||||||
entryExpression.statements.firstOrNull() ?: entryExpression.lastChild
|
|
||||||
} else {
|
|
||||||
wrapInLambdaCall(entryExpression!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
contextElement
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun replaceByLambdaCall(expression: KtExpression): KtCallExpression {
|
|
||||||
val callExpression = KtPsiFactory(expression).createExpression("{ \n${expression.text} \n}()") as KtCallExpression
|
|
||||||
return expression.replaced(callExpression)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun wrapInLambdaCall(expression: KtExpression): PsiElement? {
|
|
||||||
val replacedBody = replaceByLambdaCall(expression)
|
|
||||||
return (replacedBody.calleeExpression as? KtLambdaExpression)?.bodyExpression?.firstChild
|
|
||||||
}
|
|
||||||
+266
@@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
* 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.variables
|
||||||
|
|
||||||
|
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||||
|
import com.sun.jdi.*
|
||||||
|
import org.jetbrains.kotlin.fileClasses.internalNameWithoutInnerClasses
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder.Result
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.isSubtype
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
|
||||||
|
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||||
|
import com.sun.jdi.Type as JdiType
|
||||||
|
import kotlin.jvm.internal.Ref
|
||||||
|
|
||||||
|
@Suppress("SpellCheckingInspection")
|
||||||
|
class EvaluatorValueConverter(private val executionContext: ExecutionContext) {
|
||||||
|
private companion object {
|
||||||
|
private val UNBOXING_METHOD_NAMES = mapOf(
|
||||||
|
"java/lang/Boolean" to "booleanValue",
|
||||||
|
"java/lang/Character" to "charValue",
|
||||||
|
"java/lang/Byte" to "byteValue",
|
||||||
|
"java/lang/Short" to "shortValue",
|
||||||
|
"java/lang/Integer" to "intValue",
|
||||||
|
"java/lang/Float" to "floatValue",
|
||||||
|
"java/lang/Long" to "longValue",
|
||||||
|
"java/lang/Double" to "doubleValue"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nearly accurate: doesn't do deep checks for Ref wrappers. Use `coerce()` for more precise check.
|
||||||
|
fun typeMatches(requestedType: AsmType, actualTypeObj: JdiType?): Boolean {
|
||||||
|
if (actualTypeObj == null) return true
|
||||||
|
|
||||||
|
// Main path
|
||||||
|
if (requestedType.descriptor == "Ljava/lang/Object;" || actualTypeObj.isSubtype(requestedType)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val actualType = actualTypeObj.asmType()
|
||||||
|
|
||||||
|
fun isRefWrapper(wrapperType: AsmType, objType: AsmType): Boolean {
|
||||||
|
return !objType.isPrimitiveType && wrapperType.className == Ref.ObjectRef::class.java.name
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRefWrapper(actualType, requestedType) || isRefWrapper(requestedType, actualType)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val unwrappedActualType = unwrap(actualType)
|
||||||
|
val unwrappedRequestedType = unwrap(requestedType)
|
||||||
|
return unwrappedActualType == unwrappedRequestedType
|
||||||
|
}
|
||||||
|
|
||||||
|
fun coerce(value: Value?, type: AsmType): Result? {
|
||||||
|
val unrefResult = coerceRef(value, type) ?: return null
|
||||||
|
return coerceBoxing(unrefResult.value, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun coerceRef(value: Value?, type: AsmType): Result? {
|
||||||
|
when {
|
||||||
|
type.isRefType -> {
|
||||||
|
if (value != null && value.asmType().isRefType) {
|
||||||
|
return Result(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result(ref(value))
|
||||||
|
}
|
||||||
|
value != null && value.asmType().isRefType -> {
|
||||||
|
if (type.isRefType) {
|
||||||
|
return Result(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result(unref(value))
|
||||||
|
}
|
||||||
|
else -> return Result(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun coerceBoxing(value: Value?, type: AsmType): Result? {
|
||||||
|
when {
|
||||||
|
value == null -> return Result(value)
|
||||||
|
type == AsmType.VOID_TYPE -> return Result(executionContext.vm.mirrorOfVoid())
|
||||||
|
type.isBoxedType -> {
|
||||||
|
if (value.asmType().isBoxedType) {
|
||||||
|
return Result(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value !is PrimitiveValue) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result(box(value))
|
||||||
|
}
|
||||||
|
type.isPrimitiveType -> {
|
||||||
|
if (value is PrimitiveValue) {
|
||||||
|
return Result(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value !is ObjectReference || !value.asmType().isBoxedType) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result(unbox(value))
|
||||||
|
}
|
||||||
|
value is PrimitiveValue -> {
|
||||||
|
if (type.sort != AsmType.OBJECT) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val boxedValue = box(value)
|
||||||
|
if (!typeMatches(type, boxedValue?.type())) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result(boxedValue)
|
||||||
|
}
|
||||||
|
else -> return Result(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun box(value: Value?): Value? {
|
||||||
|
if (value !is PrimitiveValue) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
val unboxedType = value.asmType()
|
||||||
|
val boxedType = box(unboxedType)
|
||||||
|
|
||||||
|
val boxedTypeClass = (executionContext.loadClassType(boxedType) as ClassType?)
|
||||||
|
?: error("Class $boxedType is not loaded")
|
||||||
|
|
||||||
|
val methodDesc = AsmType.getMethodDescriptor(boxedType, unboxedType)
|
||||||
|
val valueOfMethod = boxedTypeClass.methodsByName("valueOf", methodDesc).first()
|
||||||
|
return boxedTypeClass.invokeMethod(executionContext.thread, valueOfMethod, listOf(value), executionContext.invokePolicy)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unbox(value: Value?): Value? {
|
||||||
|
if (value !is ObjectReference) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
val boxedTypeClass = value.referenceType() as? ClassType ?: return value
|
||||||
|
val boxedType = boxedTypeClass.asmType().takeIf { it.isBoxedType } ?: return value
|
||||||
|
val unboxedType = unbox(boxedType)
|
||||||
|
|
||||||
|
val unboxingMethodName = UNBOXING_METHOD_NAMES.getValue(boxedType.internalName)
|
||||||
|
val methodDesc = AsmType.getMethodDescriptor(unboxedType)
|
||||||
|
val valueMethod = boxedTypeClass.methodsByName(unboxingMethodName, methodDesc).first()
|
||||||
|
return value.invokeMethod(executionContext.thread, valueMethod, emptyList(), executionContext.invokePolicy)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ref(value: Value?): Value? {
|
||||||
|
if (value is VoidValue) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun wrapRef(value: Value?, refTypeClass: ClassType): Value? {
|
||||||
|
val constructor = refTypeClass.methods().single { it.isConstructor }
|
||||||
|
val ref = refTypeClass.newInstance(executionContext.thread, constructor, emptyList(), executionContext.invokePolicy)
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
DebuggerUtilsEx.keep(ref, executionContext.evaluationContext)
|
||||||
|
|
||||||
|
val elementField = refTypeClass.fieldByName("element") ?: error("'element' field not found")
|
||||||
|
ref.setValue(elementField, value)
|
||||||
|
return ref
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is PrimitiveValue) {
|
||||||
|
val primitiveType = value.asmType()
|
||||||
|
val refType = PRIMITIVE_TO_REF.getValue(primitiveType)
|
||||||
|
|
||||||
|
val refTypeClass = (executionContext.loadClassType(refType) as ClassType?)
|
||||||
|
?: error("Class $refType is not loaded")
|
||||||
|
|
||||||
|
return wrapRef(value, refTypeClass)
|
||||||
|
} else {
|
||||||
|
val refType = AsmType.getType(Ref.ObjectRef::class.java)
|
||||||
|
val refTypeClass = (executionContext.loadClassType(refType) as ClassType?)
|
||||||
|
?: error("Class $refType is not loaded")
|
||||||
|
|
||||||
|
return wrapRef(value, refTypeClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unref(value: Value?): Value? {
|
||||||
|
if (value !is ObjectReference) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
val type = value.type()
|
||||||
|
if (type !is ClassType || !type.signature().startsWith("L" + AsmTypes.REF_TYPE_PREFIX)) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
val field = type.fieldByName("element") ?: return value
|
||||||
|
return value.getValue(field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unbox(type: AsmType): AsmType {
|
||||||
|
if (type.sort == AsmType.OBJECT) {
|
||||||
|
return BOXED_TO_PRIMITIVE[type] ?: type
|
||||||
|
}
|
||||||
|
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun box(type: AsmType): AsmType {
|
||||||
|
if (type.isPrimitiveType) {
|
||||||
|
return PRIMITIVE_TO_BOXED[type] ?: type
|
||||||
|
}
|
||||||
|
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unwrap(type: AsmType): AsmType {
|
||||||
|
if (type.sort != AsmType.OBJECT) {
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
return REF_TO_PRIMITIVE[type] ?: BOXED_TO_PRIMITIVE[type] ?: type
|
||||||
|
}
|
||||||
|
|
||||||
|
private val AsmType.isPrimitiveType: Boolean
|
||||||
|
get() = sort != AsmType.OBJECT && sort != AsmType.ARRAY
|
||||||
|
|
||||||
|
private val AsmType.isRefType: Boolean
|
||||||
|
get() = sort == AsmType.OBJECT && this in REF_TYPES
|
||||||
|
|
||||||
|
private val AsmType.isBoxedType: Boolean
|
||||||
|
get() = this in BOXED_TO_PRIMITIVE
|
||||||
|
|
||||||
|
private fun Value.asmType(): AsmType {
|
||||||
|
return type().asmType()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JdiType.asmType(): AsmType {
|
||||||
|
return AsmType.getType(signature())
|
||||||
|
}
|
||||||
|
|
||||||
|
private val BOXED_TO_PRIMITIVE: Map<AsmType, AsmType> = JvmPrimitiveType.values()
|
||||||
|
.map { Pair(AsmType.getObjectType(it.wrapperFqName.internalNameWithoutInnerClasses), AsmType.getType(it.desc)) }
|
||||||
|
.toMap()
|
||||||
|
|
||||||
|
private val PRIMITIVE_TO_BOXED: Map<AsmType, AsmType> = BOXED_TO_PRIMITIVE.map { (k, v) -> Pair(v, k) }.toMap()
|
||||||
|
|
||||||
|
private val REF_TO_PRIMITIVE = mapOf(
|
||||||
|
Ref.ByteRef::class.java.name to AsmType.BYTE_TYPE,
|
||||||
|
Ref.ShortRef::class.java.name to AsmType.SHORT_TYPE,
|
||||||
|
Ref.IntRef::class.java.name to AsmType.INT_TYPE,
|
||||||
|
Ref.LongRef::class.java.name to AsmType.LONG_TYPE,
|
||||||
|
Ref.FloatRef::class.java.name to AsmType.FLOAT_TYPE,
|
||||||
|
Ref.DoubleRef::class.java.name to AsmType.DOUBLE_TYPE,
|
||||||
|
Ref.CharRef::class.java.name to AsmType.CHAR_TYPE,
|
||||||
|
Ref.BooleanRef::class.java.name to AsmType.BOOLEAN_TYPE
|
||||||
|
).mapKeys { (k, _) -> AsmType.getObjectType(k.replace('.', '/')) }
|
||||||
|
|
||||||
|
private val PRIMITIVE_TO_REF: Map<AsmType, AsmType> = REF_TO_PRIMITIVE.map { (k, v) -> Pair(v, k) }.toMap()
|
||||||
|
|
||||||
|
private val REF_TYPES: Set<AsmType> = REF_TO_PRIMITIVE.keys + AsmType.getType(Ref.ObjectRef::class.java)
|
||||||
+189
-130
@@ -1,9 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
* 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.
|
* that can be found in the license/LICENSE.txt file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
package org.jetbrains.kotlin.idea.debugger.evaluate.variables
|
||||||
|
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||||
@@ -17,24 +17,26 @@ import org.jetbrains.kotlin.codegen.AsmUtil.getLabeledThisName
|
|||||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||||
import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX
|
import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX
|
||||||
import org.jetbrains.kotlin.codegen.inline.INLINE_TRANSFORMATION_SUFFIX
|
import org.jetbrains.kotlin.codegen.inline.INLINE_TRANSFORMATION_SUFFIX
|
||||||
import org.jetbrains.kotlin.codegen.inline.NUMBERED_FUNCTION_PREFIX
|
|
||||||
import org.jetbrains.kotlin.codegen.topLevelClassAsmType
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.*
|
import org.jetbrains.kotlin.idea.debugger.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.DebugLabelPropertyDescriptorProvider
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments
|
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments
|
||||||
import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_3_FQ_NAME
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT
|
import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION
|
import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION
|
||||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
|
|
||||||
import kotlin.coroutines.Continuation
|
import kotlin.coroutines.Continuation
|
||||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||||
import com.sun.jdi.Type as JdiType
|
import com.sun.jdi.Type as JdiType
|
||||||
|
|
||||||
class VariableFinder private constructor(private val context: EvaluationContextImpl, private val frameProxy: StackFrameProxyImpl) {
|
class VariableFinder private constructor(private val context: ExecutionContext, private val frameProxy: StackFrameProxyImpl) {
|
||||||
companion object {
|
companion object {
|
||||||
private val COROUTINE_CONTEXT_SIMPLE_NAME = COROUTINE_CONTEXT_1_3_FQ_NAME.shortName().asString()
|
private const val USE_UNSAFE_FALLBACK = true
|
||||||
|
|
||||||
val CONTINUATION_TYPE: AsmType = AsmType.getType(Continuation::class.java)
|
val CONTINUATION_TYPE: AsmType = AsmType.getType(Continuation::class.java)
|
||||||
|
|
||||||
val SUSPEND_LAMBDA_CLASSES = listOf(
|
val SUSPEND_LAMBDA_CLASSES = listOf(
|
||||||
@@ -42,18 +44,14 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
|||||||
"kotlin.coroutines.jvm.internal.RestrictedSuspendLambda"
|
"kotlin.coroutines.jvm.internal.RestrictedSuspendLambda"
|
||||||
)
|
)
|
||||||
|
|
||||||
fun instance(context: EvaluationContextImpl): VariableFinder? {
|
fun instance(context: ExecutionContext): VariableFinder? {
|
||||||
val frameProxy = context.frameProxy ?: return null
|
val frameProxy = context.evaluationContext.frameProxy ?: return null
|
||||||
return VariableFinder(context, frameProxy)
|
return VariableFinder(context, frameProxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unwrapRefValue(value: ObjectReference): Value? {
|
|
||||||
return NamedEntity("<nameForUnwrapOnly>", value.type()) { value }.unwrap().value()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun variableNotFound(context: EvaluationContextImpl, message: String): Exception {
|
fun variableNotFound(context: EvaluationContextImpl, message: String): Exception {
|
||||||
val frameProxy = context.frameProxy
|
val frameProxy = context.frameProxy
|
||||||
val location = frameProxy?.location()
|
val location = frameProxy?.safeLocation()
|
||||||
val scope = context.debugProcess.searchScope
|
val scope = context.debugProcess.searchScope
|
||||||
|
|
||||||
val locationText = location?.run { "Location: ${sourceName()}:${lineNumber()}" } ?: "No location available"
|
val locationText = location?.run { "Location: ${sourceName()}:${lineNumber()}" } ?: "No location available"
|
||||||
@@ -98,12 +96,10 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
|||||||
return Regex("^$escapedName(?:$escapedSuffix)*$")
|
return Regex("^$escapedName(?:$escapedSuffix)*$")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AsmType.isFunctionType(): Boolean {
|
|
||||||
return sort == AsmType.OBJECT && internalName.startsWith(NUMBERED_FUNCTION_PREFIX)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getInlineDepth(variables: List<LocalVariableProxyImpl>): Int {
|
fun getInlineDepth(variables: List<LocalVariableProxyImpl>): Int {
|
||||||
val inlineFunVariables = variables.filter { it.name().startsWith(LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) }
|
val inlineFunVariables = variables
|
||||||
|
.filter { it.name().startsWith(LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) }
|
||||||
|
|
||||||
if (inlineFunVariables.isEmpty()) {
|
if (inlineFunVariables.isEmpty()) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -133,46 +129,43 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class VariableKind(val type: AsmType?) {
|
private val evaluatorValueConverter = EvaluatorValueConverter(context)
|
||||||
|
|
||||||
|
sealed class VariableKind(val asmType: AsmType) {
|
||||||
abstract fun capturedNameMatches(name: String): Boolean
|
abstract fun capturedNameMatches(name: String): Boolean
|
||||||
|
|
||||||
class Ordinary(val name: String, type: AsmType?) : VariableKind(type) {
|
class Ordinary(val name: String, asmType: AsmType) : VariableKind(asmType) {
|
||||||
private val capturedNameRegex = getCapturedVariableNameRegex(getCapturedFieldName(this.name))
|
private val capturedNameRegex = getCapturedVariableNameRegex(getCapturedFieldName(this.name))
|
||||||
override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name)
|
override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnlabeledThis(type: AsmType?) : VariableKind(type) {
|
// TODO Support overloaded local functions
|
||||||
|
class LocalFunction(val name: String, asmType: AsmType) : VariableKind(asmType) {
|
||||||
|
@Suppress("ConvertToStringTemplate")
|
||||||
|
override fun capturedNameMatches(name: String) = name == "$" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnlabeledThis(asmType: AsmType) : VariableKind(asmType) {
|
||||||
override fun capturedNameMatches(name: String) =
|
override fun capturedNameMatches(name: String) =
|
||||||
(name == AsmUtil.CAPTURED_RECEIVER_FIELD || name.startsWith(AsmUtil.getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD)))
|
(name == AsmUtil.CAPTURED_RECEIVER_FIELD || name.startsWith(getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD)))
|
||||||
}
|
}
|
||||||
|
|
||||||
class LabeledThis(val label: String, type: AsmType?) : VariableKind(type) {
|
class OuterClassThis(asmType: AsmType) : VariableKind(asmType) {
|
||||||
private val capturedNameRegex = getCapturedVariableNameRegex(
|
override fun capturedNameMatches(name: String) = false
|
||||||
getCapturedFieldName(getLabeledThisName(label, AsmUtil.LABELED_THIS_FIELD, AsmUtil.CAPTURED_RECEIVER_FIELD))
|
}
|
||||||
)
|
|
||||||
|
|
||||||
|
class FieldVar(val fieldName: String, asmType: AsmType) : VariableKind(asmType) {
|
||||||
|
// Captured 'field' are not supported yet
|
||||||
|
override fun capturedNameMatches(name: String) = false
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExtensionThis(val label: String, asmType: AsmType) : VariableKind(asmType) {
|
||||||
|
val parameterName = getLabeledThisName(label, AsmUtil.LABELED_THIS_PARAMETER, AsmUtil.RECEIVER_PARAMETER_NAME)
|
||||||
|
val fieldName = getLabeledThisName(label, getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD), AsmUtil.CAPTURED_RECEIVER_FIELD)
|
||||||
|
|
||||||
|
private val capturedNameRegex = getCapturedVariableNameRegex(fieldName)
|
||||||
override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name)
|
override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun typeMatches(type: JdiType?): Boolean {
|
|
||||||
if (type == null) return true
|
|
||||||
val asmType = this.type ?: return true
|
|
||||||
|
|
||||||
// Main path
|
|
||||||
if (asmType.descriptor == "Ljava/lang/Object;" || type.isSubtype(asmType)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// The latter is for boxing interventions
|
|
||||||
fun box(desc: String) = JvmPrimitiveType.getByDesc(desc)?.wrapperFqName?.topLevelClassAsmType()?.descriptor
|
|
||||||
|
|
||||||
val asmTypeDescriptor = asmType.descriptor
|
|
||||||
val jdiTypeDescriptor = type.signature()
|
|
||||||
|
|
||||||
val boxedAsmType = box(asmTypeDescriptor) ?: asmTypeDescriptor
|
|
||||||
val boxedJdiType = box(jdiTypeDescriptor) ?: jdiTypeDescriptor
|
|
||||||
return boxedAsmType == boxedJdiType
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Result(val value: Value?)
|
class Result(val value: Value?)
|
||||||
@@ -180,36 +173,25 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
|||||||
private class NamedEntity(val name: String, val type: JdiType?, val value: () -> Value?) {
|
private class NamedEntity(val name: String, val type: JdiType?, val value: () -> Value?) {
|
||||||
companion object {
|
companion object {
|
||||||
fun of(field: Field, owner: ObjectReference): NamedEntity {
|
fun of(field: Field, owner: ObjectReference): NamedEntity {
|
||||||
return NamedEntity(field.name(), field.safeType()) { owner.getValue(field) }.unwrap()
|
return NamedEntity(field.name(), field.safeType()) { owner.getValue(field) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun of(variable: LocalVariableProxyImpl, frameProxy: StackFrameProxyImpl): NamedEntity {
|
fun of(variable: LocalVariableProxyImpl, frameProxy: StackFrameProxyImpl): NamedEntity {
|
||||||
return NamedEntity(variable.name(), variable.safeType()) { frameProxy.getValue(variable) }.unwrap()
|
return NamedEntity(variable.name(), variable.safeType()) { frameProxy.getValue(variable) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unwrap(): NamedEntity {
|
|
||||||
if (type !is ClassType || !type.signature().startsWith("L" + AsmTypes.REF_TYPE_PREFIX)) {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
val obj = this.value() as? ObjectReference ?: return this
|
|
||||||
val field = type.fieldByName("element") ?: return this
|
|
||||||
|
|
||||||
val unwrappedValue = obj.getValue(field)
|
|
||||||
val unwrappedType = if (field.type() is PrimitiveType) field.type() else unwrappedValue?.type()
|
|
||||||
return NamedEntity(name, unwrappedType) { unwrappedValue }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun find(name: String, type: AsmType?): Result? {
|
fun find(parameter: CodeFragmentParameter, asmType: AsmType): Result? {
|
||||||
return when {
|
return when (parameter.kind) {
|
||||||
name.startsWith(AsmUtil.THIS + "@") -> {
|
Kind.ORDINARY -> findOrdinary(VariableKind.Ordinary(parameter.name, asmType))
|
||||||
val label = name.drop(AsmUtil.THIS.length + 1).also { require(it.isNotEmpty()) { "Invalid name '$name'" } }
|
Kind.FAKE_JAVA_OUTER_CLASS -> frameProxy.thisObject()?.let { Result(it) }
|
||||||
findLabeledThis(VariableKind.LabeledThis(label, type))
|
Kind.EXTENSION_RECEIVER -> findExtensionThis(VariableKind.ExtensionThis(parameter.name, asmType))
|
||||||
}
|
Kind.LOCAL_FUNCTION -> findLocalFunction(VariableKind.LocalFunction(parameter.name, asmType))
|
||||||
name == AsmUtil.THIS -> findUnlabeledThis(VariableKind.UnlabeledThis(type))
|
Kind.DISPATCH_RECEIVER -> findDispatchThis(VariableKind.OuterClassThis(asmType))
|
||||||
else -> findOrdinary(VariableKind.Ordinary(name, type))
|
Kind.COROUTINE_CONTEXT -> findCoroutineContext()
|
||||||
|
Kind.FIELD_VAR -> findFieldVariable(VariableKind.FieldVar(parameter.name, asmType))
|
||||||
|
Kind.DEBUG_LABEL -> findDebugLabel(parameter.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,11 +209,35 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
|||||||
return findCapturedVariable(kind, containingThis)
|
return findCapturedVariable(kind, containingThis)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findLabeledThis(kind: VariableKind.LabeledThis): Result? {
|
private fun findFieldVariable(kind: VariableKind.FieldVar): Result? {
|
||||||
|
val thisObject = frameProxy.thisObject() ?: return null
|
||||||
|
val field = thisObject.referenceType().fieldByName(kind.fieldName) ?: return null
|
||||||
|
return Result(thisObject.getValue(field))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findLocalFunction(kind: VariableKind.LocalFunction): Result? {
|
||||||
|
val variables = frameProxy.safeVisibleVariables()
|
||||||
|
|
||||||
|
// Local variables – direct search, new convention
|
||||||
|
val newConventionName = AsmUtil.LOCAL_FUNCTION_VARIABLE_PREFIX + kind.name
|
||||||
|
findLocalVariable(variables, kind, newConventionName)?.let { return it }
|
||||||
|
|
||||||
|
// Local variables – direct search, old convention (before 1.3.30)
|
||||||
|
findLocalVariable(variables, kind, kind.name + "$")?.let { return it }
|
||||||
|
|
||||||
|
// Recursive search in local receiver variables
|
||||||
|
findCapturedVariableInReceiver(variables, kind)?.let { return it }
|
||||||
|
|
||||||
|
// Recursive search in captured this
|
||||||
|
val containingThis = frameProxy.thisObject() ?: return null
|
||||||
|
return findCapturedVariable(kind, containingThis)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findExtensionThis(kind: VariableKind.ExtensionThis): Result? {
|
||||||
val variables = frameProxy.safeVisibleVariables()
|
val variables = frameProxy.safeVisibleVariables()
|
||||||
|
|
||||||
// Local variables – direct search
|
// Local variables – direct search
|
||||||
findLocalVariable(variables, kind, AsmUtil.LABELED_THIS_PARAMETER + kind.label)?.let { return it }
|
findLocalVariable(variables, kind, kind.parameterName)?.let { return it }
|
||||||
|
|
||||||
// Recursive search in local receiver variables
|
// Recursive search in local receiver variables
|
||||||
findCapturedVariableInReceiver(variables, kind)?.let { return it }
|
findCapturedVariableInReceiver(variables, kind)?.let { return it }
|
||||||
@@ -242,8 +248,56 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
|||||||
findCapturedVariable(kind, containingThis)?.let { return it }
|
findCapturedVariable(kind, containingThis)?.let { return it }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: find an unlabeled this with the compatible type
|
@Suppress("ConstantConditionIf")
|
||||||
return findUnlabeledThis(VariableKind.UnlabeledThis(kind.type))
|
if (USE_UNSAFE_FALLBACK) {
|
||||||
|
// Find an unlabeled this with the compatible type
|
||||||
|
findUnlabeledThis(VariableKind.UnlabeledThis(kind.asmType))?.let { return it }
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findDispatchThis(kind: VariableKind.OuterClassThis): Result? {
|
||||||
|
val containingThis = frameProxy.thisObject()
|
||||||
|
if (containingThis != null) {
|
||||||
|
findCapturedVariable(kind, containingThis)?.let { return it }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInsideDefaultImpls()) {
|
||||||
|
val variables = frameProxy.safeVisibleVariables()
|
||||||
|
findLocalVariable(variables, kind, getCapturedFieldName(AsmUtil.THIS))?.let { return it }
|
||||||
|
}
|
||||||
|
|
||||||
|
val variables = frameProxy.safeVisibleVariables()
|
||||||
|
val inlineDepth = getInlineDepth(variables)
|
||||||
|
|
||||||
|
if (inlineDepth > 0) {
|
||||||
|
variables.namedEntitySequence()
|
||||||
|
.filter { it.name.matches(inlinedThisRegex) && getInlineDepth(it.name) == inlineDepth && kind.typeMatches(it.type) }
|
||||||
|
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||||
|
.firstOrNull()
|
||||||
|
?.let { return it }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("ConstantConditionIf")
|
||||||
|
if (USE_UNSAFE_FALLBACK) {
|
||||||
|
// Find an unlabeled this with the compatible type
|
||||||
|
findUnlabeledThis(VariableKind.UnlabeledThis(kind.asmType))?.let { return it }
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findDebugLabel(name: String): Result? {
|
||||||
|
val markupMap = DebugLabelPropertyDescriptorProvider.getMarkupMap(context.evaluationContext.debugProcess)
|
||||||
|
|
||||||
|
for ((value, markup) in markupMap) {
|
||||||
|
if (markup.text == name) {
|
||||||
|
return Result(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findUnlabeledThis(kind: VariableKind.UnlabeledThis): Result? {
|
private fun findUnlabeledThis(kind: VariableKind.UnlabeledThis): Result? {
|
||||||
@@ -257,47 +311,35 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findLocalVariable(variables: List<LocalVariableProxyImpl>, kind: VariableKind, name: String): Result? {
|
private fun findLocalVariable(variables: List<LocalVariableProxyImpl>, kind: VariableKind, name: String): Result? {
|
||||||
|
val inlineDepth = getInlineDepth(variables)
|
||||||
|
|
||||||
|
if (inlineDepth > 0) {
|
||||||
|
val nameInlineAwareRegex = getLocalVariableNameRegexInlineAware(name)
|
||||||
|
variables.namedEntitySequence()
|
||||||
|
.filter { it.name.matches(nameInlineAwareRegex) && getInlineDepth(it.name) == inlineDepth && kind.typeMatches(it.type) }
|
||||||
|
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||||
|
.firstOrNull()
|
||||||
|
?.let { return it }
|
||||||
|
}
|
||||||
|
|
||||||
variables.namedEntitySequence()
|
variables.namedEntitySequence()
|
||||||
.filter { it.name == name && kind.typeMatches(it.type) }
|
.filter { it.name == name && kind.typeMatches(it.type) }
|
||||||
|
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?.let { return Result(it.value()) }
|
?.let { return it }
|
||||||
|
|
||||||
val canBeLocalFunction = kind is VariableKind.Ordinary
|
|
||||||
|
|
||||||
if (canBeLocalFunction && kind.type?.isFunctionType() == true) {
|
|
||||||
variables.namedEntitySequence()
|
|
||||||
.filter { isLocalFunctionName(it.name, name) && kind.typeMatches(it.type) }
|
|
||||||
.firstOrNull()
|
|
||||||
?.let { return Result(it.value()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val nameInlineAwareRegex = getLocalVariableNameRegexInlineAware(name)
|
|
||||||
|
|
||||||
val inlineDepth = getInlineDepth(variables)
|
|
||||||
variables.namedEntitySequence()
|
|
||||||
.filter { it.name.matches(nameInlineAwareRegex) && getInlineDepth(it.name) == inlineDepth && kind.typeMatches(it.type) }
|
|
||||||
.toList() // Sorted by will make a list anyway
|
|
||||||
.firstOrNull()
|
|
||||||
?.let { return Result(it.value()) }
|
|
||||||
|
|
||||||
if (name == COROUTINE_CONTEXT_SIMPLE_NAME) {
|
|
||||||
val coroutineContext = findCoroutineContext()?.takeIf { kind.typeMatches(it.type()) }
|
|
||||||
if (coroutineContext != null) {
|
|
||||||
return Result(coroutineContext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isLocalFunctionName(name: String, functionName: String): Boolean {
|
private fun isInsideDefaultImpls(): Boolean {
|
||||||
@Suppress("ConvertToStringTemplate")
|
val declaringType = frameProxy.safeLocation()?.declaringType() ?: return false
|
||||||
return name == functionName + "$" || name.startsWith(AsmUtil.LOCAL_FUNCTION_VARIABLE_PREFIX + name + "$")
|
return declaringType.name().endsWith(JvmAbi.DEFAULT_IMPLS_SUFFIX)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findCoroutineContext(): ObjectReference? {
|
private fun findCoroutineContext(): Result? {
|
||||||
val method = frameProxy.location().safeMethod() ?: return null
|
val method = frameProxy.safeLocation()?.safeMethod() ?: return null
|
||||||
return findCoroutineContextForLambda(method) ?: findCoroutineContextForMethod(method)
|
val result = findCoroutineContextForLambda(method) ?: findCoroutineContextForMethod(method) ?: return null
|
||||||
|
return Result(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findCoroutineContextForLambda(method: Method): ObjectReference? {
|
private fun findCoroutineContextForLambda(method: Method): ObjectReference? {
|
||||||
@@ -334,23 +376,22 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
|||||||
.methodsByName("getContext", "()Lkotlin/coroutines/CoroutineContext;").firstOrNull()
|
.methodsByName("getContext", "()Lkotlin/coroutines/CoroutineContext;").firstOrNull()
|
||||||
?: return null
|
?: return null
|
||||||
|
|
||||||
val threadReference = frameProxy.threadProxy().threadReference.takeIf { it.isSuspended } ?: return null
|
return continuation.invokeMethod(context.thread, getContextMethod, emptyList(), context.invokePolicy) as? ObjectReference
|
||||||
val invokePolicy = context.suspendContext.getInvokePolicy()
|
|
||||||
return continuation.invokeMethod(threadReference, getContextMethod, emptyList(), invokePolicy) as? ObjectReference
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findCapturedVariableInReceiver(variables: List<LocalVariableProxyImpl>, kind: VariableKind): Result? {
|
private fun findCapturedVariableInReceiver(variables: List<LocalVariableProxyImpl>, kind: VariableKind): Result? {
|
||||||
fun isReceiverOrPassedThis(name: String) =
|
fun isReceiverOrPassedThis(name: String) =
|
||||||
name.startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|
name.startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|
||||||
|| name == AsmUtil.RECEIVER_PARAMETER_NAME
|
|| name == AsmUtil.RECEIVER_PARAMETER_NAME
|
||||||
|| name == AsmUtil.getCapturedFieldName(AsmUtil.THIS)
|
|| name == getCapturedFieldName(AsmUtil.THIS)
|
||||||
|| inlinedThisRegex.matches(name)
|
|| inlinedThisRegex.matches(name)
|
||||||
|
|
||||||
if (kind is VariableKind.LabeledThis) {
|
if (kind is VariableKind.ExtensionThis) {
|
||||||
variables.namedEntitySequence()
|
variables.namedEntitySequence()
|
||||||
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
|
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
|
||||||
|
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?.let { return Result(it.value()) }
|
?.let { return it }
|
||||||
}
|
}
|
||||||
|
|
||||||
return variables.namedEntitySequence()
|
return variables.namedEntitySequence()
|
||||||
@@ -360,30 +401,35 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findCapturedVariable(kind: VariableKind, parent: Value?): Result? {
|
private fun findCapturedVariable(kind: VariableKind, parent: Value?): Result? {
|
||||||
if (parent != null && kind is VariableKind.UnlabeledThis && kind.typeMatches(parent.type())) {
|
val acceptsParentValue = kind is VariableKind.UnlabeledThis || kind is VariableKind.OuterClassThis
|
||||||
|
if (parent != null && acceptsParentValue && kind.typeMatches(parent.type())) {
|
||||||
return Result(parent)
|
return Result(parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
val fields = (parent as? ObjectReference)?.referenceType()?.fields() ?: return null
|
val fields = (parent as? ObjectReference)?.referenceType()?.fields() ?: return null
|
||||||
|
|
||||||
// Captured variables - direct search
|
if (kind !is VariableKind.OuterClassThis) {
|
||||||
fields.namedEntitySequence(parent)
|
// Captured variables - direct search
|
||||||
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
|
fields.namedEntitySequence(parent)
|
||||||
.firstOrNull()
|
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
|
||||||
?.let { return Result(it.value()) }
|
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||||
|
.firstOrNull()
|
||||||
|
?.let { return it }
|
||||||
|
|
||||||
// Recursive search in captured receivers
|
// Recursive search in captured receivers
|
||||||
fields.namedEntitySequence(parent)
|
fields.namedEntitySequence(parent)
|
||||||
.filter { isCapturedReceiverFieldName(it.name) }
|
.filter { isCapturedReceiverFieldName(it.name) }
|
||||||
.mapNotNull { findCapturedVariable(kind, it.value()) }
|
.mapNotNull { findCapturedVariable(kind, it.value()) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?.let { return it }
|
?.let { return it }
|
||||||
|
}
|
||||||
|
|
||||||
// Recursive search in outer and captured this
|
// Recursive search in outer and captured this
|
||||||
fields.namedEntitySequence(parent)
|
fields.namedEntitySequence(parent)
|
||||||
.filter { it.name == AsmUtil.getCapturedFieldName(AsmUtil.THIS) || it.name == AsmUtil.CAPTURED_THIS_FIELD }
|
.filter { it.name == getCapturedFieldName(AsmUtil.THIS) || it.name == AsmUtil.CAPTURED_THIS_FIELD }
|
||||||
|
.mapNotNull { findCapturedVariable(kind, it.value()) }
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?.let { return findCapturedVariable(kind, it.value()) }
|
?.let { return it }
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -393,6 +439,19 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
|||||||
|| name == AsmUtil.CAPTURED_RECEIVER_FIELD
|
|| name == AsmUtil.CAPTURED_RECEIVER_FIELD
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<Field>.namedEntitySequence(owner: ObjectReference) = asSequence().map { NamedEntity.of(it, owner) }
|
private fun VariableKind.typeMatches(actualType: JdiType?): Boolean {
|
||||||
private fun List<LocalVariableProxyImpl>.namedEntitySequence() = asSequence().map { NamedEntity.of(it, frameProxy) }
|
return evaluatorValueConverter.typeMatches(asmType, actualType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NamedEntity.unwrapAndCheck(kind: VariableKind): Result? {
|
||||||
|
return evaluatorValueConverter.coerce(value(), kind.asmType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<Field>.namedEntitySequence(owner: ObjectReference): Sequence<NamedEntity> {
|
||||||
|
return asSequence().map { NamedEntity.of(it, owner) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<LocalVariableProxyImpl>.namedEntitySequence(): Sequence<NamedEntity> {
|
||||||
|
return asSequence().map { NamedEntity.of(it, frameProxy) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,8 @@
|
|||||||
package org.jetbrains.kotlin.idea.debugger
|
package org.jetbrains.kotlin.idea.debugger
|
||||||
|
|
||||||
import com.intellij.debugger.engine.evaluation.AbsentInformationEvaluateException
|
import com.intellij.debugger.engine.evaluation.AbsentInformationEvaluateException
|
||||||
|
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||||
|
import com.intellij.debugger.engine.jdi.StackFrameProxy
|
||||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||||
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
||||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||||
@@ -43,6 +45,14 @@ fun Method.safeArguments(): List<LocalVariable>? {
|
|||||||
return wrapAbsentInformationException { arguments() }
|
return wrapAbsentInformationException { arguments() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun StackFrameProxy.safeLocation(): Location? {
|
||||||
|
return try {
|
||||||
|
this.location()
|
||||||
|
} catch (e: EvaluateException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Location.safeSourceName(): String? {
|
fun Location.safeSourceName(): String? {
|
||||||
return try {
|
return try {
|
||||||
sourceName()
|
sourceName()
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.util.InfinitePeriodicalTask
|
|||||||
import org.jetbrains.kotlin.idea.util.LongRunningReadTask
|
import org.jetbrains.kotlin.idea.util.LongRunningReadTask
|
||||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
|
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.psi.KtScript
|
import org.jetbrains.kotlin.psi.KtScript
|
||||||
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
||||||
@@ -306,6 +307,8 @@ class KotlinBytecodeToolWindow(private val myProject: Project, private val toolW
|
|||||||
override fun shouldGenerateScript(script: KtScript): Boolean {
|
override fun shouldGenerateScript(script: KtScript): Boolean {
|
||||||
return script.containingKtFile === ktFile
|
return script.containingKtFile === ktFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
|
||||||
}
|
}
|
||||||
|
|
||||||
val state = GenerationState.Builder(
|
val state = GenerationState.Builder(
|
||||||
|
|||||||
@@ -46,10 +46,7 @@ import org.jetbrains.kotlin.idea.scratch.*
|
|||||||
import org.jetbrains.kotlin.idea.scratch.output.ScratchOutput
|
import org.jetbrains.kotlin.idea.scratch.output.ScratchOutput
|
||||||
import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputType
|
import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputType
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
|
||||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
|
||||||
import org.jetbrains.kotlin.psi.KtScript
|
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
||||||
import org.jetbrains.kotlin.resolve.AnalyzingUtils
|
import org.jetbrains.kotlin.resolve.AnalyzingUtils
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -143,6 +140,7 @@ class KtCompilingExecutor(file: ScratchFile) : ScratchExecutor(file) {
|
|||||||
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject) = true
|
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject) = true
|
||||||
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = processingClassOrObject.containingKtFile == psiFile
|
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = processingClassOrObject.containingKtFile == psiFile
|
||||||
override fun shouldGenerateScript(script: KtScript) = false
|
override fun shouldGenerateScript(script: KtScript) = false
|
||||||
|
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
|
||||||
}
|
}
|
||||||
|
|
||||||
val state = GenerationState.Builder(
|
val state = GenerationState.Builder(
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
@Suppress("INVISIBLE_MEMBER")
|
||||||
MyClass().privateFun(<error>"s"</error>)
|
MyClass().privateFun(<error>"s"</error>)
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
@Suppress("INVISIBLE_MEMBER")
|
||||||
"${MyClass().privateFun(1)} ${MyClass().privateFun<Int>(<error>"s"</error>)}"
|
"${MyClass().privateFun(1)} ${MyClass().privateFun<Int>(<error>"s"</error>)}"
|
||||||
@@ -1 +1,2 @@
|
|||||||
MyClass().privateFun() + MyClass().privateVal + MyClass.PrivateClass().a
|
@Suppress("INVISIBLE_MEMBER")
|
||||||
|
(MyClass().privateFun() + MyClass().privateVal + MyClass.PrivateClass().a)
|
||||||
@@ -1 +1,2 @@
|
|||||||
MyClass().protectedFun() + MyClass().protectedVal + MyClass.ProtectedClass().a
|
@Suppress("INVISIBLE_MEMBER")
|
||||||
|
(MyClass().protectedFun() + MyClass().protectedVal + MyClass.ProtectedClass().a)
|
||||||
@@ -1 +1 @@
|
|||||||
<error>a</error> + b + <error>c</error> + <error>a1</error>
|
<error>a</error> + b + c + a1
|
||||||
+6
@@ -30,19 +30,25 @@ clearCache.kt:42
|
|||||||
clearCache.kt:52
|
clearCache.kt:52
|
||||||
Compile bytecode for o.test()
|
Compile bytecode for o.test()
|
||||||
clearCache.kt:60
|
clearCache.kt:60
|
||||||
|
Compile bytecode for o.test()
|
||||||
clearCache.kt:78
|
clearCache.kt:78
|
||||||
Compile bytecode for c.size
|
Compile bytecode for c.size
|
||||||
clearCache.kt:86
|
clearCache.kt:86
|
||||||
|
Compile bytecode for c.size
|
||||||
clearCache.kt:95
|
clearCache.kt:95
|
||||||
|
Compile bytecode for c.size
|
||||||
clearCache.kt:105
|
clearCache.kt:105
|
||||||
|
Compile bytecode for c.size
|
||||||
clearCache.kt:113
|
clearCache.kt:113
|
||||||
Compile bytecode for c.size
|
Compile bytecode for c.size
|
||||||
clearCache.kt:123
|
clearCache.kt:123
|
||||||
Compile bytecode for o.test()
|
Compile bytecode for o.test()
|
||||||
clearCache.kt:131
|
clearCache.kt:131
|
||||||
|
Compile bytecode for o.test()
|
||||||
clearCache.kt:149
|
clearCache.kt:149
|
||||||
Compile bytecode for obj.test()
|
Compile bytecode for obj.test()
|
||||||
clearCache.kt:154
|
clearCache.kt:154
|
||||||
|
Compile bytecode for obj.test()
|
||||||
clearCache.kt:161
|
clearCache.kt:161
|
||||||
Compile bytecode for o.test()
|
Compile bytecode for o.test()
|
||||||
clearCache.kt:169
|
clearCache.kt:169
|
||||||
|
|||||||
+4
-4
@@ -37,15 +37,15 @@ fun genericClassCast() {
|
|||||||
val c = ArrayList<Int>()
|
val c = ArrayList<Int>()
|
||||||
c.add(1)
|
c.add(1)
|
||||||
// EXPRESSION: c.get(0)
|
// EXPRESSION: c.get(0)
|
||||||
// RESULT: 1: I
|
// RESULT: instance of java.lang.Integer(id=ID): Ljava/lang/Integer;
|
||||||
//Breakpoint!
|
//Breakpoint!
|
||||||
val b = 1
|
val b = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (true) {
|
if (true) {
|
||||||
val c = ArrayList<String>()
|
val c = ArrayList<Int>()
|
||||||
c.add("a")
|
(c as? ArrayList<String>)?.add("a")
|
||||||
// EXPRESSION: c.get(0)
|
// EXPRESSION: c.get(0) + 1
|
||||||
// RESULT: java.lang.ClassCastException : java.lang.String cannot be cast to java.lang.Number
|
// RESULT: java.lang.ClassCastException : java.lang.String cannot be cast to java.lang.Number
|
||||||
//Breakpoint!
|
//Breakpoint!
|
||||||
val b = 1
|
val b = 1
|
||||||
|
|||||||
+3
@@ -9,12 +9,15 @@ Connected to the target VM
|
|||||||
exceptions.kt:9
|
exceptions.kt:9
|
||||||
Compile bytecode for fail()
|
Compile bytecode for fail()
|
||||||
exceptions.kt:14
|
exceptions.kt:14
|
||||||
|
Compile bytecode for fail()
|
||||||
exceptions.kt:26
|
exceptions.kt:26
|
||||||
Compile bytecode for o as Derived
|
Compile bytecode for o as Derived
|
||||||
exceptions.kt:31
|
exceptions.kt:31
|
||||||
|
Compile bytecode for o as Derived
|
||||||
exceptions.kt:42
|
exceptions.kt:42
|
||||||
Compile bytecode for c.get(0)
|
Compile bytecode for c.get(0)
|
||||||
exceptions.kt:51
|
exceptions.kt:51
|
||||||
|
Compile bytecode for c.get(0) + 1
|
||||||
Disconnected from the target VM
|
Disconnected from the target VM
|
||||||
|
|
||||||
Process finished with exit code 0
|
Process finished with exit code 0
|
||||||
|
|||||||
Vendored
+2
@@ -41,9 +41,11 @@ Compile bytecode for extClass.testCompPrivate()
|
|||||||
extensionMemberFunction.kt:88
|
extensionMemberFunction.kt:88
|
||||||
Compile bytecode for testCompPrivate()
|
Compile bytecode for testCompPrivate()
|
||||||
extensionMemberFunction.kt:98
|
extensionMemberFunction.kt:98
|
||||||
|
Compile bytecode for testCompPublic()
|
||||||
extensionMemberFunction.kt:103
|
extensionMemberFunction.kt:103
|
||||||
Compile bytecode for this.testCompPublic()
|
Compile bytecode for this.testCompPublic()
|
||||||
extensionMemberFunction.kt:108
|
extensionMemberFunction.kt:108
|
||||||
|
Compile bytecode for testCompPrivate()
|
||||||
extensionMemberFunction.kt:113
|
extensionMemberFunction.kt:113
|
||||||
Compile bytecode for this.testCompPrivate()
|
Compile bytecode for this.testCompPrivate()
|
||||||
Disconnected from the target VM
|
Disconnected from the target VM
|
||||||
|
|||||||
Vendored
+2
@@ -41,9 +41,11 @@ Compile bytecode for extClass.testCompPrivate
|
|||||||
extensionMemberProperty.kt:92
|
extensionMemberProperty.kt:92
|
||||||
Compile bytecode for testCompPrivate
|
Compile bytecode for testCompPrivate
|
||||||
extensionMemberProperty.kt:102
|
extensionMemberProperty.kt:102
|
||||||
|
Compile bytecode for testCompPublic
|
||||||
extensionMemberProperty.kt:107
|
extensionMemberProperty.kt:107
|
||||||
Compile bytecode for this.testCompPublic
|
Compile bytecode for this.testCompPublic
|
||||||
extensionMemberProperty.kt:112
|
extensionMemberProperty.kt:112
|
||||||
|
Compile bytecode for testCompPrivate
|
||||||
extensionMemberProperty.kt:117
|
extensionMemberProperty.kt:117
|
||||||
Compile bytecode for this.testCompPrivate
|
Compile bytecode for this.testCompPrivate
|
||||||
Disconnected from the target VM
|
Disconnected from the target VM
|
||||||
|
|||||||
+1
-1
@@ -24,4 +24,4 @@ fun main() {
|
|||||||
// RESULT: "field": Ljava/lang/String;
|
// RESULT: "field": Ljava/lang/String;
|
||||||
|
|
||||||
// EXPRESSION: field
|
// EXPRESSION: field
|
||||||
// RESULT: Can't find a backing field for property field
|
// RESULT: Cannot find the backing field 'b'
|
||||||
Vendored
+1
-1
@@ -23,7 +23,7 @@ class Outer {
|
|||||||
// outer isn't captured in lambda
|
// outer isn't captured in lambda
|
||||||
lambda {
|
lambda {
|
||||||
// EXPRESSION: foo() + 2
|
// EXPRESSION: foo() + 2
|
||||||
// RESULT: java.lang.AssertionError : Cannot find local variable: name = 'this@Outer', type = funFromOuterClassInLamdba.Outer
|
// RESULT: 'this@Outer' is not captured
|
||||||
//Breakpoint!
|
//Breakpoint!
|
||||||
val a = 1
|
val a = 1
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ fun test1(derived: Base) =
|
|||||||
// RESULT: 1: I
|
// RESULT: 1: I
|
||||||
|
|
||||||
// EXPRESSION: nullable.prop
|
// EXPRESSION: nullable.prop
|
||||||
// RESULT: Method threw 'kotlin.TypeCastException' exception.
|
// RESULT: java.lang.NullPointerException
|
||||||
fun test2(nullable: Derived?) =
|
fun test2(nullable: Derived?) =
|
||||||
nullable != null &&
|
nullable != null &&
|
||||||
//Breakpoint!
|
//Breakpoint!
|
||||||
|
|||||||
+2
-2
@@ -7,7 +7,7 @@ fun main() {
|
|||||||
//Breakpoint!
|
//Breakpoint!
|
||||||
block(3) c@ {
|
block(3) c@ {
|
||||||
//Breakpoint!
|
//Breakpoint!
|
||||||
val a = 5
|
val a = this@a + this@b + this@c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,4 +28,4 @@ fun <T> T.inlineBlock(t: T, block: T.() -> Unit) {
|
|||||||
// RESULT: 2: I
|
// RESULT: 2: I
|
||||||
|
|
||||||
// EXPRESSION: this + this@a + this@b + this@c
|
// EXPRESSION: this + this@a + this@b + this@c
|
||||||
// RESULT: 12: I
|
// RESULT: 9: I
|
||||||
+1
-3
@@ -4,12 +4,10 @@ fun main(args: Array<String>) {
|
|||||||
val callable = 1
|
val callable = 1
|
||||||
arrayOf(1, 2).map {
|
arrayOf(1, 2).map {
|
||||||
it + 1
|
it + 1
|
||||||
//Breakpoint!
|
//Breakpoint! (lambdaOrdinal = 1)
|
||||||
}.forEach { it + 2 }
|
}.forEach { it + 2 }
|
||||||
}
|
}
|
||||||
|
|
||||||
// STEP_INTO: 1
|
|
||||||
|
|
||||||
// EXPRESSION: callable
|
// EXPRESSION: callable
|
||||||
// RESULT: 1: I
|
// RESULT: 1: I
|
||||||
|
|
||||||
|
|||||||
+1
-2
@@ -1,8 +1,7 @@
|
|||||||
LineBreakpoint created at callableBug.kt:8
|
LineBreakpoint created at callableBug.kt:8 lambdaOrdinal = 1
|
||||||
Run Java
|
Run Java
|
||||||
Connected to the target VM
|
Connected to the target VM
|
||||||
callableBug.kt:8
|
callableBug.kt:8
|
||||||
callableBug.kt:8
|
|
||||||
Compile bytecode for callable
|
Compile bytecode for callable
|
||||||
Compile bytecode for it
|
Compile bytecode for it
|
||||||
callableBug.kt:8
|
callableBug.kt:8
|
||||||
|
|||||||
Vendored
+1
-1
@@ -16,4 +16,4 @@ interface T {
|
|||||||
// RESULT: 1: I
|
// RESULT: 1: I
|
||||||
|
|
||||||
// EXPRESSION: object: T {}
|
// EXPRESSION: object: T {}
|
||||||
// RESULT: instance of ceObject.Generated_for_debugger_class$generated_for_debugger_fun$1(id=ID): LceObject/Generated_for_debugger_class$generated_for_debugger_fun$1;
|
// RESULT: instance of Generated_for_debugger_class$generated_for_debugger_fun$1(id=ID): LGenerated_for_debugger_class$generated_for_debugger_fun$1;
|
||||||
@@ -28,4 +28,4 @@ open class Base {
|
|||||||
// RESULT: Expecting an element; looking at ERROR_ELEMENT '(1,6) in /fragment.kt
|
// RESULT: Expecting an element; looking at ERROR_ELEMENT '(1,6) in /fragment.kt
|
||||||
|
|
||||||
// EXPRESSION: super.baseFun()
|
// EXPRESSION: super.baseFun()
|
||||||
// RESULT: Cannot perform an action for expression with super call
|
// RESULT: Evaluation of 'super' call expression is not supported
|
||||||
+1
-1
@@ -2,4 +2,4 @@ prop += 1
|
|||||||
prop2 +=2
|
prop2 +=2
|
||||||
prop + prop2
|
prop + prop2
|
||||||
|
|
||||||
// RESULT: instance of kotlin.Triple(id=ID): Lkotlin/Triple;
|
// RESULT: 6: I
|
||||||
@@ -6,7 +6,7 @@ Compile bytecode for prop += 1
|
|||||||
prop2 +=2
|
prop2 +=2
|
||||||
prop + prop2
|
prop + prop2
|
||||||
|
|
||||||
// RESULT: instance of kotlin.Triple(id=ID): Lkotlin/Triple;
|
// RESULT: 6: I
|
||||||
Disconnected from the target VM
|
Disconnected from the target VM
|
||||||
|
|
||||||
Process finished with exit code 0
|
Process finished with exit code 0
|
||||||
|
|||||||
+8
-15
@@ -4,22 +4,15 @@ Connected to the target VM
|
|||||||
suspendContinuation.kt:7
|
suspendContinuation.kt:7
|
||||||
Compile bytecode for a
|
Compile bytecode for a
|
||||||
frame = main:7, SuspendContinuationKt {suspendContinuation}
|
frame = main:7, SuspendContinuationKt {suspendContinuation}
|
||||||
local = $continuation: kotlin.coroutines.Continuation = {suspendContinuation.SuspendContinuationKt$main$1@uniqueID}Continuation at suspendContinuation.SuspendContinuationKt.main(suspendContinuation.kt:5) (sp = null)
|
local = $completion: kotlin.coroutines.Continuation = {kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$1@uniqueID}Continuation at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$1 (sp = null)
|
||||||
field = result: java.lang.Object = null (sp = null)
|
|
||||||
field = label: int = 1 (sp = null)
|
field = label: int = 1 (sp = null)
|
||||||
field = I$0: int = 5 (sp = null)
|
field = $completion: kotlin.coroutines.Continuation = {kotlin.coroutines.jvm.internal.RunSuspend@uniqueID} (sp = null)
|
||||||
field = intercepted: kotlin.coroutines.Continuation = null (sp = ContinuationImpl.!EXT!)
|
field = result: kotlin.Result = null (sp = RunSuspend.!EXT!)
|
||||||
field = _context: kotlin.coroutines.CoroutineContext = {kotlin.coroutines.EmptyCoroutineContext@uniqueID}EmptyCoroutineContext (sp = ContinuationImpl.!EXT!)
|
field = $this_createCoroutineUnintercepted$inlined: kotlin.jvm.functions.Function1 = {suspendContinuation.SuspendContinuationKt$$$main@uniqueID}interface kotlin.jvm.functions.Function1 (sp = null)
|
||||||
- No fields to display
|
field = args: java.lang.String[] = {java.lang.String[0]@uniqueID} (sp = null)
|
||||||
field = completion: kotlin.coroutines.Continuation = {kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$1@uniqueID}Continuation at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$1 (sp = BaseContinuationImpl.!EXT!)
|
field = arity: int = 1 (sp = Lambda.!EXT!)
|
||||||
field = label: int = 1 (sp = null)
|
field = completion: kotlin.coroutines.Continuation = {kotlin.coroutines.jvm.internal.RunSuspend@uniqueID} (sp = BaseContinuationImpl.!EXT!)
|
||||||
field = $completion: kotlin.coroutines.Continuation = {kotlin.coroutines.jvm.internal.RunSuspend@uniqueID} (sp = null)
|
field = result: kotlin.Result = null (sp = RunSuspend.!EXT!)
|
||||||
field = result: kotlin.Result = null (sp = RunSuspend.!EXT!)
|
|
||||||
field = $this_createCoroutineUnintercepted$inlined: kotlin.jvm.functions.Function1 = {suspendContinuation.SuspendContinuationKt$$$main@uniqueID}interface kotlin.jvm.functions.Function1 (sp = null)
|
|
||||||
field = args: java.lang.String[] = {java.lang.String[0]@uniqueID} (sp = null)
|
|
||||||
field = arity: int = 1 (sp = Lambda.!EXT!)
|
|
||||||
field = completion: kotlin.coroutines.Continuation = {kotlin.coroutines.jvm.internal.RunSuspend@uniqueID} (sp = BaseContinuationImpl.!EXT!)
|
|
||||||
field = result: kotlin.Result = null (sp = RunSuspend.!EXT!)
|
|
||||||
local = a: int = 5 (sp = suspendContinuation.kt, 4)
|
local = a: int = 5 (sp = suspendContinuation.kt, 4)
|
||||||
Disconnected from the target VM
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package kt25220
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
//Breakpoint!
|
||||||
|
val a = 5
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
val a = "a"
|
||||||
|
val b = when (a) {
|
||||||
|
"a" -> "A"
|
||||||
|
"b" -> "B"
|
||||||
|
else -> throw RuntimeException()
|
||||||
|
}
|
||||||
|
b
|
||||||
|
|
||||||
|
// RESULT: "A": Ljava/lang/String;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
LineBreakpoint created at kt25220.kt:5
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
kt25220.kt:5
|
||||||
|
Compile bytecode for val a = "a"
|
||||||
|
val b = when (a) {
|
||||||
|
"a" -> "A"
|
||||||
|
"b" -> "B"
|
||||||
|
else -> throw RuntimeException()
|
||||||
|
}
|
||||||
|
b
|
||||||
|
|
||||||
|
// RESULT: "A": Ljava/lang/String;
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package kt25222
|
||||||
|
|
||||||
|
annotation class HelloWorld
|
||||||
|
|
||||||
|
class Foo {
|
||||||
|
@HelloWorld
|
||||||
|
fun bar() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val clazz = Class.forName("kt25222.Foo")
|
||||||
|
val bar = clazz.declaredMethods.first { it.name == "bar" }
|
||||||
|
val ann = bar.annotations.first()
|
||||||
|
|
||||||
|
//Breakpoint!
|
||||||
|
val a = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPRESSION: ann
|
||||||
|
// RESULT: instance of com.sun.proxy.$Proxy1(id=ID): Lcom/sun/proxy/$Proxy1;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
LineBreakpoint created at kt25222.kt:16
|
||||||
|
Run Java
|
||||||
|
Connected to the target VM
|
||||||
|
kt25222.kt:16
|
||||||
|
Compile bytecode for ann
|
||||||
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
+15
-1
@@ -3,7 +3,7 @@ package nestedInlineArguments
|
|||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
val list1 = listOf("a")
|
val list1 = listOf("a")
|
||||||
val list2 = listOf(Bar())
|
val list2 = listOf(Bar())
|
||||||
list1.map { list2.foo(1) }
|
list1.mapTest { list2.foo(1) }
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T: Bar> List<T>.foo(key: Int): T? {
|
inline fun <reified T: Bar> List<T>.foo(key: Int): T? {
|
||||||
@@ -16,4 +16,18 @@ inline fun <reified T: Bar> List<T>.foo(key: Int): T? {
|
|||||||
open class Foo
|
open class Foo
|
||||||
open class Bar {
|
open class Bar {
|
||||||
val i = 1
|
val i = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||||
|
@kotlin.internal.InlineOnly
|
||||||
|
inline fun <T, R> Iterable<T>.mapTest(transform: (T) -> R): List<R> {
|
||||||
|
return mapToTest(ArrayList<R>(), transform)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||||
|
@kotlin.internal.InlineOnly
|
||||||
|
inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapToTest(destination: C, transform: (T) -> R): C {
|
||||||
|
for (item in this)
|
||||||
|
destination.add(transform(item))
|
||||||
|
return destination
|
||||||
}
|
}
|
||||||
+1
-1
@@ -15,4 +15,4 @@ class Some {
|
|||||||
|
|
||||||
}
|
}
|
||||||
// EXPRESSION: base.a
|
// EXPRESSION: base.a
|
||||||
// RESULT: java.lang.NoSuchMethodError : Method not found: MemberDescription(ownerInternalName = privatePropertyWithExplicitDefaultGetter/Some, name = getA, desc = ()I, isStatic = false)
|
// RESULT: 1: I
|
||||||
|
|||||||
Generated
+10
@@ -231,6 +231,16 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
|||||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt22366.kt");
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt22366.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kt25220.kt")
|
||||||
|
public void testKt25220() throws Exception {
|
||||||
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt25220.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kt25222.kt")
|
||||||
|
public void testKt25222() throws Exception {
|
||||||
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt25222.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("kt28087.kt")
|
@TestMetadata("kt28087.kt")
|
||||||
public void testKt28087() throws Exception {
|
public void testKt28087() throws Exception {
|
||||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt28087.kt");
|
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt28087.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user