From fdaa6c5b4b5d84a3d3d68b71ea92d10259c0932b Mon Sep 17 00:00:00 2001 From: Chris Smowton Date: Thu, 31 Oct 2024 17:51:36 +0000 Subject: [PATCH] KE2: implement basic usage of properties, variables and flexible types --- .../src/main/kotlin/KotlinUsesExtractor.kt | 6 +- .../src/main/kotlin/TrapWriter.kt | 7 +- .../src/main/kotlin/entities/Expression.kt | 148 ++++++++++++------ .../src/main/kotlin/entities/Function.kt | 147 +++++++++-------- .../src/main/kotlin/entities/MethodCall.kt | 5 +- .../src/main/kotlin/entities/Types.kt | 2 + .../src/main/kotlin/utils/JvmNames.kt | 11 +- 7 files changed, 210 insertions(+), 116 deletions(-) diff --git a/java/kotlin-extractor2/src/main/kotlin/KotlinUsesExtractor.kt b/java/kotlin-extractor2/src/main/kotlin/KotlinUsesExtractor.kt index 9860f21620b..08cc416c06b 100644 --- a/java/kotlin-extractor2/src/main/kotlin/KotlinUsesExtractor.kt +++ b/java/kotlin-extractor2/src/main/kotlin/KotlinUsesExtractor.kt @@ -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) diff --git a/java/kotlin-extractor2/src/main/kotlin/TrapWriter.kt b/java/kotlin-extractor2/src/main/kotlin/TrapWriter.kt index 2a2610c6a09..afe475b774a 100644 --- a/java/kotlin-extractor2/src/main/kotlin/TrapWriter.kt +++ b/java/kotlin-extractor2/src/main/kotlin/TrapWriter.kt @@ -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> = - mutableMapOf>() + private val variableLabelMapping: MutableMap> = + mutableMapOf>() /** This returns the label used for a local variable, creating one if none currently exists. */ - fun getVariableLabelFor(v: KtProperty): Label { + fun getVariableLabelFor(v: KaVariableSymbol): Label { val maybeLabel = variableLabelMapping[v] if (maybeLabel == null) { val label = getFreshIdLabel() diff --git a/java/kotlin-extractor2/src/main/kotlin/entities/Expression.kt b/java/kotlin-extractor2/src/main/kotlin/entities/Expression.kt index b1433350032..1fcbd7966ab 100644 --- a/java/kotlin-extractor2/src/main/kotlin/entities/Expression.kt +++ b/java/kotlin-extractor2/src/main/kotlin/entities/Expression.kt @@ -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) { @@ -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?, - type: TypeResults, - locId: Label, - parent: Label, - idx: Int, - callable: Label, - enclosingStmt: Label - ) = - tw.getFreshIdLabel().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?, + type: TypeResults, + locId: Label, + parent: Label, + idx: Int, + callable: Label, + enclosingStmt: Label +) = + tw.getFreshIdLabel().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?, + type: KaType, + locId: Label, + parent: Label, + idx: Int, + callable: Label, + enclosingStmt: Label +) = + extractVariableAccess( + variable, + useType(type), + locId, + parent, + idx, + callable, + enclosingStmt + ) - private fun extractVariableAccess( - variable: Label?, - irType: IrType, - locId: Label, - parent: Label, - idx: Int, - callable: Label, - enclosingStmt: Label - ) = - 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() 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 { +private fun KotlinFileExtractor.useVariable(v: KaVariableSymbol): Label { return tw.getVariableLabelFor(v) } + +context(KaSession) +fun KotlinFileExtractor.extractReferenceExpression( + ref: KtReferenceExpression, + enclosingCallable: Label, + 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() + } + } +} \ No newline at end of file diff --git a/java/kotlin-extractor2/src/main/kotlin/entities/Function.kt b/java/kotlin-extractor2/src/main/kotlin/entities/Function.kt index ade3e8f0929..4d20a9db76b 100644 --- a/java/kotlin-extractor2/src/main/kotlin/entities/Function.kt +++ b/java/kotlin-extractor2/src/main/kotlin/entities/Function.kt @@ -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, @@ -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 { @@ -674,6 +691,7 @@ private fun KotlinFileExtractor.extractMethod( */ } +context(KaSession) fun KotlinUsesExtractor.useFunction( f: KaFunctionSymbol, parentId: Label, @@ -694,6 +712,7 @@ fun KotlinUsesExtractor.useFunction( return useFunction(f, javaFun, parentId /* TODO , classTypeArgsIncludingOuterClasses */) } +context(KaSession) private fun KotlinUsesExtractor.useFunction( f: KaFunctionSymbol, javaFun: KaFunctionSymbol, diff --git a/java/kotlin-extractor2/src/main/kotlin/entities/MethodCall.kt b/java/kotlin-extractor2/src/main/kotlin/entities/MethodCall.kt index b279ac7fcfc..44e0d7a6058 100644 --- a/java/kotlin-extractor2/src/main/kotlin/entities/MethodCall.kt +++ b/java/kotlin-extractor2/src/main/kotlin/entities/MethodCall.kt @@ -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, 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") diff --git a/java/kotlin-extractor2/src/main/kotlin/entities/Types.kt b/java/kotlin-extractor2/src/main/kotlin/entities/Types.kt index 2ce27692c83..ae98127eef9 100644 --- a/java/kotlin-extractor2/src/main/kotlin/entities/Types.kt +++ b/java/kotlin-extractor2/src/main/kotlin/entities/Types.kt @@ -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() } /* diff --git a/java/kotlin-extractor2/src/main/kotlin/utils/JvmNames.kt b/java/kotlin-extractor2/src/main/kotlin/utils/JvmNames.kt index 84f038172de..0995b13da41 100644 --- a/java/kotlin-extractor2/src/main/kotlin/utils/JvmNames.kt +++ b/java/kotlin-extractor2/src/main/kotlin/utils/JvmNames.kt @@ -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 } -*/