Files
regorus/bindings/csharp/Regorus/Rvm.cs
Anand Krishnamoorthi 86b4a279fa fix(ffi): eliminate aliasing UB + add Azure Policy JSON compilation FFI (#727)
* 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>
2026-05-22 12:50:16 -05:00

246 lines
7.2 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Execution mode for the RVM runtime.
/// </summary>
public enum ExecutionMode : byte
{
/// <summary>
/// Run to completion without yielding.
/// </summary>
RunToCompletion = 0,
/// <summary>
/// Suspendable execution mode.
/// </summary>
Suspendable = 1,
}
/// <summary>
/// Wrapper for the Regorus RVM runtime.
/// </summary>
public unsafe sealed class Rvm : SafeHandleWrapper
{
public Rvm()
: base(RegorusRvmHandle.Create(), nameof(Rvm))
{
}
private Rvm(RegorusRvmHandle handle)
: base(handle, nameof(Rvm))
{
}
/// <summary>
/// Create an RVM instance backed by a compiled policy (for default rule evaluation).
/// </summary>
public static Rvm CreateWithPolicy(CompiledPolicy policy)
{
if (policy is null)
{
throw new ArgumentNullException(nameof(policy));
}
return policy.UseHandleForInterop(policyPtr =>
{
var result = API.regorus_rvm_new_with_policy((RegorusCompiledPolicy*)policyPtr);
return GetRvmResult(result);
});
}
/// <summary>
/// Load a program into the VM.
/// </summary>
public void LoadProgram(Program program)
{
if (program is null)
{
throw new ArgumentNullException(nameof(program));
}
program.UseHandleForInterop(programPtr =>
{
UseHandle(vmPtr =>
{
CheckAndDropResult(API.regorus_rvm_load_program((RegorusRvm*)vmPtr, (RegorusProgram*)programPtr));
return 0;
});
return 0;
});
}
/// <summary>
/// Set the data document for the VM.
/// </summary>
public void SetDataJson(string dataJson)
{
Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
{
UseHandle(vmPtr =>
{
CheckAndDropResult(API.regorus_rvm_set_data((RegorusRvm*)vmPtr, (byte*)dataPtr));
return 0;
});
});
}
/// <summary>
/// Set the input document for the VM.
/// </summary>
public void SetInputJson(string inputJson)
{
Utf8Marshaller.WithUtf8(inputJson, inputPtr =>
{
UseHandle(vmPtr =>
{
CheckAndDropResult(API.regorus_rvm_set_input((RegorusRvm*)vmPtr, (byte*)inputPtr));
return 0;
});
});
}
/// <summary>
/// Set the context document for the VM.
/// The context provides host-supplied ambient data (e.g. resourceGroup(),
/// subscription()) that Azure Policy functions can access via LoadContext
/// instructions.
/// </summary>
public void SetContextJson(string contextJson)
{
Utf8Marshaller.WithUtf8(contextJson, contextPtr =>
{
UseHandle(vmPtr =>
{
CheckAndDropResult(API.regorus_rvm_set_context((RegorusRvm*)vmPtr, (byte*)contextPtr));
return 0;
});
});
}
/// <summary>
/// Set the execution mode (0 = run-to-completion, 1 = suspendable).
/// </summary>
public void SetExecutionMode(byte mode)
{
UseHandle(vmPtr =>
{
CheckAndDropResult(API.regorus_rvm_set_execution_mode((RegorusRvm*)vmPtr, mode));
return 0;
});
}
/// <summary>
/// Set the execution mode.
/// </summary>
public void SetExecutionMode(ExecutionMode mode)
{
SetExecutionMode((byte)mode);
}
/// <summary>
/// Execute the program and return the JSON result.
/// </summary>
public string? Execute()
{
return UseHandle(vmPtr =>
{
return CheckAndDropResult(API.regorus_rvm_execute((RegorusRvm*)vmPtr));
});
}
/// <summary>
/// Execute a named entry point.
/// </summary>
public string? ExecuteEntryPoint(string entryPoint)
{
return Utf8Marshaller.WithUtf8(entryPoint, entryPtr =>
{
return UseHandle(vmPtr =>
{
return CheckAndDropResult(API.regorus_rvm_execute_entry_point_by_name((RegorusRvm*)vmPtr, (byte*)entryPtr));
});
});
}
/// <summary>
/// Execute an entry point by index.
/// </summary>
public string? ExecuteEntryPoint(ulong index)
{
return UseHandle(vmPtr =>
{
return CheckAndDropResult(API.regorus_rvm_execute_entry_point_by_index((RegorusRvm*)vmPtr, (UIntPtr)index));
});
}
/// <summary>
/// Resume execution with an optional value.
/// </summary>
public string? Resume(string? resumeValueJson)
{
if (resumeValueJson is null)
{
return UseHandle(vmPtr =>
{
return CheckAndDropResult(API.regorus_rvm_resume((RegorusRvm*)vmPtr, null, has_value: false));
});
}
return Utf8Marshaller.WithUtf8(resumeValueJson, valuePtr =>
{
return UseHandle(vmPtr =>
{
return CheckAndDropResult(API.regorus_rvm_resume((RegorusRvm*)vmPtr, (byte*)valuePtr, has_value: true));
});
});
}
/// <summary>
/// Get the current execution state.
/// </summary>
public string? GetExecutionState()
{
return UseHandle(vmPtr =>
{
return CheckAndDropResult(API.regorus_rvm_get_execution_state((RegorusRvm*)vmPtr));
});
}
private static Rvm GetRvmResult(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 RVM pointer but got different data type");
}
var handle = RegorusRvmHandle.FromPointer((IntPtr)result.pointer_value);
return new Rvm(handle);
}
finally
{
API.regorus_result_drop(result);
}
}
private static string? CheckAndDropResult(RegorusResult result)
{
return ResultHelpers.GetStringResult(result);
}
}
}