diff --git a/java/kotlin-extractor/src/main/kotlin/utils/versions/v_2_4_0/IrCompat.kt b/java/kotlin-extractor/src/main/kotlin/utils/versions/v_2_4_0/IrCompat.kt index 2906b18c3140..cdb4e7203a4f 100644 --- a/java/kotlin-extractor/src/main/kotlin/utils/versions/v_2_4_0/IrCompat.kt +++ b/java/kotlin-extractor/src/main/kotlin/utils/versions/v_2_4_0/IrCompat.kt @@ -3,6 +3,7 @@ package com.github.codeql.utils.versions import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.declarations.IrParameterKind import org.jetbrains.kotlin.ir.declarations.IrValueParameter import org.jetbrains.kotlin.ir.expressions.IrAnnotation import org.jetbrains.kotlin.ir.expressions.IrConstructorCall @@ -21,33 +22,35 @@ import org.jetbrains.kotlin.ir.types.addAnnotations * have been removed. This file provides the 2.4.0 implementations. */ -// IrFunction: valueParameters -> parameters filtered to Regular kind +private fun IrParameterKind.isCodeQlValueParameter() = + this == IrParameterKind.Context || this == IrParameterKind.Regular + +// IrFunction: valueParameters -> context and regular parameters val IrFunction.codeQlValueParameters: List - get() = parameters.filter { it.kind == org.jetbrains.kotlin.ir.declarations.IrParameterKind.Regular } + get() = parameters.filter { it.kind.isCodeQlValueParameter() } // IrFunction: extensionReceiverParameter val IrFunction.codeQlExtensionReceiverParameter: IrValueParameter? get() = parameters.firstOrNull { it.kind == org.jetbrains.kotlin.ir.declarations.IrParameterKind.ExtensionReceiver } -// Helper: get the offset of value arguments in the arguments list -private fun IrMemberAccessExpression<*>.valueArgumentOffset(): Int { - val owner = symbol.owner as? IrFunction ?: return 0 - return owner.parameters.count { it.kind != org.jetbrains.kotlin.ir.declarations.IrParameterKind.Regular } +private fun IrMemberAccessExpression<*>.valueArgumentIndices(): List { + val owner = symbol.owner as? IrFunction ?: return arguments.indices.toList() + return owner.parameters.mapIndexedNotNull { index, parameter -> + index.takeIf { parameter.kind.isCodeQlValueParameter() } + } } // IrMemberAccessExpression: valueArgumentsCount -// In 2.4.0, arguments[] includes dispatch/extension receivers before regular params val IrMemberAccessExpression<*>.codeQlValueArgumentsCount: Int - get() = arguments.size - valueArgumentOffset() + get() = valueArgumentIndices().size // IrMemberAccessExpression: getValueArgument -// In 2.4.0, arguments[] includes dispatch/extension receivers before regular params -fun IrMemberAccessExpression<*>.codeQlGetValueArgument(index: Int): IrExpression? = arguments[index + valueArgumentOffset()] +fun IrMemberAccessExpression<*>.codeQlGetValueArgument(index: Int): IrExpression? = + arguments[valueArgumentIndices()[index]] // IrMemberAccessExpression: putValueArgument -// In 2.4.0, arguments[] includes dispatch/extension receivers before regular params fun IrMemberAccessExpression<*>.codeQlPutValueArgument(index: Int, value: IrExpression?) { - arguments[index + valueArgumentOffset()] = value + arguments[valueArgumentIndices()[index]] = value } // Re-add accessor for the extensionReceiver property removed in Kotlin 2.4.0. diff --git a/java/kotlin-extractor/src/main/kotlin/utils/versions/v_2_4_0/parameterIndexExcludingReceivers.kt b/java/kotlin-extractor/src/main/kotlin/utils/versions/v_2_4_0/parameterIndexExcludingReceivers.kt index 5e9b384b47e5..60d899742158 100644 --- a/java/kotlin-extractor/src/main/kotlin/utils/versions/v_2_4_0/parameterIndexExcludingReceivers.kt +++ b/java/kotlin-extractor/src/main/kotlin/utils/versions/v_2_4_0/parameterIndexExcludingReceivers.kt @@ -5,9 +5,15 @@ import org.jetbrains.kotlin.ir.declarations.IrParameterKind import org.jetbrains.kotlin.ir.declarations.IrValueParameter fun parameterIndexExcludingReceivers(vp: IrValueParameter): Int { - val offset = - (vp.parent as? IrFunction)?.let { f -> - f.parameters.count { it.kind == IrParameterKind.DispatchReceiver || it.kind == IrParameterKind.ExtensionReceiver || it.kind == IrParameterKind.Context } - } ?: 0 - return vp.indexInParameters - offset + if ( + vp.kind == IrParameterKind.DispatchReceiver || + vp.kind == IrParameterKind.ExtensionReceiver + ) { + return -1 + } + return (vp.parent as? IrFunction) + ?.parameters + ?.take(vp.indexInParameters) + ?.count { it.kind == IrParameterKind.Context || it.kind == IrParameterKind.Regular } + ?: vp.indexInParameters } diff --git a/java/ql/test-kotlin2/library-tests/collection-literal-operators/test.expected b/java/ql/test-kotlin2/library-tests/collection-literal-operators/test.expected new file mode 100644 index 000000000000..e26a10b424d6 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/collection-literal-operators/test.expected @@ -0,0 +1,7 @@ +literalCall +| test.kt:14:24:14:40 | of(...) | test.kt:5:18:5:62 | of | Words | test.kt:14:24:14:40 | Companion | 2 | +literalArguments +| test.kt:14:24:14:40 | of(...) | 0 | test.kt:14:25:14:32 | source(...) | +| test.kt:14:24:14:40 | of(...) | 1 | test.kt:14:35:14:39 | "two" | +#select +| test.kt:14:25:14:32 | source(...) | test.kt:15:10:15:24 | ...[...] | diff --git a/java/ql/test-kotlin2/library-tests/collection-literal-operators/test.kt b/java/ql/test-kotlin2/library-tests/collection-literal-operators/test.kt new file mode 100644 index 000000000000..521c49b3858e --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/collection-literal-operators/test.kt @@ -0,0 +1,16 @@ +// codeql-extractor-kotlin-options: -language-version 2.4 -XXLanguage:+CollectionLiterals + +class Words private constructor(val values: Array) { + companion object { + operator fun of(vararg values: String) = Words(values) + } +} + +fun source(): String = "" + +fun sink(value: String) {} + +fun test() { + val words: Words = [source(), "two"] + sink(words.values[0]) +} diff --git a/java/ql/test-kotlin2/library-tests/collection-literal-operators/test.ql b/java/ql/test-kotlin2/library-tests/collection-literal-operators/test.ql new file mode 100644 index 000000000000..e81cc5a9888a --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/collection-literal-operators/test.ql @@ -0,0 +1,30 @@ +import java +import semmle.code.java.dataflow.TaintTracking + +query predicate literalCall( + MethodCall call, Method target, string resultType, Expr qualifier, int argumentCount +) { + target = call.getMethod() and + target.hasName("of") and + call.getEnclosingCallable().fromSource() and + resultType = call.getType().toString() and + qualifier = call.getQualifier() and + argumentCount = call.getNumArgument() +} + +query predicate literalArguments(MethodCall call, int index, Expr argument) { + call.getMethod().hasName("of") and + argument = call.getArgument(index) +} + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node n) { n.asExpr().(MethodCall).getMethod().hasName("source") } + + predicate isSink(DataFlow::Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("sink") } +} + +module Flow = TaintTracking::Global; + +from DataFlow::Node source, DataFlow::Node sink +where Flow::flow(source, sink) +select source, sink diff --git a/java/ql/test-kotlin2/library-tests/collection-literal-operators/test.qlref b/java/ql/test-kotlin2/library-tests/collection-literal-operators/test.qlref new file mode 100644 index 000000000000..b79433a5b459 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/collection-literal-operators/test.qlref @@ -0,0 +1 @@ +test.ql diff --git a/java/ql/test-kotlin2/library-tests/companion-extensions/test.expected b/java/ql/test-kotlin2/library-tests/companion-extensions/test.expected new file mode 100644 index 000000000000..0eed1f5dced2 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/companion-extensions/test.expected @@ -0,0 +1,11 @@ +declarations +| test.kt:5:9:5:29 | empty | test.kt:3:1:7:1 | Box | Method | empty() | +| test.kt:9:11:9:52 | create | test.kt:0:0:0:0 | TestKt | Method | create(java.lang.String) | +| test.kt:12:5:12:19 | getDefault | test.kt:0:0:0:0 | TestKt | Method | getDefault() | +calls +| test.kt:19:14:19:29 | create(...) | test.kt:18:1:21:1 | test | test.kt:9:11:9:52 | create | test.kt:19:14:19:29 | TestKt | +| test.kt:20:14:20:20 | getDefault(...) | test.kt:18:1:21:1 | test | test.kt:12:5:12:19 | getDefault | test.kt:20:14:20:20 | TestKt | +properties +| test.kt:11:11:12:19 | default | test.kt:0:0:0:0 | TestKt | test.kt:12:5:12:19 | getDefault | +#select +| test.kt:19:21:19:28 | source(...) | test.kt:19:14:19:35 | getValue(...) | diff --git a/java/ql/test-kotlin2/library-tests/companion-extensions/test.kt b/java/ql/test-kotlin2/library-tests/companion-extensions/test.kt new file mode 100644 index 000000000000..db79f5f9d000 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/companion-extensions/test.kt @@ -0,0 +1,21 @@ +// codeql-extractor-kotlin-options: -language-version 2.4 -Xcompanion-blocks-and-extensions + +class Box(val value: String) { + companion { + fun empty() = Box("") + } +} + +companion fun Box.create(value: String) = Box(value) + +companion val Box.default: Box + get() = Box("") + +fun source(): String = "" + +fun sink(value: String) {} + +fun test() { + sink(Box.create(source()).value) + sink(Box.default.value) +} diff --git a/java/ql/test-kotlin2/library-tests/companion-extensions/test.ql b/java/ql/test-kotlin2/library-tests/companion-extensions/test.ql new file mode 100644 index 000000000000..f28b76761c02 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/companion-extensions/test.ql @@ -0,0 +1,43 @@ +import java +import semmle.code.java.dataflow.TaintTracking + +predicate isCompanionCallable(Callable callable) { + callable.getName() = ["empty", "create", "getDefault"] +} + +query predicate declarations( + Callable callable, RefType declaringType, string primaryClass, string signature +) { + isCompanionCallable(callable) and + callable.fromSource() and + declaringType = callable.getDeclaringType() and + primaryClass = callable.getAPrimaryQlClass() and + signature = callable.getSignature() +} + +query predicate calls(MethodCall call, Callable caller, Method target, Expr qualifier) { + caller.fromSource() and + call.getEnclosingCallable() = caller and + target = call.getMethod() and + isCompanionCallable(target) and + qualifier = call.getQualifier() +} + +query predicate properties(Property property, RefType declaringType, Method getter) { + property.hasName("default") and + property.fromSource() and + getter = property.getGetter() and + declaringType = getter.getDeclaringType() +} + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node n) { n.asExpr().(MethodCall).getMethod().hasName("source") } + + predicate isSink(DataFlow::Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("sink") } +} + +module Flow = TaintTracking::Global; + +from DataFlow::Node source, DataFlow::Node sink +where Flow::flow(source, sink) +select source, sink diff --git a/java/ql/test-kotlin2/library-tests/companion-extensions/test.qlref b/java/ql/test-kotlin2/library-tests/companion-extensions/test.qlref new file mode 100644 index 000000000000..b79433a5b459 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/companion-extensions/test.qlref @@ -0,0 +1 @@ +test.ql diff --git a/java/ql/test-kotlin2/library-tests/context-parameters/DB-CHECK.expected b/java/ql/test-kotlin2/library-tests/context-parameters/DB-CHECK.expected new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/java/ql/test-kotlin2/library-tests/context-parameters/test.expected b/java/ql/test-kotlin2/library-tests/context-parameters/test.expected new file mode 100644 index 000000000000..789d9f2505f7 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/context-parameters/test.expected @@ -0,0 +1,20 @@ +parameters +| test.kt:8:1:8:45 | logged | test.kt:7:9:7:22 | logger | 0 | Logger | +| test.kt:8:1:8:45 | logged | test.kt:8:12:8:24 | value | 1 | String | +| test.kt:12:5:12:28 | getLogged | test.kt:10:9:10:22 | logger | 1 | Logger | +| test.kt:12:5:12:28 | getLogged | test.kt:11:5:11:10 | | 0 | String | +| test.kt:13:5:15:5 | setLogged | test.kt:10:9:10:22 | logger | 1 | Logger | +| test.kt:13:5:15:5 | setLogged | test.kt:11:5:11:10 | | 0 | String | +| test.kt:13:5:15:5 | setLogged | test.kt:13:9:13:13 | value | 2 | String | +calls +| test.kt:18:17:18:33 | logged(...) | test.kt:17:43:21:1 | invoke | test.kt:8:1:8:45 | logged | test.kt:18:17:18:33 | TestKt | 2 | +| test.kt:18:17:18:40 | getLogged(...) | test.kt:17:43:21:1 | invoke | test.kt:12:5:12:28 | getLogged | test.kt:18:17:18:40 | TestKt | 2 | +| test.kt:19:5:19:27 | setLogged(...) | test.kt:17:43:21:1 | invoke | test.kt:13:5:15:5 | setLogged | test.kt:19:5:19:27 | TestKt | 3 | +arguments +| test.kt:18:17:18:33 | logged(...) | 0 | test.kt:0:0:0:0 | p0 | +| test.kt:18:17:18:33 | logged(...) | 1 | test.kt:18:24:18:32 | "message" | +| test.kt:18:17:18:40 | getLogged(...) | 0 | test.kt:18:17:18:33 | logged(...) | +| test.kt:18:17:18:40 | getLogged(...) | 1 | test.kt:0:0:0:0 | p0 | +| test.kt:19:5:19:27 | setLogged(...) | 0 | test.kt:19:5:19:12 | "target" | +| test.kt:19:5:19:27 | setLogged(...) | 1 | test.kt:0:0:0:0 | p0 | +| test.kt:19:5:19:27 | setLogged(...) | 2 | test.kt:19:23:19:27 | value | diff --git a/java/ql/test-kotlin2/library-tests/context-parameters/test.kt b/java/ql/test-kotlin2/library-tests/context-parameters/test.kt new file mode 100644 index 000000000000..640ec53f3c25 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/context-parameters/test.kt @@ -0,0 +1,21 @@ +// codeql-extractor-kotlin-options: -language-version 2.4 -Xcontext-parameters + +class Logger { + fun log(value: String) = value +} + +context(logger: Logger) +fun logged(value: String) = logger.log(value) + +context(logger: Logger) +var String.logged: String + get() = logger.log(this) + set(value) { + logger.log(value) + } + +fun use(logger: Logger) = context(logger) { + val value = logged("message").logged + "target".logged = value + value +} diff --git a/java/ql/test-kotlin2/library-tests/context-parameters/test.ql b/java/ql/test-kotlin2/library-tests/context-parameters/test.ql new file mode 100644 index 000000000000..d522564ed90f --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/context-parameters/test.ql @@ -0,0 +1,25 @@ +import java + +predicate isContextCallable(Callable c) { c.getName() = ["logged", "getLogged", "setLogged"] } + +query predicate parameters(Callable callable, Parameter parameter, int index, string parameterType) { + isContextCallable(callable) and + parameter = callable.getParameter(index) and + parameterType = parameter.getType().toString() +} + +query predicate calls( + MethodCall call, Callable caller, Method target, Expr qualifier, int argumentCount +) { + caller.fromSource() and + call.getEnclosingCallable() = caller and + target = call.getMethod() and + isContextCallable(target) and + qualifier = call.getQualifier() and + argumentCount = call.getNumArgument() +} + +query predicate arguments(MethodCall call, int index, Expr argument) { + isContextCallable(call.getMethod()) and + argument = call.getArgument(index) +} diff --git a/java/ql/test-kotlin2/library-tests/context-parameters/test.qlref b/java/ql/test-kotlin2/library-tests/context-parameters/test.qlref new file mode 100644 index 000000000000..b79433a5b459 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/context-parameters/test.qlref @@ -0,0 +1 @@ +test.ql diff --git a/java/ql/test-kotlin2/library-tests/full-value-classes/test.expected b/java/ql/test-kotlin2/library-tests/full-value-classes/test.expected new file mode 100644 index 000000000000..b72a61ccbe92 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/full-value-classes/test.expected @@ -0,0 +1,17 @@ +classes +| test.kt:3:1:5:1 | Base | abstract, public | +| test.kt:7:1:9:1 | PairValue | final, public | +supertypes +| test.kt:7:1:9:1 | PairValue | test.kt:3:1:5:1 | Base | +properties +| test.kt:4:14:4:27 | value | int | public | test.kt:4:14:4:27 | getValue | | +| test.kt:7:32:7:45 | value | int | public | test.kt:7:32:7:45 | getValue | value | +| test.kt:7:48:7:64 | label | String | public | test.kt:7:48:7:64 | getLabel | label | +constructors +| test.kt:3:1:5:1 | Base | Base() | +| test.kt:7:22:7:65 | PairValue | PairValue(int,java.lang.String) | +| test.kt:8:5:8:58 | PairValue | PairValue(java.lang.String) | +constructorCalls +| test.kt:7:1:9:1 | super(...) | test.kt:3:1:5:1 | Base | +| test.kt:8:34:8:58 | this(...) | test.kt:7:22:7:65 | PairValue | +| test.kt:11:42:11:57 | new PairValue(...) | test.kt:8:5:8:58 | PairValue | diff --git a/java/ql/test-kotlin2/library-tests/full-value-classes/test.kt b/java/ql/test-kotlin2/library-tests/full-value-classes/test.kt new file mode 100644 index 000000000000..52c8163da134 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/full-value-classes/test.kt @@ -0,0 +1,11 @@ +// codeql-extractor-kotlin-options: -XXLanguage:+FullValueClasses + +abstract value class Base { + abstract val value: Int +} + +value class PairValue(override val value: Int, val label: String) : Base() { + constructor(value: String) : this(value.length, value) +} + +fun makePairValue(value: String): Base = PairValue(value) diff --git a/java/ql/test-kotlin2/library-tests/full-value-classes/test.ql b/java/ql/test-kotlin2/library-tests/full-value-classes/test.ql new file mode 100644 index 000000000000..c59845dba7e8 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/full-value-classes/test.ql @@ -0,0 +1,39 @@ +import java + +string backingField(Property p) { + if exists(p.getBackingField()) then result = p.getBackingField().toString() else result = "" +} + +query predicate classes(Class c, string classModifiers) { + c.fromSource() and + not c.isCompilerGenerated() and + c.getLocation().getStartLine() > 0 and + classModifiers = concat(string m | c.hasModifier(m) | m, ", ") +} + +query predicate supertypes(Class c, Class supertype) { + c.fromSource() and + supertype.fromSource() and + extendsReftype(c, supertype) +} + +query predicate properties( + Property p, string propertyType, string propertyModifiers, Method getter, string field +) { + p.fromSource() and + propertyType = p.getGetter().getReturnType().toString() and + propertyModifiers = concat(string m | p.hasModifier(m) | m, ", ") and + getter = p.getGetter() and + field = backingField(p) +} + +query predicate constructors(Constructor c, string signature) { + c.fromSource() and + signature = c.getSignature() +} + +query predicate constructorCalls(ConstructorCall call, Constructor target) { + call.getEnclosingCallable().fromSource() and + target = call.getConstructor() and + target.getSourceDeclaration().fromSource() +} diff --git a/java/ql/test-kotlin2/library-tests/full-value-classes/test.qlref b/java/ql/test-kotlin2/library-tests/full-value-classes/test.qlref new file mode 100644 index 000000000000..b79433a5b459 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/full-value-classes/test.qlref @@ -0,0 +1 @@ +test.ql diff --git a/java/ql/test-kotlin2/library-tests/name-based-destructuring/test.expected b/java/ql/test-kotlin2/library-tests/name-based-destructuring/test.expected new file mode 100644 index 000000000000..fd9267db21f4 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/name-based-destructuring/test.expected @@ -0,0 +1,4 @@ +selectedProperty +| test.kt:10:10:10:17 | currency | test.kt:10:10:10:17 | getCurrency(...) | test.kt:3:36:3:55 | getCurrency | +#select +| test.kt:14:28:14:35 | source(...) | test.kt:11:10:11:17 | currency | diff --git a/java/ql/test-kotlin2/library-tests/name-based-destructuring/test.kt b/java/ql/test-kotlin2/library-tests/name-based-destructuring/test.kt new file mode 100644 index 000000000000..34396bc753e8 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/name-based-destructuring/test.kt @@ -0,0 +1,14 @@ +// codeql-extractor-kotlin-options: -XXLanguage:+FullValueClasses -XXLanguage:+NameBasedDestructuring -XXLanguage:+EnableNameBasedDestructuringShortForm + +value class Money(val amount: Int, val currency: String) + +fun source(): String = "" + +fun sink(value: String) {} + +fun test(money: Money) { + val (currency) = money + sink(currency) +} + +fun flow() = test(Money(0, source())) diff --git a/java/ql/test-kotlin2/library-tests/name-based-destructuring/test.ql b/java/ql/test-kotlin2/library-tests/name-based-destructuring/test.ql new file mode 100644 index 000000000000..42dbe0643e19 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/name-based-destructuring/test.ql @@ -0,0 +1,22 @@ +import java +import semmle.code.java.dataflow.TaintTracking + +query predicate selectedProperty( + LocalVariableDeclExpr variable, MethodCall initializer, Method getter +) { + variable.getVariable().hasName("currency") and + initializer = variable.getInit() and + getter = initializer.getMethod() +} + +module Config implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node n) { n.asExpr().(MethodCall).getMethod().hasName("source") } + + predicate isSink(DataFlow::Node n) { n.asExpr().(Argument).getCall().getCallee().hasName("sink") } +} + +module Flow = TaintTracking::Global; + +from DataFlow::Node source, DataFlow::Node sink +where Flow::flow(source, sink) +select source, sink diff --git a/java/ql/test-kotlin2/library-tests/name-based-destructuring/test.qlref b/java/ql/test-kotlin2/library-tests/name-based-destructuring/test.qlref new file mode 100644 index 000000000000..b79433a5b459 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/name-based-destructuring/test.qlref @@ -0,0 +1 @@ +test.ql diff --git a/java/ql/test-kotlin2/library-tests/when-indy/PrintAst.expected b/java/ql/test-kotlin2/library-tests/when-indy/PrintAst.expected new file mode 100644 index 000000000000..9ba7f92457e4 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/when-indy/PrintAst.expected @@ -0,0 +1,38 @@ +test.kt: +# 0| [CompilationUnit] test +# 0| 1: [Class] TestKt +# 3| 1: [Method] classify +# 3| 3: [TypeAccess] int +#-----| 4: (Parameters) +# 3| 0: [Parameter] value +# 3| 0: [TypeAccess] Object +# 4| 5: [BlockStmt] { ... } +# 8| 0: [ReturnStmt] return ... +# 4| 0: [StmtExpr] +# 4| 0: [BlockStmt] { ... } +# 4| 0: [LocalVariableDeclStmt] var ...; +# 4| 1: [LocalVariableDeclExpr] tmp0_subject +# 4| 0: [VarAccess] value +# 4| 1: [ExprStmt] ; +# 4| 0: [WhenExpr] when ... +# 5| 0: [WhenBranch] ... -> ... +# 5| 0: [InstanceOfExpr] ...instanceof... +# 5| 0: [VarAccess] tmp0_subject +# 5| 1: [TypeAccess] String +# 5| 1: [ExprStmt] ; +# 5| 0: [MethodCall] length(...) +# 5| -1: [ImplicitCastExpr] +# 5| 0: [TypeAccess] String +# 5| 1: [VarAccess] value +# 6| 1: [WhenBranch] ... -> ... +# 6| 0: [InstanceOfExpr] ...instanceof... +# 6| 0: [VarAccess] tmp0_subject +# 6| 1: [TypeAccess] int +# 6| 1: [ExprStmt] ; +# 6| 0: [ImplicitCastExpr] +# 6| 0: [TypeAccess] int +# 6| 1: [VarAccess] value +# 7| 2: [WhenBranch] ... -> ... +# 7| 0: [BooleanLiteral] true +# 7| 1: [ExprStmt] ; +# 7| 0: [IntegerLiteral] -1 diff --git a/java/ql/test-kotlin2/library-tests/when-indy/PrintAst.qlref b/java/ql/test-kotlin2/library-tests/when-indy/PrintAst.qlref new file mode 100644 index 000000000000..f391eb5e4636 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/when-indy/PrintAst.qlref @@ -0,0 +1 @@ +semmle/code/java/PrintAst.ql diff --git a/java/ql/test-kotlin2/library-tests/when-indy/test.kt b/java/ql/test-kotlin2/library-tests/when-indy/test.kt new file mode 100644 index 000000000000..4ca9734be110 --- /dev/null +++ b/java/ql/test-kotlin2/library-tests/when-indy/test.kt @@ -0,0 +1,8 @@ +// codeql-extractor-kotlin-options: -language-version 2.4 -jvm-target 21 -Xwhen-expressions=indy + +fun classify(value: Any): Int = + when (value) { + is String -> value.length + is Int -> value + else -> -1 + }