mirror of
https://github.com/github/codeql.git
synced 2026-05-01 19:55:15 +02:00
Kotlin: Handle binops correctly
This commit is contained in:
@@ -172,7 +172,7 @@ fun doFile(invocationTrapFile: String,
|
||||
}
|
||||
|
||||
fun <T> fakeLabel(): Label<T> {
|
||||
if (true) {
|
||||
if (false) {
|
||||
println("Fake label")
|
||||
} else {
|
||||
val sw = StringWriter()
|
||||
@@ -1414,53 +1414,144 @@ open class KotlinFileExtractor(
|
||||
}
|
||||
|
||||
fun extractCall(c: IrCall, callable: Label<out DbCallable>, parent: Label<out DbExprparent>, idx: Int) {
|
||||
val exprId: Label<out DbExpr> = when (c.origin) {
|
||||
PLUS -> {
|
||||
fun isFunction(pkgName: String, className: String?, fName: String): Boolean {
|
||||
val verbose = false
|
||||
fun verboseln(s: String) { if(verbose) println(s) }
|
||||
verboseln("Attempting match for $pkgName $className $fName")
|
||||
val target = c.symbol.owner
|
||||
if (target.name.asString() != fName) {
|
||||
verboseln("No match as function name is ${target.name.asString()} not $fName")
|
||||
return false
|
||||
}
|
||||
val extensionReceiverParameter = target.extensionReceiverParameter
|
||||
val targetClass = if (extensionReceiverParameter == null) target.parent
|
||||
else (extensionReceiverParameter.type as? IrSimpleType)?.classifier?.owner
|
||||
val targetPkg =
|
||||
if (className != null) {
|
||||
if (targetClass !is IrClass) {
|
||||
verboseln("No match as didn't find target class")
|
||||
return false
|
||||
}
|
||||
if (targetClass.name.asString() != className) {
|
||||
verboseln("No match as class name is ${targetClass.name.asString()} not $className")
|
||||
return false
|
||||
}
|
||||
targetClass.parent
|
||||
} else {
|
||||
targetClass
|
||||
}
|
||||
if (targetPkg !is IrPackageFragment) {
|
||||
verboseln("No match as didn't find target package")
|
||||
return false
|
||||
}
|
||||
if (targetPkg.fqName.asString() != pkgName) {
|
||||
verboseln("No match as class name is ${targetPkg.fqName.asString()} not $pkgName")
|
||||
return false
|
||||
}
|
||||
verboseln("Match")
|
||||
return true
|
||||
}
|
||||
|
||||
fun binopDisp(id: Label<out DbExpr>) {
|
||||
val locId = tw.getLocation(c)
|
||||
tw.writeHasLocation(id, locId)
|
||||
tw.writeCallableEnclosingExpr(id, callable)
|
||||
|
||||
val dr = c.dispatchReceiver
|
||||
if(dr == null) {
|
||||
logger.warnElement(Severity.ErrorSevere, "Dispatch receiver not found", c)
|
||||
} else {
|
||||
extractExpressionExpr(dr, callable, id, 0)
|
||||
}
|
||||
if(c.valueArgumentsCount < 1) {
|
||||
logger.warnElement(Severity.ErrorSevere, "No RHS found", c)
|
||||
} else {
|
||||
if(c.valueArgumentsCount > 1) {
|
||||
logger.warnElement(Severity.ErrorSevere, "Extra arguments found", c)
|
||||
}
|
||||
val arg = c.getValueArgument(0)
|
||||
if(arg == null) {
|
||||
logger.warnElement(Severity.ErrorSevere, "RHS null", c)
|
||||
} else {
|
||||
extractExpressionExpr(arg, callable, id, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun binop(id: Label<out DbExpr>) {
|
||||
val locId = tw.getLocation(c)
|
||||
tw.writeHasLocation(id, locId)
|
||||
tw.writeCallableEnclosingExpr(id, callable)
|
||||
|
||||
val dr = c.dispatchReceiver
|
||||
if(dr != null) {
|
||||
logger.warnElement(Severity.ErrorSevere, "Unexpected dispatch receiver found", c)
|
||||
}
|
||||
if(c.valueArgumentsCount < 1) {
|
||||
logger.warnElement(Severity.ErrorSevere, "No arguments found", c)
|
||||
} else {
|
||||
val lhs = c.getValueArgument(0)
|
||||
if(lhs == null) {
|
||||
logger.warnElement(Severity.ErrorSevere, "LHS null", c)
|
||||
} else {
|
||||
extractExpressionExpr(lhs, callable, id, 0)
|
||||
}
|
||||
if(c.valueArgumentsCount < 2) {
|
||||
logger.warnElement(Severity.ErrorSevere, "No RHS found", c)
|
||||
} else {
|
||||
val rhs = c.getValueArgument(1)
|
||||
if(rhs == null) {
|
||||
logger.warnElement(Severity.ErrorSevere, "RHS null", c)
|
||||
} else {
|
||||
extractExpressionExpr(rhs, callable, id, 1)
|
||||
}
|
||||
}
|
||||
if(c.valueArgumentsCount > 2) {
|
||||
logger.warnElement(Severity.ErrorSevere, "Extra arguments found", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when {
|
||||
c.origin == PLUS &&
|
||||
(isFunction("kotlin", "Int", "plus") || isFunction("kotlin", "String", "plus")) -> {
|
||||
val id = tw.getFreshIdLabel<DbAddexpr>()
|
||||
val type = useType(c.type)
|
||||
val locId = tw.getLocation(c)
|
||||
tw.writeExprs_addexpr(id, type.javaResult.id, type.kotlinResult.id, parent, idx)
|
||||
tw.writeHasLocation(id, locId)
|
||||
tw.writeCallableEnclosingExpr(id, callable)
|
||||
binopDisp(id)
|
||||
id
|
||||
}
|
||||
MINUS -> {
|
||||
c.origin == MINUS && isFunction("kotlin", "Int", "minus") -> {
|
||||
val id = tw.getFreshIdLabel<DbSubexpr>()
|
||||
val type = useType(c.type)
|
||||
val locId = tw.getLocation(c)
|
||||
tw.writeExprs_subexpr(id, type.javaResult.id, type.kotlinResult.id, parent, idx)
|
||||
tw.writeHasLocation(id, locId)
|
||||
tw.writeCallableEnclosingExpr(id, callable)
|
||||
binopDisp(id)
|
||||
id
|
||||
}
|
||||
DIV -> {
|
||||
c.origin == DIV && isFunction("kotlin", "Int", "div") -> {
|
||||
val id = tw.getFreshIdLabel<DbDivexpr>()
|
||||
val type = useType(c.type)
|
||||
val locId = tw.getLocation(c)
|
||||
tw.writeExprs_divexpr(id, type.javaResult.id, type.kotlinResult.id, parent, idx)
|
||||
tw.writeHasLocation(id, locId)
|
||||
tw.writeCallableEnclosingExpr(id, callable)
|
||||
binopDisp(id)
|
||||
id
|
||||
}
|
||||
PERC -> {
|
||||
c.origin == PERC && isFunction("kotlin", "Int", "rem") -> {
|
||||
val id = tw.getFreshIdLabel<DbRemexpr>()
|
||||
val type = useType(c.type)
|
||||
val locId = tw.getLocation(c)
|
||||
tw.writeExprs_remexpr(id, type.javaResult.id, type.kotlinResult.id, parent, idx)
|
||||
tw.writeHasLocation(id, locId)
|
||||
tw.writeCallableEnclosingExpr(id, callable)
|
||||
binopDisp(id)
|
||||
id
|
||||
}
|
||||
EQEQ -> {
|
||||
c.origin == EQEQ && isFunction("kotlin.internal.ir", null, "EQEQ") -> {
|
||||
val id = tw.getFreshIdLabel<DbEqexpr>()
|
||||
val type = useType(c.type)
|
||||
val locId = tw.getLocation(c)
|
||||
tw.writeExprs_eqexpr(id, type.javaResult.id, type.kotlinResult.id, parent, idx)
|
||||
tw.writeHasLocation(id, locId)
|
||||
tw.writeCallableEnclosingExpr(id, callable)
|
||||
binop(id)
|
||||
id
|
||||
}
|
||||
EXCLEQ -> {
|
||||
/*
|
||||
TODO
|
||||
c.origin == EXCLEQ -> {
|
||||
val id = tw.getFreshIdLabel<DbNeexpr>()
|
||||
val type = useType(c.type)
|
||||
val locId = tw.getLocation(c)
|
||||
@@ -1469,40 +1560,33 @@ open class KotlinFileExtractor(
|
||||
tw.writeCallableEnclosingExpr(id, callable)
|
||||
id
|
||||
}
|
||||
LT -> {
|
||||
*/
|
||||
c.origin == LT && isFunction("kotlin.internal.ir", null, "less") -> {
|
||||
val id = tw.getFreshIdLabel<DbLtexpr>()
|
||||
val type = useType(c.type)
|
||||
val locId = tw.getLocation(c)
|
||||
tw.writeExprs_ltexpr(id, type.javaResult.id, type.kotlinResult.id, parent, idx)
|
||||
tw.writeHasLocation(id, locId)
|
||||
tw.writeCallableEnclosingExpr(id, callable)
|
||||
binop(id)
|
||||
id
|
||||
}
|
||||
LTEQ -> {
|
||||
c.origin == LTEQ && isFunction("kotlin.internal.ir", null, "lessOrEqual") -> {
|
||||
val id = tw.getFreshIdLabel<DbLeexpr>()
|
||||
val type = useType(c.type)
|
||||
val locId = tw.getLocation(c)
|
||||
tw.writeExprs_leexpr(id, type.javaResult.id, type.kotlinResult.id, parent, idx)
|
||||
tw.writeHasLocation(id, locId)
|
||||
tw.writeCallableEnclosingExpr(id, callable)
|
||||
binop(id)
|
||||
id
|
||||
}
|
||||
GT -> {
|
||||
c.origin == GT && isFunction("kotlin.internal.ir", null, "greater") -> {
|
||||
val id = tw.getFreshIdLabel<DbGtexpr>()
|
||||
val type = useType(c.type)
|
||||
val locId = tw.getLocation(c)
|
||||
tw.writeExprs_gtexpr(id, type.javaResult.id, type.kotlinResult.id, parent, idx)
|
||||
tw.writeHasLocation(id, locId)
|
||||
tw.writeCallableEnclosingExpr(id, callable)
|
||||
binop(id)
|
||||
id
|
||||
}
|
||||
GTEQ -> {
|
||||
c.origin == GTEQ && isFunction("kotlin.internal.ir", null, "greaterOrEqual") -> {
|
||||
val id = tw.getFreshIdLabel<DbGeexpr>()
|
||||
val type = useType(c.type)
|
||||
val locId = tw.getLocation(c)
|
||||
tw.writeExprs_geexpr(id, type.javaResult.id, type.kotlinResult.id, parent, idx)
|
||||
tw.writeHasLocation(id, locId)
|
||||
tw.writeCallableEnclosingExpr(id, callable)
|
||||
binop(id)
|
||||
id
|
||||
}
|
||||
else -> {
|
||||
@@ -1518,16 +1602,17 @@ open class KotlinFileExtractor(
|
||||
// type arguments at index -2, -3, ...
|
||||
extractTypeArguments(c, id, callable, -2, true)
|
||||
id
|
||||
}
|
||||
}
|
||||
val dr = c.dispatchReceiver
|
||||
if(dr != null) {
|
||||
extractExpressionExpr(dr, callable, exprId, -1)
|
||||
}
|
||||
for(i in 0 until c.valueArgumentsCount) {
|
||||
val arg = c.getValueArgument(i)
|
||||
if(arg != null) {
|
||||
extractExpressionExpr(arg, callable, exprId, i)
|
||||
|
||||
val dr = c.dispatchReceiver
|
||||
if(dr != null) {
|
||||
extractExpressionExpr(dr, callable, id, -1)
|
||||
}
|
||||
for(i in 0 until c.valueArgumentsCount) {
|
||||
val arg = c.getValueArgument(i)
|
||||
if(arg != null) {
|
||||
extractExpressionExpr(arg, callable, id, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
java/ql/test/kotlin/library-tests/exprs/binop.expected
Normal file
12
java/ql/test/kotlin/library-tests/exprs/binop.expected
Normal file
@@ -0,0 +1,12 @@
|
||||
| exprs.kt:6:14:6:18 | ... + ... | exprs.kt:6:14:6:14 | x | exprs.kt:6:18:6:18 | y |
|
||||
| exprs.kt:7:14:7:18 | ... - ... | exprs.kt:7:14:7:14 | x | exprs.kt:7:18:7:18 | y |
|
||||
| exprs.kt:8:14:8:18 | ... / ... | exprs.kt:8:14:8:14 | x | exprs.kt:8:18:8:18 | y |
|
||||
| exprs.kt:9:14:9:18 | ... % ... | exprs.kt:9:14:9:14 | x | exprs.kt:9:18:9:18 | y |
|
||||
| exprs.kt:20:15:20:20 | ... == ... | exprs.kt:20:15:20:15 | x | exprs.kt:20:20:20:20 | y |
|
||||
| exprs.kt:22:15:22:19 | ... < ... | exprs.kt:22:15:22:15 | x | exprs.kt:22:19:22:19 | y |
|
||||
| exprs.kt:23:15:23:20 | ... <= ... | exprs.kt:23:15:23:15 | x | exprs.kt:23:20:23:20 | y |
|
||||
| exprs.kt:24:15:24:19 | ... > ... | exprs.kt:24:15:24:15 | x | exprs.kt:24:19:24:19 | y |
|
||||
| exprs.kt:25:15:25:20 | ... >= ... | exprs.kt:25:15:25:15 | x | exprs.kt:25:20:25:20 | y |
|
||||
| exprs.kt:50:16:50:26 | ... + ... | exprs.kt:50:16:50:19 | str1 | exprs.kt:50:23:50:26 | str2 |
|
||||
| exprs.kt:53:12:53:23 | ... > ... | exprs.kt:53:12:53:19 | variable | exprs.kt:53:23:53:23 | 0 |
|
||||
| exprs.kt:57:12:57:20 | ... + ... | exprs.kt:57:12:57:14 | 123 | exprs.kt:57:18:57:20 | 456 |
|
||||
40
java/ql/test/kotlin/library-tests/exprs/binop.ql
Normal file
40
java/ql/test/kotlin/library-tests/exprs/binop.ql
Normal file
@@ -0,0 +1,40 @@
|
||||
import java
|
||||
|
||||
newtype TMaybeElement =
|
||||
TElement(Element e) or
|
||||
TNoElement()
|
||||
|
||||
class MaybeElement extends TMaybeElement {
|
||||
abstract string toString();
|
||||
abstract Location getLocation();
|
||||
}
|
||||
|
||||
class YesMaybeElement extends MaybeElement {
|
||||
Element e;
|
||||
|
||||
YesMaybeElement() { this = TElement(e) }
|
||||
override string toString() { result = e.toString() }
|
||||
override Location getLocation() { result = e.getLocation() }
|
||||
}
|
||||
|
||||
class NoMaybeElement extends MaybeElement {
|
||||
NoMaybeElement() { this = TNoElement() }
|
||||
|
||||
override string toString() { result = "<none>" }
|
||||
override Location getLocation() { none() }
|
||||
}
|
||||
|
||||
MaybeElement lhs(BinaryExpr e) {
|
||||
if exists(e.getLeftOperand())
|
||||
then result = TElement(e.getLeftOperand())
|
||||
else result = TNoElement()
|
||||
}
|
||||
|
||||
MaybeElement rhs(BinaryExpr e) {
|
||||
if exists(e.getRightOperand())
|
||||
then result = TElement(e.getRightOperand())
|
||||
else result = TNoElement()
|
||||
}
|
||||
|
||||
from Expr e
|
||||
select e, lhs(e), rhs(e)
|
||||
@@ -20,11 +20,6 @@
|
||||
| exprs.kt:20:15:20:15 | x | exprs.kt:4:1:58:1 | topLevelMethod | VarAccess |
|
||||
| exprs.kt:20:15:20:20 | ... == ... | exprs.kt:4:1:58:1 | topLevelMethod | EQExpr |
|
||||
| exprs.kt:20:20:20:20 | y | exprs.kt:4:1:58:1 | topLevelMethod | VarAccess |
|
||||
| exprs.kt:21:5:21:20 | i14 | exprs.kt:4:1:58:1 | topLevelMethod | LocalVariableDeclExpr |
|
||||
| exprs.kt:21:15:21:15 | x | exprs.kt:4:1:58:1 | topLevelMethod | VarAccess |
|
||||
| exprs.kt:21:15:21:20 | ... != ... | exprs.kt:4:1:58:1 | topLevelMethod | NEExpr |
|
||||
| exprs.kt:21:15:21:20 | ... != ... | exprs.kt:4:1:58:1 | topLevelMethod | NEExpr |
|
||||
| exprs.kt:21:20:21:20 | y | exprs.kt:4:1:58:1 | topLevelMethod | VarAccess |
|
||||
| exprs.kt:22:5:22:19 | i15 | exprs.kt:4:1:58:1 | topLevelMethod | LocalVariableDeclExpr |
|
||||
| exprs.kt:22:15:22:15 | x | exprs.kt:4:1:58:1 | topLevelMethod | VarAccess |
|
||||
| exprs.kt:22:15:22:19 | ... < ... | exprs.kt:4:1:58:1 | topLevelMethod | LTExpr |
|
||||
@@ -76,16 +71,6 @@
|
||||
| exprs.kt:48:34:48:38 | bar | exprs.kt:4:1:58:1 | topLevelMethod | StringLiteral |
|
||||
| exprs.kt:48:40:48:43 | str2 | exprs.kt:4:1:58:1 | topLevelMethod | VarAccess |
|
||||
| exprs.kt:48:44:48:47 | baz | exprs.kt:4:1:58:1 | topLevelMethod | StringLiteral |
|
||||
| exprs.kt:49:5:49:66 | str5 | exprs.kt:4:1:58:1 | topLevelMethod | LocalVariableDeclExpr |
|
||||
| exprs.kt:49:24:49:66 | "..." | exprs.kt:4:1:58:1 | topLevelMethod | StringTemplateExpr |
|
||||
| exprs.kt:49:25:49:28 | foo | exprs.kt:4:1:58:1 | topLevelMethod | StringLiteral |
|
||||
| exprs.kt:49:31:49:34 | str1 | exprs.kt:4:1:58:1 | topLevelMethod | VarAccess |
|
||||
| exprs.kt:49:31:49:41 | ... + ... | exprs.kt:4:1:58:1 | topLevelMethod | AddExpr |
|
||||
| exprs.kt:49:38:49:41 | str2 | exprs.kt:4:1:58:1 | topLevelMethod | VarAccess |
|
||||
| exprs.kt:49:43:49:47 | bar | exprs.kt:4:1:58:1 | topLevelMethod | StringLiteral |
|
||||
| exprs.kt:49:50:49:60 | ... + ... | exprs.kt:4:1:58:1 | topLevelMethod | AddExpr |
|
||||
| exprs.kt:49:57:49:60 | str1 | exprs.kt:4:1:58:1 | topLevelMethod | VarAccess |
|
||||
| exprs.kt:49:62:49:65 | baz | exprs.kt:4:1:58:1 | topLevelMethod | StringLiteral |
|
||||
| exprs.kt:50:5:50:26 | str6 | exprs.kt:4:1:58:1 | topLevelMethod | LocalVariableDeclExpr |
|
||||
| exprs.kt:50:16:50:19 | str1 | exprs.kt:4:1:58:1 | topLevelMethod | VarAccess |
|
||||
| exprs.kt:50:16:50:26 | ... + ... | exprs.kt:4:1:58:1 | topLevelMethod | AddExpr |
|
||||
@@ -152,19 +137,6 @@
|
||||
| exprs.kt:82:5:82:25 | r | exprs.kt:81:1:88:1 | foo | LocalVariableDeclExpr |
|
||||
| exprs.kt:82:13:82:13 | p | exprs.kt:81:1:88:1 | foo | VarAccess |
|
||||
| exprs.kt:82:15:82:25 | getBounds(...) | exprs.kt:81:1:88:1 | foo | MethodAccess |
|
||||
| exprs.kt:83:5:87:5 | when ... | exprs.kt:81:1:88:1 | foo | WhenExpr |
|
||||
| exprs.kt:83:8:83:8 | r | exprs.kt:81:1:88:1 | foo | VarAccess |
|
||||
| exprs.kt:83:8:83:16 | ... != ... | exprs.kt:81:1:88:1 | foo | NEExpr |
|
||||
| exprs.kt:83:8:83:16 | ... != ... | exprs.kt:81:1:88:1 | foo | NEExpr |
|
||||
| exprs.kt:83:13:83:16 | null | exprs.kt:81:1:88:1 | foo | NullLiteral |
|
||||
| exprs.kt:84:9:84:29 | r2 | exprs.kt:81:1:88:1 | foo | LocalVariableDeclExpr |
|
||||
| exprs.kt:84:29:84:29 | (...)... | exprs.kt:81:1:88:1 | foo | CastExpr |
|
||||
| exprs.kt:84:29:84:29 | Rectangle | exprs.kt:81:1:88:1 | foo | TypeAccess |
|
||||
| exprs.kt:84:29:84:29 | r | exprs.kt:81:1:88:1 | foo | VarAccess |
|
||||
| exprs.kt:85:9:85:30 | height | exprs.kt:81:1:88:1 | foo | LocalVariableDeclExpr |
|
||||
| exprs.kt:85:25:85:30 | height | exprs.kt:81:1:88:1 | foo | VarAccess |
|
||||
| exprs.kt:86:9:86:17 | ...=... | exprs.kt:81:1:88:1 | foo | AssignExpr |
|
||||
| exprs.kt:86:21:86:21 | 3 | exprs.kt:81:1:88:1 | foo | IntegerLiteral |
|
||||
| exprs.kt:90:1:92:1 | <obinit>(...) | exprs.kt:90:6:92:1 | Direction | MethodAccess |
|
||||
| exprs.kt:90:1:92:1 | new Enum(...) | exprs.kt:90:6:92:1 | Direction | ClassInstanceExpr |
|
||||
| exprs.kt:94:1:98:1 | <obinit>(...) | exprs.kt:94:6:98:1 | Color | MethodAccess |
|
||||
@@ -179,7 +151,6 @@
|
||||
| exprs.kt:102:23:102:27 | GREEN | exprs.kt:100:1:103:1 | enums | VarAccess |
|
||||
| file://:0:0:0:0 | Color | exprs.kt:94:6:98:1 | Color | TypeAccess |
|
||||
| file://:0:0:0:0 | Direction | exprs.kt:90:6:92:1 | Direction | TypeAccess |
|
||||
| file://:0:0:0:0 | height | exprs.kt:81:1:88:1 | foo | VarAccess |
|
||||
| file://:0:0:0:0 | q | exprs.kt:72:1:79:1 | typeTests | VarAccess |
|
||||
| file://:0:0:0:0 | q | exprs.kt:72:1:79:1 | typeTests | VarAccess |
|
||||
| file://:0:0:0:0 | tmp0 | exprs.kt:4:1:58:1 | topLevelMethod | LocalVariableDeclExpr |
|
||||
|
||||
@@ -18,7 +18,7 @@ TODO
|
||||
val i12 = x.inv()
|
||||
*/
|
||||
val i13 = x == y
|
||||
val i14 = x != y
|
||||
// TODO val i14 = x != y
|
||||
val i15 = x < y
|
||||
val i16 = x <= y
|
||||
val i17 = x > y
|
||||
@@ -46,7 +46,7 @@ TODO
|
||||
val str2: String? = "string lit"
|
||||
val str3: String? = null
|
||||
val str4: String = "foo $str1 bar $str2 baz"
|
||||
val str5: String = "foo ${str1 + str2} bar ${str2 + str1} baz"
|
||||
// TODO val str5: String = "foo ${str1 + str2} bar ${str2 + str1} baz"
|
||||
val str6 = str1 + str2
|
||||
|
||||
var variable = 10
|
||||
@@ -80,11 +80,11 @@ fun typeTests(x: Root, y: Subclass1) {
|
||||
|
||||
fun foo(p: Polygon) {
|
||||
val r = p.getBounds()
|
||||
if(r != null) {
|
||||
val r2: Rectangle = r
|
||||
val height = r2.height
|
||||
r2.height = 3
|
||||
}
|
||||
// TODO if(r != null) {
|
||||
// TODO val r2: Rectangle = r
|
||||
// TODO val height = r2.height
|
||||
// TODO r2.height = 3
|
||||
// TODO }
|
||||
}
|
||||
|
||||
enum class Direction {
|
||||
|
||||
Reference in New Issue
Block a user