From 1f53b81051e6560f63147d0a9f9a3b50d97d1976 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Nov 2023 17:31:07 -0800 Subject: [PATCH 01/16] [release/8.0-staging] Support TimeSpan with RangeAttribute in Options validation source generator (#94857) Co-authored-by: Tarek Mahmoud Sayed --- .../gen/Emitter.cs | 91 +++++++++++-- .../gen/Parser.cs | 41 ++++-- .../gen/SymbolHolder.cs | 1 + .../gen/SymbolLoader.cs | 4 + .../src/Microsoft.Extensions.Options.csproj | 2 +- .../EmitterWithCustomValidator.netcore.g.cs | 8 +- .../EmitterWithCustomValidator.netfx.g.cs | 8 +- ...eneratedAttributesTest.netcore.lang10.g.cs | 81 +++++++++++- ...eneratedAttributesTest.netcore.lang11.g.cs | 81 +++++++++++- .../GeneratedAttributesTest.netfx.lang10.g.cs | 81 +++++++++++- .../GeneratedAttributesTest.netfx.lang11.g.cs | 81 +++++++++++- .../tests/SourceGeneration.Unit.Tests/Main.cs | 7 + .../Baselines/NetCoreApp/Validators.g.cs | 124 ++++++++++++++++-- .../Baselines/NetFX/Validators.g.cs | 120 +++++++++++++++-- .../Generated/OptionsValidationTests.cs | 38 ++++++ .../TestClasses/Models.cs | 20 +++ .../tests/TrimmingTests/ConfigureTests.cs | 7 +- 17 files changed, 713 insertions(+), 82 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs b/src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs index 41609ad4b2010a..1edc4293785287 100644 --- a/src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs +++ b/src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs @@ -374,12 +374,83 @@ public void EmitCompareAttribute(string modifier, string prefix, string classNam """); } - public void EmitRangeAttribute(string modifier, string prefix, string className, string suffix) + public void EmitRangeAttribute(string modifier, string prefix, string className, string suffix, bool emitTimeSpanSupport) { OutGeneratedCodeAttribute(); string qualifiedClassName = $"{prefix}{suffix}_{className}"; + string initializationString = emitTimeSpanSupport ? + """ + if (OperandType == typeof(global::System.TimeSpan)) + { + if (!global::System.TimeSpan.TryParse((string)Minimum, culture, out global::System.TimeSpan timeSpanMinimum) || + !global::System.TimeSpan.TryParse((string)Maximum, culture, out global::System.TimeSpan timeSpanMaximum)) + { + throw new global::System.InvalidOperationException(c_minMaxError); + } + Minimum = timeSpanMinimum; + Maximum = timeSpanMaximum; + } + else + { + Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + } + """ + : + """ + Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + """; + + string convertValue = emitTimeSpanSupport ? + """ + if (OperandType == typeof(global::System.TimeSpan)) + { + if (value is global::System.TimeSpan) + { + convertedValue = value; + } + else if (value is string) + { + if (!global::System.TimeSpan.TryParse((string)value, formatProvider, out global::System.TimeSpan timeSpanValue)) + { + return false; + } + convertedValue = timeSpanValue; + } + else + { + throw new global::System.InvalidOperationException($"A value type {value.GetType()} that is not a TimeSpan or a string has been given. This might indicate a problem with the source generator."); + } + } + else + { + try + { + convertedValue = ConvertValue(value, formatProvider); + } + catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) + { + return false; + } + } + """ + : + """ + try + { + convertedValue = ConvertValue(value, formatProvider); + } + catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) + { + return false; + } + """; + + + OutLn($$""" [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)] {{modifier}} class {{qualifiedClassName}} : {{StaticValidationAttributeType}} @@ -414,19 +485,20 @@ public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum); private bool NeedToConvertMinMax { get; } private bool Initialized { get; set; } + private const string c_minMaxError = "The minimum and maximum values must be set to valid values."; + public override bool IsValid(object? value) { if (!Initialized) { if (Minimum is null || Maximum is null) { - throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + throw new global::System.InvalidOperationException(c_minMaxError); } if (NeedToConvertMinMax) { System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; - Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); - Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); +{{initializationString}} } int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum); if (cmp > 0) @@ -448,14 +520,7 @@ public override bool IsValid(object? value) System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; object? convertedValue; - try - { - convertedValue = ConvertValue(value, formatProvider); - } - catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) - { - return false; - } +{{convertValue}} var min = (global::System.IComparable)Minimum; var max = (global::System.IComparable)Maximum; @@ -574,7 +639,7 @@ private void GenValidationAttributesClasses() } else if (attributeData.Key == _symbolHolder.RangeAttributeSymbol.Name) { - EmitRangeAttribute(_optionsSourceGenContext.ClassModifier, Emitter.StaticAttributeClassNamePrefix, attributeData.Key, _optionsSourceGenContext.Suffix); + EmitRangeAttribute(_optionsSourceGenContext.ClassModifier, Emitter.StaticAttributeClassNamePrefix, attributeData.Key, _optionsSourceGenContext.Suffix, attributeData.Value is not null); } } diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs b/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs index ab2c19de81923a..8a88ec6f2f4e65 100644 --- a/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs +++ b/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs @@ -624,10 +624,21 @@ private void TrackCompareAttributeForSubstitution(AttributeData attribute, IType private void TrackRangeAttributeForSubstitution(AttributeData attribute, ITypeSymbol memberType, ref string attributeFullQualifiedName) { ImmutableArray constructorParameters = attribute.AttributeConstructor?.Parameters ?? ImmutableArray.Empty; - SpecialType argumentSpecialType = SpecialType.None; + ITypeSymbol? argumentType = null; + bool hasTimeSpanType = false; + + ITypeSymbol typeSymbol = memberType; + if (typeSymbol.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) + { + typeSymbol = ((INamedTypeSymbol)typeSymbol).TypeArguments[0]; + } + if (constructorParameters.Length == 2) { - argumentSpecialType = constructorParameters[0].Type.SpecialType; + if (OptionsSourceGenContext.IsConvertibleBasicType(typeSymbol)) + { + argumentType = constructorParameters[0].Type; + } } else if (constructorParameters.Length == 3) { @@ -641,23 +652,25 @@ private void TrackRangeAttributeForSubstitution(AttributeData attribute, ITypeSy } } - if (argumentValue is INamedTypeSymbol namedTypeSymbol && OptionsSourceGenContext.IsConvertibleBasicType(namedTypeSymbol)) + if (argumentValue is INamedTypeSymbol namedTypeSymbol) { - argumentSpecialType = namedTypeSymbol.SpecialType; + // When type is provided as a parameter, it has to match the property type. + if (OptionsSourceGenContext.IsConvertibleBasicType(namedTypeSymbol) && typeSymbol.SpecialType == namedTypeSymbol.SpecialType) + { + argumentType = namedTypeSymbol; + } + else if (SymbolEqualityComparer.Default.Equals(namedTypeSymbol, _symbolHolder.TimeSpanSymbol) && + (SymbolEqualityComparer.Default.Equals(typeSymbol, _symbolHolder.TimeSpanSymbol) || typeSymbol.SpecialType == SpecialType.System_String)) + { + hasTimeSpanType = true; + argumentType = _symbolHolder.TimeSpanSymbol; + } } } - ITypeSymbol typeSymbol = memberType; - if (typeSymbol.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) - { - typeSymbol = ((INamedTypeSymbol)typeSymbol).TypeArguments[0]; - } - - if (argumentSpecialType != SpecialType.None && - OptionsSourceGenContext.IsConvertibleBasicType(typeSymbol) && - (constructorParameters.Length != 3 || typeSymbol.SpecialType == argumentSpecialType)) // When type is provided as a parameter, it has to match the property type. + if (argumentType is not null) { - _optionsSourceGenContext.EnsureTrackingAttribute(attribute.AttributeClass!.Name, createValue: false, out _); + _optionsSourceGenContext.EnsureTrackingAttribute(attribute.AttributeClass!.Name, createValue: hasTimeSpanType, out _); attributeFullQualifiedName = $"{Emitter.StaticGeneratedValidationAttributesClassesNamespace}.{Emitter.StaticAttributeClassNamePrefix}{_optionsSourceGenContext.Suffix}_{attribute.AttributeClass!.Name}"; } } diff --git a/src/libraries/Microsoft.Extensions.Options/gen/SymbolHolder.cs b/src/libraries/Microsoft.Extensions.Options/gen/SymbolHolder.cs index 3447a07d398305..8e7073f8ec2182 100644 --- a/src/libraries/Microsoft.Extensions.Options/gen/SymbolHolder.cs +++ b/src/libraries/Microsoft.Extensions.Options/gen/SymbolHolder.cs @@ -23,6 +23,7 @@ internal sealed record class SymbolHolder( INamedTypeSymbol IValidatableObjectSymbol, INamedTypeSymbol GenericIEnumerableSymbol, INamedTypeSymbol TypeSymbol, + INamedTypeSymbol TimeSpanSymbol, INamedTypeSymbol ValidateObjectMembersAttributeSymbol, INamedTypeSymbol ValidateEnumeratedItemsAttributeSymbol); } diff --git a/src/libraries/Microsoft.Extensions.Options/gen/SymbolLoader.cs b/src/libraries/Microsoft.Extensions.Options/gen/SymbolLoader.cs index ea556228929756..5be4932d8d6c41 100644 --- a/src/libraries/Microsoft.Extensions.Options/gen/SymbolLoader.cs +++ b/src/libraries/Microsoft.Extensions.Options/gen/SymbolLoader.cs @@ -19,6 +19,7 @@ internal static class SymbolLoader internal const string IValidatableObjectType = "System.ComponentModel.DataAnnotations.IValidatableObject"; internal const string IValidateOptionsType = "Microsoft.Extensions.Options.IValidateOptions`1"; internal const string TypeOfType = "System.Type"; + internal const string TimeSpanType = "System.TimeSpan"; internal const string ValidateObjectMembersAttribute = "Microsoft.Extensions.Options.ValidateObjectMembersAttribute"; internal const string ValidateEnumeratedItemsAttribute = "Microsoft.Extensions.Options.ValidateEnumeratedItemsAttribute"; internal const string GenericIEnumerableType = "System.Collections.Generic.IEnumerable`1"; @@ -42,6 +43,7 @@ public static bool TryLoad(Compilation compilation, out SymbolHolder? symbolHold var validateOptionsSymbol = GetSymbol(IValidateOptionsType); var genericIEnumerableSymbol = GetSymbol(GenericIEnumerableType); var typeSymbol = GetSymbol(TypeOfType); + var timeSpanSymbol = GetSymbol(TimeSpanType); var validateObjectMembersAttribute = GetSymbol(ValidateObjectMembersAttribute); var validateEnumeratedItemsAttribute = GetSymbol(ValidateEnumeratedItemsAttribute); var unconditionalSuppressMessageAttributeSymbol = GetSymbol(UnconditionalSuppressMessageAttributeType); @@ -70,6 +72,7 @@ public static bool TryLoad(Compilation compilation, out SymbolHolder? symbolHold validateOptionsSymbol == null || genericIEnumerableSymbol == null || typeSymbol == null || + timeSpanSymbol == null || validateObjectMembersAttribute == null || validateEnumeratedItemsAttribute == null) { @@ -93,6 +96,7 @@ public static bool TryLoad(Compilation compilation, out SymbolHolder? symbolHold ivalidatableObjectSymbol, genericIEnumerableSymbol, typeSymbol, + timeSpanSymbol, validateObjectMembersAttribute, validateEnumeratedItemsAttribute); diff --git a/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj b/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj index abbc5d2329c5c1..e35606898e3914 100644 --- a/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj +++ b/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj @@ -5,7 +5,7 @@ true true true - 1 + 2 Provides a strongly typed way of specifying and accessing settings using dependency injection. diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netcore.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netcore.g.cs index 2c5af12c5b5f24..38bacf966df052 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netcore.g.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netcore.g.cs @@ -99,19 +99,21 @@ public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum); private bool NeedToConvertMinMax { get; } private bool Initialized { get; set; } + private const string c_minMaxError = "The minimum and maximum values must be set to valid values."; + public override bool IsValid(object? value) { if (!Initialized) { if (Minimum is null || Maximum is null) { - throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + throw new global::System.InvalidOperationException(c_minMaxError); } if (NeedToConvertMinMax) { System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; - Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); - Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); } int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum); if (cmp > 0) diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netfx.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netfx.g.cs index 9dc3ded5bd4624..fe77e3e6bd924a 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netfx.g.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/EmitterWithCustomValidator.netfx.g.cs @@ -97,19 +97,21 @@ public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum); private bool NeedToConvertMinMax { get; } private bool Initialized { get; set; } + private const string c_minMaxError = "The minimum and maximum values must be set to valid values."; + public override bool IsValid(object? value) { if (!Initialized) { if (Minimum is null || Maximum is null) { - throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + throw new global::System.InvalidOperationException(c_minMaxError); } if (NeedToConvertMinMax) { System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; - Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); - Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); } int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum); if (cmp > 0) diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netcore.lang10.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netcore.lang10.g.cs index cc9864a2619c45..7cf1fe61e1a94b 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netcore.lang10.g.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netcore.lang10.g.cs @@ -150,6 +150,26 @@ partial class OptionsUsingGeneratedAttributesValidator (builder ??= new()).AddResults(validationResults); } + context.MemberName = "P13"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P13" : $"{name}.P13"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A6); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P13, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P14"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P14" : $"{name}.P14"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A7); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P14, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build(); } } @@ -175,6 +195,16 @@ internal static class __Attributes_2C497155 internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_CompareAttribute A5 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_CompareAttribute( "P5"); + + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_RangeAttribute A6 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_RangeAttribute( + typeof(global::System.TimeSpan), + "00:00:00", + "23:59:59"); + + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_RangeAttribute A7 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_RangeAttribute( + typeof(global::System.TimeSpan), + "01:00:00", + "23:59:59"); } } namespace __OptionValidationStaticInstances @@ -395,19 +425,34 @@ public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum); private bool NeedToConvertMinMax { get; } private bool Initialized { get; set; } + private const string c_minMaxError = "The minimum and maximum values must be set to valid values."; + public override bool IsValid(object? value) { if (!Initialized) { if (Minimum is null || Maximum is null) { - throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + throw new global::System.InvalidOperationException(c_minMaxError); } if (NeedToConvertMinMax) { System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; - Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); - Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + if (OperandType == typeof(global::System.TimeSpan)) + { + if (!global::System.TimeSpan.TryParse((string)Minimum, culture, out global::System.TimeSpan timeSpanMinimum) || + !global::System.TimeSpan.TryParse((string)Maximum, culture, out global::System.TimeSpan timeSpanMaximum)) + { + throw new global::System.InvalidOperationException(c_minMaxError); + } + Minimum = timeSpanMinimum; + Maximum = timeSpanMaximum; + } + else + { + Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + } } int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum); if (cmp > 0) @@ -429,13 +474,35 @@ public override bool IsValid(object? value) System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; object? convertedValue; - try + if (OperandType == typeof(global::System.TimeSpan)) { - convertedValue = ConvertValue(value, formatProvider); + if (value is global::System.TimeSpan) + { + convertedValue = value; + } + else if (value is string) + { + if (!global::System.TimeSpan.TryParse((string)value, formatProvider, out global::System.TimeSpan timeSpanValue)) + { + return false; + } + convertedValue = timeSpanValue; + } + else + { + throw new global::System.InvalidOperationException($"A value type {value.GetType()} that is not a TimeSpan or a string has been given. This might indicate a problem with the source generator."); + } } - catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) + else { - return false; + try + { + convertedValue = ConvertValue(value, formatProvider); + } + catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) + { + return false; + } } var min = (global::System.IComparable)Minimum; diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netcore.lang11.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netcore.lang11.g.cs index 2a33e51b0b6175..f7bba046033420 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netcore.lang11.g.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netcore.lang11.g.cs @@ -150,6 +150,26 @@ partial class OptionsUsingGeneratedAttributesValidator (builder ??= new()).AddResults(validationResults); } + context.MemberName = "P13"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P13" : $"{name}.P13"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A6); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P13, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P14"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P14" : $"{name}.P14"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A7); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P14, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build(); } } @@ -175,6 +195,16 @@ file static class __Attributes internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__CompareAttribute A5 = new __OptionValidationGeneratedAttributes.__SourceGen__CompareAttribute( "P5"); + + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A6 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute( + typeof(global::System.TimeSpan), + "00:00:00", + "23:59:59"); + + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A7 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute( + typeof(global::System.TimeSpan), + "01:00:00", + "23:59:59"); } } namespace __OptionValidationStaticInstances @@ -395,19 +425,34 @@ public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum); private bool NeedToConvertMinMax { get; } private bool Initialized { get; set; } + private const string c_minMaxError = "The minimum and maximum values must be set to valid values."; + public override bool IsValid(object? value) { if (!Initialized) { if (Minimum is null || Maximum is null) { - throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + throw new global::System.InvalidOperationException(c_minMaxError); } if (NeedToConvertMinMax) { System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; - Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); - Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + if (OperandType == typeof(global::System.TimeSpan)) + { + if (!global::System.TimeSpan.TryParse((string)Minimum, culture, out global::System.TimeSpan timeSpanMinimum) || + !global::System.TimeSpan.TryParse((string)Maximum, culture, out global::System.TimeSpan timeSpanMaximum)) + { + throw new global::System.InvalidOperationException(c_minMaxError); + } + Minimum = timeSpanMinimum; + Maximum = timeSpanMaximum; + } + else + { + Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + } } int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum); if (cmp > 0) @@ -429,13 +474,35 @@ public override bool IsValid(object? value) System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; object? convertedValue; - try + if (OperandType == typeof(global::System.TimeSpan)) { - convertedValue = ConvertValue(value, formatProvider); + if (value is global::System.TimeSpan) + { + convertedValue = value; + } + else if (value is string) + { + if (!global::System.TimeSpan.TryParse((string)value, formatProvider, out global::System.TimeSpan timeSpanValue)) + { + return false; + } + convertedValue = timeSpanValue; + } + else + { + throw new global::System.InvalidOperationException($"A value type {value.GetType()} that is not a TimeSpan or a string has been given. This might indicate a problem with the source generator."); + } } - catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) + else { - return false; + try + { + convertedValue = ConvertValue(value, formatProvider); + } + catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) + { + return false; + } } var min = (global::System.IComparable)Minimum; diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netfx.lang10.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netfx.lang10.g.cs index 7f5eb90a202815..4b28eb159d147b 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netfx.lang10.g.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netfx.lang10.g.cs @@ -118,6 +118,26 @@ partial class OptionsUsingGeneratedAttributesValidator (builder ??= new()).AddResults(validationResults); } + context.MemberName = "P13"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P13" : $"{name}.P13"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A5); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P13, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P14"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P14" : $"{name}.P14"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes_2C497155.A6); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P14, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build(); } } @@ -139,6 +159,16 @@ internal static class __Attributes_2C497155 internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_CompareAttribute A4 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_CompareAttribute( "P5"); + + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_RangeAttribute A5 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_RangeAttribute( + typeof(global::System.TimeSpan), + "00:00:00", + "23:59:59"); + + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__2C497155_RangeAttribute A6 = new __OptionValidationGeneratedAttributes.__SourceGen__2C497155_RangeAttribute( + typeof(global::System.TimeSpan), + "01:00:00", + "23:59:59"); } } namespace __OptionValidationStaticInstances @@ -310,19 +340,34 @@ public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum); private bool NeedToConvertMinMax { get; } private bool Initialized { get; set; } + private const string c_minMaxError = "The minimum and maximum values must be set to valid values."; + public override bool IsValid(object? value) { if (!Initialized) { if (Minimum is null || Maximum is null) { - throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + throw new global::System.InvalidOperationException(c_minMaxError); } if (NeedToConvertMinMax) { System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; - Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); - Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + if (OperandType == typeof(global::System.TimeSpan)) + { + if (!global::System.TimeSpan.TryParse((string)Minimum, culture, out global::System.TimeSpan timeSpanMinimum) || + !global::System.TimeSpan.TryParse((string)Maximum, culture, out global::System.TimeSpan timeSpanMaximum)) + { + throw new global::System.InvalidOperationException(c_minMaxError); + } + Minimum = timeSpanMinimum; + Maximum = timeSpanMaximum; + } + else + { + Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + } } int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum); if (cmp > 0) @@ -344,13 +389,35 @@ public override bool IsValid(object? value) System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; object? convertedValue; - try + if (OperandType == typeof(global::System.TimeSpan)) { - convertedValue = ConvertValue(value, formatProvider); + if (value is global::System.TimeSpan) + { + convertedValue = value; + } + else if (value is string) + { + if (!global::System.TimeSpan.TryParse((string)value, formatProvider, out global::System.TimeSpan timeSpanValue)) + { + return false; + } + convertedValue = timeSpanValue; + } + else + { + throw new global::System.InvalidOperationException($"A value type {value.GetType()} that is not a TimeSpan or a string has been given. This might indicate a problem with the source generator."); + } } - catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) + else { - return false; + try + { + convertedValue = ConvertValue(value, formatProvider); + } + catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) + { + return false; + } } var min = (global::System.IComparable)Minimum; diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netfx.lang11.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netfx.lang11.g.cs index 3ab56e21320a07..4c300abc6d05bc 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netfx.lang11.g.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/GeneratedAttributesTest.netfx.lang11.g.cs @@ -118,6 +118,26 @@ partial class OptionsUsingGeneratedAttributesValidator (builder ??= new()).AddResults(validationResults); } + context.MemberName = "P13"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P13" : $"{name}.P13"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A5); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P13, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P14"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingGeneratedAttributes.P14" : $"{name}.P14"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A6); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P14, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build(); } } @@ -139,6 +159,16 @@ file static class __Attributes internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__CompareAttribute A4 = new __OptionValidationGeneratedAttributes.__SourceGen__CompareAttribute( "P5"); + + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A5 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute( + typeof(global::System.TimeSpan), + "00:00:00", + "23:59:59"); + + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A6 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute( + typeof(global::System.TimeSpan), + "01:00:00", + "23:59:59"); } } namespace __OptionValidationStaticInstances @@ -310,19 +340,34 @@ public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum); private bool NeedToConvertMinMax { get; } private bool Initialized { get; set; } + private const string c_minMaxError = "The minimum and maximum values must be set to valid values."; + public override bool IsValid(object? value) { if (!Initialized) { if (Minimum is null || Maximum is null) { - throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + throw new global::System.InvalidOperationException(c_minMaxError); } if (NeedToConvertMinMax) { System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; - Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); - Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + if (OperandType == typeof(global::System.TimeSpan)) + { + if (!global::System.TimeSpan.TryParse((string)Minimum, culture, out global::System.TimeSpan timeSpanMinimum) || + !global::System.TimeSpan.TryParse((string)Maximum, culture, out global::System.TimeSpan timeSpanMaximum)) + { + throw new global::System.InvalidOperationException(c_minMaxError); + } + Minimum = timeSpanMinimum; + Maximum = timeSpanMaximum; + } + else + { + Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + } } int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum); if (cmp > 0) @@ -344,13 +389,35 @@ public override bool IsValid(object? value) System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; object? convertedValue; - try + if (OperandType == typeof(global::System.TimeSpan)) { - convertedValue = ConvertValue(value, formatProvider); + if (value is global::System.TimeSpan) + { + convertedValue = value; + } + else if (value is string) + { + if (!global::System.TimeSpan.TryParse((string)value, formatProvider, out global::System.TimeSpan timeSpanValue)) + { + return false; + } + convertedValue = timeSpanValue; + } + else + { + throw new global::System.InvalidOperationException($"A value type {value.GetType()} that is not a TimeSpan or a string has been given. This might indicate a problem with the source generator."); + } } - catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) + else { - return false; + try + { + convertedValue = ConvertValue(value, formatProvider); + } + catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) + { + return false; + } } var min = (global::System.IComparable)Minimum; diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs index 623251707f87ba..c72be0d72c2c0a 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs @@ -1731,6 +1731,7 @@ public async Task GeneratedAttributesTest(LanguageVersion languageVersion) #endif //NETCOREAPP string source = $$""" + using System; using System.Collections.Generic; using Microsoft.Extensions.Options; using System.ComponentModel.DataAnnotations; @@ -1782,6 +1783,12 @@ public class OptionsUsingGeneratedAttributes [MaxLengthAttribute(5)] public List? P12 { get; set; } + + [RangeAttribute(typeof(TimeSpan), "00:00:00", "23:59:59")] + public string? P13 { get; set; } + + [RangeAttribute(typeof(TimeSpan), "01:00:00", "23:59:59")] + public TimeSpan P14 { get; set; } } [OptionsValidator] diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs index 956cae26e90f6d..93c101431004ca 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs @@ -1721,6 +1721,68 @@ partial class MultipleAttributeModelValidator } } namespace TestClasses.OptionsValidation +{ + partial class OptionsUsingRangeWithTimeSpanValidator + { + /// + /// Validates a specific named options instance (or all when is ). + /// + /// The name of the options instance being validated. + /// The options instance. + /// Validation result. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", + Justification = "The created ValidationContext object is used in a way that never call reflection")] + public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.OptionsUsingRangeWithTimeSpan options) + { + global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null; + var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options); + var validationResults = new global::System.Collections.Generic.List(); + var validationAttributes = new global::System.Collections.Generic.List(1); + + context.MemberName = "P1"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingRangeWithTimeSpan.P1" : $"{name}.P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A19); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P2"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingRangeWithTimeSpan.P2" : $"{name}.P2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A19); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P3"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingRangeWithTimeSpan.P3" : $"{name}.P3"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A19); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P4"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingRangeWithTimeSpan.P4" : $"{name}.P4"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A19); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation { partial class RangeAttributeModelDateValidator { @@ -1742,7 +1804,7 @@ partial class RangeAttributeModelDateValidator context.MemberName = "Val"; context.DisplayName = string.IsNullOrEmpty(name) ? "RangeAttributeModelDate.Val" : $"{name}.Val"; - validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A19); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A20); if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val, context, validationResults, validationAttributes)) { (builder ??= new()).AddResults(validationResults); @@ -1838,7 +1900,7 @@ partial class RegularExpressionAttributeModelValidator context.MemberName = "Val"; context.DisplayName = string.IsNullOrEmpty(name) ? "RegularExpressionAttributeModel.Val" : $"{name}.Val"; - validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A20); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A21); if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val, context, validationResults, validationAttributes)) { (builder ??= new()).AddResults(validationResults); @@ -2039,6 +2101,11 @@ file static class __Attributes (int)9); internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A19 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute( + typeof(global::System.TimeSpan), + "00:00:00", + "00:00:10"); + + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A20 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute( typeof(global::System.DateTime), "1/2/2004", "3/4/2004") @@ -2046,7 +2113,7 @@ file static class __Attributes ParseLimitsInInvariantCulture = true }; - internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A20 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute( + internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A21 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute( "\\s"); } } @@ -2143,19 +2210,34 @@ public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum); private bool NeedToConvertMinMax { get; } private bool Initialized { get; set; } + private const string c_minMaxError = "The minimum and maximum values must be set to valid values."; + public override bool IsValid(object? value) { if (!Initialized) { if (Minimum is null || Maximum is null) { - throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + throw new global::System.InvalidOperationException(c_minMaxError); } if (NeedToConvertMinMax) { System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; - Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); - Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + if (OperandType == typeof(global::System.TimeSpan)) + { + if (!global::System.TimeSpan.TryParse((string)Minimum, culture, out global::System.TimeSpan timeSpanMinimum) || + !global::System.TimeSpan.TryParse((string)Maximum, culture, out global::System.TimeSpan timeSpanMaximum)) + { + throw new global::System.InvalidOperationException(c_minMaxError); + } + Minimum = timeSpanMinimum; + Maximum = timeSpanMaximum; + } + else + { + Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + } } int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum); if (cmp > 0) @@ -2177,13 +2259,35 @@ public override bool IsValid(object? value) System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; object? convertedValue; - try + if (OperandType == typeof(global::System.TimeSpan)) { - convertedValue = ConvertValue(value, formatProvider); + if (value is global::System.TimeSpan) + { + convertedValue = value; + } + else if (value is string) + { + if (!global::System.TimeSpan.TryParse((string)value, formatProvider, out global::System.TimeSpan timeSpanValue)) + { + return false; + } + convertedValue = timeSpanValue; + } + else + { + throw new global::System.InvalidOperationException($"A value type {value.GetType()} that is not a TimeSpan or a string has been given. This might indicate a problem with the source generator."); + } } - catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) + else { - return false; + try + { + convertedValue = ConvertValue(value, formatProvider); + } + catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) + { + return false; + } } var min = (global::System.IComparable)Minimum; diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs index faae7d62d9c410..3c9f86fd84f8a2 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs @@ -1637,6 +1637,66 @@ partial class MultipleAttributeModelValidator } } namespace TestClasses.OptionsValidation +{ + partial class OptionsUsingRangeWithTimeSpanValidator + { + /// + /// Validates a specific named options instance (or all when is ). + /// + /// The name of the options instance being validated. + /// The options instance. + /// Validation result. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.OptionsUsingRangeWithTimeSpan options) + { + global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null; + var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options); + var validationResults = new global::System.Collections.Generic.List(); + var validationAttributes = new global::System.Collections.Generic.List(1); + + context.MemberName = "P1"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingRangeWithTimeSpan.P1" : $"{name}.P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A19); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P2"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingRangeWithTimeSpan.P2" : $"{name}.P2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A19); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P3"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingRangeWithTimeSpan.P3" : $"{name}.P3"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A19); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P4"; + context.DisplayName = string.IsNullOrEmpty(name) ? "OptionsUsingRangeWithTimeSpan.P4" : $"{name}.P4"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A19); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation { partial class RangeAttributeModelDateValidator { @@ -1746,7 +1806,7 @@ partial class RegularExpressionAttributeModelValidator context.MemberName = "Val"; context.DisplayName = string.IsNullOrEmpty(name) ? "RegularExpressionAttributeModel.Val" : $"{name}.Val"; - validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A19); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A20); if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val, context, validationResults, validationAttributes)) { (builder ??= new()).AddResults(validationResults); @@ -1940,7 +2000,12 @@ file static class __Attributes (int)5, (int)9); - internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A19 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute( + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute A19 = new __OptionValidationGeneratedAttributes.__SourceGen__RangeAttribute( + typeof(global::System.TimeSpan), + "00:00:00", + "00:00:10"); + + internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A20 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute( "\\s"); } } @@ -2037,19 +2102,34 @@ public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, GetValidationErrorMessage(), name, Minimum, Maximum); private bool NeedToConvertMinMax { get; } private bool Initialized { get; set; } + private const string c_minMaxError = "The minimum and maximum values must be set to valid values."; + public override bool IsValid(object? value) { if (!Initialized) { if (Minimum is null || Maximum is null) { - throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + throw new global::System.InvalidOperationException(c_minMaxError); } if (NeedToConvertMinMax) { System.Globalization.CultureInfo culture = ParseLimitsInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; - Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); - Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException("The minimum and maximum values must be set to valid values."); + if (OperandType == typeof(global::System.TimeSpan)) + { + if (!global::System.TimeSpan.TryParse((string)Minimum, culture, out global::System.TimeSpan timeSpanMinimum) || + !global::System.TimeSpan.TryParse((string)Maximum, culture, out global::System.TimeSpan timeSpanMaximum)) + { + throw new global::System.InvalidOperationException(c_minMaxError); + } + Minimum = timeSpanMinimum; + Maximum = timeSpanMaximum; + } + else + { + Minimum = ConvertValue(Minimum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + Maximum = ConvertValue(Maximum, culture) ?? throw new global::System.InvalidOperationException(c_minMaxError); + } } int cmp = ((global::System.IComparable)Minimum).CompareTo((global::System.IComparable)Maximum); if (cmp > 0) @@ -2071,13 +2151,35 @@ public override bool IsValid(object? value) System.Globalization.CultureInfo formatProvider = ConvertValueInInvariantCulture ? global::System.Globalization.CultureInfo.InvariantCulture : global::System.Globalization.CultureInfo.CurrentCulture; object? convertedValue; - try + if (OperandType == typeof(global::System.TimeSpan)) { - convertedValue = ConvertValue(value, formatProvider); + if (value is global::System.TimeSpan) + { + convertedValue = value; + } + else if (value is string) + { + if (!global::System.TimeSpan.TryParse((string)value, formatProvider, out global::System.TimeSpan timeSpanValue)) + { + return false; + } + convertedValue = timeSpanValue; + } + else + { + throw new global::System.InvalidOperationException($"A value type {value.GetType()} that is not a TimeSpan or a string has been given. This might indicate a problem with the source generator."); + } } - catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) + else { - return false; + try + { + convertedValue = ConvertValue(value, formatProvider); + } + catch (global::System.Exception e) when (e is global::System.FormatException or global::System.InvalidCastException or global::System.NotSupportedException) + { + return false; + } } var min = (global::System.IComparable)Minimum; diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/OptionsValidationTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/OptionsValidationTests.cs index 49941010f1ace6..4f9770ce7a17ca 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/OptionsValidationTests.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/OptionsValidationTests.cs @@ -4,6 +4,7 @@ using System; using System.ComponentModel.DataAnnotations; using System.Globalization; +using System.Linq; using Microsoft.Extensions.Options; using TestClasses.OptionsValidation; using Xunit; @@ -439,4 +440,41 @@ public void AttributePropertyModelTestOnErrorMessageResource() var modelValidator = new AttributePropertyModelValidator(); Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1); } + + [Fact] + public void OptionsUsingRangeWithTimeSpanValid() + { + var validModel = new OptionsUsingRangeWithTimeSpan + { + P1 = TimeSpan.FromSeconds(1), + P2 = TimeSpan.FromSeconds(2), + P3 = "00:00:03", + P4 = "00:00:04", + }; + + var modelValidator = new OptionsUsingRangeWithTimeSpanValidator(); + ValidateOptionsResult result = modelValidator.Validate(nameof(validModel), validModel); + Assert.Equal(ValidateOptionsResult.Success, result); + + var invalidModel = new OptionsUsingRangeWithTimeSpan + { + P1 = TimeSpan.FromSeconds(11), + P2 = TimeSpan.FromSeconds(-2), + P3 = "01:00:03", + P4 = "02:00:04", + }; + result = modelValidator.Validate(nameof(invalidModel), invalidModel); + Assert.Equal(4, result.Failures.Count()); + + // null values pass the validation! + invalidModel = new OptionsUsingRangeWithTimeSpan + { + P1 = TimeSpan.FromSeconds(100), + P2 = null, + P3 = "00:01:00", + P4 = null, + }; + result = modelValidator.Validate(nameof(invalidModel), invalidModel); + Assert.Equal(2, result.Failures.Count()); + } } diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs index 240163023eb932..c245ebd783bdcf 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs @@ -179,6 +179,21 @@ public class ComplexModel public TypeWithoutOptionsValidator? ValWithoutOptionsValidator { get; set; } } + public class OptionsUsingRangeWithTimeSpan + { + [Range(typeof(TimeSpan), "00:00:00", "00:00:10")] + public TimeSpan P1 { get; set; } + + [Range(typeof(TimeSpan), "00:00:00", "00:00:10")] + public TimeSpan? P2 { get; set; } + + [Range(typeof(TimeSpan), "00:00:00", "00:00:10")] + public string P3 { get; set; } + + [Range(typeof(TimeSpan), "00:00:00", "00:00:10")] + public string? P4 { get; set; } + } + [OptionsValidator] public partial class RequiredAttributeModelValidator : IValidateOptions { @@ -248,4 +263,9 @@ public partial class LeafModelValidator : IValidateOptions internal sealed partial class ComplexModelValidator : IValidateOptions { } + + [OptionsValidator] + internal sealed partial class OptionsUsingRangeWithTimeSpanValidator : IValidateOptions + { + } } diff --git a/src/libraries/Microsoft.Extensions.Options/tests/TrimmingTests/ConfigureTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/TrimmingTests/ConfigureTests.cs index 7a39d8a8810fd3..90179563300b92 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/TrimmingTests/ConfigureTests.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/TrimmingTests/ConfigureTests.cs @@ -46,7 +46,8 @@ optionsC is null || P2 = new List { "1234", "12345" }, P3 = "123456", P4 = "12345", - P5 = 7 + P5 = 7, + P6 = TimeSpan.FromSeconds(5), }; ValidateOptionsResult result = localOptionsValidator.Validate("", optionsUsingValidationAttributes); @@ -113,6 +114,10 @@ public class OptionsUsingValidationAttributes [Range(1, 10, MinimumIsExclusive = true, MaximumIsExclusive = true)] public int P5 { get; set; } + + [Range(typeof(TimeSpan), "00:00:00", "00:00:10")] + public TimeSpan P6 { get; set; } + } [OptionsValidator] From 30d6848e1f13017706c6e43ec0268478ceda6b06 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Fri, 17 Nov 2023 12:38:17 -0600 Subject: [PATCH 02/16] Update dependencies from https://github.com/dotnet/emsdk build 20231116.4 (#94883) Microsoft.SourceBuild.Intermediate.emsdk , Microsoft.NET.Workload.Emscripten.Current.Manifest-8.0.100 From Version 8.0.1-servicing.23563.2 -> To Version 8.0.1-servicing.23566.4 Co-authored-by: dotnet-maestro[bot] --- NuGet.config | 9 +-------- eng/Version.Details.xml | 6 +++--- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/NuGet.config b/NuGet.config index b8ad566c065a4c..579d3b53ca8f20 100644 --- a/NuGet.config +++ b/NuGet.config @@ -9,14 +9,7 @@ - - - - - - - - + + + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 15cfbc281eb892..e9a081259ef472 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -354,9 +354,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-optimization 5914dbda1860830a56179692058d94c9f615deda - + https://github.com/dotnet/hotreload-utils - 8e108a21d0c7d8fa2050a1bdd4d4ba50d2b8df13 + 5524f726f92ef862b415793758cebbd2a1950b70 https://github.com/dotnet/runtime-assets diff --git a/eng/Versions.props b/eng/Versions.props index 9f818a66683036..e89c94c55e6275 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -186,7 +186,7 @@ 8.0.0-prerelease.23407.2 8.0.0-prerelease.23407.2 8.0.0-prerelease.23407.2 - 8.0.0-alpha.0.23563.1 + 8.0.0-alpha.0.23570.2 2.4.2 1.0.0 2.4.5 From 7dc2e67ca495b479f1081a5e193d10b7a396bae8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 11:05:17 -0800 Subject: [PATCH 04/16] Re-check for existance of syncblock in HasInteropInfo - in case the syncblock is gone. (#94649) Co-authored-by: vsadov <8218165+VSadov@users.noreply.github.com> --- src/coreclr/vm/weakreferencenative.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/vm/weakreferencenative.cpp b/src/coreclr/vm/weakreferencenative.cpp index f9a7b9a6bfc01b..bb0ac10906ef00 100644 --- a/src/coreclr/vm/weakreferencenative.cpp +++ b/src/coreclr/vm/weakreferencenative.cpp @@ -172,8 +172,7 @@ FCIMPL1(FC_BOOL_RET, ComAwareWeakReferenceNative::HasInteropInfo, Object* pObjec _ASSERTE(pObject != nullptr); SyncBlock* pSyncBlock = pObject->PassiveGetSyncBlock(); - _ASSERTE(pSyncBlock != nullptr); - return pSyncBlock->GetInteropInfoNoCreate() != nullptr; + return pSyncBlock != nullptr && pSyncBlock->GetInteropInfoNoCreate() != nullptr; } FCIMPLEND From b126b9a50434ffb164b1e2db21ba85915db38c8e Mon Sep 17 00:00:00 2001 From: Elinor Fung Date: Tue, 21 Nov 2023 11:05:52 -0800 Subject: [PATCH 05/16] Store entry assembly path for easy access for diagnostics (#95045) - Add `g_EntryAssemblyPath` global variable holding the full path to the entry assembly - Set right before loading the entry assembly (so also before startup hooks are run) - NULL if there is no entry assembly - Ensure value is included dumps - For triage dumps, the dumped value is updated to only be the assembly file name instead of the full path --- src/coreclr/debug/daccess/enummem.cpp | 28 +++++++++++++++++++++++++++ src/coreclr/inc/daccess.h | 6 ++++-- src/coreclr/inc/dacvars.h | 2 ++ src/coreclr/vm/corhost.cpp | 9 +++++++++ src/coreclr/vm/vars.cpp | 2 ++ src/coreclr/vm/vars.hpp | 3 +++ 6 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/coreclr/debug/daccess/enummem.cpp b/src/coreclr/debug/daccess/enummem.cpp index 44d98beec2de38..7856a1ff0c096e 100644 --- a/src/coreclr/debug/daccess/enummem.cpp +++ b/src/coreclr/debug/daccess/enummem.cpp @@ -224,6 +224,34 @@ HRESULT ClrDataAccess::EnumMemCLRStatic(IN CLRDataEnumMemoryFlags flags) ReportMem(g_gcDacGlobals.GetAddr(), sizeof(GcDacVars)); + PTR_WSTR entryAssemblyPath = (PTR_WSTR)g_EntryAssemblyPath; + entryAssemblyPath.EnumMem(); + + // Triage dumps must not include full paths (PII data). Replace entry assembly path with file name only. + if (flags == CLRDATA_ENUM_MEM_TRIAGE) + { + WCHAR* path = entryAssemblyPath; + if (path != NULL) + { + size_t pathLen = u16_strlen(path) + 1; + + // Get the file name based on the last directory separator + const WCHAR* name = u16_strrchr(path, DIRECTORY_SEPARATOR_CHAR_W); + if (name != NULL) + { + name += 1; + size_t len = u16_strlen(name) + 1; + wcscpy_s(path, len, name); + + // Null out the rest of the buffer + for (size_t i = len; i < pathLen; ++i) + path[i] = W('\0'); + + DacUpdateMemoryRegion(entryAssemblyPath.GetAddr(), pathLen, (BYTE*)path); + } + } + } + // We need all of the dac variables referenced by the GC DAC global struct. // This struct contains pointers to pointers, so we first dereference the pointers // to obtain the location of the variable that's reported. diff --git a/src/coreclr/inc/daccess.h b/src/coreclr/inc/daccess.h index 3ca0ca40ee42e5..54ce86551caee9 100644 --- a/src/coreclr/inc/daccess.h +++ b/src/coreclr/inc/daccess.h @@ -574,6 +574,8 @@ #include "crosscomp.h" #endif +#include + // Information stored in the DAC table of interest to the DAC implementation // Note that this information is shared between all instantiations of ClrDataAccess, so initialize // it just once in code:ClrDataAccess.GetDacGlobals (rather than use fields in ClrDataAccess); @@ -1493,10 +1495,10 @@ class __Str16Ptr : public __DPtr } void EnumMem(void) const { - char* str = DacInstantiateStringW(m_addr, maxChars, false); + WCHAR* str = DacInstantiateStringW(m_addr, maxChars, false); if (str) { - DacEnumMemoryRegion(m_addr, strlen(str) + 1); + DacEnumMemoryRegion(m_addr, u16_strlen(str) + 1); } } }; diff --git a/src/coreclr/inc/dacvars.h b/src/coreclr/inc/dacvars.h index 8fcc1cced7d439..030c18f0b9db20 100644 --- a/src/coreclr/inc/dacvars.h +++ b/src/coreclr/inc/dacvars.h @@ -231,5 +231,7 @@ DEFINE_DACVAR(SIZE_T, dac__g_clrNotificationArguments, ::g_clrNotificationArgume DEFINE_DACVAR(bool, dac__g_metadataUpdatesApplied, ::g_metadataUpdatesApplied) #endif +DEFINE_DACVAR(PTR_WSTR, dac__g_EntryAssemblyPath, ::g_EntryAssemblyPath) + #undef DEFINE_DACVAR #undef DEFINE_DACVAR_NO_DUMP diff --git a/src/coreclr/vm/corhost.cpp b/src/coreclr/vm/corhost.cpp index bb32e93cd0961a..b85331e00ce8d2 100644 --- a/src/coreclr/vm/corhost.cpp +++ b/src/coreclr/vm/corhost.cpp @@ -310,6 +310,15 @@ HRESULT CorHost2::ExecuteAssembly(DWORD dwAppDomainId, _ASSERTE (!pThread->PreemptiveGCDisabled()); + if (g_EntryAssemblyPath == NULL) + { + // Store the entry assembly path for diagnostic purposes (for example, dumps) + size_t len = u16_strlen(pwzAssemblyPath) + 1; + NewArrayHolder path { new WCHAR[len] }; + wcscpy_s(path, len, pwzAssemblyPath); + g_EntryAssemblyPath = path.Extract(); + } + Assembly *pAssembly = AssemblySpec::LoadAssembly(pwzAssemblyPath); #if defined(FEATURE_MULTICOREJIT) diff --git a/src/coreclr/vm/vars.cpp b/src/coreclr/vm/vars.cpp index 85737057d2f9c2..00840f9195651e 100644 --- a/src/coreclr/vm/vars.cpp +++ b/src/coreclr/vm/vars.cpp @@ -106,6 +106,8 @@ GVAL_IMPL_INIT(DWORD, g_debuggerWordTLSIndex, TLS_OUT_OF_INDEXES); #endif GVAL_IMPL_INIT(DWORD, g_TlsIndex, TLS_OUT_OF_INDEXES); +GVAL_IMPL_INIT(PTR_WSTR, g_EntryAssemblyPath, NULL); + #ifndef DACCESS_COMPILE // @TODO - PROMOTE. diff --git a/src/coreclr/vm/vars.hpp b/src/coreclr/vm/vars.hpp index dd92ee7b12fde3..03762a24e695d7 100644 --- a/src/coreclr/vm/vars.hpp +++ b/src/coreclr/vm/vars.hpp @@ -389,6 +389,9 @@ GVAL_DECL(DWORD, g_debuggerWordTLSIndex); #endif GVAL_DECL(DWORD, g_TlsIndex); +// Full path to the managed entry assembly - stored for ease of identifying the entry asssembly for diagnostics +GVAL_DECL(PTR_WSTR, g_EntryAssemblyPath); + // Global System Information extern SYSTEM_INFO g_SystemInfo; From f7652ef0e03e2795d112580ddc8f6d8a39db4da2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 12:04:03 -0800 Subject: [PATCH 06/16] [release/8.0-staging] Fix Vector512.CopyTo (#95026) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix Vector512.CopyTo * Also unify Vector * Handle float vectors too * Fix legacy vectors * Simplify the change * Update Vector_1.cs * Add tests * Fix build --------- Co-authored-by: Michał Petryka <35800402+MichalPetryka@users.noreply.github.com> --- .../src/System/Runtime/Intrinsics/Vector512.cs | 6 ++---- .../tests/Vectors/Vector128Tests.cs | 16 ++++++++++++++++ .../tests/Vectors/Vector256Tests.cs | 16 ++++++++++++++++ .../tests/Vectors/Vector512Tests.cs | 16 ++++++++++++++++ .../tests/Vectors/Vector64Tests.cs | 16 ++++++++++++++++ 5 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs index f95c2d34c3c5f1..824440a5bf2a7b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/Vector512.cs @@ -441,8 +441,7 @@ public static void CopyTo(this Vector512 vector, T[] destination) ThrowHelper.ThrowArgumentException_DestinationTooShort(); } - ref byte address = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(destination)); - Unsafe.WriteUnaligned(ref address, vector); + Unsafe.WriteUnaligned(ref Unsafe.As(ref destination[0]), vector); } /// Copies a to a given array starting at the specified index. @@ -468,8 +467,7 @@ public static void CopyTo(this Vector512 vector, T[] destination, int star ThrowHelper.ThrowArgumentException_DestinationTooShort(); } - ref byte address = ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(destination)); - Unsafe.WriteUnaligned(ref Unsafe.Add(ref address, startIndex), vector); + Unsafe.WriteUnaligned(ref Unsafe.As(ref destination[startIndex]), vector); } /// Copies a to a given span. diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs index 3894d835a60b08..cf1f6cec9be38d 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector128Tests.cs @@ -4517,6 +4517,22 @@ public void Vector128SingleEqualsNonCanonicalNaNTest() } } + [Fact] + public void Vector128SingleCopyToTest() + { + float[] array = new float[4]; + Vector128.Create(2.0f).CopyTo(array); + Assert.True(array.AsSpan().SequenceEqual([2.0f, 2.0f, 2.0f, 2.0f])); + } + + [Fact] + public void Vector128SingleCopyToOffsetTest() + { + float[] array = new float[5]; + Vector128.Create(2.0f).CopyTo(array, 1); + Assert.True(array.AsSpan().SequenceEqual([0.0f, 2.0f, 2.0f, 2.0f, 2.0f])); + } + [Fact] public void IsSupportedByte() => TestIsSupported(); diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector256Tests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector256Tests.cs index 6bdc86bc46f437..95c05c50310fe3 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector256Tests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector256Tests.cs @@ -5539,6 +5539,22 @@ public void Vector256SingleEqualsNonCanonicalNaNTest() } } + [Fact] + public void Vector256SingleCopyToTest() + { + float[] array = new float[8]; + Vector256.Create(2.0f).CopyTo(array); + Assert.True(array.AsSpan().SequenceEqual([2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f])); + } + + [Fact] + public void Vector256SingleCopyToOffsetTest() + { + float[] array = new float[9]; + Vector256.Create(2.0f).CopyTo(array, 1); + Assert.True(array.AsSpan().SequenceEqual([0.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f])); + } + [Fact] public void IsSupportedByte() => TestIsSupported(); diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector512Tests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector512Tests.cs index 4922cf8cab8583..2b97f49d914981 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector512Tests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector512Tests.cs @@ -5016,6 +5016,22 @@ public void Vector512SingleEqualsNonCanonicalNaNTest() } } + [Fact] + public void Vector512SingleCopyToTest() + { + float[] array = new float[16]; + Vector512.Create(2.0f).CopyTo(array); + Assert.True(array.AsSpan().SequenceEqual([2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f])); + } + + [Fact] + public void Vector512SingleCopyToOffsetTest() + { + float[] array = new float[17]; + Vector512.Create(2.0f).CopyTo(array, 1); + Assert.True(array.AsSpan().SequenceEqual([0.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f])); + } + [Fact] public void IsSupportedByte() => TestIsSupported(); diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector64Tests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector64Tests.cs index 8596f3b0ff7861..46aed6abbb06a8 100644 --- a/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector64Tests.cs +++ b/src/libraries/System.Runtime.Intrinsics/tests/Vectors/Vector64Tests.cs @@ -3937,6 +3937,22 @@ public void Vector64SingleEqualsNonCanonicalNaNTest() } } + [Fact] + public void Vector64SingleCopyToTest() + { + float[] array = new float[2]; + Vector64.Create(2.0f).CopyTo(array); + Assert.True(array.AsSpan().SequenceEqual([2.0f, 2.0f])); + } + + [Fact] + public void Vector64SingleCopyToOffsetTest() + { + float[] array = new float[3]; + Vector64.Create(2.0f).CopyTo(array, 1); + Assert.True(array.AsSpan().SequenceEqual([0.0f, 2.0f, 2.0f])); + } + [Fact] public void IsSupportedByte() => TestIsSupported(); From 07cff1c67827e5a0a37984e6d66ad0c89c9cf59c Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 12:04:52 -0800 Subject: [PATCH 07/16] Update dependencies from https://github.com/dotnet/source-build-reference-packages build 20231115.1 (#95008) Microsoft.SourceBuild.Intermediate.source-build-reference-packages From Version 8.0.0-alpha.1.23556.3 -> To Version 8.0.0-alpha.1.23565.1 Co-authored-by: dotnet-maestro[bot] --- eng/Version.Details.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index e9a081259ef472..ab9af74ea01fee 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -99,9 +99,9 @@ d75f77cb8eb87322453288a22ce9b2d880bd1cb1 - + https://github.com/dotnet/source-build-reference-packages - fa4c0e8f53ef2541a23e519af4dfb86cb88e1bae + 95f83e27806330fec09edd96e06bba3acabe3f35 From 9b0f22f6aa2c2ad07312441370ec0ae08fdc63e2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 12:07:01 -0800 Subject: [PATCH 08/16] [release/8.0-staging] Permit MD5 regardless of FIPS configuration for Linux (#94979) * Re-enable MD5 for FIPS with OpenSSL 3 * Permit MD5 on OpenSSL 1.1.1 regardless of FIPS * Fix build --------- Co-authored-by: Kevin Jones --- .../opensslshim.h | 4 ++ .../osslcompat_30.h | 1 + .../pal_evp.c | 39 ++++++++++++++++++- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index 9abc53338796e0..cf10d2f794987d 100644 --- a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h +++ b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h @@ -347,6 +347,8 @@ int EVP_DigestFinalXOF(EVP_MD_CTX *ctx, unsigned char *md, size_t len); REQUIRED_FUNCTION(EVP_MD_CTX_copy_ex) \ RENAMED_FUNCTION(EVP_MD_CTX_free, EVP_MD_CTX_destroy) \ RENAMED_FUNCTION(EVP_MD_CTX_new, EVP_MD_CTX_create) \ + REQUIRED_FUNCTION(EVP_MD_CTX_set_flags) \ + LIGHTUP_FUNCTION(EVP_MD_fetch) \ RENAMED_FUNCTION(EVP_MD_get_size, EVP_MD_size) \ REQUIRED_FUNCTION(EVP_PKCS82PKEY) \ REQUIRED_FUNCTION(EVP_PKEY2PKCS8) \ @@ -842,6 +844,8 @@ FOR_ALL_OPENSSL_FUNCTIONS #define EVP_MD_CTX_copy_ex EVP_MD_CTX_copy_ex_ptr #define EVP_MD_CTX_free EVP_MD_CTX_free_ptr #define EVP_MD_CTX_new EVP_MD_CTX_new_ptr +#define EVP_MD_CTX_set_flags EVP_MD_CTX_set_flags_ptr +#define EVP_MD_fetch EVP_MD_fetch_ptr #define EVP_MD_get_size EVP_MD_get_size_ptr #define EVP_PKCS82PKEY EVP_PKCS82PKEY_ptr #define EVP_PKEY2PKCS8 EVP_PKEY2PKCS8_ptr diff --git a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h index 095dd3176e7aa4..5167f2a0fbcd11 100644 --- a/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h +++ b/src/native/libs/System.Security.Cryptography.Native/osslcompat_30.h @@ -19,6 +19,7 @@ void ERR_new(void); void ERR_set_debug(const char *file, int line, const char *func); void ERR_set_error(int lib, int reason, const char *fmt, ...); int EVP_CIPHER_get_nid(const EVP_CIPHER *e); +EVP_MD* EVP_MD_fetch(OSSL_LIB_CTX *ctx, const char *algorithm, const char *properties); int EVP_MD_get_size(const EVP_MD* md); int EVP_PKEY_CTX_set_rsa_keygen_bits(EVP_PKEY_CTX* ctx, int bits); int EVP_PKEY_CTX_set_rsa_oaep_md(EVP_PKEY_CTX* ctx, const EVP_MD* md); diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp.c index cadc29a67000dd..b623df6b0b1784 100644 --- a/src/native/libs/System.Security.Cryptography.Native/pal_evp.c +++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp.c @@ -1,13 +1,41 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#include "openssl.h" #include "pal_evp.h" #include "pal_utilities.h" #include +#include #define SUCCESS 1 +static const EVP_MD* g_evpFetchMd5 = NULL; +static pthread_once_t g_evpFetch = PTHREAD_ONCE_INIT; + +static void EnsureFetchEvpMdAlgorithms(void) +{ + // This is called from a pthread_once - this method should not be called directly. + +#ifdef NEED_OPENSSL_3_0 + if (API_EXISTS(EVP_MD_fetch)) + { + ERR_clear_error(); + + // Try to fetch an MD5 implementation that will work regardless if + // FIPS is enforced or not. + g_evpFetchMd5 = EVP_MD_fetch(NULL, "MD5", "-fips"); + } +#endif + + // No error queue impact. + // If EVP_MD_fetch is unavailable, use the implicit loader. If it failed, use the implicit loader as a last resort. + if (g_evpFetchMd5 == NULL) + { + g_evpFetchMd5 = EVP_md5(); + } +} + EVP_MD_CTX* CryptoNative_EvpMdCtxCreate(const EVP_MD* type) { ERR_clear_error(); @@ -23,6 +51,13 @@ EVP_MD_CTX* CryptoNative_EvpMdCtxCreate(const EVP_MD* type) return NULL; } + // For OpenSSL 1.x, set the non-FIPS allow flag for MD5. OpenSSL 3 does this differently with EVP_MD_fetch + // and no longer has this flag. + if (CryptoNative_OpenSslVersionNumber() < OPENSSL_VERSION_3_0_RTM && type == EVP_md5()) + { + EVP_MD_CTX_set_flags(ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); + } + int ret = EVP_DigestInit_ex(ctx, type, NULL); if (!ret) { @@ -230,8 +265,8 @@ int32_t CryptoNative_EvpMdSize(const EVP_MD* md) const EVP_MD* CryptoNative_EvpMd5(void) { - // No error queue impact. - return EVP_md5(); + pthread_once(&g_evpFetch, EnsureFetchEvpMdAlgorithms); + return g_evpFetchMd5; } const EVP_MD* CryptoNative_EvpSha1(void) From 3e1f6722fead808a3e9ba41399f334a20eefddfa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 12:07:30 -0800 Subject: [PATCH 09/16] [release/8.0-staging] [mono][interp] Fix incorrect stack type information (#94966) * [mono][interp] Don't link bblock after rethrow * [mono][interp] Small code refactoring * [mono][interp] Fix incorrect stack type information Assume we have a basic block that it is a forward branch destination, then its stack type information will be initialized at the moment of branching (let's say there is a value of type Obj1). If later in the code we reach this bblock by falling through (let's say the current stack contains a value of Obj2), the current stack state will be copied from the original state, with the type Obj1. If later on we do a virtual call, we will try to devirtualize it as if this is Obj1 which is incorrect, since the fallthrough path would produce an Obj2. This commit adds missing checks for removing type information if we have different types on the execution types on incoming paths. --------- Co-authored-by: Vlad Brezae --- src/mono/mono/mini/interp/transform.c | 41 ++++++++++++++++----------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index d4b7a94d8a4d98..67034e9c59c81c 100644 --- a/src/mono/mono/mini/interp/transform.c +++ b/src/mono/mono/mini/interp/transform.c @@ -788,28 +788,32 @@ fixup_newbb_stack_locals (TransformData *td, InterpBasicBlock *newbb) } } +static void +merge_stack_type_information (StackInfo *state1, StackInfo *state2, int len) +{ + // Discard type information if we have type conflicts for stack contents + for (int i = 0; i < len; i++) { + if (state1 [i].klass != state2 [i].klass) { + state1 [i].klass = NULL; + state2 [i].klass = NULL; + } + } +} + // Initializes stack state at entry to bb, based on the current stack state static void init_bb_stack_state (TransformData *td, InterpBasicBlock *bb) { - // FIXME If already initialized, then we need to generate mov to the registers in the state. // Check if already initialized if (bb->stack_height >= 0) { - // Discard type information if we have type conflicts for stack contents - for (int i = 0; i < bb->stack_height; i++) { - if (bb->stack_state [i].klass != td->stack [i].klass) { - bb->stack_state [i].klass = NULL; - td->stack [i].klass = NULL; - } + merge_stack_type_information (td->stack, bb->stack_state, bb->stack_height); + } else { + bb->stack_height = GPTRDIFF_TO_INT (td->sp - td->stack); + if (bb->stack_height > 0) { + int size = bb->stack_height * sizeof (td->stack [0]); + bb->stack_state = (StackInfo*)mono_mempool_alloc (td->mempool, size); + memcpy (bb->stack_state, td->stack, size); } - return; - } - - bb->stack_height = GPTRDIFF_TO_INT (td->sp - td->stack); - if (bb->stack_height > 0) { - int size = bb->stack_height * sizeof (td->stack [0]); - bb->stack_state = (StackInfo*)mono_mempool_alloc (td->mempool, size); - memcpy (bb->stack_state, td->stack, size); } } @@ -5010,8 +5014,12 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, td->cbb = new_bb; if (new_bb->stack_height >= 0) { - if (new_bb->stack_height > 0) + if (new_bb->stack_height > 0) { + if (link_bblocks) + merge_stack_type_information (td->stack, new_bb->stack_state, new_bb->stack_height); + // This is relevant only for copying the vars associated with the values on the stack memcpy (td->stack, new_bb->stack_state, new_bb->stack_height * sizeof(td->stack [0])); + } td->sp = td->stack + new_bb->stack_height; } else if (link_bblocks) { /* This bblock is not branched to. Initialize its stack state */ @@ -7550,6 +7558,7 @@ generate_code (TransformData *td, MonoMethod *method, MonoMethodHeader *header, interp_ins_set_sreg (td->last_ins, td->sp [0].local); td->sp = td->stack; ++td->ip; + link_bblocks = FALSE; break; case CEE_MONO_LD_DELEGATE_METHOD_PTR: From adbb6ae8005674b3f85ef1868d1f0593ae547971 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 12:10:37 -0800 Subject: [PATCH 10/16] [release/8.0-staging] Update dependencies from dotnet/runtime-assets (#94919) * Update dependencies from https://github.com/dotnet/runtime-assets build 20231116.1 Microsoft.DotNet.CilStrip.Sources , System.ComponentModel.TypeConverter.TestData , System.Data.Common.TestData , System.Drawing.Common.TestData , System.Formats.Tar.TestData , System.IO.Compression.TestData , System.IO.Packaging.TestData , System.Net.TestData , System.Private.Runtime.UnicodeData , System.Runtime.Numerics.TestData , System.Runtime.TimeZoneData , System.Security.Cryptography.X509Certificates.TestData , System.Text.RegularExpressions.TestData , System.Windows.Extensions.TestData From Version 8.0.0-beta.23558.5 -> To Version 8.0.0-beta.23566.1 * Update dependencies from https://github.com/dotnet/runtime-assets build 20231116.1 Microsoft.DotNet.CilStrip.Sources , System.ComponentModel.TypeConverter.TestData , System.Data.Common.TestData , System.Drawing.Common.TestData , System.Formats.Tar.TestData , System.IO.Compression.TestData , System.IO.Packaging.TestData , System.Net.TestData , System.Private.Runtime.UnicodeData , System.Runtime.Numerics.TestData , System.Runtime.TimeZoneData , System.Security.Cryptography.X509Certificates.TestData , System.Text.RegularExpressions.TestData , System.Windows.Extensions.TestData From Version 8.0.0-beta.23558.5 -> To Version 8.0.0-beta.23566.1 * Update dependencies from https://github.com/dotnet/runtime-assets build 20231116.1 Microsoft.DotNet.CilStrip.Sources , System.ComponentModel.TypeConverter.TestData , System.Data.Common.TestData , System.Drawing.Common.TestData , System.Formats.Tar.TestData , System.IO.Compression.TestData , System.IO.Packaging.TestData , System.Net.TestData , System.Private.Runtime.UnicodeData , System.Runtime.Numerics.TestData , System.Runtime.TimeZoneData , System.Security.Cryptography.X509Certificates.TestData , System.Text.RegularExpressions.TestData , System.Windows.Extensions.TestData From Version 8.0.0-beta.23558.5 -> To Version 8.0.0-beta.23566.1 * Update dependencies from https://github.com/dotnet/runtime-assets build 20231116.1 Microsoft.DotNet.CilStrip.Sources , System.ComponentModel.TypeConverter.TestData , System.Data.Common.TestData , System.Drawing.Common.TestData , System.Formats.Tar.TestData , System.IO.Compression.TestData , System.IO.Packaging.TestData , System.Net.TestData , System.Private.Runtime.UnicodeData , System.Runtime.Numerics.TestData , System.Runtime.TimeZoneData , System.Security.Cryptography.X509Certificates.TestData , System.Text.RegularExpressions.TestData , System.Windows.Extensions.TestData From Version 8.0.0-beta.23558.5 -> To Version 8.0.0-beta.23566.1 --------- Co-authored-by: dotnet-maestro[bot] Co-authored-by: Larry Ewing From 1aa09ef4ec76baf24c5fc3371e2c83cd1607413b Mon Sep 17 00:00:00 2001 From: Viktor Hofer Date: Tue, 21 Nov 2023 21:15:03 +0100 Subject: [PATCH 11/16] [release/8.0-staging] Use live M.Bcl.AsyncInterfaces dependency (#94459) * [release/8.0-staging] Use live M.Bcl.AsyncInterfaces dependency Manual backport of https://github.com/dotnet/runtime/pull/94451 * Update Microsoft.Bcl.TimeProvider.csproj * Update Microsoft.Bcl.TimeProvider.csproj * Update Microsoft.Bcl.TimeProvider.csproj --- .../ref/Microsoft.Bcl.TimeProvider.csproj | 2 +- .../src/Microsoft.Bcl.TimeProvider.csproj | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libraries/Microsoft.Bcl.TimeProvider/ref/Microsoft.Bcl.TimeProvider.csproj b/src/libraries/Microsoft.Bcl.TimeProvider/ref/Microsoft.Bcl.TimeProvider.csproj index 11d55537a3a3b3..45eebd41ee4e3f 100644 --- a/src/libraries/Microsoft.Bcl.TimeProvider/ref/Microsoft.Bcl.TimeProvider.csproj +++ b/src/libraries/Microsoft.Bcl.TimeProvider/ref/Microsoft.Bcl.TimeProvider.csproj @@ -13,6 +13,6 @@ - + diff --git a/src/libraries/Microsoft.Bcl.TimeProvider/src/Microsoft.Bcl.TimeProvider.csproj b/src/libraries/Microsoft.Bcl.TimeProvider/src/Microsoft.Bcl.TimeProvider.csproj index 95e508e6f0d2ba..fbc46e4552c673 100644 --- a/src/libraries/Microsoft.Bcl.TimeProvider/src/Microsoft.Bcl.TimeProvider.csproj +++ b/src/libraries/Microsoft.Bcl.TimeProvider/src/Microsoft.Bcl.TimeProvider.csproj @@ -14,6 +14,8 @@ System.TimeProvider System.ITimer false + true + 1 @@ -31,7 +33,7 @@ System.ITimer - + From ed566543bb1156d96f582fd8e2c3e224151492ae Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 15:51:13 -0800 Subject: [PATCH 12/16] [release/8.0-staging] Update dependencies from dotnet/source-build-externals (#94918) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update dependencies from https://github.com/dotnet/source-build-externals build 20231116.2 Microsoft.SourceBuild.Intermediate.source-build-externals From Version 8.0.0-alpha.1.23518.1 -> To Version 8.0.0-alpha.1.23566.2 * Update dependencies from https://github.com/dotnet/source-build-externals build 20231116.2 Microsoft.SourceBuild.Intermediate.source-build-externals From Version 8.0.0-alpha.1.23518.1 -> To Version 8.0.0-alpha.1.23566.2 * Update dependencies from https://github.com/dotnet/source-build-externals build 20231116.2 Microsoft.SourceBuild.Intermediate.source-build-externals From Version 8.0.0-alpha.1.23518.1 -> To Version 8.0.0-alpha.1.23566.2 * Update dependencies from https://github.com/dotnet/source-build-externals build 20231116.2 Microsoft.SourceBuild.Intermediate.source-build-externals From Version 8.0.0-alpha.1.23518.1 -> To Version 8.0.0-alpha.1.23566.2 * Update dependencies from https://github.com/dotnet/source-build-externals build 20231120.1 Microsoft.SourceBuild.Intermediate.source-build-externals From Version 8.0.0-alpha.1.23518.1 -> To Version 8.0.0-alpha.1.23570.1 --------- Co-authored-by: dotnet-maestro[bot] Co-authored-by: Carlos Sánchez López <1175054+carlossanlop@users.noreply.github.com> --- NuGet.config | 1 + eng/Version.Details.xml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/NuGet.config b/NuGet.config index ceec57bb70dfce..a9123a91e4272c 100644 --- a/NuGet.config +++ b/NuGet.config @@ -9,6 +9,7 @@ + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index ab9af74ea01fee..ccf2f5e57c8e1a 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -104,9 +104,9 @@ 95f83e27806330fec09edd96e06bba3acabe3f35 - + https://github.com/dotnet/source-build-externals - 3dc05150cf234f76f6936dcb2853d31a0da1f60e + e844aa02a05b90d8cbe499676ec6ee0f19ec4980 From 72a375ff416aa089e9bd29955e438794cad6a790 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 18:41:40 -0800 Subject: [PATCH 13/16] Do not generate broken debug info for non-methods (#94757) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The related methods (`EmitDebugInfo`, `EmitEHClauseInfo`) already do this - if the node is not a method node, don't emit anything. It is not clear what purpose the "if this is not a method, emit broken debug information" serves. I traced it all the way back to https://github.com/dotnet/corert/blob/d78cf62480331f63b26eb08b86b838ffa355ff0d/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ObjectWriter.cs#L427-L447 - it was surrounded by TODOs so maybe it didn't fully stand out but it doesn't look right there already. Co-authored-by: Michal Strehovský --- .../Compiler/DependencyAnalysis/ObjectWriter.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs index bef41e3d7ba351..d85252cca61682 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs @@ -439,15 +439,11 @@ public void EmitDebugEHClauseInfo(ObjectNode node) private static extern void EmitDebugFunctionInfo(IntPtr objWriter, byte[] methodName, int methodSize, uint methodTypeIndex); public void EmitDebugFunctionInfo(ObjectNode node, int methodSize) { - uint methodTypeIndex = 0; - - var methodNode = node as IMethodNode; - if (methodNode != null) + if (node is IMethodNode methodNode) { - methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method); + uint methodTypeIndex = _userDefinedTypeDescriptor.GetMethodFunctionIdTypeIndex(methodNode.Method); + EmitDebugFunctionInfo(_nativeObjectWriter, _currentNodeZeroTerminatedName.UnderlyingArray, methodSize, methodTypeIndex); } - - EmitDebugFunctionInfo(_nativeObjectWriter, _currentNodeZeroTerminatedName.UnderlyingArray, methodSize, methodTypeIndex); } [DllImport(NativeObjectWriterFileName)] From b56fb2a7201976b1b6e7723960479e28171b97a3 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Tue, 21 Nov 2023 20:34:03 -0800 Subject: [PATCH 14/16] [release/8.0-staging] Update dependencies from dotnet/emsdk (#94932) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update dependencies from https://github.com/dotnet/emsdk build 20231117.1 Microsoft.SourceBuild.Intermediate.emsdk , Microsoft.NET.Workload.Emscripten.Current.Manifest-8.0.100 From Version 8.0.1-servicing.23566.4 -> To Version 8.0.1-servicing.23567.1 Dependency coherency updates runtime.linux-arm64.Microsoft.NETCore.Runtime.ObjWriter,runtime.linux-x64.Microsoft.NETCore.Runtime.ObjWriter,runtime.linux-musl-arm64.Microsoft.NETCore.Runtime.ObjWriter,runtime.linux-musl-x64.Microsoft.NETCore.Runtime.ObjWriter,runtime.win-arm64.Microsoft.NETCore.Runtime.ObjWriter,runtime.win-x64.Microsoft.NETCore.Runtime.ObjWriter,runtime.osx-arm64.Microsoft.NETCore.Runtime.ObjWriter,runtime.osx-x64.Microsoft.NETCore.Runtime.ObjWriter,runtime.linux-arm64.Microsoft.NETCore.Runtime.JIT.Tools,runtime.linux-x64.Microsoft.NETCore.Runtime.JIT.Tools,runtime.linux-musl-arm64.Microsoft.NETCore.Runtime.JIT.Tools,runtime.linux-musl-x64.Microsoft.NETCore.Runtime.JIT.Tools,runtime.win-arm64.Microsoft.NETCore.Runtime.JIT.Tools,runtime.win-x64.Microsoft.NETCore.Runtime.JIT.Tools,runtime.osx-arm64.Microsoft.NETCore.Runtime.JIT.Tools,runtime.osx-x64.Microsoft.NETCore.Runtime.JIT.Tools,runtime.linux-arm64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk,runtime.linux-arm64.Microsoft.NETCore.Runtime.Mono.LLVM.Tools,runtime.linux-musl-arm64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk,runtime.linux-musl-arm64.Microsoft.NETCore.Runtime.Mono.LLVM.Tools,runtime.linux-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk,runtime.linux-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Tools,runtime.linux-musl-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk,runtime.linux-musl-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Tools,runtime.win-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk,runtime.win-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Tools,runtime.osx-arm64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk,runtime.osx-arm64.Microsoft.NETCore.Runtime.Mono.LLVM.Tools,runtime.osx-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk,runtime.osx-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Tools From Version 16.0.5-alpha.1.23558.12 -> To Version 16.0.5-alpha.1.23566.1 (parent: Microsoft.NET.Workload.Emscripten.Current.Manifest-8.0.100 * Update dependencies from https://github.com/dotnet/emsdk build 20231121.1 Microsoft.SourceBuild.Intermediate.emsdk , Microsoft.NET.Workload.Emscripten.Current.Manifest-8.0.100 From Version 8.0.1-servicing.23567.1 -> To Version 8.0.1-servicing.23571.1 --------- Co-authored-by: dotnet-maestro[bot] Co-authored-by: Carlos Sánchez López <1175054+carlossanlop@users.noreply.github.com> --- NuGet.config | 1 + eng/Version.Details.xml | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/NuGet.config b/NuGet.config index a9123a91e4272c..f34253e5b0e2fe 100644 --- a/NuGet.config +++ b/NuGet.config @@ -9,6 +9,7 @@ + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index ccf2f5e57c8e1a..8fbbd47bfd2d06 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -92,11 +92,11 @@ https://github.com/dotnet/emsdk - d75f77cb8eb87322453288a22ce9b2d880bd1cb1 + 201f4dae9d1a1e105d8ba86d7ece61eed1f665e0 - + https://github.com/dotnet/emsdk - d75f77cb8eb87322453288a22ce9b2d880bd1cb1 + 201f4dae9d1a1e105d8ba86d7ece61eed1f665e0 From 4fc3df22ddcebcea46fc2727ef3a51870ec1ec38 Mon Sep 17 00:00:00 2001 From: Viktor Hofer Date: Wed, 22 Nov 2023 17:55:28 +0100 Subject: [PATCH 15/16] Fix incremental servicing condition (#95119) * Fix incremental servicing condition Makes non shipping packages always ship during servicing * Update packaging.targets * Update Directory.Build.props * Update Microsoft.Internal.Runtime.AspNetCore.Transport.proj * Update Microsoft.Internal.Runtime.WindowsDesktop.Transport.proj * Update packaging.targets --- eng/packaging.targets | 13 ++++++++++--- ...osoft.Internal.Runtime.AspNetCore.Transport.proj | 6 ------ ...t.Internal.Runtime.WindowsDesktop.Transport.proj | 6 ------ 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/eng/packaging.targets b/eng/packaging.targets index a1fbe07ed2a570..67405cb51aeff0 100644 --- a/eng/packaging.targets +++ b/eng/packaging.targets @@ -21,13 +21,20 @@ true $(MSBuildThisFileDirectory)useSharedDesignerContext.txt PACKAGE.md + + false true - true - true - $(PatchVersion) - - $(NoWarn);NU5131;NU5128 - - - true - $(PatchVersion) - - From a20ee6f80ceb86e010802ae77559d096a0f7bc37 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 22 Nov 2023 09:01:45 -0800 Subject: [PATCH 16/16] [release/8.0-staging] Fix JsonArray.Add and ReplaceWith regressions. (#94882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix JsonArray.Add and ReplaceWith regressions. * Add comment * Address feedback. * Update packaging metadata * Bring back removed using statement * Fix the build error that was showing up after bumping the ServicingVersion prop: Assembly 'System.Text.Json.TestLibrary.Roslyn3.11' with identity 'System.Text.Json.TestLibrary.Roslyn3.11, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' uses 'System.Text.Json, Version=8.0.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' which has a higher version than referenced assembly 'System.Text.Json' with identity 'System.Text.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' * Update src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.TestLibrary/System.Text.Json.TestLibrary.targets * Fix incremental servicing props ordering issue --------- Co-authored-by: Eirik Tsarpalis Co-authored-by: Carlos Sánchez López <1175054+carlossanlop@users.noreply.github.com> Co-authored-by: Viktor Hofer --- src/libraries/Directory.Build.targets | 20 +++--- .../src/System.Text.Json.csproj | 2 + .../src/System/Text/Json/Nodes/JsonArray.cs | 8 +-- .../src/System/Text/Json/Nodes/JsonNode.cs | 41 +++++++++-- .../JsonNode/JsonArrayTests.cs | 69 +++++++++++++++++++ 5 files changed, 119 insertions(+), 21 deletions(-) diff --git a/src/libraries/Directory.Build.targets b/src/libraries/Directory.Build.targets index cb644757a2d95a..348a71ba8da893 100644 --- a/src/libraries/Directory.Build.targets +++ b/src/libraries/Directory.Build.targets @@ -7,6 +7,18 @@ $(TestStrongNameKeyId) + + + + true + true + true + + @@ -47,14 +59,6 @@ --> $(WarningsAsErrors.Replace('NU1605', '')) - - true - true - true false diff --git a/src/libraries/System.Text.Json/src/System.Text.Json.csproj b/src/libraries/System.Text.Json/src/System.Text.Json.csproj index 5765b6cdbee865..3e867457e45ba6 100644 --- a/src/libraries/System.Text.Json/src/System.Text.Json.csproj +++ b/src/libraries/System.Text.Json/src/System.Text.Json.csproj @@ -8,6 +8,8 @@ CS8969 true true + true + 1 Provides high-performance and low-allocating types that serialize objects to JavaScript Object Notation (JSON) text and deserialize JSON text to objects, with UTF-8 support built-in. Also provides types to read and write JSON text encoded as UTF-8, and to create an in-memory document object model (DOM), that is read-only, for random access of the JSON elements within a structured view of the data. The System.Text.Json library is built-in as part of the shared framework in .NET Runtime. The package can be installed when you need to use it in other target frameworks. diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonArray.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonArray.cs index 8dc0924ad36e61..32096972d6df75 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonArray.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonArray.cs @@ -174,13 +174,7 @@ internal JsonArray(JsonElement element, JsonNodeOptions? options = null) : base( [RequiresDynamicCode(JsonValue.CreateDynamicCodeMessage)] public void Add(T? value) { - JsonNode? nodeToAdd = value switch - { - null => null, - JsonNode node => node, - _ => JsonValue.Create(value, Options) - }; - + JsonNode? nodeToAdd = ConvertFromValue(value, Options); Add(nodeToAdd); } diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs index a06fc47cfc1c42..43b6048828a84d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Nodes/JsonNode.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization.Converters; +using System.Text.Json.Serialization.Metadata; namespace System.Text.Json.Nodes { @@ -316,17 +318,16 @@ public static bool DeepEquals(JsonNode? node1, JsonNode? node2) [RequiresDynamicCode(JsonValue.CreateDynamicCodeMessage)] public void ReplaceWith(T value) { + JsonNode? node; switch (_parent) { - case null: - return; case JsonObject jsonObject: - JsonValue? jsonValue = JsonValue.Create(value); - jsonObject.SetItem(GetPropertyName(), jsonValue); + node = ConvertFromValue(value); + jsonObject.SetItem(GetPropertyName(), node); return; case JsonArray jsonArray: - JsonValue? jValue = JsonValue.Create(value); - jsonArray.SetItem(GetElementIndex(), jValue); + node = ConvertFromValue(value); + jsonArray.SetItem(GetElementIndex(), node); return; } } @@ -351,5 +352,33 @@ internal void AssignParent(JsonNode parent) Parent = parent; } + + /// + /// Adaptation of the equivalent JsonValue.Create factory method extended + /// to support arbitrary and values. + /// TODO consider making public cf. https://github.com/dotnet/runtime/issues/70427 + /// + [RequiresUnreferencedCode(JsonSerializer.SerializationUnreferencedCodeMessage)] + [RequiresDynamicCode(JsonSerializer.SerializationRequiresDynamicCodeMessage)] + internal static JsonNode? ConvertFromValue(T? value, JsonNodeOptions? options = null) + { + if (value is null) + { + return null; + } + + if (value is JsonNode node) + { + return node; + } + + if (value is JsonElement element) + { + return JsonNodeConverter.Create(element, options); + } + + var jsonTypeInfo = (JsonTypeInfo)JsonSerializerOptions.Default.GetTypeInfo(typeof(T)); + return new JsonValueCustomized(value, jsonTypeInfo, options); + } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonArrayTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonArrayTests.cs index 8f115d44b211fb..ef51703958151c 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonArrayTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/JsonNode/JsonArrayTests.cs @@ -680,5 +680,74 @@ public static void ReplaceWith() Assert.Null(jValue.Parent); Assert.Equal("[5]", jArray.ToJsonString()); } + + [Theory] + [InlineData("null")] + [InlineData("1")] + [InlineData("false")] + [InlineData("\"str\"")] + [InlineData("""{"test":"hello world"}""")] + [InlineData("[1,2,3]")] + public static void AddJsonElement(string json) + { + // Regression test for https://github.com/dotnet/runtime/issues/94842 + using var jdoc = JsonDocument.Parse(json); + var array = new JsonArray(); + + array.Add(jdoc.RootElement); + + JsonNode arrayElement = Assert.Single(array); + switch (jdoc.RootElement.ValueKind) + { + case JsonValueKind.Object: + Assert.IsAssignableFrom(arrayElement); + break; + case JsonValueKind.Array: + Assert.IsAssignableFrom(arrayElement); + break; + case JsonValueKind.Null: + Assert.Null(arrayElement); + break; + default: + Assert.IsAssignableFrom(arrayElement); + break; + } + Assert.Equal($"[{json}]", array.ToJsonString()); + } + + [Theory] + [InlineData("null")] + [InlineData("1")] + [InlineData("false")] + [InlineData("\"str\"")] + [InlineData("""{"test":"hello world"}""")] + [InlineData("[1,2,3]")] + public static void ReplaceWithJsonElement(string json) + { + // Regression test for https://github.com/dotnet/runtime/issues/94842 + using var jdoc = JsonDocument.Parse(json); + var array = new JsonArray { 1 }; + + array[0].ReplaceWith(jdoc.RootElement); + + JsonNode arrayElement = Assert.Single(array); + switch (jdoc.RootElement.ValueKind) + { + case JsonValueKind.Object: + Assert.IsAssignableFrom(arrayElement); + break; + case JsonValueKind.Array: + Assert.IsAssignableFrom(arrayElement); + break; + case JsonValueKind.Null: + Assert.Null(arrayElement); + break; + default: + Assert.IsAssignableFrom(arrayElement); + break; + } + + Assert.Equal($"[{json}]", array.ToJsonString()); + } } }