name.remal.gradle_plugins.dsl.utils.UsedServicesCollectorClassVisitor.kt Maven / Gradle / Ivy
package name.remal.gradle_plugins.dsl.utils
import name.remal.ASM_API
import name.remal.escapeRegex
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes.INVOKESTATIC
import org.objectweb.asm.Type
import org.objectweb.asm.Type.OBJECT
import org.objectweb.asm.Type.getArgumentTypes
import org.objectweb.asm.Type.getInternalName
import org.objectweb.asm.Type.getMethodDescriptor
import org.objectweb.asm.Type.getType
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.LdcInsnNode
import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode
import org.objectweb.asm.tree.analysis.Analyzer
import org.objectweb.asm.tree.analysis.BasicInterpreter
import org.objectweb.asm.tree.analysis.BasicValue
import org.objectweb.asm.tree.analysis.Interpreter
import java.util.ServiceLoader
class UsedServicesCollectorClassVisitor(
api: Int,
classesWithAllowedDynamicServiceLoader: Collection = emptyList()
) : ClassVisitor(api, null) {
companion object {
private val logger = getGradleLogger(UsedServicesCollectorClassVisitor::class.java)
}
constructor(classesWithAllowedDynamicServiceLoader: Collection = emptyList()) : this(ASM_API, classesWithAllowedDynamicServiceLoader)
private val classesWithAllowedDynamicServiceLoaderRegexps = classesWithAllowedDynamicServiceLoader.asSequence()
.map(::escapeRegex)
.map { it.replace(escapeRegex("**"), ".*") }
.map { it.replace(escapeRegex("*"), "[^.]*") }
.distinct()
.map(::Regex)
.toList()
private lateinit var classInternalName: ClassInternalName
override fun visit(version: Int, access: Int, name: ClassInternalName, signature: String?, superName: ClassInternalName?, interfaces: Array?) {
classInternalName = name
super.visit(version, access, name, signature, superName, interfaces)
}
val usedServiceClassInternalNames: Set get() = _usedServiceClassInternalNames.toSortedSet()
private val _usedServiceClassInternalNames = hashSetOf()
override fun visitMethod(access: Int, name: String, descriptor: String, signature: String?, exceptions: Array?): MethodVisitor? {
val delegate = super.visitMethod(access, name, descriptor, signature, exceptions)
return object : MethodNode(api, access, name, descriptor, signature, exceptions) {
override fun visitEnd() {
super.visitEnd()
val interpreter = UsedServicesInterpreter(api, classInternalName, this)
val analyzer = Analyzer(interpreter)
analyzer.analyze(classInternalName, this)
delegate?.let(this::accept)
}
}
}
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType")
private inner class UsedServicesInterpreter(api: Int, private val owner: ClassInternalName, private val methodNode: MethodNode) : Interpreter(api) {
private val basicInterpreter = BasicInterpreter()
override fun newOperation(insn: AbstractInsnNode): BasicValue? {
if (insn is LdcInsnNode) {
val value = insn.cst
if (value is Type && value.sort == OBJECT) {
return ClassValue(value)
}
}
return basicInterpreter.newOperation(insn)
}
override fun naryOperation(insn: AbstractInsnNode, values: List): BasicValue? {
if (insn is MethodInsnNode && insn.opcode == INVOKESTATIC) {
if (insn.owner == getInternalName(ServiceLoader::class.java) && insn.name.startsWith("load")) {
val method = ServiceLoader::class.java.methods.firstOrNull { it.name == insn.name && getMethodDescriptor(it) == insn.desc }
if (method == null) {
logger.warn(
"Method not found in current JRE: {}.{}{}",
getInternalName(ServiceLoader::class.java),
insn.name,
insn.desc
)
return basicInterpreter.naryOperation(insn, values)
}
val valuesIndex = method.parameterTypes.indexOfFirst { it == Class::class.java }
val value = values[valuesIndex]
if (value is ClassValue) {
_usedServiceClassInternalNames.add(value.clazz.internalName)
} else {
val ownerClassName = classInternalNameToClassName(owner)
val message = "Not a constant class value passed to ServiceLoader.%s method: %s.%s(%s)".format(
insn.name,
ownerClassName,
methodNode.name,
getArgumentTypes(methodNode.desc).joinToString(", ", transform = Type::getClassName)
)
if (classesWithAllowedDynamicServiceLoaderRegexps.any { it.matches(ownerClassName) }) {
logger.debug(message)
} else {
logger.warn(message)
}
}
}
}
return basicInterpreter.naryOperation(insn, values)
}
override fun newValue(type: Type?) = basicInterpreter.newValue(type)
override fun ternaryOperation(insn: AbstractInsnNode, value1: BasicValue?, value2: BasicValue?, value3: BasicValue?) = basicInterpreter.ternaryOperation(insn, value1, value2, value3)
override fun merge(value1: BasicValue?, value2: BasicValue?) = basicInterpreter.merge(value1, value2)
override fun returnOperation(insn: AbstractInsnNode, value: BasicValue?, expected: BasicValue?) = basicInterpreter.returnOperation(insn, value, expected)
override fun unaryOperation(insn: AbstractInsnNode, value: BasicValue?) = basicInterpreter.unaryOperation(insn, value)
override fun binaryOperation(insn: AbstractInsnNode, value1: BasicValue?, value2: BasicValue?) = basicInterpreter.binaryOperation(insn, value1, value2)
override fun copyOperation(insn: AbstractInsnNode, value: BasicValue?) = basicInterpreter.copyOperation(insn, value)
}
private class ClassValue(val clazz: Type) : BasicValue(getType(Class::class.java))
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy