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