diff --git a/binding/HarfBuzzSharp/Buffer.cs b/binding/HarfBuzzSharp/Buffer.cs index 4241cdf84ee..afb4047a01d 100644 --- a/binding/HarfBuzzSharp/Buffer.cs +++ b/binding/HarfBuzzSharp/Buffer.cs @@ -132,7 +132,9 @@ public int Length { public UnicodeFunctions UnicodeFunctions { get { - var r = new UnicodeFunctions (HarfBuzzApi.hb_buffer_get_unicode_funcs (Handle)); + // hb_buffer_get_unicode_funcs returns a borrowed (transfer-none) reference + // owned by the buffer, so the wrapper must not destroy it on dispose. + var r = new UnicodeFunctions (HarfBuzzApi.hb_buffer_get_unicode_funcs (Handle), owns: false); GC.KeepAlive (this); return r; } diff --git a/binding/HarfBuzzSharp/UnicodeFunctions.cs b/binding/HarfBuzzSharp/UnicodeFunctions.cs index 7228a5be4c3..6e6e9044010 100644 --- a/binding/HarfBuzzSharp/UnicodeFunctions.cs +++ b/binding/HarfBuzzSharp/UnicodeFunctions.cs @@ -16,11 +16,21 @@ public unsafe class UnicodeFunctions : NativeObject public static UnicodeFunctions Empty => emptyFunctions.Value; + // true when this wrapper owns the native handle and must destroy it on dispose; + // false when it merely borrows a handle owned by another object (e.g. a buffer). + private readonly bool owns = true; + internal UnicodeFunctions (IntPtr handle) : base (handle) { } + internal UnicodeFunctions (IntPtr handle, bool owns) + : base (handle) + { + this.owns = owns; + } + public UnicodeFunctions (UnicodeFunctions parent) : base (IntPtr.Zero) { if (parent == null) @@ -196,7 +206,7 @@ protected override void Dispose (bool disposing) => protected override void DisposeHandler () { - if (Handle != IntPtr.Zero) { + if (owns && Handle != IntPtr.Zero) { HarfBuzzApi.hb_unicode_funcs_destroy (Handle); } } diff --git a/tests/Tests/HarfBuzzSharp/HBUnicodeFuncsTest.cs b/tests/Tests/HarfBuzzSharp/HBUnicodeFuncsTest.cs index f21426f0b2b..4fed4074c78 100644 --- a/tests/Tests/HarfBuzzSharp/HBUnicodeFuncsTest.cs +++ b/tests/Tests/HarfBuzzSharp/HBUnicodeFuncsTest.cs @@ -146,5 +146,28 @@ public void ShouldSetDecomposeDelegate() Assert.Equal(7331, second); } } + + [Fact] + public void BufferUnicodeFunctionsGetterDoesNotOwnBorrowedHandle() + { + // hb_buffer_get_unicode_funcs returns a borrowed (transfer-none) reference + // owned by the buffer. Disposing the wrapper returned by the getter must not + // destroy that shared handle out from under the still-alive buffer. + var destroyed = false; + + var unicodeFunctions = new UnicodeFunctions(UnicodeFunctions.Default); + unicodeFunctions.SetScriptDelegate((f, cp) => Script.Unknown, () => destroyed = true); + + // The buffer is kept alive (via using) so its funcs reference stays valid + // through the assertion, and is disposed cleanly at the end of the scope. + using var buffer = new Buffer(); + buffer.UnicodeFunctions = unicodeFunctions; + + var borrowed = buffer.UnicodeFunctions; + borrowed.Dispose(); + unicodeFunctions.Dispose(); + + Assert.False(destroyed, "buffer.UnicodeFunctions getter over-freed a borrowed handle."); + } } }