Skip to content

Commit 8553266

Browse files
committed
Allow specialised instances of anonymous classes
1 parent 73f5dea commit 8553266

File tree

7 files changed

+74
-74
lines changed

7 files changed

+74
-74
lines changed

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

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -583,12 +583,7 @@ open class KotlinFileExtractor(
583583
var parent: IrDeclarationParent? = declarationParent
584584
while (parent != null) {
585585
if (parent is IrClass) {
586-
val parentId =
587-
if (parent.isAnonymousObject) {
588-
useAnonymousClass(parent).javaResult.id.cast<DbClass>()
589-
} else {
590-
useClassInstance(parent, parentClassTypeArguments).typeResult.id
591-
}
586+
val parentId = useClassInstance(parent, parentClassTypeArguments).typeResult.id
592587
tw.writeEnclInReftype(innerId, parentId)
593588
if (innerClass != null && innerClass.isCompanion) {
594589
// If we are a companion then our parent has a
@@ -866,7 +861,7 @@ open class KotlinFileExtractor(
866861
extractTypeAccess(useType(paramType), locId, paramId, -1)
867862
}
868863
}
869-
val paramsSignature = allParamTypeResults.joinToString(separator = ",", prefix = "(", postfix = ")") { it.javaResult.signature }
864+
val paramsSignature = allParamTypeResults.joinToString(separator = ",", prefix = "(", postfix = ")") { signatureOrWarn(it.javaResult, f) }
870865
val shortName = getDefaultsMethodName(f)
871866

872867
if (f.symbol is IrConstructorSymbol) {
@@ -1076,6 +1071,9 @@ open class KotlinFileExtractor(
10761071
}
10771072
}
10781073

1074+
private fun signatureOrWarn(t: TypeResult<*>, associatedElement: IrElement) =
1075+
t.signature ?: "<signature unavailable>".also { logger.warnElement("Needed a signature for a type that doesn't have one", associatedElement) }
1076+
10791077
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> {
10801078
with("function", f) {
10811079
DeclarationStackAdjuster(f, overriddenAttributes).use {
@@ -1112,7 +1110,7 @@ open class KotlinFileExtractor(
11121110
paramTypes
11131111
}
11141112

1115-
val paramsSignature = allParamTypes.joinToString(separator = ",", prefix = "(", postfix = ")") { it.javaResult.signature }
1113+
val paramsSignature = allParamTypes.joinToString(separator = ",", prefix = "(", postfix = ")") { signatureOrWarn(it.javaResult, f) }
11161114

11171115
val adjustedReturnType = addJavaLoweringWildcards(getAdjustedReturnType(f), false, (javaCallable as? JavaMethod)?.returnType)
11181116
val substReturnType = typeSubstitution?.let { it(adjustedReturnType, TypeContext.RETURN, pluginContext) } ?: adjustedReturnType
@@ -2933,20 +2931,8 @@ open class KotlinFileExtractor(
29332931
logger.errorElement("Constructor call has non-simple type ${eType.javaClass}", e)
29342932
return
29352933
}
2934+
val type = useType(eType)
29362935
val isAnonymous = eType.isAnonymous
2937-
val type: TypeResults = if (isAnonymous) {
2938-
if (e.typeArgumentsCount > 0) {
2939-
logger.warnElement("Unexpected type arguments (${e.typeArgumentsCount}) for anonymous class constructor call", e)
2940-
}
2941-
val c = eType.classifier.owner
2942-
if (c !is IrClass) {
2943-
logger.errorElement("Anonymous constructor call type not a class (${c.javaClass})", e)
2944-
return
2945-
}
2946-
useAnonymousClass(c)
2947-
} else {
2948-
useType(eType)
2949-
}
29502936
val locId = tw.getLocation(e)
29512937
val valueArgs = (0 until e.valueArgumentsCount).map { e.getValueArgument(it) }
29522938
// For now, don't try to use default methods for enum constructor calls,
@@ -2963,7 +2949,7 @@ open class KotlinFileExtractor(
29632949
}
29642950

29652951
if (isAnonymous) {
2966-
tw.writeIsAnonymClass(type.javaResult.id.cast<DbClass>(), id)
2952+
tw.writeIsAnonymClass(type.javaResult.id.cast(), id)
29672953
}
29682954

29692955
val dr = e.dispatchReceiver
@@ -4614,7 +4600,7 @@ open class KotlinFileExtractor(
46144600
Pair(paramId, paramType)
46154601
}
46164602

4617-
val paramsSignature = parameters.joinToString(separator = ",", prefix = "(", postfix = ")") { it.second.javaResult.signature }
4603+
val paramsSignature = parameters.joinToString(separator = ",", prefix = "(", postfix = ")") { signatureOrWarn(it.second.javaResult, declarationStack.peek().first) }
46184604

46194605
val rt = useType(returnType, TypeContext.RETURN)
46204606
tw.writeMethods(methodId, name, "$name$paramsSignature", rt.javaResult.id, parentId, methodId)

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

Lines changed: 25 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -210,10 +210,6 @@ open class KotlinUsesExtractor(
210210
// `typeArgs` can be null to describe a raw generic type.
211211
// For non-generic types it will be zero-length list.
212212
fun useClassInstance(c: IrClass, typeArgs: List<IrTypeArgument>?, inReceiverContext: Boolean = false): UseClassInstanceResult {
213-
if (c.isAnonymousObject) {
214-
logger.error("Unexpected access to anonymous class instance")
215-
}
216-
217213
val substituteClass = getJavaEquivalentClass(c)
218214

219215
val extractClass = substituteClass ?: c
@@ -418,10 +414,11 @@ open class KotlinUsesExtractor(
418414
}
419415

420416
val fqName = replacedClass.fqNameWhenAvailable
421-
val signature = if (fqName == null) {
417+
val signature = if (replacedClass.isAnonymousObject) {
418+
null
419+
} else if (fqName == null) {
422420
logger.error("Unable to find signature/fqName for ${replacedClass.name}")
423-
// TODO: Should we return null here instead?
424-
"<no signature available>"
421+
null
425422
} else {
426423
fqName.asString()
427424
}
@@ -497,16 +494,6 @@ open class KotlinUsesExtractor(
497494
// `args` can be null to describe a raw generic type.
498495
// For non-generic types it will be zero-length list.
499496
fun useSimpleTypeClass(c: IrClass, args: List<IrTypeArgument>?, hasQuestionMark: Boolean): TypeResults {
500-
if (c.isAnonymousObject) {
501-
args?.let {
502-
if (it.isNotEmpty() && !isUnspecialised(c, it, logger)) {
503-
logger.error("Unexpected specialised instance of generic anonymous class")
504-
}
505-
}
506-
507-
return useAnonymousClass(c)
508-
}
509-
510497
val classInstanceResult = useClassInstance(c, args)
511498
val javaClassId = classInstanceResult.typeResult.id
512499
val kotlinQualClassName = getUnquotedClassLabel(c, args).classLabel
@@ -795,7 +782,7 @@ open class KotlinUsesExtractor(
795782
extractFileClass(dp)
796783
}
797784
is IrClass ->
798-
if (classTypeArguments != null && !dp.isAnonymousObject) {
785+
if (classTypeArguments != null) {
799786
useClassInstance(dp, classTypeArguments, inReceiverContext).typeResult.id
800787
} else {
801788
val replacedType = tryReplaceParcelizeRawType(dp)
@@ -1411,20 +1398,24 @@ open class KotlinUsesExtractor(
14111398
private fun getUnquotedClassLabel(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?): ClassLabelResults {
14121399
val pkg = c.packageFqName?.asString() ?: ""
14131400
val cls = c.name.asString()
1414-
val label = when (val parent = c.parent) {
1415-
is IrClass -> {
1416-
"${getUnquotedClassLabel(parent, listOf()).classLabel}\$$cls"
1417-
}
1418-
is IrFunction -> {
1419-
"{${useFunction<DbMethod>(parent)}}.$cls"
1420-
}
1421-
is IrField -> {
1422-
"{${useField(parent)}}.$cls"
1423-
}
1424-
else -> {
1425-
if (pkg.isEmpty()) cls else "$pkg.$cls"
1426-
}
1427-
}
1401+
val label =
1402+
if (c.isAnonymousObject)
1403+
"{${useAnonymousClass(c).javaResult.id}}"
1404+
else
1405+
when (val parent = c.parent) {
1406+
is IrClass -> {
1407+
"${getUnquotedClassLabel(parent, listOf()).classLabel}\$$cls"
1408+
}
1409+
is IrFunction -> {
1410+
"{${useFunction<DbMethod>(parent)}}.$cls"
1411+
}
1412+
is IrField -> {
1413+
"{${useField(parent)}}.$cls"
1414+
}
1415+
else -> {
1416+
if (pkg.isEmpty()) cls else "$pkg.$cls"
1417+
}
1418+
}
14281419

14291420
val reorderedArgs = orderTypeArgsLeftToRight(c, argsIncludingOuterClasses)
14301421
val typeArgLabels = reorderedArgs?.map { getTypeArgumentLabel(it) }
@@ -1435,31 +1426,24 @@ open class KotlinUsesExtractor(
14351426
""
14361427
else
14371428
typeArgLabels.takeLast(c.typeParameters.size).joinToString(prefix = "<", postfix = ">", separator = ",") { it.shortName }
1429+
val shortNamePrefix = if (c.isAnonymousObject) "" else cls
14381430

14391431
return ClassLabelResults(
14401432
label + (typeArgLabels?.joinToString(separator = "") { ";{${it.id}}" } ?: "<>"),
1441-
cls + typeArgsShortName
1433+
shortNamePrefix + typeArgsShortName
14421434
)
14431435
}
14441436

14451437
// `args` can be null to describe a raw generic type.
14461438
// For non-generic types it will be zero-length list.
14471439
fun getClassLabel(c: IrClass, argsIncludingOuterClasses: List<IrTypeArgument>?): ClassLabelResults {
1448-
if (c.isAnonymousObject) {
1449-
logger.error("Label generation should not be requested for an anonymous class")
1450-
}
1451-
14521440
val unquotedLabel = getUnquotedClassLabel(c, argsIncludingOuterClasses)
14531441
return ClassLabelResults(
14541442
"@\"class;${unquotedLabel.classLabel}\"",
14551443
unquotedLabel.shortName)
14561444
}
14571445

14581446
fun useClassSource(c: IrClass): Label<out DbClassorinterface> {
1459-
if (c.isAnonymousObject) {
1460-
return useAnonymousClass(c).javaResult.id.cast<DbClass>()
1461-
}
1462-
14631447
// For source classes, the label doesn't include any type arguments
14641448
val classTypeResult = addClassLabel(c, listOf())
14651449
return classTypeResult.id

java/kotlin-extractor/src/main/kotlin/utils/TypeResults.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ package com.github.codeql
1212
* `shortName` is a Java primitive name (e.g. "int"), a class short name with Java-style type arguments ("InnerClass<E>" or
1313
* "OuterClass<ConcreteArgument>" or "OtherClass<? extends Bound>") or an array ("componentShortName[]").
1414
*/
15-
data class TypeResultGeneric<SignatureType,out LabelType: AnyDbType>(val id: Label<out LabelType>, val signature: SignatureType, val shortName: String) {
15+
data class TypeResultGeneric<SignatureType,out LabelType: AnyDbType>(val id: Label<out LabelType>, val signature: SignatureType?, val shortName: String) {
1616
fun <U: AnyDbType> cast(): TypeResultGeneric<SignatureType,U> {
1717
@Suppress("UNCHECKED_CAST")
1818
return this as TypeResultGeneric<SignatureType,U>

java/ql/lib/semmle/code/java/Type.qll

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,7 @@ class SrcRefType extends RefType {
686686
/** A class declaration. */
687687
class Class extends ClassOrInterface, @class {
688688
/** Holds if this class is an anonymous class. */
689-
predicate isAnonymous() { isAnonymClass(this, _) }
689+
predicate isAnonymous() { isAnonymClass(this.getSourceDeclaration(), _) }
690690

691691
override RefType getSourceDeclaration() { classes(this, _, _, result) }
692692

@@ -800,10 +800,13 @@ class AnonymousClass extends NestedClass {
800800
}
801801

802802
/** Gets the class instance expression where this anonymous class occurs. */
803-
ClassInstanceExpr getClassInstanceExpr() { isAnonymClass(this, result) }
803+
ClassInstanceExpr getClassInstanceExpr() { isAnonymClass(this.getSourceDeclaration(), result) }
804804

805805
override string toString() {
806-
result = "new " + this.getClassInstanceExpr().getTypeName() + "(...) { ... }"
806+
// Include super.toString, i.e. the name given in the database, because for Kotlin anonymous
807+
// classes we can get specialisations of anonymous generic types, and this will supply the
808+
// trailing type arguments.
809+
result = "new " + this.getClassInstanceExpr().getTypeName() + "(...) { ... }" + super.toString()
807810
}
808811

809812
/**

java/ql/test/kotlin/library-tests/private-anonymous-types/test.expected

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,33 @@
1+
enclosingTypes
2+
| file:///!unknown-binary-location/A$<no name provided>.class:0:0:0:0 | new If<T>(...) { ... }<> | file:///!unknown-binary-location/A.class:0:0:0:0 | A<CharSequence> |
3+
| file:///!unknown-binary-location/A$<no name provided>.class:0:0:0:0 | new If<T>(...) { ... }<> | file:///!unknown-binary-location/A.class:0:0:0:0 | A<String> |
4+
| test.kt:9:18:11:3 | new If<T>(...) { ... } | test.kt:7:1:22:1 | A |
5+
| test.kt:13:33:15:3 | new If<T>(...) { ... } | test.kt:7:1:22:1 | A |
6+
| test.kt:13:33:15:3 | new If<T>(...) { ... }<> | test.kt:7:1:22:1 | A<> |
7+
#select
8+
| file:///!unknown-binary-location/A$<no name provided>.class:0:0:0:0 | new If<T>(...) { ... }<> | file:///!unknown-binary-location/A$<no name provided>.class:0:0:0:0 | |
9+
| file:///!unknown-binary-location/A$<no name provided>.class:0:0:0:0 | new If<T>(...) { ... }<> | file:///!unknown-binary-location/A$<no name provided>.class:0:0:0:0 | |
10+
| file:///!unknown-binary-location/A$<no name provided>.class:0:0:0:0 | new If<T>(...) { ... }<> | file:///!unknown-binary-location/A$<no name provided>.class:0:0:0:0 | getX |
11+
| file:///!unknown-binary-location/A$<no name provided>.class:0:0:0:0 | new If<T>(...) { ... }<> | file:///!unknown-binary-location/A$<no name provided>.class:0:0:0:0 | getX |
12+
| file:///!unknown-binary-location/A.class:0:0:0:0 | A<CharSequence> | file:///!unknown-binary-location/A.class:0:0:0:0 | A<CharSequence> |
13+
| file:///!unknown-binary-location/A.class:0:0:0:0 | A<CharSequence> | file:///!unknown-binary-location/A.class:0:0:0:0 | getAnonType |
14+
| file:///!unknown-binary-location/A.class:0:0:0:0 | A<CharSequence> | file:///!unknown-binary-location/A.class:0:0:0:0 | getPrivateAnonType$private |
15+
| file:///!unknown-binary-location/A.class:0:0:0:0 | A<CharSequence> | file:///!unknown-binary-location/A.class:0:0:0:0 | privateUser |
116
| file:///!unknown-binary-location/A.class:0:0:0:0 | A<String> | file:///!unknown-binary-location/A.class:0:0:0:0 | A<String> |
217
| file:///!unknown-binary-location/A.class:0:0:0:0 | A<String> | file:///!unknown-binary-location/A.class:0:0:0:0 | getAnonType |
18+
| file:///!unknown-binary-location/A.class:0:0:0:0 | A<String> | file:///!unknown-binary-location/A.class:0:0:0:0 | getPrivateAnonType$private |
19+
| file:///!unknown-binary-location/A.class:0:0:0:0 | A<String> | file:///!unknown-binary-location/A.class:0:0:0:0 | privateUser |
320
| file:///!unknown-binary-location/If.class:0:0:0:0 | If<String> | file:///!unknown-binary-location/If.class:0:0:0:0 | getX |
421
| file:///!unknown-binary-location/If.class:0:0:0:0 | If<T> | file:///!unknown-binary-location/If.class:0:0:0:0 | getX |
522
| other.kt:1:1:1:34 | Ext | other.kt:1:1:1:34 | Ext |
6-
| test.kt:0:0:0:0 | TestKt | test.kt:19:1:19:38 | user |
23+
| test.kt:0:0:0:0 | TestKt | test.kt:24:1:24:38 | user |
724
| test.kt:1:1:5:1 | If | test.kt:3:3:3:11 | getX |
8-
| test.kt:7:1:17:1 | A | test.kt:7:6:17:1 | A |
9-
| test.kt:7:1:17:1 | A | test.kt:9:3:11:3 | anonType |
10-
| test.kt:7:1:17:1 | A | test.kt:9:3:11:3 | getAnonType |
11-
| test.kt:7:1:17:1 | A | test.kt:13:3:15:3 | privateAnonType |
12-
| test.kt:7:1:17:1 | A | test.kt:13:11:15:3 | getPrivateAnonType$private |
25+
| test.kt:7:1:22:1 | A | test.kt:7:6:22:1 | A |
26+
| test.kt:7:1:22:1 | A | test.kt:9:3:11:3 | anonType |
27+
| test.kt:7:1:22:1 | A | test.kt:9:3:11:3 | getAnonType |
28+
| test.kt:7:1:22:1 | A | test.kt:13:3:15:3 | privateAnonType |
29+
| test.kt:7:1:22:1 | A | test.kt:13:11:15:3 | getPrivateAnonType$private |
30+
| test.kt:7:1:22:1 | A | test.kt:17:3:20:3 | privateUser |
1331
| test.kt:9:18:11:3 | new If<T>(...) { ... } | test.kt:9:18:11:3 | |
1432
| test.kt:9:18:11:3 | new If<T>(...) { ... } | test.kt:10:5:10:22 | x |
1533
| test.kt:9:18:11:3 | new If<T>(...) { ... } | test.kt:10:14:10:22 | getX |

java/ql/test/kotlin/library-tests/private-anonymous-types/test.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ open class A<T>(t: T) {
1414
override val x = t
1515
}
1616

17+
fun privateUser(x: A<String>, y: A<CharSequence>) {
18+
val a = x.privateAnonType.x
19+
val b = y.privateAnonType.x
20+
}
21+
1722
}
1823

1924
fun user(x: A<String>) = x.anonType.x

java/ql/test/kotlin/library-tests/private-anonymous-types/test.ql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ import java
33
from ClassOrInterface ci, Member m
44
where m = ci.getAMember() and ci.getSourceDeclaration().fromSource()
55
select ci, m
6+
7+
query predicate enclosingTypes(NestedType nt, Type encl) {
8+
nt.getSourceDeclaration().fromSource() and encl = nt.getEnclosingType()
9+
}

0 commit comments

Comments
 (0)