Skip to content

Commit 0bb5ffd

Browse files
committed
Fix #268
1 parent 27d9914 commit 0bb5ffd

File tree

9 files changed

+863
-8
lines changed

9 files changed

+863
-8
lines changed

BitMono.sln

Lines changed: 180 additions & 1 deletion
Large diffs are not rendered by default.

props/SharedTestProps.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<PropertyGroup>
44
<TargetFramework>net10.0</TargetFramework>
55
<LangVersion>preview</LangVersion>
6+
<Nullable>enable</Nullable>
67
</PropertyGroup>
78

89
<ItemGroup>

src/BitMono.CLI/Modules/OptionsObfuscationNeedsFactory.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ public OptionsObfuscationNeedsFactory(string[] args)
3535
{
3636
obfuscationSettings = SettingsLoader.Load<ObfuscationSettings>(KnownConfigNames.Obfuscation);
3737
}
38+
else
39+
{
40+
// Fallback to application base directory
41+
var baseObfuscationFile = Path.Combine(AppContext.BaseDirectory, KnownConfigNames.Obfuscation);
42+
if (File.Exists(baseObfuscationFile))
43+
{
44+
obfuscationSettings = SettingsLoader.Load<ObfuscationSettings>(baseObfuscationFile);
45+
}
46+
}
3847

