Files
regorus/bindings/csharp/Regorus/Program.cs
Anand Krishnamoorthi 96360fa9d8 fix(bindings): add SafeHandleWrapper + memory growth checks; bump 0.9.1 (#571)
- Introduce SafeHandleWrapper with gating, short drain wait, and deferred release on last in-flight exit.
- Wire Engine/Program/Rvm/CompiledPolicy to wrapper (centralized handle use, interop helper).
- Add C# memory growth tests (using/finalizer paths) and extend xtask C# runner options.
- Add pooled marshalling utilities, ResultHelpers, and API cleanups; update versions/changelog.

Fixes #570. Closes #554

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-02-09 12:22:05 -06:00

250 lines
8.1 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Represents a compiled RVM program.
/// </summary>
public unsafe sealed class Program : SafeHandleWrapper
{
private Program(RegorusProgramHandle handle)
: base(handle, nameof(Program))
{
}
/// <summary>
/// Create an empty program.
/// </summary>
public static Program CreateEmpty()
{
return new Program(RegorusProgramHandle.Create());
}
/// <summary>
/// Compile an RVM program from modules and entry points.
/// </summary>
public static Program CompileFromModules(string dataJson, IEnumerable<PolicyModule> modules, IEnumerable<string> entryPoints)
{
if (modules is null)
{
throw new ArgumentNullException(nameof(modules));
}
if (entryPoints is null)
{
throw new ArgumentNullException(nameof(entryPoints));
}
return CompileFromModules(dataJson, modules.ToArray(), entryPoints.ToArray());
}
/// <summary>
/// Compile an RVM program from modules and entry points.
/// </summary>
public static Program CompileFromModules(string dataJson, IReadOnlyList<PolicyModule> modules, IReadOnlyList<string> entryPoints)
{
if (modules is null)
{
throw new ArgumentNullException(nameof(modules));
}
if (entryPoints is null)
{
throw new ArgumentNullException(nameof(entryPoints));
}
if (entryPoints.Count == 0)
{
throw new ArgumentException("At least one entry point is required.", nameof(entryPoints));
}
using var pinnedModules = ModuleMarshalling.PinPolicyModules(modules);
using var pinnedEntryPoints = ModuleMarshalling.PinEntryPoints(entryPoints);
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
{
fixed (RegorusPolicyModule* modulesPtr = pinnedModules.Buffer)
fixed (IntPtr* entryPtr = pinnedEntryPoints.Buffer)
{
var result = API.regorus_program_compile_from_modules(
(byte*)dataPtr,
modulesPtr,
(UIntPtr)pinnedModules.Length,
(byte**)entryPtr,
(UIntPtr)pinnedEntryPoints.Length);
return GetProgramResult(result);
}
});
}
/// <summary>
/// Compile an RVM program from an engine instance and entry points.
/// </summary>
public static Program CompileFromEngine(Engine engine, IEnumerable<string> entryPoints)
{
if (engine is null)
{
throw new ArgumentNullException(nameof(engine));
}
if (entryPoints is null)
{
throw new ArgumentNullException(nameof(entryPoints));
}
return CompileFromEngine(engine, entryPoints.ToArray());
}
/// <summary>
/// Compile an RVM program from an engine instance and entry points.
/// </summary>
public static Program CompileFromEngine(Engine engine, IReadOnlyList<string> entryPoints)
{
if (engine is null)
{
throw new ArgumentNullException(nameof(engine));
}
if (entryPoints is null)
{
throw new ArgumentNullException(nameof(entryPoints));
}
if (entryPoints.Count == 0)
{
throw new ArgumentException("At least one entry point is required.", nameof(entryPoints));
}
using var pinnedEntryPoints = ModuleMarshalling.PinEntryPoints(entryPoints);
return engine.UseHandleForInterop(enginePtr =>
{
fixed (IntPtr* entryPtr = pinnedEntryPoints.Buffer)
{
var result = API.regorus_engine_compile_program_with_entrypoints(
(RegorusEngine*)enginePtr,
(byte**)entryPtr,
(UIntPtr)pinnedEntryPoints.Length);
return GetProgramResult(result);
}
});
}
/// <summary>
/// Deserialize an RVM program from binary format.
/// </summary>
public static Program DeserializeBinary(byte[] data, out bool isPartial)
{
if (data is null)
{
throw new ArgumentNullException(nameof(data));
}
byte partialFlag = 0;
fixed (byte* dataPtr = data)
{
var result = API.regorus_program_deserialize_binary(dataPtr, (UIntPtr)data.Length, &partialFlag);
var program = GetProgramResult(result);
isPartial = partialFlag != 0;
return program;
}
}
/// <summary>
/// Serialize the program to binary format.
/// </summary>
public byte[] SerializeBinary()
{
return UseHandle(programPtr =>
{
var result = API.regorus_program_serialize_binary((RegorusProgram*)programPtr);
return ExtractBuffer(result);
});
}
/// <summary>
/// Generate a readable assembly listing.
/// </summary>
public string? GenerateListing()
{
return UseHandle(programPtr =>
{
return CheckAndDropResult(API.regorus_program_generate_listing((RegorusProgram*)programPtr));
});
}
private static Program GetProgramResult(RegorusResult result)
{
try
{
if (result.status != RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
{
throw new Exception("Expected program pointer but got different data type");
}
var handle = RegorusProgramHandle.FromPointer((IntPtr)result.pointer_value);
return new Program(handle);
}
finally
{
API.regorus_result_drop(result);
}
}
private static string? CheckAndDropResult(RegorusResult result)
{
return ResultHelpers.GetStringResult(result);
}
private static byte[] ExtractBuffer(RegorusResult result)
{
RegorusBuffer* buffer = null;
try
{
if (result.status != RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message);
throw result.status.CreateException(message);
}
if (result.data_type != RegorusDataType.Pointer || result.pointer_value == null)
{
throw new Exception("Expected buffer pointer but got different data type");
}
buffer = (RegorusBuffer*)result.pointer_value;
var length = checked((int)buffer->len);
var data = new byte[length];
if (length > 0)
{
Marshal.Copy((IntPtr)buffer->data, data, 0, length);
}
return data;
}
finally
{
if (buffer != null)
{
API.regorus_buffer_drop(buffer);
}
API.regorus_result_drop(result);
}
}
}
}