2015-09-25 15:25:59 +09:00
|
|
|
// Copyright 2015 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.
|
|
|
|
|
2017-06-03 18:14:29 +02:00
|
|
|
//! This module implements some validity checks for attributes.
|
|
|
|
//! In particular it verifies that `#[inline]` and `#[repr]` attributes are
|
|
|
|
//! attached to items that actually support them and if there are
|
|
|
|
//! conflicts between multiple such attributes attached to the same
|
|
|
|
//! item.
|
|
|
|
|
2018-01-08 13:43:42 -08:00
|
|
|
use hir;
|
|
|
|
use hir::intravisit::{self, Visitor, NestedVisitorMap};
|
2018-10-11 19:36:51 +02:00
|
|
|
use ty::TyCtxt;
|
|
|
|
use std::fmt::{self, Display};
|
|
|
|
use syntax_pos::Span;
|
2015-09-25 15:25:59 +09:00
|
|
|
|
|
|
|
#[derive(Copy, Clone, PartialEq)]
|
2018-10-11 19:36:51 +02:00
|
|
|
pub(crate) enum Target {
|
|
|
|
ExternCrate,
|
|
|
|
Use,
|
|
|
|
Static,
|
|
|
|
Const,
|
2015-09-25 15:25:59 +09:00
|
|
|
Fn,
|
2018-10-11 19:36:51 +02:00
|
|
|
Closure,
|
|
|
|
Mod,
|
|
|
|
ForeignMod,
|
|
|
|
GlobalAsm,
|
|
|
|
Ty,
|
|
|
|
Existential,
|
|
|
|
Enum,
|
2015-09-25 15:25:59 +09:00
|
|
|
Struct,
|
2016-08-10 21:00:17 +03:00
|
|
|
Union,
|
2018-10-11 19:36:51 +02:00
|
|
|
Trait,
|
|
|
|
TraitAlias,
|
|
|
|
Impl,
|
2018-03-22 08:57:26 -07:00
|
|
|
Expression,
|
|
|
|
Statement,
|
2018-10-11 19:36:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Display for Target {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
write!(f, "{}", match *self {
|
|
|
|
Target::ExternCrate => "extern crate",
|
|
|
|
Target::Use => "use",
|
|
|
|
Target::Static => "static item",
|
|
|
|
Target::Const => "constant item",
|
|
|
|
Target::Fn => "function",
|
|
|
|
Target::Closure => "closure",
|
|
|
|
Target::Mod => "module",
|
|
|
|
Target::ForeignMod => "foreign module",
|
|
|
|
Target::GlobalAsm => "global asm",
|
|
|
|
Target::Ty => "type alias",
|
|
|
|
Target::Existential => "existential type",
|
|
|
|
Target::Enum => "enum",
|
|
|
|
Target::Struct => "struct",
|
|
|
|
Target::Union => "union",
|
|
|
|
Target::Trait => "trait",
|
|
|
|
Target::TraitAlias => "trait alias",
|
|
|
|
Target::Impl => "item",
|
|
|
|
Target::Expression => "expression",
|
|
|
|
Target::Statement => "statement",
|
|
|
|
})
|
|
|
|
}
|
2015-09-25 15:25:59 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Target {
|
2018-10-11 19:36:51 +02:00
|
|
|
pub(crate) fn from_item(item: &hir::Item) -> Target {
|
2015-09-25 15:25:59 +09:00
|
|
|
match item.node {
|
2018-10-11 19:36:51 +02:00
|
|
|
hir::ItemKind::ExternCrate(..) => Target::ExternCrate,
|
|
|
|
hir::ItemKind::Use(..) => Target::Use,
|
|
|
|
hir::ItemKind::Static(..) => Target::Static,
|
|
|
|
hir::ItemKind::Const(..) => Target::Const,
|
2018-07-11 23:36:06 +08:00
|
|
|
hir::ItemKind::Fn(..) => Target::Fn,
|
2018-10-11 19:36:51 +02:00
|
|
|
hir::ItemKind::Mod(..) => Target::Mod,
|
|
|
|
hir::ItemKind::ForeignMod(..) => Target::ForeignMod,
|
|
|
|
hir::ItemKind::GlobalAsm(..) => Target::GlobalAsm,
|
|
|
|
hir::ItemKind::Ty(..) => Target::Ty,
|
|
|
|
hir::ItemKind::Existential(..) => Target::Existential,
|
|
|
|
hir::ItemKind::Enum(..) => Target::Enum,
|
2018-07-11 23:36:06 +08:00
|
|
|
hir::ItemKind::Struct(..) => Target::Struct,
|
|
|
|
hir::ItemKind::Union(..) => Target::Union,
|
2018-08-25 00:54:41 -07:00
|
|
|
hir::ItemKind::Trait(..) => Target::Trait,
|
2018-10-11 19:36:51 +02:00
|
|
|
hir::ItemKind::TraitAlias(..) => Target::TraitAlias,
|
|
|
|
hir::ItemKind::Impl(..) => Target::Impl,
|
2015-09-25 15:25:59 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-08 13:43:42 -08:00
|
|
|
struct CheckAttrVisitor<'a, 'tcx: 'a> {
|
|
|
|
tcx: TyCtxt<'a, 'tcx, 'tcx>,
|
2015-09-25 15:25:59 +09:00
|
|
|
}
|
|
|
|
|
2018-01-08 13:43:42 -08:00
|
|
|
impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
|
2017-06-03 18:14:29 +02:00
|
|
|
/// Check any attribute.
|
2018-01-08 13:43:42 -08:00
|
|
|
fn check_attributes(&self, item: &hir::Item, target: Target) {
|
2018-06-01 10:20:00 -07:00
|
|
|
if target == Target::Fn || target == Target::Const {
|
2018-05-08 16:10:16 +03:00
|
|
|
self.tcx.codegen_fn_attrs(self.tcx.hir.local_def_id(item.id));
|
2018-03-05 08:12:13 -08:00
|
|
|
} else if let Some(a) = item.attrs.iter().find(|a| a.check_name("target_feature")) {
|
|
|
|
self.tcx.sess.struct_span_err(a.span, "attribute should be applied to a function")
|
|
|
|
.span_label(item.span, "not a function")
|
|
|
|
.emit();
|
|
|
|
}
|
2018-01-08 13:43:42 -08:00
|
|
|
|
2018-01-01 21:42:12 +01:00
|
|
|
for attr in &item.attrs {
|
2018-02-10 14:28:17 -08:00
|
|
|
if attr.check_name("inline") {
|
2018-03-22 08:57:26 -07:00
|
|
|
self.check_inline(attr, &item.span, target)
|
2018-03-24 18:07:18 +00:00
|
|
|
} else if attr.check_name("non_exhaustive") {
|
|
|
|
self.check_non_exhaustive(attr, item, target)
|
2018-08-25 00:54:41 -07:00
|
|
|
} else if attr.check_name("marker") {
|
|
|
|
self.check_marker(attr, item, target)
|
2017-06-03 18:14:29 +02:00
|
|
|
}
|
|
|
|
}
|
2018-01-08 13:43:42 -08:00
|
|
|
|
2018-01-01 21:42:12 +01:00
|
|
|
self.check_repr(item, target);
|
2018-04-30 07:43:22 +02:00
|
|
|
self.check_used(item, target);
|
2017-06-03 18:14:29 +02:00
|
|
|
}
|
|
|
|
|
2018-04-27 07:20:46 +02:00
|
|
|
/// Check if an `#[inline]` is applied to a function or a closure.
|
2018-03-22 08:57:26 -07:00
|
|
|
fn check_inline(&self, attr: &hir::Attribute, span: &Span, target: Target) {
|
2018-04-27 07:20:46 +02:00
|
|
|
if target != Target::Fn && target != Target::Closure {
|
2018-01-08 13:43:42 -08:00
|
|
|
struct_span_err!(self.tcx.sess,
|
|
|
|
attr.span,
|
|
|
|
E0518,
|
2018-04-27 07:20:46 +02:00
|
|
|
"attribute should be applied to function or closure")
|
|
|
|
.span_label(*span, "not a function or closure")
|
2016-08-30 11:44:25 +10:00
|
|
|
.emit();
|
2015-09-25 15:25:59 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-24 18:07:18 +00:00
|
|
|
/// Check if the `#[non_exhaustive]` attribute on an `item` is valid.
|
|
|
|
fn check_non_exhaustive(&self, attr: &hir::Attribute, item: &hir::Item, target: Target) {
|
|
|
|
match target {
|
|
|
|
Target::Struct | Target::Enum => { /* Valid */ },
|
|
|
|
_ => {
|
|
|
|
struct_span_err!(self.tcx.sess,
|
|
|
|
attr.span,
|
2018-06-10 14:04:48 +02:00
|
|
|
E0701,
|
2018-03-24 18:07:18 +00:00
|
|
|
"attribute can only be applied to a struct or enum")
|
|
|
|
.span_label(item.span, "not a struct or enum")
|
|
|
|
.emit();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if attr.meta_item_list().is_some() || attr.value_str().is_some() {
|
|
|
|
struct_span_err!(self.tcx.sess,
|
|
|
|
attr.span,
|
2018-06-10 14:04:48 +02:00
|
|
|
E0702,
|
2018-03-24 18:07:18 +00:00
|
|
|
"attribute should be empty")
|
|
|
|
.span_label(item.span, "not empty")
|
|
|
|
.emit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-25 00:54:41 -07:00
|
|
|
/// Check if the `#[marker]` attribute on an `item` is valid.
|
|
|
|
fn check_marker(&self, attr: &hir::Attribute, item: &hir::Item, target: Target) {
|
|
|
|
match target {
|
|
|
|
Target::Trait => { /* Valid */ },
|
|
|
|
_ => {
|
2018-09-03 02:17:20 -07:00
|
|
|
self.tcx.sess
|
|
|
|
.struct_span_err(attr.span, "attribute can only be applied to a trait")
|
2018-08-25 00:54:41 -07:00
|
|
|
.span_label(item.span, "not a trait")
|
|
|
|
.emit();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-03 02:17:20 -07:00
|
|
|
if !attr.is_word() {
|
|
|
|
self.tcx.sess
|
|
|
|
.struct_span_err(attr.span, "attribute should be empty")
|
2018-08-25 00:54:41 -07:00
|
|
|
.emit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-01 21:42:12 +01:00
|
|
|
/// Check if the `#[repr]` attributes on `item` are valid.
|
2018-01-08 13:43:42 -08:00
|
|
|
fn check_repr(&self, item: &hir::Item, target: Target) {
|
2018-01-01 21:42:12 +01:00
|
|
|
// Extract the names of all repr hints, e.g., [foo, bar, align] for:
|
|
|
|
// ```
|
|
|
|
// #[repr(foo)]
|
|
|
|
// #[repr(bar, align(8))]
|
|
|
|
// ```
|
|
|
|
let hints: Vec<_> = item.attrs
|
|
|
|
.iter()
|
2018-01-30 14:53:01 +09:00
|
|
|
.filter(|attr| attr.name() == "repr")
|
2018-01-01 21:42:12 +01:00
|
|
|
.filter_map(|attr| attr.meta_item_list())
|
2018-09-12 12:31:11 +02:00
|
|
|
.flatten()
|
2018-01-01 21:42:12 +01:00
|
|
|
.collect();
|
2016-08-19 18:58:14 -07:00
|
|
|
|
2017-11-20 12:26:54 -05:00
|
|
|
let mut int_reprs = 0;
|
|
|
|
let mut is_c = false;
|
|
|
|
let mut is_simd = false;
|
2018-01-03 17:43:30 +01:00
|
|
|
let mut is_transparent = false;
|
2017-01-15 09:49:29 +11:00
|
|
|
|
2018-01-01 21:42:12 +01:00
|
|
|
for hint in &hints {
|
|
|
|
let name = if let Some(name) = hint.name() {
|
|
|
|
name
|
|
|
|
} else {
|
|
|
|
// Invalid repr hint like repr(42). We don't check for unrecognized hints here
|
|
|
|
// (libsyntax does that), so just ignore it.
|
|
|
|
continue;
|
2016-08-19 18:58:14 -07:00
|
|
|
};
|
|
|
|
|
2018-01-01 21:42:12 +01:00
|
|
|
let (article, allowed_targets) = match &*name.as_str() {
|
2015-09-25 15:25:59 +09:00
|
|
|
"C" => {
|
2017-11-20 12:26:54 -05:00
|
|
|
is_c = true;
|
2016-08-10 21:00:17 +03:00
|
|
|
if target != Target::Struct &&
|
|
|
|
target != Target::Union &&
|
|
|
|
target != Target::Enum {
|
2018-01-01 21:42:12 +01:00
|
|
|
("a", "struct, enum or union")
|
2015-11-09 22:13:55 +05:30
|
|
|
} else {
|
|
|
|
continue
|
2015-09-25 15:25:59 +09:00
|
|
|
}
|
|
|
|
}
|
2016-07-03 15:24:27 +12:00
|
|
|
"packed" => {
|
2016-08-10 21:00:17 +03:00
|
|
|
if target != Target::Struct &&
|
|
|
|
target != Target::Union {
|
2018-01-01 21:42:12 +01:00
|
|
|
("a", "struct or union")
|
2016-07-03 15:24:27 +12:00
|
|
|
} else {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"simd" => {
|
2017-11-20 12:26:54 -05:00
|
|
|
is_simd = true;
|
2015-09-25 15:25:59 +09:00
|
|
|
if target != Target::Struct {
|
2018-01-01 21:42:12 +01:00
|
|
|
("a", "struct")
|
2015-11-09 22:13:55 +05:30
|
|
|
} else {
|
|
|
|
continue
|
2015-09-25 15:25:59 +09:00
|
|
|
}
|
|
|
|
}
|
2017-01-15 09:49:29 +11:00
|
|
|
"align" => {
|
2017-07-17 00:44:13 +10:00
|
|
|
if target != Target::Struct &&
|
|
|
|
target != Target::Union {
|
2018-01-01 21:42:12 +01:00
|
|
|
("a", "struct or union")
|
2017-01-15 09:49:29 +11:00
|
|
|
} else {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2018-01-03 17:43:30 +01:00
|
|
|
"transparent" => {
|
|
|
|
is_transparent = true;
|
|
|
|
if target != Target::Struct {
|
|
|
|
("a", "struct")
|
|
|
|
} else {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2018-09-12 12:31:11 +02:00
|
|
|
"i8" | "u8" | "i16" | "u16" |
|
2015-09-25 15:25:59 +09:00
|
|
|
"i32" | "u32" | "i64" | "u64" |
|
|
|
|
"isize" | "usize" => {
|
2017-11-20 12:26:54 -05:00
|
|
|
int_reprs += 1;
|
2015-09-25 15:25:59 +09:00
|
|
|
if target != Target::Enum {
|
2018-01-01 21:42:12 +01:00
|
|
|
("an", "enum")
|
2015-11-09 22:13:55 +05:30
|
|
|
} else {
|
|
|
|
continue
|
2015-09-25 15:25:59 +09:00
|
|
|
}
|
|
|
|
}
|
2015-11-09 22:13:55 +05:30
|
|
|
_ => continue,
|
|
|
|
};
|
2018-03-22 08:57:26 -07:00
|
|
|
self.emit_repr_error(
|
|
|
|
hint.span,
|
|
|
|
item.span,
|
|
|
|
&format!("attribute should be applied to {}", allowed_targets),
|
|
|
|
&format!("not {} {}", article, allowed_targets),
|
|
|
|
)
|
2015-09-25 15:25:59 +09:00
|
|
|
}
|
2017-11-20 12:26:54 -05:00
|
|
|
|
2018-01-03 17:43:30 +01:00
|
|
|
// Just point at all repr hints if there are any incompatibilities.
|
|
|
|
// This is not ideal, but tracking precisely which ones are at fault is a huge hassle.
|
|
|
|
let hint_spans = hints.iter().map(|hint| hint.span);
|
|
|
|
|
|
|
|
// Error on repr(transparent, <anything else>).
|
|
|
|
if is_transparent && hints.len() > 1 {
|
|
|
|
let hint_spans: Vec<_> = hint_spans.clone().collect();
|
|
|
|
span_err!(self.tcx.sess, hint_spans, E0692,
|
|
|
|
"transparent struct cannot have other repr hints");
|
|
|
|
}
|
2017-11-20 12:26:54 -05:00
|
|
|
// Warn on repr(u8, u16), repr(C, simd), and c-like-enum-repr(C, u8)
|
|
|
|
if (int_reprs > 1)
|
|
|
|
|| (is_simd && is_c)
|
|
|
|
|| (int_reprs == 1 && is_c && is_c_like_enum(item)) {
|
2018-01-03 17:43:30 +01:00
|
|
|
let hint_spans: Vec<_> = hint_spans.collect();
|
|
|
|
span_warn!(self.tcx.sess, hint_spans, E0566,
|
2016-07-03 15:24:27 +12:00
|
|
|
"conflicting representation hints");
|
|
|
|
}
|
2015-09-25 15:25:59 +09:00
|
|
|
}
|
2018-03-22 08:57:26 -07:00
|
|
|
|
|
|
|
fn emit_repr_error(
|
|
|
|
&self,
|
|
|
|
hint_span: Span,
|
|
|
|
label_span: Span,
|
|
|
|
hint_message: &str,
|
|
|
|
label_message: &str,
|
|
|
|
) {
|
|
|
|
struct_span_err!(self.tcx.sess, hint_span, E0517, "{}", hint_message)
|
|
|
|
.span_label(label_span, label_message)
|
|
|
|
.emit();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_stmt_attributes(&self, stmt: &hir::Stmt) {
|
|
|
|
// When checking statements ignore expressions, they will be checked later
|
2018-07-11 18:54:37 +08:00
|
|
|
if let hir::StmtKind::Decl(_, _) = stmt.node {
|
2018-03-22 08:57:26 -07:00
|
|
|
for attr in stmt.node.attrs() {
|
|
|
|
if attr.check_name("inline") {
|
|
|
|
self.check_inline(attr, &stmt.span, Target::Statement);
|
|
|
|
}
|
|
|
|
if attr.check_name("repr") {
|
|
|
|
self.emit_repr_error(
|
|
|
|
attr.span,
|
|
|
|
stmt.span,
|
2018-07-28 14:40:32 +02:00
|
|
|
"attribute should not be applied to a statement",
|
|
|
|
"not a struct, enum or union",
|
2018-03-22 08:57:26 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_expr_attributes(&self, expr: &hir::Expr) {
|
2018-04-27 07:20:46 +02:00
|
|
|
let target = match expr.node {
|
2018-07-11 20:05:29 +08:00
|
|
|
hir::ExprKind::Closure(..) => Target::Closure,
|
2018-04-27 07:20:46 +02:00
|
|
|
_ => Target::Expression,
|
|
|
|
};
|
2018-03-22 08:57:26 -07:00
|
|
|
for attr in expr.attrs.iter() {
|
|
|
|
if attr.check_name("inline") {
|
2018-04-27 07:20:46 +02:00
|
|
|
self.check_inline(attr, &expr.span, target);
|
2018-03-22 08:57:26 -07:00
|
|
|
}
|
|
|
|
if attr.check_name("repr") {
|
|
|
|
self.emit_repr_error(
|
|
|
|
attr.span,
|
|
|
|
expr.span,
|
2018-07-28 14:40:32 +02:00
|
|
|
"attribute should not be applied to an expression",
|
|
|
|
"not defining a struct, enum or union",
|
2018-03-22 08:57:26 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-04-30 07:43:22 +02:00
|
|
|
|
|
|
|
fn check_used(&self, item: &hir::Item, target: Target) {
|
|
|
|
for attr in &item.attrs {
|
2018-04-17 15:33:39 +02:00
|
|
|
if attr.name() == "used" && target != Target::Static {
|
2018-04-30 07:43:22 +02:00
|
|
|
self.tcx.sess
|
|
|
|
.span_err(attr.span, "attribute must be applied to a `static` variable");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-09-25 15:25:59 +09:00
|
|
|
}
|
|
|
|
|
2018-01-08 13:43:42 -08:00
|
|
|
impl<'a, 'tcx> Visitor<'tcx> for CheckAttrVisitor<'a, 'tcx> {
|
|
|
|
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
|
2018-03-22 08:57:26 -07:00
|
|
|
NestedVisitorMap::OnlyBodies(&self.tcx.hir)
|
2018-01-08 13:43:42 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn visit_item(&mut self, item: &'tcx hir::Item) {
|
2015-09-25 15:25:59 +09:00
|
|
|
let target = Target::from_item(item);
|
2018-01-01 21:42:12 +01:00
|
|
|
self.check_attributes(item, target);
|
2018-03-22 08:57:26 -07:00
|
|
|
intravisit::walk_item(self, item)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt) {
|
|
|
|
self.check_stmt_attributes(stmt);
|
|
|
|
intravisit::walk_stmt(self, stmt)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
|
|
|
|
self.check_expr_attributes(expr);
|
|
|
|
intravisit::walk_expr(self, expr)
|
2015-09-25 15:25:59 +09:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-08 13:43:42 -08:00
|
|
|
pub fn check_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
|
|
|
|
let mut checker = CheckAttrVisitor { tcx };
|
|
|
|
tcx.hir.krate().visit_all_item_likes(&mut checker.as_deep_visitor());
|
2015-09-25 15:25:59 +09:00
|
|
|
}
|
2017-11-20 12:26:54 -05:00
|
|
|
|
2018-01-08 13:43:42 -08:00
|
|
|
fn is_c_like_enum(item: &hir::Item) -> bool {
|
2018-07-11 23:36:06 +08:00
|
|
|
if let hir::ItemKind::Enum(ref def, _) = item.node {
|
2017-11-20 12:26:54 -05:00
|
|
|
for variant in &def.variants {
|
|
|
|
match variant.node.data {
|
2018-01-08 13:43:42 -08:00
|
|
|
hir::VariantData::Unit(_) => { /* continue */ }
|
2017-11-20 12:26:54 -05:00
|
|
|
_ => { return false; }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|