Change error message of EXCEPTION_IN_CONST_VAL_INITIALIZER
This commit is contained in:
+1
-1
@@ -76,6 +76,6 @@ object KtDefaultJvmErrorMessages : BaseDiagnosticRendererFactory() {
|
|||||||
map.put(JvmBackendErrors.SCRIPT_CAPTURING_ENUM, "Enum class {0} captures the script class instance. Try to use class or anonymous object instead", STRING)
|
map.put(JvmBackendErrors.SCRIPT_CAPTURING_ENUM, "Enum class {0} captures the script class instance. Try to use class or anonymous object instead", STRING)
|
||||||
map.put(JvmBackendErrors.SCRIPT_CAPTURING_ENUM_ENTRY, "Enum entry {0} captures the script class instance. Try to use class or anonymous object instead", STRING)
|
map.put(JvmBackendErrors.SCRIPT_CAPTURING_ENUM_ENTRY, "Enum entry {0} captures the script class instance. Try to use class or anonymous object instead", STRING)
|
||||||
|
|
||||||
map.put(JvmBackendErrors.EXCEPTION_IN_CONST_VAL_INITIALIZER, "An exception occur while evaluating const value initializer: {0}", STRING)
|
map.put(JvmBackendErrors.EXCEPTION_IN_CONST_VAL_INITIALIZER, "Cannot evaluate constant expression: {0}", STRING)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -10,6 +10,8 @@ import org.jetbrains.kotlin.backend.common.phaser.makeIrModulePhase
|
|||||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||||
import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
|
import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
|
||||||
|
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterConfiguration
|
||||||
|
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterEnvironment
|
||||||
import org.jetbrains.kotlin.ir.interpreter.checker.EvaluationMode
|
import org.jetbrains.kotlin.ir.interpreter.checker.EvaluationMode
|
||||||
import org.jetbrains.kotlin.ir.interpreter.checker.IrConstTransformer
|
import org.jetbrains.kotlin.ir.interpreter.checker.IrConstTransformer
|
||||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmBackendErrors
|
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmBackendErrors
|
||||||
@@ -22,7 +24,8 @@ val constEvaluationPhase = makeIrModulePhase(
|
|||||||
|
|
||||||
// TODO make context common
|
// TODO make context common
|
||||||
class ConstEvaluationLowering(val context: JvmBackendContext) : FileLoweringPass {
|
class ConstEvaluationLowering(val context: JvmBackendContext) : FileLoweringPass {
|
||||||
val interpreter = IrInterpreter(context.irBuiltIns)
|
val configuration = IrInterpreterConfiguration(printOnlyExceptionMessage = true)
|
||||||
|
val interpreter = IrInterpreter(IrInterpreterEnvironment(context.irBuiltIns, configuration), emptyMap())
|
||||||
|
|
||||||
override fun lower(irFile: IrFile) {
|
override fun lower(irFile: IrFile) {
|
||||||
val transformer = IrConstTransformer(interpreter, irFile, mode = EvaluationMode.ONLY_INTRINSIC_CONST) { element, error ->
|
val transformer = IrConstTransformer(interpreter, irFile, mode = EvaluationMode.ONLY_INTRINSIC_CONST) { element, error ->
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
|
|||||||
callStack.popInstruction().handle()
|
callStack.popInstruction().handle()
|
||||||
}
|
}
|
||||||
|
|
||||||
return callStack.popState().toIrExpression(expression).apply { callStack.dropFrame() }
|
return environment.stateToIrExpression(callStack.popState(), expression).apply { callStack.dropFrame() }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun withNewCallStack(call: IrCall, init: IrInterpreter.() -> Any?): State {
|
internal fun withNewCallStack(call: IrCall, init: IrInterpreter.() -> Any?): State {
|
||||||
@@ -221,7 +221,7 @@ class IrInterpreter(internal val environment: IrInterpreterEnvironment, internal
|
|||||||
// this check is used to be sure that object will be created once
|
// this check is used to be sure that object will be created once
|
||||||
val objectState = when (constructorCall) {
|
val objectState = when (constructorCall) {
|
||||||
!is IrConstructorCall -> callStack.loadState(constructorCall.getThisReceiver())
|
!is IrConstructorCall -> callStack.loadState(constructorCall.getThisReceiver())
|
||||||
else -> (if (irClass.isSubclassOfThrowable()) ExceptionState(irClass, callStack.getStackTrace()) else Common(irClass))
|
else -> (if (irClass.isSubclassOfThrowable()) ExceptionState(irClass, environment) else Common(irClass))
|
||||||
.apply {
|
.apply {
|
||||||
// must set object's state here, before actual initialization, to avoid cyclic evaluation
|
// must set object's state here, before actual initialization, to avoid cyclic evaluation
|
||||||
if (irClass.isObject) environment.mapOfObjects[irClass.symbol] = this
|
if (irClass.isObject) environment.mapOfObjects[irClass.symbol] = this
|
||||||
|
|||||||
+2
@@ -17,4 +17,6 @@ class IrInterpreterConfiguration(
|
|||||||
val maxStack: Int = 10_000,
|
val maxStack: Int = 10_000,
|
||||||
val maxCommands: Int = 1_000_000,
|
val maxCommands: Int = 1_000_000,
|
||||||
val createNonCompileTimeObjects: Boolean = false,
|
val createNonCompileTimeObjects: Boolean = false,
|
||||||
|
val printOnlyExceptionMessage: Boolean = false,
|
||||||
|
val collapseStackTraceFromJDK: Boolean = true,
|
||||||
)
|
)
|
||||||
|
|||||||
+31
-4
@@ -9,20 +9,21 @@ import org.jetbrains.kotlin.ir.IrBuiltIns
|
|||||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrProperty
|
import org.jetbrains.kotlin.ir.declarations.IrProperty
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl
|
||||||
import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy
|
import org.jetbrains.kotlin.ir.interpreter.proxy.Proxy
|
||||||
import org.jetbrains.kotlin.ir.interpreter.stack.CallStack
|
import org.jetbrains.kotlin.ir.interpreter.stack.CallStack
|
||||||
import org.jetbrains.kotlin.ir.interpreter.state.Common
|
import org.jetbrains.kotlin.ir.interpreter.state.Common
|
||||||
import org.jetbrains.kotlin.ir.interpreter.state.Complex
|
import org.jetbrains.kotlin.ir.interpreter.state.Complex
|
||||||
|
import org.jetbrains.kotlin.ir.interpreter.state.ExceptionState
|
||||||
import org.jetbrains.kotlin.ir.interpreter.state.Primitive
|
import org.jetbrains.kotlin.ir.interpreter.state.Primitive
|
||||||
import org.jetbrains.kotlin.ir.interpreter.state.State
|
import org.jetbrains.kotlin.ir.interpreter.state.State
|
||||||
import org.jetbrains.kotlin.ir.interpreter.state.Wrapper
|
import org.jetbrains.kotlin.ir.interpreter.state.Wrapper
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
||||||
import org.jetbrains.kotlin.ir.types.IrSimpleType
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
import org.jetbrains.kotlin.ir.types.IrType
|
import org.jetbrains.kotlin.ir.util.defaultType
|
||||||
import org.jetbrains.kotlin.ir.types.classOrNull
|
|
||||||
import org.jetbrains.kotlin.ir.types.typeOrNull
|
|
||||||
import org.jetbrains.kotlin.ir.util.isSubclassOf
|
import org.jetbrains.kotlin.ir.util.isSubclassOf
|
||||||
import org.jetbrains.kotlin.ir.util.nameForIrSerialization
|
import org.jetbrains.kotlin.ir.util.nameForIrSerialization
|
||||||
|
|
||||||
@@ -116,6 +117,32 @@ class IrInterpreterEnvironment(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun stateToIrExpression(state: State, original: IrExpression): IrExpression {
|
||||||
|
val start = original.startOffset
|
||||||
|
val end = original.endOffset
|
||||||
|
val type = original.type.makeNotNull()
|
||||||
|
return when (state) {
|
||||||
|
is Primitive<*> ->
|
||||||
|
when {
|
||||||
|
state.value == null -> state.value.toIrConst(type, start, end)
|
||||||
|
type.isPrimitiveType() || type.isString() -> state.value.toIrConst(type, start, end)
|
||||||
|
else -> original // TODO support for arrays
|
||||||
|
}
|
||||||
|
is ExceptionState -> {
|
||||||
|
val message = if (configuration.printOnlyExceptionMessage) state.getShortDescription() else "\n" + state.getFullDescription()
|
||||||
|
IrErrorExpressionImpl(original.startOffset, original.endOffset, original.type, message)
|
||||||
|
}
|
||||||
|
is Complex -> {
|
||||||
|
val stateType = state.irClass.defaultType
|
||||||
|
when {
|
||||||
|
stateType.isUnsignedType() -> (state.fields.values.single() as Primitive<*>).value.toIrConst(type, start, end)
|
||||||
|
else -> original
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> original // TODO support
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun IrClassSymbol.getIrClassOfReflectionFromList(name: String): IrClassSymbol? {
|
private fun IrClassSymbol.getIrClassOfReflectionFromList(name: String): IrClassSymbol? {
|
||||||
val property = this.owner.declarations.singleOrNull { it.nameForIrSerialization.asString() == name } as? IrProperty
|
val property = this.owner.declarations.singleOrNull { it.nameForIrSerialization.asString() == name } as? IrProperty
|
||||||
val list = property?.getter?.returnType as? IrSimpleType
|
val list = property?.getter?.returnType as? IrSimpleType
|
||||||
|
|||||||
-25
@@ -65,31 +65,6 @@ fun Any?.toIrConst(irType: IrType, startOffset: Int = SYNTHETIC_OFFSET, endOffse
|
|||||||
toIrConstOrNull(irType, startOffset, endOffset)
|
toIrConstOrNull(irType, startOffset, endOffset)
|
||||||
?: throw UnsupportedOperationException("Unsupported const element type ${irType.makeNotNull().render()}")
|
?: throw UnsupportedOperationException("Unsupported const element type ${irType.makeNotNull().render()}")
|
||||||
|
|
||||||
internal fun State.toIrExpression(expression: IrExpression): IrExpression {
|
|
||||||
val start = expression.startOffset
|
|
||||||
val end = expression.endOffset
|
|
||||||
val type = expression.type.makeNotNull()
|
|
||||||
return when (this) {
|
|
||||||
is Primitive<*> ->
|
|
||||||
when {
|
|
||||||
this.value == null -> this.value.toIrConst(type, start, end)
|
|
||||||
type.isPrimitiveType() || type.isString() -> this.value.toIrConst(type, start, end)
|
|
||||||
else -> expression // TODO support for arrays
|
|
||||||
}
|
|
||||||
is ExceptionState -> {
|
|
||||||
IrErrorExpressionImpl(expression.startOffset, expression.endOffset, expression.type, "\n" + this.getFullDescription())
|
|
||||||
}
|
|
||||||
is Complex -> {
|
|
||||||
val stateType = this.irClass.defaultType
|
|
||||||
when {
|
|
||||||
stateType.isUnsignedType() -> (this.fields.values.single() as Primitive<*>).value.toIrConst(type, start, end)
|
|
||||||
else -> expression
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> expression // TODO support
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun IrFunction.createCall(origin: IrStatementOrigin? = null): IrCall {
|
internal fun IrFunction.createCall(origin: IrStatementOrigin? = null): IrCall {
|
||||||
this as IrSimpleFunction
|
this as IrSimpleFunction
|
||||||
return IrCallImpl(SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, returnType, symbol, typeParameters.size, valueParameters.size, origin)
|
return IrCallImpl(SYNTHETIC_OFFSET, SYNTHETIC_OFFSET, returnType, symbol, typeParameters.size, valueParameters.size, origin)
|
||||||
|
|||||||
+1
-1
@@ -34,6 +34,6 @@ internal fun Throwable.handleUserException(environment: IrInterpreterEnvironment
|
|||||||
val exceptionName = this::class.java.simpleName
|
val exceptionName = this::class.java.simpleName
|
||||||
val irExceptionClass = environment.irExceptions.firstOrNull { it.name.asString() == exceptionName }
|
val irExceptionClass = environment.irExceptions.firstOrNull { it.name.asString() == exceptionName }
|
||||||
?: environment.irBuiltIns.throwableClass.owner
|
?: environment.irBuiltIns.throwableClass.owner
|
||||||
environment.callStack.pushState(ExceptionState(this, irExceptionClass, environment.callStack.getStackTrace()))
|
environment.callStack.pushState(ExceptionState(this, irExceptionClass, environment.callStack.getStackTrace(), environment))
|
||||||
environment.callStack.dropFramesUntilTryCatch()
|
environment.callStack.dropFramesUntilTryCatch()
|
||||||
}
|
}
|
||||||
+18
-6
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.ir.interpreter.state
|
package org.jetbrains.kotlin.ir.interpreter.state
|
||||||
|
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
|
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterEnvironment
|
||||||
import org.jetbrains.kotlin.ir.interpreter.getOriginalPropertyByName
|
import org.jetbrains.kotlin.ir.interpreter.getOriginalPropertyByName
|
||||||
import org.jetbrains.kotlin.ir.interpreter.stack.Field
|
import org.jetbrains.kotlin.ir.interpreter.stack.Field
|
||||||
import org.jetbrains.kotlin.ir.interpreter.stack.Fields
|
import org.jetbrains.kotlin.ir.interpreter.stack.Fields
|
||||||
@@ -39,11 +40,13 @@ internal class ExceptionState private constructor(
|
|||||||
if (!fields.containsKey(causeProperty.symbol)) setCause(null)
|
if (!fields.containsKey(causeProperty.symbol)) setCause(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(irClass: IrClass, stackTrace: List<String>) : this(irClass, mutableMapOf(), stackTrace)
|
constructor(
|
||||||
|
irClass: IrClass, environment: IrInterpreterEnvironment
|
||||||
|
) : this(irClass, mutableMapOf(), environment.callStack.getStackTrace())
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
exception: Throwable, irClass: IrClass, stackTrace: List<String>
|
exception: Throwable, irClass: IrClass, stackTrace: List<String>, environment: IrInterpreterEnvironment
|
||||||
) : this(irClass, evaluateFields(exception, irClass, stackTrace), stackTrace + evaluateAdditionalStackTrace(exception)) {
|
) : this(irClass, evaluateFields(exception, irClass, environment), stackTrace + evaluateAdditionalStackTrace(exception, environment)) {
|
||||||
setCause(null) // TODO check this fact
|
setCause(null) // TODO check this fact
|
||||||
if (irClass.name.asString() != exception::class.java.simpleName) {
|
if (irClass.name.asString() != exception::class.java.simpleName) {
|
||||||
// ir class wasn't found in classpath, a stub was passed => need to save java class hierarchy
|
// ir class wasn't found in classpath, a stub was passed => need to save java class hierarchy
|
||||||
@@ -88,6 +91,10 @@ internal class ExceptionState private constructor(
|
|||||||
setField(causeProperty.symbol, causeValue ?: Primitive.nullStateOfType(causeProperty.getter!!.returnType))
|
setField(causeProperty.symbol, causeValue ?: Primitive.nullStateOfType(causeProperty.getter!!.returnType))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getShortDescription(): String {
|
||||||
|
return message.let { if (it?.isNotEmpty() == true) it else "???" }
|
||||||
|
}
|
||||||
|
|
||||||
fun getFullDescription(): String {
|
fun getFullDescription(): String {
|
||||||
// TODO remainder of the stack trace with "..."
|
// TODO remainder of the stack trace with "..."
|
||||||
val message = message.let { if (it?.isNotEmpty() == true) ": $it" else "" }
|
val message = message.let { if (it?.isNotEmpty() == true) ": $it" else "" }
|
||||||
@@ -102,18 +109,19 @@ internal class ExceptionState private constructor(
|
|||||||
override fun toString(): String = message?.let { "$exceptionFqName: $it" } ?: exceptionFqName
|
override fun toString(): String = message?.let { "$exceptionFqName: $it" } ?: exceptionFqName
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun evaluateFields(exception: Throwable, irClass: IrClass, stackTrace: List<String>): Fields {
|
private fun evaluateFields(exception: Throwable, irClass: IrClass, environment: IrInterpreterEnvironment): Fields {
|
||||||
|
val stackTrace = environment.callStack.getStackTrace()
|
||||||
val messageProperty = irClass.getOriginalPropertyByName("message")
|
val messageProperty = irClass.getOriginalPropertyByName("message")
|
||||||
val causeProperty = irClass.getOriginalPropertyByName("cause")
|
val causeProperty = irClass.getOriginalPropertyByName("cause")
|
||||||
|
|
||||||
val messageVar = messageProperty.symbol to Primitive(exception.message, messageProperty.getter!!.returnType)
|
val messageVar = messageProperty.symbol to Primitive(exception.message, messageProperty.getter!!.returnType)
|
||||||
val causeVar = exception.cause?.let {
|
val causeVar = exception.cause?.let {
|
||||||
causeProperty.symbol to ExceptionState(it, irClass, stackTrace + it.stackTrace.reversed().map { "at $it" })
|
causeProperty.symbol to ExceptionState(it, irClass, stackTrace + it.stackTrace.reversed().map { "at $it" }, environment)
|
||||||
}
|
}
|
||||||
return causeVar?.let { mutableMapOf(messageVar, it) } ?: mutableMapOf(messageVar)
|
return causeVar?.let { mutableMapOf(messageVar, it) } ?: mutableMapOf(messageVar)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun evaluateAdditionalStackTrace(e: Throwable): List<String> {
|
private fun evaluateAdditionalStackTrace(e: Throwable, environment: IrInterpreterEnvironment): List<String> {
|
||||||
// TODO do we really need this?... It will point to JVM stdlib
|
// TODO do we really need this?... It will point to JVM stdlib
|
||||||
val additionalStack = mutableListOf<String>()
|
val additionalStack = mutableListOf<String>()
|
||||||
if (e.stackTrace.any { it.className == "java.lang.invoke.MethodHandle" }) {
|
if (e.stackTrace.any { it.className == "java.lang.invoke.MethodHandle" }) {
|
||||||
@@ -137,6 +145,10 @@ internal class ExceptionState private constructor(
|
|||||||
cause = cause.cause
|
cause = cause.cause
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (environment.configuration.collapseStackTraceFromJDK && additionalStack.isNotEmpty()) {
|
||||||
|
return listOf("at <JDK>")
|
||||||
|
}
|
||||||
return additionalStack
|
return additionalStack
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-9
@@ -1,11 +1,5 @@
|
|||||||
/exceptionFromInterpreter_ir.kt:7:26: error: An exception occur while evaluating const value initializer:
|
/exceptionFromInterpreter_ir.kt:6:26: error: Cannot evaluate constant expression: / by zero
|
||||||
Exception java.lang.ArithmeticException: / by zero
|
|
||||||
at ExceptionFromInterpreter_irKt.<clinit>(exceptionFromInterpreter_ir.kt:7)
|
|
||||||
|
|
||||||
/exceptionFromInterpreter_ir.kt:8:39: error: An exception occur while evaluating const value initializer:
|
/exceptionFromInterpreter_ir.kt:7:39: error: Cannot evaluate constant expression: marginPrefix must be non-blank string.
|
||||||
Exception java.lang.IllegalArgumentException: marginPrefix must be non-blank string.
|
|
||||||
at ExceptionFromInterpreter_irKt.<clinit>(exceptionFromInterpreter_ir.kt:8)
|
|
||||||
|
|
||||||
/exceptionFromInterpreter_ir.kt:12:4: error: An exception occur while evaluating const value initializer:
|
/exceptionFromInterpreter_ir.kt:11:4: error: Cannot evaluate constant expression: / by zero
|
||||||
Exception java.lang.ArithmeticException: / by zero
|
|
||||||
at ExceptionFromInterpreter_irKt.<clinit>(exceptionFromInterpreter_ir.kt:12)
|
|
||||||
@@ -6,7 +6,6 @@ fun append(sb: StringBuilder, value: CharSequence, start: Int, end: Int): String
|
|||||||
const val a = <!EVALUATED: `Some string with not zero length!!!`!>append(StringBuilder("Some string with not zero length"), "!!!", 0, 3)<!>
|
const val a = <!EVALUATED: `Some string with not zero length!!!`!>append(StringBuilder("Some string with not zero length"), "!!!", 0, 3)<!>
|
||||||
const val b = <!WAS_NOT_EVALUATED: `
|
const val b = <!WAS_NOT_EVALUATED: `
|
||||||
Exception java.lang.IndexOutOfBoundsException: start -1, end 0, s.length() 3
|
Exception java.lang.IndexOutOfBoundsException: start -1, end 0, s.length() 3
|
||||||
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:539)
|
at <JDK>
|
||||||
at java.lang.StringBuilder.append(StringBuilder.java:175)
|
|
||||||
at ExceptionFromWrapperKt.append(exceptionFromWrapper.kt:3)
|
at ExceptionFromWrapperKt.append(exceptionFromWrapper.kt:3)
|
||||||
at ExceptionFromWrapperKt.<clinit>(exceptionFromWrapper.kt:7)`!>append(StringBuilder("Some string with not zero length"), "!!!", -1, 0)<!>
|
at ExceptionFromWrapperKt.<clinit>(exceptionFromWrapper.kt:7)`!>append(StringBuilder("Some string with not zero length"), "!!!", -1, 0)<!>
|
||||||
|
|||||||
Reference in New Issue
Block a user