diff --git a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt index e5648bf9965..abed6dc7b7a 100644 --- a/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt +++ b/java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt @@ -457,7 +457,54 @@ open class KotlinFileExtractor( extractDeclInitializers(c.declarations, false) { Pair(blockId, obinitId) } } - val jvmStaticFqName = FqName("kotlin.jvm.JvmStatic") + fun extractAnnotations(c: IrClass): Int { + var count = 0 + for (constructorCall: IrConstructorCall in c.annotations) { + + // todo: do not extract JvmName, what else? + + val t = useType(constructorCall.type) + val annotated = useClassSource(c) + + val id = tw.getLabelFor("@\"annotation;{$annotated};{${t.javaResult.id}}\"") + tw.writeExprs_declannotation(id, t.javaResult.id, annotated, count++) + tw.writeExprsKotlinType(id, t.kotlinResult.id) + + val locId = tw.getLocation(constructorCall) + tw.writeHasLocation(id, locId) + + for (i in 0 until constructorCall.valueArgumentsCount) { + val param = constructorCall.symbol.owner.valueParameters[i] + val prop = constructorCall.symbol.owner.parentAsClass.declarations.filterIsInstance().firstOrNull { it.name == param.name } + if (prop == null) { + continue + } + + val v = constructorCall.getValueArgument(i) ?: param.defaultValue?.expression + + when (v) { + is IrConst<*> -> { + val exprId = extractConstant(v, id, i) + if (exprId != null) { + + tw.writeAnnotValue(id, useFunction(prop.getter!!), exprId) + } + } + /* + Integer types; + Enum types; + String type; + Classes; + Other annotation types; + Arrays of any type listed above. + */ + + } + } + } + + return count + } fun extractClassSource(c: IrClass, extractDeclarations: Boolean, extractStaticInitializer: Boolean, extractPrivateMembers: Boolean, extractFunctionBodies: Boolean): Label { with("class source", c) { @@ -540,6 +587,8 @@ open class KotlinFileExtractor( linesOfCode?.linesOfCodeInDeclaration(c, id) + extractAnnotations(c) + if (extractFunctionBodies && !c.isAnonymousObject && !c.isLocal) externalClassExtractor.writeStubTrapFile(c) @@ -548,6 +597,8 @@ open class KotlinFileExtractor( } } + val jvmStaticFqName = FqName("kotlin.jvm.JvmStatic") + private fun extractJvmStaticProxyMethods(c: IrClass, classId: Label, extractPrivateMembers: Boolean, extractFunctionBodies: Boolean) { // Add synthetic forwarders for any JvmStatic methods or properties: @@ -3457,13 +3508,6 @@ open class KotlinFileExtractor( else -> c.toString() } - // Render a string literal as it might occur in Kotlin source. Note this is a reasonable guess; the real source - // could use other escape sequences to describe the same String. Importantly, this is the same guess the Java - // extractor makes regarding string literals occurring within annotations, which we need to coincide with to ensure - // database consistency. - private fun toQuotedLiteral(s: String) = - s.toCharArray().joinToString(separator = "", prefix = "\"", postfix = "\"") { c -> escapeCharForQuotedLiteral(c) } - private fun extractExpression(e: IrExpression, callable: Label, parent: StmtExprParent) { with("expression", e) { when(e) { @@ -3614,71 +3658,10 @@ open class KotlinFileExtractor( } is IrConst<*> -> { val exprParent = parent.expr(e, callable) - val v = e.value - when { - v is Number && (v is Int || v is Short || v is Byte) -> { - extractConstantInteger(v, tw.getLocation(e), exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt) - } - v is Long -> { - val id = tw.getFreshIdLabel() - val type = useType(e.type) - val locId = tw.getLocation(e) - tw.writeExprs_longliteral(id, type.javaResult.id, exprParent.parent, exprParent.idx) - tw.writeExprsKotlinType(id, type.kotlinResult.id) - extractExprContext(id, locId, callable, exprParent.enclosingStmt) - tw.writeNamestrings(v.toString(), v.toString(), id) - } - v is Float -> { - val id = tw.getFreshIdLabel() - val type = useType(e.type) - val locId = tw.getLocation(e) - tw.writeExprs_floatingpointliteral(id, type.javaResult.id, exprParent.parent, exprParent.idx) - tw.writeExprsKotlinType(id, type.kotlinResult.id) - extractExprContext(id, locId, callable, exprParent.enclosingStmt) - tw.writeNamestrings(v.toString(), v.toString(), id) - } - v is Double -> { - val id = tw.getFreshIdLabel() - val type = useType(e.type) - val locId = tw.getLocation(e) - tw.writeExprs_doubleliteral(id, type.javaResult.id, exprParent.parent, exprParent.idx) - tw.writeExprsKotlinType(id, type.kotlinResult.id) - extractExprContext(id, locId, callable, exprParent.enclosingStmt) - tw.writeNamestrings(v.toString(), v.toString(), id) - } - v is Boolean -> { - val id = tw.getFreshIdLabel() - val type = useType(e.type) - val locId = tw.getLocation(e) - tw.writeExprs_booleanliteral(id, type.javaResult.id, exprParent.parent, exprParent.idx) - tw.writeExprsKotlinType(id, type.kotlinResult.id) - extractExprContext(id, locId, callable, exprParent.enclosingStmt) - tw.writeNamestrings(v.toString(), v.toString(), id) - } - v is Char -> { - val id = tw.getFreshIdLabel() - val type = useType(e.type) - val locId = tw.getLocation(e) - tw.writeExprs_characterliteral(id, type.javaResult.id, exprParent.parent, exprParent.idx) - tw.writeExprsKotlinType(id, type.kotlinResult.id) - extractExprContext(id, locId, callable, exprParent.enclosingStmt) - tw.writeNamestrings(v.toString(), v.toString(), id) - } - v is String -> { - val id = tw.getFreshIdLabel() - val type = useType(e.type) - val locId = tw.getLocation(e) - tw.writeExprs_stringliteral(id, type.javaResult.id, exprParent.parent, exprParent.idx) - tw.writeExprsKotlinType(id, type.kotlinResult.id) - extractExprContext(id, locId, callable, exprParent.enclosingStmt) - tw.writeNamestrings(toQuotedLiteral(v.toString()), v.toString(), id) - } - v == null -> { - extractNull(e.type, tw.getLocation(e), exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt) - } - else -> { - logger.errorElement("Unrecognised IrConst: " + v.javaClass, e) - } + val id = extractConstant(e, exprParent.parent, exprParent.idx) + if (id != null) { + tw.writeCallableEnclosingExpr(id, callable) + tw.writeStatementEnclosingExpr(id, exprParent.enclosingStmt) } } is IrGetValue -> { @@ -4170,6 +4153,86 @@ open class KotlinFileExtractor( extractExpressionExpr(loop.condition, callable, id, 0, id) } + // Render a string literal as it might occur in Kotlin source. Note this is a reasonable guess; the real source + // could use other escape sequences to describe the same String. Importantly, this is the same guess the Java + // extractor makes regarding string literals occurring within annotations, which we need to coincide with to ensure + // database consistency. + private fun toQuotedLiteral(s: String) = + s.toCharArray().joinToString(separator = "", prefix = "\"", postfix = "\"") { c -> escapeCharForQuotedLiteral(c) } + + private fun extractConstant( + e: IrConst<*>, + parent: Label, + idx: Int + ): Label? { + val v = e.value + when { + v is Number && (v is Int || v is Short || v is Byte) -> { + extractConstantInteger(v, tw.getLocation(e), exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt) + } + v is Long -> { + val id = tw.getFreshIdLabel() + val type = useType(e.type) + val locId = tw.getLocation(e) + tw.writeExprs_longliteral(id, type.javaResult.id, exprParent.parent, exprParent.idx) + tw.writeExprsKotlinType(id, type.kotlinResult.id) + extractExprContext(id, locId, callable, exprParent.enclosingStmt) + tw.writeNamestrings(v.toString(), v.toString(), id) + } + v is Float -> { + val id = tw.getFreshIdLabel() + val type = useType(e.type) + val locId = tw.getLocation(e) + tw.writeExprs_floatingpointliteral(id, type.javaResult.id, exprParent.parent, exprParent.idx) + tw.writeExprsKotlinType(id, type.kotlinResult.id) + extractExprContext(id, locId, callable, exprParent.enclosingStmt) + tw.writeNamestrings(v.toString(), v.toString(), id) + } + v is Double -> { + val id = tw.getFreshIdLabel() + val type = useType(e.type) + val locId = tw.getLocation(e) + tw.writeExprs_doubleliteral(id, type.javaResult.id, exprParent.parent, exprParent.idx) + tw.writeExprsKotlinType(id, type.kotlinResult.id) + extractExprContext(id, locId, callable, exprParent.enclosingStmt) + tw.writeNamestrings(v.toString(), v.toString(), id) + } + v is Boolean -> { + val id = tw.getFreshIdLabel() + val type = useType(e.type) + val locId = tw.getLocation(e) + tw.writeExprs_booleanliteral(id, type.javaResult.id, exprParent.parent, exprParent.idx) + tw.writeExprsKotlinType(id, type.kotlinResult.id) + extractExprContext(id, locId, callable, exprParent.enclosingStmt) + tw.writeNamestrings(v.toString(), v.toString(), id) + } + v is Char -> { + val id = tw.getFreshIdLabel() + val type = useType(e.type) + val locId = tw.getLocation(e) + tw.writeExprs_characterliteral(id, type.javaResult.id, exprParent.parent, exprParent.idx) + tw.writeExprsKotlinType(id, type.kotlinResult.id) + extractExprContext(id, locId, callable, exprParent.enclosingStmt) + tw.writeNamestrings(v.toString(), v.toString(), id) + } + v is String -> { + val id = tw.getFreshIdLabel() + val type = useType(e.type) + val locId = tw.getLocation(e) + tw.writeExprs_stringliteral(id, type.javaResult.id, exprParent.parent, exprParent.idx) + tw.writeExprsKotlinType(id, type.kotlinResult.id) + extractExprContext(id, locId, callable, exprParent.enclosingStmt) + tw.writeNamestrings(toQuotedLiteral(v.toString()), v.toString(), id) + } + v == null -> { + extractNull(e.type, tw.getLocation(e), exprParent.parent, exprParent.idx, callable, exprParent.enclosingStmt) + } + else -> { + logger.errorElement("Unrecognised IrConst: " + v.javaClass, e) + } + } + } + private fun IrValueParameter.isExtensionReceiver(): Boolean { val parentFun = parent as? IrFunction ?: return false return parentFun.extensionReceiverParameter == this diff --git a/java/ql/lib/semmle/code/java/Annotation.qll b/java/ql/lib/semmle/code/java/Annotation.qll index 28f994053a2..dd6762776c6 100644 --- a/java/ql/lib/semmle/code/java/Annotation.qll +++ b/java/ql/lib/semmle/code/java/Annotation.qll @@ -249,7 +249,7 @@ private predicate filteredAnnotValue(Annotation a, Method m, Expr val) { private predicate sourceAnnotValue(Annotation a, Method m, Expr val) { annotValue(a, m, val) and - val.getFile().getExtension() = "java" + val.getFile().isSourceFile() } /** An abstract representation of language elements that can be annotated. */ diff --git a/java/ql/lib/semmle/code/java/DependencyCounts.qll b/java/ql/lib/semmle/code/java/DependencyCounts.qll index 1010be48055..b34e774f1e1 100644 --- a/java/ql/lib/semmle/code/java/DependencyCounts.qll +++ b/java/ql/lib/semmle/code/java/DependencyCounts.qll @@ -91,7 +91,7 @@ predicate numDepends(RefType t, RefType dep, int value) { elem = a and usesType(a.getType(), dep) or elem = [a.getValue(_), a.getAnArrayValue(_)] and - elem.getFile().getExtension() = "java" and + elem.getFile().isSourceFile() and usesType(elem.(Expr).getType(), dep) ) or diff --git a/java/ql/test/kotlin/library-tests/annotation_classes/CONSISTENCY/children.expected b/java/ql/test/kotlin/library-tests/annotation_classes/CONSISTENCY/children.expected new file mode 100644 index 00000000000..28e24b18b44 --- /dev/null +++ b/java/ql/test/kotlin/library-tests/annotation_classes/CONSISTENCY/children.expected @@ -0,0 +1 @@ +| file:///modules/java.base/java/util/Random.class:0:0:0:0 | RandomGeneratorProperties | Annotation | 1 | gap | 0, 2, 3, 4, 5 | diff --git a/java/ql/test/kotlin/library-tests/annotation_classes/PrintAst.expected b/java/ql/test/kotlin/library-tests/annotation_classes/PrintAst.expected index 321512a036f..2d34788a76e 100644 --- a/java/ql/test/kotlin/library-tests/annotation_classes/PrintAst.expected +++ b/java/ql/test/kotlin/library-tests/annotation_classes/PrintAst.expected @@ -21,6 +21,12 @@ def.kt: # 6| 1: [Method] message # 7| 2: [Method] replaceWith # 9| 5: [Class] X +#-----| -3: (Annotations) +# 9| 1: [Annotation] Deprecated +# 0| 1: [StringLiteral] This class is deprecated +# 10| 2: [Annotation] SomeAnnotation +# 0| 1: [IntegerLiteral] 5 +# 0| 1: [StringLiteral] a # 11| 1: [Constructor] X # 9| 5: [BlockStmt] { ... } # 9| 0: [SuperConstructorInvocationStmt] super(...) diff --git a/java/ql/test/kotlin/library-tests/annotation_classes/classes.expected b/java/ql/test/kotlin/library-tests/annotation_classes/classes.expected index e31fd25502f..824dd2d6d21 100644 --- a/java/ql/test/kotlin/library-tests/annotation_classes/classes.expected +++ b/java/ql/test/kotlin/library-tests/annotation_classes/classes.expected @@ -1,11 +1,16 @@ -annotation +annotationDeclarations | def.kt:1:1:1:87 | SomeAnnotation | def.kt:1:53:1:66 | abc | | def.kt:1:1:1:87 | SomeAnnotation | def.kt:1:69:1:86 | y | | def.kt:3:1:3:52 | ReplaceWith | def.kt:3:30:3:51 | expression | | def.kt:5:1:7:51 | Deprecated | def.kt:6:5:6:23 | message | | def.kt:5:1:7:51 | Deprecated | def.kt:7:5:7:50 | replaceWith | -| file:///modules/java.base/java/lang/annotation/Retention.class:0:0:0:0 | Retention | file:///modules/java.base/java/lang/annotation/Retention.class:0:0:0:0 | value | -| file:///modules/java.base/java/lang/annotation/Target.class:0:0:0:0 | Target | file:///modules/java.base/java/lang/annotation/Target.class:0:0:0:0 | value | +annotations +| def.kt:9:1:9:57 | Deprecated | def.kt:9:1:11:7 | X | def.kt:5:1:7:51 | Deprecated | +| def.kt:10:1:10:24 | SomeAnnotation | def.kt:9:1:11:7 | X | def.kt:1:1:1:87 | SomeAnnotation | +annotationValues +| def.kt:9:1:9:57 | Deprecated | def.kt:0:0:0:0 | This class is deprecated | +| def.kt:10:1:10:24 | SomeAnnotation | def.kt:0:0:0:0 | 5 | +| def.kt:10:1:10:24 | SomeAnnotation | def.kt:0:0:0:0 | a | #select | def.kt:0:0:0:0 | DefKt | Class | | def.kt:1:1:1:87 | SomeAnnotation | Interface | diff --git a/java/ql/test/kotlin/library-tests/annotation_classes/classes.ql b/java/ql/test/kotlin/library-tests/annotation_classes/classes.ql index 44582d74e53..fc3db6f0c02 100644 --- a/java/ql/test/kotlin/library-tests/annotation_classes/classes.ql +++ b/java/ql/test/kotlin/library-tests/annotation_classes/classes.ql @@ -4,6 +4,17 @@ from ClassOrInterface x where x.fromSource() select x, x.getPrimaryQlClasses() -query predicate annotation(AnnotationType at, AnnotationElement ae) { +query predicate annotationDeclarations(AnnotationType at, AnnotationElement ae) { + at.fromSource() and at.getAnAnnotationElement() = ae } + +query predicate annotations(Annotation a, Element e, AnnotationType at) { + at.fromSource() and + a.getAnnotatedElement() = e and + at = a.getType() +} + +query predicate annotationValues(Annotation a, Expr v) { + a.getAValue() = v and v.getFile().isSourceFile() +}