mirror of
https://github.com/github/codeql.git
synced 2025-12-18 01:33:15 +01:00
Move meta-annotation support code out into its own class
This commit is contained in:
@@ -7,18 +7,13 @@ import com.semmle.extractor.java.OdasaOutput
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
||||
import org.jetbrains.kotlin.backend.common.lower.parents
|
||||
import org.jetbrains.kotlin.backend.common.pop
|
||||
import org.jetbrains.kotlin.builtins.StandardNames
|
||||
import org.jetbrains.kotlin.builtins.functions.BuiltInFunctionArity
|
||||
import org.jetbrains.kotlin.config.JvmAnalysisFlags
|
||||
import org.jetbrains.kotlin.config.JvmTarget
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.KotlinRetention
|
||||
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
|
||||
import org.jetbrains.kotlin.descriptors.java.JavaVisibilities
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.*
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
@@ -29,14 +24,9 @@ import org.jetbrains.kotlin.ir.symbols.*
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
|
||||
import org.jetbrains.kotlin.ir.util.companionObject
|
||||
import org.jetbrains.kotlin.ir.util.constructedClass
|
||||
import org.jetbrains.kotlin.ir.util.constructors
|
||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||
import org.jetbrains.kotlin.ir.util.defaultType
|
||||
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
|
||||
import org.jetbrains.kotlin.ir.util.getAnnotation
|
||||
import org.jetbrains.kotlin.ir.util.hasAnnotation
|
||||
import org.jetbrains.kotlin.ir.util.hasEqualFqName
|
||||
import org.jetbrains.kotlin.ir.util.hasInterfaceParent
|
||||
import org.jetbrains.kotlin.ir.util.isAnonymousObject
|
||||
import org.jetbrains.kotlin.ir.util.isFakeOverride
|
||||
@@ -54,7 +44,6 @@ import org.jetbrains.kotlin.ir.util.parentClassOrNull
|
||||
import org.jetbrains.kotlin.ir.util.primaryConstructor
|
||||
import org.jetbrains.kotlin.ir.util.render
|
||||
import org.jetbrains.kotlin.ir.util.target
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
|
||||
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaClass
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaMethod
|
||||
@@ -62,12 +51,9 @@ import org.jetbrains.kotlin.load.java.structure.JavaTypeParameter
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameterListOwner
|
||||
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.types.Variance
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.io.Closeable
|
||||
import java.lang.annotation.ElementType
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
@@ -84,6 +70,8 @@ open class KotlinFileExtractor(
|
||||
globalExtensionState: KotlinExtractorGlobalState,
|
||||
): KotlinUsesExtractor(logger, tw, dependencyCollector, externalClassExtractor, primitiveTypeMapping, pluginContext, globalExtensionState) {
|
||||
|
||||
val metaAnnotationSupport = MetaAnnotationSupport(logger, pluginContext, this)
|
||||
|
||||
private inline fun <T> with(kind: String, element: IrElement, f: () -> T): T {
|
||||
val name = when (element) {
|
||||
is IrFile -> element.name
|
||||
@@ -492,80 +480,8 @@ open class KotlinFileExtractor(
|
||||
extractDeclInitializers(c.declarations, false) { Pair(blockId, obinitId) }
|
||||
}
|
||||
|
||||
// Taken from AdditionalIrUtils.kt (not available in Kotlin < 1.6)
|
||||
private val IrConstructorCall.annotationClass
|
||||
get() = this.symbol.owner.constructedClass
|
||||
|
||||
// Taken from AdditionalIrUtils.kt (not available in Kotlin < 1.6)
|
||||
private fun IrConstructorCall.isAnnotationWithEqualFqName(fqName: FqName): Boolean =
|
||||
annotationClass.hasEqualFqName(fqName)
|
||||
|
||||
// Adapted from RepeatedAnnotationLowering.kt
|
||||
private fun groupRepeatableAnnotations(annotations: List<IrConstructorCall>): List<IrConstructorCall> {
|
||||
if (annotations.size < 2) return annotations
|
||||
|
||||
val annotationsByClass = annotations.groupByTo(mutableMapOf()) { it.annotationClass }
|
||||
if (annotationsByClass.values.none { it.size > 1 }) return annotations
|
||||
|
||||
val result = mutableListOf<IrConstructorCall>()
|
||||
for (annotation in annotations) {
|
||||
val annotationClass = annotation.annotationClass
|
||||
val grouped = annotationsByClass.remove(annotationClass) ?: continue
|
||||
if (grouped.size < 2) {
|
||||
result.add(grouped.single())
|
||||
continue
|
||||
}
|
||||
|
||||
val containerClass = getOrCreateContainerClass(annotationClass)
|
||||
if (containerClass != null)
|
||||
wrapAnnotationEntriesInContainer(annotationClass, containerClass, grouped)?.let {
|
||||
result.add(it)
|
||||
}
|
||||
else
|
||||
logger.warnElement("Failed to find an annotation container class", annotationClass)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Adapted from RepeatedAnnotationLowering.kt
|
||||
private fun getOrCreateContainerClass(annotationClass: IrClass): IrClass? {
|
||||
val metaAnnotations = annotationClass.annotations
|
||||
val jvmRepeatable = metaAnnotations.find { it.symbol.owner.parentAsClass.fqNameWhenAvailable == JvmAnnotationNames.REPEATABLE_ANNOTATION }
|
||||
return if (jvmRepeatable != null) {
|
||||
((jvmRepeatable.getValueArgument(0) as? IrClassReference)?.symbol as? IrClassSymbol)?.owner
|
||||
} else {
|
||||
getOrCreateSyntheticRepeatableAnnotationContainer(annotationClass)
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from RepeatedAnnotationLowering.kt
|
||||
private fun wrapAnnotationEntriesInContainer(
|
||||
annotationClass: IrClass,
|
||||
containerClass: IrClass,
|
||||
entries: List<IrConstructorCall>,
|
||||
): IrConstructorCall? {
|
||||
val annotationType = annotationClass.typeWith()
|
||||
val containerConstructor = containerClass.primaryConstructor
|
||||
if (containerConstructor == null) {
|
||||
logger.warnElement("Expected container class to have a primary constructor", containerClass)
|
||||
return null
|
||||
} else {
|
||||
return IrConstructorCallImpl.fromSymbolOwner(containerClass.defaultType, containerConstructor.symbol).apply {
|
||||
putValueArgument(
|
||||
0,
|
||||
IrVarargImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||
pluginContext.irBuiltIns.arrayClass.typeWith(annotationType),
|
||||
annotationType,
|
||||
entries
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractAnnotations(annotations: List<IrConstructorCall>, parent: Label<out DbExprparent>) {
|
||||
val groupedAnnotations = groupRepeatableAnnotations(annotations)
|
||||
val groupedAnnotations = metaAnnotationSupport.groupRepeatableAnnotations(annotations)
|
||||
for ((idx, constructorCall: IrConstructorCall) in groupedAnnotations.sortedBy { v -> v.type.classFqName?.asString() }.withIndex()) {
|
||||
extractAnnotation(constructorCall, parent, idx)
|
||||
}
|
||||
@@ -678,275 +594,6 @@ open class KotlinFileExtractor(
|
||||
}
|
||||
}
|
||||
|
||||
private val javaAnnotationTargetElementType by lazy { referenceExternalClass("java.lang.annotation.ElementType") }
|
||||
|
||||
private val javaAnnotationTarget by lazy { referenceExternalClass("java.lang.annotation.Target") }
|
||||
|
||||
// Taken from AdditionalClassAnnotationLowering.kt
|
||||
private fun getApplicableTargetSet(c: IrClass): Set<KotlinTarget>? {
|
||||
val targetEntry = c.getAnnotation(StandardNames.FqNames.target) ?: return null
|
||||
return loadAnnotationTargets(targetEntry)
|
||||
}
|
||||
|
||||
// Taken from AdditionalClassAnnotationLowering.kt
|
||||
private fun loadAnnotationTargets(targetEntry: IrConstructorCall): Set<KotlinTarget>? {
|
||||
val valueArgument = targetEntry.getValueArgument(0) as? IrVararg ?: return null
|
||||
return valueArgument.elements.filterIsInstance<IrGetEnumValue>().mapNotNull {
|
||||
KotlinTarget.valueOrNull(it.symbol.owner.name.asString())
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
|
||||
private fun findEnumEntry(c: IrClass, name: String) =
|
||||
c.declarations.filterIsInstance<IrEnumEntry>().find { it.name.asString() == name }
|
||||
|
||||
// Adapted from JvmSymbols.kt
|
||||
private val jvm6TargetMap by lazy {
|
||||
javaAnnotationTargetElementType?.let {
|
||||
val etMethod = findEnumEntry(it, "METHOD")
|
||||
mapOf(
|
||||
KotlinTarget.CLASS to findEnumEntry(it, "TYPE"),
|
||||
KotlinTarget.ANNOTATION_CLASS to findEnumEntry(it, "ANNOTATION_TYPE"),
|
||||
KotlinTarget.CONSTRUCTOR to findEnumEntry(it, "CONSTRUCTOR"),
|
||||
KotlinTarget.LOCAL_VARIABLE to findEnumEntry(it, "LOCAL_VARIABLE"),
|
||||
KotlinTarget.FUNCTION to etMethod,
|
||||
KotlinTarget.PROPERTY_GETTER to etMethod,
|
||||
KotlinTarget.PROPERTY_SETTER to etMethod,
|
||||
KotlinTarget.FIELD to findEnumEntry(it, "FIELD"),
|
||||
KotlinTarget.VALUE_PARAMETER to findEnumEntry(it, "PARAMETER")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from JvmSymbols.kt
|
||||
private val jvm8TargetMap by lazy {
|
||||
javaAnnotationTargetElementType?.let {
|
||||
jvm6TargetMap?.let { j6Map ->
|
||||
j6Map + mapOf(
|
||||
KotlinTarget.TYPE_PARAMETER to findEnumEntry(it, "TYPE_PARAMETER"),
|
||||
KotlinTarget.TYPE to findEnumEntry(it, "TYPE_USE")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAnnotationTargetMap() =
|
||||
if (pluginContext.platform?.any { it.targetPlatformVersion == JvmTarget.JVM_1_6 } == true)
|
||||
jvm6TargetMap
|
||||
else
|
||||
jvm8TargetMap
|
||||
|
||||
// Adapted from AdditionalClassAnnotationLowering.kt
|
||||
private fun generateTargetAnnotation(c: IrClass): IrConstructorCall? {
|
||||
if (c.hasAnnotation(JvmAnnotationNames.TARGET_ANNOTATION))
|
||||
return null
|
||||
val elementType = javaAnnotationTargetElementType ?: return null
|
||||
val targetType = javaAnnotationTarget ?: return null
|
||||
val targetConstructor = targetType.declarations.firstIsInstanceOrNull<IrConstructor>() ?: return null
|
||||
val targets = getApplicableTargetSet(c) ?: return null
|
||||
val annotationTargetMap = getAnnotationTargetMap() ?: return null
|
||||
|
||||
val javaTargets = targets.mapNotNullTo(HashSet()) { annotationTargetMap[it] }.sortedBy {
|
||||
ElementType.valueOf(it.symbol.owner.name.asString())
|
||||
}
|
||||
val vararg = IrVarargImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||
type = pluginContext.irBuiltIns.arrayClass.typeWith(elementType.defaultType),
|
||||
varargElementType = elementType.defaultType
|
||||
)
|
||||
for (target in javaTargets) {
|
||||
vararg.elements.add(
|
||||
IrGetEnumValueImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, elementType.defaultType, target.symbol
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return IrConstructorCallImpl.fromSymbolOwner(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, targetConstructor.returnType, targetConstructor.symbol, 0
|
||||
).apply {
|
||||
putValueArgument(0, vararg)
|
||||
}
|
||||
}
|
||||
|
||||
private val javaAnnotationRetention by lazy { referenceExternalClass("java.lang.annotation.Retention") }
|
||||
private val javaAnnotationRetentionPolicy by lazy { referenceExternalClass("java.lang.annotation.RetentionPolicy") }
|
||||
private val javaAnnotationRetentionPolicyRuntime by lazy { javaAnnotationRetentionPolicy?.let { findEnumEntry(it, "RUNTIME") } }
|
||||
|
||||
private val annotationRetentionMap by lazy {
|
||||
javaAnnotationRetentionPolicy?.let {
|
||||
mapOf(
|
||||
KotlinRetention.SOURCE to findEnumEntry(it, "SOURCE"),
|
||||
KotlinRetention.BINARY to findEnumEntry(it, "CLASS"),
|
||||
KotlinRetention.RUNTIME to javaAnnotationRetentionPolicyRuntime
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from AnnotationCodegen.kt (not available in Kotlin < 1.6.20)
|
||||
private fun IrClass.getAnnotationRetention(): KotlinRetention? {
|
||||
val retentionArgument =
|
||||
getAnnotation(StandardNames.FqNames.retention)?.getValueArgument(0)
|
||||
as? IrGetEnumValue ?: return null
|
||||
val retentionArgumentValue = retentionArgument.symbol.owner
|
||||
return KotlinRetention.valueOf(retentionArgumentValue.name.asString())
|
||||
}
|
||||
|
||||
// Taken from AdditionalClassAnnotationLowering.kt
|
||||
private fun generateRetentionAnnotation(irClass: IrClass): IrConstructorCall? {
|
||||
if (irClass.hasAnnotation(JvmAnnotationNames.RETENTION_ANNOTATION))
|
||||
return null
|
||||
val retentionMap = annotationRetentionMap ?: return null
|
||||
val kotlinRetentionPolicy = irClass.getAnnotationRetention()
|
||||
val javaRetentionPolicy = kotlinRetentionPolicy?.let { retentionMap[it] } ?: javaAnnotationRetentionPolicyRuntime ?: return null
|
||||
val retentionPolicyType = javaAnnotationRetentionPolicy ?: return null
|
||||
val retentionType = javaAnnotationRetention ?: return null
|
||||
val targetConstructor = retentionType.declarations.firstIsInstanceOrNull<IrConstructor>() ?: return null
|
||||
|
||||
return IrConstructorCallImpl.fromSymbolOwner(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, targetConstructor.returnType, targetConstructor.symbol, 0
|
||||
).apply {
|
||||
putValueArgument(
|
||||
0,
|
||||
IrGetEnumValueImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, retentionPolicyType.defaultType, javaRetentionPolicy.symbol
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val javaAnnotationRepeatable by lazy { referenceExternalClass("java.lang.annotation.Repeatable") }
|
||||
private val kotlinAnnotationRepeatableContainer by lazy { referenceExternalClass("kotlin.jvm.internal.RepeatableContainer") }
|
||||
|
||||
// Taken from declarationBuilders.kt (not available in Kotlin < 1.6):
|
||||
private fun addDefaultGetter(p: IrProperty, parentClass: IrClass) {
|
||||
val field = p.backingField ?:
|
||||
run { logger.warnElement("Expected property to have a backing field", p); return }
|
||||
p.addGetter {
|
||||
origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR
|
||||
returnType = field.type
|
||||
}.apply {
|
||||
val thisReceiever = parentClass.thisReceiver ?:
|
||||
run { logger.warnElement("Expected property's parent class to have a receiver parameter", parentClass); return }
|
||||
val newParam = copyParameterToFunction(thisReceiever, this)
|
||||
dispatchReceiverParameter = newParam
|
||||
body = factory.createBlockBody(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, listOf(
|
||||
IrReturnImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||
pluginContext.irBuiltIns.nothingType,
|
||||
symbol,
|
||||
IrGetFieldImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||
field.symbol,
|
||||
field.type,
|
||||
IrGetValueImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||
newParam.type,
|
||||
newParam.symbol
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from JvmCachedDeclarations.kt
|
||||
private fun getOrCreateSyntheticRepeatableAnnotationContainer(annotationClass: IrClass) =
|
||||
globalExtensionState.syntheticRepeatableAnnotationContainers.getOrPut(annotationClass) {
|
||||
val containerClass = pluginContext.irFactory.buildClass {
|
||||
kind = ClassKind.ANNOTATION_CLASS
|
||||
name = Name.identifier("Container")
|
||||
}.apply {
|
||||
createImplicitParameterDeclarationWithWrappedDescriptor()
|
||||
parent = annotationClass
|
||||
superTypes = listOf(getAnnotationType(pluginContext))
|
||||
}
|
||||
|
||||
val propertyName = Name.identifier("value")
|
||||
val propertyType = pluginContext.irBuiltIns.arrayClass.typeWith(annotationClass.typeWith())
|
||||
|
||||
containerClass.addConstructor {
|
||||
isPrimary = true
|
||||
}.apply {
|
||||
addValueParameter(propertyName.identifier, propertyType)
|
||||
}
|
||||
|
||||
containerClass.addProperty {
|
||||
name = propertyName
|
||||
}.apply property@{
|
||||
backingField = pluginContext.irFactory.buildField {
|
||||
name = propertyName
|
||||
type = propertyType
|
||||
}.apply {
|
||||
parent = containerClass
|
||||
correspondingPropertySymbol = this@property.symbol
|
||||
}
|
||||
addDefaultGetter(this, containerClass)
|
||||
}
|
||||
|
||||
val repeatableContainerAnnotation = kotlinAnnotationRepeatableContainer?.let { it.constructors.single() }
|
||||
|
||||
containerClass.annotations = annotationClass.annotations
|
||||
.filter {
|
||||
it.isAnnotationWithEqualFqName(StandardNames.FqNames.retention) ||
|
||||
it.isAnnotationWithEqualFqName(StandardNames.FqNames.target)
|
||||
}
|
||||
.map { it.deepCopyWithSymbols(containerClass) } +
|
||||
listOfNotNull(
|
||||
repeatableContainerAnnotation?.let {
|
||||
IrConstructorCallImpl.fromSymbolOwner(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, it.returnType, it.symbol, 0
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
containerClass
|
||||
}
|
||||
|
||||
// Adapted from AdditionalClassAnnotationLowering.kt
|
||||
private fun generateRepeatableAnnotation(irClass: IrClass): IrConstructorCall? {
|
||||
if (!irClass.hasAnnotation(StandardNames.FqNames.repeatable) ||
|
||||
irClass.hasAnnotation(JvmAnnotationNames.REPEATABLE_ANNOTATION)
|
||||
) return null
|
||||
|
||||
val repeatableConstructor = javaAnnotationRepeatable?.declarations?.firstIsInstanceOrNull<IrConstructor>() ?: return null
|
||||
|
||||
val containerClass = getOrCreateSyntheticRepeatableAnnotationContainer(irClass)
|
||||
// Whenever a repeatable annotation with a Kotlin-synthesised container is extracted, extract the synthetic container to the same trap file.
|
||||
extractClassSource(containerClass, extractDeclarations = true, extractStaticInitializer = true, extractPrivateMembers = true, extractFunctionBodies = true)
|
||||
|
||||
val containerReference = IrClassReferenceImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, pluginContext.irBuiltIns.kClassClass.typeWith(containerClass.defaultType),
|
||||
containerClass.symbol, containerClass.defaultType
|
||||
)
|
||||
return IrConstructorCallImpl.fromSymbolOwner(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, repeatableConstructor.returnType, repeatableConstructor.symbol, 0
|
||||
).apply {
|
||||
putValueArgument(0, containerReference)
|
||||
}
|
||||
}
|
||||
|
||||
private val javaAnnotationDocumented by lazy { referenceExternalClass("java.lang.annotation.Documented") }
|
||||
|
||||
// Taken from AdditionalClassAnnotationLowering.kt
|
||||
private fun generateDocumentedAnnotation(irClass: IrClass): IrConstructorCall? {
|
||||
if (!irClass.hasAnnotation(StandardNames.FqNames.mustBeDocumented) ||
|
||||
irClass.hasAnnotation(JvmAnnotationNames.DOCUMENTED_ANNOTATION)
|
||||
) return null
|
||||
|
||||
val documentedConstructor = javaAnnotationDocumented?.declarations?.firstIsInstanceOrNull<IrConstructor>() ?: return null
|
||||
|
||||
return IrConstructorCallImpl.fromSymbolOwner(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, documentedConstructor.returnType, documentedConstructor.symbol, 0
|
||||
)
|
||||
}
|
||||
|
||||
private fun generateJavaMetaAnnotations(c: IrClass) =
|
||||
// This is essentially AdditionalClassAnnotationLowering adapted to run outside the backend.
|
||||
listOfNotNull(generateTargetAnnotation(c), generateRetentionAnnotation(c), generateRepeatableAnnotation(c), generateDocumentedAnnotation(c))
|
||||
|
||||
fun extractClassSource(c: IrClass, extractDeclarations: Boolean, extractStaticInitializer: Boolean, extractPrivateMembers: Boolean, extractFunctionBodies: Boolean): Label<out DbClassorinterface> {
|
||||
with("class source", c) {
|
||||
DeclarationStackAdjuster(c).use {
|
||||
@@ -1035,7 +682,7 @@ open class KotlinFileExtractor(
|
||||
|
||||
val additionalAnnotations =
|
||||
if (c.kind == ClassKind.ANNOTATION_CLASS && c.origin != IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB)
|
||||
generateJavaMetaAnnotations(c)
|
||||
metaAnnotationSupport.generateJavaMetaAnnotations(c)
|
||||
else
|
||||
listOf()
|
||||
|
||||
|
||||
396
java/kotlin-extractor/src/main/kotlin/MetaAnnotationSupport.kt
Normal file
396
java/kotlin-extractor/src/main/kotlin/MetaAnnotationSupport.kt
Normal file
@@ -0,0 +1,396 @@
|
||||
package com.github.codeql
|
||||
|
||||
import com.github.codeql.utils.versions.copyParameterToFunction
|
||||
import com.github.codeql.utils.versions.createImplicitParameterDeclarationWithWrappedDescriptor
|
||||
import com.github.codeql.utils.versions.getAnnotationType
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
||||
import org.jetbrains.kotlin.builtins.StandardNames
|
||||
import org.jetbrains.kotlin.config.JvmTarget
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.annotations.KotlinRetention
|
||||
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addConstructor
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addGetter
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addProperty
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.buildClass
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.buildField
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrConstructor
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
||||
import org.jetbrains.kotlin.ir.declarations.IrEnumEntry
|
||||
import org.jetbrains.kotlin.ir.declarations.IrProperty
|
||||
import org.jetbrains.kotlin.ir.expressions.IrClassReference
|
||||
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrGetEnumValue
|
||||
import org.jetbrains.kotlin.ir.expressions.IrVararg
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetEnumValueImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||
import org.jetbrains.kotlin.ir.types.typeWith
|
||||
import org.jetbrains.kotlin.ir.util.constructedClass
|
||||
import org.jetbrains.kotlin.ir.util.constructors
|
||||
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||
import org.jetbrains.kotlin.ir.util.defaultType
|
||||
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
|
||||
import org.jetbrains.kotlin.ir.util.getAnnotation
|
||||
import org.jetbrains.kotlin.ir.util.hasAnnotation
|
||||
import org.jetbrains.kotlin.ir.util.hasEqualFqName
|
||||
import org.jetbrains.kotlin.ir.util.parentAsClass
|
||||
import org.jetbrains.kotlin.ir.util.primaryConstructor
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import java.lang.annotation.ElementType
|
||||
import java.util.HashSet
|
||||
|
||||
class MetaAnnotationSupport(private val logger: FileLogger, private val pluginContext: IrPluginContext, private val extractor: KotlinFileExtractor) {
|
||||
|
||||
// Taken from AdditionalIrUtils.kt (not available in Kotlin < 1.6)
|
||||
private val IrConstructorCall.annotationClass
|
||||
get() = this.symbol.owner.constructedClass
|
||||
|
||||
// Taken from AdditionalIrUtils.kt (not available in Kotlin < 1.6)
|
||||
private fun IrConstructorCall.isAnnotationWithEqualFqName(fqName: FqName): Boolean =
|
||||
annotationClass.hasEqualFqName(fqName)
|
||||
|
||||
// Adapted from RepeatedAnnotationLowering.kt
|
||||
fun groupRepeatableAnnotations(annotations: List<IrConstructorCall>): List<IrConstructorCall> {
|
||||
if (annotations.size < 2) return annotations
|
||||
|
||||
val annotationsByClass = annotations.groupByTo(mutableMapOf()) { it.annotationClass }
|
||||
if (annotationsByClass.values.none { it.size > 1 }) return annotations
|
||||
|
||||
val result = mutableListOf<IrConstructorCall>()
|
||||
for (annotation in annotations) {
|
||||
val annotationClass = annotation.annotationClass
|
||||
val grouped = annotationsByClass.remove(annotationClass) ?: continue
|
||||
if (grouped.size < 2) {
|
||||
result.add(grouped.single())
|
||||
continue
|
||||
}
|
||||
|
||||
val containerClass = getOrCreateContainerClass(annotationClass)
|
||||
if (containerClass != null)
|
||||
wrapAnnotationEntriesInContainer(annotationClass, containerClass, grouped)?.let {
|
||||
result.add(it)
|
||||
}
|
||||
else
|
||||
logger.warnElement("Failed to find an annotation container class", annotationClass)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Adapted from RepeatedAnnotationLowering.kt
|
||||
private fun getOrCreateContainerClass(annotationClass: IrClass): IrClass? {
|
||||
val metaAnnotations = annotationClass.annotations
|
||||
val jvmRepeatable = metaAnnotations.find { it.symbol.owner.parentAsClass.fqNameWhenAvailable == JvmAnnotationNames.REPEATABLE_ANNOTATION }
|
||||
return if (jvmRepeatable != null) {
|
||||
((jvmRepeatable.getValueArgument(0) as? IrClassReference)?.symbol as? IrClassSymbol)?.owner
|
||||
} else {
|
||||
getOrCreateSyntheticRepeatableAnnotationContainer(annotationClass)
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from RepeatedAnnotationLowering.kt
|
||||
private fun wrapAnnotationEntriesInContainer(
|
||||
annotationClass: IrClass,
|
||||
containerClass: IrClass,
|
||||
entries: List<IrConstructorCall>
|
||||
): IrConstructorCall? {
|
||||
val annotationType = annotationClass.typeWith()
|
||||
val containerConstructor = containerClass.primaryConstructor
|
||||
if (containerConstructor == null) {
|
||||
logger.warnElement("Expected container class to have a primary constructor", containerClass)
|
||||
return null
|
||||
} else {
|
||||
return IrConstructorCallImpl.fromSymbolOwner(containerClass.defaultType, containerConstructor.symbol).apply {
|
||||
putValueArgument(
|
||||
0,
|
||||
IrVarargImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||
pluginContext.irBuiltIns.arrayClass.typeWith(annotationType),
|
||||
annotationType,
|
||||
entries
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from AdditionalClassAnnotationLowering.kt
|
||||
private fun getApplicableTargetSet(c: IrClass): Set<KotlinTarget>? {
|
||||
val targetEntry = c.getAnnotation(StandardNames.FqNames.target) ?: return null
|
||||
return loadAnnotationTargets(targetEntry)
|
||||
}
|
||||
|
||||
// Taken from AdditionalClassAnnotationLowering.kt
|
||||
private fun loadAnnotationTargets(targetEntry: IrConstructorCall): Set<KotlinTarget>? {
|
||||
val valueArgument = targetEntry.getValueArgument(0) as? IrVararg ?: return null
|
||||
return valueArgument.elements.filterIsInstance<IrGetEnumValue>().mapNotNull {
|
||||
KotlinTarget.valueOrNull(it.symbol.owner.name.asString())
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
private val javaAnnotationTargetElementType by lazy { extractor.referenceExternalClass("java.lang.annotation.ElementType") }
|
||||
|
||||
private val javaAnnotationTarget by lazy { extractor.referenceExternalClass("java.lang.annotation.Target") }
|
||||
|
||||
private fun findEnumEntry(c: IrClass, name: String) =
|
||||
c.declarations.filterIsInstance<IrEnumEntry>().find { it.name.asString() == name }
|
||||
|
||||
// Adapted from JvmSymbols.kt
|
||||
private val jvm6TargetMap by lazy {
|
||||
javaAnnotationTargetElementType?.let {
|
||||
val etMethod = findEnumEntry(it, "METHOD")
|
||||
mapOf(
|
||||
KotlinTarget.CLASS to findEnumEntry(it, "TYPE"),
|
||||
KotlinTarget.ANNOTATION_CLASS to findEnumEntry(it, "ANNOTATION_TYPE"),
|
||||
KotlinTarget.CONSTRUCTOR to findEnumEntry(it, "CONSTRUCTOR"),
|
||||
KotlinTarget.LOCAL_VARIABLE to findEnumEntry(it, "LOCAL_VARIABLE"),
|
||||
KotlinTarget.FUNCTION to etMethod,
|
||||
KotlinTarget.PROPERTY_GETTER to etMethod,
|
||||
KotlinTarget.PROPERTY_SETTER to etMethod,
|
||||
KotlinTarget.FIELD to findEnumEntry(it, "FIELD"),
|
||||
KotlinTarget.VALUE_PARAMETER to findEnumEntry(it, "PARAMETER")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Adapted from JvmSymbols.kt
|
||||
private val jvm8TargetMap by lazy {
|
||||
javaAnnotationTargetElementType?.let {
|
||||
jvm6TargetMap?.let { j6Map ->
|
||||
j6Map + mapOf(
|
||||
KotlinTarget.TYPE_PARAMETER to findEnumEntry(it, "TYPE_PARAMETER"),
|
||||
KotlinTarget.TYPE to findEnumEntry(it, "TYPE_USE")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAnnotationTargetMap() =
|
||||
if (pluginContext.platform?.any { it.targetPlatformVersion == JvmTarget.JVM_1_6 } == true)
|
||||
jvm6TargetMap
|
||||
else
|
||||
jvm8TargetMap
|
||||
|
||||
// Adapted from AdditionalClassAnnotationLowering.kt
|
||||
private fun generateTargetAnnotation(c: IrClass): IrConstructorCall? {
|
||||
if (c.hasAnnotation(JvmAnnotationNames.TARGET_ANNOTATION))
|
||||
return null
|
||||
val elementType = javaAnnotationTargetElementType ?: return null
|
||||
val targetType = javaAnnotationTarget ?: return null
|
||||
val targetConstructor = targetType.declarations.firstIsInstanceOrNull<IrConstructor>() ?: return null
|
||||
val targets = getApplicableTargetSet(c) ?: return null
|
||||
val annotationTargetMap = getAnnotationTargetMap() ?: return null
|
||||
|
||||
val javaTargets = targets.mapNotNullTo(HashSet()) { annotationTargetMap[it] }.sortedBy {
|
||||
ElementType.valueOf(it.symbol.owner.name.asString())
|
||||
}
|
||||
val vararg = IrVarargImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||
type = pluginContext.irBuiltIns.arrayClass.typeWith(elementType.defaultType),
|
||||
varargElementType = elementType.defaultType
|
||||
)
|
||||
for (target in javaTargets) {
|
||||
vararg.elements.add(
|
||||
IrGetEnumValueImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, elementType.defaultType, target.symbol
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return IrConstructorCallImpl.fromSymbolOwner(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, targetConstructor.returnType, targetConstructor.symbol, 0
|
||||
).apply {
|
||||
putValueArgument(0, vararg)
|
||||
}
|
||||
}
|
||||
|
||||
private val javaAnnotationRetention by lazy { extractor.referenceExternalClass("java.lang.annotation.Retention") }
|
||||
private val javaAnnotationRetentionPolicy by lazy { extractor.referenceExternalClass("java.lang.annotation.RetentionPolicy") }
|
||||
private val javaAnnotationRetentionPolicyRuntime by lazy { javaAnnotationRetentionPolicy?.let { findEnumEntry(it, "RUNTIME") } }
|
||||
|
||||
private val annotationRetentionMap by lazy {
|
||||
javaAnnotationRetentionPolicy?.let {
|
||||
mapOf(
|
||||
KotlinRetention.SOURCE to findEnumEntry(it, "SOURCE"),
|
||||
KotlinRetention.BINARY to findEnumEntry(it, "CLASS"),
|
||||
KotlinRetention.RUNTIME to javaAnnotationRetentionPolicyRuntime
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from AnnotationCodegen.kt (not available in Kotlin < 1.6.20)
|
||||
private fun IrClass.getAnnotationRetention(): KotlinRetention? {
|
||||
val retentionArgument =
|
||||
getAnnotation(StandardNames.FqNames.retention)?.getValueArgument(0)
|
||||
as? IrGetEnumValue ?: return null
|
||||
val retentionArgumentValue = retentionArgument.symbol.owner
|
||||
return KotlinRetention.valueOf(retentionArgumentValue.name.asString())
|
||||
}
|
||||
|
||||
// Taken from AdditionalClassAnnotationLowering.kt
|
||||
private fun generateRetentionAnnotation(irClass: IrClass): IrConstructorCall? {
|
||||
if (irClass.hasAnnotation(JvmAnnotationNames.RETENTION_ANNOTATION))
|
||||
return null
|
||||
val retentionMap = annotationRetentionMap ?: return null
|
||||
val kotlinRetentionPolicy = irClass.getAnnotationRetention()
|
||||
val javaRetentionPolicy = kotlinRetentionPolicy?.let { retentionMap[it] } ?: javaAnnotationRetentionPolicyRuntime ?: return null
|
||||
val retentionPolicyType = javaAnnotationRetentionPolicy ?: return null
|
||||
val retentionType = javaAnnotationRetention ?: return null
|
||||
val targetConstructor = retentionType.declarations.firstIsInstanceOrNull<IrConstructor>() ?: return null
|
||||
|
||||
return IrConstructorCallImpl.fromSymbolOwner(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, targetConstructor.returnType, targetConstructor.symbol, 0
|
||||
).apply {
|
||||
putValueArgument(
|
||||
0,
|
||||
IrGetEnumValueImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, retentionPolicyType.defaultType, javaRetentionPolicy.symbol
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val javaAnnotationRepeatable by lazy { extractor.referenceExternalClass("java.lang.annotation.Repeatable") }
|
||||
private val kotlinAnnotationRepeatableContainer by lazy { extractor.referenceExternalClass("kotlin.jvm.internal.RepeatableContainer") }
|
||||
|
||||
// Taken from declarationBuilders.kt (not available in Kotlin < 1.6):
|
||||
private fun addDefaultGetter(p: IrProperty, parentClass: IrClass) {
|
||||
val field = p.backingField ?:
|
||||
run { logger.warnElement("Expected property to have a backing field", p); return }
|
||||
p.addGetter {
|
||||
origin = IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR
|
||||
returnType = field.type
|
||||
}.apply {
|
||||
val thisReceiever = parentClass.thisReceiver ?:
|
||||
run { logger.warnElement("Expected property's parent class to have a receiver parameter", parentClass); return }
|
||||
val newParam = copyParameterToFunction(thisReceiever, this)
|
||||
dispatchReceiverParameter = newParam
|
||||
body = factory.createBlockBody(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, listOf(
|
||||
IrReturnImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||
pluginContext.irBuiltIns.nothingType,
|
||||
symbol,
|
||||
IrGetFieldImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||
field.symbol,
|
||||
field.type,
|
||||
IrGetValueImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
|
||||
newParam.type,
|
||||
newParam.symbol
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from JvmCachedDeclarations.kt
|
||||
private fun getOrCreateSyntheticRepeatableAnnotationContainer(annotationClass: IrClass) =
|
||||
extractor.globalExtensionState.syntheticRepeatableAnnotationContainers.getOrPut(annotationClass) {
|
||||
val containerClass = pluginContext.irFactory.buildClass {
|
||||
kind = ClassKind.ANNOTATION_CLASS
|
||||
name = Name.identifier("Container")
|
||||
}.apply {
|
||||
createImplicitParameterDeclarationWithWrappedDescriptor()
|
||||
parent = annotationClass
|
||||
superTypes = listOf(getAnnotationType(pluginContext))
|
||||
}
|
||||
|
||||
val propertyName = Name.identifier("value")
|
||||
val propertyType = pluginContext.irBuiltIns.arrayClass.typeWith(annotationClass.typeWith())
|
||||
|
||||
containerClass.addConstructor {
|
||||
isPrimary = true
|
||||
}.apply {
|
||||
addValueParameter(propertyName.identifier, propertyType)
|
||||
}
|
||||
|
||||
containerClass.addProperty {
|
||||
name = propertyName
|
||||
}.apply property@{
|
||||
backingField = pluginContext.irFactory.buildField {
|
||||
name = propertyName
|
||||
type = propertyType
|
||||
}.apply {
|
||||
parent = containerClass
|
||||
correspondingPropertySymbol = this@property.symbol
|
||||
}
|
||||
addDefaultGetter(this, containerClass)
|
||||
}
|
||||
|
||||
val repeatableContainerAnnotation = kotlinAnnotationRepeatableContainer?.constructors?.single()
|
||||
|
||||
containerClass.annotations = annotationClass.annotations
|
||||
.filter {
|
||||
it.isAnnotationWithEqualFqName(StandardNames.FqNames.retention) ||
|
||||
it.isAnnotationWithEqualFqName(StandardNames.FqNames.target)
|
||||
}
|
||||
.map { it.deepCopyWithSymbols(containerClass) } +
|
||||
listOfNotNull(
|
||||
repeatableContainerAnnotation?.let {
|
||||
IrConstructorCallImpl.fromSymbolOwner(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, it.returnType, it.symbol, 0
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
containerClass
|
||||
}
|
||||
|
||||
// Adapted from AdditionalClassAnnotationLowering.kt
|
||||
private fun generateRepeatableAnnotation(irClass: IrClass): IrConstructorCall? {
|
||||
if (!irClass.hasAnnotation(StandardNames.FqNames.repeatable) ||
|
||||
irClass.hasAnnotation(JvmAnnotationNames.REPEATABLE_ANNOTATION)
|
||||
) return null
|
||||
|
||||
val repeatableConstructor = javaAnnotationRepeatable?.declarations?.firstIsInstanceOrNull<IrConstructor>() ?: return null
|
||||
|
||||
val containerClass = getOrCreateSyntheticRepeatableAnnotationContainer(irClass)
|
||||
// Whenever a repeatable annotation with a Kotlin-synthesised container is extracted, extract the synthetic container to the same trap file.
|
||||
extractor.extractClassSource(containerClass, extractDeclarations = true, extractStaticInitializer = true, extractPrivateMembers = true, extractFunctionBodies = true)
|
||||
|
||||
val containerReference = IrClassReferenceImpl(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, pluginContext.irBuiltIns.kClassClass.typeWith(containerClass.defaultType),
|
||||
containerClass.symbol, containerClass.defaultType
|
||||
)
|
||||
return IrConstructorCallImpl.fromSymbolOwner(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, repeatableConstructor.returnType, repeatableConstructor.symbol, 0
|
||||
).apply {
|
||||
putValueArgument(0, containerReference)
|
||||
}
|
||||
}
|
||||
|
||||
private val javaAnnotationDocumented by lazy { extractor.referenceExternalClass("java.lang.annotation.Documented") }
|
||||
|
||||
// Taken from AdditionalClassAnnotationLowering.kt
|
||||
private fun generateDocumentedAnnotation(irClass: IrClass): IrConstructorCall? {
|
||||
if (!irClass.hasAnnotation(StandardNames.FqNames.mustBeDocumented) ||
|
||||
irClass.hasAnnotation(JvmAnnotationNames.DOCUMENTED_ANNOTATION)
|
||||
) return null
|
||||
|
||||
val documentedConstructor = javaAnnotationDocumented?.declarations?.firstIsInstanceOrNull<IrConstructor>() ?: return null
|
||||
|
||||
return IrConstructorCallImpl.fromSymbolOwner(
|
||||
UNDEFINED_OFFSET, UNDEFINED_OFFSET, documentedConstructor.returnType, documentedConstructor.symbol, 0
|
||||
)
|
||||
}
|
||||
|
||||
fun generateJavaMetaAnnotations(c: IrClass) =
|
||||
// This is essentially AdditionalClassAnnotationLowering adapted to run outside the backend.
|
||||
listOfNotNull(generateTargetAnnotation(c), generateRetentionAnnotation(c), generateRepeatableAnnotation(c), generateDocumentedAnnotation(c))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user