use bstr::BString;
use serde::{Deserialize, Serialize};
use std::cell::OnceCell;
use std::fmt::{Debug, Display, Formatter};
use std::hash::{Hash, Hasher};
use std::rc::Rc;
use std::{mem, ptr};
use walrus::ValType;
use walrus::ir::InstrSeqType;
use crate::modules::protos::yara::enum_value_options::Value as EnumValue;
use crate::symbols::{Symbol, SymbolLookup, SymbolTable};
use crate::wasm::WasmExport;
pub(crate) use array::*;
pub(crate) use func::*;
pub(crate) use map::*;
pub(crate) use structure::*;
thread_local! {
static STRING_BUILTIN_METHODS: OnceCell<Rc<SymbolTable>> = const { OnceCell::new() };
}
mod array;
mod func;
mod map;
mod structure;
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
pub(crate) enum Type {
#[default]
Unknown,
Integer,
Float,
Bool,
String,
Regexp,
Struct,
Array,
Map,
Func,
}
impl Display for Type {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl Debug for Type {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unknown => write!(f, "unknown"),
Self::Integer => write!(f, "integer"),
Self::Float => write!(f, "float"),
Self::Bool => write!(f, "boolean"),
Self::String => write!(f, "string"),
Self::Regexp => write!(f, "regexp"),
Self::Struct => write!(f, "struct"),
Self::Array => write!(f, "array"),
Self::Map => write!(f, "map"),
Self::Func => write!(f, "function"),
}
}
}
impl From<Type> for ValType {
fn from(ty: Type) -> ValType {
match ty {
Type::Integer => ValType::I64,
Type::Float => ValType::F64,
Type::Bool => ValType::I32,
Type::String => ValType::I64,
_ => panic!("can not create WASM primitive type for `{ty}`"),
}
}
}
impl From<Type> for InstrSeqType {
fn from(ty: Type) -> InstrSeqType {
InstrSeqType::from(ValType::from(ty))
}
}
#[derive(Clone, Serialize, Deserialize, PartialEq)]
pub(crate) enum Value<T> {
Const(T),
Var(T),
Unknown,
}
impl<T> Value<T> {
#[inline]
pub fn is_const(&self) -> bool {
matches!(self, Value::Const(_))
}
pub fn extract(&self) -> Option<&T> {
match self {
Value::Const(v) | Value::Var(v) => Some(v),
Value::Unknown => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Hash, PartialEq, Eq)]
pub struct Regexp(String);
impl Regexp {
pub fn new<R: AsRef<str>>(regexp: R) -> Self {
let regexp = regexp.as_ref();
assert!(regexp.starts_with('/'));
assert!(regexp[1..].contains('/'));
Self(regexp.to_string())
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn naked(&self) -> &str {
&self.0[1..self.0.rfind('/').unwrap()]
}
pub fn case_insensitive(&self) -> bool {
let modifiers = &self.0[self.0.rfind('/').unwrap()..];
modifiers.contains('i')
}
pub fn dot_matches_new_line(&self) -> bool {
let modifiers = &self.0[self.0.rfind('/').unwrap()..];
modifiers.contains('s')
}
}
#[derive(Clone, Serialize, Deserialize)]
pub(crate) enum TypeValue {
Unknown,
Bool {
value: Value<bool>,
},
Float {
value: Value<f64>,
},
Integer {
value: Value<i64>,
constraints: Option<Vec<IntegerConstraint>>,
},
String {
value: Value<Rc<BString>>,
constraints: Option<Vec<StringConstraint>>,
},
Regexp(Option<Regexp>),
Struct(Rc<Struct>),
Array(Rc<Array>),
Map(Rc<Map>),
Func(Rc<Func>),
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub(crate) enum StringConstraint {
Lowercase,
Uppercase,
ExactLength(usize),
}
#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub(crate) enum IntegerConstraint {
Range(i64, i64),
}
impl Hash for TypeValue {
fn hash<H: Hasher>(&self, state: &mut H) {
mem::discriminant(self).hash(state);
match self {
TypeValue::Unknown => {}
TypeValue::Integer { value, .. } => {
mem::discriminant(value).hash(state);
if let Value::Const(c) = value {
c.hash(state);
}
}
TypeValue::Float { value } => {
mem::discriminant(value).hash(state);
if let Value::Const(c) = value {
f64::to_bits(*c).hash(state);
}
}
TypeValue::Bool { value } => {
mem::discriminant(value).hash(state);
if let Value::Const(c) = value {
c.hash(state);
}
}
TypeValue::String { value, .. } => {
mem::discriminant(value).hash(state);
if let Value::Const(c) = value {
c.hash(state);
}
}
TypeValue::Regexp(v) => {
v.hash(state);
}
TypeValue::Struct(v) => ptr::hash(&**v, state),
TypeValue::Array(v) => ptr::hash(&**v, state),
TypeValue::Map(v) => ptr::hash(&**v, state),
TypeValue::Func(v) => ptr::hash(&**v, state),
}
}
}
impl TypeValue {
pub fn is_const(&self) -> bool {
match self {
TypeValue::Unknown => false,
TypeValue::Integer { value, .. } => value.is_const(),
TypeValue::Float { value } => value.is_const(),
TypeValue::Bool { value } => value.is_const(),
TypeValue::String { value, .. } => value.is_const(),
TypeValue::Regexp(_) => false,
TypeValue::Struct(_) => false,
TypeValue::Array(_) => false,
TypeValue::Map(_) => false,
TypeValue::Func(_) => false,
}
}
pub fn eq_type(&self, other: &Self) -> bool {
match (self, other) {
(Self::Integer { .. }, Self::Integer { .. }) => true,
(Self::Float { .. }, Self::Float { .. }) => true,
(Self::String { .. }, Self::String { .. }) => true,
(Self::Bool { .. }, Self::Bool { .. }) => true,
(Self::Array(a), Self::Array(b)) => {
a.deputy().eq_type(&b.deputy())
}
(Self::Map(a), Self::Map(b)) => match (a.as_ref(), b.as_ref()) {
(Map::StringKeys { .. }, Map::StringKeys { .. }) => {
a.deputy().eq_type(&b.deputy())
}
(Map::IntegerKeys { .. }, Map::IntegerKeys { .. }) => {
a.deputy().eq_type(&b.deputy())
}
_ => false,
},
(Self::Struct(a), Self::Struct(b)) => a.eq(b),
_ => false,
}
}
fn string_builtin_methods() -> Rc<SymbolTable> {
STRING_BUILTIN_METHODS.with(|cell| {
cell.get_or_init(|| {
let mut s = SymbolTable::new();
for (name, func) in WasmExport::get_methods("RuntimeString") {
s.insert(name, Symbol::Func(Rc::new(func)));
}
Rc::new(s)
})
.clone()
})
}
pub fn symbol_table(&self) -> Option<Rc<dyn SymbolLookup>> {
match self {
Self::Struct(s) => Some(s.clone()),
Self::Array(_) => Some(Array::builtin_methods()),
Self::Map(_) => Some(Map::builtin_methods()),
Self::String { .. } => Some(Self::string_builtin_methods()),
_ => None,
}
}
pub fn ty(&self) -> Type {
match self {
Self::Unknown => Type::Unknown,
Self::Integer { .. } => Type::Integer,
Self::Float { .. } => Type::Float,
Self::Bool { .. } => Type::Bool,
Self::String { .. } => Type::String,
Self::Regexp(_) => Type::Regexp,
Self::Map(_) => Type::Map,
Self::Struct(_) => Type::Struct,
Self::Array(_) => Type::Array,
Self::Func(_) => Type::Func,
}
}
pub fn clone_without_value(&self) -> Self {
match self {
Self::Unknown => Self::Unknown,
Self::Integer { .. } => Self::unknown_integer(),
Self::Float { .. } => Self::unknown_float(),
Self::Bool { .. } => Self::unknown_bool(),
Self::String { .. } => Self::unknown_string(),
Self::Regexp(_) => Self::Regexp(None),
Self::Map(v) => Self::Map(v.clone()),
Self::Struct(v) => Self::Struct(v.clone()),
Self::Array(v) => Self::Array(v.clone()),
Self::Func(v) => Self::Func(v.clone()),
}
}
pub fn cast_to_bool(&self) -> Self {
match self {
Self::Integer { value: Value::Unknown, .. } => {
Self::Bool { value: Value::Unknown }
}
Self::Integer { value: Value::Var(i), .. } => {
Self::Bool { value: Value::Var(*i != 0) }
}
Self::Integer { value: Value::Const(i), .. } => {
Self::Bool { value: Value::Const(*i != 0) }
}
Self::Float { value: Value::Unknown } => {
Self::Bool { value: Value::Unknown }
}
Self::Float { value: Value::Var(f) } => {
Self::Bool { value: Value::Var(*f != 0.0) }
}
Self::Float { value: Value::Const(f) } => {
Self::Bool { value: Value::Const(*f != 0.0) }
}
Self::String { value: Value::Unknown, .. } => {
Self::Bool { value: Value::Unknown }
}
Self::String { value: Value::Var(s), .. } => {
Self::Bool { value: Value::Var(!s.is_empty()) }
}
Self::String { value: Value::Const(s), .. } => {
Self::Bool { value: Value::Const(!s.is_empty()) }
}
Self::Bool { value: Value::Unknown } => {
Self::Bool { value: Value::Unknown }
}
Self::Bool { value: Value::Var(b) } => {
Self::Bool { value: Value::Var(*b) }
}
Self::Bool { value: Value::Const(b) } => {
Self::Bool { value: Value::Const(*b) }
}
_ => panic!("can not cast {self:?} to bool"),
}
}
pub fn as_array(&self) -> Rc<Array> {
if let TypeValue::Array(array) = self {
array.clone()
} else {
panic!(
"called `as_array` on a TypeValue that is not TypeValue::Array, it is: {self:?}"
)
}
}
pub fn as_struct(&self) -> Rc<Struct> {
if let TypeValue::Struct(structure) = self {
structure.clone()
} else {
panic!(
"called `as_struct` on a TypeValue that is not TypeValue::Struct, it is: {self:?}"
)
}
}
pub fn as_map(&self) -> Rc<Map> {
if let TypeValue::Map(map) = self {
map.clone()
} else {
panic!(
"called `as_map` on a TypeValue that is not TypeValue::Map, it is: {self:?}"
)
}
}
#[inline]
pub fn as_bool(&self) -> bool {
self.try_as_bool().expect("TypeValue doesn't have an associated value")
}
#[inline]
pub fn as_integer(&self) -> i64 {
self.try_as_integer()
.expect("TypeValue doesn't have an associated value")
}
#[inline]
pub fn as_float(&self) -> f64 {
self.try_as_float()
.expect("TypeValue doesn't have an associated value")
}
#[inline]
pub fn as_string(&self) -> Rc<BString> {
self.try_as_string()
.expect("TypeValue doesn't have an associated value")
}
pub fn try_as_bool(&self) -> Option<bool> {
if let TypeValue::Bool { value } = self {
value.extract().cloned()
} else {
panic!(
"called `try_as_bool` on a TypeValue that is not TypeValue::Bool, it is: {self:?}"
)
}
}
pub fn try_as_integer(&self) -> Option<i64> {
if let TypeValue::Integer { value, .. } = self {
value.extract().cloned()
} else {
panic!(
"called `try_as_integer` on a TypeValue that is not TypeValue::Integer, it is: {self:?}"
)
}
}
pub fn try_as_float(&self) -> Option<f64> {
if let TypeValue::Float { value } = self {
value.extract().cloned()
} else {
panic!(
"called `try_as_float` on a TypeValue that is not TypeValue::Float, it is: {self:?}"
)
}
}
pub fn try_as_string(&self) -> Option<Rc<BString>> {
if let TypeValue::String { value, .. } = self {
value.extract().cloned()
} else {
panic!(
"called `try_as_string` on a TypeValue that is not TypeValue::String, it is: {self:?}"
)
}
}
#[inline]
pub fn var_integer_from<T: Into<i64>>(i: T) -> Self {
Self::Integer { value: Value::Var(i.into()), constraints: None }
}
#[inline]
pub fn var_float_from<T: Into<f64>>(f: T) -> Self {
Self::Float { value: Value::Var(f.into()) }
}
#[inline]
pub fn var_bool_from(i: bool) -> Self {
Self::Bool { value: Value::Var(i) }
}
#[inline]
pub fn var_string_from<T: AsRef<[u8]>>(s: T) -> Self {
Self::String {
value: Value::Var(BString::from(s.as_ref()).into()),
constraints: None,
}
}
#[inline]
pub fn const_integer_from<T: Into<i64>>(i: T) -> Self {
Self::Integer { value: Value::Const(i.into()), constraints: None }
}
#[inline]
pub fn const_float_from<T: Into<f64>>(f: T) -> Self {
Self::Float { value: Value::Const(f.into()) }
}
#[inline]
pub fn const_bool_from(i: bool) -> Self {
Self::Bool { value: Value::Const(i) }
}
#[inline]
pub fn const_string_from<T: AsRef<[u8]>>(s: T) -> Self {
Self::String {
value: Value::Const(BString::from(s.as_ref()).into()),
constraints: None,
}
}
#[inline]
pub fn unknown_bool() -> Self {
Self::Bool { value: Value::Unknown }
}
#[inline]
pub fn unknown_float() -> Self {
Self::Float { value: Value::Unknown }
}
#[inline]
pub fn unknown_integer() -> Self {
Self::Integer { value: Value::Unknown, constraints: None }
}
#[inline]
pub fn unknown_string() -> Self {
Self::String { value: Value::Unknown, constraints: None }
}
#[inline]
pub fn unknown_string_with_constraints<C: Into<Vec<StringConstraint>>>(
constraints: C,
) -> Self {
Self::String {
value: Value::Unknown,
constraints: Some(constraints.into()),
}
}
#[inline]
pub fn unknown_integer_with_constraints<
C: Into<Vec<IntegerConstraint>>,
>(
constraints: C,
) -> Self {
Self::Integer {
value: Value::Unknown,
constraints: Some(constraints.into()),
}
}
}
impl Display for TypeValue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl Debug for TypeValue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Unknown => write!(f, "unknown"),
Self::Bool { value } => {
if let Some(v) = value.extract() {
write!(f, "boolean({v:?})")
} else {
write!(f, "boolean(unknown)")
}
}
Self::Integer { value, .. } => {
if let Some(v) = value.extract() {
write!(f, "integer({v:?})")
} else {
write!(f, "integer(unknown)")
}
}
Self::Float { value } => {
if let Some(v) = value.extract() {
write!(f, "float({v:?})")
} else {
write!(f, "float(unknown)")
}
}
Self::String { value, .. } => {
if let Some(v) = value.extract() {
write!(f, "string({v:?})")
} else {
write!(f, "string(unknown)")
}
}
Self::Regexp(re) => {
if let Some(re) = re {
write!(f, "regexp({re:?})")
} else {
write!(f, "regexp(unknown)")
}
}
Self::Map(_) => write!(f, "map"),
Self::Struct(_) => write!(f, "struct"),
Self::Array(_) => write!(f, "array"),
Self::Func(_) => write!(f, "function"),
}
}
}
impl From<EnumValue> for TypeValue {
fn from(value: EnumValue) -> Self {
match value {
EnumValue::I64(v) => Self::const_integer_from(v),
EnumValue::F64(v) => Self::const_float_from(v),
}
}
}
impl PartialEq for TypeValue {
fn eq(&self, rhs: &Self) -> bool {
match (self, rhs) {
(Self::Unknown, Self::Unknown) => true,
(
Self::String { value: lhs, .. },
Self::String { value: rhs, .. },
) => lhs == rhs,
(
Self::Integer { value: lhs, .. },
Self::Integer { value: rhs, .. },
) => lhs == rhs,
(Self::Float { value: lhs }, Self::Float { value: rhs }) => {
lhs == rhs
}
(Self::Bool { value: lhs }, Self::Bool { value: rhs }) => {
lhs == rhs
}
(Self::Regexp(lhs), Self::Regexp(rhs)) => lhs == rhs,
(Self::Struct(lhs), Self::Struct(rhs)) => ptr::eq(&**lhs, &**rhs),
(Self::Array(lhs), Self::Array(rhs)) => ptr::eq(&**lhs, &**rhs),
(Self::Map(lhs), Self::Map(rhs)) => ptr::eq(&**lhs, &**rhs),
(Self::Func(lhs), Self::Func(rhs)) => ptr::eq(&**lhs, &**rhs),
_ => false,
}
}
}
impl Eq for TypeValue {}