Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,13 @@ static bool HasImplicitValueWhenNotSpecified(ParameterInfo paramInfo)
return paramInfo.HasDefaultValue
// parameters of type IConfiguration are implicitly populated with provided Configuration
|| paramInfo.ParameterType == typeof(IConfiguration)
|| paramInfo.IsDefined(typeof(ParamArrayAttribute), false)
|| paramInfo.CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.ParamCollectionAttribute");
|| IsParamCollection(paramInfo);
}

static bool IsParamCollection(ParameterInfo paramInfo)
{
return paramInfo.IsDefined(typeof(ParamArrayAttribute), false)
|| paramInfo.CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.ParamCollectionAttribute");
}

internal object? GetImplicitValueForNotSpecifiedKey(ParameterInfo parameter, MethodInfo methodToInvoke)
Expand All @@ -431,9 +436,40 @@ static bool HasImplicitValueWhenNotSpecified(ParameterInfo paramInfo)
$"This is not supported when only a `IConfigSection` has been provided. (method '{methodToInvoke}')");
}

if (parameter.IsDefined(typeof(ParamArrayAttribute), false) && parameter.ParameterType.GetElementType() is { } elementType)
if (IsParamCollection(parameter))
{
return Array.CreateInstance(elementType, 0);
var paramType = parameter.ParameterType;

bool isByRefLike = paramType.CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.IsByRefLikeAttribute");
if (isByRefLike)
{
return parameter.HasDefaultValue ? parameter.DefaultValue : null;
}

if (paramType.GetElementType() is { } elementType)
{
return Array.CreateInstance(elementType, 0);
}

if (paramType.IsGenericType && paramType.IsInterface)
{
var genericTypeArg = paramType.GetGenericArguments()[0];
return Array.CreateInstance(genericTypeArg, 0);
}

if (paramType.IsGenericType && !paramType.IsAbstract)
{
try
{
return Activator.CreateInstance(paramType);
}
catch(Exception ex)
{
// Activator.CreateInstance is unlikely to succeed for collections lacking a parameterless constructor,
// or those relying on the [CollectionBuilder] attribute for initialization.
SelfLog.WriteLine($"Unable to create an implicit instance of the params collection type `{paramType}` for parameter `{parameter.Name}` on method `{methodToInvoke.Name}`: {ex}");
}
}
}

return parameter.HasDefaultValue ? parameter.DefaultValue : null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System.Reflection;
using Microsoft.Extensions.Configuration;
using Serilog.Events;
using Serilog.Formatting;
using Serilog.Settings.Configuration.Assemblies;
using Serilog.Settings.Configuration.Tests.Support;
using System.Reflection;
using static Serilog.Settings.Configuration.Tests.DummyLoggerConfigurationExtensions;
using static Serilog.Settings.Configuration.Tests.Support.ConfigurationReaderTestHelpers;

namespace Serilog.Settings.Configuration.Tests;
Expand Down Expand Up @@ -373,8 +374,26 @@ public void ParamsEnumerableParameter_GracefullyReturnsDefaultValue()
var param = method.GetParameters().Last(); // params IEnumerable<string>

var result = reader.GetImplicitValueForNotSpecifiedKey(param, method);
var array = Assert.IsType<string[]>(result);
Assert.Empty(array);
}

Assert.Null(result);
[Fact]
public void ParamsListParameter_ReturnsEmptyList()
{
var reader = new ConfigurationReader(
JsonStringConfigSource.LoadSection("{}", "Serilog"),
AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies),
new ConfigurationReaderOptions());

// Assuming you have a DummyParamsList method in your TestDummies
var method = typeof(DummyLoggerConfigurationExtensions).GetMethod("DummyParamsList")!;
var param = method.GetParameters().Last(); // params List<string>

var result = reader.GetImplicitValueForNotSpecifiedKey(param, method);

var list = Assert.IsType<List<string>>(result);
Assert.Empty(list);
}

[Fact]
Expand All @@ -392,4 +411,31 @@ public void ParamsSpanParameter_GracefullyReturnsDefaultValue()

Assert.Null(result);
}

[Fact]
public void UnsupportedCollection_LogsToSelfLogAndReturnsNull()
{
var logs = new List<string>();
Serilog.Debugging.SelfLog.Enable(msg => logs.Add(msg));

try
{
var reader = new ConfigurationReader(
JsonStringConfigSource.LoadSection("{}", "Serilog"),
AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies),
new ConfigurationReaderOptions());

var method = typeof(BrokenLoggerConfigurationExtensions).GetMethod("DummyBrokenCollection")!;
var param = method.GetParameters().Last();

var result = reader.GetImplicitValueForNotSpecifiedKey(param, method);

Assert.NotEmpty(logs);
Assert.Contains(logs, l => l.Contains("Unable to create an implicit instance"));
}
finally
{
Serilog.Debugging.SelfLog.Disable();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,37 @@ public static LoggerConfiguration DummyParamsEnumerable(
return loggerSinkConfiguration.Sink(new DummyParamsSink(values.ToArray()));
}

public static LoggerConfiguration DummyParamsList(
this LoggerSinkConfiguration loggerSinkConfiguration,
params System.Collections.Generic.List<string> list)
{
return loggerSinkConfiguration.Sink(new DummyParamsSink(list.ToArray()));
}

public static LoggerConfiguration DummyParamsSpan(
this LoggerSinkConfiguration loggerSinkConfiguration,
params ReadOnlySpan<string> values)
{
return loggerSinkConfiguration.Sink(new DummyParamsSink(values.ToArray()));
}
}

public static class BrokenLoggerConfigurationExtensions
{
public static LoggerConfiguration DummyBrokenCollection(
this LoggerSinkConfiguration loggerSinkConfiguration,
params BrokenCollection<string> list)
{
return loggerSinkConfiguration.Sink(new DummyParamsSink());
}
}

public class BrokenCollection<T> : List<T>
{
public BrokenCollection()
{
throw new InvalidOperationException("I am broken by design!");
}
public BrokenCollection(int capacity) : base(capacity) { }
}

Loading