Minor: Reformat evaluator code

This commit is contained in:
Yan Zhulanow
2019-01-15 17:27:53 +03:00
parent eaed6855ac
commit 34c5954d75
11 changed files with 284 additions and 225 deletions
@@ -26,7 +26,6 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
import org.jetbrains.kotlin.resolve.DescriptorFactory import org.jetbrains.kotlin.resolve.DescriptorFactory
import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
@@ -168,10 +168,12 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
else else
debuggerContext.frameProxy?.stackFrame debuggerContext.frameProxy?.stackFrame
val visibleVariables = frame?.let { val visibleVariables = if (frame != null) {
val values = it.getValues(it.visibleVariables()) val values = frame.getValues(frame.visibleVariables())
values.filterValues { it != null } values.filterValues { it != null }
} ?: emptyMap() } else {
emptyMap()
}
frameInfo = FrameInfo(frame?.thisObject(), visibleVariables) frameInfo = FrameInfo(frame?.thisObject(), visibleVariables)
} catch (ignored: AbsentInformationException) { } catch (ignored: AbsentInformationException) {
@@ -315,7 +317,7 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
// elementAt can be PsiWhiteSpace when codeFragment is created from line start offset (in case of first opening EE window) // elementAt can be PsiWhiteSpace when codeFragment is created from line start offset (in case of first opening EE window)
val lineStartOffset = if (elementAt is PsiWhiteSpace || elementAt is PsiComment) { val lineStartOffset = if (elementAt is PsiWhiteSpace || elementAt is PsiComment) {
PsiTreeUtil.skipSiblingsForward(elementAt, PsiWhiteSpace::class.java, PsiComment::class.java)?.textOffset PsiTreeUtil.skipSiblingsForward(elementAt, PsiWhiteSpace::class.java, PsiComment::class.java)?.textOffset
?: elementAt.textOffset ?: elementAt.textOffset
} else { } else {
elementAt.textOffset elementAt.textOffset
} }
@@ -59,31 +59,39 @@ import java.util.concurrent.ConcurrentHashMap
class KotlinDebuggerCaches(project: Project) { class KotlinDebuggerCaches(project: Project) {
private val cachedCompiledData = CachedValuesManager.getManager(project).createCachedValue( private val cachedCompiledData = CachedValuesManager.getManager(project).createCachedValue(
{ {
CachedValueProvider.Result<MultiMap<String, CompiledDataDescriptor>>( CachedValueProvider.Result<MultiMap<String, CompiledDataDescriptor>>(
MultiMap.create(), PsiModificationTracker.MODIFICATION_COUNT) MultiMap.create(), PsiModificationTracker.MODIFICATION_COUNT
}, false) )
}, false
)
private val cachedClassNames = CachedValuesManager.getManager(project).createCachedValue( private val cachedClassNames = CachedValuesManager.getManager(project).createCachedValue(
{ {
CachedValueProvider.Result<MutableMap<PsiElement, List<String>>>( CachedValueProvider.Result<MutableMap<PsiElement, List<String>>>(
ConcurrentHashMap<PsiElement, List<String>>(), ConcurrentHashMap(),
PsiModificationTracker.MODIFICATION_COUNT) PsiModificationTracker.MODIFICATION_COUNT
}, false) )
}, false
)
private val cachedTypeMappers = CachedValuesManager.getManager(project).createCachedValue( private val cachedTypeMappers = CachedValuesManager.getManager(project).createCachedValue(
{ {
CachedValueProvider.Result<MutableMap<PsiElement, KotlinTypeMapper>>( CachedValueProvider.Result<MutableMap<PsiElement, KotlinTypeMapper>>(
ConcurrentHashMap<PsiElement, KotlinTypeMapper>(), ConcurrentHashMap(),
PsiModificationTracker.MODIFICATION_COUNT) PsiModificationTracker.MODIFICATION_COUNT
}, false) )
}, false
)
private val debugInfoCache = CachedValuesManager.getManager(project).createCachedValue( private val debugInfoCache = CachedValuesManager.getManager(project).createCachedValue(
{ {
CachedValueProvider.Result( CachedValueProvider.Result(
createWeakBytecodeDebugInfoStorage(), createWeakBytecodeDebugInfoStorage(),
PsiModificationTracker.MODIFICATION_COUNT) PsiModificationTracker.MODIFICATION_COUNT
}, false) )
}, false
)
companion object { companion object {
private val LOG = Logger.getLogger(KotlinDebuggerCaches::class.java)!! private val LOG = Logger.getLogger(KotlinDebuggerCaches::class.java)!!
@@ -91,10 +99,10 @@ class KotlinDebuggerCaches(project: Project) {
fun getInstance(project: Project) = ServiceManager.getService(project, KotlinDebuggerCaches::class.java)!! fun getInstance(project: Project) = ServiceManager.getService(project, KotlinDebuggerCaches::class.java)!!
fun getOrCreateCompiledData( fun getOrCreateCompiledData(
codeFragment: KtCodeFragment, codeFragment: KtCodeFragment,
sourcePosition: SourcePosition, sourcePosition: SourcePosition,
evaluationContext: EvaluationContextImpl, evaluationContext: EvaluationContextImpl,
create: (KtCodeFragment, SourcePosition) -> CompiledDataDescriptor create: (KtCodeFragment, SourcePosition) -> CompiledDataDescriptor
): CompiledDataDescriptor { ): CompiledDataDescriptor {
val evaluateExpressionCache = getInstance(codeFragment.project) val evaluateExpressionCache = getInstance(codeFragment.project)
@@ -157,8 +165,7 @@ class KotlinDebuggerCaches(project: Project) {
val newValue = if (!isInLibrary) { val newValue = if (!isInLibrary) {
createTypeMapperForSourceFile(file) createTypeMapperForSourceFile(file)
} } else {
else {
val element = getElementToCreateTypeMapperForLibraryFile(psiElement) val element = getElementToCreateTypeMapperForLibraryFile(psiElement)
createTypeMapperForLibraryFile(element, file) createTypeMapperForLibraryFile(element, file)
} }
@@ -168,40 +175,42 @@ class KotlinDebuggerCaches(project: Project) {
} }
fun getOrReadDebugInfoFromBytecode( fun getOrReadDebugInfoFromBytecode(
project: Project, project: Project,
jvmName: JvmClassName, jvmName: JvmClassName,
file: VirtualFile): BytecodeDebugInfo? { file: VirtualFile
): BytecodeDebugInfo? {
val cache = getInstance(project) val cache = getInstance(project)
return cache.debugInfoCache.value[BinaryCacheKey(project, jvmName, file)] return cache.debugInfoCache.value[BinaryCacheKey(project, jvmName, file)]
} }
private fun getElementToCreateTypeMapperForLibraryFile(element: PsiElement?) = private fun getElementToCreateTypeMapperForLibraryFile(element: PsiElement?) =
runReadAction { element as? KtElement ?: PsiTreeUtil.getParentOfType(element, KtElement::class.java)!! } runReadAction { element as? KtElement ?: PsiTreeUtil.getParentOfType(element, KtElement::class.java)!! }
private fun createTypeMapperForLibraryFile(element: KtElement, file: KtFile): KotlinTypeMapper = private fun createTypeMapperForLibraryFile(element: KtElement, file: KtFile): KotlinTypeMapper =
runInReadActionWithWriteActionPriorityWithPCE { runInReadActionWithWriteActionPriorityWithPCE {
createTypeMapper(file, element.analyzeAndGetResult()) createTypeMapper(file, element.analyzeAndGetResult())
} }
private fun createTypeMapperForSourceFile(file: KtFile): KotlinTypeMapper = private fun createTypeMapperForSourceFile(file: KtFile): KotlinTypeMapper =
runInReadActionWithWriteActionPriorityWithPCE { runInReadActionWithWriteActionPriorityWithPCE {
createTypeMapper(file, file.analyzeWithAllCompilerChecks().apply(AnalysisResult::throwIfError)) createTypeMapper(file, file.analyzeWithAllCompilerChecks().apply(AnalysisResult::throwIfError))
} }
private fun createTypeMapper(file: KtFile, analysisResult: AnalysisResult): KotlinTypeMapper { private fun createTypeMapper(file: KtFile, analysisResult: AnalysisResult): KotlinTypeMapper {
val state = GenerationState.Builder( val state = GenerationState.Builder(
file.project, file.project,
ClassBuilderFactories.THROW_EXCEPTION, ClassBuilderFactories.THROW_EXCEPTION,
analysisResult.moduleDescriptor, analysisResult.moduleDescriptor,
analysisResult.bindingContext, analysisResult.bindingContext,
listOf(file), listOf(file),
CompilerConfiguration.EMPTY CompilerConfiguration.EMPTY
).build() ).build()
state.beforeCompile() state.beforeCompile()
return state.typeMapper return state.typeMapper
} }
@TestOnly fun addTypeMapper(file: KtFile, typeMapper: KotlinTypeMapper) { @TestOnly
fun addTypeMapper(file: KtFile, typeMapper: KotlinTypeMapper) {
getInstance(file.project).cachedTypeMappers.value[file] = typeMapper getInstance(file.project).cachedTypeMappers.value[file] = typeMapper
} }
} }
@@ -216,7 +225,12 @@ class KotlinDebuggerCaches(project: Project) {
val thisDescriptor = value.asmType.getClassDescriptor(context.debugProcess.searchScope) val thisDescriptor = value.asmType.getClassDescriptor(context.debugProcess.searchScope)
val superClassDescriptor = jetType.constructor.declarationDescriptor as? ClassDescriptor val superClassDescriptor = jetType.constructor.declarationDescriptor as? ClassDescriptor
return@all thisDescriptor != null && superClassDescriptor != null && runReadAction { DescriptorUtils.isSubclass(thisDescriptor, superClassDescriptor) } return@all thisDescriptor != null && superClassDescriptor != null && runReadAction {
DescriptorUtils.isSubclass(
thisDescriptor,
superClassDescriptor
)
}
} }
} }
@@ -230,6 +244,7 @@ class KotlinDebuggerCaches(project: Project) {
data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null, val error: EvaluateException? = null) data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null, val error: EvaluateException? = null)
class ComputedClassNames(val classNames: List<String>, val shouldBeCached: Boolean) { class ComputedClassNames(val classNames: List<String>, val shouldBeCached: Boolean) {
@Suppress("FunctionName")
companion object { companion object {
val EMPTY = ComputedClassNames.Cached(emptyList()) val EMPTY = ComputedClassNames.Cached(emptyList())
@@ -242,8 +257,7 @@ class KotlinDebuggerCaches(project: Project) {
fun distinct() = ComputedClassNames(classNames.distinct(), shouldBeCached) fun distinct() = ComputedClassNames(classNames.distinct(), shouldBeCached)
operator fun plus(other: ComputedClassNames) = ComputedClassNames( operator fun plus(other: ComputedClassNames) = ComputedClassNames(
classNames + other.classNames, shouldBeCached && other.shouldBeCached) classNames + other.classNames, shouldBeCached && other.shouldBeCached
)
} }
} }
private fun String?.toList() = if (this == null) emptyList() else listOf(this)
@@ -95,12 +95,11 @@ import org.jetbrains.org.objectweb.asm.tree.ClassNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode import org.jetbrains.org.objectweb.asm.tree.MethodNode
import java.util.* import java.util.*
internal val THIS_NAME = "this"
internal val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluator") internal val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluator")
internal val GENERATED_FUNCTION_NAME = "generated_for_debugger_fun" internal const val GENERATED_FUNCTION_NAME = "generated_for_debugger_fun"
internal val GENERATED_CLASS_NAME = "Generated_for_debugger_class" internal const val GENERATED_CLASS_NAME = "Generated_for_debugger_class"
private val DEBUG_MODE = false private const val DEBUG_MODE = false
object KotlinEvaluationBuilder : EvaluatorBuilder { object KotlinEvaluationBuilder : EvaluatorBuilder {
override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator { override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator {
@@ -117,17 +116,23 @@ object KotlinEvaluationBuilder : EvaluatorBuilder {
val document = PsiDocumentManager.getInstance(file.project).getDocument(file) val document = PsiDocumentManager.getInstance(file.project).getDocument(file)
if (document == null || document.lineCount < position.line) { if (document == null || document.lineCount < position.line) {
throw EvaluateExceptionUtil.createEvaluateException( throw EvaluateExceptionUtil.createEvaluateException(
"Couldn't evaluate kotlin expression: breakpoint is placed outside the file. " + "Couldn't evaluate kotlin expression: breakpoint is placed outside the file. " +
"It may happen when you've changed source file after starting a debug process.") "It may happen when you've changed source file after starting a debug process."
)
} }
} }
if (codeFragment.context !is KtElement) { if (codeFragment.context !is KtElement) {
val attachments = arrayOf(attachmentByPsiFile(position.file), val attachments = arrayOf(
attachmentByPsiFile(codeFragment), attachmentByPsiFile(position.file),
Attachment("breakpoint.info", "line: ${position.line}")) attachmentByPsiFile(codeFragment),
Attachment("breakpoint.info", "line: ${position.line}")
)
LOG.error("Trying to evaluate ${codeFragment::class.java} with context ${codeFragment.context?.javaClass}", mergeAttachments(*attachments)) LOG.error(
"Trying to evaluate ${codeFragment::class.java} with context ${codeFragment.context?.javaClass}",
mergeAttachments(*attachments)
)
throw EvaluateExceptionUtil.createEvaluateException("Couldn't evaluate kotlin expression in this context") throw EvaluateExceptionUtil.createEvaluateException("Couldn't evaluate kotlin expression in this context")
} }
@@ -147,8 +152,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
var isCompiledDataFromCache = true var isCompiledDataFromCache = true
try { try {
val compiledData = KotlinDebuggerCaches.getOrCreateCompiledData(codeFragment, sourcePosition, context) { val compiledData = KotlinDebuggerCaches.getOrCreateCompiledData(codeFragment, sourcePosition, context) { fragment, position ->
fragment, position ->
isCompiledDataFromCache = false isCompiledDataFromCache = false
extractAndCompile(fragment, position, context) extractAndCompile(fragment, position, context)
} }
@@ -157,8 +161,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
val result = if (classLoaderRef != null) { val result = if (classLoaderRef != null) {
evaluateWithCompilation(context, compiledData, classLoaderRef) ?: runEval4j(context, compiledData, classLoaderRef) evaluateWithCompilation(context, compiledData, classLoaderRef) ?: runEval4j(context, compiledData, classLoaderRef)
} } else {
else {
runEval4j(context, compiledData, classLoaderRef) runEval4j(context, compiledData, classLoaderRef)
} }
@@ -172,32 +175,33 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
} else { } else {
result result
} }
} } catch (e: EvaluateException) {
catch (e: EvaluateException) {
throw e throw e
} } catch (e: ProcessCanceledException) {
catch (e: ProcessCanceledException) {
exception(e) exception(e)
} } catch (e: Eval4JInterpretingException) {
catch (e: Eval4JInterpretingException) {
exception(e.cause) exception(e.cause)
} } catch (e: Exception) {
catch (e: Exception) {
val isSpecialException = isSpecialException(e) val isSpecialException = isSpecialException(e)
if (isSpecialException) { if (isSpecialException) {
exception(e) exception(e)
} }
val text = runReadAction { codeFragment.context?.text ?: "null" } val text = runReadAction { codeFragment.context?.text ?: "null" }
val attachments = arrayOf(attachmentByPsiFile(sourcePosition.file), val attachments = arrayOf(
attachmentByPsiFile(codeFragment), attachmentByPsiFile(sourcePosition.file),
Attachment("breakpoint.info", "line: ${runReadAction { sourcePosition.line }}"), attachmentByPsiFile(codeFragment),
Attachment("context.info", text)) Attachment("breakpoint.info", "line: ${runReadAction { sourcePosition.line }}"),
Attachment("context.info", text)
)
LOG.error(LogMessageEx.createEvent( LOG.error(
LogMessageEx.createEvent(
"Couldn't evaluate expression", "Couldn't evaluate expression",
ExceptionUtil.getThrowableText(e), ExceptionUtil.getThrowableText(e),
mergeAttachments(*attachments))) mergeAttachments(*attachments)
)
)
val cause = if (e.message != null) ": ${e.message}" else "" val cause = if (e.message != null) ": ${e.message}" else ""
exception("An exception occurs during Evaluate Expression Action $cause") exception("An exception occurs during Evaluate Expression Action $cause")
@@ -223,7 +227,11 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
} }
companion object { companion object {
private fun extractAndCompile(codeFragment: KtCodeFragment, sourcePosition: SourcePosition, context: EvaluationContextImpl): CompiledDataDescriptor { private fun extractAndCompile(
codeFragment: KtCodeFragment,
sourcePosition: SourcePosition,
context: EvaluationContextImpl
): CompiledDataDescriptor {
var bindingContext = codeFragment.checkForErrors().bindingContext var bindingContext = codeFragment.checkForErrors().bindingContext
if (codeFragment.wrapToStringIfNeeded(bindingContext)) { if (codeFragment.wrapToStringIfNeeded(bindingContext)) {
@@ -234,7 +242,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
val variablesCrossingInlineBounds = ScopeCheckerForEvaluator.checkScopes(bindingContext, codeFragment) val variablesCrossingInlineBounds = ScopeCheckerForEvaluator.checkScopes(bindingContext, codeFragment)
val extractionResult = getFunctionForExtractedFragment(codeFragment, sourcePosition.file, sourcePosition.line) val extractionResult = getFunctionForExtractedFragment(codeFragment, sourcePosition.file, sourcePosition.line)
?: throw IllegalStateException("Code fragment cannot be extracted to function: ${codeFragment.text}") ?: throw IllegalStateException("Code fragment cannot be extracted to function: ${codeFragment.text}")
val (parametersDescriptor, extractedFunction) = try { val (parametersDescriptor, extractedFunction) = try {
extractionResult.getParametersForDebugger(codeFragment, context) to extractionResult.declaration as KtNamedFunction extractionResult.getParametersForDebugger(codeFragment, context) to extractionResult.declaration as KtNamedFunction
} finally { } finally {
@@ -253,6 +261,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
if (LOG.isDebugEnabled) { if (LOG.isDebugEnabled) {
LOG.debug("Output file generated: ${file.relativePath}") LOG.debug("Output file generated: ${file.relativePath}")
} }
@Suppress("ConstantConditionIf")
if (DEBUG_MODE) { if (DEBUG_MODE) {
println(file.asText()) println(file.asText())
} }
@@ -297,7 +306,8 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
private val CompiledDataDescriptor.mainClass private val CompiledDataDescriptor.mainClass
get() = classes.firstOrNull { it.isMainClass() } ?: error( get() = classes.firstOrNull { it.isMainClass() } ?: error(
"Can't find main class for " + sourcePosition.elementAt.getParentOfType<KtDeclaration>(strict = false)) "Can't find main class for " + sourcePosition.elementAt.getParentOfType<KtDeclaration>(strict = false)
)
private fun evaluateWithCompilation( private fun evaluateWithCompilation(
context: EvaluationContextImpl, context: EvaluationContextImpl,
@@ -331,12 +341,12 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
val argumentTypes = Type.getArgumentTypes(methodToInvoke.desc) val argumentTypes = Type.getArgumentTypes(methodToInvoke.desc)
val args = context.getArgumentsForEvaluation(compiledData.parameters, argumentTypes, compiledData) val args = context.getArgumentsForEvaluation(compiledData.parameters, argumentTypes, compiledData)
.zip(argumentTypes) .zip(argumentTypes)
.map { (value, type) -> .map { (value, type) ->
// Make argument type classes prepared for sure // Make argument type classes prepared for sure
eval.loadClassByName(type.className, classLoader) eval.loadClassByName(type.className, classLoader)
boxOrUnboxArgumentIfNeeded(eval, value, type).asJdiValue(vm, type) boxOrUnboxArgumentIfNeeded(eval, value, type).asJdiValue(vm, type)
} }
mainClass.invokeMethod(thread, mainClass.methods().single(), args, invokePolicy) mainClass.invokeMethod(thread, mainClass.methods().single(), args, invokePolicy)
@@ -359,8 +369,15 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
val mainClassBytecode = compiledData.mainClass.bytes val mainClassBytecode = compiledData.mainClass.bytes
ClassReader(mainClassBytecode).accept(object : ClassVisitor(API_VERSION) { ClassReader(mainClassBytecode).accept(object : ClassVisitor(API_VERSION) {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?): MethodVisitor? { 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' // 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 + "-")) { if (name == GENERATED_FUNCTION_NAME || name.startsWith(GENERATED_FUNCTION_NAME + "-")) {
val argumentTypes = Type.getArgumentTypes(desc) val argumentTypes = Type.getArgumentTypes(desc)
val args = context.getArgumentsForEvaluation(compiledData.parameters, argumentTypes, compiledData) val args = context.getArgumentsForEvaluation(compiledData.parameters, argumentTypes, compiledData)
@@ -368,15 +385,25 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
return object : MethodNode(Opcodes.API_VERSION, access, name, desc, signature, exceptions) { return object : MethodNode(Opcodes.API_VERSION, access, name, desc, signature, exceptions) {
override fun visitEnd() { override fun visitEnd() {
virtualMachine.executeWithBreakpointsDisabled { virtualMachine.executeWithBreakpointsDisabled {
val eval = JDIEval(virtualMachine, val eval = JDIEval(
classLoader ?: context.classLoader, virtualMachine,
context.suspendContext.thread?.threadReference!!, classLoader ?: context.classLoader,
context.suspendContext.getInvokePolicy()) context.suspendContext.thread?.threadReference!!,
context.suspendContext.getInvokePolicy()
)
resultValue = interpreterLoop( resultValue = interpreterLoop(
this,
makeInitialFrame(
this, this,
makeInitialFrame(this, args.zip(argumentTypes).map { boxOrUnboxArgumentIfNeeded(eval, it.first, it.second) }), args.zip(argumentTypes).map {
eval boxOrUnboxArgumentIfNeeded(
eval,
it.first,
it.second
)
}),
eval
) )
} }
} }
@@ -387,7 +414,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
} }
}, 0) }, 0)
return resultValue ?: throw IllegalStateException("resultValue is null: cannot find method " + GENERATED_FUNCTION_NAME) return resultValue ?: throw IllegalStateException("resultValue is null: cannot find method $GENERATED_FUNCTION_NAME")
} }
private inline fun <T> VirtualMachine.executeWithBreakpointsDisabled(block: () -> T): T { private inline fun <T> VirtualMachine.executeWithBreakpointsDisabled(block: () -> T): T {
@@ -410,8 +437,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
if (parameterType == unboxedType) { if (parameterType == unboxedType) {
return eval.unboxType(argumentValue, parameterType) return eval.unboxType(argumentValue, parameterType)
} }
} } catch (ignored: UnsupportedOperationException) {
catch(ignored: UnsupportedOperationException) {
} }
} }
@@ -465,7 +491,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
override fun visitProperty(property: KtProperty) { override fun visitProperty(property: KtProperty) {
val value = property.getUserData(KotlinCodeFragmentFactory.LABEL_VARIABLE_VALUE_KEY) val value = property.getUserData(KotlinCodeFragmentFactory.LABEL_VARIABLE_VALUE_KEY)
if (value != null) { if (value != null) {
valuesForLabels.put(property.name?.quoteIfNeeded()!!, value.asValue()) valuesForLabels[property.name?.quoteIfNeeded()!!] = value.asValue()
} }
} }
}) })
@@ -474,7 +500,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
val parameters = mutableListOf<Parameter>() val parameters = mutableListOf<Parameter>()
val receiver = config.descriptor.receiverParameter val receiver = config.descriptor.receiverParameter
if (receiver != null) { if (receiver != null) {
parameters += Parameter(THIS_NAME + "@" + config.descriptor.name, receiver.getParameterType(true)) parameters += Parameter(AsmUtil.THIS + "@" + config.descriptor.name, receiver.getParameterType(true))
} }
for (param in config.descriptor.parameters) { for (param in config.descriptor.parameters) {
@@ -537,10 +563,10 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
} }
private fun createClassFileFactory( private fun createClassFileFactory(
codeFragment: KtCodeFragment, codeFragment: KtCodeFragment,
extractedFunction: KtNamedFunction, extractedFunction: KtNamedFunction,
context: EvaluationContextImpl, context: EvaluationContextImpl,
parameters: List<Parameter> parameters: List<Parameter>
): ClassFileFactory { ): ClassFileFactory {
return runReadAction { return runReadAction {
val fileForDebugger = createFileForDebugger(codeFragment, extractedFunction) val fileForDebugger = createFileForDebugger(codeFragment, extractedFunction)
@@ -548,22 +574,28 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
LOG.debug("File for eval4j:\n${runReadAction { fileForDebugger.text }}") LOG.debug("File for eval4j:\n${runReadAction { fileForDebugger.text }}")
} }
val (bindingContext, moduleDescriptor, files) = fileForDebugger.checkForErrors(true, codeFragment.getContextContainingFile()) val (bindingContext, moduleDescriptor, files) = fileForDebugger.checkForErrors(
true,
codeFragment.getContextContainingFile()
)
val generateClassFilter = object : GenerationState.GenerateClassFilter() { val generateClassFilter = object : GenerationState.GenerateClassFilter() {
override fun shouldGeneratePackagePart(ktFile: KtFile) = ktFile == fileForDebugger override fun shouldGeneratePackagePart(ktFile: KtFile) = ktFile == fileForDebugger
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject) = true override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject) = true
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = processingClassOrObject.containingKtFile == fileForDebugger override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) =
processingClassOrObject.containingKtFile == fileForDebugger
override fun shouldGenerateScript(script: KtScript) = false override fun shouldGenerateScript(script: KtScript) = false
} }
@Suppress("ConstantConditionIf")
val state = GenerationState.Builder( val state = GenerationState.Builder(
fileForDebugger.project, fileForDebugger.project,
if (!DEBUG_MODE) ClassBuilderFactories.BINARIES else ClassBuilderFactories.TEST, if (!DEBUG_MODE) ClassBuilderFactories.BINARIES else ClassBuilderFactories.TEST,
moduleDescriptor, moduleDescriptor,
bindingContext, bindingContext,
files, files,
CompilerConfiguration.EMPTY CompilerConfiguration.EMPTY
).generateDeclaredClassFilter(generateClassFilter).build() ).generateDeclaredClassFilter(generateClassFilter).build()
val variableFinder = VariableFinder.instance(context) ?: error("No stack frame available") val variableFinder = VariableFinder.instance(context) ?: error("No stack frame available")
@@ -577,15 +609,16 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
} }
val valueParameters = extractedFunction.valueParameters val valueParameters = extractedFunction.valueParameters
var paramIndex = 0 for ((paramIndex, param) in parameters.withIndex()) {
for (param in parameters) { val valueParameter = valueParameters[paramIndex]
val valueParameter = valueParameters[paramIndex++]
val paramRef = valueParameter.typeReference val paramRef = valueParameter.typeReference
if (paramRef == null) { if (paramRef == null) {
LOG.error("Each parameter for extracted function should have a type reference", LOG.error(
Attachment("codeFragment.txt", codeFragment.text), "Each parameter for extracted function should have a type reference",
Attachment("extractedFunction.txt", extractedFunction.text)) Attachment("codeFragment.txt", codeFragment.text),
Attachment("extractedFunction.txt", extractedFunction.text)
)
exception("An exception occurs during Evaluate Expression Action") exception("An exception occurs during Evaluate Expression Action")
} }
@@ -631,8 +664,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
return runInReadActionWithWriteActionPriorityWithPCE { return runInReadActionWithWriteActionPriorityWithPCE {
try { try {
AnalyzingUtils.checkForSyntacticErrors(this) AnalyzingUtils.checkForSyntacticErrors(this)
} } catch (e: IllegalArgumentException) {
catch (e: IllegalArgumentException) {
throw EvaluateExceptionUtil.createEvaluateException(e.message) throw EvaluateExceptionUtil.createEvaluateException(e.message)
} }
@@ -655,18 +687,21 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
if (analyzeInlineFunctions) { if (analyzeInlineFunctions) {
val (newBindingContext, files) = DebuggerUtils.analyzeInlinedFunctions(resolutionFacade, this, false) val (newBindingContext, files) = DebuggerUtils.analyzeInlinedFunctions(resolutionFacade, this, false)
ExtendedAnalysisResult(newBindingContext, analysisResult.moduleDescriptor, files) ExtendedAnalysisResult(newBindingContext, analysisResult.moduleDescriptor, files)
} } else {
else {
ExtendedAnalysisResult(bindingContext, analysisResult.moduleDescriptor, Collections.singletonList(this)) ExtendedAnalysisResult(bindingContext, analysisResult.moduleDescriptor, Collections.singletonList(this))
} }
} }
} }
private data class ExtendedAnalysisResult(val bindingContext: BindingContext, val moduleDescriptor: ModuleDescriptor, val files: List<KtFile>) private data class ExtendedAnalysisResult(
val bindingContext: BindingContext,
val moduleDescriptor: ModuleDescriptor,
val files: List<KtFile>
)
} }
} }
private val template = """ private const val template = """
@file:kotlin.jvm.JvmName("$GENERATED_CLASS_NAME") @file:kotlin.jvm.JvmName("$GENERATED_CLASS_NAME")
!PACKAGE! !PACKAGE!
@@ -675,24 +710,22 @@ private val template = """
!FUNCTION! !FUNCTION!
""" """
private fun createFileForDebugger(codeFragment: KtCodeFragment, private fun createFileForDebugger(
extractedFunction: KtNamedFunction codeFragment: KtCodeFragment,
extractedFunction: KtNamedFunction
): KtFile { ): KtFile {
val containingContextFile = codeFragment.getContextContainingFile() val containingContextFile = codeFragment.getContextContainingFile()
val importsFromContextFile = containingContextFile?.importList?.let { it.text + "\n" } ?: "" val importsFromContextFile = containingContextFile?.importList?.let { it.text + "\n" } ?: ""
var fileText = template.replace( var fileText = template.replace(
"!IMPORT_LIST!", "!IMPORT_LIST!",
importsFromContextFile + codeFragment.importsToString().split(KtCodeFragment.IMPORT_SEPARATOR).joinToString("\n") importsFromContextFile + codeFragment.importsToString().split(KtCodeFragment.IMPORT_SEPARATOR).joinToString("\n")
) )
val packageFromContextFile = containingContextFile?.packageFqName?.let { val packageFromContextFile = containingContextFile?.packageFqName?.let {
if (!it.isRoot) "package ${it.quoteSegmentsIfNeeded()}" else "" if (!it.isRoot) "package ${it.quoteSegmentsIfNeeded()}" else ""
} ?: "" } ?: ""
fileText = fileText.replace("!PACKAGE!", packageFromContextFile) fileText = fileText.replace("!PACKAGE!", packageFromContextFile)
val extractedFunctionText = extractedFunction.text
assert(extractedFunctionText != null) { "Text of extracted function shouldn't be null" }
fileText = fileText.replace("!FUNCTION!", extractedFunction.text!!) fileText = fileText.replace("!FUNCTION!", extractedFunction.text!!)
val jetFile = codeFragment.createKtFile("debugFile.kt", fileText) val jetFile = codeFragment.createKtFile("debugFile.kt", fileText)
@@ -717,7 +750,7 @@ private fun PsiElement.createKtFile(fileName: String, fileText: String): KtFile
val virtualFile = LightVirtualFile(fileName, KotlinLanguage.INSTANCE, fileText) val virtualFile = LightVirtualFile(fileName, KotlinLanguage.INSTANCE, fileText)
virtualFile.charset = CharsetToolkit.UTF8_CHARSET virtualFile.charset = CharsetToolkit.UTF8_CHARSET
val jetFile = (PsiFileFactory.getInstance(project) as PsiFileFactoryImpl) val jetFile = (PsiFileFactory.getInstance(project) as PsiFileFactoryImpl)
.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile .trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile
jetFile.analysisContext = this jetFile.analysisContext = this
return jetFile return jetFile
} }
@@ -733,7 +766,7 @@ fun Type.getClassDescriptor(scope: GlobalSearchScope): ClassDescriptor? {
// TODO: use the correct built-ins from the module instead of DefaultBuiltIns here // TODO: use the correct built-ins from the module instead of DefaultBuiltIns here
JavaToKotlinClassMap.mapJavaToKotlin(jvmName)?.let( JavaToKotlinClassMap.mapJavaToKotlin(jvmName)?.let(
DefaultBuiltIns.Instance.builtInsModule::findClassAcrossModuleDependencies DefaultBuiltIns.Instance.builtInsModule::findClassAcrossModuleDependencies
)?.let { return it } )?.let { return it }
return runReadAction { return runReadAction {
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.idea.debugger.evaluate
import com.intellij.debugger.DebuggerBundle import com.intellij.debugger.DebuggerBundle
import com.intellij.debugger.DebuggerInvocationUtil import com.intellij.debugger.DebuggerInvocationUtil
import com.intellij.debugger.EvaluatingComputable
import com.intellij.debugger.engine.ContextUtil import com.intellij.debugger.engine.ContextUtil
import com.intellij.debugger.engine.evaluation.EvaluateException import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
@@ -41,22 +40,19 @@ import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Type as AsmType import org.jetbrains.org.objectweb.asm.Type as AsmType
abstract class KotlinRuntimeTypeEvaluator( abstract class KotlinRuntimeTypeEvaluator(
editor: Editor?, editor: Editor?,
expression: KtExpression, expression: KtExpression,
context: DebuggerContextImpl, context: DebuggerContextImpl,
indicator: ProgressIndicator indicator: ProgressIndicator
) : EditorEvaluationCommand<KotlinType>(editor, expression, context, indicator) { ) : EditorEvaluationCommand<KotlinType>(editor, expression, context, indicator) {
override fun threadAction() { override fun threadAction() {
var type: KotlinType? = null var type: KotlinType? = null
try { try {
type = evaluate() type = evaluate()
} } catch (ignored: ProcessCanceledException) {
catch (ignored: ProcessCanceledException) { } catch (ignored: EvaluateException) {
} } finally {
catch (ignored: EvaluateException) {
}
finally {
typeCalculationFinished(type) typeCalculationFinished(type)
} }
} }
@@ -66,11 +62,12 @@ abstract class KotlinRuntimeTypeEvaluator(
override fun evaluate(evaluationContext: EvaluationContextImpl): KotlinType? { override fun evaluate(evaluationContext: EvaluationContextImpl): KotlinType? {
val project = evaluationContext.project val project = evaluationContext.project
val evaluator = DebuggerInvocationUtil.commitAndRunReadAction<ExpressionEvaluator>(project, EvaluatingComputable { val evaluator = DebuggerInvocationUtil.commitAndRunReadAction<ExpressionEvaluator>(project) {
val codeFragment = KtPsiFactory(myElement.project).createExpressionCodeFragment( val codeFragment = KtPsiFactory(myElement.project).createExpressionCodeFragment(
myElement.text, myElement.containingFile.context) myElement.text, myElement.containingFile.context
KotlinEvaluationBuilder.build(codeFragment, ContextUtil.getSourcePosition(evaluationContext)) )
}) KotlinEvaluationBuilder.build(codeFragment, ContextUtil.getSourcePosition(evaluationContext))
}
val value = evaluator.evaluate(evaluationContext) val value = evaluator.evaluate(evaluationContext)
if (value != null) { if (value != null) {
@@ -29,15 +29,15 @@ abstract class AbstractAndroidClassLoadingAdapter : ClassLoadingAdapter {
protected fun wrapToByteBuffer(bytes: ArrayReference, context: EvaluationContext, process: DebugProcessImpl): ObjectReference { protected fun wrapToByteBuffer(bytes: ArrayReference, context: EvaluationContext, process: DebugProcessImpl): ObjectReference {
val byteBufferClass = process.findClass(context, "java.nio.ByteBuffer", context.classLoader) as ClassType val byteBufferClass = process.findClass(context, "java.nio.ByteBuffer", context.classLoader) as ClassType
val wrapMethod = byteBufferClass.concreteMethodByName("wrap", "([B)Ljava/nio/ByteBuffer;") val wrapMethod = byteBufferClass.concreteMethodByName("wrap", "([B)Ljava/nio/ByteBuffer;")
?: error("'wrap' method not found") ?: error("'wrap' method not found")
return process.invokeMethod(context, byteBufferClass, wrapMethod, listOf(bytes)) as ObjectReference return process.invokeMethod(context, byteBufferClass, wrapMethod, listOf(bytes)) as ObjectReference
} }
protected fun DebugProcessImpl.tryLoadClass( protected fun DebugProcessImpl.tryLoadClass(
context: EvaluationContextImpl, context: EvaluationContextImpl,
fqName: String, fqName: String,
classLoader: ClassLoaderReference? classLoader: ClassLoaderReference?
): ReferenceType? { ): ReferenceType? {
return try { return try {
loadClass(context, fqName, classLoader) loadClass(context, fqName, classLoader)
@@ -20,7 +20,8 @@ import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
interface AndroidDexer { interface AndroidDexer {
companion object : ProjectExtensionDescriptor<AndroidDexer>( companion object : ProjectExtensionDescriptor<AndroidDexer>(
"org.jetbrains.kotlin.androidDexer", AndroidDexer::class.java) "org.jetbrains.kotlin.androidDexer", AndroidDexer::class.java
)
fun dex(classes: Collection<ClassToLoad>): ByteArray? fun dex(classes: Collection<ClassToLoad>): ByteArray?
} }
@@ -29,11 +29,12 @@ class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() {
} }
private fun resolveClassLoaderClass(context: EvaluationContextImpl): ClassType? { private fun resolveClassLoaderClass(context: EvaluationContextImpl): ClassType? {
try { return try {
return context.debugProcess.tryLoadClass( context.debugProcess.tryLoadClass(
context, "dalvik.system.InMemoryDexClassLoader", context.classLoader) as? ClassType context, "dalvik.system.InMemoryDexClassLoader", context.classLoader
) as? ClassType
} catch (e: EvaluateException) { } catch (e: EvaluateException) {
return null null
} }
} }
@@ -41,14 +42,17 @@ class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() {
val process = context.debugProcess val process = context.debugProcess
val inMemoryClassLoaderClass = resolveClassLoaderClass(context) ?: error("InMemoryDexClassLoader class not found") val inMemoryClassLoaderClass = resolveClassLoaderClass(context) ?: error("InMemoryDexClassLoader class not found")
val constructorMethod = inMemoryClassLoaderClass.concreteMethodByName( val constructorMethod = inMemoryClassLoaderClass.concreteMethodByName(
JVMNameUtil.CONSTRUCTOR_NAME, "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V") ?: error("Constructor method not found") JVMNameUtil.CONSTRUCTOR_NAME, "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V"
) ?: error("Constructor method not found")
val dexBytes = dex(context, classes) ?: error("Can't dex classes") val dexBytes = dex(context, classes) ?: error("Can't dex classes")
val dexBytesMirror = mirrorOfByteArray(dexBytes, context, process) val dexBytesMirror = mirrorOfByteArray(dexBytes, context, process)
val dexByteBuffer = wrapToByteBuffer(dexBytesMirror, context, process) val dexByteBuffer = wrapToByteBuffer(dexBytesMirror, context, process)
val newClassLoader = process.newInstance(context, inMemoryClassLoaderClass, constructorMethod, val newClassLoader = process.newInstance(
listOf(dexByteBuffer, context.classLoader)) context, inMemoryClassLoaderClass, constructorMethod,
listOf(dexByteBuffer, context.classLoader)
)
DebuggerUtilsEx.keep(newClassLoader, context) DebuggerUtilsEx.keep(newClassLoader, context)
@@ -73,46 +73,44 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
val classLoader = try { val classLoader = try {
ClassLoadingUtils.getClassLoader(context, process) ClassLoadingUtils.getClassLoader(context, process)
} } catch (e: Exception) {
catch (e: Exception) { throw EvaluateException("Error creating evaluation class loader: $e", e)
throw EvaluateException("Error creating evaluation class loader: " + e, e)
} }
val debugProcessVersionString = process.virtualMachineProxy.version() val debugProcessVersionString = process.virtualMachineProxy.version()
val debugProcessVersion = JavaSdkVersion.fromVersionString(debugProcessVersionString) val debugProcessVersion = JavaSdkVersion.fromVersionString(debugProcessVersionString)
?: throw EvaluateException("Unable to parse java version from $debugProcessVersionString.") ?: throw EvaluateException("Unable to parse java version from $debugProcessVersionString.")
val ideaJavaVersion = JavaSdkVersion.fromVersionString(SystemInfo.JAVA_RUNTIME_VERSION) val ideaJavaVersion = JavaSdkVersion.fromVersionString(SystemInfo.JAVA_RUNTIME_VERSION)
?: throw EvaluateException("Unable to parse java version from ${SystemInfo.JAVA_RUNTIME_VERSION}.") ?: throw EvaluateException("Unable to parse java version from ${SystemInfo.JAVA_RUNTIME_VERSION}.")
if (!ideaJavaVersion.isAtLeast(debugProcessVersion)) { if (!ideaJavaVersion.isAtLeast(debugProcessVersion)) {
throw EvaluateException( throw EvaluateException(
"Unable to compile for target level ${debugProcessVersion.description}. " + "Unable to compile for target level ${debugProcessVersion.description}. " +
"Need to run IDEA on java version at least $debugProcessVersion, " + "Need to run IDEA on java version at least $debugProcessVersion, " +
"currently running on $ideaJavaVersion") "currently running on $ideaJavaVersion"
)
} }
try { try {
defineClasses(classes, context, process, classLoader) defineClasses(classes, context, process, classLoader)
} } catch (e: Exception) {
catch (e: Exception) { throw EvaluateException("Error during classes definition $e", e)
throw EvaluateException("Error during classes definition " + e, e)
} }
return classLoader return classLoader
} }
private fun defineClasses( private fun defineClasses(
classes: Collection<ClassToLoad>, classes: Collection<ClassToLoad>,
context: EvaluationContextImpl, context: EvaluationContextImpl,
process: DebugProcessImpl, process: DebugProcessImpl,
classLoader: ClassLoaderReference classLoader: ClassLoaderReference
) { ) {
val classesToLoad = if (classes.size == 1) { val classesToLoad = if (classes.size == 1) {
// No need in loading lambda superclass if there're no lambdas // No need in loading lambda superclass if there're no lambdas
classes classes
} } else {
else {
val lambdaSuperclasses = LAMBDA_SUPERCLASSES.map { val lambdaSuperclasses = LAMBDA_SUPERCLASSES.map {
ClassToLoad(it.name, it.name.replace('.', '/') + ".class", it.bytes) ClassToLoad(it.name, it.name.replace('.', '/') + ".class", it.bytes)
} }
@@ -125,12 +123,12 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
} }
} }
fun defineClass( private fun defineClass(
name: String, name: String,
bytes: ByteArray, bytes: ByteArray,
context: EvaluationContextImpl, context: EvaluationContextImpl,
process: DebugProcessImpl, process: DebugProcessImpl,
classLoader: ClassLoaderReference classLoader: ClassLoaderReference
) { ) {
try { try {
val vm = process.virtualMachineProxy val vm = process.virtualMachineProxy
@@ -138,13 +136,15 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
val defineMethod = classLoaderType.concreteMethodByName("defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;") val defineMethod = classLoaderType.concreteMethodByName("defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;")
val nameObj = vm.mirrorOf(name) val nameObj = vm.mirrorOf(name)
// Still actual for older platform versions
@Suppress("DEPRECATION")
DebuggerUtilsEx.keep(nameObj, context) DebuggerUtilsEx.keep(nameObj, context)
process.invokeMethod( process.invokeMethod(
context, classLoader, defineMethod, context, classLoader, defineMethod,
listOf(nameObj, mirrorOfByteArray(bytes, context, process), vm.mirrorOf(0), vm.mirrorOf(bytes.size))) listOf(nameObj, mirrorOfByteArray(bytes, context, process), vm.mirrorOf(0), vm.mirrorOf(bytes.size))
} )
catch (e: Exception) { } catch (e: Exception) {
throw EvaluateException("Error during class $name definition: $e", e) throw EvaluateException("Error during class $name definition: $e", e)
} }
@@ -153,7 +153,7 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
private class ClassBytes(val name: String) { private class ClassBytes(val name: String) {
val bytes: ByteArray by lazy { val bytes: ByteArray by lazy {
val inputStream = this::class.java.classLoader.getResourceAsStream(name.replace('.', '/') + ".class") val inputStream = this::class.java.classLoader.getResourceAsStream(name.replace('.', '/') + ".class")
?: throw EvaluateException("Couldn't find $name class in current class loader") ?: throw EvaluateException("Couldn't find $name class in current class loader")
inputStream.use { inputStream.use {
it.readBytes() it.readBytes()
@@ -44,26 +44,31 @@ import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext.SMARTCAST import org.jetbrains.kotlin.resolve.BindingContext.SMARTCAST
fun getFunctionForExtractedFragment( fun getFunctionForExtractedFragment(
codeFragment: KtCodeFragment, codeFragment: KtCodeFragment,
breakpointFile: PsiFile, breakpointFile: PsiFile,
breakpointLine: Int breakpointLine: Int
): ExtractionResult? { ): ExtractionResult? {
fun getErrorMessageForExtractFunctionResult(analysisResult: AnalysisResult, tmpFile: KtFile): String { fun getErrorMessageForExtractFunctionResult(analysisResult: AnalysisResult, tmpFile: KtFile): String {
if (ApplicationManager.getApplication().isInternal) { if (ApplicationManager.getApplication().isInternal) {
val attachments = arrayOf(attachmentByPsiFile(tmpFile), val attachments = arrayOf(
attachmentByPsiFile(breakpointFile), attachmentByPsiFile(tmpFile),
attachmentByPsiFile(codeFragment), attachmentByPsiFile(breakpointFile),
Attachment("breakpoint.info", "line: $breakpointLine"), attachmentByPsiFile(codeFragment),
Attachment("context.info", codeFragment.context?.text ?: "null"), Attachment("breakpoint.info", "line: $breakpointLine"),
Attachment("errors.info", analysisResult.messages.joinToString("\n") { "$it: ${it.renderMessage()}" })) Attachment("context.info", codeFragment.context?.text ?: "null"),
LOG.error(LogMessageEx.createEvent( Attachment("errors.info", analysisResult.messages.joinToString("\n") { "$it: ${it.renderMessage()}" })
)
LOG.error(
LogMessageEx.createEvent(
"Internal error during evaluate expression", "Internal error during evaluate expression",
ExceptionUtil.getThrowableText(Throwable("Extract function fails with ${analysisResult.messages.joinToString { it.name }}")), ExceptionUtil.getThrowableText(Throwable("Extract function fails with ${analysisResult.messages.joinToString { it.name }}")),
mergeAttachments(*attachments))) mergeAttachments(*attachments)
)
)
} }
return analysisResult.messages.joinToString(", ") { errorMessage -> return analysisResult.messages.joinToString(", ") { errorMessage ->
val message = when(errorMessage) { val message = when (errorMessage) {
ErrorMessage.NO_EXPRESSION -> "Cannot perform an action without an expression" ErrorMessage.NO_EXPRESSION -> "Cannot perform an action without an expression"
ErrorMessage.NO_CONTAINER -> "Cannot perform an action at this breakpoint ${breakpointFile.name}:$breakpointLine" 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.SYNTAX_ERRORS -> "Cannot perform an action due to erroneous code"
@@ -93,11 +98,13 @@ fun getFunctionForExtractedFragment(
val targetSibling = tmpFile.declarations.firstOrNull() ?: return null val targetSibling = tmpFile.declarations.firstOrNull() ?: return null
val options = ExtractionOptions(inferUnitTypeForUnusedValues = false, val options = ExtractionOptions(
enableListBoxing = true, inferUnitTypeForUnusedValues = false,
allowSpecialClassNames = true, enableListBoxing = true,
captureLocalFunctions = true, allowSpecialClassNames = true,
canWrapInWith = true) captureLocalFunctions = true,
canWrapInWith = true
)
val extractionData = ExtractionData(tmpFile, newDebugExpressions.toRange(), targetSibling, null, options) val extractionData = ExtractionData(tmpFile, newDebugExpressions.toRange(), targetSibling, null, options)
try { try {
val analysisResult = extractionData.performAnalysis() val analysisResult = extractionData.performAnalysis()
@@ -107,12 +114,18 @@ fun getFunctionForExtractedFragment(
val validationResult = analysisResult.descriptor!!.validate() val validationResult = analysisResult.descriptor!!.validate()
if (!validationResult.conflicts.isEmpty) { if (!validationResult.conflicts.isEmpty) {
throw EvaluateExceptionUtil.createEvaluateException("Following declarations are unavailable in debug scope: ${validationResult.conflicts.keySet().joinToString(",") { it.text }}") throw EvaluateExceptionUtil.createEvaluateException(
"Following declarations are unavailable in debug scope: ${validationResult.conflicts.keySet().joinToString(
","
) { it.text }}"
)
} }
val generatorOptions = ExtractionGeneratorOptions(inTempFile = true, val generatorOptions = ExtractionGeneratorOptions(
dummyName = GENERATED_FUNCTION_NAME, inTempFile = true,
allowExpressionBody = false) dummyName = GENERATED_FUNCTION_NAME,
allowExpressionBody = false
)
return ExtractionGeneratorConfiguration(validationResult.descriptor, generatorOptions).generateDeclaration() return ExtractionGeneratorConfiguration(validationResult.descriptor, generatorOptions).generateDeclaration()
} finally { } finally {
Disposer.dispose(extractionData) Disposer.dispose(extractionData)
@@ -154,7 +167,7 @@ private fun KtCodeFragment.clearContextElement() {
} }
private fun KtFile.findContextElement(): KtElement? { private fun KtFile.findContextElement(): KtElement? {
return this.findDescendantOfType { it.IS_CONTEXT_ELEMENT == true } return this.findDescendantOfType { it.IS_CONTEXT_ELEMENT }
} }
private var PsiElement.DEBUG_SMART_CAST: PsiElement? by CopyablePsiUserDataProperty(Key.create("DEBUG_SMART_CAST")) private var PsiElement.DEBUG_SMART_CAST: PsiElement? by CopyablePsiUserDataProperty(Key.create("DEBUG_SMART_CAST"))
@@ -167,8 +180,9 @@ private fun KtCodeFragment.markSmartCasts() {
val smartCast = bindingContext.get(SMARTCAST, expression)?.defaultType val smartCast = bindingContext.get(SMARTCAST, expression)?.defaultType
if (smartCast != null) { if (smartCast != null) {
val smartCastedExpression = factory.createExpressionByPattern( val smartCastedExpression = factory.createExpressionByPattern(
"($0 as ${DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(smartCast)})", "($0 as ${DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(smartCast)})",
expression) as KtParenthesizedExpression expression
) as KtParenthesizedExpression
expression.DEBUG_SMART_CAST = smartCastedExpression expression.DEBUG_SMART_CAST = smartCastedExpression
} }
@@ -194,8 +208,7 @@ private fun addImportsToFile(newImportList: KtImportList?, tmpFile: KtFile) {
val packageDirective = tmpFile.packageDirective val packageDirective = tmpFile.packageDirective
tmpFile.addAfter(psiFactory.createNewLine(), packageDirective) tmpFile.addAfter(psiFactory.createNewLine(), packageDirective)
tmpFile.addAfter(newImportList, tmpFile.packageDirective) tmpFile.addAfter(newImportList, tmpFile.packageDirective)
} } else {
else {
newImportList.imports.forEach { newImportList.imports.forEach {
tmpFileImportList.add(psiFactory.createNewLine()) tmpFileImportList.add(psiFactory.createNewLine())
tmpFileImportList.add(it) tmpFileImportList.add(it)
@@ -293,20 +306,17 @@ private fun findElementBefore(contextElement: PsiElement): PsiElement? {
val delegateExpressionOrInitializer = contextElement.delegateExpressionOrInitializer val delegateExpressionOrInitializer = contextElement.delegateExpressionOrInitializer
if (delegateExpressionOrInitializer != null) { if (delegateExpressionOrInitializer != null) {
wrapInLambdaCall(delegateExpressionOrInitializer) wrapInLambdaCall(delegateExpressionOrInitializer)
} } else {
else {
val getter = contextElement.getter val getter = contextElement.getter
val bodyExpression = getter?.bodyExpression val bodyExpression = getter?.bodyExpression
if (getter != null && bodyExpression != null) { if (getter != null && bodyExpression != null) {
if (!getter.hasBlockBody()) { if (!getter.hasBlockBody()) {
wrapInLambdaCall(bodyExpression) wrapInLambdaCall(bodyExpression)
} } else {
else {
(bodyExpression as KtBlockExpression).statements.first() (bodyExpression as KtBlockExpression).statements.first()
} }
} } else {
else {
contextElement contextElement
} }
} }
@@ -343,8 +353,7 @@ private fun findElementBefore(contextElement: PsiElement): PsiElement? {
val entryExpression = contextElement.expression val entryExpression = contextElement.expression
if (entryExpression is KtBlockExpression) { if (entryExpression is KtBlockExpression) {
entryExpression.statements.firstOrNull() ?: entryExpression.lastChild entryExpression.statements.firstOrNull() ?: entryExpression.lastChild
} } else {
else {
wrapInLambdaCall(entryExpression!!) wrapInLambdaCall(entryExpression!!)
} }
} }