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.codegen.binding.CalculatedClosure;
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
|
||||
import org.jetbrains.kotlin.codegen.binding.MutableClosure;
|
||||
import org.jetbrains.kotlin.codegen.context.*;
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenForLambda;
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt;
|
||||
@@ -1759,6 +1760,16 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
StackValue intrinsicResult = applyIntrinsic(descriptor, IntrinsicPropertyGetter.class, resolvedCall, receiver);
|
||||
if (intrinsicResult != null) return intrinsicResult;
|
||||
|
||||
return generateNonIntrinsicSimpleNameExpression(expression, receiver, descriptor, resolvedCall, isSyntheticField);
|
||||
}
|
||||
|
||||
public StackValue generateNonIntrinsicSimpleNameExpression(
|
||||
@NotNull KtSimpleNameExpression expression,
|
||||
@NotNull StackValue receiver,
|
||||
@NotNull DeclarationDescriptor descriptor,
|
||||
@Nullable ResolvedCall<?> resolvedCall,
|
||||
boolean isSyntheticField
|
||||
) {
|
||||
if (descriptor instanceof PropertyDescriptor) {
|
||||
PropertyDescriptor propertyDescriptor = (PropertyDescriptor) descriptor;
|
||||
|
||||
@@ -2132,6 +2143,14 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
}
|
||||
else {
|
||||
skipPropertyAccessors = forceField;
|
||||
|
||||
if (JvmCodegenUtil.isDebuggerContext(context)
|
||||
&& Visibilities.isPrivate(propertyDescriptor.getVisibility())
|
||||
&& bindingContext.get(BACKING_FIELD_REQUIRED, propertyDescriptor) == Boolean.TRUE
|
||||
) {
|
||||
skipPropertyAccessors = true;
|
||||
}
|
||||
|
||||
ownerDescriptor = isBackingFieldMovedFromCompanion ? containingDeclaration : propertyDescriptor;
|
||||
}
|
||||
|
||||
@@ -2772,7 +2791,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private StackValue generateExtensionReceiver(@NotNull CallableDescriptor descriptor) {
|
||||
public StackValue generateExtensionReceiver(@NotNull CallableDescriptor descriptor) {
|
||||
ReceiverParameterDescriptor parameter = descriptor.getExtensionReceiverParameter();
|
||||
if (myFrameMap.getIndex(parameter) != -1) {
|
||||
KotlinType type = parameter.getReturnType();
|
||||
@@ -2891,24 +2910,40 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
|
||||
forceOuter = false;
|
||||
|
||||
CodegenContext enclosingContext;
|
||||
|
||||
//for constructor super call we should access to outer instance through parameter in locals, in other cases through field for captured outer
|
||||
if (inStartConstructorContext) {
|
||||
result = cur.getOuterExpression(result, false);
|
||||
cur = getNotNullParentContextForMethod(cur);
|
||||
enclosingContext = cur.getEnclosingClassContext();
|
||||
inStartConstructorContext = false;
|
||||
}
|
||||
else {
|
||||
cur = getNotNullParentContextForMethod(cur);
|
||||
// for now the script codegen only passes this branch, since the method context for script constructor is defined using function context
|
||||
if (cur instanceof ScriptContext && !(thisOrOuterClass instanceof ScriptDescriptor)) {
|
||||
return ((ScriptContext) cur).getOuterReceiverExpression(result, thisOrOuterClass);
|
||||
}
|
||||
else {
|
||||
result = cur.getOuterExpression(result, false);
|
||||
enclosingContext = cur.getEnclosingClassContext();
|
||||
|
||||
if (!(thisOrOuterClass instanceof ScriptDescriptor)) {
|
||||
if (cur instanceof ScriptLikeContext) {
|
||||
/* for now the script codegen only passes this branch,
|
||||
since the method context for script constructor is defined using function context */
|
||||
|
||||
return ((ScriptLikeContext) cur).getOuterReceiverExpression(result, thisOrOuterClass);
|
||||
} else if (enclosingContext instanceof ScriptLikeContext) {
|
||||
MutableClosure closure = cur.closure;
|
||||
if (closure != null) {
|
||||
StackValue captured = ((ScriptLikeContext) enclosingContext).captureVariable(closure, thisOrOuterClass);
|
||||
if (captured != null) {
|
||||
return captured;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = cur.getOuterExpression(result, false);
|
||||
}
|
||||
|
||||
cur = cur.getEnclosingClassContext();
|
||||
cur = enclosingContext;
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil;
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver;
|
||||
import org.jetbrains.kotlin.resolve.source.PsiSourceElement;
|
||||
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor;
|
||||
import org.jetbrains.kotlin.types.KotlinType;
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions;
|
||||
@@ -242,8 +243,20 @@ public class JvmCodegenUtil {
|
||||
}
|
||||
|
||||
public static boolean isDebuggerContext(@NotNull CodegenContext context) {
|
||||
KtFile file = DescriptorToSourceUtils.getContainingFile(context.getContextDescriptor());
|
||||
return file != null && CodeFragmentUtilKt.getSuppressDiagnosticsInDebugMode(file);
|
||||
PsiFile file = null;
|
||||
|
||||
DeclarationDescriptor contextDescriptor = context.getContextDescriptor();
|
||||
if (contextDescriptor instanceof DeclarationDescriptorWithSource) {
|
||||
SourceElement sourceElement = ((DeclarationDescriptorWithSource) contextDescriptor).getSource();
|
||||
if (sourceElement instanceof PsiSourceElement) {
|
||||
PsiElement psi = ((PsiSourceElement) sourceElement).getPsi();
|
||||
if (psi != null) {
|
||||
file = psi.getContainingFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return file instanceof KtFile && CodeFragmentUtilKt.getSuppressDiagnosticsInDebugMode((KtFile) file);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -32,10 +32,7 @@ import org.jetbrains.kotlin.fileClasses.JvmFileClassInfo;
|
||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil;
|
||||
import org.jetbrains.kotlin.name.FqName;
|
||||
import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject;
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration;
|
||||
import org.jetbrains.kotlin.psi.KtFile;
|
||||
import org.jetbrains.kotlin.psi.KtScript;
|
||||
import org.jetbrains.kotlin.psi.*;
|
||||
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
|
||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker;
|
||||
@@ -98,30 +95,40 @@ public class PackageCodegenImpl implements PackageCodegen {
|
||||
Type fileClassType = AsmUtil.asmTypeByFqNameWithoutInnerClasses(fileClassInfo.getFileClassFqName());
|
||||
PackageContext packagePartContext = state.getRootContext().intoPackagePart(packageFragment, fileClassType, file);
|
||||
|
||||
List<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()) {
|
||||
if (declaration instanceof KtClassOrObject) {
|
||||
ClassDescriptor descriptor = state.getBindingContext().get(BindingContext.CLASS, declaration);
|
||||
if (PsiUtilsKt.hasExpectModifier(declaration) &&
|
||||
(descriptor == null || !ExpectedActualDeclarationChecker.shouldGenerateExpectClass(descriptor))) {
|
||||
continue;
|
||||
for (KtDeclaration declaration : file.getDeclarations()) {
|
||||
if (declaration instanceof KtClassOrObject) {
|
||||
ClassDescriptor descriptor = state.getBindingContext().get(BindingContext.CLASS, declaration);
|
||||
if (PsiUtilsKt.hasExpectModifier(declaration) &&
|
||||
(descriptor == null || !ExpectedActualDeclarationChecker.shouldGenerateExpectClass(descriptor))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
KtClassOrObject classOrObject = (KtClassOrObject) declaration;
|
||||
if (state.getGenerateDeclaredClassFilter().shouldGenerateClass(classOrObject)) {
|
||||
classOrObjects.add(classOrObject);
|
||||
}
|
||||
}
|
||||
else if (declaration instanceof KtScript) {
|
||||
KtScript script = (KtScript) declaration;
|
||||
|
||||
KtClassOrObject classOrObject = (KtClassOrObject) declaration;
|
||||
if (state.getGenerateDeclaredClassFilter().shouldGenerateClass(classOrObject)) {
|
||||
classOrObjects.add(classOrObject);
|
||||
if (state.getGenerateDeclaredClassFilter().shouldGenerateScript(script)) {
|
||||
ScriptCodegen.createScriptCodegen(script, state, packagePartContext).generate();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (declaration instanceof KtScript) {
|
||||
KtScript script = (KtScript) declaration;
|
||||
|
||||
if (state.getGenerateDeclaredClassFilter().shouldGenerateScript(script)) {
|
||||
ScriptCodegen.createScriptCodegen(script, state, packagePartContext).generate();
|
||||
}
|
||||
}
|
||||
generateClassesAndObjectsInFile(classOrObjects, packagePartContext);
|
||||
}
|
||||
generateClassesAndObjectsInFile(classOrObjects, packagePartContext);
|
||||
|
||||
if (!state.getGenerateDeclaredClassFilter().shouldGeneratePackagePart(file)) return;
|
||||
|
||||
|
||||
+9
-1
@@ -222,7 +222,15 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
||||
|
||||
@Override
|
||||
public void visitKtFile(@NotNull KtFile file) {
|
||||
nameStack.push(AsmUtil.internalNameByFqNameWithoutInnerClasses(file.getPackageFqName()));
|
||||
String name;
|
||||
if (file instanceof KtCodeFragment) {
|
||||
CodeFragmentCodegenInfo info = CodeFragmentCodegen.getCodeFragmentInfo((KtCodeFragment) file);
|
||||
name = info.getClassDescriptor().getName().asString() + "$" + info.getMethodDescriptor().getName().asString();
|
||||
} else {
|
||||
name = AsmUtil.internalNameByFqNameWithoutInnerClasses(file.getPackageFqName());
|
||||
}
|
||||
|
||||
nameStack.push(name);
|
||||
visitKtElement(file);
|
||||
nameStack.pop();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
package org.jetbrains.kotlin.codegen.binding;
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import kotlin.collections.CollectionsKt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -90,6 +92,15 @@ public class CodegenBinding {
|
||||
CodegenAnnotatingVisitor visitor = new CodegenAnnotatingVisitor(state);
|
||||
for (KtFile file : allFilesInPackages(state.getBindingContext(), state.getFiles())) {
|
||||
file.accept(visitor);
|
||||
if (file instanceof KtCodeFragment) {
|
||||
PsiElement context = file.getContext();
|
||||
if (context != null) {
|
||||
PsiFile contextFile = context.getContainingFile();
|
||||
if (contextFile != null) {
|
||||
contextFile.accept(visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ fun ClassFileFactory.getClassFiles(): Iterable<OutputFile> {
|
||||
return asList().filterClassFiles()
|
||||
}
|
||||
|
||||
fun List<OutputFile>.filterClassFiles(): Iterable<OutputFile> {
|
||||
fun List<OutputFile>.filterClassFiles(): List<OutputFile> {
|
||||
return filter { it.relativePath.endsWith(".class") }
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.jetbrains.kotlin.codegen.context
|
||||
|
||||
import org.jetbrains.kotlin.codegen.FieldInfo
|
||||
import org.jetbrains.kotlin.codegen.OwnerKind
|
||||
import org.jetbrains.kotlin.codegen.StackValue
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
@@ -35,12 +34,12 @@ import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
|
||||
class ScriptContext(
|
||||
val typeMapper: KotlinTypeMapper,
|
||||
typeMapper: KotlinTypeMapper,
|
||||
val scriptDescriptor: ScriptDescriptor,
|
||||
val earlierScripts: List<ScriptDescriptor>,
|
||||
contextDescriptor: ClassDescriptor,
|
||||
parentContext: CodegenContext<*>?
|
||||
) : ClassContext(typeMapper, contextDescriptor, OwnerKind.IMPLEMENTATION, parentContext, null) {
|
||||
) : ScriptLikeContext(typeMapper, contextDescriptor, parentContext) {
|
||||
val lastStatement: KtExpression?
|
||||
|
||||
val resultFieldInfo: FieldInfo
|
||||
@@ -89,7 +88,7 @@ class ScriptContext(
|
||||
|
||||
fun getProvidedPropertyType(index: Int): Type = typeMapper.mapType(scriptDescriptor.scriptProvidedProperties[index].type)
|
||||
|
||||
fun getOuterReceiverExpression(prefix: StackValue?, thisOrOuterClass: ClassDescriptor): StackValue {
|
||||
override fun getOuterReceiverExpression(prefix: StackValue?, thisOrOuterClass: ClassDescriptor): StackValue {
|
||||
if (thisOrOuterClass.containingDeclaration == scriptDescriptor) {
|
||||
return prefix ?: StackValue.LOCAL_0
|
||||
}
|
||||
|
||||
@@ -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.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.resolve.*
|
||||
@@ -120,18 +121,17 @@ class GenerationState private constructor(
|
||||
abstract fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean
|
||||
abstract fun shouldGeneratePackagePart(ktFile: KtFile): Boolean
|
||||
abstract fun shouldGenerateScript(script: KtScript): Boolean
|
||||
abstract fun shouldGenerateCodeFragment(script: KtCodeFragment): Boolean
|
||||
open fun shouldGenerateClassMembers(processingClassOrObject: KtClassOrObject) = shouldGenerateClass(processingClassOrObject)
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val GENERATE_ALL: GenerateClassFilter = object : GenerateClassFilter() {
|
||||
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject): Boolean = true
|
||||
|
||||
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean = true
|
||||
|
||||
override fun shouldGenerateScript(script: KtScript): Boolean = true
|
||||
|
||||
override fun shouldGeneratePackagePart(ktFile: KtFile): Boolean = true
|
||||
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -30,10 +30,7 @@ import org.jetbrains.kotlin.codegen.CompilationErrorHandler
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtPsiUtil
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
|
||||
@@ -199,6 +196,7 @@ private class ClassFilterForClassOrObject(private val classOrObject: KtClassOrOb
|
||||
= shouldGenerateClassMembers(processingClassOrObject) || processingClassOrObject.isAncestor(classOrObject, true)
|
||||
|
||||
override fun shouldGenerateScript(script: KtScript) = PsiTreeUtil.isAncestor(script, classOrObject, false)
|
||||
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
|
||||
}
|
||||
|
||||
object ClassFilterForFacade : GenerationState.GenerateClassFilter() {
|
||||
@@ -206,6 +204,7 @@ object ClassFilterForFacade : GenerationState.GenerateClassFilter() {
|
||||
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = KtPsiUtil.isLocal(processingClassOrObject)
|
||||
override fun shouldGeneratePackagePart(ktFile: KtFile) = true
|
||||
override fun shouldGenerateScript(script: KtScript) = false
|
||||
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
|
||||
}
|
||||
|
||||
private class ClassFilterForScript(val script: KtScript) : GenerationState.GenerateClassFilter() {
|
||||
@@ -220,4 +219,5 @@ private class ClassFilterForScript(val script: KtScript) : GenerationState.Gener
|
||||
override fun shouldGeneratePackagePart(ktFile: KtFile): Boolean = script.containingKtFile === ktFile
|
||||
|
||||
override fun shouldGenerateScript(script: KtScript): Boolean = this.script === script
|
||||
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
|
||||
}
|
||||
|
||||
+6
@@ -337,6 +337,12 @@ abstract class KtLightClassForSourceDeclaration(protected val classOrObject: KtC
|
||||
}
|
||||
|
||||
fun createNoCache(classOrObject: KtClassOrObject): KtLightClassForSourceDeclaration? {
|
||||
val containingFile = classOrObject.containingFile
|
||||
if (containingFile is KtCodeFragment) {
|
||||
// Avoid building light classes for code fragments
|
||||
return null
|
||||
}
|
||||
|
||||
if (classOrObject.shouldNotBeVisibleAsLightClass()) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -199,3 +199,5 @@ abstract class KtCodeFragment(
|
||||
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 =
|
||||
(parent as? KtDotQualifiedExpression)?.receiverExpression == this
|
||||
|
||||
fun KtExpression.isDotSelector(): Boolean =
|
||||
(parent as? KtDotQualifiedExpression)?.selectorExpression == this
|
||||
|
||||
fun KtExpression.getPossiblyQualifiedCallExpression(): KtCallExpression? =
|
||||
((this as? KtQualifiedExpression)?.selectorExpression ?: this) as? KtCallExpression
|
||||
|
||||
|
||||
@@ -636,10 +636,6 @@ fun main(args: Array<String>) {
|
||||
model("debugger/smartStepInto")
|
||||
}
|
||||
|
||||
testClass<AbstractBeforeExtractFunctionInsertionTest> {
|
||||
model("debugger/insertBeforeExtractFunction", extension = "kt")
|
||||
}
|
||||
|
||||
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 = "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.AbstractTextJavaToKotlinCopyPasteConversionTest
|
||||
import org.jetbrains.kotlin.idea.coverage.AbstractKotlinCoverageOutputFilesTest
|
||||
import org.jetbrains.kotlin.idea.debugger.AbstractBeforeExtractFunctionInsertionTest
|
||||
import org.jetbrains.kotlin.idea.debugger.AbstractKotlinSteppingTest
|
||||
import org.jetbrains.kotlin.idea.debugger.AbstractPositionManagerTest
|
||||
import org.jetbrains.kotlin.idea.debugger.AbstractSmartStepIntoTest
|
||||
import org.jetbrains.kotlin.idea.debugger.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.*
|
||||
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToDecompiledLibraryTest
|
||||
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToLibrarySourceTest
|
||||
@@ -627,10 +624,6 @@ fun main(args: Array<String>) {
|
||||
model("debugger/smartStepInto")
|
||||
}
|
||||
|
||||
testClass<AbstractBeforeExtractFunctionInsertionTest> {
|
||||
model("debugger/insertBeforeExtractFunction", extension = "kt")
|
||||
}
|
||||
|
||||
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 = "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.AbstractTextJavaToKotlinCopyPasteConversionTest
|
||||
import org.jetbrains.kotlin.idea.coverage.AbstractKotlinCoverageOutputFilesTest
|
||||
import org.jetbrains.kotlin.idea.debugger.AbstractBeforeExtractFunctionInsertionTest
|
||||
import org.jetbrains.kotlin.idea.debugger.AbstractKotlinSteppingTest
|
||||
import org.jetbrains.kotlin.idea.debugger.AbstractPositionManagerTest
|
||||
import org.jetbrains.kotlin.idea.debugger.AbstractSmartStepIntoTest
|
||||
import org.jetbrains.kotlin.idea.debugger.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.*
|
||||
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToDecompiledLibraryTest
|
||||
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToLibrarySourceTest
|
||||
@@ -619,10 +616,6 @@ fun main(args: Array<String>) {
|
||||
model("debugger/smartStepInto")
|
||||
}
|
||||
|
||||
testClass<AbstractBeforeExtractFunctionInsertionTest> {
|
||||
model("debugger/insertBeforeExtractFunction", extension = "kt")
|
||||
}
|
||||
|
||||
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 = "doSmartStepIntoTest", testClassName = "SmartStepInto")
|
||||
|
||||
@@ -616,10 +616,6 @@ fun main(args: Array<String>) {
|
||||
model("debugger/smartStepInto")
|
||||
}
|
||||
|
||||
testClass<AbstractBeforeExtractFunctionInsertionTest> {
|
||||
model("debugger/insertBeforeExtractFunction", extension = "kt")
|
||||
}
|
||||
|
||||
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 = "doSmartStepIntoTest", testClassName = "SmartStepInto")
|
||||
|
||||
+110
-83
@@ -16,18 +16,23 @@
|
||||
|
||||
package org.jetbrains.kotlin.idea.caches.resolve
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
|
||||
import org.jetbrains.kotlin.idea.project.ResolveElementCache
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||
import org.jetbrains.kotlin.resolve.*
|
||||
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoAfter
|
||||
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
|
||||
import org.jetbrains.kotlin.resolve.scopes.ExplicitImportsScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.ImportingScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.utils.ErrorLexicalScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.utils.addImportingScopes
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices
|
||||
@@ -43,132 +48,154 @@ class CodeFragmentAnalyzer(
|
||||
@set:Inject // component dependency cycle
|
||||
lateinit var resolveElementCache: ResolveElementCache
|
||||
|
||||
fun analyzeCodeFragment(codeFragment: KtCodeFragment, trace: BindingTrace, bodyResolveMode: BodyResolveMode): BindingTrace {
|
||||
val codeFragmentElement = codeFragment.getContentElement()
|
||||
fun analyzeCodeFragment(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): BindingTrace {
|
||||
val contextAnalysisResult = analyzeCodeFragmentContext(codeFragment, bodyResolveMode)
|
||||
return doAnalyzeCodeFragment(codeFragment, contextAnalysisResult)
|
||||
}
|
||||
|
||||
val (scopeForContextElement, dataFlowInfo, newBindingContext) = doAnalyzeCoreFragment(codeFragment) {
|
||||
resolveElementCache!!.resolveToElements(listOf(it), bodyResolveMode)
|
||||
} ?: return trace
|
||||
private fun doAnalyzeCodeFragment(codeFragment: KtCodeFragment, contextAnalysisResult: ContextAnalysisResult): BindingTrace {
|
||||
val (bindingContext, scope, dataFlowInfo) = contextAnalysisResult
|
||||
val bindingTrace = DelegatingBindingTrace(bindingContext, "For code fragment analysis")
|
||||
|
||||
val newBindingTrace = DelegatingBindingTrace(newBindingContext, "For code fragment analysis")
|
||||
|
||||
when (codeFragmentElement) {
|
||||
when (val contentElement = codeFragment.getContentElement()) {
|
||||
is KtExpression -> {
|
||||
PreliminaryDeclarationVisitor.createForExpression(
|
||||
codeFragmentElement, newBindingTrace,
|
||||
contentElement, bindingTrace,
|
||||
expressionTypingServices.languageVersionSettings
|
||||
)
|
||||
|
||||
expressionTypingServices.getTypeInfo(
|
||||
scopeForContextElement,
|
||||
codeFragmentElement,
|
||||
TypeUtils.NO_EXPECTED_TYPE,
|
||||
dataFlowInfo,
|
||||
newBindingTrace,
|
||||
false
|
||||
scope, contentElement, TypeUtils.NO_EXPECTED_TYPE,
|
||||
dataFlowInfo, bindingTrace, false
|
||||
)
|
||||
}
|
||||
|
||||
is KtTypeReference -> {
|
||||
val context = TypeResolutionContext(
|
||||
scopeForContextElement,
|
||||
newBindingTrace,
|
||||
true,
|
||||
true,
|
||||
codeFragment.suppressDiagnosticsInDebugMode()
|
||||
scope, bindingTrace,
|
||||
true, true, codeFragment.suppressDiagnosticsInDebugMode()
|
||||
).noBareTypes()
|
||||
typeResolver.resolvePossiblyBareType(context, codeFragmentElement)
|
||||
|
||||
typeResolver.resolvePossiblyBareType(context, contentElement)
|
||||
}
|
||||
}
|
||||
|
||||
return newBindingTrace
|
||||
return bindingTrace
|
||||
}
|
||||
|
||||
//TODO: this code should be moved into debugger which should set correct context for its code fragment
|
||||
private fun KtElement.correctContextForElement(): KtElement {
|
||||
return when (this) {
|
||||
is KtProperty -> this.delegateExpressionOrInitializer
|
||||
is KtFunctionLiteral -> this.bodyExpression?.statements?.lastOrNull()
|
||||
is KtDeclarationWithBody -> this.bodyExpression
|
||||
is KtBlockExpression -> this.statements.lastOrNull()
|
||||
else -> null
|
||||
} ?: this
|
||||
}
|
||||
|
||||
private fun doAnalyzeCoreFragment(
|
||||
codeFragment: KtCodeFragment,
|
||||
resolveToElement: (KtElement) -> BindingContext
|
||||
): Triple<LexicalScope, DataFlowInfo, BindingContext>? {
|
||||
val context = codeFragment.context
|
||||
|
||||
val scopeForContextElement: LexicalScope?
|
||||
private data class ContextAnalysisResult(
|
||||
val bindingContext: BindingContext,
|
||||
val scope: LexicalScope,
|
||||
val dataFlowInfo: DataFlowInfo
|
||||
)
|
||||
|
||||
fun getClassDescriptor(classOrObject: KtClassOrObject): Pair<BindingContext, ClassDescriptorWithResolutionScopes>? {
|
||||
val bindingContext: BindingContext
|
||||
val classDescriptor: ClassDescriptor?
|
||||
|
||||
if (!KtPsiUtil.isLocal(classOrObject)) {
|
||||
bindingContext = resolveSession.bindingContext
|
||||
classDescriptor = resolveSession.getClassDescriptor(classOrObject, NoLookupLocation.FROM_IDE)
|
||||
} else {
|
||||
bindingContext = resolveToElement(classOrObject)
|
||||
classDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, classOrObject] as ClassDescriptor?
|
||||
}
|
||||
|
||||
return (classDescriptor as? ClassDescriptorWithResolutionScopes)?.let { Pair(bindingContext, it) }
|
||||
private fun analyzeCodeFragmentContext(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): ContextAnalysisResult {
|
||||
fun resolutionFactory(element: KtElement): BindingContext {
|
||||
return resolveElementCache.resolveToElements(listOf(element), bodyResolveMode)
|
||||
}
|
||||
|
||||
val bindingContextForContext: BindingContext
|
||||
val context = refineContextElement(codeFragment.context)
|
||||
|
||||
var bindingContext: BindingContext = BindingContext.EMPTY
|
||||
var dataFlowInfo: DataFlowInfo = DataFlowInfo.EMPTY
|
||||
var scope: LexicalScope? = null
|
||||
|
||||
when (context) {
|
||||
is KtPrimaryConstructor -> {
|
||||
val (bindingContext, classDescriptor) = getClassDescriptor(context.getContainingClassOrObject()) ?: return null
|
||||
|
||||
scopeForContextElement = classDescriptor.scopeForInitializerResolution
|
||||
dataFlowInfo = DataFlowInfo.EMPTY
|
||||
bindingContextForContext = bindingContext
|
||||
val containingClass = context.getContainingClassOrObject()
|
||||
val resolutionResult = getClassDescriptor(containingClass, ::resolutionFactory)
|
||||
if (resolutionResult != null) {
|
||||
bindingContext = resolutionResult.bindingContext
|
||||
scope = resolutionResult.descriptor.scopeForInitializerResolution
|
||||
}
|
||||
}
|
||||
is KtSecondaryConstructor -> {
|
||||
val correctedContext = context.getDelegationCall().calleeExpression!!
|
||||
bindingContextForContext = resolveToElement(correctedContext)
|
||||
|
||||
scopeForContextElement = bindingContextForContext[BindingContext.LEXICAL_SCOPE, correctedContext]
|
||||
dataFlowInfo = DataFlowInfo.EMPTY
|
||||
val expression = context.bodyExpression ?: context.getDelegationCall().calleeExpression
|
||||
if (expression != null) {
|
||||
bindingContext = resolutionFactory(expression)
|
||||
scope = bindingContext[BindingContext.LEXICAL_SCOPE, expression]
|
||||
}
|
||||
}
|
||||
is KtClassOrObject -> {
|
||||
val (bindingContext, classDescriptor) = getClassDescriptor(context) ?: return null
|
||||
scopeForContextElement = classDescriptor.scopeForMemberDeclarationResolution
|
||||
dataFlowInfo = DataFlowInfo.EMPTY
|
||||
bindingContextForContext = bindingContext
|
||||
val resolutionResult = getClassDescriptor(context, ::resolutionFactory)
|
||||
if (resolutionResult != null) {
|
||||
bindingContext = resolutionResult.bindingContext
|
||||
scope = resolutionResult.descriptor.scopeForMemberDeclarationResolution
|
||||
}
|
||||
}
|
||||
is KtFile -> {
|
||||
scopeForContextElement = resolveSession.fileScopeProvider.getFileResolutionScope(context)
|
||||
dataFlowInfo = DataFlowInfo.EMPTY
|
||||
bindingContextForContext = BindingContext.EMPTY
|
||||
bindingContext = resolveSession.bindingContext
|
||||
scope = resolveSession.fileScopeProvider.getFileResolutionScope(context)
|
||||
}
|
||||
is KtElement -> {
|
||||
val correctedContext = context.correctContextForElement()
|
||||
bindingContextForContext = resolveToElement(correctedContext)
|
||||
|
||||
scopeForContextElement = bindingContextForContext[BindingContext.LEXICAL_SCOPE, correctedContext]
|
||||
dataFlowInfo = bindingContextForContext.getDataFlowInfoAfter(correctedContext)
|
||||
bindingContext = resolutionFactory(context)
|
||||
scope = bindingContext[BindingContext.LEXICAL_SCOPE, context]
|
||||
dataFlowInfo = bindingContext.getDataFlowInfoAfter(context)
|
||||
}
|
||||
else -> return null
|
||||
}
|
||||
|
||||
if (scopeForContextElement == null) return null
|
||||
val scopeWithImports = enrichScopeWithImports(scope ?: ErrorLexicalScope(), codeFragment)
|
||||
return ContextAnalysisResult(bindingContext, scopeWithImports, dataFlowInfo)
|
||||
}
|
||||
|
||||
private data class ClassResolutionResult(val bindingContext: BindingContext, val descriptor: ClassDescriptorWithResolutionScopes)
|
||||
|
||||
private fun getClassDescriptor(
|
||||
classOrObject: KtClassOrObject,
|
||||
resolutionFactory: (KtElement) -> BindingContext
|
||||
): ClassResolutionResult? {
|
||||
val bindingContext: BindingContext
|
||||
val classDescriptor: ClassDescriptor?
|
||||
|
||||
if (!KtPsiUtil.isLocal(classOrObject)) {
|
||||
bindingContext = resolveSession.bindingContext
|
||||
classDescriptor = resolveSession.getClassDescriptor(classOrObject, NoLookupLocation.FROM_IDE)
|
||||
} else {
|
||||
bindingContext = resolutionFactory(classOrObject)
|
||||
classDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, classOrObject] as ClassDescriptor?
|
||||
}
|
||||
|
||||
return (classDescriptor as? ClassDescriptorWithResolutionScopes)?.let { ClassResolutionResult(bindingContext, it) }
|
||||
}
|
||||
|
||||
private fun refineContextElement(context: PsiElement?): KtElement? {
|
||||
return when (context) {
|
||||
is KtParameter -> context.getParentOfType<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()
|
||||
if (importList == null || importList.imports.isEmpty()) {
|
||||
return Triple(scopeForContextElement, dataFlowInfo, bindingContextForContext)
|
||||
if (importList != null && importList.imports.isNotEmpty()) {
|
||||
additionalImportingScopes += createImportScopes(importList)
|
||||
}
|
||||
|
||||
val importScopes = importList.imports.mapNotNull {
|
||||
if (additionalImportingScopes.isNotEmpty()) {
|
||||
return scope.addImportingScopes(additionalImportingScopes)
|
||||
}
|
||||
|
||||
return scope
|
||||
}
|
||||
|
||||
private fun createImportScopes(importList: KtImportList): List<ImportingScope> {
|
||||
return importList.imports.mapNotNull {
|
||||
qualifierResolver.processImportReference(
|
||||
it, resolveSession.moduleDescriptor, resolveSession.trace,
|
||||
excludedImportNames = emptyList(), packageFragmentForVisibilityCheck = null
|
||||
)
|
||||
}
|
||||
|
||||
return Triple(scopeForContextElement.addImportingScopes(importScopes), dataFlowInfo, bindingContextForContext)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-16
@@ -176,7 +176,9 @@ private object KotlinResolveDataProvider {
|
||||
): AnalysisResult {
|
||||
try {
|
||||
if (analyzableElement is KtCodeFragment) {
|
||||
return AnalysisResult.success(analyzeExpressionCodeFragment(codeFragmentAnalyzer, analyzableElement), moduleDescriptor)
|
||||
val bodyResolveMode = BodyResolveMode.PARTIAL_FOR_COMPLETION
|
||||
val bindingContext = codeFragmentAnalyzer.analyzeCodeFragment(analyzableElement, bodyResolveMode).bindingContext
|
||||
return AnalysisResult.success(bindingContext, moduleDescriptor)
|
||||
}
|
||||
|
||||
val trace = DelegatingBindingTrace(
|
||||
@@ -217,19 +219,4 @@ private object KotlinResolveDataProvider {
|
||||
return AnalysisResult.internalError(BindingContext.EMPTY, e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun analyzeExpressionCodeFragment(codeFragmentAnalyzer: CodeFragmentAnalyzer, codeFragment: KtCodeFragment): BindingContext {
|
||||
val contextElement = codeFragment.getContentElement()
|
||||
val trace = if (contextElement != null) {
|
||||
DelegatingBindingTrace(contextElement.analyze(BodyResolveMode.PARTIAL_FOR_COMPLETION), "Trace for code fragment resolution")
|
||||
} else {
|
||||
BindingTraceContext()
|
||||
}
|
||||
|
||||
return codeFragmentAnalyzer.analyzeCodeFragment(
|
||||
codeFragment,
|
||||
trace,
|
||||
BodyResolveMode.PARTIAL_FOR_COMPLETION //TODO: discuss it
|
||||
).bindingContext
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,14 +457,12 @@ class ResolveElementCache(
|
||||
}
|
||||
|
||||
private fun codeFragmentAdditionalResolve(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): BindingTrace {
|
||||
val trace = createDelegatingTrace(codeFragment, bodyResolveMode.bindingTraceFilter)
|
||||
|
||||
val contextResolveMode = if (bodyResolveMode == BodyResolveMode.PARTIAL)
|
||||
BodyResolveMode.PARTIAL_FOR_COMPLETION
|
||||
else
|
||||
bodyResolveMode
|
||||
|
||||
return codeFragmentAnalyzer.analyzeCodeFragment(codeFragment, trace, contextResolveMode)
|
||||
return codeFragmentAnalyzer.analyzeCodeFragment(codeFragment, contextResolveMode)
|
||||
}
|
||||
|
||||
private fun annotationAdditionalResolve(resolveSession: ResolveSession, ktAnnotationEntry: KtAnnotationEntry): BindingTrace {
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ import com.intellij.xdebugger.frame.XNamedValue
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.VariableFinder.Companion.SUSPEND_LAMBDA_CLASSES
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder.Companion.SUSPEND_LAMBDA_CLASSES
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.getInvokePolicy
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousCl
|
||||
import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME
|
||||
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
|
||||
import org.jetbrains.kotlin.codegen.coroutines.continuationAsmTypes
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineEndOffset
|
||||
@@ -27,7 +26,6 @@ import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
import java.util.*
|
||||
@@ -241,15 +239,6 @@ fun findCallByEndToken(element: PsiElement): KtCallExpression? {
|
||||
}
|
||||
}
|
||||
|
||||
fun PropertyDescriptor.getBackingFieldName(): String? {
|
||||
if (backingField == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val jvmNameAnnotation = DescriptorUtils.findJvmNameAnnotation(this) ?: return name.asString()
|
||||
return jvmNameAnnotation.allValueArguments.values.singleOrNull()?.toString()
|
||||
}
|
||||
|
||||
fun Type.isSubtype(className: String): Boolean = isSubtype(AsmType.getObjectType(className))
|
||||
|
||||
fun Type.isSubtype(type: AsmType): Boolean {
|
||||
|
||||
@@ -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.events.DebuggerCommandImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl
|
||||
import com.intellij.ide.highlighter.JavaFileType
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
@@ -35,9 +34,6 @@ import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.psi.util.PsiTypesUtil
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import com.intellij.util.concurrency.Semaphore
|
||||
import com.intellij.xdebugger.XDebuggerManager
|
||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.eval4j.jdi.asValue
|
||||
@@ -55,7 +51,6 @@ import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.idea.versions.getKotlinJvmRuntimeMarkerClass
|
||||
import org.jetbrains.kotlin.j2k.AfterConversionPass
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
||||
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.makeNullable
|
||||
@@ -63,28 +58,16 @@ import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
||||
override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
||||
val contextElement = getWrappedContextElement(project, context)
|
||||
if (contextElement == null) {
|
||||
LOG.warn("CodeFragment with null context created:\noriginalContext = ${context?.getElementTextWithContext()}")
|
||||
}
|
||||
val codeFragment = if (item.kind == CodeFragmentKind.EXPRESSION) {
|
||||
KtExpressionCodeFragment(
|
||||
project,
|
||||
"fragment.kt",
|
||||
item.text,
|
||||
initImports(item.imports),
|
||||
contextElement
|
||||
)
|
||||
} else {
|
||||
KtBlockCodeFragment(
|
||||
project,
|
||||
"fragment.kt",
|
||||
item.text,
|
||||
initImports(item.imports),
|
||||
contextElement
|
||||
)
|
||||
val contextElement = getContextElement(context)
|
||||
|
||||
val constructor = when (item.kind) {
|
||||
null -> error("Code fragment kind should be set")
|
||||
CodeFragmentKind.EXPRESSION -> ::KtExpressionCodeFragment
|
||||
CodeFragmentKind.CODE_BLOCK -> ::KtBlockCodeFragment
|
||||
}
|
||||
|
||||
val codeFragment = constructor(project, "fragment.kt", item.text, initImports(item.imports), contextElement)
|
||||
|
||||
codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR, { expression: KtExpression ->
|
||||
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val debuggerSession = debuggerContext.debuggerSession
|
||||
@@ -135,19 +118,19 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
||||
}
|
||||
|
||||
val receiverTypeReference =
|
||||
frameDescriptor.thisObject?.let { createKotlinProperty(project, "this_0", it.type().name(), it) }?.typeReference
|
||||
frameDescriptor.thisObject?.let { createKotlinProperty(project, FAKE_JAVA_THIS_NAME, it.type().name(), it) }?.typeReference
|
||||
val receiverTypeText = receiverTypeReference?.let { "${it.text}." } ?: ""
|
||||
|
||||
val kotlinVariablesText =
|
||||
frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project)
|
||||
|
||||
val fakeFunctionText = "fun ${receiverTypeText}_java_locals_debug_fun_() {\n$kotlinVariablesText\n}"
|
||||
val fakeFunctionText = "fun ${receiverTypeText}$FAKE_JAVA_CONTEXT_FUNCTION_NAME() {\n$kotlinVariablesText\n}"
|
||||
|
||||
val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement)
|
||||
val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction
|
||||
val fakeContext = fakeFunction?.bodyBlockExpression?.statements?.lastOrNull()
|
||||
|
||||
return@putCopyableUserData wrapContextIfNeeded(project, contextElement, fakeContext) ?: emptyFile
|
||||
return@putCopyableUserData fakeContext ?: emptyFile
|
||||
})
|
||||
}
|
||||
|
||||
@@ -219,12 +202,6 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
||||
return import
|
||||
}
|
||||
|
||||
private fun getWrappedContextElement(project: Project, context: PsiElement?): PsiElement? {
|
||||
val newContext = getContextElement(context)
|
||||
if (newContext !is KtElement) return newContext
|
||||
return wrapContextIfNeeded(project, context, newContext)
|
||||
}
|
||||
|
||||
override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
||||
val kotlinCodeFragment = createCodeFragment(item, context, project)
|
||||
if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtExpressionCodeFragment) {
|
||||
@@ -292,13 +269,12 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance(this::class.java)
|
||||
|
||||
val LABEL_VARIABLE_VALUE_KEY: Key<Value> = Key.create<Value>("_label_variable_value_key_")
|
||||
|
||||
private const val DEBUG_LABEL_SUFFIX: String = "_DebugLabel"
|
||||
|
||||
@TestOnly
|
||||
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? {
|
||||
if (elementAt == null) return null
|
||||
|
||||
@@ -335,17 +311,6 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
||||
return containingFile
|
||||
}
|
||||
|
||||
//internal for tests
|
||||
fun createCodeFragmentForLabeledObjects(project: Project, markupMap: Map<*, ValueMarkup>): Pair<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 {
|
||||
val sb = StringBuilder()
|
||||
|
||||
@@ -389,21 +354,6 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun wrapContextIfNeeded(project: Project, originalContext: PsiElement?, newContext: KtElement?): KtElement? {
|
||||
val markupMap: Map<*, ValueMarkup>? =
|
||||
if (ApplicationManager.getApplication().isUnitTestMode)
|
||||
NodeDescriptorImpl.getMarkupMap(originalContext?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.debugProcess)
|
||||
else
|
||||
(XDebuggerManager.getInstance(project).currentSession as? XDebugSessionImpl)?.valueMarkers?.allMarkers
|
||||
|
||||
if (markupMap == null || markupMap.isEmpty()) return newContext
|
||||
|
||||
val (text, labels) = createCodeFragmentForLabeledObjects(project, markupMap)
|
||||
if (text.isEmpty()) return newContext
|
||||
|
||||
return createWrappingContext(text, labels, newContext, project)
|
||||
}
|
||||
|
||||
private fun createFakeFileWithJavaContextElement(funWithLocalVariables: String, javaContext: PsiElement): KtFile {
|
||||
val javaFile = javaContext.containingFile as? PsiJavaFile
|
||||
|
||||
@@ -419,25 +369,4 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
||||
|
||||
return KtPsiFactory(javaContext.project).createAnalyzableFile("fakeFileForJavaContextInDebugger.kt", sb.toString(), javaContext)
|
||||
}
|
||||
|
||||
// internal for test
|
||||
private fun createWrappingContext(
|
||||
newFragmentText: String,
|
||||
labels: Map<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.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.libraries.LibraryUtil
|
||||
@@ -29,35 +28,31 @@ import com.intellij.psi.util.CachedValuesManager
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.apache.log4j.Logger
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.apache.log4j.Logger
|
||||
import org.jetbrains.eval4j.Value
|
||||
import org.jetbrains.eval4j.jdi.asValue
|
||||
import org.jetbrains.kotlin.analyzer.AnalysisResult
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
||||
import org.jetbrains.kotlin.idea.debugger.BinaryCacheKey
|
||||
import org.jetbrains.kotlin.idea.debugger.BytecodeDebugInfo
|
||||
import org.jetbrains.kotlin.idea.debugger.createWeakBytecodeDebugInfoStorage
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CompiledDataDescriptor
|
||||
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class KotlinDebuggerCaches(project: Project) {
|
||||
|
||||
private val cachedCompiledData = CachedValuesManager.getManager(project).createCachedValue(
|
||||
{
|
||||
CachedValueProvider.Result<MultiMap<String, CompiledDataDescriptor>>(
|
||||
@@ -98,37 +93,39 @@ class KotlinDebuggerCaches(project: Project) {
|
||||
|
||||
fun getInstance(project: Project) = ServiceManager.getService(project, KotlinDebuggerCaches::class.java)!!
|
||||
|
||||
fun getOrCreateCompiledData(
|
||||
fun compileCodeFragmentCacheAware(
|
||||
codeFragment: KtCodeFragment,
|
||||
sourcePosition: SourcePosition,
|
||||
evaluationContext: EvaluationContextImpl,
|
||||
create: (KtCodeFragment, SourcePosition) -> CompiledDataDescriptor
|
||||
): CompiledDataDescriptor {
|
||||
compileCode: () -> CompiledDataDescriptor,
|
||||
force: Boolean = false
|
||||
): Pair<CompiledDataDescriptor, Boolean> {
|
||||
val evaluateExpressionCache = getInstance(codeFragment.project)
|
||||
|
||||
val text = "${codeFragment.importsToString()}\n${codeFragment.text}"
|
||||
|
||||
val cached = synchronized<Collection<CompiledDataDescriptor>>(evaluateExpressionCache.cachedCompiledData) {
|
||||
val cache = evaluateExpressionCache.cachedCompiledData.value!!
|
||||
|
||||
cache[text]
|
||||
val cachedResults = synchronized<Collection<CompiledDataDescriptor>>(evaluateExpressionCache.cachedCompiledData) {
|
||||
evaluateExpressionCache.cachedCompiledData.value[text]
|
||||
}
|
||||
|
||||
val answer = cached.firstOrNull {
|
||||
it.sourcePosition == sourcePosition || evaluateExpressionCache.canBeEvaluatedInThisContext(it, evaluationContext)
|
||||
}
|
||||
if (answer != null) {
|
||||
return answer
|
||||
val existingResult = cachedResults.firstOrNull { it.sourcePosition == sourcePosition }
|
||||
if (existingResult != null) {
|
||||
if (force) {
|
||||
synchronized(evaluateExpressionCache.cachedCompiledData) {
|
||||
evaluateExpressionCache.cachedCompiledData.value.remove(text, existingResult)
|
||||
}
|
||||
} else {
|
||||
return Pair(existingResult, true)
|
||||
}
|
||||
}
|
||||
|
||||
val newCompiledData = create(codeFragment, sourcePosition)
|
||||
val newCompiledData = compileCode()
|
||||
LOG.debug("Compile bytecode for ${codeFragment.text}")
|
||||
|
||||
synchronized(evaluateExpressionCache.cachedCompiledData) {
|
||||
evaluateExpressionCache.cachedCompiledData.value.putValue(text, newCompiledData)
|
||||
}
|
||||
|
||||
return newCompiledData
|
||||
return Pair(newCompiledData, false)
|
||||
}
|
||||
|
||||
fun <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)
|
||||
|
||||
class ComputedClassNames(val classNames: List<String>, val shouldBeCached: Boolean) {
|
||||
|
||||
+229
-560
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.SuspendContext
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||
@@ -27,21 +28,17 @@ import com.intellij.openapi.diagnostic.Attachment
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.vfs.CharsetToolkit
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFileFactory
|
||||
import com.intellij.psi.impl.PsiFileFactoryImpl
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.testFramework.LightVirtualFile
|
||||
import com.intellij.testFramework.runInEdtAndWait
|
||||
import com.intellij.util.ExceptionUtil
|
||||
import com.sun.jdi.*
|
||||
import com.sun.jdi.Value
|
||||
import com.sun.jdi.request.EventRequest
|
||||
import org.jetbrains.eval4j.*
|
||||
import org.jetbrains.eval4j.Value
|
||||
import org.jetbrains.eval4j.Value as Eval4JValue
|
||||
import org.jetbrains.eval4j.jdi.JDIEval
|
||||
import org.jetbrains.eval4j.jdi.asJdiValue
|
||||
import org.jetbrains.eval4j.jdi.asValue
|
||||
@@ -50,57 +47,39 @@ import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
|
||||
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
|
||||
import org.jetbrains.kotlin.codegen.*
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil.LABELED_THIS_PARAMETER
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil.RECEIVER_PARAMETER_NAME
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
|
||||
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.quoteSegmentsIfNeeded
|
||||
import org.jetbrains.kotlin.idea.debugger.DebuggerUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companion.compileCodeFragmentCacheAware
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely
|
||||
import org.jetbrains.kotlin.idea.debugger.getBackingFieldName
|
||||
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionResult
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.EvaluatorValueConverter
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder
|
||||
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.idea.util.attachment.attachmentByPsiFile
|
||||
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.codeFragmentUtil.debugTypeInfo
|
||||
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
|
||||
import org.jetbrains.kotlin.resolve.AnalyzingUtils
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.BindingTrace
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||
import org.jetbrains.kotlin.resolve.isInlineClassType
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes.API_VERSION
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
import java.util.*
|
||||
|
||||
internal val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluator")
|
||||
internal const val GENERATED_FUNCTION_NAME = "generated_for_debugger_fun"
|
||||
internal const val GENERATED_CLASS_NAME = "Generated_for_debugger_class"
|
||||
|
||||
private const val DEBUG_MODE = false
|
||||
|
||||
object KotlinEvaluationBuilder : EvaluatorBuilder {
|
||||
override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator {
|
||||
if (codeFragment !is KtCodeFragment || position == null) {
|
||||
@@ -108,14 +87,14 @@ object KotlinEvaluationBuilder : EvaluatorBuilder {
|
||||
}
|
||||
|
||||
if (position.line < 0) {
|
||||
throw EvaluateExceptionUtil.createEvaluateException("Couldn't evaluate kotlin expression at $position")
|
||||
evaluationException("Couldn't evaluate kotlin expression at $position")
|
||||
}
|
||||
|
||||
val file = position.file
|
||||
if (file is KtFile) {
|
||||
val document = PsiDocumentManager.getInstance(file.project).getDocument(file)
|
||||
if (document == null || document.lineCount < position.line) {
|
||||
throw EvaluateExceptionUtil.createEvaluateException(
|
||||
evaluationException(
|
||||
"Couldn't evaluate kotlin expression: breakpoint is placed outside the file. " +
|
||||
"It may happen when you've changed source file after starting a debug process."
|
||||
)
|
||||
@@ -133,7 +112,7 @@ object KotlinEvaluationBuilder : EvaluatorBuilder {
|
||||
"Trying to evaluate ${codeFragment::class.java} with context ${codeFragment.context?.javaClass}",
|
||||
mergeAttachments(*attachments)
|
||||
)
|
||||
throw EvaluateExceptionUtil.createEvaluateException("Couldn't evaluate kotlin expression in this context")
|
||||
evaluationException("Couldn't evaluate kotlin expression in this context")
|
||||
}
|
||||
|
||||
return ExpressionEvaluatorImpl(KotlinEvaluator(codeFragment, position))
|
||||
@@ -147,44 +126,21 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
}
|
||||
|
||||
if (DumbService.getInstance(codeFragment.project).isDumb) {
|
||||
throw EvaluateExceptionUtil.createEvaluateException("Code fragment evaluation is not available in the dumb mode")
|
||||
evaluationException("Code fragment evaluation is not available in the dumb mode")
|
||||
}
|
||||
|
||||
var isCompiledDataFromCache = true
|
||||
try {
|
||||
val compiledData = KotlinDebuggerCaches.getOrCreateCompiledData(codeFragment, sourcePosition, context) { fragment, position ->
|
||||
isCompiledDataFromCache = false
|
||||
extractAndCompile(fragment, position, context)
|
||||
}
|
||||
|
||||
val classLoaderRef = loadClassesSafely(context, compiledData.classes)
|
||||
|
||||
val result = if (classLoaderRef != null) {
|
||||
evaluateWithCompilation(context, compiledData, classLoaderRef) ?: runEval4j(context, compiledData, classLoaderRef)
|
||||
} else {
|
||||
runEval4j(context, compiledData, classLoaderRef)
|
||||
}
|
||||
|
||||
// If bytecode was taken from cache and exception was thrown - recompile bytecode and run eval4j again
|
||||
if (isCompiledDataFromCache && result is ExceptionThrown && result.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE) {
|
||||
return runEval4j(context, extractAndCompile(codeFragment, sourcePosition, context), classLoaderRef).toJdiValue(context)
|
||||
}
|
||||
|
||||
return if (result is InterpreterResult) {
|
||||
result.toJdiValue(context)
|
||||
} else {
|
||||
result
|
||||
}
|
||||
return evaluateSafe(context)
|
||||
} catch (e: EvaluateException) {
|
||||
throw e
|
||||
} catch (e: ProcessCanceledException) {
|
||||
exception(e)
|
||||
evaluationException(e)
|
||||
} catch (e: Eval4JInterpretingException) {
|
||||
exception(e.cause)
|
||||
evaluationException(e.cause)
|
||||
} catch (e: Exception) {
|
||||
val isSpecialException = isSpecialException(e)
|
||||
if (isSpecialException) {
|
||||
exception(e)
|
||||
evaluationException(e)
|
||||
}
|
||||
|
||||
val text = runReadAction { codeFragment.context?.text ?: "null" }
|
||||
@@ -196,6 +152,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
)
|
||||
|
||||
LOG.error(
|
||||
@Suppress("DEPRECATION")
|
||||
LogMessageEx.createEvent(
|
||||
"Couldn't evaluate expression",
|
||||
ExceptionUtil.getThrowableText(e),
|
||||
@@ -204,570 +161,254 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
|
||||
)
|
||||
|
||||
val cause = if (e.message != null) ": ${e.message}" else ""
|
||||
exception("An exception occurs during Evaluate Expression Action $cause")
|
||||
evaluationException("An exception occurs during Evaluate Expression Action $cause")
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSpecialException(th: Throwable): Boolean {
|
||||
return when (th) {
|
||||
is ClassNotPreparedException,
|
||||
is InternalException,
|
||||
is AbsentInformationException,
|
||||
is ClassNotLoadedException,
|
||||
is IncompatibleThreadStateException,
|
||||
is InconsistentDebugInfoException,
|
||||
is ObjectCollectedException,
|
||||
is VMDisconnectedException -> true
|
||||
else -> false
|
||||
private fun evaluateSafe(context: EvaluationContextImpl): Any? {
|
||||
fun compilerFactory(): CompiledDataDescriptor = compileCodeFragment(context.debugProcess)
|
||||
|
||||
val (compiledData, isCompiledDataFromCache) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory)
|
||||
val classLoaderRef = loadClassesSafely(context, compiledData.classes)
|
||||
|
||||
val thread = context.suspendContext.thread?.threadReference ?: error("Can not find a thread to run evaluation on")
|
||||
val invokePolicy = context.suspendContext.getInvokePolicy()
|
||||
val executionContext = ExecutionContext(context, thread, invokePolicy)
|
||||
|
||||
val result = if (classLoaderRef != null) {
|
||||
evaluateWithCompilation(executionContext, compiledData, classLoaderRef)
|
||||
?: evaluateWithEval4J(executionContext, compiledData, classLoaderRef)
|
||||
} else {
|
||||
evaluateWithEval4J(executionContext, compiledData, classLoaderRef)
|
||||
}
|
||||
|
||||
// If bytecode was taken from cache and exception was thrown - recompile bytecode and run eval4j again
|
||||
if (isCompiledDataFromCache && result is ExceptionThrown && result.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE) {
|
||||
val (recompiledData, _) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory, force = true)
|
||||
return evaluateWithEval4J(executionContext, recompiledData, classLoaderRef).toJdiValue(executionContext)
|
||||
}
|
||||
|
||||
return when (result) {
|
||||
is InterpreterResult -> result.toJdiValue(executionContext)
|
||||
else -> result
|
||||
}
|
||||
}
|
||||
|
||||
override fun getModifier(): Modifier? {
|
||||
return null
|
||||
}
|
||||
private fun compileCodeFragment(debugProcess: DebugProcessImpl): CompiledDataDescriptor {
|
||||
var analysisResult = checkForErrors(codeFragment, debugProcess)
|
||||
|
||||
companion object {
|
||||
private fun extractAndCompile(
|
||||
codeFragment: KtCodeFragment,
|
||||
sourcePosition: SourcePosition,
|
||||
context: EvaluationContextImpl
|
||||
): CompiledDataDescriptor {
|
||||
var bindingContext = codeFragment.checkForErrors().bindingContext
|
||||
if (codeFragment.wrapToStringIfNeeded(analysisResult.bindingContext)) {
|
||||
// Repeat analysis with toString() added
|
||||
analysisResult = checkForErrors(codeFragment, debugProcess)
|
||||
}
|
||||
|
||||
if (codeFragment.wrapToStringIfNeeded(bindingContext)) {
|
||||
// Repeat analysis with toString() added
|
||||
bindingContext = codeFragment.checkForErrors().bindingContext
|
||||
}
|
||||
|
||||
val variablesCrossingInlineBounds = ScopeCheckerForEvaluator.checkScopes(bindingContext, codeFragment)
|
||||
|
||||
val extractionResult = getFunctionForExtractedFragment(codeFragment, sourcePosition.file, sourcePosition.line)
|
||||
?: throw IllegalStateException("Code fragment cannot be extracted to function: ${codeFragment.text}")
|
||||
val (parametersDescriptor, extractedFunction) = try {
|
||||
extractionResult.getParametersForDebugger(codeFragment, context) to extractionResult.declaration as KtNamedFunction
|
||||
} finally {
|
||||
Disposer.dispose(extractionResult)
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Extracted function:\n" + runReadAction { extractedFunction.text })
|
||||
}
|
||||
|
||||
val classFileFactory = createClassFileFactory(codeFragment, extractedFunction, context, parametersDescriptor)
|
||||
|
||||
val outputFiles = classFileFactory.asList().filterClassFiles()
|
||||
|
||||
for (file in outputFiles) {
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Output file generated: ${file.relativePath}")
|
||||
}
|
||||
@Suppress("ConstantConditionIf")
|
||||
if (DEBUG_MODE) {
|
||||
println(file.asText())
|
||||
}
|
||||
}
|
||||
|
||||
val additionalFiles = outputFiles.map { ClassToLoad(getClassName(it.relativePath), it.relativePath, it.asByteArray()) }
|
||||
|
||||
return CompiledDataDescriptor(
|
||||
additionalFiles,
|
||||
sourcePosition,
|
||||
parametersDescriptor,
|
||||
variablesCrossingInlineBounds
|
||||
val (bindingContext) = runReadAction {
|
||||
DebuggerUtils.analyzeInlinedFunctions(
|
||||
KotlinCacheService.getInstance(codeFragment.project).getResolutionFacade(listOf(codeFragment)),
|
||||
codeFragment, false, analysisResult.bindingContext
|
||||
)
|
||||
}
|
||||
|
||||
private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean {
|
||||
if (this !is KtExpressionCodeFragment) {
|
||||
return false
|
||||
}
|
||||
val moduleDescriptor = analysisResult.moduleDescriptor
|
||||
|
||||
val contentElement = runReadAction { getContentElement() }
|
||||
val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, contentElement]?.type
|
||||
if (contentElement != null && expressionType?.isInlineClassType() == true) {
|
||||
val newExpression = runReadAction {
|
||||
val expressionText = contentElement.text
|
||||
KtPsiFactory(project).createExpression("($expressionText).toString()")
|
||||
}
|
||||
runInEdtAndWait {
|
||||
project.executeWriteCommand("Wrap with 'toString()'") {
|
||||
contentElement.replace(newExpression)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
val result = CodeFragmentCompiler.compile(codeFragment, bindingContext, moduleDescriptor)
|
||||
return CompiledDataDescriptor.from(result, sourcePosition)
|
||||
}
|
||||
|
||||
private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean {
|
||||
if (this !is KtExpressionCodeFragment) {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getClassName(fileName: String): String {
|
||||
return fileName.substringBeforeLast(".class").replace("/", ".")
|
||||
val contentElement = runReadAction { getContentElement() }
|
||||
val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, contentElement]?.type
|
||||
if (contentElement != null && expressionType?.isInlineClassType() == true) {
|
||||
val newExpression = runReadAction {
|
||||
val expressionText = contentElement.text
|
||||
KtPsiFactory(project).createExpression("($expressionText).toString()")
|
||||
}
|
||||
runInEdtAndWait {
|
||||
project.executeWriteCommand("Wrap with 'toString()'") {
|
||||
contentElement.replace(newExpression)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private val CompiledDataDescriptor.mainClass
|
||||
get() = classes.firstOrNull { it.isMainClass() } ?: error(
|
||||
"Can't find main class for " + sourcePosition.elementAt.getParentOfType<KtDeclaration>(strict = false)
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
private fun evaluateWithCompilation(
|
||||
context: EvaluationContextImpl,
|
||||
compiledData: CompiledDataDescriptor,
|
||||
classLoader: ClassLoaderReference
|
||||
): Any? {
|
||||
val vm = context.debugProcess.virtualMachineProxy.virtualMachine
|
||||
val mainClassBytecode = compiledData.mainClass.bytes
|
||||
private data class ErrorCheckingResult(
|
||||
val bindingContext: BindingContext,
|
||||
val moduleDescriptor: ModuleDescriptor,
|
||||
val files: List<KtFile>
|
||||
)
|
||||
|
||||
private fun checkForErrors(codeFragment: KtCodeFragment, debugProcess: DebugProcessImpl): ErrorCheckingResult {
|
||||
return runInReadActionWithWriteActionPriorityWithPCE {
|
||||
try {
|
||||
val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, ClassReader.SKIP_CODE) }
|
||||
assert(mainClassAsmNode.methods.size == 1)
|
||||
|
||||
val methodToInvoke = mainClassAsmNode.methods[0]
|
||||
assert(methodToInvoke.parameters == null || methodToInvoke.parameters.isEmpty())
|
||||
|
||||
val thread = context.suspendContext.thread?.threadReference!!
|
||||
val invokePolicy = context.suspendContext.getInvokePolicy()
|
||||
val eval = JDIEval(vm, classLoader, thread, invokePolicy)
|
||||
|
||||
val mainClassValue = (eval.loadClass(Type.getObjectType(mainClassAsmNode.name), classLoader) as? ObjectValue)
|
||||
val mainClass = (mainClassValue?.value as? ClassObjectReference)?.reflectedType() as? ClassType ?: return null
|
||||
|
||||
// Preload all classes
|
||||
compiledData.classes.asSequence()
|
||||
.filter { !it.isMainClass() }
|
||||
.forEach { eval.loadClass(Type.getObjectType(it.className), classLoader) }
|
||||
|
||||
return vm.executeWithBreakpointsDisabled {
|
||||
// Prepare the main class
|
||||
|
||||
val argumentTypes = Type.getArgumentTypes(methodToInvoke.desc)
|
||||
val args = context.getArgumentsForEvaluation(compiledData.parameters, argumentTypes, compiledData)
|
||||
.zip(argumentTypes)
|
||||
.map { (value, type) ->
|
||||
// Make argument type classes prepared for sure
|
||||
eval.loadClassByName(type.className, classLoader)
|
||||
boxOrUnboxArgumentIfNeeded(eval, value, type).asJdiValue(vm, type)
|
||||
}
|
||||
|
||||
|
||||
mainClass.invokeMethod(thread, mainClass.methods().single(), args, invokePolicy)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
LOG.error("Unable to evaluate expression with compilation", e)
|
||||
return null
|
||||
AnalyzingUtils.checkForSyntacticErrors(codeFragment)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
evaluationException(e.message ?: e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun runEval4j(
|
||||
context: EvaluationContextImpl,
|
||||
compiledData: CompiledDataDescriptor,
|
||||
classLoader: ClassLoaderReference?
|
||||
): InterpreterResult {
|
||||
val virtualMachine = context.debugProcess.virtualMachineProxy.virtualMachine
|
||||
var resultValue: InterpreterResult? = null
|
||||
val filesToAnalyze = listOf(codeFragment)
|
||||
val resolutionFacade = KotlinCacheService.getInstance(codeFragment.project).getResolutionFacade(filesToAnalyze)
|
||||
|
||||
// assert [0] with some context
|
||||
val mainClassBytecode = compiledData.mainClass.bytes
|
||||
DebugLabelPropertyDescriptorProvider(codeFragment, resolutionFacade.moduleDescriptor, debugProcess).supplyDebugLabels()
|
||||
|
||||
ClassReader(mainClassBytecode).accept(object : ClassVisitor(API_VERSION) {
|
||||
override fun visitMethod(
|
||||
access: Int,
|
||||
name: String,
|
||||
desc: String,
|
||||
signature: String?,
|
||||
exceptions: Array<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)
|
||||
val analysisResult = resolutionFacade.analyzeWithAllCompilerChecks(filesToAnalyze)
|
||||
|
||||
return object : MethodNode(Opcodes.API_VERSION, access, name, desc, signature, exceptions) {
|
||||
override fun visitEnd() {
|
||||
virtualMachine.executeWithBreakpointsDisabled {
|
||||
val eval = JDIEval(
|
||||
virtualMachine,
|
||||
classLoader ?: context.classLoader,
|
||||
context.suspendContext.thread?.threadReference!!,
|
||||
context.suspendContext.getInvokePolicy()
|
||||
)
|
||||
|
||||
resultValue = interpreterLoop(
|
||||
this,
|
||||
makeInitialFrame(
|
||||
this,
|
||||
args.zip(argumentTypes).map {
|
||||
boxOrUnboxArgumentIfNeeded(
|
||||
eval,
|
||||
it.first,
|
||||
it.second
|
||||
)
|
||||
}),
|
||||
eval
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.visitMethod(access, name, desc, signature, exceptions)
|
||||
}
|
||||
}, 0)
|
||||
|
||||
return resultValue ?: throw IllegalStateException("resultValue is null: cannot find method $GENERATED_FUNCTION_NAME")
|
||||
}
|
||||
|
||||
private inline fun <T> VirtualMachine.executeWithBreakpointsDisabled(block: () -> T): T {
|
||||
val allRequests = eventRequestManager().breakpointRequests() + eventRequestManager().classPrepareRequests()
|
||||
|
||||
try {
|
||||
allRequests.forEach { it.disable() }
|
||||
return block()
|
||||
} finally {
|
||||
allRequests.forEach { it.enable() }
|
||||
if (analysisResult.isError()) {
|
||||
evaluationException(analysisResult.error)
|
||||
}
|
||||
|
||||
val bindingContext = analysisResult.bindingContext
|
||||
|
||||
bindingContext.diagnostics
|
||||
.filter { it.factory !in IGNORED_DIAGNOSTICS }
|
||||
.firstOrNull { it.severity == Severity.ERROR && it.psiElement.containingFile == codeFragment }
|
||||
?.let { evaluationException(DefaultErrorMessages.render(it)) }
|
||||
|
||||
ErrorCheckingResult(bindingContext, analysisResult.moduleDescriptor, Collections.singletonList(codeFragment))
|
||||
}
|
||||
}
|
||||
|
||||
private fun boxOrUnboxArgumentIfNeeded(eval: JDIEval, argumentValue: Value, parameterType: Type): Value {
|
||||
val argumentType = argumentValue.asmType
|
||||
private fun evaluateWithCompilation(
|
||||
context: ExecutionContext,
|
||||
compiledData: CompiledDataDescriptor,
|
||||
classLoader: ClassLoaderReference
|
||||
): Value? {
|
||||
return try {
|
||||
runEvaluation(context, compiledData, classLoader) { args ->
|
||||
val mainClassType = context.loadClassType(Type.getObjectType(GENERATED_CLASS_NAME), classLoader) as? ClassType
|
||||
?: error("Can not find class \"$GENERATED_CLASS_NAME\"")
|
||||
val mainMethod = mainClassType.methods().single { it.name() == GENERATED_FUNCTION_NAME }
|
||||
val returnValue = mainClassType.invokeMethod(context.thread, mainMethod, args, context.invokePolicy)
|
||||
EvaluatorValueConverter(context).unref(returnValue)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
LOG.error("Unable to evaluate expression with compilation", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
if (AsmUtil.isPrimitive(parameterType) && !AsmUtil.isPrimitive(argumentType)) {
|
||||
try {
|
||||
val unboxedType = AsmUtil.unboxType(argumentType)
|
||||
if (parameterType == unboxedType) {
|
||||
return eval.unboxType(argumentValue, parameterType)
|
||||
}
|
||||
} catch (ignored: UnsupportedOperationException) {
|
||||
private fun evaluateWithEval4J(
|
||||
context: ExecutionContext,
|
||||
compiledData: CompiledDataDescriptor,
|
||||
classLoader: ClassLoaderReference?
|
||||
): InterpreterResult {
|
||||
val mainClassBytecode = compiledData.mainClass.bytes
|
||||
val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, 0) }
|
||||
val mainMethod = mainClassAsmNode.methods.first { it.name == GENERATED_FUNCTION_NAME }
|
||||
|
||||
return runEvaluation(context, compiledData, classLoader ?: context.evaluationContext.classLoader) { args ->
|
||||
val eval = JDIEval(context.vm, classLoader, context.thread, context.invokePolicy)
|
||||
interpreterLoop(mainMethod, makeInitialFrame(mainMethod, args.map { it.asValue() }), eval)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <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)) {
|
||||
if (parameterType.descriptor == "Ljava/lang/Object;" || parameterType == AsmUtil.boxType(argumentType)) {
|
||||
return eval.boxType(argumentValue)
|
||||
}
|
||||
}
|
||||
|
||||
return argumentValue
|
||||
result.value
|
||||
}
|
||||
}
|
||||
|
||||
private fun InterpreterResult.toJdiValue(context: EvaluationContextImpl): com.sun.jdi.Value? {
|
||||
override fun getModifier() = null
|
||||
|
||||
companion object {
|
||||
private val IGNORED_DIAGNOSTICS: Set<DiagnosticFactory<*>> = Errors.INVISIBLE_REFERENCE_DIAGNOSTICS
|
||||
|
||||
private fun InterpreterResult.toJdiValue(context: ExecutionContext): com.sun.jdi.Value? {
|
||||
val jdiValue = when (this) {
|
||||
is ValueReturned -> result
|
||||
is ExceptionThrown -> {
|
||||
when {
|
||||
this.kind == ExceptionThrown.ExceptionKind.FROM_EVALUATED_CODE ->
|
||||
exception(InvocationException(this.exception.value as ObjectReference))
|
||||
evaluationException(InvocationException(this.exception.value as ObjectReference))
|
||||
this.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE ->
|
||||
throw exception.value as Throwable
|
||||
else ->
|
||||
exception(exception.toString())
|
||||
evaluationException(exception.toString())
|
||||
}
|
||||
}
|
||||
is AbnormalTermination -> exception(message)
|
||||
is AbnormalTermination -> evaluationException(message)
|
||||
else -> throw IllegalStateException("Unknown result value produced by eval4j")
|
||||
}
|
||||
|
||||
val vm = context.debugProcess.virtualMachineProxy.virtualMachine
|
||||
|
||||
val sharedVar = getValueIfSharedVar(jdiValue)
|
||||
return sharedVar?.value ?: jdiValue.asJdiValue(vm, jdiValue.asmType)
|
||||
val sharedVar = if ((jdiValue is AbstractValue<*>)) getValueIfSharedVar(jdiValue, context) else null
|
||||
return sharedVar?.value ?: jdiValue.asJdiValue(context.vm, jdiValue.asmType)
|
||||
}
|
||||
|
||||
private fun getValueIfSharedVar(value: Value): VariableFinder.Result? {
|
||||
private fun getValueIfSharedVar(value: Eval4JValue, context: ExecutionContext): VariableFinder.Result? {
|
||||
val obj = value.obj(value.asmType) as? ObjectReference ?: return null
|
||||
return VariableFinder.Result(VariableFinder.unwrapRefValue(obj))
|
||||
return VariableFinder.Result(EvaluatorValueConverter(context).unref(obj))
|
||||
}
|
||||
|
||||
private fun ExtractionResult.getParametersForDebugger(
|
||||
fragment: KtCodeFragment,
|
||||
context: EvaluationContextImpl
|
||||
): List<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 {
|
||||
return if (suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD) ObjectReference.INVOKE_SINGLE_THREADED else 0
|
||||
}
|
||||
|
||||
fun Type.getClassDescriptor(scope: GlobalSearchScope): ClassDescriptor? {
|
||||
fun Type.getClassDescriptor(
|
||||
scope: GlobalSearchScope,
|
||||
mapBuiltIns: Boolean = true,
|
||||
moduleDescriptor: ModuleDescriptor = DefaultBuiltIns.Instance.builtInsModule
|
||||
): ClassDescriptor? {
|
||||
if (AsmUtil.isPrimitive(this)) return null
|
||||
|
||||
val jvmName = JvmClassName.byInternalName(internalName).fqNameForClassNameWithoutDollars
|
||||
|
||||
// TODO: use the correct built-ins from the module instead of DefaultBuiltIns here
|
||||
JavaToKotlinClassMap.mapJavaToKotlin(jvmName)?.let(
|
||||
DefaultBuiltIns.Instance.builtInsModule::findClassAcrossModuleDependencies
|
||||
)?.let { return it }
|
||||
if (mapBuiltIns) {
|
||||
val mappedName = JavaToKotlinClassMap.mapJavaToKotlin(jvmName)
|
||||
if (mappedName != null) {
|
||||
moduleDescriptor.findClassAcrossModuleDependencies(mappedName)?.let { return it }
|
||||
}
|
||||
}
|
||||
|
||||
return runReadAction {
|
||||
val classes = JavaPsiFacade.getInstance(scope.project).findClasses(jvmName.asString(), scope)
|
||||
@@ -777,3 +418,31 @@ fun Type.getClassDescriptor(scope: GlobalSearchScope): ClassDescriptor? {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <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.ClassLoaderReference
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.GENERATED_FUNCTION_NAME
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.Label
|
||||
import org.jetbrains.org.objectweb.asm.tree.*
|
||||
@@ -38,7 +39,7 @@ interface ClassLoadingAdapter {
|
||||
)
|
||||
|
||||
fun loadClasses(context: EvaluationContextImpl, classes: Collection<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)
|
||||
if (!info.containsAdditionalClasses) {
|
||||
@@ -64,8 +65,8 @@ interface ClassLoadingAdapter {
|
||||
}
|
||||
|
||||
private fun analyzeClass(classToLoad: ClassToLoad, info: ClassInfoForEvaluator): ClassInfoForEvaluator {
|
||||
val classNode = ClassNode().apply { ClassReader(classToLoad.bytes).accept(this, ClassReader.EXPAND_FRAMES) }
|
||||
val methodToRun = classNode.methods.single()
|
||||
val classNode = ClassNode().apply { ClassReader(classToLoad.bytes).accept(this, 0) }
|
||||
val methodToRun = classNode.methods.single { it.name == GENERATED_FUNCTION_NAME }
|
||||
|
||||
val visitedLabels = hashSetOf<Label>()
|
||||
|
||||
|
||||
+2
-1
@@ -20,5 +20,6 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.GENERATED_CLASS_NAME
|
||||
|
||||
@Suppress("ArrayInDataClass")
|
||||
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.
|
||||
*/
|
||||
|
||||
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.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.inline.INLINE_FUN_VAR_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.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.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_FUNCTION
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
|
||||
import kotlin.coroutines.Continuation
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
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 {
|
||||
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 SUSPEND_LAMBDA_CLASSES = listOf(
|
||||
@@ -42,18 +44,14 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
||||
"kotlin.coroutines.jvm.internal.RestrictedSuspendLambda"
|
||||
)
|
||||
|
||||
fun instance(context: EvaluationContextImpl): VariableFinder? {
|
||||
val frameProxy = context.frameProxy ?: return null
|
||||
fun instance(context: ExecutionContext): VariableFinder? {
|
||||
val frameProxy = context.evaluationContext.frameProxy ?: return null
|
||||
return VariableFinder(context, frameProxy)
|
||||
}
|
||||
|
||||
fun unwrapRefValue(value: ObjectReference): Value? {
|
||||
return NamedEntity("<nameForUnwrapOnly>", value.type()) { value }.unwrap().value()
|
||||
}
|
||||
|
||||
fun variableNotFound(context: EvaluationContextImpl, message: String): Exception {
|
||||
val frameProxy = context.frameProxy
|
||||
val location = frameProxy?.location()
|
||||
val location = frameProxy?.safeLocation()
|
||||
val scope = context.debugProcess.searchScope
|
||||
|
||||
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)*$")
|
||||
}
|
||||
|
||||
private fun AsmType.isFunctionType(): Boolean {
|
||||
return sort == AsmType.OBJECT && internalName.startsWith(NUMBERED_FUNCTION_PREFIX)
|
||||
}
|
||||
|
||||
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()) {
|
||||
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
|
||||
|
||||
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))
|
||||
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) =
|
||||
(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) {
|
||||
private val capturedNameRegex = getCapturedVariableNameRegex(
|
||||
getCapturedFieldName(getLabeledThisName(label, AsmUtil.LABELED_THIS_FIELD, AsmUtil.CAPTURED_RECEIVER_FIELD))
|
||||
)
|
||||
class OuterClassThis(asmType: AsmType) : VariableKind(asmType) {
|
||||
override fun capturedNameMatches(name: String) = false
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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?)
|
||||
@@ -180,36 +173,25 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
||||
private class NamedEntity(val name: String, val type: JdiType?, val value: () -> Value?) {
|
||||
companion object {
|
||||
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 {
|
||||
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? {
|
||||
return when {
|
||||
name.startsWith(AsmUtil.THIS + "@") -> {
|
||||
val label = name.drop(AsmUtil.THIS.length + 1).also { require(it.isNotEmpty()) { "Invalid name '$name'" } }
|
||||
findLabeledThis(VariableKind.LabeledThis(label, type))
|
||||
}
|
||||
name == AsmUtil.THIS -> findUnlabeledThis(VariableKind.UnlabeledThis(type))
|
||||
else -> findOrdinary(VariableKind.Ordinary(name, type))
|
||||
fun find(parameter: CodeFragmentParameter, asmType: AsmType): Result? {
|
||||
return when (parameter.kind) {
|
||||
Kind.ORDINARY -> findOrdinary(VariableKind.Ordinary(parameter.name, asmType))
|
||||
Kind.FAKE_JAVA_OUTER_CLASS -> frameProxy.thisObject()?.let { Result(it) }
|
||||
Kind.EXTENSION_RECEIVER -> findExtensionThis(VariableKind.ExtensionThis(parameter.name, asmType))
|
||||
Kind.LOCAL_FUNCTION -> findLocalFunction(VariableKind.LocalFunction(parameter.name, asmType))
|
||||
Kind.DISPATCH_RECEIVER -> findDispatchThis(VariableKind.OuterClassThis(asmType))
|
||||
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)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
// 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
|
||||
findCapturedVariableInReceiver(variables, kind)?.let { return it }
|
||||
@@ -242,8 +248,56 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
||||
findCapturedVariable(kind, containingThis)?.let { return it }
|
||||
}
|
||||
|
||||
// Fallback: find an unlabeled this with the compatible type
|
||||
return findUnlabeledThis(VariableKind.UnlabeledThis(kind.type))
|
||||
@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 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? {
|
||||
@@ -257,47 +311,35 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
||||
}
|
||||
|
||||
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()
|
||||
.filter { it.name == name && kind.typeMatches(it.type) }
|
||||
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||
.firstOrNull()
|
||||
?.let { return Result(it.value()) }
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
?.let { return it }
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isLocalFunctionName(name: String, functionName: String): Boolean {
|
||||
@Suppress("ConvertToStringTemplate")
|
||||
return name == functionName + "$" || name.startsWith(AsmUtil.LOCAL_FUNCTION_VARIABLE_PREFIX + name + "$")
|
||||
private fun isInsideDefaultImpls(): Boolean {
|
||||
val declaringType = frameProxy.safeLocation()?.declaringType() ?: return false
|
||||
return declaringType.name().endsWith(JvmAbi.DEFAULT_IMPLS_SUFFIX)
|
||||
}
|
||||
|
||||
private fun findCoroutineContext(): ObjectReference? {
|
||||
val method = frameProxy.location().safeMethod() ?: return null
|
||||
return findCoroutineContextForLambda(method) ?: findCoroutineContextForMethod(method)
|
||||
private fun findCoroutineContext(): Result? {
|
||||
val method = frameProxy.safeLocation()?.safeMethod() ?: return null
|
||||
val result = findCoroutineContextForLambda(method) ?: findCoroutineContextForMethod(method) ?: return null
|
||||
return Result(result)
|
||||
}
|
||||
|
||||
private fun findCoroutineContextForLambda(method: Method): ObjectReference? {
|
||||
@@ -334,23 +376,22 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
||||
.methodsByName("getContext", "()Lkotlin/coroutines/CoroutineContext;").firstOrNull()
|
||||
?: return null
|
||||
|
||||
val threadReference = frameProxy.threadProxy().threadReference.takeIf { it.isSuspended } ?: return null
|
||||
val invokePolicy = context.suspendContext.getInvokePolicy()
|
||||
return continuation.invokeMethod(threadReference, getContextMethod, emptyList(), invokePolicy) as? ObjectReference
|
||||
return continuation.invokeMethod(context.thread, getContextMethod, emptyList(), context.invokePolicy) as? ObjectReference
|
||||
}
|
||||
|
||||
private fun findCapturedVariableInReceiver(variables: List<LocalVariableProxyImpl>, kind: VariableKind): Result? {
|
||||
fun isReceiverOrPassedThis(name: String) =
|
||||
name.startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|
||||
|| name == AsmUtil.RECEIVER_PARAMETER_NAME
|
||||
|| name == AsmUtil.getCapturedFieldName(AsmUtil.THIS)
|
||||
|| name == getCapturedFieldName(AsmUtil.THIS)
|
||||
|| inlinedThisRegex.matches(name)
|
||||
|
||||
if (kind is VariableKind.LabeledThis) {
|
||||
if (kind is VariableKind.ExtensionThis) {
|
||||
variables.namedEntitySequence()
|
||||
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
|
||||
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||
.firstOrNull()
|
||||
?.let { return Result(it.value()) }
|
||||
?.let { return it }
|
||||
}
|
||||
|
||||
return variables.namedEntitySequence()
|
||||
@@ -360,30 +401,35 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
val fields = (parent as? ObjectReference)?.referenceType()?.fields() ?: return null
|
||||
|
||||
// Captured variables - direct search
|
||||
fields.namedEntitySequence(parent)
|
||||
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
|
||||
.firstOrNull()
|
||||
?.let { return Result(it.value()) }
|
||||
if (kind !is VariableKind.OuterClassThis) {
|
||||
// Captured variables - direct search
|
||||
fields.namedEntitySequence(parent)
|
||||
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
|
||||
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
|
||||
// Recursive search in captured receivers
|
||||
fields.namedEntitySequence(parent)
|
||||
.filter { isCapturedReceiverFieldName(it.name) }
|
||||
.mapNotNull { findCapturedVariable(kind, it.value()) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
// Recursive search in captured receivers
|
||||
fields.namedEntitySequence(parent)
|
||||
.filter { isCapturedReceiverFieldName(it.name) }
|
||||
.mapNotNull { findCapturedVariable(kind, it.value()) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
}
|
||||
|
||||
// Recursive search in outer and captured this
|
||||
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()
|
||||
?.let { return findCapturedVariable(kind, it.value()) }
|
||||
?.let { return it }
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -393,6 +439,19 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|
||||
|| name == AsmUtil.CAPTURED_RECEIVER_FIELD
|
||||
}
|
||||
|
||||
private fun List<Field>.namedEntitySequence(owner: ObjectReference) = asSequence().map { NamedEntity.of(it, owner) }
|
||||
private fun List<LocalVariableProxyImpl>.namedEntitySequence() = asSequence().map { NamedEntity.of(it, frameProxy) }
|
||||
private fun VariableKind.typeMatches(actualType: JdiType?): Boolean {
|
||||
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
|
||||
|
||||
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.jdi.LocalVariableProxyImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
@@ -43,6 +45,14 @@ fun Method.safeArguments(): List<LocalVariable>? {
|
||||
return wrapAbsentInformationException { arguments() }
|
||||
}
|
||||
|
||||
fun StackFrameProxy.safeLocation(): Location? {
|
||||
return try {
|
||||
this.location()
|
||||
} catch (e: EvaluateException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun Location.safeSourceName(): String? {
|
||||
return try {
|
||||
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.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
|
||||
@@ -306,6 +307,8 @@ class KotlinBytecodeToolWindow(private val myProject: Project, private val toolW
|
||||
override fun shouldGenerateScript(script: KtScript): Boolean {
|
||||
return script.containingKtFile === ktFile
|
||||
}
|
||||
|
||||
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
|
||||
}
|
||||
|
||||
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.ScratchOutputType
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.KtScript
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
||||
import org.jetbrains.kotlin.resolve.AnalyzingUtils
|
||||
import java.io.File
|
||||
@@ -143,6 +140,7 @@ class KtCompilingExecutor(file: ScratchFile) : ScratchExecutor(file) {
|
||||
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject) = true
|
||||
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = processingClassOrObject.containingKtFile == psiFile
|
||||
override fun shouldGenerateScript(script: KtScript) = false
|
||||
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
|
||||
}
|
||||
|
||||
val state = GenerationState.Builder(
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
@Suppress("INVISIBLE_MEMBER")
|
||||
MyClass().privateFun(<error>"s"</error>)
|
||||
@@ -1 +1,2 @@
|
||||
@Suppress("INVISIBLE_MEMBER")
|
||||
"${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
|
||||
Compile bytecode for o.test()
|
||||
clearCache.kt:60
|
||||
Compile bytecode for o.test()
|
||||
clearCache.kt:78
|
||||
Compile bytecode for c.size
|
||||
clearCache.kt:86
|
||||
Compile bytecode for c.size
|
||||
clearCache.kt:95
|
||||
Compile bytecode for c.size
|
||||
clearCache.kt:105
|
||||
Compile bytecode for c.size
|
||||
clearCache.kt:113
|
||||
Compile bytecode for c.size
|
||||
clearCache.kt:123
|
||||
Compile bytecode for o.test()
|
||||
clearCache.kt:131
|
||||
Compile bytecode for o.test()
|
||||
clearCache.kt:149
|
||||
Compile bytecode for obj.test()
|
||||
clearCache.kt:154
|
||||
Compile bytecode for obj.test()
|
||||
clearCache.kt:161
|
||||
Compile bytecode for o.test()
|
||||
clearCache.kt:169
|
||||
|
||||
+4
-4
@@ -37,15 +37,15 @@ fun genericClassCast() {
|
||||
val c = ArrayList<Int>()
|
||||
c.add(1)
|
||||
// EXPRESSION: c.get(0)
|
||||
// RESULT: 1: I
|
||||
// RESULT: instance of java.lang.Integer(id=ID): Ljava/lang/Integer;
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
}
|
||||
|
||||
if (true) {
|
||||
val c = ArrayList<String>()
|
||||
c.add("a")
|
||||
// EXPRESSION: c.get(0)
|
||||
val c = ArrayList<Int>()
|
||||
(c as? ArrayList<String>)?.add("a")
|
||||
// EXPRESSION: c.get(0) + 1
|
||||
// RESULT: java.lang.ClassCastException : java.lang.String cannot be cast to java.lang.Number
|
||||
//Breakpoint!
|
||||
val b = 1
|
||||
|
||||
+3
@@ -9,12 +9,15 @@ Connected to the target VM
|
||||
exceptions.kt:9
|
||||
Compile bytecode for fail()
|
||||
exceptions.kt:14
|
||||
Compile bytecode for fail()
|
||||
exceptions.kt:26
|
||||
Compile bytecode for o as Derived
|
||||
exceptions.kt:31
|
||||
Compile bytecode for o as Derived
|
||||
exceptions.kt:42
|
||||
Compile bytecode for c.get(0)
|
||||
exceptions.kt:51
|
||||
Compile bytecode for c.get(0) + 1
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
|
||||
Vendored
+2
@@ -41,9 +41,11 @@ Compile bytecode for extClass.testCompPrivate()
|
||||
extensionMemberFunction.kt:88
|
||||
Compile bytecode for testCompPrivate()
|
||||
extensionMemberFunction.kt:98
|
||||
Compile bytecode for testCompPublic()
|
||||
extensionMemberFunction.kt:103
|
||||
Compile bytecode for this.testCompPublic()
|
||||
extensionMemberFunction.kt:108
|
||||
Compile bytecode for testCompPrivate()
|
||||
extensionMemberFunction.kt:113
|
||||
Compile bytecode for this.testCompPrivate()
|
||||
Disconnected from the target VM
|
||||
|
||||
Vendored
+2
@@ -41,9 +41,11 @@ Compile bytecode for extClass.testCompPrivate
|
||||
extensionMemberProperty.kt:92
|
||||
Compile bytecode for testCompPrivate
|
||||
extensionMemberProperty.kt:102
|
||||
Compile bytecode for testCompPublic
|
||||
extensionMemberProperty.kt:107
|
||||
Compile bytecode for this.testCompPublic
|
||||
extensionMemberProperty.kt:112
|
||||
Compile bytecode for testCompPrivate
|
||||
extensionMemberProperty.kt:117
|
||||
Compile bytecode for this.testCompPrivate
|
||||
Disconnected from the target VM
|
||||
|
||||
+1
-1
@@ -24,4 +24,4 @@ fun main() {
|
||||
// RESULT: "field": Ljava/lang/String;
|
||||
|
||||
// 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
|
||||
lambda {
|
||||
// EXPRESSION: foo() + 2
|
||||
// RESULT: java.lang.AssertionError : Cannot find local variable: name = 'this@Outer', type = funFromOuterClassInLamdba.Outer
|
||||
// RESULT: 'this@Outer' is not captured
|
||||
//Breakpoint!
|
||||
val a = 1
|
||||
}
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ fun test1(derived: Base) =
|
||||
// RESULT: 1: I
|
||||
|
||||
// EXPRESSION: nullable.prop
|
||||
// RESULT: Method threw 'kotlin.TypeCastException' exception.
|
||||
// RESULT: java.lang.NullPointerException
|
||||
fun test2(nullable: Derived?) =
|
||||
nullable != null &&
|
||||
//Breakpoint!
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@ fun main() {
|
||||
//Breakpoint!
|
||||
block(3) c@ {
|
||||
//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
|
||||
|
||||
// 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
|
||||
arrayOf(1, 2).map {
|
||||
it + 1
|
||||
//Breakpoint!
|
||||
//Breakpoint! (lambdaOrdinal = 1)
|
||||
}.forEach { it + 2 }
|
||||
}
|
||||
|
||||
// STEP_INTO: 1
|
||||
|
||||
// EXPRESSION: callable
|
||||
// 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
|
||||
Connected to the target VM
|
||||
callableBug.kt:8
|
||||
callableBug.kt:8
|
||||
Compile bytecode for callable
|
||||
Compile bytecode for it
|
||||
callableBug.kt:8
|
||||
|
||||
Vendored
+1
-1
@@ -16,4 +16,4 @@ interface T {
|
||||
// RESULT: 1: I
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
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
|
||||
prop + prop2
|
||||
|
||||
// RESULT: instance of kotlin.Triple(id=ID): Lkotlin/Triple;
|
||||
// RESULT: 6: I
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
|
||||
+8
-15
@@ -4,22 +4,15 @@ Connected to the target VM
|
||||
suspendContinuation.kt:7
|
||||
Compile bytecode for a
|
||||
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)
|
||||
field = result: java.lang.Object = null (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 = label: int = 1 (sp = null)
|
||||
field = I$0: int = 5 (sp = null)
|
||||
field = intercepted: kotlin.coroutines.Continuation = null (sp = ContinuationImpl.!EXT!)
|
||||
field = _context: kotlin.coroutines.CoroutineContext = {kotlin.coroutines.EmptyCoroutineContext@uniqueID}EmptyCoroutineContext (sp = ContinuationImpl.!EXT!)
|
||||
- No fields to display
|
||||
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 = label: int = 1 (sp = null)
|
||||
field = $completion: kotlin.coroutines.Continuation = {kotlin.coroutines.jvm.internal.RunSuspend@uniqueID} (sp = null)
|
||||
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!)
|
||||
field = $completion: kotlin.coroutines.Continuation = {kotlin.coroutines.jvm.internal.RunSuspend@uniqueID} (sp = null)
|
||||
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)
|
||||
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>) {
|
||||
val list1 = listOf("a")
|
||||
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? {
|
||||
@@ -16,4 +16,18 @@ inline fun <reified T: Bar> List<T>.foo(key: Int): T? {
|
||||
open class Foo
|
||||
open class Bar {
|
||||
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
|
||||
// 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");
|
||||
}
|
||||
|
||||
@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")
|
||||
public void testKt28087() throws Exception {
|
||||
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt28087.kt");
|
||||
|
||||
Reference in New Issue
Block a user