All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.sentry.android.gradle.instrumentation.util.ConstantPoolHelpers.kt Maven / Gradle / Ivy

There is a newer version: 4.11.0
Show newest version
package io.sentry.android.gradle.instrumentation.util

import java.lang.reflect.Field
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter

/**
 * Looks up for the original [ClassWriter] up the visitor chain by looking at the private `cv` field
 * of the [ClassVisitor].
 */
internal fun ClassVisitor.findClassWriter(): ClassWriter? {
    var classWriter: ClassVisitor = this
    while (!ClassWriter::class.java.isAssignableFrom(classWriter::class.java)) {
        val cvField: Field = try {
            classWriter::class.java.allFields.find { it.name == "cv" } ?: return null
        } catch (e: Throwable) {
            return null
        }
        cvField.isAccessible = true
        classWriter = (cvField.get(classWriter) as? ClassVisitor) ?: return null
    }
    return classWriter as ClassWriter
}

/**
 * Looks up for [ClassReader] of the [ClassWriter] through intermediate SymbolTable field.
 */
internal fun ClassWriter.findClassReader(): ClassReader? {
    val clazz: Class = this::class.java
    val symbolTableField: Field = try {
        clazz.allFields.find { it.name == "symbolTable" } ?: return null
    } catch (e: Throwable) {
        return null
    }
    symbolTableField.isAccessible = true
    val symbolTable = symbolTableField.get(this)
    val classReaderField: Field = try {
        symbolTable::class.java.getDeclaredField("sourceClassReader")
    } catch (e: Throwable) {
        return null
    }
    classReaderField.isAccessible = true
    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 {
    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, classReader.itemCount)
    for (i in 1 until poolSize) {
        try {
            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
                return true
            }
        } catch (e: Throwable) {
            // we ignore exceptions here, because some constant pool entries are nulls and the
            // readConst method throws IllegalArgumentException when trying to read those
        }
    }
    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 simpleClassName.isNotEmpty() &&
        simpleClassName[0].isLowerCase() &&
        (
            MINIFIED_CLASSNAME_REGEX.matches(simpleClassName) ||
                MINIFIED_CLASSNAME_SENTRY_REGEX.matches(fullClassName)
            )
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy