[IR] Two modes of signature rendering
^KT-59486
This commit is contained in:
committed by
Space Team
parent
c66bc36382
commit
4c0583e415
+1
-1
@@ -418,7 +418,7 @@ class IrModuleToJsTransformer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun Set<IrDeclaration>.computeTag(declaration: IrDeclaration): String? {
|
private fun Set<IrDeclaration>.computeTag(declaration: IrDeclaration): String? {
|
||||||
val tag = (backendContext.irFactory as IdSignatureRetriever).declarationSignature(declaration)?.toString()
|
val tag = (backendContext.irFactory as IdSignatureRetriever).declarationSignature(declaration)?.render()
|
||||||
|
|
||||||
if (tag == null && !contains(declaration)) {
|
if (tag == null && !contains(declaration)) {
|
||||||
error("signature for ${declaration.render()} not found")
|
error("signature for ${declaration.render()} not found")
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
|||||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrFileSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrFileSymbol
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.ir.util.render as newRender
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [IdSignature] is a unique key that corresponds to each Kotlin Declaration. It is used to reference declarations in klib.
|
* [IdSignature] is a unique key that corresponds to each Kotlin Declaration. It is used to reference declarations in klib.
|
||||||
@@ -248,7 +249,13 @@ sealed class IdSignature {
|
|||||||
|
|
||||||
open fun asPublic(): CommonSignature? = null
|
open fun asPublic(): CommonSignature? = null
|
||||||
|
|
||||||
abstract fun render(): String
|
@Deprecated(
|
||||||
|
"Rendering of signatures has been extracted to IdSignatureRenderer.render()",
|
||||||
|
replaceWith = ReplaceWith("render()", "org.jetbrains.kotlin.ir.util.render"),
|
||||||
|
level = DeprecationLevel.HIDDEN
|
||||||
|
)
|
||||||
|
fun render(): String = newRender()
|
||||||
|
final override fun toString() = newRender()
|
||||||
|
|
||||||
fun Flags.test(): Boolean = decode(flags())
|
fun Flags.test(): Boolean = decode(flags())
|
||||||
|
|
||||||
@@ -261,13 +268,10 @@ sealed class IdSignature {
|
|||||||
// TODO: this API is a bit hacky, consider to act somehow else
|
// TODO: this API is a bit hacky, consider to act somehow else
|
||||||
open val visibleCrossFile: Boolean get() = true
|
open val visibleCrossFile: Boolean get() = true
|
||||||
|
|
||||||
override fun toString(): String =
|
|
||||||
"${if (isLocal) "local " else ""}${render()}"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This signature corresponds to a publicly accessible Kotlin declaration.
|
* This signature corresponds to a publicly accessible Kotlin declaration.
|
||||||
*
|
*
|
||||||
* @property description This property does not affect linkage and is used only for showing humnan-readable error messages.
|
* @property description This property does not affect linkage and is used only for showing human-readable error messages.
|
||||||
* Note: currently, we store here the mangled name from which [id] was computed. Later we can reconsider.
|
* Note: currently, we store here the mangled name from which [id] was computed. Later we can reconsider.
|
||||||
*/
|
*/
|
||||||
class CommonSignature(
|
class CommonSignature(
|
||||||
@@ -319,8 +323,6 @@ sealed class IdSignature {
|
|||||||
|
|
||||||
override fun flags(): Long = mask
|
override fun flags(): Long = mask
|
||||||
|
|
||||||
override fun render(): String = "$packageFqName/$declarationFqName|$id[${mask.toString(2)}]"
|
|
||||||
|
|
||||||
override fun asPublic(): CommonSignature = this
|
override fun asPublic(): CommonSignature = this
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean =
|
override fun equals(other: Any?): Boolean =
|
||||||
@@ -359,16 +361,6 @@ sealed class IdSignature {
|
|||||||
return if (container is FileSignature) inner.packageFqName() else container.packageFqName()
|
return if (container is FileSignature) inner.packageFqName() else container.packageFqName()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun render(): String {
|
|
||||||
return buildString {
|
|
||||||
append("[ ")
|
|
||||||
append(container)
|
|
||||||
append(" <- ")
|
|
||||||
append(inner)
|
|
||||||
append(" ]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean = other is CompositeSignature && container == other.container && inner == other.inner
|
override fun equals(other: Any?): Boolean = other is CompositeSignature && container == other.container && inner == other.inner
|
||||||
|
|
||||||
override fun hashCode(): Int = container.hashCode() * 31 + inner.hashCode()
|
override fun hashCode(): Int = container.hashCode() * 31 + inner.hashCode()
|
||||||
@@ -393,8 +385,6 @@ sealed class IdSignature {
|
|||||||
|
|
||||||
override fun packageFqName(): FqName = propertySignature.packageFqName()
|
override fun packageFqName(): FqName = propertySignature.packageFqName()
|
||||||
|
|
||||||
override fun render(): String = accessorSignature.render()
|
|
||||||
|
|
||||||
override fun flags(): Long = accessorSignature.mask
|
override fun flags(): Long = accessorSignature.mask
|
||||||
|
|
||||||
override fun asPublic(): CommonSignature = accessorSignature
|
override fun asPublic(): CommonSignature = accessorSignature
|
||||||
@@ -448,10 +438,6 @@ sealed class IdSignature {
|
|||||||
|
|
||||||
override fun packageFqName(): FqName = fqName
|
override fun packageFqName(): FqName = fqName
|
||||||
|
|
||||||
override fun render(): String {
|
|
||||||
return "File '$fileName'"
|
|
||||||
}
|
|
||||||
|
|
||||||
override val hasTopLevel: Boolean
|
override val hasTopLevel: Boolean
|
||||||
get() = false
|
get() = false
|
||||||
}
|
}
|
||||||
@@ -468,34 +454,18 @@ sealed class IdSignature {
|
|||||||
override val isLocal: Boolean
|
override val isLocal: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
|
|
||||||
fun index(): Int = hashSig?.toInt() ?: error("Expected index in ${render()}")
|
fun index(): Int = hashSig?.toInt() ?: error("Expected index in $this")
|
||||||
|
|
||||||
override fun topLevelSignature(): IdSignature {
|
override fun topLevelSignature(): IdSignature {
|
||||||
error("Illegal access: Local Sig does not have toplevel (${render()}")
|
error("Illegal access: Local Sig does not have toplevel ($this")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun nearestPublicSig(): IdSignature {
|
override fun nearestPublicSig(): IdSignature {
|
||||||
error("Illegal access: Local Sig does not have information about its public part (${render()}")
|
error("Illegal access: Local Sig does not have information about its public part ($this")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun packageFqName(): FqName {
|
override fun packageFqName(): FqName {
|
||||||
error("Illegal access: Local signature does not have package (${render()}")
|
error("Illegal access: Local signature does not have package ($this")
|
||||||
}
|
|
||||||
|
|
||||||
override fun render(): String {
|
|
||||||
return buildString {
|
|
||||||
append("Local[")
|
|
||||||
append(localFqn)
|
|
||||||
hashSig?.let {
|
|
||||||
append(",")
|
|
||||||
append(it)
|
|
||||||
}
|
|
||||||
description?.let {
|
|
||||||
append(" | ")
|
|
||||||
append(it)
|
|
||||||
}
|
|
||||||
append("]")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -548,9 +518,6 @@ sealed class IdSignature {
|
|||||||
override fun packageFqName(): FqName =
|
override fun packageFqName(): FqName =
|
||||||
memberSignature.packageFqName()
|
memberSignature.packageFqName()
|
||||||
|
|
||||||
override fun render(): String =
|
|
||||||
memberSignature.render()
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
@@ -597,8 +564,6 @@ sealed class IdSignature {
|
|||||||
|
|
||||||
override fun nearestPublicSig(): IdSignature = container.nearestPublicSig()
|
override fun nearestPublicSig(): IdSignature = container.nearestPublicSig()
|
||||||
|
|
||||||
override fun render(): String = "${container.render()}:$id"
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean =
|
override fun equals(other: Any?): Boolean =
|
||||||
other is FileLocalSignature && id == other.id && container == other.container
|
other is FileLocalSignature && id == other.id && container == other.container
|
||||||
|
|
||||||
@@ -612,9 +577,7 @@ sealed class IdSignature {
|
|||||||
*
|
*
|
||||||
* This signature is not navigatable through files.
|
* This signature is not navigatable through files.
|
||||||
*/
|
*/
|
||||||
class ScopeLocalDeclaration(val id: Int, _description: String? = null) : IdSignature() {
|
class ScopeLocalDeclaration(val id: Int, val description: String? = null) : IdSignature() {
|
||||||
|
|
||||||
val description: String = _description ?: "<no description>"
|
|
||||||
|
|
||||||
override val isPubliclyVisible: Boolean get() = false
|
override val isPubliclyVisible: Boolean get() = false
|
||||||
|
|
||||||
@@ -629,8 +592,6 @@ sealed class IdSignature {
|
|||||||
|
|
||||||
override fun packageFqName(): FqName = error("Is not supported for Local ID")
|
override fun packageFqName(): FqName = error("Is not supported for Local ID")
|
||||||
|
|
||||||
override fun render(): String = "#$id"
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean =
|
override fun equals(other: Any?): Boolean =
|
||||||
other is ScopeLocalDeclaration && id == other.id
|
other is ScopeLocalDeclaration && id == other.id
|
||||||
|
|
||||||
@@ -654,8 +615,6 @@ sealed class IdSignature {
|
|||||||
|
|
||||||
override fun packageFqName(): FqName = original.packageFqName()
|
override fun packageFqName(): FqName = original.packageFqName()
|
||||||
|
|
||||||
override fun render(): String = "ic#$stage:${original.render()}-$index"
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
return other is LoweredDeclarationSignature && original == other.original && stage == other.stage && index == other.index
|
return other is LoweredDeclarationSignature && original == other.original && stage == other.stage && index == other.index
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* 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.ir.util
|
||||||
|
|
||||||
|
fun IdSignature.render(renderer: IdSignatureRenderer = IdSignatureRenderer.DEFAULT): String = renderer.render(this)
|
||||||
|
|
||||||
|
class IdSignatureRenderer private constructor(private val showDescriptionForPublicSignatures: Boolean) {
|
||||||
|
fun render(signature: IdSignature): String = buildString { render(signature) }
|
||||||
|
|
||||||
|
private fun StringBuilder.render(signature: IdSignature): StringBuilder = when (signature) {
|
||||||
|
is IdSignature.CommonSignature -> render(signature)
|
||||||
|
is IdSignature.AccessorSignature -> render(signature)
|
||||||
|
is IdSignature.CompositeSignature -> render(signature)
|
||||||
|
is IdSignature.FileSignature -> render(signature)
|
||||||
|
is IdSignature.LocalSignature -> render(signature)
|
||||||
|
is IdSignature.FileLocalSignature -> render(signature)
|
||||||
|
is IdSignature.ScopeLocalDeclaration -> render(signature)
|
||||||
|
is IdSignature.SpecialFakeOverrideSignature -> render(signature)
|
||||||
|
is IdSignature.LoweredDeclarationSignature -> render(signature)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.render(signature: IdSignature.CommonSignature): StringBuilder = with(signature) {
|
||||||
|
append(packageFqName).append('/').append(declarationFqName).append('|')
|
||||||
|
append(if (showDescriptionForPublicSignatures) signature.description ?: id else id)
|
||||||
|
append('[').append(mask.toString(2)).append(']')
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.render(signature: IdSignature.AccessorSignature): StringBuilder =
|
||||||
|
render(signature.accessorSignature)
|
||||||
|
|
||||||
|
private fun StringBuilder.render(signature: IdSignature.CompositeSignature): StringBuilder =
|
||||||
|
append("[ ").render(signature.container).append(" <- ").render(signature.inner).append(" ]")
|
||||||
|
|
||||||
|
private fun StringBuilder.render(signature: IdSignature.FileSignature): StringBuilder =
|
||||||
|
append("File '").append(signature.fileName).append('\'')
|
||||||
|
|
||||||
|
private fun StringBuilder.render(signature: IdSignature.LocalSignature): StringBuilder = with(signature) {
|
||||||
|
append("Local[").append(localFqn)
|
||||||
|
hashSig?.let { append(",").append(hashSig) }
|
||||||
|
renderDescriptionForLocalSignature(description) // Always include description for local signatures if there is any.
|
||||||
|
append(']')
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.render(signature: IdSignature.FileLocalSignature): StringBuilder = with(signature) {
|
||||||
|
render(container).append(':').append(id)
|
||||||
|
renderDescriptionForLocalSignature(description) // Always include description for local signatures if there is any.
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.render(signature: IdSignature.ScopeLocalDeclaration): StringBuilder = with(signature) {
|
||||||
|
append('#').append(id)
|
||||||
|
renderDescriptionForLocalSignature(description) // Always include description for local signatures if there is any.
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.render(signature: IdSignature.SpecialFakeOverrideSignature): StringBuilder =
|
||||||
|
render(signature.memberSignature)
|
||||||
|
|
||||||
|
private fun StringBuilder.render(signature: IdSignature.LoweredDeclarationSignature): StringBuilder = with(signature) {
|
||||||
|
append("ic#").append(stage).append(':')
|
||||||
|
render(original)
|
||||||
|
append('-').append(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StringBuilder.renderDescriptionForLocalSignature(description: String?): StringBuilder {
|
||||||
|
description?.let { append('|').append(description) }
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DEFAULT = IdSignatureRenderer(showDescriptionForPublicSignatures = true)
|
||||||
|
val LEGACY = IdSignatureRenderer(showDescriptionForPublicSignatures = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.common.linkage.issues
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer
|
import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.util.render
|
||||||
|
|
||||||
sealed class IrDeserializationException(message: String) : Exception(message) {
|
sealed class IrDeserializationException(message: String) : Exception(message) {
|
||||||
override val message: String get() = super.message!!
|
override val message: String get() = super.message!!
|
||||||
|
|||||||
+1
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.backend.common.linkage.issues.PotentialConflictReaso
|
|||||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
||||||
import org.jetbrains.kotlin.ir.util.IdSignature
|
import org.jetbrains.kotlin.ir.util.IdSignature
|
||||||
import org.jetbrains.kotlin.ir.util.IrMessageLogger
|
import org.jetbrains.kotlin.ir.util.IrMessageLogger
|
||||||
|
import org.jetbrains.kotlin.ir.util.render
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.utils.ResolvedDependency
|
import org.jetbrains.kotlin.utils.ResolvedDependency
|
||||||
import org.jetbrains.kotlin.utils.ResolvedDependencyId
|
import org.jetbrains.kotlin.utils.ResolvedDependencyId
|
||||||
|
|||||||
+1
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.ir.util.IdSignature.*
|
|||||||
import org.jetbrains.kotlin.ir.util.isAnonymousObject
|
import org.jetbrains.kotlin.ir.util.isAnonymousObject
|
||||||
import org.jetbrains.kotlin.ir.util.parentAsClass
|
import org.jetbrains.kotlin.ir.util.parentAsClass
|
||||||
import org.jetbrains.kotlin.ir.util.parentClassOrNull
|
import org.jetbrains.kotlin.ir.util.parentClassOrNull
|
||||||
|
import org.jetbrains.kotlin.ir.util.render
|
||||||
import org.jetbrains.kotlin.name.SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT
|
import org.jetbrains.kotlin.name.SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT
|
||||||
import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageUtils.Module as PLModule
|
import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageUtils.Module as PLModule
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1436,7 +1436,7 @@ open class IrFileSerializer(
|
|||||||
// TODO: keep order similar
|
// TODO: keep order similar
|
||||||
val sigIndex = protoIdSignatureMap[idSig]
|
val sigIndex = protoIdSignatureMap[idSig]
|
||||||
?: if (it is IrErrorDeclaration) protoIdSignature(idSig) else error("Not found ID for $idSig (${it.render()})")
|
?: if (it is IrErrorDeclaration) protoIdSignature(idSig) else error("Not found ID for $idSig (${it.render()})")
|
||||||
topLevelDeclarations.add(SerializedDeclaration(sigIndex, idSig.toString(), byteArray))
|
topLevelDeclarations.add(SerializedDeclaration(sigIndex, idSig.render(), byteArray))
|
||||||
proto.addDeclarationId(sigIndex)
|
proto.addDeclarationId(sigIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -213,14 +213,17 @@ class IrMangledNameAndSignatureDumpHandler(testServices: TestServices) : Abstrac
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// N.B. We do use IdSignatureRenderer.LEGACY because it renders public signatures with hashes which are
|
||||||
|
// computed by mangled names. So no real need in testing IdSignatureRenderer.DEFAULT which renders mangled names
|
||||||
|
// instead of hashes.
|
||||||
fun printActualMangledNamesAndSignatures() {
|
fun printActualMangledNamesAndSignatures() {
|
||||||
printMangledNames(computedMangledNames)
|
printMangledNames(computedMangledNames)
|
||||||
|
|
||||||
symbol.signature?.let {
|
symbol.signature?.let {
|
||||||
println("// Public signature: ${it.render()}")
|
println("// Public signature: ${it.render(IdSignatureRenderer.LEGACY)}")
|
||||||
}
|
}
|
||||||
symbol.privateSignature?.let {
|
symbol.privateSignature?.let {
|
||||||
println("// Private signature: ${it.render()}")
|
println("// Private signature: ${it.render(IdSignatureRenderer.LEGACY)}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ import org.jetbrains.kotlin.codegen.CodegenTestCase
|
|||||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||||
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
import org.jetbrains.kotlin.config.JVMConfigurationKeys
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationBase
|
import org.jetbrains.kotlin.ir.declarations.IrDeclarationBase
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||||
import org.jetbrains.kotlin.ir.util.IdSignature
|
import org.jetbrains.kotlin.ir.util.IdSignature
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
import org.jetbrains.kotlin.ir.util.render
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||||
|
|||||||
+1
@@ -5,6 +5,7 @@ import org.jetbrains.kotlin.backend.konan.serialization.KonanManglerDesc
|
|||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
|
import org.jetbrains.kotlin.ir.util.render
|
||||||
|
|
||||||
class DefaultIdSignatureRenderer(private val prefix: String? = null) : IdSignatureRenderer {
|
class DefaultIdSignatureRenderer(private val prefix: String? = null) : IdSignatureRenderer {
|
||||||
private val idSignaturer = KonanIdSignaturer(KonanManglerDesc)
|
private val idSignaturer = KonanIdSignaturer(KonanManglerDesc)
|
||||||
|
|||||||
Reference in New Issue
Block a user