Skip to content

Commit

Permalink
Move to SafeHandle for Unmanaged Pointers (#38)
Browse files Browse the repository at this point in the history
* Move to `SafeHandle` for Unmanaged Pointers

This is an API breaking change but should make the use of unsafe
poiters in this library more reliable in some corner cases. Safe
handles will ensure that the attached resources are always correctly
released, even in exceptional circumstances.

This also allows the `RureFfi` to be a tad more type safe in what it
accepts. It's bad that we were somehow _less type safe_ than C. :-/

* Bump Version

This new handle approach is not API or ABI backwards compatible. It is
however _most likely_ source compatible.
  • Loading branch information
iwillspeak committed Feb 19, 2020
1 parent 8c4068d commit 7b688ee
Show file tree
Hide file tree
Showing 18 changed files with 259 additions and 161 deletions.
24 changes: 14 additions & 10 deletions src/IronRure/CaptureNamesEnumerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,23 @@

namespace IronRure
{
internal class CaptureNamesEnumerator : UnmanagedResource, IEnumerator<string>
internal class CaptureNamesEnumerator : IDisposable, IEnumerator<string>
{
private readonly CaptureNamesHandle _handle;

public CaptureNamesEnumerator(Regex regex)
{
_handle = RureFfi.rure_iter_capture_names_new(regex.Raw);
}

public string Current { get; protected set; }

object IEnumerator.Current => (object)Current;

public CaptureNamesEnumerator(Regex regex)
: base(RureFfi.rure_iter_capture_names_new(regex.Raw))
{}

protected override void Free(IntPtr resource)
{
RureFfi.rure_iter_capture_names_free(resource);
}

public bool MoveNext()
{
while (RureFfi.rure_iter_capture_names_next(Raw, out IntPtr name))
while (RureFfi.rure_iter_capture_names_next(_handle, out IntPtr name))
{
Current = Marshal.PtrToStringAnsi(name);
if (!string.IsNullOrEmpty(Current))
Expand All @@ -35,5 +34,10 @@ public void Reset()
{
throw new NotSupportedException();
}

public void Dispose()
{
_handle.Dispose();
}
}
}
21 changes: 21 additions & 0 deletions src/IronRure/CaptureNamesHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Runtime.InteropServices;

namespace IronRure
{
public sealed class CaptureNamesHandle : SafeHandle
{
public CaptureNamesHandle()
: base(IntPtr.Zero, true)
{
}

public override bool IsInvalid => handle == IntPtr.Zero;

protected override bool ReleaseHandle()
{
RureFfi.rure_iter_capture_names_free(handle);
return true;
}
}
}
26 changes: 16 additions & 10 deletions src/IronRure/Captures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,17 @@ namespace IronRure
/// <summary>
/// A set of captures for a given regex.
/// </summary>
public class Captures : UnmanagedResource, IEnumerable<Match>
public class Captures : IDisposable, IEnumerable<Match>
{
private readonly byte[] _haystack;
private readonly Regex _reg;

internal Captures(Regex re, byte[] haystack)
: base(RureFfi.rure_captures_new(re.Raw))
: base()
{
_reg = re;
_haystack = haystack;
}

protected override void Free(IntPtr resource)
{
RureFfi.rure_captures_free(resource);
Raw = RureFfi.rure_captures_new(re.Raw);
}

/// <summary>
Expand Down Expand Up @@ -51,6 +50,11 @@ protected override void Free(IntPtr resource)

public bool Matched { get; internal set; }

/// <summary>
/// The raw, unmanaged, handle to the captures group
/// </summary>
public CapturesHandle Raw { get; }

public IEnumerator<Match> GetEnumerator()
{
var len = Length;
Expand All @@ -61,8 +65,10 @@ public IEnumerator<Match> GetEnumerator()
}

IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();

private byte[] _haystack;
private readonly Regex _reg;

public void Dispose()
{
Raw.Dispose();
}
}
}
21 changes: 21 additions & 0 deletions src/IronRure/CapturesHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Runtime.InteropServices;

namespace IronRure
{
public sealed class CapturesHandle : SafeHandle
{
public CapturesHandle()
: base(IntPtr.Zero, true)
{
}

public override bool IsInvalid => handle == IntPtr.Zero;

protected override bool ReleaseHandle()
{
RureFfi.rure_captures_free(handle);
return true;
}
}
}
19 changes: 12 additions & 7 deletions src/IronRure/Error.cs → src/IronRure/ErrorHandle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@ namespace IronRure
/// error strings get disposed of correctly even when there's
/// Exceptions about.
/// </sumary>
public class Error : UnmanagedResource
public sealed class ErrorHandle : SafeHandle
{
public Error()
: base(RureFfi.rure_error_new())
{}
public ErrorHandle()
: base(IntPtr.Zero, true)
{
}

protected override void Free(IntPtr resource)
public override bool IsInvalid => handle == IntPtr.Zero;

protected override bool ReleaseHandle()
{
RureFfi.rure_error_free(resource);
RureFfi.rure_error_free(handle);
return true;
}

/// <summary>
Expand All @@ -28,6 +32,7 @@ protected override void Free(IntPtr resource)
/// </para>
/// </summary>
public string Message =>
Marshal.PtrToStringAnsi(RureFfi.rure_error_message(Raw));
Marshal.PtrToStringAnsi(RureFfi.rure_error_message(this));

}
}
2 changes: 1 addition & 1 deletion src/IronRure/IronRure.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard1.4;netstandard2.0</TargetFrameworks>
<TargetFrameworks Condition=" '$(OS)' == 'Windows_NT' ">$(TargetFrameworks);net45</TargetFrameworks>
<VersionPrefix>1.1.3</VersionPrefix>
<VersionPrefix>2.0.0</VersionPrefix>
<Author>Will Speak (@iwillspeak)</Author>
<Description>
Bindings to the Rust `regex` crate from .NET.
Expand Down
15 changes: 9 additions & 6 deletions src/IronRure/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@

namespace IronRure
{
public class Options : UnmanagedResource
public class Options : IDisposable
{
public Options()
: base(RureFfi.rure_options_new())
{}

protected override void Free(IntPtr resource)
{
RureFfi.rure_options_free(resource);
Raw = RureFfi.rure_options_new();
}

internal OptionsHandle Raw { get; }

/// <summary>
/// Set Size Limit - Controls the size of a single regex program
/// </summary>
Expand All @@ -28,5 +26,10 @@ public uint DfaSize
{
set => RureFfi.rure_options_dfa_size_limit(Raw, new UIntPtr(value));
}

public void Dispose()
{
Raw.Dispose();
}
}
}
21 changes: 21 additions & 0 deletions src/IronRure/OptionsHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Runtime.InteropServices;

namespace IronRure
{
public sealed class OptionsHandle : SafeHandle
{
public OptionsHandle()
: base(IntPtr.Zero, true)
{
}

public override bool IsInvalid => handle == IntPtr.Zero;

protected override bool ReleaseHandle()
{
RureFfi.rure_options_free(handle);
return true;
}
}
}
27 changes: 15 additions & 12 deletions src/IronRure/Regex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ namespace IronRure
/// Managed wrapper around the rust regex class.
/// </para>
/// </summary>
public partial class Regex : UnmanagedResource
public partial class Regex : IDisposable
{
/// <summary>
/// Create a new regex instance from the given pattern.
/// </summary>
public Regex(string pattern)
: this(pattern, IntPtr.Zero, (uint)DefaultFlags)
: this(pattern, new OptionsHandle(), (uint)DefaultFlags)
{}

/// <summary>
Expand All @@ -38,7 +38,7 @@ public Regex(string pattern, Options opts)
/// Create a new regex instance from the given pattern and flags.
/// </summary>
public Regex(string pattern, RureFlags flags)
: this(pattern, IntPtr.Zero, (uint)flags)
: this(pattern, new OptionsHandle(), (uint)flags)
{}

/// <summary>
Expand All @@ -49,19 +49,22 @@ public Regex(string pattern, Options opts, RureFlags flags)
: this(pattern, opts.Raw, (uint)flags)
{}

private Regex(string pattern, IntPtr options, uint flags)
: base(Compile(pattern, options, flags))
{}
private Regex(string pattern, OptionsHandle options, uint flags)
{
Raw = Compile(pattern, options, flags);
}

internal RegexHandle Raw { get; }

/// <summary>
/// Compiles the given regex and returns the unmanaged pointer to it.
/// </summary>
private static IntPtr Compile(string pattern, IntPtr options, uint flags)
private static RegexHandle Compile(string pattern, OptionsHandle options, uint flags)
{
// Convert the pattern to a utf-8 encoded string.
var patBytes = Encoding.UTF8.GetBytes(pattern);

using (var err = new Error())
using (var err = RureFfi.rure_error_new())
{
// Compile the regex. We get back a raw handle to the underlying
// Rust object.
Expand All @@ -70,10 +73,10 @@ private static IntPtr Compile(string pattern, IntPtr options, uint flags)
new UIntPtr((uint)patBytes.Length),
flags,
options,
err.Raw);
err);

// If the regex failed to compile find out what the problem was.
if (raw == IntPtr.Zero)
if (raw.IsInvalid)
throw new RegexCompilationException(err.Message);

return raw;
Expand Down Expand Up @@ -263,9 +266,9 @@ public IEnumerable<Captures> CaptureAll(string haystack)
return CaptureAll(haystackBytes);
}

protected override void Free(IntPtr resource)
public void Dispose()
{
RureFfi.rure_free(resource);
Raw.Dispose();
}

/// <summary>
Expand Down
21 changes: 21 additions & 0 deletions src/IronRure/RegexHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Runtime.InteropServices;

namespace IronRure
{
public sealed class RegexHandle : SafeHandle
{
public RegexHandle()
: base(IntPtr.Zero, true)
{
}

public override bool IsInvalid => handle == IntPtr.Zero;

protected override bool ReleaseHandle()
{
RureFfi.rure_free(handle);
return true;
}
}
}
15 changes: 8 additions & 7 deletions src/IronRure/RegexIter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@

namespace IronRure
{
public abstract class RegexIter : UnmanagedResource
public abstract class RegexIter : IDisposable
{
protected Regex Pattern { get; }
protected byte[] Haystack { get; }

public RegexIter(Regex pattern, byte[] haystack)
: base(RureFfi.rure_iter_new(pattern.Raw))
{
Raw = RureFfi.rure_iter_new(pattern.Raw);
Pattern = pattern;
Haystack = haystack;
}

protected override void Free(IntPtr resource)
protected RegexIterHandle Raw { get; }
protected Regex Pattern { get; }
protected byte[] Haystack { get; }

public void Dispose()
{
RureFfi.rure_iter_free(resource);
Raw.Dispose();
}
}
}
21 changes: 21 additions & 0 deletions src/IronRure/RegexIterHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using System.Runtime.InteropServices;

namespace IronRure
{
public sealed class RegexIterHandle : SafeHandle
{
public RegexIterHandle()
: base(IntPtr.Zero, true)
{
}

public override bool IsInvalid => handle == IntPtr.Zero;

protected override bool ReleaseHandle()
{
RureFfi.rure_iter_free(handle);
return true;
}
}
}

0 comments on commit 7b688ee

Please sign in to comment.