All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.utils.kapt.MemoryLeakDetector.kt Maven / Gradle / Ivy

There is a newer version: 2.0.20-RC
Show newest version
/*
 * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.utils.kapt

import org.jetbrains.kotlin.utils.getSafe
import java.lang.ref.WeakReference
import java.lang.reflect.Modifier
import java.util.*
import javax.annotation.processing.*
import javax.lang.model.AnnotatedConstruct
import javax.lang.model.util.Elements
import javax.lang.model.util.Types
import kotlin.ConcurrentModificationException

class MemoryLeak(val className: String, val fieldName: String, val description: String) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as MemoryLeak

        if (className != other.className) return false
        if (fieldName != other.fieldName) return false

        return true
    }

    override fun hashCode(): Int {
        var result = className.hashCode()
        result = 31 * result + fieldName.hashCode()
        return result
    }
}

private class ClassLoaderData(classLoader: ClassLoader) {
    val ref = WeakReference(classLoader)

    @Volatile
    var age: Int = 0
}

object MemoryLeakDetector {
    private val classLoaderData = mutableListOf()

    fun add(classLoader: ClassLoader) {
        synchronized(classLoaderData) {
            classLoaderData.add(ClassLoaderData(classLoader))
        }
    }

    fun process(isParanoid: Boolean): Set {
        val memoryLeaks = mutableSetOf()

        synchronized(classLoaderData) {
            val newClassLoaderData = mutableListOf()
            for (data in classLoaderData) {
                val classLoader = data.ref.get() ?: continue
                data.age += 1

                if (isParanoid || data.age >= 5) {
                    // Inspect statics just once.
                    // Note the 'data' is not added to 'nextClassLoaderData' used the next time.
                    inspectStatics(classLoader)
                } else {
                    newClassLoaderData += data
                }
            }

            classLoaderData.clear()
            classLoaderData.addAll(newClassLoaderData)
        }

        return memoryLeaks
    }

    private fun inspectStatics(classLoader: ClassLoader): Set {
        val loadedClasses = classLoader.loadedClasses()
        val loadedClassesSet = try {
            loadedClasses.mapTo(mutableSetOf()) { it }
        } catch (e: ConcurrentModificationException) {
            Thread.sleep(100)
            return inspectStatics(classLoader)
        }

        val leaks = mutableSetOf()

        for (clazz in loadedClassesSet) {
            val declaredFields = try {
                clazz.declaredFields
            } catch (e: Throwable) {
                continue
            }

            nextField@ for (field in declaredFields) {
                if (!Modifier.isStatic(field.modifiers)) continue

                val value = field.getSafe(null)
                    ?.takeIf { !it.isPrimitiveOrString() } ?: continue@nextField

                if (value.isJavacComponent()) {
                    leaks += MemoryLeak(clazz.name, field.name, "Field leaks an Annotation Processing component ($value).")
                } else if (value is Class<*> && value in loadedClassesSet) {
                    leaks += MemoryLeak(clazz.name, field.name, "Field leaks a class type from the same ClassLoader (${value.name}).")
                }
            }
        }

        return leaks
    }
}

private fun Any.isJavacComponent(): Boolean {
    @Suppress("Reformat")
    return when (this) {
        is Processor, is ProcessingEnvironment, is RoundEnvironment,
            is Filer, is Messager, is Elements, is Types, is AnnotatedConstruct -> true
        else -> false
    }
}

private fun Any.isPrimitiveOrString(): Boolean {
    @Suppress("Reformat")
    return when (this) {
        is Boolean, is Byte, is Short, is Int, is Long,
            is Char, is Float, is Double, is Void, is String -> true
        else -> false
    }
}

private fun ClassLoader.loadedClasses(): Vector> {
    try {
        val classesField = ClassLoader::class.java.getDeclaredField("classes")

        @Suppress("UNCHECKED_CAST")
        return classesField.getSafe(this) as? Vector> ?: Vector()
    } catch (e: Throwable) {
        return Vector()
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy