Repository: bytecodealliance/wasmtime
Pull request #2604 (2021-01-25 23:38:21)

fuzzing: Add tests for dummy import generation
<!--

Please ensure that the following steps are all taken care of before submitting
the PR.

- [ ] This has been discussed in issue #..., or if not, please tell us why
  here.
- [ ] A short description of what this does, why it is needed; if the
  description becomes long, the matter should probably be discussed in an issue
  first.
- [ ] This PR contains test cases, if meaningful.
- [ ] A reviewer from the core maintainer team has been assigned for this PR.
  If you don't know who could review this, please indicate so. The list of
  suggested reviewers on the right can help you.

Please ensure all communication adheres to the [code of
conduct](https://github.com/bytecodealliance/wasmtime/blob/master/CODE_OF_CONDUCT.md).
-->


github-actions[bot]: #### Subscribe to Label Action

cc @fitzgen, @peterhuene

<details>
This issue or pull request has been labeled: "fuzzing", "wasmtime:api"

Thus the following users have been cc'd because of the following labels:

* fitzgen: fuzzing
* peterhuene: wasmtime:api

To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>

Patch:
diff --git a/crates/fuzzing/src/oracles/dummy.rs b/crates/fuzzing/src/oracles/dummy.rs
index 561a8fb32..f49cee8c5 100644
--- a/crates/fuzzing/src/oracles/dummy.rs
+++ b/crates/fuzzing/src/oracles/dummy.rs
@@ -349,3 +349,232 @@ fn wat_ty(ty: &ValType) -> &'static str {
         ValType::FuncRef => "funcref",
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::collections::HashSet;
+
+    fn store() -> Store {
+        let mut config = Config::default();
+        config.wasm_module_linking(true);
+        config.wasm_multi_memory(true);
+        let engine = wasmtime::Engine::new(&config);
+        Store::new(&engine)
+    }
+
+    #[test]
+    fn dummy_table_import() {
+        let store = store();
+        let table = dummy_table(
+            &store,
+            TableType::new(ValType::ExternRef, Limits::at_least(10)),
+        );
+        assert_eq!(table.size(), 10);
+        for i in 0..10 {
+            assert!(table.get(i).unwrap().unwrap_externref().is_none());
+        }
+    }
+
+    #[test]
+    fn dummy_global_import() {
+        let store = store();
+        let global = dummy_global(&store, GlobalType::new(ValType::I32, Mutability::Const));
+        assert_eq!(global.val_type(), ValType::I32);
+        assert_eq!(global.mutability(), Mutability::Const);
+    }
+
+    #[test]
+    fn dummy_memory_import() {
+        let store = store();
+        let memory = dummy_memory(&store, MemoryType::new(Limits::at_least(1)));
+        assert_eq!(memory.size(), 1);
+    }
+
+    #[test]
+    fn dummy_function_import() {
+        let store = store();
+        let func_ty = FuncType::new(vec![ValType::I32], vec![ValType::I64]);
+        let func = dummy_func(&store, func_ty.clone());
+        assert_eq!(func.ty(), func_ty);
+    }
+
+    #[test]
+    fn dummy_instance_import() {
+        let store = store();
+
+        let mut instance_ty = InstanceType::new();
+
+        // Functions.
+        instance_ty.add_named_export("func0", FuncType::new(vec![ValType::I32], vec![]).into());
+        instance_ty.add_named_export("func1", FuncType::new(vec![], vec![ValType::I64]).into());
+
+        // Globals.
+        instance_ty.add_named_export(
+            "global0",
+            GlobalType::new(ValType::I32, Mutability::Const).into(),
+        );
+        instance_ty.add_named_export(
+            "global1",
+            GlobalType::new(ValType::I64, Mutability::Var).into(),
+        );
+
+        // Tables.
+        instance_ty.add_named_export(
+            "table0",
+            TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
+        );
+        instance_ty.add_named_export(
+            "table1",
+            TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
+        );
+
+        // Memories.
+        instance_ty.add_named_export("memory0", MemoryType::new(Limits::at_least(1)).into());
+        instance_ty.add_named_export("memory1", MemoryType::new(Limits::at_least(1)).into());
+
+        // Modules.
+        instance_ty.add_named_export("module0", ModuleType::new().into());
+        instance_ty.add_named_export("module1", ModuleType::new().into());
+
+        // Instances.
+        instance_ty.add_named_export("instance0", InstanceType::new().into());
+        instance_ty.add_named_export("instance1", InstanceType::new().into());
+
+        let instance = dummy_instance(&store, instance_ty.clone());
+
+        let mut expected_exports = vec![
+            "func0",
+            "func1",
+            "global0",
+            "global1",
+            "table0",
+            "table1",
+            "memory0",
+            "memory1",
+            "module0",
+            "module1",
+            "instance0",
+            "instance1",
+        ]
+        .into_iter()
+        .collect::<HashSet<_>>();
+        for exp in instance.ty().exports() {
+            let was_expected = expected_exports.remove(exp.name());
+            assert!(was_expected);
+        }
+        assert!(expected_exports.is_empty());
+    }
+
+    #[test]
+    fn dummy_module_import() {
+        let store = store();
+
+        let mut module_ty = ModuleType::new();
+
+        // Multiple exported and imported functions.
+        module_ty.add_named_export("func0", FuncType::new(vec![ValType::I32], vec![]).into());
+        module_ty.add_named_export("func1", FuncType::new(vec![], vec![ValType::I64]).into());
+        module_ty.add_named_import(
+            "func2",
+            None,
+            FuncType::new(vec![ValType::I64], vec![]).into(),
+        );
+        module_ty.add_named_import(
+            "func3",
+            None,
+            FuncType::new(vec![], vec![ValType::I32]).into(),
+        );
+
+        // Multiple exported and imported globals.
+        module_ty.add_named_export(
+            "global0",
+            GlobalType::new(ValType::I32, Mutability::Const).into(),
+        );
+        module_ty.add_named_export(
+            "global1",
+            GlobalType::new(ValType::I64, Mutability::Var).into(),
+        );
+        module_ty.add_named_import(
+            "global2",
+            None,
+            GlobalType::new(ValType::I32, Mutability::Var).into(),
+        );
+        module_ty.add_named_import(
+            "global3",
+            None,
+            GlobalType::new(ValType::I64, Mutability::Const).into(),
+        );
+
+        // Multiple exported and imported tables.
+        module_ty.add_named_export(
+            "table0",
+            TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
+        );
+        module_ty.add_named_export(
+            "table1",
+            TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
+        );
+        module_ty.add_named_import(
+            "table2",
+            None,
+            TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
+        );
+        module_ty.add_named_import(
+            "table3",
+            None,
+            TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
+        );
+
+        // Multiple exported and imported memories.
+        module_ty.add_named_export("memory0", MemoryType::new(Limits::at_least(1)).into());
+        module_ty.add_named_export("memory1", MemoryType::new(Limits::at_least(1)).into());
+        module_ty.add_named_import("memory2", None, MemoryType::new(Limits::at_least(1)).into());
+        module_ty.add_named_import("memory3", None, MemoryType::new(Limits::at_least(1)).into());
+
+        // An exported and an imported module.
+        module_ty.add_named_export("module0", ModuleType::new().into());
+        module_ty.add_named_import("module1", None, ModuleType::new().into());
+
+        // An exported and an imported instance.
+        module_ty.add_named_export("instance0", InstanceType::new().into());
+        module_ty.add_named_import("instance1", None, InstanceType::new().into());
+
+        // Create the module.
+        let module = dummy_module(&store, module_ty);
+
+        // Check that we have the expected exports.
+        assert!(module.get_export("func0").is_some());
+        assert!(module.get_export("func1").is_some());
+        assert!(module.get_export("global0").is_some());
+        assert!(module.get_export("global1").is_some());
+        assert!(module.get_export("table0").is_some());
+        assert!(module.get_export("table1").is_some());
+        assert!(module.get_export("memory0").is_some());
+        assert!(module.get_export("memory1").is_some());
+        assert!(module.get_export("instance0").is_some());
+        assert!(module.get_export("module0").is_some());
+
+        // Check that we have the exported imports.
+        let mut expected_imports = vec![
+            "func2",
+            "func3",
+            "global2",
+            "global3",
+            "table2",
+            "table3",
+            "memory2",
+            "memory3",
+            "instance1",
+            "module1",
+        ]
+        .into_iter()
+        .collect::<HashSet<_>>();
+        for imp in module.imports() {
+            assert!(imp.name().is_none());
+            let was_expected = expected_imports.remove(imp.module());
+            assert!(was_expected);
+        }
+        assert!(expected_imports.is_empty());
+    }
+}
diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs
index a3cede9cb..890471b1a 100644
--- a/crates/wasmtime/src/externals.rs
+++ b/crates/wasmtime/src/externals.rs
@@ -605,6 +605,22 @@ impl Table {
     }
 }
 
