diff options
Diffstat (limited to 'binary-compatibility-validator/src/PublicApiDump.kt')
-rw-r--r-- | binary-compatibility-validator/src/PublicApiDump.kt | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/binary-compatibility-validator/src/PublicApiDump.kt b/binary-compatibility-validator/src/PublicApiDump.kt new file mode 100644 index 00000000..343df34b --- /dev/null +++ b/binary-compatibility-validator/src/PublicApiDump.kt @@ -0,0 +1,120 @@ +/* + * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.tools + +import org.objectweb.asm.* +import org.objectweb.asm.tree.* +import java.io.* +import java.util.jar.* + +fun JarFile.classEntries() = entries().asSequence().filter { + !it.isDirectory && it.name.endsWith(".class") && !it.name.startsWith("META-INF/") +} + +fun getBinaryAPI(jar: JarFile, visibilityMap: Map<String, ClassVisibility>): List<ClassBinarySignature> = + getBinaryAPI(jar.classEntries().map { entry -> jar.getInputStream(entry) }, visibilityMap) + +fun getBinaryAPI( + classStreams: Sequence<InputStream>, + visibilityMap: Map<String, ClassVisibility> +): List<ClassBinarySignature> = + classStreams.map { + it.use { stream -> + val classNode = ClassNode() + ClassReader(stream).accept(classNode, ClassReader.SKIP_CODE) + classNode + } + }.map { + with(it) { + val classVisibility = visibilityMap[name] + val classAccess = AccessFlags(effectiveAccess and Opcodes.ACC_STATIC.inv()) + val supertypes = listOf(superName) - "java/lang/Object" + interfaces.sorted() + + val memberSignatures = ( + fields.map { + with(it) { + FieldBinarySignature( + name, + desc, + isPublishedApi(), + AccessFlags(access) + ) + } + } + + methods.map { + with(it) { + MethodBinarySignature( + name, + desc, + isPublishedApi(), + AccessFlags(access) + ) + } + } + ).filter { + it.isEffectivelyPublic(classAccess, classVisibility) + } + + ClassBinarySignature( + name, + superName, + outerClassName, + supertypes, + memberSignatures, + classAccess, + isEffectivelyPublic(classVisibility), + isFileOrMultipartFacade() || isDefaultImpls() + ) + } + }.asIterable().sortedBy { it.name } + + +fun List<ClassBinarySignature>.filterOutNonPublic(nonPublicPackages: List<String> = emptyList()): List<ClassBinarySignature> { + val nonPublicPaths = nonPublicPackages.map { it.replace('.', '/') + '/' } + val classByName = associateBy { it.name } + + fun ClassBinarySignature.isInNonPublicPackage() = + nonPublicPaths.any { name.startsWith(it) } + + fun ClassBinarySignature.isPublicAndAccessible(): Boolean = + isEffectivelyPublic && + (outerName == null || classByName[outerName]?.let { outerClass -> + !(this.access.isProtected && outerClass.access.isFinal) + && outerClass.isPublicAndAccessible() + } ?: true) + + fun supertypes(superName: String) = generateSequence({ classByName[superName] }, { classByName[it.superName] }) + + fun ClassBinarySignature.flattenNonPublicBases(): ClassBinarySignature { + + val nonPublicSupertypes = supertypes(superName).takeWhile { !it.isPublicAndAccessible() }.toList() + if (nonPublicSupertypes.isEmpty()) + return this + + val inheritedStaticSignatures = + nonPublicSupertypes.flatMap { it.memberSignatures.filter { it.access.isStatic } } + + // not covered the case when there is public superclass after chain of private superclasses + return this.copy( + memberSignatures = memberSignatures + inheritedStaticSignatures, + supertypes = supertypes - superName + ) + } + + return filter { !it.isInNonPublicPackage() && it.isPublicAndAccessible() } + .map { it.flattenNonPublicBases() } + .filterNot { it.isNotUsedWhenEmpty && it.memberSignatures.isEmpty() } +} + +fun List<ClassBinarySignature>.dump() = dump(to = System.out) + +fun <T : Appendable> List<ClassBinarySignature>.dump(to: T): T = to.apply { + this@dump.forEach { + append(it.signature).appendln(" {") + it.memberSignatures.sortedWith(MEMBER_SORT_ORDER).forEach { append("\t").appendln(it.signature) } + appendln("}\n") + } +} + |