From fdf362374480bd530e8869f789c06842b121ebde Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Wed, 4 Dec 2024 13:14:41 -0800 Subject: [PATCH 01/10] implement gradle plugin to facilitate okhttp instrumentation --- platform/jvm/capture-plugin/build.gradle.kts | 36 ++++++ .../capture/AndroidComponentsConfig.kt | 54 +++++++++ .../capture/BitdriftPluginExtension.kt | 20 ++++ .../io/bitdrift/capture/CapturePlugin.kt | 41 +++++++ .../capture/InstrumentationExtension.kt | 29 +++++ .../capture/SpanAddingClassVisitorFactory.kt | 84 ++++++++++++++ .../instrumentation/ChainedInstrumentable.kt | 56 +++++++++ .../instrumentation/CommonClassVisitor.kt | 77 +++++++++++++ .../capture/instrumentation/Instrumentable.kt | 55 +++++++++ .../instrumentation/InstrumentableContext.kt | 24 ++++ .../okhttp/OkHttpEventListener.kt | 59 ++++++++++ .../OkHttpEventListenerMethodVisitor.kt | 67 +++++++++++ .../util/CatchingMethodVisitor.kt | 42 +++++++ .../util/ConstantPoolHelpers.kt | 107 ++++++++++++++++++ .../instrumentation/util/FileLogTextifier.kt | 49 ++++++++ .../capture/instrumentation/util/FileUtils.kt | 44 +++++++ platform/jvm/capture/build.gradle.kts | 2 + platform/jvm/gradle-test-app/build.gradle.kts | 8 +- .../bitdrift/gradletestapp/FirstFragment.kt | 1 - .../io/bitdrift/gradletestapp/MainActivity.kt | 1 + platform/jvm/gradle/libs.versions.toml | 6 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- platform/jvm/settings.gradle.kts | 17 +-- 23 files changed, 870 insertions(+), 11 deletions(-) create mode 100644 platform/jvm/capture-plugin/build.gradle.kts create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/AndroidComponentsConfig.kt create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/BitdriftPluginExtension.kt create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/CapturePlugin.kt create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/InstrumentationExtension.kt create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/SpanAddingClassVisitorFactory.kt create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentable.kt create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitor.kt create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/Instrumentable.kt create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/InstrumentableContext.kt create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/OkHttpEventListener.kt create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitor.kt create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/ConstantPoolHelpers.kt create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifier.kt create mode 100644 platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FileUtils.kt diff --git a/platform/jvm/capture-plugin/build.gradle.kts b/platform/jvm/capture-plugin/build.gradle.kts new file mode 100644 index 00000000..994eb2f6 --- /dev/null +++ b/platform/jvm/capture-plugin/build.gradle.kts @@ -0,0 +1,36 @@ + plugins { + alias(libs.plugins.kotlin) + + id("dependency-license-config") + id("java-gradle-plugin") + id("maven-publish") + id("com.vanniktech.maven.publish") version "0.28.0" apply false +} + +dependencies { + compileOnly("com.android.tools.build:gradle:7.4.0") + compileOnly("org.ow2.asm:asm-commons:9.4") + compileOnly("org.ow2.asm:asm-util:9.4") +} + +gradlePlugin { + plugins { + create("capturePlugin") { + id = "io.bitdrift.capture.capture-plugin" + implementationClass = "io.bitdrift.capture.CapturePlugin" + } + } +} + +apply { + plugin("com.vanniktech.maven.publish") +} + +publishing { + repositories { + mavenLocal() + } +} + +group = "io.bitdrift.capture.capture-plugin" +version = "0.1.0" \ No newline at end of file diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/AndroidComponentsConfig.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/AndroidComponentsConfig.kt new file mode 100644 index 00000000..b846c563 --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/AndroidComponentsConfig.kt @@ -0,0 +1,54 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +package io.bitdrift.capture + +import com.android.build.api.instrumentation.AsmClassVisitorFactory +import com.android.build.api.instrumentation.FramesComputationMode +import com.android.build.api.instrumentation.InstrumentationParameters +import com.android.build.api.instrumentation.InstrumentationScope +import com.android.build.api.variant.AndroidComponentsExtension +import com.android.build.api.variant.Variant +import io.bitdrift.capture.CapturePlugin.Companion.sep +import org.gradle.api.Project +import java.io.File + +fun AndroidComponentsExtension<*, *, *>.configure( + project: Project, + extension: BitdriftPluginExtension, +) { + // temp folder for sentry-related stuff + val tmpDir = File("${project.buildDir}${sep}tmp${sep}sentry") + tmpDir.mkdirs() + + onVariants { variant -> + if (extension.instrumentation.enabled.get()) { + variant.configureInstrumentation( + SpanAddingClassVisitorFactory::class.java, + InstrumentationScope.ALL, + FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS, + ) { params -> + params.tmpDir.set(tmpDir) + params.debug.set(false) + } + } + } +} + +private fun Variant.configureInstrumentation( + classVisitorFactoryImplClass: Class>, + scope: InstrumentationScope, + mode: FramesComputationMode, + instrumentationParamsConfig: (T) -> Unit, +) { + instrumentation.transformClassesWith( + classVisitorFactoryImplClass, + scope, + instrumentationParamsConfig + ) + instrumentation.setAsmFramesComputationMode(mode) +} \ No newline at end of file diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/BitdriftPluginExtension.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/BitdriftPluginExtension.kt new file mode 100644 index 00000000..001d3765 --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/BitdriftPluginExtension.kt @@ -0,0 +1,20 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +package io.bitdrift.capture + +import org.gradle.api.Project +import javax.inject.Inject + +abstract class BitdriftPluginExtension @Inject constructor(project: Project) { + private val objects = project.objects + + /** + * Configures the instrumentation feature. + */ + val instrumentation: InstrumentationExtension = objects.newInstance(InstrumentationExtension::class.java) +} \ No newline at end of file diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/CapturePlugin.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/CapturePlugin.kt new file mode 100644 index 00000000..2abad53a --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/CapturePlugin.kt @@ -0,0 +1,41 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +package io.bitdrift.capture + +import com.android.build.api.variant.AndroidComponentsExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.slf4j.LoggerFactory +import java.io.File +import javax.inject.Inject + +abstract class CapturePlugin @Inject constructor() : Plugin { + override fun apply(target: Project) { + val extension = target.extensions.create("bitdrift", BitdriftPluginExtension::class.java, target) + + target.pluginManager.withPlugin("com.android.application") { + val androidComponentsExt = + target.extensions.getByType(AndroidComponentsExtension::class.java) + + androidComponentsExt.configure( + target, + extension, + ) + + + } + } + + companion object { + internal val sep = File.separator + + internal val logger by lazy { + LoggerFactory.getLogger(CapturePlugin::class.java) + } + } +} \ No newline at end of file diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/InstrumentationExtension.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/InstrumentationExtension.kt new file mode 100644 index 00000000..76e9d0fb --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/InstrumentationExtension.kt @@ -0,0 +1,29 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +package io.bitdrift.capture + +import javax.inject.Inject +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty + +enum class InstrumentationFeature(val integrationName: String) { + OKHTTP("OkHttpInstrumentation") +} + +open class InstrumentationExtension @Inject constructor(objects: ObjectFactory) { + + val enabled: Property = objects.property(Boolean::class.java) + .convention(true) + + val features: SetProperty = objects.setProperty(InstrumentationFeature::class.java) + + val debug: Property = objects.property(Boolean::class.java).convention( + false + ) +} \ No newline at end of file diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/SpanAddingClassVisitorFactory.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/SpanAddingClassVisitorFactory.kt new file mode 100644 index 00000000..01a496d4 --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/SpanAddingClassVisitorFactory.kt @@ -0,0 +1,84 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +package io.bitdrift.capture + +import com.android.build.api.instrumentation.AsmClassVisitorFactory +import com.android.build.api.instrumentation.ClassContext +import com.android.build.api.instrumentation.ClassData +import com.android.build.api.instrumentation.InstrumentationParameters +import io.bitdrift.capture.instrumentation.ClassInstrumentable +import io.bitdrift.capture.instrumentation.okhttp.OkHttpEventListener +import io.bitdrift.capture.instrumentation.toClassContext +import io.bitdrift.capture.instrumentation.util.findClassReader +import io.bitdrift.capture.instrumentation.util.findClassWriter +import io.bitdrift.capture.instrumentation.util.isMinifiedClass +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.objectweb.asm.ClassVisitor +import java.io.File + +abstract class SpanAddingClassVisitorFactory : AsmClassVisitorFactory { + + interface SpanAddingParameters : InstrumentationParameters { + @get:Input + val debug: Property + + @get:Internal + val tmpDir: Property + + @get:Internal + var _instrumentable: ClassInstrumentable? + } + + private val instrumentable: ClassInstrumentable + get() { + val memoized = parameters.get()._instrumentable + if (memoized != null) { + return memoized + } + + val instrumentable = ChainedInstrumentable( + listOfNotNull( + OkHttpEventListener() + ) + ) + CapturePlugin.logger.info( + "Instrumentable: $instrumentable" + ) + parameters.get()._instrumentable = instrumentable + return instrumentable + } + + + override fun createClassVisitor( + classContext: ClassContext, + nextClassVisitor: ClassVisitor + ): ClassVisitor { + val className = classContext.currentClassData.className + + val classReader = nextClassVisitor.findClassWriter()?.findClassReader() + val isMinifiedClass = classReader?.isMinifiedClass() ?: false + if (isMinifiedClass) { + CapturePlugin.logger.info( + "$className skipped from instrumentation because it's a minified class." + ) + return nextClassVisitor + } + + return instrumentable.getVisitor( + classContext, + instrumentationContext.apiVersion.get(), + nextClassVisitor, + parameters = parameters.get() + ) + } + + override fun isInstrumentable(classData: ClassData): Boolean = + instrumentable.isInstrumentable(classData.toClassContext()) +} diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentable.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentable.kt new file mode 100644 index 00000000..4d7b17ac --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentable.kt @@ -0,0 +1,56 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +@file:Suppress("UnstableApiUsage") + +package io.bitdrift.capture + +import com.android.build.api.instrumentation.ClassContext +import io.bitdrift.capture.instrumentation.ClassInstrumentable +import java.util.LinkedList +import org.objectweb.asm.ClassVisitor + +class ChainedInstrumentable( + private val instrumentables: List = emptyList() +) : ClassInstrumentable { + + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters + ): ClassVisitor { + // build a chain of visitors in order they are provided + val queue = LinkedList(instrumentables) + var prevVisitor = originalVisitor + var visitor: ClassVisitor? = null + while (queue.isNotEmpty()) { + val instrumentable = queue.poll() + + visitor = if (instrumentable.isInstrumentable(instrumentableContext)) { + instrumentable.getVisitor( + instrumentableContext, + apiVersion, + prevVisitor, + parameters + ) + } else { + prevVisitor + } + prevVisitor = visitor + } + return visitor ?: originalVisitor + } + + override fun isInstrumentable(data: ClassContext): Boolean = + instrumentables.any { it.isInstrumentable(data) } + + override fun toString(): String { + return "ChainedInstrumentable(instrumentables=" + + "${instrumentables.joinToString(", ") { it.javaClass.simpleName }})" + } +} \ No newline at end of file diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitor.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitor.kt new file mode 100644 index 00000000..9195eae8 --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitor.kt @@ -0,0 +1,77 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +package io.bitdrift.capture.instrumentation + +import io.bitdrift.capture.SpanAddingClassVisitorFactory +import io.bitdrift.capture.instrumentation.util.CatchingMethodVisitor +import io.bitdrift.capture.instrumentation.util.ExceptionHandler +import io.bitdrift.capture.instrumentation.util.FileLogTextifier +import java.io.File +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.util.TraceMethodVisitor + +@Suppress("UnstableApiUsage") +class CommonClassVisitor( + apiVersion: Int, + classVisitor: ClassVisitor, + private val className: String, + private val methodInstrumentables: List, + private val parameters: SpanAddingClassVisitorFactory.SpanAddingParameters +) : ClassVisitor(apiVersion, classVisitor) { + + private lateinit var log: File + + init { + // to avoid file creation in case the debug mode is not set + if (parameters.debug.get()) { + + // create log dir. + val logDir = parameters.tmpDir.get() + logDir.mkdirs() + + // delete and recreate file + log = File(parameters.tmpDir.get(), "$className-instrumentation.log") + if (log.exists()) { + log.delete() + } + log.createNewFile() + } + } + + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array? + ): MethodVisitor { + var mv = super.visitMethod(access, name, descriptor, signature, exceptions) + val methodContext = MethodContext(access, name, descriptor, signature, exceptions?.toList()) + val instrumentable = methodInstrumentables.find { it.isInstrumentable(methodContext) } + + var textifier: ExceptionHandler? = null + if (parameters.debug.get() && instrumentable != null) { + textifier = FileLogTextifier(api, log, name, descriptor) + mv = TraceMethodVisitor(mv, textifier) + } + + val instrumentableVisitor = instrumentable?.getVisitor(methodContext, api, mv, parameters) + return if (instrumentableVisitor != null) { + CatchingMethodVisitor( + api, + instrumentableVisitor, + className, + methodContext, + textifier + ) + } else { + mv + } + } +} \ No newline at end of file diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/Instrumentable.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/Instrumentable.kt new file mode 100644 index 00000000..1d895c48 --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/Instrumentable.kt @@ -0,0 +1,55 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +package io.bitdrift.capture.instrumentation + +import com.android.build.api.instrumentation.ClassContext +import io.bitdrift.capture.SpanAddingClassVisitorFactory +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor +import java.io.Serializable + +interface Instrumentable : Serializable { + + /** + * Fully-qualified name of the instrumentable. Examples: + * Class: androidx.sqlite.db.framework.FrameworkSQLiteDatabase + * Method: query + */ + val fqName: String get() = "" + + /** + * Provides a visitor for this instrumentable. A visitor can be one of the visitors defined + * in [ASM](https://asm.ow2.io/javadoc/org/objectweb/asm/package-summary.html) + * + * @param instrumentableContext A context of the instrumentable. + * @param apiVersion Defines the ASM api version, usually provided from the parent + * @param originalVisitor The original visitor that ASM provides us with before visiting code + * @param parameters Parameters that are configured by users and passed via the Sentry gradle plugin + */ + fun getVisitor( + instrumentableContext: InstrumentableContext, + apiVersion: Int, + originalVisitor: Visitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters + ): Visitor + + /** + * Defines whether this object is instrumentable or not based on [data] + */ + fun isInstrumentable(data: InstrumentableContext): Boolean +} +interface ClassInstrumentable : Instrumentable { + + override fun isInstrumentable(data: ClassContext): Boolean = + fqName == data.currentClassData.className +} + +interface MethodInstrumentable : Instrumentable { + + override fun isInstrumentable(data: MethodContext): Boolean = fqName == data.name +} diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/InstrumentableContext.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/InstrumentableContext.kt new file mode 100644 index 00000000..3f0025d8 --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/InstrumentableContext.kt @@ -0,0 +1,24 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +package io.bitdrift.capture.instrumentation + +import com.android.build.api.instrumentation.ClassData +import com.android.build.gradle.internal.instrumentation.ClassContextImpl +import com.android.build.gradle.internal.instrumentation.ClassesDataCache +import com.android.build.gradle.internal.instrumentation.ClassesHierarchyResolver + +data class MethodContext( + val access: Int, + val name: String?, + val descriptor: String?, + val signature: String?, + val exceptions: List? +) + +fun ClassData.toClassContext() = + ClassContextImpl(this, ClassesHierarchyResolver.Builder(ClassesDataCache()).build()) \ No newline at end of file diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/OkHttpEventListener.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/OkHttpEventListener.kt new file mode 100644 index 00000000..f6c3fc60 --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/OkHttpEventListener.kt @@ -0,0 +1,59 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +package io.bitdrift.capture.instrumentation.okhttp + +import com.android.build.api.instrumentation.ClassContext +import io.bitdrift.capture.SpanAddingClassVisitorFactory +import io.bitdrift.capture.instrumentation.ClassInstrumentable +import io.bitdrift.capture.instrumentation.CommonClassVisitor +import io.bitdrift.capture.instrumentation.MethodContext +import io.bitdrift.capture.instrumentation.MethodInstrumentable +import io.bitdrift.capture.instrumentation.okhttp.visitor.OkHttpEventListenerMethodVisitor +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor + +class OkHttpEventListener( +) : ClassInstrumentable { + override val fqName: String get() = "okhttp3.OkHttpClient" + + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters + ): ClassVisitor = CommonClassVisitor( + apiVersion = apiVersion, + classVisitor = originalVisitor, + className = fqName.substringAfterLast('.'), + methodInstrumentables = listOf( + OkHttpEventListenerMethodInstrumentable( + ) + ), + parameters = parameters + ) +} + +class OkHttpEventListenerMethodInstrumentable( +) : MethodInstrumentable { + override val fqName: String get() = "" + + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters + ): MethodVisitor = OkHttpEventListenerMethodVisitor( + apiVersion = apiVersion, + originalVisitor = originalVisitor, + instrumentableContext = instrumentableContext, + ) + + override fun isInstrumentable(data: MethodContext): Boolean { + return data.name == fqName && data.descriptor == "(Lokhttp3/OkHttpClient\$Builder;)V" + } +} \ No newline at end of file diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt new file mode 100644 index 00000000..4262936c --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt @@ -0,0 +1,67 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +package io.bitdrift.capture.instrumentation.okhttp.visitor + +import io.bitdrift.capture.instrumentation.MethodContext +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.commons.AdviceAdapter + +class OkHttpEventListenerMethodVisitor( + apiVersion: Int, + originalVisitor: MethodVisitor, + instrumentableContext: MethodContext, +) : AdviceAdapter( + apiVersion, + originalVisitor, + instrumentableContext.access, + instrumentableContext.name, + instrumentableContext.descriptor +) { + + private val captureOkHttpEventListenerFactory = + "io/bitdrift/capture/network/okhttp/CaptureOkHttpEventListenerFactory" + + override fun onMethodEnter() { + super.onMethodEnter() + // Add the following call at the beginning of the constructor with the Builder parameter: + // builder.eventListenerFactory(new CaptureOkHttpEventListenerFactory()); + + // OkHttpClient.Builder is the parameter, retrieved here + visitVarInsn(Opcodes.ALOAD, 1) + + // Let's declare the SentryOkHttpEventListener variable + visitTypeInsn(Opcodes.NEW, captureOkHttpEventListenerFactory) + + // The CaptureOkHttpEventListenerFactory constructor, which is called later, will consume the + // element without pushing anything back to the stack ( returns void). + // Dup will give a reference to the CaptureOkHttpEventListenerFactory after the constructor call + visitInsn(Opcodes.DUP) +// +// // Puts parameter OkHttpClient.Builder on top of the stack. +// visitVarInsn(Opcodes.ALOAD, 1) + + // Call CaptureOkHttpEventListenerFactory constructor + visitMethodInsn( + Opcodes.INVOKESPECIAL, + captureOkHttpEventListenerFactory, + "", + "()V", + false + ) + + // Call "eventListenerFactory" function of OkHttpClient.Builder passing CaptureOkHttpEventListenerFactory + visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "okhttp3/OkHttpClient\$Builder", + "eventListenerFactory", + "(Lokhttp3/EventListener\$Factory;)Lokhttp3/OkHttpClient\$Builder;", + false + ) + } +} \ No newline at end of file diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitor.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitor.kt new file mode 100644 index 00000000..baa34b72 --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitor.kt @@ -0,0 +1,42 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +package io.bitdrift.capture.instrumentation.util + +import io.bitdrift.capture.CapturePlugin +import io.bitdrift.capture.instrumentation.MethodContext +import org.objectweb.asm.MethodVisitor +import org.slf4j.Logger + +interface ExceptionHandler { + fun handle(exception: Throwable) +} + +class CatchingMethodVisitor( + apiVersion: Int, + prevVisitor: MethodVisitor, + private val className: String, + private val methodContext: MethodContext, + private val exceptionHandler: ExceptionHandler? = null, + private val logger: Logger = CapturePlugin.logger +) : MethodVisitor(apiVersion, prevVisitor) { + + override fun visitMaxs(maxStack: Int, maxLocals: Int) { + try { + super.visitMaxs(maxStack, maxLocals) + } catch (e: Throwable) { + exceptionHandler?.handle(e) + logger.error( + """ + Error while instrumenting $className.${methodContext.name} ${methodContext.descriptor}. + Please report this issue at https://github.com/getsentry/sentry-android-gradle-plugin/issues + """.trimIndent(), e + ) + throw e + } + } +} diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/ConstantPoolHelpers.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/ConstantPoolHelpers.kt new file mode 100644 index 00000000..10fd65bf --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/ConstantPoolHelpers.kt @@ -0,0 +1,107 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +package io.bitdrift.capture.instrumentation.util + +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.ClassWriter +import java.lang.reflect.Field + +/** + * 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) + ) +} \ No newline at end of file diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifier.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifier.kt new file mode 100644 index 00000000..8b4cfd98 --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifier.kt @@ -0,0 +1,49 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +package io.bitdrift.capture.instrumentation.util + +import java.io.File +import java.io.FileOutputStream +import java.io.PrintWriter +import org.objectweb.asm.util.Textifier + +class FileLogTextifier( + apiVersion: Int, + log: File, + methodName: String?, + methodDescriptor: String? +) : Textifier(apiVersion), ExceptionHandler { + + private var hasThrown = false + + private val fileOutputStream = FileOutputStream(log, true).apply { + write("function $methodName $methodDescriptor".toByteArray()) + write("\n".toByteArray()) + } + + override fun visitMethodEnd() { + if (!hasThrown) { + flushPrinter() + } + } + + override fun handle(exception: Throwable) { + hasThrown = true + flushPrinter() + } + + private fun flushPrinter() { + val printWriter = PrintWriter(fileOutputStream) + print(printWriter) + printWriter.flush() + // ASM textifier uses plain "\n" chars, so do we. As it's only for debug and dev purpose + // it doesn't matter to the end user + fileOutputStream.write("\n".toByteArray()) + fileOutputStream.close() + } +} \ No newline at end of file diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FileUtils.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FileUtils.kt new file mode 100644 index 00000000..e44cd59f --- /dev/null +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FileUtils.kt @@ -0,0 +1,44 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + +/* + * Adapted from https://github.com/apache/commons-lang/blob/ebcb39a62fc1e47251eceaf63a4b3d731c5227a0/src/main/java/org/apache/commons/lang3/reflect/FieldUtils.java + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.bitdrift.capture.instrumentation.util + +import java.lang.reflect.Field + +/** + * Gets all fields of the given class and its parents (if any). + */ +internal val Class<*>.allFields: List + get() { + val allFields = mutableListOf() + var currentClass: Class<*>? = this + while (currentClass != null) { + val declaredFields = currentClass.declaredFields + allFields += declaredFields + currentClass = currentClass.superclass + } + return allFields + } \ No newline at end of file diff --git a/platform/jvm/capture/build.gradle.kts b/platform/jvm/capture/build.gradle.kts index 239d4fcf..5c9580ff 100644 --- a/platform/jvm/capture/build.gradle.kts +++ b/platform/jvm/capture/build.gradle.kts @@ -85,6 +85,8 @@ cargo { targetDirectory = "../../../target" targets = listOf("arm64", "x86_64") pythonCommand = "python3" + rustcCommand = "/Users/snow/.cargo/bin/rustc" + cargoCommand = "/Users/snow/.cargo/bin/cargo" } // workaround bug in rust-android-gradle plugin that causes .so to not be available on app launch diff --git a/platform/jvm/gradle-test-app/build.gradle.kts b/platform/jvm/gradle-test-app/build.gradle.kts index 4c525292..356554df 100644 --- a/platform/jvm/gradle-test-app/build.gradle.kts +++ b/platform/jvm/gradle-test-app/build.gradle.kts @@ -2,6 +2,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.apollo.graphql) alias(libs.plugins.kotlin.android) + alias(libs.plugins.capture) } dependencies { @@ -41,6 +42,10 @@ dependencies { androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.4.0") } +bitdrift { + instrumentation.debug = true +} + android { namespace = "io.bitdrift.gradletestapp" compileSdk = 35 @@ -110,6 +115,7 @@ android { kotlinOptions { jvmTarget = "1.8" } + } // graphql @@ -118,4 +124,4 @@ apollo { // https://apollo-fullstack-tutorial.herokuapp.com/graphql packageName.set("com.example.rocketreserver") } -} +} \ No newline at end of file diff --git a/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/FirstFragment.kt b/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/FirstFragment.kt index 0cb96736..9acc061c 100644 --- a/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/FirstFragment.kt +++ b/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/FirstFragment.kt @@ -147,7 +147,6 @@ class FirstFragment : Fragment() { private fun provideOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() - .eventListenerFactory(CaptureOkHttpEventListenerFactory()) .build() } diff --git a/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/MainActivity.kt b/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/MainActivity.kt index 4d2d87d6..c27831a4 100644 --- a/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/MainActivity.kt +++ b/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/MainActivity.kt @@ -16,6 +16,7 @@ import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.navigateUp import androidx.navigation.ui.setupActionBarWithNavController import io.bitdrift.gradletestapp.databinding.ActivityMainBinding +import okhttp3.OkHttpClient class MainActivity : AppCompatActivity() { diff --git a/platform/jvm/gradle/libs.versions.toml b/platform/jvm/gradle/libs.versions.toml index e52840fe..5e02ad2c 100644 --- a/platform/jvm/gradle/libs.versions.toml +++ b/platform/jvm/gradle/libs.versions.toml @@ -15,17 +15,19 @@ kotlinResultJvm = "1.1.18" lifecycleCommon = "2.6.1" mavenPublishPlugin = "0.28.0" kotlinAndroidPlugin = "1.9.24" +kotlinPlugin = "1.9.24" mockitoCore = "4.9.0" mockitoKotlin = "2.2.0" mockitoKotlinVersion = "4.1.0" okhttp = "4.12.0" robolectric = "4.13" -rustAndroidPlugin = "0.9.3" +rustAndroidPlugin = "0.9.5" material3Android = "1.2.1" startupRuntime = "1.1.1" timber = "5.0.1" truth = "1.1.4" ui = "1.4.0" +capturePlugin = "0.1.0" [libraries] androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } @@ -60,4 +62,6 @@ detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detektPlugin" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokkaPlugin" } maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPublishPlugin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlinAndroidPlugin" } +kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlinPlugin" } rust-android = { id = "org.mozilla.rust-android-gradle.rust-android", version.ref = "rustAndroidPlugin" } +capture = { id = "io.bitdrift.capture.capture-plugin", version.ref = "capturePlugin" } diff --git a/platform/jvm/gradle/wrapper/gradle-wrapper.properties b/platform/jvm/gradle/wrapper/gradle-wrapper.properties index a3638774..171d8761 100644 --- a/platform/jvm/gradle/wrapper/gradle-wrapper.properties +++ b/platform/jvm/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/platform/jvm/settings.gradle.kts b/platform/jvm/settings.gradle.kts index 36ff231f..ff56ebe1 100644 --- a/platform/jvm/settings.gradle.kts +++ b/platform/jvm/settings.gradle.kts @@ -1,22 +1,25 @@ rootProject.name = "capture-sdk" +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + google() + mavenCentral() + } +} + include(":capture") include(":capture-apollo3") include(":capture-timber") include(":common") include(":replay") +include(":capture-plugin") include(":gradle-test-app") include(":microbenchmark") -pluginManagement { - repositories { - gradlePluginPortal() - google() - mavenCentral() - } -} dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) From b7fa67b445abf4d3a255107cead828cba1d8f3d6 Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Wed, 4 Dec 2024 13:37:51 -0800 Subject: [PATCH 02/10] licenses, move stuff around --- .../capture/AndroidComponentsConfig.kt | 36 ++++++++++++++++--- .../io/bitdrift/capture/CapturePlugin.kt | 3 +- .../BitdriftPluginExtension.kt | 5 +-- .../InstrumentationExtension.kt | 9 +---- .../instrumentation/ChainedInstrumentable.kt | 29 +++++++++++++-- .../instrumentation/CommonClassVisitor.kt | 27 +++++++++++++- .../capture/instrumentation/Instrumentable.kt | 27 +++++++++++++- .../instrumentation/InstrumentableContext.kt | 26 ++++++++++++++ .../SpanAddingClassVisitorFactory.kt | 31 ++++++++++++++-- .../okhttp/OkHttpEventListener.kt | 28 ++++++++++++++- .../OkHttpEventListenerMethodVisitor.kt | 29 +++++++++++++-- 11 files changed, 221 insertions(+), 29 deletions(-) rename platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/{ => extension}/BitdriftPluginExtension.kt (86%) rename platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/{ => extension}/InstrumentationExtension.kt (70%) rename platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/{ => instrumentation}/SpanAddingClassVisitorFactory.kt (69%) diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/AndroidComponentsConfig.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/AndroidComponentsConfig.kt index b846c563..e41e67a0 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/AndroidComponentsConfig.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/AndroidComponentsConfig.kt @@ -5,6 +5,32 @@ // LICENSE file or at: // https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package io.bitdrift.capture import com.android.build.api.instrumentation.AsmClassVisitorFactory @@ -14,15 +40,17 @@ import com.android.build.api.instrumentation.InstrumentationScope import com.android.build.api.variant.AndroidComponentsExtension import com.android.build.api.variant.Variant import io.bitdrift.capture.CapturePlugin.Companion.sep +import io.bitdrift.capture.extension.BitdriftPluginExtension +import io.bitdrift.capture.instrumentation.SpanAddingClassVisitorFactory import org.gradle.api.Project import java.io.File fun AndroidComponentsExtension<*, *, *>.configure( - project: Project, - extension: BitdriftPluginExtension, + project: Project, + extension: BitdriftPluginExtension, ) { - // temp folder for sentry-related stuff - val tmpDir = File("${project.buildDir}${sep}tmp${sep}sentry") + // Temp folder for outputting debug logs + val tmpDir = File("${project.layout.buildDirectory}${sep}tmp${sep}bitdrift") tmpDir.mkdirs() onVariants { variant -> diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/CapturePlugin.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/CapturePlugin.kt index 2abad53a..6b3e7e15 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/CapturePlugin.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/CapturePlugin.kt @@ -8,6 +8,7 @@ package io.bitdrift.capture import com.android.build.api.variant.AndroidComponentsExtension +import io.bitdrift.capture.extension.BitdriftPluginExtension import org.gradle.api.Plugin import org.gradle.api.Project import org.slf4j.LoggerFactory @@ -26,8 +27,6 @@ abstract class CapturePlugin @Inject constructor() : Plugin { target, extension, ) - - } } diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/BitdriftPluginExtension.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/extension/BitdriftPluginExtension.kt similarity index 86% rename from platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/BitdriftPluginExtension.kt rename to platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/extension/BitdriftPluginExtension.kt index 001d3765..e61610db 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/BitdriftPluginExtension.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/extension/BitdriftPluginExtension.kt @@ -5,7 +5,7 @@ // LICENSE file or at: // https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt -package io.bitdrift.capture +package io.bitdrift.capture.extension import org.gradle.api.Project import javax.inject.Inject @@ -13,8 +13,5 @@ import javax.inject.Inject abstract class BitdriftPluginExtension @Inject constructor(project: Project) { private val objects = project.objects - /** - * Configures the instrumentation feature. - */ val instrumentation: InstrumentationExtension = objects.newInstance(InstrumentationExtension::class.java) } \ No newline at end of file diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/InstrumentationExtension.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/extension/InstrumentationExtension.kt similarity index 70% rename from platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/InstrumentationExtension.kt rename to platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/extension/InstrumentationExtension.kt index 76e9d0fb..3e4a848f 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/InstrumentationExtension.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/extension/InstrumentationExtension.kt @@ -5,24 +5,17 @@ // LICENSE file or at: // https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt -package io.bitdrift.capture +package io.bitdrift.capture.extension import javax.inject.Inject import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property -import org.gradle.api.provider.SetProperty - -enum class InstrumentationFeature(val integrationName: String) { - OKHTTP("OkHttpInstrumentation") -} open class InstrumentationExtension @Inject constructor(objects: ObjectFactory) { val enabled: Property = objects.property(Boolean::class.java) .convention(true) - val features: SetProperty = objects.setProperty(InstrumentationFeature::class.java) - val debug: Property = objects.property(Boolean::class.java).convention( false ) diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentable.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentable.kt index 4d7b17ac..7b47d792 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentable.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentable.kt @@ -5,12 +5,37 @@ // LICENSE file or at: // https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + @file:Suppress("UnstableApiUsage") -package io.bitdrift.capture +package io.bitdrift.capture.instrumentation import com.android.build.api.instrumentation.ClassContext -import io.bitdrift.capture.instrumentation.ClassInstrumentable import java.util.LinkedList import org.objectweb.asm.ClassVisitor diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitor.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitor.kt index 9195eae8..17273310 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitor.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitor.kt @@ -5,9 +5,34 @@ // LICENSE file or at: // https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package io.bitdrift.capture.instrumentation -import io.bitdrift.capture.SpanAddingClassVisitorFactory import io.bitdrift.capture.instrumentation.util.CatchingMethodVisitor import io.bitdrift.capture.instrumentation.util.ExceptionHandler import io.bitdrift.capture.instrumentation.util.FileLogTextifier diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/Instrumentable.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/Instrumentable.kt index 1d895c48..00b21239 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/Instrumentable.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/Instrumentable.kt @@ -5,10 +5,35 @@ // LICENSE file or at: // https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package io.bitdrift.capture.instrumentation import com.android.build.api.instrumentation.ClassContext -import io.bitdrift.capture.SpanAddingClassVisitorFactory import org.objectweb.asm.ClassVisitor import org.objectweb.asm.MethodVisitor import java.io.Serializable diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/InstrumentableContext.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/InstrumentableContext.kt index 3f0025d8..432b0aa7 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/InstrumentableContext.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/InstrumentableContext.kt @@ -5,6 +5,32 @@ // LICENSE file or at: // https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package io.bitdrift.capture.instrumentation import com.android.build.api.instrumentation.ClassData diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/SpanAddingClassVisitorFactory.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/SpanAddingClassVisitorFactory.kt similarity index 69% rename from platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/SpanAddingClassVisitorFactory.kt rename to platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/SpanAddingClassVisitorFactory.kt index 01a496d4..c6ff448b 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/SpanAddingClassVisitorFactory.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/SpanAddingClassVisitorFactory.kt @@ -5,15 +5,40 @@ // LICENSE file or at: // https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt -package io.bitdrift.capture +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.bitdrift.capture.instrumentation import com.android.build.api.instrumentation.AsmClassVisitorFactory import com.android.build.api.instrumentation.ClassContext import com.android.build.api.instrumentation.ClassData import com.android.build.api.instrumentation.InstrumentationParameters -import io.bitdrift.capture.instrumentation.ClassInstrumentable +import io.bitdrift.capture.CapturePlugin import io.bitdrift.capture.instrumentation.okhttp.OkHttpEventListener -import io.bitdrift.capture.instrumentation.toClassContext import io.bitdrift.capture.instrumentation.util.findClassReader import io.bitdrift.capture.instrumentation.util.findClassWriter import io.bitdrift.capture.instrumentation.util.isMinifiedClass diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/OkHttpEventListener.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/OkHttpEventListener.kt index f6c3fc60..64ca3410 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/OkHttpEventListener.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/OkHttpEventListener.kt @@ -5,10 +5,36 @@ // LICENSE file or at: // https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package io.bitdrift.capture.instrumentation.okhttp import com.android.build.api.instrumentation.ClassContext -import io.bitdrift.capture.SpanAddingClassVisitorFactory +import io.bitdrift.capture.instrumentation.SpanAddingClassVisitorFactory import io.bitdrift.capture.instrumentation.ClassInstrumentable import io.bitdrift.capture.instrumentation.CommonClassVisitor import io.bitdrift.capture.instrumentation.MethodContext diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt index 4262936c..42f7ff19 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt @@ -5,6 +5,32 @@ // LICENSE file or at: // https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package io.bitdrift.capture.instrumentation.okhttp.visitor import io.bitdrift.capture.instrumentation.MethodContext @@ -42,9 +68,6 @@ class OkHttpEventListenerMethodVisitor( // element without pushing anything back to the stack ( returns void). // Dup will give a reference to the CaptureOkHttpEventListenerFactory after the constructor call visitInsn(Opcodes.DUP) -// -// // Puts parameter OkHttpClient.Builder on top of the stack. -// visitVarInsn(Opcodes.ALOAD, 1) // Call CaptureOkHttpEventListenerFactory constructor visitMethodInsn( From 41d7f485a5d203d43aaa960d3c7bb6e121f87ecc Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Wed, 4 Dec 2024 13:57:10 -0800 Subject: [PATCH 03/10] more licenses --- .../util/CatchingMethodVisitor.kt | 28 ++++++++++++++++++- .../util/ConstantPoolHelpers.kt | 26 +++++++++++++++++ .../util/{FileUtils.kt => FieldUtils.kt} | 0 .../instrumentation/util/FileLogTextifier.kt | 26 +++++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) rename platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/{FileUtils.kt => FieldUtils.kt} (100%) diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitor.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitor.kt index baa34b72..348632cc 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitor.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitor.kt @@ -5,6 +5,32 @@ // LICENSE file or at: // https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package io.bitdrift.capture.instrumentation.util import io.bitdrift.capture.CapturePlugin @@ -33,7 +59,7 @@ class CatchingMethodVisitor( logger.error( """ Error while instrumenting $className.${methodContext.name} ${methodContext.descriptor}. - Please report this issue at https://github.com/getsentry/sentry-android-gradle-plugin/issues + Please report this issue at https://github.com/bitdriftlabs/capture-sdk/issues """.trimIndent(), e ) throw e diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/ConstantPoolHelpers.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/ConstantPoolHelpers.kt index 10fd65bf..9fc24a3c 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/ConstantPoolHelpers.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/ConstantPoolHelpers.kt @@ -5,6 +5,32 @@ // LICENSE file or at: // https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package io.bitdrift.capture.instrumentation.util import org.objectweb.asm.ClassReader diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FileUtils.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FieldUtils.kt similarity index 100% rename from platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FileUtils.kt rename to platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FieldUtils.kt diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifier.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifier.kt index 8b4cfd98..628e78d5 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifier.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifier.kt @@ -5,6 +5,32 @@ // LICENSE file or at: // https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package io.bitdrift.capture.instrumentation.util import java.io.File From 005841fc27fcb6437effb1315f9123fe67d11e3b Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Wed, 4 Dec 2024 14:05:49 -0800 Subject: [PATCH 04/10] support proxying to the existing factory --- .../OkHttpEventListenerMethodVisitor.kt | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt index 42f7ff19..bec36f6b 100644 --- a/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt +++ b/platform/jvm/capture-plugin/src/main/kotlin/io/bitdrift/capture/instrumentation/okhttp/visitor/OkHttpEventListenerMethodVisitor.kt @@ -55,13 +55,14 @@ class OkHttpEventListenerMethodVisitor( override fun onMethodEnter() { super.onMethodEnter() + // Add the following call at the beginning of the constructor with the Builder parameter: - // builder.eventListenerFactory(new CaptureOkHttpEventListenerFactory()); + // builder.eventListener(new CaptureOkHttpEventListener(builder.eventListenerFactory)); // OkHttpClient.Builder is the parameter, retrieved here visitVarInsn(Opcodes.ALOAD, 1) - // Let's declare the SentryOkHttpEventListener variable + // Let's declare the CaptureOkHttpEventListenerFactory variable visitTypeInsn(Opcodes.NEW, captureOkHttpEventListenerFactory) // The CaptureOkHttpEventListenerFactory constructor, which is called later, will consume the @@ -69,22 +70,34 @@ class OkHttpEventListenerMethodVisitor( // Dup will give a reference to the CaptureOkHttpEventListenerFactory after the constructor call visitInsn(Opcodes.DUP) - // Call CaptureOkHttpEventListenerFactory constructor + // Puts parameter OkHttpClient.Builder on top of the stack. + visitVarInsn(Opcodes.ALOAD, 1) + + // Read the "eventListenerFactory" field from OkHttpClient.Builder + visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "okhttp3/OkHttpClient\$Builder", + "getEventListenerFactory\$okhttp", + "()Lokhttp3/EventListener\$Factory;", + false + ) + + // Call CaptureOkHttpEventListenerFactory constructor passing "eventListenerFactory" as parameter visitMethodInsn( - Opcodes.INVOKESPECIAL, - captureOkHttpEventListenerFactory, - "", - "()V", - false + Opcodes.INVOKESPECIAL, + captureOkHttpEventListenerFactory, + "", + "(Lokhttp3/EventListener\$Factory;)V", + false ) - // Call "eventListenerFactory" function of OkHttpClient.Builder passing CaptureOkHttpEventListenerFactory + // Call "eventListener" function of OkHttpClient.Builder passing CaptureOkHttpEventListenerFactory visitMethodInsn( - Opcodes.INVOKEVIRTUAL, - "okhttp3/OkHttpClient\$Builder", - "eventListenerFactory", - "(Lokhttp3/EventListener\$Factory;)Lokhttp3/OkHttpClient\$Builder;", - false + Opcodes.INVOKEVIRTUAL, + "okhttp3/OkHttpClient\$Builder", + "eventListenerFactory", + "(Lokhttp3/EventListener\$Factory;)Lokhttp3/OkHttpClient\$Builder;", + false ) } } \ No newline at end of file From 9a8fca6f0fa33702cda917d1ab33514da2e91755 Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Wed, 4 Dec 2024 14:44:05 -0800 Subject: [PATCH 05/10] bring in tests --- platform/jvm/capture-plugin/build.gradle.kts | 8 + .../ChainedInstrumentableTest.kt | 135 ++++++++++++++++ .../instrumentation/CommonClassVisitorTest.kt | 146 +++++++++++++++++ .../instrumentation/fakes/BaseTestLogger.kt | 152 ++++++++++++++++++ .../fakes/CapturingTestLogger.kt | 60 +++++++ .../instrumentation/fakes/TestClassContext.kt | 26 +++ .../fakes/TestSpanAddingParameters.kt | 49 ++++++ .../util/CatchingMethodVisitorTest.kt | 107 ++++++++++++ .../util/FileLogTextifierTest.kt | 131 +++++++++++++++ .../util/MinifiedClassDetectionTest.kt | 93 +++++++++++ 10 files changed, 907 insertions(+) create mode 100644 platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentableTest.kt create mode 100644 platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitorTest.kt create mode 100644 platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/BaseTestLogger.kt create mode 100644 platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/CapturingTestLogger.kt create mode 100644 platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestClassContext.kt create mode 100644 platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestSpanAddingParameters.kt create mode 100644 platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitorTest.kt create mode 100644 platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifierTest.kt create mode 100644 platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/MinifiedClassDetectionTest.kt diff --git a/platform/jvm/capture-plugin/build.gradle.kts b/platform/jvm/capture-plugin/build.gradle.kts index 994eb2f6..f16fd050 100644 --- a/platform/jvm/capture-plugin/build.gradle.kts +++ b/platform/jvm/capture-plugin/build.gradle.kts @@ -11,6 +11,14 @@ dependencies { compileOnly("com.android.tools.build:gradle:7.4.0") compileOnly("org.ow2.asm:asm-commons:9.4") compileOnly("org.ow2.asm:asm-util:9.4") + + testImplementation(gradleTestKit()) + testImplementation(kotlin("test")) + testImplementation("com.android.tools.build:gradle:7.4.0") + testImplementation("junit:junit:4.13.2") + testImplementation("org.ow2.asm:asm-commons:9.4") + testImplementation("org.ow2.asm:asm-util:9.4") + testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0") } gradlePlugin { diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentableTest.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentableTest.kt new file mode 100644 index 00000000..d0916bac --- /dev/null +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentableTest.kt @@ -0,0 +1,135 @@ + +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.bitdrift.capture.instrumentation + +import com.android.build.api.instrumentation.ClassContext +import io.sentry.android.gradle.instrumentation.fakes.TestClassContext +import io.sentry.android.gradle.instrumentation.fakes.TestClassData +import io.bitdrift.capture.instrumentation.fakes.TestSpanAddingParameters +import java.io.File +import kotlin.test.assertTrue +import org.junit.Test +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.Opcodes + +class ChainedInstrumentableTest { + class Fixture { + fun getSut( + originalVisitor: ClassVisitor, + instrumentables: List = emptyList() + ): ClassVisitor { + return ChainedInstrumentable(instrumentables).getVisitor( + TestClassContext(TestClassData("RandomClass")), + Opcodes.ASM7, + originalVisitor, + TestSpanAddingParameters(inMemoryDir = File("")) + ) + } + } + + private val fixture = Fixture() + + @Test + fun `when empty instrumentables list returns original visitor`() { + val sut = fixture.getSut(OriginalVisitor()) + + assertTrue { sut is OriginalVisitor } + } + + @Test + fun `when no isInstrumentable found returns original visitor`() { + val sut = fixture.getSut( + OriginalVisitor(), + listOf( + FirstInstrumentable(isInstrumentable = false), + SecondInstrumentable(isInstrumentable = false) + ) + ) + + assertTrue { sut is OriginalVisitor } + } + + @Test + fun `skip non-instrumentables in the chain`() { + val sut = fixture.getSut( + OriginalVisitor(), + listOf( + FirstInstrumentable(isInstrumentable = false), + SecondInstrumentable(isInstrumentable = true) + ) + ) + + assertTrue { + sut is SecondInstrumentable.SecondVisitor && sut.prevVisitor is OriginalVisitor + } + } + + @Test + fun `all instrumentables`() { + val sut = + fixture.getSut(OriginalVisitor(), listOf(FirstInstrumentable(), SecondInstrumentable())) + + assertTrue { + sut is SecondInstrumentable.SecondVisitor && + sut.prevVisitor is FirstInstrumentable.FirstVisitor && + (sut.prevVisitor as FirstInstrumentable.FirstVisitor).prevVisitor is OriginalVisitor + } + } +} + +class OriginalVisitor : ClassVisitor(Opcodes.ASM7) + +class FirstInstrumentable(val isInstrumentable: Boolean = true) : ClassInstrumentable { + class FirstVisitor(prevVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM7, prevVisitor) { + val prevVisitor get() = cv + } + + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters + ): ClassVisitor = FirstVisitor(originalVisitor) + + override fun isInstrumentable(data: ClassContext): Boolean = isInstrumentable +} + +class SecondInstrumentable(val isInstrumentable: Boolean = true) : ClassInstrumentable { + class SecondVisitor(prevVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM7, prevVisitor) { + val prevVisitor get() = cv + } + + override fun getVisitor( + instrumentableContext: ClassContext, + apiVersion: Int, + originalVisitor: ClassVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters + ): ClassVisitor = SecondVisitor(originalVisitor) + + override fun isInstrumentable(data: ClassContext): Boolean = isInstrumentable +} diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitorTest.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitorTest.kt new file mode 100644 index 00000000..73f4bc4e --- /dev/null +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitorTest.kt @@ -0,0 +1,146 @@ +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.bitdrift.capture.instrumentation + +import io.bitdrift.capture.instrumentation.util.CatchingMethodVisitor +import io.bitdrift.capture.instrumentation.fakes.TestSpanAddingParameters +import java.io.File +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes + +class CommonClassVisitorTest { + + class Fixture { + + fun getSut(tmpDir: File, debug: Boolean = false) = + CommonClassVisitor( + Opcodes.ASM7, + ParentClassVisitor(), + "SomeClass", + listOf(TestInstrumentable()), + TestSpanAddingParameters(debugOutput = debug, inMemoryDir = tmpDir) + ) + } + + @get:Rule + val tmpDir = TemporaryFolder() + + private val fixture = Fixture() + + @Test + fun `when debug - creates a file with class name on init`() { + fixture.getSut(tmpDir.root, true) + + val file = File(tmpDir.root, "SomeClass-instrumentation.log") + assertTrue { file.exists() } + } + + @Test + fun `when debug and is instrumentable - prepends with TraceMethodVisitor`() { + val mv = fixture.getSut(tmpDir.root, true) + .visitMethod(Opcodes.ACC_PUBLIC, "test", null, null, null) + + mv.visitVarInsn(Opcodes.ASTORE, 0) + mv.visitEnd() + + // we read the file and compare its content to ensure that TraceMethodVisitor was called and + // wrote the instructions into the file + val file = File(tmpDir.root, "SomeClass-instrumentation.log") + assertEquals( + file.readText(), + """ + |function test null + | ASTORE 0 + | + | + """.trimMargin() + ) + } + + @Test + fun `when no debug and is instrumentable - skips TraceMethodVisitor`() { + val mv = fixture.getSut(tmpDir.root, true) + .visitMethod(Opcodes.ACC_PUBLIC, "other", null, null, null) + + mv.visitVarInsn(Opcodes.ASTORE, 0) + mv.visitEnd() + + // we read the file and compare its content to ensure that TraceMethodVisitor was skipped + val file = File(tmpDir.root, "SomeClass-instrumentation.log") + assertTrue { file.readText().isEmpty() } + } + + @Test + fun `when matches method name returns instrumentable visitor wrapped into catching visitor`() { + val mv = + fixture.getSut(tmpDir.root).visitMethod(Opcodes.ACC_PUBLIC, "test", null, null, null) + + assertTrue { mv is CatchingMethodVisitor } + } + + @Test + fun `when doesn't match method name return original visitor`() { + val mv = + fixture.getSut(tmpDir.root).visitMethod(Opcodes.ACC_PUBLIC, "other", null, null, null) + + assertTrue { mv is ParentClassVisitor.ParentMethodVisitor } + } +} + +class ParentClassVisitor : ClassVisitor(Opcodes.ASM7) { + + inner class ParentMethodVisitor : MethodVisitor(Opcodes.ASM7) + + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array? + ): MethodVisitor = ParentMethodVisitor() +} + +class TestInstrumentable : MethodInstrumentable { + + inner class TestVisitor(originalVisitor: MethodVisitor) : + MethodVisitor(Opcodes.ASM7, originalVisitor) + + override val fqName: String get() = "test" + + override fun getVisitor( + instrumentableContext: MethodContext, + apiVersion: Int, + originalVisitor: MethodVisitor, + parameters: SpanAddingClassVisitorFactory.SpanAddingParameters + ): MethodVisitor = TestVisitor(originalVisitor) +} diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/BaseTestLogger.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/BaseTestLogger.kt new file mode 100644 index 00000000..b4d4e326 --- /dev/null +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/BaseTestLogger.kt @@ -0,0 +1,152 @@ +package io.sentry.android.gradle.instrumentation.fakes + +import org.gradle.api.logging.LogLevel +import org.gradle.api.logging.Logger +import org.slf4j.Marker + +abstract class BaseTestLogger : Logger { + + override fun isTraceEnabled(): Boolean = true + + override fun isTraceEnabled(marker: Marker): Boolean = true + + override fun trace(msg: String) = Unit + + override fun trace(msg: String, arg: Any) = Unit + + override fun trace(msg: String, arg: Any, arg2: Any) = Unit + + override fun trace(msg: String, vararg args: Any) = Unit + + override fun trace(msg: String, throwable: Throwable?) = Unit + + override fun trace(marker: Marker, msg: String) = Unit + + override fun trace(marker: Marker, msg: String, arg2: Any) = Unit + + override fun trace(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit + + override fun trace(marker: Marker, msg: String, vararg args: Any) = Unit + + override fun trace(marker: Marker, msg: String, throwable: Throwable?) = Unit + + override fun isDebugEnabled(): Boolean = true + + override fun isDebugEnabled(marker: Marker): Boolean = true + + override fun debug(msg: String) = Unit + + override fun debug(msg: String, arg: Any) = Unit + + override fun debug(msg: String, arg: Any, arg2: Any) = Unit + + override fun debug(msg: String, vararg args: Any) = Unit + + override fun debug(msg: String, throwable: Throwable?) = Unit + + override fun debug(marker: Marker, msg: String) = Unit + + override fun debug(marker: Marker, msg: String, arg2: Any) = Unit + + override fun debug(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit + + override fun debug(marker: Marker, msg: String, vararg args: Any) = Unit + + override fun debug(marker: Marker, msg: String, throwable: Throwable?) = Unit + + override fun isInfoEnabled(): Boolean = true + + override fun isInfoEnabled(marker: Marker): Boolean = true + + override fun info(msg: String) = Unit + + override fun info(msg: String, arg: Any) = Unit + + override fun info(msg: String, arg: Any, arg2: Any) = Unit + + override fun info(msg: String, vararg args: Any) = Unit + + override fun info(msg: String, throwable: Throwable?) = Unit + + override fun info(marker: Marker, msg: String) = Unit + + override fun info(marker: Marker, msg: String, arg2: Any) = Unit + + override fun info(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit + + override fun info(marker: Marker, msg: String, vararg args: Any) = Unit + + override fun info(marker: Marker, msg: String, throwable: Throwable?) = Unit + + override fun isWarnEnabled(): Boolean = true + + override fun isWarnEnabled(marker: Marker): Boolean = true + + override fun warn(msg: String) = Unit + + override fun warn(msg: String, arg: Any) = Unit + + override fun warn(msg: String, vararg args: Any) = Unit + + override fun warn(msg: String, arg: Any, arg2: Any) = Unit + + override fun warn(msg: String, throwable: Throwable?) = Unit + + override fun warn(marker: Marker, msg: String) = Unit + + override fun warn(marker: Marker, msg: String, arg2: Any) = Unit + + override fun warn(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit + + override fun warn(marker: Marker, msg: String, vararg args: Any) = Unit + + override fun warn(marker: Marker, msg: String, throwable: Throwable?) = Unit + + override fun isErrorEnabled(): Boolean = true + + override fun isErrorEnabled(marker: Marker): Boolean = true + + override fun error(msg: String) = Unit + + override fun error(msg: String, arg: Any) = Unit + + override fun error(msg: String, arg: Any, arg2: Any) = Unit + + override fun error(msg: String, vararg args: Any) = Unit + + override fun error(msg: String, throwable: Throwable?) = Unit + + override fun error(marker: Marker, msg: String) = Unit + + override fun error(marker: Marker, msg: String, arg2: Any) = Unit + + override fun error(marker: Marker, msg: String, arg2: Any, arg3: Any) = Unit + + override fun error(marker: Marker, msg: String, vararg args: Any) = Unit + + override fun error(marker: Marker, msg: String, throwable: Throwable?) = Unit + + override fun isLifecycleEnabled(): Boolean = true + + override fun lifecycle(message: String?) = Unit + + override fun lifecycle(message: String?, vararg objects: Any?) = Unit + + override fun lifecycle(message: String?, throwable: Throwable?) = Unit + + override fun isQuietEnabled(): Boolean = true + + override fun quiet(message: String?) = Unit + + override fun quiet(message: String?, vararg objects: Any?) = Unit + + override fun quiet(message: String?, throwable: Throwable?) = Unit + + override fun isEnabled(level: LogLevel?): Boolean = true + + override fun log(level: LogLevel?, message: String?) = Unit + + override fun log(level: LogLevel?, message: String?, vararg objects: Any?) = Unit + + override fun log(level: LogLevel?, message: String?, throwable: Throwable?) = Unit +} diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/CapturingTestLogger.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/CapturingTestLogger.kt new file mode 100644 index 00000000..c5a03919 --- /dev/null +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/CapturingTestLogger.kt @@ -0,0 +1,60 @@ +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.bitdrift.capture.instrumentation.fakes + +import io.sentry.android.gradle.instrumentation.fakes.BaseTestLogger + +class CapturingTestLogger : BaseTestLogger() { + override fun getName(): String = "SentryPluginTest" + + var capturedMessage: String? = null + var capturedThrowable: Throwable? = null + + override fun error(msg: String, throwable: Throwable?) { + capturedMessage = msg + capturedThrowable = throwable + } + + override fun warn(msg: String, throwable: Throwable?) { + capturedMessage = msg + capturedThrowable = throwable + } + + override fun warn(msg: String) { + capturedMessage = msg + } + + override fun info(msg: String, throwable: Throwable?) { + capturedMessage = msg + capturedThrowable = throwable + } + + override fun debug(msg: String, throwable: Throwable?) { + capturedMessage = msg + capturedThrowable = throwable + } +} diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestClassContext.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestClassContext.kt new file mode 100644 index 00000000..4db089e8 --- /dev/null +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestClassContext.kt @@ -0,0 +1,26 @@ +@file:Suppress("UnstableApiUsage") + +package io.sentry.android.gradle.instrumentation.fakes + +import com.android.build.api.instrumentation.ClassContext +import com.android.build.api.instrumentation.ClassData + +data class TestClassData( + override val className: String, + override val classAnnotations: List = emptyList(), + override val interfaces: List = emptyList(), + override val superClasses: List = emptyList() +) : ClassData + +data class TestClassContext( + override val currentClassData: ClassData, + private val classLoader: (String) -> ClassData? = { null } +) : ClassContext { + + constructor(className: String) : this(TestClassData(className)) + + constructor(className: String, classLoader: (String) -> ClassData?) : + this(TestClassData(className), classLoader) + + override fun loadClassData(className: String): ClassData? = classLoader(className) +} diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestSpanAddingParameters.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestSpanAddingParameters.kt new file mode 100644 index 00000000..8323be5b --- /dev/null +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestSpanAddingParameters.kt @@ -0,0 +1,49 @@ +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.bitdrift.capture.instrumentation.fakes + +import io.bitdrift.capture.instrumentation.ClassInstrumentable +import io.bitdrift.capture.instrumentation.SpanAddingClassVisitorFactory +import java.io.File +import org.gradle.api.internal.provider.DefaultProperty +import org.gradle.api.internal.provider.PropertyHost +import org.gradle.api.provider.Property + +class TestSpanAddingParameters( + private val debugOutput: Boolean = true, + private val inMemoryDir: File +) : SpanAddingClassVisitorFactory.SpanAddingParameters { + override val debug: Property + get() = DefaultProperty(PropertyHost.NO_OP, Boolean::class.javaObjectType) + .convention(debugOutput) + + override val tmpDir: Property + get() = DefaultProperty(PropertyHost.NO_OP, File::class.java).convention(inMemoryDir) + + override var _instrumentable: ClassInstrumentable? = null + +} diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitorTest.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitorTest.kt new file mode 100644 index 00000000..234335ac --- /dev/null +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitorTest.kt @@ -0,0 +1,107 @@ +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.bitdrift.capture.instrumentation.util + +import io.bitdrift.capture.instrumentation.MethodContext +import io.bitdrift.capture.instrumentation.fakes.CapturingTestLogger +import kotlin.test.assertEquals +import org.junit.Test +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes + +class CatchingMethodVisitorTest { + + class Fixture { + private val throwingVisitor = ThrowingMethodVisitor() + val handler = CapturingExceptionHandler() + val logger = CapturingTestLogger() + + private val methodContext = + MethodContext(Opcodes.ACC_PUBLIC, "someMethod", null, null, null) + val sut + get() = CatchingMethodVisitor( + Opcodes.ASM7, + throwingVisitor, + "SomeClass", + methodContext, + handler, + logger + ) + } + + private val fixture = Fixture() + + @Test + fun `forwards exception to ExceptionHandler`() { + try { + fixture.sut.visitMaxs(0, 0) + } catch (ignored: Throwable) { + } finally { + assertEquals(fixture.handler.capturedException!!.message, "This method throws!") + } + } + + @Test(expected = CustomException::class) + fun `rethrows exception`() { + fixture.sut.visitMaxs(0, 0) + } + + @Test + fun `prints message to log`() { + try { + fixture.sut.visitMaxs(0, 0) + } catch (ignored: Throwable) { + } finally { + assertEquals(fixture.logger.capturedThrowable!!.message, "This method throws!") + assertEquals( + fixture.logger.capturedMessage, + """ + Error while instrumenting SomeClass.someMethod null. + Please report this issue at https://github.com/bitdriftlabs/capture-sdk/issues + """.trimIndent() + ) + } + } +} + +class CustomException : RuntimeException("This method throws!") + +class ThrowingMethodVisitor : MethodVisitor(Opcodes.ASM7) { + + override fun visitMaxs(maxStack: Int, maxLocals: Int) { + throw CustomException() + } +} + +class CapturingExceptionHandler : ExceptionHandler { + + var capturedException: Throwable? = null + + override fun handle(exception: Throwable) { + capturedException = exception + } +} diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifierTest.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifierTest.kt new file mode 100644 index 00000000..6958c99e --- /dev/null +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifierTest.kt @@ -0,0 +1,131 @@ +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.bitdrift.capture.instrumentation.util + +import java.io.File +import kotlin.test.assertEquals +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.objectweb.asm.Label +import org.objectweb.asm.Opcodes + +class FileLogTextifierTest { + + class Fixture { + + fun getSut(tmpFile: File) = + FileLogTextifier( + Opcodes.ASM7, + tmpFile, + "SomeMethod", + "(Ljava/lang/Throwable;)V" + ) + + fun visitMethodInstructions(sut: FileLogTextifier) { + sut.visitVarInsn(Opcodes.ASTORE, 0) + sut.visitLabel(Label()) + sut.visitLdcInsn("db") + } + } + + @get:Rule + val tmpDir = TemporaryFolder() + + private val fixture = Fixture() + + @Test + fun `prints methodName on ccreation`() { + fixture.getSut(tmpDir.newFile("instrumentation.log")) + + val file = File(tmpDir.root, "instrumentation.log") + assertEquals( + file.readText(), + "function SomeMethod (Ljava/lang/Throwable;)V\n" + ) + } + + @Test + fun `visitMethodEnd flushes output to file if hasn't thrown`() { + val sut = fixture.getSut(tmpDir.newFile("instrumentation.log")) + fixture.visitMethodInstructions(sut) + sut.visitMethodEnd() + + val file = File(tmpDir.root, "instrumentation.log") + assertEquals( + file.readText(), + """ + |function SomeMethod (Ljava/lang/Throwable;)V + | ASTORE 0 + | L0 + | LDC "db" + | + | + """.trimMargin() + ) + } + + @Test + fun `visitMethodEnd does nothing if has thrown`() { + val sut = fixture.getSut(tmpDir.newFile("instrumentation.log")) + sut.handle(RuntimeException()) + fixture.visitMethodInstructions(sut) + sut.visitMethodEnd() + + val file = File(tmpDir.root, "instrumentation.log") + // sut.handle will add one more newline to the end of file, but actual visited instructions + // will not be flushed to file + assertEquals( + file.readText(), + """ + |function SomeMethod (Ljava/lang/Throwable;)V + | + | + """.trimMargin() + ) + } + + @Test + fun `handle exception flushes output to file`() { + val sut = fixture.getSut(tmpDir.newFile("instrumentation.log")) + fixture.visitMethodInstructions(sut) + sut.handle(RuntimeException()) + + val file = File(tmpDir.root, "instrumentation.log") + assertEquals( + file.readText(), + """ + |function SomeMethod (Ljava/lang/Throwable;)V + | ASTORE 0 + | L0 + | LDC "db" + | + | + """.trimMargin() + ) + } +} diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/MinifiedClassDetectionTest.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/MinifiedClassDetectionTest.kt new file mode 100644 index 00000000..6d89b2c7 --- /dev/null +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/MinifiedClassDetectionTest.kt @@ -0,0 +1,93 @@ +/** + * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 + * + * MIT License + * + * Copyright (c) 2020 Sentry + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package io.bitdrift.capture.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) + } + } + + @Test + fun `does not consider short class names as minified classes`() { + val classNames = listOf( + Pair("Call", "retrofit2/Call"), + Pair("Call", "okhttp3/Call"), + Pair("Fill", "androidx/compose/ui/graphics/drawscope/Fill"), + Pair("Px", "androidx/annotation/Px"), + Pair("Dp", "androidx/annotation/Dp") + ) + + classNames.forEach { (simpleName, fullName) -> + assertFalse(classNameLooksMinified(simpleName, fullName)) + } + } +} From cbf7f3a44b2806b9d36552da9a0815098ff4e7e4 Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Wed, 4 Dec 2024 14:44:26 -0800 Subject: [PATCH 06/10] fix license for test code --- .../capture/instrumentation/ChainedInstrumentableTest.kt | 7 +++++++ .../capture/instrumentation/CommonClassVisitorTest.kt | 7 +++++++ .../capture/instrumentation/fakes/BaseTestLogger.kt | 7 +++++++ .../capture/instrumentation/fakes/CapturingTestLogger.kt | 7 +++++++ .../capture/instrumentation/fakes/TestClassContext.kt | 7 +++++++ .../instrumentation/fakes/TestSpanAddingParameters.kt | 7 +++++++ .../instrumentation/util/CatchingMethodVisitorTest.kt | 7 +++++++ .../capture/instrumentation/util/FileLogTextifierTest.kt | 7 +++++++ .../instrumentation/util/MinifiedClassDetectionTest.kt | 7 +++++++ 9 files changed, 63 insertions(+) diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentableTest.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentableTest.kt index d0916bac..cd3a2510 100644 --- a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentableTest.kt +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentableTest.kt @@ -1,3 +1,10 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + /** * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitorTest.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitorTest.kt index 73f4bc4e..cacbebc9 100644 --- a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitorTest.kt +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/CommonClassVisitorTest.kt @@ -1,3 +1,10 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + /** * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 * diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/BaseTestLogger.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/BaseTestLogger.kt index b4d4e326..45a0bc7e 100644 --- a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/BaseTestLogger.kt +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/BaseTestLogger.kt @@ -1,3 +1,10 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + package io.sentry.android.gradle.instrumentation.fakes import org.gradle.api.logging.LogLevel diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/CapturingTestLogger.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/CapturingTestLogger.kt index c5a03919..59abb3b9 100644 --- a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/CapturingTestLogger.kt +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/CapturingTestLogger.kt @@ -1,3 +1,10 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + /** * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 * diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestClassContext.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestClassContext.kt index 4db089e8..fdf9e852 100644 --- a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestClassContext.kt +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestClassContext.kt @@ -1,3 +1,10 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + @file:Suppress("UnstableApiUsage") package io.sentry.android.gradle.instrumentation.fakes diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestSpanAddingParameters.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestSpanAddingParameters.kt index 8323be5b..7cfaedb1 100644 --- a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestSpanAddingParameters.kt +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestSpanAddingParameters.kt @@ -1,3 +1,10 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + /** * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 * diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitorTest.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitorTest.kt index 234335ac..6890c652 100644 --- a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitorTest.kt +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/CatchingMethodVisitorTest.kt @@ -1,3 +1,10 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + /** * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 * diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifierTest.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifierTest.kt index 6958c99e..ba635ac3 100644 --- a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifierTest.kt +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/FileLogTextifierTest.kt @@ -1,3 +1,10 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + /** * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 * diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/MinifiedClassDetectionTest.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/MinifiedClassDetectionTest.kt index 6d89b2c7..55a0f143 100644 --- a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/MinifiedClassDetectionTest.kt +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/util/MinifiedClassDetectionTest.kt @@ -1,3 +1,10 @@ +// capture-sdk - bitdrift's client SDK +// Copyright Bitdrift, Inc. All rights reserved. +// +// Use of this source code is governed by a source available license that can be found in the +// LICENSE file or at: +// https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt + /** * Adapted from https://github.com/getsentry/sentry-android-gradle-plugin/tree/4.14.1 * From 4d35672a11fc2eceac78dce6d99cd193ca861922 Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Wed, 4 Dec 2024 14:51:40 -0800 Subject: [PATCH 07/10] fix package paths --- .../capture/instrumentation/ChainedInstrumentableTest.kt | 4 ++-- .../bitdrift/capture/instrumentation/fakes/BaseTestLogger.kt | 2 +- .../capture/instrumentation/fakes/CapturingTestLogger.kt | 2 -- .../capture/instrumentation/fakes/TestClassContext.kt | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentableTest.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentableTest.kt index cd3a2510..738e53fa 100644 --- a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentableTest.kt +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/ChainedInstrumentableTest.kt @@ -35,8 +35,8 @@ package io.bitdrift.capture.instrumentation import com.android.build.api.instrumentation.ClassContext -import io.sentry.android.gradle.instrumentation.fakes.TestClassContext -import io.sentry.android.gradle.instrumentation.fakes.TestClassData +import io.bitdrift.capture.instrumentation.fakes.TestClassContext +import io.bitdrift.capture.instrumentation.fakes.TestClassData import io.bitdrift.capture.instrumentation.fakes.TestSpanAddingParameters import java.io.File import kotlin.test.assertTrue diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/BaseTestLogger.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/BaseTestLogger.kt index 45a0bc7e..24031189 100644 --- a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/BaseTestLogger.kt +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/BaseTestLogger.kt @@ -5,7 +5,7 @@ // LICENSE file or at: // https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt -package io.sentry.android.gradle.instrumentation.fakes +package io.bitdrift.capture.instrumentation.fakes import org.gradle.api.logging.LogLevel import org.gradle.api.logging.Logger diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/CapturingTestLogger.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/CapturingTestLogger.kt index 59abb3b9..39eaf3a3 100644 --- a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/CapturingTestLogger.kt +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/CapturingTestLogger.kt @@ -33,8 +33,6 @@ package io.bitdrift.capture.instrumentation.fakes -import io.sentry.android.gradle.instrumentation.fakes.BaseTestLogger - class CapturingTestLogger : BaseTestLogger() { override fun getName(): String = "SentryPluginTest" diff --git a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestClassContext.kt b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestClassContext.kt index fdf9e852..d3ad5cf7 100644 --- a/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestClassContext.kt +++ b/platform/jvm/capture-plugin/src/test/kotlin/io/bitdrift/capture/instrumentation/fakes/TestClassContext.kt @@ -7,7 +7,7 @@ @file:Suppress("UnstableApiUsage") -package io.sentry.android.gradle.instrumentation.fakes +package io.bitdrift.capture.instrumentation.fakes import com.android.build.api.instrumentation.ClassContext import com.android.build.api.instrumentation.ClassData From 51d9d320fc19ae323ee81d795e07626d0035a758 Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Thu, 5 Dec 2024 07:56:25 -0800 Subject: [PATCH 08/10] PR feedback --- platform/jvm/capture-plugin/build.gradle.kts | 16 +++++----------- platform/jvm/capture/build.gradle.kts | 2 -- platform/jvm/gradle-test-app/build.gradle.kts | 5 ----- .../io/bitdrift/gradletestapp/MainActivity.kt | 1 - platform/jvm/gradle/libs.versions.toml | 2 -- 5 files changed, 5 insertions(+), 21 deletions(-) diff --git a/platform/jvm/capture-plugin/build.gradle.kts b/platform/jvm/capture-plugin/build.gradle.kts index f16fd050..a875c9e5 100644 --- a/platform/jvm/capture-plugin/build.gradle.kts +++ b/platform/jvm/capture-plugin/build.gradle.kts @@ -1,10 +1,8 @@ plugins { - alias(libs.plugins.kotlin) - - id("dependency-license-config") - id("java-gradle-plugin") - id("maven-publish") - id("com.vanniktech.maven.publish") version "0.28.0" apply false + alias(libs.plugins.kotlin) + alias(libs.plugins.maven.publish) + id("dependency-license-config") + id("java-gradle-plugin") } dependencies { @@ -30,10 +28,6 @@ gradlePlugin { } } -apply { - plugin("com.vanniktech.maven.publish") -} - publishing { repositories { mavenLocal() @@ -41,4 +35,4 @@ publishing { } group = "io.bitdrift.capture.capture-plugin" -version = "0.1.0" \ No newline at end of file +version = "0.2.0" \ No newline at end of file diff --git a/platform/jvm/capture/build.gradle.kts b/platform/jvm/capture/build.gradle.kts index 5c9580ff..239d4fcf 100644 --- a/platform/jvm/capture/build.gradle.kts +++ b/platform/jvm/capture/build.gradle.kts @@ -85,8 +85,6 @@ cargo { targetDirectory = "../../../target" targets = listOf("arm64", "x86_64") pythonCommand = "python3" - rustcCommand = "/Users/snow/.cargo/bin/rustc" - cargoCommand = "/Users/snow/.cargo/bin/cargo" } // workaround bug in rust-android-gradle plugin that causes .so to not be available on app launch diff --git a/platform/jvm/gradle-test-app/build.gradle.kts b/platform/jvm/gradle-test-app/build.gradle.kts index 356554df..c3b85eb6 100644 --- a/platform/jvm/gradle-test-app/build.gradle.kts +++ b/platform/jvm/gradle-test-app/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.apollo.graphql) alias(libs.plugins.kotlin.android) - alias(libs.plugins.capture) } dependencies { @@ -42,10 +41,6 @@ dependencies { androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.4.0") } -bitdrift { - instrumentation.debug = true -} - android { namespace = "io.bitdrift.gradletestapp" compileSdk = 35 diff --git a/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/MainActivity.kt b/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/MainActivity.kt index c27831a4..4d2d87d6 100644 --- a/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/MainActivity.kt +++ b/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/MainActivity.kt @@ -16,7 +16,6 @@ import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.navigateUp import androidx.navigation.ui.setupActionBarWithNavController import io.bitdrift.gradletestapp.databinding.ActivityMainBinding -import okhttp3.OkHttpClient class MainActivity : AppCompatActivity() { diff --git a/platform/jvm/gradle/libs.versions.toml b/platform/jvm/gradle/libs.versions.toml index 5e02ad2c..aab5e344 100644 --- a/platform/jvm/gradle/libs.versions.toml +++ b/platform/jvm/gradle/libs.versions.toml @@ -27,7 +27,6 @@ startupRuntime = "1.1.1" timber = "5.0.1" truth = "1.1.4" ui = "1.4.0" -capturePlugin = "0.1.0" [libraries] androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } @@ -64,4 +63,3 @@ maven-publish = { id = "com.vanniktech.maven.publish", version.ref = "mavenPubli kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlinAndroidPlugin" } kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlinPlugin" } rust-android = { id = "org.mozilla.rust-android-gradle.rust-android", version.ref = "rustAndroidPlugin" } -capture = { id = "io.bitdrift.capture.capture-plugin", version.ref = "capturePlugin" } From 2a988726bf4bfe8b6592832fc5a10f058dad43eb Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Thu, 5 Dec 2024 07:56:58 -0800 Subject: [PATCH 09/10] version --- platform/jvm/capture-plugin/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/jvm/capture-plugin/build.gradle.kts b/platform/jvm/capture-plugin/build.gradle.kts index a875c9e5..fe05bf20 100644 --- a/platform/jvm/capture-plugin/build.gradle.kts +++ b/platform/jvm/capture-plugin/build.gradle.kts @@ -35,4 +35,4 @@ publishing { } group = "io.bitdrift.capture.capture-plugin" -version = "0.2.0" \ No newline at end of file +version = "0.1.0" \ No newline at end of file From e66d73b91af8881ecc15c6b9502c7a1187caaae4 Mon Sep 17 00:00:00 2001 From: Snow Pettersen Date: Thu, 5 Dec 2024 07:57:41 -0800 Subject: [PATCH 10/10] reverst --- platform/jvm/gradle-test-app/build.gradle.kts | 3 +-- .../src/main/java/io/bitdrift/gradletestapp/FirstFragment.kt | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/jvm/gradle-test-app/build.gradle.kts b/platform/jvm/gradle-test-app/build.gradle.kts index c3b85eb6..4c525292 100644 --- a/platform/jvm/gradle-test-app/build.gradle.kts +++ b/platform/jvm/gradle-test-app/build.gradle.kts @@ -110,7 +110,6 @@ android { kotlinOptions { jvmTarget = "1.8" } - } // graphql @@ -119,4 +118,4 @@ apollo { // https://apollo-fullstack-tutorial.herokuapp.com/graphql packageName.set("com.example.rocketreserver") } -} \ No newline at end of file +} diff --git a/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/FirstFragment.kt b/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/FirstFragment.kt index 9acc061c..0cb96736 100644 --- a/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/FirstFragment.kt +++ b/platform/jvm/gradle-test-app/src/main/java/io/bitdrift/gradletestapp/FirstFragment.kt @@ -147,6 +147,7 @@ class FirstFragment : Fragment() { private fun provideOkHttpClient(): OkHttpClient { return OkHttpClient.Builder() + .eventListenerFactory(CaptureOkHttpEventListenerFactory()) .build() }