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>
185 lines
6.2 KiB
C#
185 lines
6.2 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
using System;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Nodes;
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
using Regorus;
|
|
|
|
namespace Regorus.Tests;
|
|
|
|
[TestClass]
|
|
public class AliasRegistryTests
|
|
{
|
|
private const string AliasesJson = @"[{
|
|
""namespace"": ""Microsoft.Storage"",
|
|
""resourceTypes"": [{
|
|
""resourceType"": ""storageAccounts"",
|
|
""aliases"": [{
|
|
""name"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"",
|
|
""defaultPath"": ""properties.supportsHttpsTrafficOnly"",
|
|
""paths"": []
|
|
}, {
|
|
""name"": ""Microsoft.Storage/storageAccounts/accessTier"",
|
|
""defaultPath"": ""properties.accessTier"",
|
|
""paths"": []
|
|
}]
|
|
}]
|
|
}]";
|
|
|
|
private const string ManifestJson = @"{
|
|
""dataNamespace"": ""Microsoft.KeyVault.Data"",
|
|
""aliases"": [],
|
|
""resourceTypeAliases"": [{
|
|
""resourceType"": ""vaults/certificates"",
|
|
""aliases"": [{
|
|
""name"": ""Microsoft.KeyVault.Data/vaults/certificates/keySize"",
|
|
""paths"": [{ ""path"": ""keySize"", ""apiVersions"": [""7.0""] }]
|
|
}]
|
|
}]
|
|
}";
|
|
|
|
[TestMethod]
|
|
public void Create_and_dispose_succeeds()
|
|
{
|
|
using var registry = AliasRegistry.Empty();
|
|
Assert.AreEqual(0, registry.Length);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void LoadJson_populates_registry()
|
|
{
|
|
using var registry = AliasRegistry.FromJson(AliasesJson);
|
|
Assert.AreEqual(1, registry.Length);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void LoadManifest_populates_registry()
|
|
{
|
|
using var registry = AliasRegistry.FromManifest(ManifestJson);
|
|
Assert.AreEqual(1, registry.Length);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void NormalizeAndWrap_produces_envelope()
|
|
{
|
|
using var registry = AliasRegistry.FromJson(AliasesJson);
|
|
|
|
var resource = @"{
|
|
""name"": ""acct1"",
|
|
""type"": ""Microsoft.Storage/storageAccounts"",
|
|
""properties"": { ""supportsHttpsTrafficOnly"": true, ""accessTier"": ""Hot"" }
|
|
}";
|
|
|
|
var result = registry.NormalizeAndWrap(resource, "2023-01-01", "{}", "{}");
|
|
Assert.IsNotNull(result);
|
|
|
|
var envelope = JsonNode.Parse(result!)!;
|
|
Assert.IsNotNull(envelope["resource"]);
|
|
Assert.IsNotNull(envelope["parameters"]);
|
|
Assert.IsNotNull(envelope["context"]);
|
|
|
|
// Normalized resource should have lowercased alias field names
|
|
var res = envelope["resource"]!;
|
|
Assert.AreEqual(true, res["supportshttpstrafficonly"]?.GetValue<bool>());
|
|
Assert.AreEqual("Hot", res["accesstier"]?.GetValue<string>());
|
|
Assert.AreEqual("acct1", res["name"]?.GetValue<string>());
|
|
}
|
|
|
|
[TestMethod]
|
|
public void NormalizeAndWrap_with_context_and_parameters()
|
|
{
|
|
using var registry = AliasRegistry.FromJson(AliasesJson);
|
|
|
|
var resource = @"{
|
|
""name"": ""acct1"",
|
|
""type"": ""Microsoft.Storage/storageAccounts"",
|
|
""properties"": { ""supportsHttpsTrafficOnly"": true }
|
|
}";
|
|
var context = @"{""resourceGroup"": {""name"": ""rg1""}}";
|
|
var parameters = @"{""env"": ""prod""}";
|
|
|
|
var result = registry.NormalizeAndWrap(resource, "2023-01-01", context, parameters);
|
|
Assert.IsNotNull(result);
|
|
|
|
var envelope = JsonNode.Parse(result!)!;
|
|
Assert.AreEqual("rg1", envelope["context"]!["resourceGroup"]!["name"]?.GetValue<string>());
|
|
Assert.AreEqual("prod", envelope["parameters"]!["env"]?.GetValue<string>());
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Denormalize_restores_properties()
|
|
{
|
|
using var registry = AliasRegistry.FromJson(AliasesJson);
|
|
|
|
var normalized = @"{
|
|
""name"": ""acct1"",
|
|
""type"": ""Microsoft.Storage/storageAccounts"",
|
|
""supportshttpstrafficonly"": true,
|
|
""accesstier"": ""Hot""
|
|
}";
|
|
|
|
var result = registry.Denormalize(normalized, "2023-01-01");
|
|
Assert.IsNotNull(result);
|
|
|
|
var arm = JsonNode.Parse(result!)!;
|
|
Assert.AreEqual("acct1", arm["name"]?.GetValue<string>());
|
|
Assert.AreEqual(true, arm["properties"]!["supportsHttpsTrafficOnly"]?.GetValue<bool>());
|
|
Assert.AreEqual("Hot", arm["properties"]!["accessTier"]?.GetValue<string>());
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Round_trip_normalize_then_denormalize()
|
|
{
|
|
using var registry = AliasRegistry.FromJson(AliasesJson);
|
|
|
|
var resource = @"{
|
|
""name"": ""acct1"",
|
|
""type"": ""Microsoft.Storage/storageAccounts"",
|
|
""properties"": { ""supportsHttpsTrafficOnly"": true, ""accessTier"": ""Hot"" }
|
|
}";
|
|
|
|
// Normalize
|
|
var envelopeJson = registry.NormalizeAndWrap(resource, "2023-01-01", "{}", "{}");
|
|
Assert.IsNotNull(envelopeJson);
|
|
|
|
var envelope = JsonNode.Parse(envelopeJson!)!;
|
|
var normalizedResource = envelope["resource"]!.ToJsonString();
|
|
|
|
// Denormalize
|
|
var armJson = registry.Denormalize(normalizedResource, "2023-01-01");
|
|
Assert.IsNotNull(armJson);
|
|
|
|
var arm = JsonNode.Parse(armJson!)!;
|
|
Assert.AreEqual(true, arm["properties"]!["supportsHttpsTrafficOnly"]?.GetValue<bool>());
|
|
Assert.AreEqual("Hot", arm["properties"]!["accessTier"]?.GetValue<string>());
|
|
Assert.AreEqual("acct1", arm["name"]?.GetValue<string>());
|
|
}
|
|
|
|
[TestMethod]
|
|
public void DataPlane_manifest_normalize()
|
|
{
|
|
using var registry = AliasRegistry.FromManifest(ManifestJson);
|
|
|
|
var resource = @"{
|
|
""type"": ""Microsoft.KeyVault.Data/vaults/certificates"",
|
|
""keySize"": 2048
|
|
}";
|
|
|
|
var result = registry.NormalizeAndWrap(resource, "7.0", "{}", "{}");
|
|
Assert.IsNotNull(result);
|
|
|
|
var envelope = JsonNode.Parse(result!)!;
|
|
Assert.AreEqual(2048, envelope["resource"]!["keysize"]?.GetValue<int>());
|
|
}
|
|
|
|
[TestMethod]
|
|
[ExpectedException(typeof(InvalidOperationException))]
|
|
public void LoadJson_invalid_throws()
|
|
{
|
|
using var builder = new AliasRegistryBuilder();
|
|
builder.LoadJson("not valid json");
|
|
}
|
|
}
|