Merge pull request #10853 from smowton/smowton/fix/specialised-anon-classes

Kotlin: extract called private methods of specialised types, and specialised instances of anonymous types
This commit is contained in:
Chris Smowton
2022-10-19 16:48:28 +01:00
committed by GitHub
12 changed files with 193 additions and 155 deletions

View File

@@ -120,11 +120,7 @@ open class KotlinFileExtractor(
}
private fun shouldExtractDecl(declaration: IrDeclaration, extractPrivateMembers: Boolean) =
extractPrivateMembers ||
when(declaration) {
is IrDeclarationWithVisibility -> declaration.visibility.let { it != DescriptorVisibilities.PRIVATE && it != DescriptorVisibilities.PRIVATE_TO_THIS }
else -> true
}
extractPrivateMembers || !isPrivate(declaration)
fun extractDeclaration(declaration: IrDeclaration, extractPrivateMembers: Boolean, extractFunctionBodies: Boolean) {
with("declaration", declaration) {
@@ -367,23 +363,37 @@ open class KotlinFileExtractor(
}
}
private fun makeTypeParamSubstitution(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?) =
when (argsIncludingOuterClasses) {
null -> { x: IrType, _: TypeContext, _: IrPluginContext -> x.toRawType() }
else -> makeGenericSubstitutionFunction(c, argsIncludingOuterClasses)
}
fun extractDeclarationPrototype(d: IrDeclaration, parentId: Label<out DbReftype>, argsIncludingOuterClasses: List<IrTypeArgument>?, typeParamSubstitutionQ: TypeSubstitution? = null) {
val typeParamSubstitution = typeParamSubstitutionQ ?:
when(val parent = d.parent) {
is IrClass -> makeTypeParamSubstitution(parent, argsIncludingOuterClasses)
else -> {
logger.warnElement("Unable to extract prototype of local declaration", d)
return
}
}
when (d) {
is IrFunction -> extractFunction(d, parentId, extractBody = false, extractMethodAndParameterTypeAccesses = false, typeParamSubstitution, argsIncludingOuterClasses)
is IrProperty -> extractProperty(d, parentId, extractBackingField = false, extractFunctionBodies = false, extractPrivateMembers = false, typeParamSubstitution, argsIncludingOuterClasses)
else -> {}
}
}
// `argsIncludingOuterClasses` can be null to describe a raw generic type.
// For non-generic types it will be zero-length list.
private fun extractNonPrivateMemberPrototypes(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?, id: Label<out DbClassorinterface>) {
with("member prototypes", c) {
val typeParamSubstitution =
when (argsIncludingOuterClasses) {
null -> { x: IrType, _: TypeContext, _: IrPluginContext -> x.toRawType() }
else -> makeGenericSubstitutionFunction(c, argsIncludingOuterClasses)
}
val typeParamSubstitution = makeTypeParamSubstitution(c, argsIncludingOuterClasses)
c.declarations.map {
if (shouldExtractDecl(it, false)) {
when(it) {
is IrFunction -> extractFunction(it, id, extractBody = false, extractMethodAndParameterTypeAccesses = false, typeParamSubstitution, argsIncludingOuterClasses)
is IrProperty -> extractProperty(it, id, extractBackingField = false, extractFunctionBodies = false, extractPrivateMembers = false, typeParamSubstitution, argsIncludingOuterClasses)
else -> {}
}
extractDeclarationPrototype(it, id, argsIncludingOuterClasses, typeParamSubstitution)
}
}
}
@@ -583,12 +593,7 @@ open class KotlinFileExtractor(
var parent: IrDeclarationParent? = declarationParent
while (parent != null) {
if (parent is IrClass) {
val parentId =
if (parent.isAnonymousObject) {
useAnonymousClass(parent).javaResult.id.cast<DbClass>()
} else {
useClassInstance(parent, parentClassTypeArguments).typeResult.id
}
val parentId = useClassInstance(parent, parentClassTypeArguments).typeResult.id
tw.writeEnclInReftype(innerId, parentId)
if (innerClass != null && innerClass.isCompanion) {
// If we are a companion then our parent has a
@@ -868,7 +873,7 @@ open class KotlinFileExtractor(
extractTypeAccess(useType(paramType), locId, paramId, -1)
}
}
val paramsSignature = allParamTypeResults.joinToString(separator = ",", prefix = "(", postfix = ")") { it.javaResult.signature }
val paramsSignature = allParamTypeResults.joinToString(separator = ",", prefix = "(", postfix = ")") { signatureOrWarn(it.javaResult, f) }
val shortName = getDefaultsMethodName(f)
if (f.symbol is IrConstructorSymbol) {
@@ -1078,6 +1083,14 @@ open class KotlinFileExtractor(
}
}
private fun signatureOrWarn(t: TypeResult<*>, associatedElement: IrElement?) =
t.signature ?: "<signature unavailable>".also {
if (associatedElement != null)
logger.warnElement("Needed a signature for a type that doesn't have one", associatedElement)
else
logger.warn("Needed a signature for a type that doesn't have one")
}
private fun forceExtractFunction(f: IrFunction, parentId: Label<out DbReftype>, extractBody: Boolean, extractMethodAndParameterTypeAccesses: Boolean, typeSubstitution: TypeSubstitution?, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?, extractOrigin: Boolean = true, overriddenAttributes: OverriddenFunctionAttributes? = null): Label<out DbCallable> {
with("function", f) {
DeclarationStackAdjuster(f, overriddenAttributes).use {
@@ -1114,7 +1127,7 @@ open class KotlinFileExtractor(
paramTypes
}
val paramsSignature = allParamTypes.joinToString(separator = ",", prefix = "(", postfix = ")") { it.javaResult.signature }
val paramsSignature = allParamTypes.joinToString(separator = ",", prefix = "(", postfix = ")") { signatureOrWarn(it.javaResult, f) }
val adjustedReturnType = addJavaLoweringWildcards(getAdjustedReturnType(f), false, (javaCallable as? JavaMethod)?.returnType)
val substReturnType = typeSubstitution?.let { it(adjustedReturnType, TypeContext.RETURN, pluginContext) } ?: adjustedReturnType
@@ -2952,20 +2965,8 @@ open class KotlinFileExtractor(
logger.errorElement("Constructor call has non-simple type ${eType.javaClass}", e)
return
}
val type = useType(eType)
val isAnonymous = eType.isAnonymous
val type: TypeResults = if (isAnonymous) {
if (e.typeArgumentsCount > 0) {
logger.warnElement("Unexpected type arguments (${e.typeArgumentsCount}) for anonymous class constructor call", e)
}
val c = eType.classifier.owner
if (c !is IrClass) {
logger.errorElement("Anonymous constructor call type not a class (${c.javaClass})", e)
return
}
useAnonymousClass(c)
} else {
useType(eType)
}
val locId = tw.getLocation(e)
val valueArgs = (0 until e.valueArgumentsCount).map { e.getValueArgument(it) }
// For now, don't try to use default methods for enum constructor calls,
@@ -2982,7 +2983,7 @@ open class KotlinFileExtractor(
}
if (isAnonymous) {
tw.writeIsAnonymClass(type.javaResult.id.cast<DbClass>(), id)
tw.writeIsAnonymClass(type.javaResult.id.cast(), id)
}
val dr = e.dispatchReceiver
@@ -4640,7 +4641,7 @@ open class KotlinFileExtractor(
Pair(paramId, paramType)
}
val paramsSignature = parameters.joinToString(separator = ",", prefix = "(", postfix = ")") { it.second.javaResult.signature }
val paramsSignature = parameters.joinToString(separator = ",", prefix = "(", postfix = ")") { signatureOrWarn(it.second.javaResult, declarationStack.tryPeek()?.first) }
val rt = useType(returnType, TypeContext.RETURN)
tw.writeMethods(methodId, name, "$name$paramsSignature", rt.javaResult.id, parentId, methodId)
@@ -5359,6 +5360,8 @@ open class KotlinFileExtractor(
fun peek() = stack.peek()
fun tryPeek() = if (stack.isEmpty()) null else stack.peek()
fun findOverriddenAttributes(f: IrFunction) =
stack.lastOrNull { it.first == f } ?.second

View File

@@ -210,10 +210,6 @@ open class KotlinUsesExtractor(
// `typeArgs` can be null to describe a raw generic type.
// For non-generic types it will be zero-length list.
fun useClassInstance(c: IrClass, typeArgs: List<IrTypeArgument>?, inReceiverContext: Boolean = false): UseClassInstanceResult {
if (c.isAnonymousObject) {
logger.error("Unexpected access to anonymous class instance")
}
val substituteClass = getJavaEquivalentClass(c)
val extractClass = substituteClass ?: c
@@ -418,10 +414,11 @@ open class KotlinUsesExtractor(
}
val fqName = replacedClass.fqNameWhenAvailable
val signature = if (fqName == null) {
val signature = if (replacedClass.isAnonymousObject) {
null
} else if (fqName == null) {
logger.error("Unable to find signature/fqName for ${replacedClass.name}")
// TODO: Should we return null here instead?
"<no signature available>"
null
} else {
fqName.asString()
}
@@ -465,7 +462,7 @@ open class KotlinUsesExtractor(
}
}
fun useAnonymousClass(c: IrClass) =
private fun useAnonymousClass(c: IrClass) =
tw.lm.anonymousTypeMapping.getOrPut(c) {
TypeResults(
TypeResult(tw.getFreshIdLabel<DbClass>(), "", ""),
@@ -473,14 +470,6 @@ open class KotlinUsesExtractor(
)
}
fun getExistingAnonymousClassLabel(c: IrClass): Label<out DbType>? {
if (!c.isAnonymousObject){
return null
}
return tw.lm.anonymousTypeMapping[c]?.javaResult?.id
}
fun fakeKotlinType(): Label<out DbKt_type> {
val fakeKotlinPackageId: Label<DbPackage> = tw.getLabelFor("@\"FakeKotlinPackage\"", {
tw.writePackages(it, "fake.kotlin")
@@ -497,16 +486,6 @@ open class KotlinUsesExtractor(
// `args` can be null to describe a raw generic type.
// For non-generic types it will be zero-length list.
fun useSimpleTypeClass(c: IrClass, args: List<IrTypeArgument>?, hasQuestionMark: Boolean): TypeResults {
if (c.isAnonymousObject) {
args?.let {
if (it.isNotEmpty() && !isUnspecialised(c, it, logger)) {
logger.error("Unexpected specialised instance of generic anonymous class")
}
}
return useAnonymousClass(c)
}
val classInstanceResult = useClassInstance(c, args)
val javaClassId = classInstanceResult.typeResult.id
val kotlinQualClassName = getUnquotedClassLabel(c, args).classLabel
@@ -795,7 +774,7 @@ open class KotlinUsesExtractor(
extractFileClass(dp)
}
is IrClass ->
if (classTypeArguments != null && !dp.isAnonymousObject) {
if (classTypeArguments != null) {
useClassInstance(dp, classTypeArguments, inReceiverContext).typeResult.id
} else {
val replacedType = tryReplaceParcelizeRawType(dp)
@@ -1319,6 +1298,12 @@ open class KotlinUsesExtractor(
}
} ?: f
fun isPrivate(d: IrDeclaration) =
when(d) {
is IrDeclarationWithVisibility -> d.visibility.let { it == DescriptorVisibilities.PRIVATE || it == DescriptorVisibilities.PRIVATE_TO_THIS }
else -> false
}
fun <T: DbCallable> useFunction(f: IrFunction, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>? = null, noReplace: Boolean = false): Label<out T> {
return useFunction(f, null, classTypeArgsIncludingOuterClasses, noReplace)
}
@@ -1330,7 +1315,9 @@ open class KotlinUsesExtractor(
}
val javaFun = kotlinFunctionToJavaEquivalent(f, noReplace)
val label = getFunctionLabel(javaFun, parentId, classTypeArgsIncludingOuterClasses)
val id: Label<T> = tw.getLabelFor(label)
val id: Label<T> = tw.getLabelFor(label) {
extractPrivateSpecialisedDeclaration(f, classTypeArgsIncludingOuterClasses)
}
if (isExternalDeclaration(javaFun)) {
extractFunctionLaterIfExternalFileMember(javaFun)
extractExternalEnclosingClassLater(javaFun)
@@ -1338,6 +1325,19 @@ open class KotlinUsesExtractor(
return id
}
private fun extractPrivateSpecialisedDeclaration(d: IrDeclaration, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?) {
// Note here `classTypeArgsIncludingOuterClasses` being null doesn't signify a raw receiver type but rather that no type args were supplied.
// This is because a call to a private method can only be observed inside Kotlin code, and Kotlin can't represent raw types.
if (this is KotlinFileExtractor && isPrivate(d) && classTypeArgsIncludingOuterClasses != null && classTypeArgsIncludingOuterClasses.isNotEmpty()) {
d.parent.let {
when(it) {
is IrClass -> this.extractDeclarationPrototype(d, useClassInstance(it, classTypeArgsIncludingOuterClasses).typeResult.id, classTypeArgsIncludingOuterClasses)
else -> logger.warnElement("Unable to extract specialised declaration that isn't a member of a class", d)
}
}
}
}
fun getTypeArgumentLabel(
arg: IrTypeArgument
): TypeResultWithoutSignature<DbReftype> {
@@ -1393,20 +1393,24 @@ open class KotlinUsesExtractor(
private fun getUnquotedClassLabel(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?): ClassLabelResults {
val pkg = c.packageFqName?.asString() ?: ""
val cls = c.name.asString()
val label = when (val parent = c.parent) {
is IrClass -> {
"${getUnquotedClassLabel(parent, listOf()).classLabel}\$$cls"
}
is IrFunction -> {
"{${useFunction<DbMethod>(parent)}}.$cls"
}
is IrField -> {
"{${useField(parent)}}.$cls"
}
else -> {
if (pkg.isEmpty()) cls else "$pkg.$cls"
}
}
val label =
if (c.isAnonymousObject)
"{${useAnonymousClass(c).javaResult.id}}"
else
when (val parent = c.parent) {
is IrClass -> {
"${getUnquotedClassLabel(parent, listOf()).classLabel}\$$cls"
}
is IrFunction -> {
"{${useFunction<DbMethod>(parent)}}.$cls"
}
is IrField -> {
"{${useField(parent)}}.$cls"
}
else -> {
if (pkg.isEmpty()) cls else "$pkg.$cls"
}
}
val reorderedArgs = orderTypeArgsLeftToRight(c, argsIncludingOuterClasses)
val typeArgLabels = reorderedArgs?.map { getTypeArgumentLabel(it) }
@@ -1417,20 +1421,17 @@ open class KotlinUsesExtractor(
""
else
typeArgLabels.takeLast(c.typeParameters.size).joinToString(prefix = "<", postfix = ">", separator = ",") { it.shortName }
val shortNamePrefix = if (c.isAnonymousObject) "" else cls
return ClassLabelResults(
label + (typeArgLabels?.joinToString(separator = "") { ";{${it.id}}" } ?: "<>"),
cls + typeArgsShortName
shortNamePrefix + typeArgsShortName
)
}
// `args` can be null to describe a raw generic type.
// For non-generic types it will be zero-length list.
fun getClassLabel(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?): ClassLabelResults {
if (c.isAnonymousObject) {
logger.error("Label generation should not be requested for an anonymous class")
}
val unquotedLabel = getUnquotedClassLabel(c, argsIncludingOuterClasses)
return ClassLabelResults(
"@\"class;${unquotedLabel.classLabel}\"",
@@ -1438,10 +1439,6 @@ open class KotlinUsesExtractor(
}
fun useClassSource(c: IrClass): Label<out DbClassorinterface> {
if (c.isAnonymousObject) {
return useAnonymousClass(c).javaResult.id.cast<DbClass>()
}
// For source classes, the label doesn't include any type arguments
val classTypeResult = addClassLabel(c, listOf())
return classTypeResult.id
@@ -1686,8 +1683,11 @@ open class KotlinUsesExtractor(
}
}
fun useProperty(p: IrProperty, parentId: Label<out DbElement>, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?): Label<out DbKt_property> =
tw.getLabelFor<DbKt_property>(getPropertyLabel(p, parentId, classTypeArgsIncludingOuterClasses)).also { extractPropertyLaterIfExternalFileMember(p) }
fun useProperty(p: IrProperty, parentId: Label<out DbElement>, classTypeArgsIncludingOuterClasses: List<IrTypeArgument>?) =
tw.getLabelFor<DbKt_property>(getPropertyLabel(p, parentId, classTypeArgsIncludingOuterClasses)) {
extractPropertyLaterIfExternalFileMember(p)
extractPrivateSpecialisedDeclaration(p, classTypeArgsIncludingOuterClasses)
}
fun getEnumEntryLabel(ee: IrEnumEntry): String {
val parentId = useDeclarationParent(ee.parent, false)

View File

@@ -127,12 +127,7 @@ class CommentExtractor(private val fileExtractor: KotlinFileExtractor, private v
// local functions are not named globally, so we need to get them from the local function label cache
label = "local function ${element.name.asString()}"
fileExtractor.getExistingLocallyVisibleFunctionLabel(element)
} else if (element is IrClass && element.isAnonymousObject) {
// anonymous objects are not named globally, so we need to get them from the cache
label = "anonymous class ${element.name.asString()}"
fileExtractor.getExistingAnonymousClassLabel(element)
}
else {
} else {
label = getLabelForNamedElement(element) ?: return null
tw.getExistingLabelFor<DbTop>(label)
}
@@ -145,12 +140,7 @@ class CommentExtractor(private val fileExtractor: KotlinFileExtractor, private v
private fun getLabelForNamedElement(element: IrElement) : String? {
when (element) {
is IrClass ->
return if (element.isAnonymousObject) {
null
} else {
fileExtractor.getClassLabel(element, listOf()).classLabel
}
is IrClass -> return fileExtractor.getClassLabel(element, listOf()).classLabel
is IrTypeParameter -> return fileExtractor.getTypeParameterLabel(element)
is IrFunction -> {
return if (element.isLocalFunction()) {

View File

@@ -12,7 +12,7 @@ package com.github.codeql
* `shortName` is a Java primitive name (e.g. "int"), a class short name with Java-style type arguments ("InnerClass<E>" or
* "OuterClass<ConcreteArgument>" or "OtherClass<? extends Bound>") or an array ("componentShortName[]").
*/
data class TypeResultGeneric<SignatureType,out LabelType: AnyDbType>(val id: Label<out LabelType>, val signature: SignatureType, val shortName: String) {
data class TypeResultGeneric<SignatureType,out LabelType: AnyDbType>(val id: Label<out LabelType>, val signature: SignatureType?, val shortName: String) {
fun <U: AnyDbType> cast(): TypeResultGeneric<SignatureType,U> {
@Suppress("UNCHECKED_CAST")
return this as TypeResultGeneric<SignatureType,U>