use std::iter;
use std::ops::Deref;
use std::rc::Rc;
use bstr::BString;
use indexmap::{IndexMap, IndexSet};
use itertools::Itertools;
use protobuf::reflect::{
EnumDescriptor, FieldDescriptor, MessageDescriptor, ReflectMapRef,
ReflectRepeatedRef, ReflectValueRef, RuntimeFieldType, RuntimeType,
};
use protobuf::reflect::{EnumValueDescriptor, Syntax};
use protobuf::{MessageDyn, MessageField};
use serde::{Deserialize, Serialize};
use crate::modules::Module;
use crate::modules::protos::yara as protos;
use crate::modules::protos::yara::enum_value_options::Value as EnumValue;
use crate::modules::protos::yara::exts::{
enum_options, enum_value, field_options, message_options, module_options,
};
use crate::symbols::{Symbol, SymbolLookup};
use crate::types::{Array, Map, StringConstraint, TypeValue};
use crate::wasm::WasmExport;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct AclEntry {
pub error_title: String,
pub error_label: String,
pub accept_if: Vec<String>,
pub reject_if: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub(crate) struct DeprecationNotice {
pub text: String,
pub help: Option<String>,
pub replacement: Option<String>,
}
impl From<MessageField<protos::DeprecationNotice>> for DeprecationNotice {
fn from(value: MessageField<protos::DeprecationNotice>) -> Self {
Self {
text: value.text.clone().expect("the `text` field is required"),
help: value.help.clone(),
replacement: value.replacement.clone(),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct StructField {
pub number: u64,
pub type_value: TypeValue,
pub acl: Option<Vec<AclEntry>>,
pub deprecation_notice: Option<DeprecationNotice>,
pub doc: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub(crate) struct Struct {
fields: IndexMap<String, StructField>,
is_root: bool,
protobuf_type_name: Option<String>,
}
impl SymbolLookup for Struct {
fn lookup(&self, ident: &str) -> Option<Symbol> {
let (field, index) = self.field_and_index_by_name(ident)?;
Some(Symbol::Field {
index,
is_root: self.is_root,
type_value: field.type_value.clone(),
acl: field.acl.clone(),
deprecation_notice: field.deprecation_notice.clone(),
})
}
}
impl Struct {
pub fn new() -> Self {
Self {
fields: IndexMap::new(),
is_root: false,
protobuf_type_name: None,
}
}
pub fn make_root(mut self) -> Self {
self.is_root = true;
self
}
pub fn protobuf_type_name(&self) -> Option<&str> {
self.protobuf_type_name.as_deref()
}
pub fn add_field<N: Into<String>>(
&mut self,
name: N,
value: TypeValue,
) -> Option<StructField> {
let name = name.into();
if let Some(dot) = name.find('.') {
let field = self
.field_entry_by_name(name[0..dot].to_owned())
.or_insert_with(|| StructField {
type_value: TypeValue::Struct(Rc::new(Struct::new())),
number: 0,
acl: None,
deprecation_notice: None,
doc: None,
});
if let TypeValue::Struct(ref mut s) = field.type_value {
let s = Rc::<Struct>::get_mut(s).unwrap_or_else(|| {
panic!(
"`add_field` was called while an `Rc` or `Weak` pointer points to field `{}`",
(&name[0..dot])
)
});
s.add_field(&name[dot + 1..], value)
} else {
panic!("field `{}` is not a struct", &name[0..dot])
}
} else {
self.fields.insert(
name,
StructField {
type_value: value,
number: 0,
acl: None,
deprecation_notice: None,
doc: None,
},
)
}
}
fn add_enum_fields(&mut self, enum_descriptor: &EnumDescriptor) {
let mut enclosing_msg = enum_descriptor.enclosing_message();
let mut path = Vec::new();
if !Self::enum_is_inline(enum_descriptor) {
path.push(Self::enum_name(enum_descriptor));
}
while let Some(msg) = enclosing_msg {
if !Self::is_module_root(&msg) {
path.push(Self::message_name(&msg));
}
enclosing_msg = msg.enclosing_message()
}
let path = path.iter().rev().join(".");
for item in enum_descriptor.values() {
let field_name = if path.is_empty() {
item.name().to_owned()
} else {
format!("{}.{}", path, item.name())
};
self.add_field(field_name, Self::enum_value(&item).into());
}
}
#[inline]
pub fn has_field(&self, name: &str) -> bool {
self.field_by_name(name).is_some()
}
#[inline]
pub fn field_by_index(&self, index: usize) -> Option<&StructField> {
self.fields.get_index(index).map(|(_, v)| v)
}
#[inline]
pub fn field_by_name(&self, name: &str) -> Option<&StructField> {
self.fields.get(name)
}
#[inline]
pub fn field_and_index_by_name(
&self,
name: &str,
) -> Option<(&StructField, usize)> {
self.fields.get_full(name).map(|(index, _, field)| (field, index))
}
#[inline]
pub fn field_by_name_mut(
&mut self,
name: &str,
) -> Option<&mut StructField> {
self.fields.get_mut(name)
}
#[inline]
pub fn field_entry_by_name(
&mut self,
name: String,
) -> indexmap::map::Entry<'_, String, StructField> {
self.fields.entry(name)
}
pub fn enum_substructures<F>(&mut self, f: &mut F)
where
F: FnMut(&mut Struct),
{
f(self);
for field in self.fields.values_mut() {
match &mut field.type_value {
TypeValue::Struct(s) => {
Rc::<Struct>::get_mut(s).unwrap().enum_substructures(f);
}
TypeValue::Array(a) => {
Rc::<Array>::get_mut(a).unwrap().enum_substructures(f);
}
_ => {}
}
}
}
pub fn from_proto_descriptor_and_msg(
msg_descriptor: &MessageDescriptor,
msg: Option<&dyn MessageDyn>,
generate_fields_for_enums: bool,
) -> Rc<Self> {
let syntax = msg_descriptor.file_descriptor().syntax();
let mut fields = Vec::new();
for fd in msg_descriptor.fields() {
if Self::ignore_field(&fd) {
continue;
}
let field_ty = fd.runtime_field_type();
let number = fd.number() as u64;
let name = Self::field_name(&fd);
let mut value = match field_ty {
RuntimeFieldType::Singular(ty) => Self::new_value(
&ty,
msg.and_then(|msg| fd.get_singular(msg)),
generate_fields_for_enums,
syntax,
),
RuntimeFieldType::Repeated(ty) => Self::new_array(
&ty,
msg.map(|msg| fd.get_repeated(msg)),
generate_fields_for_enums,
),
RuntimeFieldType::Map(key_ty, value_ty) => Self::new_map(
&key_ty,
&value_ty,
msg.map(|msg| fd.get_map(msg)),
generate_fields_for_enums,
syntax,
),
};
if Self::lowercase(&fd) {
if let TypeValue::String { constraints, .. } = &mut value {
constraints
.get_or_insert_default()
.push(StringConstraint::Lowercase);
} else {
panic!(
"`lowercase = true` in non-string field: {}",
fd.full_name()
)
}
}
fields.push((
name,
StructField {
type_value: value,
acl: Self::acl(&fd),
deprecation_notice: Self::deprecation_notice(&fd),
number,
doc: Self::field_doc(msg_descriptor.full_name(), number),
},
));
}
fields.sort_by(|a, b| a.1.number.cmp(&b.1.number));
let mut field_index = IndexMap::new();
for (name, field) in fields {
if field_index.insert(name, field).is_some() {
panic!(
"duplicate field name in message `{}`",
msg_descriptor.name()
)
}
}
let mut new_struct = Self {
fields: field_index,
is_root: false,
protobuf_type_name: Some(msg_descriptor.full_name().to_string()),
};
if generate_fields_for_enums && Self::is_module_root(msg_descriptor) {
let mut enums = IndexSet::new();
Self::nested_enums(msg_descriptor, &mut |enum_| {
enums.insert(enum_);
});
for enum_ in msg_descriptor.file_descriptor().enums() {
enums.insert(enum_);
}
for enum_ in &enums {
new_struct.add_enum_fields(enum_);
}
}
Rc::new(new_struct)
}
fn is_module_root(msg_descriptor: &MessageDescriptor) -> bool {
let file_descriptor = msg_descriptor.file_descriptor();
if let Some(options) =
module_options.get(&file_descriptor.proto().options)
{
options.root_message.unwrap() == msg_descriptor.full_name()
} else {
false
}
}
fn nested_enums<F>(msg_descriptor: &MessageDescriptor, f: &mut F)
where
F: FnMut(EnumDescriptor),
{
for e in msg_descriptor.nested_enums() {
f(e);
}
for fd in msg_descriptor.fields() {
if Self::ignore_field(&fd) {
continue;
}
match fd.runtime_field_type() {
RuntimeFieldType::Singular(RuntimeType::Enum(e)) => f(e),
RuntimeFieldType::Repeated(RuntimeType::Enum(e)) => f(e),
RuntimeFieldType::Singular(RuntimeType::Message(m)) => {
if m.full_name() == msg_descriptor.full_name() {
panic!("recursive protobuf type: {}", m.full_name())
}
Self::nested_enums(&m, f);
}
RuntimeFieldType::Repeated(RuntimeType::Message(m)) => {
if m.full_name() == msg_descriptor.full_name() {
panic!("recursive protobuf type: {}", m.full_name())
}
Self::nested_enums(&m, f);
}
_ => {}
}
}
}
fn message_name(msg_descriptor: &MessageDescriptor) -> String {
message_options
.get(&msg_descriptor.proto().options)
.and_then(|options| options.name)
.unwrap_or_else(|| msg_descriptor.name().to_owned())
}
fn enum_name(enum_descriptor: &EnumDescriptor) -> String {
enum_options
.get(&enum_descriptor.proto().options)
.and_then(|options| options.name)
.unwrap_or_else(|| enum_descriptor.name().to_owned())
}
fn enum_is_inline(enum_descriptor: &EnumDescriptor) -> bool {
enum_options
.get(&enum_descriptor.proto().options)
.and_then(|options| options.inline)
.unwrap_or(false)
}
pub(crate) fn enum_value(
enum_value_descriptor: &EnumValueDescriptor,
) -> EnumValue {
enum_value
.get(&enum_value_descriptor.proto().options)
.and_then(|options| options.value)
.unwrap_or_else(|| {
EnumValue::I64(enum_value_descriptor.value() as i64)
})
}
pub(crate) fn fields(&self) -> impl Iterator<Item = (&str, &StructField)> {
self.fields.iter().map(move |(name, field)| (name.as_str(), field))
}
pub(crate) fn enum_value_i64(
enum_value_descriptor: &EnumValueDescriptor,
) -> Option<i64> {
match Self::enum_value(enum_value_descriptor) {
EnumValue::I64(i) => Some(i),
EnumValue::F64(_) => None,
}
}
fn field_name(field_descriptor: &FieldDescriptor) -> String {
field_options
.get(&field_descriptor.proto().options)
.and_then(|options| options.name)
.unwrap_or_else(|| field_descriptor.name().to_owned())
}
fn ignore_field(field_descriptor: &FieldDescriptor) -> bool {
field_options
.get(&field_descriptor.proto().options)
.and_then(|options| options.ignore)
.unwrap_or(false)
}
fn lowercase(field_descriptor: &FieldDescriptor) -> bool {
field_options
.get(&field_descriptor.proto().options)
.and_then(|options| options.lowercase)
.unwrap_or(false)
}
fn deprecation_notice(
field_descriptor: &FieldDescriptor,
) -> Option<DeprecationNotice> {
field_options
.get(&field_descriptor.proto().options)
.filter(|options| options.deprecation_notice.is_some())
.map(|options| options.deprecation_notice.into())
}
fn acl(field_descriptor: &FieldDescriptor) -> Option<Vec<AclEntry>> {
field_options
.get(&field_descriptor.proto().options)
.map(|options| options.acl)
.filter(|acl| !acl.is_empty())
.map(|acl| {
acl.into_iter()
.map(|entry| AclEntry {
accept_if: entry.accept_if,
reject_if: entry.reject_if,
error_title: entry
.error_title
.expect("the `error_title` field is required"),
error_label: entry
.error_label
.expect("the `error_label` field is required"),
})
.collect()
})
}
fn field_doc(msg_name: &str, field_number: u64) -> Option<String> {
use crate::modules::field_docs::FIELD_DOCS;
let idx = FIELD_DOCS
.binary_search_by(|&(name, number, _)| match name.cmp(msg_name) {
std::cmp::Ordering::Equal => number.cmp(&field_number),
ord => ord,
})
.ok()?;
Some(FIELD_DOCS[idx].2.to_string())
}
fn new_value(
ty: &RuntimeType,
value: Option<ReflectValueRef>,
enum_as_fields: bool,
syntax: Syntax,
) -> TypeValue {
match ty {
RuntimeType::I32
| RuntimeType::I64
| RuntimeType::U32
| RuntimeType::U64
| RuntimeType::Enum(_) => {
if let Some(v) = value {
TypeValue::var_integer_from(Self::value_as_i64(v))
} else if syntax == Syntax::Proto3 {
TypeValue::var_integer_from(0)
} else {
TypeValue::unknown_integer()
}
}
RuntimeType::F32 | RuntimeType::F64 => {
if let Some(v) = value {
TypeValue::var_float_from(Self::value_as_f64(v))
} else if syntax == Syntax::Proto3 {
TypeValue::var_float_from(0_f64)
} else {
TypeValue::unknown_float()
}
}
RuntimeType::Bool => {
if let Some(v) = value {
TypeValue::var_bool_from(Self::value_as_bool(v))
} else if syntax == Syntax::Proto3 {
TypeValue::var_bool_from(false)
} else {
TypeValue::unknown_bool()
}
}
RuntimeType::String | RuntimeType::VecU8 => {
if let Some(v) = value {
TypeValue::var_string_from(Self::value_as_string(v))
} else if syntax == Syntax::Proto3 {
TypeValue::var_string_from(b"")
} else {
TypeValue::unknown_string()
}
}
RuntimeType::Message(msg_descriptor) => {
let structure = if let Some(value) = value {
Self::from_proto_descriptor_and_value(
msg_descriptor,
value,
enum_as_fields,
)
} else {
Self::from_proto_descriptor_and_msg(
msg_descriptor,
None,
enum_as_fields,
)
};
TypeValue::Struct(structure)
}
}
}
fn new_array(
ty: &RuntimeType,
repeated: Option<ReflectRepeatedRef>,
enum_as_fields: bool,
) -> TypeValue {
let array = match ty {
RuntimeType::I32 => {
if let Some(repeated) = repeated {
Array::Integers(
repeated
.into_iter()
.map(|value| value.to_i32().unwrap() as i64)
.collect(),
)
} else {
Array::Integers(vec![])
}
}
RuntimeType::I64 => {
if let Some(repeated) = repeated {
Array::Integers(
repeated
.into_iter()
.map(|value| value.to_i64().unwrap())
.collect(),
)
} else {
Array::Integers(vec![])
}
}
RuntimeType::U32 => {
if let Some(repeated) = repeated {
Array::Integers(
repeated
.into_iter()
.map(|value| value.to_u32().unwrap() as i64)
.collect(),
)
} else {
Array::Integers(vec![])
}
}
RuntimeType::U64 => {
todo!()
}
RuntimeType::F32 => {
if let Some(repeated) = repeated {
Array::Floats(
repeated
.into_iter()
.map(|value| value.to_f32().unwrap() as f64)
.collect(),
)
} else {
Array::Floats(vec![])
}
}
RuntimeType::F64 => {
if let Some(repeated) = repeated {
Array::Floats(
repeated
.into_iter()
.map(|value| value.to_f64().unwrap())
.collect(),
)
} else {
Array::Floats(vec![])
}
}
RuntimeType::Bool => {
if let Some(repeated) = repeated {
Array::Bools(
repeated
.into_iter()
.map(|value| value.to_bool().unwrap())
.collect(),
)
} else {
Array::Bools(vec![])
}
}
RuntimeType::String => {
if let Some(repeated) = repeated {
Array::Strings(
repeated
.into_iter()
.map(|value| {
Rc::new(BString::from(value.to_str().unwrap()))
})
.collect(),
)
} else {
Array::Strings(vec![])
}
}
RuntimeType::VecU8 => {
if let Some(repeated) = repeated {
Array::Strings(
repeated
.into_iter()
.map(|value| {
Rc::new(BString::from(
value.to_bytes().unwrap(),
))
})
.collect(),
)
} else {
Array::Strings(vec![])
}
}
RuntimeType::Enum(_) => {
if let Some(repeated) = repeated {
Array::Integers(
repeated
.into_iter()
.map(|value| value.to_enum_value().unwrap() as i64)
.collect(),
)
} else {
Array::Integers(vec![])
}
}
RuntimeType::Message(msg_descriptor) => {
if let Some(repeated) = repeated {
Array::Structs(
repeated
.into_iter()
.map(|value| {
Self::from_proto_descriptor_and_value(
msg_descriptor,
value,
enum_as_fields,
)
})
.collect(),
)
} else {
Array::Structs(vec![
Struct::from_proto_descriptor_and_msg(
msg_descriptor,
None,
enum_as_fields,
),
])
}
}
};
TypeValue::Array(Rc::new(array))
}
fn new_map(
key_ty: &RuntimeType,
value_ty: &RuntimeType,
map: Option<ReflectMapRef>,
enum_as_fields: bool,
syntax: Syntax,
) -> TypeValue {
let map = match key_ty {
RuntimeType::String => Self::new_map_with_string_key(
value_ty,
map,
enum_as_fields,
syntax,
),
RuntimeType::I32
| RuntimeType::I64
| RuntimeType::U32
| RuntimeType::U64 => Self::new_map_with_integer_key(
value_ty,
map,
enum_as_fields,
syntax,
),
ty => {
panic!("maps in YARA can't have keys of type `{ty}`");
}
};
TypeValue::Map(Rc::new(map))
}
fn new_map_with_integer_key(
value_ty: &RuntimeType,
map: Option<ReflectMapRef>,
enum_as_fields: bool,
syntax: Syntax,
) -> Map {
if let Some(map) = map {
let mut result = IndexMap::default();
for (key, value) in map.into_iter() {
result.insert(
Self::value_as_i64(key),
Self::new_value(
value_ty,
Some(value),
enum_as_fields,
syntax,
),
);
}
Map::IntegerKeys { deputy: None, map: result }
} else {
Map::IntegerKeys {
deputy: Some(Self::new_value(
value_ty,
None,
enum_as_fields,
syntax,
)),
map: Default::default(),
}
}
}
fn new_map_with_string_key(
value_ty: &RuntimeType,
map: Option<ReflectMapRef>,
enum_as_fields: bool,
syntax: Syntax,
) -> Map {
if let Some(map) = map {
let mut result = IndexMap::default();
for (key, value) in map.into_iter() {
result.insert(
BString::from(Self::value_as_string(key)),
Self::new_value(
value_ty,
Some(value),
enum_as_fields,
syntax,
),
);
}
Map::StringKeys { deputy: None, map: result }
} else {
Map::StringKeys {
deputy: Some(Self::new_value(
value_ty,
None,
enum_as_fields,
syntax,
)),
map: Default::default(),
}
}
}
fn from_proto_descriptor_and_value(
msg_descriptor: &MessageDescriptor,
value: ReflectValueRef,
enum_as_fields: bool,
) -> Rc<Self> {
if let ReflectValueRef::Message(m) = value {
Struct::from_proto_descriptor_and_msg(
msg_descriptor,
Some(m.deref()),
enum_as_fields,
)
} else {
unreachable!()
}
}
fn value_as_i64(value: ReflectValueRef) -> i64 {
match value {
ReflectValueRef::U32(v) => v as i64,
ReflectValueRef::U64(v) => v as i64,
ReflectValueRef::I32(v) => v as i64,
ReflectValueRef::I64(v) => v,
ReflectValueRef::Enum(_, v) => v as i64,
_ => panic!(),
}
}
fn value_as_f64(value: ReflectValueRef) -> f64 {
match value {
ReflectValueRef::F64(v) => v,
ReflectValueRef::F32(v) => v as f64,
_ => panic!(),
}
}
fn value_as_bool(value: ReflectValueRef) -> bool {
match value {
ReflectValueRef::Bool(v) => v,
_ => panic!(),
}
}
fn value_as_string(value: ReflectValueRef<'_>) -> &[u8] {
match value {
ReflectValueRef::String(v) => v.as_bytes(),
ReflectValueRef::Bytes(v) => v,
_ => panic!(),
}
}
}
impl PartialEq for Struct {
fn eq(&self, other: &Self) -> bool {
if self.fields.len() != other.fields.len() {
return false;
}
for (a, b) in iter::zip(&self.fields, &other.fields) {
if a.0 != b.0 {
return false;
};
if !a.1.type_value.eq_type(&b.1.type_value) {
return false;
}
}
true
}
}
impl From<&Module> for Rc<Struct> {
fn from(module: &Module) -> Self {
let mut module_struct = Struct::from_proto_descriptor_and_msg(
&module.root_struct_descriptor,
None,
true,
);
let module_struct_mut =
Rc::<Struct>::get_mut(&mut module_struct).unwrap();
if let Some(rust_module_name) = module.rust_module_name {
let functions = WasmExport::get_functions(|export| {
export.public
&& export.rust_module_path.ends_with(rust_module_name)
&& export.method_of.is_none()
})
.into_iter()
.sorted_by_key(|(name, _)| *name);
for (name, func) in functions {
let func = TypeValue::Func(Rc::new(func));
if module_struct_mut.add_field(name, func).is_some() {
panic!(
"function `{name}` has the same name than a field in `{rust_module_name}`",
)
};
}
}
module_struct_mut.enum_substructures(&mut |sub_struct| {
let methods = sub_struct.protobuf_type_name().map(WasmExport::get_methods);
if let Some(methods) = methods {
for (name, func) in methods {
let func = TypeValue::Func(Rc::new(func));
if sub_struct.add_field(name, func).is_some() {
panic!(
"method `{name}` has the same name than a field in `{}`",
sub_struct.protobuf_type_name().unwrap(),
)
};
}
}
});
module_struct
}
}
#[cfg(test)]
mod tests {
use super::Struct;
use crate::types::{Array, Type, TypeValue};
use std::rc::Rc;
#[test]
fn test_struct() {
let mut root = Struct::default();
let foo = Struct::default();
root.add_field("foo", TypeValue::Struct(Rc::new(foo)));
root.add_field("bar", TypeValue::var_integer_from(1));
let field1 = root.field_by_name("foo").unwrap();
let field2 = root.field_by_index(0).unwrap();
assert_eq!(field1.type_value.ty(), Type::Struct);
assert_eq!(field1.type_value.ty(), field2.type_value.ty());
root.add_field("foo.bar", TypeValue::var_integer_from(1));
}
#[test]
fn test_proto_struct() {
use protobuf::MessageFull;
use crate::modules::protos::test_proto2::TestProto2;
let mut structure = Struct::from_proto_descriptor_and_msg(
&TestProto2::descriptor(),
None,
true,
);
let structure = Rc::<Struct>::get_mut(&mut structure).unwrap();
let mut names = Vec::new();
structure.enum_substructures(&mut |s| {
names.push(s.protobuf_type_name().map(|n| n.to_string()));
println!("{s:?}\n\n")
});
assert_eq!(
vec![
Some("test_proto2.TestProto2".to_string()),
Some("test_proto2.NestedProto2".to_string()),
Some("test_proto2.NestedProto2".to_string()),
None,
None,
None,
None,
None,
],
names
);
}
#[test]
fn struct_eq() {
let mut sub: Struct = Struct::default();
sub.add_field("integer", TypeValue::unknown_integer());
sub.add_field("string", TypeValue::unknown_string());
sub.add_field("boolean", TypeValue::unknown_bool());
let sub = Rc::new(sub);
let mut a = Struct::default();
let mut b = Struct::default();
a.add_field("boolean", TypeValue::var_bool_from(true));
a.add_field("integer", TypeValue::var_integer_from(1));
a.add_field("structure", TypeValue::Struct(sub.clone()));
a.add_field(
"floats_array",
TypeValue::Array(Rc::new(Array::Floats(vec![]))),
);
assert_ne!(a, b);
b.add_field("boolean", TypeValue::var_bool_from(false));
b.add_field("integer", TypeValue::var_integer_from(1));
b.add_field("structure", TypeValue::Struct(sub));
b.add_field(
"floats_array",
TypeValue::Array(Rc::new(Array::Floats(vec![]))),
);
assert_eq!(a, b);
a.add_field("foo", TypeValue::var_bool_from(false));
b.add_field("foo", TypeValue::unknown_integer());
assert_ne!(a, b);
}
}