KE2: implement basic usage of properties, variables and flexible types

This commit is contained in:
Chris Smowton
2024-10-31 17:51:36 +00:00
parent fcde605569
commit fdaa6c5b4b
7 changed files with 210 additions and 116 deletions

View File

@@ -728,7 +728,11 @@ open class KotlinUsesExtractor(
return getFieldParent(d)
}
*/
return d.containingSymbol ?: d.containingFile
val rootSymbol = when (d) {
is KaPropertyAccessorSymbol -> d.containingSymbol
else -> d
}
return rootSymbol?.containingSymbol ?: rootSymbol?.containingFile
}
context(KaSession)

View File

@@ -7,6 +7,7 @@ import com.github.codeql.KotlinUsesExtractor.LocallyVisibleFunctionLabels
import com.intellij.psi.PsiElement
import com.semmle.extractor.java.PopulateFile
import com.semmle.util.unicode.UTF8Util
import org.jetbrains.kotlin.analysis.api.symbols.KaVariableSymbol
import java.io.BufferedWriter
import java.io.File
import java.util.concurrent.locks.ReentrantLock
@@ -153,11 +154,11 @@ abstract class TrapWriter(
* which label is used for a given local variable. This information is stored in this mapping.
*/
// TODO: This should be in a subclass so that DiagnosticTrapWriter doesn't include it, as it is not threadsafe
private val variableLabelMapping: MutableMap<KtProperty, Label<out DbLocalvar>> =
mutableMapOf<KtProperty, Label<out DbLocalvar>>()
private val variableLabelMapping: MutableMap<KaVariableSymbol, Label<out DbLocalvar>> =
mutableMapOf<KaVariableSymbol, Label<out DbLocalvar>>()
/** This returns the label used for a local variable, creating one if none currently exists. */
fun <T> getVariableLabelFor(v: KtProperty): Label<out DbLocalvar> {
fun <T> getVariableLabelFor(v: KaVariableSymbol): Label<out DbLocalvar> {
val maybeLabel = variableLabelMapping[v]
if (maybeLabel == null) {
val label = getFreshIdLabel<DbLocalvar>()

View File

@@ -3,11 +3,10 @@ package com.github.codeql
import com.github.codeql.KotlinFileExtractor.StmtExprParent
import org.jetbrains.kotlin.KtNodeTypes
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.resolution.KaCompoundAccessCall
import org.jetbrains.kotlin.analysis.api.resolution.KaSimpleFunctionCall
import org.jetbrains.kotlin.analysis.api.resolution.KaSuccessCallInfo
import org.jetbrains.kotlin.analysis.api.resolution.symbol
import org.jetbrains.kotlin.analysis.api.resolution.*
import org.jetbrains.kotlin.analysis.api.symbols.KaFunctionSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KaPropertySymbol
import org.jetbrains.kotlin.analysis.api.symbols.KaVariableSymbol
import org.jetbrains.kotlin.analysis.api.types.KaType
import org.jetbrains.kotlin.analysis.api.types.KaTypeNullability
import org.jetbrains.kotlin.analysis.api.types.symbol
@@ -19,6 +18,7 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.parsing.parseNumericLiteral
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.utils.mapToIndex
context(KaSession)
private fun KotlinFileExtractor.extractExpressionBody(e: KtExpression, callable: Label<out DbCallable>) {
@@ -306,9 +306,9 @@ private fun KotlinFileExtractor.extractPostfixUnaryExpression(
}
context(KaSession)
fun KtExpression.resolveCallTarget(): KaSimpleFunctionCall? {
fun KtExpression.resolveCallTarget(): KaCallableMemberCall<*, *>? {
val callInfo = this.resolveToCall() as? KaSuccessCallInfo
val functionCall = callInfo?.call as? KaSimpleFunctionCall
val functionCall = callInfo?.call as? KaCallableMemberCall<*, *>
return functionCall
}
@@ -335,7 +335,7 @@ private fun KotlinFileExtractor.extractBinaryExpression(
parent: StmtExprParent
) {
val op = expression.operationToken
val target = expression.resolveCallTarget()?.symbol
val target = expression.resolveCallTarget()?.symbol as? KaFunctionSymbol?
if (op == KtTokens.PLUS && target.isBinaryPlus()) {
extractBinaryExpression(expression, callable, parent, tw::writeExprs_addexpr)
@@ -546,6 +546,10 @@ private fun KotlinFileExtractor.extractExpression(
extractMethodCall(e, callable, parent)
}
is KtReferenceExpression -> {
extractReferenceExpression(e, callable, parent)
}
is KtIsExpression -> {
val locId = tw.getLocation(e)
@@ -1602,45 +1606,46 @@ OLD: KE1
}
}
}
*/
private fun extractVariableAccess(
variable: Label<out DbVariable>?,
type: TypeResults,
locId: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) =
tw.getFreshIdLabel<DbVaraccess>().also {
tw.writeExprs_varaccess(it, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(it, type.kotlinResult.id)
extractExprContext(it, locId, callable, enclosingStmt)
private fun KotlinFileExtractor.extractVariableAccess(
variable: Label<out DbVariable>?,
type: TypeResults,
locId: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) =
tw.getFreshIdLabel<DbVaraccess>().also {
tw.writeExprs_varaccess(it, type.javaResult.id, parent, idx)
tw.writeExprsKotlinType(it, type.kotlinResult.id)
extractExprContext(it, locId, callable, enclosingStmt)
if (variable != null) {
tw.writeVariableBinding(it, variable)
}
if (variable != null) {
tw.writeVariableBinding(it, variable)
}
}
private fun KotlinFileExtractor.extractVariableAccess(
variable: Label<out DbVariable>?,
type: KaType,
locId: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) =
extractVariableAccess(
variable,
useType(type),
locId,
parent,
idx,
callable,
enclosingStmt
)
private fun extractVariableAccess(
variable: Label<out DbVariable>?,
irType: IrType,
locId: Label<DbLocation>,
parent: Label<out DbExprparent>,
idx: Int,
callable: Label<out DbCallable>,
enclosingStmt: Label<out DbStmt>
) =
extractVariableAccess(
variable,
useType(irType),
locId,
parent,
idx,
callable,
enclosingStmt
)
*/
context(KaSession)
private fun KotlinFileExtractor.extractLoop(
loop: KtLoopExpression,
@@ -2707,7 +2712,7 @@ private fun KotlinFileExtractor.extractVariableExpr(
//extractInitializer: Boolean = true // OLD KE1
) {
with("variable expr", v) {
val varId = useVariable(v)
val varId = useVariable(v.symbol)
val exprId = tw.getFreshIdLabel<DbLocalvariabledeclexpr>()
val locId = tw.getLocation(v) // OLD KE1: getVariableLocationProvider(v))
val type = useType(v.returnType)
@@ -2734,6 +2739,61 @@ private fun KotlinFileExtractor.extractVariableExpr(
}
}
private fun KotlinFileExtractor.useVariable(v: KtProperty): Label<out DbLocalvar> {
private fun KotlinFileExtractor.useVariable(v: KaVariableSymbol): Label<out DbLocalvar> {
return tw.getVariableLabelFor<DbLocalvar>(v)
}
context(KaSession)
fun KotlinFileExtractor.extractReferenceExpression(
ref: KtReferenceExpression,
enclosingCallable: Label<out DbCallable>,
stmtExprParent: StmtExprParent
) {
val exprParent = stmtExprParent.expr(ref, enclosingCallable)
when (val resolvedCall = ref.resolveCallTarget()) {
is KaSimpleVariableAccessCall -> {
when (val varSymbol = resolvedCall.symbol) {
is KaPropertySymbol -> {
val (target, args) = when (val access = resolvedCall.simpleAccess) {
is KaSimpleVariableAccess.Read -> Pair(varSymbol.getter, listOf())
is KaSimpleVariableAccess.Write -> Pair(varSymbol.setter, listOf(access.value))
}
val qualifier: KtExpression? = (ref.parent as? KtDotQualifiedExpression)?.receiverExpression
if (target == null) {
TODO()
}
extractRawMethodAccess(
target,
tw.getLocation(ref),
ref.expressionType!!,
enclosingCallable,
exprParent.parent,
exprParent.idx,
exprParent.enclosingStmt,
qualifier,
null,
args
)
}
else -> {
extractVariableAccess(
useVariable(varSymbol),
varSymbol.returnType,
tw.getLocation(ref),
exprParent.parent,
exprParent.idx,
enclosingCallable,
exprParent.enclosingStmt
)
}
}
}
else -> {
TODO()
}
}
}

View File

@@ -1,10 +1,10 @@
package com.github.codeql
import com.github.codeql.utils.getJvmName
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.symbols.KaFunctionSymbol
import org.jetbrains.kotlin.analysis.api.symbols.name
import org.jetbrains.kotlin.analysis.api.symbols.psiSafe
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.types.KaType
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
/*
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.psi.KtDeclarationWithBody
@OptIn(ObsoleteDescriptorBasedAPI::class)
*/
context(KaSession)
fun KotlinUsesExtractor.getFunctionLabel(
f: KaFunctionSymbol,
parentId: Label<out DbElement>,
@@ -37,7 +38,7 @@ fun KotlinUsesExtractor.getFunctionLabel(
f.parent,
*/
parentId,
f.name!!.asString(), // TODO: Remove the !! // OLD KE1: getFunctionShortName(f).nameInDB,
getFunctionShortName(f).nameInDB,
/*
OLD: KE1
(maybeParameterList ?: f.valueParameters).map { it.type },
@@ -61,6 +62,7 @@ OLD: KE1
* function instead.
*/
*/
context(KaSession)
fun KotlinUsesExtractor.getFunctionLabel(
/*
OLD: KE1
@@ -191,12 +193,14 @@ fun KotlinUsesExtractor.getFunctionLabel(
val typeArgSuffix = "" // TODO
return "@\"$prefix;{$parentId}.$name($paramTypeIds){$returnTypeId}$typeArgSuffix\""
}
data class FunctionNames(val nameInDB: String, val kotlinName: String)
/*
OLD: KE1
private val IrDeclaration.isAnonymousFunction
get() = this is IrSimpleFunction && name == SpecialNames.NO_NAME_PROVIDED
data class FunctionNames(val nameInDB: String, val kotlinName: String)
@OptIn(ObsoleteDescriptorBasedAPI::class)
private fun getJvmModuleName(f: IrFunction) =
@@ -205,74 +209,87 @@ private val IrDeclaration.isAnonymousFunction
?: JvmCodegenUtil.getModuleName(pluginContext.moduleDescriptor)
)
fun getFunctionShortName(f: IrFunction): FunctionNames {
if (f.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA || f.isAnonymousFunction)
return FunctionNames(
OperatorNameConventions.INVOKE.asString(),
OperatorNameConventions.INVOKE.asString()
*/
context(KaSession)
fun KotlinUsesExtractor.getFunctionShortName(f: KaFunctionSymbol): FunctionNames {
/* OLD: KE1
if (f.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA || f.isAnonymousFunction)
return FunctionNames(
OperatorNameConventions.INVOKE.asString(),
OperatorNameConventions.INVOKE.asString()
)
*/
fun getSuffixIfInternal() = ""
/* OLD: KE1
if (
f.visibility == DescriptorVisibilities.INTERNAL &&
f !is IrConstructor &&
!(f.parent is IrFile || isExternalFileClassMember(f))
) {
"\$" + getJvmModuleName(f)
} else {
""
}
*/
(f as? KaPropertyAccessorSymbol)?.let {
val propSymbol = it.containingSymbol!! as KaPropertySymbol // TODO: Drop or justify !!
val propName = propSymbol.name.asString()
val getter = propSymbol.getter
val setter = propSymbol.setter
/* OLD: KE1
if (it.owner.parentClassOrNull?.kind == ClassKind.ANNOTATION_CLASS) {
if (getter == null) {
logger.error(
"Expected to find a getter for a property inside an annotation class"
)
return FunctionNames(propName, propName)
} else {
val jvmName = getJvmName(getter)
return FunctionNames(jvmName ?: propName, propName)
}
}
*/
fun getSuffixIfInternal() =
if (
f.visibility == DescriptorVisibilities.INTERNAL &&
f !is IrConstructor &&
!(f.parent is IrFile || isExternalFileClassMember(f))
) {
"\$" + getJvmModuleName(f)
} else {
""
}
(f as? IrSimpleFunction)?.correspondingPropertySymbol?.let {
val propName = it.owner.name.asString()
val getter = it.owner.getter
val setter = it.owner.setter
if (it.owner.parentClassOrNull?.kind == ClassKind.ANNOTATION_CLASS) {
if (getter == null) {
logger.error(
"Expected to find a getter for a property inside an annotation class"
)
return FunctionNames(propName, propName)
} else {
val jvmName = getJvmName(getter)
return FunctionNames(jvmName ?: propName, propName)
}
}
val maybeFunctionName =
when (f) {
getter -> JvmAbi.getterName(propName)
setter -> JvmAbi.setterName(propName)
else -> {
logger.error(
"Function has a corresponding property, but is neither the getter nor the setter"
)
null
}
}
maybeFunctionName?.let { defaultFunctionName ->
val suffix =
if (
f.visibility == DescriptorVisibilities.PRIVATE &&
f.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR
) {
"\$private"
} else {
getSuffixIfInternal()
}
return FunctionNames(
getJvmName(f) ?: "$defaultFunctionName$suffix",
defaultFunctionName
val maybeFunctionName =
when (f) {
getter -> JvmAbi.getterName(propName)
setter -> JvmAbi.setterName(propName)
else -> {
logger.error(
"Function has a corresponding property, but is neither the getter nor the setter"
)
null
}
}
maybeFunctionName?.let { defaultFunctionName ->
val suffix = ""
/* OLD: KE1
if (
f.visibility == DescriptorVisibilities.PRIVATE &&
f.origin == IrDeclarationOrigin.DEFAULT_PROPERTY_ACCESSOR
) {
"\$private"
} else {
getSuffixIfInternal()
}
*/
return FunctionNames(
getJvmName(f) ?: "${f.name.asString()}${getSuffixIfInternal()}",
f.name.asString()
getJvmName(f) ?: "$defaultFunctionName$suffix",
defaultFunctionName
)
}
}
return FunctionNames(
// TODO: Justify or drop !!
getJvmName(f) ?: "${f.name!!.asString()}${getSuffixIfInternal()}",
f.name!!.identifier
)
}
/*
OLD: KE1
// This excludes class type parameters that show up in (at least) constructors' typeParameters
// list.
fun getFunctionTypeParameters(f: IrFunction): List<IrTypeParameter> {
@@ -674,6 +691,7 @@ private fun KotlinFileExtractor.extractMethod(
*/
}
context(KaSession)
fun <T : DbCallable> KotlinUsesExtractor.useFunction(
f: KaFunctionSymbol,
parentId: Label<out DbElement>,
@@ -694,6 +712,7 @@ fun <T : DbCallable> KotlinUsesExtractor.useFunction(
return useFunction(f, javaFun, parentId /* TODO , classTypeArgsIncludingOuterClasses */)
}
context(KaSession)
private fun <T : DbCallable> KotlinUsesExtractor.useFunction(
f: KaFunctionSymbol,
javaFun: KaFunctionSymbol,

View File

@@ -2,6 +2,7 @@ package com.github.codeql
import com.github.codeql.KotlinFileExtractor.StmtExprParent
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.resolution.KaSimpleFunctionCall
import org.jetbrains.kotlin.analysis.api.resolution.symbol
import org.jetbrains.kotlin.analysis.api.symbols.KaFunctionSymbol
import org.jetbrains.kotlin.analysis.api.types.KaType
@@ -17,7 +18,7 @@ fun KotlinFileExtractor.extractMethodCall(
enclosingCallable: Label<out DbCallable>,
stmtExprParent: StmtExprParent
) {
val callTarget = call.resolveCallTarget()
val callTarget = call.resolveCallTarget() as? KaSimpleFunctionCall?
val target = callTarget?.symbol
val argMapping = callTarget?.argumentMapping
@@ -30,7 +31,7 @@ fun KotlinFileExtractor.extractMethodCall(
// - missing arguments due to default parameter values, in which case some indices are missing.
val args = call.valueArguments
.map { arg ->
val expr = arg.argumentExpression
val expr = arg.getArgumentExpression()
val p = argMapping[expr]
if (p == null) {
TODO("This is unexpected, no parameter was found for the argument")

View File

@@ -2,6 +2,7 @@ package com.github.codeql
import org.jetbrains.kotlin.analysis.api.symbols.KaClassSymbol
import org.jetbrains.kotlin.analysis.api.types.KaClassType
import org.jetbrains.kotlin.analysis.api.types.KaFlexibleType
import org.jetbrains.kotlin.analysis.api.types.KaType
private fun KotlinUsesExtractor.useClassType(
@@ -20,6 +21,7 @@ fun KotlinUsesExtractor.useType(t: KaType?, context: TypeContext = TypeContext.O
return extractErrorType()
}
is KaClassType -> return useClassType(t)
is KaFlexibleType -> return useType(t.lowerBound) // TODO: take a more reasoned choice here
else -> TODO()
}
/*

View File

@@ -1,5 +1,9 @@
package com.github.codeql.utils
import org.jetbrains.kotlin.analysis.api.annotations.KaAnnotated
import org.jetbrains.kotlin.analysis.api.symbols.markers.KaAnnotatedSymbol
import org.jetbrains.kotlin.analysis.api.symbols.name
/*
OLD: KE1
import com.github.codeql.utils.versions.allOverriddenIncludingSelf
@@ -74,8 +78,10 @@ private fun getSpecialJvmName(f: IrFunction): String? {
}
return null
}
*/
fun getJvmName(container: IrAnnotationContainer): String? {
fun getJvmName(container: KaAnnotatedSymbol): String? {
/* OLD: KE1
for (a: IrConstructorCall in container.annotations) {
val t = a.type
if (t is IrSimpleType && a.valueArgumentsCount == 1) {
@@ -94,5 +100,6 @@ fun getJvmName(container: IrAnnotationContainer): String? {
}
}
return (container as? IrFunction)?.let { getSpecialJvmName(container) }
*/
return container.name?.identifier
}
*/