use std::any::{TypeId, type_name};
use std::borrow::Cow;
use std::mem;
use std::ops::RangeInclusive;
use std::rc::Rc;
use std::sync::{LazyLock, OnceLock};
use bstr::{BString, ByteSlice};
#[cfg(not(feature = "inventory"))]
use linkme::distributed_slice;
use rustc_hash::FxHashMap;
use smallvec::{SmallVec, smallvec};
use yara_x_macros::wasm_export;
use crate::compiler::{LiteralId, PatternId, RegexpId, RuleId};
use crate::modules::BUILTIN_MODULES;
use crate::scanner::{RuntimeObjectHandle, ScanContext};
use crate::types::{
Array, Func, FuncSignature, Map, Struct, TypeValue, Value,
};
use crate::wasm::integer::RangedInteger;
use crate::wasm::runtime::{
AsContext, AsContextMut, Caller, Config, Engine, FuncType, Linker, ValRaw,
ValType,
};
use crate::wasm::string::RuntimeString;
use crate::wasm::string::String as _;
use crate::{ScanError, wasm};
pub(crate) mod builder;
pub(crate) mod integer;
pub(crate) mod runtime;
pub(crate) mod string;
pub(crate) const MAX_VARS: i32 = 2048;
pub(crate) const VARS_STACK_START: i32 = MAX_VARS / 8;
pub(crate) const VARS_STACK_END: i32 = VARS_STACK_START + MAX_VARS * 8;
pub(crate) const LOOKUP_INDEXES_START: i32 = VARS_STACK_END;
pub(crate) const LOOKUP_INDEXES_END: i32 = LOOKUP_INDEXES_START + 1024;
pub(crate) const MATCHING_RULES_BITMAP_BASE: i32 = LOOKUP_INDEXES_END;
#[cfg(not(feature = "inventory"))]
#[distributed_slice]
pub(crate) static WASM_EXPORTS: [WasmExport] = [..];
#[cfg(feature = "inventory")]
inventory::collect!(WasmExport);
pub(crate) fn wasm_exports() -> impl Iterator<Item = &'static WasmExport> {
#[cfg(feature = "inventory")]
return inventory::iter::<WasmExport>();
#[cfg(not(feature = "inventory"))]
WASM_EXPORTS.iter()
}
pub(crate) struct WasmExport {
pub name: &'static str,
pub mangled_name: &'static str,
pub public: bool,
pub rust_module_path: &'static str,
pub method_of: Option<&'static str>,
pub sync_flags: u32,
pub func: &'static (dyn WasmExportedFn + Send + Sync),
pub description: Option<Cow<'static, str>>,
}
impl WasmExport {
pub fn fully_qualified_mangled_name(&self) -> String {
if self.method_of.is_some() {
return self.mangled_name.to_string();
}
for (module_name, module) in BUILTIN_MODULES.iter() {
if let Some(rust_module_name) = module.rust_module_name
&& self.rust_module_path.contains(rust_module_name)
{
return format!("{}.{}", module_name, self.mangled_name);
}
}
self.mangled_name.to_owned()
}
pub fn builtin(&self) -> bool {
self.rust_module_path.strip_prefix("yara_x::modules::").is_none()
}
pub fn get_functions<P>(predicate: P) -> FxHashMap<&'static str, Func>
where
P: FnMut(&&WasmExport) -> bool,
{
let mut functions: FxHashMap<&'static str, Func> =
FxHashMap::default();
for export in wasm_exports().filter(predicate) {
let mangled_name = export.fully_qualified_mangled_name();
let description = export.description.clone();
if let Some(function) = functions.get_mut(export.name) {
let mut signature = FuncSignature::from(mangled_name);
signature.doc = description;
function.add_signature(signature);
} else {
let mut func = Func::from(mangled_name);
let signature = func.signatures_mut().get_mut(0).unwrap();
let signature = Rc::get_mut(signature).unwrap();
signature.doc = description;
functions.insert(export.name, func);
}
}
functions
}
pub fn get_methods(type_name: &str) -> FxHashMap<&'static str, Func> {
WasmExport::get_functions(|export| {
export.method_of.is_some_and(|name| name == type_name)
})
}
}
pub(crate) trait WasmExportedFn {
fn trampoline(&'static self) -> TrampolineFn;
fn wasmtime_args(&'static self) -> Vec<ValType>;
fn wasmtime_results(&'static self) -> WasmResultArray<ValType>;
fn walrus_args(&'static self) -> Vec<walrus::ValType> {
self.wasmtime_args().iter().map(wasmtime_to_walrus).collect()
}
fn walrus_results(&'static self) -> WasmResultArray<walrus::ValType> {
self.wasmtime_results().iter().map(wasmtime_to_walrus).collect()
}
}
type TrampolineFn = Box<
dyn Fn(Caller<'_, ScanContext>, &mut [ValRaw]) -> anyhow::Result<()>
+ Send
+ Sync
+ 'static,
>;
const MAX_RESULTS: usize = 4;
type WasmResultArray<T> = SmallVec<[T; MAX_RESULTS]>;
trait WasmArg<T> {
fn raw_into(self, _: &mut ScanContext) -> T;
}
impl WasmArg<i64> for ValRaw {
#[inline]
fn raw_into(self, _: &mut ScanContext) -> i64 {
self.get_i64()
}
}
impl WasmArg<i32> for ValRaw {
#[inline]
fn raw_into(self, _: &mut ScanContext) -> i32 {
self.get_i32()
}
}
impl WasmArg<f64> for ValRaw {
#[inline]
fn raw_into(self, _: &mut ScanContext) -> f64 {
f64::from_bits(self.get_f64())
}
}
impl WasmArg<f32> for ValRaw {
#[inline]
fn raw_into(self, _: &mut ScanContext) -> f32 {
f32::from_bits(self.get_f32())
}
}
impl WasmArg<bool> for ValRaw {
#[inline]
fn raw_into(self, _: &mut ScanContext) -> bool {
self.get_i32() == 1
}
}
impl WasmArg<RuleId> for ValRaw {
#[inline]
fn raw_into(self, _: &mut ScanContext) -> RuleId {
RuleId::from(self.get_i32())
}
}
impl WasmArg<PatternId> for ValRaw {
#[inline]
fn raw_into(self, _: &mut ScanContext) -> PatternId {
PatternId::from(self.get_i32())
}
}
impl WasmArg<LiteralId> for ValRaw {
#[inline]
fn raw_into(self, _: &mut ScanContext) -> LiteralId {
LiteralId::from(self.get_i32())
}
}
impl WasmArg<RegexpId> for ValRaw {
#[inline]
fn raw_into(self, _: &mut ScanContext) -> RegexpId {
RegexpId::from(self.get_i32())
}
}
impl WasmArg<RuntimeString> for ValRaw {
#[inline]
fn raw_into(self, ctx: &mut ScanContext) -> RuntimeString {
RuntimeString::from_wasm(ctx, self.get_i64())
}
}
impl WasmArg<Rc<Array>> for ValRaw {
#[inline]
fn raw_into(self, ctx: &mut ScanContext) -> Rc<Array> {
let handle = RuntimeObjectHandle::from(self.get_i64());
ctx.runtime_objects.get(&handle).unwrap().as_array()
}
}
impl WasmArg<Rc<Map>> for ValRaw {
#[inline]
fn raw_into(self, ctx: &mut ScanContext) -> Rc<Map> {
let handle = RuntimeObjectHandle::from(self.get_i64());
ctx.runtime_objects.get(&handle).unwrap().as_map()
}
}
impl WasmArg<Rc<Struct>> for ValRaw {
#[inline]
fn raw_into(self, ctx: &mut ScanContext) -> Rc<Struct> {
let handle = RuntimeObjectHandle::from(self.get_i64());
ctx.runtime_objects.get(&handle).unwrap().as_struct()
}
}
impl WasmArg<Option<Rc<Struct>>> for ValRaw {
#[inline]
fn raw_into(self, ctx: &mut ScanContext) -> Option<Rc<Struct>> {
let handle = RuntimeObjectHandle::from(self.get_i64());
if handle == RuntimeObjectHandle::NULL {
return None;
}
Some(ctx.runtime_objects.get(&handle).unwrap().as_struct())
}
}
pub(crate) trait WasmResult {
fn values(self, _: &mut ScanContext) -> WasmResultArray<ValRaw>;
fn types() -> WasmResultArray<ValType>;
}
impl WasmResult for () {
fn values(self, _: &mut ScanContext) -> WasmResultArray<ValRaw> {
smallvec![]
}
fn types() -> WasmResultArray<ValType> {
smallvec![]
}
}
impl WasmResult for i32 {
fn values(self, _: &mut ScanContext) -> WasmResultArray<ValRaw> {
smallvec![ValRaw::i32(self)]
}
fn types() -> WasmResultArray<ValType> {
smallvec![ValType::I32]
}
}
impl WasmResult for i64 {
fn values(self, _: &mut ScanContext) -> WasmResultArray<ValRaw> {
smallvec![ValRaw::i64(self)]
}
fn types() -> WasmResultArray<ValType> {
smallvec![ValType::I64]
}
}
impl<const MIN: i64, const MAX: i64> WasmResult for RangedInteger<MIN, MAX> {
fn values(self, _: &mut ScanContext) -> WasmResultArray<ValRaw> {
smallvec![ValRaw::i64(self.value())]
}
fn types() -> WasmResultArray<ValType> {
smallvec![ValType::I64]
}
}
impl WasmResult for f32 {
fn values(self, _: &mut ScanContext) -> WasmResultArray<ValRaw> {
smallvec![ValRaw::f32(f32::to_bits(self))]
}
fn types() -> WasmResultArray<ValType> {
smallvec![ValType::F32]
}
}
impl WasmResult for f64 {
fn values(self, _: &mut ScanContext) -> WasmResultArray<ValRaw> {
smallvec![ValRaw::f64(f64::to_bits(self))]
}
fn types() -> WasmResultArray<ValType> {
smallvec![ValType::F64]
}
}
impl WasmResult for bool {
fn values(self, _: &mut ScanContext) -> WasmResultArray<ValRaw> {
smallvec![ValRaw::i32(self as i32)]
}
fn types() -> WasmResultArray<ValType> {
smallvec![ValType::I32]
}
}
impl<S: wasm::string::String> WasmResult for S {
fn values(self, ctx: &mut ScanContext) -> WasmResultArray<ValRaw> {
smallvec![ValRaw::i64(self.into_wasm_with_ctx(ctx))]
}
fn types() -> WasmResultArray<ValType> {
smallvec![ValType::I64]
}
}
impl WasmResult for RuntimeObjectHandle {
fn values(self, _: &mut ScanContext) -> WasmResultArray<ValRaw> {
smallvec![ValRaw::i64(self.into())]
}
fn types() -> WasmResultArray<ValType> {
smallvec![ValType::I64]
}
}
impl WasmResult for Rc<BString> {
fn values(self, ctx: &mut ScanContext) -> WasmResultArray<ValRaw> {
let s = RuntimeString::Rc(self);
smallvec![ValRaw::i64(s.into_wasm_with_ctx(ctx))]
}
fn types() -> WasmResultArray<ValType> {
smallvec![ValType::I64]
}
}
impl WasmResult for Rc<Struct> {
fn values(self, ctx: &mut ScanContext) -> WasmResultArray<ValRaw> {
let handle = ctx.store_struct(self);
smallvec![ValRaw::i64(handle.into())]
}
fn types() -> WasmResultArray<ValType> {
smallvec![ValType::I64]
}
}
impl<A, B> WasmResult for (A, B)
where
A: WasmResult,
B: WasmResult,
{
fn values(self, ctx: &mut ScanContext) -> WasmResultArray<ValRaw> {
let mut result = self.0.values(ctx);
result.extend(self.1.values(ctx));
result
}
fn types() -> WasmResultArray<ValType> {
let mut result = A::types();
result.extend(B::types());
result
}
}
impl<T> WasmResult for Option<T>
where
T: WasmResult + Default,
{
fn values(self, ctx: &mut ScanContext) -> WasmResultArray<ValRaw> {
match self {
Some(value) => {
let mut result = value.values(ctx);
result.push(ValRaw::i32(0));
result
}
None => {
let mut result = T::default().values(ctx);
result.push(ValRaw::i32(1));
result
}
}
}
fn types() -> WasmResultArray<ValType> {
let mut result = T::types();
result.push(ValType::I32);
result
}
}
pub fn wasmtime_to_walrus(ty: &ValType) -> walrus::ValType {
#[allow(unreachable_patterns)]
match ty {
ValType::I64 => walrus::ValType::I64,
ValType::I32 => walrus::ValType::I32,
ValType::F64 => walrus::ValType::F64,
ValType::F32 => walrus::ValType::F32,
_ => unreachable!(),
}
}
#[allow(clippy::if_same_then_else)]
fn type_id_to_wasmtime(
type_id: TypeId,
type_name: &'static str,
) -> &'static [ValType] {
if type_id == TypeId::of::<i64>() {
return &[ValType::I64];
} else if type_id == TypeId::of::<i32>() {
return &[ValType::I32];
} else if type_id == TypeId::of::<f64>() {
return &[ValType::F64];
} else if type_id == TypeId::of::<f32>() {
return &[ValType::F32];
} else if type_id == TypeId::of::<bool>() {
return &[ValType::I32];
} else if type_id == TypeId::of::<LiteralId>() {
return &[ValType::I32];
} else if type_id == TypeId::of::<PatternId>() {
return &[ValType::I32];
} else if type_id == TypeId::of::<RuleId>() {
return &[ValType::I32];
} else if type_id == TypeId::of::<RegexpId>() {
return &[ValType::I32];
} else if type_id == TypeId::of::<()>() {
return &[];
} else if type_id == TypeId::of::<RuntimeString>() {
return &[ValType::I64];
} else if type_id == TypeId::of::<Option<Rc<Struct>>>() {
return &[ValType::I64];
} else if type_id == TypeId::of::<Rc<Struct>>() {
return &[ValType::I64];
} else if type_id == TypeId::of::<Rc<Array>>() {
return &[ValType::I64];
} else if type_id == TypeId::of::<Rc<Map>>() {
return &[ValType::I64];
}
panic!("type `{type_name}` can't be an argument")
}
macro_rules! impl_wasm_exported_fn {
($name:ident $($args:ident)*) => {
#[allow(dead_code)]
pub(super) struct $name <$($args,)* R>
where
$($args: 'static,)*
R: 'static,
{
pub target_fn: &'static (dyn Fn(&mut Caller<'_, ScanContext>, $($args),*) -> R
+ Send
+ Sync
+ 'static),
}
#[allow(dead_code)]
impl<$($args,)* R> WasmExportedFn for $name<$($args,)* R>
where
$(ValRaw: WasmArg<$args>,)*
R: WasmResult,
{
#[allow(unused_mut)]
fn wasmtime_args(&'static self) -> Vec<ValType> {
let mut result = Vec::new();
$(
result.extend_from_slice(type_id_to_wasmtime(
TypeId::of::<$args>(),
type_name::<$args>(),
));
)*
result
}
fn wasmtime_results(&'static self) -> WasmResultArray<ValType> {
R::types()
}
#[allow(unused_assignments)]
#[allow(unused_variables)]
#[allow(non_snake_case)]
#[allow(unused_mut)]
fn trampoline(&'static self) -> TrampolineFn {
Box::new(
|mut caller: Caller<'_, ScanContext>,
args_and_results: &mut [ValRaw]|
-> anyhow::Result<()> {
let mut i = 0;
$(
let $args = args_and_results[i].raw_into(caller.data_mut());
i += 1;
)*
let result = (self.target_fn)(&mut caller, $($args),*);
let result = result.values(caller.data_mut());
let result_slice = result.as_slice();
let num_results = result_slice.len();
args_and_results[0..num_results].clone_from_slice(result_slice);
anyhow::Ok(())
},
)
}
}
};
}
impl_wasm_exported_fn!(WasmExportedFn0);
impl_wasm_exported_fn!(WasmExportedFn1 A1);
impl_wasm_exported_fn!(WasmExportedFn2 A1 A2);
impl_wasm_exported_fn!(WasmExportedFn3 A1 A2 A3);
impl_wasm_exported_fn!(WasmExportedFn4 A1 A2 A3 A4);
#[derive(Clone)]
pub(crate) struct WasmSymbols {
pub main_memory: walrus::MemoryId,
pub check_for_pattern_match: walrus::FunctionId,
pub filesize: walrus::GlobalId,
pub pattern_search_done: walrus::GlobalId,
pub i64_tmp_a: walrus::LocalId,
pub i64_tmp_b: walrus::LocalId,
pub i32_tmp: walrus::LocalId,
pub f64_tmp: walrus::LocalId,
}
pub(crate) static CONFIG: LazyLock<Config> = LazyLock::new(|| {
let mut config = Config::default();
#[cfg(target_env = "musl")]
config.native_unwind_info(false);
config.cranelift_opt_level(runtime::OptLevel::SpeedAndSize);
config.epoch_interruption(true);
config.memory_reservation(0x1000000);
config.memory_reservation_for_growth(0);
config.memory_may_move(false);
config
});
static mut ENGINE: OnceLock<Engine> = OnceLock::new();
pub(crate) fn get_engine<'a>() -> &'a Engine {
unsafe {
#[allow(static_mut_refs)]
ENGINE.get_or_init(|| Engine::new(&CONFIG).unwrap())
}
}
pub(crate) unsafe fn free_engine() {
#[cfg(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "riscv64",
target_arch = "s390x",
))]
{
#[allow(static_mut_refs)]
unsafe {
ENGINE.take().unwrap().unload_process_handlers()
}
}
}
pub(crate) fn new_linker() -> Linker<ScanContext<'static, 'static>> {
let engine = get_engine();
let mut linker = Linker::<ScanContext<'static, 'static>>::new(engine);
for export in wasm_exports() {
let func_type = FuncType::new(
engine,
export.func.wasmtime_args(),
export.func.wasmtime_results(),
);
unsafe {
linker
.func_new_unchecked(
export.rust_module_path,
export.fully_qualified_mangled_name().as_str(),
func_type,
export.sync_flags,
export.func.trampoline(),
)
.unwrap();
}
}
linker
}
#[wasm_export(sync = "both")]
pub(crate) fn search_for_patterns(caller: &mut Caller<'_, ScanContext>) {
if matches!(
caller.data_mut().search_for_patterns(),
Err(ScanError::Timeout)
) {
caller.as_context_mut().set_epoch_deadline(0);
}
}
#[wasm_export(sync = "both")]
pub(crate) fn rule_match(
caller: &mut Caller<'_, ScanContext>,
rule_id: RuleId,
) {
caller.data_mut().track_rule_match(rule_id);
}
#[wasm_export(sync = "both")]
pub(crate) fn rule_no_match(
caller: &mut Caller<'_, ScanContext>,
rule_id: RuleId,
) {
caller.data_mut().track_rule_no_match(rule_id);
}
#[wasm_export(sync = "none")]
pub(crate) fn is_pat_match_at(
caller: &mut Caller<'_, ScanContext>,
pattern_id: PatternId,
offset: i64,
) -> bool {
if offset < 0 {
return false;
}
if let Some(matches) = caller.data().pattern_matches.get(pattern_id) {
matches.search(offset.try_into().unwrap()).is_ok()
} else {
false
}
}
#[wasm_export(sync = "none")]
pub(crate) fn is_pat_match_in(
caller: &mut Caller<'_, ScanContext>,
pattern_id: PatternId,
lower_bound: i64,
upper_bound: i64,
) -> bool {
if let Some(matches) = caller.data().pattern_matches.get(pattern_id) {
matches
.matches_in_range(lower_bound as isize..=upper_bound as isize)
.is_positive()
} else {
false
}
}
#[wasm_export(sync = "none")]
pub(crate) fn pat_range_match(
caller: &mut Caller<'_, ScanContext>,
pattern_id_start: PatternId,
pattern_id_end: PatternId,
required: i64,
) -> bool {
assert!(pattern_id_start <= pattern_id_end);
let range: RangeInclusive<usize> =
pattern_id_start.into()..=pattern_id_end.into();
let required = required.try_into().unwrap();
let ctx = caller.data();
let mut num_matches = 0;
for pattern_id in range {
let match_found = ctx
.pattern_matches
.get(pattern_id.into())
.is_some_and(|matches| matches.len() > 0);
if match_found {
num_matches += 1;
}
}
num_matches >= required
}
#[wasm_export(sync = "none")]
pub(crate) fn pat_matches(
caller: &mut Caller<'_, ScanContext>,
pattern_id: PatternId,
) -> i64 {
if let Some(matches) = caller.data().pattern_matches.get(pattern_id) {
matches.len().try_into().unwrap()
} else {
0
}
}
#[wasm_export(sync = "none")]
pub(crate) fn pat_matches_in(
caller: &mut Caller<'_, ScanContext>,
pattern_id: PatternId,
lower_bound: i64,
upper_bound: i64,
) -> i64 {
if let Some(matches) = caller.data().pattern_matches.get(pattern_id) {
matches.matches_in_range(lower_bound as isize..=upper_bound as isize)
} else {
0
}
}
#[wasm_export(sync = "none")]
pub(crate) fn pat_length(
caller: &mut Caller<'_, ScanContext>,
pattern_id: PatternId,
index: i64,
) -> Option<i64> {
if let Some(matches) = caller.data().pattern_matches.get(pattern_id) {
let index: usize = index.try_into().ok()?;
let m = matches.get(index.checked_sub(1)?)?;
Some(ExactSizeIterator::len(&m.range) as i64)
} else {
None
}
}
#[wasm_export(sync = "none")]
pub(crate) fn pat_offset(
caller: &mut Caller<'_, ScanContext>,
pattern_id: PatternId,
index: i64,
) -> Option<i64> {
if let Some(matches) = caller.data().pattern_matches.get(pattern_id) {
let index: usize = index.try_into().ok()?;
let m = matches.get(index.checked_sub(1)?)?;
Some(m.range.start as i64)
} else {
None
}
}
#[wasm_export(name = "len", method_of = "RuntimeString", sync = "none")]
pub(crate) fn string_len(
caller: &mut Caller<'_, ScanContext>,
string: RuntimeString,
) -> i64 {
string.as_bstr(caller.as_context().data()).len() as i64
}
#[wasm_export(name = "len", method_of = "Array", sync = "none")]
pub(crate) fn array_len(
_: &mut Caller<'_, ScanContext>,
array: Rc<Array>,
) -> i64 {
array.len() as i64
}
#[wasm_export(name = "len", method_of = "Map", sync = "none")]
pub(crate) fn map_len(_: &mut Caller<'_, ScanContext>, map: Rc<Map>) -> i64 {
map.len() as i64
}
fn lookup_field(
caller: &mut Caller<'_, ScanContext>,
structure: Option<Rc<Struct>>,
num_lookup_indexes: i32,
) -> TypeValue {
assert!(num_lookup_indexes > 0);
let mut store_ctx = caller.as_context_mut();
let mem_ptr = store_ctx
.data_mut()
.wasm_main_memory
.unwrap()
.data_ptr(&mut store_ctx);
let lookup_indexes_ptr =
unsafe { mem_ptr.offset(LOOKUP_INDEXES_START as isize) };
let lookup_indexes = unsafe {
std::slice::from_raw_parts::<i32>(
lookup_indexes_ptr as *const i32,
num_lookup_indexes as usize,
)
};
let mut structure =
structure.as_deref().unwrap_or(&store_ctx.data().root_struct);
let mut final_field = None;
for field_index in lookup_indexes {
let field_index = if cfg!(target_endian = "big") {
field_index.swap_bytes()
} else {
*field_index
};
let field = structure
.field_by_index(field_index as usize)
.unwrap_or_else(|| {
panic!(
"expecting field with index {field_index} in {structure:#?}"
)
});
final_field = Some(field);
if let TypeValue::Struct(s) = &field.type_value {
structure = s
}
}
final_field.unwrap().type_value.clone()
}
#[wasm_export(sync = "before")]
pub(crate) fn lookup_string(
caller: &mut Caller<'_, ScanContext>,
structure: Option<Rc<Struct>>,
num_lookup_indexes: i32,
) -> Option<RuntimeString> {
match lookup_field(caller, structure, num_lookup_indexes) {
TypeValue::String { value: Value::Var(s), .. } => {
Some(RuntimeString::Rc(s))
}
TypeValue::String { value: Value::Const(s), .. } => {
Some(RuntimeString::Rc(s))
}
TypeValue::String { value: Value::Unknown, .. } => None,
_ => unreachable!(),
}
}
#[wasm_export(sync = "before")]
pub(crate) fn lookup_object(
caller: &mut Caller<'_, ScanContext>,
structure: Option<Rc<Struct>>,
num_lookup_indexes: i32,
) -> RuntimeObjectHandle {
let type_value = lookup_field(caller, structure, num_lookup_indexes);
let ctx = caller.data_mut();
match type_value {
TypeValue::Struct(s) => ctx.store_struct(s),
TypeValue::Array(a) => ctx.store_array(a),
TypeValue::Map(m) => ctx.store_map(m),
_ => unreachable!(),
}
}
macro_rules! gen_lookup_fn {
($name:ident, $return_type:ty, $type:path) => {
#[wasm_export(sync = "before")]
pub(crate) fn $name(
caller: &mut Caller<'_, ScanContext>,
structure: Option<Rc<Struct>>,
num_lookup_indexes: i32,
) -> Option<$return_type> {
if let $type { value, .. } =
lookup_field(caller, structure, num_lookup_indexes)
{
value.extract().cloned()
} else {
None
}
}
};
}
gen_lookup_fn!(lookup_integer, i64, TypeValue::Integer);
gen_lookup_fn!(lookup_float, f64, TypeValue::Float);
gen_lookup_fn!(lookup_bool, bool, TypeValue::Bool);
macro_rules! gen_array_indexing_fn {
($name:ident, $fn:ident, $return_type:ty) => {
#[wasm_export(sync = "none")]
pub(crate) fn $name(
_: &mut Caller<'_, ScanContext>,
array: Rc<Array>,
index: i64,
) -> Option<$return_type> {
array.$fn().get(index as usize).map(|value| *value)
}
};
}
gen_array_indexing_fn!(array_indexing_integer, as_integer_array, i64);
gen_array_indexing_fn!(array_indexing_float, as_float_array, f64);
gen_array_indexing_fn!(array_indexing_bool, as_bool_array, bool);
#[wasm_export(sync = "none")]
#[rustfmt::skip]
pub(crate) fn array_indexing_string(
_: &mut Caller<'_, ScanContext>,
array: Rc<Array>,
index: i64,
) -> Option<Rc<BString>> {
array
.as_string_array()
.get(index as usize)
.cloned()
}
#[wasm_export(sync = "none")]
#[rustfmt::skip]
pub(crate) fn array_indexing_struct(
_: &mut Caller<'_, ScanContext>,
array: Rc<Array>,
index: i64,
) -> Option<Rc<Struct>> {
array
.as_struct_array()
.get(index as usize)
.cloned()
}
macro_rules! gen_map_lookup_fn {
($name:ident, i64, i64) => {
gen_map_lookup_fn!($name, i64, i64, with_integer_keys, as_integer);
};
($name:ident, i64, f64) => {
gen_map_lookup_fn!($name, i64, f64, with_integer_keys, as_float);
};
($name:ident, i64, bool) => {
gen_map_lookup_fn!($name, i64, bool, with_integer_keys, as_bool);
};
($name:ident, RuntimeString, i64) => {
gen_map_lookup_fn!(
$name,
RuntimeString,
i64,
with_string_keys,
as_integer
);
};
($name:ident, RuntimeString, f64) => {
gen_map_lookup_fn!(
$name,
RuntimeString,
f64,
with_string_keys,
as_float
);
};
($name:ident, RuntimeString, bool) => {
gen_map_lookup_fn!(
$name,
RuntimeString,
bool,
with_string_keys,
as_bool
);
};
($name:ident, i64, $return_type:ty, $with:ident, $as:ident) => {
#[wasm_export(sync = "none")]
pub(crate) fn $name(
_: &mut Caller<'_, ScanContext>,
map: Rc<Map>,
key: i64,
) -> Option<$return_type> {
map.$with().get(&key).map(|v| v.$as())
}
};
($name:ident, RuntimeString, $return_type:ty, $with:ident, $as:ident) => {
#[wasm_export(sync = "none")]
pub(crate) fn $name(
caller: &mut Caller<'_, ScanContext>,
map: Rc<Map>,
key: RuntimeString,
) -> Option<$return_type> {
let key = key.as_bstr(caller.data());
map.$with().get(key).map(|v| v.$as())
}
};
}
#[rustfmt::skip]
gen_map_lookup_fn!(
map_lookup_string_integer,
RuntimeString,
i64
);
#[rustfmt::skip]
gen_map_lookup_fn!(
map_lookup_string_float,
RuntimeString,
f64
);
#[rustfmt::skip]
gen_map_lookup_fn!(
map_lookup_string_bool,
RuntimeString,
bool
);
#[rustfmt::skip]
gen_map_lookup_fn!(
map_lookup_integer_integer,
i64,
i64
);
#[rustfmt::skip]
gen_map_lookup_fn!(
map_lookup_integer_float,
i64,
f64
);
#[rustfmt::skip]
gen_map_lookup_fn!(
map_lookup_integer_bool,
i64,
bool
);
#[wasm_export(sync = "none")]
pub(crate) fn map_lookup_integer_string(
_: &mut Caller<'_, ScanContext>,
map: Rc<Map>,
key: i64,
) -> Option<Rc<BString>> {
map.with_integer_keys().get(&key).map(|s| s.as_string())
}
#[wasm_export(sync = "none")]
pub(crate) fn map_lookup_string_string(
caller: &mut Caller<'_, ScanContext>,
map: Rc<Map>,
key: RuntimeString,
) -> Option<Rc<BString>> {
let key = key.as_bstr(caller.data());
map.with_string_keys().get(key).map(|s| s.as_string())
}
#[wasm_export(sync = "none")]
pub(crate) fn map_lookup_integer_struct(
_: &mut Caller<'_, ScanContext>,
map: Rc<Map>,
key: i64,
) -> Option<Rc<Struct>> {
map.with_integer_keys().get(&key).map(|v| v.as_struct())
}
#[wasm_export(sync = "none")]
pub(crate) fn map_lookup_string_struct(
caller: &mut Caller<'_, ScanContext>,
map: Rc<Map>,
key: RuntimeString,
) -> Option<Rc<Struct>> {
let key = key.as_bstr(caller.data());
map.with_string_keys().get(key).map(|v| v.as_struct())
}
macro_rules! gen_map_lookup_by_index_fn {
($name:ident, RuntimeString, $val:ty, $with:ident, $as:ident) => {
#[wasm_export(sync = "none")]
pub(crate) fn $name(
_: &mut Caller<'_, ScanContext>,
map: Rc<Map>,
index: i64,
) -> (Rc<BString>, $val) {
map.with_string_keys()
.get_index(index as usize)
.map(|(key, value)| (Rc::new(key.clone()), value.$as()))
.unwrap()
}
};
($name:ident, $key:ty, $val:ty, $with:ident, $as:ident) => {
#[wasm_export(sync = "none")]
pub(crate) fn $name(
_: &mut Caller<'_, ScanContext>,
map: Rc<Map>,
index: i64,
) -> ($key, $val) {
map.$with()
.get_index(index as usize)
.map(|(key, value)| (*key, value.$as()))
.unwrap()
}
};
}
#[rustfmt::skip]
gen_map_lookup_by_index_fn!(
map_lookup_by_index_integer_integer,
i64,
i64,
with_integer_keys,
as_integer
);
#[rustfmt::skip]
gen_map_lookup_by_index_fn!(
map_lookup_by_index_integer_float,
i64,
f64,
with_integer_keys,
as_float
);
#[rustfmt::skip]
gen_map_lookup_by_index_fn!(
map_lookup_by_index_integer_bool,
i64,
bool,
with_integer_keys,
as_bool
);
#[rustfmt::skip]
gen_map_lookup_by_index_fn!(
map_lookup_by_index_string_integer,
RuntimeString,
i64,
with_string_keys,
as_integer
);
#[rustfmt::skip]
gen_map_lookup_by_index_fn!(
map_lookup_by_index_string_float,
RuntimeString,
f64,
with_string_keys,
as_float
);
#[rustfmt::skip]
gen_map_lookup_by_index_fn!(
map_lookup_by_index_string_bool,
RuntimeString,
bool,
with_string_keys,
as_bool
);
#[wasm_export(sync = "none")]
pub(crate) fn map_lookup_by_index_integer_string(
_: &mut Caller<'_, ScanContext>,
map: Rc<Map>,
index: i64,
) -> (i64, Rc<BString>) {
map.with_integer_keys()
.get_index(index as usize)
.map(|(key, value)| (*key, value.as_string()))
.unwrap()
}
#[wasm_export(sync = "none")]
pub(crate) fn map_lookup_by_index_string_string(
_: &mut Caller<'_, ScanContext>,
map: Rc<Map>,
index: i64,
) -> (Rc<BString>, Rc<BString>) {
map.with_string_keys()
.get_index(index as usize)
.map(|(key, value)| {
(Rc::new(key.as_bstr().to_owned()), value.as_string())
})
.unwrap()
}
#[wasm_export(sync = "none")]
pub(crate) fn map_lookup_by_index_integer_struct(
_: &mut Caller<'_, ScanContext>,
map: Rc<Map>,
index: i64,
) -> (i64, Rc<Struct>) {
map.with_integer_keys()
.get_index(index as usize)
.map(|(key, value)| (*key, value.as_struct()))
.unwrap()
}
#[wasm_export(sync = "none")]
pub(crate) fn map_lookup_by_index_string_struct(
_: &mut Caller<'_, ScanContext>,
map: Rc<Map>,
index: i64,
) -> (Rc<BString>, Rc<Struct>) {
map.with_string_keys()
.get_index(index as usize)
.map(|(key, value)| {
(Rc::new(key.as_bstr().to_owned()), value.as_struct())
})
.unwrap()
}
macro_rules! gen_str_cmp_fn {
($name:ident, $op:tt) => {
#[wasm_export(sync = "none")]
pub(crate) fn $name(
caller: &mut Caller<'_, ScanContext>,
lhs: RuntimeString,
rhs: RuntimeString,
) -> bool {
lhs.$op(&rhs, caller.data())
}
};
}
gen_str_cmp_fn!(str_eq, eq);
gen_str_cmp_fn!(str_ne, ne);
gen_str_cmp_fn!(str_lt, lt);
gen_str_cmp_fn!(str_gt, gt);
gen_str_cmp_fn!(str_le, le);
gen_str_cmp_fn!(str_ge, ge);
macro_rules! gen_str_op_fn {
($name:ident, $op:tt, $case_insensitive:literal) => {
#[wasm_export(sync = "none")]
pub(crate) fn $name(
caller: &mut Caller<'_, ScanContext>,
lhs: RuntimeString,
rhs: RuntimeString,
) -> bool {
lhs.$op(&rhs, caller.data(), $case_insensitive)
}
};
}
gen_str_op_fn!(str_contains, contains, false);
gen_str_op_fn!(str_startswith, starts_with, false);
gen_str_op_fn!(str_endswith, ends_with, false);
gen_str_op_fn!(str_icontains, contains, true);
gen_str_op_fn!(str_istartswith, starts_with, true);
gen_str_op_fn!(str_iendswith, ends_with, true);
gen_str_op_fn!(str_iequals, equals, true);
#[wasm_export(sync = "none")]
pub(crate) fn str_len(
caller: &mut Caller<'_, ScanContext>,
s: RuntimeString,
) -> i64 {
s.len(caller.data()) as i64
}
#[wasm_export(sync = "none")]
pub(crate) fn str_matches(
caller: &mut Caller<'_, ScanContext>,
lhs: RuntimeString,
rhs: RegexpId,
) -> bool {
let ctx = caller.data();
ctx.regexp_matches(rhs, lhs.as_bstr(ctx))
}
macro_rules! gen_int_fn {
($name:ident, $return_type:ty, $from_fn:ident, $min:expr, $max:expr) => {
#[wasm_export(public = true, sync = "none")]
pub(crate) fn $name(
caller: &mut Caller<'_, ScanContext>,
offset: i64,
) -> Option<RangedInteger<$min, $max>> {
let offset = usize::try_from(offset).ok()?;
caller
.data()
.scanned_data()?
.get(offset..offset + mem::size_of::<$return_type>())
.map(|bytes| {
<$return_type>::$from_fn(bytes.try_into().unwrap()) as i64
})
.map(|i| RangedInteger::<$min, $max>::new(i))
}
};
}
gen_int_fn!(uint8, u8, from_le_bytes, 0, 255);
gen_int_fn!(uint16, u16, from_le_bytes, 0, 65_535);
gen_int_fn!(uint32, u32, from_le_bytes, 0, 4_294_967_295);
gen_int_fn!(uint8be, u8, from_be_bytes, 0, 255);
gen_int_fn!(uint16be, u16, from_be_bytes, 0, 65_535);
gen_int_fn!(uint32be, u32, from_be_bytes, 0, 4_294_967_295);
gen_int_fn!(int8, i8, from_le_bytes, -128, 127);
gen_int_fn!(int16, i16, from_le_bytes, -32_768, 32_767);
gen_int_fn!(int32, i32, from_le_bytes, -2_147_483_648, 2_147_483_647);
gen_int_fn!(int8be, i8, from_be_bytes, -128, 127);
gen_int_fn!(int16be, i16, from_be_bytes, -32_768, 32_767);
gen_int_fn!(int32be, i32, from_be_bytes, -2_147_483_648, 2_147_483_647);
macro_rules! gen_float_fn {
($name:ident, $return_type:ty, $from_fn:ident) => {
#[wasm_export(public = true, sync = "none")]
pub(crate) fn $name(
caller: &mut Caller<'_, ScanContext>,
offset: i64,
) -> Option<f64> {
let offset = usize::try_from(offset).ok()?;
caller
.data()
.scanned_data()?
.get(offset..offset + mem::size_of::<$return_type>())
.map(|bytes| {
<$return_type>::$from_fn(bytes.try_into().unwrap()) as f64
})
}
};
}
gen_float_fn!(float32, f32, from_le_bytes);
gen_float_fn!(float64, f64, from_le_bytes);
gen_float_fn!(float32be, f32, from_be_bytes);
gen_float_fn!(float64be, f64, from_be_bytes);