From 9263a25a44570f318119a0353071fb4f462f461c Mon Sep 17 00:00:00 2001 From: Nikita Ogorodnikov Date: Wed, 17 Apr 2024 14:14:34 +0200 Subject: [PATCH] RUM-4418: Don't mark internal extension functions for 3rd party types as 3rd party --- detekt_custom.yml | 84 ---------------- .../rules/AbstractCallExpressionRule.kt | 20 ++-- .../rules/sdk/UnsafeThirdPartyFunctionCall.kt | 11 +-- .../sdk/UnsafeThirdPartyFunctionCallTest.kt | 95 +++++++++++++++++-- 4 files changed, 108 insertions(+), 102 deletions(-) diff --git a/detekt_custom.yml b/detekt_custom.yml index 5a4777922d..6098b3e77d 100644 --- a/detekt_custom.yml +++ b/detekt_custom.yml @@ -313,9 +313,6 @@ datadog: # endregion knownSafeCalls: # region Android APIs - - "android.app.Activity.findNavControllerFromNavHostFragmentOrNull(kotlin.Int)" - - "android.app.Activity.findNavControllerOrNull()" - - "android.app.Activity.findNavControllerOrNull(kotlin.Int)" - "android.app.Activity.getSystemService(kotlin.String)" - "android.app.Activity.hashCode()" - "android.app.Application.registerActivityLifecycleCallbacks(android.app.Application.ActivityLifecycleCallbacks?)" @@ -421,24 +418,13 @@ datadog: - "android.webkit.WebViewClient.onReceivedSslError(android.webkit.WebView, android.webkit.SslErrorHandler, android.net.http.SslError)" # endregion # region Android View APIs - - "android.view.View.targetClassName()" - "android.widget.FrameLayout.LayoutParams.constructor(kotlin.Int, kotlin.Int)" - "android.widget.FrameLayout.removeView(android.view.View?)" - "android.widget.ImageView.getScaleType()" - "android.widget.LinearLayout.constructor(android.content.Context?)" - "android.widget.LinearLayout.post(java.lang.Runnable?)" - "android.widget.LinearLayout.removeAllViews()" - - "android.widget.NumberPicker.getNextIndex()" - - "android.widget.NumberPicker.getPrevIndex()" - - "android.widget.SeekBar.getDefaultColor()" - - "android.widget.SeekBar.getDefaultColor()" - - "android.widget.SeekBar.getThumbColor()" - - "android.widget.SeekBar.getTrackColor()" - - "android.widget.SeekBar.normalizedValue()" - - "android.widget.SeekBar.trackLeftPadding(kotlin.Float)" - "android.widget.TextView.constructor(android.content.Context?)" - - "android.widget.TextView.isInputText()" - - "android.widget.TextView.isPrivacySensitive()" - "android.widget.TextView.setBackgroundColor(kotlin.Int)" - "android.widget.TextView.setPadding(kotlin.Int)" - "android.widget.TextView.setPadding(kotlin.Int, kotlin.Int, kotlin.Int, kotlin.Int)" @@ -458,8 +444,6 @@ datadog: - "android.graphics.drawable.Drawable.getPadding(android.graphics.Rect)" - "android.graphics.drawable.Drawable.resolveShapeStyleAndBorder(kotlin.Float)" - "android.graphics.drawable.Drawable.setBounds(kotlin.Int, kotlin.Int, kotlin.Int, kotlin.Int)" - - "android.graphics.drawable.LayerDrawable.safeGetDrawable(kotlin.Int, com.datadog.android.api.InternalLogger)" - - "android.graphics.drawable.RippleDrawable.safeGetDrawable(kotlin.Int, com.datadog.android.api.InternalLogger)" - "android.graphics.drawable.RippleDrawable.findIndexByLayerId(kotlin.Int)" - "android.graphics.Point.constructor()" - "android.graphics.Rect.centerX()" @@ -481,7 +465,6 @@ datadog: - "androidx.compose.runtime.rememberUpdatedState(kotlin.Function0)" - "androidx.core.view.GestureDetectorCompat.constructor(android.content.Context?, android.view.GestureDetector.OnGestureListener?)" - "androidx.core.view.GestureDetectorCompat.onTouchEvent(android.view.MotionEvent?)" - - "androidx.fragment.app.DialogFragment.getWindowsToRecord()" - "androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks.onFragmentActivityCreated(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.os.Bundle?)" - "androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks.onFragmentAttached(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment, android.content.Context)" - "androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks.onFragmentDestroyed(androidx.fragment.app.FragmentManager, androidx.fragment.app.Fragment)" @@ -492,14 +475,10 @@ datadog: - "androidx.fragment.app.FragmentManager.findFragmentById(kotlin.Int)" - "androidx.fragment.app.FragmentManager.registerFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks, kotlin.Boolean)" - "androidx.fragment.app.FragmentManager.unregisterFragmentLifecycleCallbacks(androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks)" - - "androidx.leanback.widget.ItemBridgeAdapter.ViewHolder.action()" - "androidx.lifecycle.Lifecycle.addObserver(androidx.lifecycle.LifecycleObserver)" - "androidx.lifecycle.Lifecycle.removeObserver(androidx.lifecycle.LifecycleObserver)" - "androidx.navigation.NavController.addOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener)" - "androidx.navigation.NavController.removeOnDestinationChangedListener(androidx.navigation.NavController.OnDestinationChangedListener)" - - "androidx.recyclerview.widget.RecyclerView.ViewHolder.isBridgeAdapterViewHolder()" - - "androidx.recyclerview.widget.RecyclerView.findAction(android.view.View)" - - "androidx.recyclerview.widget.RecyclerView.findActionViewHolder(android.view.View)" - "androidx.recyclerview.widget.RecyclerView.findContainingViewHolder(android.view.View)" - "androidx.recyclerview.widget.RecyclerView.getChildAdapterPosition(android.view.View)" - "androidx.work.Constraints.Builder()" @@ -519,8 +498,6 @@ datadog: - "androidx.work.WorkManager.isInitialized()" # endregion # region Google Material - - "com.google.android.material.slider.Slider.normalizedValue()" - - "com.google.android.material.slider.Slider.trackLeftPadding(kotlin.Float)" - "com.google.android.material.tabs.TabLayout.TabView.getChildAt(kotlin.Int)" # endregion # region Glide @@ -546,8 +523,6 @@ datadog: - "com.google.gson.JsonArray.lastOrNull()" - "com.google.gson.JsonArray.map(kotlin.Function1)" - "com.google.gson.JsonArray.size()" - - "com.google.gson.JsonElement.safeGetAsJsonArray(com.datadog.android.api.InternalLogger)" - - "com.google.gson.JsonElement.safeGetAsJsonObject(com.datadog.android.api.InternalLogger)" - "com.google.gson.JsonObject.add(kotlin.String?, com.google.gson.JsonElement?)" - "com.google.gson.JsonObject.addProperty(kotlin.String?, kotlin.Number?)" - "com.google.gson.JsonObject.addProperty(kotlin.String?, kotlin.String?)" @@ -557,12 +532,7 @@ datadog: - "com.google.gson.JsonObject.getAsJsonObject(kotlin.String?)" - "com.google.gson.JsonObject.getAsJsonPrimitive(kotlin.String?)" - "com.google.gson.JsonObject.has(kotlin.String?)" - - "com.google.gson.JsonObject.records()" - "com.google.gson.JsonObject.remove(kotlin.String?)" - - "com.google.gson.JsonObject.rumContext()" - - "com.google.gson.JsonObject.timestamp()" - - "com.google.gson.JsonObject.timestamp()" - - "com.google.gson.JsonObject.timestamp()" - "com.google.gson.JsonObject.toString()" - "com.google.gson.JsonParseException.constructor(kotlin.String?)" - "com.google.gson.JsonParseException.constructor(kotlin.String?, kotlin.Throwable?)" @@ -570,8 +540,6 @@ datadog: - "com.google.gson.JsonPrimitive.constructor(kotlin.Boolean?)" - "com.google.gson.JsonPrimitive.constructor(kotlin.Number?)" - "com.google.gson.JsonPrimitive.constructor(kotlin.String?)" - - "com.google.gson.JsonPrimitive.safeGetAsLong(com.datadog.android.api.InternalLogger)" - - "com.google.gson.JsonPrimitive.safeGetAsLong(com.datadog.android.api.InternalLogger)" # endregion # region Java Collections - "java.util.ArrayList.forEach(kotlin.Function1)" @@ -626,24 +594,17 @@ datadog: - "java.util.concurrent.CopyOnWriteArraySet.remove(kotlin.String?)" - "java.util.concurrent.CopyOnWriteArraySet.toTypedArray()" - "java.util.concurrent.CountDownLatch.countDown()" - - "java.util.concurrent.ExecutorService.executeSafe(kotlin.String, com.datadog.android.api.InternalLogger, java.lang.Runnable)" - "java.util.concurrent.ExecutorService.shutdown()" - "java.util.concurrent.ExecutorService.shutdownNow()" - - "java.util.concurrent.ExecutorService.submitSafe(kotlin.String, com.datadog.android.api.InternalLogger, java.lang.Runnable)" - "java.util.concurrent.Executors.newSingleThreadExecutor()" - "java.util.concurrent.LinkedBlockingDeque.constructor()" - "java.util.concurrent.RejectedExecutionHandler(kotlin.Function2)" - - "java.util.concurrent.ScheduledExecutorService.scheduleSafe(kotlin.String, kotlin.Long, java.util.concurrent.TimeUnit, com.datadog.android.api.InternalLogger, java.lang.Runnable)" - "java.util.concurrent.ScheduledExecutorService.shutdownNow()" - "java.util.concurrent.ScheduledThreadPoolExecutor.afterExecute(java.lang.Runnable?, kotlin.Throwable?)" - "java.util.concurrent.ScheduledThreadPoolExecutor.remove(java.lang.Runnable?)" - - "java.util.concurrent.ScheduledThreadPoolExecutor.scheduleSafe(kotlin.String, kotlin.Long, java.util.concurrent.TimeUnit, com.datadog.android.api.InternalLogger, java.lang.Runnable)" - "java.util.concurrent.ScheduledThreadPoolExecutor.shutdown()" - "java.util.concurrent.ScheduledThreadPoolExecutor.shutdownNow()" - "java.util.concurrent.ThreadPoolExecutor.afterExecute(java.lang.Runnable?, kotlin.Throwable?)" - - "java.util.concurrent.ThreadPoolExecutor.isIdle()" - - "java.util.concurrent.ThreadPoolExecutor.waitToIdle(kotlin.Long)" - - "java.util.concurrent.ThreadPoolExecutor.waitToIdle(kotlin.Long, com.datadog.android.api.InternalLogger)" - "java.util.concurrent.TimeUnit.HOURS.toMillis(kotlin.Long)" - "java.util.concurrent.TimeUnit.HOURS.toNanos(kotlin.Long)" - "java.util.concurrent.TimeUnit.MICROSECONDS.toMillis(kotlin.Long)" @@ -692,35 +653,8 @@ datadog: - "java.io.ByteArrayOutputStream.size()" - "java.io.ByteArrayOutputStream.toByteArray()" - "java.io.ByteArrayOutputStream.use(kotlin.Function1)" - - "java.io.File.canReadSafe(com.datadog.android.api.InternalLogger)" - - "java.io.File.canWriteSafe(com.datadog.android.api.InternalLogger)" - "java.io.File.constructor(java.io.File?, kotlin.String?)" - "java.io.File.constructor(kotlin.String?)" - - "java.io.File.deleteSafe(com.datadog.android.api.InternalLogger)" - - "java.io.File.existsSafe(com.datadog.android.api.InternalLogger)" - - "java.io.File.extractFileId()" - - "java.io.File.extractFileId()" - - "java.io.File.isDirectorySafe(com.datadog.android.api.InternalLogger)" - - "java.io.File.isFileSafe(com.datadog.android.api.InternalLogger)" - - "java.io.File.lengthSafe(com.datadog.android.api.InternalLogger)" - - "java.io.File.listFilesSafe(com.datadog.android.api.InternalLogger)" - - "java.io.File.listFilesSafe(java.io.FileFilter, com.datadog.android.api.InternalLogger)" - - "java.io.File.mkdirsSafe(com.datadog.android.api.InternalLogger)" - - "java.io.File.nameAsTimestampSafe(com.datadog.android.api.InternalLogger)" - - "java.io.File.nameAsTimestampSafe(com.datadog.android.api.InternalLogger)" - - "java.io.File.readLinesSafe(java.nio.charset.Charset, com.datadog.android.api.InternalLogger)" - - "java.io.File.readTextSafe(java.nio.charset.Charset, com.datadog.android.api.InternalLogger)" - - "java.io.File.renameToSafe(java.io.File, com.datadog.android.api.InternalLogger)" - - "java.io.File.resolveFileOriginAsConsent()" - - "java.io.File.resolveFileOriginAsConsent()" - - "java.io.File.safeCall(kotlin.Array?, com.datadog.android.api.InternalLogger, kotlin.Function1)" - - "java.io.File.safeCall(kotlin.Boolean, com.datadog.android.api.InternalLogger, kotlin.Function1)" - - "java.io.File.safeCall(kotlin.ByteArray?, com.datadog.android.api.InternalLogger, kotlin.Function1)" - - "java.io.File.safeCall(kotlin.Long, com.datadog.android.api.InternalLogger, kotlin.Function1)" - - "java.io.File.safeCall(kotlin.String?, com.datadog.android.api.InternalLogger, kotlin.Function1)" - - "java.io.File.safeCall(kotlin.collections.List?, com.datadog.android.api.InternalLogger, kotlin.Function1)" - - "java.io.File.safeCall(kotlin.Unit?, com.datadog.android.api.InternalLogger, kotlin.Function1)" - - "java.io.File.writeTextSafe(kotlin.String, java.nio.charset.Charset, com.datadog.android.api.InternalLogger)" - "java.io.InputStream.bufferedReader(java.nio.charset.Charset)" - "java.io.InputStream.mark(kotlin.Int)" - "java.io.InputStream.markSupported()" @@ -756,7 +690,6 @@ datadog: - "java.lang.ref.WeakReference.constructor(kotlin.Nothing?)" - "java.lang.ref.WeakReference.constructor(kotlin.String?)" - "java.lang.ref.WeakReference.get()" - - "java.math.BigInteger.toHexString()" - "java.math.BigInteger.toLong()" - "java.nio.charset.Charset.defaultCharset()" - "java.security.MessageDigest.digest()" @@ -790,7 +723,6 @@ datadog: - "java.util.zip.Deflater.setInput(kotlin.ByteArray?)" # endregion # region Kotlin Stdlib - - "kotlin.Throwable.loggableStackTrace()" - "kotlin.lazy(kotlin.Function0)" - "kotlin.lazy(kotlin.LazyThreadSafetyMode, kotlin.Function0)" - "kotlin.repeat(kotlin.Int, kotlin.Function1)" @@ -810,7 +742,6 @@ datadog: - "kotlin.Array.forEachIndexed(kotlin.Function2)" - "kotlin.Array.isNotEmpty()" - "kotlin.Array.joinToString(kotlin.CharSequence, kotlin.CharSequence, kotlin.CharSequence, kotlin.Int, kotlin.CharSequence, kotlin.Function1?)" - - "kotlin.Array.loggableStackTrace()" - "kotlin.Array.none(kotlin.Function1)" - "kotlin.Array.orEmpty()" - "kotlin.Array.sorted()" @@ -868,7 +799,6 @@ datadog: - "kotlin.collections.List.ifEmpty(kotlin.Function0)" - "kotlin.collections.List.isEmpty()" - "kotlin.collections.List.isNotEmpty()" - - "kotlin.collections.List.join(kotlin.ByteArray, kotlin.ByteArray, kotlin.ByteArray, com.datadog.android.api.InternalLogger)" - "kotlin.collections.List.joinToString(kotlin.CharSequence, kotlin.CharSequence, kotlin.CharSequence, kotlin.Int, kotlin.CharSequence, kotlin.Function1?)" - "kotlin.collections.List.lastOrNull()" - "kotlin.collections.List.lastOrNull(kotlin.Function1)" @@ -905,7 +835,6 @@ datadog: - "kotlin.collections.Map.mapNotNull(kotlin.Function1)" - "kotlin.collections.Map.mapValues(kotlin.Function1)" - "kotlin.collections.Map.orEmpty()" - - "kotlin.collections.Map.safeMapValuesToJson(com.datadog.android.api.InternalLogger)" - "kotlin.collections.Map.toJsonObject()" - "kotlin.collections.Map.toMap()" - "kotlin.collections.Map.toMap()" @@ -981,7 +910,6 @@ datadog: - "kotlin.collections.MutableMap.remove(kotlin.Int)" - "kotlin.collections.MutableMap.remove(kotlin.Long)" - "kotlin.collections.MutableMap.remove(kotlin.String)" - - "kotlin.collections.MutableMap.safeMapValuesToJson(com.datadog.android.api.InternalLogger)" - "kotlin.collections.MutableMap.toMap()" - "kotlin.collections.MutableMap.toMutableMap()" - "kotlin.collections.MutableMap?.forEach(kotlin.Function1)" @@ -1060,7 +988,6 @@ datadog: - "kotlin.Boolean.hashCode()" - "kotlin.Byte.toInt()" - "kotlin.ByteArray.constructor(kotlin.Int)" - - "kotlin.ByteArray.copyTo(kotlin.Int, kotlin.ByteArray, kotlin.Int, kotlin.Int, com.datadog.android.api.InternalLogger)" - "kotlin.Char.isLowerCase()" - "kotlin.Char.titlecase(java.util.Locale)" - "kotlin.CharArray.constructor(kotlin.Int, kotlin.Function1)" @@ -1071,29 +998,23 @@ datadog: - "kotlin.Double.toFloat()" - "kotlin.Double.toInt()" - "kotlin.Double.toLong()" - - "kotlin.Float.percent()" - "kotlin.Float.toDouble()" - "kotlin.Float.toFloat()" - "kotlin.Float.toInt()" - "kotlin.Float.toLong()" - "kotlin.Int.and(kotlin.Int)" - - "kotlin.Int.densityNormalized(kotlin.Float)" - "kotlin.Int.inv()" - "kotlin.Int.toChar()" - "kotlin.Int.toDouble()" - "kotlin.Int.toFloat()" - - "kotlin.Int.toHexString()" - "kotlin.Int.toLong()" - "kotlin.IntArray.constructor(kotlin.Int)" - "kotlin.IntArray.joinToString(kotlin.CharSequence, kotlin.CharSequence, kotlin.CharSequence, kotlin.Int, kotlin.CharSequence, kotlin.Function1?)" - - "kotlin.Long.asTime()" - "kotlin.Long.coerceIn(kotlin.Long, kotlin.Long)" - - "kotlin.Long.densityNormalized(kotlin.Float)" - "kotlin.Long.hashCode()" - "kotlin.Long.or(kotlin.Long)" - "kotlin.Long.shl(kotlin.Int)" - "kotlin.Long.toDouble()" - - "kotlin.Long.toHexString()" - "kotlin.Long.toInt()" - "kotlin.LongArray.constructor(kotlin.Int)" - "kotlin.Number.toFloat()" @@ -1162,11 +1083,9 @@ datadog: - "kotlin.String.toLongOrNull()" - "kotlin.String.toMediaTypeOrNull()" - "kotlin.String.toMethod()" - - "kotlin.String.toOperationType(com.datadog.android.api.InternalLogger)" - "kotlin.String.trim()" - "kotlin.String.trimStart()" - "kotlin.String.uppercase(java.util.Locale)" - - "kotlin.String.withSdkName()" - "kotlin.text.String(kotlin.ByteArray)" - "kotlin.text.String(kotlin.ByteArray, java.nio.charset.Charset)" - "kotlin.text.String(kotlin.CharArray)" @@ -1174,7 +1093,6 @@ datadog: - "kotlin.text.buildString(kotlin.Function1)" # endregion # region Kotlin Misc - - "kotlin.Any.resolveViewUrl()" - "kotlin.IllegalArgumentException(kotlin.String?)" - "kotlin.IllegalStateException(kotlin.String?)" - "kotlin.Throwable.constructor()" @@ -1256,11 +1174,9 @@ datadog: - "okhttp3.RequestBody.contentLength()" - "okhttp3.RequestBody.contentType()" - "okhttp3.RequestBody.create(okhttp3.MediaType?, kotlin.ByteArray)" - - "okhttp3.RequestBody.toParts()" - "okhttp3.Response.code()" - "okhttp3.Response.header(kotlin.String, kotlin.String?)" - "okhttp3.ResponseBody.contentLength()" - - "okhttp3.ResponseBody.contentLengthOrNull()" - "okio.Buffer.constructor()" # endregion # region org.json diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/AbstractCallExpressionRule.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/AbstractCallExpressionRule.kt index 9b3291ec8b..067030968a 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/AbstractCallExpressionRule.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/AbstractCallExpressionRule.kt @@ -16,6 +16,7 @@ import org.jetbrains.kotlin.descriptors.containingPackage import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.calls.util.getCall import org.jetbrains.kotlin.resolve.calls.util.getImplicitReceiverValue import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall import org.jetbrains.kotlin.resolve.calls.util.getType @@ -46,12 +47,14 @@ abstract class AbstractCallExpressionRule( * @param call the call signature * @param containerFqName the fully qualified named of the container (class or package) * @param functionName the function name without prefix + * @param containingPackage the package where the function is declared * @param arguments the list of argument types */ data class ResolvedFunCall( val call: String, val containerFqName: String, val functionName: String, + val containingPackage: String, val arguments: List ) @@ -64,7 +67,11 @@ abstract class AbstractCallExpressionRule( return } - val resolvedCall = expression.getResolvedCall(bindingContext) ?: return + val resolvedCall = expression.getResolvedCall(bindingContext) + if (resolvedCall == null) { + println("Cannot resolve call for ${expression.getCall(bindingContext)}. Is classpath complete?") + return + } val call = resolvedCall.call val returnType = expression.getType(bindingContext) ?.fqTypeName(treatGenericAsSuper, includeTypeArguments) @@ -86,7 +93,7 @@ abstract class AbstractCallExpressionRule( val calleeName = call.calleeExpression?.node?.text ?: "UNKNOWNFUN" val callContainingPackage = callDescriptor.containingPackage()?.toString().orEmpty() if (receiverType == null) { - "$callContainingPackage" to calleeName + callContainingPackage to calleeName } else { "$receiverType" to calleeName } @@ -94,10 +101,11 @@ abstract class AbstractCallExpressionRule( val arguments = resolveParameterTypes(resolvedCall, containerFqName) val resolvedFunctionCall = ResolvedFunCall( - "$containerFqName.$functionName(${arguments.joinToString(", ")})", - containerFqName, - functionName, - arguments + call = "$containerFqName.$functionName(${arguments.joinToString(", ")})", + containerFqName = containerFqName, + functionName = functionName, + containingPackage = callDescriptor.containingPackage()?.toString().orEmpty(), + arguments = arguments ) visitResolvedFunctionCall(expression, resolvedFunctionCall) diff --git a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCall.kt b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCall.kt index c2b9ec5bb3..2f1eabea52 100644 --- a/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCall.kt +++ b/tools/detekt/src/main/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCall.kt @@ -43,7 +43,7 @@ class UnsafeThirdPartyFunctionCall( val splitColon = it.split(':') val key = splitColon.first() if (splitColon.size == 1) { - println("✘ ERROR WHITH KNOWN THROWING CALL: $it") + println("✘ ERROR WITH KNOWN THROWING CALL: $it") } val exceptions = splitColon[1].split(',').toList() key to exceptions @@ -82,12 +82,11 @@ class UnsafeThirdPartyFunctionCall( expression: KtCallExpression, resolvedCall: ResolvedFunCall ) { - if (internalPackagePrefix.isNotEmpty() && - resolvedCall.containerFqName.startsWith(internalPackagePrefix) - ) { - return + if (internalPackagePrefix.isNotEmpty()) { + val belongsToInternalContainer = resolvedCall.containerFqName.startsWith(internalPackagePrefix) || + resolvedCall.containingPackage.startsWith(internalPackagePrefix) + if (belongsToInternalContainer) return } - if (resolvedCall.functionName in kotlinHelperMethods) { return } diff --git a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCallTest.kt b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCallTest.kt index 7f2d336015..7cfb41f44d 100644 --- a/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCallTest.kt +++ b/tools/detekt/src/test/kotlin/com/datadog/tools/detekt/rules/sdk/UnsafeThirdPartyFunctionCallTest.kt @@ -82,6 +82,36 @@ internal class UnsafeThirdPartyFunctionCallTest { assertThat(findings).hasSize(1) } + @Test + fun `ignore call on internal package extension function`() { + // Given + val config = TestConfig( + mapOf("internalPackagePrefix" to "com.datadog") + ) + + @Suppress("UnusedReceiverParameter") + val code = + """ + package com.datadog.utils + + import java.io.File + + fun File.extensionFunction(): Any = "42" + + fun test(f: File): Any { + return f.extensionFunction() + } + + """.trimIndent() + + // When + val findings = UnsafeThirdPartyFunctionCall(config) + .compileAndLintWithContext(kotlinEnv.env, code) + + // Then + assertThat(findings).hasSize(0) + } + @Test fun `detekt unsafe call on unknown third party function`() { // Given @@ -102,6 +132,31 @@ internal class UnsafeThirdPartyFunctionCallTest { assertThat(findings).hasSize(1) } + @Test + fun `detekt unsafe call on unknown third party extension function`() { + // Given + val config = TestConfig( + mapOf("internalPackagePrefix" to "com.datadog") + ) + val code = + """ + package com.datadog + + import java.io.File + + fun test(f: File): Any { + return f.readText() + } + """.trimIndent() + + // When + val findings = UnsafeThirdPartyFunctionCall(config) + .compileAndLintWithContext(kotlinEnv.env, code) + + // Then + assertThat(findings).hasSize(1) + } + @Test fun `detekt unsafe call on unknown third party function { implicit receiver }`() { // Given @@ -206,6 +261,31 @@ internal class UnsafeThirdPartyFunctionCallTest { assertThat(findings).hasSize(1) } + @Test + fun `detekt call with catch on unknown third party function`() { + // Given + val code = + """ + import java.io.File + import java.lang.ArrayIndexOutOfBoundsException + + fun test(f: File): Any { + try { + return f.inputStream() + } catch (e: ArrayIndexOutOfBoundsException) { + return null + } + } + """.trimIndent() + + // When + val findings = UnsafeThirdPartyFunctionCall(TestConfig()) + .compileAndLintWithContext(kotlinEnv.env, code) + + // Then + assertThat(findings).hasSize(1) + } + @Test fun `ignores safe call on known third party function`() { // Given @@ -524,23 +604,26 @@ internal class UnsafeThirdPartyFunctionCallTest { @Test fun `ignore kotlin helper calls { with + run + also + println }`() { // Given + val config = TestConfig( + mapOf("knownSafeCalls" to "java.io.File.readBytes()") + ) val code = """ import java.io.File - fun test(f: File?): Any { - with(file) { - this.run { - this.readBytes().also { + fun test(f: File?): Any? { + return with(f) { + this?.run { + this.readBytes().also { println(it) } } - } + } } """.trimIndent() // When - val findings = UnsafeThirdPartyFunctionCall(Config.empty) + val findings = UnsafeThirdPartyFunctionCall(config) .compileAndLintWithContext(kotlinEnv.env, code) // Then