Skip to content

Commit 8c3d1a0

Browse files
author
Chao Zhang
committed
Trace setValue() and postValue()
1 parent 12cc37f commit 8c3d1a0

13 files changed

+334
-104
lines changed

README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
# LoggingLiveData
1+
# LiveDataDebugger
22
[![Maven Central](https://img.shields.io/maven-central/v/io.github.chao2zhang.logginglivedata/logginglivedata)](https://search.maven.org/artifact/io.github.chao2zhang.logginglivedata/logginglivedata)
33

4-
The gradle plugin to make LiveData universally debuggable through bytecode transformation
4+
The gradle plugin to make LiveData universally debuggable through bytecode transformation.
5+
Execution of `LiveData.considerNotify()`, `LiveData.setValue()` and `LiveData.postValue()`
6+
will be logged through logcat with info level and tag `LiveData`.
57

68
# Usage
79
```groovy
8-
apply plugin: 'io.github.chao2zhang.logginglivedata'
10+
apply plugin: 'io.github.chao2zhang.livedatadebugger'
911
```
1012

1113
# How it works

build.gradle

+6-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ plugins {
22
id 'org.jetbrains.kotlin.jvm' version '1.4.31'
33
id 'org.jetbrains.dokka' version '1.4.30'
44
id 'java-gradle-plugin'
5-
id 'com.vanniktech.maven.publish' version '0.13.0'
5+
id 'com.vanniktech.maven.publish' version '0.14.2'
66
}
77

88
repositories {
@@ -17,13 +17,14 @@ repositories {
1717
}
1818

1919

20-
group="io.github.chao2zhang.logginglivedata"
20+
group="io.github.chao2zhang.livedatadebugger"
2121

2222
gradlePlugin {
2323
plugins {
24-
loggingLiveDataPlugin {
25-
id = 'io.github.chao2zhang.logginglivedata'
26-
implementationClass = "io.github.chao2zhang.LoggingLiveDataPlugin"
24+
liveDataDebuggerPlugin {
25+
id = 'io.github.chao2zhang.livedatadebugger'
26+
implementationClass = "io.github.chao2zhang.LiveDataDebuggerPlugin"
27+
description = "The gradle plugin to make LiveData universally debuggable"
2728
}
2829
}
2930
}

gradle.properties

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
kotlin.code.style=official
22

3-
GROUP=io.github.chao2zhang.logginglivedata
4-
POM_ARTIFACT_ID=logginglivedata
5-
VERSION_NAME=0.0.2
3+
GROUP=io.github.chao2zhang.livedatadebugger
4+
POM_ARTIFACT_ID=livedatadebugger
5+
VERSION_NAME=0.0.3
66

7-
POM_NAME=LoggingLiveData
8-
POM_DESCRIPTION=Enforce named arguments usage for callers of a function through a Kotlin Compiler Plugin.
7+
POM_NAME=LiveDataDebugger
8+
POM_DESCRIPTION=The gradle plugin to make LiveData universally debuggable
99
POM_INCEPTION_YEAR=2021
10-
POM_URL=https://github.com/chao2zhang/LoggingLiveData
11-
POM_SCM_URL=https://github.com/chao2zhang/LoggingLiveData
12-
POM_SCM_CONNECTION=scm:git:git://github.com/chao2zhang/LoggingLiveData.git
13-
POM_SCM_DEV_CONNECTION=scm:git:ssh://[email protected]/chao2zhang/LoggingLiveData.git
10+
POM_URL=https://github.com/chao2zhang/LiveDataDebugger
11+
POM_SCM_URL=https://github.com/chao2zhang/LiveDataDebugger
12+
POM_SCM_CONNECTION=scm:git:git://github.com/chao2zhang/LiveDataDebugger.git
13+
POM_SCM_DEV_CONNECTION=scm:git:ssh://[email protected]/chao2zhang/LiveDataDebugger.git
1414
POM_LICENCE_NAME=The Apache Software License, Version 2.0
1515
POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt
1616
POM_LICENCE_DIST=repo

settings.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
rootProject.name = 'LoggingLiveData'
1+
rootProject.name = 'LiveDataDebugger'
22

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package io.github.chao2zhang
2+
3+
import org.objectweb.asm.MethodVisitor
4+
import org.objectweb.asm.Opcodes.*
5+
6+
/**
7+
* Add the following code after invoking `observer.onChanged`:
8+
* ```
9+
* Log.i("LiveData", "considerNotify() called with LiveData = " + this + " Observer = " + observer.mObserver + " Data = " + this.mData);
10+
* ```
11+
*/
12+
class ConsiderNotifyMethodVisitor(
13+
private val methodVisitor: MethodVisitor
14+
) : MethodVisitor(ASM9, methodVisitor) {
15+
16+
override fun visitMethodInsn(
17+
opcode: Int,
18+
owner: String?,
19+
name: String?,
20+
descriptor: String?,
21+
isInterface: Boolean
22+
) {
23+
methodVisitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
24+
if (opcode == INVOKEINTERFACE && owner == "androidx/lifecycle/Observer" && name == "onChanged") {
25+
methodVisitor.visitLdcInsn("LiveData")
26+
methodVisitor.visitTypeInsn(NEW, "java/lang/StringBuilder")
27+
methodVisitor.visitInsn(DUP)
28+
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false)
29+
methodVisitor.visitLdcInsn("considerNotify() called with LiveData = ")
30+
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
31+
methodVisitor.visitVarInsn(ALOAD, 0)
32+
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false)
33+
methodVisitor.visitLdcInsn(" Observer = ")
34+
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
35+
methodVisitor.visitVarInsn(ALOAD, 1)
36+
methodVisitor.visitFieldInsn(GETFIELD, "androidx/lifecycle/LiveData\$ObserverWrapper", "mObserver", "Landroidx/lifecycle/Observer;")
37+
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false)
38+
methodVisitor.visitLdcInsn(" Data = ")
39+
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
40+
methodVisitor.visitVarInsn(ALOAD, 0)
41+
methodVisitor.visitFieldInsn(GETFIELD, "androidx/lifecycle/LiveData", "mData", "Ljava/lang/Object;")
42+
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false)
43+
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false)
44+
methodVisitor.visitMethodInsn(INVOKESTATIC, "android/util/Log", "i", "(Ljava/lang/String;Ljava/lang/String;)I", false)
45+
methodVisitor.visitInsn(POP)
46+
}
47+
}
48+
49+
override fun visitMaxs(maxStack: Int, maxLocals: Int) {
50+
methodVisitor.visitMaxs(3, 2)
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.github.chao2zhang
2+
3+
import org.objectweb.asm.ClassVisitor
4+
import org.objectweb.asm.MethodVisitor
5+
import org.objectweb.asm.Opcodes.*
6+
7+
8+
class LiveDataClassVisitor(
9+
private val classVisitor: ClassVisitor
10+
) : ClassVisitor(ASM9, classVisitor) {
11+
12+
override fun visit(
13+
version: Int,
14+
access: Int,
15+
name: String?,
16+
signature: String?,
17+
superName: String?,
18+
interfaces: Array<out String>?
19+
) {
20+
super.visit(version, access, name, signature, superName, interfaces)
21+
visitLogValueMethod(classVisitor.visitMethod(
22+
ACC_PRIVATE,
23+
"logValue",
24+
"(Ljava/lang/String;Ljava/lang/Object;)V",
25+
"(Ljava/lang/String;TT;)V",
26+
null
27+
))
28+
}
29+
30+
override fun visitMethod(
31+
access: Int,
32+
name: String?,
33+
descriptor: String?,
34+
signature: String?,
35+
exceptions: Array<out String>?
36+
): MethodVisitor {
37+
val methodVisitor = classVisitor.visitMethod(access, name, descriptor, signature, exceptions)
38+
return when (name) {
39+
"considerNotify" -> ConsiderNotifyMethodVisitor(methodVisitor)
40+
"postValue" -> PostValueMethodVisitor(methodVisitor)
41+
"setValue" -> SetValueMethodVisitor(methodVisitor)
42+
else -> methodVisitor
43+
}
44+
}
45+
}

src/main/kotlin/io/github/chao2zhang/LoggingLiveDataPlugin.kt src/main/kotlin/io/github/chao2zhang/LiveDataDebuggerPlugin.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import com.android.build.gradle.AppPlugin
55
import org.gradle.api.Plugin
66
import org.gradle.api.Project
77

8-
class LoggingLiveDataPlugin : Plugin<Project> {
8+
class LiveDataDebuggerPlugin : Plugin<Project> {
99

1010
override fun apply(project: Project) {
1111
project.plugins.withType(AppPlugin::class.java) { plugin ->
1212
project.extensions.configure(AppExtension::class.java) { appExtension ->
13-
appExtension.registerTransform(LoggingLiveDataTransform(project.logger))
13+
appExtension.registerTransform(LiveDataTransform(project.logger))
1414
}
1515
}
1616
}

src/main/kotlin/io/github/chao2zhang/LoggingLiveDataTransform.kt src/main/kotlin/io/github/chao2zhang/LiveDataTransform.kt

+2-5
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,15 @@ import com.android.build.api.variant.VariantInfo
1111
import org.gradle.api.logging.Logger
1212
import org.objectweb.asm.ClassReader
1313
import org.objectweb.asm.ClassWriter
14-
import org.objectweb.asm.util.TraceClassVisitor
1514
import java.io.BufferedOutputStream
1615
import java.io.File
1716
import java.io.FileOutputStream
18-
import java.io.PrintWriter
19-
import java.io.StringWriter
2017
import java.util.zip.ZipEntry
2118
import java.util.zip.ZipFile
2219
import java.util.zip.ZipOutputStream
2320

2421
@Suppress("UnstableApiUsage")
25-
class LoggingLiveDataTransform(private val logger: Logger) : Transform() {
22+
class LiveDataTransform(private val logger: Logger) : Transform() {
2623

2724
override fun getName(): String = javaClass.simpleName
2825

@@ -84,7 +81,7 @@ class LoggingLiveDataTransform(private val logger: Logger) : Transform() {
8481
logger.lifecycle("Transforming $inputEntry to add logging statements in LiveData")
8582
val classReader = ClassReader(inputBytes)
8683
val classWriter = ClassWriter(classReader, 0)
87-
val classVisitor = LoggingLiveDataClassVisitor(classWriter)
84+
val classVisitor = LiveDataClassVisitor(classWriter)
8885
classReader.accept(classVisitor, 0)
8986
outputBytes = classWriter.toByteArray()
9087
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package io.github.chao2zhang
2+
3+
import org.objectweb.asm.Label
4+
import org.objectweb.asm.MethodVisitor
5+
import org.objectweb.asm.Opcodes
6+
7+
/**
8+
* private void logValue(String methodName, T value) {
9+
* StackTraceElement[] traces = Thread.currentThread().getStackTrace();
10+
* if (traces.length > 3) {
11+
* Log.i("LiveData", methodName + "() called with LiveData = " + this + " Value = " + value + " Caller = " + traces[3].toString());
12+
* }
13+
* }
14+
*/
15+
fun visitLogValueMethod(methodVisitor: MethodVisitor) {
16+
methodVisitor.visitCode()
17+
val label0 = Label()
18+
methodVisitor.visitLabel(label0)
19+
methodVisitor.visitLineNumber(302, label0)
20+
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Thread", "currentThread", "()Ljava/lang/Thread;", false)
21+
methodVisitor.visitMethodInsn(
22+
Opcodes.INVOKEVIRTUAL,
23+
"java/lang/Thread",
24+
"getStackTrace",
25+
"()[Ljava/lang/StackTraceElement;",
26+
false
27+
)
28+
methodVisitor.visitVarInsn(Opcodes.ASTORE, 3)
29+
val label1 = Label()
30+
methodVisitor.visitLabel(label1)
31+
methodVisitor.visitLineNumber(303, label1)
32+
methodVisitor.visitVarInsn(Opcodes.ALOAD, 3)
33+
methodVisitor.visitInsn(Opcodes.ARRAYLENGTH)
34+
methodVisitor.visitInsn(Opcodes.ICONST_5)
35+
val label2 = Label()
36+
methodVisitor.visitJumpInsn(Opcodes.IF_ICMPLE, label2)
37+
val label3 = Label()
38+
methodVisitor.visitLabel(label3)
39+
methodVisitor.visitLineNumber(304, label3)
40+
methodVisitor.visitLdcInsn("LiveData")
41+
methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder")
42+
methodVisitor.visitInsn(Opcodes.DUP)
43+
methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false)
44+
methodVisitor.visitVarInsn(Opcodes.ALOAD, 1)
45+
methodVisitor.visitMethodInsn(
46+
Opcodes.INVOKEVIRTUAL,
47+
"java/lang/StringBuilder",
48+
"append",
49+
"(Ljava/lang/String;)Ljava/lang/StringBuilder;",
50+
false
51+
)
52+
methodVisitor.visitLdcInsn("() called with LiveData = ")
53+
methodVisitor.visitMethodInsn(
54+
Opcodes.INVOKEVIRTUAL,
55+
"java/lang/StringBuilder",
56+
"append",
57+
"(Ljava/lang/String;)Ljava/lang/StringBuilder;",
58+
false
59+
)
60+
methodVisitor.visitVarInsn(Opcodes.ALOAD, 0)
61+
methodVisitor.visitMethodInsn(
62+
Opcodes.INVOKEVIRTUAL,
63+
"java/lang/StringBuilder",
64+
"append",
65+
"(Ljava/lang/Object;)Ljava/lang/StringBuilder;",
66+
false
67+
)
68+
methodVisitor.visitLdcInsn(" Value = ")
69+
methodVisitor.visitMethodInsn(
70+
Opcodes.INVOKEVIRTUAL,
71+
"java/lang/StringBuilder",
72+
"append",
73+
"(Ljava/lang/String;)Ljava/lang/StringBuilder;",
74+
false
75+
)
76+
methodVisitor.visitVarInsn(Opcodes.ALOAD, 2)
77+
methodVisitor.visitMethodInsn(
78+
Opcodes.INVOKEVIRTUAL,
79+
"java/lang/StringBuilder",
80+
"append",
81+
"(Ljava/lang/Object;)Ljava/lang/StringBuilder;",
82+
false
83+
)
84+
methodVisitor.visitLdcInsn(" Caller = ")
85+
methodVisitor.visitMethodInsn(
86+
Opcodes.INVOKEVIRTUAL,
87+
"java/lang/StringBuilder",
88+
"append",
89+
"(Ljava/lang/String;)Ljava/lang/StringBuilder;",
90+
false
91+
)
92+
methodVisitor.visitVarInsn(Opcodes.ALOAD, 3)
93+
methodVisitor.visitInsn(Opcodes.ICONST_5)
94+
methodVisitor.visitInsn(Opcodes.AALOAD)
95+
methodVisitor.visitMethodInsn(
96+
Opcodes.INVOKEVIRTUAL,
97+
"java/lang/StackTraceElement",
98+
"toString",
99+
"()Ljava/lang/String;",
100+
false
101+
)
102+
methodVisitor.visitMethodInsn(
103+
Opcodes.INVOKEVIRTUAL,
104+
"java/lang/StringBuilder",
105+
"append",
106+
"(Ljava/lang/String;)Ljava/lang/StringBuilder;",
107+
false
108+
)
109+
methodVisitor.visitMethodInsn(
110+
Opcodes.INVOKEVIRTUAL,
111+
"java/lang/StringBuilder",
112+
"toString",
113+
"()Ljava/lang/String;",
114+
false
115+
)
116+
methodVisitor.visitMethodInsn(
117+
Opcodes.INVOKESTATIC,
118+
"android/util/Log",
119+
"i",
120+
"(Ljava/lang/String;Ljava/lang/String;)I",
121+
false
122+
)
123+
methodVisitor.visitInsn(Opcodes.POP)
124+
methodVisitor.visitLabel(label2)
125+
methodVisitor.visitLineNumber(306, label2)
126+
methodVisitor.visitFrame(Opcodes.F_APPEND, 1, arrayOf<Any>("[Ljava/lang/StackTraceElement;"), 0, null)
127+
methodVisitor.visitInsn(Opcodes.RETURN)
128+
val label4 = Label()
129+
methodVisitor.visitLabel(label4)
130+
methodVisitor.visitLocalVariable(
131+
"this",
132+
"Landroidx/lifecycle/LiveData;",
133+
"Landroidx/lifecycle/LiveData<TT;>;",
134+
label0,
135+
label4,
136+
0
137+
)
138+
methodVisitor.visitLocalVariable("methodName", "Ljava/lang/String;", null, label0, label4, 1)
139+
methodVisitor.visitLocalVariable("value", "Ljava/lang/Object;", "TT;", label0, label4, 2)
140+
methodVisitor.visitLocalVariable("traces", "[Ljava/lang/StackTraceElement;", null, label1, label4, 3)
141+
methodVisitor.visitMaxs(4, 4)
142+
methodVisitor.visitEnd()
143+
}

src/main/kotlin/io/github/chao2zhang/LoggingLiveDataClassVisitor.kt

-25
This file was deleted.

0 commit comments

Comments
 (0)