From 9e06a8d40c197c205bdaf370d098335ac2cd0399 Mon Sep 17 00:00:00 2001 From: Graydon Hoare Date: Thu, 19 Oct 2023 21:27:24 -0700 Subject: [PATCH] Eliminate uses of HashMap and thread_rng (fix #810) --- soroban-env-host/src/auth.rs | 17 ++++---- soroban-env-host/src/host.rs | 41 ++++++++++++++++++- .../src/native_contract/testutils.rs | 15 ++++--- soroban-env-host/src/test/auth.rs | 9 ++-- soroban-env-host/src/test/complex.rs | 7 ++-- soroban-env-host/src/test/lifecycle.rs | 12 +++--- .../src/test/metering_benchmark.rs | 12 +++--- soroban-env-host/src/test/token.rs | 38 ++++++++--------- soroban-env-host/src/test/util.rs | 20 +++++---- .../src/call_macro_with_all_host_functions.rs | 4 +- soroban-synth-wasm/src/mod_emitter.rs | 14 +++---- 11 files changed, 120 insertions(+), 69 deletions(-) diff --git a/soroban-env-host/src/auth.rs b/soroban-env-host/src/auth.rs index 96ef70fbf..08380a784 100644 --- a/soroban-env-host/src/auth.rs +++ b/soroban-env-host/src/auth.rs @@ -1,15 +1,16 @@ use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::BTreeMap; use std::rc::Rc; -use rand::Rng; use soroban_env_common::xdr::{ ContractDataEntry, CreateContractArgs, HashIdPreimage, HashIdPreimageSorobanAuthorization, InvokeContractArgs, LedgerEntry, LedgerEntryData, LedgerEntryExt, ScAddress, ScErrorCode, ScErrorType, ScNonceKey, ScVal, SorobanAuthorizationEntry, SorobanAuthorizedFunction, SorobanCredentials, }; -use soroban_env_common::{AddressObject, Compare, Symbol, TryFromVal, TryIntoVal, Val, VecObject}; +use soroban_env_common::{ + AddressObject, Compare, Env, Symbol, TryFromVal, TryIntoVal, Val, VecObject, +}; use crate::budget::{AsBudget, Budget}; use crate::host::error::TryBorrowOrErr; @@ -119,7 +120,7 @@ pub struct AuthorizationManagerSnapshot { account_trackers_snapshot: AccountTrackersSnapshot, invoker_contract_tracker_root_snapshots: Vec, // This is set only for the recording mode. - tracker_by_address_handle: Option>, + tracker_by_address_handle: Option>, } // Snapshot of the `account_trackers` in `AuthorizationManager`. @@ -149,7 +150,7 @@ struct RecordingAuthInfo { // This allows to disambiguate between the addresses that have the same // value, but are specified as two different objects (e.g. as two different // contract function inputs). - tracker_by_address_handle: RefCell>, + tracker_by_address_handle: RefCell>, // Whether to allow root authorized invocation to not match the root // contract invocation. disable_non_root_auth: bool, @@ -159,7 +160,7 @@ impl RecordingAuthInfo { fn try_borrow_tracker_by_address_handle( &self, host: &Host, - ) -> Result>, HostError> { + ) -> Result>, HostError> { self.tracker_by_address_handle.try_borrow_or_err_with( host, "recording_auth_info.tracker_by_address_handle.try_borrow failed", @@ -168,7 +169,7 @@ impl RecordingAuthInfo { fn try_borrow_tracker_by_address_handle_mut( &self, host: &Host, - ) -> Result>, HostError> { + ) -> Result>, HostError> { self.tracker_by_address_handle.try_borrow_mut_or_err_with( host, "recording_auth_info.tracker_by_address_handle.try_borrow_mut failed", @@ -1448,7 +1449,7 @@ impl AccountAuthorizationTracker { false }; let nonce = if !is_invoker { - let random_nonce: i64 = rand::thread_rng().gen_range(0..=i64::MAX); + let random_nonce: i64 = host.prng_u64_in_inclusive_range(0, u64::MAX)? as i64; host.consume_nonce(address, random_nonce, 0)?; Some((random_nonce, 0)) } else { diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs index 8d29dd511..29b6cba36 100644 --- a/soroban-env-host/src/host.rs +++ b/soroban-env-host/src/host.rs @@ -53,6 +53,10 @@ use self::{ #[cfg(any(test, feature = "testutils"))] pub use frame::ContractFunctionSet; pub(crate) use frame::Frame; +#[cfg(test)] +use rand_chacha::ChaCha20Rng; +#[cfg(test)] +pub(crate) const TEST_PRNG_SEED: &[u8; 32] = b"12345678901234567890123456789012"; /// Defines the maximum depth for recursive calls in the host, i.e. `Val` conversion, comparison, /// and deep clone, to prevent stack overflow. @@ -113,12 +117,17 @@ struct HostImpl { authorization_manager: RefCell, diagnostic_level: RefCell, base_prng: RefCell>, + // Some tests _of the host_ rely on pseudorandom _input_ data. For these cases we attach + // yet another unmetered PRNG to the host. This should not be exposed through "testutils" + // to clients testing contracts _against_ the host. + #[cfg(test)] + test_prng: RefCell>, // Note: we're not going to charge metering for testutils because it's out of the scope // of what users will be charged for in production -- it's scaffolding for testing a contract, // but shouldn't be charged to the contract itself (and will never be compiled-in to // production hosts) #[cfg(any(test, feature = "testutils"))] - contracts: RefCell>>, + contracts: RefCell>>, // Store a copy of the `AuthorizationManager` for the last host function // invocation. In order to emulate the production behavior in tests, we reset // authorization manager after every invocation (as it's not meant to be @@ -220,8 +229,16 @@ impl_checked_borrow_helpers!( try_borrow_base_prng_mut ); +#[cfg(test)] +impl_checked_borrow_helpers!( + test_prng, + Option, + try_borrow_test_prng, + try_borrow_test_prng_mut +); + #[cfg(any(test, feature = "testutils"))] -impl_checked_borrow_helpers!(contracts, std::collections::HashMap>, try_borrow_contracts, try_borrow_contracts_mut); +impl_checked_borrow_helpers!(contracts, std::collections::BTreeMap>, try_borrow_contracts, try_borrow_contracts_mut); #[cfg(any(test, feature = "testutils"))] impl_checked_borrow_helpers!( @@ -271,6 +288,8 @@ impl Host { ), diagnostic_level: Default::default(), base_prng: RefCell::new(None), + #[cfg(test)] + test_prng: RefCell::new(None), #[cfg(any(test, feature = "testutils"))] contracts: Default::default(), #[cfg(any(test, feature = "testutils"))] @@ -296,6 +315,24 @@ impl Host { Ok(self.try_borrow_source_account()?.metered_clone(self)?) } + #[cfg(test)] + pub(crate) fn with_test_prng( + &self, + f: impl FnOnce(&mut ChaCha20Rng) -> Result, + ) -> Result { + use rand::SeedableRng; + + let mut tpo = self.try_borrow_test_prng_mut()?; + if let Some(p) = tpo.as_mut() { + f(p) + } else { + let mut p = ChaCha20Rng::from_seed(*TEST_PRNG_SEED); + let res = f(&mut p); + *tpo = Some(p); + res + } + } + pub fn source_account_address(&self) -> Result, HostError> { if let Some(acc) = self.try_borrow_source_account()?.as_ref() { Ok(Some(self.add_host_object(ScAddress::Account( diff --git a/soroban-env-host/src/native_contract/testutils.rs b/soroban-env-host/src/native_contract/testutils.rs index 9ec68b40c..9a38ef7bd 100644 --- a/soroban-env-host/src/native_contract/testutils.rs +++ b/soroban-env-host/src/native_contract/testutils.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use crate::{Host, LedgerInfo}; use ed25519_dalek::{Signer, SigningKey}; -use rand::{thread_rng, Rng}; +use rand::Rng; use soroban_env_common::xdr::{ AccountEntry, AccountEntryExt, AccountEntryExtensionV1, AccountEntryExtensionV1Ext, AccountEntryExtensionV2, AccountEntryExtensionV2Ext, AccountId, Hash, HashIdPreimage, @@ -30,8 +30,9 @@ macro_rules! host_vec { }; } -pub(crate) fn generate_signing_key() -> SigningKey { - SigningKey::generate(&mut thread_rng()) +pub(crate) fn generate_signing_key(host: &Host) -> SigningKey { + host.with_test_prng(|chacha| Ok(SigningKey::generate(chacha))) + .unwrap() } pub(crate) fn signing_key_to_account_id(key: &SigningKey) -> AccountId { @@ -203,9 +204,11 @@ pub(crate) fn authorize_single_invocation( ) { let nonce = match signer { TestSigner::AccountInvoker(_) => None, - TestSigner::Account(_) | TestSigner::AccountContract(_) => { - Some((thread_rng().gen_range(0..=i64::MAX), 10000)) - } + TestSigner::Account(_) | TestSigner::AccountContract(_) => Some(( + host.with_test_prng(|chacha| Ok(chacha.gen_range(0..=i64::MAX))) + .unwrap(), + 10000, + )), TestSigner::ContractInvoker(_) => { return; } diff --git a/soroban-env-host/src/test/auth.rs b/soroban-env-host/src/test/auth.rs index 5510bc13f..2c76e09ff 100644 --- a/soroban-env-host/src/test/auth.rs +++ b/soroban-env-host/src/test/auth.rs @@ -1,5 +1,5 @@ use ed25519_dalek::SigningKey; -use rand::{thread_rng, Rng}; +use rand::Rng; use soroban_env_common::xdr::{ AccountId, ContractDataDurability, HashIdPreimage, HashIdPreimageSorobanAuthorization, InvokeContractArgs, PublicKey, ScAddress, ScBytes, ScErrorCode, ScErrorType, ScNonceKey, @@ -110,7 +110,7 @@ impl AuthTest { .unwrap(); let mut accounts = vec![]; for _ in 0..signer_cnt { - accounts.push(generate_signing_key()); + accounts.push(generate_signing_key(&host)); } for signing_key in &accounts { create_account( @@ -218,7 +218,10 @@ impl AuthTest { let sc_address = self.key_to_sc_address(&self.keys[address_id]); let mut curr_nonces = vec![]; for sign_root in &sign_payloads[address_id] { - let nonce = thread_rng().gen_range(0..=i64::MAX); + let nonce = self + .host + .with_test_prng(|chacha| Ok(chacha.gen_range(0..=i64::MAX))) + .unwrap(); curr_nonces.push(nonce); let root_invocation = self.convert_sign_node(sign_root); let payload_preimage = diff --git a/soroban-env-host/src/test/complex.rs b/soroban-env-host/src/test/complex.rs index 8093da39e..8c79831ef 100644 --- a/soroban-env-host/src/test/complex.rs +++ b/soroban-env-host/src/test/complex.rs @@ -21,12 +21,13 @@ fn run_complex() -> Result<(), HostError> { min_temp_entry_ttl: 16, max_entry_ttl: 6312000, }; - let account_id = generate_account_id(); - let salt = generate_bytes_array(); + + let host = Host::test_host_with_recording_footprint(); + let account_id = generate_account_id(&host); + let salt = generate_bytes_array(&host); // Run 1: record footprint, emulating "preflight". let foot = { - let host = Host::test_host_with_recording_footprint(); host.set_ledger_info(info.clone())?; let contract_id_obj = host.register_test_contract_wasm_from_source_account(COMPLEX, account_id.clone(), salt); diff --git a/soroban-env-host/src/test/lifecycle.rs b/soroban-env-host/src/test/lifecycle.rs index 5339bfda0..55234a07a 100644 --- a/soroban-env-host/src/test/lifecycle.rs +++ b/soroban-env-host/src/test/lifecycle.rs @@ -69,7 +69,7 @@ fn test_host() -> Host { let host = Host::with_storage_and_budget(storage, budget); host.set_ledger_info(LedgerInfo { protocol_version: crate::meta::get_ledger_protocol_version(crate::meta::INTERFACE_VERSION), - network_id: generate_bytes_array(), + network_id: generate_bytes_array(&host), ..Default::default() }) .unwrap(); @@ -78,8 +78,8 @@ fn test_host() -> Host { } fn test_create_contract_from_source_account(host: &Host, wasm: &[u8]) -> Hash { - let source_account = generate_account_id(); - let salt = generate_bytes_array(); + let source_account = generate_account_id(host); + let salt = generate_bytes_array(host); host.set_source_account(source_account.clone()).unwrap(); let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress { address: ScAddress::Account(source_account.clone()), @@ -156,7 +156,7 @@ fn create_contract_using_parent_id_test() { let parent_contract_address = host .add_host_object(ScAddress::Contract(parent_contract_id.clone())) .unwrap(); - let salt = generate_bytes_array(); + let salt = generate_bytes_array(&host); let child_pre_image = HashIdPreimage::ContractId(HashIdPreimageContractId { network_id: host .hash_from_bytesobj_input("network_id", host.get_ledger_network_id().unwrap()) @@ -372,8 +372,8 @@ fn test_contract_wasm_update() { fn test_create_contract_from_source_account_recording_auth() { let host = Host::test_host_with_recording_footprint(); - let source_account = generate_account_id(); - let salt = generate_bytes_array(); + let source_account = generate_account_id(&host); + let salt = generate_bytes_array(&host); host.set_source_account(source_account.clone()).unwrap(); host.switch_to_recording_auth(true).unwrap(); let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress { diff --git a/soroban-env-host/src/test/metering_benchmark.rs b/soroban-env-host/src/test/metering_benchmark.rs index b6e17d2a2..07bf65562 100644 --- a/soroban-env-host/src/test/metering_benchmark.rs +++ b/soroban-env-host/src/test/metering_benchmark.rs @@ -39,15 +39,15 @@ fn run_add_i32() -> Result<(), HostError> { let _test_span = tracy_span!("run_add_i32 test"); - let account_id = generate_account_id(); - let salt = generate_bytes_array(); + let host = Host::test_host_with_recording_footprint(); + let account_id = generate_account_id(&host); + let salt = generate_bytes_array(&host); let a = 4i32; let b = 7i32; // Run 1: record footprint, emulating "preflight". let foot = { let _run_span = tracy_span!("add_i32 run 1: recording footprint"); - let host = Host::test_host_with_recording_footprint(); host.set_ledger_info(LEDGER_INFO.clone())?; let contract_id_obj = host.register_test_contract_wasm_from_source_account(ADD_I32, account_id.clone(), salt); @@ -90,13 +90,13 @@ fn run_complex() -> Result<(), HostError> { tracy_client::Client::start(); let _test_span = tracy_span!("run_complex test"); - let account_id = generate_account_id(); - let salt = generate_bytes_array(); + let host = Host::test_host_with_recording_footprint(); + let account_id = generate_account_id(&host); + let salt = generate_bytes_array(&host); // Run 1: record footprint, emulating "preflight". let foot = { let _run_span = tracy_span!("complex run 1: recording footprint"); - let host = Host::test_host_with_recording_footprint(); host.set_ledger_info(LEDGER_INFO.clone())?; let contract_id_obj = host.register_test_contract_wasm_from_source_account(COMPLEX, account_id.clone(), salt); diff --git a/soroban-env-host/src/test/token.rs b/soroban-env-host/src/test/token.rs index f6bdf3d18..38f81fbea 100644 --- a/soroban-env-host/src/test/token.rs +++ b/soroban-env-host/src/test/token.rs @@ -64,12 +64,12 @@ impl TokenTest { }) .unwrap(); Self { - host, - issuer_key: generate_signing_key(), - user_key: generate_signing_key(), - user_key_2: generate_signing_key(), - user_key_3: generate_signing_key(), - user_key_4: generate_signing_key(), + host: host.clone(), + issuer_key: generate_signing_key(&host), + user_key: generate_signing_key(&host), + user_key_2: generate_signing_key(&host), + user_key_3: generate_signing_key(&host), + user_key_4: generate_signing_key(&host), asset_code: [0_u8; 4], } } @@ -451,7 +451,7 @@ fn test_zero_amounts() { let user = TestSigner::account(&test.user_key); let user_2 = TestSigner::account(&test.user_key_2); - let user_contract_id = generate_bytes_array(); + let user_contract_id = generate_bytes_array(&test.host); let user_contract_address = contract_id_to_address(&test.host, user_contract_id); test.create_default_account(&user); @@ -1065,8 +1065,8 @@ fn test_clawback_on_contract() { .to_account_key(signing_key_to_account_id(&test.issuer_key)) .unwrap(); - let user_1 = generate_bytes_array(); - let user_2 = generate_bytes_array(); + let user_1 = generate_bytes_array(&test.host); + let user_2 = generate_bytes_array(&test.host); let user_1_addr = contract_id_to_address(&test.host, user_1); let user_2_addr = contract_id_to_address(&test.host, user_2); @@ -1132,7 +1132,7 @@ fn test_auth_revocable_on_contract() { .to_account_key(signing_key_to_account_id(&test.issuer_key)) .unwrap(); - let user_1 = generate_bytes_array(); + let user_1 = generate_bytes_array(&test.host); let user_1_addr = contract_id_to_address(&test.host, user_1); // contract is authorized by default @@ -1183,8 +1183,8 @@ fn test_auth_required() { .to_account_key(signing_key_to_account_id(&test.issuer_key)) .unwrap(); - let user_1 = generate_bytes_array(); - let user_2 = generate_bytes_array(); + let user_1 = generate_bytes_array(&test.host); + let user_2 = generate_bytes_array(&test.host); let user_1_addr = contract_id_to_address(&test.host, user_1); let user_2_addr = contract_id_to_address(&test.host, user_2); @@ -1624,7 +1624,7 @@ fn test_account_invoker_auth_with_issuer_admin() { ); // Contract invoker can't perform unauthorized admin operation. - let contract_id = generate_bytes_array(); + let contract_id = generate_bytes_array(&test.host); let contract_invoker = TestSigner::ContractInvoker(Hash(contract_id)); let contract_id_bytes = BytesN::<32>::try_from_val( &test.host, @@ -1646,8 +1646,8 @@ fn test_account_invoker_auth_with_issuer_admin() { fn test_contract_invoker_auth() { let test = TokenTest::setup(); - let admin_contract_id = generate_bytes_array(); - let user_contract_id = generate_bytes_array(); + let admin_contract_id = generate_bytes_array(&test.host); + let user_contract_id = generate_bytes_array(&test.host); let admin_contract_invoker = TestSigner::ContractInvoker(Hash(admin_contract_id)); let user_contract_invoker = TestSigner::ContractInvoker(Hash(user_contract_id)); let admin_contract_address = contract_id_to_address(&test.host, admin_contract_id); @@ -2213,7 +2213,7 @@ fn test_native_token_classic_balance_boundaries( ) { let token = TestToken::new_from_asset(&test.host, Asset::Native); - let new_balance_key = generate_signing_key(); + let new_balance_key = generate_signing_key(&test.host); let new_balance_acc = signing_key_to_account_id(&new_balance_key); let new_balance_signer = TestSigner::account_with_multisig(&new_balance_acc, vec![&new_balance_key]); @@ -2266,7 +2266,7 @@ fn test_native_token_classic_balance_boundaries( // to the account being tested. That's not a realistic scenario // given limited XLM supply, but that's the only way to // cover max_balance. - let large_balance_key = generate_signing_key(); + let large_balance_key = generate_signing_key(&test.host); let large_balance_acc = signing_key_to_account_id(&large_balance_key); let large_balance_signer = TestSigner::account_with_multisig(&large_balance_acc, vec![&large_balance_key]); @@ -2752,7 +2752,7 @@ fn test_custom_account_auth() { use soroban_test_wasms::SIMPLE_ACCOUNT_CONTRACT; let test = TokenTest::setup(); - let admin_kp = generate_signing_key(); + let admin_kp = generate_signing_key(&test.host); let account_contract_addr_obj = test .host .register_test_contract_wasm(SIMPLE_ACCOUNT_CONTRACT); @@ -2791,7 +2791,7 @@ fn test_custom_account_auth() { // Create a signer for the new admin, but not yet set its key as the account // owner. - let new_admin_kp = generate_signing_key(); + let new_admin_kp = generate_signing_key(&test.host); let new_admin = TestSigner::AccountContract(AccountContractSigner { address: account_contract_addr, sign: simple_account_sign_fn(&test.host, &new_admin_kp), diff --git a/soroban-env-host/src/test/util.rs b/soroban-env-host/src/test/util.rs index d1fd38278..97cb03e5f 100644 --- a/soroban-env-host/src/test/util.rs +++ b/soroban-env-host/src/test/util.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, collections::BTreeMap, rc::Rc}; -use rand::{thread_rng, RngCore}; +use rand::RngCore; use soroban_env_common::{ xdr::{ AccountEntry, AccountId, ContractCostType, LedgerEntry, LedgerEntryData, LedgerKey, @@ -42,15 +42,16 @@ impl AsScVal for ScVec { } } -pub(crate) fn generate_account_id() -> AccountId { +pub(crate) fn generate_account_id(host: &Host) -> AccountId { AccountId(PublicKey::PublicKeyTypeEd25519(Uint256( - generate_bytes_array(), + generate_bytes_array(host), ))) } -pub(crate) fn generate_bytes_array() -> [u8; 32] { +pub(crate) fn generate_bytes_array(host: &Host) -> [u8; 32] { let mut bytes: [u8; 32] = Default::default(); - thread_rng().fill_bytes(&mut bytes); + host.with_test_prng(|chacha| Ok(chacha.fill_bytes(&mut bytes))) + .unwrap(); bytes } @@ -100,6 +101,11 @@ impl Host { let snapshot_source = Rc::::new(MockSnapshotSource::new()); let storage = Storage::with_recording_footprint(snapshot_source); let host = Host::with_storage_and_budget(storage, Budget::default()); + // Base PRNG seed is only needed because auth recording-mode uses it to generate + // a nonce. We can't just use the test PRNG there because recording-mode works + // in non-test preflight configs. + host.set_base_prng_seed(*crate::host::TEST_PRNG_SEED) + .unwrap(); host.set_ledger_info(LedgerInfo { protocol_version: crate::meta::get_ledger_protocol_version( crate::meta::INTERFACE_VERSION, @@ -237,8 +243,8 @@ impl Host { pub(crate) fn register_test_contract_wasm(&self, contract_wasm: &[u8]) -> AddressObject { self.register_test_contract_wasm_from_source_account( contract_wasm, - generate_account_id(), - generate_bytes_array(), + generate_account_id(self), + generate_bytes_array(self), ) } diff --git a/soroban-env-macros/src/call_macro_with_all_host_functions.rs b/soroban-env-macros/src/call_macro_with_all_host_functions.rs index ebeaf7077..9698e9404 100644 --- a/soroban-env-macros/src/call_macro_with_all_host_functions.rs +++ b/soroban-env-macros/src/call_macro_with_all_host_functions.rs @@ -2,7 +2,7 @@ use itertools::iproduct; use proc_macro2::TokenStream; use quote::{format_ident, quote}; use std::{ - collections::{hash_map::Entry, HashMap}, + collections::{btree_map::Entry, BTreeMap}, fs::File, iter, }; @@ -30,7 +30,7 @@ pub fn generate(file_lit: LitStr) -> Result { ) })?; - let mut export_names = HashMap::::new(); + let mut export_names = BTreeMap::::new(); for m in root.modules.iter() { // We expect each module in the env interface to label its function // export names according to a simple scheme: _ 0-9 a-z A-Z. diff --git a/soroban-synth-wasm/src/mod_emitter.rs b/soroban-synth-wasm/src/mod_emitter.rs index 55294a673..2c16ba99e 100644 --- a/soroban-synth-wasm/src/mod_emitter.rs +++ b/soroban-synth-wasm/src/mod_emitter.rs @@ -1,5 +1,5 @@ use crate::FuncEmitter; -use std::collections::HashMap; +use std::collections::BTreeMap; use wasm_encoder::{ CodeSection, ConstExpr, CustomSection, ElementSection, Elements, EntityType, ExportKind, ExportSection, Function, FunctionSection, GlobalSection, GlobalType, ImportSection, @@ -10,14 +10,14 @@ use wasm_encoder::{ /// inputs the function takes. In this crate function types are simplified to all /// take only some number (the arity) of I64 values and return a single I64, so a /// function type can be defined strictly by its arity. -#[derive(Hash, PartialEq, Eq, Copy, Clone)] +#[derive(Hash, PartialEq, Eq, Copy, Clone, PartialOrd, Ord)] pub struct Arity(pub u32); /// Wrapper for a u32 that references a function type in the `type` section of the /// module being emitted. There will usually be only one such type for any given /// arity, though there may be none: they are emitted into the `type` section on /// demand, as references to them are required. -#[derive(Hash, PartialEq, Eq, Copy, Clone)] +#[derive(Hash, PartialEq, Eq, Copy, Clone, PartialOrd, Ord)] pub struct TypeRef(pub u32); /// Wrapper for a u32 that references a function in both the `function` and @@ -52,8 +52,8 @@ pub struct ModEmitter { elements: ElementSection, codes: CodeSection, - type_refs: HashMap, - import_refs: HashMap<(String, String, Arity), FuncRef>, + type_refs: BTreeMap, + import_refs: BTreeMap<(String, String, Arity), FuncRef>, } impl ModEmitter { @@ -93,8 +93,8 @@ impl ModEmitter { let exports = ExportSection::new(); let elements = ElementSection::new(); let codes = CodeSection::new(); - let typerefs = HashMap::new(); - let importrefs = HashMap::new(); + let typerefs = BTreeMap::new(); + let importrefs = BTreeMap::new(); Self { module, types,