Remove hashbrown and use BTree{Map,Set} from the alloc crate (#187)

* Remove hashbrown and use BTree{Map,Set} from the alloc crate

wasmi-validation must handle untrusted input and hence we switch from
Hash{Set,Map} (whether std's or hashbrown's) to BTree{Set,Map} to avoid
algorithmic complexity attacks while retaining no_std support.

Closes #183

* Improve memory locality of checking for duplicate exports

Using a sorted slice gives us the same O(N log N) worst case execution
time as using a BTreeMap, but using a single allocation as with HashMap,
so that we should see better memory locality and hence better constant
factors when checking for duplicate exports.
This commit is contained in:
adam-rhebo 2019-06-12 11:30:10 +02:00 committed by Sergei Pepyakin
parent 284c907b29
commit 8dac328ea7
6 changed files with 23 additions and 34 deletions

View File

@ -13,7 +13,6 @@ exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ]
[dependencies] [dependencies]
wasmi-validation = { version = "0.1", path = "validation", default-features = false } wasmi-validation = { version = "0.1", path = "validation", default-features = false }
parity-wasm = { version = "0.31", default-features = false } parity-wasm = { version = "0.31", default-features = false }
hashbrown = { version = "0.1.8", optional = true }
memory_units = "0.3.0" memory_units = "0.3.0"
libm = { version = "0.1.2", optional = true } libm = { version = "0.1.2", optional = true }
@ -30,10 +29,8 @@ std = [
"wasmi-validation/std", "wasmi-validation/std",
] ]
# Enable for no_std support # Enable for no_std support
# hashbrown only works on no_std
core = [ core = [
"wasmi-validation/core", "wasmi-validation/core",
"hashbrown/nightly",
"libm" "libm"
] ]

View File

@ -1,10 +1,7 @@
#[allow(unused_imports)] #[allow(unused_imports)]
use alloc::prelude::v1::*; use alloc::prelude::v1::*;
#[cfg(not(feature = "std"))] use alloc::collections::BTreeMap;
use hashbrown::HashMap;
#[cfg(feature = "std")]
use std::collections::HashMap;
use func::FuncRef; use func::FuncRef;
use global::GlobalRef; use global::GlobalRef;
@ -106,7 +103,7 @@ pub trait ImportResolver {
/// [`ImportResolver`]: trait.ImportResolver.html /// [`ImportResolver`]: trait.ImportResolver.html
/// [`ModuleImportResolver`]: trait.ModuleImportResolver.html /// [`ModuleImportResolver`]: trait.ModuleImportResolver.html
pub struct ImportsBuilder<'a> { pub struct ImportsBuilder<'a> {
modules: HashMap<String, &'a ModuleImportResolver>, modules: BTreeMap<String, &'a ModuleImportResolver>,
} }
impl<'a> Default for ImportsBuilder<'a> { impl<'a> Default for ImportsBuilder<'a> {
@ -119,7 +116,7 @@ impl<'a> ImportsBuilder<'a> {
/// Create an empty `ImportsBuilder`. /// Create an empty `ImportsBuilder`.
pub fn new() -> ImportsBuilder<'a> { pub fn new() -> ImportsBuilder<'a> {
ImportsBuilder { ImportsBuilder {
modules: HashMap::new(), modules: BTreeMap::new(),
} }
} }

View File

@ -114,8 +114,6 @@ extern crate assert_matches;
#[cfg(test)] #[cfg(test)]
extern crate wabt; extern crate wabt;
#[cfg(not(feature = "std"))]
extern crate hashbrown;
extern crate memory_units as memory_units_crate; extern crate memory_units as memory_units_crate;
extern crate parity_wasm; extern crate parity_wasm;

View File

@ -5,10 +5,7 @@ use core::cell::RefCell;
use core::fmt; use core::fmt;
use Trap; use Trap;
#[cfg(not(feature = "std"))] use alloc::collections::BTreeMap;
use hashbrown::HashMap;
#[cfg(feature = "std")]
use std::collections::HashMap;
use core::cell::Ref; use core::cell::Ref;
use func::{FuncBody, FuncInstance, FuncRef}; use func::{FuncBody, FuncInstance, FuncRef};
@ -162,7 +159,7 @@ pub struct ModuleInstance {
funcs: RefCell<Vec<FuncRef>>, funcs: RefCell<Vec<FuncRef>>,
memories: RefCell<Vec<MemoryRef>>, memories: RefCell<Vec<MemoryRef>>,
globals: RefCell<Vec<GlobalRef>>, globals: RefCell<Vec<GlobalRef>>,
exports: RefCell<HashMap<String, ExternVal>>, exports: RefCell<BTreeMap<String, ExternVal>>,
} }
impl ModuleInstance { impl ModuleInstance {
@ -173,7 +170,7 @@ impl ModuleInstance {
tables: RefCell::new(Vec::new()), tables: RefCell::new(Vec::new()),
memories: RefCell::new(Vec::new()), memories: RefCell::new(Vec::new()),
globals: RefCell::new(Vec::new()), globals: RefCell::new(Vec::new()),
exports: RefCell::new(HashMap::new()), exports: RefCell::new(BTreeMap::new()),
} }
} }

