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