From d782a3f52465ce442008b1498f4d1b2db6f90d0a Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sat, 26 Aug 2023 01:13:21 +0200 Subject: [PATCH] Implement hashing for types --- crates/rune/Cargo.toml | 3 +- crates/rune/src/compile/context.rs | 1 + crates/rune/src/lib.rs | 2 +- crates/rune/src/module/module.rs | 2 +- crates/rune/src/modules.rs | 1 + crates/rune/src/modules/hash.rs | 16 ++++++++++ crates/rune/src/modules/ops.rs | 47 ++++++++++++++++++++++++++++++ crates/rune/src/modules/tuple.rs | 21 +++++++++++++ crates/rune/src/modules/vec.rs | 19 ++++++++++++ crates/rune/src/runtime/tuple.rs | 15 ++++++++++ crates/rune/src/runtime/value.rs | 18 ++++++++++-- crates/rune/src/runtime/vec.rs | 15 ++++++++++ 12 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 crates/rune/src/modules/hash.rs diff --git a/crates/rune/Cargo.toml b/crates/rune/Cargo.toml index 61894bd65..9d05a3bd8 100644 --- a/crates/rune/Cargo.toml +++ b/crates/rune/Cargo.toml @@ -25,7 +25,7 @@ byte-code = ["alloc", "musli-storage"] capture-io = ["alloc", "parking_lot"] disable-io = ["alloc"] fmt = ["alloc"] -std = ["num/std", "serde/std", "rune-core/std", "musli/std", "musli-storage/std", "alloc", "anyhow"] +std = ["num/std", "serde/std", "rune-core/std", "musli/std", "musli-storage/std", "alloc", "anyhow", "lazy_static"] alloc = [] [dependencies] @@ -74,6 +74,7 @@ similar = { version = "2.2.1", optional = true, features = ["inline", "bytes"] } sha2 = { version = "0.10.6", optional = true } base64 = { version = "0.21.0", optional = true } rand = { version = "0.8.5", optional = true } +lazy_static = { version = "1.4.0", optional = true } [dev-dependencies] tokio = { version = "1.28.1", features = ["full"] } diff --git a/crates/rune/src/compile/context.rs b/crates/rune/src/compile/context.rs index 754a14a6a..e18475a61 100644 --- a/crates/rune/src/compile/context.rs +++ b/crates/rune/src/compile/context.rs @@ -139,6 +139,7 @@ impl Context { this.install(crate::modules::any::module()?)?; this.install(crate::modules::bytes::module()?)?; this.install(crate::modules::char::module()?)?; + this.install(crate::modules::hash::module()?)?; this.install(crate::modules::cmp::module()?)?; this.install(crate::modules::collections::module()?)?; this.install(crate::modules::f64::module()?)?; diff --git a/crates/rune/src/lib.rs b/crates/rune/src/lib.rs index a04d3ae81..6f8253e61 100644 --- a/crates/rune/src/lib.rs +++ b/crates/rune/src/lib.rs @@ -379,7 +379,7 @@ pub(crate) use rune_macros::__internal_impl_any; /// } /// /// /// Construct a new [`Struct`]. -/// #[rune::function(path = Struct::new)] +/// #[rune::function(free, path = Struct::new)] /// fn new() -> Struct { /// Struct { /// /* .. */ diff --git a/crates/rune/src/module/module.rs b/crates/rune/src/module/module.rs index ea5ad8010..d98bd0ed7 100644 --- a/crates/rune/src/module/module.rs +++ b/crates/rune/src/module/module.rs @@ -955,7 +955,7 @@ impl Module { /// } /// /// /// Construct a new [`Struct`]. - /// #[rune::function(path = Struct::new)] + /// #[rune::function(free, path = Struct::new)] /// fn new() -> Struct { /// Struct { /// /* .. */ diff --git a/crates/rune/src/modules.rs b/crates/rune/src/modules.rs index 9628e7a2a..372667d8d 100644 --- a/crates/rune/src/modules.rs +++ b/crates/rune/src/modules.rs @@ -18,6 +18,7 @@ pub mod f64; pub mod fmt; pub mod future; pub mod generator; +pub mod hash; pub mod i64; #[cfg(feature = "std")] pub mod io; diff --git a/crates/rune/src/modules/hash.rs b/crates/rune/src/modules/hash.rs new file mode 100644 index 000000000..127ad55e6 --- /dev/null +++ b/crates/rune/src/modules/hash.rs @@ -0,0 +1,16 @@ +//! The `std::hash` module. + +use crate as rune; +#[cfg(feature = "std")] +use crate::runtime::Hasher; +use crate::{ContextError, Module}; + +#[rune::module(::std::hash)] +/// Types for dealing with hashing in Rune. +pub fn module() -> Result { + #[allow(unused_mut)] + let mut module = Module::from_meta(self::module_meta); + #[cfg(feature = "std")] + module.ty::()?; + Ok(module) +} diff --git a/crates/rune/src/modules/ops.rs b/crates/rune/src/modules/ops.rs index 6dad232d3..81805b10a 100644 --- a/crates/rune/src/modules/ops.rs +++ b/crates/rune/src/modules/ops.rs @@ -3,11 +3,15 @@ use core::cmp::Ordering; use crate as rune; +#[cfg(feature = "std")] +use crate::runtime::Hasher; use crate::runtime::{ ControlFlow, EnvProtocolCaller, Function, Generator, GeneratorState, Iterator, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive, Value, Vm, VmResult, }; use crate::{ContextError, Module}; +#[cfg(feature = "std")] +use std::collections::hash_map::RandomState; #[rune::module(::std::ops)] /// Overloadable operators. @@ -96,6 +100,8 @@ pub fn module() -> Result { m.function_meta(eq)?; m.function_meta(partial_cmp)?; m.function_meta(cmp)?; + #[cfg(feature = "std")] + m.function_meta(hash)?; Ok(m) } @@ -201,6 +207,47 @@ fn cmp(lhs: Value, rhs: Value) -> VmResult { Value::cmp(&lhs, &rhs) } +#[cfg(feature = "std")] +lazy_static::lazy_static! { + static ref STATE: RandomState = RandomState::new(); +} + +/// Hashes the given value. +/// +/// For non-builtin types this uses the [`HASH`] protocol. +/// +/// # Hash stability +/// +/// The hash is guaranteed to be stable within a single virtual machine +/// invocation, but not across virtual machines. So returning the hash from one +/// and calculating it in another using an identical value is not guaranteed to +/// produce the same hash. +/// +/// # Panics +/// +/// Panics if we try to generate a hash from an unhashable value. +/// +/// # Examples +/// +/// ```rune +/// use std::ops::hash; +/// +/// assert_eq!(hash([1, 2]), hash((1, 2))); +/// ``` +#[rune::function] +#[cfg(feature = "std")] +fn hash(value: Value) -> VmResult { + let mut hasher = Hasher::new_with(&*STATE); + + vm_try!(Value::hash_with( + &value, + &mut hasher, + &mut EnvProtocolCaller + )); + + VmResult::Ok(hasher.finish() as i64) +} + /// Advance a generator producing the next value yielded. /// /// Unlike [`Generator::resume`], this can only consume the yielded values. diff --git a/crates/rune/src/modules/tuple.rs b/crates/rune/src/modules/tuple.rs index b56879cf4..e6d79c79d 100644 --- a/crates/rune/src/modules/tuple.rs +++ b/crates/rune/src/modules/tuple.rs @@ -3,6 +3,8 @@ use core::cmp::Ordering; use crate as rune; +#[cfg(feature = "std")] +use crate::runtime::Hasher; use crate::runtime::{EnvProtocolCaller, Iterator, Ref, Tuple, Value, Vec, VmResult}; use crate::{ContextError, Module}; @@ -20,6 +22,8 @@ pub fn module() -> Result { m.function_meta(eq)?; m.function_meta(partial_cmp)?; m.function_meta(cmp)?; + #[cfg(feature = "std")] + m.function_meta(hash)?; Ok(m) } @@ -174,3 +178,20 @@ fn partial_cmp(this: &Tuple, other: &Tuple) -> VmResult> { fn cmp(this: &Tuple, other: &Tuple) -> VmResult { Vec::cmp_with(this, other, &mut EnvProtocolCaller) } + +/// Calculate a hash for a tuple. +/// +/// # Examples +/// +/// ```rune +/// use std::ops::hash; +/// +/// assert_eq!(hash((0, 2, 3)), hash((0, 2, 3))); +/// // Note: this is not guaranteed to be true forever, but it's true right now. +/// assert_eq!(hash((0, 2, 3)), hash([0, 2, 3])); +/// ``` +#[rune::function(instance, protocol = HASH)] +#[cfg(feature = "std")] +fn hash(this: &Tuple, hasher: &mut Hasher) -> VmResult<()> { + Tuple::hash_with(this, hasher, &mut EnvProtocolCaller) +} diff --git a/crates/rune/src/modules/vec.rs b/crates/rune/src/modules/vec.rs index 2f0b3283a..c4ba74526 100644 --- a/crates/rune/src/modules/vec.rs +++ b/crates/rune/src/modules/vec.rs @@ -4,6 +4,8 @@ use core::cmp::Ordering; use core::fmt; use crate as rune; +#[cfg(feature = "std")] +use crate::runtime::Hasher; use crate::runtime::{ EnvProtocolCaller, Formatter, Function, Iterator, Ref, TypeOf, Value, Vec, VmErrorKind, VmResult, @@ -54,6 +56,8 @@ pub fn module() -> Result { m.function_meta(eq)?; m.function_meta(partial_cmp)?; m.function_meta(cmp)?; + #[cfg(feature = "std")] + m.function_meta(hash)?; Ok(m) } @@ -615,3 +619,18 @@ fn partial_cmp(this: &Vec, other: &Vec) -> VmResult> { fn cmp(this: &Vec, other: &Vec) -> VmResult { Vec::cmp_with(this, other, &mut EnvProtocolCaller) } + +/// Calculate the hash of a vector. +/// +/// # Examples +/// +/// ```rune +/// use std::ops::hash; +/// +/// assert_eq!(hash([0, 2, 3]), hash([0, 2, 3])); +/// ``` +#[rune::function(instance, protocol = HASH)] +#[cfg(feature = "std")] +fn hash(this: &Vec, hasher: &mut Hasher) -> VmResult<()> { + Vec::hash_with(this, hasher, &mut EnvProtocolCaller) +} diff --git a/crates/rune/src/runtime/tuple.rs b/crates/rune/src/runtime/tuple.rs index 424a47bee..401e54486 100644 --- a/crates/rune/src/runtime/tuple.rs +++ b/crates/rune/src/runtime/tuple.rs @@ -9,6 +9,8 @@ use crate::runtime::{ ConstValue, FromValue, Mut, RawMut, RawRef, Ref, Shared, ToValue, UnsafeToMut, UnsafeToRef, Value, VmErrorKind, VmResult, }; +#[cfg(feature = "std")] +use crate::runtime::{Hasher, ProtocolCaller}; use crate::Any; /// The type of a tuple slice. @@ -44,6 +46,19 @@ impl Tuple { VmResult::Ok(Some(vm_try!(T::from_value(value)))) } + + #[cfg(feature = "std")] + pub(crate) fn hash_with( + &self, + hasher: &mut Hasher, + caller: &mut impl ProtocolCaller, + ) -> VmResult<()> { + for value in self.values.iter() { + vm_try!(value.hash_with(hasher, caller)); + } + + VmResult::Ok(()) + } } impl ops::Deref for Tuple { diff --git a/crates/rune/src/runtime/value.rs b/crates/rune/src/runtime/value.rs index db9bbc7e1..1b1df07c6 100644 --- a/crates/rune/src/runtime/value.rs +++ b/crates/rune/src/runtime/value.rs @@ -12,8 +12,6 @@ use crate::no_std::vec; use crate::compile::ItemBuf; use crate::runtime::vm::CallResult; -#[cfg(feature = "std")] -use crate::runtime::Hasher; use crate::runtime::{ AccessKind, AnyObj, Bytes, ConstValue, ControlFlow, EnvProtocolCaller, Format, Formatter, FromValue, FullTypeOf, Function, Future, Generator, GeneratorState, Iterator, MaybeTypeOf, Mut, @@ -21,6 +19,8 @@ use crate::runtime::{ RangeTo, RangeToInclusive, RawMut, RawRef, Ref, Shared, Stream, ToValue, Type, TypeInfo, Variant, Vec, Vm, VmError, VmErrorKind, VmIntegerRepr, VmResult, }; +#[cfg(feature = "std")] +use crate::runtime::{Hasher, Tuple}; use crate::{Any, Hash}; use serde::{de, ser, Deserialize, Serialize}; @@ -1339,6 +1339,12 @@ impl Value { }) } + /// Hash the current value. + #[cfg(feature = "std")] + pub fn hash(&self, hasher: &mut Hasher) -> VmResult<()> { + self.hash_with(hasher, &mut EnvProtocolCaller) + } + /// Hash the current value. #[cfg(feature = "std")] pub(crate) fn hash_with( @@ -1377,6 +1383,14 @@ impl Value { hasher.write(&bytes); return VmResult::Ok(()); } + Value::Tuple(tuple) => { + let tuple = vm_try!(tuple.borrow_ref()); + return Tuple::hash_with(&tuple, hasher, caller); + } + Value::Vec(vec) => { + let vec = vm_try!(vec.borrow_ref()); + return Vec::hash_with(&vec, hasher, caller); + } value => { match vm_try!(caller.try_call_protocol_fn(Protocol::HASH, value.clone(), (hasher,))) { diff --git a/crates/rune/src/runtime/vec.rs b/crates/rune/src/runtime/vec.rs index 0c2dcd0ba..50289a35a 100644 --- a/crates/rune/src/runtime/vec.rs +++ b/crates/rune/src/runtime/vec.rs @@ -11,6 +11,8 @@ use crate::no_std::prelude::*; use crate::no_std::vec; use crate as rune; +#[cfg(feature = "std")] +use crate::runtime::Hasher; use crate::runtime::{ Formatter, FromValue, Iterator, ProtocolCaller, RawRef, Ref, Shared, ToValue, UnsafeToRef, Value, VmErrorKind, VmResult, @@ -379,6 +381,19 @@ impl Vec { VmResult::Ok(Some(Value::vec(values.to_vec()))) } + + #[cfg(feature = "std")] + pub(crate) fn hash_with( + &self, + hasher: &mut Hasher, + caller: &mut impl ProtocolCaller, + ) -> VmResult<()> { + for value in self.inner.iter() { + vm_try!(value.hash_with(hasher, caller)); + } + + VmResult::Ok(()) + } } impl fmt::Debug for Vec {