+/// Error for out of bounds [`Memory`] access.
+#[derive(Debug)]
+#[non_exhaustive]
+pub struct MemoryAccessError {
+    // Keep struct internals private for future extensibility.
+    _private: (),
+}
+
+impl std::fmt::Display for MemoryAccessError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "out of bounds memory access")
+    }
+}
+
+impl std::error::Error for MemoryAccessError {}
+
 /// A WebAssembly linear memory.
 ///
 /// WebAssembly memories represent a contiguous array of bytes that have a size
@@ -661,9 +677,24 @@ impl Table {
 /// Let's run through a few safe examples first of how you can use a `Memory`.
 ///
 /// ```rust
-/// use wasmtime::Memory;
+/// use wasmtime::{Memory, MemoryAccessError};
+///
+/// // Memory can be read and written safely with the `Memory::read` and
+/// // `Memory::write` methods.
+/// // An error is returned if the copy did not succeed.
+/// fn safe_examples(mem: &Memory) -> Result<(), MemoryAccessError> {
+///     let offset = 5;
+///     mem.write(offset, b"hello")?;
+///     let mut buffer = [0u8; 5];
+///     mem.read(offset, &mut buffer)?;
+///     assert_eq!(b"hello", &buffer);
+///     Ok(())
+/// }
+///
+/// // You can also get direct, unsafe access to the memory, but must manually
+/// // ensure that safety invariants are upheld.
 ///
-/// fn safe_examples(mem: &Memory) {
+/// fn correct_unsafe_examples(mem: &Memory) {
 ///     // Just like wasm, it's safe to read memory almost at any time. The
 ///     // gotcha here is that we need to be sure to load from the correct base
 ///     // pointer and perform the bounds check correctly. So long as this is
@@ -871,6 +902,39 @@ impl Memory {
         MemoryType::from_wasmtime_memory(&self.wasmtime_export.memory.memory)
     }
 
+    /// Safely reads memory contents at the given offset into a buffer.
+    ///
+    /// The entire buffer will be filled.
+    ///
+    /// If offset + buffer length exceed the current memory capacity,
+    /// a [`MemoryAccessError`] is returned.
+    pub fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), MemoryAccessError> {
+        unsafe {
+            let slice = self
+                .data_unchecked()
+                .get(offset..)
+                .and_then(|s| s.get(..buffer.len()))
+                .ok_or(MemoryAccessError { _private: () })?;
+            buffer.copy_from_slice(slice);
+            Ok(())
+        }
+    }
+
+    /// Safely writes contents of a buffer to this memory at the given offset.
+    ///
+    /// If the offset + buffer length exceed current memory capacity, a
+    /// [`MemoryAccessError`] is returned.
+    pub fn write(&self, offset: usize, buffer: &[u8]) -> Result<(), MemoryAccessError> {
+        unsafe {
+            self.data_unchecked_mut()
+                .get_mut(offset..)
+                .and_then(|s| s.get_mut(..buffer.len()))
+                .ok_or(MemoryAccessError { _private: () })?
+                .copy_from_slice(buffer);
+            Ok(())
+        }
+    }
+
     /// Returns this memory as a slice view that can be read natively in Rust.
     ///
     /// # Safety
