Workaround for the case when some classes are unloaded during ranking (KT-25774)

It's important that ranking preserve order of files found
in findFilesByNameInPackage()

It's only a workaround because it's expected for debugger to have
classes unloaded and some valid variants might get bad rank and
rejected because of that.

 #KT-25774 Fixed
This commit is contained in:
Nikolay Krasko
2018-07-31 19:18:06 +03:00
parent da6bcc9d46
commit f9845bab54
6 changed files with 106 additions and 13 deletions
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.debugger
import com.intellij.openapi.diagnostic.Logger
import com.intellij.psi.PsiElement
import com.sun.jdi.*
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
@@ -15,13 +16,16 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.debugger.FileRankingCalculator.Ranking.Companion.LOW
import org.jetbrains.kotlin.idea.debugger.FileRankingCalculator.Ranking.Companion.ZERO
import org.jetbrains.kotlin.idea.debugger.FileRankingCalculator.Ranking.Companion.MAJOR
import org.jetbrains.kotlin.idea.debugger.FileRankingCalculator.Ranking.Companion.MINOR
import org.jetbrains.kotlin.idea.debugger.FileRankingCalculator.Ranking.Companion.NORMAL
import org.jetbrains.kotlin.idea.debugger.FileRankingCalculator.Ranking.Companion.MAJOR
import org.jetbrains.kotlin.idea.debugger.FileRankingCalculator.Ranking.Companion.ZERO
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypes2
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypes3
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.descriptorUtil.varargParameterPosition
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
@@ -166,7 +170,7 @@ abstract class FileRankingCalculator(
val jdiPropertyType = when {
accessor.isGetter -> method.safeReturnType()
else -> method.safeArguments()?.map { it.type() }?.singleOrNull()
else -> method.safeArguments()?.map { it.safeType() }?.singleOrNull()
}
return rankingForType(descriptor.returnType, jdiPropertyType, makeTypeMapper(bindingContext))
@@ -219,7 +223,7 @@ abstract class FileRankingCalculator(
): Ranking {
return collect(
ktParameter.name.asString() == jdiParameter.name(),
rankingForType(ktParameter.type, jdiParameter.type(), typeMapper)
rankingForType(ktParameter.type, jdiParameter.safeType(), typeMapper)
)
}
@@ -266,6 +270,9 @@ abstract class FileRankingCalculator(
private fun fileRankingSafe(file: KtFile, location: Location): Ranking {
return try {
fileRanking(file, location)
} catch (e: ClassNotLoadedException) {
LOG.error("ClassNotLoadedException should never happen in FileRankingCalculator", e)
ZERO
} catch (e: AbsentInformationException) {
ZERO
} catch (e: InternalException) {
@@ -412,6 +419,10 @@ abstract class FileRankingCalculator(
private fun makeTypeMapper(bindingContext: BindingContext): KotlinTypeMapper {
return KotlinTypeMapper(bindingContext, ClassBuilderMode.LIGHT_CLASSES, IncompatibleClassTracker.DoNothing, "debugger", false)
}
companion object {
val LOG = Logger.getInstance("FileRankingCalculator")
}
}
private fun String.simpleName() = substringAfterLast('.').substringAfterLast('$')
@@ -70,6 +70,14 @@ fun Method.safeReturnType(): Type? {
}
}
fun LocalVariable.safeType(): Type? {
return try {
return type()
} catch (e: ClassNotLoadedException) {
null
}
}
fun Location.safeSourceName(): String? {
return try {
sourceName()
@@ -0,0 +1,35 @@
//FILE: a/a.kt
package a
import c.*
class B : A<SomeImpl>() {
override fun x(some: SomeImpl) {
foo()
}
}
// DO_NOT_LOAD: a.SomeImpl
class SomeImpl : Some()
//FILE: b/a.kt
package b
import c.*
class B : A<Some>() {
override fun x(some: Some) {
foo()
}
}
//FILE: c/c.kt
package c
open class Some
abstract class A<T: Some> {
abstract fun x(a: T)
}
fun foo() {}
@@ -25,10 +25,16 @@ abstract class AbstractFileRankingTest : LowLevelDebuggerTestBase() {
val problems = mutableListOf<String>()
val skipClasses = skipLoadingClasses(options)
for ((node, origin) in factory.origins) {
val classNode = node as? ClassNode ?: continue
val expectedFile = origin.element?.containingFile as? KtFile ?: continue
val className = classNode.name.replace('/', '.')
if (className in skipClasses) {
continue
}
val jdiClass = mainThread.virtualMachine().classesByName(className).singleOrNull()
?: error("Class '$className' was not found in the debuggee process class loader")
@@ -56,4 +62,10 @@ abstract class AbstractFileRankingTest : LowLevelDebuggerTestBase() {
})
}
}
override fun skipLoadingClasses(options: Set<String>): Set<String> {
val skipClasses = options.mapTo(mutableSetOf()) { it.substringAfter("DO_NOT_LOAD:", "").trim() }
skipClasses.remove("")
return skipClasses
}
}
@@ -54,6 +54,11 @@ public class FileRankingTestGenerated extends AbstractFileRankingTest {
runTest("idea/testData/debugger/fileRanking/multilinePrimaryConstructorWithBody.kt");
}
@TestMetadata("parametersWithUnloadedClass.kt")
public void testParametersWithUnloadedClass() throws Exception {
runTest("idea/testData/debugger/fileRanking/parametersWithUnloadedClass.kt");
}
@TestMetadata("propertyDelegates.kt")
public void testPropertyDelegates() throws Exception {
runTest("idea/testData/debugger/fileRanking/propertyDelegates.kt");
@@ -33,8 +33,13 @@ abstract class LowLevelDebuggerTestBase : CodegenTestCase() {
createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.ALL, *javaSources)
val options = wholeFile.readLines()
.filter { it.matches("^// ?[\\w_]+$".toRegex()) }
.mapTo(mutableSetOf()) { it.drop(2).trim() }
.asSequence()
.filter { it.matches("^// ?[\\w_]+(:.*)$".toRegex()) }
.map { it.drop(2).trim() }
.filter { !it.startsWith("FILE:") }
.toSet()
val skipLoadingClasses = skipLoadingClasses(options)
loadMultiFiles(files)
val classBuilderFactory = OriginCollectingClassBuilderFactory(ClassBuilderMode.FULL)
@@ -51,14 +56,14 @@ abstract class LowLevelDebuggerTestBase : CodegenTestCase() {
}
}
val process = startDebuggeeProcess(classesDir)
val process = startDebuggeeProcess(classesDir, skipLoadingClasses)
waitUntil { isPortOpen() }
val virtualMachine = attachDebugger()
try {
val mainThread = virtualMachine.allThreads().single { it.name() == "main" }
waitUntil { areCompiledClassesLoaded(mainThread, classBuilderFactory) }
waitUntil { areCompiledClassesLoaded(mainThread, classBuilderFactory, skipLoadingClasses) }
doTest(options, mainThread, classBuilderFactory, generationState)
} finally {
virtualMachine.exit(0)
@@ -85,16 +90,33 @@ abstract class LowLevelDebuggerTestBase : CodegenTestCase() {
}
}
private fun areCompiledClassesLoaded(mainThread: ThreadReference, factory: OriginCollectingClassBuilderFactory): Boolean {
private fun areCompiledClassesLoaded(
mainThread: ThreadReference,
factory: OriginCollectingClassBuilderFactory,
skipLoadingClasses: Set<String>
): Boolean {
for ((node, _) in factory.origins) {
val classNode = node as? ClassNode ?: continue
mainThread.virtualMachine().classesByName(classNode.name.replace('/', '.')).firstOrNull() ?: return false
val fqName = classNode.name.replace('/', '.')
if (fqName in skipLoadingClasses) {
continue
}
mainThread.virtualMachine().classesByName(fqName).firstOrNull() ?: return false
}
return true
}
private fun startDebuggeeProcess(classesDir: File): Process {
val classesToLoad = this.classFileFactory.getClassFiles().joinToString(",") { it.qualifiedName }
protected open fun skipLoadingClasses(options: Set<String>): Set<String> {
return emptySet()
}
private fun startDebuggeeProcess(classesDir: File, skipLoadingClasses: Set<String>): Process {
val classesToLoad = this.classFileFactory.getClassFiles()
.map { it.qualifiedName }
.filter { it !in skipLoadingClasses }
.joinToString(",")
val classpath = listOf(
classesDir.absolutePath,