From 2c462c0e867e211c29aae6f485cd4b3750643835 Mon Sep 17 00:00:00 2001 From: Martin Evans Date: Fri, 12 May 2023 00:03:51 +0100 Subject: [PATCH] Implemented `Store.SetLimits` (#243) * Implemented `Store.SetLimits` using the new `wasmtime_store_limiter` function. * Fixed filepath case * Added runtime checking of limiter values * Fixed braces --- src/Store.cs | 48 +++++++++++++++++++++++ tests/StoreTests.cs | 95 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 tests/StoreTests.cs diff --git a/src/Store.cs b/src/Store.cs index 37db262..0ee7b8a 100644 --- a/src/Store.cs +++ b/src/Store.cs @@ -158,6 +158,51 @@ public Store(Engine engine, object? data) handle = new Handle(Native.wasmtime_store_new(engine.NativeHandle, (IntPtr)storeHandle, Finalizer)); } + /// + /// Limit the resources that this store may consume. Note that the limits are only used to limit the creation/growth of resources in the future, + /// this does not retroactively attempt to apply limits to the store. + /// + /// the maximum number of bytes a linear memory can grow to. Growing a linear memory beyond this limit will fail. + /// Pass in a null value to use the default value (unlimited) + /// the maximum number of elements in a table. Growing a table beyond this limit will fail. + /// Pass in a null value to use the default value (unlimited) + /// the maximum number of instances that can be created for a Store. Module instantiation will fail if this limit is exceeded. + /// Pass in a null value to use the default value (10000) + /// the maximum number of tables that can be created for a Store. Module instantiation will fail if this limit is exceeded. + /// Pass in a null value to use the default value (10000) + /// the maximum number of linear memories that can be created for a Store. Instantiation will fail with an error if this limit is exceeded. + /// Pass in a null value to use the default value (10000) + public void SetLimits(long? memorySize = null, uint? tableElements = null, long? instances = null, long? tables = null, long? memories = null) + { + if (memorySize.HasValue && memorySize.Value < 0) + { + throw new ArgumentOutOfRangeException(nameof(memorySize)); + } + + if (instances.HasValue && instances.Value < 0) + { + throw new ArgumentOutOfRangeException(nameof(instances)); + } + + if (tables.HasValue && tables.Value < 0) + { + throw new ArgumentOutOfRangeException(nameof(tables)); + } + + if (memories.HasValue && memories.Value < 0) + { + throw new ArgumentOutOfRangeException(nameof(memories)); + } + + long tableElements64 = -1; + if (tableElements.HasValue) + { + tableElements64 = tableElements.Value; + } + + Native.wasmtime_store_limiter(NativeHandle, memorySize ?? -1, tableElements64, instances ?? -1, tables ?? -1, memories ?? -1); + } + /// /// Perform garbage collection within the given store. /// @@ -298,6 +343,9 @@ private static class Native [DllImport(Engine.LibraryName)] public static extern void wasmtime_store_delete(IntPtr store); + + [DllImport(Engine.LibraryName)] + public static extern void wasmtime_store_limiter(Handle store, long memory_size, long table_elements, long instances, long tables, long memories); } private readonly Handle handle; diff --git a/tests/StoreTests.cs b/tests/StoreTests.cs new file mode 100644 index 0000000..4f10f77 --- /dev/null +++ b/tests/StoreTests.cs @@ -0,0 +1,95 @@ +using FluentAssertions; +using System.IO; +using Xunit; + +namespace Wasmtime.Tests +{ + public class StoreTests + : StoreFixture + { + [Fact] + public void ItSetsLimits() + { + Store.SetLimits(1, 2, 3, 4, 5); + } + + [Fact] + public void ItSetsDefaultLimits() + { + Store.SetLimits(null, null, null, null, null); + } + + [Fact] + public void ItLimitsMemorySize() + { + Store.SetLimits(memorySize: Memory.PageSize); + + var memory = new Memory(Store, 0, 1024); + memory.GetSize().Should().Be(0); + + + memory.Grow(1).Should().Be(0); + + var act = () => { memory.Grow(1); }; + act.Should().Throw(); + } + + [Fact] + public void ItLimitsTableElements() + { + Store.SetLimits(tableElements: 5); + + var table = new Table(Store, ValueKind.ExternRef, null, 0); + table.GetSize().Should().Be(0); + + table.Grow(5, null); + + var act = () => { table.Grow(1, null); }; + act.Should().Throw(); + } + + [Fact] + public void ItLimitsInstances() + { + Store.SetLimits(instances: 3); + + using var module = Module.FromTextFile(Engine, Path.Combine("Modules", "Trap.wat")); + + var inst1 = new Instance(Store, module); + var inst2 = new Instance(Store, module); + var inst3 = new Instance(Store, module); + + var act = () => { new Instance(Store, module); }; + act.Should().Throw(); + } + + [Fact] + public void ItLimitsTables() + { + Store.SetLimits(tables: 3); + + // This module exports exactly 3 tables + using var module = Module.FromTextFile(Engine, Path.Combine("Modules", "TableExports.wat")); + + var inst1 = new Instance(Store, module); + + var act = () => { new Instance(Store, module); }; + act.Should().Throw(); + } + + [Fact] + public void ItLimitsMemories() + { + Store.SetLimits(memories: 2); + + // This module exports 1 memory + using var module = Module.FromTextFile(Engine, Path.Combine("Modules", "MemoryExports.wat")); + + var inst1 = new Instance(Store, module); + var inst2 = new Instance(Store, module); + + var act = () => { new Instance(Store, module); }; + act.Should().Throw(); + } + } +}