Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions .github/workflows/net-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
3.1.x
8.0.x
8.0.x
10.0.x

- name: Restore dependencies
run: dotnet restore
Expand All @@ -30,3 +31,21 @@ jobs:

- name: Test
run: dotnet test --no-build --verbosity normal

aot-smoke:
runs-on: windows-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x

- name: Publish AOT smoke (win-x64)
run: dotnet publish tests/NetArchTest.AotSmoke -c Release -r win-x64

- name: Run AOT smoke exe
run: tests/NetArchTest.AotSmoke/bin/Release/net10.0/win-x64/publish/NetArchTest.AotSmoke.exe
140 changes: 140 additions & 0 deletions NetArchTest.eNhancedEdition.sln

Large diffs are not rendered by default.

46 changes: 34 additions & 12 deletions sources/NetArchTest/Assemblies/PublicUse/TypeContainer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
using System.Diagnostics;
#if NET10_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
using Mono.Cecil;
using NetArchTest.Rules;

Expand All @@ -23,23 +26,42 @@ internal sealed class TypeContainer : IType
public string SourceFilePath => _sourceFilePath.Value;


#if NET10_0_OR_GREATER
[UnconditionalSuppressMessage("Trim analysis", "IL2026",
Justification = "Lazy<Type> body delegates to ResolveReflectionType (annotated). Tier 1 paths never read ReflectionType.")]
[UnconditionalSuppressMessage("AOT analysis", "IL3050",
Justification = "Lazy<Type> body delegates to ResolveReflectionType (annotated). Tier 1 paths never read ReflectionType.")]
#endif
internal TypeContainer(TypeDefinition monoTypeDefinition, string explanation)
{
_monoTypeDefinition = monoTypeDefinition;
_reflactionType = new Lazy<Type>(() =>
{
try
{
return _monoTypeDefinition.ToType();
}
catch
{
}
return null;
});
_reflactionType = new Lazy<Type>(() => ResolveReflectionType(_monoTypeDefinition));
_sourceFilePath = new Lazy<string>(() => _monoTypeDefinition.GetFilePath());
Explanation = explanation;
}
}


/// <summary>
/// Extracted Lazy initializer body so the trim analyzer can attribute the
/// reflection-into-Mono.Cecil call cleanly. Tier 1 paths never read
/// <see cref="ReflectionType"/>, so this method is unreachable for AOT consumers
/// who only use Tier 1.
/// </summary>
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
private static Type ResolveReflectionType(TypeDefinition monoTypeDefinition)
{
try
{
return monoTypeDefinition.ToType();
}
catch
{
}
return null;
}


public static implicit operator Type(TypeContainer type)
Expand Down
35 changes: 35 additions & 0 deletions sources/NetArchTest/Condition_Dependencies.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using NetArchTest.Functions;
#if NET10_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif

