Files
regorus/bindings/csharp/Regorus/AliasRegistry.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

129 lines
4.7 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Immutable Azure Policy alias registry used for resource normalization
/// and policy compilation.
/// </summary>
public unsafe sealed class AliasRegistry : SafeHandleWrapper
{
internal AliasRegistry(RegorusAliasRegistryHandle handle)
: base(handle, nameof(AliasRegistry))
{
}
/// <summary>
/// Create an empty immutable alias registry.
/// </summary>
public static AliasRegistry Empty()
{
using var builder = new AliasRegistryBuilder();
return builder.Build();
}
/// <summary>
/// Create an immutable alias registry from control-plane alias JSON.
/// </summary>
public static AliasRegistry FromJson(string json)
{
using var builder = new AliasRegistryBuilder();
builder.LoadJson(json);
return builder.Build();
}
/// <summary>
/// Create an immutable alias registry from a data-plane manifest JSON document.
/// </summary>
public static AliasRegistry FromManifest(string json)
{
using var builder = new AliasRegistryBuilder();
builder.LoadManifest(json);
return builder.Build();
}
/// <summary>
/// Gets the number of resource types loaded in the registry.
/// </summary>
public long Length
{
get
{
return UseHandle(regPtr =>
{
return ResultHelpers.GetIntResult(
API.regorus_alias_registry_len((RegorusAliasRegistry*)regPtr));
});
}
}
/// <summary>
/// Normalize an ARM resource JSON and wrap it into the standard input envelope
/// expected by a compiled Azure Policy program.
/// </summary>
public string? NormalizeAndWrap(string resourceJson, string? apiVersion = null, string contextJson = "{}", string parametersJson = "{}")
{
return Utf8Marshaller.WithUtf8(resourceJson, resPtr =>
Utf8Marshaller.WithUtf8(contextJson, ctxPtr =>
Utf8Marshaller.WithUtf8(parametersJson, paramsPtr =>
{
if (apiVersion is null)
{
return UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_normalize_and_wrap(
(RegorusAliasRegistry*)regPtr,
(byte*)resPtr, null,
(byte*)ctxPtr, (byte*)paramsPtr));
});
}
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_normalize_and_wrap(
(RegorusAliasRegistry*)regPtr,
(byte*)resPtr, (byte*)apiPtr,
(byte*)ctxPtr, (byte*)paramsPtr));
}));
})));
}
/// <summary>
/// Denormalize a previously-normalized resource JSON back to ARM format.
/// </summary>
public string? Denormalize(string normalizedJson, string? apiVersion = null)
{
return Utf8Marshaller.WithUtf8(normalizedJson, normPtr =>
{
if (apiVersion is null)
{
return UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_denormalize(
(RegorusAliasRegistry*)regPtr,
(byte*)normPtr, null));
});
}
return Utf8Marshaller.WithUtf8(apiVersion, apiPtr =>
UseHandle(regPtr =>
{
return ResultHelpers.GetStringResult(
API.regorus_alias_registry_denormalize(
(RegorusAliasRegistry*)regPtr,
(byte*)normPtr, (byte*)apiPtr));
}));
});
}
}
}