[IR] Remove strong references to IR entities in CompilationException
- `CompilationException` should not strongly reference `IrFile`s and
other IR elements, which in turn reference FIR elements strongly. It
can lead to a memory leak in the IDE as the exception is held
statically in the IDE's `MessagePool`.
- I considered fixing this on the level of `KtCodeCompilationException`
from the Analysis API, but:
1. `KtCodeCompilationException` can wrap many kinds of exceptions.
Snapshotting the cause only for `CompilationException` (somewhere
down the cause chain) would be messy, as it'd require traversing
the cause chain and referencing `CompilationException` directly,
breaking API boundaries.
2. Keeping the same level of report quality while snapshotting a cause
is not trivial.
3. Exceptions in general should be as lean as possible, so it makes
sense to fix `CompilationException` itself.
^KT-65655 fixed
This commit is contained in:
committed by
Space Team
parent
a4ccb72b94
commit
d121b529b5
+61
-39
@@ -15,65 +15,87 @@ import org.jetbrains.kotlin.ir.types.IrType
|
|||||||
import org.jetbrains.kotlin.ir.util.dumpKotlinLike
|
import org.jetbrains.kotlin.ir.util.dumpKotlinLike
|
||||||
import org.jetbrains.kotlin.ir.util.fileOrNull
|
import org.jetbrains.kotlin.ir.util.fileOrNull
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param file If [file] is not known at this moment, [initializeFileDetails] should be invoked later in a `catch` to initialize the
|
||||||
|
* exception's file details.
|
||||||
|
*/
|
||||||
class CompilationException(
|
class CompilationException(
|
||||||
message: String,
|
message: String,
|
||||||
// file is not known in any moment, need to set it later in catch to save stacktrace
|
file: IrFile?,
|
||||||
var file: IrFile?,
|
ir: Any?, /* IrElement | IrType */
|
||||||
val ir: Any?, /* IrElement | IrType */
|
|
||||||
cause: Throwable? = null
|
cause: Throwable? = null
|
||||||
) : RuntimeException(message, cause) {
|
) : RuntimeException(message, cause) {
|
||||||
|
/**
|
||||||
|
* [CompilationException] must not keep strong references to IR entities. This is because, in the IDE, the exception might be kept in a
|
||||||
|
* message log, which can prevent the IR entity and all its references (including FIR and PSI elements) from being garbage-collected.
|
||||||
|
*
|
||||||
|
* Hence, [CompilationExceptionFileDetails] encapsulates the detailed information gathered from the [IrFile], without keeping a
|
||||||
|
* reference to it. If the exception is never initialized with an [IrFile], the file details won't be included in the message.
|
||||||
|
*/
|
||||||
|
private var fileDetails: CompilationExceptionFileDetails? = null
|
||||||
|
|
||||||
|
val irStartOffset: Int? = (ir as? IrElement)?.startOffset
|
||||||
|
|
||||||
|
val content: String? = when (ir) {
|
||||||
|
is IrElement -> ir.dumpKotlinLike()
|
||||||
|
is IrType -> ir.dumpKotlinLike()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
file?.let(::initializeFileDetails)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initializeFileDetails(file: IrFile) {
|
||||||
|
if (fileDetails != null) return
|
||||||
|
|
||||||
|
fileDetails = CompilationExceptionFileDetails(file, irStartOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
val path: String? get() = fileDetails?.path
|
||||||
|
|
||||||
|
val line: Int get() = fileDetails?.line ?: UNDEFINED_OFFSET
|
||||||
|
|
||||||
|
val column: Int get() = fileDetails?.column ?: UNDEFINED_OFFSET
|
||||||
|
|
||||||
override val message: String
|
override val message: String
|
||||||
get() = try {
|
get() = try {
|
||||||
buildString {
|
buildString {
|
||||||
appendLine("Back-end: Please report this problem https://kotl.in/issue")
|
appendLine("Back-end: Please report this problem https://kotl.in/issue")
|
||||||
path?.let { appendLine("$it:$line:$column") }
|
fileDetails?.render(this)
|
||||||
content?.let { appendLine("Problem with `$it`") }
|
content?.let { appendLine("Problem with `$it`") }
|
||||||
append("Details: " + super.message)
|
append("Details: " + super.message)
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (_: Throwable) {
|
||||||
throw IllegalStateException("Problem with constructing exception message").also {
|
throw IllegalStateException("Problem with constructing exception message").also {
|
||||||
it.stackTrace = stackTrace
|
it.stackTrace = stackTrace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val line: Int
|
/**
|
||||||
get() {
|
* @see CompilationException.fileDetails
|
||||||
val irStartOffset = irStartOffset
|
*/
|
||||||
?: return UNDEFINED_OFFSET
|
private class CompilationExceptionFileDetails(file: IrFile, irStartOffset: Int?) {
|
||||||
|
val path: String = file.path
|
||||||
|
|
||||||
if (irStartOffset == UNDEFINED_OFFSET) return UNDEFINED_OFFSET
|
val line: Int = run {
|
||||||
|
if (irStartOffset == null || irStartOffset == UNDEFINED_OFFSET) {
|
||||||
val lineNumber = file?.fileEntry?.getLineNumber(irStartOffset)
|
return@run UNDEFINED_OFFSET
|
||||||
?: return UNDEFINED_OFFSET
|
|
||||||
|
|
||||||
return lineNumber + 1
|
|
||||||
}
|
}
|
||||||
|
file.fileEntry.getLineNumber(irStartOffset) + 1
|
||||||
|
}
|
||||||
|
|
||||||
val column: Int
|
val column: Int = run {
|
||||||
get() {
|
if (irStartOffset == null || irStartOffset == UNDEFINED_OFFSET) {
|
||||||
val irStartOffset = irStartOffset
|
return@run UNDEFINED_OFFSET
|
||||||
?: return UNDEFINED_OFFSET
|
|
||||||
|
|
||||||
if (irStartOffset == UNDEFINED_OFFSET) return UNDEFINED_OFFSET
|
|
||||||
|
|
||||||
val columnNumber = file?.fileEntry?.getColumnNumber(irStartOffset)
|
|
||||||
?: return UNDEFINED_OFFSET
|
|
||||||
|
|
||||||
return columnNumber + 1
|
|
||||||
}
|
}
|
||||||
|
file.fileEntry.getColumnNumber(irStartOffset) + 1
|
||||||
|
}
|
||||||
|
|
||||||
private val irStartOffset: Int?
|
fun render(stringBuilder: StringBuilder) {
|
||||||
get() = (ir as? IrElement)?.startOffset
|
stringBuilder.appendLine("$path:$line:$column")
|
||||||
|
}
|
||||||
val path: String?
|
|
||||||
get() = file?.path
|
|
||||||
|
|
||||||
val content: String?
|
|
||||||
get() = when (ir) {
|
|
||||||
is IrElement -> ir.dumpKotlinLike()
|
|
||||||
is IrType -> ir.dumpKotlinLike()
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun compilationException(message: String, element: IrElement): Nothing {
|
fun compilationException(message: String, element: IrElement): Nothing {
|
||||||
@@ -87,7 +109,7 @@ fun compilationException(message: String, type: IrType?): Nothing {
|
|||||||
fun compilationException(message: String, declaration: IrDeclaration): Nothing {
|
fun compilationException(message: String, declaration: IrDeclaration): Nothing {
|
||||||
val file = try {
|
val file = try {
|
||||||
declaration.fileOrNull
|
declaration.fileOrNull
|
||||||
} catch (e: Throwable) {
|
} catch (_: Throwable) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
throw CompilationException(message, file, declaration)
|
throw CompilationException(message, file, declaration)
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ fun FileLoweringPass.lower(
|
|||||||
try {
|
try {
|
||||||
lower(it)
|
lower(it)
|
||||||
} catch (e: CompilationException) {
|
} catch (e: CompilationException) {
|
||||||
e.file = it
|
e.initializeFileDetails(it)
|
||||||
throw e
|
throw e
|
||||||
} catch (e: KotlinExceptionWithAttachments) {
|
} catch (e: KotlinExceptionWithAttachments) {
|
||||||
throw e
|
throw e
|
||||||
@@ -149,7 +149,7 @@ fun BodyLoweringPass.runOnFilePostfix(
|
|||||||
try {
|
try {
|
||||||
declaration.accept(visitor, null)
|
declaration.accept(visitor, null)
|
||||||
} catch (e: CompilationException) {
|
} catch (e: CompilationException) {
|
||||||
e.file = irFile
|
e.initializeFileDetails(irFile)
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
throw e.wrapWithCompilationException(
|
throw e.wrapWithCompilationException(
|
||||||
@@ -227,7 +227,7 @@ interface DeclarationTransformer : FileLoweringPass {
|
|||||||
declaration.acceptVoid(visitor)
|
declaration.acceptVoid(visitor)
|
||||||
transformFlatRestricted(declaration)
|
transformFlatRestricted(declaration)
|
||||||
} catch (e: CompilationException) {
|
} catch (e: CompilationException) {
|
||||||
e.file = irFile
|
e.initializeFileDetails(irFile)
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
throw e.wrapWithCompilationException(
|
throw e.wrapWithCompilationException(
|
||||||
|
|||||||
Reference in New Issue
Block a user