From 8c3d1a02784dcb45ea78e9dee5da3c086847ea6a Mon Sep 17 00:00:00 2001 From: Chao Zhang Date: Sat, 24 Apr 2021 14:31:43 -0700 Subject: [PATCH] Trace `setValue()` and `postValue()` --- README.md | 8 +- build.gradle | 11 +- gradle.properties | 18 +-- settings.gradle | 2 +- .../chao2zhang/ConsiderNotifyMethodVisitor.kt | 52 +++++++ .../github/chao2zhang/LiveDataClassVisitor.kt | 45 ++++++ ...ataPlugin.kt => LiveDataDebuggerPlugin.kt} | 4 +- ...eDataTransform.kt => LiveDataTransform.kt} | 7 +- .../chao2zhang/LogValueMethodVisitor.kt | 143 ++++++++++++++++++ .../chao2zhang/LoggingLiveDataClassVisitor.kt | 25 --- .../LoggingLiveDataMethodVisitor.kt | 54 ------- .../chao2zhang/PostValueMethodVisitor.kt | 37 +++++ .../chao2zhang/SetValueMethodVisitor.kt | 32 ++++ 13 files changed, 334 insertions(+), 104 deletions(-) create mode 100644 src/main/kotlin/io/github/chao2zhang/ConsiderNotifyMethodVisitor.kt create mode 100644 src/main/kotlin/io/github/chao2zhang/LiveDataClassVisitor.kt rename src/main/kotlin/io/github/chao2zhang/{LoggingLiveDataPlugin.kt => LiveDataDebuggerPlugin.kt} (74%) rename src/main/kotlin/io/github/chao2zhang/{LoggingLiveDataTransform.kt => LiveDataTransform.kt} (93%) create mode 100644 src/main/kotlin/io/github/chao2zhang/LogValueMethodVisitor.kt delete mode 100644 src/main/kotlin/io/github/chao2zhang/LoggingLiveDataClassVisitor.kt delete mode 100644 src/main/kotlin/io/github/chao2zhang/LoggingLiveDataMethodVisitor.kt create mode 100644 src/main/kotlin/io/github/chao2zhang/PostValueMethodVisitor.kt create mode 100644 src/main/kotlin/io/github/chao2zhang/SetValueMethodVisitor.kt diff --git a/README.md b/README.md index e322d09..129b153 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ -# LoggingLiveData +# LiveDataDebugger [![Maven Central](https://img.shields.io/maven-central/v/io.github.chao2zhang.logginglivedata/logginglivedata)](https://search.maven.org/artifact/io.github.chao2zhang.logginglivedata/logginglivedata) -The gradle plugin to make LiveData universally debuggable through bytecode transformation +The gradle plugin to make LiveData universally debuggable through bytecode transformation. +Execution of `LiveData.considerNotify()`, `LiveData.setValue()` and `LiveData.postValue()` +will be logged through logcat with info level and tag `LiveData`. # Usage ```groovy -apply plugin: 'io.github.chao2zhang.logginglivedata' +apply plugin: 'io.github.chao2zhang.livedatadebugger' ``` # How it works diff --git a/build.gradle b/build.gradle index f639e81..a7af2f5 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ plugins { id 'org.jetbrains.kotlin.jvm' version '1.4.31' id 'org.jetbrains.dokka' version '1.4.30' id 'java-gradle-plugin' - id 'com.vanniktech.maven.publish' version '0.13.0' + id 'com.vanniktech.maven.publish' version '0.14.2' } repositories { @@ -17,13 +17,14 @@ repositories { } -group="io.github.chao2zhang.logginglivedata" +group="io.github.chao2zhang.livedatadebugger" gradlePlugin { plugins { - loggingLiveDataPlugin { - id = 'io.github.chao2zhang.logginglivedata' - implementationClass = "io.github.chao2zhang.LoggingLiveDataPlugin" + liveDataDebuggerPlugin { + id = 'io.github.chao2zhang.livedatadebugger' + implementationClass = "io.github.chao2zhang.LiveDataDebuggerPlugin" + description = "The gradle plugin to make LiveData universally debuggable" } } } diff --git a/gradle.properties b/gradle.properties index bd8f7f4..3628c6e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,16 +1,16 @@ kotlin.code.style=official -GROUP=io.github.chao2zhang.logginglivedata -POM_ARTIFACT_ID=logginglivedata -VERSION_NAME=0.0.2 +GROUP=io.github.chao2zhang.livedatadebugger +POM_ARTIFACT_ID=livedatadebugger +VERSION_NAME=0.0.3 -POM_NAME=LoggingLiveData -POM_DESCRIPTION=Enforce named arguments usage for callers of a function through a Kotlin Compiler Plugin. +POM_NAME=LiveDataDebugger +POM_DESCRIPTION=The gradle plugin to make LiveData universally debuggable POM_INCEPTION_YEAR=2021 -POM_URL=https://github.com/chao2zhang/LoggingLiveData -POM_SCM_URL=https://github.com/chao2zhang/LoggingLiveData -POM_SCM_CONNECTION=scm:git:git://github.com/chao2zhang/LoggingLiveData.git -POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/chao2zhang/LoggingLiveData.git +POM_URL=https://github.com/chao2zhang/LiveDataDebugger +POM_SCM_URL=https://github.com/chao2zhang/LiveDataDebugger +POM_SCM_CONNECTION=scm:git:git://github.com/chao2zhang/LiveDataDebugger.git +POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/chao2zhang/LiveDataDebugger.git POM_LICENCE_NAME=The Apache Software License, Version 2.0 POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt POM_LICENCE_DIST=repo diff --git a/settings.gradle b/settings.gradle index cf4b20d..9c0a168 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -rootProject.name = 'LoggingLiveData' +rootProject.name = 'LiveDataDebugger' diff --git a/src/main/kotlin/io/github/chao2zhang/ConsiderNotifyMethodVisitor.kt b/src/main/kotlin/io/github/chao2zhang/ConsiderNotifyMethodVisitor.kt new file mode 100644 index 0000000..0ee107c --- /dev/null +++ b/src/main/kotlin/io/github/chao2zhang/ConsiderNotifyMethodVisitor.kt @@ -0,0 +1,52 @@ +package io.github.chao2zhang + +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes.* + +/** + * Add the following code after invoking `observer.onChanged`: + * ``` + * Log.i("LiveData", "considerNotify() called with LiveData = " + this + " Observer = " + observer.mObserver + " Data = " + this.mData); + * ``` + */ +class ConsiderNotifyMethodVisitor( + private val methodVisitor: MethodVisitor +) : MethodVisitor(ASM9, methodVisitor) { + + override fun visitMethodInsn( + opcode: Int, + owner: String?, + name: String?, + descriptor: String?, + isInterface: Boolean + ) { + methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface) + if (opcode == INVOKEINTERFACE && owner == "androidx/lifecycle/Observer" && name == "onChanged") { + methodVisitor.visitLdcInsn("LiveData") + methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder") + methodVisitor.visitInsn(DUP) + methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false) + methodVisitor.visitLdcInsn("considerNotify() called with LiveData = ") + methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false) + methodVisitor.visitVarInsn(ALOAD, 0) + methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false) + methodVisitor.visitLdcInsn(" Observer = ") + methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false) + methodVisitor.visitVarInsn(ALOAD, 1) + methodVisitor.visitFieldInsn(GETFIELD, "androidx/lifecycle/LiveData\$ObserverWrapper", "mObserver", "Landroidx/lifecycle/Observer;") + methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false) + methodVisitor.visitLdcInsn(" Data = ") + methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false) + methodVisitor.visitVarInsn(ALOAD, 0) + methodVisitor.visitFieldInsn(GETFIELD, "androidx/lifecycle/LiveData", "mData", "Ljava/lang/Object;") + methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false) + methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false) + methodVisitor.visitMethodInsn(INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false) + methodVisitor.visitInsn(POP) + } + } + + override fun visitMaxs(maxStack: Int, maxLocals: Int) { + methodVisitor.visitMaxs(3, 2) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/chao2zhang/LiveDataClassVisitor.kt b/src/main/kotlin/io/github/chao2zhang/LiveDataClassVisitor.kt new file mode 100644 index 0000000..839b61b --- /dev/null +++ b/src/main/kotlin/io/github/chao2zhang/LiveDataClassVisitor.kt @@ -0,0 +1,45 @@ +package io.github.chao2zhang + +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes.* + + +class LiveDataClassVisitor( + private val classVisitor: ClassVisitor +) : ClassVisitor(ASM9, classVisitor) { + + override fun visit( + version: Int, + access: Int, + name: String?, + signature: String?, + superName: String?, + interfaces: Array? + ) { + super.visit(version, access, name, signature, superName, interfaces) + visitLogValueMethod(classVisitor.visitMethod( + ACC_PRIVATE, + "logValue", + "(Ljava/lang/String;Ljava/lang/Object;)V", + "(Ljava/lang/String;TT;)V", + null + )) + } + + override fun visitMethod( + access: Int, + name: String?, + descriptor: String?, + signature: String?, + exceptions: Array? + ): MethodVisitor { + val methodVisitor = classVisitor.visitMethod(access, name, descriptor, signature, exceptions) + return when (name) { + "considerNotify" -> ConsiderNotifyMethodVisitor(methodVisitor) + "postValue" -> PostValueMethodVisitor(methodVisitor) + "setValue" -> SetValueMethodVisitor(methodVisitor) + else -> methodVisitor + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/chao2zhang/LoggingLiveDataPlugin.kt b/src/main/kotlin/io/github/chao2zhang/LiveDataDebuggerPlugin.kt similarity index 74% rename from src/main/kotlin/io/github/chao2zhang/LoggingLiveDataPlugin.kt rename to src/main/kotlin/io/github/chao2zhang/LiveDataDebuggerPlugin.kt index 53fcb0b..2652855 100644 --- a/src/main/kotlin/io/github/chao2zhang/LoggingLiveDataPlugin.kt +++ b/src/main/kotlin/io/github/chao2zhang/LiveDataDebuggerPlugin.kt @@ -5,12 +5,12 @@ import com.android.build.gradle.AppPlugin import org.gradle.api.Plugin import org.gradle.api.Project -class LoggingLiveDataPlugin : Plugin { +class LiveDataDebuggerPlugin : Plugin { override fun apply(project: Project) { project.plugins.withType(AppPlugin::class.java) { plugin -> project.extensions.configure(AppExtension::class.java) { appExtension -> - appExtension.registerTransform(LoggingLiveDataTransform(project.logger)) + appExtension.registerTransform(LiveDataTransform(project.logger)) } } } diff --git a/src/main/kotlin/io/github/chao2zhang/LoggingLiveDataTransform.kt b/src/main/kotlin/io/github/chao2zhang/LiveDataTransform.kt similarity index 93% rename from src/main/kotlin/io/github/chao2zhang/LoggingLiveDataTransform.kt rename to src/main/kotlin/io/github/chao2zhang/LiveDataTransform.kt index c76b84a..67c7d21 100644 --- a/src/main/kotlin/io/github/chao2zhang/LoggingLiveDataTransform.kt +++ b/src/main/kotlin/io/github/chao2zhang/LiveDataTransform.kt @@ -11,18 +11,15 @@ import com.android.build.api.variant.VariantInfo import org.gradle.api.logging.Logger import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassWriter -import org.objectweb.asm.util.TraceClassVisitor import java.io.BufferedOutputStream import java.io.File import java.io.FileOutputStream -import java.io.PrintWriter -import java.io.StringWriter import java.util.zip.ZipEntry import java.util.zip.ZipFile import java.util.zip.ZipOutputStream @Suppress("UnstableApiUsage") -class LoggingLiveDataTransform(private val logger: Logger) : Transform() { +class LiveDataTransform(private val logger: Logger) : Transform() { override fun getName(): String = javaClass.simpleName @@ -84,7 +81,7 @@ class LoggingLiveDataTransform(private val logger: Logger) : Transform() { logger.lifecycle("Transforming $inputEntry to add logging statements in LiveData") val classReader = ClassReader(inputBytes) val classWriter = ClassWriter(classReader, 0) - val classVisitor = LoggingLiveDataClassVisitor(classWriter) + val classVisitor = LiveDataClassVisitor(classWriter) classReader.accept(classVisitor, 0) outputBytes = classWriter.toByteArray() } diff --git a/src/main/kotlin/io/github/chao2zhang/LogValueMethodVisitor.kt b/src/main/kotlin/io/github/chao2zhang/LogValueMethodVisitor.kt new file mode 100644 index 0000000..f583853 --- /dev/null +++ b/src/main/kotlin/io/github/chao2zhang/LogValueMethodVisitor.kt @@ -0,0 +1,143 @@ +package io.github.chao2zhang + +import org.objectweb.asm.Label +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes + +/** + * private void logValue(String methodName, T value) { + * StackTraceElement[] traces = Thread.currentThread().getStackTrace(); + * if (traces.length > 3) { + * Log.i("LiveData", methodName + "() called with LiveData = " + this + " Value = " + value + " Caller = " + traces[3].toString()); + * } + * } + */ +fun visitLogValueMethod(methodVisitor: MethodVisitor) { + methodVisitor.visitCode() + val label0 = Label() + methodVisitor.visitLabel(label0) + methodVisitor.visitLineNumber(302, label0) + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Thread", "currentThread", "()Ljava/lang/Thread;", false) + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/Thread", + "getStackTrace", + "()[Ljava/lang/StackTraceElement;", + false + ) + methodVisitor.visitVarInsn(Opcodes.ASTORE, 3) + val label1 = Label() + methodVisitor.visitLabel(label1) + methodVisitor.visitLineNumber(303, label1) + methodVisitor.visitVarInsn(Opcodes.ALOAD, 3) + methodVisitor.visitInsn(Opcodes.ARRAYLENGTH) + methodVisitor.visitInsn(Opcodes.ICONST_5) + val label2 = Label() + methodVisitor.visitJumpInsn(Opcodes.IF_ICMPLE, label2) + val label3 = Label() + methodVisitor.visitLabel(label3) + methodVisitor.visitLineNumber(304, label3) + methodVisitor.visitLdcInsn("LiveData") + methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder") + methodVisitor.visitInsn(Opcodes.DUP) + methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false) + methodVisitor.visitVarInsn(Opcodes.ALOAD, 1) + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/StringBuilder", + "append", + "(Ljava/lang/String;)Ljava/lang/StringBuilder;", + false + ) + methodVisitor.visitLdcInsn("() called with LiveData = ") + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/StringBuilder", + "append", + "(Ljava/lang/String;)Ljava/lang/StringBuilder;", + false + ) + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0) + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/StringBuilder", + "append", + "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", + false + ) + methodVisitor.visitLdcInsn(" Value = ") + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/StringBuilder", + "append", + "(Ljava/lang/String;)Ljava/lang/StringBuilder;", + false + ) + methodVisitor.visitVarInsn(Opcodes.ALOAD, 2) + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/StringBuilder", + "append", + "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", + false + ) + methodVisitor.visitLdcInsn(" Caller = ") + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/StringBuilder", + "append", + "(Ljava/lang/String;)Ljava/lang/StringBuilder;", + false + ) + methodVisitor.visitVarInsn(Opcodes.ALOAD, 3) + methodVisitor.visitInsn(Opcodes.ICONST_5) + methodVisitor.visitInsn(Opcodes.AALOAD) + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/StackTraceElement", + "toString", + "()Ljava/lang/String;", + false + ) + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/StringBuilder", + "append", + "(Ljava/lang/String;)Ljava/lang/StringBuilder;", + false + ) + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/lang/StringBuilder", + "toString", + "()Ljava/lang/String;", + false + ) + methodVisitor.visitMethodInsn( + Opcodes.INVOKESTATIC, + "android/util/Log", + "i", + "(Ljava/lang/String;Ljava/lang/String;)I", + false + ) + methodVisitor.visitInsn(Opcodes.POP) + methodVisitor.visitLabel(label2) + methodVisitor.visitLineNumber(306, label2) + methodVisitor.visitFrame(Opcodes.F_APPEND, 1, arrayOf("[Ljava/lang/StackTraceElement;"), 0, null) + methodVisitor.visitInsn(Opcodes.RETURN) + val label4 = Label() + methodVisitor.visitLabel(label4) + methodVisitor.visitLocalVariable( + "this", + "Landroidx/lifecycle/LiveData;", + "Landroidx/lifecycle/LiveData;", + label0, + label4, + 0 + ) + methodVisitor.visitLocalVariable("methodName", "Ljava/lang/String;", null, label0, label4, 1) + methodVisitor.visitLocalVariable("value", "Ljava/lang/Object;", "TT;", label0, label4, 2) + methodVisitor.visitLocalVariable("traces", "[Ljava/lang/StackTraceElement;", null, label1, label4, 3) + methodVisitor.visitMaxs(4, 4) + methodVisitor.visitEnd() +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/chao2zhang/LoggingLiveDataClassVisitor.kt b/src/main/kotlin/io/github/chao2zhang/LoggingLiveDataClassVisitor.kt deleted file mode 100644 index fe15c07..0000000 --- a/src/main/kotlin/io/github/chao2zhang/LoggingLiveDataClassVisitor.kt +++ /dev/null @@ -1,25 +0,0 @@ -package io.github.chao2zhang - -import org.objectweb.asm.ClassVisitor -import org.objectweb.asm.MethodVisitor -import org.objectweb.asm.Opcodes.ASM9 - -class LoggingLiveDataClassVisitor( - private val classVisitor: ClassVisitor -) : ClassVisitor(ASM9, classVisitor) { - - override fun visitMethod( - access: Int, - name: String?, - descriptor: String?, - signature: String?, - exceptions: Array? - ): MethodVisitor { - val methodVisitor = classVisitor.visitMethod(access, name, descriptor, signature, exceptions) - return if (name == "considerNotify") { - LoggingLiveDataMethodVisitor(methodVisitor) - } else { - methodVisitor - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/io/github/chao2zhang/LoggingLiveDataMethodVisitor.kt b/src/main/kotlin/io/github/chao2zhang/LoggingLiveDataMethodVisitor.kt deleted file mode 100644 index 44f4722..0000000 --- a/src/main/kotlin/io/github/chao2zhang/LoggingLiveDataMethodVisitor.kt +++ /dev/null @@ -1,54 +0,0 @@ -package io.github.chao2zhang - -import org.objectweb.asm.MethodVisitor -import org.objectweb.asm.Opcodes -import org.objectweb.asm.Opcodes.ASM9 - -/** - * Add the following code after invoking `observer.onChanged`: - * ``` - * Log.i("LiveData", "considerNotify() called with LiveData = " + this + " Observer = " + observer.mObserver + " Data = " + this.mData); - * ``` - */ -class LoggingLiveDataMethodVisitor( - private val methodVisitor: MethodVisitor -) : MethodVisitor(ASM9, methodVisitor) { - - override fun visitMethodInsn( - opcode: Int, - owner: String?, - name: String?, - descriptor: String?, - isInterface: Boolean - ) { - methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface) - if (opcode == Opcodes.INVOKEINTERFACE && owner == "androidx/lifecycle/Observer" && name == "onChanged") { - methodVisitor.visitLdcInsn("LiveData") - methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder") - methodVisitor.visitInsn(Opcodes.DUP) - methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "", "()V", false) - methodVisitor.visitLdcInsn("considerNotify() called with LiveData = ") - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false) - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0) - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false) - methodVisitor.visitLdcInsn(" Observer = ") - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false) - methodVisitor.visitVarInsn(Opcodes.ALOAD, 1) - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, "androidx/lifecycle/LiveData\$ObserverWrapper", "mObserver", "Landroidx/lifecycle/Observer;") - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false) - methodVisitor.visitLdcInsn(" Data = ") - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false) - methodVisitor.visitVarInsn(Opcodes.ALOAD, 0) - methodVisitor.visitFieldInsn(Opcodes.GETFIELD, "androidx/lifecycle/LiveData", "mData", "Ljava/lang/Object;") - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false) - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false) - methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false) - methodVisitor.visitInsn(Opcodes.POP) - } - } - - - override fun visitMaxs(maxStack: Int, maxLocals: Int) { - methodVisitor.visitMaxs(3, 2) - } -} \ No newline at end of file diff --git a/src/main/kotlin/io/github/chao2zhang/PostValueMethodVisitor.kt b/src/main/kotlin/io/github/chao2zhang/PostValueMethodVisitor.kt new file mode 100644 index 0000000..603748c --- /dev/null +++ b/src/main/kotlin/io/github/chao2zhang/PostValueMethodVisitor.kt @@ -0,0 +1,37 @@ +package io.github.chao2zhang + +import org.objectweb.asm.Label +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes.* + + +/** + * logValue("postValue", value); + */ +class PostValueMethodVisitor( + private val methodVisitor: MethodVisitor +) : MethodVisitor(ASM9, methodVisitor) { + + var tryCatchBlockCount = 0 + + override fun visitTryCatchBlock(start: Label?, end: Label?, handler: Label?, type: String?) { + super.visitTryCatchBlock(start, end, handler, type) + ++tryCatchBlockCount + if (tryCatchBlockCount == 2) { + methodVisitor.visitVarInsn(ALOAD, 0) + methodVisitor.visitLdcInsn("postValue") + methodVisitor.visitVarInsn(ALOAD, 1) + methodVisitor.visitMethodInsn( + INVOKESPECIAL, + "androidx/lifecycle/LiveData", + "logValue", + "(Ljava/lang/String;Ljava/lang/Object;)V", + false + ) + } + } + + override fun visitMaxs(maxStack: Int, maxLocals: Int) { + methodVisitor.visitMaxs(3, 5) + } +} \ No newline at end of file diff --git a/src/main/kotlin/io/github/chao2zhang/SetValueMethodVisitor.kt b/src/main/kotlin/io/github/chao2zhang/SetValueMethodVisitor.kt new file mode 100644 index 0000000..80e3ffa --- /dev/null +++ b/src/main/kotlin/io/github/chao2zhang/SetValueMethodVisitor.kt @@ -0,0 +1,32 @@ +package io.github.chao2zhang + +import org.objectweb.asm.Label +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes.* + + +/** + * logValue("setValue", value); + */ +class SetValueMethodVisitor( + private val methodVisitor: MethodVisitor +) : MethodVisitor(ASM9, methodVisitor) { + + override fun visitCode() { + super.visitCode() + methodVisitor.visitVarInsn(ALOAD, 0) + methodVisitor.visitLdcInsn("setValue") + methodVisitor.visitVarInsn(ALOAD, 1) + methodVisitor.visitMethodInsn( + INVOKESPECIAL, + "androidx/lifecycle/LiveData", + "logValue", + "(Ljava/lang/String;Ljava/lang/Object;)V", + false + ) + } + + override fun visitMaxs(maxStack: Int, maxLocals: Int) { + methodVisitor.visitMaxs(3, 2) + } +} \ No newline at end of file