diff --git a/crates/wasmtime/src/types.rs b/crates/wasmtime/src/types.rs
index fe2915402..5a090d794 100644
--- a/crates/wasmtime/src/types.rs
+++ b/crates/wasmtime/src/types.rs
@@ -466,12 +466,12 @@ impl ModuleType {
     }
 
     /// Adds a new export to this `ModuleType`.
-    pub(crate) fn add_named_export(&mut self, name: &str, ty: ExternType) {
+    pub fn add_named_export(&mut self, name: &str, ty: ExternType) {
         self.exports.push((name.to_string(), ty));
     }
 
     /// Adds a new import to this `ModuleType`.
-    pub(crate) fn add_named_import(&mut self, module: &str, field: Option<&str>, ty: ExternType) {
+    pub fn add_named_import(&mut self, module: &str, field: Option<&str>, ty: ExternType) {
         self.imports
             .push((module.to_string(), field.map(|f| f.to_string()), ty));
     }


Test patch:
diff --git a/tests/all/externals.rs b/tests/all/externals.rs
index 365d5b261..6c13aabc1 100644
--- a/tests/all/externals.rs
+++ b/tests/all/externals.rs
@@ -340,3 +340,40 @@ fn grow_externref_tables_via_api() -> anyhow::Result<()> {
 
     Ok(())
 }
+
+#[test]
+fn read_write_memory_via_api() {
+    let cfg = Config::new();
+    let store = Store::new(&Engine::new(&cfg));
+    let ty = MemoryType::new(Limits::new(1, None));
+    let mem = Memory::new(&store, ty);
+    mem.grow(1).unwrap();
+
+    let value = b"hello wasm";
+    mem.write(mem.data_size() - value.len(), value).unwrap();
+
+    let mut buffer = [0u8; 10];
+    mem.read(mem.data_size() - buffer.len(), &mut buffer)
+        .unwrap();
+    assert_eq!(value, &buffer);
+
+    // Error conditions.
+
+    // Out of bounds write.
+
+    let res = mem.write(mem.data_size() - value.len() + 1, value);
+    assert!(res.is_err());
+
+    // Out of bounds read.
+
+    let res = mem.read(mem.data_size() - buffer.len() + 1, &mut buffer);
+    assert!(res.is_err());
+
+    // Read offset overflow.
+    let res = mem.read(usize::MAX, &mut buffer);
+    assert!(res.is_err());
+
+    // Write offset overflow.
+    let res = mem.write(usize::MAX, &mut buffer);
+    assert!(res.is_err());
+}