[KLIB tool] Introduce new command: dump-ir-signatures
^KT-62341
This commit is contained in:
committed by
Space Team
parent
447b0eddac
commit
4b2776e126
+167
@@ -0,0 +1,167 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.cli.klib
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.*
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.IrLibraryFileFromBytes.Companion.extensionRegistryLite
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.encodings.BinarySymbolData
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.proto.IrDeclaration.DeclaratorCase.*
|
||||||
|
import org.jetbrains.kotlin.ir.util.IdSignature
|
||||||
|
import org.jetbrains.kotlin.library.KotlinLibrary
|
||||||
|
import org.jetbrains.kotlin.library.impl.IrArrayMemoryReader
|
||||||
|
import org.jetbrains.kotlin.metadata.ProtoBuf
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.ifTrue
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.proto.IrFile as ProtoFile
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.proto.IrDeclaration as ProtoDeclaration
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.proto.IrDeclarationBase as ProtoDeclarationBase
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.proto.IrClass as ProtoClass
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.proto.IrTypeAlias as ProtoTypeAlias
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.proto.IrFunctionBase as ProtoFunctionBase
|
||||||
|
import org.jetbrains.kotlin.backend.common.serialization.proto.IrProperty as ProtoProperty
|
||||||
|
|
||||||
|
internal class IrSignaturesExtractor(private val library: KotlinLibrary) {
|
||||||
|
data class Signatures(
|
||||||
|
val declaredSignatures: Set<IdSignature>,
|
||||||
|
val importedSignatures: Set<IdSignature>
|
||||||
|
)
|
||||||
|
|
||||||
|
private val interner = IrInterningService()
|
||||||
|
|
||||||
|
private inner class IrSignatureExtractorFromFile(
|
||||||
|
private val fileIndex: Int,
|
||||||
|
private val allKnownSignatures: MutableSet<IdSignature>,
|
||||||
|
private val ownDeclarationSignatures: OwnDeclarationSignatures
|
||||||
|
) {
|
||||||
|
private val fileProto = ProtoFile.parseFrom(library.file(fileIndex).codedInputStream, extensionRegistryLite)
|
||||||
|
private val fileReader = IrLibraryFileFromBytes(IrKlibBytesSource(library, fileIndex))
|
||||||
|
|
||||||
|
private val signatureDeserializer: IdSignatureDeserializer = run {
|
||||||
|
val packageFQN = fileReader.deserializeFqName(fileProto.fqNameList)
|
||||||
|
val fileName = if (fileProto.hasFileEntry() && fileProto.fileEntry.hasName()) fileProto.fileEntry.name else "<unknown>"
|
||||||
|
|
||||||
|
val fileSignature = IdSignature.FileSignature(
|
||||||
|
id = Any(), // Just an unique object.
|
||||||
|
fqName = FqName(packageFQN),
|
||||||
|
fileName = fileName
|
||||||
|
)
|
||||||
|
IdSignatureDeserializer(fileReader, fileSignature, interner)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extract() {
|
||||||
|
collectAllKnownSignatures()
|
||||||
|
collectSignaturesFromDeclarations()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectAllKnownSignatures() {
|
||||||
|
val maxSignatureIndex = IrArrayMemoryReader(library.signatures(fileIndex)).entryCount() - 1 // Index of the latest signature in the current file.
|
||||||
|
(0..maxSignatureIndex).mapTo(allKnownSignatures, signatureDeserializer::deserializeIdSignature)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectSignaturesFromDeclarations() {
|
||||||
|
for (topLevelDeclarationIndex in fileProto.declarationIdList) {
|
||||||
|
extractSignature(declarationProto = fileReader.declaration(topLevelDeclarationIndex), isParentPrivate = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractSignature(declarationProto: ProtoDeclaration, isParentPrivate: Boolean) {
|
||||||
|
when (declarationProto.declaratorCase) {
|
||||||
|
IR_CLASS -> extractSignatureFromClass(declarationProto.irClass, isParentPrivate)
|
||||||
|
IR_CONSTRUCTOR -> extractSignatureFromFunction(declarationProto.irConstructor.base, isParentPrivate)
|
||||||
|
IR_FUNCTION -> extractSignatureFromFunction(declarationProto.irFunction.base, isParentPrivate)
|
||||||
|
IR_PROPERTY -> extractSignatureFromProperty(declarationProto.irProperty, isParentPrivate)
|
||||||
|
IR_TYPE_ALIAS -> extractSignatureFromTypeAlias(declarationProto.irTypeAlias, isParentPrivate)
|
||||||
|
IR_ENUM_ENTRY -> extractSignatureFromSymbol(declarationProto.irEnumEntry.base.symbol, isPrivate = isParentPrivate)
|
||||||
|
IR_ANONYMOUS_INIT -> extractSignatureFromPrivateDeclaration(declarationProto.irAnonymousInit.base)
|
||||||
|
IR_TYPE_PARAMETER -> extractSignatureFromPrivateDeclaration(declarationProto.irTypeParameter.base)
|
||||||
|
IR_FIELD -> extractSignatureFromPrivateDeclaration(declarationProto.irField.base)
|
||||||
|
IR_VARIABLE -> extractSignatureFromPrivateDeclaration(declarationProto.irVariable.base)
|
||||||
|
IR_VALUE_PARAMETER -> extractSignatureFromPrivateDeclaration(declarationProto.irValueParameter.base)
|
||||||
|
IR_LOCAL_DELEGATED_PROPERTY -> extractSignatureFromPrivateDeclaration(declarationProto.irLocalDelegatedProperty.base)
|
||||||
|
IR_ERROR_DECLARATION, DECLARATOR_NOT_SET, null -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractSignatureFromClass(classProto: ProtoClass, isParentPrivate: Boolean) {
|
||||||
|
val isPrivate = isParentPrivate || classProto.base.isPrivate()
|
||||||
|
extractSignatureFromSymbol(classProto.base.symbol, isPrivate)
|
||||||
|
classProto.declarationList.forEach { extractSignature(it, isPrivate) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractSignatureFromFunction(functionProto: ProtoFunctionBase, isParentPrivate: Boolean) {
|
||||||
|
val isPrivate = isParentPrivate || functionProto.base.isPrivate()
|
||||||
|
extractSignatureFromSymbol(functionProto.base.symbol, isPrivate)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractSignatureFromProperty(propertyProto: ProtoProperty, isParentPrivate: Boolean) {
|
||||||
|
val isPrivate = isParentPrivate || propertyProto.base.isPrivate()
|
||||||
|
extractSignatureFromSymbol(propertyProto.base.symbol, isPrivate)
|
||||||
|
propertyProto.hasGetter().ifTrue { extractSignatureFromFunction(propertyProto.getter.base, isPrivate) }
|
||||||
|
propertyProto.hasSetter().ifTrue { extractSignatureFromFunction(propertyProto.setter.base, isPrivate) }
|
||||||
|
propertyProto.hasBackingField().ifTrue { extractSignatureFromPrivateDeclaration(propertyProto.backingField.base) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractSignatureFromTypeAlias(typeAliasProto: ProtoTypeAlias, isParentPrivate: Boolean) {
|
||||||
|
val isPrivate = isParentPrivate || typeAliasProto.base.isPrivate()
|
||||||
|
extractSignatureFromSymbol(typeAliasProto.base.symbol, isPrivate)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractSignatureFromPrivateDeclaration(declarationProto: ProtoDeclarationBase) {
|
||||||
|
extractSignatureFromSymbol(declarationProto.symbol, isPrivate = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractSignatureFromSymbol(symbolId: Long, isPrivate: Boolean) {
|
||||||
|
val signatureId = BinarySymbolData.decode(symbolId).signatureId
|
||||||
|
val signature = signatureDeserializer.deserializeIdSignature(signatureId)
|
||||||
|
ownDeclarationSignatures[signature] = !isPrivate
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ProtoDeclarationBase.isPrivate(): Boolean =
|
||||||
|
when (IrFlags.VISIBILITY.get(flags.toInt())) {
|
||||||
|
ProtoBuf.Visibility.PUBLIC,
|
||||||
|
ProtoBuf.Visibility.PROTECTED,
|
||||||
|
ProtoBuf.Visibility.INTERNAL -> false
|
||||||
|
ProtoBuf.Visibility.PRIVATE,
|
||||||
|
ProtoBuf.Visibility.PRIVATE_TO_THIS,
|
||||||
|
ProtoBuf.Visibility.LOCAL,
|
||||||
|
null -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extract(): Signatures {
|
||||||
|
val allKnownSignatures: MutableSet<IdSignature> = hashSetOf()
|
||||||
|
val ownDeclarationSignatures: OwnDeclarationSignatures = hashMapOf()
|
||||||
|
|
||||||
|
for (fileIndex in 0 until library.fileCount()) {
|
||||||
|
IrSignatureExtractorFromFile(fileIndex, allKnownSignatures, ownDeclarationSignatures).extract()
|
||||||
|
}
|
||||||
|
|
||||||
|
val outermostOwnDeclarationSignatures: MutableSet<IdSignature> = hashSetOf()
|
||||||
|
ownDeclarationSignatures.keys.forEach { signature ->
|
||||||
|
outermostOwnDeclarationSignatures += signature.getOutermostSignature()
|
||||||
|
if (signature is IdSignature.CompositeSignature && signature.container is IdSignature.FileSignature)
|
||||||
|
outermostOwnDeclarationSignatures += signature.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
val importedSignatures = allKnownSignatures.filterTo(hashSetOf()) { signature ->
|
||||||
|
if (signature.isLocal || !signature.isPubliclyVisible)
|
||||||
|
false
|
||||||
|
else {
|
||||||
|
val outermostSignature = signature.getOutermostSignature()
|
||||||
|
outermostSignature !is IdSignature.FileSignature && outermostSignature !in outermostOwnDeclarationSignatures
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Signatures(
|
||||||
|
declaredSignatures = ownDeclarationSignatures.entries.mapNotNullTo(hashSetOf()) { (signature, isPublic) -> signature.takeIf { isPublic } },
|
||||||
|
importedSignatures = importedSignatures
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IdSignature.getOutermostSignature() = if (hasTopLevel) topLevelSignature() else this
|
||||||
|
}
|
||||||
|
|
||||||
|
private typealias OwnDeclarationSignatures = MutableMap<IdSignature, /* isPublic? */ Boolean>
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.cli.klib
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.ir.util.IdSignature
|
||||||
|
import org.jetbrains.kotlin.ir.util.IdSignatureRenderer
|
||||||
|
import org.jetbrains.kotlin.ir.util.render
|
||||||
|
import org.jetbrains.kotlin.library.KotlinIrSignatureVersion
|
||||||
|
|
||||||
|
internal class IrSignaturesRenderer(private val output: Appendable, signatureVersion: KotlinIrSignatureVersion?) {
|
||||||
|
private val individualSignatureRenderer = when (signatureVersion) {
|
||||||
|
KotlinIrSignatureVersion.V1 -> IdSignatureRenderer.LEGACY
|
||||||
|
null, KotlinIrSignatureVersion.V2 -> IdSignatureRenderer.DEFAULT
|
||||||
|
else -> error("Unsupported signature version: ${signatureVersion.number}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun render(signatures: IrSignaturesExtractor.Signatures) {
|
||||||
|
header("Declared signatures: ${signatures.declaredSignatures.size}")
|
||||||
|
sortedSignatures(signatures.declaredSignatures)
|
||||||
|
blankLine()
|
||||||
|
|
||||||
|
header("Imported signatures: ${signatures.importedSignatures.size}")
|
||||||
|
sortedSignatures(signatures.importedSignatures)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun header(subject: String) {
|
||||||
|
output.append("// ").append(subject).append('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun blankLine() {
|
||||||
|
output.append('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sortedSignatures(signatures: Set<IdSignature>) {
|
||||||
|
signatures.asSequence()
|
||||||
|
.map { signature -> signature.render(individualSignatureRenderer) }
|
||||||
|
.sorted()
|
||||||
|
.forEach { signatureText -> output.append(signatureText).append('\n') }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,7 +63,10 @@ fun printUsage() {
|
|||||||
remove Remove the library from the local repository
|
remove Remove the library from the local repository
|
||||||
dump-ir Dump the intermediate representation (IR) of all declarations in the library
|
dump-ir Dump the intermediate representation (IR) of all declarations in the library
|
||||||
(to be used for debugging purposes only)
|
(to be used for debugging purposes only)
|
||||||
signatures Dump IR signatures of all public declarations in the library
|
dump-ir-signatures Dump IR signatures of all public declarations in the library and all public declarations consumed
|
||||||
|
by this library (as two separate lists). This command relies purely on the data in IR.
|
||||||
|
signatures [DEPRECATED] Please, use "dump-ir-signatures" instead.
|
||||||
|
Dump IR signatures of all public declarations in the library
|
||||||
Note that this command renders the signatures from the metadata. Signatures for certain
|
Note that this command renders the signatures from the metadata. Signatures for certain
|
||||||
declarations in the metadata and in IR may differ if compiler plugins (such as Compose)
|
declarations in the metadata and in IR may differ if compiler plugins (such as Compose)
|
||||||
were applied during library compilation.
|
were applied during library compilation.
|
||||||
@@ -71,6 +74,9 @@ fun printUsage() {
|
|||||||
|
|
||||||
and the options are:
|
and the options are:
|
||||||
-repository <path> Work with the specified repository
|
-repository <path> Work with the specified repository
|
||||||
|
-signature-version {${KotlinIrSignatureVersion.CURRENTLY_SUPPORTED_VERSIONS.joinToString("|") { it.number.toString() }}}
|
||||||
|
Render IR signatures of a specific version. By default, the most up-to-date signature version
|
||||||
|
supported in the library is used.
|
||||||
-print-signatures {true|false}
|
-print-signatures {true|false}
|
||||||
Print IR signature for every declaration (only for "contents" and "dump-ir" commands)
|
Print IR signature for every declaration (only for "contents" and "dump-ir" commands)
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
@@ -104,7 +110,18 @@ class Command(args: Array<String>) {
|
|||||||
|
|
||||||
val verb = args[0]
|
val verb = args[0]
|
||||||
val library = args[1]
|
val library = args[1]
|
||||||
val options = parseArgs(args.drop(2).toTypedArray())
|
val options: Map<String, List<String>> = parseArgs(args.drop(2).toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Command.parseSignatureVersion(): KotlinIrSignatureVersion? {
|
||||||
|
val rawSignatureVersion = options["-signature-version"]?.last() ?: return null
|
||||||
|
val signatureVersion = rawSignatureVersion.toIntOrNull()?.let(::KotlinIrSignatureVersion)
|
||||||
|
?: error("Invalid signature version: $rawSignatureVersion")
|
||||||
|
|
||||||
|
if (signatureVersion !in KotlinIrSignatureVersion.CURRENTLY_SUPPORTED_VERSIONS)
|
||||||
|
error("Unsupported signature version: ${signatureVersion.number}")
|
||||||
|
|
||||||
|
return signatureVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
fun warn(text: String) {
|
fun warn(text: String) {
|
||||||
@@ -264,12 +281,29 @@ class Library(val libraryNameOrPath: String, val requestedRepository: String?) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun signatures(output: Appendable) {
|
fun signatures(output: Appendable) {
|
||||||
|
output.append("WARNING: \"signatures\" is deprecated. Please, use \"dump-ir-signatures\" instead.\n\n")
|
||||||
|
|
||||||
val module = loadModule()
|
val module = loadModule()
|
||||||
val printer = SignaturePrinter(output, DefaultKlibSignatureRenderer())
|
val printer = SignaturePrinter(output, DefaultKlibSignatureRenderer())
|
||||||
|
|
||||||
printer.print(module)
|
printer.print(module)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun dumpIrSignatures(output: Appendable, signatureVersion: KotlinIrSignatureVersion?) {
|
||||||
|
val library = libraryInCurrentDir(libraryNameOrPath)
|
||||||
|
signatureVersion?.checkSupportedInLibrary(library)
|
||||||
|
|
||||||
|
val signatures = IrSignaturesExtractor(library).extract()
|
||||||
|
IrSignaturesRenderer(output, signatureVersion).render(signatures)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KotlinIrSignatureVersion.checkSupportedInLibrary(library: KotlinLibrary) {
|
||||||
|
val supportedSignatureVersions = library.versions.irSignatureVersions
|
||||||
|
if (this !in supportedSignatureVersions)
|
||||||
|
error("Signature version ${this.number} is not supported in library ${library.libraryFile}." +
|
||||||
|
" Supported versions: ${supportedSignatureVersions.joinToString { it.number.toString() }}")
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadModule(): ModuleDescriptor {
|
private fun loadModule(): ModuleDescriptor {
|
||||||
val storageManager = LockBasedStorageManager("klib")
|
val storageManager = LockBasedStorageManager("klib")
|
||||||
val library = libraryInRepoOrCurrentDir(repository, libraryNameOrPath)
|
val library = libraryInRepoOrCurrentDir(repository, libraryNameOrPath)
|
||||||
@@ -314,10 +348,13 @@ fun main(args: Array<String>) {
|
|||||||
val repository = command.options["-repository"]?.last()
|
val repository = command.options["-repository"]?.last()
|
||||||
val printSignatures = command.options["-print-signatures"]?.last()?.toBoolean() == true
|
val printSignatures = command.options["-print-signatures"]?.last()?.toBoolean() == true
|
||||||
|
|
||||||
|
val signatureVersion = command.parseSignatureVersion()
|
||||||
|
|
||||||
val library = Library(command.library, repository)
|
val library = Library(command.library, repository)
|
||||||
|
|
||||||
when (command.verb) {
|
when (command.verb) {
|
||||||
"dump-ir" -> library.ir(System.out, printSignatures)
|
"dump-ir" -> library.ir(System.out, printSignatures)
|
||||||
|
"dump-ir-signatures" -> library.dumpIrSignatures(System.out, signatureVersion)
|
||||||
"contents" -> library.contents(System.out, printSignatures)
|
"contents" -> library.contents(System.out, printSignatures)
|
||||||
"signatures" -> library.signatures(System.out)
|
"signatures" -> library.signatures(System.out)
|
||||||
"info" -> library.info()
|
"info" -> library.info()
|
||||||
|
|||||||
Reference in New Issue
Block a user