blob: 58f82b86ec2e5f5bda40fee3bd4f5b9549f0deda [file] [log] [blame]
Vsevolod Tolstopyatov74bcc922018-05-03 20:07:54 +03001package kotlinx.coroutines.experimental.tools
2
3import org.objectweb.asm.*
4import org.objectweb.asm.tree.*
5import java.io.*
6import java.util.jar.*
7
Vsevolod Tolstopyatov74bcc922018-05-03 20:07:54 +03008fun JarFile.classEntries() = entries().asSequence().filter {
9 !it.isDirectory && it.name.endsWith(".class") && !it.name.startsWith("META-INF/")
10}
11
12fun getBinaryAPI(jar: JarFile, visibilityMap: Map<String, ClassVisibility>): List<ClassBinarySignature> =
13 getBinaryAPI(jar.classEntries().map { entry -> jar.getInputStream(entry) }, visibilityMap)
14
15fun getBinaryAPI(
16 classStreams: Sequence<InputStream>,
17 visibilityMap: Map<String, ClassVisibility>
18): List<ClassBinarySignature> =
19 classStreams.map {
20 it.use { stream ->
21 val classNode = ClassNode()
22 ClassReader(stream).accept(classNode, ClassReader.SKIP_CODE)
23 classNode
24 }
25 }.map {
26 with(it) {
27 val classVisibility = visibilityMap[name]
28 val classAccess = AccessFlags(effectiveAccess and Opcodes.ACC_STATIC.inv())
29 val supertypes = listOf(superName) - "java/lang/Object" + interfaces.sorted()
30
31 val memberSignatures = (
32 fields.map {
33 with(it) {
34 FieldBinarySignature(
35 name,
36 desc,
37 isPublishedApi(),
38 AccessFlags(access)
39 )
40 }
41 } +
42 methods.map {
43 with(it) {
44 MethodBinarySignature(
45 name,
46 desc,
47 isPublishedApi(),
48 AccessFlags(access)
49 )
50 }
51 }
52 ).filter {
53 it.isEffectivelyPublic(classAccess, classVisibility)
54 }
55
56 ClassBinarySignature(
57 name,
58 superName,
59 outerClassName,
60 supertypes,
61 memberSignatures,
62 classAccess,
63 isEffectivelyPublic(classVisibility),
64 isFileOrMultipartFacade() || isDefaultImpls()
65 )
66 }
67 }.asIterable().sortedBy { it.name }
68
69
70fun List<ClassBinarySignature>.filterOutNonPublic(nonPublicPackages: List<String> = emptyList()): List<ClassBinarySignature> {
71 val nonPublicPaths = nonPublicPackages.map { it.replace('.', '/') + '/' }
72 val classByName = associateBy { it.name }
73
74 fun ClassBinarySignature.isInNonPublicPackage() =
75 nonPublicPaths.any { name.startsWith(it) }
76
77 fun ClassBinarySignature.isPublicAndAccessible(): Boolean =
78 isEffectivelyPublic &&
79 (outerName == null || classByName[outerName]?.let { outerClass ->
80 !(this.access.isProtected && outerClass.access.isFinal)
81 && outerClass.isPublicAndAccessible()
82 } ?: true)
83
84 fun supertypes(superName: String) = generateSequence({ classByName[superName] }, { classByName[it.superName] })
85
86 fun ClassBinarySignature.flattenNonPublicBases(): ClassBinarySignature {
87
88 val nonPublicSupertypes = supertypes(superName).takeWhile { !it.isPublicAndAccessible() }.toList()
89 if (nonPublicSupertypes.isEmpty())
90 return this
91
92 val inheritedStaticSignatures =
93 nonPublicSupertypes.flatMap { it.memberSignatures.filter { it.access.isStatic } }
94
95 // not covered the case when there is public superclass after chain of private superclasses
96 return this.copy(
97 memberSignatures = memberSignatures + inheritedStaticSignatures,
98 supertypes = supertypes - superName
99 )
100 }
101
102 return filter { !it.isInNonPublicPackage() && it.isPublicAndAccessible() }
103 .map { it.flattenNonPublicBases() }
104 .filterNot { it.isNotUsedWhenEmpty && it.memberSignatures.isEmpty() }
105}
106
107fun List<ClassBinarySignature>.dump() = dump(to = System.out)
108
109fun <T : Appendable> List<ClassBinarySignature>.dump(to: T): T = to.apply {
110 this@dump.forEach {
111 append(it.signature).appendln(" {")
112 it.memberSignatures.sortedWith(MEMBER_SORT_ORDER).forEach { append("\t").appendln(it.signature) }
113 appendln("}\n")
114 }
115}
116