View File

@ -9,7 +9,6 @@ description = "Wasm code validator"
[dependencies] [dependencies]
parity-wasm = { version = "0.31", default-features = false } parity-wasm = { version = "0.31", default-features = false }
hashbrown = { version = "0.1.8", optional = true }
[dev-dependencies] [dev-dependencies]
assert_matches = "1.1" assert_matches = "1.1"
@ -17,6 +16,4 @@ assert_matches = "1.1"
[features] [features]
default = ["std"] default = ["std"]
std = ["parity-wasm/std"] std = ["parity-wasm/std"]
core = [ core = []
"hashbrown/nightly"
]

View File

@ -27,15 +27,10 @@ use core::fmt;
#[cfg(feature = "std")] #[cfg(feature = "std")]
use std::error; use std::error;
#[cfg(not(feature = "std"))]
use hashbrown::HashSet;
#[cfg(feature = "std")]
use std::collections::HashSet;
use self::context::ModuleContextBuilder; use self::context::ModuleContextBuilder;
use parity_wasm::elements::{ use parity_wasm::elements::{
BlockType, External, FuncBody, GlobalEntry, GlobalType, InitExpr, Instruction, Internal, BlockType, ExportEntry, External, FuncBody, GlobalEntry, GlobalType, InitExpr, Instruction,
MemoryType, Module, ResizableLimits, TableType, Type, ValueType, Internal, MemoryType, Module, ResizableLimits, TableType, Type, ValueType,
}; };
pub mod context; pub mod context;
@ -250,13 +245,21 @@ pub fn validate_module<V: Validator>(module: &Module) -> Result<V::Output, Error
// validate export section // validate export section
if let Some(export_section) = module.export_section() { if let Some(export_section) = module.export_section() {
let mut export_names = HashSet::with_capacity(export_section.entries().len()); let mut export_names = export_section
for export in export_section.entries() { .entries()
// HashSet::insert returns false if item already in set. .iter()
let duplicate = export_names.insert(export.field()) == false; .map(ExportEntry::field)
if duplicate { .collect::<Vec<_>>();
return Err(Error(format!("duplicate export {}", export.field())));
export_names.sort_unstable();
for (fst, snd) in export_names.iter().zip(export_names.iter().skip(1)) {
if fst == snd {
return Err(Error(format!("duplicate export {}", fst)));
} }
}
for export in export_section.entries() {
match *export.internal() { match *export.internal() {
Internal::Function(function_index) => { Internal::Function(function_index) => {
context.require_function(function_index)?; context.require_function(function_index)?;