Repository: SonarSource/sonar-kotlin
Pull request #588: SONARKT-595 Rule S7416: Android production release targets should not be debuggable
Author: login: antonioaversa
Created: 2025-03-13T16:22:53Z | Merged: 2025-03-19T14:01:06Z

[SONARKT-595](https://sonarsource.atlassian.net/browse/SONARKT-595)



[SONARKT-595]: https://sonarsource.atlassian.net/browse/SONARKT-595?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ


Diff:
diff --git a/sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/KotlinGradleCheckList.kt b/sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/KotlinGradleCheckList.kt
index 55492ff2a..efe0953fc 100644
--- a/sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/KotlinGradleCheckList.kt
+++ b/sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/KotlinGradleCheckList.kt
@@ -17,6 +17,7 @@
 package org.sonarsource.kotlin.gradle
 
 import org.sonarsource.kotlin.api.checks.KotlinCheck
+import org.sonarsource.kotlin.gradle.checks.AndroidReleaseBuildDebugCheck
 import org.sonarsource.kotlin.gradle.checks.AndroidReleaseBuildObfuscationCheck
 import org.sonarsource.kotlin.gradle.checks.CorePluginsShortcutUsageCheck
 import org.sonarsource.kotlin.gradle.checks.DependencyGroupedCheck
@@ -29,6 +30,7 @@ import org.sonarsource.kotlin.gradle.checks.TaskRegisterVsCreateCheck
 
 
 val KOTLIN_GRADLE_CHECKS: List<Class<out KotlinCheck>> = listOf(
+    AndroidReleaseBuildDebugCheck::class.java,
     AndroidReleaseBuildObfuscationCheck::class.java,
     CorePluginsShortcutUsageCheck::class.java,
     RootProjectNamePresentCheck::class.java,
diff --git a/sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildDebugCheck.kt b/sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildDebugCheck.kt
new file mode 100644
index 000000000..4e9f3c590
--- /dev/null
+++ b/sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildDebugCheck.kt
@@ -0,0 +1,48 @@
+/*
+ * SonarSource Kotlin
+ * Copyright (C) 2018-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonarsource.kotlin.gradle.checks
+
+import org.jetbrains.kotlin.psi.KtScriptInitializer
+import org.sonar.check.Rule
+import org.sonarsource.kotlin.api.checks.AbstractCheck
+import org.sonarsource.kotlin.api.checks.predictRuntimeBooleanValue
+import org.sonarsource.kotlin.api.frontend.KotlinFileContext
+
+private const val message = "Make sure this debug feature is deactivated before delivering the code in production."
+
+@Rule(key = "S7416")
+class AndroidReleaseBuildDebugCheck : AbstractCheck() {
+
+    override fun visitScriptInitializer(initializer: KtScriptInitializer, data: KotlinFileContext) {
+        !data.ktFile.isSettingGradleKts() || return
+        val androidLambda = initializer.getChildCallWithLambdaOrNull("android")?.lambda ?: return
+
+        // Ensure the project is an Android app, and not a library
+        androidLambda.getApplicationId() ?: return
+
+        val buildTypes = androidLambda.getChildCallWithLambdaOrNull("buildTypes") ?: return
+        val (_, releaseLambda) = buildTypes.lambda.getChildCallWithLambdaOrNull("release")
+            ?: androidLambda.getGetByNameCallWithLambdaOrNull()
+            ?: return
+
+        val isDebuggableAssignment = releaseLambda.getPropertyAssignmentOrNull("isDebuggable")
+
+        if (isDebuggableAssignment?.right?.predictRuntimeBooleanValue() == true) {
+            data.reportIssue(isDebuggableAssignment, message)
+        }
+    }
+}
diff --git a/sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildObfuscationCheck.kt b/sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildObfuscationCheck.kt
index 3f6606187..a77344c63 100644
--- a/sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildObfuscationCheck.kt
+++ b/sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildObfuscationCheck.kt
@@ -16,27 +16,14 @@
  */
 package org.sonarsource.kotlin.gradle.checks
 
-import com.intellij.psi.util.childrenOfType
-import com.intellij.psi.util.descendantsOfType
-import org.jetbrains.kotlin.lexer.KtTokens
-import org.jetbrains.kotlin.psi.KtBinaryExpression
-import org.jetbrains.kotlin.psi.KtCallExpression
-import org.jetbrains.kotlin.psi.KtElement
-import org.jetbrains.kotlin.psi.KtExpression
-import org.jetbrains.kotlin.psi.KtFile
-import org.jetbrains.kotlin.psi.KtFunctionLiteral
-import org.jetbrains.kotlin.psi.KtLambdaExpression
 import org.jetbrains.kotlin.psi.KtScriptInitializer
-import org.jetbrains.kotlin.psi.KtValueArgument
 import org.sonar.check.Rule
 import org.sonarsource.kotlin.api.checks.AbstractCheck
 import org.sonarsource.kotlin.api.checks.predictRuntimeBooleanValue
-import org.sonarsource.kotlin.api.checks.predictRuntimeStringValue
 import org.sonarsource.kotlin.api.frontend.KotlinFileContext
 import org.sonarsource.kotlin.api.reporting.KotlinTextRanges.textRange
 import org.sonarsource.kotlin.api.reporting.SecondaryLocation
 
-private const val settingsGradleFileName = "settings.gradle.kts"
 private const val mainMessage = "Make sure that obfuscation is enabled in the release build configuration."
 private const val debuggableSetToTrueMessage = "Enabling debugging disables obfuscation for this release build. Make sure this is safe here."
 
@@ -48,11 +35,7 @@ class AndroidReleaseBuildObfuscationCheck : AbstractCheck() {
         val (androidCallee, androidLambda) = initializer.getChildCallWithLambdaOrNull("android") ?: return
 
         // Ensure the project is an Android app, and not a library
-        androidLambda
-            .getChildCallWithLambdaOrNull("defaultConfig")
-            ?.lambda
-            ?.getPropertyAssignmentOrNull("applicationId")
-            ?: return
+        androidLambda.getApplicationId() ?: return
 
         val buildTypes = androidLambda.getChildCallWithLambdaOrNull("buildTypes")
         if (buildTypes == null) {
@@ -82,40 +65,4 @@ class AndroidReleaseBuildObfuscationCheck : AbstractCheck() {
                 data.reportIssue(releaseCallee, mainMessage)
         }
     }
-
-    private fun KtFile.isSettingGradleKts() = name.endsWith(settingsGradleFileName, ignoreCase = true)
-
-    private fun KtElement.getChildCallOrNull(childCallName: String): KtCallExpression? =
-        descendantsOfType<KtCallExpression>().firstOrNull { it.calleeExpression?.text == childCallName }
-
-    private fun KtElement.getChildCallWithLambdaOrNull(childCallName: String): CalleeAndLambda? {
-        val callee = getChildCallOrNull(childCallName) ?: return null
-        val lambda = callee.functionLiteralArgumentOrNull() ?: return null
-        return CalleeAndLambda(callee.calleeExpression!!, lambda)
-    }
-
-    private fun KtElement.getGetByNameCallWithLambdaOrNull(): CalleeAndLambda? {
-        val callee = descendantsOfType<KtCallExpression>().firstOrNull {
-            it.calleeExpression?.text == "getByName" &&
-                it.valueArguments.size == 2 &&
-                it.valueArguments[0].isReleaseBuildType()
-        } ?: return null
-        val lambda = callee.functionLiteralArgumentOrNull() ?: return null
-        return CalleeAndLambda(callee.calleeExpression!!, lambda)
-    }
-
-    private fun KtValueArgument.isReleaseBuildType(): Boolean =
-        textMatches("BuildType.RELEASE") ||
-        getArgumentExpression()?.predictRuntimeStringValue() == "release"
-
-    private fun KtCallExpression.functionLiteralArgumentOrNull(): KtFunctionLiteral? =
-        valueArguments
-            .flatMap { it.childrenOfType<KtLambdaExpression>() }
-            .flatMap { it.childrenOfType<KtFunctionLiteral>() }
-            .singleOrNull()
-
-    private fun KtElement.getPropertyAssignmentOrNull(propertyName: String): KtBinaryExpression? =
-        descendantsOfType<KtBinaryExpression>().firstOrNull { it.operationToken == KtTokens.EQ && it.left?.text == propertyName }
-
-    private data class CalleeAndLambda(val callee: KtExpression, val lambda: KtFunctionLiteral)
 }
diff --git a/sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/androidReleaseBuildUtils.kt b/sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/androidReleaseBuildUtils.kt
new file mode 100644
index 000000000..9d7835d8b
--- /dev/null
+++ b/sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/androidReleaseBuildUtils.kt
@@ -0,0 +1,74 @@
+/*
+ * SonarSource Kotlin
+ * Copyright (C) 2018-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonarsource.kotlin.gradle.checks
+
+import com.intellij.psi.util.childrenOfType
+import com.intellij.psi.util.descendantsOfType
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.KtBinaryExpression
+import org.jetbrains.kotlin.psi.KtCallExpression
+import org.jetbrains.kotlin.psi.KtElement
+import org.jetbrains.kotlin.psi.KtExpression
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.psi.KtFunctionLiteral
+import org.jetbrains.kotlin.psi.KtLambdaExpression
+import org.jetbrains.kotlin.psi.KtValueArgument
+import org.sonarsource.kotlin.api.checks.predictRuntimeStringValue
+
+private const val settingsGradleFileName = "settings.gradle.kts"
+
+internal fun KtFile.isSettingGradleKts() = name.endsWith(settingsGradleFileName, ignoreCase = true)
+
+internal fun KtFunctionLiteral.getApplicationId() =
+        getChildCallWithLambdaOrNull("defaultConfig")
+        ?.lambda
+        ?.getPropertyAssignmentOrNull("applicationId")
+
+internal fun KtElement.getChildCallOrNull(childCallName: String): KtCallExpression? =
+    descendantsOfType<KtCallExpression>().firstOrNull { it.calleeExpression?.text == childCallName }
+
+internal fun KtElement.getChildCallWithLambdaOrNull(childCallName: String): CalleeAndLambda? {
+    val callee = getChildCallOrNull(childCallName) ?: return null
+    val lambda = callee.functionLiteralArgumentOrNull() ?: return null
+    return CalleeAndLambda(callee.calleeExpression!!, lambda)
+}
+
+internal fun KtElement.getGetByNameCallWithLambdaOrNull(): CalleeAndLambda? {
+    val callee = descendantsOfType<KtCallExpression>().firstOrNull {
+        it.calleeExpression?.text == "getByName" &&
+            it.valueArguments.size == 2 &&
+            it.valueArguments[0].isReleaseBuildType()
+    } ?: return null
+    val lambda = callee.functionLiteralArgumentOrNull() ?: return null
+    return CalleeAndLambda(callee.calleeExpression!!, lambda)
+}
+
+internal fun KtValueArgument.isReleaseBuildType(): Boolean =
+    textMatches("BuildType.RELEASE") ||
+        getArgumentExpression()?.predictRuntimeStringValue() == "release"
+
+internal fun KtCallExpression.functionLiteralArgumentOrNull(): KtFunctionLiteral? =
+    valueArguments
+        .flatMap { it.childrenOfType<KtLambdaExpression>() }
+        .flatMap { it.childrenOfType<KtFunctionLiteral>() }
+        .singleOrNull()
+
+internal fun KtElement.getPropertyAssignmentOrNull(propertyName: String): KtBinaryExpression? =
+    descendantsOfType<KtBinaryExpression>().firstOrNull { it.operationToken == KtTokens.EQ && it.left?.text == propertyName }
+
+internal data class CalleeAndLambda(val callee: KtExpression, val lambda: KtFunctionLiteral)
+
diff --git a/sonar-kotlin-gradle/src/test/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildDebugCheckTest.kt b/sonar-kotlin-gradle/src/test/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildDebugCheckTest.kt
new file mode 100644
index 000000000..bc0523871
--- /dev/null
+++ b/sonar-kotlin-gradle/src/test/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildDebugCheckTest.kt
@@ -0,0 +1,42 @@
+/*
+ * SonarSource Kotlin
+ * Copyright (C) 2018-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonarsource.kotlin.gradle.checks
+
+import org.junit.jupiter.api.Test
+import org.sonarsource.kotlin.testapi.KotlinVerifier
+import java.nio.file.Path
+
+internal class AndroidReleaseBuildDebugCheckTest {
+    private val check = AndroidReleaseBuildDebugCheck()
+    private val fileNamePrefix = "AndroidReleaseBuildDebugCheckSample"
+
+    @Test
+    fun `on build gradle file`() {
+        KotlinVerifier(check) {
+            this.fileName = Path.of(fileNamePrefix, "build.gradle.kts").toFile().path
+            this.baseDir = SAMPLES_BASE_DIR
+        }.verify()
+    }
+
+    @Test
+    fun `on settings gradle file`() {
+        KotlinVerifier(check) {
+            this.fileName = Path.of(fileNamePrefix, "settings.gradle.kts").toFile().path
+            this.baseDir = SAMPLES_BASE_DIR
+        }.verifyNoIssue()
+    }
+}
diff --git a/sonar-kotlin-gradle/src/test/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildObfuscationCheckTest.kt b/sonar-kotlin-gradle/src/test/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildObfuscationCheckTest.kt
index a54311a6d..af1bda316 100644
--- a/sonar-kotlin-gradle/src/test/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildObfuscationCheckTest.kt
+++ b/sonar-kotlin-gradle/src/test/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildObfuscationCheckTest.kt
@@ -25,7 +25,7 @@ internal class AndroidReleaseBuildObfuscationCheckTest {
     private val fileNamePrefix = "AndroidReleaseBuildObfuscationCheckSample"
 
     @Test
-    fun `on gradle file`() {
+    fun `on build gradle file`() {
         KotlinVerifier(check) {
             this.fileName = Path.of(fileNamePrefix, "build.gradle.kts").toFile().path
             this.baseDir = SAMPLES_BASE_DIR
diff --git a/sonar-kotlin-gradle/src/test/samples/non-compiling/AndroidReleaseBuildDebugCheckSample/build.gradle.kts b/sonar-kotlin-gradle/src/test/samples/non-compiling/AndroidReleaseBuildDebugCheckSample/build.gradle.kts
new file mode 100644
index 000000000..a0460cdc0
--- /dev/null
+++ b/sonar-kotlin-gradle/src/test/samples/non-compiling/AndroidReleaseBuildDebugCheckSample/build.gradle.kts
@@ -0,0 +1,363 @@
+// Glossary:
+// - App: Android application (identified by the presence of the applicationId property in the defaultConfig block)
+// - Library: Android library (identified by the absence of the applicationId property in the defaultConfig block)
+
+// region non-compliant scenarios
+
+// App: basic case
+android {
+    var someVariable = "com.example"
+    defaultConfig {
+        applicationId = someVariable // Android app, not a library
+    }
+
+    buildTypes {
+        release {
+            isDebuggable = true // Noncompliant {{Make sure this debug feature is deactivated before delivering the code in production.}}
+//          ^^^^^^^^^^^^^^^^^^^
+        }
+    }
+}
+
+// App: lambda in argument list within parentheses
+android({
+    var someVariable = "com.example"
+    defaultConfig {
+        applicationId = someVariable // Android app, not a library
+    }
+
+    buildTypes({
+        release({
+            isDebuggable = true // Noncompliant
+//          ^^^^^^^^^^^^^^^^^^^
+        })
+    })
+})
+
+// App: lambda within standalone parentheses (not parentheses of argument list)
+(
+    android {
+        (
+            buildTypes {
+                (
+                    release {
+                        isDebuggable = true // Noncompliant
+//                      ^^^^^^^^^^^^^^^^^^^
+                    }
+                    )
+            }
+            )
+
+        defaultConfig {
+            applicationId = AndroidConfig.ID // Android app, not a library
+        }
+    }
+    )
+
+// App: isDebuggable read and assigned
+kotlin {
+    android {
+        defaultConfig {
+            applicationId = "com.example" // Android app, not a library
+        }
+        buildTypes {
+            release {
+                print(isDebuggable)
+                isDebuggable = true // Noncompliant
+            }
+        }
+    }
+}
+
+// App: using getByName and BuildType
+android {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        getByName(BuildType.RELEASE) {
+            isDebuggable = true // Noncompliant
+//          ^^^^^^^^^^^^^^^^^^^
+        }
+    }
+}
+
+// App: using getByName and string
+android {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        getByName("release") {
+            isDebuggable = true // Noncompliant
+//          ^^^^^^^^^^^^^^^^^^^
+        }
+    }
+}
+
+// App: using getByName and triple-quote string
+android {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        getByName("""release""") {
+            isDebuggable = true // Noncompliant
+//          ^^^^^^^^^^^^^^^^^^^
+        }
+    }
+}
+
+// App: using getByName and interpolated string
+android {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        getByName("${"release"}") {
+            isDebuggable = true // Noncompliant
+        }
+    }
+}
+
+// App: using getByName and val
+android {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        val release = "release"
+        getByName(release) {
+            isDebuggable = true // Noncompliant
+        }
+    }
+}
+
+// App: using maybeCreate and apply
+android {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        maybeCreate(BuildTypes.RELEASE).apply {
+            isDebuggable = true // FN
+        }
+    }
+}
+
+// App: isDebuggable assigned val
+android {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        release {
+            val trueVal = true
+            isDebuggable = trueVal // Noncompliant
+        }
+    }
+}
+
+// App: isDebuggable assigned var
+android {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        release {
+            var trueVal = true
+            isDebuggable = trueVal // FN
+        }
+    }
+}
+
+// App: foreach of platforms
+listOf(android(), iosX64(), iosArm64()).forEach {
+    it.defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    it.buildTypes {
+        release {
+            isDebuggable = true // FN
+        }
+    }
+}
+
+// App: lambda argument not a function literal
+fun lambdaArgument(configure: BaseAppModuleExtension) = with(configure) {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        release {
+            isDebuggable = true // FN
+        }
+    }
+}
+android(::lambdaArgument)
+
+// endregion
+
+// region compliant scenarios
+
+// Different block than android
+plugins {
+    id("com.android.application")
+    alias(libs.plugins.android.application)
+    alias(libs.plugins.kotlin.android)
+    alias(libs.plugins.kotlin.compose)
+}
+
+dependencies {
+    implementation("org.jetbrains.kotlin:kotlin-stdlib:1.3.61")
+    implementation("androidx.core:core-ktx:1.2.0")
+    implementation("androidx.appcompat:appcompat:1.1.0")
+    implementation(project(":awesome-textinput-layout"))
+}
+
+// Empty android block with no lambda
+android()
+
+// Empty non-android block with no lambda
+iosX64()
+
+// Library: no buildTypes
+android {
+}
+
+// Library: no buildTypes and different block and properties under android
+android {
+    namespace = "com.example"
+    compileSdk = 33
+}
+
+// Library: no release properties
+android {
+    buildTypes {
+        release {
+        }
+    }
+}
+
+// Library: defaultConfig present, but applicationId not set
+android {
+    defaultConfig {
+        notApplicationId = "com.example"
+    }
+    buildTypes {
+        release {
+            isDebuggable = true
+        }
+    }
+}
+
+// App: android block without buildTypes
+android {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+}
+
+// App: different block (not release) under buildTypes
+android {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        debug {
+            isDebuggable = true
+        }
+    }
+}
+
+// App: using isDebuggable set to false (default)
+android {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        release {
+            isDebuggable = false
+        }
+    }
+}
+
+// App: android with isDebuggable set to false (default) under kotlin block
+kotlin {
+    android {
+        defaultConfig {
+            applicationId = "com.example" // Android app, not a library
+        }
+        buildTypes {
+            release {
+                isDebuggable = false
+            }
+        }
+    }
+}
+
+// App: block with multiple or differently-typed arguments (not the overload from org.gradle.kotlin.dsl)
+android(42) {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        release("a string")
+    }
+}
+
+// App: using getByName
+android {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        getByName(BuildType.DEBUG) {
+            isDebuggable = true
+        }
+        getByName("debug") {
+            isDebuggable = true
+        }
+        getByName("release", 42)
+    }
+}
+
+// App: using create
+android {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        create("beta") {
+            isDebuggable = true
+        }
+    }
+}
+
+// App: using maybeCreate and apply
+android {
+    defaultConfig {
+        applicationId = "com.example" // Android app, not a library
+    }
+    buildTypes {
+        maybeCreate(BuildTypes.DEBUG).apply {
+            isDebuggable = true
+        }
+    }
+}
+
+// App: isDebuggable read but not assigned
+kotlin {
+    android {
+        defaultConfig {
+            applicationId = "com.example" // Android app, not a library
+        }
+        buildTypes {
+            release {
+                print(isDebuggable)
+            }
+        }
+    }
+}
+
+// endregion
diff --git a/sonar-kotlin-gradle/src/test/samples/non-compiling/AndroidReleaseBuildDebugCheckSample/settings.gradle.kts b/sonar-kotlin-gradle/src/test/samples/non-compiling/AndroidReleaseBuildDebugCheckSample/settings.gradle.kts
new file mode 100644
index 000000000..f0fcbc2a8
--- /dev/null
+++ b/sonar-kotlin-gradle/src/test/samples/non-compiling/AndroidReleaseBuildDebugCheckSample/settings.gradle.kts
@@ -0,0 +1,7 @@
+android {
+    buildTypes {
+        release {
+            isDebuggable = true // Compliant, not in a build.gradle.kts file
+        }
+    }
+}
diff --git a/sonar-kotlin-gradle/src/test/samples/non-compiling/AndroidReleaseBuildObfuscationCheckSample/build.gradle.kts b/sonar-kotlin-gradle/src/test/samples/non-compiling/AndroidReleaseBuildObfuscationCheckSample/build.gradle.kts
index 9f9428363..56fe3fcc1 100644
--- a/sonar-kotlin-gradle/src/test/samples/non-compiling/AndroidReleaseBuildObfuscationCheckSample/build.gradle.kts
+++ b/sonar-kotlin-gradle/src/test/samples/non-compiling/AndroidReleaseBuildObfuscationCheckSample/build.gradle.kts
@@ -270,8 +270,8 @@ android {
     }
     buildTypes {
         release {
-            val trueVal = false
-            isMinifyEnabled = trueVal // Noncompliant
+            val falseVal = false
+            isMinifyEnabled = falseVal // Noncompliant
             proguardFiles("proguard-rules.pro")
         }
     }
@@ -284,8 +284,8 @@ android {
     }
     buildTypes {
         release { // FN
-            var trueVal = false
-            isMinifyEnabled = trueVal
+            var falseVal = true
+            isMinifyEnabled = falseVal
             proguardFiles("proguard-rules.pro")
         }
     }
diff --git a/sonar-kotlin-gradle/src/test/samples/non-compiling/AndroidReleaseBuildObfuscationCheckSample/settings.gradle.kts b/sonar-kotlin-gradle/src/test/samples/non-compiling/AndroidReleaseBuildObfuscationCheckSample/settings.gradle.kts
index 53c4e369c..0626b522a 100644
--- a/sonar-kotlin-gradle/src/test/samples/non-compiling/AndroidReleaseBuildObfuscationCheckSample/settings.gradle.kts
+++ b/sonar-kotlin-gradle/src/test/samples/non-compiling/AndroidReleaseBuildObfuscationCheckSample/settings.gradle.kts
@@ -1,6 +1,6 @@
 android {
     buildTypes {
-        release { // Compliant, not in a gradle.kts file
+        release { // Compliant, not in a build.gradle.kts file
         }
     }
 }
diff --git a/sonar-kotlin-plugin/src/main/resources/org/sonar/l10n/kotlin/rules/kotlin/S7416.html b/sonar-kotlin-plugin/src/main/resources/org/sonar/l10n/kotlin/rules/kotlin/S7416.html
new file mode 100644
index 000000000..043c5aa96
--- /dev/null
+++ b/sonar-kotlin-plugin/src/main/resources/org/sonar/l10n/kotlin/rules/kotlin/S7416.html
@@ -0,0 +1,57 @@
+<p>Development tools and frameworks usually have options to make debugging easier for developers. Although these features are useful during
+development, they should never be enabled for applications deployed in production. Debug instructions or error messages can leak detailed information
+about the system, like the application’s path or file names.</p>
+<h2>Why is this an issue?</h2>
+<p>When you build a release version of your application, you’re creating a distributable package of your code. Making release builds debuggable will
+make it much easier for someone to reverse-engineer your application. This means they can potentially understand your code’s logic, extract sensitive
+information like API keys, or even modify your application for malicious purposes.</p>
+<p>Release builds are meant for distribution to end users and is therefore under constant scrutiny. Making these builds debuggable creates a serious
+vulnerability. While debugging builds can be used for troubleshooting, failing to enable it for release builds exposes your application to unnecessary
+risks.</p>
+<h2>How to fix it in Android</h2>
+<h3>Code examples</h3>
+<h4>Noncompliant code example</h4>
+<pre data-diff-id="1" data-diff-type="noncompliant">
+android {
+    buildTypes {
+        release {
+            isDebuggable = true // Sensitive
+        }
+    }
+}
+</pre>
+<h4>Compliant solution</h4>
+<pre data-diff-id="1" data-diff-type="compliant">
+android {
+    buildTypes {
+        release {
+            isDebuggable = false
+        }
+    }
+}
+</pre>
+<h2>Resources</h2>
+<h3>Documentation</h3>
+<ul>
+  <li> <a href="https://developer.android.com/studio/publish/preparing">developer.android.com</a> - Prepare for release </li>
+  <li> <a href="https://developer.android.com/privacy-and-security/risks/android-debuggable">developer.android.com</a> - android:debuggable </li>
+</ul>
+<h3>Standards</h3>
+<ul>
+  <li> OWASP - <a href="https://owasp.org/Top10/A05_2021-Security_Misconfiguration/">Top 10 2021 Category A5 - Security Misconfiguration</a> </li>
+  <li> OWASP - <a href="https://owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure">Top 10 2017 Category A3 - Sensitive Data
+  Exposure</a> </li>
+  <li> OWASP - <a href="https://owasp.org/www-project-mobile-top-10/2016-risks/m9-reverse-engineering">Mobile Top 10 2016 Category M9 - Reverse
+  Engineering</a> </li>
+  <li> OWASP - <a href="https://owasp.org/www-project-mobile-top-10/2016-risks/m10-extraneous-functionality">Mobile Top 10 2016 Category M10 -
+  Extraneous Functionality</a> </li>
+  <li> OWASP - <a href="https://owasp.org/www-project-mobile-top-10/2023-risks/m7-insufficient-binary-protection">Mobile Top 10 2024 Category M7 -
+  Insufficient Binary Protection</a> </li>
+  <li> OWASP - <a href="https://owasp.org/www-project-mobile-top-10/2023-risks/m8-security-misconfiguration">Mobile Top 10 2024 Category M8 - Security
+  Misconfiguration</a> </li>
+  <li> OWASP - <a href="https://mas.owasp.org/checklists/MASVS-CODE/">Mobile AppSec Verification Standard - Code Quality and Build Setting
+  Requirements</a> </li>
+  <li> CWE - <a href="https://cwe.mitre.org/data/definitions/489">CWE-489 - Active Debug Code</a> </li>
+  <li> CWE - <a href="https://cwe.mitre.org/data/definitions/215">CWE-215 - Information Exposure Through Debug Information</a> </li>
+</ul>
+
diff --git a/sonar-kotlin-plugin/src/main/resources/org/sonar/l10n/kotlin/rules/kotlin/S7416.json b/sonar-kotlin-plugin/src/main/resources/org/sonar/l10n/kotlin/rules/kotlin/S7416.json
new file mode 100644
index 000000000..a43db06d3
--- /dev/null
+++ b/sonar-kotlin-plugin/src/main/resources/org/sonar/l10n/kotlin/rules/kotlin/S7416.json
@@ -0,0 +1,41 @@
+{
+  "title": "Android production release targets should not be debuggable",
+  "type": "VULNERABILITY",
+  "status": "ready",
+  "remediation": {
+    "func": "Constant\/Issue",
+    "constantCost": "5min"
+  },
+  "tags": [],
+  "defaultSeverity": "Minor",
+  "ruleSpecification": "RSPEC-7416",
+  "sqKey": "S7416",
+  "scope": "All",
+  "securityStandards": {
+    "CWE": [
+      489,
+      215
+    ],
+    "OWASP Mobile": [
+      "M9",
+      "M10"
+    ],
+    "OWASP Mobile Top 10 2024": [
+      "M7",
+      "M8"
+    ],
+    "OWASP": [
+      "A3"
+    ],
+    "OWASP Top 10 2021": [
+      "A5"
+    ]
+  },
+  "quickfix": "unknown",
+  "code": {
+    "impacts": {
+      "SECURITY": "LOW"
+    },
+    "attribute": "CONVENTIONAL"
+  }
+}
diff --git a/sonar-kotlin-plugin/src/main/resources/org/sonar/l10n/kotlin/rules/kotlin/Sonar_way_profile.json b/sonar-kotlin-plugin/src/main/resources/org/sonar/l10n/kotlin/rules/kotlin/Sonar_way_profile.json
index fac092314..deaeaa86c 100644
--- a/sonar-kotlin-plugin/src/main/resources/org/sonar/l10n/kotlin/rules/kotlin/Sonar_way_profile.json
+++ b/sonar-kotlin-plugin/src/main/resources/org/sonar/l10n/kotlin/rules/kotlin/Sonar_way_profile.json
@@ -126,6 +126,7 @@
     "S6629",
     "S6631",
     "S6634",
-    "S7204"
+    "S7204",
+    "S7416"
   ]
 }


Reviews:
user: login: leveretka
body: Looks good!

Don't forget to update the TODOs, since now we can use K2
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T16:34:03Z
user: login: antonioaversa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T09:56:43Z
user: login: antonioaversa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T09:57:05Z
user: login: antonioaversa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T09:57:56Z
user: login: leveretka
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T13:31:08Z

Review comments:
diff_hunk: @@ -0,0 +1,75 @@
+/*
+ * SonarSource Kotlin
+ * Copyright (C) 2018-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonarsource.kotlin.gradle.checks
+
+import com.intellij.psi.util.childrenOfType
+import com.intellij.psi.util.descendantsOfType
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.KtBinaryExpression
+import org.jetbrains.kotlin.psi.KtCallExpression
+import org.jetbrains.kotlin.psi.KtElement
+import org.jetbrains.kotlin.psi.KtExpression
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.psi.KtFunctionLiteral
+import org.jetbrains.kotlin.psi.KtLambdaExpression
+import org.jetbrains.kotlin.psi.KtValueArgument
+
+private const val settingsGradleFileName = "settings.gradle.kts"
+
+private val releaseBuildTypeExpressions = listOf("\"release\"", "\"\"\"release\"\"\"", "BuildType.RELEASE")
+
+internal fun KtFile.isSettingGradleKts() = name.endsWith(settingsGradleFileName, ignoreCase = true)
+
+internal fun KtFunctionLiteral.getApplicationId() =
+        getChildCallWithLambdaOrNull("defaultConfig")
+        ?.lambda
+        ?.getPropertyAssignmentOrNull("applicationId")
+
+internal fun KtElement.getChildCallOrNull(childCallName: String): KtCallExpression? =
+    descendantsOfType<KtCallExpression>().firstOrNull { it.calleeExpression?.text == childCallName }
+
+internal fun KtElement.getChildCallWithLambdaOrNull(childCallName: String): CalleeAndLambda? {
+    val callee = getChildCallOrNull(childCallName) ?: return null
+    val lambda = callee.functionLiteralArgumentOrNull() ?: return null
+    return CalleeAndLambda(callee.calleeExpression!!, lambda)
+}
+
+internal fun KtElement.getGetByNameCallWithLambdaOrNull(): CalleeAndLambda? {
+    val callee = descendantsOfType<KtCallExpression>().firstOrNull {
+        it.calleeExpression?.text == "getByName" &&
+            it.valueArguments.size == 2 &&
+            it.valueArguments[0].isReleaseBuildType()
+    } ?: return null
+    val lambda = callee.functionLiteralArgumentOrNull() ?: return null
+    return CalleeAndLambda(callee.calleeExpression!!, lambda)
+}
+
+// TODO: use predictRuntimeStringValue after migration to K2
path: sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/androidReleaseBuildUtils.kt
user: login: leveretka
body: I guess now you can do it
created_at: 2025-03-18T14:55:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * SonarSource Kotlin
+ * Copyright (C) 2018-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonarsource.kotlin.gradle.checks
+
+import org.jetbrains.kotlin.psi.KtScriptInitializer
+import org.sonar.check.Rule
+import org.sonarsource.kotlin.api.checks.AbstractCheck
+import org.sonarsource.kotlin.api.frontend.KotlinFileContext
+
+private const val message = "Make sure this debug feature is deactivated before delivering the code in production."
+
+@Rule(key = "S7416")
+class AndroidReleaseBuildDebugCheck : AbstractCheck() {
+
+    override fun visitScriptInitializer(initializer: KtScriptInitializer, data: KotlinFileContext) {
+        !data.ktFile.isSettingGradleKts() || return
+        val (_, androidLambda) = initializer.getChildCallWithLambdaOrNull("android") ?: return
+
+        // Ensure the project is an Android app, and not a library
+        androidLambda.getApplicationId() ?: return
+
+        val buildTypes = androidLambda.getChildCallWithLambdaOrNull("buildTypes") ?: return
+        val (_, releaseLambda) = buildTypes.lambda.getChildCallWithLambdaOrNull("release")
+            ?: androidLambda.getGetByNameCallWithLambdaOrNull()
+            ?: return
+
+        val isDebuggableAssignment = releaseLambda.getPropertyAssignmentOrNull("isDebuggable")
+
+        // TODO: use predictRuntimeBooleanValue for "true" and "false" after migration to K2
path: sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildDebugCheck.kt
user: login: leveretka
body: can use K2 
created_at: 2025-03-18T16:29:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * SonarSource Kotlin
+ * Copyright (C) 2018-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonarsource.kotlin.gradle.checks
+
+import org.jetbrains.kotlin.psi.KtScriptInitializer
+import org.sonar.check.Rule
+import org.sonarsource.kotlin.api.checks.AbstractCheck
+import org.sonarsource.kotlin.api.frontend.KotlinFileContext
+
+private const val message = "Make sure this debug feature is deactivated before delivering the code in production."
+
+@Rule(key = "S7416")
+class AndroidReleaseBuildDebugCheck : AbstractCheck() {
+
+    override fun visitScriptInitializer(initializer: KtScriptInitializer, data: KotlinFileContext) {
+        !data.ktFile.isSettingGradleKts() || return
+        val (_, androidLambda) = initializer.getChildCallWithLambdaOrNull("android") ?: return
path: sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildDebugCheck.kt
user: login: leveretka
body: I'm not a big fan of destructurring to use just one property but this is a matter of taste.
created_at: 2025-03-18T16:31:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,75 @@
+/*
+ * SonarSource Kotlin
+ * Copyright (C) 2018-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonarsource.kotlin.gradle.checks
+
+import com.intellij.psi.util.childrenOfType
+import com.intellij.psi.util.descendantsOfType
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.KtBinaryExpression
+import org.jetbrains.kotlin.psi.KtCallExpression
+import org.jetbrains.kotlin.psi.KtElement
+import org.jetbrains.kotlin.psi.KtExpression
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.psi.KtFunctionLiteral
+import org.jetbrains.kotlin.psi.KtLambdaExpression
+import org.jetbrains.kotlin.psi.KtValueArgument
+
+private const val settingsGradleFileName = "settings.gradle.kts"
+
+private val releaseBuildTypeExpressions = listOf("\"release\"", "\"\"\"release\"\"\"", "BuildType.RELEASE")
+
+internal fun KtFile.isSettingGradleKts() = name.endsWith(settingsGradleFileName, ignoreCase = true)
+
+internal fun KtFunctionLiteral.getApplicationId() =
+        getChildCallWithLambdaOrNull("defaultConfig")
+        ?.lambda
+        ?.getPropertyAssignmentOrNull("applicationId")
+
+internal fun KtElement.getChildCallOrNull(childCallName: String): KtCallExpression? =
+    descendantsOfType<KtCallExpression>().firstOrNull { it.calleeExpression?.text == childCallName }
+
+internal fun KtElement.getChildCallWithLambdaOrNull(childCallName: String): CalleeAndLambda? {
+    val callee = getChildCallOrNull(childCallName) ?: return null
+    val lambda = callee.functionLiteralArgumentOrNull() ?: return null
+    return CalleeAndLambda(callee.calleeExpression!!, lambda)
+}
+
+internal fun KtElement.getGetByNameCallWithLambdaOrNull(): CalleeAndLambda? {
+    val callee = descendantsOfType<KtCallExpression>().firstOrNull {
+        it.calleeExpression?.text == "getByName" &&
+            it.valueArguments.size == 2 &&
+            it.valueArguments[0].isReleaseBuildType()
+    } ?: return null
+    val lambda = callee.functionLiteralArgumentOrNull() ?: return null
+    return CalleeAndLambda(callee.calleeExpression!!, lambda)
+}
+
+// TODO: use predictRuntimeStringValue after migration to K2
path: sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/androidReleaseBuildUtils.kt
user: login: antonioaversa
body: Fixed, I changed to use `predictRuntimeStringValue() == "release"`
created_at: 2025-03-19T09:56:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * SonarSource Kotlin
+ * Copyright (C) 2018-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonarsource.kotlin.gradle.checks
+
+import org.jetbrains.kotlin.psi.KtScriptInitializer
+import org.sonar.check.Rule
+import org.sonarsource.kotlin.api.checks.AbstractCheck
+import org.sonarsource.kotlin.api.frontend.KotlinFileContext
+
+private const val message = "Make sure this debug feature is deactivated before delivering the code in production."
+
+@Rule(key = "S7416")
+class AndroidReleaseBuildDebugCheck : AbstractCheck() {
+
+    override fun visitScriptInitializer(initializer: KtScriptInitializer, data: KotlinFileContext) {
+        !data.ktFile.isSettingGradleKts() || return
+        val (_, androidLambda) = initializer.getChildCallWithLambdaOrNull("android") ?: return
+
+        // Ensure the project is an Android app, and not a library
+        androidLambda.getApplicationId() ?: return
+
+        val buildTypes = androidLambda.getChildCallWithLambdaOrNull("buildTypes") ?: return
+        val (_, releaseLambda) = buildTypes.lambda.getChildCallWithLambdaOrNull("release")
+            ?: androidLambda.getGetByNameCallWithLambdaOrNull()
+            ?: return
+
+        val isDebuggableAssignment = releaseLambda.getPropertyAssignmentOrNull("isDebuggable")
+
+        // TODO: use predictRuntimeBooleanValue for "true" and "false" after migration to K2
path: sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildDebugCheck.kt
user: login: antonioaversa
body: Fixed
created_at: 2025-03-19T09:57:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * SonarSource Kotlin
+ * Copyright (C) 2018-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the Sonar Source-Available License for more details.
+ *
+ * You should have received a copy of the Sonar Source-Available License
+ * along with this program; if not, see https://sonarsource.com/license/ssal/
+ */
+package org.sonarsource.kotlin.gradle.checks
+
+import org.jetbrains.kotlin.psi.KtScriptInitializer
+import org.sonar.check.Rule
+import org.sonarsource.kotlin.api.checks.AbstractCheck
+import org.sonarsource.kotlin.api.frontend.KotlinFileContext
+
+private const val message = "Make sure this debug feature is deactivated before delivering the code in production."
+
+@Rule(key = "S7416")
+class AndroidReleaseBuildDebugCheck : AbstractCheck() {
+
+    override fun visitScriptInitializer(initializer: KtScriptInitializer, data: KotlinFileContext) {
+        !data.ktFile.isSettingGradleKts() || return
+        val (_, androidLambda) = initializer.getChildCallWithLambdaOrNull("android") ?: return
path: sonar-kotlin-gradle/src/main/java/org/sonarsource/kotlin/gradle/checks/AndroidReleaseBuildDebugCheck.kt
user: login: antonioaversa
body: Changed to `val androidLambda = initializer.getChildCallWithLambdaOrNull("android")?.lambda ?: return`
created_at: 2025-03-19T09:57:56Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqube-next[bot]
created_at: 2025-03-19T10:03:33Z
body: ## [![Quality Gate passed](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://next.sonarqube.com/sonarqube/dashboard?id=org.sonarsource.kotlin%3Akotlin&pullRequest=588) **Quality Gate passed**  
Issues  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.kotlin%3Akotlin&pullRequest=588&resolved=false) [0 New issues](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.kotlin%3Akotlin&pullRequest=588&resolved=false)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/fixed-16px.png '')](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.kotlin%3Akotlin&fixedInPullRequest=588) [0 Fixed issues](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.kotlin%3Akotlin&fixedInPullRequest=588)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/accepted-16px.png '')](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.kotlin%3Akotlin&pullRequest=588&issueStatuses=ACCEPTED) [0 Accepted issues](https://next.sonarqube.com/sonarqube/project/issues?id=org.sonarsource.kotlin%3Akotlin&pullRequest=588&issueStatuses=ACCEPTED)

Measures  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/security_hotspots?id=org.sonarsource.kotlin%3Akotlin&pullRequest=588) [0 Security Hotspots](https://next.sonarqube.com/sonarqube/security_hotspots?id=org.sonarsource.kotlin%3Akotlin&pullRequest=588)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.kotlin%3Akotlin&pullRequest=588&metric=new_coverage&view=list) [91.5% Coverage on New Code](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.kotlin%3Akotlin&pullRequest=588&metric=new_coverage&view=list)  
[![](https://raw.githubusercontent.com/SonarSource/sonarqube-static-resources/master/v104/common/passed-16px.png '')](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.kotlin%3Akotlin&pullRequest=588&metric=new_duplicated_lines_density&view=list) [0.0% Duplication on New Code](https://next.sonarqube.com/sonarqube/component_measures?id=org.sonarsource.kotlin%3Akotlin&pullRequest=588&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube](https://next.sonarqube.com/sonarqube/dashboard?id=org.sonarsource.kotlin%3Akotlin&pullRequest=588)


author_association: NONE
user: login: antonioaversa
created_at: 2025-03-19T10:05:13Z
body: @leveretka I have updated this rule to use `predictRuntimeBooleanValue` after the switch to K2. We gained 3 FN out of the 7 documented in UTs. Back to you for another round.
author_association: CONTRIBUTOR