Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 65 additions & 21 deletions java/kotlin-extractor/src/main/kotlin/KotlinFileExtractor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3590,7 +3590,9 @@ open class KotlinFileExtractor(
valueArguments,
enclosingStmt,
enclosingCallable,
idxOffset
idxOffset,
valueParameters = syntacticCallTarget.codeQlValueParameters,
contextArgumentLocation = tw.getWholeFileLocation()
)
},
dispatchReceiver?.type,
Expand Down Expand Up @@ -3804,50 +3806,92 @@ open class KotlinFileExtractor(
enclosingStmt: Label<out DbStmt>,
enclosingCallable: Label<out DbCallable>,
idxOffset: Int
) =
) {
extractCallValueArguments(
callId,
(0 until call.codeQlValueArgumentsCount).map { call.codeQlGetValueArgument(it) },
enclosingStmt,
enclosingCallable,
idxOffset
idxOffset,
valueParameters = call.symbol.owner.codeQlValueParameters,
contextArgumentLocation = tw.getWholeFileLocation()
)
}

private fun extractCallValueArguments(
callId: Label<out DbExprparent>,
valueArguments: List<IrExpression?>,
enclosingStmt: Label<out DbStmt>,
enclosingCallable: Label<out DbCallable>,
idxOffset: Int,
extractVarargAsArray: Boolean = false
extractVarargAsArray: Boolean = false,
valueParameters: List<IrValueParameter>? = null,
contextArgumentLocation: Label<DbLocation>? = null
) {
var i = 0
valueArguments.forEach { arg ->
valueArguments.forEachIndexed { argumentIndex, arg ->
if (arg != null) {
if (arg is IrVararg && !extractVarargAsArray) {
arg.elements.forEachIndexed { varargNo, vararg ->
extractVarargElement(
vararg,
enclosingCallable,
callId,
i + idxOffset + varargNo,
enclosingStmt
)
}
i += arg.elements.size
} else {
extractExpressionExpr(
arg,
enclosingCallable,
val parameter = valueParameters?.getOrNull(argumentIndex)
if (
parameter?.isCodeQlContextParameter() == true &&
arg is IrGetValue &&
contextArgumentLocation != null
) {
extractVariableAccess(
useValueDeclaration(arg.symbol.owner),
arg.type,
contextArgumentLocation,
callId,
(i++) + idxOffset,
i++ + idxOffset,
enclosingCallable,
enclosingStmt
)
} else {
i +=
extractCallValueArgument(
callId,
arg,
enclosingStmt,
enclosingCallable,
i + idxOffset,
extractVarargAsArray
)
}
}
}
}

private fun extractCallValueArgument(
callId: Label<out DbExprparent>,
argument: IrExpression,
enclosingStmt: Label<out DbStmt>,
enclosingCallable: Label<out DbCallable>,
outputIndex: Int,
extractVarargAsArray: Boolean = false
): Int {
if (argument is IrVararg && !extractVarargAsArray) {
argument.elements.forEachIndexed { varargIndex, element ->
extractVarargElement(
element,
enclosingCallable,
callId,
outputIndex + varargIndex,
enclosingStmt
)
}
return argument.elements.size
}

extractExpressionExpr(
argument,
enclosingCallable,
callId,
outputIndex,
enclosingStmt
)
return 1
}

private fun findFunction(cls: IrClass, name: String): IrFunction? =
cls.declarations.findSubType<IrFunction> { it.name.asString() == name }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.github.codeql.utils.versions

import org.jetbrains.kotlin.ir.declarations.IrValueParameter

fun IrValueParameter.isCodeQlContextParameter() = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.github.codeql.utils.versions

import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrMutableAnnotationContainer
import org.jetbrains.kotlin.ir.declarations.IrParameterKind
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.fromSymbolOwner
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.addAnnotations

private fun IrParameterKind.isCodeQlValueParameter() =
this == IrParameterKind.Context || this == IrParameterKind.Regular

val IrFunction.codeQlValueParameters: List<IrValueParameter>
get() = parameters.filter { it.kind.isCodeQlValueParameter() }

val IrFunction.codeQlExtensionReceiverParameter: IrValueParameter?
get() = extensionReceiverParameter

private fun IrMemberAccessExpression<*>.valueArgumentIndices(): List<Int> {
val owner = symbol.owner as? IrFunction ?: return arguments.indices.toList()
return owner.parameters.mapIndexedNotNull { index, parameter ->
index.takeIf { parameter.kind.isCodeQlValueParameter() }
}
}

val IrMemberAccessExpression<*>.codeQlValueArgumentsCount: Int
get() = valueArgumentIndices().size

fun IrMemberAccessExpression<*>.codeQlGetValueArgument(index: Int): IrExpression? =
arguments[valueArgumentIndices()[index]]

fun IrMemberAccessExpression<*>.codeQlPutValueArgument(index: Int, value: IrExpression?) {
arguments[valueArgumentIndices()[index]] = value
}

val IrMemberAccessExpression<*>.codeQlExtensionReceiver: IrExpression?
get() = extensionReceiver

val IrMemberAccessExpression<*>.codeQlTypeArgumentsCount: Int
get() = typeArgumentsCount

fun IrMemberAccessExpression<*>.codeQlGetTypeArgument(index: Int): IrType? = getTypeArgument(index)

fun IrType.codeQlAddAnnotations(annotations: List<IrConstructorCall>): IrType =
addAnnotations(annotations)

fun codeQlSetAnnotations(
container: IrMutableAnnotationContainer,
annotations: List<IrConstructorCall>
) {
container.annotations = annotations
}

fun IrFunction.codeQlSetDispatchReceiverParameter(param: IrValueParameter?) {
dispatchReceiverParameter = param
}

fun codeQlAnnotationFromSymbolOwner(
startOffset: Int,
endOffset: Int,
type: IrType,
symbol: IrConstructorSymbol,
typeArgumentsCount: Int
): IrConstructorCall =
IrConstructorCallImpl.fromSymbolOwner(
startOffset,
endOffset,
type,
symbol,
typeArgumentsCount
)

fun codeQlAnnotationFromSymbolOwner(
type: IrType,
symbol: IrConstructorSymbol
): IrConstructorCall = IrConstructorCallImpl.fromSymbolOwner(type, symbol)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.github.codeql.utils.versions

import org.jetbrains.kotlin.ir.declarations.IrParameterKind
import org.jetbrains.kotlin.ir.declarations.IrValueParameter

fun IrValueParameter.isCodeQlContextParameter() = kind == IrParameterKind.Context
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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

fun parameterIndexExcludingReceivers(vp: IrValueParameter): Int {
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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<IrValueParameter>
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<Int> {
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kotlin:
minimumVersion: 2.4.0
languageMode: K2
Original file line number Diff line number Diff line change
@@ -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 | ...[...] |
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// codeql-extractor-kotlin-options: -language-version 2.4 -XXLanguage:+CollectionLiterals

class Words private constructor(val values: Array<out String>) {
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])
}
Original file line number Diff line number Diff line change
@@ -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<Config>;

from DataFlow::Node source, DataFlow::Node sink
where Flow::flow(source, sink)
select source, sink
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kotlin:
minimumVersion: 2.4.20-RC2
languageMode: K2
Original file line number Diff line number Diff line change
@@ -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(...) |
Loading