mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Dependencies: start tracking whether a lookup is private to a file or not.
This is sort of two commits squashed into one: first, update ReferencedNameTracker to record whether a name or type is non-private, along with changing all clients to assume non-private; and second, actually try to (conservatively) decide if a particular unqualified lookup can be considered private. What does "private" mean? That means that a dependency does not affect "downstream" files. For example, if file A depends on B, and B depends on C, then a change in C normally means A will get rebuilt. But if B's dependencies on C are all private dependencies (e.g. lookups from within function bodies), then A does not need to be rebuilt. In practice there are several rules about when we can make this assumption, and a few places where our current DeclContext model is not good enough to distinguish private uses from non-private uses. In these cases we have to be conservative and assume that the use is non-private (and thus that downstream files will need to be rebuilt). Part of rdar://problem/15353101 Swift SVN r23447
This commit is contained in:
@@ -14,28 +14,29 @@
|
||||
#define SWIFT_REFERENCEDNAMETRACKER_H
|
||||
|
||||
#include "swift/AST/Identifier.h"
|
||||
#include "swift/Basic/LLVM.h"
|
||||
#include "llvm/ADT/SmallPtrSet.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
|
||||
namespace swift {
|
||||
|
||||
class NominalTypeDecl;
|
||||
|
||||
class ReferencedNameTracker {
|
||||
SmallPtrSet<Identifier, 1> TopLevelNames;
|
||||
SmallPtrSet<const NominalTypeDecl *, 1> UsedNominals;
|
||||
llvm::DenseMap<Identifier, bool> TopLevelNames;
|
||||
llvm::DenseMap<const NominalTypeDecl *, bool> UsedNominals;
|
||||
public:
|
||||
void addTopLevelName(Identifier name) {
|
||||
TopLevelNames.insert(name);
|
||||
void addTopLevelName(Identifier name, bool isNonPrivateUse) {
|
||||
TopLevelNames[name] |= isNonPrivateUse;
|
||||
}
|
||||
|
||||
const SmallPtrSetImpl<Identifier> &getTopLevelNames() const {
|
||||
const llvm::DenseMap<Identifier, bool> &getTopLevelNames() const {
|
||||
return TopLevelNames;
|
||||
}
|
||||
|
||||
void addUsedNominal(const NominalTypeDecl *nominal) {
|
||||
UsedNominals.insert(nominal);
|
||||
void addUsedNominal(const NominalTypeDecl *nominal, bool isNonPrivateUse) {
|
||||
UsedNominals[nominal] |= isNonPrivateUse;
|
||||
}
|
||||
|
||||
const SmallPtrSetImpl<const NominalTypeDecl *> &getUsedNominals() const {
|
||||
const llvm::DenseMap<const NominalTypeDecl *, bool> &getUsedNominals() const {
|
||||
return UsedNominals;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1134,8 +1134,9 @@ SourceFile::lookup##Kind##Operator(Identifier name, SourceLoc loc) { \
|
||||
auto result = lookupOperatorDeclForName(*this, loc, name, true, \
|
||||
&SourceFile::Kind##Operators); \
|
||||
if (result.hasValue() && !result.getValue()) { \
|
||||
/* FIXME: Track whether this is a private use or not. */ \
|
||||
if (ReferencedNames) \
|
||||
ReferencedNames->addTopLevelName(name); \
|
||||
ReferencedNames->addTopLevelName(name, true); \
|
||||
result = lookupOperatorDeclForName(getParentModule(), loc, name, \
|
||||
&SourceFile::Kind##Operators); \
|
||||
} \
|
||||
|
||||
@@ -422,15 +422,64 @@ static void filterForDiscriminator(SmallVectorImpl<Result> &results,
|
||||
results.push_back(lastMatch);
|
||||
}
|
||||
|
||||
static bool isPrivateContextForLookup(const DeclContext *DC,
|
||||
bool functionsArePrivate) {
|
||||
switch (DC->getContextKind()) {
|
||||
case DeclContextKind::AbstractClosureExpr:
|
||||
break;
|
||||
|
||||
case DeclContextKind::Initializer:
|
||||
// Default arguments still require a type.
|
||||
if (isa<DefaultArgumentInitializer>(DC))
|
||||
return true;
|
||||
break;
|
||||
|
||||
case DeclContextKind::TopLevelCodeDecl:
|
||||
// FIXME: Pattern initializers at top-level scope end up here.
|
||||
return false;
|
||||
|
||||
case DeclContextKind::AbstractFunctionDecl:
|
||||
if (functionsArePrivate)
|
||||
return true;
|
||||
break;
|
||||
|
||||
case DeclContextKind::Module:
|
||||
case DeclContextKind::FileUnit:
|
||||
return false;
|
||||
|
||||
case DeclContextKind::NominalTypeDecl: {
|
||||
auto *nominal = cast<NominalTypeDecl>(DC);
|
||||
if (nominal->hasAccessibility())
|
||||
return nominal->getAccessibility() == Accessibility::Private;
|
||||
break;
|
||||
}
|
||||
|
||||
case DeclContextKind::ExtensionDecl: {
|
||||
auto *extension = cast<ExtensionDecl>(DC);
|
||||
if (extension->hasDefaultAccessibility())
|
||||
return extension->getDefaultAccessibility() == Accessibility::Private;
|
||||
// FIXME: duplicated from computeDefaultAccessibility in TypeCheckDecl.cpp.
|
||||
if (auto *AA = extension->getAttrs().getAttribute<AccessibilityAttr>())
|
||||
return AA->getAccess() == Accessibility::Private;
|
||||
if (Type extendedTy = extension->getExtendedType())
|
||||
return isPrivateContextForLookup(extendedTy->getAnyNominal(), true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return isPrivateContextForLookup(DC->getParent(), true);
|
||||
}
|
||||
|
||||
static void recordLookupOfTopLevelName(DeclContext *topLevelContext,
|
||||
DeclName name) {
|
||||
DeclName name,
|
||||
bool isNonPrivateUse) {
|
||||
auto SF = dyn_cast<SourceFile>(topLevelContext);
|
||||
if (!SF)
|
||||
return;
|
||||
auto *nameTracker = SF->getReferencedNameTracker();
|
||||
if (!nameTracker)
|
||||
return;
|
||||
nameTracker->addTopLevelName(name.getBaseName());
|
||||
nameTracker->addTopLevelName(name.getBaseName(), isNonPrivateUse);
|
||||
}
|
||||
|
||||
UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
|
||||
@@ -443,12 +492,16 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
|
||||
const SourceManager &SM = Ctx.SourceMgr;
|
||||
DebuggerClient *DebugClient = M.getDebugClient();
|
||||
|
||||
// Never perform local lookup for operators.
|
||||
if (Name.isOperator())
|
||||
DC = DC->getModuleScopeContext();
|
||||
bool isPrivateUse = false;
|
||||
|
||||
// If we are inside of a method, check to see if there are any ivars in scope,
|
||||
// and if so, whether this is a reference to one of them.
|
||||
// Never perform local lookup for operators.
|
||||
if (Name.isOperator()) {
|
||||
isPrivateUse = isPrivateContextForLookup(DC, /*includeFunctions=*/true);
|
||||
DC = DC->getModuleScopeContext();
|
||||
} else {
|
||||
// If we are inside of a method, check to see if there are any ivars in
|
||||
// scope, and if so, whether this is a reference to one of them.
|
||||
// FIXME: We should persist this information between lookups.
|
||||
while (!DC->isModuleScopeContext()) {
|
||||
ValueDecl *BaseDecl = 0;
|
||||
ValueDecl *MetaBaseDecl = 0;
|
||||
@@ -461,6 +514,8 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
|
||||
// FIXME: when we can parse and typecheck the function body partially for
|
||||
// code completion, AFD->getBody() check can be removed.
|
||||
if (Loc.isValid() && AFD->getBody()) {
|
||||
isPrivateUse =
|
||||
SM.rangeContainsTokenLoc(AFD->getBodySourceRange(), Loc);
|
||||
FindLocalVal localVal(SM, Loc, Name);
|
||||
localVal.visit(AFD->getBody());
|
||||
if (!localVal.MatchingValue) {
|
||||
@@ -472,6 +527,8 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!isPrivateUse)
|
||||
isPrivateUse = isPrivateContextForLookup(AFD, false);
|
||||
|
||||
if (AFD->getExtensionType()) {
|
||||
ExtendedType = AFD->getExtensionType();
|
||||
@@ -510,19 +567,26 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
|
||||
}
|
||||
}
|
||||
}
|
||||
isPrivateUse = isPrivateContextForLookup(ACE, false);
|
||||
} else if (ExtensionDecl *ED = dyn_cast<ExtensionDecl>(DC)) {
|
||||
ExtendedType = ED->getExtendedType();
|
||||
BaseDecl = ExtendedType->getNominalOrBoundGenericNominal();
|
||||
MetaBaseDecl = BaseDecl;
|
||||
isPrivateUse = isPrivateContextForLookup(ED, false);
|
||||
} else if (NominalTypeDecl *ND = dyn_cast<NominalTypeDecl>(DC)) {
|
||||
ExtendedType = ND->getDeclaredType();
|
||||
BaseDecl = ND;
|
||||
MetaBaseDecl = BaseDecl;
|
||||
isPrivateUse = isPrivateContextForLookup(ND, false);
|
||||
} else if (auto I = dyn_cast<DefaultArgumentInitializer>(DC)) {
|
||||
// In a default argument, skip immediately out of both the
|
||||
// initializer and the function.
|
||||
isPrivateUse = true;
|
||||
DC = I->getParent()->getParent();
|
||||
continue;
|
||||
} else {
|
||||
assert(isa<TopLevelCodeDecl>(DC) || isa<Initializer>(DC));
|
||||
isPrivateUse = isPrivateContextForLookup(DC, false);
|
||||
}
|
||||
|
||||
// Check the generic parameters for something with the given name.
|
||||
@@ -623,6 +687,7 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
|
||||
|
||||
DC = DC->getParent();
|
||||
}
|
||||
}
|
||||
|
||||
if (auto SF = dyn_cast<SourceFile>(DC)) {
|
||||
if (Loc.isValid()) {
|
||||
@@ -644,7 +709,7 @@ UnqualifiedLookup::UnqualifiedLookup(DeclName Name, DeclContext *DC,
|
||||
Loc, IsTypeLookup, Results))
|
||||
return;
|
||||
|
||||
recordLookupOfTopLevelName(DC, Name);
|
||||
recordLookupOfTopLevelName(DC, Name, !isPrivateUse);
|
||||
|
||||
// Add private imports to the extra search list.
|
||||
SmallVector<Module::ImportedModule, 8> extraImports;
|
||||
@@ -1121,7 +1186,10 @@ bool DeclContext::lookupQualified(Type type,
|
||||
Module *module = moduleTy->getModule();
|
||||
auto topLevelScope = getModuleScopeContext();
|
||||
if (module == topLevelScope->getParentModule()) {
|
||||
recordLookupOfTopLevelName(topLevelScope, member);
|
||||
// FIXME: We should be able to tell if this is part of a function body
|
||||
// or not.
|
||||
recordLookupOfTopLevelName(topLevelScope, member,
|
||||
isPrivateContextForLookup(this, false));
|
||||
lookupInModule(module, /*accessPath=*/{}, member, decls,
|
||||
NLKind::QualifiedLookup, ResolutionKind::Overloadable,
|
||||
typeResolver, topLevelScope);
|
||||
@@ -1262,8 +1330,10 @@ bool DeclContext::lookupQualified(Type type,
|
||||
while (!stack.empty()) {
|
||||
auto current = stack.back();
|
||||
stack.pop_back();
|
||||
|
||||
// FIXME: We should be able to tell if this is a local or non-local use.
|
||||
if (tracker)
|
||||
tracker->addUsedNominal(current);
|
||||
tracker->addUsedNominal(current, true);
|
||||
|
||||
// Make sure we've resolved implicit constructors, if we need them.
|
||||
if (member.getBaseName() == ctx.Id_init && typeResolver)
|
||||
|
||||
@@ -3580,8 +3580,9 @@ public:
|
||||
D->getStartLoc(), D);
|
||||
conformances.push_back(conformance);
|
||||
|
||||
// FIXME: We should be able to tell if this is a private use or not.
|
||||
if (tracker)
|
||||
tracker->addUsedNominal(proto);
|
||||
tracker->addUsedNominal(proto, true);
|
||||
}
|
||||
|
||||
D->setConformances(D->getASTContext().AllocateCopy(conformances));
|
||||
@@ -4710,8 +4711,9 @@ public:
|
||||
|
||||
if (auto *SF = PD->getParentSourceFile()) {
|
||||
if (auto *tracker = SF->getReferencedNameTracker()) {
|
||||
// FIXME: Track whether this is a private use or not.
|
||||
for (auto *parentProto : PD->getProtocols())
|
||||
tracker->addUsedNominal(parentProto);
|
||||
tracker->addUsedNominal(parentProto, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2538,8 +2538,9 @@ bool TypeChecker::conformsToProtocol(Type T, ProtocolDecl *Proto,
|
||||
const DeclContext *topLevelContext = DC->getModuleScopeContext();
|
||||
if (auto *constSF = dyn_cast<SourceFile>(topLevelContext)) {
|
||||
auto *SF = const_cast<SourceFile *>(constSF);
|
||||
// FIXME: Track whether this is a private use or not.
|
||||
if (auto *tracker = SF->getReferencedNameTracker())
|
||||
tracker->addUsedNominal(nominal);
|
||||
tracker->addUsedNominal(nominal, true);
|
||||
}
|
||||
|
||||
Module *M = topLevelContext->getParentModule();
|
||||
|
||||
@@ -33,3 +33,31 @@ protocol OtherFileProto2 {}
|
||||
struct OtherFileProtoImplementor2 : OtherFileProto2 {}
|
||||
func otherFileGetImpl2() -> OtherFileProtoImplementor2 {}
|
||||
func otherFileUseGeneric<T: OtherFileProto2>(_: T) {}
|
||||
|
||||
func topLevel1() -> Int { return 2 }
|
||||
func topLevel2() -> Int { return 2 }
|
||||
func topLevel3() -> Int { return 2 }
|
||||
func topLevel4() -> Int { return 2 }
|
||||
func topLevel5() -> Int { return 2 }
|
||||
func topLevel6() -> Int { return 2 }
|
||||
func topLevel7() -> Int { return 2 }
|
||||
func topLevel8() -> Int { return 2 }
|
||||
func topLevel9() -> Int { return 2 }
|
||||
|
||||
typealias TopLevelTy1 = Int
|
||||
typealias TopLevelTy2 = Int
|
||||
typealias TopLevelTy3 = Int
|
||||
|
||||
func privateTopLevel1() -> Int { return 2 }
|
||||
func privateTopLevel2() -> Int { return 2 }
|
||||
func privateTopLevel3() -> Int { return 2 }
|
||||
func privateTopLevel4() -> Int { return 2 }
|
||||
func privateTopLevel5() -> Int { return 2 }
|
||||
func privateTopLevel6() -> Int { return 2 }
|
||||
func privateTopLevel7() -> Int { return 2 }
|
||||
func privateTopLevel8() -> Int { return 2 }
|
||||
func privateTopLevel9() -> Int { return 2 }
|
||||
|
||||
typealias PrivateTopLevelTy1 = Int
|
||||
typealias PrivateTopLevelTy2 = Int
|
||||
typealias PrivateTopLevelTy3 = Int
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
// RUN: %swift -parse -primary-file %s %S/Inputs/reference-dependencies-helper.swift -emit-reference-dependencies-path - > %t.swiftdeps
|
||||
// RUN: rm -rf %t && mkdir %t
|
||||
// RUN: cp %s %t/main.swift
|
||||
// RUN: %swift -parse -primary-file %t/main.swift %S/Inputs/reference-dependencies-helper.swift -emit-reference-dependencies-path - > %t.swiftdeps
|
||||
// RUN: FileCheck %s < %t.swiftdeps
|
||||
// RUN: FileCheck -check-prefix=NEGATIVE %s < %t.swiftdeps
|
||||
|
||||
@@ -13,7 +15,7 @@
|
||||
// CHECK-NEXT: "someGlobal"
|
||||
// CHECK-NEXT: "ExtraFloatLiteralConvertible"
|
||||
// CHECK-NEXT: "lookUpManyTopLevelNames"
|
||||
// CHECK-NEXT: "eof"
|
||||
// CHECK: "eof"
|
||||
|
||||
// CHECK-LABEL: {{^nominals:$}}
|
||||
// CHECK-NEXT: "V4main10IntWrapper"
|
||||
@@ -74,22 +76,22 @@ func lookUpManyTopLevelNames() {
|
||||
// CHECK-DAG: "Dictionary"
|
||||
let _: Dictionary = [1:1]
|
||||
|
||||
// CHECK-DAG: "UInt"
|
||||
// CHECK-DAG: "reduce"
|
||||
// CHECK-DAG: "+"
|
||||
// CHECK-DAG: !private "UInt"
|
||||
// CHECK-DAG: !private "reduce"
|
||||
// CHECK-DAG: !private "+"
|
||||
let _: UInt = reduce([1,2], 0, +)
|
||||
|
||||
// CHECK-DAG: "AliasFromOtherFile"
|
||||
// CHECK-DAG: !private "AliasFromOtherFile"
|
||||
let _: AliasFromOtherFile = 1
|
||||
|
||||
// CHECK-DAG: "funcFromOtherFile"
|
||||
// CHECK-DAG: !private "funcFromOtherFile"
|
||||
funcFromOtherFile()
|
||||
|
||||
// "CInt" is not used as a top-level name here.
|
||||
// CHECK-DAG: "StringLiteralType"
|
||||
// NEGATIVE-NOT: "CInt"
|
||||
let CInt = "abc"
|
||||
// CHECK-DAG: "println"
|
||||
// CHECK-DAG: !private "println"
|
||||
println(CInt)
|
||||
|
||||
// NEGATIVE-NOT: "max"
|
||||
@@ -98,24 +100,83 @@ func lookUpManyTopLevelNames() {
|
||||
// NEGATIVE-NOT: "Stride"
|
||||
let _: Int.Stride = 0
|
||||
|
||||
// CHECK-DAG: "OtherFileOuterType"
|
||||
// CHECK-DAG: !private "OtherFileOuterType"
|
||||
_ = OtherFileOuterType.InnerType.sharedConstant
|
||||
|
||||
// CHECK-DAG: "OtherFileAliasForSecret"
|
||||
// CHECK-DAG: !private "OtherFileAliasForSecret"
|
||||
_ = OtherFileAliasForSecret.constant
|
||||
|
||||
// CHECK-DAG: otherFileUse
|
||||
// CHECK-DAG: otherFileGetImpl
|
||||
// CHECK-DAG: !private "otherFileUse"
|
||||
// CHECK-DAG: !private "otherFileGetImpl"
|
||||
otherFileUse(otherFileGetImpl())
|
||||
|
||||
// CHECK-DAG: otherFileUse
|
||||
// CHECK-DAG: otherFileGetImpl
|
||||
// CHECK-DAG: !private "otherFileUseGeneric"
|
||||
// CHECK-DAG: !private "otherFileGetImpl2"
|
||||
otherFileUseGeneric(otherFileGetImpl2())
|
||||
}
|
||||
|
||||
// NEGATIVE-NOT: "privateFunc"
|
||||
private func privateFunc() {}
|
||||
|
||||
// CHECK-DAG: - "topLevel1"
|
||||
var use1 = topLevel1()
|
||||
// CHECK-DAG: - "topLevel2"
|
||||
var use2 = { topLevel2() }
|
||||
// CHECK-DAG: - "topLevel3"
|
||||
var use3 = { ({ topLevel3() })() }
|
||||
// CHECK-DAG: - "topLevel4"
|
||||
struct Use4 {
|
||||
var use4 = topLevel4()
|
||||
}
|
||||
// CHECK-DAG: - "*"
|
||||
print(42 * 30)
|
||||
|
||||
// FIXME: Incorrectly marked non-private dependencies
|
||||
// CHECK-DAG: - "topLevel6"
|
||||
print(topLevel6())
|
||||
// CHECK-DAG: - "topLevel7"
|
||||
private var use7 = topLevel7()
|
||||
// CHECK-DAG: - "topLevel8"
|
||||
var use8: Int = topLevel8()
|
||||
// CHECK-DAG: - "topLevel9"
|
||||
var use9 = { () -> Int in return topLevel9() }
|
||||
|
||||
|
||||
// CHECK-DAG: - "TopLevelTy1"
|
||||
func useTy1(x: TopLevelTy1) {}
|
||||
// CHECK-DAG: - "TopLevelTy2"
|
||||
func useTy2() -> TopLevelTy2 {}
|
||||
// CHECK-DAG: - "TopLevelTy3"
|
||||
extension Use4 {
|
||||
var useTy3: TopLevelTy3? { return nil }
|
||||
}
|
||||
|
||||
// CHECK-DAG: !private "privateTopLevel1"
|
||||
func private1(a: Int = privateTopLevel1()) {}
|
||||
// CHECK-DAG: !private "privateTopLevel2"
|
||||
private struct Private2 {
|
||||
var private2 = privateTopLevel2()
|
||||
}
|
||||
// CHECK-DAG: !private "privateTopLevel3"
|
||||
func outerPrivate3() {
|
||||
let private3 = { privateTopLevel3() }
|
||||
}
|
||||
|
||||
// CHECK-DAG: !private "PrivateTopLevelTy1"
|
||||
private extension Use4 {
|
||||
var privateTy1: PrivateTopLevelTy1? { return nil }
|
||||
}
|
||||
// CHECK-DAG: !private "PrivateTopLevelTy2"
|
||||
extension Private2 {
|
||||
var privateTy2: PrivateTopLevelTy2? { return nil }
|
||||
}
|
||||
// CHECK-DAG: !private "PrivateTopLevelTy3"
|
||||
func outerPrivateTy3() {
|
||||
func inner(a: PrivateTopLevelTy3?) {}
|
||||
inner(nil)
|
||||
}
|
||||
|
||||
|
||||
// CHECK-LABEL: {{^member-access:$}}
|
||||
// CHECK-DAG: "V4main10IntWrapper"
|
||||
// CHECK-DAG: "PSs10Comparable"
|
||||
|
||||
@@ -187,20 +187,26 @@ static bool emitReferenceDependencies(DiagnosticEngine &diags,
|
||||
|
||||
// FIXME: Sort these?
|
||||
out << "top-level:\n";
|
||||
for (Identifier name : tracker->getTopLevelNames()) {
|
||||
out << "- \"" << name << "\"\n";
|
||||
for (auto &entry : tracker->getTopLevelNames()) {
|
||||
out << "- ";
|
||||
if (!entry.second)
|
||||
out << "!private ";
|
||||
out << "\"" << entry.first << "\"\n";
|
||||
}
|
||||
|
||||
// FIXME: Sort these?
|
||||
out << "member-access:\n";
|
||||
for (auto usedNominal : tracker->getUsedNominals()) {
|
||||
if (usedNominal->hasAccessibility() &&
|
||||
usedNominal->getAccessibility() == Accessibility::Private)
|
||||
for (auto &entry : tracker->getUsedNominals()) {
|
||||
if (entry.first->hasAccessibility() &&
|
||||
entry.first->getAccessibility() == Accessibility::Private)
|
||||
continue;
|
||||
|
||||
Mangle::Mangler mangler(out, /*debug style=*/false, /*Unicode=*/true);
|
||||
out << "- \"";
|
||||
mangler.mangleContext(usedNominal, Mangle::Mangler::BindGenerics::None);
|
||||
out << "- ";
|
||||
if (!entry.second)
|
||||
out << "!private ";
|
||||
out << "\"";
|
||||
mangler.mangleContext(entry.first, Mangle::Mangler::BindGenerics::None);
|
||||
out << "\"\n";
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user