Kapt: Detect memory leaks in annotation processors (KT-28025)

This commit is contained in:
Yan Zhulanow
2018-11-02 14:53:21 +09:00
parent eb3511164f
commit abd1646d42
9 changed files with 201 additions and 16 deletions
@@ -47,7 +47,7 @@ object Kapt {
val processors = processorLoader.loadProcessors(findClassLoaderWithJavac())
val annotationProcessingTime = measureTimeMillis {
kaptContext.doAnnotationProcessing(javaSourceFiles, processors)
kaptContext.doAnnotationProcessing(javaSourceFiles, processors.processors)
}
logger.info { "Annotation processing took $annotationProcessingTime ms" }
@@ -14,6 +14,8 @@ import java.net.URLClassLoader
import java.util.*
import javax.annotation.processing.Processor
class LoadedProcessors(val processors: List<Processor>, val classLoader: ClassLoader)
open class ProcessorLoader(
private val paths: KaptPaths,
private val annotationProcessorFqNames: List<String>,
@@ -21,7 +23,7 @@ open class ProcessorLoader(
) : Closeable {
private var annotationProcessingClassLoader: URLClassLoader? = null
fun loadProcessors(parentClassLoader: ClassLoader = ClassLoader.getSystemClassLoader()): List<Processor> {
fun loadProcessors(parentClassLoader: ClassLoader = ClassLoader.getSystemClassLoader()): LoadedProcessors {
clearJarURLCache()
val classpath = (paths.annotationProcessingClasspath + paths.compileClasspath).distinct()
@@ -42,7 +44,7 @@ open class ProcessorLoader(
logger.info { "Annotation processors: " + processors.joinToString { it::class.java.canonicalName } }
}
return processors
return LoadedProcessors(processors, classLoader)
}
open fun doLoadProcessors(classLoader: URLClassLoader): List<Processor> {
@@ -141,6 +141,8 @@ enum class KaptCliOption(
cliToolOption = CliToolOption("-Kapt-strict", FLAG)
),
DETECT_MEMORY_LEAKS_OPTION("detectMemoryLeaks", "true | false", "Detect memory leaks in annotation processors"),
INFO_AS_WARNINGS_OPTION("infoAsWarnings", "true | false", "Show information messages as warnings"),
@Deprecated("Do not use in CLI")
@@ -41,10 +41,7 @@ import org.jetbrains.kotlin.context.ProjectContext
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.kapt3.AptMode.APT_ONLY
import org.jetbrains.kotlin.kapt3.AptMode.WITH_COMPILATION
import org.jetbrains.kotlin.kapt3.base.KaptContext
import org.jetbrains.kotlin.kapt3.base.KaptPaths
import org.jetbrains.kotlin.kapt3.base.ProcessorLoader
import org.jetbrains.kotlin.kapt3.base.doAnnotationProcessing
import org.jetbrains.kotlin.kapt3.base.*
import org.jetbrains.kotlin.kapt3.base.stubs.KaptStubLineInformation.Companion.KAPT_METADATA_EXTENSION
import org.jetbrains.kotlin.kapt3.base.util.KaptBaseError
import org.jetbrains.kotlin.kapt3.base.util.getPackageNameJava9Aware
@@ -58,6 +55,7 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.jvm.extensions.PartialAnalysisHandlerExtension
import org.jetbrains.kotlin.utils.kapt.MemoryLeakDetector
import java.io.File
import java.io.StringWriter
import java.io.Writer
@@ -75,12 +73,13 @@ class ClasspathBasedKapt3Extension(
correctErrorTypes: Boolean,
mapDiagnosticLocations: Boolean,
strictMode: Boolean,
detectMemoryLeaks: Boolean,
pluginInitializedTime: Long,
logger: MessageCollectorBackedKaptLogger,
compilerConfiguration: CompilerConfiguration
) : AbstractKapt3Extension(
paths, options, javacOptions, annotationProcessorFqNames,
aptMode, pluginInitializedTime, logger, correctErrorTypes, mapDiagnosticLocations, strictMode,
aptMode, pluginInitializedTime, logger, correctErrorTypes, mapDiagnosticLocations, strictMode, detectMemoryLeaks,
compilerConfiguration
) {
override val analyzePartially: Boolean
@@ -88,7 +87,7 @@ class ClasspathBasedKapt3Extension(
private var processorLoader: ProcessorLoader? = null
override fun loadProcessors(): List<Processor> {
override fun loadProcessors(): LoadedProcessors {
val efficientProcessorLoader = object : ProcessorLoader(paths, annotationProcessorFqNames, logger) {
override fun doLoadProcessors(classLoader: URLClassLoader): List<Processor> {
return ServiceLoaderLite.loadImplementations(Processor::class.java, classLoader)
@@ -133,6 +132,7 @@ abstract class AbstractKapt3Extension(
val correctErrorTypes: Boolean,
val mapDiagnosticLocations: Boolean,
val strictMode: Boolean,
val detectMemoryLeaks: Boolean,
val compilerConfiguration: CompilerConfiguration
) : PartialAnalysisHandlerExtension() {
private var annotationProcessingComplete = false
@@ -186,7 +186,7 @@ abstract class AbstractKapt3Extension(
if (!aptMode.runAnnotationProcessing) return doNotGenerateCode()
val processors = loadProcessors()
if (processors.isEmpty()) return if (aptMode != WITH_COMPILATION) doNotGenerateCode() else null
if (processors.processors.isEmpty()) return if (aptMode != WITH_COMPILATION) doNotGenerateCode() else null
val kaptContext = KaptContext(paths, false, logger, mapDiagnosticLocations, options, javacOptions)
@@ -230,17 +230,32 @@ abstract class AbstractKapt3Extension(
}
}
private fun runAnnotationProcessing(kaptContext: KaptContext, processors: List<Processor>) {
private fun runAnnotationProcessing(kaptContext: KaptContext, processors: LoadedProcessors) {
if (!aptMode.runAnnotationProcessing) return
val javaSourceFiles = paths.collectJavaSourceFiles()
logger.info { "Java source files: " + javaSourceFiles.joinToString { it.canonicalPath } }
val (annotationProcessingTime) = measureTimeMillis {
kaptContext.doAnnotationProcessing(javaSourceFiles, processors)
kaptContext.doAnnotationProcessing(javaSourceFiles, processors.processors)
}
logger.info { "Annotation processing took $annotationProcessingTime ms" }
if (detectMemoryLeaks) {
MemoryLeakDetector.add(processors.classLoader)
val (leakDetectionTime, leaks) = measureTimeMillis { MemoryLeakDetector.process() }
logger.info { "Leak detection took $leakDetectionTime ms" }
for (leak in leaks) {
logger.warn(buildString {
appendln("Memory leak detected!")
appendln("Location: '${leak.className}', static field '${leak.fieldName}'")
append(leak.description)
})
}
}
}
private fun contextForStubGeneration(
@@ -344,7 +359,7 @@ abstract class AbstractKapt3Extension(
)
}
protected abstract fun loadProcessors(): List<Processor>
protected abstract fun loadProcessors(): LoadedProcessors
}
internal fun JCTree.prettyPrint(context: Context): String {
@@ -115,6 +115,9 @@ object Kapt3ConfigurationKeys {
val STRICT_MODE: CompilerConfigurationKey<String> =
CompilerConfigurationKey.create<String>(STRICT_MODE_OPTION.description)
val DETECT_MEMORY_LEAKS: CompilerConfigurationKey<String> =
CompilerConfigurationKey.create<String>(DETECT_MEMORY_LEAKS_OPTION.description)
}
class Kapt3CommandLineProcessor : CommandLineProcessor {
@@ -147,6 +150,7 @@ class Kapt3CommandLineProcessor : CommandLineProcessor {
MAP_DIAGNOSTIC_LOCATIONS_OPTION -> configuration.put(Kapt3ConfigurationKeys.MAP_DIAGNOSTIC_LOCATIONS, value)
INFO_AS_WARNINGS_OPTION -> configuration.put(Kapt3ConfigurationKeys.INFO_AS_WARNINGS, value)
STRICT_MODE_OPTION -> configuration.put(Kapt3ConfigurationKeys.STRICT_MODE, value)
DETECT_MEMORY_LEAKS_OPTION -> configuration.put(Kapt3ConfigurationKeys.DETECT_MEMORY_LEAKS, value)
CONFIGURATION -> configuration.applyOptionsFrom(decodePluginOptions(value), pluginOptions)
TOOLS_JAR_OPTION -> throw CliOptionProcessingException("'${TOOLS_JAR_OPTION.optionName}' is only supported in the kapt CLI tool")
}
@@ -199,6 +203,7 @@ class Kapt3ComponentRegistrar : ComponentRegistrar {
val isVerbose = configuration.get(Kapt3ConfigurationKeys.VERBOSE_MODE) == "true"
val infoAsWarnings = configuration.get(Kapt3ConfigurationKeys.INFO_AS_WARNINGS) == "true"
val strictMode = configuration.get(Kapt3ConfigurationKeys.STRICT_MODE) == "true"
val detectMemoryLeaks = configuration.get(Kapt3ConfigurationKeys.DETECT_MEMORY_LEAKS) != "false"
val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
?: PrintingMessageCollector(System.err, MessageRenderer.PLAIN_FULL_PATHS, isVerbose)
val logger = MessageCollectorBackedKaptLogger(isVerbose, infoAsWarnings, messageCollector)
@@ -291,7 +296,7 @@ class Kapt3ComponentRegistrar : ComponentRegistrar {
val kapt3AnalysisCompletedHandlerExtension = ClasspathBasedKapt3Extension(
paths, apOptions, javacOptions, annotationProcessors,
aptMode, useLightAnalysis, correctErrorTypes, mapDiagnosticLocations, strictMode,
aptMode, useLightAnalysis, correctErrorTypes, mapDiagnosticLocations, strictMode, detectMemoryLeaks,
System.currentTimeMillis(), logger, configuration
)
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.kapt3.*
import org.jetbrains.kotlin.kapt3.AptMode.STUBS_AND_APT
import org.jetbrains.kotlin.kapt3.base.KaptContext
import org.jetbrains.kotlin.kapt3.base.KaptPaths
import org.jetbrains.kotlin.kapt3.base.LoadedProcessors
import org.jetbrains.kotlin.kapt3.javac.KaptJavaFileObject
import org.jetbrains.kotlin.kapt3.stubs.ClassFileToSourceStubConverter
import org.jetbrains.kotlin.kapt3.stubs.ClassFileToSourceStubConverter.KaptStub
@@ -174,13 +175,13 @@ abstract class AbstractKotlinKapt3IntegrationTest : CodegenTestCase() {
emptyList(), javaSourceRoots, outputDir, outputDir, stubsOutputDir, incrementalDataOutputDir
), options, emptyMap(), emptyList(), STUBS_AND_APT, System.currentTimeMillis(),
MessageCollectorBackedKaptLogger(true),
correctErrorTypes = true, mapDiagnosticLocations = true, strictMode = true,
correctErrorTypes = true, mapDiagnosticLocations = true, strictMode = true, detectMemoryLeaks = false,
compilerConfiguration = CompilerConfiguration.EMPTY
) {
internal var savedStubs: String? = null
internal var savedBindings: Map<String, KaptJavaFileObject>? = null
override fun loadProcessors() = processors
override fun loadProcessors() = LoadedProcessors(processors, Kapt3ExtensionForTests::class.java.classLoader)
override fun saveStubs(kaptContext: KaptContext, stubs: List<KaptStub>) {
if (this.savedStubs != null) {