diff --git a/src/Linker.cs b/src/Linker.cs index 2e36b29..98a0118 100644 --- a/src/Linker.cs +++ b/src/Linker.cs @@ -27,6 +27,11 @@ public Linker(Engine engine) handle = new Handle(Native.wasmtime_linker_new(engine.NativeHandle)); } + private Linker(IntPtr handle) + { + this.handle = new Handle(handle); + } + /// /// Configures whether or not the linker allows later definitions to shadow previous definitions. /// @@ -38,6 +43,14 @@ public bool AllowShadowing } } + /// + /// Clones existing linker. + /// + public Linker Clone() + { + return new Linker(Native.wasmtime_linker_clone(handle)); + } + private void Define(string module, string name, T item) where T : IExternal { @@ -511,6 +524,9 @@ internal static class Native [DllImport(Engine.LibraryName)] public static extern void wasmtime_linker_allow_shadowing(Handle linker, [MarshalAs(UnmanagedType.I1)] bool allow); + [DllImport(Engine.LibraryName)] + public static extern IntPtr wasmtime_linker_clone(Handle linker); + [DllImport(Engine.LibraryName)] public static unsafe extern IntPtr wasmtime_linker_define(Handle linker, IntPtr context, byte* module, nuint moduleLen, byte* name, nuint nameLen, in Extern item); diff --git a/tests/LinkerTests.cs b/tests/LinkerTests.cs index 6bab78e..ab16d4b 100644 --- a/tests/LinkerTests.cs +++ b/tests/LinkerTests.cs @@ -35,6 +35,95 @@ public void ItThrowsWithNullEngine() Assert.Throws(() => new Linker(null!)); } + [Fact] + public void CloneCarriesAllowShadowing() + { + Linker.AllowShadowing = true; + + using var clone = Linker.Clone(); + + clone.DefineFunction("", "hello", () => { }); + clone.DefineFunction("", "hello", () => { }); + } + + [Fact] + public void CloneCarriesDefinitions() + { + var called = 0; + Linker.DefineFunction("", "hello", () => { called++; }); + + using var clone = Linker.Clone(); + clone.GetFunction(Store, "", "hello")!.Invoke(); + + Assert.Equal(1, called); + } + + [Fact] + public void CloneCarriesWasi() + { + Linker.DefineWasi(); + + using var clone = Linker.Clone(); + + Assert.Throws(() => clone.DefineWasi()); + } + + [Fact] + public void CloneDisallowsShadowingByDefault() + { + using var clone = Linker.Clone(); + + clone.DefineFunction("", "hello", () => { }); + Assert.Throws(() => clone.DefineFunction("", "hello", () => { })); + } + + [Fact] + public void CloneDoesNotShareDefinitionsAddedToTheClone() + { + using var clone = Linker.Clone(); + clone.DefineFunction("", "hello", () => { }); + + Assert.NotNull(clone.GetFunction(Store, "", "hello")); + Assert.Null(Linker.GetFunction(Store, "", "hello")); + } + + [Fact] + public void CloneDoesNotShareDefinitionsAddedToTheOriginal() + { + using var clone = Linker.Clone(); + Linker.DefineFunction("", "hello", () => { }); + + Assert.NotNull(Linker.GetFunction(Store, "", "hello")); + Assert.Null(clone.GetFunction(Store, "", "hello")); + } + + [Fact] + public void CloneOutlivesTheOriginal() + { + var called = 0; + var linker = new Linker(Fixture.Engine); + linker.DefineFunction("", "hello", () => { called++; }); + + using var clone = linker.Clone(); + linker.Dispose(); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + + clone.GetFunction(Store, "", "hello")!.Invoke(); + + Assert.Equal(1, called); + } + + [Fact] + public void CloneThrowsWhenDisposed() + { + var linker = new Linker(Fixture.Engine); + linker.Dispose(); + + Assert.Throws(() => linker.Clone()); + } + [Fact] public void DefineThrowsWithNullStore() {