Skip to content

Commit 4f4d3bb

Browse files
committed
fix: handle types without a namespace or assembly in reside-in checks
Function pointer types are created by the DomainResolver with a null Namespace and a null Assembly. They only ever appear as referenced types, so rules on Types() were unaffected, but any namespace or assembly predicate on Types(true) dereferenced the null and threw a NullReferenceException, e.g. Types(true).That().ResideInNamespace("System") on System.Private.CoreLib. Guard ResidesInNamespace(Matching) and ResidesInAssembly(Matching) so a type without a namespace or assembly simply does not reside in one. The corresponding conditions had the same problem in two more places: the assembly-object overloads compared via ruleType.Assembly.Equals(...), and every ResideIn*/NotResideIn* condition builds its failure description eagerly from Namespace.FullName or Assembly.FullName. Compare with the static Equals and describe such types as "does not reside in a namespace" or "does not reside in an assembly". The tests use a new TestAssemblies/FunctionPointerAssembly containing a class with a function pointer field, loaded as its own architecture. Resolves #492 Signed-off-by: Adarsh Choudhary <adarshchoudhary087@gmail.com>
1 parent d47d6a0 commit 4f4d3bb

8 files changed

Lines changed: 228 additions & 20 deletions

File tree

‎ArchUnit.sln‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlicesTestAssembly", "TestA
6767
EndProject
6868
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptimizedAssembly", "TestAssemblies\OptimizedAssembly\OptimizedAssembly.csproj", "{74857E33-09C5-43C7-AA27-E9373DEC1380}"
6969
EndProject
70+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FunctionPointerAssembly", "TestAssemblies\FunctionPointerAssembly\FunctionPointerAssembly.csproj", "{B842CBE7-3508-416A-AF20-6CB8B1C00ED9}"
71+
EndProject
7072
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A780B3D6-C525-4270-ABF1-63BA65EA1593}"
7173
ProjectSection(SolutionItems) = preProject
7274
ArchUnitNET.mtp.slnf = ArchUnitNET.mtp.slnf
@@ -473,6 +475,18 @@ Global
473475
{74857E33-09C5-43C7-AA27-E9373DEC1380}.Release|x64.Build.0 = Release|Any CPU
474476
{74857E33-09C5-43C7-AA27-E9373DEC1380}.Release|x86.ActiveCfg = Release|Any CPU
475477
{74857E33-09C5-43C7-AA27-E9373DEC1380}.Release|x86.Build.0 = Release|Any CPU
478+
{B842CBE7-3508-416A-AF20-6CB8B1C00ED9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
479+
{B842CBE7-3508-416A-AF20-6CB8B1C00ED9}.Debug|Any CPU.Build.0 = Debug|Any CPU
480+
{B842CBE7-3508-416A-AF20-6CB8B1C00ED9}.Debug|x64.ActiveCfg = Debug|Any CPU
481+
{B842CBE7-3508-416A-AF20-6CB8B1C00ED9}.Debug|x64.Build.0 = Debug|Any CPU
482+
{B842CBE7-3508-416A-AF20-6CB8B1C00ED9}.Debug|x86.ActiveCfg = Debug|Any CPU
483+
{B842CBE7-3508-416A-AF20-6CB8B1C00ED9}.Debug|x86.Build.0 = Debug|Any CPU
484+
{B842CBE7-3508-416A-AF20-6CB8B1C00ED9}.Release|Any CPU.ActiveCfg = Release|Any CPU
485+
{B842CBE7-3508-416A-AF20-6CB8B1C00ED9}.Release|Any CPU.Build.0 = Release|Any CPU
486+
{B842CBE7-3508-416A-AF20-6CB8B1C00ED9}.Release|x64.ActiveCfg = Release|Any CPU
487+
{B842CBE7-3508-416A-AF20-6CB8B1C00ED9}.Release|x64.Build.0 = Release|Any CPU
488+
{B842CBE7-3508-416A-AF20-6CB8B1C00ED9}.Release|x86.ActiveCfg = Release|Any CPU
489+
{B842CBE7-3508-416A-AF20-6CB8B1C00ED9}.Release|x86.Build.0 = Release|Any CPU
476490
EndGlobalSection
477491
GlobalSection(SolutionProperties) = preSolution
478492
HideSolutionNode = FALSE
@@ -492,6 +506,7 @@ Global
492506
{2C04EB93-5AE0-474C-B328-145A734A5E2B} = {B1191F18-91CB-4387-B775-A5EB64D3AC30}
493507
{E421FA12-BBB9-4A8B-BD29-8757836079A5} = {B1191F18-91CB-4387-B775-A5EB64D3AC30}
494508
{74857E33-09C5-43C7-AA27-E9373DEC1380} = {B1191F18-91CB-4387-B775-A5EB64D3AC30}
509+
{B842CBE7-3508-416A-AF20-6CB8B1C00ED9} = {B1191F18-91CB-4387-B775-A5EB64D3AC30}
495510
{5B3E7A23-F91B-4506-9879-B8CAD13F4AE0} = {38699834-8620-4254-AB98-C19586AD3952}
496511
{84BF2DBC-C8FA-46CE-9419-1CE4E3C4D175} = {38699834-8620-4254-AB98-C19586AD3952}
497512
EndGlobalSection

‎ArchUnitNET/Domain/Extensions/TypeExtensions.cs‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,22 +241,22 @@ public static Attribute GetAttributeOfType(this IType type, Class attributeClass
241241

242242
public static bool ResidesInNamespace(this IType e, string fullName)
243243
{
244-
return e.Namespace.FullNameEquals(fullName);
244+
return e.Namespace != null && e.Namespace.FullNameEquals(fullName);
245245
}
246246

247247
public static bool ResidesInNamespaceMatching(this IType e, string pattern)
248248
{
249-
return e.Namespace.FullNameMatches(pattern);
249+
return e.Namespace != null && e.Namespace.FullNameMatches(pattern);
250250
}
251251

252252
public static bool ResidesInAssembly(this IType e, string fullName)
253253
{
254-
return e.Assembly.FullNameEquals(fullName);
254+
return e.Assembly != null && e.Assembly.FullNameEquals(fullName);
255255
}
256256

257257
public static bool ResidesInAssemblyMatching(this IType e, string pattern)
258258
{
259-
return e.Assembly.FullNameMatches(pattern);
259+
return e.Assembly != null && e.Assembly.FullNameMatches(pattern);
260260
}
261261

262262
public static bool IsDeclaredAsFieldIn(this IType type, string fullName)

‎ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs‎

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ public static IOrderedCondition<TRuleType> ResideInNamespace(string fullName)
300300
{
301301
return new SimpleCondition<TRuleType>(
302302
type => type.ResidesInNamespace(fullName),
303-
obj => "does reside in " + obj.Namespace.FullName,
303+
obj => ResideInNamespaceFailDescription(obj),
304304
"reside in namespace with full name \"" + fullName + "\""
305305
);
306306
}
@@ -309,7 +309,7 @@ public static IOrderedCondition<TRuleType> ResideInNamespaceMatching(string patt
309309
{
310310
return new SimpleCondition<TRuleType>(
311311
type => type.ResidesInNamespaceMatching(pattern),
312-
obj => "does reside in " + obj.Namespace.FullName,
312+
obj => ResideInNamespaceFailDescription(obj),
313313
"reside in namespace with full name matching \"" + pattern + "\""
314314
);
315315
}
@@ -318,7 +318,7 @@ public static IOrderedCondition<TRuleType> ResideInAssembly(string fullName)
318318
{
319319
return new SimpleCondition<TRuleType>(
320320
type => type.ResidesInAssembly(fullName),
321-
obj => "does reside in " + obj.Assembly.FullName,
321+
obj => ResideInAssemblyFailDescription(obj),
322322
"reside in assembly with full name \"" + fullName + "\""
323323
);
324324
}
@@ -327,7 +327,7 @@ public static IOrderedCondition<TRuleType> ResideInAssemblyMatching(string patte
327327
{
328328
return new SimpleCondition<TRuleType>(
329329
type => type.ResidesInAssemblyMatching(pattern),
330-
obj => "does reside in " + obj.Assembly.FullName,
330+
obj => ResideInAssemblyFailDescription(obj),
331331
"reside in assembly with full name matching \"" + pattern + "\""
332332
);
333333
}
@@ -339,9 +339,9 @@ params System.Reflection.Assembly[] moreAssemblies
339339
{
340340
bool Condition(TRuleType ruleType, Architecture architecture)
341341
{
342-
return ruleType.Assembly.Equals(architecture.GetAssemblyOfAssembly(assembly))
342+
return Equals(ruleType.Assembly, architecture.GetAssemblyOfAssembly(assembly))
343343
|| moreAssemblies.Any(asm =>
344-
ruleType.Assembly.Equals(architecture.GetAssemblyOfAssembly(asm))
344+
Equals(ruleType.Assembly, architecture.GetAssemblyOfAssembly(asm))
345345
);
346346
}
347347

@@ -352,7 +352,7 @@ bool Condition(TRuleType ruleType, Architecture architecture)
352352

353353
return new OrderedArchitectureCondition<TRuleType>(
354354
Condition,
355-
(type, architecture) => "does reside in " + type.Assembly.FullName,
355+
(type, architecture) => ResideInAssemblyFailDescription(type),
356356
description
357357
);
358358
}
@@ -375,7 +375,7 @@ bool Condition(TRuleType ruleType)
375375

376376
return new SimpleCondition<TRuleType>(
377377
Condition,
378-
type => "does reside in " + type.Assembly.FullName,
378+
type => ResideInAssemblyFailDescription(type),
379379
description
380380
);
381381
}
@@ -706,7 +706,7 @@ public static IOrderedCondition<TRuleType> NotResideInNamespace(string fullName)
706706
{
707707
return new SimpleCondition<TRuleType>(
708708
type => !type.ResidesInNamespace(fullName),
709-
obj => "does reside in " + obj.Namespace.FullName,
709+
obj => ResideInNamespaceFailDescription(obj),
710710
"not reside in namespace with full name \"" + fullName + "\""
711711
);
712712
}
@@ -715,7 +715,7 @@ public static IOrderedCondition<TRuleType> NotResideInNamespaceMatching(string p
715715
{
716716
return new SimpleCondition<TRuleType>(
717717
type => !type.ResidesInNamespaceMatching(pattern),
718-
obj => "does reside in " + obj.Namespace.FullName,
718+
obj => ResideInNamespaceFailDescription(obj),
719719
"not reside in namespace with full name matching \"" + pattern + "\""
720720
);
721721
}
@@ -724,7 +724,7 @@ public static IOrderedCondition<TRuleType> NotResideInAssembly(string fullName)
724724
{
725725
return new SimpleCondition<TRuleType>(
726726
type => !type.ResidesInAssembly(fullName),
727-
obj => "does reside in " + obj.Assembly.FullName,
727+
obj => ResideInAssemblyFailDescription(obj),
728728
"not reside in assembly with full name \"" + fullName + "\""
729729
);
730730
}
@@ -733,7 +733,7 @@ public static IOrderedCondition<TRuleType> NotResideInAssemblyMatching(string pa
733733
{
734734
return new SimpleCondition<TRuleType>(
735735
type => !type.ResidesInAssemblyMatching(pattern),
736-
obj => "does reside in " + obj.Assembly.FullName,
736+
obj => ResideInAssemblyFailDescription(obj),
737737
"not reside in assembly with full name matching \"" + pattern + "\""
738738
);
739739
}
@@ -745,9 +745,9 @@ params System.Reflection.Assembly[] moreAssemblies
745745
{
746746
bool Condition(TRuleType ruleType, Architecture architecture)
747747
{
748-
return !ruleType.Assembly.Equals(architecture.GetAssemblyOfAssembly(assembly))
748+
return !Equals(ruleType.Assembly, architecture.GetAssemblyOfAssembly(assembly))
749749
&& !moreAssemblies.Any(asm =>
750-
ruleType.Assembly.Equals(architecture.GetAssemblyOfAssembly(asm))
750+
Equals(ruleType.Assembly, architecture.GetAssemblyOfAssembly(asm))
751751
);
752752
}
753753

@@ -758,7 +758,7 @@ bool Condition(TRuleType ruleType, Architecture architecture)
758758

759759
return new OrderedArchitectureCondition<TRuleType>(
760760
Condition,
761-
(type, architecture) => "does reside in " + type.Assembly.FullName,
761+
(type, architecture) => ResideInAssemblyFailDescription(type),
762762
description
763763
);
764764
}
@@ -781,7 +781,7 @@ bool Condition(TRuleType ruleType)
781781

782782
return new SimpleCondition<TRuleType>(
783783
Condition,
784-
type => "does reside in " + type.Assembly.FullName,
784+
type => ResideInAssemblyFailDescription(type),
785785
description
786786
);
787787
}
@@ -848,5 +848,19 @@ public static RelationCondition<TRuleType, Interface> NotImplementAnyInterfacesT
848848
"not implement any interfaces that",
849849
"does implement any interfaces that"
850850
);
851+
852+
private static string ResideInNamespaceFailDescription(IType type)
853+
{
854+
return type.Namespace == null
855+
? "does not reside in a namespace"
856+
: "does reside in " + type.Namespace.FullName;
857+
}
858+
859+
private static string ResideInAssemblyFailDescription(IType type)
860+
{
861+
return type.Assembly == null
862+
? "does not reside in an assembly"
863+
: "does reside in " + type.Assembly.FullName;
864+
}
851865
}
852866
}

‎ArchUnitNETTests/ArchUnitNETTests.csproj‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
<ProjectReference Include="..\TestAssemblies\ClassAssembly\ClassAssembly.csproj" />
3838
<ProjectReference Include="..\TestAssemblies\SlicesTestAssembly\SlicesTestAssembly.csproj" />
3939
<ProjectReference Include="..\TestAssemblies\OptimizedAssembly\OptimizedAssembly.csproj" />
40+
<ProjectReference Include="..\TestAssemblies\FunctionPointerAssembly\FunctionPointerAssembly.csproj" />
4041
</ItemGroup>
4142
<ItemGroup>
4243
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
using System.Linq;
2+
using ArchUnitNET.Domain;
3+
using ArchUnitNET.Domain.Extensions;
4+
using ArchUnitNET.Fluent.Extensions;
5+
using FunctionPointerNamespace;
6+
using Xunit;
7+
using static ArchUnitNET.Fluent.ArchRuleDefinition;
8+
9+
namespace ArchUnitNETTests.Domain
10+
{
11+
public class FunctionPointerTests
12+
{
13+
private static readonly Architecture Architecture =
14+
StaticTestArchitectures.FunctionPointerArchitecture;
15+
16+
private readonly IType _functionPointer;
17+
18+
public FunctionPointerTests()
19+
{
20+
_functionPointer = Architecture
21+
.GetClassOfType(typeof(ClassWithFunctionPointerField))
22+
.GetFieldMembers()
23+
.Single()
24+
.Type;
25+
}
26+
27+
[Fact]
28+
public void FunctionPointerHasNoNamespaceOrAssembly()
29+
{
30+
Assert.IsType<FunctionPointer>(_functionPointer);
31+
Assert.Contains(_functionPointer, Architecture.ReferencedTypes);
32+
Assert.Null(_functionPointer.Namespace);
33+
Assert.Null(_functionPointer.Assembly);
34+
}
35+
36+
[Fact]
37+
public void NamespaceExtensionsReturnFalseForTypeWithoutNamespace()
38+
{
39+
Assert.False(_functionPointer.ResidesInNamespace("System"));
40+
Assert.False(_functionPointer.ResidesInNamespaceMatching(".*"));
41+
}
42+
43+
[Fact]
44+
public void AssemblyExtensionsReturnFalseForTypeWithoutAssembly()
45+
{
46+
Assert.False(_functionPointer.ResidesInAssembly("System"));
47+
Assert.False(_functionPointer.ResidesInAssemblyMatching(".*"));
48+
}
49+
50+
[Fact]
51+
public void NamespacePredicatesHandleTypesWithoutNamespace()
52+
{
53+
var namespaceName = typeof(ClassWithFunctionPointerField).Namespace;
54+
Assert.DoesNotContain(
55+
_functionPointer,
56+
Types(true).That().ResideInNamespace(namespaceName).GetObjects(Architecture)
57+
);
58+
Assert.DoesNotContain(
59+
_functionPointer,
60+
Types(true).That().ResideInNamespaceMatching(".*").GetObjects(Architecture)
61+
);
62+
Assert.Contains(
63+
_functionPointer,
64+
Types(true).That().DoNotResideInNamespace(namespaceName).GetObjects(Architecture)
65+
);
66+
Assert.Contains(
67+
_functionPointer,
68+
Types(true).That().DoNotResideInNamespaceMatching(".*").GetObjects(Architecture)
69+
);
70+
}
71+
72+
[Fact]
73+
public void AssemblyPredicatesHandleTypesWithoutAssembly()
74+
{
75+
var assembly = typeof(ClassWithFunctionPointerField).Assembly;
76+
Assert.DoesNotContain(
77+
_functionPointer,
78+
Types(true).That().ResideInAssembly(assembly.FullName).GetObjects(Architecture)
79+
);
80+
Assert.DoesNotContain(
81+
_functionPointer,
82+
Types(true).That().ResideInAssemblyMatching(".*").GetObjects(Architecture)
83+
);
84+
Assert.Contains(
85+
_functionPointer,
86+
Types(true).That().DoNotResideInAssembly(assembly.FullName).GetObjects(Architecture)
87+
);
88+
}
89+
90+
[Fact]
91+
public void NamespaceConditionsReportTypesWithoutNamespace()
92+
{
93+
var functionPointers = Types(true).That().Are(_functionPointer);
94+
AssertViolation(
95+
functionPointers.Should().ResideInNamespace("System"),
96+
"does not reside in a namespace"
97+
);
98+
AssertViolation(
99+
functionPointers.Should().ResideInNamespaceMatching(".*"),
100+
"does not reside in a namespace"
101+
);
102+
Assert.True(
103+
functionPointers
104+
.Should()
105+
.NotResideInNamespace("System")
106+
.HasNoViolations(Architecture)
107+
);
108+
}
109+
110+
[Fact]
111+
public void AssemblyConditionsReportTypesWithoutAssembly()
112+
{
113+
var assembly = typeof(ClassWithFunctionPointerField).Assembly;
114+
var functionPointers = Types(true).That().Are(_functionPointer);
115+
AssertViolation(
116+
functionPointers.Should().ResideInAssembly(assembly.FullName),
117+
"does not reside in an assembly"
118+
);
119+
AssertViolation(
120+
functionPointers.Should().ResideInAssemblyMatching(".*"),
121+
"does not reside in an assembly"
122+
);
123+
AssertViolation(
124+
functionPointers.Should().ResideInAssembly(assembly),
125+
"does not reside in an assembly"
126+
);
127+
AssertViolation(
128+
functionPointers
129+
.Should()
130+
.ResideInAssembly(Architecture.GetAssemblyOfAssembly(assembly)),
131+
"does not reside in an assembly"
132+
);
133+
Assert.True(
134+
functionPointers
135+
.Should()
136+
.NotResideInAssembly(assembly)
137+
.HasNoViolations(Architecture)
138+
);
139+
}
140+
141+
private static void AssertViolation(
142+
ArchUnitNET.Fluent.IArchRule rule,
143+
string expectedDescription
144+
)
145+
{
146+
var result = rule.Evaluate(Architecture).Single();
147+
Assert.False(result.Passed);
148+
Assert.Contains(expectedDescription, result.Description);
149+
}
150+
}
151+
}

‎ArchUnitNETTests/StaticTestArchitectures.cs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ public static class StaticTestArchitectures
106106
.LoadAssemblies(typeof(OptimizedAssembly.ClassWithAsyncMethod).Assembly)
107107
.Build();
108108

109+
public static readonly Architecture FunctionPointerArchitecture = new ArchLoader()
110+
.WithoutRuleEvaluationCache()
111+
.WithoutArchitectureCache()
112+
.LoadAssemblies(typeof(FunctionPointerNamespace.ClassWithFunctionPointerField).Assembly)
113+
.Build();
114+
109115
public static readonly Architecture ArchUnitNETTestAssemblyArchitecture = new ArchLoader()
110116
.LoadAssemblies(typeof(Class1).Assembly)
111117
.Build();
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace FunctionPointerNamespace;
2+
3+
// The field's type is a function pointer, which the loader resolves to a referenced type
4+
// without a namespace or an assembly.
5+
public unsafe class ClassWithFunctionPointerField
6+
{
7+
public delegate* <int, void> FunctionPointerField;
8+
}

0 commit comments

Comments
 (0)