diff --git a/NuGet.config b/NuGet.config index 963019c74b9b4..f34253e5b0e2f 100644 --- a/NuGet.config +++ b/NuGet.config @@ -10,7 +10,11 @@ + + + + + + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 9cfaf1bccf913..c8a893de7d463 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -99,14 +99,14 @@ 201f4dae9d1a1e105d8ba86d7ece61eed1f665e0 - + https://github.com/dotnet/source-build-reference-packages - fa4c0e8f53ef2541a23e519af4dfb86cb88e1bae + 95f83e27806330fec09edd96e06bba3acabe3f35 - + https://github.com/dotnet/source-build-externals - 3dc05150cf234f76f6936dcb2853d31a0da1f60e + e844aa02a05b90d8cbe499676ec6ee0f19ec4980 @@ -354,9 +354,9 @@ https://dev.azure.com/dnceng/internal/_git/dotnet-optimization 67613417f5e1af250e6ddfba79f8f2885d8e90fb - + https://github.com/dotnet/hotreload-utils - 12d02e5c2310be6460f896bc3aeb0ddf1e8926bc + 5524f726f92ef862b415793758cebbd2a1950b70 https://github.com/dotnet/runtime-assets diff --git a/eng/Versions.props b/eng/Versions.props index 799ab19e9a8af..da037c2c7ca92 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 diff --git a/eng/packaging.targets b/eng/packaging.targets index a1fbe07ed2a57..67405cb51aeff 100644 --- a/eng/packaging.targets +++ b/eng/packaging.targets @@ -21,13 +21,20 @@ true $(MSBuildThisFileDirectory)useSharedDesignerContext.txt PACKAGE.md + + false true - true + // 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 8fcc1cced7d43..030c18f0b9db2 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/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ObjectWriter.cs index bef41e3d7ba35..d85252cca6168 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)] diff --git a/src/coreclr/vm/corhost.cpp b/src/coreclr/vm/corhost.cpp index bb32e93cd0961..b85331e00ce8d 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 85737057d2f9c..00840f9195651 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 dd92ee7b12fde..03762a24e695d 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; diff --git a/src/coreclr/vm/weakreferencenative.cpp b/src/coreclr/vm/weakreferencenative.cpp index f9a7b9a6bfc01..bb0ac10906ef0 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 diff --git a/src/libraries/Directory.Build.targets b/src/libraries/Directory.Build.targets index cb644757a2d95..348a71ba8da89 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/Microsoft.Bcl.TimeProvider/ref/Microsoft.Bcl.TimeProvider.csproj b/src/libraries/Microsoft.Bcl.TimeProvider/ref/Microsoft.Bcl.TimeProvider.csproj index 11d55537a3a3b..45eebd41ee4e3 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 95e508e6f0d2b..fbc46e4552c67 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 - + diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs b/src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs index 41609ad4b2010..1edc429378528 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 ab2c19de81923..8a88ec6f2f4e6 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 3447a07d39830..8e7073f8ec218 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 ea55622892975..5be4932d8d6c4 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 abbc5d2329c5c..e35606898e391 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 2c5af12c5b5f2..38bacf966df05 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 9dc3ded5bd462..fe77e3e6bd924 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 cc9864a2619c4..7cf1fe61e1a94 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 2a33e51b0b617..f7bba04603342 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 7f5eb90a20281..4b28eb159d147 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 3ab56e21320a0..4c300abc6d05b 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 623251707f87b..c72be0d72c2c0 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 956cae26e90f6..93c101431004c 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 faae7d62d9c41..3c9f86fd84f8a 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 49941010f1ace..4f9770ce7a17c 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 240163023eb93..c245ebd783bdc 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 7a39d8a8810fd..90179563300b9 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] diff --git a/src/libraries/Microsoft.Internal.Runtime.AspNetCore.Transport/src/Microsoft.Internal.Runtime.AspNetCore.Transport.proj b/src/libraries/Microsoft.Internal.Runtime.AspNetCore.Transport/src/Microsoft.Internal.Runtime.AspNetCore.Transport.proj index 732c102bab245..f628ef93cc78c 100644 --- a/src/libraries/Microsoft.Internal.Runtime.AspNetCore.Transport/src/Microsoft.Internal.Runtime.AspNetCore.Transport.proj +++ b/src/libraries/Microsoft.Internal.Runtime.AspNetCore.Transport/src/Microsoft.Internal.Runtime.AspNetCore.Transport.proj @@ -13,12 +13,6 @@ $(NoWarn);NU5131 - - - true - $(PatchVersion) - - $(NoWarn);NU5131;NU5128 - - - true - $(PatchVersion) - - 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 f95c2d34c3c5f..824440a5bf2a7 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 3894d835a60b0..cf1f6cec9be38 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 6bdc86bc46f43..95c05c50310fe 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 4922cf8cab858..2b97f49d91498 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 8596f3b0ff786..46aed6abbb06a 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(); 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 5765b6cdbee86..3e867457e45ba 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 8dc0924ad36e6..32096972d6df7 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 a06fc47cfc1c4..43b6048828a84 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 8f115d44b211f..ef51703958151 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()); + } } } diff --git a/src/mono/mono/mini/interp/transform.c b/src/mono/mono/mini/interp/transform.c index d4b7a94d8a4d9..67034e9c59c81 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: diff --git a/src/native/libs/System.Security.Cryptography.Native/opensslshim.h b/src/native/libs/System.Security.Cryptography.Native/opensslshim.h index 9abc53338796e..cf10d2f794987 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 095dd3176e7aa..5167f2a0fbcd1 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 cadc29a67000d..b623df6b0b178 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)