Skip to content

Commit

Permalink
SONARKT-423 Migrate MobileDatabaseEncryptionKeysCheck to kotlin-analy…
Browse files Browse the repository at this point in the history
…sis-api

Co-authored-by: Marharyta Nedzelska <[email protected]>
  • Loading branch information
Godin and leveretka authored Jan 27, 2025
1 parent 88f3368 commit 5b84ed7
Showing 1 changed file with 28 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
package org.sonarsource.kotlin.checks

import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analysis.api.resolution.KaFunctionCall
import org.jetbrains.kotlin.analysis.api.resolution.successfulFunctionCallOrNull
import org.jetbrains.kotlin.analysis.api.resolution.symbol
import org.jetbrains.kotlin.analysis.api.symbols.KaConstructorSymbol
import org.jetbrains.kotlin.analysis.api.symbols.name
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtElement
Expand All @@ -26,26 +31,21 @@ import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.KtStringTemplateExpression
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.psi2ir.deparenthesize
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.sonar.check.Rule
import org.sonarsource.kotlin.api.checks.CallAbstractCheck
import org.sonarsource.kotlin.api.checks.ConstructorMatcher
import org.sonarsource.kotlin.api.checks.FunMatcher
import org.sonarsource.kotlin.api.reporting.SecondaryLocation
import org.sonarsource.kotlin.api.checks.predictRuntimeValueExpression
import org.sonarsource.kotlin.api.reporting.KotlinTextRanges.textRange
import org.sonarsource.kotlin.api.frontend.KotlinFileContext
import org.sonarsource.kotlin.api.reporting.KotlinTextRanges.textRange
import org.sonarsource.kotlin.api.reporting.SecondaryLocation
import org.sonarsource.kotlin.api.visiting.withKaSession

private const val SQLITE = "net.sqlcipher.database.SQLiteDatabase"
private const val ENCRYPTION_KEY = "encryptionKey"
private const val CHANGE_PASSWORD = "changePassword"
private val CREATE_CHAR_BYTE_ARRAY = FunMatcher(qualifier = "kotlin") { withNames("byteArrayOf", "charArrayOf") }

@org.sonarsource.kotlin.api.frontend.K1only
@Rule(key = "S6301")
class MobileDatabaseEncryptionKeysCheck : CallAbstractCheck() {
override val functionsToVisit = listOf(
Expand All @@ -58,60 +58,58 @@ class MobileDatabaseEncryptionKeysCheck : CallAbstractCheck() {

override fun visitFunctionCall(
callExpression: KtCallExpression,
resolvedCall: ResolvedCall<*>,
resolvedCall: KaFunctionCall<*>,
kotlinFileContext: KotlinFileContext
) {
val bindingContext = kotlinFileContext.bindingContext
val functionName = resolvedCall.resultingDescriptor.name.asString()

val valueArgumentsList = resolvedCall.valueArgumentsByIndex
if (valueArgumentsList == null || (valueArgumentsList.size < 2 && functionName != ENCRYPTION_KEY)) return
val symbol = resolvedCall.partiallyAppliedSymbol.symbol
val functionName = if (symbol is KaConstructorSymbol) "<init>" else symbol.name?.asString() ?: return
val valueArgumentsList = resolvedCall.argumentMapping.keys.toList()
if (valueArgumentsList.size < 2 && functionName != ENCRYPTION_KEY) return

val arg = if (functionName in setOf(ENCRYPTION_KEY, CHANGE_PASSWORD)) {
valueArgumentsList[0]
} else valueArgumentsList[1]
val argExpr = (arg as? ExpressionValueArgument)?.valueArgument?.getArgumentExpression() ?: return

val secondaries = mutableListOf<PsiElement>()
val argValueExpr = argExpr.predictRuntimeValueExpression(bindingContext, secondaries)
if (argValueExpr.isHardCoded(bindingContext, secondaries)) {
val argValueExpr = arg.predictRuntimeValueExpression(secondaries)
if (argValueExpr.isHardCoded(secondaries)) {
val parameter = if (functionName == ENCRYPTION_KEY) ENCRYPTION_KEY else "password"

kotlinFileContext.reportIssue(
argExpr,
arg,
"""The "$parameter" parameter should not be hardcoded.""",
secondaries.map { SecondaryLocation(kotlinFileContext.textRange(it)) },
)
}
}
}

private fun KtElement.isHardCoded(bindingContext: BindingContext, secondaries: MutableList<PsiElement>): Boolean =
private fun KtElement.isHardCoded(secondaries: MutableList<PsiElement>): Boolean =
when (this) {
is KtStringTemplateExpression -> true
is KtParenthesizedExpression ->
deparenthesize().apply { secondaries.add(this) }.isHardCoded(bindingContext, secondaries)
deparenthesize().apply { secondaries.add(this) }.isHardCoded(secondaries)
is KtDotQualifiedExpression ->
(selectorExpression?.isHardCoded(bindingContext, secondaries) ?: false)
(selectorExpression?.isHardCoded(secondaries) ?: false)
|| receiverExpression
.predictRuntimeValueExpression(bindingContext, secondaries)
.isHardCoded(bindingContext, secondaries)
.predictRuntimeValueExpression(secondaries)
.isHardCoded(secondaries)
is KtCallExpression ->
if (CREATE_CHAR_BYTE_ARRAY.matches(this, bindingContext)) {
if (CREATE_CHAR_BYTE_ARRAY.matches(this)) {
secondaries.add(calleeExpression!!)
true
} else returnsHardcoded(bindingContext, secondaries)
} else returnsHardcoded(secondaries)
else -> false
}

fun KtCallExpression.returnsHardcoded(bindingContext: BindingContext, secondaries: MutableList<PsiElement>) : Boolean {
val resultingDescriptor = this.getResolvedCall(bindingContext)?.resultingDescriptor ?: return false
val declaration = DescriptorToSourceUtils.descriptorToDeclaration(resultingDescriptor) as? KtNamedFunction ?: return false
fun KtCallExpression.returnsHardcoded(secondaries: MutableList<PsiElement>): Boolean = withKaSession {
val resultingDescriptor = this@returnsHardcoded.resolveToCall()?.successfulFunctionCallOrNull() ?: return false
val declaration = resultingDescriptor.partiallyAppliedSymbol.symbol.psi as? KtNamedFunction ?: return false

if (!declaration.hasBody()) return false
return if (declaration.hasBlockBody()) {
declaration.anyDescendantOfType<KtReturnExpression> {
it.returnedExpression?.isHardCoded(bindingContext, secondaries) ?: false
it.returnedExpression?.isHardCoded(secondaries) ?: false
}
} else declaration.bodyExpression?.isHardCoded(bindingContext, secondaries) ?: false
} else declaration.bodyExpression?.isHardCoded(secondaries) ?: false
}

0 comments on commit 5b84ed7

Please sign in to comment.