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.codeFragmentUtil.suppressDiagnosticsInDebugMode
import org.jetbrains.kotlin.resolve.DescriptorFactory
import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
@@ -168,10 +168,12 @@ class KotlinCodeFragmentFactory : CodeFragmentFactory() {
else
debuggerContext.frameProxy?.stackFrame
val visibleVariables = frame?.let {
val values = it.getValues(it.visibleVariables())
val visibleVariables = if (frame != null) {
val values = frame.getValues(frame.visibleVariables())
values.filterValues { it != null }
} ?: emptyMap()
} else {
emptyMap()
}
frameInfo = FrameInfo(frame?.thisObject(), visibleVariables)
} 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)
val lineStartOffset = if (elementAt is PsiWhiteSpace || elementAt is PsiComment) {
PsiTreeUtil.skipSiblingsForward(elementAt, PsiWhiteSpace::class.java, PsiComment::class.java)?.textOffset
?: elementAt.textOffset
?: elementAt.textOffset
} else {
elementAt.textOffset
}
@@ -59,31 +59,39 @@ import java.util.concurrent.ConcurrentHashMap
class KotlinDebuggerCaches(project: Project) {
private val cachedCompiledData = CachedValuesManager.getManager(project).createCachedValue(
{
CachedValueProvider.Result<MultiMap<String, CompiledDataDescriptor>>(
MultiMap.create(), PsiModificationTracker.MODIFICATION_COUNT)
}, false)
{
CachedValueProvider.Result<MultiMap<String, CompiledDataDescriptor>>(
MultiMap.create(), PsiModificationTracker.MODIFICATION_COUNT
)
}, false
)
private val cachedClassNames = CachedValuesManager.getManager(project).createCachedValue(
{
CachedValueProvider.Result<MutableMap<PsiElement, List<String>>>(
ConcurrentHashMap<PsiElement, List<String>>(),
PsiModificationTracker.MODIFICATION_COUNT)
}, false)
{
CachedValueProvider.Result<MutableMap<PsiElement, List<String>>>(
ConcurrentHashMap(),
PsiModificationTracker.MODIFICATION_COUNT
)
}, false
)
private val cachedTypeMappers = CachedValuesManager.getManager(project).createCachedValue(
{
CachedValueProvider.Result<MutableMap<PsiElement, KotlinTypeMapper>>(
ConcurrentHashMap<PsiElement, KotlinTypeMapper>(),
PsiModificationTracker.MODIFICATION_COUNT)
}, false)
{
CachedValueProvider.Result<MutableMap<PsiElement, KotlinTypeMapper>>(
ConcurrentHashMap(),
PsiModificationTracker.MODIFICATION_COUNT
)
}, false
)
private val debugInfoCache = CachedValuesManager.getManager(project).createCachedValue(
{
CachedValueProvider.Result(
createWeakBytecodeDebugInfoStorage(),
PsiModificationTracker.MODIFICATION_COUNT)
}, false)
{
CachedValueProvider.Result(
createWeakBytecodeDebugInfoStorage(),
PsiModificationTracker.MODIFICATION_COUNT
)
}, false
)
companion object {
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 getOrCreateCompiledData(
codeFragment: KtCodeFragment,
sourcePosition: SourcePosition,
evaluationContext: EvaluationContextImpl,
create: (KtCodeFragment, SourcePosition) -> CompiledDataDescriptor
codeFragment: KtCodeFragment,
sourcePosition: SourcePosition,
evaluationContext: EvaluationContextImpl,
create: (KtCodeFragment, SourcePosition) -> CompiledDataDescriptor
): CompiledDataDescriptor {
val evaluateExpressionCache = getInstance(codeFragment.project)
@@ -157,8 +165,7 @@ class KotlinDebuggerCaches(project: Project) {
val newValue = if (!isInLibrary) {
createTypeMapperForSourceFile(file)
}
else {
} else {
val element = getElementToCreateTypeMapperForLibraryFile(psiElement)
createTypeMapperForLibraryFile(element, file)
}
@@ -168,40 +175,42 @@ class KotlinDebuggerCaches(project: Project) {
}
fun getOrReadDebugInfoFromBytecode(
project: Project,
jvmName: JvmClassName,
file: VirtualFile): BytecodeDebugInfo? {
project: Project,
jvmName: JvmClassName,
file: VirtualFile
): BytecodeDebugInfo? {
val cache = getInstance(project)
return cache.debugInfoCache.value[BinaryCacheKey(project, jvmName, file)]
}
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 =
runInReadActionWithWriteActionPriorityWithPCE {
createTypeMapper(file, element.analyzeAndGetResult())
}
runInReadActionWithWriteActionPriorityWithPCE {
createTypeMapper(file, element.analyzeAndGetResult())
}
private fun createTypeMapperForSourceFile(file: KtFile): KotlinTypeMapper =
runInReadActionWithWriteActionPriorityWithPCE {
createTypeMapper(file, file.analyzeWithAllCompilerChecks().apply(AnalysisResult::throwIfError))
}
runInReadActionWithWriteActionPriorityWithPCE {
createTypeMapper(file, file.analyzeWithAllCompilerChecks().apply(AnalysisResult::throwIfError))
}
private fun createTypeMapper(file: KtFile, analysisResult: AnalysisResult): KotlinTypeMapper {
val state = GenerationState.Builder(
file.project,
ClassBuilderFactories.THROW_EXCEPTION,
analysisResult.moduleDescriptor,
analysisResult.bindingContext,
listOf(file),
CompilerConfiguration.EMPTY
file.project,
ClassBuilderFactories.THROW_EXCEPTION,
analysisResult.moduleDescriptor,
analysisResult.bindingContext,
listOf(file),
CompilerConfiguration.EMPTY
).build()
state.beforeCompile()
return state.typeMapper
}
@TestOnly fun addTypeMapper(file: KtFile, typeMapper: KotlinTypeMapper) {
@TestOnly
fun addTypeMapper(file: KtFile, typeMapper: KotlinTypeMapper) {
getInstance(file.project).cachedTypeMappers.value[file] = typeMapper
}
}
@@ -216,7 +225,12 @@ class KotlinDebuggerCaches(project: Project) {
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) }
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)
class ComputedClassNames(val classNames: List<String>, val shouldBeCached: Boolean) {
@Suppress("FunctionName")
companion object {
val EMPTY = ComputedClassNames.Cached(emptyList())
@@ -242,8 +257,7 @@ class KotlinDebuggerCaches(project: Project) {
fun distinct() = ComputedClassNames(classNames.distinct(), shouldBeCached)
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 java.util.*
internal val THIS_NAME = "this"
internal val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluator")
internal val GENERATED_FUNCTION_NAME = "generated_for_debugger_fun"
internal val GENERATED_CLASS_NAME = "Generated_for_debugger_class"
internal const val GENERATED_FUNCTION_NAME = "generated_for_debugger_fun"
internal const val GENERATED_CLASS_NAME = "Generated_for_debugger_class"
private val DEBUG_MODE = false
private const val DEBUG_MODE = false
object KotlinEvaluationBuilder : EvaluatorBuilder {
override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator {
@@ -117,17 +116,23 @@ object KotlinEvaluationBuilder : EvaluatorBuilder {
val document = PsiDocumentManager.getInstance(file.project).getDocument(file)
if (document == null || document.lineCount < position.line) {
throw EvaluateExceptionUtil.createEvaluateException(
"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.")
"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."
)
}
}
if (codeFragment.context !is KtElement) {
val attachments = arrayOf(attachmentByPsiFile(position.file),
attachmentByPsiFile(codeFragment),
Attachment("breakpoint.info", "line: ${position.line}"))
val attachments = arrayOf(
attachmentByPsiFile(position.file),
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")
}
@@ -147,8 +152,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
var isCompiledDataFromCache = true
try {
val compiledData = KotlinDebuggerCaches.getOrCreateCompiledData(codeFragment, sourcePosition, context) {
fragment, position ->
val compiledData = KotlinDebuggerCaches.getOrCreateCompiledData(codeFragment, sourcePosition, context) { fragment, position ->
isCompiledDataFromCache = false
extractAndCompile(fragment, position, context)
}
@@ -157,8 +161,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
val result = if (classLoaderRef != null) {
evaluateWithCompilation(context, compiledData, classLoaderRef) ?: runEval4j(context, compiledData, classLoaderRef)
}
else {
} else {
runEval4j(context, compiledData, classLoaderRef)
}
@@ -172,32 +175,33 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
} else {
result
}
}
catch (e: EvaluateException) {
} catch (e: EvaluateException) {
throw e
}
catch (e: ProcessCanceledException) {
} catch (e: ProcessCanceledException) {
exception(e)
}
catch (e: Eval4JInterpretingException) {
} catch (e: Eval4JInterpretingException) {
exception(e.cause)
}
catch (e: Exception) {
} catch (e: Exception) {
val isSpecialException = isSpecialException(e)
if (isSpecialException) {
exception(e)
}
val text = runReadAction { codeFragment.context?.text ?: "null" }
val attachments = arrayOf(attachmentByPsiFile(sourcePosition.file),
attachmentByPsiFile(codeFragment),
Attachment("breakpoint.info", "line: ${runReadAction { sourcePosition.line }}"),
Attachment("context.info", text))
val attachments = arrayOf(
attachmentByPsiFile(sourcePosition.file),
attachmentByPsiFile(codeFragment),
Attachment("breakpoint.info", "line: ${runReadAction { sourcePosition.line }}"),
Attachment("context.info", text)
)
LOG.error(LogMessageEx.createEvent(
LOG.error(
LogMessageEx.createEvent(
"Couldn't evaluate expression",
ExceptionUtil.getThrowableText(e),
mergeAttachments(*attachments)))
mergeAttachments(*attachments)
)
)
val cause = if (e.message != null) ": ${e.message}" else ""
exception("An exception occurs during Evaluate Expression Action $cause")
@@ -223,7 +227,11 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
}
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
if (codeFragment.wrapToStringIfNeeded(bindingContext)) {
@@ -234,7 +242,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
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}")
?: 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 {
@@ -253,6 +261,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
if (LOG.isDebugEnabled) {
LOG.debug("Output file generated: ${file.relativePath}")
}
@Suppress("ConstantConditionIf")
if (DEBUG_MODE) {
println(file.asText())
}
@@ -297,7 +306,8 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
private val CompiledDataDescriptor.mainClass
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(
context: EvaluationContextImpl,
@@ -331,12 +341,12 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
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)
}
.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)
@@ -359,8 +369,15 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
val mainClassBytecode = compiledData.mainClass.bytes
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'
@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)
@@ -368,15 +385,25 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
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())
val eval = JDIEval(
virtualMachine,
classLoader ?: context.classLoader,
context.suspendContext.thread?.threadReference!!,
context.suspendContext.getInvokePolicy()
)
resultValue = interpreterLoop(
this,
makeInitialFrame(
this,
makeInitialFrame(this, args.zip(argumentTypes).map { boxOrUnboxArgumentIfNeeded(eval, it.first, it.second) }),
eval
args.zip(argumentTypes).map {
boxOrUnboxArgumentIfNeeded(
eval,
it.first,
it.second
)
}),
eval
)
}
}
@@ -387,7 +414,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
}
}, 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 {
@@ -410,8 +437,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
if (parameterType == unboxedType) {
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) {
val value = property.getUserData(KotlinCodeFragmentFactory.LABEL_VARIABLE_VALUE_KEY)
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 receiver = config.descriptor.receiverParameter
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) {
@@ -537,10 +563,10 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
}
private fun createClassFileFactory(
codeFragment: KtCodeFragment,
extractedFunction: KtNamedFunction,
context: EvaluationContextImpl,
parameters: List<Parameter>
codeFragment: KtCodeFragment,
extractedFunction: KtNamedFunction,
context: EvaluationContextImpl,
parameters: List<Parameter>
): ClassFileFactory {
return runReadAction {
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 }}")
}
val (bindingContext, moduleDescriptor, files) = fileForDebugger.checkForErrors(true, codeFragment.getContextContainingFile())
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 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
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")
@@ -577,15 +609,16 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
}
val valueParameters = extractedFunction.valueParameters
var paramIndex = 0
for (param in parameters) {
val valueParameter = valueParameters[paramIndex++]
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))
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")
}
@@ -631,8 +664,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
return runInReadActionWithWriteActionPriorityWithPCE {
try {
AnalyzingUtils.checkForSyntacticErrors(this)
}
catch (e: IllegalArgumentException) {
} catch (e: IllegalArgumentException) {
throw EvaluateExceptionUtil.createEvaluateException(e.message)
}
@@ -655,18 +687,21 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: Sour
if (analyzeInlineFunctions) {
val (newBindingContext, files) = DebuggerUtils.analyzeInlinedFunctions(resolutionFacade, this, false)
ExtendedAnalysisResult(newBindingContext, analysisResult.moduleDescriptor, files)
}
else {
} else {
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")
!PACKAGE!
@@ -675,24 +710,22 @@ private val template = """
!FUNCTION!
"""
private fun createFileForDebugger(codeFragment: KtCodeFragment,
extractedFunction: KtNamedFunction
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")
"!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)
val extractedFunctionText = extractedFunction.text
assert(extractedFunctionText != null) { "Text of extracted function shouldn't be null" }
fileText = fileText.replace("!FUNCTION!", extractedFunction.text!!)
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)
virtualFile.charset = CharsetToolkit.UTF8_CHARSET
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
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
JavaToKotlinClassMap.mapJavaToKotlin(jvmName)?.let(
DefaultBuiltIns.Instance.builtInsModule::findClassAcrossModuleDependencies
DefaultBuiltIns.Instance.builtInsModule::findClassAcrossModuleDependencies
)?.let { return it }
return runReadAction {
@@ -18,7 +18,6 @@ package org.jetbrains.kotlin.idea.debugger.evaluate
import com.intellij.debugger.DebuggerBundle
import com.intellij.debugger.DebuggerInvocationUtil
import com.intellij.debugger.EvaluatingComputable
import com.intellij.debugger.engine.ContextUtil
import com.intellij.debugger.engine.evaluation.EvaluateException
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
abstract class KotlinRuntimeTypeEvaluator(
editor: Editor?,
expression: KtExpression,
context: DebuggerContextImpl,
indicator: ProgressIndicator
editor: Editor?,
expression: KtExpression,
context: DebuggerContextImpl,
indicator: ProgressIndicator
) : EditorEvaluationCommand<KotlinType>(editor, expression, context, indicator) {
override fun threadAction() {
var type: KotlinType? = null
try {
type = evaluate()
}
catch (ignored: ProcessCanceledException) {
}
catch (ignored: EvaluateException) {
}
finally {
} catch (ignored: ProcessCanceledException) {
} catch (ignored: EvaluateException) {
} finally {
typeCalculationFinished(type)
}
}
@@ -66,11 +62,12 @@ abstract class KotlinRuntimeTypeEvaluator(
override fun evaluate(evaluationContext: EvaluationContextImpl): KotlinType? {
val project = evaluationContext.project
val evaluator = DebuggerInvocationUtil.commitAndRunReadAction<ExpressionEvaluator>(project, EvaluatingComputable {
val codeFragment = KtPsiFactory(myElement.project).createExpressionCodeFragment(
myElement.text, myElement.containingFile.context)
KotlinEvaluationBuilder.build(codeFragment, ContextUtil.getSourcePosition(evaluationContext))
})
val evaluator = DebuggerInvocationUtil.commitAndRunReadAction<ExpressionEvaluator>(project) {
val codeFragment = KtPsiFactory(myElement.project).createExpressionCodeFragment(
myElement.text, myElement.containingFile.context
)
KotlinEvaluationBuilder.build(codeFragment, ContextUtil.getSourcePosition(evaluationContext))
}
val value = evaluator.evaluate(evaluationContext)
if (value != null) {
@@ -51,7 +51,7 @@ object ScopeCheckerForEvaluator {
return true
}
}
currentParent = when (currentParent) {
is KtCodeFragment -> currentParent.context
else -> currentParent.parent
@@ -29,15 +29,15 @@ abstract class AbstractAndroidClassLoadingAdapter : ClassLoadingAdapter {
protected fun wrapToByteBuffer(bytes: ArrayReference, context: EvaluationContext, process: DebugProcessImpl): ObjectReference {
val byteBufferClass = process.findClass(context, "java.nio.ByteBuffer", context.classLoader) as ClassType
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
}
protected fun DebugProcessImpl.tryLoadClass(
context: EvaluationContextImpl,
fqName: String,
classLoader: ClassLoaderReference?
context: EvaluationContextImpl,
fqName: String,
classLoader: ClassLoaderReference?
): ReferenceType? {
return try {
loadClass(context, fqName, classLoader)
@@ -20,7 +20,8 @@ import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
interface 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?
}
@@ -29,11 +29,12 @@ class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() {
}
private fun resolveClassLoaderClass(context: EvaluationContextImpl): ClassType? {
try {
return context.debugProcess.tryLoadClass(
context, "dalvik.system.InMemoryDexClassLoader", context.classLoader) as? ClassType
return try {
context.debugProcess.tryLoadClass(
context, "dalvik.system.InMemoryDexClassLoader", context.classLoader
) as? ClassType
} catch (e: EvaluateException) {
return null
null
}
}
@@ -41,14 +42,17 @@ class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() {
val process = context.debugProcess
val inMemoryClassLoaderClass = resolveClassLoaderClass(context) ?: error("InMemoryDexClassLoader class not found")
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 dexBytesMirror = mirrorOfByteArray(dexBytes, context, process)
val dexByteBuffer = wrapToByteBuffer(dexBytesMirror, context, process)
val newClassLoader = process.newInstance(context, inMemoryClassLoaderClass, constructorMethod,
listOf(dexByteBuffer, context.classLoader))
val newClassLoader = process.newInstance(
context, inMemoryClassLoaderClass, constructorMethod,
listOf(dexByteBuffer, context.classLoader)
)
DebuggerUtilsEx.keep(newClassLoader, context)
@@ -73,46 +73,44 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
val classLoader = try {
ClassLoadingUtils.getClassLoader(context, process)
}
catch (e: Exception) {
throw EvaluateException("Error creating evaluation class loader: " + e, e)
} catch (e: Exception) {
throw EvaluateException("Error creating evaluation class loader: $e", e)
}
val debugProcessVersionString = process.virtualMachineProxy.version()
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)
?: 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)) {
throw EvaluateException(
"Unable to compile for target level ${debugProcessVersion.description}. " +
"Need to run IDEA on java version at least $debugProcessVersion, " +
"currently running on $ideaJavaVersion")
"Unable to compile for target level ${debugProcessVersion.description}. " +
"Need to run IDEA on java version at least $debugProcessVersion, " +
"currently running on $ideaJavaVersion"
)
}
try {
defineClasses(classes, context, process, classLoader)
}
catch (e: Exception) {
throw EvaluateException("Error during classes definition " + e, e)
} catch (e: Exception) {
throw EvaluateException("Error during classes definition $e", e)
}
return classLoader
}
private fun defineClasses(
classes: Collection<ClassToLoad>,
context: EvaluationContextImpl,
process: DebugProcessImpl,
classLoader: ClassLoaderReference
classes: Collection<ClassToLoad>,
context: EvaluationContextImpl,
process: DebugProcessImpl,
classLoader: ClassLoaderReference
) {
val classesToLoad = if (classes.size == 1) {
// No need in loading lambda superclass if there're no lambdas
classes
}
else {
} else {
val lambdaSuperclasses = LAMBDA_SUPERCLASSES.map {
ClassToLoad(it.name, it.name.replace('.', '/') + ".class", it.bytes)
}
@@ -125,12 +123,12 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
}
}
fun defineClass(
name: String,
bytes: ByteArray,
context: EvaluationContextImpl,
process: DebugProcessImpl,
classLoader: ClassLoaderReference
private fun defineClass(
name: String,
bytes: ByteArray,
context: EvaluationContextImpl,
process: DebugProcessImpl,
classLoader: ClassLoaderReference
) {
try {
val vm = process.virtualMachineProxy
@@ -138,13 +136,15 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
val defineMethod = classLoaderType.concreteMethodByName("defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;")
val nameObj = vm.mirrorOf(name)
// Still actual for older platform versions
@Suppress("DEPRECATION")
DebuggerUtilsEx.keep(nameObj, context)
process.invokeMethod(
context, classLoader, defineMethod,
listOf(nameObj, mirrorOfByteArray(bytes, context, process), vm.mirrorOf(0), vm.mirrorOf(bytes.size)))
}
catch (e: Exception) {
context, classLoader, defineMethod,
listOf(nameObj, mirrorOfByteArray(bytes, context, process), vm.mirrorOf(0), vm.mirrorOf(bytes.size))
)
} catch (e: Exception) {
throw EvaluateException("Error during class $name definition: $e", e)
}
@@ -153,7 +153,7 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
private class ClassBytes(val name: String) {
val bytes: ByteArray by lazy {
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 {
it.readBytes()
@@ -44,26 +44,31 @@ import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext.SMARTCAST
fun getFunctionForExtractedFragment(
codeFragment: KtCodeFragment,
breakpointFile: PsiFile,
breakpointLine: Int
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(
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)))
mergeAttachments(*attachments)
)
)
}
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_CONTAINER -> "Cannot perform an action at this breakpoint ${breakpointFile.name}:$breakpointLine"
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 options = ExtractionOptions(inferUnitTypeForUnusedValues = false,
enableListBoxing = true,
allowSpecialClassNames = true,
captureLocalFunctions = true,
canWrapInWith = true)
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()
@@ -107,12 +114,18 @@ fun getFunctionForExtractedFragment(
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 }}")
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)
val generatorOptions = ExtractionGeneratorOptions(
inTempFile = true,
dummyName = GENERATED_FUNCTION_NAME,
allowExpressionBody = false
)
return ExtractionGeneratorConfiguration(validationResult.descriptor, generatorOptions).generateDeclaration()
} finally {
Disposer.dispose(extractionData)
@@ -154,7 +167,7 @@ private fun KtCodeFragment.clearContextElement() {
}
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"))
@@ -167,8 +180,9 @@ private fun KtCodeFragment.markSmartCasts() {
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
"($0 as ${DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(smartCast)})",
expression
) as KtParenthesizedExpression
expression.DEBUG_SMART_CAST = smartCastedExpression
}
@@ -194,8 +208,7 @@ private fun addImportsToFile(newImportList: KtImportList?, tmpFile: KtFile) {
val packageDirective = tmpFile.packageDirective
tmpFile.addAfter(psiFactory.createNewLine(), packageDirective)
tmpFile.addAfter(newImportList, tmpFile.packageDirective)
}
else {
} else {
newImportList.imports.forEach {
tmpFileImportList.add(psiFactory.createNewLine())
tmpFileImportList.add(it)
@@ -293,20 +306,17 @@ private fun findElementBefore(contextElement: PsiElement): PsiElement? {
val delegateExpressionOrInitializer = contextElement.delegateExpressionOrInitializer
if (delegateExpressionOrInitializer != null) {
wrapInLambdaCall(delegateExpressionOrInitializer)
}
else {
} else {
val getter = contextElement.getter
val bodyExpression = getter?.bodyExpression
if (getter != null && bodyExpression != null) {
if (!getter.hasBlockBody()) {
wrapInLambdaCall(bodyExpression)
}
else {
} else {
(bodyExpression as KtBlockExpression).statements.first()
}
}
else {
} else {
contextElement
}
}
@@ -343,8 +353,7 @@ private fun findElementBefore(contextElement: PsiElement): PsiElement? {
val entryExpression = contextElement.expression
if (entryExpression is KtBlockExpression) {
entryExpression.statements.firstOrNull() ?: entryExpression.lastChild
}
else {
} else {
wrapInLambdaCall(entryExpression!!)
}
}