Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion binding/HarfBuzzSharp/Buffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
12 changes: 11 additions & 1 deletion binding/HarfBuzzSharp/UnicodeFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}
}
Expand Down
23 changes: 23 additions & 0 deletions tests/Tests/HarfBuzzSharp/HBUnicodeFuncsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,5 +146,28 @@ public void ShouldSetDecomposeDelegate()
Assert.Equal(7331, second);
}
}

[Fact]
public void BufferUnicodeFunctionsGetterDoesNotOwnBorrowedHandle()
Comment thread
Copilot marked this conversation as resolved.
{
// 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.");
}
}
}
Loading