3948
if (obfuscationSettings != null && options.NoWatermark)
4049
{

src/BitMono.Host/Modules/BitMonoModule.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@ public BitMonoModule(
5151
[SuppressMessage("ReSharper", "IdentifierTypo")]
5252
public void Load(Container container)
5353
{
54-
_configureContainer?.Invoke(container);
55-
5654
var loggerConfiguration = new LoggerConfiguration
5755
{
5856
WriteToConsole = true,
@@ -80,15 +78,19 @@ public void Load(Container container)
8078
container.Register<IEngineContextAccessor, EngineContextAccessor>().AsSingleton();
8179
container.Register<ProtectionContextFactory>().AsSingleton();
8280
container.Register<ProtectionParametersFactory>().AsSingleton();
83-
container.Register<RandomNext>(() => RandomService.RandomNext).AsSingleton();
81+
container.Register<RandomNext>(new RandomNext(RandomService.RandomNext)).AsSingleton();
8482
container.Register<Renamer>().AsSingleton();
8583

8684
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
8785

88-
container.RegisterAssemblyTypes(assemblies, typeof(IMemberResolver));
86+
container.RegisterCollection<IMemberResolver>(assemblies);
8987

9088
container.RegisterClosedTypesOf(assemblies, typeof(ICriticalAnalyzer<>));
9189

9290
container.RegisterClosedTypesOf(assemblies, typeof(IAttributeResolver<>));
91+
92+
// Call configureContainer AFTER all core dependencies are registered
93+
// This allows AddProtections to resolve protection dependencies correctly
94+
_configureContainer?.Invoke(container);
9395
}
9496
}

src/BitMono.Shared/DependencyInjection/ContainerExtensions.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public static Container RegisterAssemblyTypes(
102102

103103
foreach (var type in types)
104104
{
105-
if (!type.IsClass || type.IsAbstract || !type.IsPublic)
105+
if (!type.IsClass || type.IsAbstract || !type.IsPublic || type.IsGenericTypeDefinition)
106106
continue;
107107

108108
if (filter != null && !filter(type))
@@ -149,7 +149,7 @@ public static Container RegisterClosedTypesOf(
149149

150150
foreach (var type in types)
151151
{
152-
if (!type.IsClass || type.IsAbstract || !type.IsPublic)
152+
if (!type.IsClass || type.IsAbstract || !type.IsPublic || type.IsGenericTypeDefinition)
153153
continue;
154154

155155
if (filter != null && !filter(type))
@@ -159,9 +159,17 @@ public static Container RegisterClosedTypesOf(
159159
var implementedInterfaces = type.GetInterfaces()
160160
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == openGenericType);
161161

162+
var hasMatch = false;
162163
foreach (var closedInterface in implementedInterfaces)
163164
{
164165
container.Register(closedInterface, type).AsSingleton();
166+
hasMatch = true;
167+
}
168+
169+
// Also register the concrete type itself so it can be resolved directly
170+
if (hasMatch && !container.IsRegistered(type))
171+
{
172+
container.Register(type, type).AsSingleton();
165173
}
166174
}
167175
}
@@ -198,7 +206,7 @@ public static Container RegisterCollection<T>(
198206

199207
foreach (var type in types)
200208
{
201-
if (!type.IsClass || type.IsAbstract || !type.IsPublic)
209+
if (!type.IsClass || type.IsAbstract || !type.IsPublic || type.IsGenericTypeDefinition)
202210
continue;
203211

204212
if (filter != null && !filter(type))
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<Import Project="$(MSBuildThisFileDirectory)..\..\props\SharedTestProps.props"/>
4+
5+
<ItemGroup>
6+
<ProjectReference Include="..\..\src\BitMono.Shared\BitMono.Shared.csproj" />
7+
</ItemGroup>
8+
9+
</Project>
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
using System.Reflection;
2+
3+
namespace BitMono.Shared.Tests;
4+
5+
public interface ITestResolver { }
6+
7+
public class ConcreteTestResolver : ITestResolver { }
8+
9+
public class AnotherTestResolver : ITestResolver { }
10+
11+
public interface IGenericTestResolver<T> { }
12+
13+
public class GenericTestResolverBase<T> : IGenericTestResolver<T> { }
14+
15+
public class StringTestResolver : GenericTestResolverBase<string> { }
16+
17+
public class IntTestResolver : GenericTestResolverBase<int> { }
18+
19+
public interface ITestAnalyzer<T> { }
20+
21+
public abstract class TestAnalyzerBase<T> : ITestAnalyzer<T> { }
22+
23+
public class StringTestAnalyzer : TestAnalyzerBase<string> { }
24+
25+
public class IntTestAnalyzer : ITestAnalyzer<int> { }
26+
27+
/// <summary>
28+
/// Simulates the AttributeResolver&lt;TModel&gt; scenario from
29+
/// https://github.com/sunnamed434/BitMono/issues/268
30+
/// </summary>
31+
public class OpenGenericTestImplementation<T> : IGenericTestResolver<T> { }
32+
33+
public class ContainerExtensionsTests
34+
{
35+
[Fact]
36+
public void RegisterAssemblyTypes_ShouldRegisterImplementations()
37+
{
38+
using var container = new Container();
39+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
40+
41+
container.RegisterAssemblyTypes(assemblies, typeof(ITestResolver));
42+
43+
container.IsRegistered<ITestResolver>().Should().BeTrue();
44+
}
45+
46+
[Fact]
47+
public void RegisterAssemblyTypes_ShouldSkipAbstractClasses()
48+
{
49+
using var container = new Container();
50+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
51+
52+
container.RegisterAssemblyTypes(assemblies, typeof(ITestAnalyzer<string>));
53+
54+
// Should not throw - abstract TestAnalyzerBase should be skipped
55+
var service = container.GetService(typeof(ITestAnalyzer<string>));
56+
service.Should().NotBeNull();
57+
}
58+
59+
[Fact]
60+
public void RegisterAssemblyTypes_ShouldSkipGenericTypeDefinitions()
61+
{
62+
using var container = new Container();
63+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
64+
65+
// This should not throw - generic type definitions should be skipped
66+
var act = () => container.RegisterAssemblyTypes(assemblies, typeof(IGenericTestResolver<string>));
67+
68+
act.Should().NotThrow();
69+
}
70+
71+
[Fact]
72+
public void RegisterAssemblyTypes_WithFilter_ShouldApplyFilter()
73+
{
74+
using var container = new Container();
75+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
76+
77+
container.RegisterAssemblyTypes(assemblies, typeof(ITestResolver),
78+
type => type.Name.StartsWith("Concrete"));
79+
80+
var service = container.GetService(typeof(ITestResolver));
81+
service.Should().BeOfType<ConcreteTestResolver>();
82+
}
83+
84+
[Fact]
85+
public void RegisterClosedTypesOf_ShouldRegisterClosedGenericImplementations()
86+
{
87+
using var container = new Container();
88+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
89+
90+
container.RegisterClosedTypesOf(assemblies, typeof(ITestAnalyzer<>));
91+
92+
container.IsRegistered(typeof(ITestAnalyzer<string>)).Should().BeTrue();
93+
container.IsRegistered(typeof(ITestAnalyzer<int>)).Should().BeTrue();
94+
}
95+
96+
[Fact]
97+
public void RegisterClosedTypesOf_ShouldSkipAbstractClasses()
98+
{
99+
using var container = new Container();
100+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
101+
102+
// TestAnalyzerBase<T> is abstract and should be skipped
103+
var act = () => container.RegisterClosedTypesOf(assemblies, typeof(ITestAnalyzer<>));
104+
105+
act.Should().NotThrow();
106+
}
107+
108+
/// <summary>
109+
/// Regression test for https://github.com/sunnamed434/BitMono/issues/268
110+
/// OpenGenericTestImplementation&lt;T&gt; is a generic type definition and should be skipped.
111+
/// </summary>
112+
[Fact]
113+
public void RegisterClosedTypesOf_ShouldSkipGenericTypeDefinitions()
114+
{
115+
using var container = new Container();
116+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
117+
118+
var act = () => container.RegisterClosedTypesOf(assemblies, typeof(IGenericTestResolver<>));
119+
120+
act.Should().NotThrow();
121+
}
122+
123+
[Fact]
124+
public void RegisterClosedTypesOf_ShouldResolveRegisteredTypes()
125+
{
126+
using var container = new Container();
127+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
128+
129+
container.RegisterClosedTypesOf(assemblies, typeof(ITestAnalyzer<>));
130+
131+
var stringAnalyzer = container.GetService(typeof(ITestAnalyzer<string>));
132+
var intAnalyzer = container.GetService(typeof(ITestAnalyzer<int>));
133+
134+
stringAnalyzer.Should().NotBeNull();
135+
stringAnalyzer.Should().BeOfType<StringTestAnalyzer>();
136+
intAnalyzer.Should().NotBeNull();
137+
intAnalyzer.Should().BeOfType<IntTestAnalyzer>();
138+
}
139+
140+
[Fact]
141+
public void RegisterClosedTypesOf_ShouldThrowForNonGenericType()
142+
{
143+
using var container = new Container();
144+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
145+
146+
var act = () => container.RegisterClosedTypesOf(assemblies, typeof(ITestResolver));
147+
148+
act.Should().Throw<ArgumentException>()
149+
.WithMessage("*open generic type*");
150+
}
151+
152+
[Fact]
153+
public void RegisterClosedTypesOf_WithFilter_ShouldApplyFilter()
154+
{
155+
using var container = new Container();
156+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
157+
158+
container.RegisterClosedTypesOf(assemblies, typeof(ITestAnalyzer<>),
159+
type => type.Name.StartsWith("String"));
160+
161+
container.IsRegistered(typeof(ITestAnalyzer<string>)).Should().BeTrue();
162+
container.IsRegistered(typeof(ITestAnalyzer<int>)).Should().BeFalse();
163+
}
164+
165+
/// <summary>
166+
/// Ensures concrete types are also registered, not just interfaces.
167+
/// This is needed when other services depend on concrete analyzer types (e.g., Renamer depends on NameCriticalAnalyzer).
168+
/// </summary>
169+
[Fact]
170+
public void RegisterClosedTypesOf_ShouldAlsoRegisterConcreteTypes()
171+
{
172+
using var container = new Container();
173+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
174+
175+
container.RegisterClosedTypesOf(assemblies, typeof(ITestAnalyzer<>));
176+
177+
// Should be able to resolve by interface
178+
container.IsRegistered(typeof(ITestAnalyzer<string>)).Should().BeTrue();
179+
180+
// Should ALSO be able to resolve by concrete type (this is the fix)
181+
container.IsRegistered(typeof(StringTestAnalyzer)).Should().BeTrue();
182+
container.IsRegistered(typeof(IntTestAnalyzer)).Should().BeTrue();
183+
}
184+
185+
[Fact]
186+
public void RegisterCollection_ShouldRegisterAllImplementations()
187+
{
188+
using var container = new Container();
189+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
190+
191+
container.RegisterCollection<ITestResolver>(assemblies);
192+
193+
var collection = container.GetService(typeof(ICollection<ITestResolver>)) as ICollection<ITestResolver>;
194+
195+
collection.Should().NotBeNull();
196+
collection!.Count.Should().BeGreaterThan(0);
197+
}
198+
199+
[Fact]
200+
public void RegisterCollection_ShouldContainAllImplementations()
201+
{
202+
using var container = new Container();
203+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
204+
205+
container.RegisterCollection<ITestResolver>(assemblies);
206+
207+
var collection = container.Resolve<ICollection<ITestResolver>>();
208+
209+
collection.Should().Contain(r => r is ConcreteTestResolver);
210+
collection.Should().Contain(r => r is AnotherTestResolver);
211+
}
212+
213+
[Fact]
214+
public void RegisterCollection_ShouldSkipGenericTypeDefinitions()
215+
{
216+
using var container = new Container();
217+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
218+
219+
// Should not throw when encountering generic type definitions
220+
var act = () => container.RegisterCollection<ITestResolver>(assemblies);
221+
222+
act.Should().NotThrow();
223+
}
224+
225+
[Fact]
226+
public void RegisterCollection_WithFilter_ShouldApplyFilter()
227+
{
228+
using var container = new Container();
229+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
230+
231+
container.RegisterCollection<ITestResolver>(assemblies,
232+
type => type.Name.StartsWith("Concrete"));
233+
234+
var collection = container.Resolve<ICollection<ITestResolver>>();
235+
236+
collection.Count.Should().Be(1);
237+
collection.Should().AllBeOfType<ConcreteTestResolver>();
238+
}
239+
240+
[Fact]
241+
public void RegisterCollection_ShouldAlsoRegisterIndividualTypes()
242+
{
243+
using var container = new Container();
244+
var assemblies = new[] { typeof(ContainerExtensionsTests).Assembly };
245+
246+
container.RegisterCollection<ITestResolver>(assemblies);
247+
248+
container.IsRegistered<ConcreteTestResolver>().Should().BeTrue();
249+
container.IsRegistered<AnotherTestResolver>().Should().BeTrue();
250+
}
251+
}

0 commit comments

Comments
 (0)