-
Notifications
You must be signed in to change notification settings - Fork 580
/
JavassistTransformer.kt
110 lines (94 loc) · 4.08 KB
/
JavassistTransformer.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package com.didiglobal.booster.transform.javassist
import com.didiglobal.booster.annotations.Priority
import com.didiglobal.booster.kotlinx.touch
import com.didiglobal.booster.transform.TransformContext
import com.didiglobal.booster.transform.Transformer
import com.didiglobal.booster.transform.util.diff
import com.google.auto.service.AutoService
import javassist.ClassPool
import java.io.ByteArrayOutputStream
import java.io.DataOutputStream
import java.io.File
import java.lang.management.ManagementFactory
import java.lang.management.ThreadMXBean
import java.time.Duration
import java.util.ServiceLoader
/**
* Represents bytecode transformer using Javassist
*
* @author johnsonlee
*/
@AutoService(Transformer::class)
class JavassistTransformer : Transformer {
private val pool = ClassPool()
private val threadMxBean = ManagementFactory.getThreadMXBean()
private val durations = mutableMapOf<ClassTransformer, Duration>()
private val classLoader: ClassLoader
private val transformers: Iterable<ClassTransformer>
constructor() : this(Thread.currentThread().contextClassLoader)
constructor(classLoader: ClassLoader = Thread.currentThread().contextClassLoader) : this(classLoader, ServiceLoader.load(ClassTransformer::class.java, classLoader).sortedBy {
it.javaClass.getAnnotation(Priority::class.java)?.value ?: 0
})
constructor(classLoader: ClassLoader = Thread.currentThread().contextClassLoader, transformers: Iterable<ClassTransformer>) {
this.classLoader = classLoader
this.transformers = transformers
}
override fun onPreTransform(context: TransformContext) {
context.bootClasspath.forEach { file ->
this.pool.appendClassPath(file.canonicalPath)
}
context.compileClasspath.forEach { file ->
this.pool.appendClassPath(file.canonicalPath)
}
this.transformers.forEach { transformer ->
this.threadMxBean.sumCpuTime(transformer) {
transformer.onPreTransform(context)
}
}
}
override fun transform(context: TransformContext, bytecode: ByteArray): ByteArray {
val diffEnabled = context.getProperty("booster.transform.diff", false)
return ByteArrayOutputStream().use { output ->
bytecode.inputStream().use { input ->
this.transformers.fold(this.pool.makeClass(input)) { a, transformer ->
this.threadMxBean.sumCpuTime(transformer) {
if (diffEnabled) {
val left = a.textify()
transformer.transform(context, a).also trans@{ b ->
val right = b.textify()
val diff = if (left == right) "" else left diff right
if (diff.isEmpty() || diff.isBlank()) {
return@trans
}
transformer.getReport(context, "${a.name}.diff").touch().writeText(diff)
}
} else {
transformer.transform(context, a)
}
}
}.classFile.write(DataOutputStream(output))
}
output.toByteArray()
}
}
override fun onPostTransform(context: TransformContext) {
this.transformers.forEach {
it.onPostTransform(context)
}
val w1 = this.durations.keys.map {
it.javaClass.name.length
}.maxOrNull() ?: 20
this.durations.forEach { (transformer, ns) ->
println("${transformer.javaClass.name.padEnd(w1 + 1)}: ${ns.toMillis()} ms")
}
}
private fun <R> ThreadMXBean.sumCpuTime(transformer: ClassTransformer, action: () -> R): R {
val ct0 = this.currentThreadCpuTime
val result = action()
val ct1 = this.currentThreadCpuTime
durations[transformer] = durations.getOrPut(transformer) {
Duration.ofNanos(0)
} + Duration.ofNanos(ct1 - ct0)
return result
}
}