Add lint for unknown feature attributes
This commit is contained in:
parent
a00ba4d71e
commit
5242dce01d
@ -615,6 +615,8 @@ define_dep_nodes!( <'tcx>
|
||||
[input] CrateName(CrateNum),
|
||||
[] ItemChildren(DefId),
|
||||
[] ExternModStmtCnum(DefId),
|
||||
[input] GetLibFeatures,
|
||||
[] DefinedLibFeatures(CrateNum),
|
||||
[input] GetLangItems,
|
||||
[] DefinedLangItems(CrateNum),
|
||||
[] MissingLangItems(CrateNum),
|
||||
|
@ -2138,4 +2138,5 @@ register_diagnostics! {
|
||||
E0708, // `async` non-`move` closures with arguments are not currently supported
|
||||
E0709, // multiple different lifetimes used in arguments of `async fn`
|
||||
E0710, // an unknown tool name found in scoped lint
|
||||
E0711, // a feature has been declared with conflicting stability attributes
|
||||
}
|
||||
|
@ -1072,6 +1072,11 @@ impl_stable_hash_for!(struct hir::def::Export {
|
||||
span
|
||||
});
|
||||
|
||||
impl_stable_hash_for!(struct ::middle::lib_features::LibFeatures {
|
||||
stable,
|
||||
unstable
|
||||
});
|
||||
|
||||
impl<'a> HashStable<StableHashingContext<'a>> for ::middle::lang_items::LangItem {
|
||||
fn hash_stable<W: StableHasherResult>(&self,
|
||||
_: &mut StableHashingContext<'a>,
|
||||
|
@ -141,6 +141,7 @@ pub mod middle {
|
||||
pub mod exported_symbols;
|
||||
pub mod free_region;
|
||||
pub mod intrinsicck;
|
||||
pub mod lib_features;
|
||||
pub mod lang_items;
|
||||
pub mod liveness;
|
||||
pub mod mem_categorization;
|
||||
|
@ -102,7 +102,13 @@ declare_lint! {
|
||||
declare_lint! {
|
||||
pub UNUSED_FEATURES,
|
||||
Warn,
|
||||
"unused or unknown features found in crate-level #[feature] directives"
|
||||
"unused features found in crate-level #[feature] directives"
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
pub UNKNOWN_FEATURES,
|
||||
Deny,
|
||||
"unknown features found in crate-level #[feature] directives"
|
||||
}
|
||||
|
||||
declare_lint! {
|
||||
@ -362,6 +368,7 @@ impl LintPass for HardwiredLints {
|
||||
UNUSED_MACROS,
|
||||
WARNINGS,
|
||||
UNUSED_FEATURES,
|
||||
UNKNOWN_FEATURES,
|
||||
STABLE_FEATURES,
|
||||
UNKNOWN_CRATE_TYPES,
|
||||
TRIVIAL_CASTS,
|
||||
|
153
src/librustc/middle/lib_features.rs
Normal file
153
src/librustc/middle/lib_features.rs
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
// Detecting lib features (i.e. features that are not lang features).
|
||||
//
|
||||
// These are declared using stability attributes (e.g. `#[stable(..)]`
|
||||
// and `#[unstable(..)]`), but are not declared in one single location
|
||||
// (unlike lang features), which means we need to collect them instead.
|
||||
|
||||
use ty::TyCtxt;
|
||||
use syntax::symbol::Symbol;
|
||||
use syntax::ast::{Attribute, MetaItem, MetaItemKind};
|
||||
use syntax_pos::{Span, DUMMY_SP};
|
||||
use hir;
|
||||
use hir::itemlikevisit::ItemLikeVisitor;
|
||||
use rustc_data_structures::fx::{FxHashSet, FxHashMap};
|
||||
use errors::DiagnosticId;
|
||||
|
||||
pub struct LibFeatures {
|
||||
// A map from feature to stabilisation version.
|
||||
pub stable: FxHashMap<Symbol, Symbol>,
|
||||
pub unstable: FxHashSet<Symbol>,
|
||||
}
|
||||
|
||||
impl LibFeatures {
|
||||
fn new() -> LibFeatures {
|
||||
LibFeatures {
|
||||
stable: FxHashMap(),
|
||||
unstable: FxHashSet(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Vec<(Symbol, Option<Symbol>)> {
|
||||
self.stable.iter().map(|(f, s)| (*f, Some(*s)))
|
||||
.chain(self.unstable.iter().map(|f| (*f, None)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LibFeatureCollector<'a, 'tcx: 'a> {
|
||||
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
||||
lib_features: LibFeatures,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> LibFeatureCollector<'a, 'tcx> {
|
||||
fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>) -> LibFeatureCollector<'a, 'tcx> {
|
||||
LibFeatureCollector {
|
||||
tcx,
|
||||
lib_features: LibFeatures::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract(&self, attrs: &[Attribute]) -> Vec<(Symbol, Option<Symbol>, Span)> {
|
||||
let stab_attrs = vec!["stable", "unstable", "rustc_const_unstable"];
|
||||
let mut features = vec![];
|
||||
|
||||
for attr in attrs {
|
||||
// FIXME(varkor): the stability attribute might be behind a `#[cfg]` attribute.
|
||||
|
||||
// Find a stability attribute (i.e. `#[stable(..)]`, `#[unstable(..)]`,
|
||||
// `#[rustc_const_unstable(..)]`).
|
||||
if stab_attrs.iter().any(|stab_attr| attr.check_name(stab_attr)) {
|
||||
let meta_item = attr.meta();
|
||||
if let Some(MetaItem { node: MetaItemKind::List(ref metas), .. }) = meta_item {
|
||||
let mut feature = None;
|
||||
let mut since = None;
|
||||
for meta in metas {
|
||||
if let Some(mi) = meta.meta_item() {
|
||||
// Find the `feature = ".."` meta-item.
|
||||
match (&*mi.name().as_str(), mi.value_str()) {
|
||||
("feature", val) => feature = val,
|
||||
("since", val) => since = val,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(feature) = feature {
|
||||
features.push((feature, since, attr.span));
|
||||
}
|
||||
// We need to iterate over the other attributes, because
|
||||
// `rustc_const_unstable` is not mutually exclusive with
|
||||
// the other stability attributes, so we can't just `break`
|
||||
// here.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
features
|
||||
}
|
||||
|
||||
fn collect_feature(&mut self, feature: Symbol, since: Option<Symbol>, span: Span) {
|
||||
let already_in_stable = self.lib_features.stable.contains_key(&feature);
|
||||
let already_in_unstable = self.lib_features.unstable.contains(&feature);
|
||||
|
||||
match (since, already_in_stable, already_in_unstable) {
|
||||
(Some(since), _, false) => {
|
||||
self.lib_features.stable.insert(feature, since);
|
||||
}
|
||||
(None, false, _) => {
|
||||
self.lib_features.unstable.insert(feature);
|
||||
}
|
||||
(Some(_), _, true) | (None, true, _) => {
|
||||
let msg = format!(
|
||||
"feature `{}` is declared {}, but was previously declared {}",
|
||||
feature,
|
||||
if since.is_some() { "stable"} else { "unstable" },
|
||||
if since.is_none() { "stable"} else { "unstable" },
|
||||
);
|
||||
self.tcx.sess.struct_span_err_with_code(span, &msg,
|
||||
DiagnosticId::Error("E0711".into())).emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_from_attrs(&mut self, attrs: &[Attribute]) {
|
||||
for (feature, stable, span) in self.extract(attrs) {
|
||||
self.collect_feature(feature, stable, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'v, 'tcx> ItemLikeVisitor<'v> for LibFeatureCollector<'a, 'tcx> {
|
||||
fn visit_item(&mut self, item: &hir::Item) {
|
||||
self.collect_from_attrs(&item.attrs);
|
||||
}
|
||||
|
||||
fn visit_trait_item(&mut self, trait_item: &hir::TraitItem) {
|
||||
self.collect_from_attrs(&trait_item.attrs);
|
||||
}
|
||||
|
||||
fn visit_impl_item(&mut self, impl_item: &hir::ImplItem) {
|
||||
self.collect_from_attrs(&impl_item.attrs);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) -> LibFeatures {
|
||||
let mut collector = LibFeatureCollector::new(tcx);
|
||||
for &cnum in tcx.crates().iter() {
|
||||
for &(feature, since) in tcx.defined_lib_features(cnum).iter() {
|
||||
collector.collect_feature(feature, since, DUMMY_SP);
|
||||
}
|
||||
}
|
||||
collector.collect_from_attrs(&tcx.hir.krate().attrs);
|
||||
tcx.hir.krate().visit_all_item_likes(&mut collector);
|
||||
collector.lib_features
|
||||
}
|
@ -813,37 +813,42 @@ pub fn check_unused_or_stable_features<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
|
||||
krate.visit_all_item_likes(&mut missing.as_deep_visitor());
|
||||
}
|
||||
|
||||
let ref declared_lib_features = tcx.features().declared_lib_features;
|
||||
let mut remaining_lib_features: FxHashMap<Symbol, Span>
|
||||
= declared_lib_features.clone().into_iter().collect();
|
||||
remaining_lib_features.remove(&Symbol::intern("proc_macro"));
|
||||
|
||||
for &(ref stable_lang_feature, span) in &tcx.features().declared_stable_lang_features {
|
||||
let version = find_lang_feature_accepted_version(&stable_lang_feature.as_str())
|
||||
let since = find_lang_feature_accepted_version(&stable_lang_feature.as_str())
|
||||
.expect("unexpectedly couldn't find version feature was stabilized");
|
||||
tcx.lint_node(lint::builtin::STABLE_FEATURES,
|
||||
ast::CRATE_NODE_ID,
|
||||
span,
|
||||
&format_stable_since_msg(version));
|
||||
&format_stable_since_msg(*stable_lang_feature, since));
|
||||
}
|
||||
|
||||
// FIXME(#44232) the `used_features` table no longer exists, so we don't
|
||||
// lint about unknown or unused features. We should reenable
|
||||
// this one day!
|
||||
//
|
||||
// let index = tcx.stability();
|
||||
// for (used_lib_feature, level) in &index.used_features {
|
||||
// remaining_lib_features.remove(used_lib_feature);
|
||||
// }
|
||||
//
|
||||
// for &span in remaining_lib_features.values() {
|
||||
// tcx.lint_node(lint::builtin::UNUSED_FEATURES,
|
||||
// ast::CRATE_NODE_ID,
|
||||
// span,
|
||||
// "unused or unknown feature");
|
||||
// }
|
||||
let ref declared_lib_features = tcx.features().declared_lib_features;
|
||||
|
||||
let mut remaining_lib_features = FxHashMap();
|
||||
for (feature, span) in declared_lib_features.clone().into_iter() {
|
||||
remaining_lib_features.insert(feature, span);
|
||||
}
|
||||
// FIXME(varkor): we don't properly handle lib features behind `cfg` attributes yet,
|
||||
// but it happens just to affect `libc`, so we're just going to hard-code it for now.
|
||||
remaining_lib_features.remove(&Symbol::intern("libc"));
|
||||
|
||||
for (feature, stable) in tcx.lib_features().iter() {
|
||||
remaining_lib_features.remove(&feature);
|
||||
}
|
||||
|
||||
for (feature, span) in remaining_lib_features {
|
||||
tcx.lint_node(lint::builtin::UNKNOWN_FEATURES,
|
||||
ast::CRATE_NODE_ID,
|
||||
span,
|
||||
&format!("unknown feature `{}`", feature));
|
||||
}
|
||||
|
||||
// FIXME(#44232): the `used_features` table no longer exists, so we
|
||||
// don't lint about unused features. We should reenable this one day!
|
||||
}
|
||||
|
||||
fn format_stable_since_msg(version: &str) -> String {
|
||||
format!("this feature has been stable since {}. Attribute no longer needed", version)
|
||||
fn format_stable_since_msg(feature: Symbol, since: &str) -> String {
|
||||
// "this feature has been stable since {}. Attribute no longer needed"
|
||||
format!("the feature `{}` has been stable since {} and no longer requires \
|
||||
an attribute to enable", feature, since)
|
||||
}
|
||||
|
@ -1192,6 +1192,10 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
|
||||
self.sess.consider_optimizing(&cname, msg)
|
||||
}
|
||||
|
||||
pub fn lib_features(self) -> Lrc<middle::lib_features::LibFeatures> {
|
||||
self.get_lib_features(LOCAL_CRATE)
|
||||
}
|
||||
|
||||
pub fn lang_items(self) -> Lrc<middle::lang_items::LanguageItems> {
|
||||
self.get_lang_items(LOCAL_CRATE)
|
||||
}
|
||||
@ -2840,6 +2844,11 @@ pub fn provide(providers: &mut ty::query::Providers) {
|
||||
assert_eq!(id, LOCAL_CRATE);
|
||||
tcx.crate_name
|
||||
};
|
||||
providers.get_lib_features = |tcx, id| {
|
||||
assert_eq!(id, LOCAL_CRATE);
|
||||
// FIXME(#42293): see comment below.
|
||||
tcx.dep_graph.with_ignore(|| Lrc::new(middle::lib_features::collect(tcx)))
|
||||
};
|
||||
providers.get_lang_items = |tcx, id| {
|
||||
assert_eq!(id, LOCAL_CRATE);
|
||||
// FIXME(#42293) Right now we insert a `with_ignore` node in the dep
|
||||
|
@ -626,6 +626,18 @@ impl<'tcx> QueryDescription<'tcx> for queries::crate_name<'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> QueryDescription<'tcx> for queries::get_lib_features<'tcx> {
|
||||
fn describe(_tcx: TyCtxt, _: CrateNum) -> String {
|
||||
format!("calculating the lib features map")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> QueryDescription<'tcx> for queries::defined_lib_features<'tcx> {
|
||||
fn describe(_tcx: TyCtxt, _: CrateNum) -> String {
|
||||
format!("calculating the lib features defined in a crate")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> QueryDescription<'tcx> for queries::get_lang_items<'tcx> {
|
||||
fn describe(_tcx: TyCtxt, _: CrateNum) -> String {
|
||||
"calculating the lang items map".to_string()
|
||||
|
@ -24,6 +24,7 @@ use middle::reachable::ReachableSet;
|
||||
use middle::region;
|
||||
use middle::resolve_lifetime::{ResolveLifetimes, Region, ObjectLifetimeDefault};
|
||||
use middle::stability::{self, DeprecationEntry};
|
||||
use middle::lib_features::LibFeatures;
|
||||
use middle::lang_items::{LanguageItems, LangItem};
|
||||
use middle::exported_symbols::{SymbolExportLevel, ExportedSymbol};
|
||||
use mir::interpret::ConstEvalResult;
|
||||
@ -492,6 +493,9 @@ define_queries! { <'tcx>
|
||||
[] fn item_children: ItemChildren(DefId) -> Lrc<Vec<Export>>,
|
||||
[] fn extern_mod_stmt_cnum: ExternModStmtCnum(DefId) -> Option<CrateNum>,
|
||||
|
||||
[] fn get_lib_features: get_lib_features_node(CrateNum) -> Lrc<LibFeatures>,
|
||||
[] fn defined_lib_features: DefinedLibFeatures(CrateNum)
|
||||
-> Lrc<Vec<(Symbol, Option<Symbol>)>>,
|
||||
[] fn get_lang_items: get_lang_items_node(CrateNum) -> Lrc<LanguageItems>,
|
||||
[] fn defined_lang_items: DefinedLangItems(CrateNum) -> Lrc<Vec<(DefId, usize)>>,
|
||||
[] fn missing_lang_items: MissingLangItems(CrateNum) -> Lrc<Vec<LangItem>>,
|
||||
@ -800,6 +804,10 @@ fn link_args_node<'tcx>(_: CrateNum) -> DepConstructor<'tcx> {
|
||||
DepConstructor::LinkArgs
|
||||
}
|
||||
|
||||
fn get_lib_features_node<'tcx>(_: CrateNum) -> DepConstructor<'tcx> {
|
||||
DepConstructor::GetLibFeatures
|
||||
}
|
||||
|
||||
fn get_lang_items_node<'tcx>(_: CrateNum) -> DepConstructor<'tcx> {
|
||||
DepConstructor::GetLangItems
|
||||
}
|
||||
|
@ -1218,6 +1218,8 @@ pub fn force_from_dep_node<'a, 'gcx, 'lcx>(tcx: TyCtxt<'a, 'gcx, 'lcx>,
|
||||
DepKind::CrateName => { force!(crate_name, krate!()); }
|
||||
DepKind::ItemChildren => { force!(item_children, def_id!()); }
|
||||
DepKind::ExternModStmtCnum => { force!(extern_mod_stmt_cnum, def_id!()); }
|
||||
DepKind::GetLibFeatures => { force!(get_lib_features, LOCAL_CRATE); }
|
||||
DepKind::DefinedLibFeatures => { force!(defined_lib_features, krate!()); }
|
||||
DepKind::GetLangItems => { force!(get_lang_items, LOCAL_CRATE); }
|
||||
DepKind::DefinedLangItems => { force!(defined_lang_items, krate!()); }
|
||||
DepKind::MissingLangItems => { force!(missing_lang_items, krate!()); }
|
||||
|
@ -188,6 +188,7 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
|
||||
UNUSED_DOC_COMMENTS,
|
||||
UNUSED_EXTERN_CRATES,
|
||||
UNUSED_FEATURES,
|
||||
UNKNOWN_FEATURES,
|
||||
UNUSED_LABELS,
|
||||
UNUSED_PARENS);
|
||||
|
||||
@ -342,7 +343,6 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
|
||||
store.register_renamed("bare_trait_object", "bare_trait_objects");
|
||||
store.register_renamed("unstable_name_collision", "unstable_name_collisions");
|
||||
store.register_renamed("unused_doc_comment", "unused_doc_comments");
|
||||
store.register_renamed("unknown_features", "unused_features");
|
||||
store.register_removed("unsigned_negation", "replaced by negate_unsigned feature gate");
|
||||
store.register_removed("negate_unsigned", "cast a signed value instead");
|
||||
store.register_removed("raw_pointer_derive", "using derive with raw pointers is ok");
|
||||
|
@ -240,6 +240,7 @@ provide! { <'tcx> tcx, def_id, other, cdata,
|
||||
cdata.each_child_of_item(def_id.index, |child| result.push(child), tcx.sess);
|
||||
Lrc::new(result)
|
||||
}
|
||||
defined_lib_features => { Lrc::new(cdata.get_lib_features()) }
|
||||
defined_lang_items => { Lrc::new(cdata.get_lang_items()) }
|
||||
missing_lang_items => { Lrc::new(cdata.get_missing_lang_items()) }
|
||||
|
||||
|
@ -645,6 +645,14 @@ impl<'a, 'tcx> CrateMetadata {
|
||||
self.get_impl_data(id).trait_ref.map(|tr| tr.decode((self, tcx)))
|
||||
}
|
||||
|
||||
/// Iterates over all the stability attributes in the given crate.
|
||||
pub fn get_lib_features(&self) -> Vec<(ast::Name, Option<ast::Name>)> {
|
||||
self.root
|
||||
.lib_features
|
||||
.decode(self)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Iterates over the language items in the given crate.
|
||||
pub fn get_lang_items(&self) -> Vec<(DefId, usize)> {
|
||||
self.root
|
||||
|
@ -394,6 +394,11 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
|
||||
());
|
||||
let dep_bytes = self.position() - i;
|
||||
|
||||
// Encode the lib features.
|
||||
i = self.position();
|
||||
let lib_features = self.tracked(IsolatedEncoder::encode_lib_features, ());
|
||||
let lib_feature_bytes = self.position() - i;
|
||||
|
||||
// Encode the language items.
|
||||
i = self.position();
|
||||
let lang_items = self.tracked(IsolatedEncoder::encode_lang_items, ());
|
||||
@ -513,6 +518,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
|
||||
|
||||
crate_deps,
|
||||
dylib_dependency_formats,
|
||||
lib_features,
|
||||
lang_items,
|
||||
lang_items_missing,
|
||||
native_libraries,
|
||||
@ -537,6 +543,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
|
||||
|
||||
println!("metadata stats:");
|
||||
println!(" dep bytes: {}", dep_bytes);
|
||||
println!(" lib feature bytes: {}", lib_feature_bytes);
|
||||
println!(" lang item bytes: {}", lang_item_bytes);
|
||||
println!(" native bytes: {}", native_lib_bytes);
|
||||
println!(" codemap bytes: {}", codemap_bytes);
|
||||
@ -1456,6 +1463,12 @@ impl<'a, 'b: 'a, 'tcx: 'b> IsolatedEncoder<'a, 'b, 'tcx> {
|
||||
self.lazy_seq_ref(deps.iter().map(|&(_, ref dep)| dep))
|
||||
}
|
||||
|
||||
fn encode_lib_features(&mut self, _: ()) -> LazySeq<(ast::Name, Option<ast::Name>)> {
|
||||
let tcx = self.tcx;
|
||||
let lib_features = tcx.lib_features();
|
||||
self.lazy_seq(lib_features.iter())
|
||||
}
|
||||
|
||||
fn encode_lang_items(&mut self, _: ()) -> LazySeq<(DefIndex, usize)> {
|
||||
let tcx = self.tcx;
|
||||
let lang_items = tcx.lang_items();
|
||||
|
@ -198,6 +198,7 @@ pub struct CrateRoot {
|
||||
|
||||
pub crate_deps: LazySeq<CrateDep>,
|
||||
pub dylib_dependency_formats: LazySeq<Option<LinkagePreference>>,
|
||||
pub lib_features: LazySeq<(Symbol, Option<Symbol>)>,
|
||||
pub lang_items: LazySeq<(DefIndex, usize)>,
|
||||
pub lang_items_missing: LazySeq<lang_items::LangItem>,
|
||||
pub native_libraries: LazySeq<NativeLibrary>,
|
||||
|
@ -374,6 +374,18 @@ and likely to change in the future.
|
||||
|
||||
"##,
|
||||
|
||||
E0635: r##"
|
||||
The `#![feature]` attribute specified an unknown feature.
|
||||
|
||||
Erroneous code example:
|
||||
|
||||
```compile_fail,E0635
|
||||
#![feature(nonexistent_rust_feature)] // error: unknown feature
|
||||
```
|
||||
|
||||
"##,
|
||||
|
||||
|
||||
}
|
||||
|
||||
register_diagnostics! {
|
||||
|
Loading…
x
Reference in New Issue
Block a user