diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/.gitignore b/BuildScripts/IgnoresAccessChecksToGenerator/.gitignore
new file mode 100644
index 000000000..a6ff8d5c7
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/.gitignore
@@ -0,0 +1,4 @@
+artifacts/
+**/bin/
+**/obj/
+*.log
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/Consumer/Consumer.csproj b/BuildScripts/IgnoresAccessChecksToGenerator/Consumer/Consumer.csproj
new file mode 100644
index 000000000..97e1eb0dd
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/Consumer/Consumer.csproj
@@ -0,0 +1,26 @@
+
+
+ net8.0;net10.0
+ Exe
+ Disable
+ enable
+ enable
+ ../artifacts/$(TargetFramework)/private
+ true
+ $(DefineConstants);GRANT_PRIVATE_ACCESS
+ $(DefineConstants);MISSING_MEMBER
+ $(DefineConstants);WRONG_SIGNATURE
+ $(DefineConstants);GENERATED_ACCESS_ATTRIBUTE
+
+
+
+ <_Parameter1>windows
+
+
+ $(FacadeDirectory)/System.Private.CoreLib.dll
+ false
+
+
+
+
+
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/Consumer/Program.cs b/BuildScripts/IgnoresAccessChecksToGenerator/Consumer/Program.cs
new file mode 100644
index 000000000..37d985919
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/Consumer/Program.cs
@@ -0,0 +1,61 @@
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.CompilerServices;
+
+#if GRANT_PRIVATE_ACCESS && !GENERATED_ACCESS_ATTRIBUTE
+[assembly: IgnoresAccessChecksTo("System.Private.CoreLib")]
+#endif
+
+var actual = typeof(object).Assembly.Location;
+var expected = Environment.GetEnvironmentVariable("EXPECTED_CORELIB");
+if (expected != null && !string.Equals(Path.GetFullPath(actual), Path.GetFullPath(expected), StringComparison.OrdinalIgnoreCase))
+ throw new Exception("Consumer executed a different runtime than the selected reference: " + actual);
+if (!actual.StartsWith(System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(), StringComparison.OrdinalIgnoreCase))
+ throw new Exception("Unexpected runtime CoreLib: " + actual);
+Console.WriteLine("Executing installed CoreLib: " + actual);
+var method = new DynamicMethod("direct_private_access", typeof(int), Type.EmptyTypes, typeof(Program), true);
+var il = (RuntimeILGenerator)method.GetILGenerator(16);
+Console.WriteLine($"Original visibility: stream={typeof(RuntimeILGenerator).GetField("m_ILStream", BindingFlags.Instance | BindingFlags.NonPublic)!.Attributes}; increase={typeof(RuntimeILGenerator).GetMethod("IncreaseCapacity", BindingFlags.Instance | BindingFlags.NonPublic)!.Attributes}; nested={typeof(GC.StartNoGCRegionStatus).Attributes}");
+if (!typeof(RuntimeILGenerator).GetField("m_ILStream", BindingFlags.Instance | BindingFlags.NonPublic)!.IsPrivate ||
+ !typeof(RuntimeILGenerator).GetMethod("IncreaseCapacity", BindingFlags.Instance | BindingFlags.NonPublic)!.IsPrivate ||
+ !typeof(GC.StartNoGCRegionStatus).IsNestedPrivate)
+ throw new Exception("The probe must exercise genuinely private members/types in the installed runtime");
+#if MISSING_MEMBER
+Console.WriteLine(il.FieldThatDoesNotExist);
+#endif
+#if WRONG_SIGNATURE
+string wrong = il.m_localCount;
+#endif
+var oldSize = il.m_ILStream.Length;
+il.IncreaseCapacity(4096); // Private method, not just an internal member.
+if (il.m_ILStream.Length <= oldSize) throw new Exception("Private method did not grow the stream");
+il.m_localSignature.AddArgument(typeof(int), false);
+++il.m_localCount;
+var scope = ((DynamicILGenerator)il).m_scope;
+scope.m_tokens.Add(typeof(Math).GetMethod(nameof(Math.Abs), new[] { typeof(int) })!.MethodHandle);
+var token = (scope.m_tokens.Count - 1) | 0x06000000;
+il.m_ILStream[il.m_length++] = (byte)OpCodes.Ldc_I4_S.Value;
+il.m_ILStream[il.m_length++] = unchecked((byte)-42);
+il.UpdateStackSize(OpCodes.Ldc_I4_S, 1);
+il.m_ILStream[il.m_length++] = (byte)OpCodes.Call.Value;
+for (var shift = 0; shift != 32; shift += 8)
+ il.m_ILStream[il.m_length++] = (byte)(token >> shift);
+il.UpdateStackSize(OpCodes.Call, 0);
+il.Emit(OpCodes.Stloc_0);
+il.Emit(OpCodes.Ldloc_0);
+il.Emit(OpCodes.Ret);
+var result = ((Func)method.CreateDelegate(typeof(Func)))();
+if (result != 42) throw new Exception("Unexpected result: " + result);
+Console.WriteLine("Direct private fields + private method + internal types: result=" + result);
+Console.WriteLine("Private nested type: " + typeof(GC.StartNoGCRegionStatus).FullName);
+
+#if !GENERATED_ACCESS_ATTRIBUTE
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ internal sealed class IgnoresAccessChecksToAttribute(string assemblyName) : Attribute
+ {
+ public string AssemblyName { get; } = assemblyName;
+ }
+}
+#endif
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/Directory.Build.props b/BuildScripts/IgnoresAccessChecksToGenerator/Directory.Build.props
new file mode 100644
index 000000000..66325ee99
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/Directory.Build.props
@@ -0,0 +1,5 @@
+
+
+ latest
+
+
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/FecSmoke/FecSmoke.csproj b/BuildScripts/IgnoresAccessChecksToGenerator/FecSmoke/FecSmoke.csproj
new file mode 100644
index 000000000..2f41e7572
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/FecSmoke/FecSmoke.csproj
@@ -0,0 +1,22 @@
+
+
+ net8.0;net10.0
+ Exe
+ Disable
+ enable
+ disable
+ $(DefineConstants);LIGHT_EXPRESSION
+ ../artifacts/$(TargetFramework)/private
+
+
+
+ <_Parameter1>windows
+
+
+
+
+
+
+
+
+
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/FecSmoke/Program.cs b/BuildScripts/IgnoresAccessChecksToGenerator/FecSmoke/Program.cs
new file mode 100644
index 000000000..739896d8f
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/FecSmoke/Program.cs
@@ -0,0 +1,42 @@
+using System.Runtime.CompilerServices;
+#if LIGHT_EXPRESSION
+using FastExpressionCompiler.LightExpression;
+using FastExpressionCompiler.LightExpression.UnitTests;
+using E = FastExpressionCompiler.LightExpression.Expression;
+#else
+using FastExpressionCompiler;
+using FastExpressionCompiler.UnitTests;
+using E = System.Linq.Expressions.Expression;
+#endif
+
+[assembly: IgnoresAccessChecksTo("System.Private.CoreLib")]
+
+var expected = Environment.GetEnvironmentVariable("EXPECTED_CORELIB");
+if (expected != null && !string.Equals(Path.GetFullPath(typeof(object).Assembly.Location), Path.GetFullPath(expected), StringComparison.OrdinalIgnoreCase))
+ throw new Exception("FEC smoke executed a different runtime than the selected reference");
+var parameter = E.Parameter(typeof(int), "value");
+var local = E.Variable(typeof(int), "local");
+var block = E.Block(new[] { local },
+ E.Assign(local, E.Add(parameter, E.Constant(1))),
+ E.TryFinally(E.Add(local, E.Constant(1)), E.Empty()));
+var lambda = E.Lambda>(block, parameter);
+for (var i = 0; i < 100; ++i)
+{
+ var compiled = lambda.CompileFast(true);
+ if (compiled(40) != 42) throw new Exception("Compilation/local/exception smoke failed");
+}
+var nested = E.Lambda>>(E.Lambda>(E.Add(parameter, E.Constant(2))), parameter).CompileFast(true);
+if (nested(40)() != 42 || nested(50)() != 52) throw new Exception("Nested closure smoke failed");
+var tests = new TestRun(TestFlags.RethrowException);
+tests.Run(new RuntimeAccessorContractTests());
+if (tests.FailedTestCount != 0) throw new Exception("Runtime contracts failed");
+Console.WriteLine($"{typeof(ExpressionCompiler).Namespace}: 100 repeated local/exception compilations, nested capture and {tests.TotalTestCount} runtime contracts passed");
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ internal sealed class IgnoresAccessChecksToAttribute(string assemblyName) : Attribute
+ {
+ public string AssemblyName { get; } = assemblyName;
+ }
+}
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/FecSmoke/RuntimeAccessorContractTests.cs b/BuildScripts/IgnoresAccessChecksToGenerator/FecSmoke/RuntimeAccessorContractTests.cs
new file mode 100644
index 000000000..230ef8aa1
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/FecSmoke/RuntimeAccessorContractTests.cs
@@ -0,0 +1,159 @@
+#if NET8_0_OR_GREATER
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+#if NET10_0_OR_GREATER
+using System.Runtime.CompilerServices;
+#endif
+
+#if LIGHT_EXPRESSION
+namespace FastExpressionCompiler.LightExpression.UnitTests;
+#else
+namespace FastExpressionCompiler.UnitTests;
+#endif
+
+// These are intentionally strict contracts for the CoreCLR versions exercised by CI.
+// A runtime update must not silently disable an optimization while correctness tests pass.
+public struct RuntimeAccessorContractTests : ITestX
+{
+ private const BindingFlags NonPublicStatic = BindingFlags.NonPublic | BindingFlags.Static;
+ private const BindingFlags InstanceMembers = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
+
+ public void Run(TestRun t)
+ {
+ Local_allocation_and_generator_reuse_remain_available(t);
+#if NET10_0_OR_GREATER
+ Accessor_declarations_match_the_runtime_members(t);
+ Production_accessors_bind_and_emit_a_working_method(t);
+#endif
+ }
+
+ public void Local_allocation_and_generator_reuse_remain_available(TestContext t)
+ {
+ var hacks = typeof(DynamicMethodHacks);
+ t.IsNotNull(hacks.GetField("GetNextLocalVarLocation", NonPublicStatic).GetValue(null));
+ t.IsNotNull(hacks.GetField("ReuseDynamicILGenerator", NonPublicStatic).GetValue(null));
+ var pool = hacks.GetField("_pooledILGenerator", NonPublicStatic);
+ var previous = pool.GetValue(null);
+ pool.SetValue(null, null);
+ try
+ {
+ var first = new DynamicMethod("first", typeof(int), new[] { typeof(int) }, typeof(RuntimeAccessorContractTests), true);
+ var il = DynamicMethodHacks.RentPooledOrNewILGenerator(first, typeof(int), new[] { typeof(int) });
+ t.AreEqual(0, il.GetNextLocalVarIndex(typeof(int)));
+ il.BeginExceptionBlock();
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Call, typeof(Math).GetMethod(nameof(Math.Abs), new[] { typeof(int) }));
+ il.Emit(OpCodes.Stloc_0);
+ il.BeginFinallyBlock();
+ il.Emit(OpCodes.Nop);
+ il.EndExceptionBlock();
+ il.Emit(OpCodes.Ldloc_0);
+ il.Emit(OpCodes.Ret);
+ var f = (Func)first.CreateDelegate(typeof(Func));
+ t.AreEqual(42, f(-42));
+ DynamicMethodHacks.FreePooledILGenerator(first, il);
+
+ // Change the signature and local type after tokens, exception regions and locals were used.
+ var second = new DynamicMethod("second", typeof(string), new[] { typeof(string) }, typeof(RuntimeAccessorContractTests), true);
+ var reused = DynamicMethodHacks.RentPooledOrNewILGenerator(second, typeof(string), new[] { typeof(string) });
+ t.IsTrue(ReferenceEquals(il, reused));
+ t.AreEqual(0, reused.GetNextLocalVarIndex(typeof(string)));
+ reused.Emit(OpCodes.Ldarg_0);
+ reused.Emit(OpCodes.Stloc_0);
+ reused.Emit(OpCodes.Ldloc_0);
+ reused.Emit(OpCodes.Ret);
+ var g = (Func)second.CreateDelegate(typeof(Func));
+ t.AreEqual("second", g("second"));
+ t.AreEqual(17, f(-17)); // Reuse must not change the already-created method.
+ DynamicMethodHacks.FreePooledILGenerator(second, reused);
+ }
+ finally
+ {
+ pool.SetValue(null, previous);
+ }
+ }
+
+#if NET10_0_OR_GREATER
+ public void Accessor_declarations_match_the_runtime_members(TestContext t)
+ {
+ var count = 0;
+ foreach (var accessor in typeof(DynamicMethodHacks).GetMethods(NonPublicStatic))
+ {
+ var attribute = accessor.GetCustomAttribute();
+ if (attribute == null)
+ continue;
+ ++count;
+ var parameters = accessor.GetParameters();
+ var ownerName = parameters[0].GetCustomAttribute().TypeName;
+ var owner = typeof(ILGenerator).Assembly.GetType(ownerName, throwOnError: true);
+ // UnsafeAccessor does not search base classes. Check the exact declaring type.
+ if (attribute.Kind == UnsafeAccessorKind.Field)
+ {
+ var field = owner.GetField(attribute.Name, InstanceMembers | BindingFlags.DeclaredOnly);
+ if (field == null)
+ throw new MissingFieldException(owner.FullName, attribute.Name);
+ t.IsTrue(!field.IsStatic);
+ t.AreEqual(field.FieldType.MakeByRefType(), accessor.ReturnType);
+ t.AreEqual(1, parameters.Length);
+ }
+ else
+ {
+ t.AreEqual(UnsafeAccessorKind.Method, attribute.Kind);
+ var argumentTypes = new Type[parameters.Length - 1];
+ for (var i = 1; i < parameters.Length; ++i)
+ argumentTypes[i - 1] = parameters[i].ParameterType;
+ var method = owner.GetMethod(attribute.Name, InstanceMembers | BindingFlags.DeclaredOnly, null, argumentTypes, null);
+ if (method == null)
+ throw new MissingMethodException(owner.FullName, attribute.Name);
+ t.IsTrue(!method.IsStatic);
+ t.AreEqual(method.ReturnType, accessor.ReturnType);
+ }
+ }
+ t.AreEqual(6, count);
+ }
+
+ private delegate ref T FieldAccessor(object instance);
+
+ private static T Bind(string name) where T : Delegate =>
+ (T)typeof(DynamicMethodHacks).GetMethod(name, NonPublicStatic).CreateDelegate(typeof(T));
+
+ public void Production_accessors_bind_and_emit_a_working_method(TestContext t)
+ {
+ // Bind the production declarations, not duplicate UnsafeAccessor stubs in the test assembly.
+ var signature = Bind>("GetMLocalSignature");
+ var localCount = Bind>("GetMLocalCount");
+ var length = Bind>("GetMLength");
+ var stream = Bind>("GetMILStream");
+ var tokens = Bind>>("GetMTokens");
+ var updateStack = Bind>("UpdateStackSize");
+ var method = new DynamicMethod("accessors", typeof(int), Type.EmptyTypes, typeof(RuntimeAccessorContractTests), true);
+ var il = method.GetILGenerator(32);
+ t.AreEqual(0, localCount(il));
+ signature(il).AddArgument(typeof(int), false);
+ ++localCount(il);
+ t.AreEqual(1, localCount(il));
+
+ var scope = il.GetType().GetField("m_scope", InstanceMembers).GetValue(il);
+ var table = tokens(scope);
+ table.Add(typeof(Math).GetMethod(nameof(Math.Abs), new[] { typeof(int) }).MethodHandle);
+ var token = (table.Count - 1) | 0x06000000;
+ var bytes = stream(il);
+ ref var position = ref length(il);
+ t.AreEqual(0, position);
+ bytes[position++] = (byte)OpCodes.Ldc_I4_S.Value;
+ bytes[position++] = unchecked((byte)-42);
+ updateStack(il, OpCodes.Ldc_I4_S, 1);
+ bytes[position++] = (byte)OpCodes.Call.Value;
+ for (var shift = 0; shift != 32; shift += 8)
+ bytes[position++] = (byte)(token >> shift);
+ updateStack(il, OpCodes.Call, 0); // Abs consumes and returns one value.
+ il.Emit(OpCodes.Stloc_0);
+ il.Emit(OpCodes.Ldloc_0);
+ il.Emit(OpCodes.Ret);
+ t.AreEqual(42, ((Func)method.CreateDelegate(typeof(Func)))());
+ }
+#endif
+}
+#endif
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/Generator/Generator.csproj b/BuildScripts/IgnoresAccessChecksToGenerator/Generator/Generator.csproj
new file mode 100644
index 000000000..b56604351
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/Generator/Generator.csproj
@@ -0,0 +1,12 @@
+
+
+ net10.0
+ Exe
+ enable
+ enable
+
+
+
+
+
+
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/Generator/Program.cs b/BuildScripts/IgnoresAccessChecksToGenerator/Generator/Program.cs
new file mode 100644
index 000000000..3f178945c
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/Generator/Program.cs
@@ -0,0 +1,199 @@
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+using System.Reflection.PortableExecutable;
+
+if (args.Length >= 3 && args[0] == "--verify")
+{
+ ReferenceVerifier.Verify(args[1], args[2], args.Contains("--private"));
+ return;
+}
+if (args.Length < 2)
+ throw new ArgumentException("Usage: Generator input-implementation.dll output-reference.dll [--private]");
+ReferencePublicizer.Generate(args[0], args[1], args.Contains("--private"));
+
+internal static class ReferencePublicizer
+{
+ public static void Generate(string input, string output, bool includePrivate)
+ {
+ using var file = File.OpenRead(input);
+ using var pe = new PEReader(file);
+ var r = pe.GetMetadataReader();
+ if (r.GetString(r.GetAssemblyDefinition().Name) != "System.Private.CoreLib")
+ throw new NotSupportedException("This local prototype currently supports CoreLib only, not arbitrary package assemblies.");
+ var b = new MetadataBuilder();
+ StringHandle S(StringHandle h) => h.IsNil ? default : b.GetOrAddString(r.GetString(h));
+ BlobHandle B(BlobHandle h) => h.IsNil ? default : b.GetOrAddBlob(r.GetBlobBytes(h));
+ GuidHandle G(GuidHandle h) => h.IsNil ? default : b.GetOrAddGuid(r.GetGuid(h));
+
+ var module = r.GetModuleDefinition();
+ b.AddModule(module.Generation, S(module.Name), G(module.Mvid), G(module.GenerationId), G(module.BaseGenerationId));
+ var assembly = r.GetAssemblyDefinition();
+ b.AddAssembly(S(assembly.Name), assembly.Version, S(assembly.Culture), B(assembly.PublicKey), assembly.Flags, assembly.HashAlgorithm);
+ foreach (var h in r.AssemblyReferences)
+ {
+ var v = r.GetAssemblyReference(h);
+ b.AddAssemblyReference(S(v.Name), v.Version, S(v.Culture), B(v.PublicKeyOrToken), v.Flags, B(v.HashValue));
+ }
+ foreach (var h in r.TypeReferences)
+ {
+ var v = r.GetTypeReference(h);
+ b.AddTypeReference(v.ResolutionScope, S(v.Namespace), S(v.Name));
+ }
+ // Preserve row numbers in all signature-addressable tables; signature blobs can then be copied unchanged.
+ for (var i = 1; i <= r.GetTableRowCount(TableIndex.TypeSpec); ++i)
+ b.AddTypeSpecification(B(r.GetTypeSpecification(MetadataTokens.TypeSpecificationHandle(i)).Signature));
+ for (var i = 1; i <= r.GetTableRowCount(TableIndex.ModuleRef); ++i)
+ b.AddModuleReference(S(r.GetModuleReference(MetadataTokens.ModuleReferenceHandle(i)).Name));
+ foreach (var h in r.MemberReferences)
+ {
+ var v = r.GetMemberReference(h);
+ b.AddMemberReference(v.Parent, S(v.Name), B(v.Signature));
+ }
+ for (var i = 1; i <= r.GetTableRowCount(TableIndex.MethodSpec); ++i)
+ {
+ var v = r.GetMethodSpecification(MetadataTokens.MethodSpecificationHandle(i));
+ b.AddMethodSpecification(v.Method, B(v.Signature));
+ }
+
+ var fieldRow = 1;
+ var methodRow = 1;
+ MethodDefinitionHandle referenceAttributeCtor = default;
+ foreach (var h in r.TypeDefinitions)
+ {
+ var v = r.GetTypeDefinition(h);
+ var attributes = v.Attributes;
+ var visibility = attributes & TypeAttributes.VisibilityMask;
+ if (r.GetString(v.Name) != "" &&
+ (includePrivate || visibility is TypeAttributes.NotPublic or TypeAttributes.NestedAssembly or TypeAttributes.NestedFamORAssem or TypeAttributes.NestedFamANDAssem))
+ attributes = (attributes & ~TypeAttributes.VisibilityMask) | (v.IsNested ? TypeAttributes.NestedPublic : TypeAttributes.Public);
+ b.AddTypeDefinition(attributes, S(v.Namespace), S(v.Name), v.BaseType,
+ MetadataTokens.FieldDefinitionHandle(fieldRow), MetadataTokens.MethodDefinitionHandle(methodRow));
+ fieldRow += v.GetFields().Count;
+ methodRow += v.GetMethods().Count;
+ if (v.IsNested)
+ b.AddNestedType(h, v.GetDeclaringType());
+ var layout = v.GetLayout();
+ if (!layout.IsDefault)
+ b.AddTypeLayout(h, (ushort)layout.PackingSize, (uint)layout.Size);
+ foreach (var ih in v.GetInterfaceImplementations())
+ b.AddInterfaceImplementation(h, r.GetInterfaceImplementation(ih).Interface);
+ foreach (var mh in v.GetMethodImplementations())
+ {
+ var implementation = r.GetMethodImplementation(mh);
+ b.AddMethodImplementation(h, implementation.MethodBody, implementation.MethodDeclaration);
+ }
+ if (r.GetString(v.Namespace) == "System.Runtime.CompilerServices" && r.GetString(v.Name) == "ReferenceAssemblyAttribute")
+ referenceAttributeCtor = v.GetMethods().First(m => r.GetString(r.GetMethodDefinition(m).Name) == ".ctor");
+ }
+ foreach (var h in r.FieldDefinitions)
+ {
+ var v = r.GetFieldDefinition(h);
+ var attributes = v.Attributes & ~FieldAttributes.HasFieldRVA;
+ var access = attributes & FieldAttributes.FieldAccessMask;
+ if (includePrivate || access is FieldAttributes.Assembly or FieldAttributes.FamORAssem or FieldAttributes.FamANDAssem)
+ attributes = (attributes & ~FieldAttributes.FieldAccessMask) | FieldAttributes.Public;
+ b.AddFieldDefinition(attributes, S(v.Name), B(v.Signature));
+ var offset = v.GetOffset();
+ if (offset >= 0)
+ b.AddFieldLayout(h, offset);
+ var marshal = v.GetMarshallingDescriptor();
+ if (!marshal.IsNil)
+ b.AddMarshallingDescriptor(h, B(marshal));
+ }
+ var paramRow = 1;
+ foreach (var h in r.MethodDefinitions)
+ {
+ var v = r.GetMethodDefinition(h);
+ var attributes = v.Attributes;
+ var access = attributes & MethodAttributes.MemberAccessMask;
+ if (includePrivate || access is MethodAttributes.Assembly or MethodAttributes.FamORAssem or MethodAttributes.FamANDAssem)
+ attributes = (attributes & ~MethodAttributes.MemberAccessMask) | MethodAttributes.Public;
+ b.AddMethodDefinition(attributes, v.ImplAttributes, S(v.Name), B(v.Signature), -1, MetadataTokens.ParameterHandle(paramRow));
+ foreach (var ph in v.GetParameters())
+ {
+ var p = r.GetParameter(ph);
+ b.AddParameter(p.Attributes, S(p.Name), p.SequenceNumber);
+ var marshal = p.GetMarshallingDescriptor();
+ if (!marshal.IsNil)
+ b.AddMarshallingDescriptor(ph, B(marshal));
+ ++paramRow;
+ }
+ var import = v.GetImport();
+ if (!import.Module.IsNil)
+ b.AddMethodImport(h, import.Attributes, S(import.Name), import.Module);
+ }
+ for (var i = 1; i <= r.GetTableRowCount(TableIndex.GenericParam); ++i)
+ {
+ var v = r.GetGenericParameter(MetadataTokens.GenericParameterHandle(i));
+ b.AddGenericParameter(v.Parent, v.Attributes, S(v.Name), v.Index);
+ }
+ for (var i = 1; i <= r.GetTableRowCount(TableIndex.GenericParamConstraint); ++i)
+ {
+ var v = r.GetGenericParameterConstraint(MetadataTokens.GenericParameterConstraintHandle(i));
+ b.AddGenericParameterConstraint(v.Parameter, v.Type);
+ }
+ foreach (var h in r.PropertyDefinitions)
+ {
+ var v = r.GetPropertyDefinition(h);
+ b.AddProperty(v.Attributes, S(v.Name), B(v.Signature));
+ var a = v.GetAccessors();
+ if (!a.Getter.IsNil) b.AddMethodSemantics(h, MethodSemanticsAttributes.Getter, a.Getter);
+ if (!a.Setter.IsNil) b.AddMethodSemantics(h, MethodSemanticsAttributes.Setter, a.Setter);
+ foreach (var m in a.Others) b.AddMethodSemantics(h, MethodSemanticsAttributes.Other, m);
+ }
+ foreach (var h in r.EventDefinitions)
+ {
+ var v = r.GetEventDefinition(h);
+ b.AddEvent(v.Attributes, S(v.Name), v.Type);
+ var a = v.GetAccessors();
+ if (!a.Adder.IsNil) b.AddMethodSemantics(h, MethodSemanticsAttributes.Adder, a.Adder);
+ if (!a.Remover.IsNil) b.AddMethodSemantics(h, MethodSemanticsAttributes.Remover, a.Remover);
+ if (!a.Raiser.IsNil) b.AddMethodSemantics(h, MethodSemanticsAttributes.Raiser, a.Raiser);
+ foreach (var m in a.Others) b.AddMethodSemantics(h, MethodSemanticsAttributes.Other, m);
+ }
+ foreach (var h in r.TypeDefinitions)
+ {
+ var v = r.GetTypeDefinition(h);
+ if (v.GetProperties().Count != 0) b.AddPropertyMap(h, v.GetProperties().First());
+ if (v.GetEvents().Count != 0) b.AddEventMap(h, v.GetEvents().First());
+ }
+ for (var i = 1; i <= r.GetTableRowCount(TableIndex.Constant); ++i)
+ {
+ var v = r.GetConstant(MetadataTokens.ConstantHandle(i));
+ var blob = r.GetBlobReader(v.Value);
+ b.AddConstant(v.Parent, blob.ReadConstant(v.TypeCode));
+ }
+ for (var i = 1; i <= r.GetTableRowCount(TableIndex.DeclSecurity); ++i)
+ {
+ var v = r.GetDeclarativeSecurityAttribute(MetadataTokens.DeclarativeSecurityAttributeHandle(i));
+ b.AddDeclarativeSecurityAttribute(v.Parent, v.Action, B(v.PermissionSet));
+ }
+ foreach (var h in r.CustomAttributes)
+ {
+ var v = r.GetCustomAttribute(h);
+ b.AddCustomAttribute(v.Parent, v.Constructor, B(v.Value));
+ }
+ if (referenceAttributeCtor.IsNil)
+ throw new NotSupportedException("Prototype currently requires ReferenceAssemblyAttribute in the source assembly (CoreLib).");
+ b.AddCustomAttribute(MetadataTokens.EntityHandle(TableIndex.Assembly, 1), referenceAttributeCtor, b.GetOrAddBlob(new byte[] { 1, 0, 0, 0 }));
+ // A new IL-only image: no ReadyToRun/native sections, method bodies, or executable resources are copied.
+ var builder = new ManagedPEBuilder(new PEHeaderBuilder(imageCharacteristics: Characteristics.ExecutableImage | Characteristics.Dll),
+ new MetadataRootBuilder(b), new BlobBuilder(), flags: CorFlags.ILOnly);
+ var image = new BlobBuilder();
+ builder.Serialize(image);
+ Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(output))!);
+ using var destination = File.Create(output);
+ image.WriteContentTo(destination);
+ Console.WriteLine($"{r.GetString(assembly.Name)} {assembly.Version}: {r.TypeDefinitions.Count} types, {r.FieldDefinitions.Count} fields, {r.MethodDefinitions.Count} methods; private={includePrivate}; {destination.Length:N0} bytes");
+ var references = new List();
+ foreach (var path in Directory.EnumerateFiles(Path.GetDirectoryName(Path.GetFullPath(input))!, "*.dll").Order())
+ {
+ using var source = File.OpenRead(path);
+ using var runtimeImage = new PEReader(source);
+ if (runtimeImage.HasMetadata)
+ references.Add(Path.GetFullPath(path) == Path.GetFullPath(input) ? Path.GetFullPath(output) : Path.GetFullPath(path));
+ }
+ File.WriteAllLines(Path.Combine(Path.GetDirectoryName(Path.GetFullPath(output))!, "runtime-references.txt"), references);
+ }
+}
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/Generator/PublicizeInternals.cs b/BuildScripts/IgnoresAccessChecksToGenerator/Generator/PublicizeInternals.cs
new file mode 100644
index 000000000..60f5f9cb6
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/Generator/PublicizeInternals.cs
@@ -0,0 +1,45 @@
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace IgnoresAccessChecksToGenerator.Tasks;
+
+// Experimental replacement backend for the upstream task, using only System.Reflection.Metadata.
+// Unlike upstream this prototype is .NET-10-MSBuild/CoreLib-only and returns the matching reference universe.
+public sealed class PublicizeInternals : Microsoft.Build.Utilities.Task
+{
+ [Required] public string ImplementationAssembly { get; set; } = "";
+ [Required] public string IntermediateOutputPath { get; set; } = "";
+ [Required] public string GeneratedCodeFilePath { get; set; } = "";
+ public bool IncludePrivateMembers { get; set; }
+ [Output] public ITaskItem[] ReferencePaths { get; set; } = [];
+
+ public override bool Execute()
+ {
+ try
+ {
+ var output = Path.Combine(Path.GetFullPath(IntermediateOutputPath), "System.Private.CoreLib.dll");
+ ReferencePublicizer.Generate(ImplementationAssembly, output, IncludePrivateMembers);
+ ReferenceVerifier.Verify(ImplementationAssembly, output, IncludePrivateMembers);
+ ReferencePaths = File.ReadAllLines(Path.Combine(Path.GetDirectoryName(output)!, "runtime-references.txt"))
+ .Select(path => (ITaskItem)new TaskItem(path)).ToArray();
+ Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(GeneratedCodeFilePath))!);
+ File.WriteAllText(GeneratedCodeFilePath, """
+ [assembly: System.Runtime.CompilerServices.IgnoresAccessChecksTo("System.Private.CoreLib")]
+ namespace System.Runtime.CompilerServices
+ {
+ [System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true)]
+ internal sealed class IgnoresAccessChecksToAttribute(string assemblyName) : System.Attribute
+ {
+ public string AssemblyName { get; } = assemblyName;
+ }
+ }
+ """);
+ return true;
+ }
+ catch (Exception error)
+ {
+ Log.LogErrorFromException(error, true);
+ return false;
+ }
+ }
+}
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/Generator/ReferenceVerifier.cs b/BuildScripts/IgnoresAccessChecksToGenerator/Generator/ReferenceVerifier.cs
new file mode 100644
index 000000000..35a28460a
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/Generator/ReferenceVerifier.cs
@@ -0,0 +1,59 @@
+using System.Reflection;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
+
+internal static class ReferenceVerifier
+{
+ public static void Verify(string input, string output, bool includePrivate)
+ {
+ using var source = new PEReader(File.OpenRead(input));
+ using var facade = new PEReader(File.OpenRead(output));
+ var a = source.GetMetadataReader();
+ var b = facade.GetMetadataReader();
+ static void Require(bool condition, string message)
+ {
+ if (!condition) throw new InvalidDataException(message);
+ }
+ var aa = a.GetAssemblyDefinition();
+ var ba = b.GetAssemblyDefinition();
+ Require(a.GetString(aa.Name) == b.GetString(ba.Name) && a.GetString(aa.Culture) == b.GetString(ba.Culture) && aa.Version == ba.Version && aa.Flags == ba.Flags &&
+ a.GetBlobBytes(aa.PublicKey).SequenceEqual(b.GetBlobBytes(ba.PublicKey)), "Assembly identity changed");
+ Require(facade.PEHeaders.CorHeader!.Flags.HasFlag(CorFlags.ILOnly), "Facade must be IL-only");
+ Require(facade.PEHeaders.CorHeader.ManagedNativeHeaderDirectory.Size == 0, "Native implementation leaked into facade");
+ Require(a.TypeDefinitions.Count == b.TypeDefinitions.Count && a.FieldDefinitions.Count == b.FieldDefinitions.Count &&
+ a.MethodDefinitions.Count == b.MethodDefinitions.Count, "Definition row counts changed");
+ foreach (var handle in a.TypeDefinitions)
+ {
+ var x = a.GetTypeDefinition(handle);
+ var y = b.GetTypeDefinition(handle);
+ Require(a.GetString(x.Name) == b.GetString(y.Name) && a.GetString(x.Namespace) == b.GetString(y.Namespace) && x.BaseType == y.BaseType &&
+ x.GetDeclaringType() == y.GetDeclaringType() && x.GetFields().SequenceEqual(y.GetFields()) && x.GetMethods().SequenceEqual(y.GetMethods()), "Type token/range changed");
+ if (includePrivate && a.GetString(x.Name) != "")
+ Require((y.Attributes & TypeAttributes.VisibilityMask) == (y.IsNested ? TypeAttributes.NestedPublic : TypeAttributes.Public), "Type not publicized");
+ }
+ foreach (var handle in a.FieldDefinitions)
+ {
+ var x = a.GetFieldDefinition(handle);
+ var y = b.GetFieldDefinition(handle);
+ Require(a.GetString(x.Name) == b.GetString(y.Name) && a.GetBlobBytes(x.Signature).SequenceEqual(b.GetBlobBytes(y.Signature)), "Field name/signature changed");
+ if (includePrivate) Require((y.Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Public, "Private field not exposed");
+ }
+ foreach (var handle in a.MethodDefinitions)
+ {
+ var x = a.GetMethodDefinition(handle);
+ var y = b.GetMethodDefinition(handle);
+ Require(a.GetString(x.Name) == b.GetString(y.Name) && a.GetBlobBytes(x.Signature).SequenceEqual(b.GetBlobBytes(y.Signature)) &&
+ x.GetParameters().SequenceEqual(y.GetParameters()), "Method name/signature/parameter range changed");
+ Require(y.RelativeVirtualAddress == 0, "Executable body leaked into facade");
+ if (includePrivate) Require((y.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public, "Private method not exposed");
+ }
+ var refAttribute = b.CustomAttributes.Any(h =>
+ {
+ var attr = b.GetCustomAttribute(h);
+ return attr.Parent.Kind == HandleKind.AssemblyDefinition && attr.Constructor.Kind == HandleKind.MethodDefinition &&
+ b.GetString(b.GetTypeDefinition(b.GetMethodDefinition((MethodDefinitionHandle)attr.Constructor).GetDeclaringType()).Name) == "ReferenceAssemblyAttribute";
+ });
+ Require(refAttribute, "Missing ReferenceAssemblyAttribute");
+ Console.WriteLine("Verified identity, IL-only/reference marker, all definition tokens/ranges/signatures and absence of method bodies");
+ }
+}
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/LICENSE b/BuildScripts/IgnoresAccessChecksToGenerator/LICENSE
new file mode 100644
index 000000000..25cb8ca2f
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Eli Arbel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/README.md b/BuildScripts/IgnoresAccessChecksToGenerator/README.md
new file mode 100644
index 000000000..97374380b
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/README.md
@@ -0,0 +1,57 @@
+# Local metadata publicizer experiment for #560
+
+**Draft follow-up experiment to #567, for #560.** Branch `experiment/metadata-private-access-560`, based on FEC master `734314ede7c5e58845d42345e1cfb4d59591b998`. It is not wired into FEC's production build or solution.
+
+This is a local source/backend fork experiment based on [IgnoresAccessChecksToGenerator](https://github.com/aelij/IgnoresAccessChecksToGenerator), upstream revision `882b30bf82fbb72f3bee8878ad9fd31f5c93c875`. The original task/targets snapshots are in `upstream/`, under the retained MIT `LICENSE`. The experimental backend replaces Cecil with `System.Reflection.Metadata` / `System.Reflection.PortableExecutable`, plus an MSBuild task. The compiled generator has no Cecil or NuGet package dependency. This is **not yet a drop-in replacement for the whole upstream NuGet package**.
+
+## What works
+
+The backend reads the installed CoreLib with `PEReader`, copies compiler-relevant metadata into `MetadataBuilder`, and writes a **new IL-only reference assembly** with `ManagedPEBuilder` and `ReferenceAssemblyAttribute`. It does not rewrite the runtime executable image. Assembly identity, type/field/method tokens, signature blobs, generic definitions, nested relationships and relevant member metadata are retained. Method bodies, native sections, RVA field data and embedded resources are not copied.
+
+Default exposure matches the upstream internal-member policy. `--private` additionally exposes private fields, methods and nested types (and the remaining accessibilities) in the generated compiler reference. The actual runtime members remain private; the consumer's `IgnoresAccessChecksTo` attribute authorizes access at runtime.
+
+Both .NET 8.0.28 and .NET 10.0.9 on Windows ARM64 pass:
+
+- C# compilation and execution using `RuntimeILGenerator`, genuine private fields `m_ILStream`/`m_length`, private method `IncreaseCapacity`, and private nested enum `GC.StartNoGCRegionStatus`. Runtime reflection confirms these are genuinely private in the installed assembly. The directly emitted method returns 42.
+- MSBuild integration: the new `PublicizeInternals` task generates the reference and access attribute, then supplies compiler references. The same consumer compiles and executes.
+- Compiling the real Standard and LightExpression FEC source in an isolated host against the generated references. Only an ignored source snapshot changes the local-allocation helper to use direct typed field access on .NET 8/10; production source files are untouched. Each configuration passes 100 repeated local/try-finally compilations, independent nested captures, and the runtime-contract tests from PR #567 (1 per API on .NET 8, 3 per API on .NET 10).
+- Disabling private exposure fails compilation; removing the access attribute fails with `TypeAccessException`; a nonexistent field fails compilation (`CS1061`); using the wrong field type fails compilation (`CS0029`). These are expected failures checked by the replay script.
+- All 25 replay steps pass. The installed CoreLib's SHA-256 is unchanged before/after. The generated CoreLib is not copied into consumer output directories. See `results.json` for commands, exit codes and input hashes; logs are in ignored `artifacts/logs/`.
+
+An exploratory `Array.ArrayAssignType` probe compiled on .NET 10 but failed on .NET 8 because that nested type is absent there. The retained cross-version probe uses `GC.StartNoGCRegionStatus`, verified private on both. This illustrates why checks must run against each intended runtime, rather than assuming their internals match.
+
+## Important build constraint
+
+Adding a full CoreLib facade alongside SDK reference assemblies causes duplicate core-type definitions: SDK `System.Runtime` defines types that the implementation puts in CoreLib. This prototype therefore uses one consistent set of **managed implementation references from the explicitly selected runtime**, replacing CoreLib with the generated reference. Native DLLs are excluded using `PEReader.HasMetadata`.
+
+This is deliberately a runtime-version/OS-specific build experiment, not a portable targeting pack. Compiler checks validate the selected implementation version, not every runtime a published FEC package could encounter. Windows implementation references also carry Windows-specific platform annotations; the sample consumers explicitly declare their Windows platform when built on Windows. No reference assembly is substituted at execution time.
+
+## Replay
+
+Requires the .NET 10 SDK, Python, and the installed .NET 8.0.28 / 10.0.9 runtimes. From this directory:
+
+```sh
+python replay.py
+```
+
+The script discovers the two exact installed runtime paths, builds the generator, verifies metadata and positive/negative behavior, runs the FEC smoke configurations, and records `results.json`. Consumer runtime versions are pinned with roll-forward disabled, and the executing CoreLib path must match the selected input. No git commit/push, network publication, runtime replacement or machine configuration change is performed. Build outputs/logs are ignored. The FEC smoke fixture is copied from PR #567 commit `ff41665593d64d18196aed3f202ef1a1944a4e4f` and compiled locally, so replay does not need that commit available as an object.
+
+CLI use:
+
+```sh
+dotnet run --project Generator/Generator.csproj -c Release -- path/to/System.Private.CoreLib.dll artifacts/custom/System.Private.CoreLib.dll --private
+dotnet run --project Generator/Generator.csproj -c Release -- --verify path/to/System.Private.CoreLib.dll artifacts/custom/System.Private.CoreLib.dll --private
+```
+
+## What is not established
+
+- No compilation/startup/allocation/runtime speedup has been measured. Typed access is feasible; that alone does not prove it beats UnsafeAccessor or generated helpers.
+- The task currently requires .NET 10 MSBuild and supports CoreLib only. Arbitrary packages, upstream exclusion/legacy options, older MSBuild hosts, packaging and incremental-generation caching are not implemented.
+- No cross-platform, x64, Framework 4.7.2, preview-runtime, or complete compatibility-suite claim is made. These are focused compile/runtime and metadata checks, not a full FEC regression run.
+- The verifier checks assembly identity, the reference/IL-only marker, definition tokens/ranges/signatures, visibility and absence of executable method bodies. This is not a general metadata-rewriter certification.
+
+## Merge recommendation
+
+**Keep this as a draft; do not merge the current prototype as a production solution for #560.** It establishes feasibility but adds a runtime-specific compiler-reference strategy without an established performance benefit. It also lacks cross-platform/x64 coverage and the validation needed for a general metadata writer.
+
+Next useful experiments are a narrower exposure policy/reference strategy and measured direct typed reuse/local-allocation helpers versus FEC's existing helpers, including build-time cost. Production integration should wait for those results and a decision on runtime-version support. The smaller runtime-contract tests in #567 can be reviewed independently; this draft does not replace or modify that PR.
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/build/IgnoresAccessChecksToGenerator.targets b/BuildScripts/IgnoresAccessChecksToGenerator/build/IgnoresAccessChecksToGenerator.targets
new file mode 100644
index 000000000..f73bdd1cd
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/build/IgnoresAccessChecksToGenerator.targets
@@ -0,0 +1,26 @@
+
+
+ <_MetadataTaskAssembly>$(MSBuildThisFileDirectory)../Generator/bin/Release/net10.0/Generator.dll
+
+
+
+
+ <_MetadataOutput>$(IntermediateOutputPath)PrivateRuntimeReferences
+ <_MetadataAttributeFile>$(_MetadataOutput)/IgnoresAccessChecksTo.g.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/build/RuntimeReferences.targets b/BuildScripts/IgnoresAccessChecksToGenerator/build/RuntimeReferences.targets
new file mode 100644
index 000000000..61648518a
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/build/RuntimeReferences.targets
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/replay.py b/BuildScripts/IgnoresAccessChecksToGenerator/replay.py
new file mode 100644
index 000000000..0245eb382
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/replay.py
@@ -0,0 +1,89 @@
+"""Local-only, pinned-runtime experiment. Does not modify installed runtimes, commit, push or publish."""
+import hashlib
+import json
+import os
+import pathlib
+import re
+import subprocess
+
+root = pathlib.Path(__file__).resolve().parent
+repo = root.parents[1]
+artifacts = root / "artifacts"
+logs = artifacts / "logs"
+logs.mkdir(parents=True, exist_ok=True)
+results = []
+child_environment = os.environ.copy()
+
+
+def run(label, arguments, expected=0, contains=None):
+ completed = subprocess.run(arguments, cwd=root, env=child_environment, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ output = completed.stdout.decode("utf-8", errors="replace")
+ (logs / (label + ".log")).write_text(output, encoding="utf-8")
+ ok = (completed.returncode == 0 if expected == 0 else completed.returncode != 0)
+ if contains is not None:
+ ok = ok and contains in output
+ results.append({"label": label, "arguments": arguments, "exit_code": completed.returncode,
+ "expected": "success" if expected == 0 else "failure", "passed": ok})
+ print(label + ": " + ("PASS" if ok else "FAIL"), flush=True)
+ if not ok:
+ print(output[-6000:])
+ raise RuntimeError(label)
+
+
+def digest(path):
+ return hashlib.sha256(path.read_bytes()).hexdigest()
+
+
+version = subprocess.check_output(["dotnet", "--version"], text=True).strip()
+current = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=repo, text=True).strip()
+installed = subprocess.check_output(["dotnet", "--list-runtimes"], text=True)
+paths = {v: pathlib.Path(p) / v / "System.Private.CoreLib.dll"
+ for v, p in re.findall(r"Microsoft.NETCore.App (\S+) \[([^\]]+)\]", installed)}
+selected = {"net8.0": "8.0.28", "net10.0": "10.0.9"}
+
+# Keep the real FEC source untouched. Transform only a throwaway compilation snapshot.
+source = (repo / "src/FastExpressionCompiler/FastExpressionCompiler.cs").read_text(encoding="utf-8")
+old = "#if NET10_0_OR_GREATER\n // In .NET 10+, use UnsafeAccessorType"
+assert source.count(old) == 1
+source = source.replace(old, "#if NET8_0_OR_GREATER\n // Local prototype: direct typed access through a metadata facade", 1)
+old = "GetMLocalSignature(il).AddArgument(t, false);\n return PostInc(ref GetMLocalCount(il));"
+assert source.count(old) == 1
+source = source.replace(old, "((System.Reflection.Emit.RuntimeILGenerator)il).m_localSignature.AddArgument(t, false);\n return PostInc(ref ((System.Reflection.Emit.RuntimeILGenerator)il).m_localCount);", 1)
+(artifacts / "Compiler.Direct.cs").write_bytes(source.encode("utf-8"))
+
+run("generator-build", ["dotnet", "build", "Generator/Generator.csproj", "-c", "Release"])
+runtime_hashes = {}
+try:
+ for tfm, runtime in selected.items():
+ corelib = paths[runtime]
+ child_environment["EXPECTED_CORELIB"] = str(corelib)
+ original_hash = digest(corelib)
+ runtime_hashes[runtime] = {"implementation": str(corelib), "sha256_before": original_hash}
+ private = artifacts / tfm / "private" / "System.Private.CoreLib.dll"
+ internal = artifacts / tfm / "internal" / "System.Private.CoreLib.dll"
+ generator = ["dotnet", str(root / "Generator/bin/Release/net10.0/Generator.dll")]
+ run(tfm + "-generate-private", generator + [str(corelib), str(private), "--private"])
+ run(tfm + "-verify-private", generator + ["--verify", str(corelib), str(private), "--private"])
+ run(tfm + "-generate-internal", generator + [str(corelib), str(internal)])
+ consumer = ["dotnet", "run", "--project", "Consumer/Consumer.csproj", "-c", "Release", "-f", tfm,
+ "-p:RuntimeFrameworkVersion=" + runtime]
+ run(tfm + "-consumer", consumer, contains="result=42")
+ run(tfm + "-task", consumer + ["-p:UseGeneratorTask=true", "-p:RuntimeCoreLib=" + str(corelib)], contains="result=42")
+ run(tfm + "-private-disabled", consumer + ["-p:FacadeDirectory=" + str(internal.parent)], expected=1, contains="error CS")
+ run(tfm + "-permission-disabled", consumer + ["-p:GrantPrivateAccess=false"], expected=1, contains="TypeAccessException")
+ run(tfm + "-missing-member", consumer + ["-p:MissingMemberProbe=true"], expected=1, contains="CS1061")
+ run(tfm + "-wrong-signature", consumer + ["-p:WrongSignatureProbe=true"], expected=1, contains="CS0029")
+ for light in (False, True):
+ run(tfm + ("-light" if light else "-standard"),
+ ["dotnet", "run", "--project", "FecSmoke/FecSmoke.csproj", "-c", "Release", "-f", tfm,
+ "-p:RuntimeFrameworkVersion=" + runtime, "-p:Light=" + str(light).lower()], contains="runtime contracts passed")
+ # Finish with a successful consumer output, not a deliberately broken negative-test build.
+ run(tfm + "-consumer-final", consumer, contains="result=42")
+ runtime_hashes[runtime]["sha256_after"] = digest(corelib)
+ assert runtime_hashes[runtime]["sha256_after"] == original_hash, "Installed runtime changed"
+ assert not (root / "Consumer/bin/Release" / tfm / "System.Private.CoreLib.dll").exists(), "Facade leaked into output"
+finally:
+ (root / "results.json").write_bytes((json.dumps({"sdk": version, "fec_base": current,
+ "upstream_generator": "882b30bf82fbb72f3bee8878ad9fd31f5c93c875", "runtimes": runtime_hashes,
+ "results": results}, indent=2) + "\n").encode("utf-8"))
+print(f"All {len(results)} checks passed; installed runtime hashes unchanged; no runtime DLL copied to consumer outputs")
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/results.json b/BuildScripts/IgnoresAccessChecksToGenerator/results.json
new file mode 100644
index 000000000..0c1c387b2
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/results.json
@@ -0,0 +1,432 @@
+{
+ "sdk": "10.0.301",
+ "fec_base": "734314ede7c5e58845d42345e1cfb4d59591b998",
+ "upstream_generator": "882b30bf82fbb72f3bee8878ad9fd31f5c93c875",
+ "runtimes": {
+ "8.0.28": {
+ "implementation": "C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\8.0.28\\System.Private.CoreLib.dll",
+ "sha256_before": "1326005d3c6c4dfc129c5b0b21138b4053f45294276be22c5beefa478707323a",
+ "sha256_after": "1326005d3c6c4dfc129c5b0b21138b4053f45294276be22c5beefa478707323a"
+ },
+ "10.0.9": {
+ "implementation": "C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\10.0.9\\System.Private.CoreLib.dll",
+ "sha256_before": "a0b776c3065a323080ed88c2fcce2b3711741346931618428bd1e1ad9144c931",
+ "sha256_after": "a0b776c3065a323080ed88c2fcce2b3711741346931618428bd1e1ad9144c931"
+ }
+ },
+ "results": [
+ {
+ "label": "generator-build",
+ "arguments": [
+ "dotnet",
+ "build",
+ "Generator/Generator.csproj",
+ "-c",
+ "Release"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net8.0-generate-private",
+ "arguments": [
+ "dotnet",
+ "C:\\Data\\Git\\Github\\FastExpressionCompiler\\BuildScripts\\IgnoresAccessChecksToGenerator\\Generator\\bin\\Release\\net10.0\\Generator.dll",
+ "C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\8.0.28\\System.Private.CoreLib.dll",
+ "C:\\Data\\Git\\Github\\FastExpressionCompiler\\BuildScripts\\IgnoresAccessChecksToGenerator\\artifacts\\net8.0\\private\\System.Private.CoreLib.dll",
+ "--private"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net8.0-verify-private",
+ "arguments": [
+ "dotnet",
+ "C:\\Data\\Git\\Github\\FastExpressionCompiler\\BuildScripts\\IgnoresAccessChecksToGenerator\\Generator\\bin\\Release\\net10.0\\Generator.dll",
+ "--verify",
+ "C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\8.0.28\\System.Private.CoreLib.dll",
+ "C:\\Data\\Git\\Github\\FastExpressionCompiler\\BuildScripts\\IgnoresAccessChecksToGenerator\\artifacts\\net8.0\\private\\System.Private.CoreLib.dll",
+ "--private"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net8.0-generate-internal",
+ "arguments": [
+ "dotnet",
+ "C:\\Data\\Git\\Github\\FastExpressionCompiler\\BuildScripts\\IgnoresAccessChecksToGenerator\\Generator\\bin\\Release\\net10.0\\Generator.dll",
+ "C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\8.0.28\\System.Private.CoreLib.dll",
+ "C:\\Data\\Git\\Github\\FastExpressionCompiler\\BuildScripts\\IgnoresAccessChecksToGenerator\\artifacts\\net8.0\\internal\\System.Private.CoreLib.dll"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net8.0-consumer",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "Consumer/Consumer.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net8.0",
+ "-p:RuntimeFrameworkVersion=8.0.28"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net8.0-task",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "Consumer/Consumer.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net8.0",
+ "-p:RuntimeFrameworkVersion=8.0.28",
+ "-p:UseGeneratorTask=true",
+ "-p:RuntimeCoreLib=C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\8.0.28\\System.Private.CoreLib.dll"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net8.0-private-disabled",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "Consumer/Consumer.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net8.0",
+ "-p:RuntimeFrameworkVersion=8.0.28",
+ "-p:FacadeDirectory=C:\\Data\\Git\\Github\\FastExpressionCompiler\\BuildScripts\\IgnoresAccessChecksToGenerator\\artifacts\\net8.0\\internal"
+ ],
+ "exit_code": 1,
+ "expected": "failure",
+ "passed": true
+ },
+ {
+ "label": "net8.0-permission-disabled",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "Consumer/Consumer.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net8.0",
+ "-p:RuntimeFrameworkVersion=8.0.28",
+ "-p:GrantPrivateAccess=false"
+ ],
+ "exit_code": 3762504530,
+ "expected": "failure",
+ "passed": true
+ },
+ {
+ "label": "net8.0-missing-member",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "Consumer/Consumer.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net8.0",
+ "-p:RuntimeFrameworkVersion=8.0.28",
+ "-p:MissingMemberProbe=true"
+ ],
+ "exit_code": 1,
+ "expected": "failure",
+ "passed": true
+ },
+ {
+ "label": "net8.0-wrong-signature",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "Consumer/Consumer.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net8.0",
+ "-p:RuntimeFrameworkVersion=8.0.28",
+ "-p:WrongSignatureProbe=true"
+ ],
+ "exit_code": 1,
+ "expected": "failure",
+ "passed": true
+ },
+ {
+ "label": "net8.0-standard",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "FecSmoke/FecSmoke.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net8.0",
+ "-p:RuntimeFrameworkVersion=8.0.28",
+ "-p:Light=false"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net8.0-light",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "FecSmoke/FecSmoke.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net8.0",
+ "-p:RuntimeFrameworkVersion=8.0.28",
+ "-p:Light=true"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net8.0-consumer-final",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "Consumer/Consumer.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net8.0",
+ "-p:RuntimeFrameworkVersion=8.0.28"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net10.0-generate-private",
+ "arguments": [
+ "dotnet",
+ "C:\\Data\\Git\\Github\\FastExpressionCompiler\\BuildScripts\\IgnoresAccessChecksToGenerator\\Generator\\bin\\Release\\net10.0\\Generator.dll",
+ "C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\10.0.9\\System.Private.CoreLib.dll",
+ "C:\\Data\\Git\\Github\\FastExpressionCompiler\\BuildScripts\\IgnoresAccessChecksToGenerator\\artifacts\\net10.0\\private\\System.Private.CoreLib.dll",
+ "--private"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net10.0-verify-private",
+ "arguments": [
+ "dotnet",
+ "C:\\Data\\Git\\Github\\FastExpressionCompiler\\BuildScripts\\IgnoresAccessChecksToGenerator\\Generator\\bin\\Release\\net10.0\\Generator.dll",
+ "--verify",
+ "C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\10.0.9\\System.Private.CoreLib.dll",
+ "C:\\Data\\Git\\Github\\FastExpressionCompiler\\BuildScripts\\IgnoresAccessChecksToGenerator\\artifacts\\net10.0\\private\\System.Private.CoreLib.dll",
+ "--private"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net10.0-generate-internal",
+ "arguments": [
+ "dotnet",
+ "C:\\Data\\Git\\Github\\FastExpressionCompiler\\BuildScripts\\IgnoresAccessChecksToGenerator\\Generator\\bin\\Release\\net10.0\\Generator.dll",
+ "C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\10.0.9\\System.Private.CoreLib.dll",
+ "C:\\Data\\Git\\Github\\FastExpressionCompiler\\BuildScripts\\IgnoresAccessChecksToGenerator\\artifacts\\net10.0\\internal\\System.Private.CoreLib.dll"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net10.0-consumer",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "Consumer/Consumer.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net10.0",
+ "-p:RuntimeFrameworkVersion=10.0.9"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net10.0-task",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "Consumer/Consumer.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net10.0",
+ "-p:RuntimeFrameworkVersion=10.0.9",
+ "-p:UseGeneratorTask=true",
+ "-p:RuntimeCoreLib=C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\10.0.9\\System.Private.CoreLib.dll"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net10.0-private-disabled",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "Consumer/Consumer.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net10.0",
+ "-p:RuntimeFrameworkVersion=10.0.9",
+ "-p:FacadeDirectory=C:\\Data\\Git\\Github\\FastExpressionCompiler\\BuildScripts\\IgnoresAccessChecksToGenerator\\artifacts\\net10.0\\internal"
+ ],
+ "exit_code": 1,
+ "expected": "failure",
+ "passed": true
+ },
+ {
+ "label": "net10.0-permission-disabled",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "Consumer/Consumer.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net10.0",
+ "-p:RuntimeFrameworkVersion=10.0.9",
+ "-p:GrantPrivateAccess=false"
+ ],
+ "exit_code": 3762504530,
+ "expected": "failure",
+ "passed": true
+ },
+ {
+ "label": "net10.0-missing-member",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "Consumer/Consumer.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net10.0",
+ "-p:RuntimeFrameworkVersion=10.0.9",
+ "-p:MissingMemberProbe=true"
+ ],
+ "exit_code": 1,
+ "expected": "failure",
+ "passed": true
+ },
+ {
+ "label": "net10.0-wrong-signature",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "Consumer/Consumer.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net10.0",
+ "-p:RuntimeFrameworkVersion=10.0.9",
+ "-p:WrongSignatureProbe=true"
+ ],
+ "exit_code": 1,
+ "expected": "failure",
+ "passed": true
+ },
+ {
+ "label": "net10.0-standard",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "FecSmoke/FecSmoke.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net10.0",
+ "-p:RuntimeFrameworkVersion=10.0.9",
+ "-p:Light=false"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net10.0-light",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "FecSmoke/FecSmoke.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net10.0",
+ "-p:RuntimeFrameworkVersion=10.0.9",
+ "-p:Light=true"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ },
+ {
+ "label": "net10.0-consumer-final",
+ "arguments": [
+ "dotnet",
+ "run",
+ "--project",
+ "Consumer/Consumer.csproj",
+ "-c",
+ "Release",
+ "-f",
+ "net10.0",
+ "-p:RuntimeFrameworkVersion=10.0.9"
+ ],
+ "exit_code": 0,
+ "expected": "success",
+ "passed": true
+ }
+ ]
+}
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/upstream/IgnoresAccessChecksToGenerator.targets b/BuildScripts/IgnoresAccessChecksToGenerator/upstream/IgnoresAccessChecksToGenerator.targets
new file mode 100644
index 000000000..48bb79276
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/upstream/IgnoresAccessChecksToGenerator.targets
@@ -0,0 +1,62 @@
+
+
+
+ <_IACTG_TargetFramework Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0
+ <_IACTG_TargetFramework Condition="'$(MSBuildRuntimeType)' != 'Core'">net462
+ <_IACTG_TaskAssembly>$(MSBuildThisFileDirectory)../tools/$(_IACTG_TargetFramework)/IgnoresAccessChecksToGenerator.Tasks.dll
+ <_IACTG_IntermediatePath>$(IntermediateOutputPath)/IgnoresAccessChecksToGenerator
+ <_IACTG_CodeFilePath>$(_IACTG_IntermediatePath)/IgnoresAccessChecksTo.cs
+ <_IACTG_ManifestFilePath>$(_IACTG_IntermediatePath)/IgnoresAccessChecksToGenerator.manifest
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_IACTG_ReferencePathJoin Include="@(ReferencePath)" IgnoresAccessChecksTo="%(IgnoresAccessChecksTo.Identity)" />
+ <_IACTG_InputReferencePath Include="@(_IACTG_ReferencePathJoin)" Condition=" '%(Filename)' == '%(IgnoresAccessChecksTo)' " />
+ <_IACTG_ReferencePathJoin Remove="@(_IACTG_ReferencePathJoin)" />
+ <_IACTG_OutputReferencePath Include="@(_IACTG_InputReferencePath->'$(_IACTG_IntermediatePath)/%(Filename)%(Extension)')" OriginalItemSpec="%(Identity)" />
+
+
+ <_IACTG_Manifest Include="@(IgnoresAccessChecksToExcludeTypeName->'%(Identity)^%(Assembly)')" />
+ <_IACTG_Manifest Include="@(_IACTG_InputReferencePath)" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_IACTG_Directory Include="$(_IACTG_IntermediatePath)" />
+
+
+
+
+
diff --git a/BuildScripts/IgnoresAccessChecksToGenerator/upstream/PublicizeInternals.cs b/BuildScripts/IgnoresAccessChecksToGenerator/upstream/PublicizeInternals.cs
new file mode 100644
index 000000000..34f799568
--- /dev/null
+++ b/BuildScripts/IgnoresAccessChecksToGenerator/upstream/PublicizeInternals.cs
@@ -0,0 +1,273 @@
+using System.Runtime.CompilerServices;
+using Microsoft.Build.Framework;
+using Mono.Cecil;
+
+[assembly: InternalsVisibleTo("IgnoresAccessChecksToGenerator.Tasks.Test")]
+
+#nullable disable
+
+namespace IgnoresAccessChecksToGenerator.Tasks
+{
+ public class PublicizeInternals : Microsoft.Build.Utilities.Task
+ {
+ private readonly AssemblyResolver _resolver = new AssemblyResolver();
+
+ [Required]
+ public ITaskItem[] SourceReferences { get; set; }
+
+ [Required]
+ public ITaskItem[] AssemblyNames { get; set; }
+
+ [Required]
+ public string IntermediateOutputPath { get; set; }
+
+ [Required]
+ public string GeneratedCodeFilePath { get; set; }
+
+ public ITaskItem[] ExcludeTypeNames { get; set; }
+
+ public bool OmitAttributeDefinition { get; set; }
+
+ public override bool Execute()
+ {
+ if (SourceReferences == null) throw new ArgumentNullException(nameof(SourceReferences));
+
+ var assemblyNames = new HashSet(AssemblyNames.Select(t => t.ItemSpec), StringComparer.OrdinalIgnoreCase);
+
+ if (assemblyNames.Count == 0)
+ {
+ return true;
+ }
+
+ var excludedTypeNames = new TypeExclusions();
+ if (ExcludeTypeNames != null)
+ {
+ foreach (var item in ExcludeTypeNames)
+ {
+ excludedTypeNames.Add(item.ItemSpec, item.GetMetadata("Assembly"));
+ }
+ }
+
+ var targetPath = IntermediateOutputPath;
+ Directory.CreateDirectory(targetPath);
+
+ GenerateAttributes(targetPath, assemblyNames, OmitAttributeDefinition);
+
+ foreach (var assemblyPath in SourceReferences
+ .Select(a => Path.GetDirectoryName(GetFullFilePath(targetPath, a.ItemSpec))))
+ {
+ _resolver.AddSearchDirectory(assemblyPath);
+ }
+
+ foreach (var assembly in SourceReferences)
+ {
+ var assemblyPath = GetFullFilePath(targetPath, assembly.ItemSpec);
+ var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
+ if (assemblyNames.Contains(assemblyName))
+ {
+ var targetAssemblyPath = Path.Combine(targetPath, Path.GetFileName(assemblyPath));
+
+ CreatePublicAssembly(assemblyName, assemblyPath, targetAssemblyPath, excludedTypeNames);
+ Log.LogMessageFromText("Created publicized assembly at " + targetAssemblyPath, MessageImportance.Normal);
+ }
+ }
+
+ return true;
+ }
+
+ private void GenerateAttributes(string path, IEnumerable assemblyNames, bool omitAttributeDefinition)
+ {
+ var content = BuildAttributeFileContent(assemblyNames, omitAttributeDefinition);
+ File.WriteAllText(GeneratedCodeFilePath, content, System.Text.Encoding.UTF8);
+
+ Log.LogMessageFromText("Generated IgnoresAccessChecksTo attributes", MessageImportance.Low);
+ }
+
+ internal static string BuildAttributeFileContent(IEnumerable assemblyNames, bool omitAttributeDefinition)
+ {
+ var attributes = string.Join(Environment.NewLine,
+ assemblyNames.Select(a => $@"[assembly: System.Runtime.CompilerServices.IgnoresAccessChecksTo(""{a}"")]"));
+
+ var attributeDefinition = omitAttributeDefinition ? "" : """
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
+ internal sealed class IgnoresAccessChecksToAttribute : Attribute
+ {
+ public IgnoresAccessChecksToAttribute(string assemblyName)
+ {
+ }
+ }
+}
+""";
+
+ var content = $$"""
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+{{attributes}}
+{{attributeDefinition}}
+""";
+ return content.TrimEnd() + Environment.NewLine;
+ }
+
+ private void CreatePublicAssembly(string assemblyName, string source, string target, TypeExclusions excludedTypeNames)
+ {
+ var assembly = AssemblyDefinition.ReadAssembly(source,
+ new ReaderParameters { AssemblyResolver = _resolver });
+
+ foreach (var module in assembly.Modules)
+ {
+ foreach (var type in module.GetTypes().Where(type => !excludedTypeNames.IsExcluded(assemblyName, type.FullName)))
+ {
+ if (!type.IsNested && type.IsNotPublic)
+ {
+ type.IsPublic = true;
+ }
+ else if (type.IsNestedAssembly ||
+ type.IsNestedFamilyOrAssembly ||
+ type.IsNestedFamilyAndAssembly)
+ {
+ type.IsNestedPublic = true;
+ }
+
+ foreach (var field in type.Fields)
+ {
+ if (field.IsAssembly ||
+ field.IsFamilyOrAssembly ||
+ field.IsFamilyAndAssembly)
+ {
+ field.IsPublic = true;
+ }
+ }
+
+ foreach (var method in type.Methods)
+ {
+ if (method.IsAssembly ||
+ method.IsFamilyOrAssembly ||
+ method.IsFamilyAndAssembly)
+ {
+ method.IsPublic = true;
+ }
+ }
+ }
+ }
+
+ assembly.Write(target);
+ // copy XML docs alongside the new assembly
+ var xmlSource = Path.ChangeExtension(source, ".xml");
+ if (File.Exists(xmlSource))
+ {
+ var xmlTarget = Path.ChangeExtension(target, ".xml");
+ File.Copy(xmlSource, xmlTarget, overwrite: true);
+ Log.LogMessageFromText($"Copied XML docs to {xmlTarget}", MessageImportance.Low);
+ }
+ }
+
+ private string GetFullFilePath(string basePath, string path) =>
+ Path.IsPathRooted(path) ? Path.GetFullPath(path) : Path.Combine(basePath, path);
+
+ internal sealed class TypeExclusions
+ {
+ private readonly HashSet _global = new HashSet(StringComparer.OrdinalIgnoreCase);
+ private readonly Dictionary> _perAssembly = new Dictionary>(StringComparer.OrdinalIgnoreCase);
+
+ public void Add(string typeName, string assemblyName)
+ {
+ if (string.IsNullOrEmpty(typeName))
+ {
+ return;
+ }
+
+ if (string.IsNullOrEmpty(assemblyName))
+ {
+ _global.Add(typeName);
+ }
+ else
+ {
+ if (!_perAssembly.TryGetValue(assemblyName, out var set))
+ {
+ set = new HashSet(StringComparer.OrdinalIgnoreCase);
+ _perAssembly[assemblyName] = set;
+ }
+
+ set.Add(typeName);
+ }
+ }
+
+ public bool IsExcluded(string assemblyName, string typeFullName)
+ {
+ return _global.Contains(typeFullName) ||
+ (_perAssembly.TryGetValue(assemblyName, out var set) && set.Contains(typeFullName));
+ }
+ }
+
+ private class AssemblyResolver : IAssemblyResolver
+ {
+ private readonly HashSet _directories = new HashSet(StringComparer.OrdinalIgnoreCase);
+
+ public void AddSearchDirectory(string directory)
+ {
+ _directories.Add(directory);
+ }
+
+ public AssemblyDefinition Resolve(AssemblyNameReference name)
+ {
+ return Resolve(name, new ReaderParameters());
+ }
+
+ public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters)
+ {
+ var assembly = SearchDirectory(name, _directories, parameters);
+ if (assembly != null)
+ {
+ return assembly;
+ }
+
+ throw new AssemblyResolutionException(name);
+ }
+
+ public void Dispose()
+ {
+ }
+
+ private AssemblyDefinition SearchDirectory(AssemblyNameReference name, IEnumerable directories, ReaderParameters parameters)
+ {
+ var extensions = name.IsWindowsRuntime ? new[] { ".winmd", ".dll" } : new[] { ".exe", ".dll" };
+ foreach (var directory in directories)
+ {
+ foreach (var extension in extensions)
+ {
+ var file = Path.Combine(directory, name.Name + extension);
+ if (!File.Exists(file))
+ continue;
+ try
+ {
+ return GetAssembly(file, parameters);
+ }
+ catch (BadImageFormatException)
+ {
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private AssemblyDefinition GetAssembly(string file, ReaderParameters parameters)
+ {
+ if (parameters.AssemblyResolver == null)
+ parameters.AssemblyResolver = this;
+
+ return ModuleDefinition.ReadModule(file, parameters).Assembly;
+ }
+ }
+ }
+}
\ No newline at end of file