New evaluator that doesn't depend on the 'extract function' refactoring (KT-28192, KT-25220, KT-25222, KT-21650)

This commit is contained in:
Yan Zhulanow
2019-01-30 02:35:10 +03:00
parent 3e849294f0
commit 0657a3d399
69 changed files with 2300 additions and 1497 deletions
@@ -0,0 +1,279 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.codegen
import com.intellij.openapi.util.Key
import org.jetbrains.kotlin.codegen.CalculatedCodeFragmentCodegenInfo.CalculatedParameter
import org.jetbrains.kotlin.codegen.CodeFragmentCodegenInfo.IParameter
import org.jetbrains.kotlin.codegen.binding.MutableClosure
import org.jetbrains.kotlin.codegen.context.*
import org.jetbrains.kotlin.codegen.context.LocalLookup.*
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin.Companion.NO_ORIGIN
import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Opcodes.*
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension.Context as InCo
class CodeFragmentCodegenInfo(
val classDescriptor: ClassDescriptor,
val methodDescriptor: FunctionDescriptor,
val parameters: List<IParameter>
) {
val classType: Type = Type.getObjectType(classDescriptor.name.asString())
interface IParameter {
val targetDescriptor: DeclarationDescriptor
val targetType: KotlinType
}
}
class CodeFragmentCodegen private constructor(
private val codeFragment: KtCodeFragment,
private val info: CodeFragmentCodegenInfo,
private val calculatedInfo: CalculatedCodeFragmentCodegenInfo,
private val classContext: CodeFragmentContext,
state: GenerationState,
builder: ClassBuilder
) : MemberCodegen<KtCodeFragment>(state, null, classContext, codeFragment, builder) {
private val methodDescriptor = info.methodDescriptor
override fun generateDeclaration() {
v.defineClass(
codeFragment,
state.classFileVersion,
ACC_PUBLIC or ACC_SUPER,
info.classType.internalName,
null,
"java/lang/Object",
emptyArray()
)
}
override fun generateBody() {
genConstructor()
genMethod(classContext.intoFunction(methodDescriptor))
}
override fun generateKotlinMetadataAnnotation() {
writeSyntheticClassMetadata(v, state)
}
private fun genConstructor() {
val mv = v.newMethod(NO_ORIGIN, ACC_PUBLIC, "<init>", "()V", null, null)
if (state.classBuilderMode.generateBodies) {
mv.visitCode()
val iv = InstructionAdapter(mv)
iv.load(0, info.classType)
iv.invokespecial("java/lang/Object", "<init>", "()V", false)
iv.areturn(Type.VOID_TYPE)
}
mv.visitMaxs(-1, -1)
mv.visitEnd()
}
private fun genMethod(methodContext: MethodContext) {
val returnType = calculatedInfo.returnAsmType
val parameters = calculatedInfo.parameters
val methodDesc = Type.getMethodDescriptor(returnType, *parameters.map { it.asmType }.toTypedArray())
val mv = v.newMethod(
OtherOrigin(codeFragment, methodContext.functionDescriptor),
ACC_PUBLIC or ACC_STATIC,
methodDescriptor.name.asString(), methodDesc,
null, null
)
if (state.classBuilderMode.generateBodies) {
mv.visitCode()
val frameMap = FrameMap()
parameters.forEach { frameMap.enter(it.targetDescriptor, it.asmType) }
val codegen = object : ExpressionCodegen(mv, frameMap, returnType, methodContext, state, this) {
override fun findCapturedValue(descriptor: DeclarationDescriptor): StackValue? {
val parameter = calculatedInfo.findParameter(descriptor) ?: return super.findCapturedValue(descriptor)
return parameter.stackValue
}
override fun generateThisOrOuter(calleeContainingClass: ClassDescriptor, isSuper: Boolean): StackValue {
findCapturedValue(calleeContainingClass)?.let { return it }
return super.generateThisOrOuter(calleeContainingClass, isSuper)
}
override fun generateExtensionReceiver(descriptor: CallableDescriptor): StackValue {
val receiverParameter = descriptor.extensionReceiverParameter
if (receiverParameter != null) {
findCapturedValue(receiverParameter)?.let { return it }
}
return super.generateExtensionReceiver(descriptor)
}
override fun generateNonIntrinsicSimpleNameExpression(
expression: KtSimpleNameExpression, receiver: StackValue,
descriptor: DeclarationDescriptor, resolvedCall: ResolvedCall<*>?, isSyntheticField: Boolean
): StackValue {
val resultingDescriptor = resolvedCall?.resultingDescriptor
if (resultingDescriptor != null) {
findCapturedValue(resultingDescriptor)?.let { return it }
}
return super.generateNonIntrinsicSimpleNameExpression(expression, receiver, descriptor, resolvedCall, isSyntheticField)
}
override fun visitThisExpression(expression: KtThisExpression, receiver: StackValue?): StackValue {
val instanceReference = expression.instanceReference
val target = bindingContext[BindingContext.REFERENCE_TARGET, instanceReference]
if (target != null) {
findCapturedValue(target)?.let { return it }
}
return super.visitThisExpression(expression, receiver)
}
}
codegen.gen(codeFragment.getContentElement(), returnType)
codegen.v.areturn(returnType)
parameters.asReversed().forEach { frameMap.leave(it.targetDescriptor) }
}
mv.visitMaxs(-1, -1)
mv.visitEnd()
}
companion object {
private val INFO_USERDATA_KEY = Key.create<CodeFragmentCodegenInfo>("CODE_FRAGMENT_CODEGEN_INFO")
fun setCodeFragmentInfo(codeFragment: KtCodeFragment, info: CodeFragmentCodegenInfo) {
codeFragment.putUserData(INFO_USERDATA_KEY, info)
}
@JvmStatic
fun getCodeFragmentInfo(codeFragment: KtCodeFragment): CodeFragmentCodegenInfo {
return codeFragment.getUserData(INFO_USERDATA_KEY) ?: error("Codegen info user data is not set")
}
@JvmStatic
fun createCodegen(
declaration: KtCodeFragment,
state: GenerationState,
parentContext: CodegenContext<*>
): CodeFragmentCodegen {
val info = getCodeFragmentInfo(declaration)
val classDescriptor = info.classDescriptor
val builder = state.factory.newVisitor(OtherOrigin(declaration, classDescriptor), info.classType, declaration.containingFile)
val calculatedInfo = calculateInfo(info, state.typeMapper)
val classContext = CodeFragmentContext(state.typeMapper, classDescriptor, parentContext, calculatedInfo)
return CodeFragmentCodegen(declaration, info, calculatedInfo, classContext, state, builder)
}
private fun calculateInfo(info: CodeFragmentCodegenInfo, typeMapper: KotlinTypeMapper): CalculatedCodeFragmentCodegenInfo {
val methodSignature = typeMapper.mapSignatureSkipGeneric(info.methodDescriptor)
require(info.parameters.size == methodSignature.valueParameters.size)
require(info.parameters.size == info.methodDescriptor.valueParameters.size)
var stackIndex = 0
val parameters = mutableListOf<CalculatedParameter>()
for (parameterIndex in 0 until info.parameters.size) {
val parameter = info.parameters[parameterIndex]
val asmParameter = methodSignature.valueParameters[parameterIndex]
val parameterDescriptor = info.methodDescriptor.valueParameters[parameterIndex]
val asmType: Type
val stackValue: StackValue
val sharedAsmType = getSharedTypeIfApplicable(parameter.targetDescriptor, typeMapper)
if (sharedAsmType != null) {
asmType = sharedAsmType
val unwrappedType = typeMapper.mapType(parameter.targetType)
stackValue = StackValue.shared(stackIndex, unwrappedType)
} else {
asmType = asmParameter.asmType
stackValue = StackValue.local(stackIndex, asmType)
}
val calculatedParameter = CalculatedParameter(parameter, asmType, stackValue, parameterDescriptor)
parameters += calculatedParameter
stackIndex += if (asmType == Type.DOUBLE_TYPE || asmType == Type.LONG_TYPE) 2 else 1
}
return CalculatedCodeFragmentCodegenInfo(parameters, methodSignature.returnType)
}
fun getSharedTypeIfApplicable(descriptor: DeclarationDescriptor, typeMapper: KotlinTypeMapper): Type? {
return when (descriptor) {
is LocalVariableDescriptor -> typeMapper.getSharedVarType(descriptor)
else -> null
}
}
}
}
private class CalculatedCodeFragmentCodegenInfo(val parameters: List<CalculatedParameter>, val returnAsmType: Type) {
class CalculatedParameter(
parameter: IParameter,
val asmType: Type,
val stackValue: StackValue,
val parameterDescriptor: ValueParameterDescriptor
) : IParameter by parameter
fun findParameter(target: DeclarationDescriptor): CalculatedParameter? {
for (parameter in parameters) {
if (parameter.targetDescriptor == target || parameter.parameterDescriptor == target) {
return parameter
}
}
return null
}
}
private class CodeFragmentContext(
typeMapper: KotlinTypeMapper,
contextDescriptor: ClassDescriptor,
parentContext: CodegenContext<*>?,
private val calculatedInfo: CalculatedCodeFragmentCodegenInfo
) : ScriptLikeContext(typeMapper, contextDescriptor, parentContext) {
private val localLookup = object : LocalLookup {
override fun isLocal(descriptor: DeclarationDescriptor?): Boolean {
return calculatedInfo.parameters.any { descriptor == it.targetDescriptor || descriptor == it.parameterDescriptor }
}
}
override fun getOuterReceiverExpression(prefix: StackValue?, thisOrOuterClass: ClassDescriptor): StackValue {
val parameter = calculatedInfo.findParameter(thisOrOuterClass)
?: throw IllegalStateException("Can not generate outer receiver value for $thisOrOuterClass")
return parameter.stackValue
}
override fun captureVariable(closure: MutableClosure, target: DeclarationDescriptor): StackValue? {
val parameter = calculatedInfo.findParameter(target) ?: return null
val parameterDescriptor = parameter.parameterDescriptor
// Value is already captured
closure.captureVariables[parameterDescriptor]?.let { return it.innerValue }
// Capture new value
val closureAsmType = typeMapper.mapType(closure.closureClass)
return LocalLookupCase.VAR.innerValue(parameterDescriptor, localLookup, state, closure, closureAsmType)
}
}
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.backend.common.CodegenUtil;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.codegen.binding.CalculatedClosure; import org.jetbrains.kotlin.codegen.binding.CalculatedClosure;
import org.jetbrains.kotlin.codegen.binding.CodegenBinding; import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
import org.jetbrains.kotlin.codegen.binding.MutableClosure;
import org.jetbrains.kotlin.codegen.context.*; import org.jetbrains.kotlin.codegen.context.*;
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenForLambda; import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenForLambda;
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt; import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt;
@@ -1759,6 +1760,16 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
StackValue intrinsicResult = applyIntrinsic(descriptor, IntrinsicPropertyGetter.class, resolvedCall, receiver); StackValue intrinsicResult = applyIntrinsic(descriptor, IntrinsicPropertyGetter.class, resolvedCall, receiver);
if (intrinsicResult != null) return intrinsicResult; if (intrinsicResult != null) return intrinsicResult;
return generateNonIntrinsicSimpleNameExpression(expression, receiver, descriptor, resolvedCall, isSyntheticField);
}
public StackValue generateNonIntrinsicSimpleNameExpression(
@NotNull KtSimpleNameExpression expression,
@NotNull StackValue receiver,
@NotNull DeclarationDescriptor descriptor,
@Nullable ResolvedCall<?> resolvedCall,
boolean isSyntheticField
) {
if (descriptor instanceof PropertyDescriptor) { if (descriptor instanceof PropertyDescriptor) {
PropertyDescriptor propertyDescriptor = (PropertyDescriptor) descriptor; PropertyDescriptor propertyDescriptor = (PropertyDescriptor) descriptor;
@@ -2132,6 +2143,14 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
} }
else { else {
skipPropertyAccessors = forceField; skipPropertyAccessors = forceField;
if (JvmCodegenUtil.isDebuggerContext(context)
&& Visibilities.isPrivate(propertyDescriptor.getVisibility())
&& bindingContext.get(BACKING_FIELD_REQUIRED, propertyDescriptor) == Boolean.TRUE
) {
skipPropertyAccessors = true;
}
ownerDescriptor = isBackingFieldMovedFromCompanion ? containingDeclaration : propertyDescriptor; ownerDescriptor = isBackingFieldMovedFromCompanion ? containingDeclaration : propertyDescriptor;
} }
@@ -2772,7 +2791,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
} }
@NotNull @NotNull
private StackValue generateExtensionReceiver(@NotNull CallableDescriptor descriptor) { public StackValue generateExtensionReceiver(@NotNull CallableDescriptor descriptor) {
ReceiverParameterDescriptor parameter = descriptor.getExtensionReceiverParameter(); ReceiverParameterDescriptor parameter = descriptor.getExtensionReceiverParameter();
if (myFrameMap.getIndex(parameter) != -1) { if (myFrameMap.getIndex(parameter) != -1) {
KotlinType type = parameter.getReturnType(); KotlinType type = parameter.getReturnType();
@@ -2891,24 +2910,40 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
forceOuter = false; forceOuter = false;
CodegenContext enclosingContext;
//for constructor super call we should access to outer instance through parameter in locals, in other cases through field for captured outer //for constructor super call we should access to outer instance through parameter in locals, in other cases through field for captured outer
if (inStartConstructorContext) { if (inStartConstructorContext) {
result = cur.getOuterExpression(result, false); result = cur.getOuterExpression(result, false);
cur = getNotNullParentContextForMethod(cur); cur = getNotNullParentContextForMethod(cur);
enclosingContext = cur.getEnclosingClassContext();
inStartConstructorContext = false; inStartConstructorContext = false;
} }
else { else {
cur = getNotNullParentContextForMethod(cur); cur = getNotNullParentContextForMethod(cur);
// for now the script codegen only passes this branch, since the method context for script constructor is defined using function context enclosingContext = cur.getEnclosingClassContext();
if (cur instanceof ScriptContext && !(thisOrOuterClass instanceof ScriptDescriptor)) {
return ((ScriptContext) cur).getOuterReceiverExpression(result, thisOrOuterClass); if (!(thisOrOuterClass instanceof ScriptDescriptor)) {
} if (cur instanceof ScriptLikeContext) {
else { /* for now the script codegen only passes this branch,
result = cur.getOuterExpression(result, false); since the method context for script constructor is defined using function context */
return ((ScriptLikeContext) cur).getOuterReceiverExpression(result, thisOrOuterClass);
} else if (enclosingContext instanceof ScriptLikeContext) {
MutableClosure closure = cur.closure;
if (closure != null) {
StackValue captured = ((ScriptLikeContext) enclosingContext).captureVariable(closure, thisOrOuterClass);
if (captured != null) {
return captured;
}
}
}
} }
result = cur.getOuterExpression(result, false);
} }
cur = cur.getEnclosingClassContext(); cur = enclosingContext;
} }
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
@@ -44,6 +44,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.inline.InlineUtil; import org.jetbrains.kotlin.resolve.inline.InlineUtil;
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue; import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver; import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver;
import org.jetbrains.kotlin.resolve.source.PsiSourceElement;
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor; import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor;
import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.util.OperatorNameConventions; import org.jetbrains.kotlin.util.OperatorNameConventions;
@@ -242,8 +243,20 @@ public class JvmCodegenUtil {
} }
public static boolean isDebuggerContext(@NotNull CodegenContext context) { public static boolean isDebuggerContext(@NotNull CodegenContext context) {
KtFile file = DescriptorToSourceUtils.getContainingFile(context.getContextDescriptor()); PsiFile file = null;
return file != null && CodeFragmentUtilKt.getSuppressDiagnosticsInDebugMode(file);
DeclarationDescriptor contextDescriptor = context.getContextDescriptor();
if (contextDescriptor instanceof DeclarationDescriptorWithSource) {
SourceElement sourceElement = ((DeclarationDescriptorWithSource) contextDescriptor).getSource();
if (sourceElement instanceof PsiSourceElement) {
PsiElement psi = ((PsiSourceElement) sourceElement).getPsi();
if (psi != null) {
file = psi.getContainingFile();
}
}
}
return file instanceof KtFile && CodeFragmentUtilKt.getSuppressDiagnosticsInDebugMode((KtFile) file);
} }
@Nullable @Nullable
@@ -32,10 +32,7 @@ import org.jetbrains.kotlin.fileClasses.JvmFileClassInfo;
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil; import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil;
import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus; import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus;
import org.jetbrains.kotlin.psi.KtClassOrObject; import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.KtDeclaration;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.psi.KtScript;
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
import org.jetbrains.kotlin.resolve.BindingContext; import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker; import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker;
@@ -98,30 +95,40 @@ public class PackageCodegenImpl implements PackageCodegen {
Type fileClassType = AsmUtil.asmTypeByFqNameWithoutInnerClasses(fileClassInfo.getFileClassFqName()); Type fileClassType = AsmUtil.asmTypeByFqNameWithoutInnerClasses(fileClassInfo.getFileClassFqName());
PackageContext packagePartContext = state.getRootContext().intoPackagePart(packageFragment, fileClassType, file); PackageContext packagePartContext = state.getRootContext().intoPackagePart(packageFragment, fileClassType, file);
List<KtClassOrObject> classOrObjects = new ArrayList<>(); if (file instanceof KtCodeFragment) {
// Avoid generating light classes for code fragments
if (state.getClassBuilderMode().generateBodies
&& state.getGenerateDeclaredClassFilter().shouldGenerateCodeFragment((KtCodeFragment) file)
) {
CodeFragmentCodegen.createCodegen((KtCodeFragment) file, state, packagePartContext).generate();
}
} else {
List<KtClassOrObject> classOrObjects = new ArrayList<>();
for (KtDeclaration declaration : file.getDeclarations()) { for (KtDeclaration declaration : file.getDeclarations()) {
if (declaration instanceof KtClassOrObject) { if (declaration instanceof KtClassOrObject) {
ClassDescriptor descriptor = state.getBindingContext().get(BindingContext.CLASS, declaration); ClassDescriptor descriptor = state.getBindingContext().get(BindingContext.CLASS, declaration);
if (PsiUtilsKt.hasExpectModifier(declaration) && if (PsiUtilsKt.hasExpectModifier(declaration) &&
(descriptor == null || !ExpectedActualDeclarationChecker.shouldGenerateExpectClass(descriptor))) { (descriptor == null || !ExpectedActualDeclarationChecker.shouldGenerateExpectClass(descriptor))) {
continue; continue;
}
KtClassOrObject classOrObject = (KtClassOrObject) declaration;
if (state.getGenerateDeclaredClassFilter().shouldGenerateClass(classOrObject)) {
classOrObjects.add(classOrObject);
}
} }
else if (declaration instanceof KtScript) {
KtScript script = (KtScript) declaration;
KtClassOrObject classOrObject = (KtClassOrObject) declaration; if (state.getGenerateDeclaredClassFilter().shouldGenerateScript(script)) {
if (state.getGenerateDeclaredClassFilter().shouldGenerateClass(classOrObject)) { ScriptCodegen.createScriptCodegen(script, state, packagePartContext).generate();
classOrObjects.add(classOrObject); }
} }
} }
else if (declaration instanceof KtScript) {
KtScript script = (KtScript) declaration;
if (state.getGenerateDeclaredClassFilter().shouldGenerateScript(script)) { generateClassesAndObjectsInFile(classOrObjects, packagePartContext);
ScriptCodegen.createScriptCodegen(script, state, packagePartContext).generate();
}
}
} }
generateClassesAndObjectsInFile(classOrObjects, packagePartContext);
if (!state.getGenerateDeclaredClassFilter().shouldGeneratePackagePart(file)) return; if (!state.getGenerateDeclaredClassFilter().shouldGeneratePackagePart(file)) return;
@@ -222,7 +222,15 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
@Override @Override
public void visitKtFile(@NotNull KtFile file) { public void visitKtFile(@NotNull KtFile file) {
nameStack.push(AsmUtil.internalNameByFqNameWithoutInnerClasses(file.getPackageFqName())); String name;
if (file instanceof KtCodeFragment) {
CodeFragmentCodegenInfo info = CodeFragmentCodegen.getCodeFragmentInfo((KtCodeFragment) file);
name = info.getClassDescriptor().getName().asString() + "$" + info.getMethodDescriptor().getName().asString();
} else {
name = AsmUtil.internalNameByFqNameWithoutInnerClasses(file.getPackageFqName());
}
nameStack.push(name);
visitKtElement(file); visitKtElement(file);
nameStack.pop(); nameStack.pop();
} }
@@ -6,6 +6,8 @@
package org.jetbrains.kotlin.codegen.binding; package org.jetbrains.kotlin.codegen.binding;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import kotlin.collections.CollectionsKt; import kotlin.collections.CollectionsKt;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -90,6 +92,15 @@ public class CodegenBinding {
CodegenAnnotatingVisitor visitor = new CodegenAnnotatingVisitor(state); CodegenAnnotatingVisitor visitor = new CodegenAnnotatingVisitor(state);
for (KtFile file : allFilesInPackages(state.getBindingContext(), state.getFiles())) { for (KtFile file : allFilesInPackages(state.getBindingContext(), state.getFiles())) {
file.accept(visitor); file.accept(visitor);
if (file instanceof KtCodeFragment) {
PsiElement context = file.getContext();
if (context != null) {
PsiFile contextFile = context.getContainingFile();
if (contextFile != null) {
contextFile.accept(visitor);
}
}
}
} }
} }
@@ -27,7 +27,7 @@ fun ClassFileFactory.getClassFiles(): Iterable<OutputFile> {
return asList().filterClassFiles() return asList().filterClassFiles()
} }
fun List<OutputFile>.filterClassFiles(): Iterable<OutputFile> { fun List<OutputFile>.filterClassFiles(): List<OutputFile> {
return filter { it.relativePath.endsWith(".class") } return filter { it.relativePath.endsWith(".class") }
} }
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.codegen.context package org.jetbrains.kotlin.codegen.context
import org.jetbrains.kotlin.codegen.FieldInfo import org.jetbrains.kotlin.codegen.FieldInfo
import org.jetbrains.kotlin.codegen.OwnerKind
import org.jetbrains.kotlin.codegen.StackValue import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
@@ -35,12 +34,12 @@ import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.Type
class ScriptContext( class ScriptContext(
val typeMapper: KotlinTypeMapper, typeMapper: KotlinTypeMapper,
val scriptDescriptor: ScriptDescriptor, val scriptDescriptor: ScriptDescriptor,
val earlierScripts: List<ScriptDescriptor>, val earlierScripts: List<ScriptDescriptor>,
contextDescriptor: ClassDescriptor, contextDescriptor: ClassDescriptor,
parentContext: CodegenContext<*>? parentContext: CodegenContext<*>?
) : ClassContext(typeMapper, contextDescriptor, OwnerKind.IMPLEMENTATION, parentContext, null) { ) : ScriptLikeContext(typeMapper, contextDescriptor, parentContext) {
val lastStatement: KtExpression? val lastStatement: KtExpression?
val resultFieldInfo: FieldInfo val resultFieldInfo: FieldInfo
@@ -89,7 +88,7 @@ class ScriptContext(
fun getProvidedPropertyType(index: Int): Type = typeMapper.mapType(scriptDescriptor.scriptProvidedProperties[index].type) fun getProvidedPropertyType(index: Int): Type = typeMapper.mapType(scriptDescriptor.scriptProvidedProperties[index].type)
fun getOuterReceiverExpression(prefix: StackValue?, thisOrOuterClass: ClassDescriptor): StackValue { override fun getOuterReceiverExpression(prefix: StackValue?, thisOrOuterClass: ClassDescriptor): StackValue {
if (thisOrOuterClass.containingDeclaration == scriptDescriptor) { if (thisOrOuterClass.containingDeclaration == scriptDescriptor) {
return prefix ?: StackValue.LOCAL_0 return prefix ?: StackValue.LOCAL_0
} }
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.codegen.context
import org.jetbrains.kotlin.codegen.OwnerKind
import org.jetbrains.kotlin.codegen.StackValue
import org.jetbrains.kotlin.codegen.binding.MutableClosure
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
abstract class ScriptLikeContext(
val typeMapper: KotlinTypeMapper,
contextDescriptor: ClassDescriptor,
parentContext: CodegenContext<*>?
) : ClassContext(typeMapper, contextDescriptor, OwnerKind.IMPLEMENTATION, parentContext, null) {
abstract fun getOuterReceiverExpression(prefix: StackValue?, thisOrOuterClass: ClassDescriptor): StackValue
open fun captureVariable(closure: MutableClosure, target: DeclarationDescriptor): StackValue? {
return null
}
}
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmMetadataVersion
import org.jetbrains.kotlin.modules.TargetId import org.jetbrains.kotlin.modules.TargetId
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.*
@@ -120,18 +121,17 @@ class GenerationState private constructor(
abstract fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean abstract fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean
abstract fun shouldGeneratePackagePart(ktFile: KtFile): Boolean abstract fun shouldGeneratePackagePart(ktFile: KtFile): Boolean
abstract fun shouldGenerateScript(script: KtScript): Boolean abstract fun shouldGenerateScript(script: KtScript): Boolean
abstract fun shouldGenerateCodeFragment(script: KtCodeFragment): Boolean
open fun shouldGenerateClassMembers(processingClassOrObject: KtClassOrObject) = shouldGenerateClass(processingClassOrObject) open fun shouldGenerateClassMembers(processingClassOrObject: KtClassOrObject) = shouldGenerateClass(processingClassOrObject)
companion object { companion object {
@JvmField @JvmField
val GENERATE_ALL: GenerateClassFilter = object : GenerateClassFilter() { val GENERATE_ALL: GenerateClassFilter = object : GenerateClassFilter() {
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject): Boolean = true override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject): Boolean = true
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean = true override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean = true
override fun shouldGenerateScript(script: KtScript): Boolean = true override fun shouldGenerateScript(script: KtScript): Boolean = true
override fun shouldGeneratePackagePart(ktFile: KtFile): Boolean = true override fun shouldGeneratePackagePart(ktFile: KtFile): Boolean = true
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = true
} }
} }
} }
@@ -30,10 +30,7 @@ import org.jetbrains.kotlin.codegen.CompilationErrorHandler
import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiUtil
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.Type
@@ -199,6 +196,7 @@ private class ClassFilterForClassOrObject(private val classOrObject: KtClassOrOb
= shouldGenerateClassMembers(processingClassOrObject) || processingClassOrObject.isAncestor(classOrObject, true) = shouldGenerateClassMembers(processingClassOrObject) || processingClassOrObject.isAncestor(classOrObject, true)
override fun shouldGenerateScript(script: KtScript) = PsiTreeUtil.isAncestor(script, classOrObject, false) override fun shouldGenerateScript(script: KtScript) = PsiTreeUtil.isAncestor(script, classOrObject, false)
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
} }
object ClassFilterForFacade : GenerationState.GenerateClassFilter() { object ClassFilterForFacade : GenerationState.GenerateClassFilter() {
@@ -206,6 +204,7 @@ object ClassFilterForFacade : GenerationState.GenerateClassFilter() {
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = KtPsiUtil.isLocal(processingClassOrObject) override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = KtPsiUtil.isLocal(processingClassOrObject)
override fun shouldGeneratePackagePart(ktFile: KtFile) = true override fun shouldGeneratePackagePart(ktFile: KtFile) = true
override fun shouldGenerateScript(script: KtScript) = false override fun shouldGenerateScript(script: KtScript) = false
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
} }
private class ClassFilterForScript(val script: KtScript) : GenerationState.GenerateClassFilter() { private class ClassFilterForScript(val script: KtScript) : GenerationState.GenerateClassFilter() {
@@ -220,4 +219,5 @@ private class ClassFilterForScript(val script: KtScript) : GenerationState.Gener
override fun shouldGeneratePackagePart(ktFile: KtFile): Boolean = script.containingKtFile === ktFile override fun shouldGeneratePackagePart(ktFile: KtFile): Boolean = script.containingKtFile === ktFile
override fun shouldGenerateScript(script: KtScript): Boolean = this.script === script override fun shouldGenerateScript(script: KtScript): Boolean = this.script === script
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
} }
@@ -337,6 +337,12 @@ abstract class KtLightClassForSourceDeclaration(protected val classOrObject: KtC
} }
fun createNoCache(classOrObject: KtClassOrObject): KtLightClassForSourceDeclaration? { fun createNoCache(classOrObject: KtClassOrObject): KtLightClassForSourceDeclaration? {
val containingFile = classOrObject.containingFile
if (containingFile is KtCodeFragment) {
// Avoid building light classes for code fragments
return null
}
if (classOrObject.shouldNotBeVisibleAsLightClass()) { if (classOrObject.shouldNotBeVisibleAsLightClass()) {
return null return null
} }
@@ -199,3 +199,5 @@ abstract class KtCodeFragment(
private val LOG = Logger.getInstance(KtCodeFragment::class.java) private val LOG = Logger.getInstance(KtCodeFragment::class.java)
} }
} }
var KtCodeFragment.externalDescriptors: List<DeclarationDescriptor>? by CopyablePsiUserDataProperty(Key.create("EXTERNAL_DESCRIPTORS"))
@@ -153,6 +153,9 @@ fun KtExpression.getQualifiedExpressionForReceiverOrThis(): KtExpression {
fun KtExpression.isDotReceiver(): Boolean = fun KtExpression.isDotReceiver(): Boolean =
(parent as? KtDotQualifiedExpression)?.receiverExpression == this (parent as? KtDotQualifiedExpression)?.receiverExpression == this
fun KtExpression.isDotSelector(): Boolean =
(parent as? KtDotQualifiedExpression)?.selectorExpression == this
fun KtExpression.getPossiblyQualifiedCallExpression(): KtCallExpression? = fun KtExpression.getPossiblyQualifiedCallExpression(): KtCallExpression? =
((this as? KtQualifiedExpression)?.selectorExpression ?: this) as? KtCallExpression ((this as? KtQualifiedExpression)?.selectorExpression ?: this) as? KtCallExpression
@@ -636,10 +636,6 @@ fun main(args: Array<String>) {
model("debugger/smartStepInto") model("debugger/smartStepInto")
} }
testClass<AbstractBeforeExtractFunctionInsertionTest> {
model("debugger/insertBeforeExtractFunction", extension = "kt")
}
testClass<AbstractKotlinSteppingTest> { testClass<AbstractKotlinSteppingTest> {
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest", testClassName = "StepInto") model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest", testClassName = "StepInto")
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto") model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto")
@@ -75,10 +75,7 @@ import org.jetbrains.kotlin.idea.conversion.copy.AbstractLiteralKotlinToKotlinCo
import org.jetbrains.kotlin.idea.conversion.copy.AbstractLiteralTextToKotlinCopyPasteTest import org.jetbrains.kotlin.idea.conversion.copy.AbstractLiteralTextToKotlinCopyPasteTest
import org.jetbrains.kotlin.idea.conversion.copy.AbstractTextJavaToKotlinCopyPasteConversionTest import org.jetbrains.kotlin.idea.conversion.copy.AbstractTextJavaToKotlinCopyPasteConversionTest
import org.jetbrains.kotlin.idea.coverage.AbstractKotlinCoverageOutputFilesTest import org.jetbrains.kotlin.idea.coverage.AbstractKotlinCoverageOutputFilesTest
import org.jetbrains.kotlin.idea.debugger.AbstractBeforeExtractFunctionInsertionTest import org.jetbrains.kotlin.idea.debugger.*
import org.jetbrains.kotlin.idea.debugger.AbstractKotlinSteppingTest
import org.jetbrains.kotlin.idea.debugger.AbstractPositionManagerTest
import org.jetbrains.kotlin.idea.debugger.AbstractSmartStepIntoTest
import org.jetbrains.kotlin.idea.debugger.evaluate.* import org.jetbrains.kotlin.idea.debugger.evaluate.*
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToDecompiledLibraryTest import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToDecompiledLibraryTest
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToLibrarySourceTest import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToLibrarySourceTest
@@ -627,10 +624,6 @@ fun main(args: Array<String>) {
model("debugger/smartStepInto") model("debugger/smartStepInto")
} }
testClass<AbstractBeforeExtractFunctionInsertionTest> {
model("debugger/insertBeforeExtractFunction", extension = "kt")
}
testClass<AbstractKotlinSteppingTest> { testClass<AbstractKotlinSteppingTest> {
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest", testClassName = "StepInto") model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest", testClassName = "StepInto")
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto") model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto")
@@ -67,10 +67,7 @@ import org.jetbrains.kotlin.idea.conversion.copy.AbstractLiteralKotlinToKotlinCo
import org.jetbrains.kotlin.idea.conversion.copy.AbstractLiteralTextToKotlinCopyPasteTest import org.jetbrains.kotlin.idea.conversion.copy.AbstractLiteralTextToKotlinCopyPasteTest
import org.jetbrains.kotlin.idea.conversion.copy.AbstractTextJavaToKotlinCopyPasteConversionTest import org.jetbrains.kotlin.idea.conversion.copy.AbstractTextJavaToKotlinCopyPasteConversionTest
import org.jetbrains.kotlin.idea.coverage.AbstractKotlinCoverageOutputFilesTest import org.jetbrains.kotlin.idea.coverage.AbstractKotlinCoverageOutputFilesTest
import org.jetbrains.kotlin.idea.debugger.AbstractBeforeExtractFunctionInsertionTest import org.jetbrains.kotlin.idea.debugger.*
import org.jetbrains.kotlin.idea.debugger.AbstractKotlinSteppingTest
import org.jetbrains.kotlin.idea.debugger.AbstractPositionManagerTest
import org.jetbrains.kotlin.idea.debugger.AbstractSmartStepIntoTest
import org.jetbrains.kotlin.idea.debugger.evaluate.* import org.jetbrains.kotlin.idea.debugger.evaluate.*
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToDecompiledLibraryTest import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToDecompiledLibraryTest
import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToLibrarySourceTest import org.jetbrains.kotlin.idea.decompiler.navigation.AbstractNavigateToLibrarySourceTest
@@ -619,10 +616,6 @@ fun main(args: Array<String>) {
model("debugger/smartStepInto") model("debugger/smartStepInto")
} }
testClass<AbstractBeforeExtractFunctionInsertionTest> {
model("debugger/insertBeforeExtractFunction", extension = "kt")
}
testClass<AbstractKotlinSteppingTest> { testClass<AbstractKotlinSteppingTest> {
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest", testClassName = "StepInto") model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest", testClassName = "StepInto")
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto") model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto")
@@ -616,10 +616,6 @@ fun main(args: Array<String>) {
model("debugger/smartStepInto") model("debugger/smartStepInto")
} }
testClass<AbstractBeforeExtractFunctionInsertionTest> {
model("debugger/insertBeforeExtractFunction", extension = "kt")
}
testClass<AbstractKotlinSteppingTest> { testClass<AbstractKotlinSteppingTest> {
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest", testClassName = "StepInto") model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doStepIntoTest", testClassName = "StepInto")
model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto") model("debugger/tinyApp/src/stepping/stepIntoAndSmartStepInto", pattern = KT_WITHOUT_DOTS_IN_NAME, testMethod = "doSmartStepIntoTest", testClassName = "SmartStepInto")
@@ -16,18 +16,23 @@
package org.jetbrains.kotlin.idea.caches.resolve package org.jetbrains.kotlin.idea.caches.resolve
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes import org.jetbrains.kotlin.descriptors.ClassDescriptorWithResolutionScopes
import org.jetbrains.kotlin.idea.project.ResolveElementCache import org.jetbrains.kotlin.idea.project.ResolveElementCache
import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoAfter import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoAfter
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.lazy.ResolveSession import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.jetbrains.kotlin.resolve.scopes.ExplicitImportsScope
import org.jetbrains.kotlin.resolve.scopes.ImportingScope
import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.utils.ErrorLexicalScope
import org.jetbrains.kotlin.resolve.scopes.utils.addImportingScopes import org.jetbrains.kotlin.resolve.scopes.utils.addImportingScopes
import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices
@@ -43,132 +48,154 @@ class CodeFragmentAnalyzer(
@set:Inject // component dependency cycle @set:Inject // component dependency cycle
lateinit var resolveElementCache: ResolveElementCache lateinit var resolveElementCache: ResolveElementCache
fun analyzeCodeFragment(codeFragment: KtCodeFragment, trace: BindingTrace, bodyResolveMode: BodyResolveMode): BindingTrace { fun analyzeCodeFragment(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): BindingTrace {
val codeFragmentElement = codeFragment.getContentElement() val contextAnalysisResult = analyzeCodeFragmentContext(codeFragment, bodyResolveMode)
return doAnalyzeCodeFragment(codeFragment, contextAnalysisResult)
}
val (scopeForContextElement, dataFlowInfo, newBindingContext) = doAnalyzeCoreFragment(codeFragment) { private fun doAnalyzeCodeFragment(codeFragment: KtCodeFragment, contextAnalysisResult: ContextAnalysisResult): BindingTrace {
resolveElementCache!!.resolveToElements(listOf(it), bodyResolveMode) val (bindingContext, scope, dataFlowInfo) = contextAnalysisResult
} ?: return trace val bindingTrace = DelegatingBindingTrace(bindingContext, "For code fragment analysis")
val newBindingTrace = DelegatingBindingTrace(newBindingContext, "For code fragment analysis") when (val contentElement = codeFragment.getContentElement()) {
when (codeFragmentElement) {
is KtExpression -> { is KtExpression -> {
PreliminaryDeclarationVisitor.createForExpression( PreliminaryDeclarationVisitor.createForExpression(
codeFragmentElement, newBindingTrace, contentElement, bindingTrace,
expressionTypingServices.languageVersionSettings expressionTypingServices.languageVersionSettings
) )
expressionTypingServices.getTypeInfo( expressionTypingServices.getTypeInfo(
scopeForContextElement, scope, contentElement, TypeUtils.NO_EXPECTED_TYPE,
codeFragmentElement, dataFlowInfo, bindingTrace, false
TypeUtils.NO_EXPECTED_TYPE,
dataFlowInfo,
newBindingTrace,
false
) )
} }
is KtTypeReference -> { is KtTypeReference -> {
val context = TypeResolutionContext( val context = TypeResolutionContext(
scopeForContextElement, scope, bindingTrace,
newBindingTrace, true, true, codeFragment.suppressDiagnosticsInDebugMode()
true,
true,
codeFragment.suppressDiagnosticsInDebugMode()
).noBareTypes() ).noBareTypes()
typeResolver.resolvePossiblyBareType(context, codeFragmentElement)
typeResolver.resolvePossiblyBareType(context, contentElement)
} }
} }
return newBindingTrace return bindingTrace
} }
//TODO: this code should be moved into debugger which should set correct context for its code fragment private data class ContextAnalysisResult(
private fun KtElement.correctContextForElement(): KtElement { val bindingContext: BindingContext,
return when (this) { val scope: LexicalScope,
is KtProperty -> this.delegateExpressionOrInitializer
is KtFunctionLiteral -> this.bodyExpression?.statements?.lastOrNull()
is KtDeclarationWithBody -> this.bodyExpression
is KtBlockExpression -> this.statements.lastOrNull()
else -> null
} ?: this
}
private fun doAnalyzeCoreFragment(
codeFragment: KtCodeFragment,
resolveToElement: (KtElement) -> BindingContext
): Triple<LexicalScope, DataFlowInfo, BindingContext>? {
val context = codeFragment.context
val scopeForContextElement: LexicalScope?
val dataFlowInfo: DataFlowInfo val dataFlowInfo: DataFlowInfo
)
fun getClassDescriptor(classOrObject: KtClassOrObject): Pair<BindingContext, ClassDescriptorWithResolutionScopes>? { private fun analyzeCodeFragmentContext(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): ContextAnalysisResult {
val bindingContext: BindingContext fun resolutionFactory(element: KtElement): BindingContext {
val classDescriptor: ClassDescriptor? return resolveElementCache.resolveToElements(listOf(element), bodyResolveMode)
if (!KtPsiUtil.isLocal(classOrObject)) {
bindingContext = resolveSession.bindingContext
classDescriptor = resolveSession.getClassDescriptor(classOrObject, NoLookupLocation.FROM_IDE)
} else {
bindingContext = resolveToElement(classOrObject)
classDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, classOrObject] as ClassDescriptor?
}
return (classDescriptor as? ClassDescriptorWithResolutionScopes)?.let { Pair(bindingContext, it) }
} }
val bindingContextForContext: BindingContext val context = refineContextElement(codeFragment.context)
var bindingContext: BindingContext = BindingContext.EMPTY
var dataFlowInfo: DataFlowInfo = DataFlowInfo.EMPTY
var scope: LexicalScope? = null
when (context) { when (context) {
is KtPrimaryConstructor -> { is KtPrimaryConstructor -> {
val (bindingContext, classDescriptor) = getClassDescriptor(context.getContainingClassOrObject()) ?: return null val containingClass = context.getContainingClassOrObject()
val resolutionResult = getClassDescriptor(containingClass, ::resolutionFactory)
scopeForContextElement = classDescriptor.scopeForInitializerResolution if (resolutionResult != null) {
dataFlowInfo = DataFlowInfo.EMPTY bindingContext = resolutionResult.bindingContext
bindingContextForContext = bindingContext scope = resolutionResult.descriptor.scopeForInitializerResolution
}
} }
is KtSecondaryConstructor -> { is KtSecondaryConstructor -> {
val correctedContext = context.getDelegationCall().calleeExpression!! val expression = context.bodyExpression ?: context.getDelegationCall().calleeExpression
bindingContextForContext = resolveToElement(correctedContext) if (expression != null) {
bindingContext = resolutionFactory(expression)
scopeForContextElement = bindingContextForContext[BindingContext.LEXICAL_SCOPE, correctedContext] scope = bindingContext[BindingContext.LEXICAL_SCOPE, expression]
dataFlowInfo = DataFlowInfo.EMPTY }
} }
is KtClassOrObject -> { is KtClassOrObject -> {
val (bindingContext, classDescriptor) = getClassDescriptor(context) ?: return null val resolutionResult = getClassDescriptor(context, ::resolutionFactory)
scopeForContextElement = classDescriptor.scopeForMemberDeclarationResolution if (resolutionResult != null) {
dataFlowInfo = DataFlowInfo.EMPTY bindingContext = resolutionResult.bindingContext
bindingContextForContext = bindingContext scope = resolutionResult.descriptor.scopeForMemberDeclarationResolution
}
} }
is KtFile -> { is KtFile -> {
scopeForContextElement = resolveSession.fileScopeProvider.getFileResolutionScope(context) bindingContext = resolveSession.bindingContext
dataFlowInfo = DataFlowInfo.EMPTY scope = resolveSession.fileScopeProvider.getFileResolutionScope(context)
bindingContextForContext = BindingContext.EMPTY
} }
is KtElement -> { is KtElement -> {
val correctedContext = context.correctContextForElement() bindingContext = resolutionFactory(context)
bindingContextForContext = resolveToElement(correctedContext) scope = bindingContext[BindingContext.LEXICAL_SCOPE, context]
dataFlowInfo = bindingContext.getDataFlowInfoAfter(context)
scopeForContextElement = bindingContextForContext[BindingContext.LEXICAL_SCOPE, correctedContext]
dataFlowInfo = bindingContextForContext.getDataFlowInfoAfter(correctedContext)
} }
else -> return null
} }
if (scopeForContextElement == null) return null val scopeWithImports = enrichScopeWithImports(scope ?: ErrorLexicalScope(), codeFragment)
return ContextAnalysisResult(bindingContext, scopeWithImports, dataFlowInfo)
}
private data class ClassResolutionResult(val bindingContext: BindingContext, val descriptor: ClassDescriptorWithResolutionScopes)
private fun getClassDescriptor(
classOrObject: KtClassOrObject,
resolutionFactory: (KtElement) -> BindingContext
): ClassResolutionResult? {
val bindingContext: BindingContext
val classDescriptor: ClassDescriptor?
if (!KtPsiUtil.isLocal(classOrObject)) {
bindingContext = resolveSession.bindingContext
classDescriptor = resolveSession.getClassDescriptor(classOrObject, NoLookupLocation.FROM_IDE)
} else {
bindingContext = resolutionFactory(classOrObject)
classDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, classOrObject] as ClassDescriptor?
}
return (classDescriptor as? ClassDescriptorWithResolutionScopes)?.let { ClassResolutionResult(bindingContext, it) }
}
private fun refineContextElement(context: PsiElement?): KtElement? {
return when (context) {
is KtParameter -> context.getParentOfType<KtFunction>(true)
is KtProperty -> context.delegateExpressionOrInitializer
is KtConstructor<*> -> context
is KtFunctionLiteral -> context.bodyExpression?.statements?.lastOrNull()
is KtDeclarationWithBody -> context.bodyExpression
is KtBlockExpression -> context.statements.lastOrNull()
else -> null
} ?: context as? KtElement
}
private fun enrichScopeWithImports(scope: LexicalScope, codeFragment: KtCodeFragment): LexicalScope {
val additionalImportingScopes = mutableListOf<ImportingScope>()
val externalDescriptors = codeFragment.externalDescriptors ?: emptyList()
if (externalDescriptors.isNotEmpty()) {
additionalImportingScopes += ExplicitImportsScope(externalDescriptors)
}
val importList = codeFragment.importsAsImportList() val importList = codeFragment.importsAsImportList()
if (importList == null || importList.imports.isEmpty()) { if (importList != null && importList.imports.isNotEmpty()) {
return Triple(scopeForContextElement, dataFlowInfo, bindingContextForContext) additionalImportingScopes += createImportScopes(importList)
} }
val importScopes = importList.imports.mapNotNull { if (additionalImportingScopes.isNotEmpty()) {
return scope.addImportingScopes(additionalImportingScopes)
}
return scope
}
private fun createImportScopes(importList: KtImportList): List<ImportingScope> {
return importList.imports.mapNotNull {
qualifierResolver.processImportReference( qualifierResolver.processImportReference(
it, resolveSession.moduleDescriptor, resolveSession.trace, it, resolveSession.moduleDescriptor, resolveSession.trace,
excludedImportNames = emptyList(), packageFragmentForVisibilityCheck = null excludedImportNames = emptyList(), packageFragmentForVisibilityCheck = null
) )
} }
return Triple(scopeForContextElement.addImportingScopes(importScopes), dataFlowInfo, bindingContextForContext)
} }
} }
@@ -176,7 +176,9 @@ private object KotlinResolveDataProvider {
): AnalysisResult { ): AnalysisResult {
try { try {
if (analyzableElement is KtCodeFragment) { if (analyzableElement is KtCodeFragment) {
return AnalysisResult.success(analyzeExpressionCodeFragment(codeFragmentAnalyzer, analyzableElement), moduleDescriptor) val bodyResolveMode = BodyResolveMode.PARTIAL_FOR_COMPLETION
val bindingContext = codeFragmentAnalyzer.analyzeCodeFragment(analyzableElement, bodyResolveMode).bindingContext
return AnalysisResult.success(bindingContext, moduleDescriptor)
} }
val trace = DelegatingBindingTrace( val trace = DelegatingBindingTrace(
@@ -217,19 +219,4 @@ private object KotlinResolveDataProvider {
return AnalysisResult.internalError(BindingContext.EMPTY, e) return AnalysisResult.internalError(BindingContext.EMPTY, e)
} }
} }
private fun analyzeExpressionCodeFragment(codeFragmentAnalyzer: CodeFragmentAnalyzer, codeFragment: KtCodeFragment): BindingContext {
val contextElement = codeFragment.getContentElement()
val trace = if (contextElement != null) {
DelegatingBindingTrace(contextElement.analyze(BodyResolveMode.PARTIAL_FOR_COMPLETION), "Trace for code fragment resolution")
} else {
BindingTraceContext()
}
return codeFragmentAnalyzer.analyzeCodeFragment(
codeFragment,
trace,
BodyResolveMode.PARTIAL_FOR_COMPLETION //TODO: discuss it
).bindingContext
}
} }
@@ -457,14 +457,12 @@ class ResolveElementCache(
} }
private fun codeFragmentAdditionalResolve(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): BindingTrace { private fun codeFragmentAdditionalResolve(codeFragment: KtCodeFragment, bodyResolveMode: BodyResolveMode): BindingTrace {
val trace = createDelegatingTrace(codeFragment, bodyResolveMode.bindingTraceFilter)
val contextResolveMode = if (bodyResolveMode == BodyResolveMode.PARTIAL) val contextResolveMode = if (bodyResolveMode == BodyResolveMode.PARTIAL)
BodyResolveMode.PARTIAL_FOR_COMPLETION BodyResolveMode.PARTIAL_FOR_COMPLETION
else else
bodyResolveMode bodyResolveMode
return codeFragmentAnalyzer.analyzeCodeFragment(codeFragment, trace, contextResolveMode) return codeFragmentAnalyzer.analyzeCodeFragment(codeFragment, contextResolveMode)
} }
private fun annotationAdditionalResolve(resolveSession: ResolveSession, ktAnnotationEntry: KtAnnotationEntry): BindingTrace { private fun annotationAdditionalResolve(resolveSession: ResolveSession, ktAnnotationEntry: KtAnnotationEntry): BindingTrace {
@@ -20,7 +20,7 @@ import com.intellij.xdebugger.frame.XNamedValue
import com.sun.jdi.* import com.sun.jdi.*
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
import org.jetbrains.kotlin.idea.debugger.evaluate.VariableFinder.Companion.SUSPEND_LAMBDA_CLASSES import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder.Companion.SUSPEND_LAMBDA_CLASSES
import org.jetbrains.kotlin.idea.debugger.evaluate.getInvokePolicy import org.jetbrains.kotlin.idea.debugger.evaluate.getInvokePolicy
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousCl
import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
import org.jetbrains.kotlin.codegen.coroutines.continuationAsmTypes import org.jetbrains.kotlin.codegen.coroutines.continuationAsmTypes
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
import org.jetbrains.kotlin.idea.refactoring.getLineEndOffset import org.jetbrains.kotlin.idea.refactoring.getLineEndOffset
@@ -27,7 +26,6 @@ import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.inline.InlineUtil import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.org.objectweb.asm.Type as AsmType import org.jetbrains.org.objectweb.asm.Type as AsmType
import java.util.* import java.util.*
@@ -241,15 +239,6 @@ fun findCallByEndToken(element: PsiElement): KtCallExpression? {
} }
} }
fun PropertyDescriptor.getBackingFieldName(): String? {
if (backingField == null) {
return null
}
val jvmNameAnnotation = DescriptorUtils.findJvmNameAnnotation(this) ?: return name.asString()
return jvmNameAnnotation.allValueArguments.values.singleOrNull()?.toString()
}
fun Type.isSubtype(className: String): Boolean = isSubtype(AsmType.getObjectType(className)) fun Type.isSubtype(className: String): Boolean = isSubtype(AsmType.getObjectType(className))
fun Type.isSubtype(type: AsmType): Boolean { fun Type.isSubtype(type: AsmType): Boolean {
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.debugger.evaluate
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.sun.jdi.*
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.org.objectweb.asm.Type
class ExecutionContext(val evaluationContext: EvaluationContextImpl, val thread: ThreadReference, val invokePolicy: Int) {
val vm: VirtualMachine
get() = thread.virtualMachine()
fun loadClassType(asmType: Type, classLoader: ClassLoaderReference? = null): ReferenceType? {
if (asmType.sort == Type.ARRAY) {
return loadClassType(asmType.elementType, classLoader)
}
if (asmType.sort != Type.OBJECT) {
return null
}
val vm = thread.virtualMachine()
val className = asmType.className
val classClass = vm.classesByName(Class::class.java.name).firstIsInstanceOrNull<ClassType>() ?: return null
val method: Method?
val args: List<Value?>
if (classLoader != null) {
method = classClass.methodsByName("forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;").firstOrNull()
args = listOf(vm.mirrorOf(className), vm.mirrorOf(true), classLoader)
} else {
method = classClass.methodsByName("forName", "(Ljava/lang/String;)Ljava/lang/Class;").firstOrNull()
args = listOf(vm.mirrorOf(className))
}
if (method == null) {
return null
}
return (classClass.invokeMethod(thread, method, args, invokePolicy) as? ClassObjectReference)?.reflectedType()
}
}
@@ -22,7 +22,6 @@ import com.intellij.debugger.engine.evaluation.CodeFragmentKind
import com.intellij.debugger.engine.evaluation.TextWithImports import com.intellij.debugger.engine.evaluation.TextWithImports
import com.intellij.debugger.engine.events.DebuggerCommandImpl import com.intellij.debugger.engine.events.DebuggerCommandImpl
import com.intellij.debugger.impl.DebuggerContextImpl import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl
import com.intellij.ide.highlighter.JavaFileType import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
@@ -35,9 +34,6 @@ import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiTypesUtil import com.intellij.psi.util.PsiTypesUtil
import com.intellij.util.IncorrectOperationException import com.intellij.util.IncorrectOperationException
import com.intellij.util.concurrency.Semaphore import com.intellij.util.concurrency.Semaphore
import com.intellij.xdebugger.XDebuggerManager
import com.intellij.xdebugger.impl.XDebugSessionImpl
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
import com.sun.jdi.* import com.sun.jdi.*
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.jetbrains.eval4j.jdi.asValue import org.jetbrains.eval4j.jdi.asValue
@@ -55,7 +51,6 @@ import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.versions.getKotlinJvmRuntimeMarkerClass import org.jetbrains.kotlin.idea.versions.getKotlinJvmRuntimeMarkerClass
import org.jetbrains.kotlin.j2k.AfterConversionPass import org.jetbrains.kotlin.j2k.AfterConversionPass
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.makeNullable import org.jetbrains.kotlin.types.typeUtil.makeNullable
@@ -63,28 +58,16 @@ import java.util.concurrent.atomic.AtomicReference
class KotlinCodeFragmentFactory : CodeFragmentFactory() { class KotlinCodeFragmentFactory : CodeFragmentFactory() {
override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment { override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
val contextElement = getWrappedContextElement(project, context) val contextElement = getContextElement(context)
if (contextElement == null) {
LOG.warn("CodeFragment with null context created:\noriginalContext = ${context?.getElementTextWithContext()}") val constructor = when (item.kind) {
} null -> error("Code fragment kind should be set")
val codeFragment = if (item.kind == CodeFragmentKind.EXPRESSION) { CodeFragmentKind.EXPRESSION -> ::KtExpressionCodeFragment
KtExpressionCodeFragment( CodeFragmentKind.CODE_BLOCK -> ::KtBlockCodeFragment
project,
"fragment.kt",
item.text,
initImports(item.imports),
contextElement
)
} else {
KtBlockCodeFragment(
project,
"fragment.kt",
item.text,
initImports(item.imports),
contextElement
)
} }
val codeFragment = constructor(project, "fragment.kt", item.text, initImports(item.imports), contextElement)
codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR, { expression: KtExpression -> codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR, { expression: KtExpression ->
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
val debuggerSession = debuggerContext.debuggerSession val debuggerSession = debuggerContext.debuggerSession
@@ -135,19 +118,19 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
} }
val receiverTypeReference = val receiverTypeReference =
frameDescriptor.thisObject?.let { createKotlinProperty(project, "this_0", it.type().name(), it) }?.typeReference frameDescriptor.thisObject?.let { createKotlinProperty(project, FAKE_JAVA_THIS_NAME, it.type().name(), it) }?.typeReference
val receiverTypeText = receiverTypeReference?.let { "${it.text}." } ?: "" val receiverTypeText = receiverTypeReference?.let { "${it.text}." } ?: ""
val kotlinVariablesText = val kotlinVariablesText =
frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project) frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project)
val fakeFunctionText = "fun ${receiverTypeText}_java_locals_debug_fun_() {\n$kotlinVariablesText\n}" val fakeFunctionText = "fun ${receiverTypeText}$FAKE_JAVA_CONTEXT_FUNCTION_NAME() {\n$kotlinVariablesText\n}"
val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement) val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement)
val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction
val fakeContext = fakeFunction?.bodyBlockExpression?.statements?.lastOrNull() val fakeContext = fakeFunction?.bodyBlockExpression?.statements?.lastOrNull()
return@putCopyableUserData wrapContextIfNeeded(project, contextElement, fakeContext) ?: emptyFile return@putCopyableUserData fakeContext ?: emptyFile
}) })
} }
@@ -219,12 +202,6 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
return import return import
} }
private fun getWrappedContextElement(project: Project, context: PsiElement?): PsiElement? {
val newContext = getContextElement(context)
if (newContext !is KtElement) return newContext
return wrapContextIfNeeded(project, context, newContext)
}
override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment { override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
val kotlinCodeFragment = createCodeFragment(item, context, project) val kotlinCodeFragment = createCodeFragment(item, context, project)
if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtExpressionCodeFragment) { if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtExpressionCodeFragment) {
@@ -292,13 +269,12 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
companion object { companion object {
private val LOG = Logger.getInstance(this::class.java) private val LOG = Logger.getInstance(this::class.java)
val LABEL_VARIABLE_VALUE_KEY: Key<Value> = Key.create<Value>("_label_variable_value_key_")
private const val DEBUG_LABEL_SUFFIX: String = "_DebugLabel"
@TestOnly @TestOnly
val DEBUG_CONTEXT_FOR_TESTS: Key<DebuggerContextImpl> = Key.create("DEBUG_CONTEXT_FOR_TESTS") val DEBUG_CONTEXT_FOR_TESTS: Key<DebuggerContextImpl> = Key.create("DEBUG_CONTEXT_FOR_TESTS")
const val FAKE_JAVA_CONTEXT_FUNCTION_NAME = "_java_locals_debug_fun_"
const val FAKE_JAVA_THIS_NAME = "\$this\$_java_locals_debug_fun_"
fun getContextElement(elementAt: PsiElement?): PsiElement? { fun getContextElement(elementAt: PsiElement?): PsiElement? {
if (elementAt == null) return null if (elementAt == null) return null
@@ -335,17 +311,6 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
return containingFile return containingFile
} }
//internal for tests
fun createCodeFragmentForLabeledObjects(project: Project, markupMap: Map<*, ValueMarkup>): Pair<String, Map<String, Value>> {
@Suppress("UNCHECKED_CAST")
val variables = markupMap.entries.associate {
val (value, markup) = it
"${markup.text}$DEBUG_LABEL_SUFFIX" to value as? Value
}.filterValues { it != null } as Map<String, Value>
return variables.kotlinVariablesAsText(project) to variables
}
private fun Map<String, Value>.kotlinVariablesAsText(project: Project): String { private fun Map<String, Value>.kotlinVariablesAsText(project: Project): String {
val sb = StringBuilder() val sb = StringBuilder()
@@ -389,21 +354,6 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
} }
} }
private fun wrapContextIfNeeded(project: Project, originalContext: PsiElement?, newContext: KtElement?): KtElement? {
val markupMap: Map<*, ValueMarkup>? =
if (ApplicationManager.getApplication().isUnitTestMode)
NodeDescriptorImpl.getMarkupMap(originalContext?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.debugProcess)
else
(XDebuggerManager.getInstance(project).currentSession as? XDebugSessionImpl)?.valueMarkers?.allMarkers
if (markupMap == null || markupMap.isEmpty()) return newContext
val (text, labels) = createCodeFragmentForLabeledObjects(project, markupMap)
if (text.isEmpty()) return newContext
return createWrappingContext(text, labels, newContext, project)
}
private fun createFakeFileWithJavaContextElement(funWithLocalVariables: String, javaContext: PsiElement): KtFile { private fun createFakeFileWithJavaContextElement(funWithLocalVariables: String, javaContext: PsiElement): KtFile {
val javaFile = javaContext.containingFile as? PsiJavaFile val javaFile = javaContext.containingFile as? PsiJavaFile
@@ -419,25 +369,4 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
return KtPsiFactory(javaContext.project).createAnalyzableFile("fakeFileForJavaContextInDebugger.kt", sb.toString(), javaContext) return KtPsiFactory(javaContext.project).createAnalyzableFile("fakeFileForJavaContextInDebugger.kt", sb.toString(), javaContext)
} }
// internal for test
private fun createWrappingContext(
newFragmentText: String,
labels: Map<String, Value>,
originalContext: KtElement?,
project: Project
): KtElement? {
val codeFragment = KtPsiFactory(project).createBlockCodeFragment(newFragmentText, originalContext)
codeFragment.accept(object : KtTreeVisitorVoid() {
override fun visitProperty(property: KtProperty) {
val reference = labels[property.name]
if (reference != null) {
property.putUserData(LABEL_VARIABLE_VALUE_KEY, reference)
}
}
})
return codeFragment.getContentElement().statements.lastOrNull()
}
} }
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.idea.debugger.evaluate
import com.intellij.debugger.SourcePosition import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.evaluation.EvaluateException import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.openapi.components.ServiceManager import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.libraries.LibraryUtil import com.intellij.openapi.roots.libraries.LibraryUtil
@@ -29,35 +28,31 @@ import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.PsiModificationTracker
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.containers.MultiMap import com.intellij.util.containers.MultiMap
import org.apache.log4j.Logger
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import org.apache.log4j.Logger
import org.jetbrains.eval4j.Value import org.jetbrains.eval4j.Value
import org.jetbrains.eval4j.jdi.asValue
import org.jetbrains.kotlin.analyzer.AnalysisResult import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.codegen.ClassBuilderFactories import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
import org.jetbrains.kotlin.idea.debugger.BinaryCacheKey import org.jetbrains.kotlin.idea.debugger.BinaryCacheKey
import org.jetbrains.kotlin.idea.debugger.BytecodeDebugInfo import org.jetbrains.kotlin.idea.debugger.BytecodeDebugInfo
import org.jetbrains.kotlin.idea.debugger.createWeakBytecodeDebugInfoStorage import org.jetbrains.kotlin.idea.debugger.createWeakBytecodeDebugInfoStorage
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CompiledDataDescriptor
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.KtCodeFragment import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
class KotlinDebuggerCaches(project: Project) { class KotlinDebuggerCaches(project: Project) {
private val cachedCompiledData = CachedValuesManager.getManager(project).createCachedValue( private val cachedCompiledData = CachedValuesManager.getManager(project).createCachedValue(
{ {
CachedValueProvider.Result<MultiMap<String, CompiledDataDescriptor>>( CachedValueProvider.Result<MultiMap<String, CompiledDataDescriptor>>(
@@ -98,37 +93,39 @@ class KotlinDebuggerCaches(project: Project) {
fun getInstance(project: Project) = ServiceManager.getService(project, KotlinDebuggerCaches::class.java)!! fun getInstance(project: Project) = ServiceManager.getService(project, KotlinDebuggerCaches::class.java)!!
fun getOrCreateCompiledData( fun compileCodeFragmentCacheAware(
codeFragment: KtCodeFragment, codeFragment: KtCodeFragment,
sourcePosition: SourcePosition, sourcePosition: SourcePosition,
evaluationContext: EvaluationContextImpl, compileCode: () -> CompiledDataDescriptor,
create: (KtCodeFragment, SourcePosition) -> CompiledDataDescriptor force: Boolean = false
): CompiledDataDescriptor { ): Pair<CompiledDataDescriptor, Boolean> {
val evaluateExpressionCache = getInstance(codeFragment.project) val evaluateExpressionCache = getInstance(codeFragment.project)
val text = "${codeFragment.importsToString()}\n${codeFragment.text}" val text = "${codeFragment.importsToString()}\n${codeFragment.text}"
val cached = synchronized<Collection<CompiledDataDescriptor>>(evaluateExpressionCache.cachedCompiledData) { val cachedResults = synchronized<Collection<CompiledDataDescriptor>>(evaluateExpressionCache.cachedCompiledData) {
val cache = evaluateExpressionCache.cachedCompiledData.value!! evaluateExpressionCache.cachedCompiledData.value[text]
cache[text]
} }
val answer = cached.firstOrNull { val existingResult = cachedResults.firstOrNull { it.sourcePosition == sourcePosition }
it.sourcePosition == sourcePosition || evaluateExpressionCache.canBeEvaluatedInThisContext(it, evaluationContext) if (existingResult != null) {
} if (force) {
if (answer != null) { synchronized(evaluateExpressionCache.cachedCompiledData) {
return answer evaluateExpressionCache.cachedCompiledData.value.remove(text, existingResult)
}
} else {
return Pair(existingResult, true)
}
} }
val newCompiledData = create(codeFragment, sourcePosition) val newCompiledData = compileCode()
LOG.debug("Compile bytecode for ${codeFragment.text}") LOG.debug("Compile bytecode for ${codeFragment.text}")
synchronized(evaluateExpressionCache.cachedCompiledData) { synchronized(evaluateExpressionCache.cachedCompiledData) {
evaluateExpressionCache.cachedCompiledData.value.putValue(text, newCompiledData) evaluateExpressionCache.cachedCompiledData.value.putValue(text, newCompiledData)
} }
return newCompiledData return Pair(newCompiledData, false)
} }
fun <T : PsiElement> getOrComputeClassNames(psiElement: T?, create: (T) -> ComputedClassNames): List<String> { fun <T : PsiElement> getOrComputeClassNames(psiElement: T?, create: (T) -> ComputedClassNames): List<String> {
@@ -215,32 +212,6 @@ class KotlinDebuggerCaches(project: Project) {
} }
} }
private fun canBeEvaluatedInThisContext(compiledData: CompiledDataDescriptor, context: EvaluationContextImpl): Boolean {
val variableFinder = VariableFinder.instance(context) ?: return false
return compiledData.parameters.all { p ->
val (name, jetType) = p
val lookupResult = variableFinder.find(name, null) ?: return@all false
val value = lookupResult.value.asValue()
val thisDescriptor = value.asmType.getClassDescriptor(context.debugProcess.searchScope)
val superClassDescriptor = jetType.constructor.declarationDescriptor as? ClassDescriptor
return@all thisDescriptor != null && superClassDescriptor != null && runReadAction {
DescriptorUtils.isSubclass(
thisDescriptor,
superClassDescriptor
)
}
}
}
data class CompiledDataDescriptor(
val classes: List<ClassToLoad>,
val sourcePosition: SourcePosition,
val parameters: List<Parameter>,
val variablesCrossingInlineBounds: Set<String>
)
data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null, val error: EvaluateException? = null) data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null, val error: EvaluateException? = null)
class ComputedClassNames(val classNames: List<String>, val shouldBeCached: Boolean) { class ComputedClassNames(val classNames: List<String>, val shouldBeCached: Boolean) {
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.idea.debugger.evaluate package org.jetbrains.kotlin.idea.debugger.evaluate
import com.intellij.debugger.SourcePosition import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.SuspendContext import com.intellij.debugger.engine.SuspendContext
import com.intellij.debugger.engine.evaluation.EvaluateException import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
@@ -27,21 +28,17 @@ import com.intellij.openapi.diagnostic.Attachment
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.DumbService
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.psi.JavaPsiFacade import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.testFramework.LightVirtualFile
import com.intellij.testFramework.runInEdtAndWait import com.intellij.testFramework.runInEdtAndWait
import com.intellij.util.ExceptionUtil import com.intellij.util.ExceptionUtil
import com.sun.jdi.* import com.sun.jdi.*
import com.sun.jdi.Value
import com.sun.jdi.request.EventRequest import com.sun.jdi.request.EventRequest
import org.jetbrains.eval4j.* import org.jetbrains.eval4j.*
import org.jetbrains.eval4j.Value import org.jetbrains.eval4j.Value as Eval4JValue
import org.jetbrains.eval4j.jdi.JDIEval import org.jetbrains.eval4j.jdi.JDIEval
import org.jetbrains.eval4j.jdi.asJdiValue import org.jetbrains.eval4j.jdi.asJdiValue
import org.jetbrains.eval4j.jdi.asValue import org.jetbrains.eval4j.jdi.asValue
@@ -50,57 +47,39 @@ import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.AsmUtil.LABELED_THIS_PARAMETER
import org.jetbrains.kotlin.codegen.AsmUtil.RECEIVER_PARAMETER_NAME
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.diagnostics.Severity import org.jetbrains.kotlin.diagnostics.Severity
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaClassDescriptor import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaClassDescriptor
import org.jetbrains.kotlin.idea.core.quoteSegmentsIfNeeded
import org.jetbrains.kotlin.idea.debugger.DebuggerUtils import org.jetbrains.kotlin.idea.debugger.DebuggerUtils
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.* import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companion.compileCodeFragmentCacheAware
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.*
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely
import org.jetbrains.kotlin.idea.debugger.getBackingFieldName import org.jetbrains.kotlin.idea.debugger.evaluate.variables.EvaluatorValueConverter
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionResult import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.attachment.attachmentByPsiFile import org.jetbrains.kotlin.idea.util.attachment.attachmentByPsiFile
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.codeFragmentUtil.debugTypeInfo
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
import org.jetbrains.kotlin.resolve.AnalyzingUtils import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.isInlineClassType import org.jetbrains.kotlin.resolve.isInlineClassType
import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.org.objectweb.asm.* import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.Opcodes.API_VERSION
import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.ClassNode import org.jetbrains.org.objectweb.asm.tree.ClassNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import java.util.* import java.util.*
internal val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluator") internal val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluator")
internal const val GENERATED_FUNCTION_NAME = "generated_for_debugger_fun" internal const val GENERATED_FUNCTION_NAME = "generated_for_debugger_fun"
internal const val GENERATED_CLASS_NAME = "Generated_for_debugger_class" internal const val GENERATED_CLASS_NAME = "Generated_for_debugger_class"
private const val DEBUG_MODE = false
object KotlinEvaluationBuilder : EvaluatorBuilder { object KotlinEvaluationBuilder : EvaluatorBuilder {
override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator { override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator {
if (codeFragment !is KtCodeFragment || position == null) { if (codeFragment !is KtCodeFragment || position == null) {
@@ -108,14 +87,14 @@ object KotlinEvaluationBuilder : EvaluatorBuilder {
} }
if (position.line < 0) { if (position.line < 0) {
throw EvaluateExceptionUtil.createEvaluateException("Couldn't evaluate kotlin expression at $position") evaluationException("Couldn't evaluate kotlin expression at $position")
} }
val file = position.file val file = position.file
if (file is KtFile) { if (file is KtFile) {
val document = PsiDocumentManager.getInstance(file.project).getDocument(file) val document = PsiDocumentManager.getInstance(file.project).getDocument(file)
if (document == null || document.lineCount < position.line) { if (document == null || document.lineCount < position.line) {
throw EvaluateExceptionUtil.createEvaluateException( evaluationException(
"Couldn't evaluate kotlin expression: breakpoint is placed outside the file. " + "Couldn't evaluate kotlin expression: breakpoint is placed outside the file. " +
"It may happen when you've changed source file after starting a debug process." "It may happen when you've changed source file after starting a debug process."
) )
@@ -133,7 +112,7 @@ object KotlinEvaluationBuilder : EvaluatorBuilder {
"Trying to evaluate ${codeFragment::class.java} with context ${codeFragment.context?.javaClass}", "Trying to evaluate ${codeFragment::class.java} with context ${codeFragment.context?.javaClass}",
mergeAttachments(*attachments) mergeAttachments(*attachments)
) )
throw EvaluateExceptionUtil.createEvaluateException("Couldn't evaluate kotlin expression in this context") evaluationException("Couldn't evaluate kotlin expression in this context")
} }
return ExpressionEvaluatorImpl(KotlinEvaluator(codeFragment, position)) return ExpressionEvaluatorImpl(KotlinEvaluator(codeFragment, position))
@@ -147,44 +126,21 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
} }
if (DumbService.getInstance(codeFragment.project).isDumb) { if (DumbService.getInstance(codeFragment.project).isDumb) {
throw EvaluateExceptionUtil.createEvaluateException("Code fragment evaluation is not available in the dumb mode") evaluationException("Code fragment evaluation is not available in the dumb mode")
} }
var isCompiledDataFromCache = true
try { try {
val compiledData = KotlinDebuggerCaches.getOrCreateCompiledData(codeFragment, sourcePosition, context) { fragment, position -> return evaluateSafe(context)
isCompiledDataFromCache = false
extractAndCompile(fragment, position, context)
}
val classLoaderRef = loadClassesSafely(context, compiledData.classes)
val result = if (classLoaderRef != null) {
evaluateWithCompilation(context, compiledData, classLoaderRef) ?: runEval4j(context, compiledData, classLoaderRef)
} else {
runEval4j(context, compiledData, classLoaderRef)
}
// If bytecode was taken from cache and exception was thrown - recompile bytecode and run eval4j again
if (isCompiledDataFromCache && result is ExceptionThrown && result.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE) {
return runEval4j(context, extractAndCompile(codeFragment, sourcePosition, context), classLoaderRef).toJdiValue(context)
}
return if (result is InterpreterResult) {
result.toJdiValue(context)
} else {
result
}
} catch (e: EvaluateException) { } catch (e: EvaluateException) {
throw e throw e
} catch (e: ProcessCanceledException) { } catch (e: ProcessCanceledException) {
exception(e) evaluationException(e)
} catch (e: Eval4JInterpretingException) { } catch (e: Eval4JInterpretingException) {
exception(e.cause) evaluationException(e.cause)
} catch (e: Exception) { } catch (e: Exception) {
val isSpecialException = isSpecialException(e) val isSpecialException = isSpecialException(e)
if (isSpecialException) { if (isSpecialException) {
exception(e) evaluationException(e)
} }
val text = runReadAction { codeFragment.context?.text ?: "null" } val text = runReadAction { codeFragment.context?.text ?: "null" }
@@ -196,6 +152,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
) )
LOG.error( LOG.error(
@Suppress("DEPRECATION")
LogMessageEx.createEvent( LogMessageEx.createEvent(
"Couldn't evaluate expression", "Couldn't evaluate expression",
ExceptionUtil.getThrowableText(e), ExceptionUtil.getThrowableText(e),
@@ -204,570 +161,254 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
) )
val cause = if (e.message != null) ": ${e.message}" else "" val cause = if (e.message != null) ": ${e.message}" else ""
exception("An exception occurs during Evaluate Expression Action $cause") evaluationException("An exception occurs during Evaluate Expression Action $cause")
} }
} }
private fun isSpecialException(th: Throwable): Boolean { private fun evaluateSafe(context: EvaluationContextImpl): Any? {
return when (th) { fun compilerFactory(): CompiledDataDescriptor = compileCodeFragment(context.debugProcess)
is ClassNotPreparedException,
is InternalException, val (compiledData, isCompiledDataFromCache) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory)
is AbsentInformationException, val classLoaderRef = loadClassesSafely(context, compiledData.classes)
is ClassNotLoadedException,
is IncompatibleThreadStateException, val thread = context.suspendContext.thread?.threadReference ?: error("Can not find a thread to run evaluation on")
is InconsistentDebugInfoException, val invokePolicy = context.suspendContext.getInvokePolicy()
is ObjectCollectedException, val executionContext = ExecutionContext(context, thread, invokePolicy)
is VMDisconnectedException -> true
else -> false val result = if (classLoaderRef != null) {
evaluateWithCompilation(executionContext, compiledData, classLoaderRef)
?: evaluateWithEval4J(executionContext, compiledData, classLoaderRef)
} else {
evaluateWithEval4J(executionContext, compiledData, classLoaderRef)
}
// If bytecode was taken from cache and exception was thrown - recompile bytecode and run eval4j again
if (isCompiledDataFromCache && result is ExceptionThrown && result.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE) {
val (recompiledData, _) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory, force = true)
return evaluateWithEval4J(executionContext, recompiledData, classLoaderRef).toJdiValue(executionContext)
}
return when (result) {
is InterpreterResult -> result.toJdiValue(executionContext)
else -> result
} }
} }
override fun getModifier(): Modifier? { private fun compileCodeFragment(debugProcess: DebugProcessImpl): CompiledDataDescriptor {
return null var analysisResult = checkForErrors(codeFragment, debugProcess)
}
companion object { if (codeFragment.wrapToStringIfNeeded(analysisResult.bindingContext)) {
private fun extractAndCompile( // Repeat analysis with toString() added
codeFragment: KtCodeFragment, analysisResult = checkForErrors(codeFragment, debugProcess)
sourcePosition: SourcePosition, }
context: EvaluationContextImpl
): CompiledDataDescriptor {
var bindingContext = codeFragment.checkForErrors().bindingContext
if (codeFragment.wrapToStringIfNeeded(bindingContext)) { val (bindingContext) = runReadAction {
// Repeat analysis with toString() added DebuggerUtils.analyzeInlinedFunctions(
bindingContext = codeFragment.checkForErrors().bindingContext KotlinCacheService.getInstance(codeFragment.project).getResolutionFacade(listOf(codeFragment)),
} codeFragment, false, analysisResult.bindingContext
val variablesCrossingInlineBounds = ScopeCheckerForEvaluator.checkScopes(bindingContext, codeFragment)
val extractionResult = getFunctionForExtractedFragment(codeFragment, sourcePosition.file, sourcePosition.line)
?: throw IllegalStateException("Code fragment cannot be extracted to function: ${codeFragment.text}")
val (parametersDescriptor, extractedFunction) = try {
extractionResult.getParametersForDebugger(codeFragment, context) to extractionResult.declaration as KtNamedFunction
} finally {
Disposer.dispose(extractionResult)
}
if (LOG.isDebugEnabled) {
LOG.debug("Extracted function:\n" + runReadAction { extractedFunction.text })
}
val classFileFactory = createClassFileFactory(codeFragment, extractedFunction, context, parametersDescriptor)
val outputFiles = classFileFactory.asList().filterClassFiles()
for (file in outputFiles) {
if (LOG.isDebugEnabled) {
LOG.debug("Output file generated: ${file.relativePath}")
}
@Suppress("ConstantConditionIf")
if (DEBUG_MODE) {
println(file.asText())
}
}
val additionalFiles = outputFiles.map { ClassToLoad(getClassName(it.relativePath), it.relativePath, it.asByteArray()) }
return CompiledDataDescriptor(
additionalFiles,
sourcePosition,
parametersDescriptor,
variablesCrossingInlineBounds
) )
} }
private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean { val moduleDescriptor = analysisResult.moduleDescriptor
if (this !is KtExpressionCodeFragment) {
return false
}
val contentElement = runReadAction { getContentElement() } val result = CodeFragmentCompiler.compile(codeFragment, bindingContext, moduleDescriptor)
val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, contentElement]?.type return CompiledDataDescriptor.from(result, sourcePosition)
if (contentElement != null && expressionType?.isInlineClassType() == true) { }
val newExpression = runReadAction {
val expressionText = contentElement.text
KtPsiFactory(project).createExpression("($expressionText).toString()")
}
runInEdtAndWait {
project.executeWriteCommand("Wrap with 'toString()'") {
contentElement.replace(newExpression)
}
}
return true
}
private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean {
if (this !is KtExpressionCodeFragment) {
return false return false
} }
private fun getClassName(fileName: String): String { val contentElement = runReadAction { getContentElement() }
return fileName.substringBeforeLast(".class").replace("/", ".") val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, contentElement]?.type
if (contentElement != null && expressionType?.isInlineClassType() == true) {
val newExpression = runReadAction {
val expressionText = contentElement.text
KtPsiFactory(project).createExpression("($expressionText).toString()")
}
runInEdtAndWait {
project.executeWriteCommand("Wrap with 'toString()'") {
contentElement.replace(newExpression)
}
}
return true
} }
private val CompiledDataDescriptor.mainClass return false
get() = classes.firstOrNull { it.isMainClass() } ?: error( }
"Can't find main class for " + sourcePosition.elementAt.getParentOfType<KtDeclaration>(strict = false)
)
private fun evaluateWithCompilation( private data class ErrorCheckingResult(
context: EvaluationContextImpl, val bindingContext: BindingContext,
compiledData: CompiledDataDescriptor, val moduleDescriptor: ModuleDescriptor,
classLoader: ClassLoaderReference val files: List<KtFile>
): Any? { )
val vm = context.debugProcess.virtualMachineProxy.virtualMachine
val mainClassBytecode = compiledData.mainClass.bytes
private fun checkForErrors(codeFragment: KtCodeFragment, debugProcess: DebugProcessImpl): ErrorCheckingResult {
return runInReadActionWithWriteActionPriorityWithPCE {
try { try {
val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, ClassReader.SKIP_CODE) } AnalyzingUtils.checkForSyntacticErrors(codeFragment)
assert(mainClassAsmNode.methods.size == 1) } catch (e: IllegalArgumentException) {
evaluationException(e.message ?: e.toString())
val methodToInvoke = mainClassAsmNode.methods[0]
assert(methodToInvoke.parameters == null || methodToInvoke.parameters.isEmpty())
val thread = context.suspendContext.thread?.threadReference!!
val invokePolicy = context.suspendContext.getInvokePolicy()
val eval = JDIEval(vm, classLoader, thread, invokePolicy)
val mainClassValue = (eval.loadClass(Type.getObjectType(mainClassAsmNode.name), classLoader) as? ObjectValue)
val mainClass = (mainClassValue?.value as? ClassObjectReference)?.reflectedType() as? ClassType ?: return null
// Preload all classes
compiledData.classes.asSequence()
.filter { !it.isMainClass() }
.forEach { eval.loadClass(Type.getObjectType(it.className), classLoader) }
return vm.executeWithBreakpointsDisabled {
// Prepare the main class
val argumentTypes = Type.getArgumentTypes(methodToInvoke.desc)
val args = context.getArgumentsForEvaluation(compiledData.parameters, argumentTypes, compiledData)
.zip(argumentTypes)
.map { (value, type) ->
// Make argument type classes prepared for sure
eval.loadClassByName(type.className, classLoader)
boxOrUnboxArgumentIfNeeded(eval, value, type).asJdiValue(vm, type)
}
mainClass.invokeMethod(thread, mainClass.methods().single(), args, invokePolicy)
}
} catch (e: Throwable) {
LOG.error("Unable to evaluate expression with compilation", e)
return null
} }
}
private fun runEval4j( val filesToAnalyze = listOf(codeFragment)
context: EvaluationContextImpl, val resolutionFacade = KotlinCacheService.getInstance(codeFragment.project).getResolutionFacade(filesToAnalyze)
compiledData: CompiledDataDescriptor,
classLoader: ClassLoaderReference?
): InterpreterResult {
val virtualMachine = context.debugProcess.virtualMachineProxy.virtualMachine
var resultValue: InterpreterResult? = null
// assert [0] with some context DebugLabelPropertyDescriptorProvider(codeFragment, resolutionFacade.moduleDescriptor, debugProcess).supplyDebugLabels()
val mainClassBytecode = compiledData.mainClass.bytes
ClassReader(mainClassBytecode).accept(object : ClassVisitor(API_VERSION) { val analysisResult = resolutionFacade.analyzeWithAllCompilerChecks(filesToAnalyze)
override fun visitMethod(
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor? {
// Maybe just take the single method from the class, as it is done in 'evaluateWithCompilation'
@Suppress("ConvertToStringTemplate")
if (name == GENERATED_FUNCTION_NAME || name.startsWith(GENERATED_FUNCTION_NAME + "-")) {
val argumentTypes = Type.getArgumentTypes(desc)
val args = context.getArgumentsForEvaluation(compiledData.parameters, argumentTypes, compiledData)
return object : MethodNode(Opcodes.API_VERSION, access, name, desc, signature, exceptions) { if (analysisResult.isError()) {
override fun visitEnd() { evaluationException(analysisResult.error)
virtualMachine.executeWithBreakpointsDisabled {
val eval = JDIEval(
virtualMachine,
classLoader ?: context.classLoader,
context.suspendContext.thread?.threadReference!!,
context.suspendContext.getInvokePolicy()
)
resultValue = interpreterLoop(
this,
makeInitialFrame(
this,
args.zip(argumentTypes).map {
boxOrUnboxArgumentIfNeeded(
eval,
it.first,
it.second
)
}),
eval
)
}
}
}
}
return super.visitMethod(access, name, desc, signature, exceptions)
}
}, 0)
return resultValue ?: throw IllegalStateException("resultValue is null: cannot find method $GENERATED_FUNCTION_NAME")
}
private inline fun <T> VirtualMachine.executeWithBreakpointsDisabled(block: () -> T): T {
val allRequests = eventRequestManager().breakpointRequests() + eventRequestManager().classPrepareRequests()
try {
allRequests.forEach { it.disable() }
return block()
} finally {
allRequests.forEach { it.enable() }
} }
val bindingContext = analysisResult.bindingContext
bindingContext.diagnostics
.filter { it.factory !in IGNORED_DIAGNOSTICS }
.firstOrNull { it.severity == Severity.ERROR && it.psiElement.containingFile == codeFragment }
?.let { evaluationException(DefaultErrorMessages.render(it)) }
ErrorCheckingResult(bindingContext, analysisResult.moduleDescriptor, Collections.singletonList(codeFragment))
} }
}
private fun boxOrUnboxArgumentIfNeeded(eval: JDIEval, argumentValue: Value, parameterType: Type): Value { private fun evaluateWithCompilation(
val argumentType = argumentValue.asmType context: ExecutionContext,
compiledData: CompiledDataDescriptor,
classLoader: ClassLoaderReference
): Value? {
return try {
runEvaluation(context, compiledData, classLoader) { args ->
val mainClassType = context.loadClassType(Type.getObjectType(GENERATED_CLASS_NAME), classLoader) as? ClassType
?: error("Can not find class \"$GENERATED_CLASS_NAME\"")
val mainMethod = mainClassType.methods().single { it.name() == GENERATED_FUNCTION_NAME }
val returnValue = mainClassType.invokeMethod(context.thread, mainMethod, args, context.invokePolicy)
EvaluatorValueConverter(context).unref(returnValue)
}
} catch (e: Throwable) {
LOG.error("Unable to evaluate expression with compilation", e)
return null
}
}
if (AsmUtil.isPrimitive(parameterType) && !AsmUtil.isPrimitive(argumentType)) { private fun evaluateWithEval4J(
try { context: ExecutionContext,
val unboxedType = AsmUtil.unboxType(argumentType) compiledData: CompiledDataDescriptor,
if (parameterType == unboxedType) { classLoader: ClassLoaderReference?
return eval.unboxType(argumentValue, parameterType) ): InterpreterResult {
} val mainClassBytecode = compiledData.mainClass.bytes
} catch (ignored: UnsupportedOperationException) { val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, 0) }
val mainMethod = mainClassAsmNode.methods.first { it.name == GENERATED_FUNCTION_NAME }
return runEvaluation(context, compiledData, classLoader ?: context.evaluationContext.classLoader) { args ->
val eval = JDIEval(context.vm, classLoader, context.thread, context.invokePolicy)
interpreterLoop(mainMethod, makeInitialFrame(mainMethod, args.map { it.asValue() }), eval)
}
}
private fun <T> runEvaluation(
context: ExecutionContext,
compiledData: CompiledDataDescriptor,
classLoader: ClassLoaderReference?,
block: (List<Value?>) -> T
): T {
// Preload additional classes
compiledData.classes
.filter { !it.isMainClass }
.forEach { context.loadClassType(Type.getObjectType(it.className), classLoader) }
return context.vm.executeWithBreakpointsDisabled {
for (parameterType in compiledData.mainMethodSignature.parameterTypes) {
context.loadClassType(parameterType, classLoader)
}
val args = context.calculateMainMethodCallArguments(compiledData)
block(args)
}
}
private fun ExecutionContext.calculateMainMethodCallArguments(compiledData: CompiledDataDescriptor): List<Value?> {
val asmValueParameters = compiledData.mainMethodSignature.parameterTypes
val valueParameters = compiledData.parameters
require(asmValueParameters.size == valueParameters.size)
val args = valueParameters.zip(asmValueParameters)
val variableFinder = VariableFinder.instance(this) ?: error("Frame map is not available")
return args.map { (parameter, asmType) ->
val result = variableFinder.find(parameter, asmType)
if (result == null) {
val name = parameter.debugString
if (parameter in compiledData.crossingBounds) {
evaluationException("'$name' is not captured")
} else if (parameter.kind == CodeFragmentParameter.Kind.FIELD_VAR) {
evaluationException("Cannot find the backing field '${parameter.name}'")
} else {
throw VariableFinder.variableNotFound(evaluationContext, buildString {
append("Cannot find local variable: name = '").append(name).append("', type = ").append(asmType.className)
})
} }
} }
if (!AsmUtil.isPrimitive(parameterType) && AsmUtil.isPrimitive(argumentType)) { result.value
if (parameterType.descriptor == "Ljava/lang/Object;" || parameterType == AsmUtil.boxType(argumentType)) {
return eval.boxType(argumentValue)
}
}
return argumentValue
} }
}
private fun InterpreterResult.toJdiValue(context: EvaluationContextImpl): com.sun.jdi.Value? { override fun getModifier() = null
companion object {
private val IGNORED_DIAGNOSTICS: Set<DiagnosticFactory<*>> = Errors.INVISIBLE_REFERENCE_DIAGNOSTICS
private fun InterpreterResult.toJdiValue(context: ExecutionContext): com.sun.jdi.Value? {
val jdiValue = when (this) { val jdiValue = when (this) {
is ValueReturned -> result is ValueReturned -> result
is ExceptionThrown -> { is ExceptionThrown -> {
when { when {
this.kind == ExceptionThrown.ExceptionKind.FROM_EVALUATED_CODE -> this.kind == ExceptionThrown.ExceptionKind.FROM_EVALUATED_CODE ->
exception(InvocationException(this.exception.value as ObjectReference)) evaluationException(InvocationException(this.exception.value as ObjectReference))
this.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE -> this.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE ->
throw exception.value as Throwable throw exception.value as Throwable
else -> else ->
exception(exception.toString()) evaluationException(exception.toString())
} }
} }
is AbnormalTermination -> exception(message) is AbnormalTermination -> evaluationException(message)
else -> throw IllegalStateException("Unknown result value produced by eval4j") else -> throw IllegalStateException("Unknown result value produced by eval4j")
} }
val vm = context.debugProcess.virtualMachineProxy.virtualMachine val sharedVar = if ((jdiValue is AbstractValue<*>)) getValueIfSharedVar(jdiValue, context) else null
return sharedVar?.value ?: jdiValue.asJdiValue(context.vm, jdiValue.asmType)
val sharedVar = getValueIfSharedVar(jdiValue)
return sharedVar?.value ?: jdiValue.asJdiValue(vm, jdiValue.asmType)
} }
private fun getValueIfSharedVar(value: Value): VariableFinder.Result? { private fun getValueIfSharedVar(value: Eval4JValue, context: ExecutionContext): VariableFinder.Result? {
val obj = value.obj(value.asmType) as? ObjectReference ?: return null val obj = value.obj(value.asmType) as? ObjectReference ?: return null
return VariableFinder.Result(VariableFinder.unwrapRefValue(obj)) return VariableFinder.Result(EvaluatorValueConverter(context).unref(obj))
} }
private fun ExtractionResult.getParametersForDebugger(
fragment: KtCodeFragment,
context: EvaluationContextImpl
): List<Parameter> {
return runReadAction {
val valuesForLabels = HashMap<String, Value>()
val contextElementFile = fragment.context?.containingFile
if (contextElementFile is KtCodeFragment) {
contextElementFile.accept(object : KtTreeVisitorVoid() {
override fun visitProperty(property: KtProperty) {
val value = property.getUserData(KotlinCodeFragmentFactory.LABEL_VARIABLE_VALUE_KEY)
if (value != null) {
valuesForLabels[property.name?.quoteIfNeeded()!!] = value.asValue()
}
}
})
}
val parameters = mutableListOf<Parameter>()
val receiver = config.descriptor.receiverParameter
if (receiver != null) {
parameters += Parameter(AsmUtil.THIS + "@" + config.descriptor.name, receiver.getParameterType(true))
}
for (param in config.descriptor.parameters) {
val argument = param.argumentText
val paramName = if (argument.startsWith("::")) argument.substring(2) else argument
val paramDescriptor = param.originalDescriptor
if (paramDescriptor is SyntheticFieldDescriptor) {
val backingFieldName = paramDescriptor.propertyDescriptor.getBackingFieldName()
if (backingFieldName != null) {
val thisObject = context.suspendContext.frameProxy?.thisObject()
val field = thisObject?.referenceType()?.fieldByName(backingFieldName)
val parameter = if (thisObject != null && field != null) {
Parameter(backingFieldName, param.getParameterType(true), thisObject.getValue(field).asValue())
} else {
Parameter(
backingFieldName, paramDescriptor.builtIns.unitType,
error = EvaluateException("Can't find a backing field for property ${paramDescriptor.name}")
)
}
parameters += parameter
continue
}
}
parameters += Parameter(paramName, param.getParameterType(true), valuesForLabels[paramName])
}
parameters
}
}
private fun EvaluationContextImpl.getArgumentsForEvaluation(
parameters: List<Parameter>,
parameterTypes: Array<Type>,
compiledData: CompiledDataDescriptor
): List<Value> {
val variableFinder = VariableFinder.instance(this) ?: error("No stack frame available")
return parameters.zip(parameterTypes).map { (parameter, type) ->
parameter.error?.let { throw it }
parameter.value?.let { return@map it }
val name = parameter.callText
val result = variableFinder.find(name, type)
if (result == null) {
if (name in compiledData.variablesCrossingInlineBounds) {
throw EvaluateExceptionUtil.createEvaluateException("'$name' is not captured")
} else {
throw VariableFinder.variableNotFound(this, buildString {
append("Cannot find local variable: name = '").append(name).append("', type = ").append(type.className)
})
}
} else {
return@map result.value.asValue()
}
}
}
private fun createClassFileFactory(
codeFragment: KtCodeFragment,
extractedFunction: KtNamedFunction,
context: EvaluationContextImpl,
parameters: List<Parameter>
): ClassFileFactory {
return runReadAction {
val fileForDebugger = createFileForDebugger(codeFragment, extractedFunction)
if (LOG.isDebugEnabled) {
LOG.debug("File for eval4j:\n${runReadAction { fileForDebugger.text }}")
}
val (bindingContext, moduleDescriptor, files) = fileForDebugger.checkForErrors(
true,
codeFragment.getContextContainingFile()
)
val generateClassFilter = object : GenerationState.GenerateClassFilter() {
override fun shouldGeneratePackagePart(ktFile: KtFile) = ktFile == fileForDebugger
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject) = true
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) =
processingClassOrObject.containingKtFile == fileForDebugger
override fun shouldGenerateScript(script: KtScript) = false
}
@Suppress("ConstantConditionIf")
val state = GenerationState.Builder(
fileForDebugger.project,
if (!DEBUG_MODE) ClassBuilderFactories.BINARIES else ClassBuilderFactories.TEST,
moduleDescriptor,
bindingContext,
files,
CompilerConfiguration.EMPTY
).generateDeclaredClassFilter(generateClassFilter).build()
val variableFinder = VariableFinder.instance(context) ?: error("No stack frame available")
val extractedFunctionName = extractedFunction.name
?: error("Extracted function has an empty name: ${extractedFunction.text}")
extractedFunction.receiverTypeReference?.let {
val name = AsmUtil.getLabeledThisName(extractedFunctionName, LABELED_THIS_PARAMETER, RECEIVER_PARAMETER_NAME)
state.bindingTrace.recordAnonymousType(it, name, variableFinder)
}
val valueParameters = extractedFunction.valueParameters
for ((paramIndex, param) in parameters.withIndex()) {
val valueParameter = valueParameters[paramIndex]
val paramRef = valueParameter.typeReference
if (paramRef == null) {
LOG.error(
"Each parameter for extracted function should have a type reference",
Attachment("codeFragment.txt", codeFragment.text),
Attachment("extractedFunction.txt", extractedFunction.text)
)
exception("An exception occurs during Evaluate Expression Action")
}
state.bindingTrace.recordAnonymousType(paramRef, param.callText, variableFinder)
}
KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION)
state.factory
}
}
private fun BindingTrace.recordAnonymousType(
typeReference: KtTypeReference,
localVariableName: String,
variableFinder: VariableFinder
) {
val paramAnonymousType = typeReference.debugTypeInfo
if (paramAnonymousType != null) {
val declarationDescriptor = paramAnonymousType.constructor.declarationDescriptor
if (declarationDescriptor is ClassDescriptor) {
val lookupResult = variableFinder.find(localVariableName, null)
?: exception("Couldn't find local variable this in current frame to get classType for anonymous type $paramAnonymousType}")
val localVariable = lookupResult.value.asValue()
record(CodegenBinding.ASM_TYPE, declarationDescriptor, localVariable.asmType)
if (LOG.isDebugEnabled) {
LOG.debug("Asm type ${localVariable.asmType.className} was recorded for ${declarationDescriptor.name}")
}
}
}
}
private fun exception(msg: String): Nothing = throw EvaluateExceptionUtil.createEvaluateException(msg)
private fun exception(e: Throwable): Nothing = throw EvaluateExceptionUtil.createEvaluateException(e)
private val IGNORED_DIAGNOSTICS: Set<DiagnosticFactory<*>> = Errors.INVISIBLE_REFERENCE_DIAGNOSTICS
// contextFile must be NotNull when analyzeInlineFunctions = true
private fun KtFile.checkForErrors(analyzeInlineFunctions: Boolean = false, contextFile: KtFile? = null): ExtendedAnalysisResult {
return runInReadActionWithWriteActionPriorityWithPCE {
try {
AnalyzingUtils.checkForSyntacticErrors(this)
} catch (e: IllegalArgumentException) {
throw EvaluateExceptionUtil.createEvaluateException(e.message)
}
val filesToAnalyze = if (contextFile == null) listOf(this) else listOf(this, contextFile)
val resolutionFacade = KotlinCacheService.getInstance(project).getResolutionFacade(filesToAnalyze)
val analysisResult = resolutionFacade.analyzeWithAllCompilerChecks(filesToAnalyze)
if (analysisResult.isError()) {
exception(analysisResult.error)
}
val bindingContext = analysisResult.bindingContext
val filteredDiagnostics = bindingContext.diagnostics.filter { it.factory !in IGNORED_DIAGNOSTICS }
filteredDiagnostics.firstOrNull { it.severity == Severity.ERROR }?.let {
if (it.psiElement.containingFile == this) {
exception(DefaultErrorMessages.render(it))
}
}
if (analyzeInlineFunctions) {
val (newBindingContext, files) = DebuggerUtils.analyzeInlinedFunctions(resolutionFacade, this, false)
ExtendedAnalysisResult(newBindingContext, analysisResult.moduleDescriptor, files)
} else {
ExtendedAnalysisResult(bindingContext, analysisResult.moduleDescriptor, Collections.singletonList(this))
}
}
}
private data class ExtendedAnalysisResult(
val bindingContext: BindingContext,
val moduleDescriptor: ModuleDescriptor,
val files: List<KtFile>
)
} }
} }
private const val template = """
@file:kotlin.jvm.JvmName("$GENERATED_CLASS_NAME")
!PACKAGE!
!IMPORT_LIST!
!FUNCTION!
"""
private fun createFileForDebugger(
codeFragment: KtCodeFragment,
extractedFunction: KtNamedFunction
): KtFile {
val containingContextFile = codeFragment.getContextContainingFile()
val importsFromContextFile = containingContextFile?.importList?.let { it.text + "\n" } ?: ""
var fileText = template.replace(
"!IMPORT_LIST!",
importsFromContextFile + codeFragment.importsToString().split(KtCodeFragment.IMPORT_SEPARATOR).joinToString("\n")
)
val packageFromContextFile = containingContextFile?.packageFqName?.let {
if (!it.isRoot) "package ${it.quoteSegmentsIfNeeded()}" else ""
} ?: ""
fileText = fileText.replace("!PACKAGE!", packageFromContextFile)
fileText = fileText.replace("!FUNCTION!", extractedFunction.text!!)
val jetFile = codeFragment.createKtFile("debugFile.kt", fileText)
jetFile.suppressDiagnosticsInDebugMode = true
val list = jetFile.declarations
val function = list[0] as KtNamedFunction
function.receiverTypeReference?.debugTypeInfo = extractedFunction.receiverTypeReference?.debugTypeInfo
for ((newParam, oldParam) in function.valueParameters.zip(extractedFunction.valueParameters)) {
newParam.typeReference?.debugTypeInfo = oldParam.typeReference?.debugTypeInfo
}
function.typeReference?.debugTypeInfo = extractedFunction.typeReference?.debugTypeInfo
return jetFile
}
private fun PsiElement.createKtFile(fileName: String, fileText: String): KtFile {
// Not using KtPsiFactory because we need a virtual file attached to the KtFile
val virtualFile = LightVirtualFile(fileName, KotlinLanguage.INSTANCE, fileText)
virtualFile.charset = CharsetToolkit.UTF8_CHARSET
val jetFile = (PsiFileFactory.getInstance(project) as PsiFileFactoryImpl)
.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile
jetFile.analysisContext = this
return jetFile
}
internal fun SuspendContext.getInvokePolicy(): Int { internal fun SuspendContext.getInvokePolicy(): Int {
return if (suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD) ObjectReference.INVOKE_SINGLE_THREADED else 0 return if (suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD) ObjectReference.INVOKE_SINGLE_THREADED else 0
} }
fun Type.getClassDescriptor(scope: GlobalSearchScope): ClassDescriptor? { fun Type.getClassDescriptor(
scope: GlobalSearchScope,
mapBuiltIns: Boolean = true,
moduleDescriptor: ModuleDescriptor = DefaultBuiltIns.Instance.builtInsModule
): ClassDescriptor? {
if (AsmUtil.isPrimitive(this)) return null if (AsmUtil.isPrimitive(this)) return null
val jvmName = JvmClassName.byInternalName(internalName).fqNameForClassNameWithoutDollars val jvmName = JvmClassName.byInternalName(internalName).fqNameForClassNameWithoutDollars
// TODO: use the correct built-ins from the module instead of DefaultBuiltIns here if (mapBuiltIns) {
JavaToKotlinClassMap.mapJavaToKotlin(jvmName)?.let( val mappedName = JavaToKotlinClassMap.mapJavaToKotlin(jvmName)
DefaultBuiltIns.Instance.builtInsModule::findClassAcrossModuleDependencies if (mappedName != null) {
)?.let { return it } moduleDescriptor.findClassAcrossModuleDependencies(mappedName)?.let { return it }
}
}
return runReadAction { return runReadAction {
val classes = JavaPsiFacade.getInstance(scope.project).findClasses(jvmName.asString(), scope) val classes = JavaPsiFacade.getInstance(scope.project).findClasses(jvmName.asString(), scope)
@@ -777,3 +418,31 @@ fun Type.getClassDescriptor(scope: GlobalSearchScope): ClassDescriptor? {
} }
} }
} }
private fun <T> VirtualMachine.executeWithBreakpointsDisabled(block: () -> T): T {
val allRequests = eventRequestManager().breakpointRequests() + eventRequestManager().classPrepareRequests()
try {
allRequests.forEach { it.disable() }
return block()
} finally {
allRequests.forEach { it.enable() }
}
}
private fun isSpecialException(th: Throwable): Boolean {
return when (th) {
is ClassNotPreparedException,
is InternalException,
is AbsentInformationException,
is ClassNotLoadedException,
is IncompatibleThreadStateException,
is InconsistentDebugInfoException,
is ObjectCollectedException,
is VMDisconnectedException -> true
else -> false
}
}
private fun evaluationException(msg: String): Nothing = throw EvaluateExceptionUtil.createEvaluateException(msg)
private fun evaluationException(e: Throwable): Nothing = throw EvaluateExceptionUtil.createEvaluateException(e)
@@ -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)
}
}
@@ -23,6 +23,7 @@ import com.sun.jdi.ArrayReference
import com.sun.jdi.ArrayType import com.sun.jdi.ArrayType
import com.sun.jdi.ClassLoaderReference import com.sun.jdi.ClassLoaderReference
import com.sun.jdi.Value import com.sun.jdi.Value
import org.jetbrains.kotlin.idea.debugger.evaluate.GENERATED_FUNCTION_NAME
import org.jetbrains.org.objectweb.asm.ClassReader import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.Label import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.tree.* import org.jetbrains.org.objectweb.asm.tree.*
@@ -38,7 +39,7 @@ interface ClassLoadingAdapter {
) )
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? { fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderReference? {
val mainClass = classes.firstOrNull { it.isMainClass() } ?: return null val mainClass = classes.firstOrNull { it.isMainClass } ?: return null
var info = ClassInfoForEvaluator(containsAdditionalClasses = classes.size > 1) var info = ClassInfoForEvaluator(containsAdditionalClasses = classes.size > 1)
if (!info.containsAdditionalClasses) { if (!info.containsAdditionalClasses) {
@@ -64,8 +65,8 @@ interface ClassLoadingAdapter {
} }
private fun analyzeClass(classToLoad: ClassToLoad, info: ClassInfoForEvaluator): ClassInfoForEvaluator { private fun analyzeClass(classToLoad: ClassToLoad, info: ClassInfoForEvaluator): ClassInfoForEvaluator {
val classNode = ClassNode().apply { ClassReader(classToLoad.bytes).accept(this, ClassReader.EXPAND_FRAMES) } val classNode = ClassNode().apply { ClassReader(classToLoad.bytes).accept(this, 0) }
val methodToRun = classNode.methods.single() val methodToRun = classNode.methods.single { it.name == GENERATED_FUNCTION_NAME }
val visitedLabels = hashSetOf<Label>() val visitedLabels = hashSetOf<Label>()
@@ -20,5 +20,6 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.GENERATED_CLASS_NAME
@Suppress("ArrayInDataClass") @Suppress("ArrayInDataClass")
data class ClassToLoad(val className: String, val relativeFileName: String, val bytes: ByteArray) { data class ClassToLoad(val className: String, val relativeFileName: String, val bytes: ByteArray) {
fun isMainClass() = className.endsWith(GENERATED_CLASS_NAME) val isMainClass: Boolean
get() = className == GENERATED_CLASS_NAME
} }
@@ -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('/', '.')
@@ -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
}
}
@@ -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 }
@@ -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
)
@@ -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
}
@@ -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)
@@ -1,9 +1,9 @@
/* /*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license * Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file. * that can be found in the license/LICENSE.txt file.
*/ */
package org.jetbrains.kotlin.idea.debugger.evaluate package org.jetbrains.kotlin.idea.debugger.evaluate.variables
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
@@ -17,24 +17,26 @@ import org.jetbrains.kotlin.codegen.AsmUtil.getLabeledThisName
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX
import org.jetbrains.kotlin.codegen.inline.INLINE_TRANSFORMATION_SUFFIX import org.jetbrains.kotlin.codegen.inline.INLINE_TRANSFORMATION_SUFFIX
import org.jetbrains.kotlin.codegen.inline.NUMBERED_FUNCTION_PREFIX
import org.jetbrains.kotlin.codegen.topLevelClassAsmType
import org.jetbrains.kotlin.idea.debugger.* import org.jetbrains.kotlin.idea.debugger.*
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.DebugLabelPropertyDescriptorProvider
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments
import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_3_FQ_NAME import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT
import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.JvmClassName import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import org.jetbrains.org.objectweb.asm.Type as AsmType import org.jetbrains.org.objectweb.asm.Type as AsmType
import com.sun.jdi.Type as JdiType import com.sun.jdi.Type as JdiType
class VariableFinder private constructor(private val context: EvaluationContextImpl, private val frameProxy: StackFrameProxyImpl) { class VariableFinder private constructor(private val context: ExecutionContext, private val frameProxy: StackFrameProxyImpl) {
companion object { companion object {
private val COROUTINE_CONTEXT_SIMPLE_NAME = COROUTINE_CONTEXT_1_3_FQ_NAME.shortName().asString() private const val USE_UNSAFE_FALLBACK = true
val CONTINUATION_TYPE: AsmType = AsmType.getType(Continuation::class.java) val CONTINUATION_TYPE: AsmType = AsmType.getType(Continuation::class.java)
val SUSPEND_LAMBDA_CLASSES = listOf( val SUSPEND_LAMBDA_CLASSES = listOf(
@@ -42,18 +44,14 @@ class VariableFinder private constructor(private val context: EvaluationContextI
"kotlin.coroutines.jvm.internal.RestrictedSuspendLambda" "kotlin.coroutines.jvm.internal.RestrictedSuspendLambda"
) )
fun instance(context: EvaluationContextImpl): VariableFinder? { fun instance(context: ExecutionContext): VariableFinder? {
val frameProxy = context.frameProxy ?: return null val frameProxy = context.evaluationContext.frameProxy ?: return null
return VariableFinder(context, frameProxy) return VariableFinder(context, frameProxy)
} }
fun unwrapRefValue(value: ObjectReference): Value? {
return NamedEntity("<nameForUnwrapOnly>", value.type()) { value }.unwrap().value()
}
fun variableNotFound(context: EvaluationContextImpl, message: String): Exception { fun variableNotFound(context: EvaluationContextImpl, message: String): Exception {
val frameProxy = context.frameProxy val frameProxy = context.frameProxy
val location = frameProxy?.location() val location = frameProxy?.safeLocation()
val scope = context.debugProcess.searchScope val scope = context.debugProcess.searchScope
val locationText = location?.run { "Location: ${sourceName()}:${lineNumber()}" } ?: "No location available" val locationText = location?.run { "Location: ${sourceName()}:${lineNumber()}" } ?: "No location available"
@@ -98,12 +96,10 @@ class VariableFinder private constructor(private val context: EvaluationContextI
return Regex("^$escapedName(?:$escapedSuffix)*$") return Regex("^$escapedName(?:$escapedSuffix)*$")
} }
private fun AsmType.isFunctionType(): Boolean {
return sort == AsmType.OBJECT && internalName.startsWith(NUMBERED_FUNCTION_PREFIX)
}
fun getInlineDepth(variables: List<LocalVariableProxyImpl>): Int { fun getInlineDepth(variables: List<LocalVariableProxyImpl>): Int {
val inlineFunVariables = variables.filter { it.name().startsWith(LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) } val inlineFunVariables = variables
.filter { it.name().startsWith(LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) }
if (inlineFunVariables.isEmpty()) { if (inlineFunVariables.isEmpty()) {
return 0 return 0
} }
@@ -133,46 +129,43 @@ class VariableFinder private constructor(private val context: EvaluationContextI
} }
} }
sealed class VariableKind(val type: AsmType?) { private val evaluatorValueConverter = EvaluatorValueConverter(context)
sealed class VariableKind(val asmType: AsmType) {
abstract fun capturedNameMatches(name: String): Boolean abstract fun capturedNameMatches(name: String): Boolean
class Ordinary(val name: String, type: AsmType?) : VariableKind(type) { class Ordinary(val name: String, asmType: AsmType) : VariableKind(asmType) {
private val capturedNameRegex = getCapturedVariableNameRegex(getCapturedFieldName(this.name)) private val capturedNameRegex = getCapturedVariableNameRegex(getCapturedFieldName(this.name))
override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name) override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name)
} }
class UnlabeledThis(type: AsmType?) : VariableKind(type) { // TODO Support overloaded local functions
class LocalFunction(val name: String, asmType: AsmType) : VariableKind(asmType) {
@Suppress("ConvertToStringTemplate")
override fun capturedNameMatches(name: String) = name == "$" + name
}
class UnlabeledThis(asmType: AsmType) : VariableKind(asmType) {
override fun capturedNameMatches(name: String) = override fun capturedNameMatches(name: String) =
(name == AsmUtil.CAPTURED_RECEIVER_FIELD || name.startsWith(AsmUtil.getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD))) (name == AsmUtil.CAPTURED_RECEIVER_FIELD || name.startsWith(getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD)))
} }
class LabeledThis(val label: String, type: AsmType?) : VariableKind(type) { class OuterClassThis(asmType: AsmType) : VariableKind(asmType) {
private val capturedNameRegex = getCapturedVariableNameRegex( override fun capturedNameMatches(name: String) = false
getCapturedFieldName(getLabeledThisName(label, AsmUtil.LABELED_THIS_FIELD, AsmUtil.CAPTURED_RECEIVER_FIELD)) }
)
class FieldVar(val fieldName: String, asmType: AsmType) : VariableKind(asmType) {
// Captured 'field' are not supported yet
override fun capturedNameMatches(name: String) = false
}
class ExtensionThis(val label: String, asmType: AsmType) : VariableKind(asmType) {
val parameterName = getLabeledThisName(label, AsmUtil.LABELED_THIS_PARAMETER, AsmUtil.RECEIVER_PARAMETER_NAME)
val fieldName = getLabeledThisName(label, getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD), AsmUtil.CAPTURED_RECEIVER_FIELD)
private val capturedNameRegex = getCapturedVariableNameRegex(fieldName)
override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name) override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name)
} }
fun typeMatches(type: JdiType?): Boolean {
if (type == null) return true
val asmType = this.type ?: return true
// Main path
if (asmType.descriptor == "Ljava/lang/Object;" || type.isSubtype(asmType)) {
return true
}
// The latter is for boxing interventions
fun box(desc: String) = JvmPrimitiveType.getByDesc(desc)?.wrapperFqName?.topLevelClassAsmType()?.descriptor
val asmTypeDescriptor = asmType.descriptor
val jdiTypeDescriptor = type.signature()
val boxedAsmType = box(asmTypeDescriptor) ?: asmTypeDescriptor
val boxedJdiType = box(jdiTypeDescriptor) ?: jdiTypeDescriptor
return boxedAsmType == boxedJdiType
}
} }
class Result(val value: Value?) class Result(val value: Value?)
@@ -180,36 +173,25 @@ class VariableFinder private constructor(private val context: EvaluationContextI
private class NamedEntity(val name: String, val type: JdiType?, val value: () -> Value?) { private class NamedEntity(val name: String, val type: JdiType?, val value: () -> Value?) {
companion object { companion object {
fun of(field: Field, owner: ObjectReference): NamedEntity { fun of(field: Field, owner: ObjectReference): NamedEntity {
return NamedEntity(field.name(), field.safeType()) { owner.getValue(field) }.unwrap() return NamedEntity(field.name(), field.safeType()) { owner.getValue(field) }
} }
fun of(variable: LocalVariableProxyImpl, frameProxy: StackFrameProxyImpl): NamedEntity { fun of(variable: LocalVariableProxyImpl, frameProxy: StackFrameProxyImpl): NamedEntity {
return NamedEntity(variable.name(), variable.safeType()) { frameProxy.getValue(variable) }.unwrap() return NamedEntity(variable.name(), variable.safeType()) { frameProxy.getValue(variable) }
} }
} }
fun unwrap(): NamedEntity {
if (type !is ClassType || !type.signature().startsWith("L" + AsmTypes.REF_TYPE_PREFIX)) {
return this
}
val obj = this.value() as? ObjectReference ?: return this
val field = type.fieldByName("element") ?: return this
val unwrappedValue = obj.getValue(field)
val unwrappedType = if (field.type() is PrimitiveType) field.type() else unwrappedValue?.type()
return NamedEntity(name, unwrappedType) { unwrappedValue }
}
} }
fun find(name: String, type: AsmType?): Result? { fun find(parameter: CodeFragmentParameter, asmType: AsmType): Result? {
return when { return when (parameter.kind) {
name.startsWith(AsmUtil.THIS + "@") -> { Kind.ORDINARY -> findOrdinary(VariableKind.Ordinary(parameter.name, asmType))
val label = name.drop(AsmUtil.THIS.length + 1).also { require(it.isNotEmpty()) { "Invalid name '$name'" } } Kind.FAKE_JAVA_OUTER_CLASS -> frameProxy.thisObject()?.let { Result(it) }
findLabeledThis(VariableKind.LabeledThis(label, type)) Kind.EXTENSION_RECEIVER -> findExtensionThis(VariableKind.ExtensionThis(parameter.name, asmType))
} Kind.LOCAL_FUNCTION -> findLocalFunction(VariableKind.LocalFunction(parameter.name, asmType))
name == AsmUtil.THIS -> findUnlabeledThis(VariableKind.UnlabeledThis(type)) Kind.DISPATCH_RECEIVER -> findDispatchThis(VariableKind.OuterClassThis(asmType))
else -> findOrdinary(VariableKind.Ordinary(name, type)) Kind.COROUTINE_CONTEXT -> findCoroutineContext()
Kind.FIELD_VAR -> findFieldVariable(VariableKind.FieldVar(parameter.name, asmType))
Kind.DEBUG_LABEL -> findDebugLabel(parameter.name)
} }
} }
@@ -227,11 +209,35 @@ class VariableFinder private constructor(private val context: EvaluationContextI
return findCapturedVariable(kind, containingThis) return findCapturedVariable(kind, containingThis)
} }
private fun findLabeledThis(kind: VariableKind.LabeledThis): Result? { private fun findFieldVariable(kind: VariableKind.FieldVar): Result? {
val thisObject = frameProxy.thisObject() ?: return null
val field = thisObject.referenceType().fieldByName(kind.fieldName) ?: return null
return Result(thisObject.getValue(field))
}
private fun findLocalFunction(kind: VariableKind.LocalFunction): Result? {
val variables = frameProxy.safeVisibleVariables()
// Local variables direct search, new convention
val newConventionName = AsmUtil.LOCAL_FUNCTION_VARIABLE_PREFIX + kind.name
findLocalVariable(variables, kind, newConventionName)?.let { return it }
// Local variables direct search, old convention (before 1.3.30)
findLocalVariable(variables, kind, kind.name + "$")?.let { return it }
// Recursive search in local receiver variables
findCapturedVariableInReceiver(variables, kind)?.let { return it }
// Recursive search in captured this
val containingThis = frameProxy.thisObject() ?: return null
return findCapturedVariable(kind, containingThis)
}
private fun findExtensionThis(kind: VariableKind.ExtensionThis): Result? {
val variables = frameProxy.safeVisibleVariables() val variables = frameProxy.safeVisibleVariables()
// Local variables direct search // Local variables direct search
findLocalVariable(variables, kind, AsmUtil.LABELED_THIS_PARAMETER + kind.label)?.let { return it } findLocalVariable(variables, kind, kind.parameterName)?.let { return it }
// Recursive search in local receiver variables // Recursive search in local receiver variables
findCapturedVariableInReceiver(variables, kind)?.let { return it } findCapturedVariableInReceiver(variables, kind)?.let { return it }
@@ -242,8 +248,56 @@ class VariableFinder private constructor(private val context: EvaluationContextI
findCapturedVariable(kind, containingThis)?.let { return it } findCapturedVariable(kind, containingThis)?.let { return it }
} }
// Fallback: find an unlabeled this with the compatible type @Suppress("ConstantConditionIf")
return findUnlabeledThis(VariableKind.UnlabeledThis(kind.type)) if (USE_UNSAFE_FALLBACK) {
// Find an unlabeled this with the compatible type
findUnlabeledThis(VariableKind.UnlabeledThis(kind.asmType))?.let { return it }
}
return null
}
private fun findDispatchThis(kind: VariableKind.OuterClassThis): Result? {
val containingThis = frameProxy.thisObject()
if (containingThis != null) {
findCapturedVariable(kind, containingThis)?.let { return it }
}
if (isInsideDefaultImpls()) {
val variables = frameProxy.safeVisibleVariables()
findLocalVariable(variables, kind, getCapturedFieldName(AsmUtil.THIS))?.let { return it }
}
val variables = frameProxy.safeVisibleVariables()
val inlineDepth = getInlineDepth(variables)
if (inlineDepth > 0) {
variables.namedEntitySequence()
.filter { it.name.matches(inlinedThisRegex) && getInlineDepth(it.name) == inlineDepth && kind.typeMatches(it.type) }
.mapNotNull { it.unwrapAndCheck(kind) }
.firstOrNull()
?.let { return it }
}
@Suppress("ConstantConditionIf")
if (USE_UNSAFE_FALLBACK) {
// Find an unlabeled this with the compatible type
findUnlabeledThis(VariableKind.UnlabeledThis(kind.asmType))?.let { return it }
}
return null
}
private fun findDebugLabel(name: String): Result? {
val markupMap = DebugLabelPropertyDescriptorProvider.getMarkupMap(context.evaluationContext.debugProcess)
for ((value, markup) in markupMap) {
if (markup.text == name) {
return Result(value)
}
}
return null
} }
private fun findUnlabeledThis(kind: VariableKind.UnlabeledThis): Result? { private fun findUnlabeledThis(kind: VariableKind.UnlabeledThis): Result? {
@@ -257,47 +311,35 @@ class VariableFinder private constructor(private val context: EvaluationContextI
} }
private fun findLocalVariable(variables: List<LocalVariableProxyImpl>, kind: VariableKind, name: String): Result? { private fun findLocalVariable(variables: List<LocalVariableProxyImpl>, kind: VariableKind, name: String): Result? {
val inlineDepth = getInlineDepth(variables)
if (inlineDepth > 0) {
val nameInlineAwareRegex = getLocalVariableNameRegexInlineAware(name)
variables.namedEntitySequence()
.filter { it.name.matches(nameInlineAwareRegex) && getInlineDepth(it.name) == inlineDepth && kind.typeMatches(it.type) }
.mapNotNull { it.unwrapAndCheck(kind) }
.firstOrNull()
?.let { return it }
}
variables.namedEntitySequence() variables.namedEntitySequence()
.filter { it.name == name && kind.typeMatches(it.type) } .filter { it.name == name && kind.typeMatches(it.type) }
.mapNotNull { it.unwrapAndCheck(kind) }
.firstOrNull() .firstOrNull()
?.let { return Result(it.value()) } ?.let { return it }
val canBeLocalFunction = kind is VariableKind.Ordinary
if (canBeLocalFunction && kind.type?.isFunctionType() == true) {
variables.namedEntitySequence()
.filter { isLocalFunctionName(it.name, name) && kind.typeMatches(it.type) }
.firstOrNull()
?.let { return Result(it.value()) }
}
val nameInlineAwareRegex = getLocalVariableNameRegexInlineAware(name)
val inlineDepth = getInlineDepth(variables)
variables.namedEntitySequence()
.filter { it.name.matches(nameInlineAwareRegex) && getInlineDepth(it.name) == inlineDepth && kind.typeMatches(it.type) }
.toList() // Sorted by will make a list anyway
.firstOrNull()
?.let { return Result(it.value()) }
if (name == COROUTINE_CONTEXT_SIMPLE_NAME) {
val coroutineContext = findCoroutineContext()?.takeIf { kind.typeMatches(it.type()) }
if (coroutineContext != null) {
return Result(coroutineContext)
}
}
return null return null
} }
private fun isLocalFunctionName(name: String, functionName: String): Boolean { private fun isInsideDefaultImpls(): Boolean {
@Suppress("ConvertToStringTemplate") val declaringType = frameProxy.safeLocation()?.declaringType() ?: return false
return name == functionName + "$" || name.startsWith(AsmUtil.LOCAL_FUNCTION_VARIABLE_PREFIX + name + "$") return declaringType.name().endsWith(JvmAbi.DEFAULT_IMPLS_SUFFIX)
} }
private fun findCoroutineContext(): ObjectReference? { private fun findCoroutineContext(): Result? {
val method = frameProxy.location().safeMethod() ?: return null val method = frameProxy.safeLocation()?.safeMethod() ?: return null
return findCoroutineContextForLambda(method) ?: findCoroutineContextForMethod(method) val result = findCoroutineContextForLambda(method) ?: findCoroutineContextForMethod(method) ?: return null
return Result(result)
} }
private fun findCoroutineContextForLambda(method: Method): ObjectReference? { private fun findCoroutineContextForLambda(method: Method): ObjectReference? {
@@ -334,23 +376,22 @@ class VariableFinder private constructor(private val context: EvaluationContextI
.methodsByName("getContext", "()Lkotlin/coroutines/CoroutineContext;").firstOrNull() .methodsByName("getContext", "()Lkotlin/coroutines/CoroutineContext;").firstOrNull()
?: return null ?: return null
val threadReference = frameProxy.threadProxy().threadReference.takeIf { it.isSuspended } ?: return null return continuation.invokeMethod(context.thread, getContextMethod, emptyList(), context.invokePolicy) as? ObjectReference
val invokePolicy = context.suspendContext.getInvokePolicy()
return continuation.invokeMethod(threadReference, getContextMethod, emptyList(), invokePolicy) as? ObjectReference
} }
private fun findCapturedVariableInReceiver(variables: List<LocalVariableProxyImpl>, kind: VariableKind): Result? { private fun findCapturedVariableInReceiver(variables: List<LocalVariableProxyImpl>, kind: VariableKind): Result? {
fun isReceiverOrPassedThis(name: String) = fun isReceiverOrPassedThis(name: String) =
name.startsWith(AsmUtil.LABELED_THIS_PARAMETER) name.startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|| name == AsmUtil.RECEIVER_PARAMETER_NAME || name == AsmUtil.RECEIVER_PARAMETER_NAME
|| name == AsmUtil.getCapturedFieldName(AsmUtil.THIS) || name == getCapturedFieldName(AsmUtil.THIS)
|| inlinedThisRegex.matches(name) || inlinedThisRegex.matches(name)
if (kind is VariableKind.LabeledThis) { if (kind is VariableKind.ExtensionThis) {
variables.namedEntitySequence() variables.namedEntitySequence()
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) } .filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
.mapNotNull { it.unwrapAndCheck(kind) }
.firstOrNull() .firstOrNull()
?.let { return Result(it.value()) } ?.let { return it }
} }
return variables.namedEntitySequence() return variables.namedEntitySequence()
@@ -360,30 +401,35 @@ class VariableFinder private constructor(private val context: EvaluationContextI
} }
private fun findCapturedVariable(kind: VariableKind, parent: Value?): Result? { private fun findCapturedVariable(kind: VariableKind, parent: Value?): Result? {
if (parent != null && kind is VariableKind.UnlabeledThis && kind.typeMatches(parent.type())) { val acceptsParentValue = kind is VariableKind.UnlabeledThis || kind is VariableKind.OuterClassThis
if (parent != null && acceptsParentValue && kind.typeMatches(parent.type())) {
return Result(parent) return Result(parent)
} }
val fields = (parent as? ObjectReference)?.referenceType()?.fields() ?: return null val fields = (parent as? ObjectReference)?.referenceType()?.fields() ?: return null
// Captured variables - direct search if (kind !is VariableKind.OuterClassThis) {
fields.namedEntitySequence(parent) // Captured variables - direct search
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) } fields.namedEntitySequence(parent)
.firstOrNull() .filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
?.let { return Result(it.value()) } .mapNotNull { it.unwrapAndCheck(kind) }
.firstOrNull()
?.let { return it }
// Recursive search in captured receivers // Recursive search in captured receivers
fields.namedEntitySequence(parent) fields.namedEntitySequence(parent)
.filter { isCapturedReceiverFieldName(it.name) } .filter { isCapturedReceiverFieldName(it.name) }
.mapNotNull { findCapturedVariable(kind, it.value()) } .mapNotNull { findCapturedVariable(kind, it.value()) }
.firstOrNull() .firstOrNull()
?.let { return it } ?.let { return it }
}
// Recursive search in outer and captured this // Recursive search in outer and captured this
fields.namedEntitySequence(parent) fields.namedEntitySequence(parent)
.filter { it.name == AsmUtil.getCapturedFieldName(AsmUtil.THIS) || it.name == AsmUtil.CAPTURED_THIS_FIELD } .filter { it.name == getCapturedFieldName(AsmUtil.THIS) || it.name == AsmUtil.CAPTURED_THIS_FIELD }
.mapNotNull { findCapturedVariable(kind, it.value()) }
.firstOrNull() .firstOrNull()
?.let { return findCapturedVariable(kind, it.value()) } ?.let { return it }
return null return null
} }
@@ -393,6 +439,19 @@ class VariableFinder private constructor(private val context: EvaluationContextI
|| name == AsmUtil.CAPTURED_RECEIVER_FIELD || name == AsmUtil.CAPTURED_RECEIVER_FIELD
} }
private fun List<Field>.namedEntitySequence(owner: ObjectReference) = asSequence().map { NamedEntity.of(it, owner) } private fun VariableKind.typeMatches(actualType: JdiType?): Boolean {
private fun List<LocalVariableProxyImpl>.namedEntitySequence() = asSequence().map { NamedEntity.of(it, frameProxy) } return evaluatorValueConverter.typeMatches(asmType, actualType)
}
private fun NamedEntity.unwrapAndCheck(kind: VariableKind): Result? {
return evaluatorValueConverter.coerce(value(), kind.asmType)
}
private fun List<Field>.namedEntitySequence(owner: ObjectReference): Sequence<NamedEntity> {
return asSequence().map { NamedEntity.of(it, owner) }
}
private fun List<LocalVariableProxyImpl>.namedEntitySequence(): Sequence<NamedEntity> {
return asSequence().map { NamedEntity.of(it, frameProxy) }
}
} }
@@ -6,6 +6,8 @@
package org.jetbrains.kotlin.idea.debugger package org.jetbrains.kotlin.idea.debugger
import com.intellij.debugger.engine.evaluation.AbsentInformationEvaluateException import com.intellij.debugger.engine.evaluation.AbsentInformationEvaluateException
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.jdi.StackFrameProxy
import com.intellij.debugger.impl.DebuggerUtilsEx import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.jdi.LocalVariableProxyImpl import com.intellij.debugger.jdi.LocalVariableProxyImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.jdi.StackFrameProxyImpl
@@ -43,6 +45,14 @@ fun Method.safeArguments(): List<LocalVariable>? {
return wrapAbsentInformationException { arguments() } return wrapAbsentInformationException { arguments() }
} }
fun StackFrameProxy.safeLocation(): Location? {
return try {
this.location()
} catch (e: EvaluateException) {
null
}
}
fun Location.safeSourceName(): String? { fun Location.safeSourceName(): String? {
return try { return try {
sourceName() sourceName()
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.util.InfinitePeriodicalTask
import org.jetbrains.kotlin.idea.util.LongRunningReadTask import org.jetbrains.kotlin.idea.util.LongRunningReadTask
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
@@ -306,6 +307,8 @@ class KotlinBytecodeToolWindow(private val myProject: Project, private val toolW
override fun shouldGenerateScript(script: KtScript): Boolean { override fun shouldGenerateScript(script: KtScript): Boolean {
return script.containingKtFile === ktFile return script.containingKtFile === ktFile
} }
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
} }
val state = GenerationState.Builder( val state = GenerationState.Builder(
@@ -46,10 +46,7 @@ import org.jetbrains.kotlin.idea.scratch.*
import org.jetbrains.kotlin.idea.scratch.output.ScratchOutput import org.jetbrains.kotlin.idea.scratch.output.ScratchOutput
import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputType import org.jetbrains.kotlin.idea.scratch.output.ScratchOutputType
import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.resolve.AnalyzingUtils import org.jetbrains.kotlin.resolve.AnalyzingUtils
import java.io.File import java.io.File
@@ -143,6 +140,7 @@ class KtCompilingExecutor(file: ScratchFile) : ScratchExecutor(file) {
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject) = true override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject) = true
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = processingClassOrObject.containingKtFile == psiFile override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = processingClassOrObject.containingKtFile == psiFile
override fun shouldGenerateScript(script: KtScript) = false override fun shouldGenerateScript(script: KtScript) = false
override fun shouldGenerateCodeFragment(script: KtCodeFragment) = false
} }
val state = GenerationState.Builder( val state = GenerationState.Builder(
@@ -1 +1,2 @@
@Suppress("INVISIBLE_MEMBER")
MyClass().privateFun(<error>"s"</error>) MyClass().privateFun(<error>"s"</error>)
@@ -1 +1,2 @@
@Suppress("INVISIBLE_MEMBER")
"${MyClass().privateFun(1)} ${MyClass().privateFun<Int>(<error>"s"</error>)}" "${MyClass().privateFun(1)} ${MyClass().privateFun<Int>(<error>"s"</error>)}"
@@ -1 +1,2 @@
MyClass().privateFun() + MyClass().privateVal + MyClass.PrivateClass().a @Suppress("INVISIBLE_MEMBER")
(MyClass().privateFun() + MyClass().privateVal + MyClass.PrivateClass().a)
@@ -1 +1,2 @@
MyClass().protectedFun() + MyClass().protectedVal + MyClass.ProtectedClass().a @Suppress("INVISIBLE_MEMBER")
(MyClass().protectedFun() + MyClass().protectedVal + MyClass.ProtectedClass().a)
@@ -1 +1 @@
<error>a</error> + b + <error>c</error> + <error>a1</error> <error>a</error> + b + c + a1
@@ -30,19 +30,25 @@ clearCache.kt:42
clearCache.kt:52 clearCache.kt:52
Compile bytecode for o.test() Compile bytecode for o.test()
clearCache.kt:60 clearCache.kt:60
Compile bytecode for o.test()
clearCache.kt:78 clearCache.kt:78
Compile bytecode for c.size Compile bytecode for c.size
clearCache.kt:86 clearCache.kt:86
Compile bytecode for c.size
clearCache.kt:95 clearCache.kt:95
Compile bytecode for c.size
clearCache.kt:105 clearCache.kt:105
Compile bytecode for c.size
clearCache.kt:113 clearCache.kt:113
Compile bytecode for c.size Compile bytecode for c.size
clearCache.kt:123 clearCache.kt:123
Compile bytecode for o.test() Compile bytecode for o.test()
clearCache.kt:131 clearCache.kt:131
Compile bytecode for o.test()
clearCache.kt:149 clearCache.kt:149
Compile bytecode for obj.test() Compile bytecode for obj.test()
clearCache.kt:154 clearCache.kt:154
Compile bytecode for obj.test()
clearCache.kt:161 clearCache.kt:161
Compile bytecode for o.test() Compile bytecode for o.test()
clearCache.kt:169 clearCache.kt:169
@@ -37,15 +37,15 @@ fun genericClassCast() {
val c = ArrayList<Int>() val c = ArrayList<Int>()
c.add(1) c.add(1)
// EXPRESSION: c.get(0) // EXPRESSION: c.get(0)
// RESULT: 1: I // RESULT: instance of java.lang.Integer(id=ID): Ljava/lang/Integer;
//Breakpoint! //Breakpoint!
val b = 1 val b = 1
} }
if (true) { if (true) {
val c = ArrayList<String>() val c = ArrayList<Int>()
c.add("a") (c as? ArrayList<String>)?.add("a")
// EXPRESSION: c.get(0) // EXPRESSION: c.get(0) + 1
// RESULT: java.lang.ClassCastException : java.lang.String cannot be cast to java.lang.Number // RESULT: java.lang.ClassCastException : java.lang.String cannot be cast to java.lang.Number
//Breakpoint! //Breakpoint!
val b = 1 val b = 1
@@ -9,12 +9,15 @@ Connected to the target VM
exceptions.kt:9 exceptions.kt:9
Compile bytecode for fail() Compile bytecode for fail()
exceptions.kt:14 exceptions.kt:14
Compile bytecode for fail()
exceptions.kt:26 exceptions.kt:26
Compile bytecode for o as Derived Compile bytecode for o as Derived
exceptions.kt:31 exceptions.kt:31
Compile bytecode for o as Derived
exceptions.kt:42 exceptions.kt:42
Compile bytecode for c.get(0) Compile bytecode for c.get(0)
exceptions.kt:51 exceptions.kt:51
Compile bytecode for c.get(0) + 1
Disconnected from the target VM Disconnected from the target VM
Process finished with exit code 0 Process finished with exit code 0
@@ -41,9 +41,11 @@ Compile bytecode for extClass.testCompPrivate()
extensionMemberFunction.kt:88 extensionMemberFunction.kt:88
Compile bytecode for testCompPrivate() Compile bytecode for testCompPrivate()
extensionMemberFunction.kt:98 extensionMemberFunction.kt:98
Compile bytecode for testCompPublic()
extensionMemberFunction.kt:103 extensionMemberFunction.kt:103
Compile bytecode for this.testCompPublic() Compile bytecode for this.testCompPublic()
extensionMemberFunction.kt:108 extensionMemberFunction.kt:108
Compile bytecode for testCompPrivate()
extensionMemberFunction.kt:113 extensionMemberFunction.kt:113
Compile bytecode for this.testCompPrivate() Compile bytecode for this.testCompPrivate()
Disconnected from the target VM Disconnected from the target VM
@@ -41,9 +41,11 @@ Compile bytecode for extClass.testCompPrivate
extensionMemberProperty.kt:92 extensionMemberProperty.kt:92
Compile bytecode for testCompPrivate Compile bytecode for testCompPrivate
extensionMemberProperty.kt:102 extensionMemberProperty.kt:102
Compile bytecode for testCompPublic
extensionMemberProperty.kt:107 extensionMemberProperty.kt:107
Compile bytecode for this.testCompPublic Compile bytecode for this.testCompPublic
extensionMemberProperty.kt:112 extensionMemberProperty.kt:112
Compile bytecode for testCompPrivate
extensionMemberProperty.kt:117 extensionMemberProperty.kt:117
Compile bytecode for this.testCompPrivate Compile bytecode for this.testCompPrivate
Disconnected from the target VM Disconnected from the target VM
@@ -24,4 +24,4 @@ fun main() {
// RESULT: "field": Ljava/lang/String; // RESULT: "field": Ljava/lang/String;
// EXPRESSION: field // EXPRESSION: field
// RESULT: Can't find a backing field for property field // RESULT: Cannot find the backing field 'b'
@@ -23,7 +23,7 @@ class Outer {
// outer isn't captured in lambda // outer isn't captured in lambda
lambda { lambda {
// EXPRESSION: foo() + 2 // EXPRESSION: foo() + 2
// RESULT: java.lang.AssertionError : Cannot find local variable: name = 'this@Outer', type = funFromOuterClassInLamdba.Outer // RESULT: 'this@Outer' is not captured
//Breakpoint! //Breakpoint!
val a = 1 val a = 1
} }
@@ -22,7 +22,7 @@ fun test1(derived: Base) =
// RESULT: 1: I // RESULT: 1: I
// EXPRESSION: nullable.prop // EXPRESSION: nullable.prop
// RESULT: Method threw 'kotlin.TypeCastException' exception. // RESULT: java.lang.NullPointerException
fun test2(nullable: Derived?) = fun test2(nullable: Derived?) =
nullable != null && nullable != null &&
//Breakpoint! //Breakpoint!
@@ -7,7 +7,7 @@ fun main() {
//Breakpoint! //Breakpoint!
block(3) c@ { block(3) c@ {
//Breakpoint! //Breakpoint!
val a = 5 val a = this@a + this@b + this@c
} }
} }
} }
@@ -28,4 +28,4 @@ fun <T> T.inlineBlock(t: T, block: T.() -> Unit) {
// RESULT: 2: I // RESULT: 2: I
// EXPRESSION: this + this@a + this@b + this@c // EXPRESSION: this + this@a + this@b + this@c
// RESULT: 12: I // RESULT: 9: I
@@ -4,12 +4,10 @@ fun main(args: Array<String>) {
val callable = 1 val callable = 1
arrayOf(1, 2).map { arrayOf(1, 2).map {
it + 1 it + 1
//Breakpoint! //Breakpoint! (lambdaOrdinal = 1)
}.forEach { it + 2 } }.forEach { it + 2 }
} }
// STEP_INTO: 1
// EXPRESSION: callable // EXPRESSION: callable
// RESULT: 1: I // RESULT: 1: I
@@ -1,8 +1,7 @@
LineBreakpoint created at callableBug.kt:8 LineBreakpoint created at callableBug.kt:8 lambdaOrdinal = 1
Run Java Run Java
Connected to the target VM Connected to the target VM
callableBug.kt:8 callableBug.kt:8
callableBug.kt:8
Compile bytecode for callable Compile bytecode for callable
Compile bytecode for it Compile bytecode for it
callableBug.kt:8 callableBug.kt:8
@@ -16,4 +16,4 @@ interface T {
// RESULT: 1: I // RESULT: 1: I
// EXPRESSION: object: T {} // EXPRESSION: object: T {}
// RESULT: instance of ceObject.Generated_for_debugger_class$generated_for_debugger_fun$1(id=ID): LceObject/Generated_for_debugger_class$generated_for_debugger_fun$1; // RESULT: instance of Generated_for_debugger_class$generated_for_debugger_fun$1(id=ID): LGenerated_for_debugger_class$generated_for_debugger_fun$1;
@@ -28,4 +28,4 @@ open class Base {
// RESULT: Expecting an element; looking at ERROR_ELEMENT '(1,6) in /fragment.kt // RESULT: Expecting an element; looking at ERROR_ELEMENT '(1,6) in /fragment.kt
// EXPRESSION: super.baseFun() // EXPRESSION: super.baseFun()
// RESULT: Cannot perform an action for expression with super call // RESULT: Evaluation of 'super' call expression is not supported
@@ -2,4 +2,4 @@ prop += 1
prop2 +=2 prop2 +=2
prop + prop2 prop + prop2
// RESULT: instance of kotlin.Triple(id=ID): Lkotlin/Triple; // RESULT: 6: I
@@ -6,7 +6,7 @@ Compile bytecode for prop += 1
prop2 +=2 prop2 +=2
prop + prop2 prop + prop2
// RESULT: instance of kotlin.Triple(id=ID): Lkotlin/Triple; // RESULT: 6: I
Disconnected from the target VM Disconnected from the target VM
Process finished with exit code 0 Process finished with exit code 0
@@ -4,22 +4,15 @@ Connected to the target VM
suspendContinuation.kt:7 suspendContinuation.kt:7
Compile bytecode for a Compile bytecode for a
frame = main:7, SuspendContinuationKt {suspendContinuation} frame = main:7, SuspendContinuationKt {suspendContinuation}
local = $continuation: kotlin.coroutines.Continuation = {suspendContinuation.SuspendContinuationKt$main$1@uniqueID}Continuation at suspendContinuation.SuspendContinuationKt.main(suspendContinuation.kt:5) (sp = null) local = $completion: kotlin.coroutines.Continuation = {kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$1@uniqueID}Continuation at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$1 (sp = null)
field = result: java.lang.Object = null (sp = null)
field = label: int = 1 (sp = null) field = label: int = 1 (sp = null)
field = I$0: int = 5 (sp = null) field = $completion: kotlin.coroutines.Continuation = {kotlin.coroutines.jvm.internal.RunSuspend@uniqueID} (sp = null)
field = intercepted: kotlin.coroutines.Continuation = null (sp = ContinuationImpl.!EXT!) field = result: kotlin.Result = null (sp = RunSuspend.!EXT!)
field = _context: kotlin.coroutines.CoroutineContext = {kotlin.coroutines.EmptyCoroutineContext@uniqueID}EmptyCoroutineContext (sp = ContinuationImpl.!EXT!) field = $this_createCoroutineUnintercepted$inlined: kotlin.jvm.functions.Function1 = {suspendContinuation.SuspendContinuationKt$$$main@uniqueID}interface kotlin.jvm.functions.Function1 (sp = null)
- No fields to display field = args: java.lang.String[] = {java.lang.String[0]@uniqueID} (sp = null)
field = completion: kotlin.coroutines.Continuation = {kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$1@uniqueID}Continuation at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt$createCoroutineUnintercepted$$inlined$createCoroutineFromSuspendFunction$IntrinsicsKt__IntrinsicsJvmKt$1 (sp = BaseContinuationImpl.!EXT!) field = arity: int = 1 (sp = Lambda.!EXT!)
field = label: int = 1 (sp = null) field = completion: kotlin.coroutines.Continuation = {kotlin.coroutines.jvm.internal.RunSuspend@uniqueID} (sp = BaseContinuationImpl.!EXT!)
field = $completion: kotlin.coroutines.Continuation = {kotlin.coroutines.jvm.internal.RunSuspend@uniqueID} (sp = null) field = result: kotlin.Result = null (sp = RunSuspend.!EXT!)
field = result: kotlin.Result = null (sp = RunSuspend.!EXT!)
field = $this_createCoroutineUnintercepted$inlined: kotlin.jvm.functions.Function1 = {suspendContinuation.SuspendContinuationKt$$$main@uniqueID}interface kotlin.jvm.functions.Function1 (sp = null)
field = args: java.lang.String[] = {java.lang.String[0]@uniqueID} (sp = null)
field = arity: int = 1 (sp = Lambda.!EXT!)
field = completion: kotlin.coroutines.Continuation = {kotlin.coroutines.jvm.internal.RunSuspend@uniqueID} (sp = BaseContinuationImpl.!EXT!)
field = result: kotlin.Result = null (sp = RunSuspend.!EXT!)
local = a: int = 5 (sp = suspendContinuation.kt, 4) local = a: int = 5 (sp = suspendContinuation.kt, 4)
Disconnected from the target VM Disconnected from the target VM
@@ -0,0 +1,6 @@
package kt25220
fun main() {
//Breakpoint!
val a = 5
}
@@ -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
@@ -3,7 +3,7 @@ package nestedInlineArguments
fun main(args: Array<String>) { fun main(args: Array<String>) {
val list1 = listOf("a") val list1 = listOf("a")
val list2 = listOf(Bar()) val list2 = listOf(Bar())
list1.map { list2.foo(1) } list1.mapTest { list2.foo(1) }
} }
inline fun <reified T: Bar> List<T>.foo(key: Int): T? { inline fun <reified T: Bar> List<T>.foo(key: Int): T? {
@@ -16,4 +16,18 @@ inline fun <reified T: Bar> List<T>.foo(key: Int): T? {
open class Foo open class Foo
open class Bar { open class Bar {
val i = 1 val i = 1
}
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@kotlin.internal.InlineOnly
inline fun <T, R> Iterable<T>.mapTest(transform: (T) -> R): List<R> {
return mapToTest(ArrayList<R>(), transform)
}
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
@kotlin.internal.InlineOnly
inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapToTest(destination: C, transform: (T) -> R): C {
for (item in this)
destination.add(transform(item))
return destination
} }
@@ -15,4 +15,4 @@ class Some {
} }
// EXPRESSION: base.a // EXPRESSION: base.a
// RESULT: java.lang.NoSuchMethodError : Method not found: MemberDescription(ownerInternalName = privatePropertyWithExplicitDefaultGetter/Some, name = getA, desc = ()I, isStatic = false) // RESULT: 1: I
@@ -231,6 +231,16 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt22366.kt"); runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt22366.kt");
} }
@TestMetadata("kt25220.kt")
public void testKt25220() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt25220.kt");
}
@TestMetadata("kt25222.kt")
public void testKt25222() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt25222.kt");
}
@TestMetadata("kt28087.kt") @TestMetadata("kt28087.kt")
public void testKt28087() throws Exception { public void testKt28087() throws Exception {
runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt28087.kt"); runTest("idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/kt28087.kt");