Skip to content

Commit 54a2890

Browse files
author
Sergey Mashkov
committed
IO: stateful suspend functions dumper
1 parent 8d69c7c commit 54a2890

File tree

1 file changed

+184
-0
lines changed

1 file changed

+184
-0
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package kotlinx.coroutines.experimental.io
2+
3+
import org.objectweb.asm.*
4+
import java.io.*
5+
6+
class StateMachineChecker {
7+
private interface Filter {
8+
fun nonSuspend(className: String, methodName: String): Boolean
9+
fun suspend(className: String, methodName: String, hasStateMachine: Boolean): Boolean
10+
}
11+
private interface Dumper {
12+
fun dump(className: String, methodName: String, desc: String, suspend: Boolean, hasStateMachine: Boolean, access: Int)
13+
}
14+
15+
private enum class ViewModes : Filter {
16+
ALL { // dump all methods
17+
override fun nonSuspend(className: String, methodName: String) = true
18+
override fun suspend(className: String, methodName: String, hasStateMachine: Boolean) = true
19+
},
20+
ALL_SUSPEND { // dump all suspend functions
21+
override fun nonSuspend(className: String, methodName: String) = false
22+
override fun suspend(className: String, methodName: String, hasStateMachine: Boolean) = true
23+
},
24+
ALL_SUSPEND_SM { // dump all suspend functions with state machine
25+
override fun nonSuspend(className: String, methodName: String) = false
26+
override fun suspend(className: String, methodName: String, hasStateMachine: Boolean) = hasStateMachine
27+
},
28+
ALL_SUSPEND_SM_FILTERED { // all stateful suspend functions that don't end with Suspend suffix
29+
override fun nonSuspend(className: String, methodName: String) = false
30+
override fun suspend(className: String, methodName: String, hasStateMachine: Boolean) =
31+
hasStateMachine && !methodName.endsWith("Suspend")
32+
}
33+
}
34+
35+
private class DefaultDumper(private val out: Writer) : Dumper {
36+
override fun dump(className: String, methodName: String, desc: String, suspend: Boolean, hasStateMachine: Boolean, access: Int) {
37+
out.write("[")
38+
if (suspend) out.write("s") else out.write(" ")
39+
if (hasStateMachine) out.write("M") else out.write(" ")
40+
out.write(",")
41+
42+
when {
43+
access and Opcodes.ACC_PRIVATE != 0 -> out.write("pvt")
44+
access and Opcodes.ACC_PUBLIC != 0 -> out.write("pub")
45+
access and Opcodes.ACC_PROTECTED != 0 -> out.write("prt")
46+
else -> out.write(" ")
47+
}
48+
49+
out.write("] ")
50+
51+
out.write(className.substringAfterLast("/"))
52+
out.write(".")
53+
out.write(methodName)
54+
out.write(simplifyDesc(desc))
55+
56+
if (access and Opcodes.ACC_SYNTHETIC != 0) {
57+
out.write(" #synthetic")
58+
}
59+
if (access and Opcodes.ACC_DEPRECATED != 0) {
60+
out.write(" #deprecated")
61+
}
62+
if (access and Opcodes.ACC_FINAL == 0) {
63+
out.write(" #open")
64+
}
65+
if (access and Opcodes.ACC_BRIDGE != 0) {
66+
out.write(" #bridge")
67+
}
68+
69+
out.write("\n")
70+
out.flush()
71+
}
72+
73+
private fun simplifyDesc(desc: String): String {
74+
val components = """\[?(L[^;]+;|[ZBCSIFDJV])""".toRegex().findAll(desc).map { it.value }.toList()
75+
76+
return buildString {
77+
append("(")
78+
val last = components.lastIndex
79+
for (i in 0 until last) {
80+
append(simplifyType(components[i]))
81+
}
82+
if (last >= 0) {
83+
append(")")
84+
append(simplifyType(components[last]))
85+
} else {
86+
append(")?")
87+
}
88+
}
89+
}
90+
91+
private fun simplifyType(type: String): String {
92+
return if (type.startsWith("[")) "[" + simplifyType(type.removePrefix("["))
93+
else if (type.startsWith("L")) "L" + type.removePrefix("L").substringAfterLast("/")
94+
else type
95+
}
96+
}
97+
98+
private var filter: Filter = ViewModes.ALL_SUSPEND_SM_FILTERED
99+
private var dumper: Dumper = DefaultDumper(System.out.bufferedWriter())
100+
101+
private fun process(f: File) {
102+
val reader = f.inputStream().use { ClassReader(it) }
103+
reader.accept(object: ClassVisitor(Opcodes.ASM5) {
104+
private var className: String = "?class"
105+
106+
override fun visit(version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array<out String>?) {
107+
className = name ?: "?class"
108+
super.visit(version, access, name, signature, superName, interfaces)
109+
}
110+
111+
override fun visitMethod(access: Int, name: String?, desc: String?, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
112+
if (desc != null && desc.contains("Lkotlin/coroutines/experimental/Continuation;)")) {
113+
val visitor = MyMethodVisitor
114+
visitor.filter = filter
115+
visitor.dumper = dumper
116+
117+
visitor.className = className
118+
visitor.methodName = name ?: "?method"
119+
visitor.methodDesc = desc ?: "?desc"
120+
visitor.access = access
121+
122+
return visitor
123+
} else if (filter.nonSuspend(className, name ?: "?method")) {
124+
if (filter.nonSuspend(className, name ?: "?method")) {
125+
dumper.dump(className, name ?: "?method", desc ?: "", false, false, access)
126+
}
127+
}
128+
return super.visitMethod(access, name, desc, signature, exceptions)
129+
}
130+
}, 0)
131+
}
132+
133+
private fun processDir(dir: File) {
134+
dir.walkTopDown().asSequence().filter { it.name.endsWith(".class") && it.isFile }.forEach { file ->
135+
process(file)
136+
}
137+
}
138+
139+
private object MyMethodVisitor : MethodVisitor(Opcodes.ASM5) {
140+
lateinit var filter: Filter
141+
lateinit var dumper: Dumper
142+
143+
var className: String = "?class"
144+
var methodName: String = "?method"
145+
var methodDesc: String = "?desc"
146+
var access: Int = 0
147+
148+
var foundGetLabel = false
149+
var foundTableSwitch = false
150+
151+
override fun visitMethodInsn(opcode: Int, owner: String?, name: String?, desc: String?, itf: Boolean) {
152+
if(name == "getLabel") {
153+
foundGetLabel = true
154+
}
155+
156+
super.visitMethodInsn(opcode, owner, name, desc, itf)
157+
}
158+
159+
override fun visitTableSwitchInsn(min: Int, max: Int, dflt: Label?, vararg labels: Label?) {
160+
foundTableSwitch = true
161+
super.visitTableSwitchInsn(min, max, dflt, *labels)
162+
}
163+
164+
override fun visitEnd() {
165+
val hasStateMachine = foundGetLabel && foundTableSwitch
166+
if (filter.suspend(className, methodName, hasStateMachine)) {
167+
dumper.dump(className, methodName, methodDesc, true, hasStateMachine, access)
168+
}
169+
170+
foundGetLabel = false
171+
foundTableSwitch = false
172+
173+
super.visitEnd()
174+
}
175+
}
176+
177+
companion object {
178+
@JvmStatic
179+
fun main(args: Array<String>) {
180+
val f = File("build/classes/kotlin/main")
181+
StateMachineChecker().processDir(f)
182+
}
183+
}
184+
}

0 commit comments

Comments
 (0)