Skip to content

Commit 9e0eb56

Browse files
committed
Extract repeatable annotation container types, including synthetic containers
1 parent 0146d82 commit 9e0eb56

File tree

2 files changed

+121
-6
lines changed

2 files changed

+121
-6
lines changed

java/kotlin-extractor/src/main/kotlin/KotlinExtractorExtension.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ class KotlinExtractorGlobalState {
207207
val syntheticToRealClassMap = HashMap<IrClass, IrClass?>()
208208
val syntheticToRealFunctionMap = HashMap<IrFunction, IrFunction?>()
209209
val syntheticToRealFieldMap = HashMap<IrField, IrField?>()
210+
val syntheticRepeatableAnnotationContainers = HashMap<IrClass, IrClass>()
210211
}
211212

212213
/*

java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt

Lines changed: 120 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.github.codeql.utils.versions.functionN
77
import com.github.codeql.utils.versions.isUnderscoreParameter
88
import com.semmle.extractor.java.OdasaOutput
99
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
10+
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
1011
import org.jetbrains.kotlin.backend.common.lower.parents
1112
import org.jetbrains.kotlin.backend.common.pop
1213
import org.jetbrains.kotlin.backend.jvm.ir.getAnnotationRetention
@@ -22,6 +23,7 @@ import org.jetbrains.kotlin.ir.IrStatement
2223
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
2324
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
2425
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
26+
import org.jetbrains.kotlin.ir.builders.declarations.*
2527
import org.jetbrains.kotlin.ir.declarations.*
2628
import org.jetbrains.kotlin.ir.declarations.lazy.IrLazyFunction
2729
import org.jetbrains.kotlin.ir.expressions.*
@@ -39,6 +41,7 @@ import org.jetbrains.kotlin.load.java.structure.JavaTypeParameter
3941
import org.jetbrains.kotlin.load.java.structure.JavaTypeParameterListOwner
4042
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
4143
import org.jetbrains.kotlin.name.FqName
44+
import org.jetbrains.kotlin.name.Name
4245
import org.jetbrains.kotlin.types.Variance
4346
import org.jetbrains.kotlin.util.OperatorNameConventions
4447
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
@@ -468,8 +471,65 @@ open class KotlinFileExtractor(
468471
extractDeclInitializers(c.declarations, false) { Pair(blockId, obinitId) }
469472
}
470473

474+
// Adapted from RepeatedAnnotationLowering.kt
475+
private fun groupRepeatableAnnotations(annotations: List<IrConstructorCall>): List<IrConstructorCall> {
476+
if (annotations.size < 2) return annotations
477+
478+
val annotationsByClass = annotations.groupByTo(mutableMapOf()) { it.symbol.owner.constructedClass }
479+
if (annotationsByClass.values.none { it.size > 1 }) return annotations
480+
481+
val result = mutableListOf<IrConstructorCall>()
482+
for (annotation in annotations) {
483+
val annotationClass = annotation.symbol.owner.constructedClass
484+
val grouped = annotationsByClass.remove(annotationClass) ?: continue
485+
if (grouped.size < 2) {
486+
result.add(grouped.single())
487+
continue
488+
}
489+
490+
val containerClass = getOrCreateContainerClass(annotationClass)
491+
if (containerClass != null)
492+
result.add(wrapAnnotationEntriesInContainer(annotationClass, containerClass, grouped))
493+
else
494+
logger.warnElement("Failed to find an annotation container class", annotationClass)
495+
}
496+
return result
497+
}
498+
499+
// Adapted from RepeatedAnnotationLowering.kt
500+
private fun getOrCreateContainerClass(annotationClass: IrClass): IrClass? {
501+
val metaAnnotations = annotationClass.annotations
502+
val jvmRepeatable = metaAnnotations.find { it.isAnnotation(JvmAnnotationNames.REPEATABLE_ANNOTATION) }
503+
return if (jvmRepeatable != null) {
504+
((jvmRepeatable.getValueArgument(0) as? IrClassReference)?.symbol as? IrClassSymbol)?.owner
505+
} else {
506+
getOrCreateSyntheticRepeatableAnnotationContainer(annotationClass)
507+
}
508+
}
509+
510+
// Adapted from RepeatedAnnotationLowering.kt
511+
private fun wrapAnnotationEntriesInContainer(
512+
annotationClass: IrClass,
513+
containerClass: IrClass,
514+
entries: List<IrConstructorCall>,
515+
): IrConstructorCall {
516+
val annotationType = annotationClass.typeWith()
517+
return IrConstructorCallImpl.fromSymbolOwner(containerClass.defaultType, containerClass.primaryConstructor!!.symbol).apply {
518+
putValueArgument(
519+
0,
520+
IrVarargImpl(
521+
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
522+
pluginContext.irBuiltIns.arrayClass.typeWith(annotationType),
523+
annotationType,
524+
entries
525+
)
526+
)
527+
}
528+
}
529+
471530
private fun extractAnnotations(annotations: List<IrConstructorCall>, parent: Label<out DbExprparent>) {
472-
for ((idx, constructorCall: IrConstructorCall) in annotations.sortedBy { v -> v.type.classFqName?.asString() }.withIndex()) {
531+
val groupedAnnotations = groupRepeatableAnnotations(annotations)
532+
for ((idx, constructorCall: IrConstructorCall) in groupedAnnotations.sortedBy { v -> v.type.classFqName?.asString() }.withIndex()) {
473533
extractAnnotation(constructorCall, parent, idx)
474534
}
475535
}
@@ -699,19 +759,73 @@ open class KotlinFileExtractor(
699759
}
700760

701761
private val javaAnnotationRepeatable by lazy { referenceExternalClass("java.lang.annotation.Repeatable") }
762+
private val kotlinAnnotationRepeatableContainer by lazy { referenceExternalClass("kotlin.jvm.internal.RepeatableContainer") }
763+
764+
// Taken from JvmCachedDeclarations.kt
765+
private fun getOrCreateSyntheticRepeatableAnnotationContainer(annotationClass: IrClass) =
766+
globalExtensionState.syntheticRepeatableAnnotationContainers.getOrPut(annotationClass) {
767+
val containerClass = pluginContext.irFactory.buildClass {
768+
kind = ClassKind.ANNOTATION_CLASS
769+
name = Name.identifier(JvmAbi.REPEATABLE_ANNOTATION_CONTAINER_NAME)
770+
}.apply {
771+
createImplicitParameterDeclarationWithWrappedDescriptor()
772+
parent = annotationClass
773+
superTypes = listOf(pluginContext.irBuiltIns.annotationType)
774+
}
775+
776+
val propertyName = Name.identifier("value")
777+
val propertyType = pluginContext.irBuiltIns.arrayClass.typeWith(annotationClass.typeWith())
778+
779+
containerClass.addConstructor {
780+
isPrimary = true
781+
}.apply {
782+
addValueParameter(propertyName, propertyType)
783+
}
784+
785+
containerClass.addProperty {
786+
name = propertyName
787+
}.apply property@{
788+
backingField = pluginContext.irFactory.buildField {
789+
name = propertyName
790+
type = propertyType
791+
}.apply {
792+
parent = containerClass
793+
correspondingPropertySymbol = this@property.symbol
794+
}
795+
addDefaultGetter(containerClass, pluginContext.irBuiltIns)
796+
}
797+
798+
val repeatableContainerAnnotation = kotlinAnnotationRepeatableContainer?.let { it.constructors.single() }
799+
800+
containerClass.annotations = annotationClass.annotations
801+
.filter {
802+
it.isAnnotationWithEqualFqName(StandardNames.FqNames.retention) ||
803+
it.isAnnotationWithEqualFqName(StandardNames.FqNames.target)
804+
}
805+
.map { it.deepCopyWithSymbols(containerClass) } +
806+
listOfNotNull(
807+
repeatableContainerAnnotation?.let {
808+
IrConstructorCallImpl.fromSymbolOwner(
809+
UNDEFINED_OFFSET, UNDEFINED_OFFSET, it.returnType, it.symbol, 0
810+
)
811+
}
812+
)
702813

703-
// Taken from AdditionalClassAnnotationLowering.kt
814+
containerClass
815+
}
816+
817+
// Adapted from AdditionalClassAnnotationLowering.kt
704818
private fun generateRepeatableAnnotation(irClass: IrClass): IrConstructorCall? {
705819
if (!irClass.hasAnnotation(StandardNames.FqNames.repeatable) ||
706820
irClass.hasAnnotation(JvmAnnotationNames.REPEATABLE_ANNOTATION)
707821
) return null
708822

709823
val repeatableConstructor = javaAnnotationRepeatable?.declarations?.firstIsInstanceOrNull<IrConstructor>() ?: return null
710824

711-
val containerClass =
712-
irClass.declarations.singleOrNull {
713-
it is IrClass && it.name.asString() == JvmAbi.REPEATABLE_ANNOTATION_CONTAINER_NAME
714-
} as IrClass? ?: return null
825+
val containerClass = getOrCreateSyntheticRepeatableAnnotationContainer(irClass)
826+
// Whenever a repeatable annotation with a Kotlin-synthesised container is extracted, extract the synthetic container to the same trap file.
827+
extractClassSource(containerClass, extractDeclarations = true, extractStaticInitializer = true, extractPrivateMembers = true, extractFunctionBodies = true)
828+
715829
val containerReference = IrClassReferenceImpl(
716830
UNDEFINED_OFFSET, UNDEFINED_OFFSET, pluginContext.irBuiltIns.kClassClass.typeWith(containerClass.defaultType),
717831
containerClass.symbol, containerClass.defaultType

0 commit comments

Comments
 (0)