namespace NetArchTest.Rules
{
Expand All @@ -9,6 +12,10 @@ public sealed partial class Condition
/// </summary>
/// <param name="dependencies">The dependencies to match against. These can be namespaces or specific types.</param>
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
public ConditionList HaveDependencyOnAny(params string[] dependencies)
{
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAny(context, inputTypes, dependencies, true));
Expand All @@ -20,6 +27,10 @@ public ConditionList HaveDependencyOnAny(params string[] dependencies)
/// </summary>
/// <param name="dependencies">The dependencies to match against. These can be namespaces or specific types.</param>
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
public ConditionList HaveDependencyOnAll(params string[] dependencies)
{
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAll(context, inputTypes, dependencies, true));
Expand All @@ -31,6 +42,10 @@ public ConditionList HaveDependencyOnAll(params string[] dependencies)
/// </summary>
/// <param name="dependencies">The dependencies to match against. These can be namespaces or specific types.</param>
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
public ConditionList OnlyHaveDependencyOn(params string[] dependencies)
{
AddFunctionCall((context, inputTypes) => FunctionDelegates.OnlyHaveDependenciesOnAnyOrNone(context, inputTypes, dependencies, true));
Expand All @@ -42,6 +57,10 @@ public ConditionList OnlyHaveDependencyOn(params string[] dependencies)
/// </summary>
/// <param name="dependencies">The dependencies to match against. These can be namespaces or specific types.</param>
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
public ConditionList NotHaveDependencyOnAny(params string[] dependencies)
{
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAny(context, inputTypes, dependencies, false));
Expand All @@ -53,6 +72,10 @@ public ConditionList NotHaveDependencyOnAny(params string[] dependencies)
/// </summary>
/// <param name="dependencies">The dependencies to match against. These can be namespaces or specific types.</param>
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
public ConditionList NotHaveDependencyOnAll(params string[] dependencies)
{
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveDependencyOnAll(context, inputTypes, dependencies, false));
Expand All @@ -64,6 +87,10 @@ public ConditionList NotHaveDependencyOnAll(params string[] dependencies)
/// </summary>
/// <param name="dependencies">The dependencies to match against. These can be namespaces or specific types.</param>
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
public ConditionList HaveDependencyOtherThan(params string[] dependencies)
{
AddFunctionCall((context, inputTypes) => FunctionDelegates.OnlyHaveDependenciesOnAnyOrNone(context, inputTypes, dependencies, false));
Expand All @@ -76,6 +103,10 @@ public ConditionList HaveDependencyOtherThan(params string[] dependencies)
/// </summary>
/// <param name="users">The types to match against. These can be namespaces or specific types.</param>
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
public ConditionList BeUsedByAny(params string[] users)
{
AddFunctionCall((context, inputTypes) => FunctionDelegates.AreUsedByAny(context, inputTypes, users, true));
Expand All @@ -87,6 +118,10 @@ public ConditionList BeUsedByAny(params string[] users)
/// </summary>
/// <param name="users">The types to match against. These can be namespaces or specific types.</param>
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
public ConditionList NotBeUsedByAny(params string[] users)
{
AddFunctionCall((context, inputTypes) => FunctionDelegates.AreUsedByAny(context, inputTypes, users, false));
Expand Down
32 changes: 31 additions & 1 deletion sources/NetArchTest/Dependencies/DataStructures/NamespaceTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
{
using System;
using System.Collections.Generic;
#if NET10_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
using System.Diagnostics;
using System.Text;
using Mono.Cecil;
#if NET10_0_OR_GREATER
using NetArchTest.Rules;
#endif


/// <summary>
Expand Down Expand Up @@ -114,6 +120,12 @@ public NamespaceTree(IEnumerable<string> fullNames, bool parseNames = false)
/// </summary>
/// <param name="fullName">Can be empty, but not null.</param>
/// <param name="parseNames">if names should be parsed by mono parser</param>
#if NET10_0_OR_GREATER
[UnconditionalSuppressMessage("Trim analysis", "IL2026",
Justification = "ParseTokensRequiringReflection is only invoked when parseNames=true; Tier 1 callers pass false.")]
[UnconditionalSuppressMessage("AOT analysis", "IL3050",
Justification = "ParseTokensRequiringReflection is only invoked when parseNames=true; Tier 1 callers pass false.")]
#endif
private void Add(string fullName, bool parseNames)
{
if (fullName == null)
Expand All @@ -122,7 +134,10 @@ private void Add(string fullName, bool parseNames)
}

var deepestNode = _root;
foreach (var token in TypeParser.Parse(fullName, parseNames))
var tokens = parseNames
? ParseTokensRequiringReflection(fullName)
: new[] { fullName };
foreach (var token in tokens)
{
int subnameEndIndex = -1;
while (subnameEndIndex != token.Length)
Expand All @@ -140,6 +155,21 @@ private void Add(string fullName, bool parseNames)
}
}

/// <summary>
/// Extracted parse path that flows through <see cref="TypeParser"/>'s private reflection
/// into Mono.Cecil internals. Isolated into its own method so the trim analyzer attributes
/// it correctly, and so the <c>parseNames=false</c> branch in <see cref="Add"/> remains
/// trim-/AOT-clean (the analyzer can't follow the conditional otherwise).
/// </summary>
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
private static IEnumerable<string> ParseTokensRequiringReflection(string fullName)
{
return TypeParser.Parse(fullName, parseNames: true);
}

/// <summary> Count of terminated nodes in the tree. </summary>
public int TerminatedNodesCount { get; private set; } = 0;

Expand Down
31 changes: 30 additions & 1 deletion sources/NetArchTest/Dependencies/TypeParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,30 @@
{
using System;
using System.Collections.Generic;
#if NET10_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
using System.Reflection;
using System.Text;
using System.Text;
#if NET10_0_OR_GREATER
using NetArchTest.Rules;
#endif

#if NET10_0_OR_GREATER
// TypeParser uses unbounded reflection against Mono.Cecil internals
// (Mono.Cecil.TypeParser is private to the Cecil assembly). Under
// PublishAot/PublishTrimmed the IL2057/IL2080 warnings on these static
// field initializers are unavoidable. Suppress at the type level — every
// public entry point on the class is itself annotated with
// [RequiresUnreferencedCode] + [RequiresDynamicCode], so callers are
// correctly warned at use time.
[UnconditionalSuppressMessage("Trim analysis", "IL2057",
Justification = "Internal Mono.Cecil reflection; entry points are RequiresUnreferencedCode.")]
[UnconditionalSuppressMessage("Trim analysis", "IL2077",
Justification = "Internal Mono.Cecil reflection; entry points are RequiresUnreferencedCode.")]
[UnconditionalSuppressMessage("Trim analysis", "IL2080",
Justification = "Internal Mono.Cecil reflection; entry points are RequiresUnreferencedCode.")]
#endif
internal static class TypeParser
{
static readonly Type mono_TypeParserType = Type.GetType("Mono.Cecil.TypeParser, " + typeof(Mono.Cecil.TypeReference).Assembly);
Expand All @@ -16,6 +37,10 @@ internal static class TypeParser
static readonly FieldInfo mono_specsField = mono_TypeType.GetField("specs", BindingFlags.Instance | BindingFlags.Public);


#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
public static IEnumerable<string> Parse(string fullName, bool parseNames)
{
if (parseNames == false)
Expand Down Expand Up @@ -87,6 +112,10 @@ private static IEnumerable<string> WalkThroughMonoType(object monoType)



#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
public static string ParseReflectionNameToRuntimeName(string fullName)
{
var monoTypeParser = Activator.CreateInstance(mono_TypeParserType, BindingFlags.Instance | BindingFlags.NonPublic, null, args: new object[] { fullName }, null);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
#if NET10_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
using System.Linq;
using System.Runtime.CompilerServices;
#if NET10_0_OR_GREATER
using NetArchTest.Rules;
#endif

namespace Mono.Cecil
{
Expand Down Expand Up @@ -58,6 +64,10 @@ internal static bool IsAlmostEqualTo(this TypeReference child, TypeDefinition pa
/// </summary>
/// <param name="typeDefinition">The type definition to convert.</param>
/// <returns>The equivalent <see cref="Type"/> object instance.</returns>
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
public static Type ToType(this TypeDefinition typeDefinition)
{
var fullName = typeDefinition.FullName.RuntimeNameToReflectionName();
Expand Down
20 changes: 20 additions & 0 deletions sources/NetArchTest/Functions/FunctionDelegates_Dependencies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@
using System.Linq;
using NetArchTest.Assemblies;
using NetArchTest.Dependencies;
using NetArchTest.Rules;
using NetArchTest.RuleEngine;
#if NET10_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif

namespace NetArchTest.Functions
{
internal static partial class FunctionDelegates
{
/// <summary> Function for finding types that have a dependency on any of the supplied types. </summary>
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
internal static IEnumerable<TypeSpec> HaveDependencyOnAny(FunctionSequenceExecutionContext context, IEnumerable<TypeSpec> input, IEnumerable<string> dependencies, bool condition)
{
// Get the types that contain the dependencies
Expand All @@ -18,6 +26,10 @@ internal static IEnumerable<TypeSpec> HaveDependencyOnAny(FunctionSequenceExecut
}

/// <summary> Function for finding types that have a dependency on all of the supplied types. </summary>
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
internal static IEnumerable<TypeSpec> HaveDependencyOnAll(FunctionSequenceExecutionContext context, IEnumerable<TypeSpec> input, IEnumerable<string> dependencies, bool condition)
{
// Get the types that contain the dependencies
Expand All @@ -28,6 +40,10 @@ internal static IEnumerable<TypeSpec> HaveDependencyOnAll(FunctionSequenceExecut
}

/// <summary> Function for finding types that have a dependency on type other than one of the supplied types.</summary>
#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
internal static IEnumerable<TypeSpec> OnlyHaveDependenciesOnAnyOrNone(FunctionSequenceExecutionContext context, IEnumerable<TypeSpec> input, IEnumerable<string> dependencies, bool condition)
{
var search = new DependencySearch(context.IsFailPathRun, context.UserOptions.SerachForDependencyInFieldConstant, context.DependencyFilter);
Expand All @@ -38,6 +54,10 @@ internal static IEnumerable<TypeSpec> OnlyHaveDependenciesOnAnyOrNone(FunctionSe



#if NET10_0_OR_GREATER
[RequiresUnreferencedCode(NetArchTestAotMessages.DependencySearch)]
[RequiresDynamicCode(NetArchTestAotMessages.DependencySearch)]
#endif
internal static IEnumerable<TypeSpec> AreUsedByAny(FunctionSequenceExecutionContext context, IEnumerable<TypeSpec> input, IEnumerable<string> dependencies, bool condition)
{
var search = new DependencySearch(context.IsFailPathRun, context.UserOptions.SerachForDependencyInFieldConstant, context.DependencyFilter);
Expand Down
Loading