diff --git a/CHANGELOG.md b/CHANGELOG.md index e41bef9e..94f219a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Fixes + +- Detect minified classes and skip instrumentation to avoid build problems ([#362](https://github.com/getsentry/sentry-android-gradle-plugin/pull/362)) + ### Features - Bump AGP to 7.2.1 and Gradle to 7.5.0 ([#363](https://github.com/getsentry/sentry-android-gradle-plugin/pull/363)) diff --git a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/ConstantPoolHelpers.kt b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/ConstantPoolHelpers.kt index 8e8b2bd7..6d33ac64 100644 --- a/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/ConstantPoolHelpers.kt +++ b/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/util/ConstantPoolHelpers.kt @@ -44,17 +44,25 @@ internal fun ClassWriter.findClassReader(): ClassReader? { return (classReaderField.get(symbolTable) as? ClassReader) } +internal fun ClassReader.getSimpleClassName(): String { + return className.substringAfterLast("/") +} + /** * Looks at the constant pool entries and searches for R8 markers */ internal fun ClassReader.isMinifiedClass(): Boolean { - val charBuffer = CharArray(maxStringLength) + return isR8Minified(this) || classNameLooksMinified(this.getSimpleClassName(), this.className) +} + +private fun isR8Minified(classReader: ClassReader): Boolean { + val charBuffer = CharArray(classReader.maxStringLength) // R8 marker is usually in the first 3-5 entries, so we limit it at 10 to speed it up // (constant pool size can be huge otherwise) - val poolSize = minOf(10, itemCount) + val poolSize = minOf(10, classReader.itemCount) for (i in 1 until poolSize) { try { - val constantPoolEntry = readConst(i, charBuffer) + val constantPoolEntry = classReader.readConst(i, charBuffer) if (constantPoolEntry is String && "~~R8" in constantPoolEntry) { // ~~R8 is a marker in the class' constant pool, which r8 itself is looking at when // parsing a .class file. See here -> https://r8.googlesource.com/r8/+/refs/heads/main/src/main/java/com/android/tools/r8/dex/Marker.java#53 @@ -68,6 +76,25 @@ internal fun ClassReader.isMinifiedClass(): Boolean { return false } +/** + * See https://github.com/getsentry/sentry-android-gradle-plugin/issues/360 + * and https://github.com/getsentry/sentry-android-gradle-plugin/issues/359#issuecomment-1193782500 + */ +/* ktlint-disable max-line-length */ +private val MINIFIED_CLASSNAME_REGEX = """^(((([a-zA-z])\4{1,}|[a-zA-Z]{1,2})([0-9]{1,})?(([a-zA-Z])\7{1,})?)|([a-zA-Z]([0-9])?))(${'\\'}${'$'}((((\w)\14{1,}|[a-zA-Z]{1,2})([0-9]{1,})?(([a-zA-Z])\17{1,})?)|(\w([0-9])?)))*${'$'}""".toRegex() + +/** + * See https://github.com/getsentry/sentry/blob/c943de2afc785083554e7fdfb10c67d0c0de0f98/static/app/components/events/eventEntries.tsx#L57-L58 + */ +private val MINIFIED_CLASSNAME_SENTRY_REGEX = + """^(([\w\${'$'}]\/[\w\${'$'}]{1,2})|([\w\${'$'}]{2}\/[\w\${'$'}]\/[\w\${'$'}]))(\/|${'$'})""".toRegex() +/* ktlint-enable max-line-length */ + +fun classNameLooksMinified(simpleClassName: String, fullClassName: String): Boolean { + return MINIFIED_CLASSNAME_REGEX.matches(simpleClassName) || + MINIFIED_CLASSNAME_SENTRY_REGEX.matches(fullClassName) +} + /** * Gets all fields of the given class and its parents (if any). * diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryPluginWithMinifiedLibsTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryPluginWithMinifiedLibsTest.kt index b763a2df..246a3df2 100644 --- a/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryPluginWithMinifiedLibsTest.kt +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/SentryPluginWithMinifiedLibsTest.kt @@ -23,6 +23,7 @@ class SentryPluginWithMinifiedLibsTest : implementation 'com.google.android.gms:play-services-mlkit-text-recognition:18.0.0' implementation 'com.adcolony:sdk:4.7.1' implementation 'com.appboy:android-sdk-ui:19.0.0' + implementation 'com.stripe:stripeterminal-internal-common:2.12.0' } """.trimIndent() ) diff --git a/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/MinifiedClassDetectionTest.kt b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/MinifiedClassDetectionTest.kt new file mode 100644 index 00000000..7c9b1d94 --- /dev/null +++ b/plugin-build/src/test/kotlin/io/sentry/android/gradle/instrumentation/util/MinifiedClassDetectionTest.kt @@ -0,0 +1,52 @@ +package io.sentry.android.gradle.instrumentation.util + +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import org.junit.Test + +class MinifiedClassDetectionTest { + + @Test + fun `detects minified class names`() { + val classNames = listOf( + "l0", + """a${'$'}a""", + "ccc017zz", + """ccc017zz${'$'}a""", + "aa", + "aa${'$'}a", + "ab", + "aa${'$'}ab", + "ab${'$'}a" + ) + + classNames.forEach { + assertTrue(classNameLooksMinified(it, "com/example/$it"), it) + } + } + + @Test + fun `detects minified class names with minified package name`() { + val classNames = listOf( + """a${'$'}""", + "aa" + ) + + classNames.forEach { + assertTrue(classNameLooksMinified(it, "a/$it"), it) + } + } + + @Test + fun `does not consider non minified classes as minified`() { + val classNames = listOf( + "ConstantPoolHelpers", + "FileUtil", + """FileUtil${"$"}Inner""" + ) + + classNames.forEach { + assertFalse(classNameLooksMinified(it, "com/example/$it"), it) + } + } +}