mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
* fix(ffi): eliminate aliasing UB via to_shared_ref migration Add to_shared_ref() helper that creates &T (shared reference) from raw pointers instead of &mut T. This eliminates undefined behavior caused by violating Rust's aliasing invariant when C# SafeHandle permits concurrent FFI calls on the same handle. With &mut T, the compiler may assume exclusive (noalias) access and reorder or elide reads/writes — a miscompilation risk when another thread holds a reference to the same object. Switching to &T removes that assumption; actual mutation is mediated by the interior RwLock inside Handle<T>, which is the sole synchronization mechanism. Migrated sites: - rvm.rs: 20 non-drop call sites - engine.rs: 30 non-drop call sites + with_unwind_guard for timer fns - compiled_policy.rs: 2 call sites - Fix null-data UB in regorus_program_deserialize_binary Drop paths retain to_ref() where exclusive access is guaranteed by the caller contract (preventing use-after-free). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat(ffi): add Azure Policy JSON compilation FFI and C# bindings - AliasRegistry builder pattern: RegorusAliasRegistryBuilder (mutable, single-threaded) + RegorusAliasRegistry (immutable, Arc-wrapped) - Azure Policy JSON compilation: regorus_compile_azure_policy_rule and regorus_compile_azure_policy_definition with alias registry support - regorus_rvm_set_context for host-supplied ambient data - C# AliasRegistryBuilder and AliasRegistry classes with convenience factories (FromJson, FromManifest, Empty) - C# AzurePolicyCompiler static class for policy rule/definition compilation - Compile functions take *const RegorusAliasRegistry (read-only via to_shared_ref for concurrent compilation safety) - Fix pre-existing clippy warnings across multiple crates Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
250 lines
8.1 KiB
C#
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
|
|
{
|
|
internal 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);
|
|
}
|
|
}
|
|
}
|
|
}
|