diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index dd9fd6c0c04..f2868e258bc 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -628,14 +628,16 @@ open class KotlinFileExtractor( private fun extractValueParameter(vp: IrValueParameter, parent: Label, idx: Int, typeSubstitution: TypeSubstitution?, parentSourceDeclaration: Label, classTypeArgsIncludingOuterClasses: List?, extractTypeAccess: Boolean, locOverride: Label? = null): TypeResults { with("value parameter", vp) { val location = locOverride ?: getLocation(vp, classTypeArgsIncludingOuterClasses) - val maybeErasedType = (vp.parent as? IrFunction)?.let { + val maybeAlteredType = (vp.parent as? IrFunction)?.let { if (overridesCollectionsMethodWithAlteredParameterTypes(it)) eraseCollectionsMethodParameterType(vp.type, it.name.asString(), idx) + else if ((vp.parent as? IrConstructor)?.parentClassOrNull?.kind == ClassKind.ANNOTATION_CLASS) + kClassToJavaClass(vp.type) else null } ?: vp.type val javaType = (vp.parent as? IrFunction)?.let { getJavaCallable(it)?.let { jCallable -> getJavaValueParameterType(jCallable, idx) } } - val typeWithWildcards = addJavaLoweringWildcards(maybeErasedType, !hasWildcardSuppressionAnnotation(vp), javaType) + val typeWithWildcards = addJavaLoweringWildcards(maybeAlteredType, !hasWildcardSuppressionAnnotation(vp), javaType) val substitutedType = typeSubstitution?.let { it(typeWithWildcards, TypeContext.OTHER, pluginContext) } ?: typeWithWildcards val id = useValueParameter(vp, parent) if (extractTypeAccess) { @@ -734,14 +736,17 @@ open class KotlinFileExtractor( val initializer: IrExpressionBody? val lhsType: TypeResults? val vId: Label? + val isAnnotationClassField: Boolean if (f is IrField) { static = f.isStatic initializer = f.initializer - lhsType = useType(f.type) + isAnnotationClassField = isAnnotationClassField(f) + lhsType = useType(if (isAnnotationClassField) kClassToJavaClass(f.type) else f.type) vId = useField(f) } else if (f is IrEnumEntry) { static = true initializer = f.initializerExpression + isAnnotationClassField = false lhsType = getEnumEntryType(f) if (lhsType == null) { return @@ -762,7 +767,7 @@ open class KotlinFileExtractor( tw.writeStmts_exprstmt(stmtId, blockAndFunctionId.first, idx++, blockAndFunctionId.second) tw.writeHasLocation(stmtId, declLocId) val assignmentId = tw.getFreshIdLabel() - val type = useType(expr.type) + val type = useType(if (isAnnotationClassField) kClassToJavaClass(expr.type) else expr.type) tw.writeExprs_assignexpr(assignmentId, type.javaResult.id, stmtId, 0) tw.writeExprsKotlinType(assignmentId, type.kotlinResult.id) tw.writeHasLocation(assignmentId, declLocId) @@ -936,7 +941,8 @@ open class KotlinFileExtractor( with("field", f) { DeclarationStackAdjuster(f).use { val fNameSuffix = getExtensionReceiverType(f)?.let { it.classFqName?.asString()?.replace(".", "$$") } ?: "" - return extractField(useField(f), "${f.name.asString()}$fNameSuffix", f.type, parentId, tw.getLocation(f), f.visibility, f, isExternalDeclaration(f), f.isFinal) + val extractType = if (isAnnotationClassField(f)) kClassToJavaClass(f.type) else f.type + return extractField(useField(f), "${f.name.asString()}$fNameSuffix", extractType, parentId, tw.getLocation(f), f.visibility, f, isExternalDeclaration(f), f.isFinal) } } } @@ -2917,14 +2923,17 @@ open class KotlinFileExtractor( if (owner is IrValueParameter && owner.index == -1 && !owner.isExtensionReceiver()) { extractThisAccess(e, exprParent, callable) } else { - extractVariableAccess(useValueDeclaration(owner), e.type, tw.getLocation(e), exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt) + val isAnnotationClassParameter = ((owner as? IrValueParameter)?.parent as? IrConstructor)?.parentClassOrNull?.kind == ClassKind.ANNOTATION_CLASS + val extractType = if (isAnnotationClassParameter) kClassToJavaClass(e.type) else e.type + extractVariableAccess(useValueDeclaration(owner), extractType, tw.getLocation(e), exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt) } } is IrGetField -> { val exprParent = parent.expr(e, callable) val owner = tryReplaceAndroidSyntheticField(e.symbol.owner) val locId = tw.getLocation(e) - extractVariableAccess(useField(owner), e.type, locId, exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt).also { id -> + val fieldType = if (isAnnotationClassField(owner)) kClassToJavaClass(e.type) else e.type + extractVariableAccess(useField(owner), fieldType, locId, exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt).also { id -> val receiver = e.receiver if (receiver != null) { extractExpressionExpr(receiver, callable, id, -1, exprParent.enclosingStmt) diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt index 8a3dd214837..15695dd8b00 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinUsesExtractor.kt @@ -1103,7 +1103,56 @@ open class KotlinUsesExtractor( return "@\"$prefix;{$parentId}.$name($paramTypeIds){$returnTypeId}${typeArgSuffix}\"" } + val javaLangClass by lazy { + val result = pluginContext.referenceClass(FqName("java.lang.Class"))?.owner + result?.let { extractExternalClassLater(it) } + result + } + + fun kClassToJavaClass(t: IrType): IrType { + when(t) { + is IrSimpleType -> { + if (t.classifier == pluginContext.irBuiltIns.kClassClass) { + javaLangClass?.let { jlc -> + return jlc.symbol.typeWithArguments(t.arguments) + } + } else { + t.classOrNull?.let { tCls -> + if (t.isArray() || t.isNullableArray()) { + (t.arguments.singleOrNull() as? IrTypeProjection)?.let { elementTypeArg -> + val elementType = elementTypeArg.type + val replacedElementType = kClassToJavaClass(elementType) + if (replacedElementType !== elementType) { + val newArg = makeTypeProjection(replacedElementType, elementTypeArg.variance) + return tCls.typeWithArguments(listOf(newArg)).codeQlWithHasQuestionMark(t.isNullableArray()) + } + } + } + } + } + } + } + return t + } + + fun isAnnotationClassField(f: IrField) = + f.correspondingPropertySymbol?.let { + isAnnotationClassProperty(it) + } ?: false + + private fun isAnnotationClassProperty(p: IrPropertySymbol) = + p.owner.parentClassOrNull?.kind == ClassKind.ANNOTATION_CLASS + fun getAdjustedReturnType(f: IrFunction) : IrType { + // Replace annotation val accessor types as needed: + (f as? IrSimpleFunction)?.correspondingPropertySymbol?.let { + if (isAnnotationClassProperty(it) && f == it.owner.getter) { + val replaced = kClassToJavaClass(f.returnType) + if (replaced != f.returnType) + return replaced + } + } + // The return type of `java.util.concurrent.ConcurrentHashMap.keySet/0` is defined as `Set` in the stubs inside the Android SDK. // This does not match the Java SDK return type: `ConcurrentHashMap.KeySetView`, so it's adjusted here. // This is a deliberate change in the Android SDK: https://github.com/AndroidSDKSources/android-sdk-sources-for-api-level-31/blob/2c56b25f619575bea12f9c5520ed2259620084ac/java/util/concurrent/ConcurrentHashMap.java#L1244-L1249 diff --git a/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/PrintAst.expected b/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/PrintAst.expected new file mode 100644 index 00000000000..816b559db7f --- /dev/null +++ b/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/PrintAst.expected @@ -0,0 +1,82 @@ +test.kt: +# 0| [CompilationUnit] test +# 3| 1: [Interface] A +# 3| 1: [Constructor] A +#-----| 4: (Parameters) +# 3| 0: [Parameter] c1 +# 3| 0: [TypeAccess] Class +# 3| 0: [WildcardTypeAccess] ? ... +# 3| 1: [Parameter] c2 +# 3| 0: [TypeAccess] Class +# 3| 0: [WildcardTypeAccess] ? ... +# 3| 0: [TypeAccess] CharSequence +# 3| 2: [Parameter] c3 +# 3| 0: [TypeAccess] Class +# 3| 0: [TypeAccess] String +# 3| 3: [Parameter] c4 +# 3| 0: [TypeAccess] Class[] +# 3| 0: [TypeAccess] Class +# 3| 0: [WildcardTypeAccess] ? ... +# 3| 5: [BlockStmt] { ... } +# 3| 0: [SuperConstructorInvocationStmt] super(...) +# 3| 1: [BlockStmt] { ... } +# 3| 0: [ExprStmt] ; +# 3| 0: [KtInitializerAssignExpr] ...=... +# 3| 0: [VarAccess] c1 +# 3| 1: [ExprStmt] ; +# 3| 0: [KtInitializerAssignExpr] ...=... +# 3| 0: [VarAccess] c2 +# 3| 2: [ExprStmt] ; +# 3| 0: [KtInitializerAssignExpr] ...=... +# 3| 0: [VarAccess] c3 +# 3| 3: [ExprStmt] ; +# 3| 0: [KtInitializerAssignExpr] ...=... +# 3| 0: [VarAccess] c4 +# 3| 2: [FieldDeclaration] Class c1; +# 3| -1: [TypeAccess] Class +# 3| 0: [WildcardTypeAccess] ? ... +# 3| 0: [VarAccess] c1 +# 3| 3: [Method] c1 +# 3| 3: [TypeAccess] Class +# 3| 0: [WildcardTypeAccess] ? ... +# 3| 5: [BlockStmt] { ... } +# 3| 0: [ReturnStmt] return ... +# 3| 0: [VarAccess] this.c1 +# 3| -1: [ThisAccess] this +# 3| 4: [FieldDeclaration] Class c2; +# 3| -1: [TypeAccess] Class +# 3| 0: [WildcardTypeAccess] ? ... +# 3| 0: [TypeAccess] CharSequence +# 3| 0: [VarAccess] c2 +# 3| 5: [Method] c2 +# 3| 3: [TypeAccess] Class +# 3| 0: [WildcardTypeAccess] ? ... +# 3| 0: [TypeAccess] CharSequence +# 3| 5: [BlockStmt] { ... } +# 3| 0: [ReturnStmt] return ... +# 3| 0: [VarAccess] this.c2 +# 3| -1: [ThisAccess] this +# 3| 6: [FieldDeclaration] Class c3; +# 3| -1: [TypeAccess] Class +# 3| 0: [TypeAccess] String +# 3| 0: [VarAccess] c3 +# 3| 7: [Method] c3 +# 3| 3: [TypeAccess] Class +# 3| 0: [TypeAccess] String +# 3| 5: [BlockStmt] { ... } +# 3| 0: [ReturnStmt] return ... +# 3| 0: [VarAccess] this.c3 +# 3| -1: [ThisAccess] this +# 3| 8: [FieldDeclaration] Class[] c4; +# 3| -1: [TypeAccess] Class[] +# 3| 0: [TypeAccess] Class +# 3| 0: [WildcardTypeAccess] ? ... +# 3| 0: [VarAccess] c4 +# 3| 9: [Method] c4 +# 3| 3: [TypeAccess] Class[] +# 3| 0: [TypeAccess] Class +# 3| 0: [WildcardTypeAccess] ? ... +# 3| 5: [BlockStmt] { ... } +# 3| 0: [ReturnStmt] return ... +# 3| 0: [VarAccess] this.c4 +# 3| -1: [ThisAccess] this diff --git a/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/PrintAst.qlref b/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/PrintAst.qlref new file mode 100644 index 00000000000..c7fd5faf239 --- /dev/null +++ b/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/PrintAst.qlref @@ -0,0 +1 @@ +semmle/code/java/PrintAst.ql \ No newline at end of file diff --git a/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/test.expected b/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/test.expected new file mode 100644 index 00000000000..ce28362cbca --- /dev/null +++ b/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/test.expected @@ -0,0 +1,37 @@ +classExprs +| test.kt:3:20:3:36 | ...=... | Class | +| test.kt:3:20:3:36 | Class | Class | +| test.kt:3:20:3:36 | Class | Class | +| test.kt:3:20:3:36 | Class | Class | +| test.kt:3:20:3:36 | c1 | Class | +| test.kt:3:20:3:36 | c1 | Class | +| test.kt:3:20:3:36 | this.c1 | Class | +| test.kt:3:39:3:70 | ...=... | Class | +| test.kt:3:39:3:70 | Class | Class | +| test.kt:3:39:3:70 | Class | Class | +| test.kt:3:39:3:70 | Class | Class | +| test.kt:3:39:3:70 | c2 | Class | +| test.kt:3:39:3:70 | c2 | Class | +| test.kt:3:39:3:70 | this.c2 | Class | +| test.kt:3:73:3:94 | ...=... | Class | +| test.kt:3:73:3:94 | Class | Class | +| test.kt:3:73:3:94 | Class | Class | +| test.kt:3:73:3:94 | Class | Class | +| test.kt:3:73:3:94 | c3 | Class | +| test.kt:3:73:3:94 | c3 | Class | +| test.kt:3:73:3:94 | this.c3 | Class | +| test.kt:3:97:3:120 | ...=... | Class[] | +| test.kt:3:97:3:120 | Class | Class | +| test.kt:3:97:3:120 | Class | Class | +| test.kt:3:97:3:120 | Class | Class | +| test.kt:3:97:3:120 | Class[] | Class[] | +| test.kt:3:97:3:120 | Class[] | Class[] | +| test.kt:3:97:3:120 | Class[] | Class[] | +| test.kt:3:97:3:120 | c4 | Class[] | +| test.kt:3:97:3:120 | c4 | Class[] | +| test.kt:3:97:3:120 | this.c4 | Class[] | +#select +| test.kt:3:20:3:36 | c1 | Class | +| test.kt:3:39:3:70 | c2 | Class | +| test.kt:3:73:3:94 | c3 | Class | +| test.kt:3:97:3:120 | c4 | Class[] | diff --git a/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/test.kt b/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/test.kt new file mode 100644 index 00000000000..15d03914a1a --- /dev/null +++ b/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/test.kt @@ -0,0 +1,3 @@ +import kotlin.reflect.KClass + +annotation class A(val c1: KClass<*>, val c2: KClass, val c3: KClass, val c4: Array>) { } diff --git a/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/test.ql b/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/test.ql new file mode 100644 index 00000000000..1f245499552 --- /dev/null +++ b/java/ql/test/kotlin/library-tests/annotation-accessor-result-type/test.ql @@ -0,0 +1,10 @@ +import java + +query predicate classExprs(Expr e, string tstr) { + tstr = e.getType().toString() and + tstr.matches("%Class%") +} + +from Method m +where m.getDeclaringType().fromSource() +select m, m.getReturnType().toString()