mirror of
https://github.com/github/codeql.git
synced 2026-03-30 20:28:15 +02:00
Merge pull request #21344 from github/tausbn/python-remove-points-to-from-metrics-libraries
Python: Remove points-to from metrics library
This commit is contained in:
@@ -430,3 +430,179 @@ private predicate exits_early(BasicBlock b) {
|
||||
f.getACall().getBasicBlock() = b
|
||||
)
|
||||
}
|
||||
|
||||
/** The metrics for a function that require points-to analysis */
|
||||
class FunctionMetricsWithPointsTo extends FunctionMetrics {
|
||||
/**
|
||||
* Gets the cyclomatic complexity of the function:
|
||||
* The number of linearly independent paths through the source code.
|
||||
* Computed as E - N + 2P,
|
||||
* where
|
||||
* E = the number of edges of the graph.
|
||||
* N = the number of nodes of the graph.
|
||||
* P = the number of connected components, which for a single function is 1.
|
||||
*/
|
||||
int getCyclomaticComplexity() {
|
||||
exists(int e, int n |
|
||||
n = count(BasicBlockWithPointsTo b | b = this.getABasicBlock() and b.likelyReachable()) and
|
||||
e =
|
||||
count(BasicBlockWithPointsTo b1, BasicBlockWithPointsTo b2 |
|
||||
b1 = this.getABasicBlock() and
|
||||
b1.likelyReachable() and
|
||||
b2 = this.getABasicBlock() and
|
||||
b2.likelyReachable() and
|
||||
b2 = b1.getASuccessor() and
|
||||
not b1.unlikelySuccessor(b2)
|
||||
)
|
||||
|
|
||||
result = e - n + 2
|
||||
)
|
||||
}
|
||||
|
||||
private BasicBlock getABasicBlock() {
|
||||
result = this.getEntryNode().getBasicBlock()
|
||||
or
|
||||
exists(BasicBlock mid | mid = this.getABasicBlock() and result = mid.getASuccessor())
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency of Callables
|
||||
* One callable "this" depends on another callable "result"
|
||||
* if "this" makes some call to a method that may end up being "result".
|
||||
*/
|
||||
FunctionMetricsWithPointsTo getADependency() {
|
||||
result != this and
|
||||
not non_coupling_method(result) and
|
||||
exists(Call call | call.getScope() = this |
|
||||
exists(FunctionObject callee | callee.getFunction() = result |
|
||||
call.getAFlowNode().getFunction().(ControlFlowNodeWithPointsTo).refersTo(callee)
|
||||
)
|
||||
or
|
||||
exists(Attribute a | call.getFunc() = a |
|
||||
unique_root_method(result, a.getName())
|
||||
or
|
||||
exists(Name n | a.getObject() = n and n.getId() = "self" |
|
||||
result.getScope() = this.getScope() and
|
||||
result.getName() = a.getName()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Afferent Coupling
|
||||
* the number of callables that depend on this method.
|
||||
* This is sometimes called the "fan-in" of a method.
|
||||
*/
|
||||
int getAfferentCoupling() {
|
||||
result = count(FunctionMetricsWithPointsTo m | m.getADependency() = this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Efferent Coupling
|
||||
* the number of methods that this method depends on
|
||||
* This is sometimes called the "fan-out" of a method.
|
||||
*/
|
||||
int getEfferentCoupling() {
|
||||
result = count(FunctionMetricsWithPointsTo m | this.getADependency() = m)
|
||||
}
|
||||
|
||||
override string getAQlClass() { result = "FunctionMetrics" }
|
||||
}
|
||||
|
||||
/** The metrics for a class that require points-to analysis */
|
||||
class ClassMetricsWithPointsTo extends ClassMetrics {
|
||||
private predicate dependsOn(Class other) {
|
||||
other != this and
|
||||
(
|
||||
exists(FunctionMetricsWithPointsTo f1, FunctionMetricsWithPointsTo f2 |
|
||||
f1.getADependency() = f2
|
||||
|
|
||||
f1.getScope() = this and f2.getScope() = other
|
||||
)
|
||||
or
|
||||
exists(Function f, Call c, ClassObject cls | c.getScope() = f and f.getScope() = this |
|
||||
c.getFunc().(ExprWithPointsTo).refersTo(cls) and
|
||||
cls.getPyClass() = other
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the afferent coupling of a class -- the number of classes that
|
||||
* directly depend on it.
|
||||
*/
|
||||
int getAfferentCoupling() { result = count(ClassMetricsWithPointsTo t | t.dependsOn(this)) }
|
||||
|
||||
/**
|
||||
* Gets the efferent coupling of a class -- the number of classes that
|
||||
* it directly depends on.
|
||||
*/
|
||||
int getEfferentCoupling() { result = count(ClassMetricsWithPointsTo t | this.dependsOn(t)) }
|
||||
|
||||
/** Gets the depth of inheritance of the class. */
|
||||
int getInheritanceDepth() {
|
||||
exists(ClassObject cls | cls.getPyClass() = this | result = max(classInheritanceDepth(cls)))
|
||||
}
|
||||
|
||||
override string getAQlClass() { result = "ClassMetrics" }
|
||||
}
|
||||
|
||||
private int classInheritanceDepth(ClassObject cls) {
|
||||
/* Prevent run-away recursion in case of circular inheritance */
|
||||
not cls.getASuperType() = cls and
|
||||
(
|
||||
exists(ClassObject sup | cls.getABaseType() = sup | result = classInheritanceDepth(sup) + 1)
|
||||
or
|
||||
not exists(cls.getABaseType()) and
|
||||
(
|
||||
major_version() = 2 and result = 0
|
||||
or
|
||||
major_version() > 2 and result = 1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/** The metrics for a module that require points-to analysis */
|
||||
class ModuleMetricsWithPointsTo extends ModuleMetrics {
|
||||
/**
|
||||
* Gets the afferent coupling of a module -- the number of modules that
|
||||
* directly depend on it.
|
||||
*/
|
||||
int getAfferentCoupling() { result = count(ModuleMetricsWithPointsTo t | t.dependsOn(this)) }
|
||||
|
||||
/**
|
||||
* Gets the efferent coupling of a module -- the number of modules that
|
||||
* it directly depends on.
|
||||
*/
|
||||
int getEfferentCoupling() { result = count(ModuleMetricsWithPointsTo t | this.dependsOn(t)) }
|
||||
|
||||
private predicate dependsOn(Module other) {
|
||||
other != this and
|
||||
(
|
||||
exists(FunctionMetricsWithPointsTo f1, FunctionMetricsWithPointsTo f2 |
|
||||
f1.getADependency() = f2
|
||||
|
|
||||
f1.getEnclosingModule() = this and f2.getEnclosingModule() = other
|
||||
)
|
||||
or
|
||||
exists(Function f, Call c, ClassObject cls | c.getScope() = f and f.getScope() = this |
|
||||
c.getFunc().(ExprWithPointsTo).refersTo(cls) and
|
||||
cls.getPyClass().getEnclosingModule() = other
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override string getAQlClass() { result = "ModuleMetrics" }
|
||||
}
|
||||
|
||||
/** Helpers for coupling */
|
||||
predicate unique_root_method(Function func, string name) {
|
||||
name = func.getName() and
|
||||
not exists(FunctionObject f, FunctionObject other |
|
||||
f.getFunction() = func and
|
||||
other.getName() = name
|
||||
|
|
||||
not other.overrides(f)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
category: breaking
|
||||
---
|
||||
|
||||
- The `Metrics` library no longer contains code that depends on the points-to analysis. The removed functionality has instead been moved to the `LegacyPointsTo` module, to classes like `ModuleMetricsWithPointsTo` etc. If you depend on any of these classes, you must now remember to import `LegacyPointsTo`, and use the appropriate types in order to use the points-to-based functionality.
|
||||
@@ -14,7 +14,7 @@ import semmle.python.Patterns
|
||||
import semmle.python.Keywords
|
||||
import semmle.python.Comprehensions
|
||||
import semmle.python.Flow
|
||||
private import semmle.python.Metrics
|
||||
import semmle.python.Metrics
|
||||
import semmle.python.Constants
|
||||
import semmle.python.Scope
|
||||
import semmle.python.Comment
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
private import semmle.python.SelfAttribute
|
||||
|
||||
/** The metrics for a function */
|
||||
class FunctionMetrics extends Function {
|
||||
@@ -18,76 +18,6 @@ class FunctionMetrics extends Function {
|
||||
/** Gets the number of lines of docstring in the function */
|
||||
int getNumberOfLinesOfDocStrings() { py_docstringlines(this, result) }
|
||||
|
||||
/**
|
||||
* Gets the cyclomatic complexity of the function:
|
||||
* The number of linearly independent paths through the source code.
|
||||
* Computed as E - N + 2P,
|
||||
* where
|
||||
* E = the number of edges of the graph.
|
||||
* N = the number of nodes of the graph.
|
||||
* P = the number of connected components, which for a single function is 1.
|
||||
*/
|
||||
int getCyclomaticComplexity() {
|
||||
exists(int e, int n |
|
||||
n = count(BasicBlockWithPointsTo b | b = this.getABasicBlock() and b.likelyReachable()) and
|
||||
e =
|
||||
count(BasicBlockWithPointsTo b1, BasicBlockWithPointsTo b2 |
|
||||
b1 = this.getABasicBlock() and
|
||||
b1.likelyReachable() and
|
||||
b2 = this.getABasicBlock() and
|
||||
b2.likelyReachable() and
|
||||
b2 = b1.getASuccessor() and
|
||||
not b1.unlikelySuccessor(b2)
|
||||
)
|
||||
|
|
||||
result = e - n + 2
|
||||
)
|
||||
}
|
||||
|
||||
private BasicBlock getABasicBlock() {
|
||||
result = this.getEntryNode().getBasicBlock()
|
||||
or
|
||||
exists(BasicBlock mid | mid = this.getABasicBlock() and result = mid.getASuccessor())
|
||||
}
|
||||
|
||||
/**
|
||||
* Dependency of Callables
|
||||
* One callable "this" depends on another callable "result"
|
||||
* if "this" makes some call to a method that may end up being "result".
|
||||
*/
|
||||
FunctionMetrics getADependency() {
|
||||
result != this and
|
||||
not non_coupling_method(result) and
|
||||
exists(Call call | call.getScope() = this |
|
||||
exists(FunctionObject callee | callee.getFunction() = result |
|
||||
call.getAFlowNode().getFunction().(ControlFlowNodeWithPointsTo).refersTo(callee)
|
||||
)
|
||||
or
|
||||
exists(Attribute a | call.getFunc() = a |
|
||||
unique_root_method(result, a.getName())
|
||||
or
|
||||
exists(Name n | a.getObject() = n and n.getId() = "self" |
|
||||
result.getScope() = this.getScope() and
|
||||
result.getName() = a.getName()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Afferent Coupling
|
||||
* the number of callables that depend on this method.
|
||||
* This is sometimes called the "fan-in" of a method.
|
||||
*/
|
||||
int getAfferentCoupling() { result = count(FunctionMetrics m | m.getADependency() = this) }
|
||||
|
||||
/**
|
||||
* Efferent Coupling
|
||||
* the number of methods that this method depends on
|
||||
* This is sometimes called the "fan-out" of a method.
|
||||
*/
|
||||
int getEfferentCoupling() { result = count(FunctionMetrics m | this.getADependency() = m) }
|
||||
|
||||
int getNumberOfParametersWithoutDefault() {
|
||||
result =
|
||||
this.getPositionalParameterCount() -
|
||||
@@ -116,36 +46,6 @@ class ClassMetrics extends Class {
|
||||
/** Gets the number of lines of docstrings in the class */
|
||||
int getNumberOfLinesOfDocStrings() { py_docstringlines(this, result) }
|
||||
|
||||
private predicate dependsOn(Class other) {
|
||||
other != this and
|
||||
(
|
||||
exists(FunctionMetrics f1, FunctionMetrics f2 | f1.getADependency() = f2 |
|
||||
f1.getScope() = this and f2.getScope() = other
|
||||
)
|
||||
or
|
||||
exists(Function f, Call c, ClassObject cls | c.getScope() = f and f.getScope() = this |
|
||||
c.getFunc().(ExprWithPointsTo).refersTo(cls) and
|
||||
cls.getPyClass() = other
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the afferent coupling of a class -- the number of classes that
|
||||
* directly depend on it.
|
||||
*/
|
||||
int getAfferentCoupling() { result = count(ClassMetrics t | t.dependsOn(this)) }
|
||||
|
||||
/**
|
||||
* Gets the efferent coupling of a class -- the number of classes that
|
||||
* it directly depends on.
|
||||
*/
|
||||
int getEfferentCoupling() { result = count(ClassMetrics t | this.dependsOn(t)) }
|
||||
|
||||
int getInheritanceDepth() {
|
||||
exists(ClassObject cls | cls.getPyClass() = this | result = max(classInheritanceDepth(cls)))
|
||||
}
|
||||
|
||||
/* -------- CHIDAMBER AND KEMERER LACK OF COHESION IN METHODS ------------ */
|
||||
/*
|
||||
* The aim of this metric is to try and determine whether a class
|
||||
@@ -245,21 +145,6 @@ class ClassMetrics extends Class {
|
||||
int getLackOfCohesionHM() { result = count(int line | this.unionSubgraph(_, line)) }
|
||||
}
|
||||
|
||||
private int classInheritanceDepth(ClassObject cls) {
|
||||
/* Prevent run-away recursion in case of circular inheritance */
|
||||
not cls.getASuperType() = cls and
|
||||
(
|
||||
exists(ClassObject sup | cls.getABaseType() = sup | result = classInheritanceDepth(sup) + 1)
|
||||
or
|
||||
not exists(cls.getABaseType()) and
|
||||
(
|
||||
major_version() = 2 and result = 0
|
||||
or
|
||||
major_version() > 2 and result = 1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
class ModuleMetrics extends Module {
|
||||
/** Gets the total number of lines (including blank lines) in the module */
|
||||
int getNumberOfLines() { py_alllines(this, result) }
|
||||
@@ -272,43 +157,6 @@ class ModuleMetrics extends Module {
|
||||
|
||||
/** Gets the number of lines of docstrings in the module */
|
||||
int getNumberOfLinesOfDocStrings() { py_docstringlines(this, result) }
|
||||
|
||||
/**
|
||||
* Gets the afferent coupling of a class -- the number of classes that
|
||||
* directly depend on it.
|
||||
*/
|
||||
int getAfferentCoupling() { result = count(ModuleMetrics t | t.dependsOn(this)) }
|
||||
|
||||
/**
|
||||
* Gets the efferent coupling of a class -- the number of classes that
|
||||
* it directly depends on.
|
||||
*/
|
||||
int getEfferentCoupling() { result = count(ModuleMetrics t | this.dependsOn(t)) }
|
||||
|
||||
private predicate dependsOn(Module other) {
|
||||
other != this and
|
||||
(
|
||||
exists(FunctionMetrics f1, FunctionMetrics f2 | f1.getADependency() = f2 |
|
||||
f1.getEnclosingModule() = this and f2.getEnclosingModule() = other
|
||||
)
|
||||
or
|
||||
exists(Function f, Call c, ClassObject cls | c.getScope() = f and f.getScope() = this |
|
||||
c.getFunc().(ExprWithPointsTo).refersTo(cls) and
|
||||
cls.getPyClass().getEnclosingModule() = other
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Helpers for coupling */
|
||||
predicate unique_root_method(Function func, string name) {
|
||||
name = func.getName() and
|
||||
not exists(FunctionObject f, FunctionObject other |
|
||||
f.getFunction() = func and
|
||||
other.getName() = name
|
||||
|
|
||||
not other.overrides(f)
|
||||
)
|
||||
}
|
||||
|
||||
predicate non_coupling_method(Function f) {
|
||||
|
||||
@@ -18,6 +18,6 @@ from FunctionValue method
|
||||
where
|
||||
exists(ClassValue c |
|
||||
c.declaredAttribute("__del__") = method and
|
||||
method.getScope().(FunctionMetrics).getCyclomaticComplexity() > 3
|
||||
method.getScope().(FunctionMetricsWithPointsTo).getCyclomaticComplexity() > 3
|
||||
)
|
||||
select method, "Overly complex '__del__' method."
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from FunctionMetrics f
|
||||
select f, f.getNumberOfLinesOfCode() as n order by n desc
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ClassMetrics cls
|
||||
from ClassMetricsWithPointsTo cls
|
||||
select cls, cls.getAfferentCoupling() as n order by n desc
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ClassMetrics cls
|
||||
from ClassMetricsWithPointsTo cls
|
||||
select cls, cls.getEfferentCoupling() as n order by n desc
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ModuleMetrics mm
|
||||
where mm.getNumberOfLines() > 0
|
||||
|
||||
@@ -15,6 +15,6 @@
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from FunctionMetrics func, int complexity
|
||||
from FunctionMetricsWithPointsTo func, int complexity
|
||||
where complexity = func.getCyclomaticComplexity()
|
||||
select func, complexity order by complexity desc
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ModuleMetrics mm
|
||||
where mm.getNumberOfLines() > 0
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ModuleMetrics m, int n
|
||||
where n = m.getNumberOfLines()
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ModuleMetrics m, int n
|
||||
where n = m.getNumberOfLinesOfCode()
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ModuleMetrics m, int n
|
||||
where n = m.getNumberOfLinesOfComments() + m.getNumberOfLinesOfDocStrings()
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from FunctionMetrics func
|
||||
select func, func.getNumberOfCalls() as n order by n desc
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from FunctionMetrics func
|
||||
select func, func.getStatementNestingDepth() as n order by n desc
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
import python
|
||||
import external.VCS
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ModuleMetrics m, int n
|
||||
where
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
import python
|
||||
import external.VCS
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ModuleMetrics m, int n
|
||||
where
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
import python
|
||||
import external.VCS
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ModuleMetrics m, int n
|
||||
where
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
import python
|
||||
import external.VCS
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ModuleMetrics m
|
||||
where exists(m.getNumberOfLinesOfCode())
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
import python
|
||||
import external.VCS
|
||||
private import LegacyPointsTo
|
||||
|
||||
int committedFiles(Commit commit) { result = count(commit.getAnAffectedFile()) }
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
import python
|
||||
import external.VCS
|
||||
private import LegacyPointsTo
|
||||
|
||||
predicate inRange(Commit first, Commit second) {
|
||||
first.getAnAffectedFile() = second.getAnAffectedFile() and
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
import python
|
||||
import external.VCS
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ModuleMetrics m
|
||||
where exists(m.getNumberOfLinesOfCode())
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
import python
|
||||
import external.VCS
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ModuleMetrics m
|
||||
where
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ClassMetrics cls
|
||||
select cls, cls.getLackOfCohesionCK() as n order by n desc
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ClassMetrics cls
|
||||
select cls, cls.getLackOfCohesionHM() as n order by n desc
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ModuleMetrics m
|
||||
from ModuleMetricsWithPointsTo m
|
||||
select m, m.getAfferentCoupling() as n order by n desc
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from ModuleMetrics m
|
||||
from ModuleMetricsWithPointsTo m
|
||||
select m, m.getEfferentCoupling() as n order by n desc
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from FunctionMetrics func
|
||||
select func, func.getNumberOfParametersWithoutDefault() as n order by n desc
|
||||
|
||||
@@ -10,6 +10,5 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
select sum(ModuleMetrics m | | m.getNumberOfLinesOfCode())
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
import python
|
||||
import semmle.python.filters.GeneratedCode
|
||||
private import LegacyPointsTo
|
||||
|
||||
select sum(ModuleMetrics m |
|
||||
exists(m.getFile().getRelativePath()) and
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import python
|
||||
private import LegacyPointsTo
|
||||
|
||||
from FunctionMetrics func
|
||||
from FunctionMetricsWithPointsTo func
|
||||
select func.toString(), func.getCyclomaticComplexity()
|
||||
|
||||
Reference in New Issue
Block a user