From 7c9b2522ab52e6452e3d39bca9f91111d73bed53 Mon Sep 17 00:00:00 2001 From: Andrew Au Date: Wed, 10 Aug 2022 16:09:13 -0700 Subject: [PATCH 01/68] Handle the case when pid replacement is not needed (#73664) --- src/coreclr/inc/stresslog.h | 2 +- src/coreclr/utilcode/stresslog.cpp | 11 ++++++++--- src/coreclr/vm/finalizerthread.cpp | 4 ++-- src/coreclr/vm/gcenv.ee.cpp | 4 ++-- src/coreclr/vm/genanalysis.cpp | 4 ++-- 5 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/coreclr/inc/stresslog.h b/src/coreclr/inc/stresslog.h index be76401f0f6271..a245cc2af49f45 100644 --- a/src/coreclr/inc/stresslog.h +++ b/src/coreclr/inc/stresslog.h @@ -259,7 +259,7 @@ #define STRESS_LOG_GC_STACK #endif //_DEBUG -void AppendPid(LPCWSTR logFilename, LPWSTR fileName, size_t fileNameLength); +LPCWSTR ReplacePid(LPCWSTR logFilename, LPWSTR fileName, size_t fileNameLength); class ThreadStressLog; diff --git a/src/coreclr/utilcode/stresslog.cpp b/src/coreclr/utilcode/stresslog.cpp index 2b5b7feaf5b56d..4f59246c50bbcc 100644 --- a/src/coreclr/utilcode/stresslog.cpp +++ b/src/coreclr/utilcode/stresslog.cpp @@ -143,7 +143,7 @@ void StressLog::Leave(CRITSEC_COOKIE) { DecCantAllocCount(); } -void AppendPid(LPCWSTR logFilename, LPWSTR fileName, size_t fileNameLength) +LPCWSTR ReplacePid(LPCWSTR logFilename, LPWSTR fileName, size_t fileNameLength) { // if the string "{pid}" occurs in the logFilename, // replace it by the PID of our process @@ -164,6 +164,11 @@ void AppendPid(LPCWSTR logFilename, LPWSTR fileName, size_t fileNameLength) // append the rest of the filename wcscat_s(fileName, fileNameLength, logFilename + pidInx + wcslen(pidLit)); + return fileName; + } + else + { + return logFilename; } } @@ -176,9 +181,9 @@ static LPVOID CreateMemoryMappedFile(LPWSTR logFilename, size_t maxBytesTotal) } WCHAR fileName[MAX_PATH]; - AppendPid(logFilename, fileName, MAX_PATH); + LPCWSTR logFilenameReplaced = ReplacePid(logFilename, fileName, MAX_PATH); - HandleHolder hFile = WszCreateFile(fileName, + HandleHolder hFile = WszCreateFile(logFilenameReplaced, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, // default security descriptor diff --git a/src/coreclr/vm/finalizerthread.cpp b/src/coreclr/vm/finalizerthread.cpp index 9f25edf97ffa8b..94b313f9b8bb9e 100644 --- a/src/coreclr/vm/finalizerthread.cpp +++ b/src/coreclr/vm/finalizerthread.cpp @@ -284,8 +284,8 @@ VOID FinalizerThread::FinalizerThreadWorker(void *args) // Writing an empty file to indicate completion WCHAR outputPath[MAX_PATH]; - AppendPid(GENAWARE_COMPLETION_FILE_NAME, outputPath, MAX_PATH); - fclose(_wfopen(outputPath, W("w+"))); + LPCWSTR outputPathReplaced = ReplacePid(GENAWARE_COMPLETION_FILE_NAME, outputPath, MAX_PATH); + fclose(_wfopen(outputPathReplaced, W("w+"))); } if (!bPriorityBoosted) diff --git a/src/coreclr/vm/gcenv.ee.cpp b/src/coreclr/vm/gcenv.ee.cpp index 27c6cad6443fa9..1dd2fcdc3b60f6 100644 --- a/src/coreclr/vm/gcenv.ee.cpp +++ b/src/coreclr/vm/gcenv.ee.cpp @@ -1702,8 +1702,8 @@ void GCToEEInterface::AnalyzeSurvivorsFinished(size_t gcIndex, int condemnedGene EX_TRY { WCHAR outputPath[MAX_PATH]; - AppendPid(GENAWARE_DUMP_FILE_NAME, outputPath, MAX_PATH); - GenerateDump (outputPath, 2, GenerateDumpFlagsNone, nullptr, 0); + LPCWSTR outputPathReplaced = ReplacePid(GENAWARE_DUMP_FILE_NAME, outputPath, MAX_PATH); + GenerateDump (outputPathReplaced, 2, GenerateDumpFlagsNone, nullptr, 0); } EX_CATCH {} EX_END_CATCH(SwallowAllExceptions); diff --git a/src/coreclr/vm/genanalysis.cpp b/src/coreclr/vm/genanalysis.cpp index 358d0f0270138c..eb477e7b1496d8 100644 --- a/src/coreclr/vm/genanalysis.cpp +++ b/src/coreclr/vm/genanalysis.cpp @@ -77,7 +77,7 @@ bool gcGenAnalysisDump = false; /* static */ void GenAnalysis::EnableGenerationalAwareSession() { WCHAR outputPath[MAX_PATH]; - AppendPid(GENAWARE_TRACE_FILE_NAME, outputPath, MAX_PATH); + LPCWSTR outputPathReplaced = ReplacePid(GENAWARE_TRACE_FILE_NAME, outputPath, MAX_PATH); NewArrayHolder pProviders; int providerCnt = 1; @@ -94,7 +94,7 @@ bool gcGenAnalysisDump = false; EventPipeProviderConfigurationAdapter configAdapter(pProviders, providerCnt); gcGenAnalysisEventPipeSessionId = EventPipeAdapter::Enable( - outputPath, + outputPathReplaced, gcGenAnalysisBufferMB, configAdapter, EP_SESSION_TYPE_FILE, From 85aa0521681592bb0ae4c6c89f7fb97895e2c7fc Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 11 Aug 2022 01:28:06 +0200 Subject: [PATCH 02/68] [android] Disable failing JIT.Directed test (#73704) --- src/tests/issues.targets | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tests/issues.targets b/src/tests/issues.targets index 66b2d741706d88..7caad0c5838b2f 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -3703,6 +3703,9 @@ needs triage + + https://github.com/dotnet/runtime/issues/73539 + needs triage From ef77c417b7720c5dad830df3900b3cc16d17044c Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Wed, 10 Aug 2022 17:48:35 -0700 Subject: [PATCH 03/68] consume Debian images with updated msquic (#73727) --- eng/pipelines/coreclr/templates/helix-queues-setup.yml | 8 ++++---- eng/pipelines/libraries/helix-queues-setup.yml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/eng/pipelines/coreclr/templates/helix-queues-setup.yml b/eng/pipelines/coreclr/templates/helix-queues-setup.yml index a12440dc03d8b0..e65486aa2aec23 100644 --- a/eng/pipelines/coreclr/templates/helix-queues-setup.yml +++ b/eng/pipelines/coreclr/templates/helix-queues-setup.yml @@ -88,14 +88,14 @@ jobs: - ${{ if and(eq(variables['System.TeamProject'], 'public'), in(parameters.jobParameters.helixQueueGroup, 'pr', 'ci', 'libraries')) }}: - Ubuntu.1804.Amd64.Open - ${{ if and(eq(variables['System.TeamProject'], 'public'), notIn(parameters.jobParameters.helixQueueGroup, 'pr', 'ci', 'libraries')) }}: - - (Debian.10.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-20210304164434-56c6673 - - (Debian.11.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64-20210304164428-5a7c380 + - (Debian.10.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-20220810215022-f344011 + - (Debian.11.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64-20220810215032-f344011 - Ubuntu.1804.Amd64.Open - (Centos.8.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-8-helix-20201229003624-c1bf759 - RedHat.7.Amd64.Open - ${{ if eq(variables['System.TeamProject'], 'internal') }}: - - (Debian.10.Amd64)Ubuntu.1804.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-20210304164434-56c6673 - - (Debian.11.Amd64)Ubuntu.1804.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64-20210304164428-5a7c380 + - (Debian.10.Amd64)Ubuntu.1804.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-20220810215022-f344011 + - (Debian.11.Amd64)Ubuntu.1804.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64-20220810215032-f344011 - Ubuntu.1804.Amd64 - (Centos.8.Amd64)Ubuntu.1604.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-8-helix-20201229003624-c1bf759 - (Fedora.34.Amd64)Ubuntu.1604.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix-20220716172051-4f64125 diff --git a/eng/pipelines/libraries/helix-queues-setup.yml b/eng/pipelines/libraries/helix-queues-setup.yml index f146897e3d2841..ed9edccfffac97 100644 --- a/eng/pipelines/libraries/helix-queues-setup.yml +++ b/eng/pipelines/libraries/helix-queues-setup.yml @@ -64,20 +64,20 @@ jobs: - SLES.15.Amd64.Open - (Fedora.34.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix-20220716172051-4f64125 - (Ubuntu.2204.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-22.04-helix-amd64-20220504035722-1b9461f - - (Debian.10.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-bfcd90a-20200121150006 + - (Debian.10.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-20220810215022-f344011 - ${{ if or(ne(parameters.jobParameters.testScope, 'outerloop'), ne(parameters.jobParameters.runtimeFlavor, 'mono')) }}: - ${{ if or(eq(parameters.jobParameters.isExtraPlatforms, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: - (Centos.8.Amd64.Open)Ubuntu.1604.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-8-helix-20201229003624-c1bf759 - SLES.15.Amd64.Open - (Fedora.34.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix-20220716172051-4f64125 - Ubuntu.2204.Amd64.Open - - (Debian.11.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64-20210304164428-5a7c380 + - (Debian.11.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64-20220810215032-f344011 - (Mariner.1.0.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:cbl-mariner-1.0-helix-20210528192219-92bf620 - (openSUSE.15.2.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:opensuse-15.2-helix-amd64-20211018152525-9cc02fe - ${{ if or(ne(parameters.jobParameters.isExtraPlatforms, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: - (Centos.7.Amd64.Open)Ubuntu.1604.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-7-mlnet-helix-20220601183719-dde38af - RedHat.7.Amd64.Open - - (Debian.10.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-20210304164434-56c6673 + - (Debian.10.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-20220810215022-f344011 - Ubuntu.1804.Amd64.Open - ${{ if or(eq(parameters.jobParameters.interpreter, 'true'), eq(parameters.jobParameters.isSingleFile, true)) }}: # Limiting interp runs as we don't need as much coverage. From 0559d4a6cf6c89e3fdc6a639bd631287868f00e2 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Wed, 10 Aug 2022 18:05:09 -0700 Subject: [PATCH 04/68] Remove non-determinism from R2R field access (#73663) * Remove non-determinism from R2R field access - Remove all trace of storing off the field token in a compiler non-deterministic manner - Notably, remove the _fieldToRefTokens field from ModuleTokenResolver, and remove the ability of that class to resolve tokens for fields - Add a new concept called FieldWithToken modeled after MethodWithToken - Plumb through the various dependency analysis structures as needed Fixes #73403 --- .../tools/Common/JitInterface/CorInfoImpl.cs | 7 +- .../ReadyToRun/FieldFixupSignature.cs | 28 ++--- .../ReadyToRun/GenericLookupSignature.cs | 10 +- .../ReadyToRun/ModuleTokenResolver.cs | 61 ---------- .../ReadyToRun/SignatureBuilder.cs | 14 +-- .../ReadyToRun/SignatureContext.cs | 10 -- .../ReadyToRunSymbolNodeFactory.cs | 40 +++---- .../Compiler/RuntimeDeterminedTypeHelper.cs | 18 +++ .../JitInterface/CorInfoImpl.ReadyToRun.cs | 110 ++++++++++++++++-- 9 files changed, 160 insertions(+), 138 deletions(-) diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 73b17aedf51e28..c1f59c124b6769 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -1713,12 +1713,7 @@ private void resolveToken(ref CORINFO_RESOLVED_TOKEN pResolvedToken) _compilation.TypeSystemContext.EnsureLoadableType(owningClass); #endif -#if READYTORUN - if (recordToken) - { - _compilation.NodeFactory.Resolver.AddModuleTokenForField(field, HandleToModuleToken(ref pResolvedToken)); - } -#else +#if !READYTORUN _compilation.NodeFactory.MetadataManager.GetDependenciesDueToAccess(ref _additionalDependencies, _compilation.NodeFactory, (MethodIL)methodIL, field); #endif } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/FieldFixupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/FieldFixupSignature.cs index 4cf38ae5ee601c..93c5569c5b01ea 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/FieldFixupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/FieldFixupSignature.cs @@ -17,15 +17,15 @@ public class FieldFixupSignature : Signature public const int MaxCheckableOffset = 0x1FFFFFFF; private readonly ReadyToRunFixupKind _fixupKind; - private readonly FieldDesc _fieldDesc; + private readonly FieldWithToken _fieldWithToken; - public FieldFixupSignature(ReadyToRunFixupKind fixupKind, FieldDesc fieldDesc, NodeFactory factory) + public FieldFixupSignature(ReadyToRunFixupKind fixupKind, FieldWithToken fieldWithToken, NodeFactory factory) { _fixupKind = fixupKind; - _fieldDesc = fieldDesc; + _fieldWithToken = fieldWithToken; // Ensure types in signature are loadable and resolvable, otherwise we'll fail later while emitting the signature - ((CompilerTypeSystemContext)fieldDesc.Context).EnsureLoadableType(fieldDesc.OwningType); + ((CompilerTypeSystemContext)fieldWithToken.Field.Context).EnsureLoadableType(fieldWithToken.Field.OwningType); } public override int ClassCode => 271828182; @@ -38,19 +38,19 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { dataBuilder.AddSymbol(this); - IEcmaModule targetModule = factory.SignatureContext.GetTargetModule(_fieldDesc); + IEcmaModule targetModule = _fieldWithToken.Token.Module; SignatureContext innerContext = dataBuilder.EmitFixup(factory, _fixupKind, targetModule, factory.SignatureContext); uint baseOffset = 0; - uint fieldOffset = (uint)_fieldDesc.Offset.AsInt; + uint fieldOffset = (uint)_fieldWithToken.Field.Offset.AsInt; if (_fixupKind == ReadyToRunFixupKind.Verify_FieldOffset) { - TypeDesc baseType = _fieldDesc.OwningType.BaseType; - if ((_fieldDesc.OwningType.BaseType != null) - && !_fieldDesc.IsStatic - && !_fieldDesc.OwningType.IsValueType) + TypeDesc baseType = _fieldWithToken.Field.OwningType.BaseType; + if ((_fieldWithToken.Field.OwningType.BaseType != null) + && !_fieldWithToken.Field.IsStatic + && !_fieldWithToken.Field.OwningType.IsValueType) { - MetadataType owningType = (MetadataType)_fieldDesc.OwningType; + MetadataType owningType = (MetadataType)_fieldWithToken.Field.OwningType; baseOffset = (uint)owningType.FieldBaseOffset().AsInt; if (factory.CompilationModuleGroup.NeedsAlignmentBetweenBaseTypeAndDerived((MetadataType)baseType, owningType)) { @@ -67,7 +67,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) dataBuilder.EmitUInt(fieldOffset); } - dataBuilder.EmitFieldSignature(_fieldDesc, innerContext); + dataBuilder.EmitFieldSignature(_fieldWithToken, innerContext); } return dataBuilder.ToObjectData(); @@ -77,7 +77,7 @@ public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilde { sb.Append(nameMangler.CompilationUnitPrefix); sb.Append($@"FieldFixupSignature({_fixupKind.ToString()}): "); - sb.Append(nameMangler.GetMangledFieldName(_fieldDesc)); + _fieldWithToken.AppendMangledName(nameMangler, sb); } public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) @@ -87,7 +87,7 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer if (result != 0) return result; - return comparer.Compare(_fieldDesc, otherNode._fieldDesc); + return _fieldWithToken.CompareTo(otherNode._fieldWithToken, comparer); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GenericLookupSignature.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GenericLookupSignature.cs index eae25a978cb17e..05287235097ad9 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GenericLookupSignature.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GenericLookupSignature.cs @@ -22,7 +22,7 @@ public class GenericLookupSignature : Signature private readonly MethodWithToken _methodArgument; - private readonly FieldDesc _fieldArgument; + private readonly FieldWithToken _fieldArgument; private readonly GenericContext _methodContext; @@ -31,7 +31,7 @@ public GenericLookupSignature( ReadyToRunFixupKind fixupKind, TypeDesc typeArgument, MethodWithToken methodArgument, - FieldDesc fieldArgument, + FieldWithToken fieldArgument, GenericContext methodContext) { Debug.Assert(typeArgument != null || methodArgument != null || fieldArgument != null); @@ -64,7 +64,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) } else if (_fieldArgument != null) { - targetModule = factory.SignatureContext.GetTargetModule(_fieldArgument); + targetModule = _fieldArgument.Token.Module; } else { @@ -173,7 +173,7 @@ public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilde } if (_fieldArgument != null) { - sb.Append(nameMangler.GetMangledFieldName(_fieldArgument)); + _fieldArgument.AppendMangledName(nameMangler, sb); } sb.Append(" ("); _methodContext.AppendMangledName(nameMangler, sb); @@ -210,7 +210,7 @@ public override int CompareToImpl(ISortableNode other, CompilerComparer comparer if (otherNode._fieldArgument == null) return 1; - result = comparer.Compare(_fieldArgument, otherNode._fieldArgument); + result = _fieldArgument.CompareTo(otherNode._fieldArgument, comparer); if (result != 0) return result; } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ModuleTokenResolver.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ModuleTokenResolver.cs index 0abaedfa8e0218..e9ec9f6e0396a0 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ModuleTokenResolver.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ModuleTokenResolver.cs @@ -27,8 +27,6 @@ public class ModuleTokenResolver /// private readonly ConcurrentDictionary _typeToRefTokens = new ConcurrentDictionary(); - private readonly ConcurrentDictionary _fieldToRefTokens = new ConcurrentDictionary(); - private readonly CompilationModuleGroup _compilationModuleGroup; private Func _moduleIndexLookup; @@ -127,36 +125,6 @@ public ModuleToken GetModuleTokenForMethod(MethodDesc method, bool allowDynamica } } - public ModuleToken GetModuleTokenForField(FieldDesc field, bool throwIfNotFound = true) - { - if (_compilationModuleGroup.VersionsWithType(field.OwningType) && field is EcmaField ecmaField) - { - return new ModuleToken(ecmaField.Module, ecmaField.Handle); - } - - TypeDesc owningCanonType = field.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific); - FieldDesc canonField = field; - if (owningCanonType != field.OwningType) - { - canonField = CompilerContext.GetFieldForInstantiatedType(field.GetTypicalFieldDefinition(), (InstantiatedType)owningCanonType); - } - - ModuleToken token; - if (_fieldToRefTokens.TryGetValue(canonField, out token)) - { - return token; - } - - if (throwIfNotFound) - { - throw new NotImplementedException(field.ToString()); - } - else - { - return default(ModuleToken); - } - } - public void AddModuleTokenForMethod(MethodDesc method, ModuleToken token) { if (token.TokenType == CorTokenType.mdtMethodSpec) @@ -188,35 +156,6 @@ private void AddModuleTokenForFieldReference(TypeDesc owningType, ModuleToken to memberRef.DecodeFieldSignature(new TokenResolverProvider(this, token.Module), this); } - public void AddModuleTokenForField(FieldDesc field, ModuleToken token) - { - if (_compilationModuleGroup.VersionsWithType(field.OwningType) && field.OwningType is EcmaType) - { - // We don't need to store handles within the current compilation group - // as we can read them directly from the ECMA objects. - return; - } - - TypeDesc owningCanonType = field.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific); - FieldDesc canonField = field; - if (owningCanonType != field.OwningType) - { - canonField = CompilerContext.GetFieldForInstantiatedType(field.GetTypicalFieldDefinition(), (InstantiatedType)owningCanonType); - } - - SetModuleTokenForTypeSystemEntity(_fieldToRefTokens, canonField, token); - - switch (token.TokenType) - { - case CorTokenType.mdtMemberRef: - AddModuleTokenForFieldReference(owningCanonType, token); - break; - - default: - throw new NotImplementedException(); - } - } - // Add TypeSystemEntity -> ModuleToken mapping to a ConcurrentDictionary. Using CompareTo sort the token used, so it will // be consistent in all runs of the compiler void SetModuleTokenForTypeSystemEntity(ConcurrentDictionary dictionary, T tse, ModuleToken token) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs index 01bb5fe766d78d..fe5595531ad810 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureBuilder.cs @@ -530,23 +530,17 @@ private void EmitMethodSpecificationSignature(MethodWithToken method, } } - public void EmitFieldSignature(FieldDesc field, SignatureContext context) + public void EmitFieldSignature(FieldWithToken field, SignatureContext context) { uint fieldSigFlags = 0; - TypeDesc canonOwnerType = field.OwningType.ConvertToCanonForm(CanonicalFormKind.Specific); TypeDesc ownerType = null; - if (canonOwnerType.HasInstantiation) + if (field.OwningTypeNotDerivedFromToken) { - ownerType = field.OwningType; + ownerType = field.Field.OwningType; fieldSigFlags |= (uint)ReadyToRunFieldSigFlags.READYTORUN_FIELD_SIG_OwnerType; } - if (canonOwnerType != field.OwningType) - { - // Convert field to canonical form as this is what the field - module token lookup stores - field = field.Context.GetFieldForInstantiatedType(field.GetTypicalFieldDefinition(), (InstantiatedType)canonOwnerType); - } - ModuleToken fieldToken = context.GetModuleTokenForField(field); + ModuleToken fieldToken = field.Token; switch (fieldToken.TokenType) { case CorTokenType.mdtMemberRef: diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureContext.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureContext.cs index 84ac68fb24208a..2e251e60b91902 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureContext.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/SignatureContext.cs @@ -66,11 +66,6 @@ public IEcmaModule GetTargetModule(TypeDesc type) return LocalContext; } - public IEcmaModule GetTargetModule(FieldDesc field) - { - return GetModuleTokenForField(field).Module; - } - public IEcmaModule GetTargetModule(MethodDesc method) { return GetModuleTokenForMethod(method).Module; @@ -86,11 +81,6 @@ public ModuleToken GetModuleTokenForMethod(MethodDesc method) return Resolver.GetModuleTokenForMethod(method, throwIfNotFound: false, allowDynamicallyCreatedReference: false); } - public ModuleToken GetModuleTokenForField(FieldDesc field, bool throwIfNotFound = true) - { - return Resolver.GetModuleTokenForField(field, throwIfNotFound); - } - public bool Equals(SignatureContext other) { return GlobalContext == other.GlobalContext diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs index 940daa8294ccc3..1fff139e7c179c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunSymbolNodeFactory.cs @@ -65,7 +65,7 @@ private void CreateNodeCaches() new ReadyToRunInstructionSetSupportSignature(key)); }); - _fieldAddressCache = new NodeCache(key => + _fieldAddressCache = new NodeCache(key => { return new DelayLoadHelperImport( _codegenNodeFactory, @@ -75,7 +75,7 @@ private void CreateNodeCaches() ); }); - _fieldOffsetCache = new NodeCache(key => + _fieldOffsetCache = new NodeCache(key => { return new PrecodeHelperImport( _codegenNodeFactory, @@ -91,7 +91,7 @@ private void CreateNodeCaches() ); }); - _checkFieldOffsetCache = new NodeCache(key => + _checkFieldOffsetCache = new NodeCache(key => { return new PrecodeHelperImport( _codegenNodeFactory, @@ -241,7 +241,7 @@ private ISymbolNode CreateReadyToRunHelper(ReadyToRunHelperKey key) return CreateMethodHandleHelper((MethodWithToken)key.Target); case ReadyToRunHelperId.FieldHandle: - return CreateFieldHandleHelper((FieldDesc)key.Target); + return CreateFieldHandleHelper((FieldWithToken)key.Target); case ReadyToRunHelperId.CctorTrigger: return CreateCctorTrigger((TypeDesc)key.Target); @@ -361,7 +361,7 @@ private ISymbolNode CreateMethodHandleHelper(MethodWithToken method) isInstantiatingStub: useInstantiatingStub)); } - private ISymbolNode CreateFieldHandleHelper(FieldDesc field) + private ISymbolNode CreateFieldHandleHelper(FieldWithToken field) { return new PrecodeHelperImport( _codegenNodeFactory, @@ -395,25 +395,25 @@ private ISymbolNode CreateMethodDictionary(MethodWithToken method) isInstantiatingStub: true)); } - private NodeCache _fieldAddressCache; + private NodeCache _fieldAddressCache; - public ISymbolNode FieldAddress(FieldDesc fieldDesc) + public ISymbolNode FieldAddress(FieldWithToken fieldWithToken) { - return _fieldAddressCache.GetOrAdd(fieldDesc); + return _fieldAddressCache.GetOrAdd(fieldWithToken); } - private NodeCache _fieldOffsetCache; + private NodeCache _fieldOffsetCache; - public ISymbolNode FieldOffset(FieldDesc fieldDesc) + public ISymbolNode FieldOffset(FieldWithToken fieldWithToken) { - return _fieldOffsetCache.GetOrAdd(fieldDesc); + return _fieldOffsetCache.GetOrAdd(fieldWithToken); } - private NodeCache _checkFieldOffsetCache; + private NodeCache _checkFieldOffsetCache; - public ISymbolNode CheckFieldOffset(FieldDesc fieldDesc) + public ISymbolNode CheckFieldOffset(FieldWithToken fieldWithToken) { - return _checkFieldOffsetCache.GetOrAdd(fieldDesc); + return _checkFieldOffsetCache.GetOrAdd(fieldWithToken); } private NodeCache _fieldBaseOffsetCache; @@ -502,7 +502,7 @@ private struct GenericLookupKey : IEquatable public readonly ReadyToRunFixupKind FixupKind; public readonly TypeDesc TypeArgument; public readonly MethodWithToken MethodArgument; - public readonly FieldDesc FieldArgument; + public readonly FieldWithToken FieldArgument; public readonly GenericContext MethodContext; public GenericLookupKey( @@ -510,7 +510,7 @@ public GenericLookupKey( ReadyToRunFixupKind fixupKind, TypeDesc typeArgument, MethodWithToken methodArgument, - FieldDesc fieldArgument, + FieldWithToken fieldArgument, GenericContext methodContext) { LookupKind = lookupKind; @@ -603,7 +603,7 @@ public ISymbolNode GenericLookupHelper( return GenericLookupFieldHelper( runtimeLookupKind, ReadyToRunFixupKind.FieldHandle, - (FieldDesc)helperArgument, + (FieldWithToken)helperArgument, methodContext); default: @@ -622,9 +622,9 @@ private ISymbolNode GenericLookupTypeHelper( { typeArgument = methodWithToken.Method.OwningType; } - else if (helperArgument is FieldDesc fieldDesc) + else if (helperArgument is FieldWithToken fieldWithToken) { - typeArgument = fieldDesc.OwningType; + typeArgument = fieldWithToken.Field.OwningType; } else { @@ -638,7 +638,7 @@ private ISymbolNode GenericLookupTypeHelper( private ISymbolNode GenericLookupFieldHelper( CORINFO_RUNTIME_LOOKUP_KIND runtimeLookupKind, ReadyToRunFixupKind fixupKind, - FieldDesc fieldArgument, + FieldWithToken fieldArgument, GenericContext methodContext) { GenericLookupKey key = new GenericLookupKey(runtimeLookupKind, fixupKind, typeArgument: null, methodArgument: null, fieldArgument: fieldArgument, methodContext); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/RuntimeDeterminedTypeHelper.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/RuntimeDeterminedTypeHelper.cs index 1af2b045acba46..f2f56713670d9a 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/RuntimeDeterminedTypeHelper.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/RuntimeDeterminedTypeHelper.cs @@ -146,6 +146,15 @@ public static bool Equals(FieldDesc field1, FieldDesc field2) RuntimeDeterminedTypeHelper.Equals(field1.FieldType, field2.FieldType); } + public static bool Equals(FieldWithToken field1, FieldWithToken field2) + { + if (field1 == null || field2 == null) + { + return field1 == null && field2 == null; + } + return RuntimeDeterminedTypeHelper.Equals(field1.Field, field2.Field); + } + public static int GetHashCode(Instantiation instantiation) { int hashcode = unchecked(instantiation.Length << 24); @@ -198,5 +207,14 @@ public static int GetHashCode(FieldDesc field) } return unchecked(GetHashCode(field.OwningType) + 97 * GetHashCode(field.FieldType) + 31 * field.Name.GetHashCode()); } + + public static int GetHashCode(FieldWithToken field) + { + if (field == null) + { + return 0; + } + return GetHashCode(field.Field); + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs index dce751e370c76b..4d07cd8e68610c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs @@ -37,6 +37,82 @@ public RequiresRuntimeJitIfUsedSymbol(string message) public string Message { get; } } + public class FieldWithToken : IEquatable + { + public readonly FieldDesc Field; + public readonly ModuleToken Token; + public readonly bool OwningTypeNotDerivedFromToken; + + public FieldWithToken(FieldDesc field, ModuleToken token) + { + Field = field; + Token = token; + if (token.TokenType == CorTokenType.mdtMemberRef) + { + var memberRef = token.MetadataReader.GetMemberReference((MemberReferenceHandle)token.Handle); + switch (memberRef.Parent.Kind) + { + case HandleKind.TypeDefinition: + case HandleKind.TypeReference: + case HandleKind.TypeSpecification: + OwningTypeNotDerivedFromToken = token.Module.GetType(memberRef.Parent) != field.OwningType; + break; + + default: + break; + } + } + } + + public override bool Equals(object obj) + { + return obj is FieldWithToken fieldWithToken && + Equals(fieldWithToken); + } + + public override int GetHashCode() + { + return Field.GetHashCode() ^ unchecked(17 * Token.GetHashCode()); + } + + public bool Equals(FieldWithToken fieldWithToken) + { + if (fieldWithToken == null) + return false; + + return Field == fieldWithToken.Field && Token.Equals(fieldWithToken.Token); + } + + public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append(nameMangler.GetMangledFieldName(Field)); + sb.Append("; "); + sb.Append(Token.ToString()); + } + + public override string ToString() + { + StringBuilder debuggingName = new StringBuilder(); + debuggingName.Append(Field.ToString()); + + debuggingName.Append("; "); + debuggingName.Append(Token.ToString()); + + return debuggingName.ToString(); + } + + public int CompareTo(FieldWithToken other, TypeSystemComparer comparer) + { + int result; + + result = comparer.Compare(Field, other.Field); + if (result != 0) + return result; + + return Token.CompareTo(other.Token); + } + } + public class MethodWithToken { public readonly MethodDesc Method; @@ -660,6 +736,10 @@ private bool getReadyToRunHelper(ref CORINFO_RESOLVED_TOKEN pResolvedToken, ref MethodDesc sharedMethod = methodIL.OwningMethod.GetSharedRuntimeFormMethodTarget(); helperArg = new MethodWithToken(methodDesc, HandleToModuleToken(ref pResolvedToken), constrainedType, unboxing: false, context: sharedMethod); } + else if (helperArg is FieldDesc fieldDesc) + { + helperArg = new FieldWithToken(fieldDesc, HandleToModuleToken(ref pResolvedToken)); + } GenericContext methodContext = new GenericContext(entityFromContext(pResolvedToken.tokenContext)); ISymbolNode helper = _compilation.SymbolNodeFactory.GenericLookupHelper( @@ -1041,6 +1121,12 @@ private bool canTailCall(CORINFO_METHOD_STRUCT_* callerHnd, CORINFO_METHOD_STRUC return true; } + private FieldWithToken ComputeFieldWithToken(FieldDesc field, ref CORINFO_RESOLVED_TOKEN pResolvedToken) + { + ModuleToken token = HandleToModuleToken(ref pResolvedToken); + return new FieldWithToken(field, token); + } + private MethodWithToken ComputeMethodWithToken(MethodDesc method, ref CORINFO_RESOLVED_TOKEN pResolvedToken, TypeDesc constrainedType, bool unboxing) { ModuleToken token = HandleToModuleToken(ref pResolvedToken, method, out object context, ref constrainedType); @@ -1416,7 +1502,7 @@ private void getFieldInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_MET if (_compilation.SymbolNodeFactory.VerifyTypeAndFieldLayout && (fieldOffset <= FieldFixupSignature.MaxCheckableOffset)) { // ENCODE_CHECK_FIELD_OFFSET - AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(field)); + AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken))); } } else @@ -1457,7 +1543,7 @@ private void getFieldInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_MET // Static fields outside of the version bubble need to be accessed using the ENCODE_FIELD_ADDRESS // helper in accordance with ZapInfo::getFieldInfo in CoreCLR. - pResult->fieldLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.FieldAddress(field)); + pResult->fieldLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.FieldAddress(ComputeFieldWithToken(field, ref pResolvedToken))); pResult->helper = CorInfoHelpFunc.CORINFO_HELP_READYTORUN_STATIC_BASE; @@ -1470,7 +1556,7 @@ private void getFieldInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_MET if (_compilation.SymbolNodeFactory.VerifyTypeAndFieldLayout && (fieldOffset <= FieldFixupSignature.MaxCheckableOffset)) { // ENCODE_CHECK_FIELD_OFFSET - AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(field)); + AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken))); } pResult->fieldLookup = CreateConstLookupToSymbol( @@ -1493,7 +1579,7 @@ private void getFieldInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_MET pResult->accessAllowed = CorInfoIsAccessAllowedResult.CORINFO_ACCESS_ALLOWED; pResult->offset = fieldOffset; - EncodeFieldBaseOffset(field, pResult, callerMethod); + EncodeFieldBaseOffset(field, ref pResolvedToken, pResult, callerMethod); // TODO: We need to implement access checks for fields and methods. See JitInterface.cpp in mrtjit // and STS::AccessCheck::CanAccess. @@ -2407,7 +2493,7 @@ private void embedGenericHandle(ref CORINFO_RESOLVED_TOKEN pResolvedToken, bool case CorInfoGenericHandleType.CORINFO_HANDLETYPE_FIELD: symbolNode = _compilation.SymbolNodeFactory.CreateReadyToRunHelper( ReadyToRunHelperId.FieldHandle, - HandleToObject(pResolvedToken.hField)); + ComputeFieldWithToken(HandleToObject(pResolvedToken.hField), ref pResolvedToken)); break; default: @@ -2455,7 +2541,7 @@ private void PreventRecursiveFieldInlinesOutsideVersionBubble(FieldDesc field, M } } - private void EncodeFieldBaseOffset(FieldDesc field, CORINFO_FIELD_INFO* pResult, MethodDesc callerMethod) + private void EncodeFieldBaseOffset(FieldDesc field, ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_FIELD_INFO* pResult, MethodDesc callerMethod) { TypeDesc pMT = field.OwningType; @@ -2471,7 +2557,7 @@ private void EncodeFieldBaseOffset(FieldDesc field, CORINFO_FIELD_INFO* pResult, if (pResult->offset > FieldFixupSignature.MaxCheckableOffset) throw new RequiresRuntimeJitException(callerMethod.ToString() + " -> " + field.ToString()); - AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(field)); + AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken))); // No-op other than generating the check field offset fixup } else @@ -2481,7 +2567,7 @@ private void EncodeFieldBaseOffset(FieldDesc field, CORINFO_FIELD_INFO* pResult, // ENCODE_FIELD_OFFSET pResult->offset = 0; pResult->fieldAccessor = CORINFO_FIELD_ACCESSOR.CORINFO_FIELD_INSTANCE_WITH_BASE; - pResult->fieldLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.FieldOffset(field)); + pResult->fieldLookup = CreateConstLookupToSymbol(_compilation.SymbolNodeFactory.FieldOffset(ComputeFieldWithToken(field, ref pResolvedToken))); } } else if (pMT.IsValueType) @@ -2490,7 +2576,7 @@ private void EncodeFieldBaseOffset(FieldDesc field, CORINFO_FIELD_INFO* pResult, { // ENCODE_CHECK_FIELD_OFFSET if (_compilation.CompilationModuleGroup.VersionsWithType(field.OwningType)) // Only verify versions with types - AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(field)); + AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken))); } // ENCODE_NONE } @@ -2500,14 +2586,14 @@ private void EncodeFieldBaseOffset(FieldDesc field, CORINFO_FIELD_INFO* pResult, { // ENCODE_CHECK_FIELD_OFFSET if (_compilation.CompilationModuleGroup.VersionsWithType(field.OwningType)) // Only verify versions with types - AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(field)); + AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken))); } // ENCODE_NONE } else if (TypeCannotUseBasePlusOffsetEncoding(pMT.BaseType as MetadataType)) { // ENCODE_CHECK_FIELD_OFFSET - AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(field)); + AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken))); // No-op other than generating the check field offset fixup } else @@ -2518,7 +2604,7 @@ private void EncodeFieldBaseOffset(FieldDesc field, CORINFO_FIELD_INFO* pResult, { // ENCODE_CHECK_FIELD_OFFSET if (_compilation.CompilationModuleGroup.VersionsWithType(field.OwningType)) // Only verify versions with types - AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(field)); + AddPrecodeFixup(_compilation.SymbolNodeFactory.CheckFieldOffset(ComputeFieldWithToken(field, ref pResolvedToken))); } // ENCODE_FIELD_BASE_OFFSET From cbe6514025ec9867dce188f46da9d3a656ff2204 Mon Sep 17 00:00:00 2001 From: Jose Perez Rodriguez Date: Wed, 10 Aug 2022 18:06:37 -0700 Subject: [PATCH 05/68] Adding cultureName to GeneratedRegexAttribute (#73490) --- .../gen/RegexGenerator.Parser.cs | 63 +++++++++-- .../gen/UpgradeToGeneratedRegexCodeFixer.cs | 50 ++++++++- .../ref/System.Text.RegularExpressions.cs | 3 + .../GeneratedRegexAttribute.cs | 38 ++++++- .../Text/RegularExpressions/RegexParser.cs | 20 +++- .../GeneratedRegexAttributeTests.cs | 40 +++++-- .../tests/FunctionalTests/MonoRegexTests.cs | 3 +- .../FunctionalTests/Regex.Groups.Tests.cs | 2 +- .../Regex.KnownPattern.Tests.cs | 2 +- .../FunctionalTests/Regex.Match.Tests.cs | 2 +- .../FunctionalTests/Regex.Tests.Common.cs | 12 ++- .../RegexGeneratorHelper.netcoreapp.cs | 10 +- .../RegexGeneratorHelper.netfx.cs | 5 +- .../RegexGeneratorParserTests.cs | 38 ++++++- .../FunctionalTests/RegexIgnoreCaseTests.cs | 3 +- .../tests/FunctionalTests/RegexPcreTests.cs | 3 +- .../UpgradeToGeneratedRegexAnalyzerTests.cs | 100 +++++++++++++++++- .../tests/UnitTests/RegexReductionTests.cs | 17 +++ 18 files changed, 362 insertions(+), 49 deletions(-) diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs index 7a472a994c9c99..e6e6e57a16e349 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs @@ -57,6 +57,7 @@ public partial class RegexGenerator string? pattern = null; int? options = null; int? matchTimeout = null; + string? cultureName = string.Empty; foreach (AttributeData attributeData in boundAttributes) { if (!SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, generatedRegexAttributeSymbol)) @@ -75,7 +76,7 @@ public partial class RegexGenerator } ImmutableArray items = attributeData.ConstructorArguments; - if (items.Length == 0 || items.Length > 3) + if (items.Length == 0 || items.Length > 4) { return Diagnostic.Create(DiagnosticDescriptors.InvalidGeneratedRegexAttribute, methodSyntax.GetLocation()); } @@ -85,9 +86,23 @@ public partial class RegexGenerator if (items.Length >= 2) { options = items[1].Value as int?; - if (items.Length == 3) + if (items.Length == 4) { matchTimeout = items[2].Value as int?; + cultureName = items[3].Value as string; + } + // If there are 3 parameters, we need to check if the third argument is + // int matchTimeoutMilliseconds, or string cultureName. + else if (items.Length == 3) + { + if (items[2].Type.SpecialType == SpecialType.System_Int32) + { + matchTimeout = items[2].Value as int?; + } + else + { + cultureName = items[2].Value as string; + } } } } @@ -97,7 +112,7 @@ public partial class RegexGenerator return null; } - if (pattern is null) + if (pattern is null || cultureName is null) { return Diagnostic.Create(DiagnosticDescriptors.InvalidRegexArguments, methodSyntax.GetLocation(), "(null)"); } @@ -113,14 +128,40 @@ public partial class RegexGenerator RegexOptions regexOptions = options is not null ? (RegexOptions)options : RegexOptions.None; - // TODO: This is going to include the culture that's current at the time of compilation. - // What should we do about that? We could: - // - say not specifying CultureInvariant is invalid if anything about options or the expression will look at culture - // - fall back to not generating source if it's not specified - // - just use whatever culture is present at build time - // - devise a new way of not using the culture present at build time - // - ... - CultureInfo culture = (regexOptions & RegexOptions.CultureInvariant) != 0 ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture; + // If RegexOptions.IgnoreCase was specified or the inline ignore case option `(?i)` is present in the pattern, then we will (in priority order): + // - If a culture name was passed in: + // - If RegexOptions.CultureInvariant was also passed in, then we emit a diagnostic due to the explicit conflict. + // - We try to initialize a culture using the passed in culture name to be used for case-sensitive comparisons. If + // the culture name is invalid, we'll emit a diagnostic. + // - Default to use Invariant Culture if no culture name was passed in. + CultureInfo culture = CultureInfo.InvariantCulture; + RegexOptions regexOptionsWithPatternOptions; + try + { + regexOptionsWithPatternOptions = regexOptions | RegexParser.ParseOptionsInPattern(pattern, regexOptions); + } + catch (Exception e) + { + return Diagnostic.Create(DiagnosticDescriptors.InvalidRegexArguments, methodSyntax.GetLocation(), e.Message); + } + + if ((regexOptionsWithPatternOptions & RegexOptions.IgnoreCase) != 0 && !string.IsNullOrEmpty(cultureName)) + { + if ((regexOptions & RegexOptions.CultureInvariant) != 0) + { + // User passed in both a culture name and set RegexOptions.CultureInvariant which causes an explicit conflict. + return Diagnostic.Create(DiagnosticDescriptors.InvalidRegexArguments, methodSyntax.GetLocation(), "cultureName"); + } + + try + { + culture = CultureInfo.GetCultureInfo(cultureName); + } + catch (CultureNotFoundException) + { + return Diagnostic.Create(DiagnosticDescriptors.InvalidRegexArguments, methodSyntax.GetLocation(), "cultureName"); + } + } // Validate the options const RegexOptions SupportedOptions = diff --git a/src/libraries/System.Text.RegularExpressions/gen/UpgradeToGeneratedRegexCodeFixer.cs b/src/libraries/System.Text.RegularExpressions/gen/UpgradeToGeneratedRegexCodeFixer.cs index 61fdc155a4b265..87d50364bb3d44 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/UpgradeToGeneratedRegexCodeFixer.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/UpgradeToGeneratedRegexCodeFixer.cs @@ -191,11 +191,34 @@ private static async Task ConvertToSourceGenerator(Document document, // Allow user to pick a different name for the method. newMethod = newMethod.ReplaceToken(newMethod.Identifier, SyntaxFactory.Identifier(methodName).WithAdditionalAnnotations(RenameAnnotation.Create())); + // We now need to check if we have to pass in the cultureName parameter. This parameter will be required in case the option + // RegexOptions.IgnoreCase is set for this Regex. To determine that, we first get the passed in options (if any), and then, + // we also need to parse the pattern in case there are options that were specified inside the pattern via the `(?i)` switch. + SyntaxNode? cultureNameValue = null; + RegexOptions regexOptions = regexOptionsValue is not null ? GetRegexOptionsFromArgument(operationArguments) : RegexOptions.None; + string pattern = GetRegexPatternFromArgument(operationArguments); + regexOptions |= RegexParser.ParseOptionsInPattern(pattern, regexOptions); + + // If the options include IgnoreCase and don't specify CultureInvariant then we will have to calculate the user's current culture in order to pass + // it in as a parameter. If the user specified IgnoreCase, but also selected CultureInvariant, then we skip as the default is to use Invariant culture. + if ((regexOptions & RegexOptions.IgnoreCase) != 0 && (regexOptions & RegexOptions.CultureInvariant) == 0) + { + // If CultureInvariant wasn't specified as options, we default to the current culture. + cultureNameValue = generator.LiteralExpression(CultureInfo.CurrentCulture.Name); + + // If options weren't passed in, then we need to define it as well in order to use the three parameter constructor. + if (regexOptionsValue is null) + { + regexOptionsValue = generator.MemberAccessExpression(SyntaxFactory.IdentifierName("RegexOptions"), "None"); + } + } + // Generate the GeneratedRegex attribute syntax node with the specified parameters. - SyntaxNode attributes = generator.Attribute(generator.TypeExpression(generatedRegexAttributeSymbol), attributeArguments: (patternValue, regexOptionsValue) switch + SyntaxNode attributes = generator.Attribute(generator.TypeExpression(generatedRegexAttributeSymbol), attributeArguments: (patternValue, regexOptionsValue, cultureNameValue) switch { - ({ }, null) => new[] { patternValue }, - ({ }, { }) => new[] { patternValue, regexOptionsValue }, + ({ }, null, null) => new[] { patternValue }, + ({ }, { }, null) => new[] { patternValue, regexOptionsValue }, + ({ }, { }, { }) => new[] { patternValue, regexOptionsValue, cultureNameValue }, _ => Array.Empty(), }); @@ -223,10 +246,29 @@ static IEnumerable GetAllMembers(ITypeSymbol? symbol) } } + static string GetRegexPatternFromArgument(ImmutableArray arguments) + { + IArgumentOperation? patternArgument = arguments.SingleOrDefault(arg => arg.Parameter.Name == UpgradeToGeneratedRegexAnalyzer.PatternArgumentName); + if (patternArgument is null) + { + return null; + } + + return patternArgument.Value.ConstantValue.Value as string; + } + + static RegexOptions GetRegexOptionsFromArgument(ImmutableArray arguments) + { + IArgumentOperation? optionsArgument = arguments.SingleOrDefault(arg => arg.Parameter.Name == UpgradeToGeneratedRegexAnalyzer.OptionsArgumentName); + + return optionsArgument is null ? RegexOptions.None : + (RegexOptions)(int)optionsArgument.Value.ConstantValue.Value; + } + // Helper method that looks generates the node for pattern argument or options argument. static SyntaxNode? GetNode(ImmutableArray arguments, SyntaxGenerator generator, string parameterName) { - var argument = arguments.SingleOrDefault(arg => arg.Parameter.Name == parameterName); + IArgumentOperation? argument = arguments.SingleOrDefault(arg => arg.Parameter.Name == parameterName); if (argument is null) { return null; diff --git a/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs b/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs index e400187b268ff5..29cd116d641b26 100644 --- a/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs +++ b/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs @@ -253,7 +253,10 @@ public sealed partial class GeneratedRegexAttribute : System.Attribute { public GeneratedRegexAttribute([System.Diagnostics.CodeAnalysis.StringSyntax(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.Regex)] string pattern) { } public GeneratedRegexAttribute([System.Diagnostics.CodeAnalysis.StringSyntax(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.Regex, "options")] string pattern, System.Text.RegularExpressions.RegexOptions options) { } + public GeneratedRegexAttribute([System.Diagnostics.CodeAnalysis.StringSyntax(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.Regex, "options")] string pattern, System.Text.RegularExpressions.RegexOptions options, string cultureName) { } public GeneratedRegexAttribute([System.Diagnostics.CodeAnalysis.StringSyntax(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.Regex, "options")] string pattern, System.Text.RegularExpressions.RegexOptions options, int matchTimeoutMilliseconds) { } + public GeneratedRegexAttribute([System.Diagnostics.CodeAnalysis.StringSyntax(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.Regex, "options")] string pattern, System.Text.RegularExpressions.RegexOptions options, int matchTimeoutMilliseconds, string cultureName) { } + public string CultureName { get; } public string Pattern { get; } public System.Text.RegularExpressions.RegexOptions Options { get; } public int MatchTimeoutMilliseconds { get; } diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/GeneratedRegexAttribute.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/GeneratedRegexAttribute.cs index 938f61d23eaa2b..cab35c27c1039b 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/GeneratedRegexAttribute.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/GeneratedRegexAttribute.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Threading; namespace System.Text.RegularExpressions; @@ -24,15 +25,47 @@ public GeneratedRegexAttribute([StringSyntax(StringSyntaxAttribute.Regex, nameof { } + /// Initializes a new instance of the with the specified pattern and options. + /// The regular expression pattern to match. + /// A bitwise combination of the enumeration values that modify the regular expression. + /// The name of a culture to be used for case sensitive comparisons. is not case-sensitive. + /// + /// For a list of predefined culture names on Windows systems, see the Language tag column in the list of + /// language/region names suported by Windows. Culture names follow the standard defined by BCP 47. In addition, + /// starting with Windows 10, can be any valid BCP-47 language tag. + /// + /// If is , the invariant culture will be used. + /// + public GeneratedRegexAttribute([StringSyntax(StringSyntaxAttribute.Regex, nameof(options))] string pattern, RegexOptions options, string cultureName) : this(pattern, options, Timeout.Infinite, cultureName) + { + } + + /// Initializes a new instance of the with the specified pattern, options, and timeout. + /// The regular expression pattern to match. + /// A bitwise combination of the enumeration values that modify the regular expression. + /// A time-out interval (milliseconds), or to indicate that the method should not time out. + public GeneratedRegexAttribute([StringSyntax(StringSyntaxAttribute.Regex, nameof(options))] string pattern, RegexOptions options, int matchTimeoutMilliseconds) : this(pattern, options, matchTimeoutMilliseconds, string.Empty /* Empty string means Invariant culture */) + { + } + /// Initializes a new instance of the with the specified pattern, options, and timeout. /// The regular expression pattern to match. /// A bitwise combination of the enumeration values that modify the regular expression. /// A time-out interval (milliseconds), or to indicate that the method should not time out. - public GeneratedRegexAttribute([StringSyntax(StringSyntaxAttribute.Regex, nameof(options))] string pattern, RegexOptions options, int matchTimeoutMilliseconds) + /// The name of a culture to be used for case sensitive comparisons. is not case-sensitive. + /// + /// For a list of predefined culture names on Windows systems, see the Language tag column in the list of + /// language/region names suported by Windows. Culture names follow the standard defined by BCP 47. In addition, + /// starting with Windows 10, can be any valid BCP-47 language tag. + /// + /// If is , the invariant culture will be used. + /// + public GeneratedRegexAttribute([StringSyntax(StringSyntaxAttribute.Regex, nameof(options))] string pattern, RegexOptions options, int matchTimeoutMilliseconds, string cultureName) { Pattern = pattern; Options = options; MatchTimeoutMilliseconds = matchTimeoutMilliseconds; + CultureName = cultureName; } /// Gets the regular expression pattern to match. @@ -43,4 +76,7 @@ public GeneratedRegexAttribute([StringSyntax(StringSyntaxAttribute.Regex, nameof /// Gets a time-out interval (milliseconds), or to indicate that the method should not time out. public int MatchTimeoutMilliseconds { get; } + + /// Gets the name of the culture to be used for case sensitive comparisons. + public string CultureName { get; } } diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexParser.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexParser.cs index 9b31906a5f2a2d..275f3f0c08b96f 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexParser.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexParser.cs @@ -84,11 +84,24 @@ private RegexParser(string pattern, RegexOptions options, CultureInfo culture, H internal static CultureInfo GetTargetCulture(RegexOptions options) => (options & RegexOptions.CultureInvariant) != 0 ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture; + public static RegexOptions ParseOptionsInPattern(string pattern, RegexOptions options) + { + using var parser = new RegexParser(pattern, options, CultureInfo.InvariantCulture, // since we won't perform case conversions, culture doesn't matter in this case. + new Hashtable(), 0, null, stackalloc int[OptionStackDefaultSize]); + + // We don't really need to Count the Captures, but this method will already do a quick + // pass through the pattern, and will scan the options found and return them as an out + // parameter, so we use that to get out the pattern inline options. + parser.CountCaptures(out RegexOptions foundOptionsInPattern); + parser.Reset(options); + return foundOptionsInPattern; + } + public static RegexTree Parse(string pattern, RegexOptions options, CultureInfo culture) { using var parser = new RegexParser(pattern, options, culture, new Hashtable(), 0, null, stackalloc int[OptionStackDefaultSize]); - parser.CountCaptures(); + parser.CountCaptures(out _); parser.Reset(options); RegexNode root = parser.ScanRegex(); @@ -1772,10 +1785,10 @@ private static RegexOptions OptionFromCode(char ch) => /// /// A prescanner for deducing the slots used for captures by doing a partial tokenization of the pattern. /// - private void CountCaptures() + private void CountCaptures(out RegexOptions optionsFoundInPattern) { NoteCaptureSlot(0, 0); - + optionsFoundInPattern = RegexOptions.None; _autocap = 1; while (CharsRight() > 0) @@ -1850,6 +1863,7 @@ private void CountCaptures() // get the options if it's an option construct (?cimsx-cimsx...) ScanOptions(); + optionsFoundInPattern |= _options; if (CharsRight() > 0) { diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/GeneratedRegexAttributeTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/GeneratedRegexAttributeTests.cs index 213dd053e826dc..1997be9e66d5aa 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/GeneratedRegexAttributeTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/GeneratedRegexAttributeTests.cs @@ -16,26 +16,44 @@ public void Ctor_Roundtrips(string pattern, RegexOptions options, int matchTimeo { GeneratedRegexAttribute a; - if (matchTimeoutMilliseconds == -1) + foreach (string cultureName in new string[] { string.Empty, "en-US" }) { - if (options == RegexOptions.None) + if (matchTimeoutMilliseconds == -1) { - a = new GeneratedRegexAttribute(pattern); + if (options == RegexOptions.None) + { + a = new GeneratedRegexAttribute(pattern); + Assert.Equal(pattern, a.Pattern); + Assert.Equal(RegexOptions.None, a.Options); + Assert.Equal(Timeout.Infinite, a.MatchTimeoutMilliseconds); + Assert.Equal(string.Empty, a.CultureName); + } + + a = new GeneratedRegexAttribute(pattern, options); + Assert.Equal(pattern, a.Pattern); + Assert.Equal(options, a.Options); + Assert.Equal(Timeout.Infinite, a.MatchTimeoutMilliseconds); + Assert.Equal(string.Empty, a.CultureName); + + a = new GeneratedRegexAttribute(pattern, options, cultureName); Assert.Equal(pattern, a.Pattern); - Assert.Equal(RegexOptions.None, a.Options); + Assert.Equal(options, a.Options); Assert.Equal(Timeout.Infinite, a.MatchTimeoutMilliseconds); + Assert.Equal(cultureName, a.CultureName); } - a = new GeneratedRegexAttribute(pattern, options); + a = new GeneratedRegexAttribute(pattern, options, matchTimeoutMilliseconds); Assert.Equal(pattern, a.Pattern); Assert.Equal(options, a.Options); - Assert.Equal(Timeout.Infinite, a.MatchTimeoutMilliseconds); - } + Assert.Equal(matchTimeoutMilliseconds, a.MatchTimeoutMilliseconds); + Assert.Equal(string.Empty, a.CultureName); - a = new GeneratedRegexAttribute(pattern, options, matchTimeoutMilliseconds); - Assert.Equal(pattern, a.Pattern); - Assert.Equal(options, a.Options); - Assert.Equal(matchTimeoutMilliseconds, a.MatchTimeoutMilliseconds); + a = new GeneratedRegexAttribute(pattern, options, matchTimeoutMilliseconds, cultureName); + Assert.Equal(pattern, a.Pattern); + Assert.Equal(options, a.Options); + Assert.Equal(matchTimeoutMilliseconds, a.MatchTimeoutMilliseconds); + Assert.Equal(cultureName, a.CultureName); + } } } } diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/MonoRegexTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/MonoRegexTests.cs index 878bae75c133d7..8aef06bae0cb21 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/MonoRegexTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/MonoRegexTests.cs @@ -9,6 +9,7 @@ // (c) 2002 using System.Collections.Generic; +using System.Globalization; using System.Linq; using Xunit; @@ -54,7 +55,7 @@ public static IEnumerable ValidateRegex_MemberData() { (string Pattern, RegexOptions Options, string Input, string Expected)[] allEngineCases = Cases(engine).ToArray(); - Regex[] results = RegexHelpers.GetRegexesAsync(engine, allEngineCases.Select(c => (c.Pattern, (RegexOptions?)c.Options, (TimeSpan?)null)).ToArray()).Result; + Regex[] results = RegexHelpers.GetRegexesAsync(engine, allEngineCases.Select(c => (c.Pattern, (CultureInfo?)null, (RegexOptions?)c.Options, (TimeSpan?)null)).ToArray()).Result; for (int i = 0; i < results.Length; i++) { string expected = allEngineCases[i].Expected; diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Groups.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Groups.Tests.cs index 126aa539bd8bab..ddee764c5e1324 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Groups.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Groups.Tests.cs @@ -953,7 +953,7 @@ public async Task Groups(RegexEngine engine, string cultureName, string pattern, Regex regex; try { - regex = await RegexHelpers.GetRegexAsync(engine, pattern, options); + regex = await RegexHelpers.GetRegexAsync(engine, pattern, options, CultureInfo.GetCultureInfo(cultureName)); } catch (Exception e) when (e is NotSupportedException or ArgumentOutOfRangeException && RegexHelpers.IsNonBacktracking(engine)) { diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.KnownPattern.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.KnownPattern.Tests.cs index 74f952c41fc1af..fa43702d2029be 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.KnownPattern.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.KnownPattern.Tests.cs @@ -1553,7 +1553,7 @@ public void PatternsDataSet_ConstructRegexForAll_SourceGenerated() Parallel.ForEach(s_patternsDataSet.Value.Chunk(50), chunk => { RegexHelpers.GetRegexesAsync(RegexEngine.SourceGenerated, - chunk.Select(r => (r.Pattern, (RegexOptions?)r.Options, (TimeSpan?)null)).ToArray()).GetAwaiter().GetResult(); + chunk.Select(r => (r.Pattern, (CultureInfo?)null, (RegexOptions?)r.Options, (TimeSpan?)null)).ToArray()).GetAwaiter().GetResult(); }); } #endif diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs index c6a7464efc8323..4478392f134762 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Match.Tests.cs @@ -21,7 +21,7 @@ public static IEnumerable Match_MemberData() foreach (RegexEngine engine in RegexHelpers.AvailableEngines) { (string Pattern, string Input, RegexOptions Options, int Beginning, int Length, bool ExpectedSuccess, string ExpectedValue)[] cases = Match_MemberData_Cases(engine).ToArray(); - Regex[] regexes = RegexHelpers.GetRegexesAsync(engine, cases.Select(c => (c.Pattern, (RegexOptions?)c.Options, (TimeSpan?)null)).ToArray()).Result; + Regex[] regexes = RegexHelpers.GetRegexesAsync(engine, cases.Select(c => (c.Pattern, (CultureInfo?)null, (RegexOptions?)c.Options, (TimeSpan?)null)).ToArray()).Result; for (int i = 0; i < regexes.Length; i++) { yield return new object[] { engine, cases[i].Pattern, cases[i].Input, cases[i].Options, regexes[i], cases[i].Beginning, cases[i].Length, cases[i].ExpectedSuccess, cases[i].ExpectedValue }; diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Tests.Common.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Tests.Common.cs index 9a571dcfc8bb71..97a683e1708d0b 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Tests.Common.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.Tests.Common.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using Xunit; @@ -51,6 +52,11 @@ public static bool IsDefaultStart(string input, RegexOptions options, int start) public static async Task GetRegexAsync(RegexEngine engine, [StringSyntax(StringSyntaxAttribute.Regex)] string pattern, RegexOptions options, Globalization.CultureInfo culture) { + if (engine == RegexEngine.SourceGenerated) + { + return await RegexGeneratorHelper.SourceGenRegexAsync(pattern, culture, options); + } + using (new System.Tests.ThreadCultureChange(culture)) { return await GetRegexAsync(engine, pattern, options); @@ -110,7 +116,7 @@ public static async Task GetRegexAsync(RegexEngine engine, [StringSyntax( if (engine == RegexEngine.SourceGenerated) { - return await RegexGeneratorHelper.SourceGenRegexAsync(pattern, options, matchTimeout); + return await RegexGeneratorHelper.SourceGenRegexAsync(pattern, null, options, matchTimeout); } // TODO-NONBACKTRACKING @@ -122,7 +128,7 @@ public static async Task GetRegexAsync(RegexEngine engine, [StringSyntax( new Regex(pattern, options.Value | OptionsFromEngine(engine), matchTimeout.Value); } - public static async Task GetRegexesAsync(RegexEngine engine, params (string pattern, RegexOptions? options, TimeSpan? matchTimeout)[] regexes) + public static async Task GetRegexesAsync(RegexEngine engine, params (string pattern, CultureInfo? culture, RegexOptions? options, TimeSpan? matchTimeout)[] regexes) { if (engine == RegexEngine.SourceGenerated) { @@ -135,7 +141,7 @@ public static async Task GetRegexesAsync(RegexEngine engine, params (st var results = new Regex[regexes.Length]; for (int i = 0; i < regexes.Length; i++) { - (string pattern, RegexOptions? options, TimeSpan? matchTimeout) = regexes[i]; + (string pattern, CultureInfo? culture, RegexOptions? options, TimeSpan? matchTimeout) = regexes[i]; results[i] = options is null ? new Regex(pattern, OptionsFromEngine(engine)) : matchTimeout is null ? new Regex(pattern, options.Value | OptionsFromEngine(engine)) : diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs index 5afe2490b3e538..d3a54c331f3d3e 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netcoreapp.cs @@ -108,14 +108,14 @@ internal static async Task> RunGenerator( } internal static async Task SourceGenRegexAsync( - string pattern, RegexOptions? options = null, TimeSpan? matchTimeout = null, CancellationToken cancellationToken = default) + string pattern, CultureInfo? culture, RegexOptions? options = null, TimeSpan? matchTimeout = null, CancellationToken cancellationToken = default) { - Regex[] results = await SourceGenRegexAsync(new[] { (pattern, options, matchTimeout) }, cancellationToken).ConfigureAwait(false); + Regex[] results = await SourceGenRegexAsync(new[] { (pattern, culture, options, matchTimeout) }, cancellationToken).ConfigureAwait(false); return results[0]; } internal static async Task SourceGenRegexAsync( - (string pattern, RegexOptions? options, TimeSpan? matchTimeout)[] regexes, CancellationToken cancellationToken = default) + (string pattern, CultureInfo? culture, RegexOptions? options, TimeSpan? matchTimeout)[] regexes, CancellationToken cancellationToken = default) { // Un-ifdef to compile each regex individually, which can be useful if one regex among thousands is causing a failure. // We compile them all en mass for test efficiency, but it can make it harder to debug a compilation failure in one of them. @@ -153,6 +153,10 @@ internal static async Task SourceGenRegexAsync( code.Append(string.Create(CultureInfo.InvariantCulture, $", {(int)regex.matchTimeout.Value.TotalMilliseconds}")); } } + if (regex.culture is not null) + { + code.Append($", {SymbolDisplay.FormatLiteral(regex.culture.Name, quote: true)}"); + } code.AppendLine($")] public static partial Regex Get{count}();"); count++; diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netfx.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netfx.cs index 2ceecb15795a48..c712b8e8f8e196 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netfx.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorHelper.netfx.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Globalization; using System.Threading; using System.Threading.Tasks; @@ -8,10 +9,10 @@ namespace System.Text.RegularExpressions.Tests { public sealed class RegexGeneratorHelper { - internal static Task SourceGenRegexAsync(string pattern, RegexOptions? options = null, TimeSpan? matchTimeout = null, CancellationToken cancellationToken = default) => + internal static Task SourceGenRegexAsync(string pattern, CultureInfo? culture, RegexOptions? options = null, TimeSpan? matchTimeout = null, CancellationToken cancellationToken = default) => throw new NotSupportedException(); - internal static Task SourceGenRegexAsync((string pattern, RegexOptions? options, TimeSpan? matchTimeout)[] regexes, CancellationToken cancellationToken = default) => + internal static Task SourceGenRegexAsync((string pattern, CultureInfo? culture, RegexOptions? options, TimeSpan? matchTimeout)[] regexes, CancellationToken cancellationToken = default) => throw new NotSupportedException(); } } diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs index 39968b9f5bad54..f9ccaaf568a382 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexGeneratorParserTests.cs @@ -120,6 +120,40 @@ partial class C Assert.Equal("SYSLIB1042", Assert.Single(diagnostics).Id); } + [Theory] + [InlineData("null")] + [InlineData("\"xxxxxxxxxxxxxxxxxxxx-ThisIsNotAValidCultureName-xxxxxxxxxxxxxxxxxxxx\"")] + public async Task Diagnostic_InvalidCultureName(string cultureName) + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@$" + using System.Text.RegularExpressions; + partial class C + {{ + [GeneratedRegex(""(?i)ab"", RegexOptions.None, {cultureName})] + private static partial Regex InvalidPattern(); + }} + "); + + Assert.Equal("SYSLIB1042", Assert.Single(diagnostics).Id); + } + + [Theory] + [InlineData("(?i)abc", "RegexOptions.CultureInvariant")] + [InlineData("abc", "RegexOptions.CultureInvariant | RegexOptions.IgnoreCase")] + public async Task Diagnostic_InvalidOptionsForCaseInsensitive(string pattern, string options) + { + IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@$" + using System.Text.RegularExpressions; + partial class C + {{ + [GeneratedRegex(""{pattern}"", {options}, ""en-US"")] + private static partial Regex InvalidPattern(); + }} + "); + + Assert.Equal("SYSLIB1042", Assert.Single(diagnostics).Id); + } + [Fact] public async Task Diagnostic_MethodMustReturnRegex() { @@ -289,13 +323,13 @@ public sealed class GeneratedRegexAttribute : Attribute } [Fact] - public async Task Diagnostic_CustomGeneratedRegexAttribute_FourArgCtor() + public async Task Diagnostic_CustomGeneratedRegexAttribute_FiveArgCtor() { IReadOnlyList diagnostics = await RegexGeneratorHelper.RunGenerator(@" using System.Text.RegularExpressions; partial class C { - [GeneratedRegex(""a"", RegexOptions.None, -1, ""b""] + [GeneratedRegex(""a"", RegexOptions.None, -1, ""en-Us"", ""b""] private static partial Regex InvalidCtor(); } diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexIgnoreCaseTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexIgnoreCaseTests.cs index b65723d88c92b2..6262bb3df1af41 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexIgnoreCaseTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexIgnoreCaseTests.cs @@ -77,8 +77,7 @@ public static IEnumerable Characters_With_Common_Lowercase_Match_Backr [MemberData(nameof(Characters_With_Common_Lowercase_Match_Data))] public async Task Characters_With_Common_Lowercase_Match(RegexEngine engine, string pattern, string input, string culture) { - using var _ = new ThreadCultureChange(culture); - Regex regex = await RegexHelpers.GetRegexAsync(engine, pattern, RegexOptions.IgnoreCase); + Regex regex = await RegexHelpers.GetRegexAsync(engine, pattern, RegexOptions.IgnoreCase, CultureInfo.GetCultureInfo(culture)); Assert.True(regex.IsMatch(input)); } diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexPcreTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexPcreTests.cs index 374283353ba494..9c0ac708ba8522 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexPcreTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexPcreTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Text; using System.Text.Unicode; @@ -25,7 +26,7 @@ public static IEnumerable PcreTestData() foreach (RegexEngine engine in RegexHelpers.AvailableEngines) { (string pattern, RegexOptions options, string input, bool expectedSuccess)[] cases = PcreTestData_Cases(engine).ToArray(); - Regex[] regexes = RegexHelpers.GetRegexesAsync(engine, cases.Select(c => (c.pattern, (RegexOptions?)c.options, (TimeSpan?)null)).ToArray()).Result; + Regex[] regexes = RegexHelpers.GetRegexesAsync(engine, cases.Select(c => (c.pattern, (CultureInfo?)null, (RegexOptions?)c.options, (TimeSpan?)null)).ToArray()).Result; for (int i = 0; i < regexes.Length; i++) { yield return new object[] { regexes[i], cases[i].input, cases[i].expectedSuccess }; diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/UpgradeToGeneratedRegexAnalyzerTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/UpgradeToGeneratedRegexAnalyzerTests.cs index ae734a7b8590d8..18c9e25b6717b5 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/UpgradeToGeneratedRegexAnalyzerTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/UpgradeToGeneratedRegexAnalyzerTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Text; using System.Text.RegularExpressions.Generator; @@ -107,7 +108,7 @@ partial class Program public static IEnumerable StaticInvocationWithTimeoutTestData() { - foreach(string method in new[] { "Count", "EnumerateMatches", "IsMatch", "Match", "Matches", "Split"}) + foreach (string method in new[] { "Count", "EnumerateMatches", "IsMatch", "Match", "Matches", "Split" }) { yield return new object[] { @"using System; using System.Text.RegularExpressions; @@ -559,7 +560,7 @@ public static void Main(string[] args) foreach (bool includeRegexOptions in new[] { true, false }) { - foreach (string methodName in new[] { "Count", "EnumerateMatches" , "IsMatch", "Match", "Matches", "Split" }) + foreach (string methodName in new[] { "Count", "EnumerateMatches", "IsMatch", "Match", "Matches", "Split" }) { if (includeRegexOptions) { @@ -922,6 +923,101 @@ public void Foo() await VerifyCS.VerifyCodeFixAsync(test, fixedSource); } + public static IEnumerable DetectsCurrentCultureTestData() + { + foreach (InvocationType invocationType in new[] { InvocationType.Constructor, InvocationType.StaticMethods }) + { + string isMatchInvocation = invocationType == InvocationType.Constructor ? @".IsMatch("""")" : string.Empty; + + foreach (bool useInlineIgnoreCase in new[] { true, false }) + { + string pattern = useInlineIgnoreCase ? "\"(?:(?>abc)(?:(?s)d|e)(?:(?:(?xi)ki)*))\"" : "\"abc\""; + string options = useInlineIgnoreCase ? "RegexOptions.None" : "RegexOptions.IgnoreCase"; + + // Test using current culture + yield return new object[] { @"using System.Text.RegularExpressions; + +public class Program +{ + public static void Main(string[] args) + { + var isMatch = [|" + ConstructRegexInvocation(invocationType, pattern, options) + @"|]" + isMatchInvocation + @"; + } +}", @"using System.Text.RegularExpressions; + +public partial class Program +{ + public static void Main(string[] args) + { + var isMatch = MyRegex().IsMatch(""""); + } + + [GeneratedRegex(" + $"{pattern}, {options}, \"{CultureInfo.CurrentCulture.Name}" + @""")] + private static partial Regex MyRegex(); +}" }; + + // Test using CultureInvariant which should default to the 2 parameter constructor + options = useInlineIgnoreCase ? "RegexOptions.CultureInvariant" : "RegexOptions.IgnoreCase | RegexOptions.CultureInvariant"; + yield return new object[] { @"using System.Text.RegularExpressions; + +public class Program +{ + public static void Main(string[] args) + { + var isMatch = [|" + ConstructRegexInvocation(invocationType, pattern, options) + @"|]" + isMatchInvocation + @"; + } +}", @"using System.Text.RegularExpressions; + +public partial class Program +{ + public static void Main(string[] args) + { + var isMatch = MyRegex().IsMatch(""""); + } + + [GeneratedRegex(" + $"{pattern}, {options}" + @")] + private static partial Regex MyRegex(); +}" }; + } + } + } + + public static IEnumerable NoOptionsCultureTestData() + { + foreach (InvocationType invocationType in new[] { InvocationType.Constructor, InvocationType.StaticMethods }) + { + string isMatchInvocation = invocationType == InvocationType.Constructor ? @".IsMatch("""")" : string.Empty; + + // Test no options passed in + yield return new object[] { @"using System.Text.RegularExpressions; + +public class Program +{ + public static void Main(string[] args) + { + var isMatch = [|" + ConstructRegexInvocation(invocationType, "\"(?i)abc\"") + @"|]" + isMatchInvocation + @"; + } +}", @"using System.Text.RegularExpressions; + +public partial class Program +{ + public static void Main(string[] args) + { + var isMatch = MyRegex().IsMatch(""""); + } + + [GeneratedRegex(" + $"\"(?i)abc\", RegexOptions.None, \"{CultureInfo.CurrentCulture.Name}" + @""")] + private static partial Regex MyRegex(); +}" }; + } + } + + [Theory] + [MemberData(nameof(DetectsCurrentCultureTestData))] + [MemberData(nameof(NoOptionsCultureTestData))] + public async Task DetectsCurrentCulture(string test, string fixedSource) + => await VerifyCS.VerifyCodeFixAsync(test, fixedSource); + #region Test helpers private static string ConstructRegexInvocation(InvocationType invocationType, string pattern, string? options = null) diff --git a/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexReductionTests.cs b/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexReductionTests.cs index 321ee1c65c8cf0..b21b26071bc2e6 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexReductionTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/UnitTests/RegexReductionTests.cs @@ -593,5 +593,22 @@ public void MinMaxLengthIsCorrect_HugeDepth() maxPossibleLength == 1 /* successfully analyzed */ || maxPossibleLength is null /* ran out of stack space to complete analysis */, $"Expected 1 or null, got {maxPossibleLength}"); } + + [Theory] + [InlineData("(?i)abc", RegexOptions.IgnoreCase)] + [InlineData("(?i)abc(?-i)", RegexOptions.IgnoreCase)] + [InlineData("(?:hello(nested(?:abc|(?:(?i:b)))))", RegexOptions.IgnoreCase)] + [InlineData("(?-i)abc", RegexOptions.None)] + [InlineData("(?mi)abc", RegexOptions.IgnoreCase | RegexOptions.Multiline)] + [InlineData("(?im)abc", RegexOptions.IgnoreCase | RegexOptions.Multiline)] + [InlineData("(?i)ab(?m)c", RegexOptions.IgnoreCase | RegexOptions.Multiline)] + [InlineData("(?xmi)abc", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline)] + [InlineData("(?s)abc", RegexOptions.Singleline)] + [InlineData("(?-simx)abc", RegexOptions.None)] + public void FoundOptionsInPatternIsCorrect(string pattern, RegexOptions expectedOptions) + { + RegexOptions foundOptions = RegexParser.ParseOptionsInPattern(pattern, RegexOptions.None); + Assert.Equal(expectedOptions, foundOptions); + } } } From 0736b64852e4e37e4e5efd20c5963e926a6ed473 Mon Sep 17 00:00:00 2001 From: Kunal Pathak Date: Wed, 10 Aug 2022 18:34:45 -0700 Subject: [PATCH 06/68] Disable gcstress on osx-x64 (#73732) --- eng/pipelines/coreclr/crossgen2-gcstress.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/eng/pipelines/coreclr/crossgen2-gcstress.yml b/eng/pipelines/coreclr/crossgen2-gcstress.yml index fc00d1d7b9a940..70a33503ece4f0 100644 --- a/eng/pipelines/coreclr/crossgen2-gcstress.yml +++ b/eng/pipelines/coreclr/crossgen2-gcstress.yml @@ -18,7 +18,8 @@ jobs: - Linux_x64 - Linux_arm64 - OSX_arm64 - - OSX_x64 + # See https://github.com/dotnet/runtime/issues/71931 + # - OSX_x64 - windows_x64 - windows_arm64 - CoreClrTestBuildHost # Either OSX_x64 or Linux_x64 @@ -43,7 +44,8 @@ jobs: - Linux_x64 - Linux_arm64 - OSX_arm64 - - OSX_x64 + # See https://github.com/dotnet/runtime/issues/71931 + # - OSX_x64 - windows_x64 - windows_arm64 jobParameters: From 37235c41ed211aa5a6c6fd05285bdb46b052703a Mon Sep 17 00:00:00 2001 From: Levi Broderick Date: Wed, 10 Aug 2022 18:37:39 -0700 Subject: [PATCH 07/68] Remove [Serializable] from OptionException (#73316) We are no longer introducing [Serializable] exception types unless the library targets both .NET and downlevel. (Serialization of exceptions doesn't work in wasm anyway.) This removes the stray annotation. --- src/mono/wasm/host/Options.cs | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/src/mono/wasm/host/Options.cs b/src/mono/wasm/host/Options.cs index d2f64bf287f9ff..3cdf71e2c6c319 100644 --- a/src/mono/wasm/host/Options.cs +++ b/src/mono/wasm/host/Options.cs @@ -169,9 +169,6 @@ using System.IO; #if PCL using System.Reflection; -#else -using System.Runtime.Serialization; -using System.Security.Permissions; #endif using System.Text; using System.Text.RegularExpressions; @@ -766,9 +763,6 @@ public override bool GetArguments(string value, out IEnumerable replacem } #endif -#if !PCL - [Serializable] -#endif public class OptionException : Exception { private string option; @@ -789,29 +783,10 @@ public OptionException(string message, string optionName, Exception innerExcepti this.option = optionName; } -#if !PCL - protected OptionException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - this.option = info.GetString("OptionName"); - } -#endif - public string OptionName { get { return this.option; } } - -#if !PCL -#pragma warning disable 618 // SecurityPermissionAttribute is obsolete - // [SecurityPermission(SecurityAction.LinkDemand, SerializationFormatter = true)] -#pragma warning restore 618 - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - base.GetObjectData(info, context); - info.AddValue("OptionName", option); - } -#endif } public delegate void OptionAction(TKey key, TValue value); From fc06697c933e44af31720f19eabb3e9ae76b37c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 11 Aug 2022 10:44:39 +0900 Subject: [PATCH 08/68] Get rid of more .NET Nativisms (#73612) --- .../Core/Execution/ExecutionDomain.cs | 28 +-- .../Reflection/Core/ReflectionDomainSetup.cs | 2 - .../NonPortable/CustomAttributeSearcher.cs | 19 +- .../src/MatchingRefApiCompatBaseline.txt | 2 - .../src/System.Private.CoreLib.csproj | 3 - .../Reflection/MissingMetadataException.cs | 19 -- .../BindingFlagSupport/QueriedMemberList.cs | 20 +- .../NativeFormatCustomAttributeData.cs | 12 +- .../RuntimeCustomAttributeData.cs | 59 ++--- .../RuntimePseudoCustomAttributeData.cs | 8 - .../Runtime/EventInfos/RuntimeEventInfo.cs | 2 +- .../NativeFormatRuntimeFieldInfo.cs | 4 +- .../Runtime/General/QSignatureTypeHandle.cs | 26 -- .../Runtime/General/ToStringUtils.cs | 39 --- .../General/TypeResolver.NativeFormat.cs | 4 +- .../Reflection/Runtime/General/TypeUnifier.cs | 37 --- .../MethodInfos/RuntimeMethodHelpers.cs | 7 +- .../MethodInfos/RuntimeNamedMethodInfo.cs | 2 +- .../RuntimeMethodParameterInfo.cs | 8 - .../ParameterInfos/RuntimeParameterInfo.cs | 6 +- .../RuntimePropertyIndexParameterInfo.cs | 8 - .../RuntimeSyntheticParameterInfo.cs | 8 - .../PropertyInfos/RuntimePropertyInfo.cs | 3 +- ...veFormatRuntimeGenericParameterTypeInfo.cs | 11 +- .../NativeFormatRuntimeNamedTypeInfo.cs | 11 +- .../TypeInfos/RuntimeBlockedTypeInfo.cs | 11 +- .../Runtime/TypeInfos/RuntimeClsIdTypeInfo.cs | 3 +- .../RuntimeConstructedGenericTypeInfo.cs | 28 +-- .../RuntimeGenericParameterTypeInfo.cs | 2 - .../TypeInfos/RuntimeHasElementTypeInfo.cs | 11 +- .../Runtime/TypeInfos/RuntimeNamedTypeInfo.cs | 2 - .../RuntimeNoMetadataNamedTypeInfo.cs | 222 ------------------ .../RuntimeTypeInfo.CoreGetDeclared.cs | 8 - .../Runtime/TypeInfos/RuntimeTypeInfo.cs | 22 +- .../src/System/RuntimeExceptionHelpers.cs | 2 +- .../src/System/RuntimeType.cs | 2 +- .../src/System/Type.Internal.cs | 97 ++------ ...EnvironmentImplementation.MappingTables.cs | 2 +- .../DiagnosticMappingTables.cs | 79 ++----- .../MissingMetadataExceptionCreator.cs | 168 +++---------- .../ReflectionDomainSetupImplementation.cs | 12 +- ...nExecutionDomainCallbacksImplementation.cs | 4 +- .../DelegateMethodInfoRetriever.cs | 8 +- .../TypeLoaderEnvironment.Metadata.cs | 2 +- .../SmokeTests/Reflection/Reflection.cs | 6 +- 45 files changed, 151 insertions(+), 888 deletions(-) delete mode 100644 src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/MissingMetadataException.cs delete mode 100644 src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ToStringUtils.cs delete mode 100644 src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeNoMetadataNamedTypeInfo.cs diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionDomain.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionDomain.cs index 02f6a206015db2..85199ce870ed09 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionDomain.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionDomain.cs @@ -4,6 +4,7 @@ using System; using System.Reflection; using System.Collections.Generic; +using System.Diagnostics; using System.Reflection.Runtime.General; using System.Reflection.Runtime.TypeInfos; using System.Reflection.Runtime.TypeInfos.NativeFormat; @@ -206,14 +207,8 @@ public Type GetNamedTypeForHandle(RuntimeTypeHandle typeHandle, bool isGenericTy } else { - if (ExecutionEnvironment.IsReflectionBlocked(typeHandle)) - { - return RuntimeBlockedTypeInfo.GetRuntimeBlockedTypeInfo(typeHandle, isGenericTypeDefinition); - } - else - { - return RuntimeNoMetadataNamedTypeInfo.GetRuntimeNoMetadataNamedTypeInfo(typeHandle, isGenericTypeDefinition); - } + Debug.Assert(ExecutionEnvironment.IsReflectionBlocked(typeHandle)); + return RuntimeBlockedTypeInfo.GetRuntimeBlockedTypeInfo(typeHandle, isGenericTypeDefinition); } } @@ -285,23 +280,13 @@ public Type GetConstructedGenericTypeForHandle(RuntimeTypeHandle typeHandle) } //======================================================================================= - // MissingMetadataExceptions. + // Missing metadata exceptions. //======================================================================================= public Exception CreateMissingMetadataException(Type? pertainant) { return this.ReflectionDomainSetup.CreateMissingMetadataException(pertainant); } - public Exception CreateMissingMetadataException(TypeInfo? pertainant) - { - return this.ReflectionDomainSetup.CreateMissingMetadataException(pertainant); - } - - public Exception CreateMissingMetadataException(TypeInfo pertainant, string nestedTypeName) - { - return this.ReflectionDomainSetup.CreateMissingMetadataException(pertainant, nestedTypeName); - } - public Exception CreateNonInvokabilityException(MemberInfo pertainant) { return this.ReflectionDomainSetup.CreateNonInvokabilityException(pertainant); @@ -336,16 +321,13 @@ public bool SupportsReflection(Type type) if (type is not RuntimeType) return false; - RuntimeTypeInfo runtimeType = type.CastToRuntimeTypeInfo(); - if (null == runtimeType.InternalNameIfAvailable) - return false; - if (ExecutionEnvironment.IsReflectionBlocked(type.TypeHandle)) { // The type is an internal framework type and is blocked from reflection return false; } + RuntimeTypeInfo runtimeType = type.CastToRuntimeTypeInfo(); if (runtimeType.InternalFullNameOfAssembly == Internal.Runtime.Augments.RuntimeAugments.HiddenScopeAssemblyName) { // The type is an internal framework type but is reflectable for internal class library use diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/ReflectionDomainSetup.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/ReflectionDomainSetup.cs index 17bf45902fdca3..dfb955cf69139d 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/ReflectionDomainSetup.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/ReflectionDomainSetup.cs @@ -14,9 +14,7 @@ public abstract class ReflectionDomainSetup { protected ReflectionDomainSetup() { } public abstract AssemblyBinder AssemblyBinder { get; } - public abstract Exception CreateMissingMetadataException(TypeInfo? pertainant); public abstract Exception CreateMissingMetadataException(Type? pertainant); - public abstract Exception CreateMissingMetadataException(TypeInfo pertainant, string nestedTypeName); public abstract Exception CreateNonInvokabilityException(MemberInfo pertainant); public abstract Exception CreateMissingArrayTypeException(Type elementType, bool isMultiDim, int rank); public abstract Exception CreateMissingConstructedGenericTypeException(Type genericTypeDefinition, Type[] genericTypeArguments); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Extensions/NonPortable/CustomAttributeSearcher.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Extensions/NonPortable/CustomAttributeSearcher.cs index c01d470b258a6f..ace00c0923f7bd 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Extensions/NonPortable/CustomAttributeSearcher.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Extensions/NonPortable/CustomAttributeSearcher.cs @@ -45,24 +45,7 @@ public IEnumerable GetMatchingCustomAttributes(E element, T optionalAttributeTypeFilter.IsSubclassOf(typeof(Attribute)))) throw new ArgumentException(SR.Argument_MustHaveAttributeBaseClass); - try - { - typeFilterKnownToBeSealed = optionalAttributeTypeFilter.IsSealed; - } - catch (MissingMetadataException) - { - // If we got here, the custom attribute type itself was not opted into metadata. This can and does happen in the real world when an app - // contains a check for custom attributes that never actually appear on any entity within the app. - // - // Since "typeFilterKnownToBeSealed" is only used to enable an optimization, it's always safe to leave it "false". - // - // Because the NativeAOT toolchain removes any custom attribute that refuses to opt into metadata so at this point, - // we could simply return an empty enumeration and "be correct." However, the code paths following this already do that naturally. - // (i.e. the "passFilter" will never return true, thus we will never attempt to query the custom attribute type for its - // own AttributeUsage custom attribute.) If the toolchain behavior changes in the future, it's preferable that - // this shows up as new MissingMetadataExceptions rather than incorrect results from the api so we will not put - // in an explicit return here. - } + typeFilterKnownToBeSealed = optionalAttributeTypeFilter.IsSealed; } Func passesFilter; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/MatchingRefApiCompatBaseline.txt b/src/coreclr/nativeaot/System.Private.CoreLib/src/MatchingRefApiCompatBaseline.txt index 666219875bb7cb..5a44b79d56903d 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/MatchingRefApiCompatBaseline.txt +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/MatchingRefApiCompatBaseline.txt @@ -209,7 +209,6 @@ TypesMustExist : Type 'System.MDArray' does not exist in the reference but it do MembersMustExist : Member 'public void System.ModuleHandle..ctor(System.Reflection.Module)' does not exist in the reference but it does exist in the implementation. TypesMustExist : Type 'System.RuntimeExceptionHelpers' does not exist in the reference but it does exist in the implementation. TypesMustExist : Type 'System.RuntimeType' does not exist in the reference but it does exist in the implementation. -MembersMustExist : Member 'public System.String System.Type.InternalNameIfAvailable.get()' does not exist in the reference but it does exist in the implementation. MembersMustExist : Member 'public System.Boolean System.TypedReference.IsNull.get()' does not exist in the reference but it does exist in the implementation. TypesMustExist : Type 'System.Diagnostics.DebugAnnotations' does not exist in the reference but it does exist in the implementation. TypesMustExist : Type 'System.Diagnostics.DebuggerGuidedStepThroughAttribute' does not exist in the reference but it does exist in the implementation. @@ -229,7 +228,6 @@ TypesMustExist : Type 'System.Reflection.DynamicInvokeInfo' does not exist in th TypesMustExist : Type 'System.Reflection.EnumInfo' does not exist in the reference but it does exist in the implementation. MembersMustExist : Member 'public System.Reflection.ParameterInfo[] System.Reflection.MethodBase.GetParametersNoCopy()' does not exist in the reference but it does exist in the implementation. MembersMustExist : Member 'public System.Reflection.MethodBase System.Reflection.MethodBase.MetadataDefinitionMethod.get()' does not exist in the reference but it does exist in the implementation. -TypesMustExist : Type 'System.Reflection.MissingMetadataException' does not exist in the reference but it does exist in the implementation. MembersMustExist : Member 'protected System.ModuleHandle System.Reflection.Module.GetModuleHandleImpl()' does not exist in the reference but it does exist in the implementation. TypesMustExist : Type 'System.Reflection.RuntimeAssembly' does not exist in the reference but it does exist in the implementation. TypesMustExist : Type 'System.Reflection.RuntimeAssemblyName' does not exist in the reference but it does exist in the implementation. diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 3a113147f4eac1..e84f5ed4947036 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -186,7 +186,6 @@ - @@ -461,7 +460,6 @@ - @@ -522,7 +520,6 @@ - diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/MissingMetadataException.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/MissingMetadataException.cs deleted file mode 100644 index 51c5acd7d3905d..00000000000000 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/MissingMetadataException.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; - -namespace System.Reflection -{ - public sealed class MissingMetadataException : TypeAccessException - { - public MissingMetadataException() - { - } - - public MissingMetadataException(string message) - : base(message) - { - } - } -} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/BindingFlagSupport/QueriedMemberList.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/BindingFlagSupport/QueriedMemberList.cs index 427d00b8d506b8..ed5cabdba05f1a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/BindingFlagSupport/QueriedMemberList.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/BindingFlagSupport/QueriedMemberList.cs @@ -29,24 +29,21 @@ private QueriedMemberList() _allFlagsThatMustMatch = new BindingFlags[Grow]; } - private QueriedMemberList(int totalCount, int declaredOnlyCount, M[] members, BindingFlags[] allFlagsThatMustMatch, RuntimeTypeInfo typeThatBlockedBrowsing) + private QueriedMemberList(int totalCount, int declaredOnlyCount, M[] members, BindingFlags[] allFlagsThatMustMatch) { _totalCount = totalCount; _declaredOnlyCount = declaredOnlyCount; _members = members; _allFlagsThatMustMatch = allFlagsThatMustMatch; - _typeThatBlockedBrowsing = typeThatBlockedBrowsing; } /// - /// Returns the # of candidates for a non-DeclaredOnly search. Caution: Can throw MissingMetadataException. Use DeclaredOnlyCount if you don't want to search base classes. + /// Returns the # of candidates for a non-DeclaredOnly search. Use DeclaredOnlyCount if you don't want to search base classes. /// public int TotalCount { get { - if (_typeThatBlockedBrowsing != null) - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(_typeThatBlockedBrowsing); return _totalCount; } } @@ -93,7 +90,7 @@ public QueriedMemberList Filter(Func predicate) } } - return new QueriedMemberList(newTotalCount, newDeclaredOnlyCount, newMembers, newAllFlagsThatMustMatch, _typeThatBlockedBrowsing); + return new QueriedMemberList(newTotalCount, newDeclaredOnlyCount, newMembers, newAllFlagsThatMustMatch); } // @@ -151,16 +148,6 @@ public static QueriedMemberList Create(RuntimeTypeInfo type, string optionalN } type = type.BaseType.CastToRuntimeTypeInfo(); - - if (type != null && !type.CanBrowseWithoutMissingMetadataExceptions) - { - // If we got here, one of the base classes is missing metadata. We don't want to throw a MissingMetadataException now because we may be - // building a cached result for a caller who passed BindingFlags.DeclaredOnly. So we'll mark the results in a way that - // it will throw a MissingMetadataException if a caller attempts to iterate past the declared-only subset. - queriedMembers._typeThatBlockedBrowsing = type; - queriedMembers._totalCount = queriedMembers._declaredOnlyCount; - break; - } } return queriedMembers; @@ -196,7 +183,6 @@ private void Add(M member, BindingFlags allFlagsThatMustMatch) private int _declaredOnlyCount; // # of entries for members only in the most derived class. private M[] _members; // Length is equal to or greater than _totalCount. Entries beyond _totalCount contain null or garbage and should be read. private BindingFlags[] _allFlagsThatMustMatch; // Length will be equal to _members.Length - private RuntimeTypeInfo _typeThatBlockedBrowsing; // If non-null, one of the base classes was missing metadata. private const int Grow = 64; } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/NativeFormat/NativeFormatCustomAttributeData.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/NativeFormat/NativeFormatCustomAttributeData.cs index f564f311cc7454..07801d02a50f52 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/NativeFormat/NativeFormatCustomAttributeData.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/NativeFormat/NativeFormatCustomAttributeData.cs @@ -92,16 +92,8 @@ public sealed override ConstructorInfo Constructor } } - internal sealed override string AttributeTypeString - { - get - { - return new QTypeDefRefOrSpec(_reader, _customAttribute.GetAttributeTypeHandle(_reader)).FormatTypeName(new TypeContext(null, null)); - } - } - // - // If throwIfMissingMetadata is false, returns null rather than throwing a MissingMetadataException. + // If throwIfMissingMetadata is false, returns null rather than throwing a missing metadata exception. // internal sealed override IList GetConstructorArguments(bool throwIfMissingMetadata) { @@ -158,7 +150,7 @@ internal sealed override IList GetConstructorArgum } // - // If throwIfMissingMetadata is false, returns null rather than throwing a MissingMetadataException. + // If throwIfMissingMetadata is false, returns null rather than throwing a missing metadata exception. // internal sealed override IList GetNamedArguments(bool throwIfMissingMetadata) { diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/RuntimeCustomAttributeData.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/RuntimeCustomAttributeData.cs index 2b964c6b0b5605..26b43b401491f7 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/RuntimeCustomAttributeData.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/RuntimeCustomAttributeData.cs @@ -37,39 +37,32 @@ public sealed override IList NamedArguments public sealed override string ToString() { - try - { - string ctorArgs = ""; - IList constructorArguments = GetConstructorArguments(throwIfMissingMetadata: false); - if (constructorArguments == null) - return LastResortToString; - for (int i = 0; i < constructorArguments.Count; i++) - ctorArgs += string.Format(i == 0 ? "{0}" : ", {0}", ComputeTypedArgumentString(constructorArguments[i], typed: false)); - - string namedArgs = ""; - IList namedArguments = GetNamedArguments(throwIfMissingMetadata: false); - if (namedArguments == null) - return LastResortToString; - for (int i = 0; i < namedArguments.Count; i++) - { - CustomAttributeNamedArgument namedArgument = namedArguments[i]; - - // Legacy: Desktop sets "typed" to "namedArgument.ArgumentType != typeof(Object)" - on Project N, this property is not available - // (nor conveniently computable as it's not captured in the Project N metadata.) The only consequence is that for - // the rare case of fields and properties typed "Object", we won't decorate the argument value with its actual type name. - bool typed = true; - namedArgs += string.Format( - i == 0 && ctorArgs.Length == 0 ? "{0} = {1}" : ", {0} = {1}", - namedArgument.MemberName, - ComputeTypedArgumentString(namedArgument.TypedValue, typed)); - } + string ctorArgs = ""; + IList constructorArguments = GetConstructorArguments(throwIfMissingMetadata: false); + if (constructorArguments == null) + return LastResortToString; + for (int i = 0; i < constructorArguments.Count; i++) + ctorArgs += string.Format(i == 0 ? "{0}" : ", {0}", ComputeTypedArgumentString(constructorArguments[i], typed: false)); - return string.Format("[{0}({1}{2})]", AttributeTypeString, ctorArgs, namedArgs); - } - catch (MissingMetadataException) - { + string namedArgs = ""; + IList namedArguments = GetNamedArguments(throwIfMissingMetadata: false); + if (namedArguments == null) return LastResortToString; + for (int i = 0; i < namedArguments.Count; i++) + { + CustomAttributeNamedArgument namedArgument = namedArguments[i]; + + // Legacy: Desktop sets "typed" to "namedArgument.ArgumentType != typeof(Object)" - on Project N, this property is not available + // (nor conveniently computable as it's not captured in the Project N metadata.) The only consequence is that for + // the rare case of fields and properties typed "Object", we won't decorate the argument value with its actual type name. + bool typed = true; + namedArgs += string.Format( + i == 0 && ctorArgs.Length == 0 ? "{0} = {1}" : ", {0} = {1}", + namedArgument.MemberName, + ComputeTypedArgumentString(namedArgument.TypedValue, typed)); } + + return string.Format("[{0}({1}{2})]", AttributeType.FormatTypeNameForReflection(), ctorArgs, namedArgs); } protected static ConstructorInfo ResolveAttributeConstructor( @@ -95,15 +88,13 @@ protected static ConstructorInfo ResolveAttributeConstructor( throw new MissingMethodException(); } - internal abstract string AttributeTypeString { get; } - // - // If throwIfMissingMetadata is false, returns null rather than throwing a MissingMetadataException. + // If throwIfMissingMetadata is false, returns null rather than throwing a missing metadata exception. // internal abstract IList GetConstructorArguments(bool throwIfMissingMetadata); // - // If throwIfMissingMetadata is false, returns null rather than throwing a MissingMetadataException. + // If throwIfMissingMetadata is false, returns null rather than throwing a missing metadata exception. // internal abstract IList GetNamedArguments(bool throwIfMissingMetadata); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/RuntimePseudoCustomAttributeData.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/RuntimePseudoCustomAttributeData.cs index 84e8358f42ab87..a055fd068214de 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/RuntimePseudoCustomAttributeData.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/CustomAttributes/RuntimePseudoCustomAttributeData.cs @@ -51,14 +51,6 @@ public sealed override ConstructorInfo Constructor } } - internal sealed override string AttributeTypeString - { - get - { - return _attributeType.FormatTypeNameForReflection(); - } - } - internal sealed override IList GetConstructorArguments(bool throwIfMissingMetadata) { return _constructorArguments; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/EventInfos/RuntimeEventInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/EventInfos/RuntimeEventInfo.cs index bb46778ed15d46..da4d3743253592 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/EventInfos/RuntimeEventInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/EventInfos/RuntimeEventInfo.cs @@ -115,7 +115,7 @@ public sealed override string ToString() if (parameters.Length == 0) throw new InvalidOperationException(); // Legacy: Why is a ToString() intentionally throwing an exception? RuntimeParameterInfo runtimeParameterInfo = (RuntimeParameterInfo)(parameters[0]); - return runtimeParameterInfo.ParameterTypeString + " " + this.Name; + return runtimeParameterInfo.ParameterType.FormatTypeNameForReflection() + " " + this.Name; } protected RuntimeEventInfo WithDebugName() diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/FieldInfos/NativeFormat/NativeFormatRuntimeFieldInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/FieldInfos/NativeFormat/NativeFormatRuntimeFieldInfo.cs index 83835e4ce9e1b2..d60162242750f4 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/FieldInfos/NativeFormat/NativeFormatRuntimeFieldInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/FieldInfos/NativeFormat/NativeFormatRuntimeFieldInfo.cs @@ -89,9 +89,7 @@ protected sealed override string MetadataName public sealed override string ToString() { - TypeContext typeContext = _contextTypeInfo.TypeContext; - Handle typeHandle = _field.Signature.GetFieldSignature(_reader).Type; - return (new QTypeDefRefOrSpec(_reader, typeHandle).FormatTypeName(typeContext)) + " " + this.Name; + return FieldRuntimeType.FormatTypeNameForReflection() + " " + this.Name; } public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/QSignatureTypeHandle.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/QSignatureTypeHandle.cs index 59925d98a73fd8..116249d661f409 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/QSignatureTypeHandle.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/QSignatureTypeHandle.cs @@ -61,31 +61,5 @@ internal Type[] GetCustomModifiers(TypeContext typeContext, bool optional) return _handle.GetCustomModifiers((global::Internal.Metadata.NativeFormat.MetadataReader)Reader, typeContext, optional); #endif } - - // - // This is a port of the desktop CLR's RuntimeType.FormatTypeName() routine. This routine is used by various Reflection ToString() methods - // to display the name of a type. Do not use for any other purpose as it inherits some pretty quirky desktop behavior. - // - internal string FormatTypeName(TypeContext typeContext) - { - try - { - // Though we wrap this in a try-catch as a failsafe, this code must still strive to avoid triggering MissingMetadata exceptions - // (non-error exceptions are very annoying when debugging.) - - Exception? exception = null; - RuntimeTypeInfo? runtimeType = TryResolve(typeContext, ref exception); - if (runtimeType == null) - return Type.DefaultTypeNameWhenMissingMetadata; - - // Because this runtimeType came from a successful TryResolve() call, it is safe to querying the TypeInfo's of the type and its component parts. - // If we're wrong, we do have the safety net of a try-catch. - return runtimeType.FormatTypeNameForReflection(); - } - catch (Exception) - { - return Type.DefaultTypeNameWhenMissingMetadata; - } - } } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ToStringUtils.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ToStringUtils.cs deleted file mode 100644 index 0a9ff9c82f83aa..00000000000000 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/ToStringUtils.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Reflection.Runtime.TypeInfos; -using Internal.Reflection.Core.Execution; - -namespace System.Reflection.Runtime.General -{ - internal static class ToStringUtils - { - // - // This is a port of the desktop CLR's RuntimeType.FormatTypeName() routine. This routine is used by various Reflection ToString() methods - // to display the name of a type. Do not use for any other purpose as it inherits some pretty quirky desktop behavior. - // - // The Project N version takes a raw metadata handle rather than a completed type so that it remains robust in the face of missing metadata. - // - public static string FormatTypeName(this QTypeDefRefOrSpec qualifiedTypeHandle, TypeContext typeContext) - { - try - { - // Though we wrap this in a try-catch as a failsafe, this code must still strive to avoid triggering MissingMetadata exceptions - // (non-error exceptions are very annoying when debugging.) - - Exception? exception = null; - RuntimeTypeInfo runtimeType = qualifiedTypeHandle.TryResolve(typeContext, ref exception); - if (runtimeType == null) - return Type.DefaultTypeNameWhenMissingMetadata; - - // Because this runtimeType came from a successful TryResolve() call, it is safe to querying the TypeInfo's of the type and its component parts. - // If we're wrong, we do have the safety net of a try-catch. - return runtimeType.FormatTypeNameForReflection(); - } - catch (Exception) - { - return Type.DefaultTypeNameWhenMissingMetadata; - } - } - } -} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/TypeResolver.NativeFormat.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/TypeResolver.NativeFormat.cs index 838f6a1f6dfa11..1979b411cabe7e 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/TypeResolver.NativeFormat.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/TypeResolver.NativeFormat.cs @@ -180,7 +180,7 @@ internal static RuntimeTypeInfo ResolveTypeDefinition(this TypeDefinitionHandle RuntimeTypeInfo? outerType = parent.ToTypeReferenceHandle(reader).TryResolveTypeReference(reader, ref exception); if (outerType == null) return null; - outerTypeInfo = outerType; // Since we got to outerType via a metadata reference, we're assured GetTypeInfo() won't throw a MissingMetadataException. + outerTypeInfo = outerType; // Since we got to outerType via a metadata reference, we're assured GetTypeInfo() won't throw a missing metadata exception. } if (outerTypeInfo != null) { @@ -188,7 +188,7 @@ internal static RuntimeTypeInfo ResolveTypeDefinition(this TypeDefinitionHandle TypeInfo? resolvedTypeInfo = outerTypeInfo.GetDeclaredNestedType(name); if (resolvedTypeInfo == null) { - exception = ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(outerTypeInfo, name); + exception = Helpers.CreateTypeLoadException(outerTypeInfo.FullName + "+" + name, outerTypeInfo.Assembly); return null; } return resolvedTypeInfo.CastToRuntimeTypeInfo(); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/TypeUnifier.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/TypeUnifier.cs index cdc37f01a1b53d..1a42512ea4afe5 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/TypeUnifier.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/General/TypeUnifier.cs @@ -156,43 +156,6 @@ public static RuntimeTypeInfo GetConstructedGenericType(this RuntimeTypeInfo gen namespace System.Reflection.Runtime.TypeInfos { - //----------------------------------------------------------------------------------------------------------- - // TypeInfos for type definitions (i.e. "Foo" and "Foo<>" but not "Foo") that aren't opted into metadata. - //----------------------------------------------------------------------------------------------------------- - internal sealed partial class RuntimeNoMetadataNamedTypeInfo - { - internal static RuntimeNoMetadataNamedTypeInfo GetRuntimeNoMetadataNamedTypeInfo(RuntimeTypeHandle typeHandle, bool isGenericTypeDefinition) - { - RuntimeNoMetadataNamedTypeInfo type; - if (isGenericTypeDefinition) - type = GenericNoMetadataNamedTypeTable.Table.GetOrAdd(new RuntimeTypeHandleKey(typeHandle)); - else - type = NoMetadataNamedTypeTable.Table.GetOrAdd(new RuntimeTypeHandleKey(typeHandle)); - type.EstablishDebugName(); - return type; - } - - private sealed class NoMetadataNamedTypeTable : ConcurrentUnifierW - { - protected sealed override RuntimeNoMetadataNamedTypeInfo Factory(RuntimeTypeHandleKey key) - { - return new RuntimeNoMetadataNamedTypeInfo(key.TypeHandle, isGenericTypeDefinition: false); - } - - public static readonly NoMetadataNamedTypeTable Table = new NoMetadataNamedTypeTable(); - } - - private sealed class GenericNoMetadataNamedTypeTable : ConcurrentUnifierW - { - protected sealed override RuntimeNoMetadataNamedTypeInfo Factory(RuntimeTypeHandleKey key) - { - return new RuntimeNoMetadataNamedTypeInfo(key.TypeHandle, isGenericTypeDefinition: true); - } - - public static readonly GenericNoMetadataNamedTypeTable Table = new GenericNoMetadataNamedTypeTable(); - } - } - //----------------------------------------------------------------------------------------------------------- // TypeInfos that represent type definitions (i.e. Foo or Foo<>) or constructed generic types (Foo) // that can never be reflection-enabled due to the framework Reflection block. diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeMethodHelpers.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeMethodHelpers.cs index f3377c9c32ec2c..9ece9ba048a5c6 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeMethodHelpers.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeMethodHelpers.cs @@ -73,7 +73,7 @@ internal static string ComputeParametersString(RuntimeParameterInfo[] parameters { if (i != 0) sb.Append(", "); - string parameterTypeString = parameters[i].ParameterTypeString; + string parameterTypeString = parameters[i].ParameterType.FormatTypeNameForReflection(); // Legacy: Why use "ByRef" for by ref parameters? What language is this? // VB uses "ByRef" but it should precede (not follow) the parameter name. @@ -94,7 +94,7 @@ internal static string ComputeParametersString(RuntimeParameterInfo[] parameters internal static string ComputeToString(MethodBase contextMethod, RuntimeTypeInfo[] methodTypeArguments, RuntimeParameterInfo[] parameters, RuntimeParameterInfo returnParameter) { StringBuilder sb = new StringBuilder(30); - sb.Append(returnParameter == null ? "Void" : returnParameter.ParameterTypeString); // ConstructorInfos allowed to pass in null rather than craft a ReturnParameterInfo that's always of type void. + sb.Append(returnParameter == null ? "Void" : returnParameter.ParameterType.FormatTypeNameForReflection()); // ConstructorInfos allowed to pass in null rather than craft a ReturnParameterInfo that's always of type void. sb.Append(' '); sb.Append(contextMethod.Name); if (methodTypeArguments.Length != 0) @@ -105,9 +105,6 @@ internal static string ComputeToString(MethodBase contextMethod, RuntimeTypeInfo { sb.Append(sep); sep = ","; - string name = - methodTypeArgument.InternalNameIfAvailable ?? - Type.DefaultTypeNameWhenMissingMetadata; sb.Append(methodTypeArgument.Name); } sb.Append(']'); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeNamedMethodInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeNamedMethodInfo.cs index c64b56818aa3b4..6827850879b360 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeNamedMethodInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/MethodInfos/RuntimeNamedMethodInfo.cs @@ -145,7 +145,7 @@ public sealed override MethodInfo MakeGenericMethod(params Type[] typeArguments) if (typeArguments.Length != GenericTypeParameters.Length) throw new ArgumentException(SR.Format(SR.Argument_NotEnoughGenArguments, typeArguments.Length, GenericTypeParameters.Length)); RuntimeMethodInfo methodInfo = (RuntimeMethodInfo)RuntimeConstructedGenericMethodInfo.GetRuntimeConstructedGenericMethodInfo(this, genericTypeArguments); - MethodInvoker _ = methodInfo.MethodInvoker; // For compatibility with other Make* apis, trigger any MissingMetadataExceptions now rather than later. + MethodInvoker _ = methodInfo.MethodInvoker; // For compatibility with other Make* apis, trigger any missing metadata exceptions now rather than later. return methodInfo; } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimeMethodParameterInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimeMethodParameterInfo.cs index 19a8b8a40c8dbd..b146eadb260b99 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimeMethodParameterInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimeMethodParameterInfo.cs @@ -36,14 +36,6 @@ public sealed override Type ParameterType } } - internal sealed override string ParameterTypeString - { - get - { - return QualifiedParameterTypeHandle.FormatTypeName(_typeContext); - } - } - protected readonly QSignatureTypeHandle QualifiedParameterTypeHandle; private readonly TypeContext _typeContext; private volatile Type _lazyParameterType; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimeParameterInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimeParameterInfo.cs index 24f628042880f9..2707ed7f41c27d 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimeParameterInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimeParameterInfo.cs @@ -72,13 +72,9 @@ public sealed override int Position public sealed override string ToString() { - return this.ParameterTypeString + " " + this.Name; + return this.ParameterType.FormatTypeNameForReflection() + " " + this.Name; } - // Gets the ToString() output of ParameterType in a pay-to-play-safe way: Other Reflection ToString() methods should always use this rather than - // "ParameterType.ToString()". - internal abstract string ParameterTypeString { get; } - private readonly MemberInfo _member; private readonly int _position; } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimePropertyIndexParameterInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimePropertyIndexParameterInfo.cs index 133da4c63e8096..8a2a1a2591d31a 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimePropertyIndexParameterInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimePropertyIndexParameterInfo.cs @@ -87,14 +87,6 @@ public sealed override Type ParameterType } } - internal sealed override string ParameterTypeString - { - get - { - return _backingParameter.ParameterTypeString; - } - } - public sealed override int MetadataToken { get diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimeSyntheticParameterInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimeSyntheticParameterInfo.cs index 2f68d703a80df3..8cd670f755967d 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimeSyntheticParameterInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/ParameterInfos/RuntimeSyntheticParameterInfo.cs @@ -93,14 +93,6 @@ public sealed override int MetadataToken } } - internal sealed override string ParameterTypeString - { - get - { - return _parameterType.FormatTypeNameForReflection(); - } - } - private readonly RuntimeTypeInfo _parameterType; } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/PropertyInfos/RuntimePropertyInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/PropertyInfos/RuntimePropertyInfo.cs index 0c0a91beb36bb6..50ea6c7a219ae7 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/PropertyInfos/RuntimePropertyInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/PropertyInfos/RuntimePropertyInfo.cs @@ -215,8 +215,7 @@ public sealed override string ToString() { StringBuilder sb = new StringBuilder(30); - TypeContext typeContext = ContextTypeInfo.TypeContext; - sb.Append(PropertyTypeHandle.FormatTypeName(typeContext)); + sb.Append(PropertyType.FormatTypeNameForReflection()); sb.Append(' '); sb.Append(this.Name); ParameterInfo[] indexParameters = this.GetIndexParameters(); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/NativeFormat/NativeFormatRuntimeGenericParameterTypeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/NativeFormat/NativeFormatRuntimeGenericParameterTypeInfo.cs index b4789f8af68d17..fc45d8f992e069 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/NativeFormat/NativeFormatRuntimeGenericParameterTypeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/NativeFormat/NativeFormatRuntimeGenericParameterTypeInfo.cs @@ -58,11 +58,14 @@ protected sealed override int InternalGetHashCode() protected MetadataReader Reader { get; } - internal sealed override string? InternalGetNameIfAvailable(ref Type? rootCauseForFailure) + public sealed override string Name { - if (_genericParameter.Name.IsNull(Reader)) - return string.Empty; - return _genericParameter.Name.GetString(Reader); + get + { + if (_genericParameter.Name.IsNull(Reader)) + return string.Empty; + return _genericParameter.Name.GetString(Reader); + } } protected sealed override QTypeDefRefOrSpec[] Constraints diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/NativeFormat/NativeFormatRuntimeNamedTypeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/NativeFormat/NativeFormatRuntimeNamedTypeInfo.cs index 3e351f102360e9..fe54edb63ad300 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/NativeFormat/NativeFormatRuntimeNamedTypeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/NativeFormat/NativeFormatRuntimeNamedTypeInfo.cs @@ -168,12 +168,15 @@ internal sealed override string InternalFullNameOfAssembly } } - internal sealed override string? InternalGetNameIfAvailable(ref Type? rootCauseForFailure) + public sealed override string Name { - ConstantStringValueHandle nameHandle = _typeDefinition.Name; - string name = nameHandle.GetString(_reader); + get + { + ConstantStringValueHandle nameHandle = _typeDefinition.Name; + string name = nameHandle.GetString(_reader); - return name.EscapeTypeNameIdentifier(); + return name.EscapeTypeNameIdentifier(); + } } protected sealed override IEnumerable TrueCustomAttributes => RuntimeCustomAttributeData.GetCustomAttributes(_reader, _typeDefinition.CustomAttributes); diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeBlockedTypeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeBlockedTypeInfo.cs index 1e560f7f6991df..f3f0d11a40ac80 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeBlockedTypeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeBlockedTypeInfo.cs @@ -24,7 +24,7 @@ namespace System.Reflection.Runtime.TypeInfos // that can never be reflection-enabled due to the framework Reflection block. // // These types differ from NoMetadata TypeInfos in that properties that inquire about members, - // custom attributes or interfaces return an empty list rather than throwing a MissingMetadataException. + // custom attributes or interfaces return an empty list rather than throwing a missing metadata exception. // // Since these represent "internal framework types", the app cannot prove we are lying. // @@ -154,8 +154,6 @@ internal sealed override RuntimeNamedTypeInfo AnchoringTypeDefinitionForDeclared } } - internal sealed override bool CanBrowseWithoutMissingMetadataExceptions => true; - internal sealed override RuntimeTypeInfo[] RuntimeGenericTypeParameters { get @@ -172,9 +170,12 @@ internal sealed override Type InternalDeclaringType } } - internal sealed override string? InternalGetNameIfAvailable(ref Type? rootCauseForFailure) + public sealed override string Name { - return GeneratedName; + get + { + return GeneratedName; + } } internal sealed override string InternalFullNameOfAssembly diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeClsIdTypeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeClsIdTypeInfo.cs index 1c767450be9962..03a65e7156d88c 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeClsIdTypeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeClsIdTypeInfo.cs @@ -28,7 +28,7 @@ private RuntimeCLSIDTypeInfo(Guid clsid, string server) public sealed override bool ContainsGenericParameters => false; public sealed override string FullName => BaseType.FullName; public sealed override Guid GUID => _key.ClsId; - internal sealed override string? InternalGetNameIfAvailable(ref Type? rootCauseForFailure) => BaseType.InternalGetNameIfAvailable(ref rootCauseForFailure); + public sealed override string Name => BaseType.Name; public sealed override bool IsGenericTypeDefinition => false; public sealed override int MetadataToken => BaseType.MetadataToken; public sealed override string Namespace => BaseType.Namespace; @@ -56,7 +56,6 @@ public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) protected sealed override int InternalGetHashCode() => _key.GetHashCode(); internal sealed override Type BaseTypeWithoutTheGenericParameterQuirk => typeof(object); - internal sealed override bool CanBrowseWithoutMissingMetadataExceptions => BaseType.CastToRuntimeTypeInfo().CanBrowseWithoutMissingMetadataExceptions; internal sealed override Type InternalDeclaringType => null; internal sealed override string InternalFullNameOfAssembly => BaseType.Assembly.FullName; internal sealed override IEnumerable SyntheticConstructors => _constructors; diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeConstructedGenericTypeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeConstructedGenericTypeInfo.cs index 69779ae9b3ad84..aece6012b1dc74 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeConstructedGenericTypeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeConstructedGenericTypeInfo.cs @@ -170,26 +170,9 @@ public sealed override int MetadataToken public sealed override string ToString() { - // Get the FullName of the generic type definition in a pay-for-play safe way. - RuntimeTypeInfo genericTypeDefinition = GenericTypeDefinitionTypeInfo; - string? genericTypeDefinitionString = null; - if (genericTypeDefinition.InternalNameIfAvailable != null) // Want to avoid "cry-wolf" exceptions: if we can't even get the simple name, don't bother getting the FullName. - { - // Given our current pay for play policy, it should now be safe to attempt getting the FullName. (But guard with a try-catch in case this assumption is wrong.) - try - { - genericTypeDefinitionString = genericTypeDefinition.FullName; - } - catch (Exception) - { - } - } - // If all else fails, use the ToString() - it won't match the legacy CLR but with no metadata, we can't match it anyway. - genericTypeDefinitionString ??= genericTypeDefinition.ToString(); - // Now, append the generic type arguments. StringBuilder sb = new StringBuilder(); - sb.Append(genericTypeDefinitionString); + sb.Append(GenericTypeDefinitionTypeInfo.FullName); sb.Append('['); RuntimeTypeInfo[] genericTypeArguments = _key.GenericTypeArguments; for (int i = 0; i < genericTypeArguments.Length; i++) @@ -237,8 +220,6 @@ internal sealed override RuntimeNamedTypeInfo AnchoringTypeDefinitionForDeclared } } - internal sealed override bool CanBrowseWithoutMissingMetadataExceptions => GenericTypeDefinitionTypeInfo.CanBrowseWithoutMissingMetadataExceptions; - internal sealed override Type InternalDeclaringType { get @@ -258,9 +239,12 @@ internal sealed override string InternalFullNameOfAssembly } } - internal sealed override string? InternalGetNameIfAvailable(ref Type? rootCauseForFailure) + public sealed override string Name { - return GenericTypeDefinitionTypeInfo.InternalGetNameIfAvailable(ref rootCauseForFailure); + get + { + return GenericTypeDefinitionTypeInfo.Name; + } } internal sealed override RuntimeTypeInfo[] InternalRuntimeGenericTypeArguments diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeGenericParameterTypeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeGenericParameterTypeInfo.cs index 28f19623348ee9..387255917f24c8 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeGenericParameterTypeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeGenericParameterTypeInfo.cs @@ -116,8 +116,6 @@ protected sealed override TypeAttributes GetAttributeFlagsImpl() return TypeAttributes.Public; } - internal sealed override bool CanBrowseWithoutMissingMetadataExceptions => true; - internal sealed override string InternalFullNameOfAssembly { get diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeHasElementTypeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeHasElementTypeInfo.cs index 2c06872ac3c0f5..a1b30994ae51bb 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeHasElementTypeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeHasElementTypeInfo.cs @@ -152,8 +152,6 @@ protected sealed override int InternalGetHashCode() return _key.ElementType.GetHashCode(); } - internal sealed override bool CanBrowseWithoutMissingMetadataExceptions => true; - internal sealed override Type InternalDeclaringType { get @@ -162,15 +160,12 @@ internal sealed override Type InternalDeclaringType } } - internal sealed override string? InternalGetNameIfAvailable(ref Type? rootCauseForFailure) + public sealed override string Name { - string? elementTypeName = _key.ElementType.InternalGetNameIfAvailable(ref rootCauseForFailure); - if (elementTypeName == null) + get { - rootCauseForFailure = _key.ElementType; - return null; + return _key.ElementType.Name + Suffix; } - return elementTypeName + Suffix; } internal sealed override string InternalFullNameOfAssembly diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeNamedTypeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeNamedTypeInfo.cs index 1a4f49a8b41b26..4e96a263cc0c50 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeNamedTypeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeNamedTypeInfo.cs @@ -177,8 +177,6 @@ internal sealed override RuntimeNamedTypeInfo AnchoringTypeDefinitionForDeclared } } - internal sealed override bool CanBrowseWithoutMissingMetadataExceptions => true; - internal sealed override RuntimeTypeHandle InternalTypeHandleIfAvailable { get diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeNoMetadataNamedTypeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeNoMetadataNamedTypeInfo.cs deleted file mode 100644 index 7176f4901bc6b9..00000000000000 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeNoMetadataNamedTypeInfo.cs +++ /dev/null @@ -1,222 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Reflection; -using System.Diagnostics; -using System.Collections.Generic; -using System.Reflection.Runtime.General; -using System.Reflection.Runtime.TypeInfos; -using System.Reflection.Runtime.Assemblies; -using System.Reflection.Runtime.CustomAttributes; - -using Internal.LowLevelLinq; -using Internal.Reflection.Core.Execution; - -using StructLayoutAttribute = System.Runtime.InteropServices.StructLayoutAttribute; - -namespace System.Reflection.Runtime.TypeInfos -{ - // - // TypeInfos that represent type definitions (i.e. Foo or Foo<>, but not Foo or arrays/pointers/byrefs.) - // that not opted into pay-for-play metadata. - // - internal sealed partial class RuntimeNoMetadataNamedTypeInfo : RuntimeTypeDefinitionTypeInfo - { - private RuntimeNoMetadataNamedTypeInfo(RuntimeTypeHandle typeHandle, bool isGenericTypeDefinition) - { - _typeHandle = typeHandle; - _isGenericTypeDefinition = isGenericTypeDefinition; - } - - public sealed override Assembly Assembly - { - get - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - } - - public sealed override bool ContainsGenericParameters - { - get - { - return _isGenericTypeDefinition; - } - } - - public sealed override IEnumerable CustomAttributes - { - get - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - } - - public sealed override string FullName - { - get - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - } - - public sealed override Guid GUID - { - get - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - } - - public sealed override bool IsGenericTypeDefinition - { - get - { - return _isGenericTypeDefinition; - } - } - -#if DEBUG - public sealed override bool HasSameMetadataDefinitionAs(MemberInfo other) => base.HasSameMetadataDefinitionAs(other); -#endif - - public sealed override string Namespace - { - get - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - } - - public sealed override StructLayoutAttribute StructLayoutAttribute - { - get - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - } - - public sealed override string ToString() - { - return _typeHandle.LastResortString(); - } - - public sealed override int MetadataToken - { - get - { - throw new InvalidOperationException(SR.NoMetadataTokenAvailable); - } - } - - protected sealed override TypeAttributes GetAttributeFlagsImpl() - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - - protected sealed override int InternalGetHashCode() - { - return _typeHandle.GetHashCode(); - } - - // - // Returns the anchoring typedef that declares the members that this type wants returned by the Declared*** properties. - // The Declared*** properties will project the anchoring typedef's members by overriding their DeclaringType property with "this" - // and substituting the value of this.TypeContext into any generic parameters. - // - // Default implementation returns null which causes the Declared*** properties to return no members. - // - // Note that this does not apply to DeclaredNestedTypes. Nested types and their containers have completely separate generic instantiation environments - // (despite what C# might lead you to think.) Constructed generic types return the exact same same nested types that its generic type definition does - // - i.e. their DeclaringTypes refer back to the generic type definition, not the constructed generic type.) - // - // Note also that we cannot use this anchoring concept for base types because of generic parameters. Generic parameters return - // baseclass and interfaces based on its constraints. - // - internal sealed override RuntimeNamedTypeInfo AnchoringTypeDefinitionForDeclaredMembers - { - get - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - } - - internal sealed override bool CanBrowseWithoutMissingMetadataExceptions => false; - - internal sealed override Type InternalDeclaringType - { - get - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - } - - internal sealed override string? InternalGetNameIfAvailable(ref Type? rootCauseForFailure) - { - rootCauseForFailure = this; - return null; - } - - internal sealed override string InternalFullNameOfAssembly - { - get - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - } - - internal sealed override RuntimeTypeHandle InternalTypeHandleIfAvailable - { - get - { - return _typeHandle; - } - } - - internal sealed override RuntimeTypeInfo[] RuntimeGenericTypeParameters - { - get - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - } - - // - // Returns the base type as a typeDef, Ref, or Spec. Default behavior is to QTypeDefRefOrSpec.Null, which causes BaseType to return null. - // - internal sealed override QTypeDefRefOrSpec TypeRefDefOrSpecForBaseType - { - get - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - } - - // - // Returns the *directly implemented* interfaces as typedefs, specs or refs. ImplementedInterfaces will take care of the transitive closure and - // insertion of the TypeContext. - // - internal sealed override QTypeDefRefOrSpec[] TypeRefDefOrSpecsForDirectlyImplementedInterfaces - { - get - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - } - - // - // Returns the generic parameter substitutions to use when enumerating declared members, base class and implemented interfaces. - // - internal sealed override TypeContext TypeContext - { - get - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - } - - private readonly RuntimeTypeHandle _typeHandle; - private readonly bool _isGenericTypeDefinition; - } -} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeTypeInfo.CoreGetDeclared.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeTypeInfo.CoreGetDeclared.cs index d97fada651f205..9ef282eae5bcd4 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeTypeInfo.CoreGetDeclared.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeTypeInfo.CoreGetDeclared.cs @@ -147,12 +147,4 @@ internal sealed override IEnumerable CoreGetDeclaredNestedTypes(NameFilter return Array.Empty(); } } - - internal sealed partial class RuntimeNoMetadataNamedTypeInfo - { - internal sealed override IEnumerable CoreGetDeclaredNestedTypes(NameFilter optionalNameFilter) - { - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(this); - } - } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeTypeInfo.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeTypeInfo.cs index 870740aaa4c2f3..909d0798c9dae0 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeTypeInfo.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/Runtime/TypeInfos/RuntimeTypeInfo.cs @@ -53,6 +53,7 @@ protected RuntimeTypeInfo() public abstract override bool IsConstructedGenericType { get; } public abstract override bool IsByRefLike { get; } public sealed override bool IsCollectible => false; + public abstract override string Name { get; } public abstract override Assembly Assembly { get; } @@ -446,7 +447,7 @@ public sealed override Type MakeGenericType(params Type[] typeArguments) throw new InvalidOperationException(SR.Format(SR.Arg_NotGenericTypeDefinition, this)); // We intentionally don't validate the number of arguments or their suitability to the generic type's constraints. - // In a pay-for-play world, this can cause needless MissingMetadataExceptions. There is no harm in creating + // In a pay-for-play world, this can cause needless missing metadata exceptions. There is no harm in creating // the Type object for an inconsistent generic type - no MethodTable will ever match it so any attempt to "invoke" it // will throw an exception. bool foundSignatureType = false; @@ -501,18 +502,6 @@ public sealed override Type DeclaringType } } - public sealed override string Name - { - get - { - Type? rootCauseForFailure = null; - string? name = InternalGetNameIfAvailable(ref rootCauseForFailure); - if (name == null) - throw ReflectionCoreExecution.ExecutionDomain.CreateMissingMetadataException(rootCauseForFailure); - return name; - } - } - public sealed override Type ReflectedType { get @@ -612,8 +601,6 @@ internal virtual RuntimeNamedTypeInfo AnchoringTypeDefinitionForDeclaredMembers // internal abstract string InternalFullNameOfAssembly { get; } - internal abstract override string? InternalGetNameIfAvailable(ref Type? rootCauseForFailure); - // // Left unsealed as HasElement types must override this. // @@ -648,11 +635,6 @@ internal bool IsDelegate } } - // - // Returns true if it's possible to ask for a list of members and the base type without triggering a MissingMetadataException. - // - internal abstract bool CanBrowseWithoutMissingMetadataExceptions { get; } - // // The non-public version of TypeInfo.GenericTypeParameters (does not array-copy.) // diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs index e9d090b10c8e01..6d146348ee816d 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs @@ -211,7 +211,7 @@ public static void RuntimeFailFast(RhFailFastReason reason, Exception? exception } failFastMessage = string.Format("Runtime-generated FailFast: ({0}): {1}{2}", - reason.ToString(), // Explicit call to ToString() to avoid MissingMetadataException inside String.Format() + reason.ToString(), // Explicit call to ToString() to avoid missing metadata exception inside String.Format() GetStringForFailFastReason(reason), exception != null ? " [exception object available]" : ""); } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.cs index 719424c6faefd2..0422e7ba82e312 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeType.cs @@ -100,7 +100,7 @@ public sealed override Array GetEnumValues() Array values = Enum.GetEnumInfo(this).ValuesAsUnderlyingType; int count = values.Length; // Without universal shared generics, chances are slim that we'll have the appropriate - // array type available. Offer an escape hatch that avoids a MissingMetadataException + // array type available. Offer an escape hatch that avoids a missing metadata exception // at the cost of a small appcompat risk. Array result; if (AppContext.TryGetSwitch("Switch.System.Enum.RelaxedGetValues", out bool isRelaxed) && isRelaxed) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Type.Internal.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Type.Internal.cs index 23d968e8608718..20df381cbb63b5 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Type.Internal.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Type.Internal.cs @@ -35,98 +35,33 @@ internal bool TryGetEEType(out EETypePtr eeType) return true; } - /// - /// Return Type.Name if sufficient metadata is available to do so - otherwise return null. - /// - public string? InternalNameIfAvailable - { - get - { - Type? ignore = null; - return InternalGetNameIfAvailable(ref ignore); - } - } - - /// - /// Return Type.Name if sufficient metadata is available to do so - otherwise return null and set "rootCauseForFailure" to an object to pass to MissingMetadataException. - /// - internal virtual string? InternalGetNameIfAvailable(ref Type? rootCauseForFailure) => Name; - - /// - /// Return Type.Name if sufficient metadata is available to do so - otherwise return a default (non-null) string. - /// - internal string NameOrDefault - { - get - { - return InternalNameIfAvailable ?? DefaultTypeNameWhenMissingMetadata; - } - } - - /// - /// Return Type.FullName if sufficient metadata is available to do so - otherwise return a default (non-null) string. - /// - internal string FullNameOrDefault - { - get - { - // First, see if Type.Name is available. If Type.Name is available, then we can be reasonably confident that it is safe to call Type.FullName. - // We'll still wrap the call in a try-catch as a failsafe. - if (InternalNameIfAvailable == null) - return DefaultTypeNameWhenMissingMetadata; - - try - { - return FullName; - } - catch (MissingMetadataException) - { - return DefaultTypeNameWhenMissingMetadata; - } - } - } - // // This is a port of the desktop CLR's RuntimeType.FormatTypeName() routine. This routine is used by various Reflection ToString() methods // to display the name of a type. Do not use for any other purpose as it inherits some pretty quirky desktop behavior. // - // The Project N version takes a raw metadata handle rather than a completed type so that it remains robust in the face of missing metadata. - // internal string FormatTypeNameForReflection() { - try + // Legacy: this doesn't make sense, why use only Name for nested types but otherwise + // ToString() which contains namespace. + Type rootElementType = this; + while (rootElementType.HasElementType) + rootElementType = rootElementType.GetElementType()!; + if (rootElementType.IsNested) { - // Though we wrap this in a try-catch as a failsafe, this code must still strive to avoid triggering MissingMetadata exceptions - // (non-error exceptions are very annoying when debugging.) - - // Legacy: this doesn't make sense, why use only Name for nested types but otherwise - // ToString() which contains namespace. - Type rootElementType = this; - while (rootElementType.HasElementType) - rootElementType = rootElementType.GetElementType()!; - if (rootElementType.IsNested) - { - return InternalNameIfAvailable ?? DefaultTypeNameWhenMissingMetadata; - } + return Name!; + } - // Legacy: why removing "System"? Is it just because C# has keywords for these types? - // If so why don't we change it to lower case to match the C# keyword casing? - string typeName = ToString(); - if (typeName.StartsWith("System.")) + // Legacy: why removing "System"? Is it just because C# has keywords for these types? + // If so why don't we change it to lower case to match the C# keyword casing? + string typeName = ToString(); + if (typeName.StartsWith("System.")) + { + if (rootElementType.IsPrimitive || rootElementType == typeof(void)) { - if (rootElementType.IsPrimitive || rootElementType == typeof(void)) - { - typeName = typeName.Substring("System.".Length); - } + typeName = typeName.Substring("System.".Length); } - return typeName; - } - catch (Exception) - { - return DefaultTypeNameWhenMissingMetadata; } + return typeName; } - - internal const string DefaultTypeNameWhenMissingMetadata = "UnknownType"; } } diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.MappingTables.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.MappingTables.cs index b2e3c4a6ec5f01..3654c9c6fd12c9 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.MappingTables.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ExecutionEnvironmentImplementation.MappingTables.cs @@ -174,7 +174,7 @@ public sealed override unsafe bool TryGetNamedTypeForMetadata(QTypeDefinition qT /// Return the metadata handle for a TypeRef if this type was referenced indirectly by other type that pay-for-play has denoted as browsable /// (for example, as part of a method signature.) /// - /// This is only used in "debug" builds to provide better MissingMetadataException diagnostics. + /// This is only used in "debug" builds to provide better missing metadata diagnostics. /// /// Preconditions: /// runtimeTypeHandle is a typedef (not a constructed type such as an array or generic instance.) diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/DiagnosticMappingTables.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/DiagnosticMappingTables.cs index eca67f2164b231..d49f576080a42e 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/DiagnosticMappingTables.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/DiagnosticMappingTables.cs @@ -15,20 +15,11 @@ namespace Internal.Reflection.Execution.PayForPlayExperience { internal static partial class DiagnosticMappingTables { - // Get the diagnostic name string for a type. This attempts to reformat the string into something that is essentially human readable. + // Get the diagnostic name string for a type. // Returns true if the function is successful. // runtimeTypeHandle represents the type to get a name for // diagnosticName is the name that is returned - // - // the genericParameterOffsets list is an optional parameter that contains the list of the locations of where generic parameters may be inserted - // to make the string represent an instantiated generic. - // - // For example for Dictionary, metadata names the type Dictionary`2, but this function will return Dictionary<,> - // For consumers of this function that will be inserting generic arguments, the genericParameterOffsets list is used to find where to insert the generic parameter name. - // - // That isn't all that interesting for Dictionary, but it becomes substantially more interesting for nested generic types, or types which are compiler named as - // those may contain embedded <> pairs and such. - public static bool TryGetDiagnosticStringForNamedType(RuntimeTypeHandle runtimeTypeHandle, out string diagnosticName, List genericParameterOffsets) + public static bool TryGetDiagnosticStringForNamedType(RuntimeTypeHandle runtimeTypeHandle, out string diagnosticName) { diagnosticName = null; ExecutionEnvironmentImplementation executionEnvironment = ReflectionExecution.ExecutionEnvironment; @@ -37,30 +28,30 @@ public static bool TryGetDiagnosticStringForNamedType(RuntimeTypeHandle runtimeT TypeReferenceHandle typeReferenceHandle; if (executionEnvironment.TryGetTypeReferenceForNamedType(runtimeTypeHandle, out reader, out typeReferenceHandle)) { - diagnosticName = GetTypeFullNameFromTypeRef(typeReferenceHandle, reader, genericParameterOffsets); + diagnosticName = GetTypeFullNameFromTypeRef(typeReferenceHandle, reader); return true; } QTypeDefinition qTypeDefinition; if (executionEnvironment.TryGetMetadataForNamedType(runtimeTypeHandle, out qTypeDefinition)) { - TryGetFullNameFromTypeDefEcma(qTypeDefinition, genericParameterOffsets, ref diagnosticName); + TryGetFullNameFromTypeDefEcma(qTypeDefinition, ref diagnosticName); if (diagnosticName != null) return true; if (qTypeDefinition.IsNativeFormatMetadataBased) { TypeDefinitionHandle typeDefinitionHandle = qTypeDefinition.NativeFormatHandle; - diagnosticName = GetTypeFullNameFromTypeDef(typeDefinitionHandle, qTypeDefinition.NativeFormatReader, genericParameterOffsets); + diagnosticName = GetTypeFullNameFromTypeDef(typeDefinitionHandle, qTypeDefinition.NativeFormatReader); return true; } } return false; } - static partial void TryGetFullNameFromTypeDefEcma(QTypeDefinition qTypeDefinition, List genericParameterOffsets, ref string result); + static partial void TryGetFullNameFromTypeDefEcma(QTypeDefinition qTypeDefinition, ref string result); - private static string GetTypeFullNameFromTypeRef(TypeReferenceHandle typeReferenceHandle, MetadataReader reader, List genericParameterOffsets) + private static string GetTypeFullNameFromTypeRef(TypeReferenceHandle typeReferenceHandle, MetadataReader reader) { TypeReference typeReference = typeReferenceHandle.GetTypeReference(reader); string s = typeReference.TypeName.GetString(reader); @@ -68,8 +59,8 @@ private static string GetTypeFullNameFromTypeRef(TypeReferenceHandle typeReferen HandleType parentHandleType = parentHandle.HandleType; if (parentHandleType == HandleType.TypeReference) { - string containingTypeName = GetTypeFullNameFromTypeRef(parentHandle.ToTypeReferenceHandle(reader), reader, genericParameterOffsets); - s = containingTypeName + "." + s; + string containingTypeName = GetTypeFullNameFromTypeRef(parentHandle.ToTypeReferenceHandle(reader), reader); + s = containingTypeName + "+" + s; } else if (parentHandleType == HandleType.NamespaceReference) { @@ -92,40 +83,10 @@ private static string GetTypeFullNameFromTypeRef(TypeReferenceHandle typeReferen // If we got here, the metadata is illegal but this helper is for ToString() - better to // return something partial than throw. } - return ConvertBackTickNameToNameWithReducerInputFormat(s, genericParameterOffsets); + return s; } - public static string ConvertBackTickNameToNameWithReducerInputFormat(string typename, List genericParameterOffsets) - { - int indexOfBackTick = typename.LastIndexOf('`'); - if (indexOfBackTick != -1) - { - string typeNameSansBackTick = typename.Substring(0, indexOfBackTick); - if ((indexOfBackTick + 1) < typename.Length) - { - string textAfterBackTick = typename.Substring(indexOfBackTick + 1); - int genericParameterCount; - if (int.TryParse(textAfterBackTick, out genericParameterCount) && (genericParameterCount > 0)) - { - // Replace the `Number with <,,,> where the count of ',' is one less than Number. - StringBuilder genericTypeName = new StringBuilder(); - genericTypeName.Append(typeNameSansBackTick); - genericTypeName.Append('<'); - genericParameterOffsets?.Add(genericTypeName.Length); - for (int i = 1; i < genericParameterCount; i++) - { - genericTypeName.Append(','); - genericParameterOffsets?.Add(genericTypeName.Length); - } - genericTypeName.Append('>'); - return genericTypeName.ToString(); - } - } - } - return typename; - } - - private static string GetTypeFullNameFromTypeDef(TypeDefinitionHandle typeDefinitionHandle, MetadataReader reader, List genericParameterOffsets) + private static string GetTypeFullNameFromTypeDef(TypeDefinitionHandle typeDefinitionHandle, MetadataReader reader) { string s; @@ -135,8 +96,8 @@ private static string GetTypeFullNameFromTypeDef(TypeDefinitionHandle typeDefini TypeDefinitionHandle enclosingTypeDefHandle = typeDefinition.EnclosingType; if (!enclosingTypeDefHandle.IsNull(reader)) { - string containingTypeName = GetTypeFullNameFromTypeDef(enclosingTypeDefHandle, reader, genericParameterOffsets); - s = containingTypeName + "." + s; + string containingTypeName = GetTypeFullNameFromTypeDef(enclosingTypeDefHandle, reader); + s = containingTypeName + "+" + s; } else { @@ -154,19 +115,7 @@ private static string GetTypeFullNameFromTypeDef(TypeDefinitionHandle typeDefini namespaceHandle = namespaceDefinition.ParentScopeOrNamespace.ToNamespaceDefinitionHandle(reader); } } - return ConvertBackTickNameToNameWithReducerInputFormat(s, genericParameterOffsets); - } - - public static bool TryGetArrayTypeElementType(RuntimeTypeHandle arrayTypeHandle, out RuntimeTypeHandle elementTypeHandle) - { - elementTypeHandle = RuntimeAugments.GetRelatedParameterTypeHandle(arrayTypeHandle); - return true; - } - - public static bool TryGetPointerTypeTargetType(RuntimeTypeHandle pointerTypeHandle, out RuntimeTypeHandle targetTypeHandle) - { - targetTypeHandle = RuntimeAugments.GetRelatedParameterTypeHandle(pointerTypeHandle); - return true; + return s; } } } diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/MissingMetadataExceptionCreator.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/MissingMetadataExceptionCreator.cs index ebe6da8349665a..54c485cd4b339a 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/MissingMetadataExceptionCreator.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/PayForPlayExperience/MissingMetadataExceptionCreator.cs @@ -15,96 +15,51 @@ namespace Internal.Reflection.Execution.PayForPlayExperience { public static class MissingMetadataExceptionCreator { - internal static MissingMetadataException Create(string resourceId, MemberInfo? pertainant) - { - return CreateFromMetadataObject(resourceId, pertainant); - } - - internal static MissingMetadataException Create(TypeInfo? pertainant) + internal static NotSupportedException Create(Type? pertainant) { return CreateFromMetadataObject(SR.Reflection_InsufficientMetadata_EdbNeeded, pertainant); } - internal static MissingMetadataException Create(TypeInfo? pertainant, string nestedTypeName) + private static NotSupportedException CreateFromString(string? pertainant) { if (pertainant == null) - return new MissingMetadataException(SR.Format(SR.Reflection_InsufficientMetadata_NoHelpAvailable, "")); - - string usefulPertainant = ComputeUsefulPertainantIfPossible(pertainant); - if (usefulPertainant == null) - return new MissingMetadataException(SR.Format(SR.Reflection_InsufficientMetadata_NoHelpAvailable, pertainant.ToString())); + return new NotSupportedException(SR.Format(SR.Reflection_InsufficientMetadata_NoHelpAvailable, "")); else - { - usefulPertainant = usefulPertainant + "." + DiagnosticMappingTables.ConvertBackTickNameToNameWithReducerInputFormat(nestedTypeName, null); - return new MissingMetadataException(SR.Format(SR.Reflection_InsufficientMetadata_EdbNeeded, usefulPertainant)); - } + return new NotSupportedException(SR.Format(SR.Reflection_InsufficientMetadata_EdbNeeded, pertainant)); } - internal static MissingMetadataException Create(Type? pertainant) - { - return CreateFromMetadataObject(SR.Reflection_InsufficientMetadata_EdbNeeded, pertainant); - } - - internal static MissingMetadataException Create(RuntimeTypeHandle pertainant) - { - return CreateFromMetadataObject(SR.Reflection_InsufficientMetadata_EdbNeeded, pertainant); - } - - private static MissingMetadataException CreateFromString(string? pertainant) - { - if (pertainant == null) - return new MissingMetadataException(SR.Format(SR.Reflection_InsufficientMetadata_NoHelpAvailable, "")); - else - return new MissingMetadataException(SR.Format(SR.Reflection_InsufficientMetadata_EdbNeeded, pertainant)); - } - - internal static MissingMetadataException CreateMissingArrayTypeException(Type elementType, bool isMultiDim, int rank) + internal static NotSupportedException CreateMissingArrayTypeException(Type elementType, bool isMultiDim, int rank) { Debug.Assert(rank == 1 || isMultiDim); string s = CreateArrayTypeStringIfAvailable(elementType, rank); return CreateFromString(s); } - internal static MissingMetadataException CreateMissingConstructedGenericTypeException(Type genericTypeDefinition, Type[] genericTypeArguments) + internal static NotSupportedException CreateMissingConstructedGenericTypeException(Type genericTypeDefinition, Type[] genericTypeArguments) { string s = CreateConstructedGenericTypeStringIfAvailable(genericTypeDefinition, genericTypeArguments); return CreateFromString(s); } - internal static MissingMetadataException CreateFromMetadataObject(string resourceId, object? pertainant) + internal static NotSupportedException CreateFromMetadataObject(string resourceId, Type? pertainant) { if (pertainant == null) - return new MissingMetadataException(SR.Format(SR.Reflection_InsufficientMetadata_NoHelpAvailable, "")); + return new NotSupportedException(SR.Format(SR.Reflection_InsufficientMetadata_NoHelpAvailable, "")); - string usefulPertainant = ComputeUsefulPertainantIfPossible(pertainant); + string usefulPertainant = pertainant.ToDisplayStringIfAvailable(); if (usefulPertainant == null) - return new MissingMetadataException(SR.Format(SR.Reflection_InsufficientMetadata_NoHelpAvailable, pertainant.ToString())); + return new NotSupportedException(SR.Format(SR.Reflection_InsufficientMetadata_NoHelpAvailable, pertainant.ToString())); else - return new MissingMetadataException(SR.Format(resourceId, usefulPertainant)); + return new NotSupportedException(SR.Format(resourceId, usefulPertainant)); } - public static string ComputeUsefulPertainantIfPossible(object pertainant) + public static string ComputeUsefulPertainantIfPossible(MemberInfo memberInfo) { { - Type type = null; - - if (pertainant is TypeInfo) - type = ((TypeInfo)pertainant).AsType(); - else if (pertainant is Type) - type = (Type)pertainant; - else if (pertainant is RuntimeTypeHandle) - type = Type.GetTypeFromHandle((RuntimeTypeHandle)pertainant); - - if (type != null) - return type.ToDisplayStringIfAvailable(null); - } - - if (pertainant is MemberInfo memberInfo) - { - StringBuilder friendlyName = new StringBuilder(memberInfo.DeclaringType.ToDisplayStringIfAvailable(null)); + StringBuilder friendlyName = new StringBuilder(memberInfo.DeclaringType.ToDisplayStringIfAvailable()); friendlyName.Append('.'); friendlyName.Append(memberInfo.Name); - if (pertainant is MethodBase method) + if (memberInfo is MethodBase method) { bool first; @@ -112,16 +67,16 @@ public static string ComputeUsefulPertainantIfPossible(object pertainant) if (method.IsConstructedGenericMethod) { first = true; - friendlyName.Append('<'); + friendlyName.Append('['); foreach (Type genericParameter in method.GetGenericArguments()) { if (!first) friendlyName.Append(','); first = false; - friendlyName.Append(genericParameter.ToDisplayStringIfAvailable(null)); + friendlyName.Append(genericParameter.ToDisplayStringIfAvailable()); } - friendlyName.Append('>'); + friendlyName.Append(']'); } // write out actual parameters @@ -133,33 +88,16 @@ public static string ComputeUsefulPertainantIfPossible(object pertainant) friendlyName.Append(','); first = false; - if (parameter.IsOut && parameter.IsIn) - { - friendlyName.Append("ref "); - } - else if (parameter.IsOut) - { - friendlyName.Append("out "); - } - - Type parameterType = parameter.ParameterType; - if (parameterType.IsByRef) - { - parameterType = parameterType.GetElementType(); - } - - friendlyName.Append(parameter.ParameterType.ToDisplayStringIfAvailable(null)); + friendlyName.Append(parameter.ParameterType.ToDisplayStringIfAvailable()); } friendlyName.Append(')'); } return friendlyName.ToString(); } - - return null; //Give up } - internal static string ToDisplayStringIfAvailable(this Type type, List genericParameterOffsets) + internal static string ToDisplayStringIfAvailable(this Type type) { RuntimeTypeHandle runtimeTypeHandle = ReflectionCoreExecution.ExecutionDomain.GetTypeHandleIfAvailable(type); bool hasRuntimeTypeHandle = !runtimeTypeHandle.Equals(default(RuntimeTypeHandle)); @@ -178,7 +116,7 @@ internal static string ToDisplayStringIfAvailable(this Type type, List gene } else { - string s = type.GetElementType().ToDisplayStringIfAvailable(null); + string s = type.GetElementType().ToDisplayStringIfAvailable(); if (s == null) return null; return s + (type.IsPointer ? "*" : "&"); @@ -214,50 +152,20 @@ internal static string ToDisplayStringIfAvailable(this Type type, List gene else if (hasRuntimeTypeHandle) { string s; - if (!DiagnosticMappingTables.TryGetDiagnosticStringForNamedType(runtimeTypeHandle, out s, genericParameterOffsets)) + if (!DiagnosticMappingTables.TryGetDiagnosticStringForNamedType(runtimeTypeHandle, out s)) return null; return s; } else { - // First, see if Type.Name is available. If Type.Name is available, then we can be reasonably confident that it is safe to call Type.FullName. - // We'll still wrap the call in a try-catch as a failsafe. - string s = type.InternalNameIfAvailable; - if (s == null) - return null; - - try - { - s = type.FullName; - } - catch (MissingMetadataException) - { - } - - // Insert commas so that CreateConstructedGenericTypeStringIfAvailable can fill the blanks. - // This is not strictly correct for types nested under generic types, but at this point we're doing - // best effort within reason. - if (type.IsGenericTypeDefinition) - { - s += "["; - int genericArgCount = type.GetGenericArguments().Length; - while (genericArgCount-- > 0) - { - genericParameterOffsets.Add(s.Length); - if (genericArgCount > 0) - s += ","; - } - s += "]"; - } - - return s; + return type.FullName; } } private static string CreateArrayTypeStringIfAvailable(Type elementType, int rank) { - string s = elementType.ToDisplayStringIfAvailable(null); + string s = elementType.ToDisplayStringIfAvailable(); if (s == null) return null; @@ -266,38 +174,22 @@ private static string CreateArrayTypeStringIfAvailable(Type elementType, int ran private static string CreateConstructedGenericTypeStringIfAvailable(Type genericTypeDefinition, Type[] genericTypeArguments) { - List genericParameterOffsets = new List(); - string genericTypeDefinitionString = genericTypeDefinition.ToDisplayStringIfAvailable(genericParameterOffsets); + string genericTypeDefinitionString = genericTypeDefinition.ToDisplayStringIfAvailable(); if (genericTypeDefinitionString == null) return null; - // If we found too many generic arguments to insert things, strip out the excess. This is wrong, but also, nothing is right. - if (genericTypeArguments.Length < genericParameterOffsets.Count) - { - genericParameterOffsets.RemoveRange(genericTypeArguments.Length, genericParameterOffsets.Count - genericTypeArguments.Length); - } - // Similarly, if we found too few, add them at the end. - while (genericTypeArguments.Length > genericParameterOffsets.Count) - { - genericTypeDefinitionString += ","; - genericParameterOffsets.Add(genericTypeDefinitionString.Length); - } - - // Ensure the list is sorted in ascending order - genericParameterOffsets.Sort(); - - // The s string Now contains a string like "Namespace.MoreNamespace.TypeName.NestedGenericType<,,>.MoreNestedGenericType<>" - // where the generic parameters locations are recorded in genericParameterOffsets - // Walk backwards through the generic parameter locations, filling in as needed. StringBuilder genericTypeName = new StringBuilder(genericTypeDefinitionString); - for (int i = genericParameterOffsets.Count - 1; i >= 0; --i) + genericTypeName.Append('['); + for (int i = 0; i < genericTypeArguments.Length; i++) { - genericTypeName.Insert(genericParameterOffsets[i], genericTypeArguments[i].ToDisplayStringIfAvailable(null)); + if (i > 0) + genericTypeName.Append(", "); + genericTypeName.Append(genericTypeArguments[i].ToDisplayStringIfAvailable()); } + genericTypeName.Append(']'); return genericTypeName.ToString(); } - } } diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ReflectionDomainSetupImplementation.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ReflectionDomainSetupImplementation.cs index 143dbf0b2011e7..73506680de2b4a 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ReflectionDomainSetupImplementation.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ReflectionDomainSetupImplementation.cs @@ -21,21 +21,11 @@ public ReflectionDomainSetupImplementation() // Obtain it lazily to avoid using RuntimeAugments.Callbacks before it is initialized public sealed override AssemblyBinder AssemblyBinder => AssemblyBinderImplementation.Instance; - public sealed override Exception CreateMissingMetadataException(TypeInfo pertainant) - { - return MissingMetadataExceptionCreator.Create(pertainant); - } - public sealed override Exception CreateMissingMetadataException(Type pertainant) { return MissingMetadataExceptionCreator.Create(pertainant); } - public sealed override Exception CreateMissingMetadataException(TypeInfo pertainant, string nestedTypeName) - { - return MissingMetadataExceptionCreator.Create(pertainant, nestedTypeName); - } - public sealed override Exception CreateNonInvokabilityException(MemberInfo pertainant) { string resourceName = SR.Object_NotInvokable; @@ -52,7 +42,7 @@ public sealed override Exception CreateNonInvokabilityException(MemberInfo perta } string pertainantString = MissingMetadataExceptionCreator.ComputeUsefulPertainantIfPossible(pertainant); - return new MissingMetadataException(SR.Format(resourceName, pertainantString ?? "?")); + return new NotSupportedException(SR.Format(resourceName, pertainantString ?? "?")); } public sealed override Exception CreateMissingArrayTypeException(Type elementType, bool isMultiDim, int rank) diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ReflectionExecutionDomainCallbacksImplementation.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ReflectionExecutionDomainCallbacksImplementation.cs index a7941c10e01941..bea0d44a506cc5 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ReflectionExecutionDomainCallbacksImplementation.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Execution/ReflectionExecutionDomainCallbacksImplementation.cs @@ -80,7 +80,7 @@ public sealed override Type GetConstructedGenericTypeForHandle(RuntimeTypeHandle } //======================================================================================= - // MissingMetadataException support. + // Missing metadata exception support. //======================================================================================= public sealed override Exception CreateMissingMetadataException(Type pertainant) { @@ -91,7 +91,7 @@ public sealed override Exception CreateMissingMetadataException(Type pertainant) // This helper makes a "best effort" to give the caller something better than "EETypePtr nnnnnnnnn". public sealed override string GetBetterDiagnosticInfoIfAvailable(RuntimeTypeHandle runtimeTypeHandle) { - return Type.GetTypeFromHandle(runtimeTypeHandle).ToDisplayStringIfAvailable(null); + return Type.GetTypeFromHandle(runtimeTypeHandle).ToDisplayStringIfAvailable(); } public sealed override MethodBase GetMethodBaseFromStartAddressIfAvailable(IntPtr methodStartAddress) diff --git a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Extensions/NonPortable/DelegateMethodInfoRetriever.cs b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Extensions/NonPortable/DelegateMethodInfoRetriever.cs index 68cbc9f19918bd..100ba63e959c39 100644 --- a/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Extensions/NonPortable/DelegateMethodInfoRetriever.cs +++ b/src/coreclr/nativeaot/System.Private.Reflection.Execution/src/Internal/Reflection/Extensions/NonPortable/DelegateMethodInfoRetriever.cs @@ -29,7 +29,7 @@ public static MethodInfo GetDelegateMethodInfo(Delegate del) // This is a special kind of delegate where the invoke method is "ObjectArrayThunk". Typically, // this will be a delegate that points the LINQ Expression interpreter. We could manufacture // a MethodInfo based on the delegate's Invoke signature, but let's just throw for now. - throw new PlatformNotSupportedException(SR.DelegateGetMethodInfo_ObjectArrayDelegate); + throw new NotSupportedException(SR.DelegateGetMethodInfo_ObjectArrayDelegate); } if (originalLdFtnResult == (IntPtr)0) @@ -64,7 +64,7 @@ public static MethodInfo GetDelegateMethodInfo(Delegate del) methodHandle = QMethodDefinition.FromObjectAndInt(resolver->Reader, resolver->Handle); if (!TypeLoaderEnvironment.Instance.TryGetRuntimeMethodHandleComponents(resolver->GVMMethodHandle, out _, out _, out genericMethodTypeArgumentHandles)) - throw new MissingMetadataException(SR.DelegateGetMethodInfo_NoInstantiation); + throw new NotSupportedException(SR.DelegateGetMethodInfo_NoInstantiation); } } } @@ -77,9 +77,9 @@ public static MethodInfo GetDelegateMethodInfo(Delegate del) string methodDisplayString = RuntimeAugments.TryGetMethodDisplayStringFromIp(ip); if (methodDisplayString == null) - throw new MissingMetadataException(SR.DelegateGetMethodInfo_NoDynamic); + throw new NotSupportedException(SR.DelegateGetMethodInfo_NoDynamic); else - throw new MissingMetadataException(SR.Format(SR.DelegateGetMethodInfo_NoDynamic_WithDisplayString, methodDisplayString)); + throw new NotSupportedException(SR.Format(SR.DelegateGetMethodInfo_NoDynamic_WithDisplayString, methodDisplayString)); } } MethodBase methodBase = ReflectionCoreExecution.ExecutionDomain.GetMethod(typeOfFirstParameterIfInstanceDelegate, methodHandle, genericMethodTypeArgumentHandles); diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.Metadata.cs b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.Metadata.cs index 7c619907610f79..367ff35a59f0af 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.Metadata.cs +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/TypeLoaderEnvironment.Metadata.cs @@ -106,7 +106,7 @@ internal static unsafe NativeReader GetNativeReaderForBlob(NativeFormatModuleInf /// Return the metadata handle for a TypeRef if this type was referenced indirectly by other type that pay-for-play has denoted as browsable /// (for example, as part of a method signature.) /// - /// This is only used in "debug" builds to provide better MissingMetadataException diagnostics. + /// This is only used in "debug" builds to provide better diagnostics when metadata is missing. /// /// Preconditions: /// runtimeTypeHandle is a typedef (not a constructed type such as an array or generic instance.) diff --git a/src/tests/nativeaot/SmokeTests/Reflection/Reflection.cs b/src/tests/nativeaot/SmokeTests/Reflection/Reflection.cs index 676f4e457ed420..02c66ce71430c0 100644 --- a/src/tests/nativeaot/SmokeTests/Reflection/Reflection.cs +++ b/src/tests/nativeaot/SmokeTests/Reflection/Reflection.cs @@ -1620,7 +1620,7 @@ public static void Run() { message1 = ex.Message; } - if (!message1.Contains("ReflectionTest.TypeConstructionTest.Gen")) + if (!message1.Contains("ReflectionTest+TypeConstructionTest+Gen`1[ReflectionTest+TypeConstructionTest+Atom]")) throw new Exception(); string message2 = ""; @@ -1632,7 +1632,7 @@ public static void Run() { message2 = ex.Message; } - if (!message2.Contains("ReflectionTest.TypeConstructionTest.Atom[]")) + if (!message2.Contains("ReflectionTest+TypeConstructionTest+Atom[]")) throw new Exception(); string message3 = ""; @@ -1644,7 +1644,7 @@ public static void Run() { message3 = ex.Message; } - if (!message3.Contains("ReflectionTest.TypeConstructionTest.Atom[]")) + if (!message3.Contains("ReflectionTest+TypeConstructionTest+Atom[]")) throw new Exception(); } } From 579efcb494b3d55ecd26534728ca19b045991199 Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Wed, 10 Aug 2022 23:09:08 -0300 Subject: [PATCH 09/68] [wasm][debugger] Debug on Node.js (#68807) * Debug on Node.js * addressing @pavelsavara comments, fixing when dictionary doesn't contain type key. * Addressing @radical comments. * Fixing CI --- src/mono/sample/wasm/Directory.Build.targets | 3 +++ src/mono/sample/wasm/wasm.mk | 3 +++ src/mono/wasm/debugger/BrowserDebugHost/Startup.cs | 7 ++++++- .../wasm/debugger/tests/debugger-test/debugger-driver.html | 3 +-- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/mono/sample/wasm/Directory.Build.targets b/src/mono/sample/wasm/Directory.Build.targets index be23863e311932..6905d9d6d7e369 100644 --- a/src/mono/sample/wasm/Directory.Build.targets +++ b/src/mono/sample/wasm/Directory.Build.targets @@ -34,6 +34,9 @@ + + + diff --git a/src/mono/sample/wasm/wasm.mk b/src/mono/sample/wasm/wasm.mk index fbe782a3deb95a..a2a49b0f93ea21 100644 --- a/src/mono/sample/wasm/wasm.mk +++ b/src/mono/sample/wasm/wasm.mk @@ -46,3 +46,6 @@ run-console: run-console-node: cd bin/$(CONFIG)/AppBundle && node --stack-trace-limit=1000 --single-threaded --expose_wasm $(MAIN_JS) $(ARGS) + +debug-console-node: + cd bin/$(CONFIG)/AppBundle && node --inspect=9222 --stack-trace-limit=1000 --single-threaded --expose_wasm $(MAIN_JS) $(ARGS) \ No newline at end of file diff --git a/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs b/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs index 4717f77153fe7e..9e092af92f639f 100644 --- a/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs +++ b/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs @@ -77,6 +77,7 @@ public static Dictionary MapValues(Dictionary re { var filtered = new Dictionary(); HttpRequest request = context.Request; + var isNode = response.TryGetValue("type", out string type) && type == "node"; foreach (string key in response.Keys) { @@ -84,7 +85,10 @@ public static Dictionary MapValues(Dictionary re { case "devtoolsFrontendUrl": string front = response[key]; - filtered[key] = $"{debuggerHost.Scheme}://{debuggerHost.Authority}{front.Replace($"ws={debuggerHost.Authority}", $"ws={request.Host}")}"; + if (!isNode) + filtered[key] = $"{debuggerHost.Scheme}://{debuggerHost.Authority}{front.Replace($"ws={debuggerHost.Authority}", $"ws={request.Host}")}"; + else + filtered[key] = $"{front.Replace($"ws={debuggerHost.Authority}", $"ws={request.Host}")}"; break; case "webSocketDebuggerUrl": var page = new Uri(response[key]); @@ -118,6 +122,7 @@ public static IApplicationBuilder UseDebugProxy( router.MapGet("json/new", RewriteSingle); router.MapGet("devtools/page/{pageId}", ConnectProxy); router.MapGet("devtools/browser/{pageId}", ConnectProxy); + router.MapGet("{pageId}", ConnectProxy); //for node this is the URL format: ws://localhost:54693/dbad4979-2d2e-4ada-b449-d583a83b0545 string GetEndpoint(HttpContext context) { diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html b/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html index a3d8991fd33773..fdbbc640696ac3 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html @@ -17,8 +17,7 @@ App.method_with_structs = exports.DebuggerTests.ValueTypesTest.MethodWithLocalStructs; App.run_all = exports.DebuggerTest.run_all; App.static_method_table = {}; - console.log ("ready"); // HACK: firefox tests are looking for this - console.debug ("#debugger-app-ready#"); + console.debug ("#debugger-app-ready#"); console.log ("ready"); // HACK: firefox tests are looking for this "ready" }, }; function invoke_static_method (method_name, ...args) { From 856ac5ddcce2ce80c070b0799e687b2444902009 Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Wed, 10 Aug 2022 19:27:14 -0700 Subject: [PATCH 10/68] consume fedora images with msquic updated to 2.1 (#73707) --- eng/pipelines/coreclr/templates/helix-queues-setup.yml | 2 +- eng/pipelines/libraries/helix-queues-setup.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/pipelines/coreclr/templates/helix-queues-setup.yml b/eng/pipelines/coreclr/templates/helix-queues-setup.yml index e65486aa2aec23..6e9a1ed26516ef 100644 --- a/eng/pipelines/coreclr/templates/helix-queues-setup.yml +++ b/eng/pipelines/coreclr/templates/helix-queues-setup.yml @@ -98,7 +98,7 @@ jobs: - (Debian.11.Amd64)Ubuntu.1804.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64-20220810215032-f344011 - Ubuntu.1804.Amd64 - (Centos.8.Amd64)Ubuntu.1604.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-8-helix-20201229003624-c1bf759 - - (Fedora.34.Amd64)Ubuntu.1604.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix-20220716172051-4f64125 + - (Fedora.34.Amd64)Ubuntu.1804.amd64@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix-20220809205730-e7e8d1c - RedHat.7.Amd64 # OSX arm64 diff --git a/eng/pipelines/libraries/helix-queues-setup.yml b/eng/pipelines/libraries/helix-queues-setup.yml index ed9edccfffac97..7bfad19026b524 100644 --- a/eng/pipelines/libraries/helix-queues-setup.yml +++ b/eng/pipelines/libraries/helix-queues-setup.yml @@ -62,14 +62,14 @@ jobs: - (Centos.8.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-8-helix-20201229003624-c1bf759 - RedHat.7.Amd64.Open - SLES.15.Amd64.Open - - (Fedora.34.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix-20220716172051-4f64125 + - (Fedora.34.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix-20220809205730-e7e8d1c - (Ubuntu.2204.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-22.04-helix-amd64-20220504035722-1b9461f - (Debian.10.Amd64.Open)Ubuntu.2204.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-10-helix-amd64-20220810215022-f344011 - ${{ if or(ne(parameters.jobParameters.testScope, 'outerloop'), ne(parameters.jobParameters.runtimeFlavor, 'mono')) }}: - ${{ if or(eq(parameters.jobParameters.isExtraPlatforms, true), eq(parameters.jobParameters.includeAllPlatforms, true)) }}: - (Centos.8.Amd64.Open)Ubuntu.1604.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:centos-8-helix-20201229003624-c1bf759 - SLES.15.Amd64.Open - - (Fedora.34.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix-20220716172051-4f64125 + - (Fedora.34.Amd64.Open)ubuntu.1804.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:fedora-34-helix-20220809205730-e7e8d1c - Ubuntu.2204.Amd64.Open - (Debian.11.Amd64.Open)Ubuntu.1804.Amd64.Open@mcr.microsoft.com/dotnet-buildtools/prereqs:debian-11-helix-amd64-20220810215032-f344011 - (Mariner.1.0.Amd64.Open)ubuntu.1604.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:cbl-mariner-1.0-helix-20210528192219-92bf620 From b908ecf514f32c7ba7d59ecc28fa3fdd64a10a1a Mon Sep 17 00:00:00 2001 From: Anton Lapounov Date: Wed, 10 Aug 2022 19:31:17 -0700 Subject: [PATCH 11/68] Account for availability of multiple processor groups on Windows 11+ (#68639) --- src/coreclr/inc/configuration.h | 14 +++++++++++--- src/coreclr/utilcode/configuration.cpp | 12 ++++++++++++ src/coreclr/utilcode/util.cpp | 13 +++++++++++-- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/coreclr/inc/configuration.h b/src/coreclr/inc/configuration.h index bf2d805e049b92..cfc7dcd5819a15 100644 --- a/src/coreclr/inc/configuration.h +++ b/src/coreclr/inc/configuration.h @@ -33,7 +33,9 @@ class Configuration // Unfortunately our traditional config system insists on interpreting numbers as 32-bit so interpret the config // in the traditional way separately if you need to. // - // Returns value for name if found in config. + // Returns (in priority order): + // - The value of the ConfigurationKnob (searched by name) if it's set (performs a _wcstoui64) + // - The default value passed in static ULONGLONG GetKnobULONGLONGValue(LPCWSTR name, ULONGLONG defaultValue); // Returns (in priority order): @@ -48,11 +50,17 @@ class Configuration static LPCWSTR GetKnobStringValue(LPCWSTR name); // Returns (in priority order): - // - The value of the ConfigDWORDInfo if it's set (1 is true, anything else is false) + // - The value of the ConfigDWORDInfo if it's set (0 is false, anything else is true) // - The value of the ConfigurationKnob (searched by name) if it's set (performs a wcscmp with "true"). - // - The default set in the ConfigDWORDInfo (1 is true, anything else is false) + // - The default set in the ConfigDWORDInfo (0 is false, anything else is true) static bool GetKnobBooleanValue(LPCWSTR name, const CLRConfig::ConfigDWORDInfo& dwordInfo); + // Returns (in priority order): + // - The value of the ConfigDWORDInfo if it's set (0 is false, anything else is true) + // - The value of the ConfigurationKnob (searched by name) if it's set (performs a wcscmp with "true"). + // - The default value passed in + static bool GetKnobBooleanValue(LPCWSTR name, const CLRConfig::ConfigDWORDInfo& dwordInfo, bool defaultValue); + // Returns (in priority order): // - The value of the ConfigurationKnob (searched by name) if it's set (performs a wcscmp with "true"). // - The default value passed in diff --git a/src/coreclr/utilcode/configuration.cpp b/src/coreclr/utilcode/configuration.cpp index 50a5e335a742b9..170edc5b273bc2 100644 --- a/src/coreclr/utilcode/configuration.cpp +++ b/src/coreclr/utilcode/configuration.cpp @@ -123,6 +123,18 @@ bool Configuration::GetKnobBooleanValue(LPCWSTR name, const CLRConfig::ConfigDWO return (legacyValue != 0); } +bool Configuration::GetKnobBooleanValue(LPCWSTR name, const CLRConfig::ConfigDWORDInfo& dwordInfo, bool defaultValue) +{ + bool returnedDefaultValue; + DWORD legacyValue = CLRConfig::GetConfigValue(dwordInfo, &returnedDefaultValue); + if (!returnedDefaultValue) + { + return (legacyValue != 0); + } + + return GetKnobBooleanValue(name, defaultValue); +} + bool Configuration::GetKnobBooleanValue(LPCWSTR name, bool defaultValue) { LPCWSTR knobValue = GetConfigurationValue(name); diff --git a/src/coreclr/utilcode/util.cpp b/src/coreclr/utilcode/util.cpp index 2d2d2d22de2ec1..623ef923516ce5 100644 --- a/src/coreclr/utilcode/util.cpp +++ b/src/coreclr/utilcode/util.cpp @@ -760,7 +760,16 @@ DWORD LCM(DWORD u, DWORD v) CONTRACTL_END; #if !defined(FEATURE_NATIVEAOT) && (defined(TARGET_AMD64) || defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64)) - BOOL enableGCCPUGroups = Configuration::GetKnobBooleanValue(W("System.GC.CpuGroup"), CLRConfig::EXTERNAL_GCCpuGroup); + USHORT groupCount = 0; + + // On Windows 11+ and Windows Server 2022+, a process is no longer restricted to a single processor group by default. + // If more than one processor group is available to the process (a non-affinitized process on Windows 11+), + // default to using multiple processor groups; otherwise, default to using a single processor group. This default + // behavior may be overridden by the configuration values below. + if (GetProcessGroupAffinity(GetCurrentProcess(), &groupCount, NULL) || GetLastError() != ERROR_INSUFFICIENT_BUFFER) + groupCount = 1; + + BOOL enableGCCPUGroups = Configuration::GetKnobBooleanValue(W("System.GC.CpuGroup"), CLRConfig::EXTERNAL_GCCpuGroup, groupCount > 1); if (!enableGCCPUGroups) return; @@ -772,7 +781,7 @@ DWORD LCM(DWORD u, DWORD v) if (m_nGroups > 1) { m_enableGCCPUGroups = TRUE; - m_threadUseAllCpuGroups = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_Thread_UseAllCpuGroups) != 0; + m_threadUseAllCpuGroups = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_Thread_UseAllCpuGroups, groupCount > 1) != 0; m_threadAssignCpuGroups = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_Thread_AssignCpuGroups) != 0; // Save the processor group affinity of the initial thread From e65f2241465d1136c5f633441fcb4f98b1d8243c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 11 Aug 2022 11:42:24 +0900 Subject: [PATCH 12/68] Expand NativeAOT testing platforms (#73546) --- eng/pipelines/runtime-extra-platforms-other.yml | 3 ++- .../System.Net.Quic.Functional.Tests.csproj | 3 +++ .../tests/FunctionalTests/default.rd.xml | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/libraries/System.Net.Quic/tests/FunctionalTests/default.rd.xml diff --git a/eng/pipelines/runtime-extra-platforms-other.yml b/eng/pipelines/runtime-extra-platforms-other.yml index ba4f7ceaad6be1..28e2e1b6a83300 100644 --- a/eng/pipelines/runtime-extra-platforms-other.yml +++ b/eng/pipelines/runtime-extra-platforms-other.yml @@ -86,10 +86,11 @@ jobs: - windows_arm64 - Linux_x64 - Linux_arm64 + - Linux_musl_x64 jobParameters: testGroup: innerloop isSingleFile: true - nameSuffix: NativeAOT_Libs_Passing + nameSuffix: NativeAOT_Libs buildArgs: -s clr.aot+libs+libs.tests -c $(_BuildConfig) /p:TestNativeAot=true /p:ArchiveTests=true timeoutInMinutes: 240 # extra steps, run tests diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj b/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj index cfb6c5c21e1a7f..7d0986b7d1be63 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/System.Net.Quic.Functional.Tests.csproj @@ -5,6 +5,9 @@ $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix true + + + diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/default.rd.xml b/src/libraries/System.Net.Quic/tests/FunctionalTests/default.rd.xml new file mode 100644 index 00000000000000..708c08d9af5005 --- /dev/null +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/default.rd.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + From 264b45eff9a82aa93744c9239a5935a9468d0e6a Mon Sep 17 00:00:00 2001 From: Andrew Au Date: Wed, 10 Aug 2022 19:51:19 -0700 Subject: [PATCH 13/68] Move the XML comments from reference assembly to CoreCLR implementation (#73725) --- .../System.Private.CoreLib/src/System/GC.CoreCLR.cs | 8 ++++++++ src/libraries/System.Runtime/ref/System.Runtime.cs | 8 -------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs index 89f59b0b9e2e35..b965a4c5792ea4 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs @@ -19,11 +19,19 @@ namespace System { + /// Specifies the behavior for a forced garbage collection. public enum GCCollectionMode { + /// The default setting for this enumeration, which is currently . Default = 0, + + /// Forces the garbage collection to occur immediately. Forced = 1, + + /// Allows the garbage collector to determine whether the current time is optimal to reclaim objects. Optimized = 2, + + /// Requests that the garbage collector decommit as much memory as possible. Aggressive = 3, } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 13f52c2f105042..c2d10c07e434cf 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -2608,19 +2608,11 @@ public static void WaitForPendingFinalizers() { } public static System.Collections.Generic.IReadOnlyDictionary GetConfigurationVariables() { throw null; } } - /// Specifies the behavior for a forced garbage collection. public enum GCCollectionMode { - /// The default setting for this enumeration, which is currently . Default = 0, - - /// Forces the garbage collection to occur immediately. Forced = 1, - - /// Allows the garbage collector to determine whether the current time is optimal to reclaim objects. Optimized = 2, - - /// Requests that the garbage collector decommit as much memory as possible. Aggressive = 3, } From 834abed7da485105ac7594ccd17e942674ae6f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Wed, 10 Aug 2022 22:57:43 -0500 Subject: [PATCH 14/68] COSE: Add documentation and exception correction (#73394) * Add documentation and exception correction * Address feedback, add paramName to ArgumentExceptions, update tests * Fix Remove(KeyValuePair) * Address feedback * Remove whitespace from xml end-tags * Address one more comment * Address yet another comment * Update src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseMultiSignMessage.cs Co-authored-by: Jeremy Barton Co-authored-by: Jeremy Barton --- .../System/Formats/Cbor/Reader/CborReader.cs | 2 +- .../src/Resources/Strings.resx | 17 +- .../Cryptography/Cose/CoseHeaderLabel.cs | 57 ++- .../Cryptography/Cose/CoseHeaderMap.cs | 146 ++++++- .../Cryptography/Cose/CoseHeaderValue.cs | 92 +++- .../Security/Cryptography/Cose/CoseHelpers.cs | 4 +- .../Security/Cryptography/Cose/CoseMessage.cs | 115 ++++- .../Cryptography/Cose/CoseMultiSignMessage.cs | 395 +++++++++++++++++- .../Cryptography/Cose/CoseSign1Message.cs | 369 +++++++++++++++- .../Cryptography/Cose/CoseSignature.cs | 202 +++++++++ .../Security/Cryptography/Cose/CoseSigner.cs | 90 +++- .../tests/CoseHeaderLabelTests.cs | 6 + .../tests/CoseHeaderMapTests.cs | 79 +++- .../tests/CoseHeaderValueTests.cs | 2 +- .../CoseMessageTests.Sign.CustomHeaderMaps.cs | 31 +- .../tests/CoseMessageTests.Sign.cs | 4 +- .../tests/CoseSignerTests.cs | 8 +- 17 files changed, 1537 insertions(+), 82 deletions(-) diff --git a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.cs b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.cs index ac765527626ac9..ed8004b4323679 100644 --- a/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.cs +++ b/src/libraries/System.Formats.Cbor/src/System/Formats/Cbor/Reader/CborReader.cs @@ -48,7 +48,7 @@ public partial class CborReader public int BytesRemaining => _data.Length - _offset; /// Initializes a instance over the specified with the given configuration. - /// The CBOR encoded data to read. + /// The CBOR-encoded data to read. /// One of the enumeration values to specify a conformance mode guiding the checks performed on the encoded data. /// Defaults to conformance mode. /// to indicate that multiple root-level values are supported by the reader; otherwise, . diff --git a/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx index b3100114fc6931..4a371d8e133298 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.Cose/src/Resources/Strings.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + The destination is too small to hold the value. + The destination is too small to hold the encoded value. @@ -127,10 +130,10 @@ Content was included in the message (embedded message) and yet another content was provided for verification. - Not a valid CBOR encoded value on CoseHeaderValue on header '{0}', see inner exception for details. + Not a valid CBOR-encoded value on CoseHeaderValue on header '{0}', see inner exception for details. - Not a valid CBOR encoded value, it must be a single value with no trailing data. + Not a valid CBOR-encoded value, it must be a single value with no trailing data. Decoded map is read only, headers cannot be added nor deleted. @@ -139,7 +142,7 @@ Header '{0}' does not accept the specified value. - Error while decoding CBOR encoded value, see inner exception for details. + Error while decoding CBOR-encoded value, see inner exception for details. RSA key needs a signature padding. @@ -162,6 +165,9 @@ Error while decoding COSE message. See the inner exception for details. + + CBOR payload contained trailing data after message was complete. + Incorrect tag. Expected Sign(98) or Untagged, Actual '{0}'. @@ -177,9 +183,6 @@ Map label was incorrect. - - CBOR payload contained trailing data after Sign1 message was complete. - Payload was incorrect. @@ -208,7 +211,7 @@ Protected and Unprotected buckets must not contain duplicate labels. - Unsuppoerted hash algorithm '{0}'. + Unsupported hash algorithm '{0}'. COSE algorithm '{0}' is unknown. diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderLabel.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderLabel.cs index 5455ccbff9e637..6646c87c80e3b7 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderLabel.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderLabel.cs @@ -3,10 +3,12 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Runtime.Versioning; namespace System.Security.Cryptography.Cose { + /// + /// Represents a COSE header label. + /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public readonly struct CoseHeaderLabel : IEquatable { @@ -14,15 +16,35 @@ namespace System.Security.Cryptography.Cose private string DebuggerDisplay => $"Label = {LabelName}, Type = {(LabelAsString != null ? typeof(string) : typeof(int))}"; // https://www.iana.org/assignments/cose/cose.xhtml#header-parameters + /// + /// Gets a header label that represents the known header parameter "alg". + /// + /// A header label that represents the known header parameter "alg". public static CoseHeaderLabel Algorithm => new CoseHeaderLabel(KnownHeaders.Alg); + /// + /// Gets a header label that represents the known header parameter "crit". + /// + /// A header label that represents the known header parameter "crit". public static CoseHeaderLabel CriticalHeaders => new CoseHeaderLabel(KnownHeaders.Crit); + /// + /// Gets a header label that represents the known header parameter "content type". + /// + /// A header label> that represents the known header parameter "content type". public static CoseHeaderLabel ContentType => new CoseHeaderLabel(KnownHeaders.ContentType); + /// + /// Gets a header label that represents the known header parameter "kid". + /// + /// A header label that represents the known header parameter "kid". public static CoseHeaderLabel KeyIdentifier => new CoseHeaderLabel(KnownHeaders.Kid); internal int LabelAsInt32 { get; } internal string? LabelAsString { get; } internal int EncodedSize { get; } + /// + /// Initializes a new instance of the struct. + /// + /// The header label as an integer. public CoseHeaderLabel(int label) { this = default; @@ -30,11 +52,16 @@ public CoseHeaderLabel(int label) EncodedSize = CoseHelpers.GetIntegerEncodedSize(label); } + /// + /// Initializes a new instance of the struct. + /// + /// The header label as a text string. + /// is . public CoseHeaderLabel(string label) { if (label is null) { - throw new ArgumentException(null, nameof(label)); + throw new ArgumentNullException(nameof(label)); } this = default; @@ -42,13 +69,27 @@ public CoseHeaderLabel(string label) EncodedSize = CoseHelpers.GetTextStringEncodedSize(label); } + /// + /// Returns a value indicating whether this instance is equal to the specified instance. + /// + /// The object to compare to this instance. + /// if the value parameter equals the value of this instance; otherwise, . public bool Equals(CoseHeaderLabel other) { return LabelAsString == other.LabelAsString && LabelAsInt32 == other.LabelAsInt32; } + /// + /// Returns a value indicating whether this instance is equal to a specified object. + /// + /// The object to compare to this instance. + /// if value is an instance of and equals the value of this instance; otherwise, . public override bool Equals([NotNullWhen(true)] object? obj) => obj is CoseHeaderLabel otherObj && Equals(otherObj); + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer hash code. public override int GetHashCode() { // Since this type is used as a key in a dictionary (see CoseHeaderMap) @@ -63,8 +104,20 @@ public override int GetHashCode() return LabelAsInt32.GetRandomizedHashCode(); } + /// + /// Determines whether two specified header label instances are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// if left and right represent the same label; otherwise, . public static bool operator ==(CoseHeaderLabel left, CoseHeaderLabel right) => left.Equals(right); + /// + /// Determines whether two specified header label instances are not equal. + /// + /// The first object to compare. + /// The second object to compare. + /// if left and right do not represent the same label; otherwise, . public static bool operator !=(CoseHeaderLabel left, CoseHeaderLabel right) => !left.Equals(right); } } diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderMap.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderMap.cs index c9e0c842ec8731..5b9e30bc4cf332 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderMap.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderMap.cs @@ -4,19 +4,29 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Formats.Cbor; namespace System.Security.Cryptography.Cose { + /// + /// Represents a collection of header parameters of a COSE message. + /// public sealed class CoseHeaderMap : IDictionary, IReadOnlyDictionary { private static readonly CoseHeaderMap s_emptyMap = new CoseHeaderMap(isReadOnly: true); private readonly Dictionary _headerParameters = new Dictionary(); + /// + /// Gets a value that indicates whether the header map is read-only. + /// + /// if the header map is read-only; otherwise, + /// The "protected headers" collection in a COSE message is always read-only. public bool IsReadOnly { get; internal set; } + /// + /// Initializes a new instance of the class. + /// public CoseHeaderMap() : this(isReadOnly: false) { } private CoseHeaderMap(bool isReadOnly) @@ -26,16 +36,38 @@ private CoseHeaderMap(bool isReadOnly) private ICollection> HeaderParametersAsCollection => _headerParameters; + /// + /// Gets a collection containing the labels in the header map. + /// + /// A collection containing the labels in the header map. public ICollection Keys => _headerParameters.Keys; + /// + /// Gets a collection containing the values in the header map. + /// + /// A collection containing the values in the header map. public ICollection Values => _headerParameters.Values; + /// + /// Gets the number of label/value pairs contained in the header map. + /// + /// The number of label/value pairs contained in the header map. public int Count => _headerParameters.Count; + /// IEnumerable IReadOnlyDictionary.Keys => _headerParameters.Keys; + /// IEnumerable IReadOnlyDictionary.Values => _headerParameters.Values; + /// + /// Gets or sets the value associated with the specified label. + /// + /// The label of the value to get or set. + /// The value associated with the specified label. + /// The property is set and the is read-only. + /// The property is set and the encoded bytes in the specified contain trailing data or more than one CBOR value. + /// The property is retrieved and is not found. public CoseHeaderValue this[CoseHeaderLabel key] { get => _headerParameters[key]; @@ -47,14 +79,50 @@ public CoseHeaderValue this[CoseHeaderLabel key] } } + /// + /// Gets the value associated with the specified label, as a signed integer. + /// + /// The label of the value to get. + /// The value associated with the specified label, as a signed integer. + /// The value could not be decoded as a 32-bit signed integer. + /// is not found. public int GetValueAsInt32(CoseHeaderLabel label) => _headerParameters[label].GetValueAsInt32(); + /// + /// Gets the value associated with the specified label, as a text string. + /// + /// The label of the value to get. + /// The value associated with the specified label, as a text string. + /// The value could not be decoded as text string. + /// is not found. public string GetValueAsString(CoseHeaderLabel label) => _headerParameters[label].GetValueAsString(); + /// + /// Gets the value associated with the specified label, as a byte string. + /// + /// The label of the value to get. + /// The value associated with the specified label, as a byte string. + /// The value could not be decoded as byte string. public byte[] GetValueAsBytes(CoseHeaderLabel label) => _headerParameters[label].GetValueAsBytes(); + /// + /// Gets the value associated with the specified label, as a byte string. + /// + /// The label of the value to get. + /// The buffer in which to write the value. + /// The number of bytes written to . + /// is too small to hold the value. + /// The value could not be decoded as byte string. + /// is not found. public int GetValueAsBytes(CoseHeaderLabel label, Span destination) => _headerParameters[label].GetValueAsBytes(destination); + /// + /// Adds the specified key and value to the dictionary. + /// + /// The key of the element to add. + /// The value of the element to add. + /// The header map is read-only. + /// is not a valid CBOR value. public void Add(CoseHeaderLabel key, CoseHeaderValue value) { ValidateIsReadOnly(); @@ -62,44 +130,104 @@ public void Add(CoseHeaderLabel key, CoseHeaderValue value) _headerParameters.Add(key, value); } + /// + /// Adds the specified value to the header map with the specified key. + /// + /// The label (key) and value to add to the header map. + /// The header map is read-only. + /// 's value is not a valid CBOR value. public void Add(KeyValuePair item) => Add(item.Key, item.Value); + /// + /// Adds the specified label and value to the header map. + /// + /// The label for the header to add. + /// The value of the header to add. + /// The header map is read-only. public void Add(CoseHeaderLabel label, int value) => Add(label, CoseHeaderValue.FromInt32(value)); + /// + /// Adds the specified label and value to the header map. + /// + /// The label for the header to add. + /// The value of the header to add. + /// The header map is read-only. public void Add(CoseHeaderLabel label, string value) => Add(label, CoseHeaderValue.FromString(value)); + /// + /// Adds the specified label and value to the header map. + /// + /// The label for the header to add. + /// The value of the header to add. + /// The header map is read-only. + /// + /// does not need to contain a valid CBOR-encoded value, as it will be encoded as a CBOR byte string. + /// To specify a CBOR-encoded value directly, see and . + /// public void Add(CoseHeaderLabel label, byte[] value) => Add(label, CoseHeaderValue.FromBytes(value)); + /// + /// Adds the specified label and value to the header map. + /// + /// The label for the header to add. + /// The value of the header to add. + /// The header map is read-only. + /// + /// does not need to contain a valid CBOR-encoded value, as it will be encoded as a CBOR byte string. + /// To specify a CBOR-encoded value directly, see and . + /// public void Add(CoseHeaderLabel label, ReadOnlySpan value) => Add(label, CoseHeaderValue.FromBytes(value)); + /// public bool ContainsKey(CoseHeaderLabel key) => _headerParameters.ContainsKey(key); + /// public bool TryGetValue(CoseHeaderLabel key, out CoseHeaderValue value) => _headerParameters.TryGetValue(key, out value); + /// + /// Removes all labels and values from the header map. + /// + /// The header map is read-only. public void Clear() { ValidateIsReadOnly(); _headerParameters.Clear(); } + /// public bool Contains(KeyValuePair item) => HeaderParametersAsCollection.Contains(item); + /// public void CopyTo(KeyValuePair[] array, int arrayIndex) => HeaderParametersAsCollection.CopyTo(array, arrayIndex); + /// public IEnumerator> GetEnumerator() => _headerParameters.GetEnumerator(); + /// IEnumerator IEnumerable.GetEnumerator() => _headerParameters.GetEnumerator(); + /// + /// Removes the value with the specified label from the header map. + /// + /// The label of the element to remove. + /// if was found in the map; otherwise, . + /// The header map is read-only. public bool Remove(CoseHeaderLabel label) { ValidateIsReadOnly(); return _headerParameters.Remove(label); } + /// + /// Removes the first occurrence of a specific object from the header map. + /// + /// The object to remove from the map. + /// if the key and value represented by are successfully found in the map; otherwise, . + /// The header map is read-only. public bool Remove(KeyValuePair item) { ValidateIsReadOnly(); @@ -114,9 +242,9 @@ private void ValidateIsReadOnly() } } - private static void ValidateInsertion(CoseHeaderLabel label, CoseHeaderValue headerValue) + private static void ValidateInsertion(CoseHeaderLabel label, CoseHeaderValue value) { - var reader = new CborReader(headerValue.EncodedValue); + var reader = new CborReader(value.EncodedValue); try { if (label.LabelAsString != null) // all known headers are integers. @@ -133,7 +261,7 @@ private static void ValidateInsertion(CoseHeaderLabel label, CoseHeaderValue hea initialState != CborReaderState.UnsignedInteger && initialState != CborReaderState.TextString) { - throw new ArgumentException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label.LabelName)); + throw new ArgumentException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label.LabelName), nameof(value)); } reader.SkipValue(); break; @@ -141,7 +269,7 @@ private static void ValidateInsertion(CoseHeaderLabel label, CoseHeaderValue hea int length = reader.ReadStartArray().GetValueOrDefault(); if (length < 1) { - throw new ArgumentException(SR.CriticalHeadersMustBeArrayOfAtLeastOne); + throw new ArgumentException(SR.CriticalHeadersMustBeArrayOfAtLeastOne, nameof(value)); } for (int i = 0; i < length; i++) @@ -157,7 +285,7 @@ private static void ValidateInsertion(CoseHeaderLabel label, CoseHeaderValue hea } else { - throw new ArgumentException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label.LabelName)); + throw new ArgumentException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label.LabelName), nameof(value)); } } reader.SkipToParent(); @@ -166,14 +294,14 @@ private static void ValidateInsertion(CoseHeaderLabel label, CoseHeaderValue hea if (initialState != CborReaderState.TextString && initialState != CborReaderState.UnsignedInteger) { - throw new ArgumentException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label.LabelName)); + throw new ArgumentException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label.LabelName), nameof(value)); } reader.SkipValue(); break; case KnownHeaders.Kid: if (initialState != CborReaderState.ByteString) { - throw new ArgumentException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label.LabelName)); + throw new ArgumentException(SR.Format(SR.CoseHeaderMapHeaderDoesNotAcceptSpecifiedValue, label.LabelName), nameof(value)); } reader.SkipValue(); break; @@ -190,7 +318,7 @@ private static void ValidateInsertion(CoseHeaderLabel label, CoseHeaderValue hea } catch (Exception ex) when (ex is CborContentException or InvalidOperationException) { - throw new ArgumentException(SR.Format(SR.CoseHeaderMapArgumentCoseHeaderValueIncorrect, label.LabelName), ex); + throw new ArgumentException(SR.Format(SR.CoseHeaderMapArgumentCoseHeaderValueIncorrect, label.LabelName), nameof(value), ex); } } diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderValue.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderValue.cs index 2785f62c0593c4..dffda0904c90b4 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderValue.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHeaderValue.cs @@ -6,8 +6,15 @@ namespace System.Security.Cryptography.Cose { + /// + /// Represents a COSE header value. + /// public readonly struct CoseHeaderValue : IEquatable { + /// + /// Gets the CBOR-encoded value of this instance. + /// + /// A view of the CBOR-encoded value as a contiguous region of memory. public readonly ReadOnlyMemory EncodedValue { get; } private CoseHeaderValue(ReadOnlyMemory encodedValue) @@ -22,12 +29,23 @@ private static CoseHeaderValue FromEncodedValue(ReadOnlyMemory encodedValu return value; } + /// + /// Creates a instance from a CBOR-encoded value. + /// + /// A CBOR-encoded value to represent. + /// An instance that represents the encoded value. public static CoseHeaderValue FromEncodedValue(ReadOnlySpan encodedValue) { var encodedValueCopy = new ReadOnlyMemory(encodedValue.ToArray()); return FromEncodedValue(encodedValueCopy); } + /// + /// Creates a instance from a CBOR-encoded value. + /// + /// A CBOR-encoded value to represent. + /// An instance that represents the encoded value. + /// is . public static CoseHeaderValue FromEncodedValue(byte[] encodedValue) { if (encodedValue == null) @@ -46,6 +64,11 @@ private static ReadOnlyMemory Encode(CborWriter writer) return buffer.AsMemory(); } + /// + /// Creates a instance from a signed integer. + /// + /// The value to represent. + /// An instance that represents the specified value. public static CoseHeaderValue FromInt32(int value) { var writer = new CborWriter(); @@ -54,6 +77,12 @@ public static CoseHeaderValue FromInt32(int value) return FromEncodedValue(Encode(writer)); } + /// + /// Creates a instance from a string. + /// + /// The value to represent. + /// An instance that represents the specified value. + /// is . public static CoseHeaderValue FromString(string value) { if (value == null) @@ -67,6 +96,12 @@ public static CoseHeaderValue FromString(string value) return FromEncodedValue(Encode(writer)); } + /// + /// Creates a instance from a span of bytes. + /// + /// The bytes to be encoded and that the instance will represent. + /// An instance that represents the CBOR-encoded . + /// public static CoseHeaderValue FromBytes(ReadOnlySpan value) { var writer = new CborWriter(); @@ -75,6 +110,13 @@ public static CoseHeaderValue FromBytes(ReadOnlySpan value) return FromEncodedValue(Encode(writer)); } + /// + /// Creates a instance from a byte array. + /// + /// The bytes to be encoded and that the instance will represent. + /// An instance that represents the CBOR-encoded . + /// is . + /// public static CoseHeaderValue FromBytes(byte[] value) { if (value == null) @@ -85,6 +127,11 @@ public static CoseHeaderValue FromBytes(byte[] value) return FromBytes(value.AsSpan()); } + /// + /// Gets the value as a signed integer. + /// + /// The value as a signed integer. + /// The value could not be decoded as a 32-bit signed integer. public int GetValueAsInt32() { var reader = new CborReader(EncodedValue); @@ -107,6 +154,11 @@ public int GetValueAsInt32() return retVal; } + /// + /// Gets the value as a text string. + /// + /// The value as a text string. + /// The value could not be decoded as text string. public string GetValueAsString() { var reader = new CborReader(EncodedValue); @@ -129,6 +181,11 @@ public string GetValueAsString() return retVal; } + /// + /// Gets the CBOR-encoded value as a byte string. + /// + /// The decoded value as a byte array. + /// The value could not be decoded as byte string. public byte[] GetValueAsBytes() { var reader = new CborReader(EncodedValue); @@ -151,6 +208,13 @@ public byte[] GetValueAsBytes() return retVal; } + /// + /// Gets the CBOR-encoded value as a byte string. + /// + /// The buffer in which to write the decoded value. + /// The number of bytes written to . + /// is too small to hold the value. + /// The value could not be decoded as byte string. public int GetValueAsBytes(Span destination) { var reader = new CborReader(EncodedValue); @@ -160,7 +224,7 @@ public int GetValueAsBytes(Span destination) { if (!reader.TryReadByteString(destination, out bytesWritten)) { - throw new ArgumentException(SR.Argument_EncodeDestinationTooSmall); + throw new ArgumentException(SR.Argument_DestinationTooSmall, nameof(destination)); } } catch (Exception ex) when (ex is CborContentException or InvalidOperationException) @@ -176,10 +240,24 @@ public int GetValueAsBytes(Span destination) return bytesWritten; } + /// + /// Returns a value indicating whether this instance is equal to the specified instance. + /// + /// The object to compare to this instance. + /// if the value parameter equals the value of this instance; otherwise, . public override bool Equals([NotNullWhen(true)] object? obj) => obj is CoseHeaderValue otherObj && Equals(otherObj); + /// + /// Returns a value indicating whether this instance is equal to a specified object. + /// + /// The object to compare to this instance. + /// if value is an instance of and equals the value of this instance; otherwise, . public bool Equals(CoseHeaderValue other) => EncodedValue.Span.SequenceEqual(other.EncodedValue.Span); + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer hash code. public override int GetHashCode() { HashCode hashCode = default; @@ -190,8 +268,20 @@ public override int GetHashCode() return hashCode.ToHashCode(); } + /// + /// Determines whether two specified header value instances are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// if left and right represent the same value; otherwise, . public static bool operator ==(CoseHeaderValue left, CoseHeaderValue right) => left.Equals(right); + /// + /// Determines whether two specified header value instances are not equal. + /// + /// The first object to compare. + /// The second object to compare. + /// if left and right do not represent the same value; otherwise, . public static bool operator !=(CoseHeaderValue left, CoseHeaderValue right) => !left.Equals(right); } } diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHelpers.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHelpers.cs index 32f5f65a196ff1..12e9fd9a787368 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHelpers.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseHelpers.cs @@ -235,7 +235,7 @@ internal static int ComputeSignatureSize(CoseSigner signer) if (reader.BytesRemaining != 0) { - throw new CryptographicException(SR.Sign1VerifyAlgHeaderWasIncorrect); + return null; } return (int)alg; @@ -247,7 +247,7 @@ internal static int ComputeSignatureSize(CoseSigner signer) if (reader.BytesRemaining != 0) { - throw new CryptographicException(SR.Sign1VerifyAlgHeaderWasIncorrect); + return null; } return alg; diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseMessage.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseMessage.cs index 7dfe9064dc3307..c04a35c47f485f 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseMessage.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseMessage.cs @@ -13,6 +13,9 @@ namespace System.Security.Cryptography.Cose { + /// + /// Represents a COSE message as described in RFC 8152. + /// public abstract class CoseMessage { private const string SigStructureContextSign = "Signature"; @@ -30,7 +33,17 @@ public abstract class CoseMessage private CoseHeaderMap _protectedHeaders; private CoseHeaderMap _unprotectedHeaders; + + /// + /// Gets the protected header parameters associated to this message. + /// + /// A collection of protected header parameters associated to this message. public CoseHeaderMap ProtectedHeaders => _protectedHeaders; + + /// + /// Gets the unprotected header parameters associated to this message. + /// + /// A collection of unprotected header parameters associated to this message. public CoseHeaderMap UnprotectedHeaders => _unprotectedHeaders; internal CoseMessage(CoseHeaderMap protectedHeader, CoseHeaderMap unprotectedHeader, byte[]? content, byte[] encodedProtectedHeader, bool isTagged) @@ -42,6 +55,10 @@ internal CoseMessage(CoseHeaderMap protectedHeader, CoseHeaderMap unprotectedHea _isTagged = isTagged; } + /// + /// Gets the content of this message or if the content was detached from the message. + /// + /// A region of memory that contains the content of this message or if the content was detached from the message. // Sign and MAC also refer to the content as payload. // Encrypt also refers to the content as cyphertext. public ReadOnlyMemory? Content @@ -60,6 +77,13 @@ public ReadOnlyMemory? Content [MemberNotNullWhen(false, nameof(Content))] internal bool IsDetached => _content == null; + /// + /// Decodes a CBOR payload as a COSE_Sign1 message. + /// + /// The sequence of bytes to decode. + /// The decoded message. + /// is . + /// could not be decoded as a COSE_Sign1 message. public static CoseSign1Message DecodeSign1(byte[] cborPayload) { if (cborPayload is null) @@ -68,6 +92,12 @@ public static CoseSign1Message DecodeSign1(byte[] cborPayload) return DecodeCoseSign1Core(new CborReader(cborPayload)); } + /// + /// Decodes a CBOR payload as a COSE_Sign1 message. + /// + /// The sequence of CBOR-encoded bytes to decode. + /// The decoded message. + /// could not be decoded as a COSE_Sign1 message. public static CoseSign1Message DecodeSign1(ReadOnlySpan cborPayload) { unsafe @@ -104,7 +134,10 @@ private static CoseSign1Message DecodeCoseSign1Core(CborReader reader) var unprotectedHeader = new CoseHeaderMap(); DecodeUnprotectedBucket(reader, unprotectedHeader); - ThrowIfDuplicateLabels(protectedHeader, unprotectedHeader); + if (ContainDuplicateLabels(protectedHeader, unprotectedHeader)) + { + throw new CryptographicException(SR.Sign1SignHeaderDuplicateLabels); + } byte[]? payload = DecodePayload(reader); byte[] signature = DecodeSignature(reader); @@ -112,7 +145,7 @@ private static CoseSign1Message DecodeCoseSign1Core(CborReader reader) if (reader.BytesRemaining != 0) { - throw new CryptographicException(SR.Format(SR.DecodeErrorWhileDecoding, SR.DecodeSign1MessageContainedTrailingData)); + throw new CryptographicException(SR.Format(SR.DecodeErrorWhileDecoding, SR.DecodeMessageContainedTrailingData)); } return new CoseSign1Message(protectedHeader, unprotectedHeader, payload, signature, protectedHeaderAsBstr, tag.HasValue); @@ -123,6 +156,13 @@ private static CoseSign1Message DecodeCoseSign1Core(CborReader reader) } } + /// + /// Decodes a CBOR payload as a COSE_Sign message. + /// + /// The sequence of bytes to decode. + /// The decoded message. + /// is . + /// could not be decoded as a COSE_Sign message. public static CoseMultiSignMessage DecodeMultiSign(byte[] cborPayload) { if (cborPayload is null) @@ -131,6 +171,12 @@ public static CoseMultiSignMessage DecodeMultiSign(byte[] cborPayload) return DecodeCoseMultiSignCore(new CborReader(cborPayload)); } + /// + /// Decodes a CBOR payload as a COSE_Sign message. + /// + /// The sequence of bytes to decode. + /// The decoded message. + /// could not be decoded as a COSE_Sign message. public static CoseMultiSignMessage DecodeMultiSign(ReadOnlySpan cborPayload) { unsafe @@ -167,7 +213,10 @@ private static CoseMultiSignMessage DecodeCoseMultiSignCore(CborReader reader) var unprotectedHeaders = new CoseHeaderMap(); DecodeUnprotectedBucket(reader, unprotectedHeaders); - ThrowIfDuplicateLabels(protectedHeaders, unprotectedHeaders); + if (ContainDuplicateLabels(protectedHeaders, unprotectedHeaders)) + { + throw new CryptographicException(SR.Sign1SignHeaderDuplicateLabels); + } byte[]? payload = DecodePayload(reader); List signatures = DecodeCoseSignaturesArray(reader, encodedProtectedHeaders); @@ -176,7 +225,7 @@ private static CoseMultiSignMessage DecodeCoseMultiSignCore(CborReader reader) if (reader.BytesRemaining != 0) { - throw new CryptographicException(SR.Format(SR.DecodeErrorWhileDecoding, SR.DecodeSign1MessageContainedTrailingData)); + throw new CryptographicException(SR.Format(SR.DecodeErrorWhileDecoding, SR.DecodeMessageContainedTrailingData)); } return new CoseMultiSignMessage(protectedHeaders, unprotectedHeaders, payload, signatures, encodedProtectedHeaders, tag.HasValue); @@ -206,7 +255,12 @@ private static void DecodeProtectedBucket(CborReader reader, CoseHeaderMap heade var protectedHeaderReader = new CborReader(protectedHeaderAsBstr); DecodeBucket(protectedHeaderReader, headerParameters); - ThrowIfMissingCriticalHeaders(headerParameters); + + if (MissingCriticalHeaders(headerParameters, out string? labelName)) + { + throw new CryptographicException(SR.Format(SR.CriticalHeaderMissing, labelName)); + } + headerParameters.IsReadOnly = true; if (protectedHeaderReader.BytesRemaining != 0) @@ -285,7 +339,10 @@ private static List DecodeCoseSignaturesArray(CborReader reader, var unprotectedHeaders = new CoseHeaderMap(); DecodeUnprotectedBucket(reader, unprotectedHeaders); - ThrowIfDuplicateLabels(protectedHeaders, unprotectedHeaders); + if (ContainDuplicateLabels(protectedHeaders, unprotectedHeaders)) + { + throw new CryptographicException(SR.Sign1SignHeaderDuplicateLabels); + } byte[] signatureBytes = DecodeSignature(reader); @@ -429,28 +486,31 @@ internal static int ComputeToBeSignedEncodedSize(SigStructureContext context, in } // Validate duplicate labels https://datatracker.ietf.org/doc/html/rfc8152#section-3. - internal static void ThrowIfDuplicateLabels(CoseHeaderMap? protectedHeaders, CoseHeaderMap? unprotectedHeaders) + internal static bool ContainDuplicateLabels(CoseHeaderMap? protectedHeaders, CoseHeaderMap? unprotectedHeaders) { if (protectedHeaders == null || unprotectedHeaders == null) { - return; + return false; } foreach (KeyValuePair kvp in protectedHeaders) { if (unprotectedHeaders.ContainsKey(kvp.Key)) { - throw new CryptographicException(SR.Sign1SignHeaderDuplicateLabels); + return true; } } + + return false; } - internal static void ThrowIfMissingCriticalHeaders(CoseHeaderMap? protectedHeders) + internal static bool MissingCriticalHeaders(CoseHeaderMap? protectedHeders, out string? labelName) { if (protectedHeders == null || !protectedHeders.TryGetValue(CoseHeaderLabel.CriticalHeaders, out CoseHeaderValue critHeaderValue)) { - return; + labelName = null; + return false; } var reader = new CborReader(critHeaderValue.EncodedValue); @@ -468,11 +528,20 @@ internal static void ThrowIfMissingCriticalHeaders(CoseHeaderMap? protectedHeder if (!protectedHeders.ContainsKey(label)) { - throw new CryptographicException(SR.Format(SR.CriticalHeaderMissing, label.LabelName)); + labelName = label.LabelName; + return true; } } + + labelName = null; + return false; } + /// + /// Encodes this message as CBOR. + /// + /// The message encoded as CBOR. + /// The and collections have one or more labels in common. public byte[] Encode() { byte[] buffer = new byte[GetEncodedLength()]; @@ -482,6 +551,15 @@ public byte[] Encode() return buffer; } + /// + /// Encodes this message as CBOR. + /// + /// The buffer in which to write the encoded value. + /// The number of bytes written to . + /// Use to determine how many bytes result in encoding this message. + /// is too small to hold the value. + /// The and collections have one or more labels in common. + /// public int Encode(Span destination) { if (!TryEncode(destination, out int bytesWritten)) @@ -492,8 +570,21 @@ public int Encode(Span destination) return bytesWritten; } + /// + /// When overriden in a derived class, attempts to encode this message into the specified buffer. + /// + /// The buffer in which to write the encoded value. + /// On success, receives the number of bytes written to . This parameter is treated as uninitialized. + /// if had sufficient length to receive the value; otherwise, . + /// Use to determine how many bytes result in encoding this message. + /// The and collections have one or more labels in common. + /// public abstract bool TryEncode(Span destination, out int bytesWritten); + /// + /// When overriden in a derived class, calculates the number of bytes produced by encoding this . + /// + /// The number of bytes produced by encoding this message. public abstract int GetEncodedLength(); } } diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseMultiSignMessage.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseMultiSignMessage.cs index b32b4ee8133464..3e5b9367551298 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseMultiSignMessage.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseMultiSignMessage.cs @@ -12,6 +12,9 @@ namespace System.Security.Cryptography.Cose { + /// + /// Represents a multiple signature COSE_Sign message. + /// public sealed class CoseMultiSignMessage : CoseMessage { private const int MultiSignArrayLength = 4; @@ -19,6 +22,11 @@ public sealed class CoseMultiSignMessage : CoseMessage internal const int CoseSignatureArrayLength = 3; private readonly List _signatures; + + /// + /// Gets a read-only collection of signatures associated to this message. + /// + /// A read-only collection of signatures associated to this message. public ReadOnlyCollection Signatures { get; } internal CoseMultiSignMessage(CoseHeaderMap protectedHeader, CoseHeaderMap unprotectedHeader, byte[]? content, List signatures, byte[] encodedProtectedHeader, bool isTagged) @@ -33,6 +41,29 @@ internal CoseMultiSignMessage(CoseHeaderMap protectedHeader, CoseHeaderMap unpro _signatures = signatures; } + /// + /// Signs the specified content and encodes it as a COSE_Sign message with detached content. + /// + /// The content to sign. + /// The signer information used to sign . + /// The protected header parameters to append to the message's content layer. + /// The unprotected header parameters to append to the message's content layer. + /// The extra data associated with the signature, which must also be provided during verification. + /// The encoded message. + /// or is . + /// + /// + /// The and collections have one or more labels in common. + /// + /// -or- + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static byte[] SignDetached(byte[] detachedContent, CoseSigner signer, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null, byte[]? associatedData = null) { if (detachedContent is null) @@ -41,6 +72,29 @@ public static byte[] SignDetached(byte[] detachedContent, CoseSigner signer, Cos return SignCore(detachedContent, null, signer, protectedHeaders, unprotectedHeaders, associatedData, isDetached: true); } + /// + /// Signs the specified content and encodes it as a COSE_Sign message with embedded content. + /// + /// The content to sign and to include in the message. + /// The signer information used to sign . + /// The protected header parameters to append to the message's content layer. + /// The unprotected header parameters to append to the message's content layer. + /// The extra data associated with the signature, which must also be provided during verification. + /// The encoded message. + /// or is . + /// + /// + /// The and collections have one or more labels in common. + /// + /// -or- + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static byte[] SignEmbedded(byte[] embeddedContent, CoseSigner signer, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null, byte[]? associatedData = null) { if (embeddedContent is null) @@ -49,12 +103,87 @@ public static byte[] SignEmbedded(byte[] embeddedContent, CoseSigner signer, Cos return SignCore(embeddedContent, null, signer, protectedHeaders, unprotectedHeaders, associatedData, isDetached: false); } + /// + /// Signs the specified content and encodes it as a COSE_Sign message with detached content. + /// + /// The content to sign. + /// The signer information used to sign . + /// The protected header parameters to append to the message's content layer. + /// The unprotected header parameters to append to the message's content layer. + /// The extra data associated with the signature, which must also be provided during verification. + /// The encoded message. + /// is . + /// + /// + /// The and collections have one or more labels in common. + /// + /// -or- + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static byte[] SignDetached(ReadOnlySpan detachedContent, CoseSigner signer, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null, ReadOnlySpan associatedData = default) => SignCore(detachedContent, null, signer, protectedHeaders, unprotectedHeaders, associatedData, isDetached: true); + + /// + /// Signs the specified content and encodes it as a COSE_Sign message with detached content. + /// + /// The content to sign and to include in the message. + /// The signer information used to sign . + /// The protected header parameters to append to the message's content layer. + /// The unprotected header parameters to append to the message's content layer. + /// The extra data associated with the signature, which must also be provided during verification. + /// The encoded message. + /// is . + /// + /// + /// The and collections have one or more labels in common. + /// + /// -or- + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static byte[] SignEmbedded(ReadOnlySpan embeddedContent, CoseSigner signer, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null, ReadOnlySpan associatedData = default) => SignCore(embeddedContent, null, signer, protectedHeaders, unprotectedHeaders, associatedData, isDetached: false); + /// + /// Signs the specified content and encodes it as a COSE_Sign message with detached content. + /// + /// The content to sign. + /// The signer information used to sign . + /// The protected header parameters to append to the message's content layer. + /// The unprotected header parameters to append to the message's content layer. + /// The extra data associated with the signature, which must also be provided during verification. + /// The encoded message. + /// or is . + /// + /// + /// does not support reading or seeking. + /// + /// -or- + /// + /// The and collections have one or more labels in common. + /// + /// -or- + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// + /// public static byte[] SignDetached(Stream detachedContent, CoseSigner signer, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null, ReadOnlySpan associatedData = default) { if (detachedContent is null) @@ -92,6 +221,34 @@ private static byte[] SignCore( return buffer; } + /// + /// Asynchronously signs the specified content and encodes it as a COSE_Sign message with detached content. + /// + /// The content to sign. + /// The signer information used to sign . + /// The protected header parameters to append to the message's content layer. + /// The unprotected header parameters to append to the message's content layer. + /// The extra data associated with the signature, which must also be provided during verification. + /// The token to monitor for cancellation requests. The default value is . + /// A task that represents the asynchronous operation. The value of its property contains the encoded message. + /// or is . + /// + /// + /// does not support reading or seeking. + /// + /// -or- + /// + /// The and collections have one or more labels in common. + /// + /// -or- + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static Task SignDetachedAsync( Stream detachedContent, CoseSigner signer, @@ -134,9 +291,59 @@ private static async Task SignAsyncCore( return buffer; } + /// + /// Attempts to sign the specified content and encode it as a COSE_Sign message with detached content into the specified buffer. + /// + /// The content to sign. + /// The buffer in which to write the encoded bytes. + /// The signer information used to sign . + /// On success, receives the number of bytes written to . + /// The protected header parameters to append to the message's content layer. + /// The unprotected header parameters to append to the message's content layer. + /// The extra data associated with the signature, which must also be provided during verification. + /// if had sufficient length to receive the encoded message; otherwise, . + /// is . + /// + /// + /// The and collections have one or more labels in common. + /// + /// -or- + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static bool TrySignDetached(ReadOnlySpan detachedContent, Span destination, CoseSigner signer, out int bytesWritten, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null, ReadOnlySpan associatedData = default) => TrySign(detachedContent, destination, signer, protectedHeaders, unprotectedHeaders, out bytesWritten, associatedData, isDetached: true); + /// + /// Signs the specified content and encodes it as a COSE_Sign message with embedded content. + /// + /// The content to sign and to include in the message. + /// The buffer in which to write the encoded bytes. + /// The signer information used to sign . + /// On success, receives the number of bytes written to . + /// The protected header parameters to append to the message's content layer. + /// The unprotected header parameters to append to the message's content layer. + /// The extra data associated with the signature, which must also be provided during verification. + /// if had sufficient length to receive the encoded message; otherwise, . + /// is . + /// + /// + /// The and collections have one or more labels in common. + /// + /// -or- + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static bool TrySignEmbedded(ReadOnlySpan embeddedContent, Span destination, CoseSigner signer, out int bytesWritten, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null, ReadOnlySpan associatedData = default) => TrySign(embeddedContent, destination, signer, protectedHeaders, unprotectedHeaders, out bytesWritten, associatedData, isDetached: false); @@ -214,10 +421,25 @@ private static async Task CreateCoseMultiSignMessageAsync( private static void ValidateBeforeSign(CoseSigner signer, CoseHeaderMap? protectedHeaders, CoseHeaderMap? unprotectedHeaders) { - ThrowIfDuplicateLabels(signer._protectedHeaders, signer._unprotectedHeaders); - ThrowIfDuplicateLabels(protectedHeaders, unprotectedHeaders); - ThrowIfMissingCriticalHeaders(signer._protectedHeaders); - ThrowIfMissingCriticalHeaders(protectedHeaders); + if (ContainDuplicateLabels(signer._protectedHeaders, signer._unprotectedHeaders)) + { + throw new ArgumentException(SR.Sign1SignHeaderDuplicateLabels, nameof(signer)); + } + + if (ContainDuplicateLabels(protectedHeaders, unprotectedHeaders)) + { + throw new ArgumentException(SR.Sign1SignHeaderDuplicateLabels); + } + + if (MissingCriticalHeaders(signer._protectedHeaders, out string? labelName)) + { + throw new ArgumentException(SR.Format(SR.CriticalHeaderMissing, labelName), nameof(signer)); + } + + if (MissingCriticalHeaders(protectedHeaders, out labelName)) + { + throw new ArgumentException(SR.Format(SR.CriticalHeaderMissing, labelName), nameof(protectedHeaders)); + } } private static void WriteCoseSignaturesArray( @@ -312,6 +534,10 @@ private static int ComputeEncodedSize(CoseSigner signer, CoseHeaderMap? protecte return encodedSize; } + /// + /// Calculates the number of bytes produced by encoding this message. + /// + /// The number of bytes produced by encoding this message. public override int GetEncodedLength() { int encodedLength = CoseHelpers.GetCoseSignEncodedLengthMinusSignature(_isTagged, MultiSignSizeOfCborTag, _protectedHeaderAsBstr.Length, UnprotectedHeaders, _content); @@ -328,12 +554,33 @@ public override int GetEncodedLength() return encodedLength; } + /// + /// Attempts to encode this message into the specified buffer. + /// + /// The buffer in which to write the encoded value. + /// On success, receives the number of bytes written to . + /// if had sufficient length to receive the value; otherwise, . + /// Use to determine how many bytes result in encoding this message. + /// + /// + /// The and collections have one or more labels in common. + /// + /// -or- + /// + /// The message does not contain at least one signature. + /// + /// + /// public override bool TryEncode(Span destination, out int bytesWritten) { - ThrowIfDuplicateLabels(ProtectedHeaders, UnprotectedHeaders); + if (ContainDuplicateLabels(ProtectedHeaders, UnprotectedHeaders)) + { + throw new InvalidOperationException(SR.Sign1SignHeaderDuplicateLabels); + } + if (Signatures.Count < 1) { - throw new CryptographicException(SR.MultiSignMessageMustCarryAtLeastOneSignature); + throw new InvalidOperationException(SR.MultiSignMessageMustCarryAtLeastOneSignature); } if (destination.Length < GetEncodedLength()) @@ -360,7 +607,11 @@ public override bool TryEncode(Span destination, out int bytesWritten) writer.WriteStartArray(Signatures.Count); foreach (CoseSignature signature in Signatures) { - ThrowIfDuplicateLabels(signature.ProtectedHeaders, signature.UnprotectedHeaders); + if (ContainDuplicateLabels(signature.ProtectedHeaders, signature.UnprotectedHeaders)) + { + throw new InvalidOperationException(SR.Sign1SignHeaderDuplicateLabels); + } + writer.WriteStartArray(CoseSignatureArrayLength); writer.WriteByteString(signature._encodedSignProtectedHeaders); @@ -379,9 +630,41 @@ public override bool TryEncode(Span destination, out int bytesWritten) return true; } + /// + /// Adds a signature for the content embedded in this message. + /// + /// The signer information used to sign the content. + /// The extra data associated with the signature, which must also be provided during verification. + /// is . + /// + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// + /// The content is detached from this message, use an overload that accepts a detached content. public void AddSignatureForEmbedded(CoseSigner signer, byte[]? associatedData = null) => AddSignatureForEmbedded(signer, associatedData.AsSpan()); + /// + /// Adds a signature for the content embedded in this message. + /// + /// The signer information used to sign the content. + /// The extra data associated with the signature, which must also be provided during verification. + /// is . + /// + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// + /// The content is detached from this message, use an overload that accepts a detached content. public void AddSignatureForEmbedded(CoseSigner signer, ReadOnlySpan associatedData) { if (signer == null) @@ -397,6 +680,23 @@ public void AddSignatureForEmbedded(CoseSigner signer, ReadOnlySpan associ AddSignatureCore(_content, null, signer, associatedData); } + /// + /// Adds a signature for the specified content to this message. + /// + /// The content to sign. + /// The signer information used to sign the content. + /// The extra data associated with the signature, which must also be provided during verification. + /// or is . + /// + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// + /// The content is embedded on this message, use an overload that uses embedded content. public void AddSignatureForDetached(byte[] detachedContent, CoseSigner signer, byte[]? associatedData = null) { if (detachedContent == null) @@ -407,6 +707,23 @@ public void AddSignatureForDetached(byte[] detachedContent, CoseSigner signer, b AddSignatureForDetached(detachedContent.AsSpan(), signer, associatedData); } + /// + /// Adds a signature for the specified content to this message. + /// + /// The content to sign. + /// The signer information used to sign the content. + /// The extra data associated with the signature, which must also be provided during verification. + /// is . + /// + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// + /// The content is embedded on this message, use an overload that uses embedded content. public void AddSignatureForDetached(ReadOnlySpan detachedContent, CoseSigner signer, ReadOnlySpan associatedData = default) { if (signer == null) @@ -422,6 +739,27 @@ public void AddSignatureForDetached(ReadOnlySpan detachedContent, CoseSign AddSignatureCore(detachedContent, null, signer, associatedData); } + /// + /// Adds a signature for the specified content to this message. + /// + /// The content to sign. + /// The signer information used to sign the content. + /// The extra data associated with the signature, which must also be provided during verification. + /// or is . + /// + /// + /// does not support reading or seeking. + /// + /// -or- + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// + /// The content is embedded on this message, use an overload that uses embedded content. public void AddSignatureForDetached(Stream detachedContent, CoseSigner signer, ReadOnlySpan associatedData = default) { if (detachedContent == null) @@ -475,6 +813,29 @@ private void AddSignatureCore(ReadOnlySpan contentBytes, Stream? contentSt } } + /// + /// Asynchronously adds a signature for the specified content to this message. + /// + /// The content to sign. + /// The signer information used to sign the content. + /// The extra data associated with the signature, which must also be provided during verification. + /// The token to monitor for cancellation requests. The default value is . + /// A task that represents the asynchronous operation. + /// or is . + /// + /// + /// does not support reading or seeking. + /// + /// -or- + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// + /// The content is embedded on this message, use an overload that uses embedded content. public Task AddSignatureForDetachedAsync(Stream detachedContent, CoseSigner signer, ReadOnlyMemory associatedData = default, CancellationToken cancellationToken = default) { if (detachedContent == null) @@ -522,6 +883,12 @@ private async Task AddSignatureCoreAsync(Stream content, CoseSigner signer, Read ArrayPool.Shared.Return(buffer, clearArray: true); } + /// + /// Removes the specified signature from the message. + /// + /// The signature to remove from the message. + /// is . + /// public void RemoveSignature(CoseSignature signature) { if (signature == null) @@ -532,6 +899,20 @@ public void RemoveSignature(CoseSignature signature) _signatures.Remove(signature); } + /// + /// Removes the signature at the specified index from the message. + /// + /// The zero-based index of the signature to remove. + /// + /// + /// is less than 0. + /// + /// -or- + /// + /// is equal to or greater than the number of elements in . + /// + /// + /// public void RemoveSignature(int index) => _signatures.RemoveAt(index); } diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSign1Message.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSign1Message.cs index 773c6d065b091c..c2ffd0cecbb0a0 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSign1Message.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSign1Message.cs @@ -10,6 +10,9 @@ namespace System.Security.Cryptography.Cose { + /// + /// Represents a single-signature COSE_Sign1 message. + /// public sealed class CoseSign1Message : CoseMessage { private const int Sign1ArrayLength = 4; @@ -22,6 +25,23 @@ internal CoseSign1Message(CoseHeaderMap protectedHeader, CoseHeaderMap unprotect _signature = signature; } + /// + /// Signs the specified content and encodes it as a COSE_Sign1 message with detached content. + /// + /// The content to sign. + /// The signer information used to sign . + /// The extra data associated with the signature, which must also be provided during verification. + /// The encoded message. + /// or is . + /// + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static byte[] SignDetached(byte[] detachedContent, CoseSigner signer, byte[]? associatedData = null) { if (detachedContent is null) @@ -33,6 +53,23 @@ public static byte[] SignDetached(byte[] detachedContent, CoseSigner signer, byt return SignCore(detachedContent.AsSpan(), null, signer, associatedData, isDetached: true); } + /// + /// Signs the specified content and encodes it as a COSE_Sign1 message with embedded content. + /// + /// The content to sign and to include in the message. + /// The signer information used to sign . + /// The extra data associated with the signature, which must also be provided during verification. + /// The encoded message. + /// or is . + /// + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static byte[] SignEmbedded(byte[] embeddedContent, CoseSigner signer, byte[]? associatedData = null) { if (embeddedContent is null) @@ -44,6 +81,23 @@ public static byte[] SignEmbedded(byte[] embeddedContent, CoseSigner signer, byt return SignCore(embeddedContent.AsSpan(), null, signer, associatedData, isDetached: false); } + /// + /// Signs the specified content and encodes it as a COSE_Sign1 message with detached content. + /// + /// The content to sign. + /// The signer information used to sign . + /// The extra data associated with the signature, which must also be provided during verification. + /// The encoded message. + /// is . + /// + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static byte[] SignDetached(ReadOnlySpan detachedContent, CoseSigner signer, ReadOnlySpan associatedData = default) { if (signer is null) @@ -52,6 +106,23 @@ public static byte[] SignDetached(ReadOnlySpan detachedContent, CoseSigner return SignCore(detachedContent, null, signer, associatedData, isDetached: true); } + /// + /// Signs the specified content and encodes it as a COSE_Sign1 message with embedded content. + /// + /// The content to sign and to include in the message. + /// The signer information used to sign . + /// The extra data associated with the signature, which must also be provided during verification. + /// The encoded message. + /// is . + /// + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static byte[] SignEmbedded(ReadOnlySpan embeddedContent, CoseSigner signer, ReadOnlySpan associatedData = default) { if (signer is null) @@ -60,6 +131,27 @@ public static byte[] SignEmbedded(ReadOnlySpan embeddedContent, CoseSigner return SignCore(embeddedContent, null, signer, associatedData, isDetached: false); } + /// + /// Signs the specified content and encodes it as a COSE_Sign1 message with detached content. + /// + /// The content to sign. + /// The signer information used to sign . + /// The extra data associated with the signature, which must also be provided during verification. + /// The encoded message. + /// or is . + /// + /// + /// does not support reading or seeking. + /// + /// -or- + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static byte[] SignDetached(Stream detachedContent, CoseSigner signer, ReadOnlySpan associatedData = default) { if (detachedContent is null) @@ -92,6 +184,28 @@ internal static byte[] SignCore(ReadOnlySpan contentBytes, Stream? content return buffer; } + /// + /// Asynchronously signs the specified content and encodes it as a COSE_Sign1 message with detached content. + /// + /// The content to sign. + /// The signer information used to sign . + /// The extra data associated with the signature, which must also be provided during verification. + /// The token to monitor for cancellation requests. The default value is . + /// A task that represents the asynchronous operation. The value of its property contains the encoded message. + /// or is . + /// + /// + /// does not support reading or seeking. + /// + /// -or- + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static Task SignDetachedAsync(Stream detachedContent, CoseSigner signer, ReadOnlyMemory associatedData = default, CancellationToken cancellationToken = default) { if (detachedContent is null) @@ -121,9 +235,47 @@ private static async Task SignAsyncCore(int expectedSize, Stream content return buffer; } + /// + /// Attempts to sign the specified content and encode it as a COSE_Sign1 message with detached content into the specified buffer. + /// + /// The content to sign. + /// The buffer in which to write the encoded bytes. + /// The signer information used to sign . + /// On success, receives the number of bytes written to . + /// The extra data associated with the signature, which must also be provided during verification. + /// if had sufficient length to receive the encoded message; otherwise, . + /// is . + /// + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static bool TrySignDetached(ReadOnlySpan detachedContent, Span destination, CoseSigner signer, out int bytesWritten, ReadOnlySpan associatedData = default) => TrySign(detachedContent, destination, signer, out bytesWritten, associatedData, isDetached: true); + /// + /// Attempts to sign the specified content and encode it as a COSE_Sign1 message with embedded content into the specified buffer. + /// + /// The content to sign and to include in the message. + /// The buffer in which to write the encoded bytes. + /// The signer information used to sign . + /// On success, receives the number of bytes written to . + /// The extra data associated with the signature, which must also be provided during verification. + /// if had sufficient length to receive the encoded message; otherwise, . + /// is . + /// + /// + /// The and collections in have one or more labels in common. + /// + /// -or- + /// + /// One or more of the labels specified in a header is missing. + /// + /// public static bool TrySignEmbedded(ReadOnlySpan embeddedContent, Span destination, CoseSigner signer, out int bytesWritten, ReadOnlySpan associatedData = default) => TrySign(embeddedContent, destination, signer, out bytesWritten, associatedData, isDetached: false); @@ -149,8 +301,15 @@ private static bool TrySign(ReadOnlySpan content, Span destination, internal static void ValidateBeforeSign(CoseSigner signer) { - ThrowIfDuplicateLabels(signer._protectedHeaders, signer._unprotectedHeaders); - ThrowIfMissingCriticalHeaders(signer._protectedHeaders); + if (ContainDuplicateLabels(signer._protectedHeaders, signer._unprotectedHeaders)) + { + throw new ArgumentException(SR.Sign1SignHeaderDuplicateLabels, nameof(signer)); + } + + if (MissingCriticalHeaders(signer._protectedHeaders, out string? labelName)) + { + throw new ArgumentException(SR.Format(SR.CriticalHeaderMissing, labelName), nameof(signer)); + } } private static int CreateCoseSign1Message(ReadOnlySpan contentBytes, Stream? contentStream, Span buffer, CoseSigner signer, ReadOnlySpan associatedData, bool isDetached) @@ -198,6 +357,34 @@ private static async Task CreateCoseSign1MessageAsync(Stream content, byte[ return writer.Encode(buffer); } + /// + /// Verifies that the signature is valid for the content using the specified key. + /// + /// The public key that is associated with the private key that was used to sign the content. + /// The extra data associated with the signature, which must match the value provided during signing. + /// if the signature is valid; otherwise, . + /// is . + /// is of an unsupported type. + /// The content is detached from this message, use an overload that accepts a detached content. + /// + /// + /// does not have a value for the header. + /// + /// -or- + /// + /// The algorithm protected header was incorrectly formatted. + /// + /// -or- + /// + /// The algorithm protected header was not one of the values supported by this implementation. + /// + /// -or- + /// + /// The algorithm protected header doesn't match with the algorithms supported by the specified . + /// + /// + /// + /// public bool VerifyEmbedded(AsymmetricAlgorithm key, byte[]? associatedData = null) { if (key is null) @@ -213,6 +400,33 @@ public bool VerifyEmbedded(AsymmetricAlgorithm key, byte[]? associatedData = nul return VerifyCore(key, _content, null, associatedData, CoseHelpers.GetKeyType(key)); } + /// + /// Verifies that the signature is valid for the content using the specified key. + /// + /// The public key that is associated with the private key that was used to sign the content. + /// The extra data associated with the signature, which must match the value provided during signing. + /// if the signature is valid; otherwise, . + /// is . + /// is of an unsupported type. + /// The content is detached from this message, use an overload that accepts a detached content. /// + /// + /// does not have a value for the header. + /// + /// -or- + /// + /// The algorithm protected header was incorrectly formatted. + /// + /// -or- + /// + /// The algorithm protected header was not one of the values supported by this implementation. + /// + /// -or- + /// + /// The algorithm protected header doesn't match with the algorithms supported by the specified . + /// + /// + /// + /// public bool VerifyEmbedded(AsymmetricAlgorithm key, ReadOnlySpan associatedData) { if (key is null) @@ -228,6 +442,35 @@ public bool VerifyEmbedded(AsymmetricAlgorithm key, ReadOnlySpan associate return VerifyCore(key, _content, null, associatedData, CoseHelpers.GetKeyType(key)); } + /// + /// Verifies that the signature is valid for the content using the specified key. + /// + /// The public key that is associated with the private key that was used to sign the content. + /// The content that was previously signed. + /// The extra data associated with the signature, which must match the value provided during signing. + /// if the signature is valid; otherwise, . + /// or is . + /// is of an unsupported type. + /// The content is embedded on this message, use an overload that uses embedded content. + /// + /// + /// does not have a value for the header. + /// + /// -or- + /// + /// The algorithm protected header was incorrectly formatted. + /// + /// -or- + /// + /// The algorithm protected header was not one of the values supported by this implementation. + /// + /// -or- + /// + /// The algorithm protected header doesn't match with the algorithms supported by the specified . + /// + /// + /// + /// public bool VerifyDetached(AsymmetricAlgorithm key, byte[] detachedContent, byte[]? associatedData = null) { if (key is null) @@ -247,6 +490,35 @@ public bool VerifyDetached(AsymmetricAlgorithm key, byte[] detachedContent, byte return VerifyCore(key, detachedContent, null, associatedData, CoseHelpers.GetKeyType(key)); } + /// + /// Verifies that the signature is valid for the content using the specified key. + /// + /// The public key that is associated with the private key that was used to sign the content. + /// The content that was previously signed. + /// The extra data associated with the signature, which must match the value provided during signing. + /// if the signature is valid; otherwise, . + /// is . + /// is of an unsupported type. + /// The content is embedded on this message, use an overload that uses embedded content. + /// + /// + /// does not have a value for the header. + /// + /// -or- + /// + /// The algorithm protected header was incorrectly formatted. + /// + /// -or- + /// + /// The algorithm protected header was not one of the values supported by this implementation. + /// + /// -or- + /// + /// The algorithm protected header doesn't match with the algorithms supported by the specified . + /// + /// + /// + /// public bool VerifyDetached(AsymmetricAlgorithm key, ReadOnlySpan detachedContent, ReadOnlySpan associatedData = default) { if (key is null) @@ -262,6 +534,43 @@ public bool VerifyDetached(AsymmetricAlgorithm key, ReadOnlySpan detachedC return VerifyCore(key, detachedContent, null, associatedData, CoseHelpers.GetKeyType(key)); } + /// + /// Verifies that the signature is valid for the content using the specified key. + /// + /// The public key that is associated with the private key that was used to sign the content. + /// The content that was previously signed. + /// The extra data associated with the signature, which must match the value provided during signing. + /// if the signature is valid; otherwise, . + /// or is . + /// + /// + /// is of an unsupported type. + /// + /// -or- + /// + /// does not support reading or seeking. + /// + /// + /// The content is embedded on this message, use an overload that uses embedded content. + /// + /// + /// does not have a value for the header. + /// + /// -or- + /// + /// The algorithm protected header was incorrectly formatted. + /// + /// -or- + /// + /// The algorithm protected header was not one of the values supported by this implementation. + /// + /// -or- + /// + /// The algorithm protected header doesn't match with the algorithms supported by the specified . + /// + /// + /// + /// public bool VerifyDetached(AsymmetricAlgorithm key, Stream detachedContent, ReadOnlySpan associatedData = default) { if (key is null) @@ -325,6 +634,44 @@ private bool VerifyCore(AsymmetricAlgorithm key, ReadOnlySpan contentBytes } } + /// + /// Asynchronously verifies that the signature is valid for the content using the specified key. + /// + /// The public key that is associated with the private key that was used to sign the content. + /// The content that was previously signed. + /// The extra data associated with the signature, which must match the value provided during signing. + /// The token to monitor for cancellation requests. The default value is . + /// A task whose property is if the signature is valid; otherwise, . + /// or is . + /// + /// + /// is of an unsupported type. + /// + /// -or- + /// + /// does not support reading or seeking. + /// + /// + /// The content is embedded on this message, use an overload that uses embedded content. + /// + /// + /// does not have a value for the header. + /// + /// -or- + /// + /// The algorithm protected header was incorrectly formatted. + /// + /// -or- + /// + /// The algorithm protected header was not one of the values supported by this implementation. + /// + /// -or- + /// + /// The algorithm protected header doesn't match with the algorithms supported by the specified . + /// + /// + /// + /// public Task VerifyDetachedAsync(AsymmetricAlgorithm key, Stream detachedContent, ReadOnlyMemory associatedData = default, CancellationToken cancellationToken = default) { if (key is null) @@ -429,13 +776,29 @@ private static int ComputeEncodedSize(CoseSigner signer, int contentLength, bool return encodedSize; } + /// + /// Calculates the number of bytes produced by encoding this message. + /// + /// The number of bytes produced by encoding this message. public override int GetEncodedLength() => CoseHelpers.GetCoseSignEncodedLengthMinusSignature(_isTagged, Sign1SizeOfCborTag, _protectedHeaderAsBstr.Length, UnprotectedHeaders, _content) + CoseHelpers.GetByteStringEncodedSize(_signature.Length); + /// + /// Attempts to encode this message into the specified buffer. + /// + /// The buffer in which to write the encoded value. + /// On success, receives the number of bytes written to . + /// if had sufficient length to receive the value; otherwise, . + /// Use to determine how many bytes result in encoding this message. + /// The and collections have one or more labels in common. + /// public override bool TryEncode(Span destination, out int bytesWritten) { - ThrowIfDuplicateLabels(ProtectedHeaders, UnprotectedHeaders); + if (ContainDuplicateLabels(ProtectedHeaders, UnprotectedHeaders)) + { + throw new InvalidOperationException(SR.Sign1SignHeaderDuplicateLabels); + } if (destination.Length < GetEncodedLength()) { diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSignature.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSignature.cs index 870d4bb8086482..b723988f51f5f7 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSignature.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSignature.cs @@ -9,6 +9,9 @@ namespace System.Security.Cryptography.Cose { + /// + /// Represents a COSE_Signature that carries one signature and information about that signature associated with a . + /// public sealed class CoseSignature { private readonly byte[] _encodedBodyProtectedHeaders; @@ -16,7 +19,16 @@ public sealed class CoseSignature internal readonly byte[] _signature; private CoseMultiSignMessage? _message; + /// + /// Gets the protected header parameters of this instance. + /// + /// A collection of protected header parameters associated to this instance. public CoseHeaderMap ProtectedHeaders { get; } + + /// + /// Gets the unprotected header parameters of this instance. + /// + /// A collection of unprotected header parameters associated to this instance. public CoseHeaderMap UnprotectedHeaders { get; } @@ -34,6 +46,7 @@ internal CoseSignature(CoseHeaderMap protectedHeaders, CoseHeaderMap unprotected _encodedSignProtectedHeaders = encodedSignProtectedHeaders; _signature = signature; } + internal CoseMultiSignMessage Message { get @@ -47,6 +60,34 @@ internal CoseMultiSignMessage Message } } + /// + /// Verifies that the signature is valid for the message's content using the specified key. + /// + /// The private key used to sign the content. + /// The extra data associated with the signature, which must match the value provided during signing. + /// if the signature is valid; otherwise, . + /// is . + /// is of an unsupported type. + /// The content is detached from the associated message, use an overload that accepts a detached content. + /// + /// + /// does not have a value for the header. + /// + /// -or- + /// + /// The algorithm protected header was incorrectly formatted. + /// + /// -or- + /// + /// The algorithm protected header was not one of the values supported by this implementation. + /// + /// -or- + /// + /// The algorithm protected header doesn't match with the algorithms supported by the specified . + /// + /// + /// + /// public bool VerifyEmbedded(AsymmetricAlgorithm key, ReadOnlySpan associatedData) { if (key is null) @@ -62,6 +103,34 @@ public bool VerifyEmbedded(AsymmetricAlgorithm key, ReadOnlySpan associate return VerifyCore(key, Message.Content.Value.Span, null, associatedData, CoseHelpers.GetKeyType(key)); } + /// + /// Verifies that the signature is valid for the message's content using the specified key. + /// + /// The private key used to sign the content. + /// The extra data associated with the signature, which must match the value provided during signing. + /// if the signature is valid; otherwise, . + /// is . + /// is of an unsupported type. + /// The content is detached from the associated message, use an overload that accepts a detached content. + /// + /// + /// does not have a value for the header. + /// + /// -or- + /// + /// The algorithm protected header was incorrectly formatted. + /// + /// -or- + /// + /// The algorithm protected header was not one of the values supported by this implementation. + /// + /// -or- + /// + /// The algorithm protected header doesn't match with the algorithms supported by the specified . + /// + /// + /// + /// public bool VerifyEmbedded(AsymmetricAlgorithm key, byte[]? associatedData = null) { if (key is null) @@ -77,6 +146,35 @@ public bool VerifyEmbedded(AsymmetricAlgorithm key, byte[]? associatedData = nul return VerifyCore(key, Message.Content.Value.Span, null, associatedData, CoseHelpers.GetKeyType(key)); } + /// + /// Verifies that the signature is valid for the message's content using the specified key. + /// + /// The private key used to sign the content. + /// The content that was previously signed. + /// The extra data associated with the signature, which must match the value provided during signing. + /// if the signature is valid; otherwise, . + /// or is . + /// is of an unsupported type. + /// The content is embedded on the associated message, use an overload that uses embedded content. + /// + /// + /// does not have a value for the header. + /// + /// -or- + /// + /// The algorithm protected header was incorrectly formatted. + /// + /// -or- + /// + /// The algorithm protected header was not one of the values supported by this implementation. + /// + /// -or- + /// + /// The algorithm protected header doesn't match with the algorithms supported by the specified . + /// + /// + /// + /// public bool VerifyDetached(AsymmetricAlgorithm key, byte[] detachedContent, byte[]? associatedData = null) { if (key is null) @@ -97,6 +195,35 @@ public bool VerifyDetached(AsymmetricAlgorithm key, byte[] detachedContent, byte return VerifyCore(key, detachedContent, null, associatedData, CoseHelpers.GetKeyType(key)); } + /// + /// Verifies that the signature is valid for the message's content using the specified key. + /// + /// The private key used to sign the content. + /// The content that was previously signed. + /// The extra data associated with the signature, which must match the value provided during signing. + /// if the signature is valid; otherwise, . + /// is . + /// is of an unsupported type. + /// The content is embedded on the associated message, use an overload that uses embedded content. + /// + /// + /// does not have a value for the header. + /// + /// -or- + /// + /// The algorithm protected header was incorrectly formatted. + /// + /// -or- + /// + /// The algorithm protected header was not one of the values supported by this implementation. + /// + /// -or- + /// + /// The algorithm protected header doesn't match with the algorithms supported by the specified . + /// + /// + /// + /// public bool VerifyDetached(AsymmetricAlgorithm key, ReadOnlySpan detachedContent, ReadOnlySpan associatedData = default) { if (key is null) @@ -112,6 +239,43 @@ public bool VerifyDetached(AsymmetricAlgorithm key, ReadOnlySpan detachedC return VerifyCore(key, detachedContent, null, associatedData, CoseHelpers.GetKeyType(key)); } + /// + /// Verifies that the signature is valid for the message's content using the specified key. + /// + /// The private key used to sign the content. + /// The content that was previously signed. + /// The extra data associated with the signature, which must match the value provided during signing. + /// if the signature is valid; otherwise, . + /// or is . + /// + /// + /// is of an unsupported type. + /// + /// -or- + /// + /// does not support reading or seeking. + /// + /// + /// The content is embedded on the associated message, use an overload that uses embedded content. + /// + /// + /// does not have a value for the header. + /// + /// -or- + /// + /// The algorithm protected header was incorrectly formatted. + /// + /// -or- + /// + /// The algorithm protected header was not one of the values supported by this implementation. + /// + /// -or- + /// + /// The algorithm protected header doesn't match with the algorithms supported by the specified . + /// + /// + /// + /// public bool VerifyDetached(AsymmetricAlgorithm key, Stream detachedContent, ReadOnlySpan associatedData = default) { if (key is null) @@ -142,6 +306,44 @@ public bool VerifyDetached(AsymmetricAlgorithm key, Stream detachedContent, Read return VerifyCore(key, default, detachedContent, associatedData, CoseHelpers.GetKeyType(key)); } + /// + /// Asynchronously verifies that the signature is valid for the message's content using the specified key. + /// + /// The private key used to sign the content. + /// The content that was previously signed. + /// The extra data associated with the signature, which must match the value provided during signing. + /// The token to monitor for cancellation requests. The default value is . + /// A task whose property is if the signature is valid; otherwise, . + /// or is . + /// + /// + /// is of an unsupported type. + /// + /// -or- + /// + /// does not support reading or seeking. + /// + /// + /// The content is embedded on the associated message, use an overload that uses embedded content. + /// + /// + /// does not have a value for the header. + /// + /// -or- + /// + /// The algorithm protected header was incorrectly formatted. + /// + /// -or- + /// + /// The algorithm protected header was not one of the values supported by this implementation. + /// + /// -or- + /// + /// The algorithm protected header doesn't match with the algorithms supported by the specified . + /// + /// + /// + /// public Task VerifyDetachedAsync(AsymmetricAlgorithm key, Stream detachedContent, ReadOnlyMemory associatedData = default, CancellationToken cancellationToken = default) { if (key is null) diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSigner.cs b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSigner.cs index eab71d31506664..e7ae5d9ea01e2b 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSigner.cs +++ b/src/libraries/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSigner.cs @@ -5,23 +5,70 @@ namespace System.Security.Cryptography.Cose { + /// + /// Provides signing information to be used with sign operations in and . + /// public sealed class CoseSigner { internal readonly KeyType _keyType; internal readonly int? _algHeaderValueToSlip; internal CoseHeaderMap? _protectedHeaders; internal CoseHeaderMap? _unprotectedHeaders; + + /// + /// Gets the private key to use during signing. + /// + /// The private key to use during signing. public AsymmetricAlgorithm Key { get; } + + /// + /// Gets the hash algorithm to use to create the hash value for signing. + /// + /// The hash algorithm to use to create the hash value for signing. public HashAlgorithmName HashAlgorithm { get; } + + /// + /// Gets the padding mode to use when signing. + /// + /// The padding mode to use when signing. public RSASignaturePadding? RSASignaturePadding { get; } + /// + /// Initializes a new instance of the class. + /// + /// The private key to use for signing. + /// The hash algorithm to use to create the hash value for signing. + /// The collection of protected header parameters to append to the message when signing. + /// The collection of unprotected header parameters to append to the message when signing. + /// is . + /// + /// + /// is , use to specify a signature padding. + /// + /// -or- + /// + /// is of an unsupported type. + /// + /// -or- + /// + /// contains a value with the label, but the value was incorrect based on the and . + /// + /// -or- + /// + /// specifies a value with the label. + /// + /// + /// + /// For sign operations in , and are used as the buckets of the content (and only) layer. + /// For sign operations in , and are used as the buckets of the signature layer. + /// public CoseSigner(AsymmetricAlgorithm key, HashAlgorithmName hashAlgorithm, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null) { if (key is null) throw new ArgumentNullException(nameof(key)); if (key is RSA) - throw new CryptographicException(SR.CoseSignerRSAKeyNeedsPadding); + throw new ArgumentException(SR.CoseSignerRSAKeyNeedsPadding, nameof(key)); Key = key; HashAlgorithm = hashAlgorithm; @@ -32,6 +79,28 @@ public CoseSigner(AsymmetricAlgorithm key, HashAlgorithmName hashAlgorithm, Cose _algHeaderValueToSlip = ValidateOrSlipAlgorithmHeader(); } + /// + /// Initializes a new instance of the class. + /// + /// The private key to use for signing. + /// The padding mode to use when signing. + /// The hash algorithm to use to create the hash value for signing. + /// The collection of protected header parameters to append to the message when signing. + /// The collection of unprotected header parameters to append to the message when signing. + /// is . + /// + /// + /// contains a value with the label, but the value was incorrect based on the , and . + /// + /// -or- + /// + /// specifies a value with the label. + /// + /// + /// + /// For sign operations in , and are used as the header parameters of the content layer. + /// For sign operations in , and are used as the header parameters of the signature layer. + /// public CoseSigner(RSA key, RSASignaturePadding signaturePadding, HashAlgorithmName hashAlgorithm, CoseHeaderMap? protectedHeaders = null, CoseHeaderMap? unprotectedHeaders = null) { if (key is null) @@ -50,7 +119,16 @@ public CoseSigner(RSA key, RSASignaturePadding signaturePadding, HashAlgorithmNa _algHeaderValueToSlip = ValidateOrSlipAlgorithmHeader(); } + /// + /// Gets the protected header parameters to append to the message when signing. + /// + /// A collection of protected header parameters to append to the message when signing. public CoseHeaderMap ProtectedHeaders => _protectedHeaders ??= new CoseHeaderMap(); + + /// + /// Gets the unprotected header parameters to append to the message when signing. + /// + /// A collection of unprotected header parameters to append to the message when signing. public CoseHeaderMap UnprotectedHeaders => _unprotectedHeaders ??= new CoseHeaderMap(); // If we Validate: The caller specified a COSE Algorithm, we will make sure it matches the specified key and hash algorithm. @@ -67,7 +145,7 @@ public CoseSigner(RSA key, RSASignaturePadding signaturePadding, HashAlgorithmNa if (_unprotectedHeaders != null && _unprotectedHeaders.ContainsKey(CoseHeaderLabel.Algorithm)) { - throw new CryptographicException(SR.Sign1SignAlgMustBeProtected); + throw new ArgumentException(SR.Sign1SignAlgMustBeProtected, "unprotectedHeaders"); } return algHeaderValue; @@ -90,7 +168,7 @@ private void ValidateAlgorithmHeader(ReadOnlyMemory encodedAlg, int expect exMsg = SR.Format(SR.Sign1SignCoseAlgorithmDoesNotMatchSpecifiedKeyAndHashAlgorithm, alg.Value, _keyType, HashAlgorithm.Name); } - throw new CryptographicException(exMsg); + throw new ArgumentException(exMsg, "protectedHeaders"); } } @@ -104,7 +182,7 @@ private int GetCoseAlgorithmHeader() nameof(HashAlgorithmName.SHA256) => KnownCoseAlgorithms.ES256, nameof(HashAlgorithmName.SHA384) => KnownCoseAlgorithms.ES384, nameof(HashAlgorithmName.SHA512) => KnownCoseAlgorithms.ES512, - _ => throw new CryptographicException(SR.Format(SR.Sign1SignUnsupportedHashAlgorithm, hashAlgorithmName)) + _ => throw new ArgumentException(SR.Format(SR.Sign1SignUnsupportedHashAlgorithm, hashAlgorithmName), "hashAlgorithm") }; } @@ -118,7 +196,7 @@ private int GetCoseAlgorithmHeader() nameof(HashAlgorithmName.SHA256) => KnownCoseAlgorithms.PS256, nameof(HashAlgorithmName.SHA384) => KnownCoseAlgorithms.PS384, nameof(HashAlgorithmName.SHA512) => KnownCoseAlgorithms.PS512, - _ => throw new CryptographicException(SR.Format(SR.Sign1SignUnsupportedHashAlgorithm, hashAlgorithmName)) + _ => throw new ArgumentException(SR.Format(SR.Sign1SignUnsupportedHashAlgorithm, hashAlgorithmName), "hashAlgorithm") }; } @@ -129,7 +207,7 @@ private int GetCoseAlgorithmHeader() nameof(HashAlgorithmName.SHA256) => KnownCoseAlgorithms.RS256, nameof(HashAlgorithmName.SHA384) => KnownCoseAlgorithms.RS384, nameof(HashAlgorithmName.SHA512) => KnownCoseAlgorithms.RS512, - _ => throw new CryptographicException(SR.Format(SR.Sign1SignUnsupportedHashAlgorithm, hashAlgorithmName)) + _ => throw new ArgumentException(SR.Format(SR.Sign1SignUnsupportedHashAlgorithm, hashAlgorithmName), "hashAlgorithm") }; } } diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseHeaderLabelTests.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseHeaderLabelTests.cs index d4c4b44b296879..d542c3de5da1e1 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseHeaderLabelTests.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseHeaderLabelTests.cs @@ -7,6 +7,12 @@ namespace System.Security.Cryptography.Cose.Tests { public class CoseHeaderLabelTests { + [Fact] + public void CoseHeaderLabel_StringCtor_ThrowIfNull() + { + Assert.Throws("label", () => new CoseHeaderLabel(null!)); + } + [Fact] public void CoseHeaderLabel_GetHashCode() { diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseHeaderMapTests.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseHeaderMapTests.cs index 99cc78d6287fdd..c21562ba690202 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseHeaderMapTests.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseHeaderMapTests.cs @@ -4,6 +4,7 @@ using Xunit; using System.Formats.Cbor; using System.Collections.Generic; +using System.Linq; using static System.Security.Cryptography.Cose.Tests.CoseTestHelpers; namespace System.Security.Cryptography.Cose.Tests @@ -56,13 +57,13 @@ public void SetValue_KnownHeaders_ThrowIf_IncorrectValue(SetValueMethod method) { var map = new CoseHeaderMap(); // only accepts int or tstr - Assert.Throws(() => SetValue(map, CoseHeaderLabel.Algorithm, ReadOnlySpan.Empty, method)); + Assert.Throws("value", () => SetValue(map, CoseHeaderLabel.Algorithm, ReadOnlySpan.Empty, method)); // [ +label ] (non-empty array) - Assert.Throws(() => SetValue(map, CoseHeaderLabel.CriticalHeaders, ReadOnlySpan.Empty, method)); + Assert.Throws("value", () => SetValue(map, CoseHeaderLabel.CriticalHeaders, ReadOnlySpan.Empty, method)); // tstr / uint - Assert.Throws(() => SetValue(map, CoseHeaderLabel.ContentType, -1, method)); + Assert.Throws("value", () => SetValue(map, CoseHeaderLabel.ContentType, -1, method)); // bstr - Assert.Throws(() => SetValue(map, CoseHeaderLabel.KeyIdentifier, "foo", method)); + Assert.Throws("value", () => SetValue(map, CoseHeaderLabel.KeyIdentifier, "foo", method)); } [Theory] @@ -80,17 +81,17 @@ public void SetEncodedValue_KnownHeaders_ThrowIf_IncorrectValue(SetValueMethod m var map = new CoseHeaderMap(); // only accepts int or tstr - Assert.Throws(() => SetEncodedValue(map, CoseHeaderLabel.Algorithm, encodedNullValue, method)); + Assert.Throws("value", () => SetEncodedValue(map, CoseHeaderLabel.Algorithm, encodedNullValue, method)); // [ +label ] (non-empty array) - Assert.Throws(() => SetEncodedValue(map, CoseHeaderLabel.CriticalHeaders, encodedNullValue, method)); + Assert.Throws("value", () => SetEncodedValue(map, CoseHeaderLabel.CriticalHeaders, encodedNullValue, method)); writer.Reset(); writer.WriteStartArray(0); writer.WriteEndArray(); - Assert.Throws(() => SetEncodedValue(map, CoseHeaderLabel.CriticalHeaders, writer.Encode(), method)); + Assert.Throws("value", () => SetEncodedValue(map, CoseHeaderLabel.CriticalHeaders, writer.Encode(), method)); // tstr / uint - Assert.Throws(() => SetEncodedValue(map, CoseHeaderLabel.ContentType, encodedNullValue, method)); + Assert.Throws("value", () => SetEncodedValue(map, CoseHeaderLabel.ContentType, encodedNullValue, method)); // bstr - Assert.Throws(() => SetEncodedValue(map, CoseHeaderLabel.KeyIdentifier, encodedNullValue, method)); + Assert.Throws("value", () => SetEncodedValue(map, CoseHeaderLabel.KeyIdentifier, encodedNullValue, method)); } [Fact] @@ -109,11 +110,11 @@ public void SetValue_InvalidCoseHeaderValue() { var map = new CoseHeaderMap(); - Assert.Throws(() => map.Add(label, new CoseHeaderValue())); - Assert.Throws(() => map[label] = new CoseHeaderValue()); + Assert.Throws("value", () => map.Add(label, new CoseHeaderValue())); + Assert.Throws("value", () => map[label] = new CoseHeaderValue()); - Assert.Throws(() => map.Add(label, default(CoseHeaderValue))); - Assert.Throws(() => map[label] = default(CoseHeaderValue)); + Assert.Throws("value", () => map.Add(label, default(CoseHeaderValue))); + Assert.Throws("value", () => map[label] = default(CoseHeaderValue)); } } @@ -242,6 +243,58 @@ static void VerifyThrows(CoseHeaderMap map, CoseHeaderLabel label) } } + [Fact] + public void RemoveKeyValuePair_MatchKeyAndValue_UsingNewKeyValuePair() + { + CoseHeaderLabel label = new("foo"); + CoseHeaderValue value = CoseHeaderValue.FromString("bar"); + + CoseHeaderMap map = new(); + map.Add(label, value); + Assert.Equal(1, map.Count); + + KeyValuePair kvp = new(label, value); + Assert.True(map.Remove(kvp)); + Assert.Equal(0, map.Count); + } + + [Fact] + public void RemoveKeyValuePair_MatchKeyAndValue_UsingKeyValuePairFromEnumerator() + { + CoseHeaderLabel label = new("foo"); + CoseHeaderValue value = CoseHeaderValue.FromString("bar"); + + CoseHeaderMap map = new(); + map.Add(label, value); + Assert.Equal(1, map.Count); + + KeyValuePair kvp = map.First(); + Assert.True(map.Remove(kvp)); + Assert.Equal(0, map.Count); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void RemoveKeyValuePair_DoesNotMatchKeyOrValue(bool changeKey) + { + CoseHeaderLabel label = new("foo"); + CoseHeaderValue value = CoseHeaderValue.FromString("bar"); + + CoseHeaderMap map = new(); + map.Add(label, value); + Assert.Equal(1, map.Count); + + KeyValuePair kvp; + if (changeKey) + kvp = new(new CoseHeaderLabel("other"), value); + else + kvp = new(label, CoseHeaderValue.FromString("other")); + + Assert.False(map.Remove(kvp)); + Assert.Equal(1, map.Count); + } + public enum SetValueMethod { ItemSet, diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseHeaderValueTests.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseHeaderValueTests.cs index 89c5ba0af9ab47..b01cbc953177c4 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseHeaderValueTests.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseHeaderValueTests.cs @@ -256,7 +256,7 @@ public void FromBytesThrowsBufferTooSmall(ContentTestCase @case) byte[] content = GetDummyContent(@case); CoseHeaderValue headerValue = CoseHeaderValue.FromBytes(content.AsSpan()); Memory buffer = new byte[content.Length - 1]; - Assert.Throws(() => headerValue.GetValueAsBytes(buffer.Span)); + Assert.Throws("destination", () => headerValue.GetValueAsBytes(buffer.Span)); } [Fact] diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseMessageTests.Sign.CustomHeaderMaps.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseMessageTests.Sign.CustomHeaderMaps.cs index 3ce9483edd4954..a7b1348a3f7322 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseMessageTests.Sign.CustomHeaderMaps.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseMessageTests.Sign.CustomHeaderMaps.cs @@ -132,7 +132,7 @@ public void SignWith_EmptyProtectedHeaderMap_UnprotectedHeaderMapWithAlgorithm() CoseHeaderMap protectedHeaders = GetEmptyHeaderMap(); CoseHeaderMap unprotectedHeaders = GetHeaderMapWithAlgorithm(DefaultAlgorithm); - Assert.Throws(() => Sign(s_sampleContent, DefaultKey, DefaultHash, protectedHeaders, unprotectedHeaders)); + Assert.Throws("unprotectedHeaders", () => Sign(s_sampleContent, DefaultKey, DefaultHash, protectedHeaders, unprotectedHeaders)); } [Fact] @@ -217,27 +217,28 @@ public void SignWithDuplicateHeaderBetweenProtectedAndUnprotected() // Algorithm header is duplicated. It is a special case because it is mandatory that the header exists in the protected map. Initialize(DefaultAlgorithm); unprotectedHeaders.Add(CoseHeaderLabel.Algorithm, (int)DefaultAlgorithm); - Assert.Throws(() => Sign(s_sampleContent, DefaultKey, DefaultHash, protectedHeaders, unprotectedHeaders)); + + Assert.Throws("signer", () => Sign(s_sampleContent, DefaultKey, DefaultHash, protectedHeaders, unprotectedHeaders)); // other known header is duplicate. Initialize(DefaultAlgorithm); protectedHeaders.Add(CoseHeaderLabel.ContentType, ContentTypeDummyValue); unprotectedHeaders.Add(CoseHeaderLabel.ContentType, ContentTypeDummyValue); - Assert.Throws(() => Sign(s_sampleContent, DefaultKey, DefaultHash, protectedHeaders, unprotectedHeaders)); + Assert.Throws("signer", () => Sign(s_sampleContent, DefaultKey, DefaultHash, protectedHeaders, unprotectedHeaders)); // not-known int header is duplicate. Initialize(DefaultAlgorithm); var myLabel = new CoseHeaderLabel(42); protectedHeaders.Add(myLabel, 42); unprotectedHeaders.Add(myLabel, 42); - Assert.Throws(() => Sign(s_sampleContent, DefaultKey, DefaultHash, protectedHeaders, unprotectedHeaders)); + Assert.Throws("signer", () => Sign(s_sampleContent, DefaultKey, DefaultHash, protectedHeaders, unprotectedHeaders)); // not-known tstr header is duplicate. Initialize(DefaultAlgorithm); myLabel = new CoseHeaderLabel("42"); protectedHeaders.Add(myLabel, 42); unprotectedHeaders.Add(myLabel, 42); - Assert.Throws(() => Sign(s_sampleContent, DefaultKey, DefaultHash, protectedHeaders, unprotectedHeaders)); + Assert.Throws("signer", () => Sign(s_sampleContent, DefaultKey, DefaultHash, protectedHeaders, unprotectedHeaders)); void Initialize(CoseAlgorithm algorithm) { @@ -261,14 +262,14 @@ public void MultiSign_AddSignatureWithDuplicateHeaderBetweenProtectedAndUnprotec // Algorithm header is duplicated. It is a special case because it is mandatory that the header exists in the protected map. unprotectedHeaders.Add(CoseHeaderLabel.Algorithm, (int)DefaultAlgorithm); CoseSigner signer = GetCoseSigner(DefaultKey, DefaultHash, protectedHeaders, unprotectedHeaders); - Assert.Throws(() => AddSignature(msg, s_sampleContent, signer)); + Assert.Throws("signer", () => AddSignature(msg, s_sampleContent, signer)); // other known header is duplicate. Initialize(DefaultAlgorithm); protectedHeaders.Add(CoseHeaderLabel.ContentType, ContentTypeDummyValue); unprotectedHeaders.Add(CoseHeaderLabel.ContentType, ContentTypeDummyValue); signer = GetCoseSigner(DefaultKey, DefaultHash, protectedHeaders, unprotectedHeaders); - Assert.Throws(() => AddSignature(msg, s_sampleContent, signer)); + Assert.Throws("signer", () => AddSignature(msg, s_sampleContent, signer)); // not-known int header is duplicate. Initialize(DefaultAlgorithm); @@ -276,7 +277,7 @@ public void MultiSign_AddSignatureWithDuplicateHeaderBetweenProtectedAndUnprotec protectedHeaders.Add(myLabel, 42); unprotectedHeaders.Add(myLabel, 42); signer = GetCoseSigner(DefaultKey, DefaultHash, protectedHeaders, unprotectedHeaders); - Assert.Throws(() => AddSignature(msg, s_sampleContent, signer)); + Assert.Throws("signer", () => AddSignature(msg, s_sampleContent, signer)); // not-known tstr header is duplicate. Initialize(DefaultAlgorithm); @@ -284,7 +285,7 @@ public void MultiSign_AddSignatureWithDuplicateHeaderBetweenProtectedAndUnprotec protectedHeaders.Add(myLabel, 42); unprotectedHeaders.Add(myLabel, 42); signer = GetCoseSigner(DefaultKey, DefaultHash, protectedHeaders, unprotectedHeaders); - Assert.Throws(() => AddSignature(msg, s_sampleContent, signer)); + Assert.Throws("signer", () => AddSignature(msg, s_sampleContent, signer)); void Initialize(CoseAlgorithm algorithm) { @@ -379,10 +380,10 @@ public void MultiSign_ReEncodeWithDuplicateHeaderBetweenProtectedAndUnprotected_ private void AllEncodeOverloadsShouldThrow(CoseMessage msg) { - Assert.Throws(msg.Encode); + Assert.Throws(msg.Encode); byte[] destination = new byte[msg.GetEncodedLength()]; - Assert.Throws(() => msg.Encode(destination)); - Assert.Throws(() => msg.TryEncode(destination, out _)); + Assert.Throws(() => msg.Encode(destination)); + Assert.Throws(() => msg.TryEncode(destination, out _)); } [Theory] @@ -535,7 +536,7 @@ public void SignWithCriticalHeaders_NotTransportingTheSpecifiedCriticalHeaderThr AddCriticalHeaders(protectedHeaders, null, includeSpecifiedCritHeader: false); CoseSigner signer = GetCoseSigner(DefaultKey, DefaultHash, protectedHeaders); - Assert.Throws(() => Sign(s_sampleContent, signer)); + Assert.Throws("signer", () => Sign(s_sampleContent, signer)); } [Fact] @@ -568,7 +569,7 @@ public void MultiSign_SignWithCriticalHeaders_NotTransportingTheSpecifiedCritica AddCriticalHeaders(bodyProtectedHeaders, null, includeSpecifiedCritHeader: false); CoseSigner signer = GetCoseSigner(DefaultKey, DefaultHash); - Assert.Throws(() => Sign(s_sampleContent, signer, bodyProtectedHeaders)); + Assert.Throws("protectedHeaders", () => Sign(s_sampleContent, signer, bodyProtectedHeaders)); } [Fact] @@ -611,7 +612,7 @@ public void MultiSign_SignWithCriticalHeaders_NotTransportingTheSpecifiedCritica AddCriticalHeaders(signProtectedHeaders, null, includeSpecifiedCritHeader: false); CoseSigner signer = GetCoseSigner(DefaultKey, DefaultHash, signProtectedHeaders); - Assert.Throws(() => AddSignature(multiSignMsg, s_sampleContent, signer)); + Assert.Throws("signer", () => AddSignature(multiSignMsg, s_sampleContent, signer)); } private static void AddCriticalHeaders( diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseMessageTests.Sign.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseMessageTests.Sign.cs index c0f350a060fc8c..cfbc305659e8b4 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseMessageTests.Sign.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseMessageTests.Sign.cs @@ -139,7 +139,7 @@ in GetKeyHashAlgorithmPaddingQuadruplet(useNonPrivateKey: true)) [InlineData("FOO")] public void SignWithUnsupportedHashAlgorithm(string hashAlgorithm) { - Assert.Throws(() => Sign(s_sampleContent, GetCoseSigner(DefaultKey, new HashAlgorithmName(hashAlgorithm)))); + Assert.Throws("hashAlgorithm", () => Sign(s_sampleContent, GetCoseSigner(DefaultKey, new HashAlgorithmName(hashAlgorithm)))); } [Theory] @@ -213,7 +213,7 @@ public void MultiSign_RemoveSignature(bool useIndex) Assert.Equal(0, signatures.Count); // You can't create a message without signatures. - Assert.Throws(multiSignMsg.Encode); + Assert.Throws(multiSignMsg.Encode); } [Theory] diff --git a/src/libraries/System.Security.Cryptography.Cose/tests/CoseSignerTests.cs b/src/libraries/System.Security.Cryptography.Cose/tests/CoseSignerTests.cs index 6e00f42eb901c0..3269f0acf0a20e 100644 --- a/src/libraries/System.Security.Cryptography.Cose/tests/CoseSignerTests.cs +++ b/src/libraries/System.Security.Cryptography.Cose/tests/CoseSignerTests.cs @@ -29,7 +29,7 @@ public void CoseSigner_RSA_Success() public void CoseSigner_RSAKeyNeedsSignaturePadding() { RSA rsa = RSA.Create(); - Assert.Throws(() => new CoseSigner(rsa, HashAlgorithmName.SHA256)); + Assert.Throws("key", () => new CoseSigner(rsa, HashAlgorithmName.SHA256)); var signer = new CoseSigner(rsa, RSASignaturePadding.Pss, HashAlgorithmName.SHA256); Assert.Equal(signer.RSASignaturePadding, RSASignaturePadding.Pss); @@ -47,5 +47,11 @@ public void CoseSigner_NullKey() Assert.Throws("key", () => new CoseSigner(null!, HashAlgorithmName.SHA256)); Assert.Throws("key", () => new CoseSigner(null!, RSASignaturePadding.Pss, HashAlgorithmName.SHA256)); } + + [Fact] + public void CoseSigner_NullSignaturePadding() + { + Assert.Throws("signaturePadding", () => new CoseSigner(RSA.Create(), null!, HashAlgorithmName.SHA256)); + } } } From 7be37908e5a1cbb83b1062768c1649827eeaceaa Mon Sep 17 00:00:00 2001 From: Mike McLaughlin Date: Wed, 10 Aug 2022 21:03:37 -0700 Subject: [PATCH 15/68] Add logging callback to DAC EnumMemoryRegion API (#73599) * Add logging callback to DAC EnumMemoryRegion API Add the ICLRDataEnumMemoryRegionsLoggingCallback interface so createdump can receive logging from the EnumMemoryRegion API. Add logging to MethodTable EnumMemoryRegion for invalid EEClass/Canonical MT. * Add checked build logging after waitpid for createdump to help diagnose issue https://github.com/dotnet/runtime/issues/72755. * Add more DAC logging * Code review feedback Co-authored-by: Juan Hoyos --- src/coreclr/debug/createdump/crashinfo.cpp | 14 ++ src/coreclr/debug/createdump/crashinfo.h | 5 +- src/coreclr/debug/daccess/daccess.cpp | 22 +++ src/coreclr/debug/daccess/dacfn.cpp | 1 + src/coreclr/debug/daccess/dacimpl.h | 14 ++ src/coreclr/debug/daccess/enummem.cpp | 8 + src/coreclr/inc/clrdata.idl | 15 ++ src/coreclr/inc/daccess.h | 5 +- src/coreclr/pal/prebuilt/idl/clrdata_i.cpp | 11 +- src/coreclr/pal/prebuilt/inc/clrdata.h | 184 ++++++++++++++++++++- src/coreclr/pal/src/thread/process.cpp | 12 +- src/coreclr/vm/methodtable.cpp | 8 + src/coreclr/vm/threads.cpp | 5 + 13 files changed, 286 insertions(+), 18 deletions(-) diff --git a/src/coreclr/debug/createdump/crashinfo.cpp b/src/coreclr/debug/createdump/crashinfo.cpp index 0549f8846bba10..23601cfde1b5d9 100644 --- a/src/coreclr/debug/createdump/crashinfo.cpp +++ b/src/coreclr/debug/createdump/crashinfo.cpp @@ -88,6 +88,12 @@ CrashInfo::QueryInterface( AddRef(); return S_OK; } + else if (InterfaceId == IID_ICLRDataLoggingCallback) + { + *Interface = (ICLRDataLoggingCallback*)this; + AddRef(); + return S_OK; + } else { *Interface = nullptr; @@ -122,6 +128,14 @@ CrashInfo::EnumMemoryRegion( return S_OK; } +HRESULT STDMETHODCALLTYPE +CrashInfo::LogMessage( + /* [in] */ LPCSTR message) +{ + Trace("%s", message); + return S_OK; +} + // // Gather all the necessary crash dump info. // diff --git a/src/coreclr/debug/createdump/crashinfo.h b/src/coreclr/debug/createdump/crashinfo.h index 1acb734c0c0ec9..50c0cca3eec723 100644 --- a/src/coreclr/debug/createdump/crashinfo.h +++ b/src/coreclr/debug/createdump/crashinfo.h @@ -35,7 +35,7 @@ extern const std::string GetDirectory(const std::string& fileName); extern std::string FormatString(const char* format, ...); extern std::string FormatGuid(const GUID* guid); -class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, +class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, public ICLRDataLoggingCallback, #ifdef __APPLE__ public MachOReader #else @@ -135,6 +135,9 @@ class CrashInfo : public ICLRDataEnumMemoryRegionsCallback, // ICLRDataEnumMemoryRegionsCallback virtual HRESULT STDMETHODCALLTYPE EnumMemoryRegion(/* [in] */ CLRDATA_ADDRESS address, /* [in] */ ULONG32 size); + // ICLRDataLoggingCallback + virtual HRESULT STDMETHODCALLTYPE LogMessage( /* [in] */ LPCSTR message); + private: #ifdef __APPLE__ bool EnumerateMemoryRegions(); diff --git a/src/coreclr/debug/daccess/daccess.cpp b/src/coreclr/debug/daccess/daccess.cpp index 3a28c4c21f34c1..71bfcd7f898f47 100644 --- a/src/coreclr/debug/daccess/daccess.cpp +++ b/src/coreclr/debug/daccess/daccess.cpp @@ -3155,6 +3155,7 @@ ClrDataAccess::ClrDataAccess(ICorDebugDataTarget * pTarget, ICLRDataTarget * pLe m_enumMemCb = NULL; m_updateMemCb = NULL; + m_logMessageCb = NULL; m_enumMemFlags = (CLRDataEnumMemoryFlags)-1; // invalid m_jitNotificationTable = NULL; m_gcNotificationTable = NULL; @@ -6364,6 +6365,27 @@ bool ClrDataAccess::DacUpdateMemoryRegion(TADDR addr, TSIZE_T bufferSize, BYTE* return true; } +// +// DacLogMessage - logs a message to an external DAC client +// +// Parameters: +// message - message to log +// +void DacLogMessage(LPCSTR format, ...) +{ + SUPPORTS_DAC_HOST_ONLY; + + if (g_dacImpl->IsLogMessageEnabled()) + { + va_list args; + va_start(args, format); + char buffer[1024]; + _vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, format, args); + g_dacImpl->LogMessage(buffer); + va_end(args); + } +} + // // Check whether a region of target memory is fully readable. // diff --git a/src/coreclr/debug/daccess/dacfn.cpp b/src/coreclr/debug/daccess/dacfn.cpp index 2a34d278377b7f..c3131e2d571efe 100644 --- a/src/coreclr/debug/daccess/dacfn.cpp +++ b/src/coreclr/debug/daccess/dacfn.cpp @@ -471,6 +471,7 @@ DacInstantiateTypeByAddressHelper(TADDR addr, ULONG32 size, bool throwEx, bool f g_dacImpl->m_instances.ReturnAlloc(inst); if (throwEx) { + DacLogMessage("DacReadAll(%p, %08x) FAILED %08x\n", addr, size, status); DacError(status); } return NULL; diff --git a/src/coreclr/debug/daccess/dacimpl.h b/src/coreclr/debug/daccess/dacimpl.h index 7dc1dd34c772c4..c6a01bd2d48ff9 100644 --- a/src/coreclr/debug/daccess/dacimpl.h +++ b/src/coreclr/debug/daccess/dacimpl.h @@ -1337,6 +1337,19 @@ class ClrDataAccess bool ReportMem(TADDR addr, TSIZE_T size, bool fExpectSuccess = true); bool DacUpdateMemoryRegion(TADDR addr, TSIZE_T bufferSize, BYTE* buffer); + inline bool IsLogMessageEnabled() + { + return m_logMessageCb != NULL; + } + + void LogMessage(LPCSTR message) + { + if (m_logMessageCb != NULL) + { + m_logMessageCb->LogMessage(message); + } + } + void ClearDumpStats(); JITNotification* GetHostJitNotificationTable(); GcNotification* GetHostGcNotificationTable(); @@ -1448,6 +1461,7 @@ class ClrDataAccess MDImportsCache m_mdImports; ICLRDataEnumMemoryRegionsCallback* m_enumMemCb; ICLRDataEnumMemoryRegionsCallback2* m_updateMemCb; + ICLRDataLoggingCallback* m_logMessageCb; CLRDataEnumMemoryFlags m_enumMemFlags; JITNotification* m_jitNotificationTable; GcNotification* m_gcNotificationTable; diff --git a/src/coreclr/debug/daccess/enummem.cpp b/src/coreclr/debug/daccess/enummem.cpp index 5c7c40087429a2..7db97915d132d1 100644 --- a/src/coreclr/debug/daccess/enummem.cpp +++ b/src/coreclr/debug/daccess/enummem.cpp @@ -1937,6 +1937,9 @@ ClrDataAccess::EnumMemoryRegions(IN ICLRDataEnumMemoryRegionsCallback* callback, // It is expected to fail on pre Win8 OSes. callback->QueryInterface(IID_ICLRDataEnumMemoryRegionsCallback2, (void **)&m_updateMemCb); + // QI for optional logging callback that createdump uses + callback->QueryInterface(IID_ICLRDataLoggingCallback, (void **)&m_logMessageCb); + EX_TRY { ClearDumpStats(); @@ -2000,6 +2003,11 @@ ClrDataAccess::EnumMemoryRegions(IN ICLRDataEnumMemoryRegionsCallback* callback, m_updateMemCb->Release(); m_updateMemCb = NULL; } + if (m_logMessageCb) + { + m_logMessageCb->Release(); + m_logMessageCb = NULL; + } m_enumMemCb = NULL; DAC_LEAVE(); diff --git a/src/coreclr/inc/clrdata.idl b/src/coreclr/inc/clrdata.idl index 0ce58c6fa7d653..2663a7b057fd7d 100644 --- a/src/coreclr/inc/clrdata.idl +++ b/src/coreclr/inc/clrdata.idl @@ -25,6 +25,7 @@ import "unknwn.idl"; interface ICLRDataEnumMemoryRegions; interface ICLRDataEnumMemoryRegionsCallback; interface ICLRDataEnumMemoryRegionsCallback2; +interface ICLRDataEnumMemoryRegionsCallback3; interface ICLRDataTarget; interface ICLRDataTarget2; interface ICLRMetadataLocator; @@ -287,6 +288,20 @@ interface ICLRDataEnumMemoryRegionsCallback2 : ICLRDataEnumMemoryRegionsCallback [in, size_is(bufferSize)] BYTE* buffer); } +/* + * Optional callback interface for logging EnumMemoryRegions operations and errors. + */ +[ + object, + local, + uuid(F315248D-8B79-49DB-B184-37426559F703) +] +interface ICLRDataLoggingCallback : IUnknown +{ + HRESULT LogMessage( + [in] LPCSTR message); +} + /* * Flags for controlling which memory regions are enumerated. */ diff --git a/src/coreclr/inc/daccess.h b/src/coreclr/inc/daccess.h index a6ea11d31497bd..c2053d748b0c73 100644 --- a/src/coreclr/inc/daccess.h +++ b/src/coreclr/inc/daccess.h @@ -710,12 +710,15 @@ HRESULT DacWriteHostInstance(PVOID host, bool throwEx); // gathering cancelation for details see // code:ClrDataAccess.EnumMemoryRegionsWrapper +extern void DacLogMessage(LPCSTR format, ...); + // This is usable in EX_TRY exactly how RethrowTerminalExceptions et cetera #define RethrowCancelExceptions \ if (GET_EXCEPTION()->GetHR() == COR_E_OPERATIONCANCELED) \ { \ EX_RETHROW; \ - } + } \ + DacLogMessage("DAC exception caught at %s:%d\n", __FILE__, __LINE__); // Occasionally it's necessary to allocate some host memory for // instance data that's created on the fly and so doesn't directly diff --git a/src/coreclr/pal/prebuilt/idl/clrdata_i.cpp b/src/coreclr/pal/prebuilt/idl/clrdata_i.cpp index 7d6b61a14f1336..26d36c133bf46c 100644 --- a/src/coreclr/pal/prebuilt/idl/clrdata_i.cpp +++ b/src/coreclr/pal/prebuilt/idl/clrdata_i.cpp @@ -6,11 +6,9 @@ /* link this file in with the server and any clients */ - /* File created by MIDL compiler version 8.01.0622 */ -/* at Mon Jan 18 19:14:07 2038 - */ -/* Compiler settings for C:/ssd/runtime/src/coreclr/inc/clrdata.idl: - Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 8.01.0622 + /* File created by MIDL compiler version 8.01.0626 */ +/* Compiler settings for clrdata.idl: + Oicf, W1, Zp8, env=Win64 (32b run), target_arch=AMD64 8.01.0626 protocol : dce , ms_ext, c_ext, robust error checks: allocation ref bounds_check enum stub_data VC __declspec() decoration level: @@ -89,6 +87,9 @@ MIDL_DEFINE_GUID(IID, IID_ICLRDataEnumMemoryRegionsCallback,0xBCDD6908,0xBA2D,0x MIDL_DEFINE_GUID(IID, IID_ICLRDataEnumMemoryRegionsCallback2,0x3721A26F,0x8B91,0x4D98,0xA3,0x88,0xDB,0x17,0xB3,0x56,0xFA,0xDB); +MIDL_DEFINE_GUID(IID, IID_ICLRDataLoggingCallback,0xF315248D,0x8B79,0x49DB,0xB1,0x84,0x37,0x42,0x65,0x59,0xF7,0x03); + + MIDL_DEFINE_GUID(IID, IID_ICLRDataEnumMemoryRegions,0x471c35b4,0x7c2f,0x4ef0,0xa9,0x45,0x00,0xf8,0xc3,0x80,0x56,0xf1); #undef MIDL_DEFINE_GUID diff --git a/src/coreclr/pal/prebuilt/inc/clrdata.h b/src/coreclr/pal/prebuilt/inc/clrdata.h index 29f72974c5cf8f..09b24af8ccf094 100644 --- a/src/coreclr/pal/prebuilt/inc/clrdata.h +++ b/src/coreclr/pal/prebuilt/inc/clrdata.h @@ -4,11 +4,9 @@ /* this ALWAYS GENERATED file contains the definitions for the interfaces */ - /* File created by MIDL compiler version 8.01.0622 */ -/* at Mon Jan 18 19:14:07 2038 - */ -/* Compiler settings for C:/ssd/runtime/src/coreclr/inc/clrdata.idl: - Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 8.01.0622 + /* File created by MIDL compiler version 8.01.0626 */ +/* Compiler settings for clrdata.idl: + Oicf, W1, Zp8, env=Win64 (32b run), target_arch=AMD64 8.01.0626 protocol : dce , ms_ext, c_ext, robust error checks: allocation ref bounds_check enum stub_data VC __declspec() decoration level: @@ -44,6 +42,14 @@ #pragma once #endif +#ifndef DECLSPEC_XFGVIRT +#if _CONTROL_FLOW_GUARD_XFG +#define DECLSPEC_XFGVIRT(base, func) __declspec(xfg_virtual(base, func)) +#else +#define DECLSPEC_XFGVIRT(base, func) +#endif +#endif + /* Forward Declarations */ #ifndef __ICLRDataTarget_FWD_DEFINED__ @@ -95,6 +101,13 @@ typedef interface ICLRDataEnumMemoryRegionsCallback2 ICLRDataEnumMemoryRegionsCa #endif /* __ICLRDataEnumMemoryRegionsCallback2_FWD_DEFINED__ */ +#ifndef __ICLRDataLoggingCallback_FWD_DEFINED__ +#define __ICLRDataLoggingCallback_FWD_DEFINED__ +typedef interface ICLRDataLoggingCallback ICLRDataLoggingCallback; + +#endif /* __ICLRDataLoggingCallback_FWD_DEFINED__ */ + + #ifndef __ICLRDataEnumMemoryRegions_FWD_DEFINED__ #define __ICLRDataEnumMemoryRegions_FWD_DEFINED__ typedef interface ICLRDataEnumMemoryRegions ICLRDataEnumMemoryRegions; @@ -119,6 +132,7 @@ extern "C"{ + typedef ULONG64 CLRDATA_ADDRESS; STDAPI CLRDataCreateInstance(REFIID iid, ICLRDataTarget* target, void** iface); @@ -205,31 +219,38 @@ EXTERN_C const IID IID_ICLRDataTarget; { BEGIN_INTERFACE + DECLSPEC_XFGVIRT(IUnknown, QueryInterface) HRESULT ( STDMETHODCALLTYPE *QueryInterface )( ICLRDataTarget * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ _COM_Outptr_ void **ppvObject); + DECLSPEC_XFGVIRT(IUnknown, AddRef) ULONG ( STDMETHODCALLTYPE *AddRef )( ICLRDataTarget * This); + DECLSPEC_XFGVIRT(IUnknown, Release) ULONG ( STDMETHODCALLTYPE *Release )( ICLRDataTarget * This); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetMachineType) HRESULT ( STDMETHODCALLTYPE *GetMachineType )( ICLRDataTarget * This, /* [out] */ ULONG32 *machineType); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetPointerSize) HRESULT ( STDMETHODCALLTYPE *GetPointerSize )( ICLRDataTarget * This, /* [out] */ ULONG32 *pointerSize); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetImageBase) HRESULT ( STDMETHODCALLTYPE *GetImageBase )( ICLRDataTarget * This, /* [string][in] */ LPCWSTR imagePath, /* [out] */ CLRDATA_ADDRESS *baseAddress); + DECLSPEC_XFGVIRT(ICLRDataTarget, ReadVirtual) HRESULT ( STDMETHODCALLTYPE *ReadVirtual )( ICLRDataTarget * This, /* [in] */ CLRDATA_ADDRESS address, @@ -237,6 +258,7 @@ EXTERN_C const IID IID_ICLRDataTarget; /* [in] */ ULONG32 bytesRequested, /* [out] */ ULONG32 *bytesRead); + DECLSPEC_XFGVIRT(ICLRDataTarget, WriteVirtual) HRESULT ( STDMETHODCALLTYPE *WriteVirtual )( ICLRDataTarget * This, /* [in] */ CLRDATA_ADDRESS address, @@ -244,22 +266,26 @@ EXTERN_C const IID IID_ICLRDataTarget; /* [in] */ ULONG32 bytesRequested, /* [out] */ ULONG32 *bytesWritten); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetTLSValue) HRESULT ( STDMETHODCALLTYPE *GetTLSValue )( ICLRDataTarget * This, /* [in] */ ULONG32 threadID, /* [in] */ ULONG32 index, /* [out] */ CLRDATA_ADDRESS *value); + DECLSPEC_XFGVIRT(ICLRDataTarget, SetTLSValue) HRESULT ( STDMETHODCALLTYPE *SetTLSValue )( ICLRDataTarget * This, /* [in] */ ULONG32 threadID, /* [in] */ ULONG32 index, /* [in] */ CLRDATA_ADDRESS value); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetCurrentThreadID) HRESULT ( STDMETHODCALLTYPE *GetCurrentThreadID )( ICLRDataTarget * This, /* [out] */ ULONG32 *threadID); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetThreadContext) HRESULT ( STDMETHODCALLTYPE *GetThreadContext )( ICLRDataTarget * This, /* [in] */ ULONG32 threadID, @@ -267,12 +293,14 @@ EXTERN_C const IID IID_ICLRDataTarget; /* [in] */ ULONG32 contextSize, /* [size_is][out] */ BYTE *context); + DECLSPEC_XFGVIRT(ICLRDataTarget, SetThreadContext) HRESULT ( STDMETHODCALLTYPE *SetThreadContext )( ICLRDataTarget * This, /* [in] */ ULONG32 threadID, /* [in] */ ULONG32 contextSize, /* [size_is][in] */ BYTE *context); + DECLSPEC_XFGVIRT(ICLRDataTarget, Request) HRESULT ( STDMETHODCALLTYPE *Request )( ICLRDataTarget * This, /* [in] */ ULONG32 reqCode, @@ -384,31 +412,38 @@ EXTERN_C const IID IID_ICLRDataTarget2; { BEGIN_INTERFACE + DECLSPEC_XFGVIRT(IUnknown, QueryInterface) HRESULT ( STDMETHODCALLTYPE *QueryInterface )( ICLRDataTarget2 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ _COM_Outptr_ void **ppvObject); + DECLSPEC_XFGVIRT(IUnknown, AddRef) ULONG ( STDMETHODCALLTYPE *AddRef )( ICLRDataTarget2 * This); + DECLSPEC_XFGVIRT(IUnknown, Release) ULONG ( STDMETHODCALLTYPE *Release )( ICLRDataTarget2 * This); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetMachineType) HRESULT ( STDMETHODCALLTYPE *GetMachineType )( ICLRDataTarget2 * This, /* [out] */ ULONG32 *machineType); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetPointerSize) HRESULT ( STDMETHODCALLTYPE *GetPointerSize )( ICLRDataTarget2 * This, /* [out] */ ULONG32 *pointerSize); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetImageBase) HRESULT ( STDMETHODCALLTYPE *GetImageBase )( ICLRDataTarget2 * This, /* [string][in] */ LPCWSTR imagePath, /* [out] */ CLRDATA_ADDRESS *baseAddress); + DECLSPEC_XFGVIRT(ICLRDataTarget, ReadVirtual) HRESULT ( STDMETHODCALLTYPE *ReadVirtual )( ICLRDataTarget2 * This, /* [in] */ CLRDATA_ADDRESS address, @@ -416,6 +451,7 @@ EXTERN_C const IID IID_ICLRDataTarget2; /* [in] */ ULONG32 bytesRequested, /* [out] */ ULONG32 *bytesRead); + DECLSPEC_XFGVIRT(ICLRDataTarget, WriteVirtual) HRESULT ( STDMETHODCALLTYPE *WriteVirtual )( ICLRDataTarget2 * This, /* [in] */ CLRDATA_ADDRESS address, @@ -423,22 +459,26 @@ EXTERN_C const IID IID_ICLRDataTarget2; /* [in] */ ULONG32 bytesRequested, /* [out] */ ULONG32 *bytesWritten); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetTLSValue) HRESULT ( STDMETHODCALLTYPE *GetTLSValue )( ICLRDataTarget2 * This, /* [in] */ ULONG32 threadID, /* [in] */ ULONG32 index, /* [out] */ CLRDATA_ADDRESS *value); + DECLSPEC_XFGVIRT(ICLRDataTarget, SetTLSValue) HRESULT ( STDMETHODCALLTYPE *SetTLSValue )( ICLRDataTarget2 * This, /* [in] */ ULONG32 threadID, /* [in] */ ULONG32 index, /* [in] */ CLRDATA_ADDRESS value); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetCurrentThreadID) HRESULT ( STDMETHODCALLTYPE *GetCurrentThreadID )( ICLRDataTarget2 * This, /* [out] */ ULONG32 *threadID); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetThreadContext) HRESULT ( STDMETHODCALLTYPE *GetThreadContext )( ICLRDataTarget2 * This, /* [in] */ ULONG32 threadID, @@ -446,12 +486,14 @@ EXTERN_C const IID IID_ICLRDataTarget2; /* [in] */ ULONG32 contextSize, /* [size_is][out] */ BYTE *context); + DECLSPEC_XFGVIRT(ICLRDataTarget, SetThreadContext) HRESULT ( STDMETHODCALLTYPE *SetThreadContext )( ICLRDataTarget2 * This, /* [in] */ ULONG32 threadID, /* [in] */ ULONG32 contextSize, /* [size_is][in] */ BYTE *context); + DECLSPEC_XFGVIRT(ICLRDataTarget, Request) HRESULT ( STDMETHODCALLTYPE *Request )( ICLRDataTarget2 * This, /* [in] */ ULONG32 reqCode, @@ -460,6 +502,7 @@ EXTERN_C const IID IID_ICLRDataTarget2; /* [in] */ ULONG32 outBufferSize, /* [size_is][out] */ BYTE *outBuffer); + DECLSPEC_XFGVIRT(ICLRDataTarget2, AllocVirtual) HRESULT ( STDMETHODCALLTYPE *AllocVirtual )( ICLRDataTarget2 * This, /* [in] */ CLRDATA_ADDRESS addr, @@ -468,6 +511,7 @@ EXTERN_C const IID IID_ICLRDataTarget2; /* [in] */ ULONG32 protectFlags, /* [out] */ CLRDATA_ADDRESS *virt); + DECLSPEC_XFGVIRT(ICLRDataTarget2, FreeVirtual) HRESULT ( STDMETHODCALLTYPE *FreeVirtual )( ICLRDataTarget2 * This, /* [in] */ CLRDATA_ADDRESS addr, @@ -585,31 +629,38 @@ EXTERN_C const IID IID_ICLRDataTarget3; { BEGIN_INTERFACE + DECLSPEC_XFGVIRT(IUnknown, QueryInterface) HRESULT ( STDMETHODCALLTYPE *QueryInterface )( ICLRDataTarget3 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ _COM_Outptr_ void **ppvObject); + DECLSPEC_XFGVIRT(IUnknown, AddRef) ULONG ( STDMETHODCALLTYPE *AddRef )( ICLRDataTarget3 * This); + DECLSPEC_XFGVIRT(IUnknown, Release) ULONG ( STDMETHODCALLTYPE *Release )( ICLRDataTarget3 * This); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetMachineType) HRESULT ( STDMETHODCALLTYPE *GetMachineType )( ICLRDataTarget3 * This, /* [out] */ ULONG32 *machineType); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetPointerSize) HRESULT ( STDMETHODCALLTYPE *GetPointerSize )( ICLRDataTarget3 * This, /* [out] */ ULONG32 *pointerSize); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetImageBase) HRESULT ( STDMETHODCALLTYPE *GetImageBase )( ICLRDataTarget3 * This, /* [string][in] */ LPCWSTR imagePath, /* [out] */ CLRDATA_ADDRESS *baseAddress); + DECLSPEC_XFGVIRT(ICLRDataTarget, ReadVirtual) HRESULT ( STDMETHODCALLTYPE *ReadVirtual )( ICLRDataTarget3 * This, /* [in] */ CLRDATA_ADDRESS address, @@ -617,6 +668,7 @@ EXTERN_C const IID IID_ICLRDataTarget3; /* [in] */ ULONG32 bytesRequested, /* [out] */ ULONG32 *bytesRead); + DECLSPEC_XFGVIRT(ICLRDataTarget, WriteVirtual) HRESULT ( STDMETHODCALLTYPE *WriteVirtual )( ICLRDataTarget3 * This, /* [in] */ CLRDATA_ADDRESS address, @@ -624,22 +676,26 @@ EXTERN_C const IID IID_ICLRDataTarget3; /* [in] */ ULONG32 bytesRequested, /* [out] */ ULONG32 *bytesWritten); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetTLSValue) HRESULT ( STDMETHODCALLTYPE *GetTLSValue )( ICLRDataTarget3 * This, /* [in] */ ULONG32 threadID, /* [in] */ ULONG32 index, /* [out] */ CLRDATA_ADDRESS *value); + DECLSPEC_XFGVIRT(ICLRDataTarget, SetTLSValue) HRESULT ( STDMETHODCALLTYPE *SetTLSValue )( ICLRDataTarget3 * This, /* [in] */ ULONG32 threadID, /* [in] */ ULONG32 index, /* [in] */ CLRDATA_ADDRESS value); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetCurrentThreadID) HRESULT ( STDMETHODCALLTYPE *GetCurrentThreadID )( ICLRDataTarget3 * This, /* [out] */ ULONG32 *threadID); + DECLSPEC_XFGVIRT(ICLRDataTarget, GetThreadContext) HRESULT ( STDMETHODCALLTYPE *GetThreadContext )( ICLRDataTarget3 * This, /* [in] */ ULONG32 threadID, @@ -647,12 +703,14 @@ EXTERN_C const IID IID_ICLRDataTarget3; /* [in] */ ULONG32 contextSize, /* [size_is][out] */ BYTE *context); + DECLSPEC_XFGVIRT(ICLRDataTarget, SetThreadContext) HRESULT ( STDMETHODCALLTYPE *SetThreadContext )( ICLRDataTarget3 * This, /* [in] */ ULONG32 threadID, /* [in] */ ULONG32 contextSize, /* [size_is][in] */ BYTE *context); + DECLSPEC_XFGVIRT(ICLRDataTarget, Request) HRESULT ( STDMETHODCALLTYPE *Request )( ICLRDataTarget3 * This, /* [in] */ ULONG32 reqCode, @@ -661,6 +719,7 @@ EXTERN_C const IID IID_ICLRDataTarget3; /* [in] */ ULONG32 outBufferSize, /* [size_is][out] */ BYTE *outBuffer); + DECLSPEC_XFGVIRT(ICLRDataTarget2, AllocVirtual) HRESULT ( STDMETHODCALLTYPE *AllocVirtual )( ICLRDataTarget3 * This, /* [in] */ CLRDATA_ADDRESS addr, @@ -669,24 +728,28 @@ EXTERN_C const IID IID_ICLRDataTarget3; /* [in] */ ULONG32 protectFlags, /* [out] */ CLRDATA_ADDRESS *virt); + DECLSPEC_XFGVIRT(ICLRDataTarget2, FreeVirtual) HRESULT ( STDMETHODCALLTYPE *FreeVirtual )( ICLRDataTarget3 * This, /* [in] */ CLRDATA_ADDRESS addr, /* [in] */ ULONG32 size, /* [in] */ ULONG32 typeFlags); + DECLSPEC_XFGVIRT(ICLRDataTarget3, GetExceptionRecord) HRESULT ( STDMETHODCALLTYPE *GetExceptionRecord )( ICLRDataTarget3 * This, /* [in] */ ULONG32 bufferSize, /* [out] */ ULONG32 *bufferUsed, /* [size_is][out] */ BYTE *buffer); + DECLSPEC_XFGVIRT(ICLRDataTarget3, GetExceptionContextRecord) HRESULT ( STDMETHODCALLTYPE *GetExceptionContextRecord )( ICLRDataTarget3 * This, /* [in] */ ULONG32 bufferSize, /* [out] */ ULONG32 *bufferUsed, /* [size_is][out] */ BYTE *buffer); + DECLSPEC_XFGVIRT(ICLRDataTarget3, GetExceptionThreadID) HRESULT ( STDMETHODCALLTYPE *GetExceptionThreadID )( ICLRDataTarget3 * This, /* [out] */ ULONG32 *threadID); @@ -802,18 +865,22 @@ EXTERN_C const IID IID_ICLRRuntimeLocator; { BEGIN_INTERFACE + DECLSPEC_XFGVIRT(IUnknown, QueryInterface) HRESULT ( STDMETHODCALLTYPE *QueryInterface )( ICLRRuntimeLocator * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ _COM_Outptr_ void **ppvObject); + DECLSPEC_XFGVIRT(IUnknown, AddRef) ULONG ( STDMETHODCALLTYPE *AddRef )( ICLRRuntimeLocator * This); + DECLSPEC_XFGVIRT(IUnknown, Release) ULONG ( STDMETHODCALLTYPE *Release )( ICLRRuntimeLocator * This); + DECLSPEC_XFGVIRT(ICLRRuntimeLocator, GetRuntimeBase) HRESULT ( STDMETHODCALLTYPE *GetRuntimeBase )( ICLRRuntimeLocator * This, /* [out] */ CLRDATA_ADDRESS *baseAddress); @@ -890,18 +957,22 @@ EXTERN_C const IID IID_ICLRMetadataLocator; { BEGIN_INTERFACE + DECLSPEC_XFGVIRT(IUnknown, QueryInterface) HRESULT ( STDMETHODCALLTYPE *QueryInterface )( ICLRMetadataLocator * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ _COM_Outptr_ void **ppvObject); + DECLSPEC_XFGVIRT(IUnknown, AddRef) ULONG ( STDMETHODCALLTYPE *AddRef )( ICLRMetadataLocator * This); + DECLSPEC_XFGVIRT(IUnknown, Release) ULONG ( STDMETHODCALLTYPE *Release )( ICLRMetadataLocator * This); + DECLSPEC_XFGVIRT(ICLRMetadataLocator, GetMetadata) HRESULT ( STDMETHODCALLTYPE *GetMetadata )( ICLRMetadataLocator * This, /* [in] */ LPCWSTR imagePath, @@ -979,18 +1050,22 @@ EXTERN_C const IID IID_ICLRDataEnumMemoryRegionsCallback; { BEGIN_INTERFACE + DECLSPEC_XFGVIRT(IUnknown, QueryInterface) HRESULT ( STDMETHODCALLTYPE *QueryInterface )( ICLRDataEnumMemoryRegionsCallback * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ _COM_Outptr_ void **ppvObject); + DECLSPEC_XFGVIRT(IUnknown, AddRef) ULONG ( STDMETHODCALLTYPE *AddRef )( ICLRDataEnumMemoryRegionsCallback * This); + DECLSPEC_XFGVIRT(IUnknown, Release) ULONG ( STDMETHODCALLTYPE *Release )( ICLRDataEnumMemoryRegionsCallback * This); + DECLSPEC_XFGVIRT(ICLRDataEnumMemoryRegionsCallback, EnumMemoryRegion) HRESULT ( STDMETHODCALLTYPE *EnumMemoryRegion )( ICLRDataEnumMemoryRegionsCallback * This, /* [in] */ CLRDATA_ADDRESS address, @@ -1062,23 +1137,28 @@ EXTERN_C const IID IID_ICLRDataEnumMemoryRegionsCallback2; { BEGIN_INTERFACE + DECLSPEC_XFGVIRT(IUnknown, QueryInterface) HRESULT ( STDMETHODCALLTYPE *QueryInterface )( ICLRDataEnumMemoryRegionsCallback2 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ _COM_Outptr_ void **ppvObject); + DECLSPEC_XFGVIRT(IUnknown, AddRef) ULONG ( STDMETHODCALLTYPE *AddRef )( ICLRDataEnumMemoryRegionsCallback2 * This); + DECLSPEC_XFGVIRT(IUnknown, Release) ULONG ( STDMETHODCALLTYPE *Release )( ICLRDataEnumMemoryRegionsCallback2 * This); + DECLSPEC_XFGVIRT(ICLRDataEnumMemoryRegionsCallback, EnumMemoryRegion) HRESULT ( STDMETHODCALLTYPE *EnumMemoryRegion )( ICLRDataEnumMemoryRegionsCallback2 * This, /* [in] */ CLRDATA_ADDRESS address, /* [in] */ ULONG32 size); + DECLSPEC_XFGVIRT(ICLRDataEnumMemoryRegionsCallback2, UpdateMemoryRegion) HRESULT ( STDMETHODCALLTYPE *UpdateMemoryRegion )( ICLRDataEnumMemoryRegionsCallback2 * This, /* [in] */ CLRDATA_ADDRESS address, @@ -1126,7 +1206,91 @@ EXTERN_C const IID IID_ICLRDataEnumMemoryRegionsCallback2; #endif /* __ICLRDataEnumMemoryRegionsCallback2_INTERFACE_DEFINED__ */ -/* interface __MIDL_itf_clrdata_0000_0007 */ +#ifndef __ICLRDataLoggingCallback_INTERFACE_DEFINED__ +#define __ICLRDataLoggingCallback_INTERFACE_DEFINED__ + +/* interface ICLRDataLoggingCallback */ +/* [uuid][local][object] */ + + +EXTERN_C const IID IID_ICLRDataLoggingCallback; + +#if defined(__cplusplus) && !defined(CINTERFACE) + + MIDL_INTERFACE("F315248D-8B79-49DB-B184-37426559F703") + ICLRDataLoggingCallback : public IUnknown + { + public: + virtual HRESULT STDMETHODCALLTYPE LogMessage( + /* [in] */ LPCSTR message) = 0; + + }; + + +#else /* C style interface */ + + typedef struct ICLRDataLoggingCallbackVtbl + { + BEGIN_INTERFACE + + DECLSPEC_XFGVIRT(IUnknown, QueryInterface) + HRESULT ( STDMETHODCALLTYPE *QueryInterface )( + ICLRDataLoggingCallback * This, + /* [in] */ REFIID riid, + /* [annotation][iid_is][out] */ + _COM_Outptr_ void **ppvObject); + + DECLSPEC_XFGVIRT(IUnknown, AddRef) + ULONG ( STDMETHODCALLTYPE *AddRef )( + ICLRDataLoggingCallback * This); + + DECLSPEC_XFGVIRT(IUnknown, Release) + ULONG ( STDMETHODCALLTYPE *Release )( + ICLRDataLoggingCallback * This); + + DECLSPEC_XFGVIRT(ICLRDataLoggingCallback, LogMessage) + HRESULT ( STDMETHODCALLTYPE *LogMessage )( + ICLRDataLoggingCallback * This, + /* [in] */ LPCSTR message); + + END_INTERFACE + } ICLRDataLoggingCallbackVtbl; + + interface ICLRDataLoggingCallback + { + CONST_VTBL struct ICLRDataLoggingCallbackVtbl *lpVtbl; + }; + + + +#ifdef COBJMACROS + + +#define ICLRDataLoggingCallback_QueryInterface(This,riid,ppvObject) \ + ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) + +#define ICLRDataLoggingCallback_AddRef(This) \ + ( (This)->lpVtbl -> AddRef(This) ) + +#define ICLRDataLoggingCallback_Release(This) \ + ( (This)->lpVtbl -> Release(This) ) + + +#define ICLRDataLoggingCallback_LogMessage(This,message) \ + ( (This)->lpVtbl -> LogMessage(This,message) ) + +#endif /* COBJMACROS */ + + +#endif /* C style interface */ + + + + +#endif /* __ICLRDataLoggingCallback_INTERFACE_DEFINED__ */ + + +/* interface __MIDL_itf_clrdata_0000_0008 */ /* [local] */ typedef @@ -1140,8 +1304,8 @@ enum CLRDataEnumMemoryFlags -extern RPC_IF_HANDLE __MIDL_itf_clrdata_0000_0007_v0_0_c_ifspec; -extern RPC_IF_HANDLE __MIDL_itf_clrdata_0000_0007_v0_0_s_ifspec; +extern RPC_IF_HANDLE __MIDL_itf_clrdata_0000_0008_v0_0_c_ifspec; +extern RPC_IF_HANDLE __MIDL_itf_clrdata_0000_0008_v0_0_s_ifspec; #ifndef __ICLRDataEnumMemoryRegions_INTERFACE_DEFINED__ #define __ICLRDataEnumMemoryRegions_INTERFACE_DEFINED__ @@ -1172,18 +1336,22 @@ EXTERN_C const IID IID_ICLRDataEnumMemoryRegions; { BEGIN_INTERFACE + DECLSPEC_XFGVIRT(IUnknown, QueryInterface) HRESULT ( STDMETHODCALLTYPE *QueryInterface )( ICLRDataEnumMemoryRegions * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ _COM_Outptr_ void **ppvObject); + DECLSPEC_XFGVIRT(IUnknown, AddRef) ULONG ( STDMETHODCALLTYPE *AddRef )( ICLRDataEnumMemoryRegions * This); + DECLSPEC_XFGVIRT(IUnknown, Release) ULONG ( STDMETHODCALLTYPE *Release )( ICLRDataEnumMemoryRegions * This); + DECLSPEC_XFGVIRT(ICLRDataEnumMemoryRegions, EnumMemoryRegions) HRESULT ( STDMETHODCALLTYPE *EnumMemoryRegions )( ICLRDataEnumMemoryRegions * This, /* [in] */ ICLRDataEnumMemoryRegionsCallback *callback, diff --git a/src/coreclr/pal/src/thread/process.cpp b/src/coreclr/pal/src/thread/process.cpp index a9b9282c2a39f2..69c7fe04326a4e 100644 --- a/src/coreclr/pal/src/thread/process.cpp +++ b/src/coreclr/pal/src/thread/process.cpp @@ -2436,7 +2436,7 @@ PROCCreateCrashDump( { // Ignore any error because on some CentOS and OpenSUSE distros, it isn't // supported but createdump works just fine. - ERROR("PROCCreateCrashDump: prctl() FAILED %s (%d)\n", errno, strerror(errno)); + ERROR("PROCCreateCrashDump: prctl() FAILED %s (%d)\n", strerror(errno), errno); } #endif // HAVE_PRCTL_H && HAVE_PR_SET_PTRACER close(child_pipe); @@ -2464,11 +2464,17 @@ PROCCreateCrashDump( int result = waitpid(childpid, &wstatus, 0); if (result != childpid) { - fprintf(stderr, "Problem waiting for createdump: waitpid() FAILED result %d wstatus %d errno %s (%d)\n", + fprintf(stderr, "Problem waiting for createdump: waitpid() FAILED result %d wstatus %08x errno %s (%d)\n", result, wstatus, strerror(errno), errno); return false; } - return !WIFEXITED(wstatus) || WEXITSTATUS(wstatus) == 0; + else + { +#ifdef _DEBUG + fprintf(stderr, "[createdump] waitpid() returned successfully (wstatus %08x)\n", wstatus); +#endif + return !WIFEXITED(wstatus) || WEXITSTATUS(wstatus) == 0; + } } return true; } diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index a0f06d04622047..c4d8156fc939b8 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -7828,6 +7828,10 @@ MethodTable::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) { pMTCanonical->EnumMemoryRegions(flags); } + else + { + DacLogMessage("MT %p invalid canonical MT %p\n", dac_cast(this), dac_cast(pMTCanonical)); + } } else { @@ -7846,6 +7850,10 @@ MethodTable::EnumMemoryRegions(CLRDataEnumMemoryFlags flags) } pClass->EnumMemoryRegions(flags, this); } + else + { + DacLogMessage("MT %p invalid class %p\n", dac_cast(this), dac_cast(pClass)); + } } PTR_MethodTable pMTParent = GetParentMethodTable(); diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 5ab2dbcce38e5f..91b2f55d9ea33a 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -8459,6 +8459,11 @@ Thread::EnumMemoryRegionsWorker(CLRDataEnumMemoryFlags flags) } else { + // Skip any thread that doesn't have a OS thread id because DacGetThreadContext is going to throw an exception. + if (GetOSThreadId() == 0) + { + return; + } DacGetThreadContext(this, &context); } From 7cd6a16ffc71530510f79695978f5b4d762623d3 Mon Sep 17 00:00:00 2001 From: Jeff Handley Date: Wed, 10 Aug 2022 22:42:50 -0700 Subject: [PATCH 16/68] Remove [RequiresPreviewFeatures] from System.Security.Cryptography.Cose --- .../ref/System.Security.Cryptography.Cose.csproj | 7 ------- .../src/System.Security.Cryptography.Cose.csproj | 6 ------ .../tests/System.Security.Cryptography.Cose.Tests.csproj | 6 ------ 3 files changed, 19 deletions(-) diff --git a/src/libraries/System.Security.Cryptography.Cose/ref/System.Security.Cryptography.Cose.csproj b/src/libraries/System.Security.Cryptography.Cose/ref/System.Security.Cryptography.Cose.csproj index 15a639306653dd..e1f24a27466df6 100644 --- a/src/libraries/System.Security.Cryptography.Cose/ref/System.Security.Cryptography.Cose.csproj +++ b/src/libraries/System.Security.Cryptography.Cose/ref/System.Security.Cryptography.Cose.csproj @@ -1,19 +1,12 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) - True - true - - - - diff --git a/src/libraries/System.Security.Cryptography.Cose/src/System.Security.Cryptography.Cose.csproj b/src/libraries/System.Security.Cryptography.Cose/src/System.Security.Cryptography.Cose.csproj index 9e5d34d2b19934..197ecdc08489cc 100644 --- a/src/libraries/System.Security.Cryptography.Cose/src/System.Security.Cryptography.Cose.csproj +++ b/src/libraries/System.Security.Cryptography.Cose/src/System.Security.Cryptography.Cose.csproj @@ -2,8 +2,6 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true - True - true true 7.0.0-rc.1.22375.1 + 2.1 7.0.0-alpha.1.22406.1 11.1.0-alpha.1.22376.4 diff --git a/eng/pipelines/libraries/helix-queues-setup.yml b/eng/pipelines/libraries/helix-queues-setup.yml index 7bfad19026b524..014726d5ab5424 100644 --- a/eng/pipelines/libraries/helix-queues-setup.yml +++ b/eng/pipelines/libraries/helix-queues-setup.yml @@ -184,6 +184,7 @@ jobs: # windows arm64 - ${{ if eq(parameters.platform, 'windows_arm64') }}: - Windows.10.Arm64.Open + - Windows.11.Arm64.Open # WebAssembly - ${{ if eq(parameters.platform, 'Browser_wasm') }}: diff --git a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj index 86f1e5c79e7209..a19e8592056f62 100644 --- a/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj +++ b/src/libraries/System.Net.Quic/src/System.Net.Quic.csproj @@ -103,6 +103,11 @@ PrivateAssets="all" GeneratePathProperty="true" Condition="'$(DotNetBuildFromSource)' != 'true'" /> + @@ -141,6 +146,14 @@ + + + + + + + From 4893732ba881a4fb9023af1d6d4e64bb2a6eddbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 11 Aug 2022 19:33:32 +0900 Subject: [PATCH 28/68] Disable newly added TraceSource.Config.Tests on NativeAOT (#73742) Likely a single file related issue. Not sure how ConfigurationManager grabs the config files, but probably Assembly.Location. Will need to look at that. ``` [FAIL] System.Diagnostics.TraceSourceConfigTests.ConfigurationTests.ConfigWithEvents_RuntimeListener System.Configuration.ConfigurationErrorsException : Could not create System.Diagnostics.SourceSwitch. at System.Diagnostics.TraceUtils.GetRuntimeObject(String, Type, String) + 0x5b8 at System.Diagnostics.TraceConfiguration.g__CreateSwitch|3_0(String, String, TraceConfiguration.<>c__DisplayClass3_0&) + 0x44 at System.Diagnostics.TraceConfiguration.InitializingTraceSource(Object, InitializingTraceSourceEventArgs) + 0x208 at System.Diagnostics.TraceSource.Config!+0xf7b98c at System.Diagnostics.TraceSource.OnInitializing(InitializingTraceSourceEventArgs) + 0x3c at System.Diagnostics.TraceSource.Initialize() + 0x78 at System.Diagnostics.TraceSourceConfigTests.ConfigurationTests.ConfigWithEvents_RuntimeListener() + 0x94 at System.Diagnostics.TraceSource.Config!+0x10bcbe4 at System.Reflection.DynamicInvokeInfo.Invoke(Object, IntPtr, Object[], BinderBundle, Boolean) + 0xd8 ``` --- src/libraries/tests.proj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index d1a1ffe538402e..9820d280330719 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -512,6 +512,7 @@ + From 14be2a080974c454bed200dbdaa3fd1293ee530a Mon Sep 17 00:00:00 2001 From: Krzysztof Wicher Date: Thu, 11 Aug 2022 12:44:53 +0200 Subject: [PATCH 29/68] Fix JSON source gen to work with public context and public types with internal properties (#73622) --- .../gen/JsonSourceGenerator.Parser.cs | 10 +++ .../gen/Reflection/RoslynExtensions.cs | 4 +- .../gen/Reflection/TypeWrapper.cs | 26 +++++++- .../ContextClasses.cs | 3 + .../Serialization/PropertyVisibilityTests.cs | 61 +++++++++++++++++++ .../TestClasses.cs | 49 +++++++++++++++ 6 files changed, 149 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index 3718cc53c4186c..60a0be1265182a 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -1041,6 +1041,11 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener } spec = GetPropertyGenerationSpec(propertyInfo, isVirtual, generationMode); + if (!spec.IsPublic && !propertyInfo.PropertyType.IsPublic) + { + continue; + } + CacheMemberHelper(propertyInfo.GetDiagnosticLocation()); } @@ -1052,6 +1057,11 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener } spec = GetPropertyGenerationSpec(fieldInfo, isVirtual: false, generationMode); + if (!spec.IsPublic && !fieldInfo.FieldType.IsPublic) + { + continue; + } + CacheMemberHelper(fieldInfo.GetDiagnosticLocation()); } diff --git a/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs index 6da48554ff30b1..7add066f093807 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Microsoft.CodeAnalysis; @@ -9,7 +10,8 @@ namespace System.Text.Json.Reflection { internal static class RoslynExtensions { - public static Type AsType(this ITypeSymbol typeSymbol, MetadataLoadContextInternal metadataLoadContext) + [return: NotNullIfNotNull("typeSymbol")] + public static Type? AsType(this ITypeSymbol? typeSymbol, MetadataLoadContextInternal metadataLoadContext) { if (typeSymbol == null) { diff --git a/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs b/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs index c34339e6ca153c..64e49cc5527419 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/TypeWrapper.cs @@ -109,7 +109,7 @@ public override string AssemblyQualifiedName } } - public override Type BaseType => _typeSymbol.BaseType!.AsType(_metadataLoadContext); + public override Type BaseType => _typeSymbol.BaseType.AsType(_metadataLoadContext); public override Type DeclaringType => _typeSymbol.ContainingType?.ConstructedFrom.AsType(_metadataLoadContext); @@ -499,9 +499,29 @@ protected override TypeAttributes GetAttributeFlagsImpl() _typeAttributes |= TypeAttributes.Interface; } - if (_typeSymbol.ContainingType != null && _typeSymbol.DeclaredAccessibility == Accessibility.Private) + bool isNested = _typeSymbol.ContainingType != null; + + switch (_typeSymbol.DeclaredAccessibility) { - _typeAttributes |= TypeAttributes.NestedPrivate; + case Accessibility.NotApplicable: + case Accessibility.Private: + _typeAttributes |= isNested ? TypeAttributes.NestedPrivate : TypeAttributes.NotPublic; + break; + case Accessibility.ProtectedAndInternal: + _typeAttributes |= isNested ? TypeAttributes.NestedFamANDAssem : TypeAttributes.NotPublic; + break; + case Accessibility.Protected: + _typeAttributes |= isNested ? TypeAttributes.NestedFamily : TypeAttributes.NotPublic; + break; + case Accessibility.Internal: + _typeAttributes |= isNested ? TypeAttributes.NestedAssembly : TypeAttributes.NotPublic; + break; + case Accessibility.ProtectedOrInternal: + _typeAttributes |= isNested ? TypeAttributes.NestedFamORAssem : TypeAttributes.NotPublic; + break; + case Accessibility.Public: + _typeAttributes |= isNested ? TypeAttributes.NestedPublic : TypeAttributes.Public; + break; } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs index a36d06f62799c1..0d87b280c992a8 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/ContextClasses.cs @@ -117,6 +117,9 @@ public JsonTypeInfo JsonMessage internal partial class DictionaryTypeContext : JsonSerializerContext { } [JsonSerializable(typeof(JsonMessage))] + [JsonSerializable(typeof(PublicClassWithDifferentAccessibilitiesProperties))] + [JsonSerializable(typeof(JsonConverter))] + [JsonSerializable(typeof(JsonSerializerOptions))] public partial class PublicContext : JsonSerializerContext { } [JsonSerializable(typeof(JsonMessage))] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs index 5a0861ef7f1872..624854c1bca3ff 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/Serialization/PropertyVisibilityTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Reflection; using System.Text.Json.Serialization; +using System.Text.Json.Serialization.Metadata; using System.Text.Json.Serialization.Tests; using System.Threading.Tasks; using Xunit; @@ -326,6 +327,66 @@ public override async Task JsonIgnoreCondition_WhenWritingNull_OnValueType_Fail_ await Assert.ThrowsAsync(async () => await Serializer.SerializeWrapper(Activator.CreateInstance(type), type)); } + [Fact] + public static void PublicContexAndTestClassWithPropertiesWithDifferentAccesibilities() + { + JsonSerializerOptions options = new() + { + IncludeFields = true, + }; + + options.AddContext(); + + PublicClassWithDifferentAccessibilitiesProperties obj = new() + { + PublicProperty = new(), + PublicField = new(), + }; + + string json = JsonSerializer.Serialize(obj, options); + Assert.Equal("""{"PublicProperty":{},"PublicField":{}}""", json); + + var deserialized = JsonSerializer.Deserialize(json, options); + Assert.NotNull(deserialized.PublicProperty); + Assert.NotNull(deserialized.PublicField); + + json = "{}"; + deserialized = JsonSerializer.Deserialize(json, options); + Assert.Null(deserialized.PublicProperty); + Assert.Null(deserialized.PublicField); + } + + [Fact] + public static void PublicContexAndJsonConverter() + { + JsonConverter obj = JsonMetadataServices.BooleanConverter; + + string json = JsonSerializer.Serialize(obj, PublicContext.Default.Options); + Assert.Equal("{}", json); + + Assert.Throws(() => JsonSerializer.Deserialize(json, PublicContext.Default.Options)); + } + + [Fact] + public static void PublicContexAndJsonSerializerOptions() + { + JsonSerializerOptions obj = new() + { + DefaultBufferSize = 123, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + IncludeFields = true, + }; + + string json = JsonSerializer.Serialize(obj, PublicContext.Default.Options); + + JsonSerializerOptions deserialized = JsonSerializer.Deserialize(json, PublicContext.Default.Options); + Assert.Equal(obj.DefaultBufferSize, deserialized.DefaultBufferSize); + Assert.Equal(obj.DefaultIgnoreCondition, deserialized.DefaultIgnoreCondition); + Assert.Equal(obj.IncludeFields, deserialized.IncludeFields); + Assert.Equal(obj.IgnoreReadOnlyFields, deserialized.IgnoreReadOnlyFields); + Assert.Equal(obj.MaxDepth, deserialized.MaxDepth); + } + [JsonSerializable(typeof(ClassWithNewSlotField))] [JsonSerializable(typeof(int))] [JsonSerializable(typeof(object))] diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs index 8a424c81b6d681..4a52c8ce8179b0 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/TestClasses.cs @@ -226,4 +226,53 @@ public class MyNestedGenericNestedGenericClass } } } + + public class PublicClassWithDifferentAccessibilitiesProperties + { + public PublicTestClass PublicProperty { get; set; } + internal PublicTestClass.InternalNestedClass InternalProperty1 { get; set; } + protected ProtectedClass ProtectedProperty1 { get; set; } + protected ProtectedInternalClass ProtectedProperty2 { get; set; } + internal InternalTestClass InternalProperty2 { get; set; } + internal InternalTestClass.PublicClass InternalProperty3 { get; set; } + internal InternalTestClass.ProtectedInternalClass InternalProperty4 { get; set; } + private InternalTestClass PrivateProperty1 { get; set; } + private PrivateClass PrivateProperty2 { get; set; } + private PrivateClass2 PrivateProperty3 { get; set; } + PrivateClass2 PrivateProperty4 { get; set; } + private PrivateProtectedClass PrivateProperty5 { get; set; } + + public PublicTestClass PublicField; + +#pragma warning disable CS0414 // The field ... is assigned but its value is never used. + internal PublicTestClass.InternalNestedClass InternalField1 = null; + protected ProtectedClass ProtectedField1 = null; + protected ProtectedInternalClass ProtectedField2 = null; + internal InternalTestClass InternalField2 = null; + internal InternalTestClass.PublicClass InternalField3 = null; + internal InternalTestClass.ProtectedInternalClass InternalField4 = null; + private InternalTestClass PrivateField1 = null; + private PrivateClass PrivateField2 = null; + private PrivateClass2 PrivateField3 = null; + PrivateClass2 PrivateField4 = null; + private PrivateProtectedClass PrivateField5 = null; +#pragma warning restore + + private class PrivateClass { } + protected class ProtectedClass { } + protected internal class ProtectedInternalClass { } + private protected class PrivateProtectedClass { } + class PrivateClass2 { } + } + + internal class InternalTestClass + { + public class PublicClass { } + protected internal class ProtectedInternalClass { } + } + + public class PublicTestClass + { + internal class InternalNestedClass { } + } } From d2c991effcdf543cc60632e5588984aa22dd6772 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 11 Aug 2022 14:05:11 +0200 Subject: [PATCH 30/68] [Android][iOS] Enable HttpClientHandler.DangerousAcceptAnyServerCertificateValidator when using native handlers (#73763) The static `HttpClientHandler.DangerousAcceptAnyServerCertificateValidator` getter throws PNSE when the native HTTP handler is enabled because Xamarin.Android's `AndroidMessageHandler` and Xamarin.iOS `NSUrlSessionHandler` didn't use to have support for the `ServerCertificateCustomValidationCallback`. We already implemented the Android part in .NET 6 and support in the iOS implementation is WIP and we should be able to implement it in time for .NET 7. IMO it's safe to remove the exception in the getter in .NET 7. Closes #68898 --- .../System/Net/Http/HttpClientHandler.AnyMobile.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.cs index 02f84782fbcb6b..a355a7af61e038 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.cs @@ -738,16 +738,9 @@ protected internal override Task SendAsync(HttpRequestMessa { get { - if (IsNativeHandlerEnabled) - { - throw new PlatformNotSupportedException(); - } - else - { - return Volatile.Read(ref s_dangerousAcceptAnyServerCertificateValidator) ?? - Interlocked.CompareExchange(ref s_dangerousAcceptAnyServerCertificateValidator, delegate { return true; }, null) ?? - s_dangerousAcceptAnyServerCertificateValidator; - } + return Volatile.Read(ref s_dangerousAcceptAnyServerCertificateValidator) ?? + Interlocked.CompareExchange(ref s_dangerousAcceptAnyServerCertificateValidator, delegate { return true; }, null) ?? + s_dangerousAcceptAnyServerCertificateValidator; } } From deac993c3640730e5f4acaed8d257dde0b17c2fe Mon Sep 17 00:00:00 2001 From: Mitchell Hwang <16830051+mdh1418@users.noreply.github.com> Date: Thu, 11 Aug 2022 10:50:42 -0400 Subject: [PATCH 31/68] [libs] Remove JitInfoIsPopulated mobile activeissue (#73716) --- .../System.Runtime/tests/System/Runtime/JitInfoTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libraries/System.Runtime/tests/System/Runtime/JitInfoTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/JitInfoTests.cs index da5ff53a9a40f4..239bceb52effe9 100644 --- a/src/libraries/System.Runtime/tests/System/Runtime/JitInfoTests.cs +++ b/src/libraries/System.Runtime/tests/System/Runtime/JitInfoTests.cs @@ -38,7 +38,6 @@ private long MakeAndInvokeDynamicSquareMethod(int input) } [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsReflectionEmitSupported))] // JitInfo metrics will be 0 in AOT scenarios - [ActiveIssue("https://github.com/dotnet/runtime/issues/55712", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)] public void JitInfoIsPopulated() { TimeSpan beforeCompilationTime = System.Runtime.JitInfo.GetCompilationTime(); From 06060cde583c57820bd6578171c352c5e635b000 Mon Sep 17 00:00:00 2001 From: Peter Sollich Date: Thu, 11 Aug 2022 18:18:50 +0200 Subject: [PATCH 32/68] Fix two issues: (#73761) - verify_region_to_generation_map needs to skip read only segments as they are not represented in the region to generation map. - move check to verify the mark queue is empty from the destructor to a normal method, and add a call to that method at the end of the mark phase. --- src/coreclr/gc/gc.cpp | 9 ++++++++- src/coreclr/gc/gcpriv.h | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 77a0979055ab4c..993719b1536cf8 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -23961,7 +23961,7 @@ uint8_t* mark_queue_t::get_next_marked() return nullptr; } -mark_queue_t::~mark_queue_t() +void mark_queue_t::verify_empty() { for (size_t slot_index = 0; slot_index < slot_count; slot_index++) { @@ -26043,6 +26043,11 @@ void gc_heap::verify_region_to_generation_map() generation *gen = hp->generation_of (gen_number); for (heap_segment *region = generation_start_segment (gen); region != nullptr; region = heap_segment_next (region)) { + if (heap_segment_read_only_p (region)) + { + // the region to generation map doesn't cover read only segments + continue; + } size_t region_index_start = get_basic_region_index_for_address (get_region_start (region)); size_t region_index_end = get_basic_region_index_for_address (heap_segment_reserved (region)); int gen_num = min (gen_number, soh_gen2); @@ -26767,6 +26772,8 @@ void gc_heap::mark_phase (int condemned_gen_number, BOOL mark_only_p) finalization_promoted_bytes = total_promoted_bytes - promoted_bytes_live; + mark_queue.verify_empty(); + dprintf(2,("---- End of mark phase ----")); } diff --git a/src/coreclr/gc/gcpriv.h b/src/coreclr/gc/gcpriv.h index e0640a860400af..75b2388ffd87b4 100644 --- a/src/coreclr/gc/gcpriv.h +++ b/src/coreclr/gc/gcpriv.h @@ -1230,7 +1230,7 @@ class mark_queue_t uint8_t* get_next_marked(); - ~mark_queue_t(); + void verify_empty(); }; //class definition of the internal class From 50fec864a06a6187c1cc0b7281b640094d64861b Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Thu, 11 Aug 2022 09:51:46 -0700 Subject: [PATCH 33/68] Honor naming policy for string enum deserialization (#73348) * Honor naming policy for string enum deserialization * Remove cache bounds in favor of correctness * Address feedback * Remove possibly invalid dictionary key policy cache * Address feedback * Address feedback * Clean up --- .../Converters/Value/EnumConverter.cs | 407 ++++++++++-------- .../Serialization/EnumConverterTests.cs | 81 ++++ 2 files changed, 302 insertions(+), 186 deletions(-) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs index a4589b4843f3ee..1f14cc9acddb6d 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverter.cs @@ -24,9 +24,17 @@ internal sealed class EnumConverter : JsonConverter private readonly JsonNamingPolicy? _namingPolicy; - private readonly ConcurrentDictionary _nameCache; - - private ConcurrentDictionary? _dictionaryKeyPolicyCache; + /// + /// Holds a mapping from enum value to text that might be formatted with . + /// is as the key used rather than given measurements that + /// show private memory savings when a single type is used https://github.com/dotnet/runtime/pull/36726#discussion_r428868336. + /// + private readonly ConcurrentDictionary _nameCacheForWriting; + + /// + /// Holds a mapping from text that might be formatted with to enum value. + /// + private readonly ConcurrentDictionary? _nameCacheForReading; // This is used to prevent flooding the cache due to exponential bitwise combinations of flags. // Since multiple threads can add to the cache, a few more values might be added. @@ -46,7 +54,12 @@ public EnumConverter(EnumConverterOptions converterOptions, JsonNamingPolicy? na { _converterOptions = converterOptions; _namingPolicy = namingPolicy; - _nameCache = new ConcurrentDictionary(); + _nameCacheForWriting = new ConcurrentDictionary(); + + if (namingPolicy != null) + { + _nameCacheForReading = new ConcurrentDictionary(); + } #if NETCOREAPP string[] names = Enum.GetNames(); @@ -61,11 +74,6 @@ public EnumConverter(EnumConverterOptions converterOptions, JsonNamingPolicy? na for (int i = 0; i < names.Length; i++) { - if (_nameCache.Count >= NameCacheSizeSoftLimit) - { - break; - } - #if NETCOREAPP T value = values[i]; #else @@ -74,11 +82,9 @@ public EnumConverter(EnumConverterOptions converterOptions, JsonNamingPolicy? na ulong key = ConvertToUInt64(value); string name = names[i]; - _nameCache.TryAdd( - key, - namingPolicy == null - ? JsonEncodedText.Encode(name, encoder) - : FormatEnumValue(name, encoder)); + string jsonName = FormatJsonName(name, namingPolicy); + _nameCacheForWriting.TryAdd(key, JsonEncodedText.Encode(jsonName, encoder)); + _nameCacheForReading?.TryAdd(jsonName, value); } } @@ -94,7 +100,20 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial return default; } - return ReadAsPropertyNameCore(ref reader, typeToConvert, options); +#if NETCOREAPP + if (TryParseEnumCore(ref reader, options, out T value)) +#else + string? enumString = reader.GetString(); + if (TryParseEnumCore(enumString, options, out T value)) +#endif + { + return value; + } +#if NETCOREAPP + return ReadEnumUsingNamingPolicy(reader.GetString()); +#else + return ReadEnumUsingNamingPolicy(enumString); +#endif } if (token != JsonTokenType.Number || !_converterOptions.HasFlag(EnumConverterOptions.AllowNumbers)) @@ -168,39 +187,33 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions { ulong key = ConvertToUInt64(value); - if (_nameCache.TryGetValue(key, out JsonEncodedText formatted)) + if (_nameCacheForWriting.TryGetValue(key, out JsonEncodedText formatted)) { writer.WriteStringValue(formatted); return; } string original = value.ToString(); + if (IsValidIdentifier(original)) { // We are dealing with a combination of flag constants since // all constant values were cached during warm-up. Debug.Assert(original.Contains(ValueSeparator)); - JavaScriptEncoder? encoder = options.Encoder; + original = FormatJsonName(original, _namingPolicy); - if (_nameCache.Count < NameCacheSizeSoftLimit) + if (_nameCacheForWriting.Count < NameCacheSizeSoftLimit) { - formatted = _namingPolicy == null - ? JsonEncodedText.Encode(original, encoder) - : FormatEnumValue(original, encoder); - + formatted = JsonEncodedText.Encode(original, options.Encoder); writer.WriteStringValue(formatted); - - _nameCache.TryAdd(key, formatted); + _nameCacheForWriting.TryAdd(key, formatted); } else { // We also do not create a JsonEncodedText instance here because passing the string // directly to the writer is cheaper than creating one and not caching it for reuse. - writer.WriteStringValue( - _namingPolicy == null - ? original - : FormatEnumValueToString(original, encoder)); + writer.WriteStringValue(original); } return; @@ -244,81 +257,96 @@ public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions } } - // This method is adapted from Enum.ToUInt64 (an internal method): - // https://github.com/dotnet/runtime/blob/bd6cbe3642f51d70839912a6a666e5de747ad581/src/libraries/System.Private.CoreLib/src/System/Enum.cs#L240-L260 - private static ulong ConvertToUInt64(object value) + internal override T ReadAsPropertyNameCore(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - Debug.Assert(value is T); - ulong result = s_enumTypeCode switch - { - TypeCode.Int32 => (ulong)(int)value, - TypeCode.UInt32 => (uint)value, - TypeCode.UInt64 => (ulong)value, - TypeCode.Int64 => (ulong)(long)value, - TypeCode.SByte => (ulong)(sbyte)value, - TypeCode.Byte => (byte)value, - TypeCode.Int16 => (ulong)(short)value, - TypeCode.UInt16 => (ushort)value, - _ => throw new InvalidOperationException(), - }; - return result; - } +#if NETCOREAPP + bool success = TryParseEnumCore(ref reader, options, out T value); +#else + bool success = TryParseEnumCore(reader.GetString(), options, out T value); +#endif - private static bool IsValidIdentifier(string value) - { - // Trying to do this check efficiently. When an enum is converted to - // string the underlying value is given if it can't find a matching - // identifier (or identifiers in the case of flags). - // - // The underlying value will be given back with a digit (e.g. 0-9) possibly - // preceded by a negative sign. Identifiers have to start with a letter - // so we'll just pick the first valid one and check for a negative sign - // if needed. - return (value[0] >= 'A' && - (!s_isSignedEnum || !value.StartsWith(NumberFormatInfo.CurrentInfo.NegativeSign))); - } + if (!success) + { + ThrowHelper.ThrowJsonException(); + } - private JsonEncodedText FormatEnumValue(string value, JavaScriptEncoder? encoder) - { - Debug.Assert(_namingPolicy != null); - string formatted = FormatEnumValueToString(value, encoder); - return JsonEncodedText.Encode(formatted, encoder); + return value; } - private string FormatEnumValueToString(string value, JavaScriptEncoder? encoder) + internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, T value, JsonSerializerOptions options, bool isWritingExtensionDataProperty) { - Debug.Assert(_namingPolicy != null); + ulong key = ConvertToUInt64(value); - string converted; - if (!value.Contains(ValueSeparator)) + if (options.DictionaryKeyPolicy == null && _nameCacheForWriting.TryGetValue(key, out JsonEncodedText formatted)) { - converted = _namingPolicy.ConvertName(value); + writer.WritePropertyName(formatted); + return; } - else + + string original = value.ToString(); + + if (IsValidIdentifier(original)) { - // todo: optimize implementation here by leveraging https://github.com/dotnet/runtime/issues/934. - string[] enumValues = value.Split( -#if NETCOREAPP - ValueSeparator -#else - new string[] { ValueSeparator }, StringSplitOptions.None -#endif - ); + if (options.DictionaryKeyPolicy != null) + { + original = FormatJsonName(original, options.DictionaryKeyPolicy); + writer.WritePropertyName(original); + return; + } - for (int i = 0; i < enumValues.Length; i++) + original = FormatJsonName(original, _namingPolicy); + + if (_nameCacheForWriting.Count < NameCacheSizeSoftLimit) { - enumValues[i] = _namingPolicy.ConvertName(enumValues[i]); + formatted = JsonEncodedText.Encode(original, options.Encoder); + writer.WritePropertyName(formatted); + _nameCacheForWriting.TryAdd(key, formatted); + } + else + { + // We also do not create a JsonEncodedText instance here because passing the string + // directly to the writer is cheaper than creating one and not caching it for reuse. + writer.WritePropertyName(original); } - converted = string.Join(ValueSeparator, enumValues); + return; } - return converted; + switch (s_enumTypeCode) + { + case TypeCode.Int32: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + case TypeCode.UInt32: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + case TypeCode.UInt64: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + case TypeCode.Int64: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + case TypeCode.Int16: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + case TypeCode.UInt16: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + case TypeCode.Byte: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + case TypeCode.SByte: + writer.WritePropertyName(Unsafe.As(ref value)); + break; + default: + ThrowHelper.ThrowJsonException(); + break; + } } - internal override T ReadAsPropertyNameCore(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { #if NETCOREAPP + private static bool TryParseEnumCore(ref Utf8JsonReader reader, JsonSerializerOptions options, out T value) + { char[]? rentedBuffer = null; int bufferLength = reader.ValueLength; @@ -330,144 +358,151 @@ internal override T ReadAsPropertyNameCore(ref Utf8JsonReader reader, Type typeT ReadOnlySpan source = charBuffer.Slice(0, charsWritten); // Try parsing case sensitive first - bool success = Enum.TryParse(source, out T value) || Enum.TryParse(source, ignoreCase: true, out value); + bool success = Enum.TryParse(source, out T result) || Enum.TryParse(source, ignoreCase: true, out result); if (rentedBuffer != null) { charBuffer.Slice(0, charsWritten).Clear(); ArrayPool.Shared.Return(rentedBuffer); } + + value = result; + return success; + } #else - string? enumString = reader.GetString(); + private static bool TryParseEnumCore(string? enumString, JsonSerializerOptions options, out T value) + { // Try parsing case sensitive first - bool success = Enum.TryParse(enumString, out T value) || Enum.TryParse(enumString, ignoreCase: true, out value); -#endif - if (!success) - { - ThrowHelper.ThrowJsonException(); - } - - return value; + bool success = Enum.TryParse(enumString, out T result) || Enum.TryParse(enumString, ignoreCase: true, out result); + value = result; + return success; } +#endif - internal override void WriteAsPropertyNameCore(Utf8JsonWriter writer, T value, JsonSerializerOptions options, bool isWritingExtensionDataProperty) + private T ReadEnumUsingNamingPolicy(string? enumString) { - // An EnumConverter that invokes this method - // can only be created by JsonSerializerOptions.GetDictionaryKeyConverter - // hence no naming policy is expected. - Debug.Assert(_namingPolicy == null); - - ulong key = ConvertToUInt64(value); - - // Try to obtain values from caches - if (options.DictionaryKeyPolicy != null) + if (_namingPolicy == null) { - Debug.Assert(!isWritingExtensionDataProperty); - - if (_dictionaryKeyPolicyCache != null && _dictionaryKeyPolicyCache.TryGetValue(key, out JsonEncodedText formatted)) - { - writer.WritePropertyName(formatted); - return; - } + ThrowHelper.ThrowJsonException(); } - else if (_nameCache.TryGetValue(key, out JsonEncodedText formatted)) + + if (enumString == null) { - writer.WritePropertyName(formatted); - return; + ThrowHelper.ThrowJsonException(); } - // if there are not cached values - string original = value.ToString(); - if (IsValidIdentifier(original)) + Debug.Assert(_nameCacheForReading != null, "Enum value cache should be instantiated if a naming policy is specified."); + + bool success; + + if (!(success = _nameCacheForReading.TryGetValue(enumString, out T value)) && enumString.Contains(ValueSeparator)) { - Debug.Assert(original.Contains(ValueSeparator)); + string[] enumValues = SplitFlagsEnum(enumString); + ulong result = 0; - if (options.DictionaryKeyPolicy != null) + for (int i = 0; i < enumValues.Length; i++) { - original = options.DictionaryKeyPolicy.ConvertName(original); - - if (original == null) + success = _nameCacheForReading.TryGetValue(enumValues[i], out value); + if (!success) { - ThrowHelper.ThrowInvalidOperationException_NamingPolicyReturnNull(options.DictionaryKeyPolicy); + break; } - _dictionaryKeyPolicyCache ??= new ConcurrentDictionary(); + result |= ConvertToUInt64(value); + } - if (_dictionaryKeyPolicyCache.Count < NameCacheSizeSoftLimit) - { - JavaScriptEncoder? encoder = options.Encoder; + value = (T)Enum.ToObject(typeof(T), result); - JsonEncodedText formatted = JsonEncodedText.Encode(original, encoder); + if (success && _nameCacheForReading.Count < NameCacheSizeSoftLimit) + { + _nameCacheForReading[enumString] = value; + } + } - writer.WritePropertyName(formatted); + if (!success) + { + ThrowHelper.ThrowJsonException(); + } - _dictionaryKeyPolicyCache.TryAdd(key, formatted); - } - else - { - // We also do not create a JsonEncodedText instance here because passing the string - // directly to the writer is cheaper than creating one and not caching it for reuse. - writer.WritePropertyName(original); - } + return value; + } - return; - } - else - { - // We might be dealing with a combination of flag constants since all constant values were - // likely cached during warm - up(assuming the number of constants <= NameCacheSizeSoftLimit). + // This method is adapted from Enum.ToUInt64 (an internal method): + // https://github.com/dotnet/runtime/blob/bd6cbe3642f51d70839912a6a666e5de747ad581/src/libraries/System.Private.CoreLib/src/System/Enum.cs#L240-L260 + private static ulong ConvertToUInt64(object value) + { + Debug.Assert(value is T); + ulong result = s_enumTypeCode switch + { + TypeCode.Int32 => (ulong)(int)value, + TypeCode.UInt32 => (uint)value, + TypeCode.UInt64 => (ulong)value, + TypeCode.Int64 => (ulong)(long)value, + TypeCode.SByte => (ulong)(sbyte)value, + TypeCode.Byte => (byte)value, + TypeCode.Int16 => (ulong)(short)value, + TypeCode.UInt16 => (ushort)value, + _ => throw new InvalidOperationException(), + }; + return result; + } - JavaScriptEncoder? encoder = options.Encoder; + private static bool IsValidIdentifier(string value) + { + // Trying to do this check efficiently. When an enum is converted to + // string the underlying value is given if it can't find a matching + // identifier (or identifiers in the case of flags). + // + // The underlying value will be given back with a digit (e.g. 0-9) possibly + // preceded by a negative sign. Identifiers have to start with a letter + // so we'll just pick the first valid one and check for a negative sign + // if needed. + return (value[0] >= 'A' && + (!s_isSignedEnum || !value.StartsWith(NumberFormatInfo.CurrentInfo.NegativeSign))); + } - if (_nameCache.Count < NameCacheSizeSoftLimit) - { - JsonEncodedText formatted = JsonEncodedText.Encode(original, encoder); + private static string FormatJsonName(string value, JsonNamingPolicy? namingPolicy) + { + if (namingPolicy is null) + { + return value; + } - writer.WritePropertyName(formatted); + string converted; + if (!value.Contains(ValueSeparator)) + { + converted = namingPolicy.ConvertName(value); + } + else + { + string[] enumValues = SplitFlagsEnum(value); - _nameCache.TryAdd(key, formatted); - } - else + for (int i = 0; i < enumValues.Length; i++) + { + string name = namingPolicy.ConvertName(enumValues[i]); + if (name == null) { - // We also do not create a JsonEncodedText instance here because passing the string - // directly to the writer is cheaper than creating one and not caching it for reuse. - writer.WritePropertyName(original); + ThrowHelper.ThrowInvalidOperationException_NamingPolicyReturnNull(namingPolicy); } - - return; + enumValues[i] = name; } - } - switch (s_enumTypeCode) - { - case TypeCode.Int32: - writer.WritePropertyName(Unsafe.As(ref value)); - break; - case TypeCode.UInt32: - writer.WritePropertyName(Unsafe.As(ref value)); - break; - case TypeCode.UInt64: - writer.WritePropertyName(Unsafe.As(ref value)); - break; - case TypeCode.Int64: - writer.WritePropertyName(Unsafe.As(ref value)); - break; - case TypeCode.Int16: - writer.WritePropertyName(Unsafe.As(ref value)); - break; - case TypeCode.UInt16: - writer.WritePropertyName(Unsafe.As(ref value)); - break; - case TypeCode.Byte: - writer.WritePropertyName(Unsafe.As(ref value)); - break; - case TypeCode.SByte: - writer.WritePropertyName(Unsafe.As(ref value)); - break; - default: - ThrowHelper.ThrowJsonException(); - break; + converted = string.Join(ValueSeparator, enumValues); } + + return converted; + } + + private static string[] SplitFlagsEnum(string value) + { + // todo: optimize implementation here by leveraging https://github.com/dotnet/runtime/issues/934. + return value.Split( +#if NETCOREAPP + ValueSeparator +#else + new string[] { ValueSeparator }, StringSplitOptions.None +#endif + ); } } } diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/EnumConverterTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/EnumConverterTests.cs index e6677eab029a39..ddf0e5fa7cc4ea 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/EnumConverterTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/Serialization/EnumConverterTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Reflection; using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; using Xunit; @@ -554,5 +555,85 @@ public enum SampleEnumSByte : sbyte F = 1 << 5, G = 1 << 6, } + + [Fact] + public static void Honor_EnumNamingPolicy_On_Deserialization() + { + JsonSerializerOptions options = new() + { + Converters = + { + new JsonStringEnumConverter(namingPolicy: new SimpleSnakeCasePolicy() ) + } + }; + + BindingFlags bindingFlags = JsonSerializer.Deserialize(@"""non_public""", options); + Assert.Equal(BindingFlags.NonPublic, bindingFlags); + + // Flags supported without naming policy. + bindingFlags = JsonSerializer.Deserialize(@"""NonPublic, Public""", options); + Assert.Equal(BindingFlags.NonPublic | BindingFlags.Public, bindingFlags); + + // Flags supported with naming policy. + bindingFlags = JsonSerializer.Deserialize(@"""static, public""", options); + Assert.Equal(BindingFlags.Static | BindingFlags.Public, bindingFlags); + + // Null not supported. + Assert.Throws(() => JsonSerializer.Deserialize("null", options)); + + // Null supported for nullable enum. + Assert.Null(JsonSerializer.Deserialize("null", options)); + } + + [Fact] + public static void EnumDictionaryKeyDeserialization() + { + JsonNamingPolicy snakeCasePolicy = new SimpleSnakeCasePolicy(); + JsonSerializerOptions options = new() + { + Converters = + { + new JsonStringEnumConverter(namingPolicy: snakeCasePolicy) + }, + DictionaryKeyPolicy = snakeCasePolicy + }; + + // Baseline. + var dict = JsonSerializer.Deserialize>(@"{""NonPublic, Public"": 1}", options); + Assert.Equal(1, dict[BindingFlags.NonPublic | BindingFlags.Public]); + + // DictionaryKeyPolicy not honored for dict key deserialization. + Assert.Throws(() => JsonSerializer.Deserialize>(@"{""NonPublic0, Public0"": 1}", options)); + + // EnumConverter naming policy not honored. + Assert.Throws(() => JsonSerializer.Deserialize>(@"{""non_public, static"": 0, ""NonPublic, Public"": 1}", options)); + } + + [Fact] + public static void EnumDictionaryKeySerialization() + { + JsonSerializerOptions options = new() + { + DictionaryKeyPolicy = new SimpleSnakeCasePolicy() + }; + + Dictionary dict = new() + { + [BindingFlags.NonPublic | BindingFlags.Public] = 1, + [BindingFlags.Static] = 2, + }; + + string expected = @"{ + ""public, non_public"": 1, + ""static"": 2 +}"; + + JsonTestHelper.AssertJsonEqual(expected, JsonSerializer.Serialize(dict, options)); + } + + private class ZeroAppenderPolicy : JsonNamingPolicy + { + public override string ConvertName(string name) => name + "0"; + } } } From 3e0a5adaefc0936bdb8ec4b152914b5c996c97c6 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 11 Aug 2022 10:24:35 -0700 Subject: [PATCH 34/68] Ensure BitIncrement/BitDecrement for Half works correctly (#73631) --- .../System.Private.CoreLib/src/System/Half.cs | 50 ++++++++++- .../System.Runtime/tests/System/HalfTests.cs | 89 +++++++++++++++++++ 2 files changed, 137 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Half.cs b/src/libraries/System.Private.CoreLib/src/System/Half.cs index e45a2e8fb49cd1..ba931c1f93cec9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Half.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Half.cs @@ -1261,10 +1261,56 @@ bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destination public static Half Atan2Pi(Half y, Half x) => (Half)float.Atan2Pi((float)y, (float)x); /// - public static Half BitDecrement(Half x) => (Half)MathF.BitDecrement((float)x); + public static Half BitDecrement(Half x) + { + ushort bits = x._value; + + if ((bits & PositiveInfinityBits) >= PositiveInfinityBits) + { + // NaN returns NaN + // -Infinity returns -Infinity + // +Infinity returns MaxValue + return (bits == PositiveInfinityBits) ? MaxValue : x; + } + + if (bits == PositiveZeroBits) + { + // +0.0 returns -Epsilon + return -Epsilon; + } + + // Negative values need to be incremented + // Positive values need to be decremented + + bits += (ushort)(((short)bits < 0) ? +1 : -1); + return new Half(bits); + } /// - public static Half BitIncrement(Half x) => (Half)MathF.BitIncrement((float)x); + public static Half BitIncrement(Half x) + { + ushort bits = x._value; + + if ((bits & PositiveInfinityBits) >= PositiveInfinityBits) + { + // NaN returns NaN + // -Infinity returns MinValue + // +Infinity returns +Infinity + return (bits == NegativeInfinityBits) ? MinValue : x; + } + + if (bits == NegativeZeroBits) + { + // -0.0 returns Epsilon + return Epsilon; + } + + // Negative values need to be decremented + // Positive values need to be incremented + + bits += (ushort)(((short)bits < 0) ? -1 : +1); + return new Half(bits); + } /// public static Half FusedMultiplyAdd(Half left, Half right, Half addend) => (Half)MathF.FusedMultiplyAdd((float)left, (float)right, (float)addend); diff --git a/src/libraries/System.Runtime/tests/System/HalfTests.cs b/src/libraries/System.Runtime/tests/System/HalfTests.cs index e3a1136c9bcd40..2dbe1ffb797883 100644 --- a/src/libraries/System.Runtime/tests/System/HalfTests.cs +++ b/src/libraries/System.Runtime/tests/System/HalfTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; +using System.Dynamic; using System.Globalization; using Xunit; @@ -1986,5 +1987,93 @@ public static void TanPiTest(Half value, Half expectedResult, Half allowedVarian AssertExtensions.Equal(-expectedResult, Half.TanPi(-value), allowedVariance); AssertExtensions.Equal(+expectedResult, Half.TanPi(+value), allowedVariance); } + + public static IEnumerable BitDecrement_TestData() + { + yield return new object[] { Half.NegativeInfinity, Half.NegativeInfinity }; + yield return new object[] { BitConverter.UInt16BitsToHalf(0xC248), BitConverter.UInt16BitsToHalf(0xC249) }; // value: -(pi) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xC170), BitConverter.UInt16BitsToHalf(0xC171) }; // value: -(e) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xC09B), BitConverter.UInt16BitsToHalf(0xC09C) }; // value: -(ln(10)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xBE48), BitConverter.UInt16BitsToHalf(0xBE49) }; // value: -(pi / 2) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xBDC5), BitConverter.UInt16BitsToHalf(0xBDC6) }; // value: -(log2(e)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xBDA8), BitConverter.UInt16BitsToHalf(0xBDA9) }; // value: -(sqrt(2)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xBC83), BitConverter.UInt16BitsToHalf(0xBC84) }; // value: -(2 / sqrt(pi)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xBC00), BitConverter.UInt16BitsToHalf(0xBC01) }; + yield return new object[] { BitConverter.UInt16BitsToHalf(0xBA48), BitConverter.UInt16BitsToHalf(0xBA49) }; // value: -(pi / 4) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xB9A8), BitConverter.UInt16BitsToHalf(0xB9A9) }; // value: -(1 / sqrt(2)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xB98C), BitConverter.UInt16BitsToHalf(0xB98D) }; // value: -(ln(2)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xB918), BitConverter.UInt16BitsToHalf(0xB919) }; // value: -(2 / pi) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xB6F3), BitConverter.UInt16BitsToHalf(0xB6F4) }; // value: -(log10(e)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xB518), BitConverter.UInt16BitsToHalf(0xB519) }; // value: -(1 / pi) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x8000), -Half.Epsilon }; + yield return new object[] { Half.NaN, Half.NaN }; + yield return new object[] { BitConverter.UInt16BitsToHalf(0x0000), -Half.Epsilon }; + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3518), BitConverter.UInt16BitsToHalf(0x3517) }; // value: (1 / pi) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x36F3), BitConverter.UInt16BitsToHalf(0x36F2) }; // value: (log10(e)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3918), BitConverter.UInt16BitsToHalf(0x3917) }; // value: (2 / pi) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x398C), BitConverter.UInt16BitsToHalf(0x398B) }; // value: (ln(2)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x39A8), BitConverter.UInt16BitsToHalf(0x39A7) }; // value: (1 / sqrt(2)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3A48), BitConverter.UInt16BitsToHalf(0x3A47) }; // value: (pi / 4) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3C00), BitConverter.UInt16BitsToHalf(0x3BFF) }; + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3C83), BitConverter.UInt16BitsToHalf(0x3C82) }; // value: (2 / sqrt(pi)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3DA8), BitConverter.UInt16BitsToHalf(0x3DA7) }; // value: (sqrt(2)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3DC5), BitConverter.UInt16BitsToHalf(0x3DC4) }; // value: (log2(e)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3E48), BitConverter.UInt16BitsToHalf(0x3E47) }; // value: (pi / 2) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x409B), BitConverter.UInt16BitsToHalf(0x409A) }; // value: (ln(10)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x4170), BitConverter.UInt16BitsToHalf(0x416F) }; // value: (e) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x4248), BitConverter.UInt16BitsToHalf(0x4247) }; // value: (pi) + yield return new object[] { Half.PositiveInfinity, Half.MaxValue }; + } + + [Theory] + [MemberData(nameof(BitDecrement_TestData))] + public static void BitDecrement(Half value, Half expectedResult) + { + AssertExtensions.Equal(expectedResult, Half.BitDecrement(value), Half.Zero); + } + + public static IEnumerable BitIncrement_TestData() + { + yield return new object[] { Half.NegativeInfinity, Half.MinValue }; + yield return new object[] { BitConverter.UInt16BitsToHalf(0xC248), BitConverter.UInt16BitsToHalf(0xC247) }; // value: -(pi) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xC170), BitConverter.UInt16BitsToHalf(0xC16F) }; // value: -(e) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xC09B), BitConverter.UInt16BitsToHalf(0xC09A) }; // value: -(ln(10)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xBE48), BitConverter.UInt16BitsToHalf(0xBE47) }; // value: -(pi / 2) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xBDC5), BitConverter.UInt16BitsToHalf(0xBDC4) }; // value: -(log2(e)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xBDA8), BitConverter.UInt16BitsToHalf(0xBDA7) }; // value: -(sqrt(2)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xBC83), BitConverter.UInt16BitsToHalf(0xBC82) }; // value: -(2 / sqrt(pi)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xBC00), BitConverter.UInt16BitsToHalf(0xBBFF) }; + yield return new object[] { BitConverter.UInt16BitsToHalf(0xBA48), BitConverter.UInt16BitsToHalf(0xBA47) }; // value: -(pi / 4) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xB9A8), BitConverter.UInt16BitsToHalf(0xB9A7) }; // value: -(1 / sqrt(2)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xB98C), BitConverter.UInt16BitsToHalf(0xB98B) }; // value: -(ln(2)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xB918), BitConverter.UInt16BitsToHalf(0xB917) }; // value: -(2 / pi) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xB6F3), BitConverter.UInt16BitsToHalf(0xB6F2) }; // value: -(log10(e)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0xB518), BitConverter.UInt16BitsToHalf(0xB517) }; // value: -(1 / pi) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x8000), Half.Epsilon }; + yield return new object[] { Half.NaN, Half.NaN }; + yield return new object[] { BitConverter.UInt16BitsToHalf(0x0000), Half.Epsilon }; + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3518), BitConverter.UInt16BitsToHalf(0x3519) }; // value: (1 / pi) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x36F3), BitConverter.UInt16BitsToHalf(0x36F4) }; // value: (log10(e)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3918), BitConverter.UInt16BitsToHalf(0x3919) }; // value: (2 / pi) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x398C), BitConverter.UInt16BitsToHalf(0x398D) }; // value: (ln(2)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x39A8), BitConverter.UInt16BitsToHalf(0x39A9) }; // value: (1 / sqrt(2)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3A48), BitConverter.UInt16BitsToHalf(0x3A49) }; // value: (pi / 4) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3C00), BitConverter.UInt16BitsToHalf(0x3C01) }; + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3C83), BitConverter.UInt16BitsToHalf(0x3C84) }; // value: (2 / sqrt(pi)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3DA8), BitConverter.UInt16BitsToHalf(0x3DA9) }; // value: (sqrt(2)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3DC5), BitConverter.UInt16BitsToHalf(0x3DC6) }; // value: (log2(e)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x3E48), BitConverter.UInt16BitsToHalf(0x3E49) }; // value: (pi / 2) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x409B), BitConverter.UInt16BitsToHalf(0x409C) }; // value: (ln(10)) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x4170), BitConverter.UInt16BitsToHalf(0x4171) }; // value: (e) + yield return new object[] { BitConverter.UInt16BitsToHalf(0x4248), BitConverter.UInt16BitsToHalf(0x4249) }; // value: (pi) + yield return new object[] { Half.PositiveInfinity, Half.PositiveInfinity }; + } + + [Theory] + [MemberData(nameof(BitIncrement_TestData))] + public static void BitIncrement(Half value, Half expectedResult) + { + AssertExtensions.Equal(expectedResult, Half.BitIncrement(value), Half.Zero); + } } } From b942f21d3d316448734569478e3688c4649cdf01 Mon Sep 17 00:00:00 2001 From: Radek Zikmund <32671551+rzikm@users.noreply.github.com> Date: Thu, 11 Aug 2022 21:35:07 +0200 Subject: [PATCH 35/68] Temporarily remove Windows.11.Arm64.Open queue (#73780) https://github.com/dotnet/runtime/pull/73713 added Windows.11.Arm64.Open runs to check MsQuic functionality there, but currently, there is no HW servicing the queue and the ci job timeouts. This PR temporarily suspends runs on the queue until appropriate HW is deployed to helix. --- eng/pipelines/libraries/helix-queues-setup.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/eng/pipelines/libraries/helix-queues-setup.yml b/eng/pipelines/libraries/helix-queues-setup.yml index 014726d5ab5424..cac632e79177b1 100644 --- a/eng/pipelines/libraries/helix-queues-setup.yml +++ b/eng/pipelines/libraries/helix-queues-setup.yml @@ -184,7 +184,8 @@ jobs: # windows arm64 - ${{ if eq(parameters.platform, 'windows_arm64') }}: - Windows.10.Arm64.Open - - Windows.11.Arm64.Open + # TODO: Uncomment once there is HW deployed to service Win11 ARM64 queue + # - Windows.11.Arm64.Open # WebAssembly - ${{ if eq(parameters.platform, 'Browser_wasm') }}: From 24c0d7000ca3fd13eb5bc11e2cc2bec8fc4a899e Mon Sep 17 00:00:00 2001 From: SingleAccretion <62474226+SingleAccretion@users.noreply.github.com> Date: Thu, 11 Aug 2022 22:35:20 +0300 Subject: [PATCH 36/68] Properly encode small structs in bitcasts (#73717) * Encode small structs as bitcast targets * Add a test --- src/coreclr/jit/valuenum.cpp | 83 ++++++++++++++++--- src/coreclr/jit/valuenum.h | 9 +- .../JitBlue/Runtime_73628/Runtime_73628.cs | 50 +++++++++++ .../Runtime_73628/Runtime_73628.csproj | 22 +++++ 4 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 src/tests/JIT/Regression/JitBlue/Runtime_73628/Runtime_73628.cs create mode 100644 src/tests/JIT/Regression/JitBlue/Runtime_73628/Runtime_73628.csproj diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 0ffb254761bc64..b94624743595ca 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -4656,7 +4656,7 @@ ValueNum ValueNumStore::VNForLoadStoreBitCast(ValueNum value, var_types indType, { assert((typeOfValue == TYP_STRUCT) || (indType == TYP_STRUCT) || (genTypeSize(indType) == indSize)); - value = VNForBitCast(value, indType); + value = VNForBitCast(value, indType, indSize); JITDUMP(" VNForLoadStoreBitcast returns "); JITDUMPEXEC(m_pComp->vnPrint(value, 1)); @@ -6838,9 +6838,15 @@ void ValueNumStore::vnDumpCast(Compiler* comp, ValueNum castVN) void ValueNumStore::vnDumpBitCast(Compiler* comp, VNFuncApp* bitCast) { var_types srcType = TypeOfVN(bitCast->m_args[0]); - var_types castToType = static_cast(ConstantValue(bitCast->m_args[1])); + unsigned size = 0; + var_types castToType = DecodeBitCastType(bitCast->m_args[1], &size); - printf("BitCast<%s <- %s>(", varTypeName(castToType), varTypeName(srcType)); + printf("BitCast<%s", varTypeName(castToType)); + if (castToType == TYP_STRUCT) + { + printf("<%u>", size); + } + printf(" <- %s>(", varTypeName(srcType)); comp->vnPrint(bitCast->m_args[0], 0); printf(")"); } @@ -9558,7 +9564,7 @@ ValueNumPair ValueNumStore::VNPairForCast(ValueNumPair srcVNPair, // void Compiler::fgValueNumberBitCast(GenTree* tree) { - assert(tree->OperGet() == GT_BITCAST); + assert(tree->OperIs(GT_BITCAST)); ValueNumPair srcVNPair = tree->gtGetOp1()->gtVNPair; var_types castToType = tree->TypeGet(); @@ -9567,18 +9573,75 @@ void Compiler::fgValueNumberBitCast(GenTree* tree) ValueNumPair srcExcVNPair; vnStore->VNPUnpackExc(srcVNPair, &srcNormVNPair, &srcExcVNPair); - ValueNumPair resultNormVNPair = vnStore->VNPairForBitCast(srcNormVNPair, castToType); + ValueNumPair resultNormVNPair = vnStore->VNPairForBitCast(srcNormVNPair, castToType, genTypeSize(castToType)); ValueNumPair resultExcVNPair = srcExcVNPair; tree->gtVNPair = vnStore->VNPWithExc(resultNormVNPair, resultExcVNPair); } +//------------------------------------------------------------------------ +// EncodeBitCastType: Encode the target type of a bitcast. +// +// In most cases, it is sufficient to simply encode the numerical value of +// "castToType", as "size" will be implicitly encoded in the source VN's +// type. There is one instance where this is not true: small structs, as +// numbering, much like IR, does not support "true" small types. Thus, we +// encode structs (all of them, for simplicity) specially. +// +// Arguments: +// castToType - The target type +// size - Its size +// +// Return Value: +// Value number representing the target type. +// +ValueNum ValueNumStore::EncodeBitCastType(var_types castToType, unsigned size) +{ + if (castToType != TYP_STRUCT) + { + assert(size == genTypeSize(castToType)); + return VNForIntCon(castToType); + } + + assert(size != 0); + return VNForIntCon(TYP_COUNT + size); +} + +//------------------------------------------------------------------------ +// DecodeBitCastType: Decode the target type of a bitcast. +// +// Decodes VNs produced by "EncodeBitCastType". +// +// Arguments: +// castToTypeVN - VN representing the target type +// pSize - [out] parameter for the size of the target type +// +// Return Value: +// The target type. +// +var_types ValueNumStore::DecodeBitCastType(ValueNum castToTypeVN, unsigned* pSize) +{ + unsigned encodedType = ConstantValue(castToTypeVN); + + if (encodedType < TYP_COUNT) + { + var_types castToType = static_cast(encodedType); + + *pSize = genTypeSize(castToType); + return castToType; + } + + *pSize = encodedType - TYP_COUNT; + return TYP_STRUCT; +} + //------------------------------------------------------------------------ // VNForBitCast: Get the VN representing bitwise reinterpretation of types. // // Arguments: // srcVN - (VN of) the value being cast from // castToType - The type being cast to +// size - Size of the target type // // Return Value: // The value number representing "IND(ADDR(srcVN))". Notably, @@ -9591,7 +9654,7 @@ void Compiler::fgValueNumberBitCast(GenTree* tree) // process, and "VNF_BitCast" is that function. See also the notes for // "VNForLoadStoreBitCast". // -ValueNum ValueNumStore::VNForBitCast(ValueNum srcVN, var_types castToType) +ValueNum ValueNumStore::VNForBitCast(ValueNum srcVN, var_types castToType, unsigned size) { // BitCast(BitCast(x)) => BitCast(x). // This ensures we do not end up with pathologically long chains of @@ -9620,18 +9683,18 @@ ValueNum ValueNumStore::VNForBitCast(ValueNum srcVN, var_types castToType) return VNZeroForType(castToType); } - return VNForFunc(castToType, VNF_BitCast, srcVN, VNForIntCon(castToType)); + return VNForFunc(castToType, VNF_BitCast, srcVN, EncodeBitCastType(castToType, size)); } //------------------------------------------------------------------------ // VNPairForBitCast: VNForBitCast applied to a ValueNumPair. // -ValueNumPair ValueNumStore::VNPairForBitCast(ValueNumPair srcVNPair, var_types castToType) +ValueNumPair ValueNumStore::VNPairForBitCast(ValueNumPair srcVNPair, var_types castToType, unsigned size) { ValueNum srcLibVN = srcVNPair.GetLiberal(); ValueNum srcConVN = srcVNPair.GetConservative(); - ValueNum bitCastLibVN = VNForBitCast(srcLibVN, castToType); + ValueNum bitCastLibVN = VNForBitCast(srcLibVN, castToType, size); ValueNum bitCastConVN; if (srcVNPair.BothEqual()) @@ -9640,7 +9703,7 @@ ValueNumPair ValueNumStore::VNPairForBitCast(ValueNumPair srcVNPair, var_types c } else { - bitCastConVN = VNForBitCast(srcConVN, castToType); + bitCastConVN = VNForBitCast(srcConVN, castToType, size); } return ValueNumPair(bitCastLibVN, bitCastConVN); diff --git a/src/coreclr/jit/valuenum.h b/src/coreclr/jit/valuenum.h index d28f2bc5a640b1..bfe1f8222106ba 100644 --- a/src/coreclr/jit/valuenum.h +++ b/src/coreclr/jit/valuenum.h @@ -784,8 +784,13 @@ class ValueNumStore bool srcIsUnsigned = false, bool hasOverflowCheck = false); - ValueNum VNForBitCast(ValueNum srcVN, var_types castToType); - ValueNumPair VNPairForBitCast(ValueNumPair srcVNPair, var_types castToType); + ValueNum EncodeBitCastType(var_types castToType, unsigned size); + + var_types DecodeBitCastType(ValueNum castToTypeVN, unsigned* pSize); + + ValueNum VNForBitCast(ValueNum srcVN, var_types castToType, unsigned size); + + ValueNumPair VNPairForBitCast(ValueNumPair srcVNPair, var_types castToType, unsigned size); ValueNum VNForFieldSeq(FieldSeq* fieldSeq); diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_73628/Runtime_73628.cs b/src/tests/JIT/Regression/JitBlue/Runtime_73628/Runtime_73628.cs new file mode 100644 index 00000000000000..fa96720159eca4 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_73628/Runtime_73628.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.CompilerServices; + +public unsafe class Runtime_73628 +{ + public static int Main() + { + return Problem() ? 101 : 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool Problem() + { + byte* p = stackalloc byte[4]; + Unsafe.InitBlock(p, 0xBB, 4); + + return Problem(default, *(StructWithByte*)&p); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool Problem(StructWithInt a, StructWithByte b) + { + *(int*)&a = 0; + *(byte*)&b = 0; + + bool result = IsNotZero(a.Int); + JitUse(b); + + return result; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool IsNotZero(int a) => a != 0; + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void JitUse(T arg) { } + + struct StructWithInt + { + public int Int; + } + + struct StructWithByte + { + public byte Byte; + } +} diff --git a/src/tests/JIT/Regression/JitBlue/Runtime_73628/Runtime_73628.csproj b/src/tests/JIT/Regression/JitBlue/Runtime_73628/Runtime_73628.csproj new file mode 100644 index 00000000000000..b7bff1143f6f77 --- /dev/null +++ b/src/tests/JIT/Regression/JitBlue/Runtime_73628/Runtime_73628.csproj @@ -0,0 +1,22 @@ + + + Exe + True + true + + + + + + + + + \ No newline at end of file From 478a8a28af4b2b298745accffdc742f0ad6ce367 Mon Sep 17 00:00:00 2001 From: Pavel Savara Date: Thu, 11 Aug 2022 21:50:20 +0200 Subject: [PATCH 37/68] [wasm] fix error cases in assets loading (#73702) * fix bad rename of maxParallelDownloads - throttling * fix re-try logic * fixed counting of loaded assets * catching more error cases * fix blazor detection --- src/mono/wasm/runtime/assets.ts | 333 ++++++++++++++++++++---------- src/mono/wasm/runtime/dotnet.d.ts | 32 ++- src/mono/wasm/runtime/imports.ts | 9 +- src/mono/wasm/runtime/startup.ts | 9 +- src/mono/wasm/runtime/types.ts | 48 +++-- 5 files changed, 293 insertions(+), 138 deletions(-) diff --git a/src/mono/wasm/runtime/assets.ts b/src/mono/wasm/runtime/assets.ts index 362d6ba0f38a55..4830f0234093e3 100644 --- a/src/mono/wasm/runtime/assets.ts +++ b/src/mono/wasm/runtime/assets.ts @@ -1,30 +1,50 @@ import cwraps from "./cwraps"; import { mono_wasm_load_icu_data } from "./icu"; -import { ENVIRONMENT_IS_WEB, Module, runtimeHelpers } from "./imports"; +import { ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, ENVIRONMENT_IS_WEB, Module, runtimeHelpers } from "./imports"; import { mono_wasm_load_bytes_into_heap } from "./memory"; import { MONO } from "./net6-legacy/imports"; import { createPromiseController, PromiseAndController } from "./promise-controller"; import { delay } from "./promise-utils"; -import { beforeOnRuntimeInitialized } from "./startup"; -import { AssetBehaviours, AssetEntry, LoadingResource, mono_assert, ResourceRequest } from "./types"; +import { abort_startup, beforeOnRuntimeInitialized } from "./startup"; +import { AssetBehaviours, AssetEntry, AssetEntryInternal, LoadingResource, mono_assert, ResourceRequest } from "./types"; import { InstantiateWasmSuccessCallback, VoidPtr } from "./types/emscripten"; const allAssetsInMemory = createPromiseController(); const allDownloadsQueued = createPromiseController(); -let downloded_assets_count = 0; -let instantiated_assets_count = 0; +let actual_downloaded_assets_count = 0; +let actual_instantiated_assets_count = 0; +let expected_downloaded_assets_count = 0; +let expected_instantiated_assets_count = 0; const loaded_files: { url: string, file: string }[] = []; const loaded_assets: { [id: string]: [VoidPtr, number] } = Object.create(null); // in order to prevent net::ERR_INSUFFICIENT_RESOURCES if we start downloading too many files at same time let parallel_count = 0; -let throttling: PromiseAndController | undefined; -const skipDownloadsAssetTypes: { +let throttlingPromise: PromiseAndController | undefined; + +// don't `fetch` javaScript files +const skipDownloadsByAssetTypes: { [k: string]: boolean } = { "js-module-crypto": true, "js-module-threads": true, }; +// `response.arrayBuffer()` can't be called twice. Some usecases are calling it on response in the instantiation. +const skipBufferByAssetTypes: { + [k: string]: boolean +} = { + "dotnetwasm": true, +}; + +// these assets are instantiated differently than the main flow +const skipInstantiateByAssetTypes: { + [k: string]: boolean +} = { + "js-module-crypto": true, + "js-module-threads": true, + "dotnetwasm": true, +}; + export function resolve_asset_path(behavior: AssetBehaviours) { const asset: AssetEntry | undefined = runtimeHelpers.config.assets?.find(a => a.behavior == behavior); mono_assert(asset, () => `Can't find asset for ${behavior}`); @@ -33,39 +53,87 @@ export function resolve_asset_path(behavior: AssetBehaviours) { } return asset; } - +type AssetWithBuffer = { + asset: AssetEntryInternal, + buffer?: ArrayBuffer +} export async function mono_download_assets(): Promise { if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: mono_download_assets"); runtimeHelpers.maxParallelDownloads = runtimeHelpers.config.maxParallelDownloads || runtimeHelpers.maxParallelDownloads; try { - const download_promises: Promise[] = []; + const promises_of_assets_with_buffer: Promise[] = []; // start fetching and instantiating all assets in parallel - for (const asset of runtimeHelpers.config.assets!) { - if (!asset.pending && !skipDownloadsAssetTypes[asset.behavior]) { - download_promises.push(start_asset_download(asset)); + for (const a of runtimeHelpers.config.assets!) { + const asset: AssetEntryInternal = a; + if (!skipInstantiateByAssetTypes[asset.behavior]) { + expected_instantiated_assets_count++; + } + if (!skipDownloadsByAssetTypes[asset.behavior]) { + const headersOnly = skipBufferByAssetTypes[asset.behavior];// `response.arrayBuffer()` can't be called twice. Some usecases are calling it on response in the instantiation. + expected_downloaded_assets_count++; + if (asset.pendingDownload) { + asset.pendingDownloadInternal = asset.pendingDownload; + const waitForExternalData: () => Promise = async () => { + const response = await asset.pendingDownloadInternal!.response; + ++actual_downloaded_assets_count; + if (!headersOnly) { + asset.buffer = await response.arrayBuffer(); + } + return { asset, buffer: asset.buffer }; + }; + promises_of_assets_with_buffer.push(waitForExternalData()); + } else { + const waitForExternalData: () => Promise = async () => { + asset.buffer = await start_asset_download_with_retries(asset, !headersOnly); + return { asset, buffer: asset.buffer }; + }; + promises_of_assets_with_buffer.push(waitForExternalData()); + } } } allDownloadsQueued.promise_control.resolve(); - const asset_promises: Promise[] = []; - for (const downloadPromise of download_promises) { - const downloadedAsset = await downloadPromise; - if (downloadedAsset) { - asset_promises.push((async () => { - const url = downloadedAsset.pending!.url; - const response = await downloadedAsset.pending!.response; - downloadedAsset.pending = undefined; //GC - const buffer = await response.arrayBuffer(); - await beforeOnRuntimeInitialized.promise; - // this is after onRuntimeInitialized - _instantiate_asset(downloadedAsset, url, new Uint8Array(buffer)); - })()); - } + const promises_of_asset_instantiation: Promise[] = []; + for (const downloadPromise of promises_of_assets_with_buffer) { + promises_of_asset_instantiation.push((async () => { + const assetWithBuffer = await downloadPromise; + const asset = assetWithBuffer.asset; + if (assetWithBuffer.buffer) { + if (!skipInstantiateByAssetTypes[asset.behavior]) { + const url = asset.pendingDownloadInternal!.url; + const data = new Uint8Array(asset.buffer!); + asset.pendingDownloadInternal = null as any; // GC + asset.pendingDownload = null as any; // GC + asset.buffer = null as any; // GC + assetWithBuffer.buffer = null as any; // GC + + await beforeOnRuntimeInitialized.promise; + // this is after onRuntimeInitialized + _instantiate_asset(asset, url, data); + } + } else { + const headersOnly = skipBufferByAssetTypes[asset.behavior]; + if (!headersOnly) { + mono_assert(asset.isOptional, "Expected asset to have the downloaded buffer"); + if (!skipDownloadsByAssetTypes[asset.behavior]) { + expected_downloaded_assets_count--; + } + if (!skipInstantiateByAssetTypes[asset.behavior]) { + expected_instantiated_assets_count--; + } + } + } + })()); } // this await will get past the onRuntimeInitialized because we are not blocking via addRunDependency // and we are not awating it here - Promise.all(asset_promises).then(() => allAssetsInMemory.promise_control.resolve()); + Promise.all(promises_of_asset_instantiation).then(() => { + allAssetsInMemory.promise_control.resolve(); + }).catch(err => { + Module.printErr("MONO_WASM: Error in mono_download_assets: " + err); + abort_startup(err, true); + }); // OPTIMIZATION explained: // we do it this way so that we could allocate memory immediately after asset is downloaded (and after onRuntimeInitialized which happened already) // spreading in time @@ -76,72 +144,78 @@ export async function mono_download_assets(): Promise { } } -export async function start_asset_download(asset: AssetEntry): Promise { +// FIXME: Connection reset is probably the only good one for which we should retry +export async function start_asset_download_with_retries(asset: AssetEntryInternal, downloadData: boolean): Promise { try { - return await start_asset_download_throttle(asset); + return await start_asset_download_with_throttle(asset, downloadData); } catch (err: any) { + if (ENVIRONMENT_IS_SHELL || ENVIRONMENT_IS_NODE) { + // we will not re-try on shell + throw err; + } + if (asset.pendingDownload && asset.pendingDownloadInternal == asset.pendingDownload) { + // we will not re-try with external source + throw err; + } + if (asset.resolvedUrl && asset.resolvedUrl.indexOf("file://") != -1) { + // we will not re-try with local file + throw err; + } if (err && err.status == 404) { + // we will not re-try with 404 throw err; } + asset.pendingDownloadInternal = undefined; // second attempt only after all first attempts are queued await allDownloadsQueued.promise; try { - return await start_asset_download_throttle(asset); + return await start_asset_download_with_throttle(asset, downloadData); } catch (err) { + asset.pendingDownloadInternal = undefined; // third attempt after small delay await delay(100); - return await start_asset_download_throttle(asset); + return await start_asset_download_with_throttle(asset, downloadData); } } } -function resolve_path(asset: AssetEntry, sourcePrefix: string): string { - let attemptUrl; - const assemblyRootFolder = runtimeHelpers.config.assemblyRootFolder; - if (!asset.resolvedUrl) { - if (sourcePrefix === "") { - if (asset.behavior === "assembly" || asset.behavior === "pdb") - attemptUrl = assemblyRootFolder + "/" + asset.name; - else if (asset.behavior === "resource") { - const path = asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name; - attemptUrl = assemblyRootFolder + "/" + path; - } - else { - attemptUrl = asset.name; - } - } else { - attemptUrl = sourcePrefix + asset.name; - } - attemptUrl = runtimeHelpers.locateFile(attemptUrl); - } - else { - attemptUrl = asset.resolvedUrl; +async function start_asset_download_with_throttle(asset: AssetEntry, downloadData: boolean): Promise { + // we don't addRunDependency to allow download in parallel with onRuntimeInitialized event! + while (throttlingPromise) { + await throttlingPromise.promise; } - return attemptUrl; -} + try { + ++parallel_count; + if (parallel_count == runtimeHelpers.maxParallelDownloads) { + if (runtimeHelpers.diagnosticTracing) + console.debug("MONO_WASM: Throttling further parallel downloads"); + throttlingPromise = createPromiseController(); + } -function download_resource(request: ResourceRequest): LoadingResource { - if (typeof Module.downloadResource === "function") { - const loading = Module.downloadResource(request); - if (loading) return loading; + const response = await start_asset_download_sources(asset); + if (!downloadData || !response) { + return undefined; + } + return await response.arrayBuffer(); } - const options: any = {}; - if (request.hash) { - options.integrity = request.hash; + finally { + --parallel_count; + if (throttlingPromise && parallel_count == runtimeHelpers.maxParallelDownloads - 1) { + if (runtimeHelpers.diagnosticTracing) + console.debug("MONO_WASM: Resuming more parallel downloads"); + const old_throttling = throttlingPromise; + throttlingPromise = undefined; + old_throttling.promise_control.resolve(); + } } - const response = runtimeHelpers.fetch_like(request.resolvedUrl!, options); - return { - name: request.name, url: request.resolvedUrl!, response - }; } -async function start_asset_download_sources(asset: AssetEntry): Promise { +async function start_asset_download_sources(asset: AssetEntryInternal): Promise { // we don't addRunDependency to allow download in parallel with onRuntimeInitialized event! if (asset.buffer) { - ++downloded_assets_count; const buffer = asset.buffer; - asset.buffer = undefined;//GC later - asset.pending = { + asset.buffer = null as any; // GC + asset.pendingDownloadInternal = { url: "undefined://" + asset.name, name: asset.name, response: Promise.resolve({ @@ -151,11 +225,12 @@ async function start_asset_download_sources(asset: AssetEntry): Promise `Response undefined ${asset.name}`); + if (!isOkToFail) { + const err: any = new Error(`MONO_WASM: download '${response.url}' for ${asset.name} failed ${response.status} ${response.statusText}`); + err.status = response.status; + throw err; + } else { + Module.print(`MONO_WASM: optional download '${response.url}' for ${asset.name} failed ${response.status} ${response.statusText}`); + return undefined; + } } -async function start_asset_download_throttle(asset: AssetEntry): Promise { - // we don't addRunDependency to allow download in parallel with onRuntimeInitialized event! - while (throttling) { - await throttling.promise; - } - try { - ++parallel_count; - if (parallel_count == runtimeHelpers.maxParallelDownloads) { - if (runtimeHelpers.diagnosticTracing) - console.debug("MONO_WASM: Throttling further parallel downloads"); - throttling = createPromiseController(); +function resolve_path(asset: AssetEntry, sourcePrefix: string): string { + mono_assert(sourcePrefix !== null && sourcePrefix !== undefined, () => `sourcePrefix must be provided for ${asset.name}`); + let attemptUrl; + const assemblyRootFolder = runtimeHelpers.config.assemblyRootFolder; + if (!asset.resolvedUrl) { + if (sourcePrefix === "") { + if (asset.behavior === "assembly" || asset.behavior === "pdb") { + attemptUrl = assemblyRootFolder + ? (assemblyRootFolder + "/" + asset.name) + : asset.name; + } + else if (asset.behavior === "resource") { + const path = asset.culture !== "" ? `${asset.culture}/${asset.name}` : asset.name; + attemptUrl = assemblyRootFolder + ? (assemblyRootFolder + "/" + path) + : path; + } + else { + attemptUrl = asset.name; + } + } else { + attemptUrl = sourcePrefix + asset.name; } - return await start_asset_download_sources(asset); + attemptUrl = runtimeHelpers.locateFile(attemptUrl); } - catch (response: any) { - const isOkToFail = asset.isOptional || (asset.name.match(/\.pdb$/) && runtimeHelpers.config.ignorePdbLoadErrors); - if (!isOkToFail) { - const err: any = new Error(`MONO_WASM: download '${response.url}' for ${asset.name} failed ${response.status} ${response.statusText}`); - err.status = response.status; - throw err; - } + else { + attemptUrl = asset.resolvedUrl; } - finally { - --parallel_count; - if (throttling && parallel_count == runtimeHelpers.maxParallelDownloads - 1) { - if (runtimeHelpers.diagnosticTracing) - console.debug("MONO_WASM: Resuming more parallel downloads"); - const old_throttling = throttling; - throttling = undefined; - old_throttling.promise_control.resolve(); + mono_assert(attemptUrl && typeof attemptUrl == "string", "attemptUrl need to be path or url string"); + return attemptUrl; +} + +function download_resource(request: ResourceRequest): LoadingResource { + try { + if (typeof Module.downloadResource === "function") { + const loading = Module.downloadResource(request); + if (loading) return loading; + } + const options: any = {}; + if (request.hash) { + options.integrity = request.hash; } + const response = runtimeHelpers.fetch_like(request.resolvedUrl!, options); + return { + name: request.name, url: request.resolvedUrl!, response + }; + } catch (err) { + const response = { + ok: false, + url: request.resolvedUrl, + status: 500, + statusText: "ERR29: " + err, + arrayBuffer: () => { throw err; }, + json: () => { throw err; } + }; + return { + name: request.name, url: request.resolvedUrl!, response: Promise.resolve(response) + }; } } @@ -311,16 +422,16 @@ function _instantiate_asset(asset: AssetEntry, url: string, bytes: Uint8Array) { else if (asset.behavior === "resource") { cwraps.mono_wasm_add_satellite_assembly(virtualName, asset.culture!, offset!, bytes.length); } - ++instantiated_assets_count; + ++actual_instantiated_assets_count; } export async function instantiate_wasm_asset( - pendingAsset: AssetEntry, + pendingAsset: AssetEntryInternal, wasmModuleImports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback, ) { - mono_assert(pendingAsset && pendingAsset.pending, "Can't load dotnet.wasm"); - const response = await pendingAsset.pending.response; + mono_assert(pendingAsset && pendingAsset.pendingDownloadInternal, "Can't load dotnet.wasm"); + const response = await pendingAsset.pendingDownloadInternal.response; const contentType = response.headers ? response.headers.get("Content-Type") : undefined; let compiledInstance: WebAssembly.Instance; let compiledModule: WebAssembly.Module; @@ -339,7 +450,6 @@ export async function instantiate_wasm_asset( compiledInstance = arrayBufferResult.instance; compiledModule = arrayBufferResult.module; } - ++instantiated_assets_count; successCallback(compiledInstance, compiledModule); } @@ -401,9 +511,8 @@ export async function wait_for_all_assets() { // wait for all assets in memory await allAssetsInMemory.promise; if (runtimeHelpers.config.assets) { - const expected_asset_count = runtimeHelpers.config.assets.filter(a => !skipDownloadsAssetTypes[a.behavior]).length; - mono_assert(downloded_assets_count == expected_asset_count, "Expected assets to be downloaded"); - mono_assert(instantiated_assets_count == expected_asset_count, "Expected assets to be in memory"); + mono_assert(actual_downloaded_assets_count == expected_downloaded_assets_count, () => `Expected ${expected_downloaded_assets_count} assets to be downloaded, but only finished ${actual_downloaded_assets_count}`); + mono_assert(actual_instantiated_assets_count == expected_instantiated_assets_count, () => `Expected ${expected_instantiated_assets_count} assets to be in memory, but only instantiated ${actual_instantiated_assets_count}`); loaded_files.forEach(value => MONO.loaded_files.push(value.url)); if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: all assets are loaded in wasm memory"); } diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index eacddc7a017bbe..4d2208681b4719 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -88,13 +88,38 @@ interface ResourceRequest { resolvedUrl?: string; hash?: string; } +interface LoadingResource { + name: string; + url: string; + response: Promise; +} interface AssetEntry extends ResourceRequest { + /** + * If specified, overrides the path of the asset in the virtual filesystem and similar data structures once downloaded. + */ virtualPath?: string; + /** + * Culture code + */ culture?: string; + /** + * If true, an attempt will be made to load the asset from each location in MonoConfig.remoteSources. + */ loadRemote?: boolean; + /** + * If true, the runtime startup would not fail if the asset download was not successful. + */ isOptional?: boolean; + /** + * If provided, runtime doesn't have to fetch the data. + * Runtime would set the buffer to null after instantiation to free the memory. + */ buffer?: ArrayBuffer; - pending?: LoadingResource; + /** + * It's metadata + fetch-like Promise + * If provided, the runtime doesn't have to initiate the download. It would just await the response. + */ + pendingDownload?: LoadingResource; } declare type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-crypto" | "js-module-threads"; declare type GlobalizationMode = "icu" | // load ICU globalization data from any runtime assets with behavior "icu". @@ -118,11 +143,6 @@ declare type DotnetModuleConfig = { exports?: string[]; downloadResource?: (request: ResourceRequest) => LoadingResource | undefined; } & Partial; -interface LoadingResource { - name: string; - url: string; - response: Promise; -} declare type APIType = { runMain: (mainAssemblyName: string, args: string[]) => Promise; diff --git a/src/mono/wasm/runtime/imports.ts b/src/mono/wasm/runtime/imports.ts index 2e4a619238fd7b..48142cccd4faad 100644 --- a/src/mono/wasm/runtime/imports.ts +++ b/src/mono/wasm/runtime/imports.ts @@ -39,12 +39,13 @@ export function set_imports_exports( runtimeHelpers.requirePromise = imports.requirePromise; } -export const runtimeHelpers: RuntimeHelpers = { - javaScriptExports: {}, +const initialRuntimeHelpers: Partial = +{ + javaScriptExports: {} as any, mono_wasm_load_runtime_done: false, mono_wasm_bindings_is_ready: false, - max_parallel_downloads: 16, + maxParallelDownloads: 16, config: {}, diagnosticTracing: false, - fetch: null }; +export const runtimeHelpers: RuntimeHelpers = initialRuntimeHelpers as any; diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index ff9f53f74a41a7..9ee66858a7ea75 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -24,7 +24,7 @@ import { cwraps_internal } from "./exports-internal"; import { cwraps_binding_api, cwraps_mono_api } from "./net6-legacy/exports-legacy"; import { DotnetPublicAPI } from "./exports"; import { CharPtr, InstantiateWasmCallBack, InstantiateWasmSuccessCallback } from "./types/emscripten"; -import { instantiate_wasm_asset, mono_download_assets, resolve_asset_path, start_asset_download, wait_for_all_assets } from "./assets"; +import { instantiate_wasm_asset, mono_download_assets, resolve_asset_path, start_asset_download_with_retries, wait_for_all_assets } from "./assets"; import { BINDING, MONO } from "./net6-legacy/imports"; import { readSymbolMapFile } from "./logging"; import { mono_wasm_init_diagnostics } from "./diagnostics"; @@ -53,7 +53,7 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: // eslint-disable-next-line @typescript-eslint/no-empty-function const userOnRuntimeInitialized: () => void = module.onRuntimeInitialized ? module.onRuntimeInitialized : () => { }; // when assets don't contain DLLs it means this is Blazor or another custom startup - isCustomStartup = !module.configSrc && (!module.config || !module.config.assets || module.config.assets.findIndex(a => a.behavior === "assembly") != -1); // like blazor + isCustomStartup = !module.configSrc && (!module.config || !module.config.assets || module.config.assets.findIndex(a => a.behavior === "assembly") == -1); // like blazor // execution order == [0] == // - default or user Module.instantiateWasm (will start downloading dotnet.wasm) @@ -366,10 +366,11 @@ async function instantiate_wasm_module( await mono_wasm_load_config(Module.configSrc); if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module"); const assetToLoad = resolve_asset_path("dotnetwasm"); - const pendingAsset = await start_asset_download(assetToLoad); + // FIXME: this would not apply re-try (on connection reset during download) for dotnet.wasm because we could not download the buffer before we pass it to instantiate_wasm_asset + await start_asset_download_with_retries(assetToLoad, false); await beforePreInit.promise; Module.addRunDependency("instantiate_wasm_module"); - instantiate_wasm_asset(pendingAsset!, imports, successCallback); + instantiate_wasm_asset(assetToLoad, imports, successCallback); if (runtimeHelpers.diagnosticTracing) console.debug("MONO_WASM: instantiate_wasm_module done"); afterInstantiateWasm.promise_control.resolve(); diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index d9edc82f9587b6..b7082b21f41500 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -99,14 +99,45 @@ export interface ResourceRequest { hash?: string; } +export interface LoadingResource { + name: string; + url: string; + response: Promise; +} + // Types of assets that can be in the mono-config.js/mono-config.json file (taken from /src/tasks/WasmAppBuilder/WasmAppBuilder.cs) export interface AssetEntry extends ResourceRequest { - virtualPath?: string, // if specified, overrides the path of the asset in the virtual filesystem and similar data structures once loaded. + /** + * If specified, overrides the path of the asset in the virtual filesystem and similar data structures once downloaded. + */ + virtualPath?: string, + /** + * Culture code + */ culture?: string, - loadRemote?: boolean, // if true, an attempt will be made to load the asset from each location in @args.remoteSources. - isOptional?: boolean // if true, any failure to load this asset will be ignored. - buffer?: ArrayBuffer // if provided, we don't have to fetch it - pending?: LoadingResource // if provided, we don't have to start fetching it + /** + * If true, an attempt will be made to load the asset from each location in MonoConfig.remoteSources. + */ + loadRemote?: boolean, // + /** + * If true, the runtime startup would not fail if the asset download was not successful. + */ + isOptional?: boolean + /** + * If provided, runtime doesn't have to fetch the data. + * Runtime would set the buffer to null after instantiation to free the memory. + */ + buffer?: ArrayBuffer + /** + * It's metadata + fetch-like Promise + * If provided, the runtime doesn't have to initiate the download. It would just await the response. + */ + pendingDownload?: LoadingResource +} + +export interface AssetEntryInternal extends AssetEntry { + // this is almost the same as pendingDownload, but it could have multiple values in time, because of re-try download logic + pendingDownloadInternal?: LoadingResource } export type AssetBehaviours = @@ -198,13 +229,6 @@ export type DotnetModuleConfigImports = { url?: any; } -export interface LoadingResource { - name: string; - url: string; - response: Promise; -} - - // see src\mono\wasm\runtime\rollup.config.js // inline this, because the lambda could allocate closure on hot path otherwise export function mono_assert(condition: unknown, messageFactory: string | (() => string)): asserts condition { From d8226260c9630c2fb196acaa6af7f5dbb45685ee Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 11 Aug 2022 16:00:59 -0400 Subject: [PATCH 38/68] Update cmake endif statement (#73787) #73769 --- src/coreclr/clrdefinitions.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/clrdefinitions.cmake b/src/coreclr/clrdefinitions.cmake index bcb533d0e2051d..fad480f2aea957 100644 --- a/src/coreclr/clrdefinitions.cmake +++ b/src/coreclr/clrdefinitions.cmake @@ -64,7 +64,7 @@ endif(CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_I386 OR CLR_CMAKE_TAR if(CLR_CMAKE_TARGET_WIN32 AND CLR_CMAKE_TARGET_ARCH_AMD64) add_compile_definitions(OUT_OF_PROCESS_SETTHREADCONTEXT) -endif(CLR_CMAKE_TARGET_WIN32) +endif(CLR_CMAKE_TARGET_WIN32 AND CLR_CMAKE_TARGET_ARCH_AMD64) # Features - please keep them alphabetically sorted if(CLR_CMAKE_TARGET_WIN32) From 0d4f62428b6adacc32d0f3ab156c03b56cc03ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 12 Aug 2022 05:51:28 +0900 Subject: [PATCH 39/68] Fix handling of no metadata type (#73759) Turns out we do have one situation where a MethodTable would have no metadata - when it's the unconstructed MethodTable. User code doesn't see them. --- .../Core/Execution/ExecutionDomain.cs | 2 +- .../Runtime/Augments/RuntimeAugments.cs | 8 ++++++++ .../DeadCodeElimination.cs | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionDomain.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionDomain.cs index 85199ce870ed09..181b2ad0513fbd 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionDomain.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Core/Execution/ExecutionDomain.cs @@ -207,7 +207,7 @@ public Type GetNamedTypeForHandle(RuntimeTypeHandle typeHandle, bool isGenericTy } else { - Debug.Assert(ExecutionEnvironment.IsReflectionBlocked(typeHandle)); + Debug.Assert(ExecutionEnvironment.IsReflectionBlocked(typeHandle) || RuntimeAugments.MightBeUnconstructedType(typeHandle)); return RuntimeBlockedTypeInfo.GetRuntimeBlockedTypeInfo(typeHandle, isGenericTypeDefinition); } } diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs index 0e0ba33b060caf..970a2b1e1b5bbe 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/Augments/RuntimeAugments.cs @@ -152,6 +152,14 @@ public static unsafe Array NewMultiDimArray(RuntimeTypeHandle typeHandleForArray return Array.NewMultiDimArray(typeHandleForArrayType.ToEETypePtr(), pImmutableLengths, lengths.Length); } + public static unsafe bool MightBeUnconstructedType(RuntimeTypeHandle type) + { + // If there are no vtable slots the type is likely an unconstructed type. + // But could also be an interface with no virtuals. We can't distinguish those. + Debug.Assert(MethodTable.Of()->NumVtableSlots != 0); + return CreateEETypePtr(type).ToPointer()->NumVtableSlots == 0; + } + public static IntPtr GetAllocateObjectHelperForType(RuntimeTypeHandle type) { return RuntimeImports.RhGetRuntimeHelperForType(CreateEETypePtr(type), RuntimeHelperKind.AllocateObject); diff --git a/src/tests/nativeaot/SmokeTests/DeadCodeElimination/DeadCodeElimination.cs b/src/tests/nativeaot/SmokeTests/DeadCodeElimination/DeadCodeElimination.cs index ad1b6753909770..2510e425a96d4d 100644 --- a/src/tests/nativeaot/SmokeTests/DeadCodeElimination/DeadCodeElimination.cs +++ b/src/tests/nativeaot/SmokeTests/DeadCodeElimination/DeadCodeElimination.cs @@ -19,6 +19,7 @@ static int Main() TestUnusedDefaultInterfaceMethod.Run(); TestArrayElementTypeOperations.Run(); TestStaticVirtualMethodOptimizations.Run(); + TestTypeEquals.Run(); return 100; } @@ -311,6 +312,24 @@ public static void Run() } } + class TestTypeEquals + { + sealed class Never { } + + static Type s_type = null; + + public static void Run() + { + // This was asserting the BCL because Never would not have reflection metadata + // despite the typeof + Console.WriteLine(s_type == typeof(Never)); + +#if !DEBUG + ThrowIfPresent(typeof(TestTypeEquals), nameof(Never)); +#endif + } + } + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", Justification = "That's the point")] private static bool IsTypePresent(Type testType, string typeName) => testType.GetNestedType(typeName, BindingFlags.NonPublic | BindingFlags.Public) != null; From 231255dac0fe015ebc6fb55466c75cf5b4e05f56 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Thu, 11 Aug 2022 17:14:53 -0500 Subject: [PATCH 40/68] EnableAOTAnalyzer for most of Microsoft.Extensions libraries (#73737) The only libraries that aren't enabled yet are DependencyInjection and Hosting. These will come in a separate PR. Contributes to #71654 --- ...oft.Extensions.Caching.Abstractions.csproj | 1 + ...Microsoft.Extensions.Caching.Memory.csproj | 1 + ...tensions.Configuration.Abstractions.csproj | 5 ++- ...crosoft.Extensions.Configuration.Binder.cs | 7 ++++ ...oft.Extensions.Configuration.Binder.csproj | 6 ++- .../src/ConfigurationBinder.cs | 18 +++++++++ ...oft.Extensions.Configuration.Binder.csproj | 5 +++ ...xtensions.Configuration.CommandLine.csproj | 1 + ....Configuration.EnvironmentVariables.csproj | 1 + ...nsions.Configuration.FileExtensions.csproj | 1 + ...rosoft.Extensions.Configuration.Ini.csproj | 1 + ...osoft.Extensions.Configuration.Json.csproj | 1 + ...xtensions.Configuration.UserSecrets.csproj | 1 + ...rosoft.Extensions.Configuration.Xml.csproj | 1 + .../Microsoft.Extensions.Configuration.csproj | 1 + ...icrosoft.Extensions.DependencyModel.csproj | 1 + ...tensions.FileProviders.Abstractions.csproj | 1 + ....Extensions.FileProviders.Composite.csproj | 1 + ...t.Extensions.FileProviders.Physical.csproj | 1 + ...osoft.Extensions.FileSystemGlobbing.csproj | 1 + ...oft.Extensions.Hosting.Abstractions.csproj | 1 + .../src/Microsoft.Extensions.Http.csproj | 1 + ...oft.Extensions.Logging.Abstractions.csproj | 1 + ...rosoft.Extensions.Logging.Configuration.cs | 3 +- ...ft.Extensions.Logging.Configuration.csproj | 4 ++ .../LoggerProviderConfigurationExtensions.cs | 2 + .../src/LoggerProviderConfigureOptions.cs | 1 + ...ft.Extensions.Logging.Configuration.csproj | 5 +++ .../Microsoft.Extensions.Logging.Console.cs | 8 ++-- ...icrosoft.Extensions.Logging.Console.csproj | 1 + .../src/ConsoleLoggerExtensions.cs | 6 +++ ...icrosoft.Extensions.Logging.Console.csproj | 2 + .../ConsoleLoggerExtensionsTests.cs | 38 +++++++++++++++++++ .../Microsoft.Extensions.Logging.Debug.csproj | 1 + ...crosoft.Extensions.Logging.EventLog.csproj | 1 + ...soft.Extensions.Logging.EventSource.csproj | 1 + ...soft.Extensions.Logging.TraceSource.csproj | 1 + .../src/Microsoft.Extensions.Logging.csproj | 1 + ...ensions.Options.ConfigurationExtensions.cs | 30 ++++++++++----- ...ons.Options.ConfigurationExtensions.csproj | 4 ++ .../src/ConfigureFromConfigurationOptions.cs | 7 +--- ...ons.Options.ConfigurationExtensions.csproj | 5 +++ .../NamedConfigureFromConfigurationOptions.cs | 8 ++-- .../OptionsBuilderConfigurationExtensions.cs | 23 +++++------ ...onfigurationServiceCollectionExtensions.cs | 4 ++ ....Extensions.Options.DataAnnotations.csproj | 1 + .../src/Microsoft.Extensions.Options.csproj | 1 + .../Microsoft.Extensions.Primitives.csproj | 1 + 48 files changed, 180 insertions(+), 38 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Microsoft.Extensions.Caching.Abstractions.csproj b/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Microsoft.Extensions.Caching.Abstractions.csproj index bf6448ad896b93..bd291ccdba7bde 100644 --- a/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Microsoft.Extensions.Caching.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Microsoft.Extensions.Caching.Abstractions.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true Caching abstractions for in-memory cache and distributed cache. Commonly Used Types: diff --git a/src/libraries/Microsoft.Extensions.Caching.Memory/src/Microsoft.Extensions.Caching.Memory.csproj b/src/libraries/Microsoft.Extensions.Caching.Memory/src/Microsoft.Extensions.Caching.Memory.csproj index 3443e064abc493..1a93164a89fa46 100644 --- a/src/libraries/Microsoft.Extensions.Caching.Memory/src/Microsoft.Extensions.Caching.Memory.csproj +++ b/src/libraries/Microsoft.Extensions.Caching.Memory/src/Microsoft.Extensions.Caching.Memory.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true In-memory cache implementation of Microsoft.Extensions.Caching.Memory.IMemoryCache. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Abstractions/src/Microsoft.Extensions.Configuration.Abstractions.csproj b/src/libraries/Microsoft.Extensions.Configuration.Abstractions/src/Microsoft.Extensions.Configuration.Abstractions.csproj index 3668c5c0f6e1a3..0f89142c400739 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Abstractions/src/Microsoft.Extensions.Configuration.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Abstractions/src/Microsoft.Extensions.Configuration.Abstractions.csproj @@ -1,9 +1,10 @@ - + $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) - true true + true + true Abstractions of key-value pair based configuration. Commonly Used Types: diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/ref/Microsoft.Extensions.Configuration.Binder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/ref/Microsoft.Extensions.Configuration.Binder.cs index d42ef6222df5f1..0f28d4f2fb9af8 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/ref/Microsoft.Extensions.Configuration.Binder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/ref/Microsoft.Extensions.Configuration.Binder.cs @@ -14,14 +14,19 @@ public BinderOptions() { } } public static partial class ConfigurationBinder { + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values requires generating dynamic code at runtime, for example instantiating generic types.")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Cannot statically analyze the type of instance so its members may be trimmed")] public static void Bind(this Microsoft.Extensions.Configuration.IConfiguration configuration, object? instance) { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values requires generating dynamic code at runtime, for example instantiating generic types.")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Cannot statically analyze the type of instance so its members may be trimmed")] public static void Bind(this Microsoft.Extensions.Configuration.IConfiguration configuration, object? instance, System.Action? configureOptions) { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values requires generating dynamic code at runtime, for example instantiating generic types.")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Cannot statically analyze the type of instance so its members may be trimmed")] public static void Bind(this Microsoft.Extensions.Configuration.IConfiguration configuration, string key, object? instance) { } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values requires generating dynamic code at runtime, for example instantiating generic types.")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("In case the type is non-primitive, the trimmer cannot statically analyze the object's type so its members may be trimmed.")] public static object? Get(this Microsoft.Extensions.Configuration.IConfiguration configuration, System.Type type) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values requires generating dynamic code at runtime, for example instantiating generic types.")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("In case the type is non-primitive, the trimmer cannot statically analyze the object's type so its members may be trimmed.")] public static object? Get(this Microsoft.Extensions.Configuration.IConfiguration configuration, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] System.Type type, System.Action? configureOptions) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("In case the type is non-primitive, the trimmer cannot statically analyze the object's type so its members may be trimmed.")] @@ -32,8 +37,10 @@ public static void Bind(this Microsoft.Extensions.Configuration.IConfiguration c public static T? GetValue<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] T>(this Microsoft.Extensions.Configuration.IConfiguration configuration, string key) { throw null; } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("In case the type is non-primitive, the trimmer cannot statically analyze the object's type so its members may be trimmed.")] public static T? GetValue<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] T>(this Microsoft.Extensions.Configuration.IConfiguration configuration, string key, T defaultValue) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values requires generating dynamic code at runtime, for example instantiating generic types.")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("In case the type is non-primitive, the trimmer cannot statically analyze the object's type so its members may be trimmed.")] public static T? Get<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] T>(this Microsoft.Extensions.Configuration.IConfiguration configuration) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values requires generating dynamic code at runtime, for example instantiating generic types.")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("In case the type is non-primitive, the trimmer cannot statically analyze the object's type so its members may be trimmed.")] public static T? Get<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] T>(this Microsoft.Extensions.Configuration.IConfiguration configuration, System.Action? configureOptions) { throw null; } } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/ref/Microsoft.Extensions.Configuration.Binder.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/ref/Microsoft.Extensions.Configuration.Binder.csproj index 1d3a95f6f69837..91ca02cfc3944a 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/ref/Microsoft.Extensions.Configuration.Binder.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/ref/Microsoft.Extensions.Configuration.Binder.csproj @@ -12,7 +12,11 @@ - + + + + + diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs index f8fae6dcb0587c..a26b2fe68e7190 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/ConfigurationBinder.cs @@ -19,6 +19,7 @@ namespace Microsoft.Extensions.Configuration public static class ConfigurationBinder { private const BindingFlags DeclaredOnlyLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly; + private const string DynamicCodeWarningMessage = "Binding strongly typed objects to configuration values requires generating dynamic code at runtime, for example instantiating generic types."; private const string TrimmingWarningMessage = "In case the type is non-primitive, the trimmer cannot statically analyze the object's type so its members may be trimmed."; private const string InstanceGetTypeTrimmingWarningMessage = "Cannot statically analyze the type of instance so its members may be trimmed"; private const string PropertyTrimmingWarningMessage = "Cannot statically analyze property.PropertyType so its members may be trimmed."; @@ -31,6 +32,7 @@ public static class ConfigurationBinder /// The type of the new instance to bind. /// The configuration instance to bind. /// The new instance of T if successful, default(T) otherwise. + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode(TrimmingWarningMessage)] public static T? Get<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this IConfiguration configuration) => configuration.Get(_ => { }); @@ -44,6 +46,7 @@ public static class ConfigurationBinder /// The configuration instance to bind. /// Configures the binder options. /// The new instance of T if successful, default(T) otherwise. + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode(TrimmingWarningMessage)] public static T? Get<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>(this IConfiguration configuration, Action? configureOptions) { @@ -65,6 +68,7 @@ public static class ConfigurationBinder /// The configuration instance to bind. /// The type of the new instance to bind. /// The new instance if successful, null otherwise. + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode(TrimmingWarningMessage)] public static object? Get(this IConfiguration configuration, Type type) => configuration.Get(type, _ => { }); @@ -78,6 +82,7 @@ public static class ConfigurationBinder /// The type of the new instance to bind. /// Configures the binder options. /// The new instance if successful, null otherwise. + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode(TrimmingWarningMessage)] public static object? Get( this IConfiguration configuration, @@ -100,6 +105,7 @@ public static class ConfigurationBinder /// The configuration instance to bind. /// The key of the configuration section to bind. /// The object to bind. + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode(InstanceGetTypeTrimmingWarningMessage)] public static void Bind(this IConfiguration configuration, string key, object? instance) => configuration.GetSection(key).Bind(instance); @@ -109,6 +115,7 @@ public static void Bind(this IConfiguration configuration, string key, object? i /// /// The configuration instance to bind. /// The object to bind. + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode(InstanceGetTypeTrimmingWarningMessage)] public static void Bind(this IConfiguration configuration, object? instance) => configuration.Bind(instance, _ => { }); @@ -119,6 +126,7 @@ public static void Bind(this IConfiguration configuration, object? instance) /// The configuration instance to bind. /// The object to bind. /// Configures the binder options. + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode(InstanceGetTypeTrimmingWarningMessage)] public static void Bind(this IConfiguration configuration, object? instance, Action? configureOptions) { @@ -201,6 +209,7 @@ public static void Bind(this IConfiguration configuration, object? instance, Act return defaultValue; } + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode(PropertyTrimmingWarningMessage)] private static void BindProperties(object instance, IConfiguration configuration, BinderOptions options) { @@ -231,6 +240,7 @@ private static void BindProperties(object instance, IConfiguration configuration } } + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode(PropertyTrimmingWarningMessage)] private static void BindProperty(PropertyInfo property, object instance, IConfiguration config, BinderOptions options) { @@ -258,6 +268,7 @@ private static void BindProperty(PropertyInfo property, object instance, IConfig } } + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode(TrimmingWarningMessage)] private static void BindInstance( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type type, @@ -368,6 +379,7 @@ private static void BindInstance( } } + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode( "In case type is a Nullable, cannot statically analyze what the underlying type is so its members may be trimmed.")] private static object CreateInstance( @@ -480,6 +492,7 @@ private static bool CanBindToTheseConstructorParameters(ParameterInfo[] construc return true; } + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode("Cannot statically analyze what the element type is of the value objects in the dictionary so its members may be trimmed.")] private static object? BindDictionaryInterface( object? source, @@ -542,6 +555,7 @@ private static bool CanBindToTheseConstructorParameters(ParameterInfo[] construc // When a user specifies a concrete dictionary in their config class, then that // value is used as-us. When a user specifies an interface (instantiated) in their config class, // then it is cloned to a new dictionary, the same way as other collections. + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode("Cannot statically analyze what the element type is of the value objects in the dictionary so its members may be trimmed.")] private static void BindConcreteDictionary( object? dictionary, @@ -603,6 +617,7 @@ private static void BindConcreteDictionary( } } + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode("Cannot statically analyze what the element type is of the object collection so its members may be trimmed.")] private static void BindCollection( object collection, @@ -635,6 +650,7 @@ private static void BindCollection( } } + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode("Cannot statically analyze what the element type is of the Array so its members may be trimmed.")] private static Array BindArray(Type type, IEnumerable? source, IConfiguration config, BinderOptions options) { @@ -687,6 +703,7 @@ private static Array BindArray(Type type, IEnumerable? source, IConfiguration co return result; } + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode("Cannot statically analyze what the element type is of the Array so its members may be trimmed.")] private static object? BindSet(Type type, IEnumerable? source, IConfiguration config, BinderOptions options) { @@ -893,6 +910,7 @@ private static List GetAllProperties([DynamicallyAccessedMembers(D return allProperties; } + [RequiresDynamicCode(DynamicCodeWarningMessage)] [RequiresUnreferencedCode(PropertyTrimmingWarningMessage)] private static object? BindParameter(ParameterInfo parameter, Type type, IConfiguration config, BinderOptions options) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Microsoft.Extensions.Configuration.Binder.csproj b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Microsoft.Extensions.Configuration.Binder.csproj index d74d502d6b573e..f5517f57f23c64 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Microsoft.Extensions.Configuration.Binder.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/src/Microsoft.Extensions.Configuration.Binder.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true Functionality to bind an object to data in configuration providers for Microsoft.Extensions.Configuration. @@ -21,6 +22,10 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.Configuration.CommandLine/src/Microsoft.Extensions.Configuration.CommandLine.csproj b/src/libraries/Microsoft.Extensions.Configuration.CommandLine/src/Microsoft.Extensions.Configuration.CommandLine.csproj index 00ecad03ffa56d..e7821f8217db96 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.CommandLine/src/Microsoft.Extensions.Configuration.CommandLine.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.CommandLine/src/Microsoft.Extensions.Configuration.CommandLine.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true Command line configuration provider implementation for Microsoft.Extensions.Configuration. diff --git a/src/libraries/Microsoft.Extensions.Configuration.EnvironmentVariables/src/Microsoft.Extensions.Configuration.EnvironmentVariables.csproj b/src/libraries/Microsoft.Extensions.Configuration.EnvironmentVariables/src/Microsoft.Extensions.Configuration.EnvironmentVariables.csproj index 79544766c2f3f8..8415a9a65bd283 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.EnvironmentVariables/src/Microsoft.Extensions.Configuration.EnvironmentVariables.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.EnvironmentVariables/src/Microsoft.Extensions.Configuration.EnvironmentVariables.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true Environment variables configuration provider implementation for Microsoft.Extensions.Configuration. diff --git a/src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/Microsoft.Extensions.Configuration.FileExtensions.csproj b/src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/Microsoft.Extensions.Configuration.FileExtensions.csproj index 25693a45b1c699..42815662498204 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/Microsoft.Extensions.Configuration.FileExtensions.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.FileExtensions/src/Microsoft.Extensions.Configuration.FileExtensions.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true Extension methods for configuring file-based configuration providers for Microsoft.Extensions.Configuration. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Ini/src/Microsoft.Extensions.Configuration.Ini.csproj b/src/libraries/Microsoft.Extensions.Configuration.Ini/src/Microsoft.Extensions.Configuration.Ini.csproj index 5c859dcf9aaf61..c2a4beba8fa495 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Ini/src/Microsoft.Extensions.Configuration.Ini.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Ini/src/Microsoft.Extensions.Configuration.Ini.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true INI configuration provider implementation for Microsoft.Extensions.Configuration. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Json/src/Microsoft.Extensions.Configuration.Json.csproj b/src/libraries/Microsoft.Extensions.Configuration.Json/src/Microsoft.Extensions.Configuration.Json.csproj index d0733925f9f230..47373297f2ac69 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Json/src/Microsoft.Extensions.Configuration.Json.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Json/src/Microsoft.Extensions.Configuration.Json.csproj @@ -5,6 +5,7 @@ true true true + true JSON configuration provider implementation for Microsoft.Extensions.Configuration. diff --git a/src/libraries/Microsoft.Extensions.Configuration.UserSecrets/src/Microsoft.Extensions.Configuration.UserSecrets.csproj b/src/libraries/Microsoft.Extensions.Configuration.UserSecrets/src/Microsoft.Extensions.Configuration.UserSecrets.csproj index f3585bc67c4dfc..145f88c0bf50fb 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.UserSecrets/src/Microsoft.Extensions.Configuration.UserSecrets.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.UserSecrets/src/Microsoft.Extensions.Configuration.UserSecrets.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true User secrets configuration provider implementation for Microsoft.Extensions.Configuration. diff --git a/src/libraries/Microsoft.Extensions.Configuration.Xml/src/Microsoft.Extensions.Configuration.Xml.csproj b/src/libraries/Microsoft.Extensions.Configuration.Xml/src/Microsoft.Extensions.Configuration.Xml.csproj index bfc0ab612f9e4e..0cda0b6eedb3ee 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Xml/src/Microsoft.Extensions.Configuration.Xml.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration.Xml/src/Microsoft.Extensions.Configuration.Xml.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true XML configuration provider implementation for Microsoft.Extensions.Configuration. diff --git a/src/libraries/Microsoft.Extensions.Configuration/src/Microsoft.Extensions.Configuration.csproj b/src/libraries/Microsoft.Extensions.Configuration/src/Microsoft.Extensions.Configuration.csproj index d61fbd55c06e9a..1c22e8c954f381 100644 --- a/src/libraries/Microsoft.Extensions.Configuration/src/Microsoft.Extensions.Configuration.csproj +++ b/src/libraries/Microsoft.Extensions.Configuration/src/Microsoft.Extensions.Configuration.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true Implementation of key-value pair based configuration for Microsoft.Extensions.Configuration. Includes the memory configuration provider. diff --git a/src/libraries/Microsoft.Extensions.DependencyModel/src/Microsoft.Extensions.DependencyModel.csproj b/src/libraries/Microsoft.Extensions.DependencyModel/src/Microsoft.Extensions.DependencyModel.csproj index 01dd63044f7881..1f0b891c35d2c8 100644 --- a/src/libraries/Microsoft.Extensions.DependencyModel/src/Microsoft.Extensions.DependencyModel.csproj +++ b/src/libraries/Microsoft.Extensions.DependencyModel/src/Microsoft.Extensions.DependencyModel.csproj @@ -3,6 +3,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true Provides abstractions for reading `.deps` files. When a .NET application is compiled, the SDK generates a JSON manifest file (`<ApplicationName>.deps.json`) that contains information about application dependencies. You can use `Microsoft.Extensions.DependencyModel` to read information from this manifest at run time. This is useful when you want to dynamically compile code (for example, using Roslyn Emit API) referencing the same dependencies as your main application. By default, the dependency manifest contains information about the application's target framework and runtime dependencies. Set the PreserveCompilationContext project property to `true` to additionally include information about reference assemblies used during compilation. diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Abstractions/src/Microsoft.Extensions.FileProviders.Abstractions.csproj b/src/libraries/Microsoft.Extensions.FileProviders.Abstractions/src/Microsoft.Extensions.FileProviders.Abstractions.csproj index ac9af12a6adc36..e87de141d2c02b 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Abstractions/src/Microsoft.Extensions.FileProviders.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.FileProviders.Abstractions/src/Microsoft.Extensions.FileProviders.Abstractions.csproj @@ -5,6 +5,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true Abstractions of files and directories. Commonly Used Types: diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Composite/src/Microsoft.Extensions.FileProviders.Composite.csproj b/src/libraries/Microsoft.Extensions.FileProviders.Composite/src/Microsoft.Extensions.FileProviders.Composite.csproj index 5c4e43724d03de..f69efd6f971add 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Composite/src/Microsoft.Extensions.FileProviders.Composite.csproj +++ b/src/libraries/Microsoft.Extensions.FileProviders.Composite/src/Microsoft.Extensions.FileProviders.Composite.csproj @@ -5,6 +5,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true Composite file and directory providers for Microsoft.Extensions.FileProviders. diff --git a/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Microsoft.Extensions.FileProviders.Physical.csproj b/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Microsoft.Extensions.FileProviders.Physical.csproj index e467f6d4cbca05..1fdc08dd893fa5 100644 --- a/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Microsoft.Extensions.FileProviders.Physical.csproj +++ b/src/libraries/Microsoft.Extensions.FileProviders.Physical/src/Microsoft.Extensions.FileProviders.Physical.csproj @@ -6,6 +6,7 @@ true true true + true File provider for physical files for Microsoft.Extensions.FileProviders. diff --git a/src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/Microsoft.Extensions.FileSystemGlobbing.csproj b/src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/Microsoft.Extensions.FileSystemGlobbing.csproj index afc52b29a0c386..459b25b4859f52 100644 --- a/src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/Microsoft.Extensions.FileSystemGlobbing.csproj +++ b/src/libraries/Microsoft.Extensions.FileSystemGlobbing/src/Microsoft.Extensions.FileSystemGlobbing.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true File system globbing to find files matching a specified pattern. diff --git a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/Microsoft.Extensions.Hosting.Abstractions.csproj b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/Microsoft.Extensions.Hosting.Abstractions.csproj index 470c28257d2ba8..66949d90e33fa4 100644 --- a/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/Microsoft.Extensions.Hosting.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/Microsoft.Extensions.Hosting.Abstractions.csproj @@ -5,6 +5,7 @@ Microsoft.Extensions.Hosting true true + true Hosting and startup abstractions for applications. diff --git a/src/libraries/Microsoft.Extensions.Http/src/Microsoft.Extensions.Http.csproj b/src/libraries/Microsoft.Extensions.Http/src/Microsoft.Extensions.Http.csproj index dbc74f13e85dc3..5d9cc8d6951183 100644 --- a/src/libraries/Microsoft.Extensions.Http/src/Microsoft.Extensions.Http.csproj +++ b/src/libraries/Microsoft.Extensions.Http/src/Microsoft.Extensions.Http.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true The HttpClient factory is a pattern for configuring and retrieving named HttpClients in a composable way. The HttpClient factory provides extensibility to plug in DelegatingHandlers that address cross-cutting concerns such as service location, load balancing, and reliability. The default HttpClient factory provides built-in diagnostics and logging and manages the lifetimes of connections in a performant way. Commonly Used Types: diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj index bcc77451fa58c1..09e2e23824b20e 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/Microsoft.Extensions.Logging.Abstractions.csproj @@ -5,6 +5,7 @@ true true true + true Logging abstractions for Microsoft.Extensions.Logging. Commonly Used Types: diff --git a/src/libraries/Microsoft.Extensions.Logging.Configuration/ref/Microsoft.Extensions.Logging.Configuration.cs b/src/libraries/Microsoft.Extensions.Logging.Configuration/ref/Microsoft.Extensions.Logging.Configuration.cs index 224bf6d42372c1..bfa54bb28b8834 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Configuration/ref/Microsoft.Extensions.Logging.Configuration.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Configuration/ref/Microsoft.Extensions.Logging.Configuration.cs @@ -23,7 +23,8 @@ public partial interface ILoggerProviderConfiguration } public static partial class LoggerProviderOptions { - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding TOptions to configuration values may require generating dynamic code at runtime.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] public static void RegisterProviderOptions<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TOptions, TProvider>(Microsoft.Extensions.DependencyInjection.IServiceCollection services) where TOptions : class { } } public partial class LoggerProviderOptionsChangeTokenSource : Microsoft.Extensions.Options.ConfigurationChangeTokenSource diff --git a/src/libraries/Microsoft.Extensions.Logging.Configuration/ref/Microsoft.Extensions.Logging.Configuration.csproj b/src/libraries/Microsoft.Extensions.Logging.Configuration/ref/Microsoft.Extensions.Logging.Configuration.csproj index 6d25b454f2c0ed..834b7e7f3e1663 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Configuration/ref/Microsoft.Extensions.Logging.Configuration.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Configuration/ref/Microsoft.Extensions.Logging.Configuration.csproj @@ -13,6 +13,10 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.Logging.Configuration/src/LoggerProviderConfigurationExtensions.cs b/src/libraries/Microsoft.Extensions.Logging.Configuration/src/LoggerProviderConfigurationExtensions.cs index 3cbbbd60667263..231e3dd2b2cddf 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Configuration/src/LoggerProviderConfigurationExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Configuration/src/LoggerProviderConfigurationExtensions.cs @@ -13,6 +13,7 @@ namespace Microsoft.Extensions.Logging.Configuration /// public static class LoggerProviderOptions { + internal const string RequiresDynamicCodeMessage = "Binding TOptions to configuration values may require generating dynamic code at runtime."; internal const string TrimmingRequiresUnreferencedCodeMessage = "TOptions's dependent types may have their members trimmed. Ensure all required members are preserved."; /// @@ -21,6 +22,7 @@ public static class LoggerProviderOptions /// The to register on. /// The options class /// The provider class + [RequiresDynamicCode(RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(TrimmingRequiresUnreferencedCodeMessage)] public static void RegisterProviderOptions<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions, TProvider>(IServiceCollection services) where TOptions : class { diff --git a/src/libraries/Microsoft.Extensions.Logging.Configuration/src/LoggerProviderConfigureOptions.cs b/src/libraries/Microsoft.Extensions.Logging.Configuration/src/LoggerProviderConfigureOptions.cs index c7e83445baaef6..2e474ae68e4229 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Configuration/src/LoggerProviderConfigureOptions.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Configuration/src/LoggerProviderConfigureOptions.cs @@ -11,6 +11,7 @@ namespace Microsoft.Extensions.Logging.Configuration /// internal sealed class LoggerProviderConfigureOptions<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions, TProvider> : ConfigureFromConfigurationOptions where TOptions : class { + [RequiresDynamicCode(LoggerProviderOptions.RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(LoggerProviderOptions.TrimmingRequiresUnreferencedCodeMessage)] public LoggerProviderConfigureOptions(ILoggerProviderConfiguration providerConfiguration) : base(providerConfiguration.Configuration) diff --git a/src/libraries/Microsoft.Extensions.Logging.Configuration/src/Microsoft.Extensions.Logging.Configuration.csproj b/src/libraries/Microsoft.Extensions.Logging.Configuration/src/Microsoft.Extensions.Logging.Configuration.csproj index 455ebab7df9abf..fd6708f9e93825 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Configuration/src/Microsoft.Extensions.Logging.Configuration.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Configuration/src/Microsoft.Extensions.Logging.Configuration.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true Configuration support for Microsoft.Extensions.Logging. @@ -21,6 +22,10 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.cs b/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.cs index dabec63ae00d39..0941865d6e3db9 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.cs @@ -6,13 +6,15 @@ namespace Microsoft.Extensions.Logging { - [System.Runtime.Versioning.UnsupportedOSPlatform("browser")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public static partial class ConsoleLoggerExtensions { public static Microsoft.Extensions.Logging.ILoggingBuilder AddConsole(this Microsoft.Extensions.Logging.ILoggingBuilder builder) { throw null; } public static Microsoft.Extensions.Logging.ILoggingBuilder AddConsole(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action configure) { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding TOptions to configuration values may require generating dynamic code at runtime.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] public static Microsoft.Extensions.Logging.ILoggingBuilder AddConsoleFormatter<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TFormatter, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TOptions>(this Microsoft.Extensions.Logging.ILoggingBuilder builder) where TFormatter : Microsoft.Extensions.Logging.Console.ConsoleFormatter where TOptions : Microsoft.Extensions.Logging.Console.ConsoleFormatterOptions { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding TOptions to configuration values may require generating dynamic code at runtime.")] [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] public static Microsoft.Extensions.Logging.ILoggingBuilder AddConsoleFormatter<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] TFormatter, [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TOptions>(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action configure) where TFormatter : Microsoft.Extensions.Logging.Console.ConsoleFormatter where TOptions : Microsoft.Extensions.Logging.Console.ConsoleFormatterOptions { throw null; } public static Microsoft.Extensions.Logging.ILoggingBuilder AddJsonConsole(this Microsoft.Extensions.Logging.ILoggingBuilder builder) { throw null; } @@ -69,8 +71,8 @@ public ConsoleLoggerOptions() { } [System.ObsoleteAttribute("ConsoleLoggerOptions.UseUtcTimestamp has been deprecated. Use ConsoleFormatterOptions.UseUtcTimestamp instead.")] public bool UseUtcTimestamp { get { throw null; } set { } } } - [System.Runtime.Versioning.UnsupportedOSPlatform("browser")] [Microsoft.Extensions.Logging.ProviderAliasAttribute("Console")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] public partial class ConsoleLoggerProvider : Microsoft.Extensions.Logging.ILoggerProvider, Microsoft.Extensions.Logging.ISupportExternalScope, System.IDisposable { public ConsoleLoggerProvider(Microsoft.Extensions.Options.IOptionsMonitor options) { } diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.csproj b/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.csproj index 5e97ae48d2e3e9..ffa055b59f20fa 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Console/ref/Microsoft.Extensions.Logging.Console.csproj @@ -15,6 +15,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerExtensions.cs b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerExtensions.cs index 9df43d02c54c38..f10f581fca6c3d 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerExtensions.cs @@ -16,12 +16,15 @@ namespace Microsoft.Extensions.Logging [UnsupportedOSPlatform("browser")] public static class ConsoleLoggerExtensions { + internal const string RequiresDynamicCodeMessage = "Binding TOptions to configuration values may require generating dynamic code at runtime."; internal const string TrimmingRequiresUnreferencedCodeMessage = "TOptions's dependent types may have their members trimmed. Ensure all required members are preserved."; /// /// Adds a console logger named 'Console' to the factory. /// /// The to use. + [UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode", + Justification = "AddConsoleFormatter and RegisterProviderOptions are only called with Options types which only have simple properties.")] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "AddConsoleFormatter and RegisterProviderOptions are only dangerous when the Options type cannot be statically analyzed, but that is not the case here. " + "The DynamicallyAccessedMembers annotations on them will make sure to preserve the right members from the different options objects.")] @@ -123,6 +126,7 @@ private static ILoggingBuilder AddFormatterWithName(this ILoggingBuilder builder /// Adds a custom console logger formatter 'TFormatter' to be configured with options 'TOptions'. /// /// The to use. + [RequiresDynamicCode(RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(TrimmingRequiresUnreferencedCodeMessage)] public static ILoggingBuilder AddConsoleFormatter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TFormatter, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this ILoggingBuilder builder) where TOptions : ConsoleFormatterOptions @@ -142,6 +146,7 @@ private static ILoggingBuilder AddFormatterWithName(this ILoggingBuilder builder /// /// The to use. /// A delegate to configure options 'TOptions' for custom formatter 'TFormatter'. + [RequiresDynamicCode(RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(TrimmingRequiresUnreferencedCodeMessage)] public static ILoggingBuilder AddConsoleFormatter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TFormatter, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this ILoggingBuilder builder, Action configure) where TOptions : ConsoleFormatterOptions @@ -160,6 +165,7 @@ internal sealed class ConsoleLoggerFormatterConfigureOptions providerConfiguration) : base(providerConfiguration.Configuration.GetSection("FormatterOptions")) diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/src/Microsoft.Extensions.Logging.Console.csproj b/src/libraries/Microsoft.Extensions.Logging.Console/src/Microsoft.Extensions.Logging.Console.csproj index 970258c772ae5e..dcadc414c5299d 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/src/Microsoft.Extensions.Logging.Console.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Console/src/Microsoft.Extensions.Logging.Console.csproj @@ -7,6 +7,7 @@ $(DefineConstants);NO_SUPPRESS_GC_TRANSITION true true + true Console logger provider implementation for Microsoft.Extensions.Logging. @@ -40,6 +41,7 @@ + diff --git a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs index de5dd9310cd781..d5843e65cce3d1 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Console/tests/Microsoft.Extensions.Logging.Console.Tests/ConsoleLoggerExtensionsTests.cs @@ -565,5 +565,43 @@ public static TheoryData FormatterNames return data; } } + + /// + /// Tests to ensure the suppression of IL3050 on ConsoleLoggerExtensions.AddConsole is valid. + /// + [Theory] + [InlineData(typeof(JsonConsoleFormatterOptions))] + [InlineData(typeof(ConsoleFormatterOptions))] + [InlineData(typeof(SimpleConsoleFormatterOptions))] + [InlineData(typeof(ConsoleLoggerOptions))] + public void EnsureFormatterOptions_OnlyHaveSimpleProperties(Type type) + { + VerifyHasOnlySimpleProperties(type); + } + + private static void VerifyHasOnlySimpleProperties(Type type) + { + foreach (PropertyInfo prop in type.GetProperties()) + { + if (type == typeof(JsonConsoleFormatterOptions) && prop.Name == "JsonWriterOptions") + { + VerifyHasOnlySimpleProperties(prop.PropertyType); + continue; + } + + if (type == typeof(JsonWriterOptions) && prop.Name == "Encoder") + { + // skip JsonWriterOptions.Encoder, since that can't be set through IConfiguration + continue; + } + + // verify only "simple" types are used in the Options classes, there can't be any generic collections + // or else NativeAOT would break + Assert.True(prop.PropertyType == typeof(string) || + prop.PropertyType == typeof(bool) || + prop.PropertyType == typeof(int) || + prop.PropertyType.IsEnum, $"ConsoleOptions property '{type.Name}.{prop.Name}' must be a simple type in order for NativeAOT to work"); + } + } } } diff --git a/src/libraries/Microsoft.Extensions.Logging.Debug/src/Microsoft.Extensions.Logging.Debug.csproj b/src/libraries/Microsoft.Extensions.Logging.Debug/src/Microsoft.Extensions.Logging.Debug.csproj index 4d3c4cdac967cb..8f31bf6e49cfc2 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Debug/src/Microsoft.Extensions.Logging.Debug.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.Debug/src/Microsoft.Extensions.Logging.Debug.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true Debug output logger provider implementation for Microsoft.Extensions.Logging. This logger logs messages to a debugger monitor by writing messages with System.Diagnostics.Debug.WriteLine(). diff --git a/src/libraries/Microsoft.Extensions.Logging.EventLog/src/Microsoft.Extensions.Logging.EventLog.csproj b/src/libraries/Microsoft.Extensions.Logging.EventLog/src/Microsoft.Extensions.Logging.EventLog.csproj index 2d320fe34fb147..0f2b4164f8d8b3 100644 --- a/src/libraries/Microsoft.Extensions.Logging.EventLog/src/Microsoft.Extensions.Logging.EventLog.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.EventLog/src/Microsoft.Extensions.Logging.EventLog.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true Windows Event Log logger provider implementation for Microsoft.Extensions.Logging. diff --git a/src/libraries/Microsoft.Extensions.Logging.EventSource/src/Microsoft.Extensions.Logging.EventSource.csproj b/src/libraries/Microsoft.Extensions.Logging.EventSource/src/Microsoft.Extensions.Logging.EventSource.csproj index 4ed143b3a6a1e6..eb664296cdc134 100644 --- a/src/libraries/Microsoft.Extensions.Logging.EventSource/src/Microsoft.Extensions.Logging.EventSource.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.EventSource/src/Microsoft.Extensions.Logging.EventSource.csproj @@ -5,6 +5,7 @@ true true true + true EventSource/EventListener logger provider implementation for Microsoft.Extensions.Logging. diff --git a/src/libraries/Microsoft.Extensions.Logging.TraceSource/src/Microsoft.Extensions.Logging.TraceSource.csproj b/src/libraries/Microsoft.Extensions.Logging.TraceSource/src/Microsoft.Extensions.Logging.TraceSource.csproj index 6fd1b72b9b56f7..c0fe06b3cd8937 100644 --- a/src/libraries/Microsoft.Extensions.Logging.TraceSource/src/Microsoft.Extensions.Logging.TraceSource.csproj +++ b/src/libraries/Microsoft.Extensions.Logging.TraceSource/src/Microsoft.Extensions.Logging.TraceSource.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true TraceSource logger provider implementation for Microsoft.Extensions.Logging. This logger logs messages to a trace listener by writing messages with System.Diagnostics.TraceSource.TraceEvent(). diff --git a/src/libraries/Microsoft.Extensions.Logging/src/Microsoft.Extensions.Logging.csproj b/src/libraries/Microsoft.Extensions.Logging/src/Microsoft.Extensions.Logging.csproj index 83b547c44debad..2bd514f088cd3a 100644 --- a/src/libraries/Microsoft.Extensions.Logging/src/Microsoft.Extensions.Logging.csproj +++ b/src/libraries/Microsoft.Extensions.Logging/src/Microsoft.Extensions.Logging.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.1;netstandard2.0;$(NetFrameworkMinimum) true true + true Logging infrastructure default implementation for Microsoft.Extensions.Logging. diff --git a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/ref/Microsoft.Extensions.Options.ConfigurationExtensions.cs b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/ref/Microsoft.Extensions.Options.ConfigurationExtensions.cs index e63a02a67320c1..154143a512d562 100644 --- a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/ref/Microsoft.Extensions.Options.ConfigurationExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/ref/Microsoft.Extensions.Options.ConfigurationExtensions.cs @@ -8,22 +8,29 @@ namespace Microsoft.Extensions.DependencyInjection { public static partial class OptionsBuilderConfigurationExtensions { - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values may require generating dynamic code at runtime.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] public static Microsoft.Extensions.Options.OptionsBuilder BindConfiguration<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TOptions>(this Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, string configSectionPath, System.Action? configureBinder = null) where TOptions : class { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values may require generating dynamic code at runtime.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] public static Microsoft.Extensions.Options.OptionsBuilder Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TOptions>(this Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, Microsoft.Extensions.Configuration.IConfiguration config) where TOptions : class { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values may require generating dynamic code at runtime.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] public static Microsoft.Extensions.Options.OptionsBuilder Bind<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TOptions>(this Microsoft.Extensions.Options.OptionsBuilder optionsBuilder, Microsoft.Extensions.Configuration.IConfiguration config, System.Action? configureBinder) where TOptions : class { throw null; } } public static partial class OptionsConfigurationServiceCollectionExtensions { - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values may require generating dynamic code at runtime.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] public static Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TOptions>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.Configuration.IConfiguration config) where TOptions : class { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values may require generating dynamic code at runtime.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] public static Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TOptions>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.Configuration.IConfiguration config, System.Action? configureBinder) where TOptions : class { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values may require generating dynamic code at runtime.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] public static Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TOptions>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, Microsoft.Extensions.Configuration.IConfiguration config) where TOptions : class { throw null; } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values may require generating dynamic code at runtime.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] public static Microsoft.Extensions.DependencyInjection.IServiceCollection Configure<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TOptions>(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, string? name, Microsoft.Extensions.Configuration.IConfiguration config, System.Action? configureBinder) where TOptions : class { throw null; } } } @@ -38,14 +45,17 @@ public ConfigurationChangeTokenSource(string? name, Microsoft.Extensions.Configu } public partial class ConfigureFromConfigurationOptions<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TOptions> : Microsoft.Extensions.Options.ConfigureOptions where TOptions : class { - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values may require generating dynamic code at runtime.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] public ConfigureFromConfigurationOptions(Microsoft.Extensions.Configuration.IConfiguration config) : base (default(System.Action)) { } } public partial class NamedConfigureFromConfigurationOptions<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.All)] TOptions> : Microsoft.Extensions.Options.ConfigureNamedOptions where TOptions : class { - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values may require generating dynamic code at runtime.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] public NamedConfigureFromConfigurationOptions(string? name, Microsoft.Extensions.Configuration.IConfiguration config) : base (default(string), default(System.Action)) { } - [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Binding strongly typed objects to configuration values may require generating dynamic code at runtime.")] + [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("TOptions's dependent types may have their members trimmed. Ensure all required members are preserved.")] public NamedConfigureFromConfigurationOptions(string? name, Microsoft.Extensions.Configuration.IConfiguration config, System.Action? configureBinder) : base (default(string), default(System.Action)) { } } } diff --git a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/ref/Microsoft.Extensions.Options.ConfigurationExtensions.csproj b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/ref/Microsoft.Extensions.Options.ConfigurationExtensions.csproj index 46f069b2238306..d7601dcb8a2ede 100644 --- a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/ref/Microsoft.Extensions.Options.ConfigurationExtensions.csproj +++ b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/ref/Microsoft.Extensions.Options.ConfigurationExtensions.csproj @@ -13,6 +13,10 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/ConfigureFromConfigurationOptions.cs b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/ConfigureFromConfigurationOptions.cs index b4190cf4d90b27..545beb98c1c484 100644 --- a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/ConfigureFromConfigurationOptions.cs +++ b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/ConfigureFromConfigurationOptions.cs @@ -22,15 +22,12 @@ public class ConfigureFromConfigurationOptions<[DynamicallyAccessedMembers(Dynam /// /// The instance. //Even though TOptions is annotated, we need to annotate as RUC as we can't guarantee properties on referenced types are preserved. + [RequiresDynamicCode(OptionsBuilderConfigurationExtensions.RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)] public ConfigureFromConfigurationOptions(IConfiguration config) - : base(options => BindFromOptions(options, config)) + : base(options => ConfigurationBinder.Bind(config, options)) { ThrowHelper.ThrowIfNull(config); } - - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "The only call to this method is the constructor which is already annotated as RequiresUnreferencedCode.")] - private static void BindFromOptions(TOptions options, IConfiguration config) => ConfigurationBinder.Bind(config, options); } } diff --git a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/Microsoft.Extensions.Options.ConfigurationExtensions.csproj b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/Microsoft.Extensions.Options.ConfigurationExtensions.csproj index 4c823cdd7049b9..9657a633b41f20 100644 --- a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/Microsoft.Extensions.Options.ConfigurationExtensions.csproj +++ b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/Microsoft.Extensions.Options.ConfigurationExtensions.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) true true + true Provides additional configuration specific functionality related to Options. @@ -19,6 +20,10 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/NamedConfigureFromConfigurationOptions.cs b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/NamedConfigureFromConfigurationOptions.cs index b91aab542cec78..5eb03033e1db9d 100644 --- a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/NamedConfigureFromConfigurationOptions.cs +++ b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/NamedConfigureFromConfigurationOptions.cs @@ -20,6 +20,7 @@ public class NamedConfigureFromConfigurationOptions<[DynamicallyAccessedMembers( /// /// The name of the options instance. /// The instance. + [RequiresDynamicCode(OptionsBuilderConfigurationExtensions.RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)] public NamedConfigureFromConfigurationOptions(string? name, IConfiguration config) : this(name, config, _ => { }) @@ -31,15 +32,12 @@ public NamedConfigureFromConfigurationOptions(string? name, IConfiguration confi /// The name of the options instance. /// The instance. /// Used to configure the . + [RequiresDynamicCode(OptionsBuilderConfigurationExtensions.RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)] public NamedConfigureFromConfigurationOptions(string? name, IConfiguration config, Action? configureBinder) - : base(name, options => BindFromOptions(options, config, configureBinder)) + : base(name, options => config.Bind(options, configureBinder)) { ThrowHelper.ThrowIfNull(config); } - - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "The only call to this method is the constructor which is already annotated as RequiresUnreferencedCode.")] - private static void BindFromOptions(TOptions options, IConfiguration config, Action? configureBinder) => config.Bind(options, configureBinder); } } diff --git a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/OptionsBuilderConfigurationExtensions.cs b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/OptionsBuilderConfigurationExtensions.cs index 6f73a1959ef7a4..4d38fdbf492c0a 100644 --- a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/OptionsBuilderConfigurationExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/OptionsBuilderConfigurationExtensions.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; +using static System.Collections.Specialized.BitVector32; namespace Microsoft.Extensions.DependencyInjection { @@ -13,6 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection /// public static class OptionsBuilderConfigurationExtensions { + internal const string RequiresDynamicCodeMessage = "Binding strongly typed objects to configuration values may require generating dynamic code at runtime."; internal const string TrimmingRequiredUnreferencedCodeMessage = "TOptions's dependent types may have their members trimmed. Ensure all required members are preserved."; /// @@ -22,6 +24,7 @@ public static class OptionsBuilderConfigurationExtensions /// The options builder to add the services to. /// The configuration being bound. /// The so that additional calls can be chained. + [RequiresDynamicCode(RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(TrimmingRequiredUnreferencedCodeMessage)] public static OptionsBuilder Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this OptionsBuilder optionsBuilder, IConfiguration config) where TOptions : class => optionsBuilder.Bind(config, _ => { }); @@ -34,6 +37,7 @@ public static class OptionsBuilderConfigurationExtensions /// The configuration being bound. /// Used to configure the . /// The so that additional calls can be chained. + [RequiresDynamicCode(RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(TrimmingRequiredUnreferencedCodeMessage)] public static OptionsBuilder Bind<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this OptionsBuilder optionsBuilder, IConfiguration config, Action? configureBinder) where TOptions : class { @@ -56,6 +60,7 @@ public static class OptionsBuilderConfigurationExtensions /// or is . /// /// + [RequiresDynamicCode(RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(TrimmingRequiredUnreferencedCodeMessage)] public static OptionsBuilder BindConfiguration<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>( this OptionsBuilder optionsBuilder, @@ -66,19 +71,15 @@ public static class OptionsBuilderConfigurationExtensions ThrowHelper.ThrowIfNull(optionsBuilder); ThrowHelper.ThrowIfNull(configSectionPath); - optionsBuilder.Configure((opts, config) => BindFromOptions(opts, config, configSectionPath, configureBinder)); + optionsBuilder.Configure((opts, config) => + { + IConfiguration section = string.Equals("", configSectionPath, StringComparison.OrdinalIgnoreCase) + ? config + : config.GetSection(configSectionPath); + section.Bind(opts, configureBinder); + }); optionsBuilder.Services.AddSingleton, ConfigurationChangeTokenSource>(); return optionsBuilder; } - - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", - Justification = "The only call to this method is in BindConfiguration method which is already annotated as RequiresUnreferencedCode.")] - private static void BindFromOptions(TOptions opts, IConfiguration config, string configSectionPath, Action? configureBinder) where TOptions : class - { - IConfiguration section = string.Equals("", configSectionPath, StringComparison.OrdinalIgnoreCase) - ? config - : config.GetSection(configSectionPath); - section.Bind(opts, configureBinder); - } } } diff --git a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/OptionsConfigurationServiceCollectionExtensions.cs b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/OptionsConfigurationServiceCollectionExtensions.cs index 2b1e2015b0dfc2..89c15c052eea3c 100644 --- a/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/OptionsConfigurationServiceCollectionExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Options.ConfigurationExtensions/src/OptionsConfigurationServiceCollectionExtensions.cs @@ -20,6 +20,7 @@ public static class OptionsConfigurationServiceCollectionExtensions /// The to add the services to. /// The configuration being bound. /// The so that additional calls can be chained. + [RequiresDynamicCode(OptionsBuilderConfigurationExtensions.RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)] public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, IConfiguration config) where TOptions : class => services.Configure(Options.Options.DefaultName, config); @@ -32,6 +33,7 @@ public static class OptionsConfigurationServiceCollectionExtensions /// The name of the options instance. /// The configuration being bound. /// The so that additional calls can be chained. + [RequiresDynamicCode(OptionsBuilderConfigurationExtensions.RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)] public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, string? name, IConfiguration config) where TOptions : class => services.Configure(name, config, _ => { }); @@ -44,6 +46,7 @@ public static class OptionsConfigurationServiceCollectionExtensions /// The configuration being bound. /// Used to configure the . /// The so that additional calls can be chained. + [RequiresDynamicCode(OptionsBuilderConfigurationExtensions.RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)] public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, IConfiguration config, Action? configureBinder) where TOptions : class @@ -58,6 +61,7 @@ public static class OptionsConfigurationServiceCollectionExtensions /// The configuration being bound. /// Used to configure the . /// The so that additional calls can be chained. + [RequiresDynamicCode(OptionsBuilderConfigurationExtensions.RequiresDynamicCodeMessage)] [RequiresUnreferencedCode(OptionsBuilderConfigurationExtensions.TrimmingRequiredUnreferencedCodeMessage)] public static IServiceCollection Configure<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TOptions>(this IServiceCollection services, string? name, IConfiguration config, Action? configureBinder) where TOptions : class diff --git a/src/libraries/Microsoft.Extensions.Options.DataAnnotations/src/Microsoft.Extensions.Options.DataAnnotations.csproj b/src/libraries/Microsoft.Extensions.Options.DataAnnotations/src/Microsoft.Extensions.Options.DataAnnotations.csproj index 4f4ce39d370e69..35d7cbf0408447 100644 --- a/src/libraries/Microsoft.Extensions.Options.DataAnnotations/src/Microsoft.Extensions.Options.DataAnnotations.csproj +++ b/src/libraries/Microsoft.Extensions.Options.DataAnnotations/src/Microsoft.Extensions.Options.DataAnnotations.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.1;netstandard2.0;$(NetFrameworkMinimum) true true + true Provides additional DataAnnotations specific functionality related to Options. 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 bd4453bcd72836..39bfd89a00736f 100644 --- a/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj +++ b/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.1;netstandard2.0;$(NetFrameworkMinimum) true true + true Provides a strongly typed way of specifying and accessing settings using dependency injection. diff --git a/src/libraries/Microsoft.Extensions.Primitives/src/Microsoft.Extensions.Primitives.csproj b/src/libraries/Microsoft.Extensions.Primitives/src/Microsoft.Extensions.Primitives.csproj index 9ebe9571f786f2..91b52a8589ad20 100644 --- a/src/libraries/Microsoft.Extensions.Primitives/src/Microsoft.Extensions.Primitives.csproj +++ b/src/libraries/Microsoft.Extensions.Primitives/src/Microsoft.Extensions.Primitives.csproj @@ -4,6 +4,7 @@ true true true + true Primitives shared by framework extensions. Commonly used types include: Commonly Used Types: From 48ce77b3de820fa3788938c4abc6e90e0ab39276 Mon Sep 17 00:00:00 2001 From: Hong Li Date: Thu, 11 Aug 2022 15:35:05 -0700 Subject: [PATCH 41/68] fix a couple issues with Microsoft.XmlSerailizer.Generator (#73550) * fix a couple issues with Microsoft.XmlSerailizer.Generator * update per PR feedback * update 2 - change default to True * correct a mistake --- .../src/GenerateNupkgProps.targets | 6 ++++++ .../src/build/Microsoft.XmlSerializer.Generator.targets | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.XmlSerializer.Generator/src/GenerateNupkgProps.targets b/src/libraries/Microsoft.XmlSerializer.Generator/src/GenerateNupkgProps.targets index 74fd8ebb92b2cd..031f9371df0aa7 100644 --- a/src/libraries/Microsoft.XmlSerializer.Generator/src/GenerateNupkgProps.targets +++ b/src/libraries/Microsoft.XmlSerializer.Generator/src/GenerateNupkgProps.targets @@ -19,6 +19,12 @@ <ItemGroup> <DotNetCliToolReference Include="$(PackageId)" Version="$(PackageVersion)" /> </ItemGroup> + <PropertyGroup> + <SgenPackSerializer Condition="'%24(SgenPackSerializer)'==''">True</SgenPackSerializer> + </PropertyGroup> + <ItemGroup Condition="'%24(SgenPackSerializer)'=='True'"> + <BuildOutputInPackage Include="%24(OutputPath)%24(AssemblyName).XmlSerializers.dll" /> + </ItemGroup> </Project> diff --git a/src/libraries/Microsoft.XmlSerializer.Generator/src/build/Microsoft.XmlSerializer.Generator.targets b/src/libraries/Microsoft.XmlSerializer.Generator/src/build/Microsoft.XmlSerializer.Generator.targets index 6d905920740908..86f3ed67ff7658 100644 --- a/src/libraries/Microsoft.XmlSerializer.Generator/src/build/Microsoft.XmlSerializer.Generator.targets +++ b/src/libraries/Microsoft.XmlSerializer.Generator/src/build/Microsoft.XmlSerializer.Generator.targets @@ -43,7 +43,7 @@ Encoding="Unicode"/> - + From 4710133ea0ed60c96ea77e66aee461eadc6c6f78 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Thu, 11 Aug 2022 17:32:57 -0700 Subject: [PATCH 42/68] Add missing doc tags to TimeOnly type. (#73801) --- src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs index 60de482f4f8b60..03786dac191f29 100644 --- a/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs +++ b/src/libraries/System.Private.CoreLib/src/System/TimeOnly.cs @@ -933,8 +933,10 @@ public string ToString([StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)] stri // IParsable // + /// public static TimeOnly Parse(string s, IFormatProvider? provider) => Parse(s, provider, DateTimeStyles.None); + /// public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out TimeOnly result) => TryParse(s, provider, DateTimeStyles.None, out result); // From 87533c7273efb898e6b6e1b489d26f5ccd2caf39 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Thu, 11 Aug 2022 20:59:55 -0400 Subject: [PATCH 43/68] Add missing interop doc (#73805) --- .../Runtime/InteropServices/Marshalling/ArrayMarshaller.cs | 2 ++ .../InteropServices/Marshalling/PointerArrayMarshaller.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ArrayMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ArrayMarshaller.cs index 315cc97f2ad4bb..4890c0fdf2e61c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ArrayMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/ArrayMarshaller.cs @@ -166,11 +166,13 @@ public void FromManaged(T[]? array, Span buffer) /// /// Returns a reference to the marshalled array. /// + /// A pinnable reference to the unmanaged marshalled array. public ref TUnmanagedElement GetPinnableReference() => ref MemoryMarshal.GetReference(_span); /// /// Returns the unmanaged value representing the array. /// + /// A pointer to the beginning of the unmanaged value. public TUnmanagedElement* ToUnmanaged() => (TUnmanagedElement*)Unsafe.AsPointer(ref GetPinnableReference()); /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/PointerArrayMarshaller.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/PointerArrayMarshaller.cs index ba7cd6ddd2a00a..aa3e26b1ac16e9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/PointerArrayMarshaller.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshalling/PointerArrayMarshaller.cs @@ -167,11 +167,13 @@ public void FromManaged(T*[]? array, Span buffer) /// /// Returns a reference to the marshalled array. /// + /// A pinnable reference to the unmanaged marshalled array. public ref TUnmanagedElement GetPinnableReference() => ref MemoryMarshal.GetReference(_span); /// /// Returns the unmanaged value representing the array. /// + /// A pointer to the beginning of the unmanaged value. public TUnmanagedElement* ToUnmanaged() => (TUnmanagedElement*)Unsafe.AsPointer(ref GetPinnableReference()); /// From 35ee382758bdeb3a29f3eeb486911420dc7352d1 Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Fri, 12 Aug 2022 04:47:38 +0300 Subject: [PATCH 44/68] Fix `IBinaryInteger.Read***Endian` to accept a `ReadOnlySpan`. (#73802) --- src/libraries/Common/tests/System/GenericMathHelpers.cs | 4 ++-- .../src/System/Numerics/IBinaryInteger.cs | 4 ++-- src/libraries/System.Runtime/ref/System.Runtime.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libraries/Common/tests/System/GenericMathHelpers.cs b/src/libraries/Common/tests/System/GenericMathHelpers.cs index 188f447b1c12d4..277580dc5920d8 100644 --- a/src/libraries/Common/tests/System/GenericMathHelpers.cs +++ b/src/libraries/Common/tests/System/GenericMathHelpers.cs @@ -33,13 +33,13 @@ public static class BinaryIntegerHelper public static TSelf ReadBigEndian(byte[] source, int startIndex, bool isUnsigned) => TSelf.ReadBigEndian(source, startIndex, isUnsigned); - public static TSelf ReadBigEndian(Span source, bool isUnsigned) => TSelf.ReadBigEndian(source, isUnsigned); + public static TSelf ReadBigEndian(ReadOnlySpan source, bool isUnsigned) => TSelf.ReadBigEndian(source, isUnsigned); public static TSelf ReadLittleEndian(byte[] source, bool isUnsigned) => TSelf.ReadLittleEndian(source, isUnsigned); public static TSelf ReadLittleEndian(byte[] source, int startIndex, bool isUnsigned) => TSelf.ReadLittleEndian(source, startIndex, isUnsigned); - public static TSelf ReadLittleEndian(Span source, bool isUnsigned) => TSelf.ReadLittleEndian(source, isUnsigned); + public static TSelf ReadLittleEndian(ReadOnlySpan source, bool isUnsigned) => TSelf.ReadLittleEndian(source, isUnsigned); public static TSelf RotateLeft(TSelf value, int rotateAmount) => TSelf.RotateLeft(value, rotateAmount); diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryInteger.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryInteger.cs index cca46cdc1bcd7a..e182d35b04246b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryInteger.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/IBinaryInteger.cs @@ -74,7 +74,7 @@ static virtual TSelf ReadBigEndian(byte[] source, int startIndex, bool isUnsigne /// true if represents an unsigned two's complement number; otherwise, false to indicate it represents a signed two's complement number. /// The value read from . /// is not representable by - static virtual TSelf ReadBigEndian(Span source, bool isUnsigned) + static virtual TSelf ReadBigEndian(ReadOnlySpan source, bool isUnsigned) { if (!TSelf.TryReadBigEndian(source, isUnsigned, out TSelf value)) { @@ -117,7 +117,7 @@ static virtual TSelf ReadLittleEndian(byte[] source, int startIndex, bool isUnsi /// true if represents an unsigned two's complement number; otherwise, false to indicate it represents a signed two's complement number. /// The value read from . /// is not representable by - static virtual TSelf ReadLittleEndian(Span source, bool isUnsigned) + static virtual TSelf ReadLittleEndian(ReadOnlySpan source, bool isUnsigned) { if (!TSelf.TryReadLittleEndian(source, isUnsigned, out TSelf value)) { diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index c2d10c07e434cf..1212c55a7d9630 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -10239,10 +10239,10 @@ public partial interface IBinaryInteger : System.IComparable, System.ICom static abstract TSelf PopCount(TSelf value); static virtual TSelf ReadBigEndian(byte[] source, bool isUnsigned) { throw null; } static virtual TSelf ReadBigEndian(byte[] source, int startIndex, bool isUnsigned) { throw null; } - static virtual TSelf ReadBigEndian(System.Span source, bool isUnsigned) { throw null; } + static virtual TSelf ReadBigEndian(System.ReadOnlySpan source, bool isUnsigned) { throw null; } static virtual TSelf ReadLittleEndian(byte[] source, bool isUnsigned) { throw null; } static virtual TSelf ReadLittleEndian(byte[] source, int startIndex, bool isUnsigned) { throw null; } - static virtual TSelf ReadLittleEndian(System.Span source, bool isUnsigned) { throw null; } + static virtual TSelf ReadLittleEndian(System.ReadOnlySpan source, bool isUnsigned) { throw null; } static virtual TSelf RotateLeft(TSelf value, int rotateAmount) { throw null; } static virtual TSelf RotateRight(TSelf value, int rotateAmount) { throw null; } static abstract TSelf TrailingZeroCount(TSelf value); From f52d8c59bb49360eb2cbeeb863c5856ebd62adda Mon Sep 17 00:00:00 2001 From: Buyaa Namnan Date: Thu, 11 Aug 2022 19:39:19 -0700 Subject: [PATCH 45/68] Copy default value back when Missing.Value is provided as argument (#73670) * Copy back Missing.Value after Invoke * Refactor to use existing copy back mechanism Co-authored-by: Steve Harter --- .../src/System/RuntimeType.CoreCLR.cs | 10 +-- .../src/System/Reflection/MethodBase.cs | 71 ++++++++++--------- .../tests/MethodInfoTests.cs | 52 ++++++++++++++ 3 files changed, 91 insertions(+), 42 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index 17d4a4aa1f1b65..6d3de7a32f7236 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3623,7 +3623,7 @@ internal bool CheckValue( } bool isValueType; - CheckValueStatus result = TryChangeType(ref value, out copyBack, out isValueType); + CheckValueStatus result = TryChangeType(ref value, ref copyBack, out isValueType); if (result == CheckValueStatus.Success) { return isValueType; @@ -3653,7 +3653,7 @@ internal bool CheckValue( return IsValueType; // Note the call to IsValueType, not the variable. } - result = TryChangeType(ref value, out copyBack, out isValueType); + result = TryChangeType(ref value, ref copyBack, out isValueType); if (result == CheckValueStatus.Success) { return isValueType; @@ -3675,7 +3675,7 @@ internal bool CheckValue( private CheckValueStatus TryChangeType( ref object? value, - out ParameterCopyBackAction copyBack, + ref ParameterCopyBackAction copyBack, out bool isValueType) { RuntimeType? sigElementType; @@ -3731,7 +3731,6 @@ private CheckValueStatus TryChangeType( if (value == null) { - copyBack = ParameterCopyBackAction.None; isValueType = RuntimeTypeHandle.IsValueType(this); if (!isValueType) { @@ -3762,7 +3761,6 @@ private CheckValueStatus TryChangeType( if (!CanValueSpecialCast(srcType, this)) { isValueType = false; - copyBack = ParameterCopyBackAction.None; return CheckValueStatus.ArgumentException; } @@ -3781,12 +3779,10 @@ private CheckValueStatus TryChangeType( } isValueType = true; - copyBack = ParameterCopyBackAction.None; return CheckValueStatus.Success; } isValueType = false; - copyBack = ParameterCopyBackAction.None; return CheckValueStatus.ArgumentException; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs index 77cc0fc6028696..f05878fb516217 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBase.cs @@ -150,7 +150,7 @@ private protected unsafe void CheckArguments( BindingFlags invokeAttr ) { - Debug.Assert(!parameters.IsEmpty); + Debug.Assert(parameters.Length > 0); ParameterInfo[]? paramInfos = null; for (int i = 0; i < parameters.Length; i++) @@ -160,6 +160,40 @@ BindingFlags invokeAttr object? arg = parameters[i]; RuntimeType sigType = sigTypes[i]; + // Convert a Type.Missing to the default value. + if (ReferenceEquals(arg, Type.Missing)) + { + paramInfos ??= GetParametersNoCopy(); + ParameterInfo paramInfo = paramInfos[i]; + + if (paramInfo.DefaultValue == DBNull.Value) + { + throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters)); + } + + arg = paramInfo.DefaultValue; + + if (sigType.IsNullableOfT) + { + copyBackArg = ParameterCopyBackAction.CopyNullable; + + if (arg is not null) + { + // For nullable Enum types, the ParameterInfo.DefaultValue returns a raw value which + // needs to be parsed to the Enum type, for more info: https://github.com/dotnet/runtime/issues/12924 + Type argumentType = sigType.GetGenericArguments()[0]; + if (argumentType.IsEnum) + { + arg = Enum.ToObject(argumentType, arg); + } + } + } + else + { + copyBackArg = ParameterCopyBackAction.Copy; + } + } + if (arg is null) { // Fast path for null reference types. @@ -183,44 +217,11 @@ BindingFlags invokeAttr // Fast path when the value's type matches the signature type of a byref parameter. copyBackArg = ParameterCopyBackAction.Copy; } - else if (!ReferenceEquals(arg, Type.Missing)) + else { // Slow path that supports type conversions. isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); } - else - { - // Convert Type.Missing to the default value. - paramInfos ??= GetParametersNoCopy(); - ParameterInfo paramInfo = paramInfos[i]; - - if (paramInfo.DefaultValue == DBNull.Value) - { - throw new ArgumentException(SR.Arg_VarMissNull, nameof(parameters)); - } - - arg = paramInfo.DefaultValue; - if (ReferenceEquals(arg?.GetType(), sigType)) - { - // Fast path when the default value's type matches the signature type. - isValueType = RuntimeTypeHandle.IsValueType(sigType); - } - else - { - if (arg != null && sigType.IsNullableOfT) - { - // In case if the parameter is nullable Enum type the ParameterInfo.DefaultValue returns a raw value which - // needs to be parsed to the Enum type, for more info: https://github.com/dotnet/runtime/issues/12924 - Type argumentType = sigType.GetGenericArguments()[0]; - if (argumentType.IsEnum) - { - arg = Enum.ToObject(argumentType, arg); - } - } - - isValueType = sigType.CheckValue(ref arg, ref copyBackArg, binder, culture, invokeAttr); - } - } } // We need to perform type safety validation against the incoming arguments, but we also need diff --git a/src/libraries/System.Reflection/tests/MethodInfoTests.cs b/src/libraries/System.Reflection/tests/MethodInfoTests.cs index b01cc2701e09fd..3c59c8dcf334ca 100644 --- a/src/libraries/System.Reflection/tests/MethodInfoTests.cs +++ b/src/libraries/System.Reflection/tests/MethodInfoTests.cs @@ -763,6 +763,48 @@ public static void InvokeNullableEnumParameterNoDefault() Assert.Throws(() => method.Invoke(null, new object?[] { Type.Missing })); } + public static IEnumerable MethodNameAndArgumetns() + { + yield return new object[] { nameof(Sample.DefaultString), "Hello", "Hi" }; + yield return new object[] { nameof(Sample.DefaultNullString), null, "Hi" }; + yield return new object[] { nameof(Sample.DefaultNullableInt), 3, 5 }; + yield return new object[] { nameof(Sample.DefaultNullableEnum), YesNo.Yes, YesNo.No }; + } + + [Theory] + [MemberData(nameof(MethodNameAndArgumetns))] + public static void InvokeCopiesBackMissingArgument(string methodName, object defaultValue, object passingValue) + { + MethodInfo method = typeof(Sample).GetMethod(methodName); + object[] args = new object[] { Missing.Value }; + + Assert.Equal(defaultValue, method.Invoke(null, args)); + Assert.Equal(defaultValue, args[0]); + + args[0] = passingValue; + + Assert.Equal(passingValue, method.Invoke(null, args)); + Assert.Equal(passingValue, args[0]); + + args[0] = null; + Assert.Null(method.Invoke(null, args)); + Assert.Null(args[0]); + } + + [Fact] + public static void InvokeCopiesBackMissingParameterAndArgument() + { + MethodInfo method = typeof(Sample).GetMethod(nameof(Sample.DefaultMissing)); + object[] args = new object[] { Missing.Value }; + + Assert.Null(method.Invoke(null, args)); + Assert.Null(args[0]); + + args[0] = null; + Assert.Null(method.Invoke(null, args)); + Assert.Null(args[0]); + } + [Fact] public void ValueTypeMembers_WithOverrides() { @@ -1087,6 +1129,16 @@ public string Method2(string t2, T t1, S t3) { return ""; } + + public static string DefaultString(string value = "Hello") => value; + + public static string DefaultNullString(string value = null) => value; + + public static YesNo? DefaultNullableEnum(YesNo? value = YesNo.Yes) => value; + + public static int? DefaultNullableInt(int? value = 3) => value; + + public static Missing DefaultMissing(Missing value = null) => value; } public class SampleG From 7262fe1ddc8020b8b33bb63ee955e2650aa656b0 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 12 Aug 2022 00:09:16 -0400 Subject: [PATCH 46/68] Avoid url string allocation in WebProxy.IsMatchInBypassList (#73807) * Avoid url string allocation in WebProxy.IsMatchInBypassList * Update src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs Co-authored-by: Miha Zupan Co-authored-by: Miha Zupan --- .../src/System.Net.WebProxy.csproj | 1 + .../src/System/Net/WebProxy.cs | 26 ++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Net.WebProxy/src/System.Net.WebProxy.csproj b/src/libraries/System.Net.WebProxy/src/System.Net.WebProxy.csproj index 980f1552ded92f..fbe0e197d113c3 100644 --- a/src/libraries/System.Net.WebProxy/src/System.Net.WebProxy.csproj +++ b/src/libraries/System.Net.WebProxy/src/System.Net.WebProxy.csproj @@ -10,6 +10,7 @@ + diff --git a/src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs b/src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs index f2e8b5fdbb5748..9460000a28e752 100644 --- a/src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs +++ b/src/libraries/System.Net.WebProxy/src/System/Net/WebProxy.cs @@ -1,7 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Collections; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Runtime.Serialization; @@ -154,16 +156,26 @@ private bool IsMatchInBypassList(Uri input) { UpdateRegexList(canThrow: false); - if (_regexBypassList != null) + if (_regexBypassList is Regex[] bypassList) { - Span stackBuffer = stackalloc char[128]; - string matchUriString = input.IsDefaultPort ? - string.Create(null, stackBuffer, $"{input.Scheme}://{input.Host}") : - string.Create(null, stackBuffer, $"{input.Scheme}://{input.Host}:{(uint)input.Port}"); + bool isDefaultPort = input.IsDefaultPort; + int lengthRequired = input.Scheme.Length + 3 + input.Host.Length; + if (!isDefaultPort) + { + lengthRequired += 1 + 5; // 1 for ':' and 5 for max formatted length of a port (16 bit value) + } + + int charsWritten; + Span url = lengthRequired <= 256 ? stackalloc char[256] : new char[lengthRequired]; + bool formatted = isDefaultPort ? + url.TryWrite($"{input.Scheme}://{input.Host}", out charsWritten) : + url.TryWrite($"{input.Scheme}://{input.Host}:{(uint)input.Port}", out charsWritten); + Debug.Assert(formatted); + url = url.Slice(0, charsWritten); - foreach (Regex r in _regexBypassList) + foreach (Regex r in bypassList) { - if (r.IsMatch(matchUriString)) + if (r.IsMatch(url)) { return true; } From 831d0861edfed54f7abf8cf0b484144b450f67a0 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Fri, 12 Aug 2022 09:24:18 +0200 Subject: [PATCH 47/68] [main] Update dependencies from 8 repositories (#72934) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update dependencies from https://github.com/dotnet/runtime-assets build 20220726.1 Microsoft.DotNet.CilStrip.Sources , System.ComponentModel.TypeConverter.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 7.0.0-beta.22361.2 -> To Version 7.0.0-beta.22376.1 * Update dependencies from https://github.com/dotnet/emsdk build 20220726.1 Microsoft.NET.Workload.Emscripten.Manifest-7.0.100 From Version 7.0.0-rc.1.22368.1 -> To Version 7.0.0-rc.1.22376.1 * Update dependencies from https://github.com/dotnet/roslyn-analyzers build 20220728.2 Microsoft.CodeAnalysis.NetAnalyzers From Version 7.0.0-preview1.22373.2 -> To Version 7.0.0-preview1.22378.2 * Update dependencies from https://github.com/dotnet/roslyn-analyzers build 20220729.1 Microsoft.CodeAnalysis.NetAnalyzers From Version 7.0.0-preview1.22373.2 -> To Version 7.0.0-preview1.22379.1 * Update dependencies from https://github.com/dotnet/runtime build 20220731.5 Microsoft.NET.Sdk.IL , Microsoft.NETCore.App.Runtime.win-x64 , Microsoft.NETCore.DotNetHost , Microsoft.NETCore.DotNetHostPolicy , Microsoft.NETCore.ILAsm , runtime.native.System.IO.Ports , System.Text.Json From Version 7.0.0-rc.1.22374.4 -> To Version 7.0.0-rc.1.22381.5 * Update dependencies from https://github.com/dotnet/icu build 20220802.1 Microsoft.NETCore.Runtime.ICU.Transport From Version 7.0.0-rc.1.22375.1 -> To Version 7.0.0-rc.1.22402.1 * Update dependencies from https://github.com/dotnet/xharness build 20220801.2 Microsoft.DotNet.XHarness.CLI , Microsoft.DotNet.XHarness.TestRunners.Common , Microsoft.DotNet.XHarness.TestRunners.Xunit From Version 1.0.0-prerelease.22375.5 -> To Version 1.0.0-prerelease.22401.2 * Update dependencies from https://github.com/dotnet/emsdk build 20220801.1 Microsoft.NET.Workload.Emscripten.Manifest-7.0.100 From Version 7.0.0-rc.1.22368.1 -> To Version 7.0.0-rc.1.22401.1 * Update dependencies from https://github.com/dotnet/runtime-assets build 20220802.1 Microsoft.DotNet.CilStrip.Sources , System.ComponentModel.TypeConverter.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 7.0.0-beta.22361.2 -> To Version 7.0.0-beta.22402.1 * Update dependencies from https://github.com/dotnet/roslyn-analyzers build 20220803.2 Microsoft.CodeAnalysis.NetAnalyzers From Version 7.0.0-preview1.22373.2 -> To Version 7.0.0-preview1.22403.2 * Update dependencies from https://github.com/dotnet/msquic build 20220803.1 System.Net.MsQuic.Transport From Version 7.0.0-alpha.1.22371.1 -> To Version 7.0.0-alpha.1.22403.1 * Update dependencies from https://github.com/dotnet/llvm-project build 20220804.1 runtime.linux-arm64.Microsoft.NETCore.Runtime.ObjWriter , runtime.linux-musl-arm64.Microsoft.NETCore.Runtime.ObjWriter , runtime.linux-musl-x64.Microsoft.NETCore.Runtime.ObjWriter , runtime.linux-x64.Microsoft.NETCore.Runtime.ObjWriter , runtime.osx.11.0-arm64.Microsoft.NETCore.Runtime.ObjWriter , runtime.osx.10.12-x64.Microsoft.NETCore.Runtime.ObjWriter , runtime.win-arm64.Microsoft.NETCore.Runtime.ObjWriter , runtime.win-x64.Microsoft.NETCore.Runtime.ObjWriter From Version 1.0.0-alpha.1.22364.1 -> To Version 1.0.0-alpha.1.22404.1 * Update dependencies from https://github.com/dotnet/xharness build 20220805.1 Microsoft.DotNet.XHarness.CLI , Microsoft.DotNet.XHarness.TestRunners.Common , Microsoft.DotNet.XHarness.TestRunners.Xunit From Version 1.0.0-prerelease.22375.5 -> To Version 1.0.0-prerelease.22405.1 * Update dependencies from https://github.com/dotnet/emsdk build 20220805.3 Microsoft.NET.Workload.Emscripten.Manifest-7.0.100 From Version 7.0.0-rc.1.22368.1 -> To Version 7.0.0-rc.1.22405.3 * Update dependencies from https://github.com/dotnet/msquic build 20220805.1 System.Net.MsQuic.Transport From Version 7.0.0-alpha.1.22371.1 -> To Version 7.0.0-alpha.1.22405.1 * Update dependencies from https://github.com/dotnet/llvm-project build 20220806.4 runtime.linux-arm64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk , runtime.linux-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.osx.10.12-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk , runtime.osx.10.12-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 From Version 11.1.0-alpha.1.22376.4 -> To Version 11.1.0-alpha.1.22406.4 * Update dependencies from https://github.com/dotnet/runtime build 20220807.4 Microsoft.NET.Sdk.IL , Microsoft.NETCore.App.Runtime.win-x64 , Microsoft.NETCore.ILAsm , runtime.native.System.IO.Ports , System.Text.Json From Version 7.0.0-rc.1.22374.4 -> To Version 7.0.0-rc.1.22407.4 * Update dependencies from https://github.com/dotnet/icu build 20220808.1 Microsoft.NETCore.Runtime.ICU.Transport From Version 7.0.0-rc.1.22375.1 -> To Version 7.0.0-rc.1.22408.1 * Update dependencies from https://github.com/dotnet/xharness build 20220808.1 Microsoft.DotNet.XHarness.CLI , Microsoft.DotNet.XHarness.TestRunners.Common , Microsoft.DotNet.XHarness.TestRunners.Xunit From Version 1.0.0-prerelease.22375.5 -> To Version 1.0.0-prerelease.22408.1 * Update dependencies from https://github.com/dotnet/emsdk build 20220808.2 Microsoft.NET.Workload.Emscripten.Manifest-7.0.100 From Version 7.0.0-rc.1.22368.1 -> To Version 7.0.0-rc.1.22408.2 * Update dependencies from https://github.com/dotnet/llvm-project build 20220808.2 runtime.linux-arm64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk , runtime.linux-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.osx.10.12-x64.Microsoft.NETCore.Runtime.Mono.LLVM.Sdk , runtime.osx.10.12-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 From Version 11.1.0-alpha.1.22376.4 -> To Version 11.1.0-alpha.1.22408.2 * Update dependencies from https://github.com/dotnet/runtime-assets build 20220809.1 Microsoft.DotNet.CilStrip.Sources , System.ComponentModel.TypeConverter.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 7.0.0-beta.22361.2 -> To Version 7.0.0-beta.22409.1 * Use stable VS2022 build images llvm-project uses that one too now so we shouldn't hit the MSVC ABI issue * Update dependencies from https://github.com/dotnet/llvm-project build 20220811.1 runtime.linux-arm64.Microsoft.NETCore.Runtime.ObjWriter , runtime.linux-musl-arm64.Microsoft.NETCore.Runtime.ObjWriter , runtime.linux-musl-x64.Microsoft.NETCore.Runtime.ObjWriter , runtime.linux-x64.Microsoft.NETCore.Runtime.ObjWriter , runtime.osx.11.0-arm64.Microsoft.NETCore.Runtime.ObjWriter , runtime.osx.10.12-x64.Microsoft.NETCore.Runtime.ObjWriter , runtime.win-arm64.Microsoft.NETCore.Runtime.ObjWriter , runtime.win-x64.Microsoft.NETCore.Runtime.ObjWriter From Version 1.0.0-alpha.1.22364.1 -> To Version 1.0.0-alpha.1.22411.1 * Update dependencies from https://github.com/dotnet/emsdk build 20220811.1 Microsoft.NET.Workload.Emscripten.Manifest-7.0.100 From Version 7.0.0-rc.1.22368.1 -> To Version 7.0.0-rc.1.22411.1 Co-authored-by: dotnet-maestro[bot] Co-authored-by: Larry Ewing Co-authored-by: Ankit Jain Co-authored-by: Alexander Köplinger --- .config/dotnet-tools.json | 2 +- eng/Version.Details.xml | 168 ++++++++++++++------------- eng/Versions.props | 80 ++++++------- eng/pipelines/common/xplat-setup.yml | 4 +- global.json | 2 +- 5 files changed, 133 insertions(+), 123 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 4ef4e71b3bff41..856cc4c5edb1ff 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "microsoft.dotnet.xharness.cli": { - "version": "1.0.0-prerelease.22375.5", + "version": "1.0.0-prerelease.22408.1", "commands": [ "xharness" ] diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 29b21fd47da85d..1c412944b83883 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,52 +1,52 @@ - + https://github.com/dotnet/icu - d65ae2e51ad292901e89d06b31d31d7038c30c0f + c04d1340510269c5cd07a285abb097f587924d5b https://github.com/dotnet/msquic dc012a715ceb9b5d5258f2fda77520586af5a36a - + https://github.com/dotnet/emsdk - 7b2cd1ee7169143248a7da9fd749caf22affa624 + 216093204c415b6e37dfadfcbcf183881b443636 https://github.com/dotnet/wcf 7f504aabb1988e9a093c1e74d8040bd52feb2f01 - + https://github.com/dotnet/llvm-project - 754d13817d834b716d339183e21aac7d2489c496 + e73d65f0f80655b463162bd41a8365377ba6565d - + https://github.com/dotnet/llvm-project - 754d13817d834b716d339183e21aac7d2489c496 + e73d65f0f80655b463162bd41a8365377ba6565d - + https://github.com/dotnet/llvm-project - 754d13817d834b716d339183e21aac7d2489c496 + e73d65f0f80655b463162bd41a8365377ba6565d - + https://github.com/dotnet/llvm-project - 754d13817d834b716d339183e21aac7d2489c496 + e73d65f0f80655b463162bd41a8365377ba6565d - + https://github.com/dotnet/llvm-project - 754d13817d834b716d339183e21aac7d2489c496 + e73d65f0f80655b463162bd41a8365377ba6565d - + https://github.com/dotnet/llvm-project - 754d13817d834b716d339183e21aac7d2489c496 + e73d65f0f80655b463162bd41a8365377ba6565d - + https://github.com/dotnet/llvm-project - 754d13817d834b716d339183e21aac7d2489c496 + e73d65f0f80655b463162bd41a8365377ba6565d - + https://github.com/dotnet/llvm-project - 754d13817d834b716d339183e21aac7d2489c496 + e73d65f0f80655b463162bd41a8365377ba6565d https://github.com/dotnet/command-line-api @@ -126,121 +126,129 @@ https://github.com/dotnet/arcade 13b342360ba81ca3fdf911fda985dc8420d51627 - + https://github.com/dotnet/runtime-assets - 86e7816b9869ab19ee3f44aba7ac5f0ef7d04c8c + 77acd39a813579e1e9b12cd98466787e7f90e059 - + https://github.com/dotnet/runtime-assets - 86e7816b9869ab19ee3f44aba7ac5f0ef7d04c8c + 77acd39a813579e1e9b12cd98466787e7f90e059 - + https://github.com/dotnet/runtime-assets - 86e7816b9869ab19ee3f44aba7ac5f0ef7d04c8c + 77acd39a813579e1e9b12cd98466787e7f90e059 - + https://github.com/dotnet/runtime-assets - 86e7816b9869ab19ee3f44aba7ac5f0ef7d04c8c + 77acd39a813579e1e9b12cd98466787e7f90e059 - + https://github.com/dotnet/runtime-assets - 86e7816b9869ab19ee3f44aba7ac5f0ef7d04c8c + 77acd39a813579e1e9b12cd98466787e7f90e059 - + https://github.com/dotnet/runtime-assets - 86e7816b9869ab19ee3f44aba7ac5f0ef7d04c8c + 77acd39a813579e1e9b12cd98466787e7f90e059 - + https://github.com/dotnet/runtime-assets - 86e7816b9869ab19ee3f44aba7ac5f0ef7d04c8c + 77acd39a813579e1e9b12cd98466787e7f90e059 - + https://github.com/dotnet/runtime-assets - 86e7816b9869ab19ee3f44aba7ac5f0ef7d04c8c + 77acd39a813579e1e9b12cd98466787e7f90e059 - + https://github.com/dotnet/runtime-assets - 86e7816b9869ab19ee3f44aba7ac5f0ef7d04c8c + 77acd39a813579e1e9b12cd98466787e7f90e059 - + https://github.com/dotnet/runtime-assets - 86e7816b9869ab19ee3f44aba7ac5f0ef7d04c8c + 77acd39a813579e1e9b12cd98466787e7f90e059 - + https://github.com/dotnet/runtime-assets - 86e7816b9869ab19ee3f44aba7ac5f0ef7d04c8c + 77acd39a813579e1e9b12cd98466787e7f90e059 - + https://github.com/dotnet/runtime-assets - 86e7816b9869ab19ee3f44aba7ac5f0ef7d04c8c + 77acd39a813579e1e9b12cd98466787e7f90e059 - + https://github.com/dotnet/llvm-project - d87702da408b8aad0680f4f1c8b858c87d98f1df + 527481d107abeb3d6aa2881643a3efeda949f244 - + https://github.com/dotnet/llvm-project - d87702da408b8aad0680f4f1c8b858c87d98f1df + 527481d107abeb3d6aa2881643a3efeda949f244 - + https://github.com/dotnet/llvm-project - d87702da408b8aad0680f4f1c8b858c87d98f1df + 527481d107abeb3d6aa2881643a3efeda949f244 - + https://github.com/dotnet/llvm-project - d87702da408b8aad0680f4f1c8b858c87d98f1df + 527481d107abeb3d6aa2881643a3efeda949f244 - + https://github.com/dotnet/llvm-project - d87702da408b8aad0680f4f1c8b858c87d98f1df + 527481d107abeb3d6aa2881643a3efeda949f244 - + https://github.com/dotnet/llvm-project - d87702da408b8aad0680f4f1c8b858c87d98f1df + 527481d107abeb3d6aa2881643a3efeda949f244 - + https://github.com/dotnet/llvm-project - d87702da408b8aad0680f4f1c8b858c87d98f1df + 527481d107abeb3d6aa2881643a3efeda949f244 - + https://github.com/dotnet/llvm-project - d87702da408b8aad0680f4f1c8b858c87d98f1df + 527481d107abeb3d6aa2881643a3efeda949f244 - + https://github.com/dotnet/runtime - 214ca6db481923aa49bac2d2b75b9aca4041b304 + 0c5ca95fec9b6e2cc5e757ad36d0b094f81a59a6 - + https://github.com/dotnet/runtime - 214ca6db481923aa49bac2d2b75b9aca4041b304 + 508fef51e841aa16ffed1aae32bf4793a2cea363 - + https://github.com/dotnet/runtime - 214ca6db481923aa49bac2d2b75b9aca4041b304 + 508fef51e841aa16ffed1aae32bf4793a2cea363 - + https://github.com/dotnet/runtime - 214ca6db481923aa49bac2d2b75b9aca4041b304 + 0c5ca95fec9b6e2cc5e757ad36d0b094f81a59a6 - + https://github.com/dotnet/runtime - 214ca6db481923aa49bac2d2b75b9aca4041b304 + 0c5ca95fec9b6e2cc5e757ad36d0b094f81a59a6 + + + https://github.com/dotnet/runtime + 0c5ca95fec9b6e2cc5e757ad36d0b094f81a59a6 + + + https://github.com/dotnet/runtime + 0c5ca95fec9b6e2cc5e757ad36d0b094f81a59a6 https://github.com/dotnet/linker f09bacf09ef10b61cf9f19825f8782171a816dab - + https://github.com/dotnet/xharness - d43453b9981bfd8705025250caff58d18e2abc7e + c98b5a327992608c54e50d48e24bb4dde0fa853e - + https://github.com/dotnet/xharness - d43453b9981bfd8705025250caff58d18e2abc7e + c98b5a327992608c54e50d48e24bb4dde0fa853e - + https://github.com/dotnet/xharness - d43453b9981bfd8705025250caff58d18e2abc7e + c98b5a327992608c54e50d48e24bb4dde0fa853e https://github.com/dotnet/arcade @@ -266,13 +274,13 @@ https://github.com/dotnet/hotreload-utils c6dfd18158f2290855b98e71734f5f169cf1dfe7 - + https://github.com/dotnet/runtime-assets - 86e7816b9869ab19ee3f44aba7ac5f0ef7d04c8c + 77acd39a813579e1e9b12cd98466787e7f90e059 - + https://github.com/dotnet/roslyn-analyzers - 4dce248f4ca16d6fa04be777c07d2a7a5f117370 + 793113a41d7f21b03470521bf48438f2abd9b12f https://github.com/dotnet/sdk diff --git a/eng/Versions.props b/eng/Versions.props index c9c2e20e739da0..06c3924253c555 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -44,7 +44,7 @@ 4.3.0-2.final 4.3.0-2.final 4.3.0-2.final - 7.0.0-preview1.22373.2 + 7.0.0-preview1.22403.2 4.3.0-2.final 6.0.0-preview.1.102 - 7.0.0-rc.1.22374.4 + 7.0.0-rc.1.22407.4 + 7.0.0-rc.1.22381.5 + 7.0.0-rc.1.22381.5 6.0.0 - 7.0.0-rc.1.22374.4 - 1.0.0-alpha.1.22364.1 - 1.0.0-alpha.1.22364.1 - 1.0.0-alpha.1.22364.1 - 1.0.0-alpha.1.22364.1 - 1.0.0-alpha.1.22364.1 - 1.0.0-alpha.1.22364.1 - 1.0.0-alpha.1.22364.1 - 1.0.0-alpha.1.22364.1 + 7.0.0-rc.1.22407.4 + 1.0.0-alpha.1.22411.1 + 1.0.0-alpha.1.22411.1 + 1.0.0-alpha.1.22411.1 + 1.0.0-alpha.1.22411.1 + 1.0.0-alpha.1.22411.1 + 1.0.0-alpha.1.22411.1 + 1.0.0-alpha.1.22411.1 + 1.0.0-alpha.1.22411.1 6.0.0 1.1.1 @@ -111,25 +113,25 @@ 5.0.0 5.0.0 4.9.0 - 7.0.0-rc.1.22374.4 + 7.0.0-rc.1.22407.4 6.0.0 4.5.4 4.5.0 - 7.0.0-rc.1.22374.4 + 7.0.0-rc.1.22407.4 - 7.0.0-beta.22361.2 - 7.0.0-beta.22361.2 - 7.0.0-beta.22361.2 - 7.0.0-beta.22361.2 - 7.0.0-beta.22361.2 - 7.0.0-beta.22361.2 - 7.0.0-beta.22361.2 - 7.0.0-beta.22361.2 - 7.0.0-beta.22361.2 - 7.0.0-beta.22361.2 - 7.0.0-beta.22361.2 - 7.0.0-beta.22361.2 - 7.0.0-beta.22361.2 + 7.0.0-beta.22409.1 + 7.0.0-beta.22409.1 + 7.0.0-beta.22409.1 + 7.0.0-beta.22409.1 + 7.0.0-beta.22409.1 + 7.0.0-beta.22409.1 + 7.0.0-beta.22409.1 + 7.0.0-beta.22409.1 + 7.0.0-beta.22409.1 + 7.0.0-beta.22409.1 + 7.0.0-beta.22409.1 + 7.0.0-beta.22409.1 + 7.0.0-beta.22409.1 1.0.0-prerelease.22375.7 1.0.0-prerelease.22375.7 @@ -150,9 +152,9 @@ 1.1.0 17.4.0-preview-20220707-01 - 1.0.0-prerelease.22375.5 - 1.0.0-prerelease.22375.5 - 1.0.0-prerelease.22375.5 + 1.0.0-prerelease.22408.1 + 1.0.0-prerelease.22408.1 + 1.0.0-prerelease.22408.1 1.1.0-alpha.0.22362.1 2.4.2-pre.22 0.12.0-pre.20 @@ -172,21 +174,21 @@ 7.0.100-1.22377.1 $(MicrosoftNETILLinkTasksVersion) - 7.0.0-rc.1.22375.1 + 7.0.0-rc.1.22408.1 2.1 7.0.0-alpha.1.22406.1 - 11.1.0-alpha.1.22376.4 - 11.1.0-alpha.1.22376.4 - 11.1.0-alpha.1.22376.4 - 11.1.0-alpha.1.22376.4 - 11.1.0-alpha.1.22376.4 - 11.1.0-alpha.1.22376.4 - 11.1.0-alpha.1.22376.4 - 11.1.0-alpha.1.22376.4 + 11.1.0-alpha.1.22408.2 + 11.1.0-alpha.1.22408.2 + 11.1.0-alpha.1.22408.2 + 11.1.0-alpha.1.22408.2 + 11.1.0-alpha.1.22408.2 + 11.1.0-alpha.1.22408.2 + 11.1.0-alpha.1.22408.2 + 11.1.0-alpha.1.22408.2 - 7.0.0-rc.1.22368.1 + 7.0.0-rc.1.22411.1 $(MicrosoftNETWorkloadEmscriptenManifest70100Version) 1.1.87-gba258badda diff --git a/eng/pipelines/common/xplat-setup.yml b/eng/pipelines/common/xplat-setup.yml index 9d45d7cddf15d8..83a0efd39874f0 100644 --- a/eng/pipelines/common/xplat-setup.yml +++ b/eng/pipelines/common/xplat-setup.yml @@ -150,12 +150,12 @@ jobs: # Official Build Windows Pool ${{ if and(eq(parameters.osGroup, 'windows'), ne(variables['System.TeamProject'], 'public')) }}: name: NetCore1ESPool-Internal - demands: ImageOverride -equals windows.vs2022preview.amd64 + demands: ImageOverride -equals windows.vs2022.amd64 # Public Windows Build Pool ${{ if and(or(eq(parameters.osGroup, 'windows'), eq(parameters.jobParameters.hostedOs, 'windows')), eq(variables['System.TeamProject'], 'public')) }}: name: NetCore1ESPool-Public - demands: ImageOverride -equals windows.vs2022preview.amd64.open + demands: ImageOverride -equals windows.vs2022.amd64.open ${{ if eq(parameters.helixQueuesTemplate, '') }}: diff --git a/global.json b/global.json index 81d425f8329cc2..31b8c266d8aabc 100644 --- a/global.json +++ b/global.json @@ -13,6 +13,6 @@ "Microsoft.DotNet.SharedFramework.Sdk": "7.0.0-beta.22408.3", "Microsoft.Build.NoTargets": "3.5.0", "Microsoft.Build.Traversal": "3.1.6", - "Microsoft.NET.Sdk.IL": "7.0.0-rc.1.22374.4" + "Microsoft.NET.Sdk.IL": "7.0.0-rc.1.22407.4" } } From 6a2c63f95561bf6c943781e8a5aaff562b06e2a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 12 Aug 2022 16:53:01 +0900 Subject: [PATCH 48/68] Fix repro project for Release builds (#73836) --- .../tools/aot/ILCompiler/reproNative/reproNative.vcxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler/reproNative/reproNative.vcxproj b/src/coreclr/tools/aot/ILCompiler/reproNative/reproNative.vcxproj index 86cfe3c795f883..2266c102c8ceaf 100644 --- a/src/coreclr/tools/aot/ILCompiler/reproNative/reproNative.vcxproj +++ b/src/coreclr/tools/aot/ILCompiler/reproNative/reproNative.vcxproj @@ -121,7 +121,7 @@ true true true - $(ArtifactsRoot)bin\repro\x64\Release\repro.obj;$(Win32SDKLibs);%(AdditionalDependencies);$(ArtifactsRoot)bin\coreclr\windows.x64.Release\aotsdk\Runtime.WorkstationGC.lib$(ArtifactsRoot)bin\coreclr\windows.x64.Release\aotsdk\System.Globalization.Native.Aot.lib + $(ArtifactsRoot)bin\repro\x64\Release\repro.obj;$(Win32SDKLibs);%(AdditionalDependencies);$(ArtifactsRoot)bin\coreclr\windows.x64.Release\aotsdk\Runtime.WorkstationGC.lib;$(ArtifactsRoot)bin\coreclr\windows.x64.Release\aotsdk\System.Globalization.Native.Aot.lib @@ -134,4 +134,4 @@ - \ No newline at end of file + From c55d76d43c6c451548590c479ed06d1566838f8b Mon Sep 17 00:00:00 2001 From: Shay Rojansky Date: Fri, 12 Aug 2022 12:58:48 +0300 Subject: [PATCH 49/68] Port distributed transaction support for Windows (#72051) Closes #715 --- .../src/Interop/Windows/Interop.Libraries.cs | 1 + .../src/Resources/Strings.resx | 405 ++++- .../src/System.Transactions.Local.csproj | 75 +- .../Transactions/CommittableTransaction.cs | 9 +- .../Transactions/DependentTransaction.cs | 2 +- .../Transactions/DistributedTransaction.cs | 166 -- .../DtcInterfaces/IPrepareInfo.cs | 16 + .../DtcInterfaces/IResourceManager.cs | 30 + .../DtcInterfaces/IResourceManagerFactory2.cs | 24 + .../DtcInterfaces/IResourceManagerSink.cs | 13 + .../DtcProxyShim/DtcInterfaces/ITmNodeName.cs | 15 + .../DtcInterfaces/ITransaction.cs | 19 + .../DtcInterfaces/ITransactionCloner.cs | 14 + .../DtcInterfaces/ITransactionDispenser.cs | 22 + .../ITransactionEnlistmentAsync.cs | 18 + .../DtcInterfaces/ITransactionExport.cs | 20 + .../ITransactionExportFactory.cs | 19 + .../DtcInterfaces/ITransactionImport.cs | 18 + .../ITransactionImportWhereabouts.cs | 18 + .../DtcInterfaces/ITransactionOptions.cs | 16 + .../ITransactionOutcomeEvents.cs | 20 + .../ITransactionPhase0EnlistmentAsync.cs | 21 + .../ITransactionPhase0Factory.cs | 15 + .../ITransactionPhase0NotifyAsync.cs | 15 + .../DtcInterfaces/ITransactionReceiver.cs | 26 + .../ITransactionReceiverFactory.cs | 14 + .../ITransactionResourceAsync.cs | 25 + .../DtcInterfaces/ITransactionTransmitter.cs | 27 + .../ITransactionTransmitterFactory.cs | 14 + .../ITransactionVoterBallotAsync2.cs | 13 + .../ITransactionVoterFactory2.cs | 16 + .../ITransactionVoterNotifyAsync2.cs | 22 + .../DtcProxyShim/DtcProxyShimFactory.cs | 353 ++++ .../DtcProxyShim/EnlistmentNotifyShim.cs | 75 + .../DtcProxyShim/EnlistmentShim.cs | 82 + .../System/Transactions/DtcProxyShim/Guids.cs | 20 + .../Transactions/DtcProxyShim/NativeEnums.cs | 143 ++ .../DtcProxyShim/NotificationShimBase.cs | 24 + .../Transactions/DtcProxyShim/OletxHelper.cs | 55 + .../DtcProxyShim/OletxXactTransInfo.cs | 19 + .../DtcProxyShim/Phase0NotifyShim.cs | 27 + .../Transactions/DtcProxyShim/Phase0Shim.cs | 41 + .../DtcProxyShim/ResourceManagerNotifyShim.cs | 23 + .../DtcProxyShim/ResourceManagerShim.cs | 61 + .../DtcProxyShim/TransactionNotifyShim.cs | 44 + .../DtcProxyShim/TransactionOutcome.cs | 11 + .../DtcProxyShim/TransactionShim.cs | 98 ++ .../DtcProxyShim/VoterNotifyShim.cs | 51 + .../Transactions/DtcProxyShim/VoterShim.cs | 28 + .../Transactions/DtcProxyShim/Xactopt.cs | 20 + .../Transactions/DurableEnlistmentState.cs | 4 +- .../src/System/Transactions/Enlistment.cs | 18 +- .../System/Transactions/IDtcTransaction.cs | 19 + .../Transactions/InternalTransaction.cs | 15 +- .../Transactions/NonWindowsUnsupported.cs | 154 ++ .../Oletx/DtcTransactionManager.cs | 132 ++ .../Oletx/OletxCommittableTransaction.cs | 57 + .../Oletx/OletxDependentTransaction.cs | 64 + .../Transactions/Oletx/OletxEnlistment.cs | 1212 +++++++++++++ .../Oletx/OletxResourceManager.cs | 896 ++++++++++ .../Transactions/Oletx/OletxTransaction.cs | 1373 +++++++++++++++ .../Oletx/OletxTransactionManager.cs | 804 +++++++++ .../Oletx/OletxVolatileEnlistment.cs | 1508 +++++++++++++++++ .../src/System/Transactions/Transaction.cs | 14 +- .../System/Transactions/TransactionInterop.cs | 355 +++- .../TransactionInteropNonWindows.cs | 257 +++ .../System/Transactions/TransactionManager.cs | 66 +- .../System/Transactions/TransactionScope.cs | 2 +- .../System/Transactions/TransactionState.cs | 89 +- .../Transactions/TransactionsEtwProvider.cs | 554 ++++-- .../Transactions/VolatileEnlistmentState.cs | 10 +- .../tests/LTMEnlistmentTests.cs | 142 +- .../tests/NonMsdtcPromoterTests.cs | 125 -- .../tests/OleTxNonWindowsUnsupportedTests.cs | 64 + .../tests/OleTxTests.cs | 477 ++++++ .../System.Transactions.Local.Tests.csproj | 5 + .../tests/TestEnlistments.cs | 107 +- 77 files changed, 10091 insertions(+), 755 deletions(-) delete mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DistributedTransaction.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IPrepareInfo.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManager.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManagerFactory2.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManagerSink.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITmNodeName.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransaction.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionCloner.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionDispenser.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionEnlistmentAsync.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExport.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExportFactory.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionImport.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionImportWhereabouts.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOptions.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOutcomeEvents.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionPhase0EnlistmentAsync.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionPhase0Factory.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionPhase0NotifyAsync.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionReceiver.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionReceiverFactory.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionResourceAsync.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionTransmitter.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionTransmitterFactory.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionVoterBallotAsync2.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionVoterFactory2.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionVoterNotifyAsync2.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcProxyShimFactory.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/EnlistmentNotifyShim.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/EnlistmentShim.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Guids.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/NativeEnums.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/NotificationShimBase.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/OletxHelper.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/OletxXactTransInfo.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Phase0NotifyShim.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Phase0Shim.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/ResourceManagerNotifyShim.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/ResourceManagerShim.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/TransactionNotifyShim.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/TransactionOutcome.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/TransactionShim.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/VoterNotifyShim.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/VoterShim.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Xactopt.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/IDtcTransaction.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/NonWindowsUnsupported.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/DtcTransactionManager.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxCommittableTransaction.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxDependentTransaction.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxEnlistment.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxResourceManager.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxTransaction.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxTransactionManager.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxVolatileEnlistment.cs create mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/TransactionInteropNonWindows.cs create mode 100644 src/libraries/System.Transactions.Local/tests/OleTxNonWindowsUnsupportedTests.cs create mode 100644 src/libraries/System.Transactions.Local/tests/OleTxTests.cs diff --git a/src/libraries/Common/src/Interop/Windows/Interop.Libraries.cs b/src/libraries/Common/src/Interop/Windows/Interop.Libraries.cs index 81b31db1cff99a..bf53b2717ea2ec 100644 --- a/src/libraries/Common/src/Interop/Windows/Interop.Libraries.cs +++ b/src/libraries/Common/src/Interop/Windows/Interop.Libraries.cs @@ -46,5 +46,6 @@ internal static partial class Libraries internal const string MsQuic = "msquic.dll"; internal const string HostPolicy = "hostpolicy.dll"; internal const string Ucrtbase = "ucrtbase.dll"; + internal const string Xolehlp = "xolehlp.dll"; } } diff --git a/src/libraries/System.Transactions.Local/src/Resources/Strings.resx b/src/libraries/System.Transactions.Local/src/Resources/Strings.resx index a11f02e372d5c5..4880e4f544a4b8 100644 --- a/src/libraries/System.Transactions.Local/src/Resources/Strings.resx +++ b/src/libraries/System.Transactions.Local/src/Resources/Strings.resx @@ -1,4 +1,64 @@ - + + + @@ -57,41 +117,310 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - TransactionScope with TransactionScopeAsyncFlowOption.Enabled option is not supported when the TransactionScope is used within Enterprise Service context with Automatic or Full EnterpriseServicesInteropOption enabled in parent scope. - The IAsyncResult parameter must be the same parameter returned by BeginCommit. - Resource Manager Identifiers cannot be Guid.Empty. - Transactions with IsolationLevel Snapshot cannot be promoted. - Current cannot be set directly when Com+ Interop is enabled. - The delegate for an external current can only be set once. - The current TransactionScope is already complete. You should dispose the TransactionScope. - The operation is not valid for the current state of the enlistment. - Com+ Interop features cannot be supported. - Internal Error - The argument is invalid. - The specified IPromotableSinglePhaseNotification is not the same as the one provided to EnlistPromotableSinglePhase. - Transaction Manager in the Recovery Information does not match the configured transaction manager. - A TransactionScope must be disposed on the same thread that it was created. - There was an error promoting the transaction to a distributed transaction. - The Promote method returned an invalid value for the distributed transaction. - The transaction returned from Promote already exists as a distributed transaction. - It is too late to add enlistments to this transaction. - Transaction Timeout - The transaction has aborted. - DependentTransaction.Complete or CommittableTransaction.Commit has already been called for this transaction. - The transaction is in doubt. - Communication with the underlying transaction manager has failed. - The current TransactionScope is already complete. - Transaction.Current has changed inside of the TransactionScope. - TransactionScope nested incorrectly. - The transaction specified for TransactionScope has a different IsolationLevel than the value requested for the scope. - TransactionScope timer object is invalid. - The operation is not valid for the state of the transaction. - There was an unexpected failure of QueueUserWorkItem. - There was an unexpected failure of a timer. - The RecoveryInformation provided is not recognized by this version of System.Transactions. - Volatile enlistments do not generate recovery information. - {0} Distributed Transaction ID is {1} - The specified PromoterType is invalid. - There is a promotable enlistment for the transaction which has a PromoterType value that is not recognized by System.Transactions. {0} - This platform does not support distributed transactions. + + TransactionScope with TransactionScopeAsyncFlowOption.Enabled option is not supported when the TransactionScope is used within Enterprise Service context with Automatic or Full EnterpriseServicesInteropOption enabled in parent scope. + + + The IAsyncResult parameter must be the same parameter returned by BeginCommit. + + + Resource Manager Identifiers cannot be Guid.Empty. + + + Transactions with IsolationLevel Snapshot cannot be promoted. + + + Current cannot be set directly when Com+ Interop is enabled. + + + The delegate for an external current can only be set once. + + + The current TransactionScope is already complete. You should dispose the TransactionScope. + + + The operation is not valid for the current state of the enlistment. + + + Com+ Interop features cannot be supported. + + + Internal Error + + + The argument is invalid. + + + The specified IPromotableSinglePhaseNotification is not the same as the one provided to EnlistPromotableSinglePhase. + + + Transaction Manager in the Recovery Information does not match the configured transaction manager. + + + A TransactionScope must be disposed on the same thread that it was created. + + + There was an error promoting the transaction to a distributed transaction. + + + The Promote method returned an invalid value for the distributed transaction. + + + The transaction returned from Promote already exists as a distributed transaction. + + + It is too late to add enlistments to this transaction. + + + Transaction Timeout + + + The transaction has aborted. + + + DependentTransaction.Complete or CommittableTransaction.Commit has already been called for this transaction. + + + The transaction is in doubt. + + + Communication with the underlying transaction manager has failed. + + + The current TransactionScope is already complete. + + + Transaction.Current has changed inside of the TransactionScope. + + + TransactionScope nested incorrectly. + + + The transaction specified for TransactionScope has a different IsolationLevel than the value requested for the scope. + + + TransactionScope timer object is invalid. + + + The operation is not valid for the state of the transaction. + + + There was an unexpected failure of QueueUserWorkItem. + + + There was an unexpected failure of a timer. + + + The RecoveryInformation provided is not recognized by this version of System.Transactions. + + + Volatile enlistments do not generate recovery information. + + + {0} Distributed Transaction ID is {1} + + + The specified PromoterType is invalid. + + + There is a promotable enlistment for the transaction which has a PromoterType value that is not recognized by System.Transactions. {0} + + + This platform does not support distributed transactions. + + + [Distributed] + + + Configured DefaultTimeout is greater than configured DefaultMaximumTimeout - adjusting down to DefaultMaximumTimeout + + + Method Entered + + + Method Exited + + + The operation is invalid because the document is empty. + + + Cannot add an element to a closed document. + + + The text node already has a value. + + + Enlistment Created + + + Transaction Promoted + + + Enlistment Notification Call + + + Enlistment Callback Positive + + + Enlistment Callback Negative + + + Cannot close an element on a closed document. + + + Internal Error + + + InvalidOperationException Thrown + + + Exception Consumed + + + TransactionException Thrown + + + Transaction Deserialized + + + Transaction Serialized + + + Transaction Manager Instance Created + + + TransactionManager.Reenlist Called + + + Transaction Created + + + CommittableTransaction.Commit Called + + + Transaction Committed + + + Transaction Aborted + + + Transaction InDoubt + + + TransactionScope Created + + + TransactionScope Disposed + + + MSDTC Proxy cannot support specification of different node names in the same process. + + + Cannot support specification of node name for the distributed transaction manager through System.Transactions due to MSDTC Proxy version. + + + Failed to create trace source. + + + Failed to initialize trace source. + + + TransactionManager.RecoveryComplete Called + + + Clone Created + + + Dependent Clone Completed + + + Dependent Clone Created + + + TransactionScope Timeout + + + Transaction.Rollback Called + + + Trace Event Type: {0}\nTrace Code: {1}\nTrace Description {2}\nObject: {3} + + + Internal Error - Unexpected transaction status value in enlistment constructor. + + + Unable to deserialize the transaction. + + + Internal Error - Unable to deserialize the transaction. + + + The transaction has already been implicitly or explicitly committed or aborted. + + + Process Name: {0}\nProcess Id: {1}\nCode: {2}\nDescription: {3} + + + Source: {0} + + + Exception: {0} + + + Event ID: {0} + + + Other information : {0} + + + TransactionScope Current Transaction Changed + + + TransactionScope Nested Incorrectly + + + TransactionScope Incomplete + + + Distributed transaction manager is unable to create the NotificationShimFactory object. + + + Unable to obtain the transaction identifier. + + + Calling TransactionManager.Reenlist is not allowed after TransactionManager.RecoveryComplete is called for a given resource manager identifier. + + + RecoveryComplete must not be called twice by the same resource manager identifier instance. + + + MSDTC Transaction Manager is unavailable. + + + Network access for Distributed Transaction Manager (MSDTC) has been disabled. Please enable DTC for network access in the security configuration for MSDTC using the Component Services Administrative tool. + + + [Lightweight] + + + AppDomain unloading. + + + Unhandled Exception + + + The resourceManagerIdentifier does not match the contents of the specified recovery information. + + + The distributed transaction manager does not allow any more durable enlistments on the transaction. + + + Failed to trace event: {0}. + + + [Base] + + + Distributed transactions are currently unsupported on 32-bit version of the .NET runtime. + \ No newline at end of file diff --git a/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj b/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj index 9b7d1861a67279..86a68905ab6ed6 100644 --- a/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj +++ b/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj @@ -2,17 +2,20 @@ true true - $(NetCoreAppCurrent) + $(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent) + CA1805;IDE0059;CS1591 + $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) + false - + @@ -25,7 +28,6 @@ - @@ -40,7 +42,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -48,5 +116,6 @@ + diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/CommittableTransaction.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/CommittableTransaction.cs index 20fc0d0e4eab8e..dfeace8bd4a531 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/CommittableTransaction.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/CommittableTransaction.cs @@ -5,6 +5,8 @@ using System.Runtime.Versioning; using System.Threading; +#pragma warning disable CS1591 + namespace System.Transactions { [UnsupportedOSPlatform("browser")] @@ -37,7 +39,7 @@ internal CommittableTransaction(IsolationLevel isoLevel, TimeSpan timeout) : bas TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.TransactionCreated(this, "CommittableTransaction"); + etwLog.TransactionCreated(TraceSourceType.TraceSourceLtm, TransactionTraceId, "CommittableTransaction"); } } @@ -47,7 +49,7 @@ public IAsyncResult BeginCommit(AsyncCallback? asyncCallback, object? asyncState if (etwLog.IsEnabled()) { etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this); - etwLog.TransactionCommit(this, "CommittableTransaction"); + etwLog.TransactionCommit(TraceSourceType.TraceSourceLtm, TransactionTraceId, "CommittableTransaction"); } ObjectDisposedException.ThrowIf(Disposed, this); @@ -81,7 +83,7 @@ public void Commit() if (etwLog.IsEnabled()) { etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this); - etwLog.TransactionCommit(this, "CommittableTransaction"); + etwLog.TransactionCommit(TraceSourceType.TraceSourceLtm, TransactionTraceId, "CommittableTransaction"); } ObjectDisposedException.ThrowIf(Disposed, this); @@ -113,7 +115,6 @@ public void Commit() { etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this); } - } internal override void InternalDispose() diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DependentTransaction.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DependentTransaction.cs index 7f11478f773a32..9a5c890a076c6c 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DependentTransaction.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DependentTransaction.cs @@ -61,7 +61,7 @@ public void Complete() if (etwLog.IsEnabled()) { - etwLog.TransactionDependentCloneComplete(this, "DependentTransaction"); + etwLog.TransactionDependentCloneComplete(TraceSourceType.TraceSourceLtm, TransactionTraceId, "DependentTransaction"); etwLog.MethodExit(TraceSourceType.TraceSourceLtm, this); } } diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DistributedTransaction.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DistributedTransaction.cs deleted file mode 100644 index 59d7fea1829f78..00000000000000 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DistributedTransaction.cs +++ /dev/null @@ -1,166 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.Serialization; - -namespace System.Transactions.Distributed -{ - internal sealed class DistributedTransactionManager - { - internal object? NodeName { get; set; } - - internal static IPromotedEnlistment ReenlistTransaction(Guid resourceManagerIdentifier, byte[] resourceManagerRecoveryInformation, RecoveringInternalEnlistment internalEnlistment) - { - throw DistributedTransaction.NotSupported(); - } - - internal static DistributedCommittableTransaction CreateTransaction(TransactionOptions options) - { - throw DistributedTransaction.NotSupported(); - } - - internal static void ResourceManagerRecoveryComplete(Guid resourceManagerIdentifier) - { - throw DistributedTransaction.NotSupported(); - } - - internal static byte[] GetWhereabouts() - { - throw DistributedTransaction.NotSupported(); - } - - internal static Transaction GetTransactionFromDtcTransaction(IDtcTransaction transactionNative) - { - throw DistributedTransaction.NotSupported(); - } - - internal static DistributedTransaction GetTransactionFromExportCookie(byte[] cookie, Guid txId) - { - throw DistributedTransaction.NotSupported(); - } - - internal static DistributedTransaction GetDistributedTransactionFromTransmitterPropagationToken(byte[] propagationToken) - { - throw DistributedTransaction.NotSupported(); - } - } - - /// - /// A Transaction object represents a single transaction. It is created by TransactionManager - /// objects through CreateTransaction or through deserialization. Alternatively, the static Create - /// methods provided, which creates a "default" TransactionManager and requests that it create - /// a new transaction with default values. A transaction can only be committed by - /// the client application that created the transaction. If a client application wishes to allow - /// access to the transaction by multiple threads, but wants to prevent those other threads from - /// committing the transaction, the application can make a "clone" of the transaction. Transaction - /// clones have the same capabilities as the original transaction, except for the ability to commit - /// the transaction. - /// - internal class DistributedTransaction : ISerializable, IObjectReference - { - internal DistributedTransaction() - { - } - - protected DistributedTransaction(SerializationInfo serializationInfo, StreamingContext context) - { - //if (serializationInfo == null) - //{ - // throw new ArgumentNullException(nameof(serializationInfo)); - //} - - //throw NotSupported(); - throw new PlatformNotSupportedException(); - } - - internal Exception? InnerException { get; set; } - internal Guid Identifier { get; set; } - internal RealDistributedTransaction? RealTransaction { get; set; } - internal TransactionTraceIdentifier TransactionTraceId { get; set; } - internal IsolationLevel IsolationLevel { get; set; } - internal Transaction? SavedLtmPromotedTransaction { get; set; } - - internal static IPromotedEnlistment EnlistVolatile(InternalEnlistment internalEnlistment, EnlistmentOptions enlistmentOptions) - { - throw NotSupported(); - } - - internal static IPromotedEnlistment EnlistDurable(Guid resourceManagerIdentifier, DurableInternalEnlistment internalEnlistment, bool v, EnlistmentOptions enlistmentOptions) - { - throw NotSupported(); - } - - internal static void Rollback() - { - throw NotSupported(); - } - - internal static DistributedDependentTransaction DependentClone(bool v) - { - throw NotSupported(); - } - - internal static IPromotedEnlistment EnlistVolatile(VolatileDemultiplexer volatileDemux, EnlistmentOptions enlistmentOptions) - { - throw NotSupported(); - } - - internal static byte[] GetExportCookie(byte[] whereaboutsCopy) - { - throw NotSupported(); - } - - public object GetRealObject(StreamingContext context) - { - throw NotSupported(); - } - - internal static byte[] GetTransmitterPropagationToken() - { - throw NotSupported(); - } - - internal static IDtcTransaction GetDtcTransaction() - { - throw NotSupported(); - } - - void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingContext context) - { - //if (serializationInfo == null) - //{ - // throw new ArgumentNullException(nameof(serializationInfo)); - //} - - //throw NotSupported(); - - throw new PlatformNotSupportedException(); - } - - internal static Exception NotSupported() - { - return new PlatformNotSupportedException(SR.DistributedNotSupported); - } - - internal sealed class RealDistributedTransaction - { - internal InternalTransaction? InternalTransaction { get; set; } - } - } - - internal sealed class DistributedDependentTransaction : DistributedTransaction - { - internal static void Complete() - { - throw NotSupported(); - } - } - - internal sealed class DistributedCommittableTransaction : DistributedTransaction - { - internal static void BeginCommit(InternalTransaction tx) - { - throw NotSupported(); - } - } -} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IPrepareInfo.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IPrepareInfo.cs new file mode 100644 index 00000000000000..0b6c1158f59b31 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IPrepareInfo.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms686533(v=vs.85) +[ComImport, Guid("80c7bfd0-87ee-11ce-8081-0080c758527e"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface IPrepareInfo +{ + void GetPrepareInfoSize(out uint pcbPrepInfo); + + void GetPrepareInfo([MarshalAs(UnmanagedType.LPArray), Out] byte[] pPrepInfo); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManager.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManager.cs new file mode 100644 index 00000000000000..4cec70b044228c --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManager.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms681790(v=vs.85) +[ComImport, Guid(Guids.IID_IResourceManager), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface IResourceManager +{ + internal void Enlist( + [MarshalAs(UnmanagedType.Interface)] ITransaction pTransaction, + [MarshalAs(UnmanagedType.Interface)] ITransactionResourceAsync pRes, + out Guid pUOW, + out OletxTransactionIsolationLevel pisoLevel, + [MarshalAs(UnmanagedType.Interface)] out ITransactionEnlistmentAsync ppEnlist); + + internal void Reenlist( + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] byte[] pPrepInfo, + uint cbPrepInfom, + uint lTimeout, + [MarshalAs(UnmanagedType.I4)] out OletxXactStat pXactStat); + + void ReenlistmentComplete(); + + void GetDistributedTransactionManager( + in Guid riid, + [MarshalAs(UnmanagedType.Interface)] out object ppvObject); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManagerFactory2.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManagerFactory2.cs new file mode 100644 index 00000000000000..b788a17032a5de --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManagerFactory2.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms686489(v=vs.85) +[ComImport, Guid("6B369C21-FBD2-11d1-8F47-00C04F8EE57D"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface IResourceManagerFactory2 +{ + internal void Create( + Guid pguidRM, + [MarshalAs(UnmanagedType.LPStr)] string pszRMName, + [MarshalAs(UnmanagedType.Interface)] IResourceManagerSink pIResMgrSink, + [MarshalAs(UnmanagedType.Interface)] out IResourceManager rm); + + internal void CreateEx( + Guid pguidRM, + [MarshalAs(UnmanagedType.LPStr)] string pszRMName, + [MarshalAs(UnmanagedType.Interface)] IResourceManagerSink pIResMgrSink, + Guid riidRequested, + [MarshalAs(UnmanagedType.Interface)] out object rm); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManagerSink.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManagerSink.cs new file mode 100644 index 00000000000000..0e872188cfb6b5 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/IResourceManagerSink.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms686073(v=vs.85) +[ComImport, Guid("0D563181-DEFB-11CE-AED1-00AA0051E2C4"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface IResourceManagerSink +{ + void TMDown(); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITmNodeName.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITmNodeName.cs new file mode 100644 index 00000000000000..4816352f86164f --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITmNodeName.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms687122(v=vs.85) +[ComImport, Guid("30274F88-6EE4-474e-9B95-7807BC9EF8CF"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface ITmNodeName +{ + internal void GetNodeNameSize(out uint pcbNodeNameSize); + + internal void GetNodeName(uint cbNodeNameBufferSize, [MarshalAs(UnmanagedType.LPWStr)] out string pcbNodeSize); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransaction.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransaction.cs new file mode 100644 index 00000000000000..e516d2c0d038fc --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransaction.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using System.Transactions.Oletx; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms686531(v=vs.85) +[ComImport, Guid(Guids.IID_ITransaction), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface ITransaction +{ + void Commit([MarshalAs(UnmanagedType.Bool)] bool fRetainingt, [MarshalAs(UnmanagedType.U4)] OletxXacttc grfTC, uint grfRM); + + void Abort(IntPtr reason, [MarshalAs(UnmanagedType.Bool)] bool retaining, [MarshalAs(UnmanagedType.Bool)] bool async); + + void GetTransactionInfo(out OletxXactTransInfo xactInfo); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionCloner.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionCloner.cs new file mode 100644 index 00000000000000..7ce0b361bdf5ff --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionCloner.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms684377(v=vs.85) +[ComImport, Guid("02656950-2152-11d0-944C-00A0C905416E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface ITransactionCloner +{ + void CloneWithCommitDisabled([MarshalAs(UnmanagedType.Interface)] out ITransaction ppITransaction); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionDispenser.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionDispenser.cs new file mode 100644 index 00000000000000..c45ea2c5f11631 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionDispenser.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Transactions.DtcProxyShim; +using System.Transactions.Oletx; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms679525(v=vs.85) +[ComImport, Guid(Guids.IID_ITransactionDispenser), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface ITransactionDispenser +{ + void GetOptionsObject([MarshalAs(UnmanagedType.Interface)] out ITransactionOptions ppOptions); + + void BeginTransaction( + IntPtr punkOuter, + [MarshalAs(UnmanagedType.I4)] OletxTransactionIsolationLevel isoLevel, + [MarshalAs(UnmanagedType.I4)] OletxTransactionIsoFlags isoFlags, + [MarshalAs(UnmanagedType.Interface)] ITransactionOptions pOptions, + [MarshalAs(UnmanagedType.Interface)] out ITransaction ppTransaction); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionEnlistmentAsync.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionEnlistmentAsync.cs new file mode 100644 index 00000000000000..da738568efd5ec --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionEnlistmentAsync.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms686429(v=vs.85) +[ComImport, Guid("0fb15081-af41-11ce-bd2b-204c4f4f5020"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface ITransactionEnlistmentAsync +{ + void PrepareRequestDone(int hr, IntPtr pmk, IntPtr pboidReason); + + void CommitRequestDone(int hr); + + void AbortRequestDone(int hr); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExport.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExport.cs new file mode 100644 index 00000000000000..02f64369697ae8 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExport.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms678954(v=vs.85) +[ComImport, Guid("0141fda5-8fc0-11ce-bd18-204c4f4f5020"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface ITransactionExport +{ + void Export([MarshalAs(UnmanagedType.Interface)] ITransaction punkTransaction, out uint pcbTransactionCookie); + + void GetTransactionCookie( + [MarshalAs(UnmanagedType.Interface)] ITransaction pITransaction, + uint cbTransactionCookie, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1), Out] byte[] rgbTransactionCookie, + out uint pcbUsed); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExportFactory.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExportFactory.cs new file mode 100644 index 00000000000000..b82c8f5ebac37f --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionExportFactory.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms686771(v=vs.85) +[ComImport, Guid("E1CF9B53-8745-11ce-A9BA-00AA006C3706"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface ITransactionExportFactory +{ + void GetRemoteClassId(out Guid pclsid); + + void Create( + uint cbWhereabouts, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] byte[] rgbWhereabouts, + [MarshalAs(UnmanagedType.Interface)] out ITransactionExport ppExport); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionImport.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionImport.cs new file mode 100644 index 00000000000000..310f7d98a54eb7 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionImport.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms681296(v=vs.85) +[ComImport, Guid("E1CF9B5A-8745-11ce-A9BA-00AA006C3706"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface ITransactionImport +{ + void Import( + uint cbTransactionCookie, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] byte[] rgbTransactionCookie, + Guid piid, + [MarshalAs(UnmanagedType.Interface)] out object ppvTransaction); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionImportWhereabouts.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionImportWhereabouts.cs new file mode 100644 index 00000000000000..28f932e72dbed2 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionImportWhereabouts.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms682783(v=vs.85) +[ComImport, Guid("0141fda4-8fc0-11ce-bd18-204c4f4f5020"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface ITransactionImportWhereabouts +{ + internal void GetWhereaboutsSize(out uint pcbSize); + + internal void GetWhereabouts( + uint cbWhereabouts, + [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0), Out] byte[] rgbWhereabouts, + out uint pcbUsed); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOptions.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOptions.cs new file mode 100644 index 00000000000000..645b4cbcf98b33 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOptions.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms686489(v=vs.85) +[ComImport, Guid("3A6AD9E0-23B9-11cf-AD60-00AA00A74CCD"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface ITransactionOptions +{ + void SetOptions(Xactopt pOptions); + + void GetOptions(); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOutcomeEvents.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOutcomeEvents.cs new file mode 100644 index 00000000000000..d0cabc4d708311 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionOutcomeEvents.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Transactions.Oletx; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms686465(v=vs.85) +[ComImport, Guid("3A6AD9E2-23B9-11cf-AD60-00AA00A74CCD"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] +internal interface ITransactionOutcomeEvents +{ + void Committed([MarshalAs(UnmanagedType.Bool)] bool fRetaining, IntPtr pNewUOW /* always null? */, int hresult); + + void Aborted(IntPtr pboidReason, [MarshalAs(UnmanagedType.Bool)] bool fRetaining, IntPtr pNewUOW, int hresult); + + void HeuristicDecision([MarshalAs(UnmanagedType.U4)] OletxTransactionHeuristic dwDecision, IntPtr pboidReason, int hresult); + + void Indoubt(); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionPhase0EnlistmentAsync.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionPhase0EnlistmentAsync.cs new file mode 100644 index 00000000000000..ca6872fca528ad --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcInterfaces/ITransactionPhase0EnlistmentAsync.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim.DtcInterfaces; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms685087(v=vs.85). _notifications = new(); + + private readonly ConcurrentQueue _cachedOptions = new(); + private readonly ConcurrentQueue _cachedTransmitters = new(); + private readonly ConcurrentQueue _cachedReceivers = new(); + + private readonly EventWaitHandle _eventHandle; + + private ITransactionDispenser _transactionDispenser = null!; // Late-initialized in ConnectToProxy + + internal DtcProxyShimFactory(EventWaitHandle notificationEventHandle) + => _eventHandle = notificationEventHandle; + + // https://docs.microsoft.com/previous-versions/windows/desktop/ms678898(v=vs.85) + [DllImport(Interop.Libraries.Xolehlp, CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] + private static extern void DtcGetTransactionManagerExW( + [MarshalAs(UnmanagedType.LPWStr)] string? pszHost, + [MarshalAs(UnmanagedType.LPWStr)] string? pszTmName, + in Guid riid, + int grfOptions, + object? pvConfigPararms, + [MarshalAs(UnmanagedType.Interface)] out ITransactionDispenser ppvObject); + + [UnconditionalSuppressMessage("Trimming", "IL2050", Justification = "Leave me alone")] + public void ConnectToProxy( + string? nodeName, + Guid resourceManagerIdentifier, + object managedIdentifier, + out bool nodeNameMatches, + out byte[] whereabouts, + out ResourceManagerShim resourceManagerShim) + { + if (RuntimeInformation.ProcessArchitecture == Architecture.X86) + { + throw new PlatformNotSupportedException(SR.DistributedNotSupportOn32Bits); + } + + lock (_proxyInitLock) + { + DtcGetTransactionManagerExW(nodeName, null, Guids.IID_ITransactionDispenser_Guid, 0, null, out ITransactionDispenser? localDispenser); + + // Check to make sure the node name matches. + if (nodeName is not null) + { + var pTmNodeName = (ITmNodeName)localDispenser; + pTmNodeName.GetNodeNameSize(out uint tmNodeNameLength); + pTmNodeName.GetNodeName(tmNodeNameLength, out string tmNodeName); + + nodeNameMatches = tmNodeName == nodeName; + } + else + { + nodeNameMatches = true; + } + + var pImportWhereabouts = (ITransactionImportWhereabouts)localDispenser; + + // Adding retry logic as a work around for MSDTC's GetWhereAbouts/GetWhereAboutsSize API + // which is single threaded and will return XACT_E_ALREADYINPROGRESS if another thread invokes the API. + uint whereaboutsSize = 0; + OletxHelper.Retry(() => pImportWhereabouts.GetWhereaboutsSize(out whereaboutsSize)); + + var tmpWhereabouts = new byte[(int)whereaboutsSize]; + + // Adding retry logic as a work around for MSDTC's GetWhereAbouts/GetWhereAboutsSize API + // which is single threaded and will return XACT_E_ALREADYINPROGRESS if another thread invokes the API. + OletxHelper.Retry(() => + { + pImportWhereabouts.GetWhereabouts(whereaboutsSize, tmpWhereabouts, out uint pcbUsed); + Debug.Assert(pcbUsed == tmpWhereabouts.Length); + }); + whereabouts = tmpWhereabouts; + + // Now we need to create the internal resource manager. + var rmFactory = (IResourceManagerFactory2)localDispenser; + + var rmNotifyShim = new ResourceManagerNotifyShim(this, managedIdentifier); + var rmShim = new ResourceManagerShim(this); + + OletxHelper.Retry(() => + { + rmFactory.CreateEx( + resourceManagerIdentifier, + "System.Transactions.InternalRM", + rmNotifyShim, + Guids.IID_IResourceManager_Guid, + out object? rm); + + rmShim.ResourceManager = (IResourceManager)rm; + }); + + resourceManagerShim = rmShim; + _transactionDispenser = localDispenser; + } + } + + internal void NewNotification(NotificationShimBase notification) + { + lock (_notificationLock) + { + _notifications.Enqueue(notification); + } + + _eventHandle.Set(); + } + + public void ReleaseNotificationLock() + => Monitor.Exit(_notificationLock); + + public void BeginTransaction( + uint timeout, + OletxTransactionIsolationLevel isolationLevel, + object? managedIdentifier, + out Guid transactionIdentifier, + out TransactionShim transactionShim) + { + ITransactionOptions options = GetCachedOptions(); + + try + { + var xactopt = new Xactopt(timeout, string.Empty); + options.SetOptions(xactopt); + + _transactionDispenser.BeginTransaction(IntPtr.Zero, isolationLevel, OletxTransactionIsoFlags.ISOFLAG_NONE, options, out ITransaction? pTx); + + SetupTransaction(pTx, managedIdentifier, out transactionIdentifier, out OletxTransactionIsolationLevel localIsoLevel, out transactionShim); + } + finally + { + ReturnCachedOptions(options); + } + } + + public void CreateResourceManager( + Guid resourceManagerIdentifier, + OletxResourceManager managedIdentifier, + out ResourceManagerShim resourceManagerShim) + { + var rmFactory = (IResourceManagerFactory2)_transactionDispenser; + + var rmNotifyShim = new ResourceManagerNotifyShim(this, managedIdentifier); + var rmShim = new ResourceManagerShim(this); + + OletxHelper.Retry(() => + { + rmFactory.CreateEx( + resourceManagerIdentifier, + "System.Transactions.ResourceManager", + rmNotifyShim, + Guids.IID_IResourceManager_Guid, + out object? rm); + + rmShim.ResourceManager = (IResourceManager)rm; + }); + + resourceManagerShim = rmShim; + } + + public void Import( + byte[] cookie, + OutcomeEnlistment managedIdentifier, + out Guid transactionIdentifier, + out OletxTransactionIsolationLevel isolationLevel, + out TransactionShim transactionShim) + { + var txImport = (ITransactionImport)_transactionDispenser; + txImport.Import(Convert.ToUInt32(cookie.Length), cookie, Guids.IID_ITransaction_Guid, out object? tx); + + SetupTransaction((ITransaction)tx, managedIdentifier, out transactionIdentifier, out isolationLevel, out transactionShim); + } + + public void ReceiveTransaction( + byte[] propagationToken, + OutcomeEnlistment managedIdentifier, + out Guid transactionIdentifier, + out OletxTransactionIsolationLevel isolationLevel, + out TransactionShim transactionShim) + { + ITransactionReceiver receiver = GetCachedReceiver(); + + try + { + receiver.UnmarshalPropagationToken( + Convert.ToUInt32(propagationToken.Length), + propagationToken, + out ITransaction? tx); + + SetupTransaction(tx, managedIdentifier, out transactionIdentifier, out isolationLevel, out transactionShim); + } + finally + { + ReturnCachedReceiver(receiver); + } + } + + public void CreateTransactionShim( + IDtcTransaction transactionNative, + OutcomeEnlistment managedIdentifier, + out Guid transactionIdentifier, + out OletxTransactionIsolationLevel isolationLevel, + out TransactionShim transactionShim) + { + var cloner = (ITransactionCloner)transactionNative; + cloner.CloneWithCommitDisabled(out ITransaction transaction); + + SetupTransaction(transaction, managedIdentifier, out transactionIdentifier, out isolationLevel, out transactionShim); + } + + internal ITransactionExportFactory ExportFactory + => (ITransactionExportFactory)_transactionDispenser; + + internal ITransactionVoterFactory2 VoterFactory + => (ITransactionVoterFactory2)_transactionDispenser; + + public void GetNotification( + out object? managedIdentifier, + out ShimNotificationType shimNotificationType, + out bool isSinglePhase, + out bool abortingHint, + out bool releaseLock, + out byte[]? prepareInfo) + { + managedIdentifier = null; + shimNotificationType = ShimNotificationType.None; + isSinglePhase = false; + abortingHint = false; + releaseLock = false; + prepareInfo = null; + + Monitor.Enter(_notificationLock); + + bool entryRemoved = _notifications.TryDequeue(out NotificationShimBase? notification); + if (entryRemoved) + { + managedIdentifier = notification!.EnlistmentIdentifier; + shimNotificationType = notification.NotificationType; + isSinglePhase = notification.IsSinglePhase; + abortingHint = notification.AbortingHint; + prepareInfo = notification.PrepareInfo; + } + + // We release the lock if we didn't find an entry or if the notification type + // is NOT ResourceManagerTMDownNotify. If it is a ResourceManagerTMDownNotify, the managed + // code will call ReleaseNotificationLock after processing the TMDown. We need to prevent + // other notifications from being processed while we are processing TMDown. But we don't want + // to force 3 roundtrips to this NotificationShimFactory for all notifications ( 1 to grab the lock, + // one to get the notification, and one to release the lock). + if (!entryRemoved || shimNotificationType != ShimNotificationType.ResourceManagerTmDownNotify) + { + Monitor.Exit(_notificationLock); + } + else + { + releaseLock = true; + } + } + + private void SetupTransaction( + ITransaction transaction, + object? managedIdentifier, + out Guid pTransactionIdentifier, + out OletxTransactionIsolationLevel pIsolationLevel, + out TransactionShim ppTransactionShim) + { + var transactionNotifyShim = new TransactionNotifyShim(this, managedIdentifier); + + // Get the transaction id. + transaction.GetTransactionInfo(out OletxXactTransInfo xactInfo); + + // Register for outcome events. + var pContainer = (IConnectionPointContainer)transaction; + var guid = Guids.IID_ITransactionOutcomeEvents_Guid; + pContainer.FindConnectionPoint(ref guid, out IConnectionPoint? pConnPoint); + pConnPoint!.Advise(transactionNotifyShim, out int connPointCookie); + + var transactionShim = new TransactionShim(this, transactionNotifyShim, transaction); + pTransactionIdentifier = xactInfo.Uow; + pIsolationLevel = xactInfo.IsoLevel; + ppTransactionShim = transactionShim; + } + + private ITransactionOptions GetCachedOptions() + { + if (_cachedOptions.TryDequeue(out ITransactionOptions? options)) + { + return options; + } + + _transactionDispenser.GetOptionsObject(out ITransactionOptions? transactionOptions); + return transactionOptions; + } + + internal void ReturnCachedOptions(ITransactionOptions options) + => _cachedOptions.Enqueue(options); + + internal ITransactionTransmitter GetCachedTransmitter(ITransaction transaction) + { + if (!_cachedTransmitters.TryDequeue(out ITransactionTransmitter? transmitter)) + { + var transmitterFactory = (ITransactionTransmitterFactory)_transactionDispenser; + transmitterFactory.Create(out transmitter); + } + + transmitter.Set(transaction); + + return transmitter; + } + + internal void ReturnCachedTransmitter(ITransactionTransmitter transmitter) + => _cachedTransmitters.Enqueue(transmitter); + + internal ITransactionReceiver GetCachedReceiver() + { + if (_cachedReceivers.TryDequeue(out ITransactionReceiver? receiver)) + { + return receiver; + } + + var receiverFactory = (ITransactionReceiverFactory)_transactionDispenser; + receiverFactory.Create(out ITransactionReceiver transactionReceiver); + + return transactionReceiver; + } + + internal void ReturnCachedReceiver(ITransactionReceiver receiver) + => _cachedReceivers.Enqueue(receiver); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/EnlistmentNotifyShim.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/EnlistmentNotifyShim.cs new file mode 100644 index 00000000000000..b8f46ce85bd7ff --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/EnlistmentNotifyShim.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading; +using System.Transactions.DtcProxyShim.DtcInterfaces; +using System.Transactions.Oletx; + +namespace System.Transactions.DtcProxyShim; + +internal sealed class EnlistmentNotifyShim : NotificationShimBase, ITransactionResourceAsync +{ + internal ITransactionEnlistmentAsync? EnlistmentAsync; + + // MSDTCPRX behaves unpredictably in that if the TM is down when we vote + // no it will send an AbortRequest. However if the TM does not go down + // the enlistment is not go down the AbortRequest is not sent. This + // makes reliable cleanup a problem. To work around this the enlisment + // shim will eat the AbortRequest if it knows that it has voted No. + + // On Win2k this same problem applies to responding Committed to a + // single phase commit request. + private bool _ignoreSpuriousProxyNotifications; + + internal EnlistmentNotifyShim(DtcProxyShimFactory shimFactory, OletxEnlistment enlistmentIdentifier) + : base(shimFactory, enlistmentIdentifier) + { + _ignoreSpuriousProxyNotifications = false; + } + + internal void SetIgnoreSpuriousProxyNotifications() + => _ignoreSpuriousProxyNotifications = true; + + public void PrepareRequest(bool fRetaining, OletxXactRm grfRM, bool fWantMoniker, bool fSinglePhase) + { + ITransactionEnlistmentAsync? pEnlistmentAsync = Interlocked.Exchange(ref EnlistmentAsync, null); + + if (pEnlistmentAsync is null) + { + throw new InvalidOperationException("Unexpected null in pEnlistmentAsync"); + } + + var pPrepareInfo = (IPrepareInfo)pEnlistmentAsync; + pPrepareInfo.GetPrepareInfoSize(out uint prepareInfoLength); + var prepareInfoBuffer = new byte[prepareInfoLength]; + pPrepareInfo.GetPrepareInfo(prepareInfoBuffer); + + PrepareInfo = prepareInfoBuffer; + IsSinglePhase = fSinglePhase; + NotificationType = ShimNotificationType.PrepareRequestNotify; + ShimFactory.NewNotification(this); + } + + public void CommitRequest(OletxXactRm grfRM, IntPtr pNewUOW) + { + NotificationType = ShimNotificationType.CommitRequestNotify; + ShimFactory.NewNotification(this); + } + + public void AbortRequest(IntPtr pboidReason, bool fRetaining, IntPtr pNewUOW) + { + if (!_ignoreSpuriousProxyNotifications) + { + // Only create the notification if we have not already voted. + NotificationType = ShimNotificationType.AbortRequestNotify; + ShimFactory.NewNotification(this); + } + } + + public void TMDown() + { + NotificationType = ShimNotificationType.ResourceManagerTmDownNotify; + ShimFactory.NewNotification(this); + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/EnlistmentShim.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/EnlistmentShim.cs new file mode 100644 index 00000000000000..844bc36f9d76c7 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/EnlistmentShim.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Transactions.DtcProxyShim.DtcInterfaces; + +namespace System.Transactions.DtcProxyShim; + +internal sealed class EnlistmentShim +{ + private readonly EnlistmentNotifyShim _enlistmentNotifyShim; + + internal ITransactionEnlistmentAsync? EnlistmentAsync { get; set; } + + internal EnlistmentShim(EnlistmentNotifyShim notifyShim) + => _enlistmentNotifyShim = notifyShim; + + public void PrepareRequestDone(OletxPrepareVoteType voteType) + { + var voteHr = OletxHelper.S_OK; + var releaseEnlistment = false; + + switch (voteType) + { + case OletxPrepareVoteType.ReadOnly: + { + // On W2k Proxy may send a spurious aborted notification if the TM goes down. + _enlistmentNotifyShim.SetIgnoreSpuriousProxyNotifications(); + voteHr = OletxHelper.XACT_S_READONLY; + break; + } + + case OletxPrepareVoteType.SinglePhase: + { + // On W2k Proxy may send a spurious aborted notification if the TM goes down. + _enlistmentNotifyShim.SetIgnoreSpuriousProxyNotifications(); + voteHr = OletxHelper.XACT_S_SINGLEPHASE; + break; + } + + case OletxPrepareVoteType.Prepared: + { + voteHr = OletxHelper.S_OK; + break; + } + + case OletxPrepareVoteType.Failed: + { + // Proxy may send a spurious aborted notification if the TM goes down. + _enlistmentNotifyShim.SetIgnoreSpuriousProxyNotifications(); + voteHr = OletxHelper.E_FAIL; + break; + } + + case OletxPrepareVoteType.InDoubt: + { + releaseEnlistment = true; + break; + } + + default: // unexpected, vote no. + { + voteHr = OletxHelper.E_FAIL; + break; + } + } + + if (!releaseEnlistment) + { + EnlistmentAsync!.PrepareRequestDone( + voteHr, + IntPtr.Zero, + IntPtr.Zero); + } + } + + public void CommitRequestDone() + => EnlistmentAsync!.CommitRequestDone(OletxHelper.S_OK); + + public void AbortRequestDone() + => EnlistmentAsync!.AbortRequestDone(OletxHelper.S_OK); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Guids.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Guids.cs new file mode 100644 index 00000000000000..9e395a2154037b --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Guids.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace System.Transactions.DtcProxyShim; + +internal static class Guids +{ + internal const string IID_ITransactionDispenser = "3A6AD9E1-23B9-11cf-AD60-00AA00A74CCD"; + internal const string IID_IResourceManager = "13741D21-87EB-11CE-8081-0080C758527E"; + internal const string IID_ITransactionOutcomeEvents = "3A6AD9E2-23B9-11cf-AD60-00AA00A74CCD"; + internal const string IID_ITransaction = "0fb15084-af41-11ce-bd2b-204c4f4f5020"; + + internal static readonly Guid IID_ITransactionDispenser_Guid = Guid.Parse(IID_ITransactionDispenser); + internal static readonly Guid IID_IResourceManager_Guid = Guid.Parse(IID_IResourceManager); + internal static readonly Guid IID_ITransactionOutcomeEvents_Guid = Guid.Parse(IID_ITransactionOutcomeEvents); + internal static readonly Guid IID_ITransaction_Guid = Guid.Parse(IID_ITransaction); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/NativeEnums.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/NativeEnums.cs new file mode 100644 index 00000000000000..f852af728def2e --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/NativeEnums.cs @@ -0,0 +1,143 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Transactions.DtcProxyShim; + +internal enum ShimNotificationType +{ + None = 0, + Phase0RequestNotify = 1, + VoteRequestNotify = 2, + PrepareRequestNotify = 3, + CommitRequestNotify = 4, + AbortRequestNotify = 5, + CommittedNotify = 6, + AbortedNotify = 7, + InDoubtNotify = 8, + EnlistmentTmDownNotify = 9, + ResourceManagerTmDownNotify = 10 +} + +internal enum OletxPrepareVoteType +{ + ReadOnly = 0, + SinglePhase = 1, + Prepared = 2, + Failed = 3, + InDoubt = 4 +} + +internal enum OletxTransactionOutcome +{ + NotKnownYet = 0, + Committed = 1, + Aborted = 2 +} + +internal enum OletxTransactionIsolationLevel +{ + ISOLATIONLEVEL_UNSPECIFIED = -1, + ISOLATIONLEVEL_CHAOS = 0x10, + ISOLATIONLEVEL_READUNCOMMITTED = 0x100, + ISOLATIONLEVEL_BROWSE = 0x100, + ISOLATIONLEVEL_CURSORSTABILITY = 0x1000, + ISOLATIONLEVEL_READCOMMITTED = 0x1000, + ISOLATIONLEVEL_REPEATABLEREAD = 0x10000, + ISOLATIONLEVEL_SERIALIZABLE = 0x100000, + ISOLATIONLEVEL_ISOLATED = 0x100000 +} + +[Flags] +internal enum OletxTransactionIsoFlags +{ + ISOFLAG_NONE = 0, + ISOFLAG_RETAIN_COMMIT_DC = 1, + ISOFLAG_RETAIN_COMMIT = 2, + ISOFLAG_RETAIN_COMMIT_NO = 3, + ISOFLAG_RETAIN_ABORT_DC = 4, + ISOFLAG_RETAIN_ABORT = 8, + ISOFLAG_RETAIN_ABORT_NO = 12, + ISOFLAG_RETAIN_DONTCARE = ISOFLAG_RETAIN_COMMIT_DC | ISOFLAG_RETAIN_ABORT_DC, + ISOFLAG_RETAIN_BOTH = ISOFLAG_RETAIN_COMMIT | ISOFLAG_RETAIN_ABORT, + ISOFLAG_RETAIN_NONE = ISOFLAG_RETAIN_COMMIT_NO | ISOFLAG_RETAIN_ABORT_NO, + ISOFLAG_OPTIMISTIC = 16, + ISOFLAG_READONLY = 32 +} + +internal enum OletxXacttc : uint +{ + XACTTC_NONE = 0, + XACTTC_SYNC_PHASEONE = 1, + XACTTC_SYNC_PHASETWO = 2, + XACTTC_SYNC = 2, + XACTTC_ASYNC_PHASEONE = 4, + XACTTC_ASYNC = 4 +} + +internal enum OletxXactRm : uint +{ + XACTRM_OPTIMISTICLASTWINS = 1, + XACTRM_NOREADONLYPREPARES = 2 +} + +internal enum OletxTransactionStatus +{ + OLETX_TRANSACTION_STATUS_NONE = 0, + OLETX_TRANSACTION_STATUS_OPENNORMAL = 0x1, + OLETX_TRANSACTION_STATUS_OPENREFUSED = 0x2, + OLETX_TRANSACTION_STATUS_PREPARING = 0x4, + OLETX_TRANSACTION_STATUS_PREPARED = 0x8, + OLETX_TRANSACTION_STATUS_PREPARERETAINING = 0x10, + OLETX_TRANSACTION_STATUS_PREPARERETAINED = 0x20, + OLETX_TRANSACTION_STATUS_COMMITTING = 0x40, + OLETX_TRANSACTION_STATUS_COMMITRETAINING = 0x80, + OLETX_TRANSACTION_STATUS_ABORTING = 0x100, + OLETX_TRANSACTION_STATUS_ABORTED = 0x200, + OLETX_TRANSACTION_STATUS_COMMITTED = 0x400, + OLETX_TRANSACTION_STATUS_HEURISTIC_ABORT = 0x800, + OLETX_TRANSACTION_STATUS_HEURISTIC_COMMIT = 0x1000, + OLETX_TRANSACTION_STATUS_HEURISTIC_DAMAGE = 0x2000, + OLETX_TRANSACTION_STATUS_HEURISTIC_DANGER = 0x4000, + OLETX_TRANSACTION_STATUS_FORCED_ABORT = 0x8000, + OLETX_TRANSACTION_STATUS_FORCED_COMMIT = 0x10000, + OLETX_TRANSACTION_STATUS_INDOUBT = 0x20000, + OLETX_TRANSACTION_STATUS_CLOSED = 0x40000, + OLETX_TRANSACTION_STATUS_OPEN = 0x3, + OLETX_TRANSACTION_STATUS_NOTPREPARED = 0x7ffc3, + OLETX_TRANSACTION_STATUS_ALL = 0x7ffff +} + +internal enum OletxTransactionHeuristic : uint +{ + XACTHEURISTIC_ABORT = 1, + XACTHEURISTIC_COMMIT = 2, + XACTHEURISTIC_DAMAGE = 3, + XACTHEURISTIC_DANGER = 4 +} + +internal enum OletxXactStat +{ + XACTSTAT_NONE = 0, + XACTSTAT_OPENNORMAL = 0x1, + XACTSTAT_OPENREFUSED = 0x2, + XACTSTAT_PREPARING = 0x4, + XACTSTAT_PREPARED = 0x8, + XACTSTAT_PREPARERETAINING = 0x10, + XACTSTAT_PREPARERETAINED = 0x20, + XACTSTAT_COMMITTING = 0x40, + XACTSTAT_COMMITRETAINING = 0x80, + XACTSTAT_ABORTING = 0x100, + XACTSTAT_ABORTED = 0x200, + XACTSTAT_COMMITTED = 0x400, + XACTSTAT_HEURISTIC_ABORT = 0x800, + XACTSTAT_HEURISTIC_COMMIT = 0x1000, + XACTSTAT_HEURISTIC_DAMAGE = 0x2000, + XACTSTAT_HEURISTIC_DANGER = 0x4000, + XACTSTAT_FORCED_ABORT = 0x8000, + XACTSTAT_FORCED_COMMIT = 0x10000, + XACTSTAT_INDOUBT = 0x20000, + XACTSTAT_CLOSED = 0x40000, + XACTSTAT_OPEN = 0x3, + XACTSTAT_NOTPREPARED = 0x7ffc3, + XACTSTAT_ALL = 0x7ffff +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/NotificationShimBase.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/NotificationShimBase.cs new file mode 100644 index 00000000000000..bed600488c502c --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/NotificationShimBase.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Transactions.DtcProxyShim; + +internal class NotificationShimBase +{ + public object? EnlistmentIdentifier; + public ShimNotificationType NotificationType; + public bool AbortingHint; + public bool IsSinglePhase; + public byte[]? PrepareInfo; + + protected DtcProxyShimFactory ShimFactory; + + internal NotificationShimBase(DtcProxyShimFactory shimFactory, object? enlistmentIdentifier) + { + ShimFactory = shimFactory; + EnlistmentIdentifier = enlistmentIdentifier; + NotificationType = ShimNotificationType.None; + AbortingHint = false; + IsSinglePhase = false; + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/OletxHelper.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/OletxHelper.cs new file mode 100644 index 00000000000000..ce0bb80e059971 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/OletxHelper.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Threading; + +namespace System.Transactions.DtcProxyShim; + +internal static class OletxHelper +{ + private const int RetryInterval = 50; // in milliseconds + private const int MaxRetryCount = 100; + + internal static int S_OK = 0; + internal static int E_FAIL = -2147467259; // 0x80004005, -2147467259 + internal static int XACT_S_READONLY = 315394; // 0x0004D002, 315394 + internal static int XACT_S_SINGLEPHASE = 315401; // 0x0004D009, 315401 + internal static int XACT_E_ABORTED = -2147168231; // 0x8004D019, -2147168231 + internal static int XACT_E_NOTRANSACTION = -2147168242; // 0x8004D00E, -2147168242 + internal static int XACT_E_CONNECTION_DOWN = -2147168228; // 0x8004D01C, -2147168228 + internal static int XACT_E_REENLISTTIMEOUT = -2147168226; // 0x8004D01E, -2147168226 + internal static int XACT_E_RECOVERYALREADYDONE = -2147167996; // 0x8004D104, -2147167996 + internal static int XACT_E_TMNOTAVAILABLE = -2147168229; // 0x8004d01b, -2147168229 + internal static int XACT_E_INDOUBT = -2147168234; // 0x8004d016, + internal static int XACT_E_ALREADYINPROGRESS = -2147168232; // x08004d018, + internal static int XACT_E_TOOMANY_ENLISTMENTS = -2147167999; // 0x8004d101 + internal static int XACT_E_PROTOCOL = -2147167995; // 8004d105 + internal static int XACT_E_FIRST = -2147168256; // 0x8004D000 + internal static int XACT_E_LAST = -2147168215; // 0x8004D029 + internal static int XACT_E_NOTSUPPORTED = -2147168241; // 0x8004D00F + internal static int XACT_E_NETWORK_TX_DISABLED = -2147168220; // 0x8004D024 + + internal static void Retry(Action action) + { + int nRetries = MaxRetryCount; + + while (true) + { + try + { + action(); + return; + } + catch (COMException e) when (e.ErrorCode == XACT_E_ALREADYINPROGRESS) + { + if (--nRetries == 0) + { + throw; + } + + Thread.Sleep(RetryInterval); + } + } + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/OletxXactTransInfo.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/OletxXactTransInfo.cs new file mode 100644 index 00000000000000..1d097d97b78d5e --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/OletxXactTransInfo.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim; + +[ComVisible(false)] +[StructLayout(LayoutKind.Sequential)] +internal struct OletxXactTransInfo +{ + internal Guid Uow; + internal OletxTransactionIsolationLevel IsoLevel; + internal OletxTransactionIsoFlags IsoFlags; + internal int GrfTCSupported; + internal int GrfRMSupported; + internal int GrfTCSupportedRetaining; + internal int GrfRMSupportedRetaining; +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Phase0NotifyShim.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Phase0NotifyShim.cs new file mode 100644 index 00000000000000..dc2d19226225cf --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Phase0NotifyShim.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Transactions.DtcProxyShim.DtcInterfaces; +using System.Transactions.Oletx; + +namespace System.Transactions.DtcProxyShim; + +internal sealed class Phase0NotifyShim : NotificationShimBase, ITransactionPhase0NotifyAsync +{ + internal Phase0NotifyShim(DtcProxyShimFactory shimFactory, object enlistmentIdentifier) + : base(shimFactory, enlistmentIdentifier) + { + } + + public void Phase0Request(bool fAbortHint) + { + AbortingHint = fAbortHint; + NotificationType = ShimNotificationType.Phase0RequestNotify; + ShimFactory.NewNotification(this); + } + + public void EnlistCompleted(int status) + { + // We don't care about these. The managed code waited for the enlistment to be completed. + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Phase0Shim.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Phase0Shim.cs new file mode 100644 index 00000000000000..511c52a7f7eb36 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Phase0Shim.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Transactions.DtcProxyShim.DtcInterfaces; + +namespace System.Transactions.DtcProxyShim; + +internal sealed class Phase0EnlistmentShim +{ + private Phase0NotifyShim _phase0NotifyShim; + + internal ITransactionPhase0EnlistmentAsync? Phase0EnlistmentAsync { get; set; } + + internal Phase0EnlistmentShim(Phase0NotifyShim notifyShim) + => _phase0NotifyShim = notifyShim; + + public void Unenlist() + { + // VSWhidbey 405624 - There is a race between the enlistment and abort of a transaction + // that could cause out proxy interface to already be released when Unenlist is called. + Phase0EnlistmentAsync?.Unenlist(); + } + + public void Phase0Done(bool voteYes) + { + if (voteYes) + { + try + { + Phase0EnlistmentAsync!.Phase0Done(); + } + catch (COMException e) when (e.ErrorCode == OletxHelper.XACT_E_PROTOCOL) + { + // Deal with the proxy bug where we get a Phase0Request(false) on a + // TMDown and the proxy object state is not changed. + return; + } + } + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/ResourceManagerNotifyShim.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/ResourceManagerNotifyShim.cs new file mode 100644 index 00000000000000..53ac780b696595 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/ResourceManagerNotifyShim.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Transactions.Oletx; +using System.Transactions.DtcProxyShim.DtcInterfaces; + +namespace System.Transactions.DtcProxyShim; + +internal sealed class ResourceManagerNotifyShim : NotificationShimBase, IResourceManagerSink +{ + internal ResourceManagerNotifyShim( + DtcProxyShimFactory shimFactory, + object enlistmentIdentifier) + : base(shimFactory, enlistmentIdentifier) + { + } + + public void TMDown() + { + NotificationType = ShimNotificationType.ResourceManagerTmDownNotify; + ShimFactory.NewNotification(this); + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/ResourceManagerShim.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/ResourceManagerShim.cs new file mode 100644 index 00000000000000..f9dff75d4b7d7a --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/ResourceManagerShim.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using System.Transactions.Oletx; +using System.Transactions.DtcProxyShim.DtcInterfaces; + +namespace System.Transactions.DtcProxyShim; + +internal sealed class ResourceManagerShim +{ + private readonly DtcProxyShimFactory _shimFactory; + + internal ResourceManagerShim(DtcProxyShimFactory shimFactory) + => _shimFactory = shimFactory; + + public IResourceManager? ResourceManager { get; set; } + + public void Enlist( + TransactionShim transactionShim, + OletxEnlistment managedIdentifier, + out EnlistmentShim enlistmentShim) + { + var pEnlistmentNotifyShim = new EnlistmentNotifyShim(_shimFactory, managedIdentifier); + var pEnlistmentShim = new EnlistmentShim(pEnlistmentNotifyShim); + + ITransaction transaction = transactionShim.Transaction; + ResourceManager!.Enlist(transaction, pEnlistmentNotifyShim, out Guid txUow, out OletxTransactionIsolationLevel isoLevel, out ITransactionEnlistmentAsync pEnlistmentAsync); + + pEnlistmentNotifyShim.EnlistmentAsync = pEnlistmentAsync; + pEnlistmentShim.EnlistmentAsync = pEnlistmentAsync; + + enlistmentShim = pEnlistmentShim; + } + + public void Reenlist(byte[] prepareInfo, out OletxTransactionOutcome outcome) + { + // Call Reenlist on the proxy, waiting for 5 milliseconds for it to get the outcome. If it doesn't know that outcome in that + // amount of time, tell the caller we don't know the outcome yet. The managed code will reschedule the check by using the + // ReenlistThread. + try + { + ResourceManager!.Reenlist(prepareInfo, (uint)prepareInfo.Length, 5, out OletxXactStat xactStatus); + outcome = xactStatus switch + { + OletxXactStat.XACTSTAT_ABORTED => OletxTransactionOutcome.Aborted, + OletxXactStat.XACTSTAT_COMMITTED => OletxTransactionOutcome.Committed, + _ => OletxTransactionOutcome.Aborted + }; + } + catch (COMException e) when (e.ErrorCode == OletxHelper.XACT_E_REENLISTTIMEOUT) + { + outcome = OletxTransactionOutcome.NotKnownYet; + return; + } + } + + public void ReenlistComplete() + => ResourceManager!.ReenlistmentComplete(); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/TransactionNotifyShim.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/TransactionNotifyShim.cs new file mode 100644 index 00000000000000..d605f13053a009 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/TransactionNotifyShim.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Transactions.DtcProxyShim.DtcInterfaces; + +namespace System.Transactions.DtcProxyShim; + +internal sealed class TransactionNotifyShim : NotificationShimBase, ITransactionOutcomeEvents +{ + internal TransactionNotifyShim(DtcProxyShimFactory shimFactory, object? enlistmentIdentifier) + : base(shimFactory, enlistmentIdentifier) + { + } + + public void Committed(bool fRetaining, IntPtr pNewUOW, int hresult) + { + NotificationType = ShimNotificationType.CommittedNotify; + ShimFactory.NewNotification(this); + } + + public void Aborted(IntPtr pboidReason, bool fRetaining, IntPtr pNewUOW, int hresult) + { + NotificationType = ShimNotificationType.AbortedNotify; + ShimFactory.NewNotification(this); + } + + public void HeuristicDecision(OletxTransactionHeuristic dwDecision, IntPtr pboidReason, int hresult) + { + NotificationType = dwDecision switch + { + OletxTransactionHeuristic.XACTHEURISTIC_ABORT => ShimNotificationType.AbortedNotify, + OletxTransactionHeuristic.XACTHEURISTIC_COMMIT => ShimNotificationType.CommittedNotify, + _ => ShimNotificationType.InDoubtNotify + }; + + ShimFactory.NewNotification(this); + } + + public void Indoubt() + { + NotificationType = ShimNotificationType.InDoubtNotify; + ShimFactory.NewNotification(this); + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/TransactionOutcome.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/TransactionOutcome.cs new file mode 100644 index 00000000000000..eb7ec1d460edb9 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/TransactionOutcome.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Transactions.DtcProxyShim; + +internal enum TransactionOutcome +{ + NotKnownYet = 0, + Committed = 1, + Aborted = 2 +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/TransactionShim.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/TransactionShim.cs new file mode 100644 index 00000000000000..349d051999f529 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/TransactionShim.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Transactions.DtcProxyShim.DtcInterfaces; +using System.Transactions.Oletx; + +namespace System.Transactions.DtcProxyShim; + +internal sealed class TransactionShim +{ + private DtcProxyShimFactory _shimFactory; + private TransactionNotifyShim _transactionNotifyShim; + + internal ITransaction Transaction { get; set; } + + internal TransactionShim(DtcProxyShimFactory shimFactory, TransactionNotifyShim notifyShim, ITransaction transaction) + { + _shimFactory = shimFactory; + _transactionNotifyShim = notifyShim; + Transaction = transaction; + } + + public void Commit() + => Transaction.Commit(false, OletxXacttc.XACTTC_ASYNC, 0); + + public void Abort() + => Transaction.Abort(IntPtr.Zero, false, false); + + public void CreateVoter(OletxPhase1VolatileEnlistmentContainer managedIdentifier, out VoterBallotShim voterBallotShim) + { + var voterNotifyShim = new VoterNotifyShim(_shimFactory, managedIdentifier); + var voterShim = new VoterBallotShim(_shimFactory, voterNotifyShim); + _shimFactory.VoterFactory.Create(Transaction, voterNotifyShim, out ITransactionVoterBallotAsync2 voterBallot); + voterShim.VoterBallotAsync2 = voterBallot; + voterBallotShim = voterShim; + } + + public void Export(byte[] whereabouts, out byte[] cookieBuffer) + { + _shimFactory.ExportFactory.Create((uint)whereabouts.Length, whereabouts, out ITransactionExport export); + + uint cookieSizeULong = 0; + + OletxHelper.Retry(() => export.Export(Transaction, out cookieSizeULong)); + + var cookieSize = (uint)cookieSizeULong; + var buffer = new byte[cookieSize]; + uint bytesUsed = 0; + + OletxHelper.Retry(() => export.GetTransactionCookie(Transaction, cookieSize, buffer, out bytesUsed)); + + cookieBuffer = buffer; + } + + public void GetITransactionNative(out IDtcTransaction transactionNative) + { + var cloner = (ITransactionCloner)Transaction; + cloner.CloneWithCommitDisabled(out ITransaction returnTransaction); + + transactionNative = (IDtcTransaction)returnTransaction; + } + + public unsafe byte[] GetPropagationToken() + { + ITransactionTransmitter transmitter = _shimFactory.GetCachedTransmitter(Transaction); + + try + { + transmitter.GetPropagationTokenSize(out uint propagationTokenSizeULong); + + var propagationTokenSize = (int)propagationTokenSizeULong; + var propagationToken = new byte[propagationTokenSize]; + + transmitter.MarshalPropagationToken((uint)propagationTokenSize, propagationToken, out uint propagationTokenSizeUsed); + + return propagationToken; + } + finally + { + _shimFactory.ReturnCachedTransmitter(transmitter); + } + } + + public void Phase0Enlist(object managedIdentifier, out Phase0EnlistmentShim phase0EnlistmentShim) + { + var phase0Factory = (ITransactionPhase0Factory)Transaction; + var phase0NotifyShim = new Phase0NotifyShim(_shimFactory, managedIdentifier); + var phase0Shim = new Phase0EnlistmentShim(phase0NotifyShim); + + phase0Factory.Create(phase0NotifyShim, out ITransactionPhase0EnlistmentAsync phase0Async); + phase0Shim.Phase0EnlistmentAsync = phase0Async; + + phase0Async.Enable(); + phase0Async.WaitForEnlistment(); + + phase0EnlistmentShim = phase0Shim; + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/VoterNotifyShim.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/VoterNotifyShim.cs new file mode 100644 index 00000000000000..fad9958acbef99 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/VoterNotifyShim.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Transactions.DtcProxyShim.DtcInterfaces; +using System.Transactions.Oletx; + +namespace System.Transactions.DtcProxyShim; + +internal sealed class VoterNotifyShim : NotificationShimBase, ITransactionVoterNotifyAsync2 +{ + internal VoterNotifyShim(DtcProxyShimFactory shimFactory, object enlistmentIdentifier) + : base(shimFactory, enlistmentIdentifier) + { + } + + public void VoteRequest() + { + NotificationType = ShimNotificationType.VoteRequestNotify; + ShimFactory.NewNotification(this); + } + + public void Committed([MarshalAs(UnmanagedType.Bool)] bool fRetaining, IntPtr pNewUOW, uint hresult) + { + NotificationType = ShimNotificationType.CommittedNotify; + ShimFactory.NewNotification(this); + } + + public void Aborted(IntPtr pboidReason, [MarshalAs(UnmanagedType.Bool)] bool fRetaining, IntPtr pNewUOW, uint hresult) + { + NotificationType = ShimNotificationType.AbortedNotify; + ShimFactory.NewNotification(this); + } + + public void HeuristicDecision([MarshalAs(UnmanagedType.U4)] OletxTransactionHeuristic dwDecision, IntPtr pboidReason, uint hresult) + { + NotificationType = dwDecision switch { + OletxTransactionHeuristic.XACTHEURISTIC_ABORT => ShimNotificationType.AbortedNotify, + OletxTransactionHeuristic.XACTHEURISTIC_COMMIT => ShimNotificationType.CommittedNotify, + _ => ShimNotificationType.InDoubtNotify + }; + + ShimFactory.NewNotification(this); + } + + public void Indoubt() + { + NotificationType = ShimNotificationType.InDoubtNotify; + ShimFactory.NewNotification(this); + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/VoterShim.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/VoterShim.cs new file mode 100644 index 00000000000000..db354738f7fc1e --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/VoterShim.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Transactions.DtcProxyShim.DtcInterfaces; + +namespace System.Transactions.DtcProxyShim; + +internal sealed class VoterBallotShim +{ + private VoterNotifyShim _voterNotifyShim; + + internal ITransactionVoterBallotAsync2? VoterBallotAsync2 { get; set; } + + internal VoterBallotShim(DtcProxyShimFactory shimFactory, VoterNotifyShim notifyShim) + => _voterNotifyShim = notifyShim; + + public void Vote(bool voteYes) + { + int voteHr = OletxHelper.S_OK; + + if (!voteYes) + { + voteHr = OletxHelper.E_FAIL; + } + + VoterBallotAsync2!.VoteRequestDone(voteHr, IntPtr.Zero); + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Xactopt.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Xactopt.cs new file mode 100644 index 00000000000000..2f6bac5637eeb2 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/Xactopt.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +namespace System.Transactions.DtcProxyShim; + +// https://docs.microsoft.com/previous-versions/windows/desktop/ms679195(v=vs.85) +[StructLayout(LayoutKind.Sequential)] +internal struct Xactopt +{ + internal Xactopt(uint ulTimeout, string szDescription) + => (UlTimeout, SzDescription) = (ulTimeout, szDescription); + + public uint UlTimeout; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 40)] + public string SzDescription; +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DurableEnlistmentState.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DurableEnlistmentState.cs index 8c0a9c3e8464bc..f434ea6a67eab7 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DurableEnlistmentState.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DurableEnlistmentState.cs @@ -96,7 +96,7 @@ internal override void EnterState(InternalEnlistment enlistment) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.EnlistmentStatus(enlistment, NotificationCall.Rollback); + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, enlistment.EnlistmentTraceId, NotificationCall.Rollback); } // Send the Rollback notification to the enlistment @@ -147,7 +147,7 @@ internal override void EnterState(InternalEnlistment enlistment) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.EnlistmentStatus(enlistment, NotificationCall.SinglePhaseCommit); + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, enlistment.EnlistmentTraceId, NotificationCall.SinglePhaseCommit); } // Send the Commit notification to the enlistment diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/Enlistment.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/Enlistment.cs index e19dae454de151..7d23de2aad39dc 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/Enlistment.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/Enlistment.cs @@ -29,7 +29,7 @@ internal interface IPromotedEnlistment byte[] GetRecoveryInformation(); - InternalEnlistment InternalEnlistment + InternalEnlistment? InternalEnlistment { get; set; @@ -270,36 +270,28 @@ void ISinglePhaseNotificationInternal.SinglePhaseCommit(IPromotedEnlistment sing } } - void IEnlistmentNotificationInternal.Prepare( - IPromotedEnlistment preparingEnlistment - ) + void IEnlistmentNotificationInternal.Prepare(IPromotedEnlistment preparingEnlistment) { Debug.Assert(_twoPhaseNotifications != null); _promotedEnlistment = preparingEnlistment; _twoPhaseNotifications.Prepare(PreparingEnlistment); } - void IEnlistmentNotificationInternal.Commit( - IPromotedEnlistment enlistment - ) + void IEnlistmentNotificationInternal.Commit(IPromotedEnlistment enlistment) { Debug.Assert(_twoPhaseNotifications != null); _promotedEnlistment = enlistment; _twoPhaseNotifications.Commit(Enlistment); } - void IEnlistmentNotificationInternal.Rollback( - IPromotedEnlistment enlistment - ) + void IEnlistmentNotificationInternal.Rollback(IPromotedEnlistment enlistment) { Debug.Assert(_twoPhaseNotifications != null); _promotedEnlistment = enlistment; _twoPhaseNotifications.Rollback(Enlistment); } - void IEnlistmentNotificationInternal.InDoubt( - IPromotedEnlistment enlistment - ) + void IEnlistmentNotificationInternal.InDoubt(IPromotedEnlistment enlistment) { Debug.Assert(_twoPhaseNotifications != null); _promotedEnlistment = enlistment; diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/IDtcTransaction.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/IDtcTransaction.cs new file mode 100644 index 00000000000000..e0796c1b3c622f --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/IDtcTransaction.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Transactions +{ + [ComImport] + [Guid("0fb15084-af41-11ce-bd2b-204c4f4f5020")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IDtcTransaction + { + void Commit(int retaining, [MarshalAs(UnmanagedType.I4)] int commitType, int reserved); + + void Abort(IntPtr reason, int retaining, int async); + + void GetTransactionInfo(IntPtr transactionInformation); + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/InternalTransaction.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/InternalTransaction.cs index 581c9552a60a51..42e1d4c461ed52 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/InternalTransaction.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/InternalTransaction.cs @@ -5,7 +5,8 @@ using System.Diagnostics; using System.Globalization; using System.Threading; -using System.Transactions.Distributed; +using System.Transactions.Oletx; +using OletxTransaction = System.Transactions.Oletx.OletxTransaction; namespace System.Transactions { @@ -93,7 +94,7 @@ internal long CreationTime // These members are used for promoted waves of dependent blocking clones. The Ltm // does not register individually for each blocking clone created in phase 0. Instead // it multiplexes a single phase 0 blocking clone only created after phase 0 has started. - internal DistributedDependentTransaction? _phase0WaveDependentClone; + internal OletxDependentTransaction? _phase0WaveDependentClone; internal int _phase0WaveDependentCloneCount; // These members are used for keeping track of aborting dependent clones if we promote @@ -105,7 +106,7 @@ internal long CreationTime // on the distributed TM takes care of checking to make sure all the aborting dependent // clones have completed as part of its Prepare processing. These are used in conjunction with // phase1volatiles.dependentclones. - internal DistributedDependentTransaction? _abortingDependentClone; + internal OletxDependentTransaction? _abortingDependentClone; internal int _abortingDependentCloneCount; // When the size of the volatile enlistment array grows increase it by this amount. @@ -119,10 +120,10 @@ internal long CreationTime internal TransactionCompletedEventHandler? _transactionCompletedDelegate; // If this transaction get's promoted keep a reference to the promoted transaction - private DistributedTransaction? _promotedTransaction; - internal DistributedTransaction? PromotedTransaction + private OletxTransaction? _promotedTransaction; + internal OletxTransaction? PromotedTransaction { - get { return _promotedTransaction; } + get => _promotedTransaction; set { Debug.Assert(_promotedTransaction == null, "A transaction can only be promoted once!"); @@ -248,7 +249,7 @@ internal InternalTransaction(TimeSpan timeout, CommittableTransaction committabl } // Construct an internal transaction - internal InternalTransaction(Transaction outcomeSource, DistributedTransaction distributedTx) + internal InternalTransaction(Transaction outcomeSource, OletxTransaction distributedTx) { _promotedTransaction = distributedTx; diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/NonWindowsUnsupported.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/NonWindowsUnsupported.cs new file mode 100644 index 00000000000000..01d25a58cc96a0 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/NonWindowsUnsupported.cs @@ -0,0 +1,154 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.Serialization; +using System.Transactions.Oletx; + +// This files contains non-Windows stubs for Windows-only functionality, so that Sys.Tx can build. The APIs below +// are only ever called when a distributed transaction is needed, and throw PlatformNotSupportedException. + +#pragma warning disable CA1822 + +namespace System.Transactions.Oletx +{ + internal sealed class OletxTransactionManager + { + internal object? NodeName { get; set; } + + internal OletxTransactionManager(string nodeName) + { + } + + internal IPromotedEnlistment ReenlistTransaction( + Guid resourceManagerIdentifier, + byte[] resourceManagerRecoveryInformation, + RecoveringInternalEnlistment internalEnlistment) + => throw NotSupported(); + + internal OletxCommittableTransaction CreateTransaction(TransactionOptions options) + => throw NotSupported(); + + internal void ResourceManagerRecoveryComplete(Guid resourceManagerIdentifier) + => throw NotSupported(); + + internal static byte[] GetWhereabouts() + => throw NotSupported(); + + internal static Transaction GetTransactionFromDtcTransaction(IDtcTransaction transactionNative) + => throw NotSupported(); + + internal static OletxTransaction GetTransactionFromExportCookie(byte[] cookie, Guid txId) + => throw NotSupported(); + + internal static OletxTransaction GetOletxTransactionFromTransmitterPropagationToken(byte[] propagationToken) + => throw NotSupported(); + + internal static Exception NotSupported() + => new PlatformNotSupportedException(SR.DistributedNotSupported); + } + + /// + /// A Transaction object represents a single transaction. It is created by TransactionManager + /// objects through CreateTransaction or through deserialization. Alternatively, the static Create + /// methods provided, which creates a "default" TransactionManager and requests that it create + /// a new transaction with default values. A transaction can only be committed by + /// the client application that created the transaction. If a client application wishes to allow + /// access to the transaction by multiple threads, but wants to prevent those other threads from + /// committing the transaction, the application can make a "clone" of the transaction. Transaction + /// clones have the same capabilities as the original transaction, except for the ability to commit + /// the transaction. + /// + internal class OletxTransaction : ISerializable, IObjectReference + { + internal OletxTransaction() + { + } + + protected OletxTransaction(SerializationInfo serializationInfo, StreamingContext context) + { + //if (serializationInfo == null) + //{ + // throw new ArgumentNullException(nameof(serializationInfo)); + //} + + //throw NotSupported(); + throw new PlatformNotSupportedException(); + } + + internal Exception? InnerException { get; set; } + internal Guid Identifier { get; set; } + internal RealOletxTransaction? RealTransaction { get; set; } + internal TransactionTraceIdentifier TransactionTraceId { get; set; } + internal IsolationLevel IsolationLevel { get; set; } + internal Transaction? SavedLtmPromotedTransaction { get; set; } + + internal IPromotedEnlistment EnlistVolatile( + InternalEnlistment internalEnlistment, + EnlistmentOptions enlistmentOptions) + => throw NotSupported(); + + internal IPromotedEnlistment EnlistDurable( + Guid resourceManagerIdentifier, + DurableInternalEnlistment internalEnlistment, + bool v, + EnlistmentOptions enlistmentOptions) + => throw NotSupported(); + + internal void Rollback() + => throw NotSupported(); + + internal OletxDependentTransaction DependentClone(bool delayCommit) + => throw NotSupported(); + + internal IPromotedEnlistment EnlistVolatile( + VolatileDemultiplexer volatileDemux, + EnlistmentOptions enlistmentOptions) + => throw NotSupported(); + + internal static byte[] GetExportCookie(byte[] whereaboutsCopy) + => throw NotSupported(); + + public object GetRealObject(StreamingContext context) + => throw NotSupported(); + + internal static byte[] GetTransmitterPropagationToken() + => throw NotSupported(); + + internal static IDtcTransaction GetDtcTransaction() + => throw NotSupported(); + + void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingContext context) + { + //if (serializationInfo == null) + //{ + // throw new ArgumentNullException(nameof(serializationInfo)); + //} + + //throw NotSupported(); + + throw new PlatformNotSupportedException(); + } + + internal void Dispose() + { + } + + internal static Exception NotSupported() + => new PlatformNotSupportedException(SR.DistributedNotSupported); + + internal sealed class RealOletxTransaction + { + internal InternalTransaction? InternalTransaction { get; set; } + } + } + + internal sealed class OletxDependentTransaction : OletxTransaction + { + internal void Complete() => throw NotSupported(); + } + + internal sealed class OletxCommittableTransaction : OletxTransaction + { + internal void BeginCommit(InternalTransaction tx) => throw NotSupported(); + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/DtcTransactionManager.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/DtcTransactionManager.cs new file mode 100644 index 00000000000000..b844f208cccd68 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/DtcTransactionManager.cs @@ -0,0 +1,132 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Globalization; +using System.Transactions.DtcProxyShim; + +namespace System.Transactions.Oletx; + +internal sealed class DtcTransactionManager +{ + private readonly string? _nodeName; + private readonly OletxTransactionManager _oletxTm; + private readonly DtcProxyShimFactory _proxyShimFactory; + private byte[]? _whereabouts; + + internal DtcTransactionManager(string? nodeName, OletxTransactionManager oletxTm) + { + _nodeName = nodeName; + _oletxTm = oletxTm; + _proxyShimFactory = OletxTransactionManager.ProxyShimFactory; + } + + [MemberNotNull(nameof(_whereabouts))] + private void Initialize() + { + if (_whereabouts is not null) + { + return; + } + + OletxInternalResourceManager internalRM = _oletxTm.InternalResourceManager; + bool nodeNameMatches; + + try + { + _proxyShimFactory.ConnectToProxy( + _nodeName, + internalRM.Identifier, + internalRM, + out nodeNameMatches, + out _whereabouts, + out ResourceManagerShim resourceManagerShim); + + // If the node name does not match, throw. + if (!nodeNameMatches) + { + throw new NotSupportedException(SR.ProxyCannotSupportMultipleNodeNames); + } + + // Give the IResourceManagerShim to the internalRM and tell it to call ReenlistComplete. + internalRM.ResourceManagerShim = resourceManagerShim; + internalRM.CallReenlistComplete(); + } + catch (COMException ex) + { + if (ex.ErrorCode == OletxHelper.XACT_E_NOTSUPPORTED) + { + throw new NotSupportedException(SR.CannotSupportNodeNameSpecification); + } + + OletxTransactionManager.ProxyException(ex); + + // Unfortunately MSDTCPRX may return unknown error codes when attempting to connect to MSDTC + // that error should be propagated back as a TransactionManagerCommunicationException. + throw TransactionManagerCommunicationException.Create(SR.TransactionManagerCommunicationException, ex); + } + } + + internal DtcProxyShimFactory ProxyShimFactory + { + get + { + if (_whereabouts is null) + { + lock (this) + { + Initialize(); + } + } + + return _proxyShimFactory; + } + } + + internal void ReleaseProxy() + { + lock (this) + { + _whereabouts = null; + } + } + + internal byte[] Whereabouts + { + get + { + if (_whereabouts is null) + { + lock (this) + { + Initialize(); + } + } + + return _whereabouts; + } + } + + internal static uint AdjustTimeout(TimeSpan timeout) + { + uint returnTimeout = 0; + + try + { + returnTimeout = Convert.ToUInt32(timeout.TotalMilliseconds, CultureInfo.CurrentCulture); + } + catch (OverflowException caughtEx) + { + // timeout.TotalMilliseconds might be negative, so let's catch overflow exceptions, just in case. + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, caughtEx); + } + + returnTimeout = uint.MaxValue; + } + return returnTimeout; + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxCommittableTransaction.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxCommittableTransaction.cs new file mode 100644 index 00000000000000..9e61ccf3689841 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxCommittableTransaction.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace System.Transactions.Oletx; + +/// +/// A Transaction object represents a single transaction. It is created by TransactionManager +/// objects through CreateTransaction or UnmarshalTransaction. Alternatively, the static Create +/// methodis provided, which creates a "default" TransactionManager and requests that it create +/// a new transaction with default values. A transaction can only be committed by +/// the client application that created the transaction. If a client application wishes to allow +/// access to the transaction by multiple threads, but wants to prevent those other threads from +/// committing the transaction, the application can make a "clone" of the transaction. Transaction +/// clones have the same capabilities as the original transaction, except for the ability to commit +/// the transaction. +/// +[Serializable] +internal sealed class OletxCommittableTransaction : OletxTransaction +{ + private bool _commitCalled; + + /// + /// Constructor for the Transaction object. Specifies the TransactionManager instance that is + /// creating the transaction. + /// + internal OletxCommittableTransaction(RealOletxTransaction realOletxTransaction) + : base(realOletxTransaction) + { + realOletxTransaction.CommittableTransaction = this; + } + + internal bool CommitCalled => _commitCalled; + + internal void BeginCommit(InternalTransaction internalTransaction) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this); + etwLog.TransactionCommit(TraceSourceType.TraceSourceOleTx, TransactionTraceId, "CommittableTransaction"); + } + + Debug.Assert(0 == Disposed, "OletxTransction object is disposed"); + RealOletxTransaction.InternalTransaction = internalTransaction; + + _commitCalled = true; + + RealOletxTransaction.Commit(); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxCommittableTransaction)}.{nameof(BeginCommit)}"); + } + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxDependentTransaction.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxDependentTransaction.cs new file mode 100644 index 00000000000000..9b2822027a7ab8 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxDependentTransaction.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Threading; + +namespace System.Transactions.Oletx; + +[Serializable] +internal sealed class OletxDependentTransaction : OletxTransaction +{ + private OletxVolatileEnlistmentContainer _volatileEnlistmentContainer; + + private int _completed; + + internal OletxDependentTransaction(RealOletxTransaction realTransaction, bool delayCommit) + : base(realTransaction) + { + if (realTransaction == null) + { + throw new ArgumentNullException(nameof(realTransaction)); + } + + _volatileEnlistmentContainer = RealOletxTransaction.AddDependentClone(delayCommit); + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.TransactionDependentCloneCreate(TraceSourceType.TraceSourceOleTx, TransactionTraceId, delayCommit + ? DependentCloneOption.BlockCommitUntilComplete + : DependentCloneOption.RollbackIfNotComplete); + } + } + + public void Complete() + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(DependentTransaction)}.{nameof(Complete)}"); + } + + Debug.Assert(Disposed == 0, "OletxTransction object is disposed"); + + int localCompleted = Interlocked.Exchange(ref _completed, 1); + if (localCompleted == 1) + { + throw TransactionException.CreateTransactionCompletedException(DistributedTxId); + } + + if (etwLog.IsEnabled()) + { + etwLog.TransactionDependentCloneComplete(TraceSourceType.TraceSourceOleTx, TransactionTraceId, "DependentTransaction"); + } + + _volatileEnlistmentContainer.DependentCloneCompleted(); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(DependentTransaction)}.{nameof(Complete)}"); + } + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxEnlistment.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxEnlistment.cs new file mode 100644 index 00000000000000..682388828f6c95 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxEnlistment.cs @@ -0,0 +1,1212 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using System.Transactions.DtcProxyShim; + +namespace System.Transactions.Oletx; + +internal sealed class OletxEnlistment : OletxBaseEnlistment, IPromotedEnlistment +{ + internal enum OletxEnlistmentState + { + Active, + Phase0Preparing, + Preparing, + SinglePhaseCommitting, + Prepared, + Committing, + Committed, + Aborting, + Aborted, + InDoubt, + Done + } + + private Phase0EnlistmentShim? _phase0Shim; + private bool _canDoSinglePhase; + private IEnlistmentNotificationInternal? _iEnlistmentNotification; + // The information that comes from/goes to the proxy. + private byte[]? _proxyPrepareInfoByteArray; + + private bool _isSinglePhase; + private Guid _transactionGuid = Guid.Empty; + + // Set to true if we receive an AbortRequest while we still have + // another notification, like prepare, outstanding. It indicates that + // we need to fabricate a rollback to the app after it responds to Prepare. + private bool _fabricateRollback; + + private bool _tmWentDown; + private bool _aborting; + + private byte[]? _prepareInfoByteArray; + + internal Guid TransactionIdentifier => _transactionGuid; + + #region Constructor + + internal OletxEnlistment( + bool canDoSinglePhase, + IEnlistmentNotificationInternal enlistmentNotification, + Guid transactionGuid, + EnlistmentOptions enlistmentOptions, + OletxResourceManager oletxResourceManager, + OletxTransaction oletxTransaction) + : base(oletxResourceManager, oletxTransaction) + { + // This will get set later by the creator of this object after it + // has enlisted with the proxy. + EnlistmentShim = null; + _phase0Shim = null; + + _canDoSinglePhase = canDoSinglePhase; + _iEnlistmentNotification = enlistmentNotification; + State = OletxEnlistmentState.Active; + _transactionGuid = transactionGuid; + + _proxyPrepareInfoByteArray = null; + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentCreated(TraceSourceType.TraceSourceOleTx, InternalTraceIdentifier, EnlistmentType.Durable, enlistmentOptions); + } + + // Always do this last in case anything earlier fails. + AddToEnlistmentTable(); + } + + internal OletxEnlistment( + IEnlistmentNotificationInternal enlistmentNotification, + OletxTransactionStatus xactStatus, + byte[] prepareInfoByteArray, + OletxResourceManager oletxResourceManager) + : base(oletxResourceManager, null) + { + // This will get set later by the creator of this object after it + // has enlisted with the proxy. + EnlistmentShim = null; + _phase0Shim = null; + + _canDoSinglePhase = false; + _iEnlistmentNotification = enlistmentNotification; + State = OletxEnlistmentState.Active; + + // Do this before we do any tracing because it will affect the trace identifiers that we generate. + Debug.Assert(prepareInfoByteArray != null, + "OletxEnlistment.ctor - null oletxTransaction without a prepareInfoByteArray"); + + int prepareInfoLength = prepareInfoByteArray.Length; + _proxyPrepareInfoByteArray = new byte[prepareInfoLength]; + Array.Copy(prepareInfoByteArray, _proxyPrepareInfoByteArray, prepareInfoLength); + + byte[] txGuidByteArray = new byte[16]; + Array.Copy(_proxyPrepareInfoByteArray, txGuidByteArray, 16); + + _transactionGuid = new Guid(txGuidByteArray); + TransactionGuidString = _transactionGuid.ToString(); + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + + // If this is being created as part of a Reenlist and we already know the + // outcome, then tell the application. + switch (xactStatus) + { + case OletxTransactionStatus.OLETX_TRANSACTION_STATUS_ABORTED: + { + State = OletxEnlistmentState.Aborting; + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceOleTx, InternalTraceIdentifier, NotificationCall.Rollback); + } + + _iEnlistmentNotification.Rollback(this); + break; + } + + case OletxTransactionStatus.OLETX_TRANSACTION_STATUS_COMMITTED: + { + State = OletxEnlistmentState.Committing; + // We are going to send the notification to the RM. We need to put the + // enlistment on the reenlistPendingList. We lock the reenlistList because + // we have decided that is the lock that protects both lists. The entry will + // be taken off the reenlistPendingList when the enlistment has + // EnlistmentDone called on it. The enlistment will call + // RemoveFromReenlistPending. + lock (oletxResourceManager.ReenlistList) + { + oletxResourceManager.ReenlistPendingList.Add(this); + } + + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceOleTx, InternalTraceIdentifier, NotificationCall.Commit); + } + + _iEnlistmentNotification.Commit(this); + break; + } + + case OletxTransactionStatus.OLETX_TRANSACTION_STATUS_PREPARED: + { + State = OletxEnlistmentState.Prepared; + lock (oletxResourceManager.ReenlistList) + { + oletxResourceManager.ReenlistList.Add(this); + oletxResourceManager.StartReenlistThread(); + } + break; + } + + default: + { + if (etwLog.IsEnabled()) + { + etwLog.InternalError(SR.OletxEnlistmentUnexpectedTransactionStatus); + } + + throw TransactionException.Create( + SR.OletxEnlistmentUnexpectedTransactionStatus, null, DistributedTxId); + } + } + + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentCreated(TraceSourceType.TraceSourceOleTx, InternalTraceIdentifier, EnlistmentType.Durable, EnlistmentOptions.None); + } + + // Always do this last in case anything prior to this fails. + AddToEnlistmentTable(); + } + #endregion + + internal IEnlistmentNotificationInternal? EnlistmentNotification => _iEnlistmentNotification; + + internal EnlistmentShim? EnlistmentShim { get; set; } + + internal Phase0EnlistmentShim? Phase0EnlistmentShim + { + get => _phase0Shim; + set + { + lock (this) + { + // If this.aborting is set to true, then we must have already received a + // Phase0Request. This could happen if the transaction aborts after the + // enlistment is made, but before we are given the shim. + if (value != null && (_aborting || _tmWentDown)) + { + value.Phase0Done(false); + } + _phase0Shim = value; + } + } + } + + internal OletxEnlistmentState State { get; set; } = OletxEnlistmentState.Active; + + internal byte[]? ProxyPrepareInfoByteArray => _proxyPrepareInfoByteArray; + + internal void FinishEnlistment() + { + lock (this) + { + // If we don't have a wrappedTransactionEnlistmentAsync, we may + // need to remove ourselves from the reenlistPendingList in the + // resource manager. + if (EnlistmentShim == null) + { + OletxResourceManager.RemoveFromReenlistPending(this); + } + _iEnlistmentNotification = null; + + RemoveFromEnlistmentTable(); + } + } + + internal void TMDownFromInternalRM(OletxTransactionManager oletxTm) + { + lock (this) + { + // If we don't have an oletxTransaction or the passed oletxTm matches that of my oletxTransaction, the TM went down. + if (oletxTransaction == null || oletxTm == oletxTransaction.RealOletxTransaction.OletxTransactionManagerInstance) + { + _tmWentDown = true; + } + } + } + + #region ITransactionResourceAsync methods + + // ITranactionResourceAsync.PrepareRequest + public bool PrepareRequest(bool singlePhase, byte[] prepareInfo) + { + EnlistmentShim? localEnlistmentShim; + OletxEnlistmentState localState = OletxEnlistmentState.Active; + IEnlistmentNotificationInternal localEnlistmentNotification; + bool enlistmentDone; + + lock (this) + { + if (OletxEnlistmentState.Active == State) + { + localState = State = OletxEnlistmentState.Preparing; + } + else + { + // We must have done the prepare work in Phase0, so just remember what state we are + // in now. + localState = State; + } + + localEnlistmentNotification = _iEnlistmentNotification!; + + localEnlistmentShim = EnlistmentShim; + + oletxTransaction!.RealOletxTransaction.TooLateForEnlistments = true; + } + + // If we went to Preparing state, send the app + // a prepare request. + if (OletxEnlistmentState.Preparing == localState) + { + _isSinglePhase = singlePhase; + + // Store the prepare info we are given. + Debug.Assert(_proxyPrepareInfoByteArray == null, "Unexpected value in this.proxyPrepareInfoByteArray"); + long arrayLength = prepareInfo.Length; + _proxyPrepareInfoByteArray = new byte[arrayLength]; + Array.Copy(prepareInfo, _proxyPrepareInfoByteArray, arrayLength); + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + + if (_isSinglePhase && _canDoSinglePhase) + { + ISinglePhaseNotificationInternal singlePhaseNotification = (ISinglePhaseNotificationInternal)localEnlistmentNotification; + State = OletxEnlistmentState.SinglePhaseCommitting; + // We don't call DecrementUndecidedEnlistments for Phase1 enlistments. + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceOleTx, InternalTraceIdentifier, NotificationCall.SinglePhaseCommit); + } + + singlePhaseNotification.SinglePhaseCommit(this); + enlistmentDone = true; + } + else + { + State = OletxEnlistmentState.Preparing; + + _prepareInfoByteArray = TransactionManager.GetRecoveryInformation( + OletxResourceManager.OletxTransactionManager.CreationNodeName, + prepareInfo); + + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceOleTx, InternalTraceIdentifier, NotificationCall.Prepare); + } + + localEnlistmentNotification.Prepare(this); + enlistmentDone = false; + } + } + else if (OletxEnlistmentState.Prepared == localState) + { + // We must have done our prepare work during Phase0 so just vote Yes. + try + { + localEnlistmentShim!.PrepareRequestDone(OletxPrepareVoteType.Prepared); + enlistmentDone = false; + } + catch (COMException comException) + { + OletxTransactionManager.ProxyException(comException); + throw; + } + } + else if (OletxEnlistmentState.Done == localState) + { + try + { + // This was an early vote. Respond ReadOnly + try + { + localEnlistmentShim!.PrepareRequestDone(OletxPrepareVoteType.ReadOnly); + enlistmentDone = true; + } + finally + { + FinishEnlistment(); + } + } + catch (COMException comException) + { + OletxTransactionManager.ProxyException(comException); + throw; + } + } + else + { + // Any other state means we should vote NO to the proxy. + try + { + localEnlistmentShim!.PrepareRequestDone(OletxPrepareVoteType.Failed); + } + catch (COMException ex) + { + // No point in rethrowing this. We are not on an app thread and we have already told + // the app that the transaction is aborting. When the app calls EnlistmentDone, we will + // do the final release of the ITransactionEnlistmentAsync. + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + + enlistmentDone = true; + } + + return enlistmentDone; + } + + + public void CommitRequest() + { + OletxEnlistmentState localState = OletxEnlistmentState.Active; + IEnlistmentNotificationInternal? localEnlistmentNotification = null; + EnlistmentShim? localEnlistmentShim = null; + bool finishEnlistment = false; + + lock (this) + { + if (OletxEnlistmentState.Prepared == State) + { + localState = State = OletxEnlistmentState.Committing; + localEnlistmentNotification = _iEnlistmentNotification; + } + else + { + // We must have received an EnlistmentDone already. + localState = State; + localEnlistmentShim = EnlistmentShim; + finishEnlistment = true; + } + } + + if (localEnlistmentNotification != null) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceOleTx, InternalTraceIdentifier, NotificationCall.Commit); + } + + localEnlistmentNotification.Commit(this); + } + else if (localEnlistmentShim != null) + { + // We need to respond to the proxy now. + try + { + localEnlistmentShim.CommitRequestDone(); + } + catch (COMException ex) + { + // If the TM went down during our call, there is nothing special we have to do because + // the App doesn't expect any more notifications. We do want to mark the enlistment + // to finish, however. + if (ex.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN || + ex.ErrorCode == OletxHelper.XACT_E_TMNOTAVAILABLE) + { + finishEnlistment = true; + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + else + { + throw; + } + } + finally + { + if (finishEnlistment) + { + FinishEnlistment(); + } + } + } + } + + public void AbortRequest() + { + OletxEnlistmentState localState = OletxEnlistmentState.Active; + IEnlistmentNotificationInternal? localEnlistmentNotification = null; + EnlistmentShim? localEnlistmentShim = null; + bool finishEnlistment = false; + + lock (this) + { + if (State is OletxEnlistmentState.Active or OletxEnlistmentState.Prepared) + { + localState = State = OletxEnlistmentState.Aborting; + localEnlistmentNotification = _iEnlistmentNotification; + } + else + { + // We must have received an EnlistmentDone already or we have + // a notification outstanding (Phase0 prepare). + localState = State; + if (OletxEnlistmentState.Phase0Preparing == State) + { + _fabricateRollback = true; + } + else + { + finishEnlistment = true; + } + + localEnlistmentShim = EnlistmentShim; + } + } + + if (localEnlistmentNotification != null) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceOleTx, InternalTraceIdentifier, NotificationCall.Rollback); + } + + localEnlistmentNotification.Rollback(this); + } + else if (localEnlistmentShim != null) + { + // We need to respond to the proxy now. + try + { + localEnlistmentShim.AbortRequestDone(); + } + catch (COMException ex) + { + // If the TM went down during our call, there is nothing special we have to do because + // the App doesn't expect any more notifications. We do want to mark the enlistment + // to finish, however. + if (ex.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN || + ex.ErrorCode == OletxHelper.XACT_E_TMNOTAVAILABLE) + { + finishEnlistment = true; + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + else + { + throw; + } + } + finally + { + if (finishEnlistment) + { + FinishEnlistment(); + } + } + } + } + + public void TMDown() + { + // We aren't telling our enlistments about TMDown, only + // resource managers. + // Put this enlistment on the Reenlist list. The Reenlist thread will get + // started when the RMSink gets the TMDown notification. + lock (OletxResourceManager.ReenlistList) + { + lock (this) + { + // Remember that we got the TMDown in case we get a Phase0Request after so we + // can avoid doing a Prepare to the app. + _tmWentDown = true; + + // Only move Prepared and Committing enlistments to the ReenlistList. All others + // do not require a Reenlist to figure out what to do. We save off Committing + // enlistments because the RM has not acknowledged the commit, so we can't + // call RecoveryComplete on the proxy until that has happened. The Reenlist thread + // will loop until the reenlist list is empty and it will leave a Committing + // enlistment on the list until it is done, but will NOT call Reenlist on the proxy. + if (State is OletxEnlistmentState.Prepared or OletxEnlistmentState.Committing) + { + OletxResourceManager.ReenlistList.Add(this); + } + } + } + } + + #endregion + + #region ITransactionPhase0NotifyAsync methods + + // ITransactionPhase0NotifyAsync + public void Phase0Request(bool abortingHint) + { + IEnlistmentNotificationInternal? localEnlistmentNotification = null; + OletxEnlistmentState localState = OletxEnlistmentState.Active; + OletxCommittableTransaction? committableTx; + bool commitNotYetCalled = false; + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxEnlistment)}.{nameof(Phase0Request)}"); + } + + committableTx = oletxTransaction!.RealOletxTransaction.CommittableTransaction; + if (committableTx != null) + { + // We are dealing with the committable transaction. If Commit or BeginCommit has NOT been + // called, then we are dealing with a situation where the TM went down and we are getting + // a bogus Phase0Request with abortHint = false (COMPlus bug 36760/36758). This is an attempt + // to not send the app a Prepare request when we know the transaction is going to abort. + if (!committableTx.CommitCalled) + { + commitNotYetCalled = true; + } + } + + lock (this) + { + _aborting = abortingHint; + + // The app may have already called EnlistmentDone. If this occurs, don't bother sending + // the notification to the app and we don't need to tell the proxy. + if (OletxEnlistmentState.Active == State) + { + // If we got an abort hint or we are the committable transaction and Commit has not yet been called or the TM went down, + // we don't want to do any more work on the transaction. The abort notifications will be sent by the phase 1 + // enlistment + if (_aborting || commitNotYetCalled || _tmWentDown) + { + // There is a possible race where we could get the Phase0Request before we are given the + // shim. In that case, we will vote "no" when we are given the shim. + if (_phase0Shim != null) + { + try + { + _phase0Shim.Phase0Done(false); + } + // I am not going to check for XACT_E_PROTOCOL here because that check is a workaround for a bug + // that only shows up if abortingHint is false. + catch (COMException ex) + { + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + } + } + else + { + localState = State = OletxEnlistmentState.Phase0Preparing; + localEnlistmentNotification = _iEnlistmentNotification; + } + } + } + + // Tell the application to do the work. + if (localEnlistmentNotification != null) + { + if (OletxEnlistmentState.Phase0Preparing == localState) + { + byte[] txGuidArray = _transactionGuid.ToByteArray(); + byte[] rmGuidArray = OletxResourceManager.ResourceManagerIdentifier.ToByteArray(); + + byte[] temp = new byte[txGuidArray.Length + rmGuidArray.Length]; + Thread.MemoryBarrier(); + _proxyPrepareInfoByteArray = temp; + for (int index = 0; index < txGuidArray.Length; index++) + { + _proxyPrepareInfoByteArray[index] = + txGuidArray[index]; + } + + for (int index = 0; index < rmGuidArray.Length; index++) + { + _proxyPrepareInfoByteArray[txGuidArray.Length + index] = + rmGuidArray[index]; + } + + _prepareInfoByteArray = TransactionManager.GetRecoveryInformation( + OletxResourceManager.OletxTransactionManager.CreationNodeName, + _proxyPrepareInfoByteArray); + + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceOleTx, InternalTraceIdentifier, NotificationCall.Prepare); + } + + localEnlistmentNotification.Prepare(this); + } + else + { + // We must have had a race between EnlistmentDone and the proxy telling + // us Phase0Request. Just return. + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxEnlistment)}.{nameof(Phase0Request)}"); + } + + return; + } + + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxEnlistment)}.{nameof(Phase0Request)}"); + } + } + + #endregion + + public void EnlistmentDone() + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxEnlistment)}.{nameof(EnlistmentDone)}"); + etwLog.EnlistmentCallbackPositive(InternalTraceIdentifier, EnlistmentCallback.Done); + } + + EnlistmentShim? localEnlistmentShim = null; + Phase0EnlistmentShim? localPhase0Shim = null; + OletxEnlistmentState localState = OletxEnlistmentState.Active; + bool finishEnlistment; + bool localFabricateRollback; + + lock (this) + { + localState = State; + if (OletxEnlistmentState.Active == State) + { + // Early vote. If we are doing Phase0, we need to unenlist. Otherwise, just + // remember. + localPhase0Shim = Phase0EnlistmentShim; + if (localPhase0Shim != null) + { + // We are a Phase0 enlistment and we have a vote - decrement the undecided enlistment count. + // We only do this for Phase0 because we don't count Phase1 durable enlistments. + oletxTransaction!.RealOletxTransaction.DecrementUndecidedEnlistments(); + } + finishEnlistment = false; + } + else if (OletxEnlistmentState.Preparing == State) + { + // Read only vote. Tell the proxy and go to the Done state. + localEnlistmentShim = EnlistmentShim; + // We don't decrement the undecided enlistment count for Preparing because we only count + // Phase0 enlistments and we are in Phase1 in Preparing state. + finishEnlistment = true; + } + else if (OletxEnlistmentState.Phase0Preparing == State) + { + // Read only vote to Phase0. Tell the proxy okay and go to the Done state. + localPhase0Shim = Phase0EnlistmentShim; + // We are a Phase0 enlistment and we have a vote - decrement the undecided enlistment count. + // We only do this for Phase0 because we don't count Phase1 durable enlistments. + oletxTransaction!.RealOletxTransaction.DecrementUndecidedEnlistments(); + + // If we would have fabricated a rollback then we have already received an abort request + // from proxy and will not receive any more notifications. Otherwise more notifications + // will be coming. + if (_fabricateRollback) + { + finishEnlistment = true; + } + else + { + finishEnlistment = false; + } + } + else if (State is OletxEnlistmentState.Committing + or OletxEnlistmentState.Aborting + or OletxEnlistmentState.SinglePhaseCommitting) + { + localEnlistmentShim = EnlistmentShim; + finishEnlistment = true; + // We don't decrement the undecided enlistment count for SinglePhaseCommitting because we only + // do it for Phase0 enlistments. + } + else + { + throw TransactionException.CreateEnlistmentStateException(null, DistributedTxId); + } + + // If this.fabricateRollback is true, it means that we are fabricating this + // AbortRequest, rather than having the proxy tell us. So we don't need + // to respond to the proxy with AbortRequestDone. + localFabricateRollback = _fabricateRollback; + + State = OletxEnlistmentState.Done; + } + + try + { + if (localEnlistmentShim != null) + { + if (OletxEnlistmentState.Preparing == localState) + { + localEnlistmentShim.PrepareRequestDone(OletxPrepareVoteType.ReadOnly); + } + else if (OletxEnlistmentState.Committing == localState) + { + localEnlistmentShim.CommitRequestDone(); + } + else if (OletxEnlistmentState.Aborting == localState) + { + // If localFabricatRollback is true, it means that we are fabricating this + // AbortRequest, rather than having the proxy tell us. So we don't need + // to respond to the proxy with AbortRequestDone. + if (!localFabricateRollback) + { + localEnlistmentShim.AbortRequestDone(); + } + } + else if (OletxEnlistmentState.SinglePhaseCommitting == localState) + { + localEnlistmentShim.PrepareRequestDone(OletxPrepareVoteType.SinglePhase); + } + else + { + throw TransactionException.CreateEnlistmentStateException(null, DistributedTxId); + } + } + else if (localPhase0Shim != null) + { + if (localState == OletxEnlistmentState.Active) + { + localPhase0Shim.Unenlist(); + } + else if (localState == OletxEnlistmentState.Phase0Preparing) + { + localPhase0Shim.Phase0Done(true); + } + else + { + throw TransactionException.CreateEnlistmentStateException(null, DistributedTxId); + } + } + } + catch (COMException ex) + { + // If we get an error talking to the proxy, there is nothing special we have to do because + // the App doesn't expect any more notifications. We do want to mark the enlistment + // to finish, however. + finishEnlistment = true; + + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + finally + { + if (finishEnlistment) + { + FinishEnlistment(); + } + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxEnlistment)}.{nameof(EnlistmentDone)}"); + } + } + + public EnlistmentTraceIdentifier EnlistmentTraceId + { + get + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxEnlistment)}.{nameof(EnlistmentTraceId)}"); + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxEnlistment)}.{nameof(EnlistmentTraceId)}"); + } + + return InternalTraceIdentifier; + } + } + + public void Prepared() + { + int hrResult = OletxHelper.S_OK; + EnlistmentShim? localEnlistmentShim = null; + Phase0EnlistmentShim? localPhase0Shim = null; + bool localFabricateRollback = false; + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"OletxPreparingEnlistment.{nameof(Prepared)}"); + etwLog.EnlistmentCallbackPositive(InternalTraceIdentifier, EnlistmentCallback.Prepared); + } + + lock (this) + { + if (State == OletxEnlistmentState.Preparing) + { + localEnlistmentShim = EnlistmentShim; + } + else if (OletxEnlistmentState.Phase0Preparing == State) + { + // If the transaction is doomed or we have fabricateRollback is true because the + // transaction aborted while the Phase0 Prepare request was outstanding, + // release the WrappedTransactionPhase0EnlistmentAsync and remember that + // we have a pending rollback. + localPhase0Shim = Phase0EnlistmentShim; + if (oletxTransaction!.RealOletxTransaction.Doomed || _fabricateRollback) + { + // Set fabricateRollback in case we got here because the transaction is doomed. + _fabricateRollback = true; + localFabricateRollback = _fabricateRollback; + } + } + else + { + throw TransactionException.CreateEnlistmentStateException(null, DistributedTxId); + } + + State = OletxEnlistmentState.Prepared; + } + + try + { + if (localEnlistmentShim != null) + { + localEnlistmentShim.PrepareRequestDone(OletxPrepareVoteType.Prepared); + } + else if (localPhase0Shim != null) + { + // We have a vote - decrement the undecided enlistment count. We do + // this after checking Doomed because ForceRollback will decrement also. + // We also do this only for Phase0 enlistments. + oletxTransaction!.RealOletxTransaction.DecrementUndecidedEnlistments(); + + localPhase0Shim.Phase0Done(!localFabricateRollback); + } + else + { + // The TM must have gone down, thus causing our interface pointer to be + // invalidated. So we need to drive abort of the enlistment as if we + // received an AbortRequest. + localFabricateRollback = true; + } + + if (localFabricateRollback) + { + AbortRequest(); + } + } + catch (COMException ex) + { + // If the TM went down during our call, the TMDown notification to the enlistment + // and RM will put this enlistment on the ReenlistList, if appropriate. The outcome + // will be obtained by the ReenlistThread. + if ((ex.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN || ex.ErrorCode == OletxHelper.XACT_E_TMNOTAVAILABLE) && etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + // In the case of Phase0, there is a bug in the proxy that causes an XACT_E_PROTOCOL + // error if the TM goes down while the enlistment is still active. The Phase0Request is + // sent out with abortHint false, but the state of the proxy object is not changed, causing + // Phase0Done request to fail with XACT_E_PROTOCOL. + // For Prepared, we want to make sure the proxy aborts the transaction. We don't need + // to drive the abort to the application here because the Phase1 enlistment will do that. + // In other words, treat this as if the proxy said Phase0Request( abortingHint = true ). + else if (ex.ErrorCode == OletxHelper.XACT_E_PROTOCOL) + { + Phase0EnlistmentShim = null; + + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + else + { + throw; + } + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"OletxPreparingEnlistment.{nameof(Prepared)}"); + } + } + + public void ForceRollback() + => ForceRollback(null); + + public void ForceRollback(Exception? e) + { + EnlistmentShim? localEnlistmentShim = null; + Phase0EnlistmentShim? localPhase0Shim = null; + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"OletxPreparingEnlistment.{nameof(ForceRollback)}"); + etwLog.EnlistmentCallbackNegative(InternalTraceIdentifier, EnlistmentCallback.ForceRollback); + } + + lock (this) + { + if (OletxEnlistmentState.Preparing == State) + { + localEnlistmentShim = EnlistmentShim; + } + else if (OletxEnlistmentState.Phase0Preparing == State) + { + localPhase0Shim = Phase0EnlistmentShim; + if (localPhase0Shim != null) + { + // We have a vote - decrement the undecided enlistment count. We only do this + // if we are Phase0 enlistment. + oletxTransaction!.RealOletxTransaction.DecrementUndecidedEnlistments(); + } + } + else + { + throw TransactionException.CreateEnlistmentStateException(null, DistributedTxId); + } + + State = OletxEnlistmentState.Aborted; + } + + Interlocked.CompareExchange(ref oletxTransaction!.RealOletxTransaction.InnerException, e, null); + + try + { + if (localEnlistmentShim != null) + { + localEnlistmentShim.PrepareRequestDone(OletxPrepareVoteType.Failed); + } + } + catch (COMException ex) + { + // If the TM went down during our call, there is nothing special we have to do because + // the App doesn't expect any more notifications. + if (ex.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN || + ex.ErrorCode == OletxHelper.XACT_E_TMNOTAVAILABLE) + { + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + else + { + throw; + } + } + finally + { + FinishEnlistment(); + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"OletxPreparingEnlistment.{nameof(ForceRollback)}"); + } + } + + public void Committed() + { + EnlistmentShim? localEnlistmentShim = null; + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"OletxSinglePhaseEnlistment.{nameof(Committed)}"); + etwLog.EnlistmentCallbackPositive(InternalTraceIdentifier, EnlistmentCallback.Committed); + } + + lock (this) + { + if (!_isSinglePhase || OletxEnlistmentState.SinglePhaseCommitting != State) + { + throw TransactionException.CreateEnlistmentStateException(null, DistributedTxId); + } + State = OletxEnlistmentState.Committed; + localEnlistmentShim = EnlistmentShim; + } + + try + { + // This may be the result of a reenlist, which means we don't have a + // reference to the proxy. + if (localEnlistmentShim != null) + { + localEnlistmentShim.PrepareRequestDone(OletxPrepareVoteType.SinglePhase); + } + } + catch (COMException ex) + { + // If the TM went down during our call, there is nothing special we have to do because + // the App doesn't expect any more notifications. + if (ex.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN || + ex.ErrorCode == OletxHelper.XACT_E_TMNOTAVAILABLE) + { + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + else + { + throw; + } + } + finally + { + FinishEnlistment(); + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"OletxSinglePhaseEnlistment.{nameof(Committed)}"); + } + } + + public void Aborted() + => Aborted(null); + + public void Aborted(Exception? e) + { + EnlistmentShim? localEnlistmentShim = null; + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"OletxSinglePhaseEnlistment.{nameof(Aborted)}"); + etwLog.EnlistmentCallbackNegative(InternalTraceIdentifier, EnlistmentCallback.Aborted); + } + + lock (this) + { + if (!_isSinglePhase || OletxEnlistmentState.SinglePhaseCommitting != State) + { + throw TransactionException.CreateEnlistmentStateException(null, DistributedTxId); + } + State = OletxEnlistmentState.Aborted; + + localEnlistmentShim = EnlistmentShim; + } + + Interlocked.CompareExchange(ref oletxTransaction!.RealOletxTransaction.InnerException, e, null); + + try + { + if (localEnlistmentShim != null) + { + localEnlistmentShim.PrepareRequestDone(OletxPrepareVoteType.Failed); + } + } + // If the TM went down during our call, there is nothing special we have to do because + // the App doesn't expect any more notifications. + catch (COMException ex) when ( + (ex.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN || ex.ErrorCode == OletxHelper.XACT_E_TMNOTAVAILABLE) && etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + finally + { + FinishEnlistment(); + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"OletxSinglePhaseEnlistment.{nameof(Aborted)}"); + } + } + + public void InDoubt() + => InDoubt(null); + + public void InDoubt(Exception? e) + { + EnlistmentShim? localEnlistmentShim = null; + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"OletxSinglePhaseEnlistment.{nameof(InDoubt)}"); + etwLog.EnlistmentCallbackNegative(InternalTraceIdentifier, EnlistmentCallback.InDoubt); + } + + lock (this) + { + if (!_isSinglePhase || OletxEnlistmentState.SinglePhaseCommitting != State) + { + throw TransactionException.CreateEnlistmentStateException(null, DistributedTxId); + } + State = OletxEnlistmentState.InDoubt; + localEnlistmentShim = EnlistmentShim; + } + + lock (oletxTransaction!.RealOletxTransaction) + { + oletxTransaction.RealOletxTransaction.InnerException ??= e; + } + + try + { + if (localEnlistmentShim != null) + { + localEnlistmentShim.PrepareRequestDone(OletxPrepareVoteType.InDoubt); + } + } + // If the TM went down during our call, there is nothing special we have to do because + // the App doesn't expect any more notifications. + catch (COMException ex) when ( + (ex.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN || ex.ErrorCode == OletxHelper.XACT_E_TMNOTAVAILABLE) && etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + finally + { + FinishEnlistment(); + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"OletxSinglePhaseEnlistment.{nameof(InDoubt)}"); + } + } + + public byte[] GetRecoveryInformation() + { + if (_prepareInfoByteArray == null) + { + Debug.Fail(string.Format(null, "this.prepareInfoByteArray == null in RecoveryInformation()")); + throw TransactionException.CreateEnlistmentStateException(null, DistributedTxId); + } + + return _prepareInfoByteArray; + } + + InternalEnlistment? IPromotedEnlistment.InternalEnlistment + { + get => base.InternalEnlistment; + set => base.InternalEnlistment = value; + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxResourceManager.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxResourceManager.cs new file mode 100644 index 00000000000000..be9a099184c488 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxResourceManager.cs @@ -0,0 +1,896 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using System.Transactions.DtcProxyShim; + +namespace System.Transactions.Oletx; + +internal sealed class OletxResourceManager +{ + internal Guid ResourceManagerIdentifier; + + internal ResourceManagerShim? resourceManagerShim; + internal Hashtable EnlistmentHashtable; + internal static Hashtable VolatileEnlistmentHashtable = new Hashtable(); + internal OletxTransactionManager OletxTransactionManager; + + // reenlistList is a simple ArrayList of OletxEnlistment objects that are either in the + // Preparing or Prepared state when we receive a TMDown notification or have had + // ReenlistTransaction called for them. The ReenlistThread is responsible for traversing this + // list trying to obtain the outcome for the enlistments. All access, read or write, to this + // list should get a lock on the list. + // Special Note: If you are going to lock both the OletxResourceManager object AND the + // reenlistList, lock the reenlistList FIRST. + internal ArrayList ReenlistList; + + // reenlistPendingList is also a simple ArrayList of OletxEnlistment objects. But for these + // we have received the outcome from the proxy and have called into the RM to deliver the + // notification, but the RM has not yet called EnlistmentDone to let us know that the outcome + // has been processed. This list must be empty, in addition to the reenlistList, in order for + // the ReenlistThread to call RecoveryComplete and not be rescheduled. Access to this list + // should be protected by locking the reenlistList. The lists are always accessed together, + // so there is no reason to grab two locks. + internal ArrayList ReenlistPendingList; + + // This is where we keep the reenlistThread and thread timer values. If there is a reenlist thread running, + // reenlistThread will be non-null. If reenlistThreadTimer is non-null, we have a timer scheduled which will + // fire off a reenlist thread when it expires. Only one or the other should be non-null at a time. However, they + // could both be null, which means that there is no reenlist thread running and there is no timer scheduled to + // create one. Access to these members should be done only after obtaining a lock on the OletxResourceManager object. + internal Timer? ReenlistThreadTimer; + internal Thread? reenlistThread; + + // This boolean is set to true if the resource manager application has called RecoveryComplete. + // A lock on the OletxResourceManager instance will be obtained when retrieving or modifying + // this value. Before calling ReenlistComplete on the DTC proxy, this value must be true. + internal bool RecoveryCompleteCalledByApplication { get; set; } + + internal OletxResourceManager(OletxTransactionManager transactionManager, Guid resourceManagerIdentifier) + { + Debug.Assert(transactionManager != null, "Argument is null"); + + // This will get set later, after the resource manager is created with the proxy. + resourceManagerShim = null; + OletxTransactionManager = transactionManager; + ResourceManagerIdentifier = resourceManagerIdentifier; + + EnlistmentHashtable = new Hashtable(); + ReenlistList = new ArrayList(); + ReenlistPendingList = new ArrayList(); + + ReenlistThreadTimer = null; + reenlistThread = null; + RecoveryCompleteCalledByApplication = false; + } + + internal ResourceManagerShim? ResourceManagerShim + { + get + { + ResourceManagerShim? localResourceManagerShim = null; + + if (resourceManagerShim == null) + { + lock (this) + { + if (resourceManagerShim == null) + { + OletxTransactionManager.DtcTransactionManagerLock.AcquireReaderLock( -1 ); + try + { + Guid rmGuid = ResourceManagerIdentifier; + + OletxTransactionManager.DtcTransactionManager.ProxyShimFactory.CreateResourceManager( + rmGuid, + this, + out localResourceManagerShim); + } + catch (COMException ex) + { + if (ex.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN || + ex.ErrorCode == OletxHelper.XACT_E_TMNOTAVAILABLE) + { + // Just to make sure... + localResourceManagerShim = null; + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + else + { + throw; + } + } + catch (TransactionException ex) + { + if (ex.InnerException is COMException comEx) + { + // Tolerate TM down. + if (comEx.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN || + comEx.ErrorCode == OletxHelper.XACT_E_TMNOTAVAILABLE) + { + // Just to make sure... + localResourceManagerShim = null; + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + else + { + throw; + } + } + else + { + throw; + } + } + finally + { + OletxTransactionManager.DtcTransactionManagerLock.ReleaseReaderLock(); + } + Thread.MemoryBarrier(); + resourceManagerShim = localResourceManagerShim; + } + } + } + return resourceManagerShim; + } + + set + { + Debug.Assert(value == null, "set_ResourceManagerShim, value not null"); + resourceManagerShim = value; + } + } + + internal bool CallProxyReenlistComplete() + { + bool success = false; + if (RecoveryCompleteCalledByApplication) + { + ResourceManagerShim? localResourceManagerShim; + try + { + localResourceManagerShim = ResourceManagerShim; + if (localResourceManagerShim != null) + { + localResourceManagerShim.ReenlistComplete(); + success = true; + } + // If we don't have an iResourceManagerOletx, just tell the caller that + // we weren't successful and it will schedule a retry. + } + catch (COMException ex) + { + // If we get a TMDown error, eat it and indicate that we were unsuccessful. + if (ex.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN || + ex.ErrorCode == OletxHelper.XACT_E_TMNOTAVAILABLE) + { + success = false; + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + + // We might get an XACT_E_RECOVERYALREADYDONE if there are multiple OletxTransactionManager + // objects for the same backend TM. We can safely ignore this error. + else if (ex.ErrorCode != OletxHelper.XACT_E_RECOVERYALREADYDONE) + { + OletxTransactionManager.ProxyException(ex); + throw; + } + // Getting XACT_E_RECOVERYALREADYDONE is considered success. + else + { + success = true; + } + } + finally + { + localResourceManagerShim = null; + } + } + else // The application has not yet called RecoveryComplete, so lie just a little. + { + success = true; + } + + return success; + } + + // This is called by the internal RM when it gets a TM Down notification. This routine will + // tell the enlistments about the TMDown from the internal RM. The enlistments will then + // decide what to do, based on their state. This is mainly to work around COMPlus bug 36760/36758, + // where Phase0 enlistments get Phase0Request( abortHint = false ) when the TM goes down. We want + // to try to avoid telling the application to prepare when we know the transaction will abort. + // We can't do this out of the normal TMDown notification to the RM because it is too late. The + // Phase0Request gets sent before the TMDown notification. + internal void TMDownFromInternalRM(OletxTransactionManager oletxTM) + { + Hashtable localEnlistmentHashtable; + IDictionaryEnumerator enlistEnum; + OletxEnlistment? enlistment; + + // If the internal RM got a TMDown, we will shortly, so null out our ResourceManagerShim now. + ResourceManagerShim = null; + + // Make our own copy of the hashtable of enlistments. + lock (EnlistmentHashtable.SyncRoot) + { + localEnlistmentHashtable = (Hashtable)EnlistmentHashtable.Clone(); + } + + // Tell all of our enlistments that the TM went down. The proxy only + // tells enlistments that are in the Prepared state, but we want our Phase0 + // enlistments to know so they can avoid sending Prepare when they get a + // Phase0Request - COMPlus bug 36760/36758. + enlistEnum = localEnlistmentHashtable.GetEnumerator(); + while (enlistEnum.MoveNext()) + { + enlistment = enlistEnum.Value as OletxEnlistment; + enlistment?.TMDownFromInternalRM(oletxTM); + } + } + + public void TMDown() + { + // The ResourceManagerShim was already set to null by TMDownFromInternalRM, so we don't need to do it again here. + // Just start the ReenlistThread. + StartReenlistThread(); + } + + internal OletxEnlistment EnlistDurable( + OletxTransaction oletxTransaction, + bool canDoSinglePhase, + IEnlistmentNotificationInternal enlistmentNotification, + EnlistmentOptions enlistmentOptions) + { + ResourceManagerShim? localResourceManagerShim; + + Debug.Assert(oletxTransaction != null, "Argument is null" ); + Debug.Assert(enlistmentNotification != null, "Argument is null" ); + + EnlistmentShim enlistmentShim; + Phase0EnlistmentShim phase0Shim; + Guid txUow = Guid.Empty; + bool undecidedEnlistmentsIncremented = false; + + // Create our enlistment object. + OletxEnlistment enlistment = new( + canDoSinglePhase, + enlistmentNotification, + oletxTransaction.RealTransaction.TxGuid, + enlistmentOptions, + this, + oletxTransaction); + + bool enlistmentSucceeded = false; + + try + { + if ((enlistmentOptions & EnlistmentOptions.EnlistDuringPrepareRequired) != 0) + { + oletxTransaction.RealTransaction.IncrementUndecidedEnlistments(); + undecidedEnlistmentsIncremented = true; + } + + // This entire sequence needs to be executed before we can go on. + lock (enlistment) + { + try + { + // Do the enlistment on the proxy. + localResourceManagerShim = ResourceManagerShim; + if (localResourceManagerShim == null) + { + // The TM must be down. Throw the appropriate exception. + throw TransactionManagerCommunicationException.Create(SR.TraceSourceOletx, null); + } + + if ((enlistmentOptions & EnlistmentOptions.EnlistDuringPrepareRequired) != 0) + { + oletxTransaction.RealTransaction.TransactionShim.Phase0Enlist(enlistment, out phase0Shim); + enlistment.Phase0EnlistmentShim = phase0Shim; + } + + localResourceManagerShim.Enlist(oletxTransaction.RealTransaction.TransactionShim, enlistment, out enlistmentShim); + + enlistment.EnlistmentShim = enlistmentShim; + } + catch (COMException comException) + { + // There is no string mapping for XACT_E_TOOMANY_ENLISTMENTS, so we need to do it here. + if (comException.ErrorCode == OletxHelper.XACT_E_TOOMANY_ENLISTMENTS) + { + throw TransactionException.Create( + SR.OletxTooManyEnlistments, + comException, + enlistment == null ? Guid.Empty : enlistment.DistributedTxId); + } + + OletxTransactionManager.ProxyException(comException); + + throw; + } + } + + enlistmentSucceeded = true; + } + finally + { + if (!enlistmentSucceeded && + (enlistmentOptions & EnlistmentOptions.EnlistDuringPrepareRequired) != 0 && + undecidedEnlistmentsIncremented) + { + oletxTransaction.RealTransaction.DecrementUndecidedEnlistments(); + } + } + + return enlistment; + } + + internal OletxEnlistment Reenlist(byte[] prepareInfo, IEnlistmentNotificationInternal enlistmentNotification) + { + OletxTransactionOutcome outcome = OletxTransactionOutcome.NotKnownYet; + OletxTransactionStatus xactStatus = OletxTransactionStatus.OLETX_TRANSACTION_STATUS_NONE; + + if (prepareInfo == null) + { + throw new ArgumentException(SR.InvalidArgument, nameof(prepareInfo)); + } + + // Verify that the resource manager guid in the recovery info matches that of the calling resource manager. + byte[] rmGuidArray = new byte[16]; + for (int i = 0; i < 16; i++) + { + rmGuidArray[i] = prepareInfo[i + 16]; + } + Guid rmGuid = new(rmGuidArray); + if (rmGuid != ResourceManagerIdentifier) + { + throw TransactionException.Create(TraceSourceType.TraceSourceOleTx, SR.ResourceManagerIdDoesNotMatchRecoveryInformation, null); + } + + // Ask the proxy resource manager to reenlist. + ResourceManagerShim? localResourceManagerShim = null; + try + { + localResourceManagerShim = ResourceManagerShim; + if (localResourceManagerShim == null) + { + // The TM must be down. Throw the exception that will get caught below and will cause + // the enlistment to start the ReenlistThread. The TMDown thread will be trying to reestablish + // connection with the TM and will start the reenlist thread when it does. + throw new COMException(SR.DtcTransactionManagerUnavailable, OletxHelper.XACT_E_CONNECTION_DOWN); + } + + // Only wait for 5 milliseconds. If the TM doesn't have the outcome now, we will + // put the enlistment on the reenlistList for later processing. + localResourceManagerShim.Reenlist(prepareInfo, out outcome); + + if (OletxTransactionOutcome.Committed == outcome) + { + xactStatus = OletxTransactionStatus.OLETX_TRANSACTION_STATUS_COMMITTED; + } + else if (OletxTransactionOutcome.Aborted == outcome) + { + xactStatus = OletxTransactionStatus.OLETX_TRANSACTION_STATUS_ABORTED; + } + else // we must not know the outcome yet. + { + xactStatus = OletxTransactionStatus.OLETX_TRANSACTION_STATUS_PREPARED; + StartReenlistThread(); + } + } + catch (COMException ex) when (ex.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN ) + { + xactStatus = OletxTransactionStatus.OLETX_TRANSACTION_STATUS_PREPARED; + ResourceManagerShim = null; + StartReenlistThread(); + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + finally + { + localResourceManagerShim = null; + } + + // Now create our enlistment to tell the client the outcome. + return new OletxEnlistment(enlistmentNotification, xactStatus, prepareInfo, this); + } + + internal void RecoveryComplete() + { + Timer? localTimer = null; + + // Remember that the application has called RecoveryComplete. + RecoveryCompleteCalledByApplication = true; + + try + { + // Remove the OletxEnlistment objects from the reenlist list because the RM says it doesn't + // have any unresolved transactions, so we don't need to keep asking and the reenlist thread can exit. + // Leave the reenlistPendingList alone. If we have notifications outstanding, we still can't remove those. + lock (ReenlistList) + { + // If the ReenlistThread is not running and there are no reenlistPendingList entries, we need to call ReenlistComplete ourself. + lock (this) + { + if (ReenlistList.Count == 0 && ReenlistPendingList.Count == 0) + { + if (ReenlistThreadTimer != null) + { + // If we have a pending reenlistThreadTimer, cancel it. We do the cancel + // in the finally block to satisfy FXCop. + localTimer = ReenlistThreadTimer; + ReenlistThreadTimer = null; + } + + // Try to tell the proxy ReenlistmentComplete. + bool success = CallProxyReenlistComplete(); + if (!success) + { + // We are now responsible for calling RecoveryComplete. Fire up the ReenlistThread + // to do it for us. + StartReenlistThread(); + } + } + else + { + StartReenlistThread(); + } + } + } + } + finally + { + if (localTimer != null) + { + localTimer.Dispose(); + } + } + } + + internal void StartReenlistThread() + { + // We are not going to check the reenlistList.Count. Just always start the thread. We do this because + // if we get a COMException from calling ReenlistComplete, we start the reenlistThreadTimer to retry it for us + // in the background. + lock (this) + { + // We don't need a MemoryBarrier here because all access to the reenlistThreadTimer member is done while + // holding a lock on the OletxResourceManager object. + if (ReenlistThreadTimer == null && reenlistThread == null) + { + ReenlistThreadTimer = new Timer(ReenlistThread, this, 10, Timeout.Infinite); + } + } + } + + // This routine searches the reenlistPendingList for the specified enlistment and if it finds + // it, removes it from the list. An enlistment calls this routine when it is "finishing" because + // the RM has called EnlistmentDone or it was InDoubt. But it only calls it if the enlistment does NOT + // have a WrappedTransactionEnlistmentAsync value, indicating that it is a recovery enlistment. + internal void RemoveFromReenlistPending(OletxEnlistment enlistment) + { + // We lock the reenlistList because we have decided to lock that list when accessing either + // the reenlistList or the reenlistPendingList. + lock (ReenlistList) + { + // This will do a linear search of the list, but that is what we need to do because + // the enlistments may change indicies while notifications are outstanding. Also, + // this does not throw if the enlistment isn't on the list. + ReenlistPendingList.Remove(enlistment); + + lock (this) + { + // If we have a ReenlistThread timer and both the reenlistList and the reenlistPendingList + // are empty, kick the ReenlistThread now. + if (ReenlistThreadTimer != null && ReenlistList.Count == 0 && ReenlistPendingList.Count == 0) + { + if (!ReenlistThreadTimer.Change( 0, Timeout.Infinite)) + { + throw TransactionException.CreateInvalidOperationException( + TraceSourceType.TraceSourceOleTx, + SR.UnexpectedTimerFailure, + null); + } + } + } + } + } + + internal void ReenlistThread(object? state) + { + int localLoopCount; + bool done; + OletxEnlistment? localEnlistment; + ResourceManagerShim? localResourceManagerShim; + bool success; + Timer? localTimer = null; + bool disposeLocalTimer = false; + + OletxResourceManager resourceManager = (OletxResourceManager)state!; + + try + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxResourceManager)}.{nameof(ReenlistThread)}"); + } + + lock (resourceManager) + { + localResourceManagerShim = resourceManager.ResourceManagerShim; + localTimer = resourceManager.ReenlistThreadTimer; + resourceManager.ReenlistThreadTimer = null; + resourceManager.reenlistThread = Thread.CurrentThread; + } + + // We only want to do work if we have a resourceManagerShim. + if (localResourceManagerShim != null) + { + lock (resourceManager.ReenlistList) + { + // Get the current count on the list. + localLoopCount = resourceManager.ReenlistList.Count; + } + + done = false; + while (!done && localLoopCount > 0 && localResourceManagerShim != null) + { + lock (resourceManager.ReenlistList) + { + localEnlistment = null; + localLoopCount--; + if (resourceManager.ReenlistList.Count == 0) + { + done = true; + } + else + { + localEnlistment = resourceManager.ReenlistList[0] as OletxEnlistment; + if (localEnlistment == null) + { + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + throw TransactionException.Create(SR.InternalError, null); + } + + resourceManager.ReenlistList.RemoveAt(0); + object syncRoot = localEnlistment; + lock (syncRoot) + { + if (OletxEnlistment.OletxEnlistmentState.Done == localEnlistment.State) + { + // We may be racing with a RecoveryComplete here. Just forget about this + // enlistment. + localEnlistment = null; + } + + else if (OletxEnlistment.OletxEnlistmentState.Prepared != localEnlistment.State) + { + // The app hasn't yet responded to Prepare, so we don't know + // if it is indoubt or not yet. So just re-add it to the end + // of the list. + resourceManager.ReenlistList.Add(localEnlistment); + localEnlistment = null; + } + } + } + } + + if (localEnlistment != null) + { + OletxTransactionOutcome localOutcome = OletxTransactionOutcome.NotKnownYet; + try + { + Debug.Assert(localResourceManagerShim != null, "ReenlistThread - localResourceManagerShim is null" ); + + // Make sure we have a prepare info. + if (localEnlistment.ProxyPrepareInfoByteArray == null) + { + Debug.Assert(false, string.Format(null, "this.prepareInfoByteArray == null in RecoveryInformation()")); + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + throw TransactionException.Create(SR.InternalError, null); + } + + localResourceManagerShim.Reenlist(localEnlistment.ProxyPrepareInfoByteArray, out localOutcome); + + if (localOutcome == OletxTransactionOutcome.NotKnownYet) + { + object syncRoot = localEnlistment; + lock (syncRoot) + { + if (OletxEnlistment.OletxEnlistmentState.Done == localEnlistment.State) + { + // We may be racing with a RecoveryComplete here. Just forget about this + // enlistment. + localEnlistment = null; + } + else + { + // Put the enlistment back on the end of the list for retry later. + lock (resourceManager.ReenlistList) + { + resourceManager.ReenlistList.Add(localEnlistment); + localEnlistment = null; + } + } + } + } + } + catch (COMException ex) when (ex.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN) + { + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + + // Release the resource manager so we can create a new one. + resourceManager.ResourceManagerShim = null; + + // Now create a new resource manager with the proxy. + localResourceManagerShim = resourceManager.ResourceManagerShim; + } + + // If we get here and we still have localEnlistment, then we got the outcome. + if (localEnlistment != null) + { + object syncRoot = localEnlistment; + lock (syncRoot) + { + if (OletxEnlistment.OletxEnlistmentState.Done == localEnlistment.State) + { + // We may be racing with a RecoveryComplete here. Just forget about this + // enlistment. + localEnlistment = null; + } + else + { + // We are going to send the notification to the RM. We need to put the + // enlistment on the reenlistPendingList. We lock the reenlistList because + // we have decided that is the lock that protects both lists. The entry will + // be taken off the reenlistPendingList when the enlistment has + // EnlistmentDone called on it. The enlistment will call + // RemoveFromReenlistPending. + lock (resourceManager.ReenlistList) + { + resourceManager.ReenlistPendingList.Add(localEnlistment); + } + + if (localOutcome == OletxTransactionOutcome.Committed) + { + localEnlistment.State = OletxEnlistment.OletxEnlistmentState.Committing; + + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceOleTx, localEnlistment.EnlistmentTraceId, NotificationCall.Commit); + } + + localEnlistment.EnlistmentNotification!.Commit(localEnlistment); + } + else if (localOutcome == OletxTransactionOutcome.Aborted) + { + localEnlistment.State = OletxEnlistment.OletxEnlistmentState.Aborting; + + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceOleTx, localEnlistment.EnlistmentTraceId, NotificationCall.Rollback); + } + + localEnlistment.EnlistmentNotification!.Rollback(localEnlistment); + } + else + { + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + throw TransactionException.Create(SR.InternalError, null); + } + } + } + } // end of if null != localEnlistment + } // end of if null != localEnlistment + } + } + + localResourceManagerShim = null; + + // Check to see if there is more work to do. + lock (resourceManager.ReenlistList) + { + lock (resourceManager) + { + // Get the current count on the list. + localLoopCount = resourceManager.ReenlistList.Count; + if (localLoopCount <= 0 && resourceManager.ReenlistPendingList.Count <= 0) + { + // No more entries on the list. Try calling ReenlistComplete on the proxy, if + // appropriate. + // If the application has called RecoveryComplete, + // we are responsible for calling ReenlistComplete on the + // proxy. + success = resourceManager.CallProxyReenlistComplete(); + if (success) + { + // Okay, the reenlist thread is done and we don't need to schedule another one. + disposeLocalTimer = true; + } + else + { + // We couldn't talk to the proxy to do ReenlistComplete, so schedule + // the thread again for 10 seconds from now. + resourceManager.ReenlistThreadTimer = localTimer; + if (!localTimer!.Change(10000, Timeout.Infinite)) + { + throw TransactionException.CreateInvalidOperationException( + TraceSourceType.TraceSourceLtm, + SR.UnexpectedTimerFailure, + null); + } + } + } + else + { + // There are still entries on the list, so they must not be + // resovled, yet. Schedule the thread again in 10 seconds. + resourceManager.ReenlistThreadTimer = localTimer; + if (!localTimer!.Change(10000, Timeout.Infinite)) + { + throw TransactionException.CreateInvalidOperationException( + TraceSourceType.TraceSourceLtm, + SR.UnexpectedTimerFailure, + null); + } + } + + resourceManager.reenlistThread = null; + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxResourceManager)}.{nameof(ReenlistThread)}"); + } + } + } // end of outer-most try + finally + { + localResourceManagerShim = null; + if (disposeLocalTimer && localTimer != null) + { + localTimer.Dispose(); + } + } + } // end of ReenlistThread method; +} + +// This is the base class for all enlistment objects. The enlistment objects provide the callback +// that is made from the application and pass it through to the proxy. +internal abstract class OletxBaseEnlistment +{ + protected Guid EnlistmentGuid; + protected OletxResourceManager OletxResourceManager; + protected OletxTransaction? oletxTransaction; + internal OletxTransaction? OletxTransaction => oletxTransaction; + + internal Guid DistributedTxId + { + get + { + Guid returnValue = Guid.Empty; + + if (OletxTransaction != null) + { + returnValue = OletxTransaction.DistributedTxId; + } + return returnValue; + } + } + + protected string TransactionGuidString; + protected int EnlistmentId; + // this needs to be internal so it can be set from the recovery information during Reenlist. + internal EnlistmentTraceIdentifier TraceIdentifier; + + // Owning public Enlistment object + protected InternalEnlistment? InternalEnlistment; + + public OletxBaseEnlistment(OletxResourceManager oletxResourceManager, OletxTransaction? oletxTransaction) + { + Guid resourceManagerId = Guid.Empty; + + EnlistmentGuid = Guid.NewGuid(); + OletxResourceManager = oletxResourceManager; + this.oletxTransaction = oletxTransaction; + if (oletxTransaction != null) + { + EnlistmentId = oletxTransaction.RealOletxTransaction._enlistmentCount++; + TransactionGuidString = oletxTransaction.RealOletxTransaction.TxGuid.ToString(); + } + else + { + TransactionGuidString = Guid.Empty.ToString(); + } + TraceIdentifier = EnlistmentTraceIdentifier.Empty; + } + + protected EnlistmentTraceIdentifier InternalTraceIdentifier + { + get + { + if (EnlistmentTraceIdentifier.Empty == TraceIdentifier ) + { + lock (this) + { + if (EnlistmentTraceIdentifier.Empty == TraceIdentifier ) + { + Guid rmId = Guid.Empty; + if (OletxResourceManager != null) + { + rmId = OletxResourceManager.ResourceManagerIdentifier; + } + EnlistmentTraceIdentifier temp; + if (oletxTransaction != null) + { + temp = new EnlistmentTraceIdentifier(rmId, oletxTransaction.TransactionTraceId, EnlistmentId); + } + else + { + TransactionTraceIdentifier txTraceId = new(TransactionGuidString, 0); + temp = new EnlistmentTraceIdentifier( rmId, txTraceId, EnlistmentId); + } + Thread.MemoryBarrier(); + TraceIdentifier = temp; + } + } + } + + return TraceIdentifier; + } + } + + protected void AddToEnlistmentTable() + { + lock (OletxResourceManager.EnlistmentHashtable.SyncRoot) + { + OletxResourceManager.EnlistmentHashtable.Add(EnlistmentGuid, this); + } + } + + protected void RemoveFromEnlistmentTable() + { + lock (OletxResourceManager.EnlistmentHashtable.SyncRoot) + { + OletxResourceManager.EnlistmentHashtable.Remove(EnlistmentGuid); + } + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxTransaction.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxTransaction.cs new file mode 100644 index 00000000000000..6f18c18cc8143f --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxTransaction.cs @@ -0,0 +1,1373 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Threading; +using System.Transactions.DtcProxyShim; + +namespace System.Transactions.Oletx +{ + /// + /// A Transaction object represents a single transaction. It is created by TransactionManager + /// objects through CreateTransaction or through deserialization. Alternatively, the static Create + /// method is provided, which creates a "default" TransactionManager and requests that it create + /// a new transaction with default values. A transaction can only be committed by + /// the client application that created the transaction. If a client application wishes to allow + /// access to the transaction by multiple threads, but wants to prevent those other threads from + /// committing the transaction, the application can make a "clone" of the transaction. Transaction + /// clones have the same capabilities as the original transaction, except for the ability to commit + /// the transaction. + /// + [Serializable] + internal class OletxTransaction : ISerializable, IObjectReference + { + // We have a strong reference on realOletxTransaction which does the real work + internal RealOletxTransaction RealOletxTransaction; + + // String that is used as a name for the propagationToken + // while serializing and deserializing this object + protected const string PropagationTokenString = "OletxTransactionPropagationToken"; + + // When an OletxTransaction is being created via deserialization, this member is + // filled with the propagation token from the serialization info. Later, when + // GetRealObject is called, this array is used to decide whether or not a new + // transation needs to be created and if so, to create the transaction. + private byte[]? _propagationTokenForDeserialize; + + protected int Disposed; + + // In GetRealObject, we ask LTM if it has a promoted transaction with the same ID. If it does, + // we need to remember that transaction because GetRealObject is called twice during + // deserialization. In this case, GetRealObject returns the LTM transaction, not this OletxTransaction. + // The OletxTransaction will get GC'd because there will be no references to it. + internal Transaction? SavedLtmPromotedTransaction; + + private TransactionTraceIdentifier _traceIdentifier = TransactionTraceIdentifier.Empty; + + // Property + internal RealOletxTransaction RealTransaction + => RealOletxTransaction; + + internal Guid Identifier + { + get + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxTransaction)}.{nameof(Identifier)}"); + } + + Guid returnValue = RealOletxTransaction.Identifier; + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxTransaction)}.{nameof(Identifier)}"); + } + + return returnValue; + } + } + + internal Guid DistributedTxId + { + get + { + Guid returnValue = Guid.Empty; + + if (RealOletxTransaction != null && RealOletxTransaction.InternalTransaction != null) + { + returnValue = RealOletxTransaction.InternalTransaction.DistributedTxId; + } + + return returnValue; + } + } + + internal TransactionStatus Status + { + get + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxTransaction)}.{nameof(Status)}"); + } + + TransactionStatus returnValue = RealOletxTransaction.Status; + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxTransaction)}.{nameof(Status)}"); + } + + return returnValue; + } + } + + internal Exception? InnerException + => RealOletxTransaction.InnerException; + + internal OletxTransaction(RealOletxTransaction realOletxTransaction) + { + RealOletxTransaction = realOletxTransaction; + + // Tell the realOletxTransaction that we are here. + RealOletxTransaction.OletxTransactionCreated(); + } + + protected OletxTransaction(SerializationInfo? serializationInfo, StreamingContext context) + { + if (serializationInfo == null) + { + throw new ArgumentNullException(nameof(serializationInfo)); + } + + // Simply store the propagation token from the serialization info. GetRealObject will + // decide whether or not we will use it. + _propagationTokenForDeserialize = (byte[])serializationInfo.GetValue(PropagationTokenString, typeof(byte[]))!; + + if (_propagationTokenForDeserialize.Length < 24) + { + throw new ArgumentException(SR.InvalidArgument, nameof(serializationInfo)); + } + + RealOletxTransaction = null!; + } + + public object GetRealObject(StreamingContext context) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(IObjectReference)}.{nameof(GetRealObject)}"); + } + + if (_propagationTokenForDeserialize == null) + { + if (etwLog.IsEnabled()) + { + etwLog.InternalError(SR.UnableToDeserializeTransaction); + } + + throw TransactionException.Create(SR.UnableToDeserializeTransactionInternalError, null); + } + + // This may be a second call. If so, just return. + if (SavedLtmPromotedTransaction != null) + { + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(IObjectReference)}.{nameof(GetRealObject)}"); + } + + return SavedLtmPromotedTransaction; + } + + Transaction returnValue = TransactionInterop.GetTransactionFromTransmitterPropagationToken(_propagationTokenForDeserialize); + Debug.Assert(returnValue != null, "OletxTransaction.GetRealObject - GetTxFromPropToken returned null"); + + SavedLtmPromotedTransaction = returnValue; + + if (etwLog.IsEnabled()) + { + etwLog.TransactionDeserialized(returnValue._internalTransaction.PromotedTransaction!.TransactionTraceId); + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(IObjectReference)}.{nameof(GetRealObject)}"); + } + + return returnValue; + } + + /// + /// Implementation of IDisposable.Dispose. Releases managed, and unmanaged resources + /// associated with the Transaction object. + /// + internal void Dispose() + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(IDisposable)}.{nameof(Dispose)}"); + } + + int localDisposed = Interlocked.CompareExchange(ref Disposed, 1, 0); + if (localDisposed == 0) + { + RealOletxTransaction.OletxTransactionDisposed(); + } + GC.SuppressFinalize(this); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(IDisposable)}.{nameof(Dispose)}"); + } + } + + // Specific System.Transactions implementation + + /// + /// Initiates commit processing of the transaction. The caller must have created the transaction + /// as a new transaction through TransactionManager.CreateTransaction or Transaction.Create. + /// + /// If the transaction is already aborted due to some other participant making a Rollback call, + /// the transaction timeout period expiring, or some sort of network failure, an exception will + /// be raised. + /// + /// + /// Initiates rollback processing of the transaction. This method can be called on any instance + /// of a Transaction class, regardless of how the Transaction was obtained. It is possible for this + /// method to be called "too late", after the outcome of the transaction has already been determined. + /// In this case, an exception is raised. + /// + internal void Rollback() + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxTransaction)}.{nameof(Rollback)}"); + etwLog.TransactionRollback(TraceSourceType.TraceSourceOleTx, TransactionTraceId, "Transaction"); + } + + Debug.Assert(Disposed == 0, "OletxTransction object is disposed"); + + RealOletxTransaction.Rollback(); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxTransaction)}.{nameof(Rollback)}"); + } + } + + internal IPromotedEnlistment EnlistVolatile( + ISinglePhaseNotificationInternal singlePhaseNotification, + EnlistmentOptions enlistmentOptions) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxEnlistment)}.{nameof(EnlistVolatile)}(({nameof(ISinglePhaseNotificationInternal)}"); + } + + Debug.Assert(singlePhaseNotification != null, "Argument is null"); + Debug.Assert(Disposed == 0, "OletxTransction object is disposed"); + + if (RealOletxTransaction == null || RealOletxTransaction.TooLateForEnlistments) + { + throw TransactionException.Create(SR.TooLate, null, DistributedTxId); + } + + IPromotedEnlistment enlistment = RealOletxTransaction.EnlistVolatile( + singlePhaseNotification, + enlistmentOptions, + this); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxEnlistment)}.{nameof(EnlistVolatile)}(({nameof(ISinglePhaseNotificationInternal)}"); + } + + return enlistment; + } + + internal IPromotedEnlistment EnlistVolatile( + IEnlistmentNotificationInternal enlistmentNotification, + EnlistmentOptions enlistmentOptions) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxTransaction)}.{nameof(EnlistVolatile)}({nameof(IEnlistmentNotificationInternal)}"); + } + + Debug.Assert(enlistmentNotification != null, "Argument is null"); + Debug.Assert(Disposed == 0, "OletxTransction object is disposed"); + + if (RealOletxTransaction == null || RealOletxTransaction.TooLateForEnlistments ) + { + throw TransactionException.Create(SR.TooLate, null, DistributedTxId); + } + + IPromotedEnlistment enlistment = RealOletxTransaction.EnlistVolatile( + enlistmentNotification, + enlistmentOptions, + this); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxTransaction)}.{nameof(EnlistVolatile)}({nameof(IEnlistmentNotificationInternal)}"); + } + + return enlistment; + } + + internal IPromotedEnlistment EnlistDurable( + Guid resourceManagerIdentifier, + ISinglePhaseNotificationInternal singlePhaseNotification, + bool canDoSinglePhase, + EnlistmentOptions enlistmentOptions) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter( + TraceSourceType.TraceSourceOleTx, + this, + $"{nameof(OletxTransaction)}.{nameof(EnlistDurable)}({nameof(ISinglePhaseNotificationInternal)})"); + } + + Debug.Assert(Disposed == 0, "OletxTransction object is disposed"); + + if (RealOletxTransaction == null || RealOletxTransaction.TooLateForEnlistments) + { + throw TransactionException.Create(SR.TooLate, null, DistributedTxId); + } + + // get the Oletx TM from the real class + OletxTransactionManager oletxTM = RealOletxTransaction.OletxTransactionManagerInstance; + + // get the resource manager from the Oletx TM + OletxResourceManager rm = oletxTM.FindOrRegisterResourceManager(resourceManagerIdentifier); + + // ask the rm to do the durable enlistment + OletxEnlistment enlistment = rm.EnlistDurable( + this, + canDoSinglePhase, + singlePhaseNotification, + enlistmentOptions); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit( + TraceSourceType.TraceSourceOleTx, + this, + $"{nameof(OletxTransaction)}.{nameof(EnlistDurable)}({nameof(ISinglePhaseNotificationInternal)})"); + } + + return enlistment; + } + + + internal OletxDependentTransaction DependentClone(bool delayCommit) + { + OletxDependentTransaction dependentClone; + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxTransaction)}.{nameof(DependentClone)}"); + } + + Debug.Assert(Disposed == 0, "OletxTransction object is disposed"); + + if (TransactionStatus.Aborted == Status) + { + throw TransactionAbortedException.Create( + SR.TransactionAborted, RealOletxTransaction.InnerException, DistributedTxId); + } + if (TransactionStatus.InDoubt == Status) + { + throw TransactionInDoubtException.Create( + SR.TransactionIndoubt, RealOletxTransaction.InnerException, DistributedTxId); + } + if (TransactionStatus.Active != Status) + { + throw TransactionException.Create(SR.TransactionAlreadyOver, null, DistributedTxId); + } + + dependentClone = new OletxDependentTransaction(RealOletxTransaction, delayCommit); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxTransaction)}.{nameof(DependentClone)}"); + } + + return dependentClone; + + } + + internal TransactionTraceIdentifier TransactionTraceId + { + get + { + if (_traceIdentifier == TransactionTraceIdentifier.Empty) + { + lock (RealOletxTransaction) + { + if (_traceIdentifier == TransactionTraceIdentifier.Empty) + { + try + { + TransactionTraceIdentifier temp = new(RealOletxTransaction.Identifier.ToString(), 0); + Thread.MemoryBarrier(); + _traceIdentifier = temp; + } + catch (TransactionException ex) + { + // realOletxTransaction.Identifier throws a TransactionException if it can't determine the guid of the + // transaction because the transaction was already committed or aborted before the RealOletxTransaction was + // created. If that happens, we don't want to throw just because we are trying to trace. So just use + // the TransactionTraceIdentifier.Empty. + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + + } + } + } + return _traceIdentifier; + } + } + + public void GetObjectData(SerializationInfo serializationInfo, StreamingContext context) + { + if (serializationInfo == null) + { + throw new ArgumentNullException(nameof(serializationInfo)); + } + + byte[] propagationToken; + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxTransaction)}.{nameof(GetObjectData)}"); + } + + Debug.Assert(Disposed == 0, "OletxTransction object is disposed"); + + propagationToken = TransactionInterop.GetTransmitterPropagationToken(this); + + serializationInfo.SetType(typeof(OletxTransaction)); + serializationInfo.AddValue(PropagationTokenString, propagationToken); + + if (etwLog.IsEnabled()) + { + etwLog.TransactionSerialized(TransactionTraceId); + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxTransaction)}.{nameof(GetObjectData)}"); + } + } + + public virtual IsolationLevel IsolationLevel + => RealOletxTransaction.TransactionIsolationLevel; + } + + // Internal class used by OletxTransaction class which is public + internal sealed class RealOletxTransaction + { + // Transaction manager + internal OletxTransactionManager OletxTransactionManagerInstance { get; } + + private TransactionShim? _transactionShim; + + // guid related to transaction + internal Guid TxGuid { get; private set; } + + // Isolation level of the transaction + internal IsolationLevel TransactionIsolationLevel { get; private set; } + + // Record the exception that caused the transaction to abort. + internal Exception? InnerException; + + // Store status + internal TransactionStatus Status { get; private set; } + + // This is the count of undisposed OletxTransaction objects that reference + // this RealOletxTransaction. This is incremented when an OletxTransaction is created + // and decremented when OletxTransactionDisposed is + // called. When it is decremented to zero, the transactionShim + // field is "released", thus releasing the unmanged proxy interface + // pointer. + private int _undisposedOletxTransactionCount; + + // The list of containers for phase0 volatile enlistment multiplexing so we only enlist with the proxy once per wave. + // The last one on the list is the "current" one. + internal ArrayList? Phase0EnlistVolatilementContainerList; + + // The container for phase1 volatile enlistment multiplexing so we only enlist with the proxy once. + internal OletxPhase1VolatileEnlistmentContainer? Phase1EnlistVolatilementContainer; + + // Used to get outcomes of transactions with a voter. + private OutcomeEnlistment? _outcomeEnlistment; + + // This is a count of volatile and Phase0 durable enlistments on this transaction that have not yet voted. + // This is incremented when an enlistment is made and decremented when the + // enlistment votes. It is checked in Rollback. If the count is greater than 0, + // then the doomed field is set to true and the Rollback is allowed. If the count + // is zero in Rollback, the rollback is rejected with a "too late" exception. + // All checking and modification of this field needs to be done under a lock( this ). + private int _undecidedEnlistmentCount; + + // If true, indicates that the transaction should NOT commit. This is set to + // true if Rollback is called when there are outstanding enlistments. This is + // checked when enlistments vote Prepared. If true, then the enlistment's vote + // is turned into a ForceRollback. All checking and modification of this field + // needs to be done under a lock (this). + internal bool Doomed { get; private set; } + + // This property is used to allocate enlistment identifiers for enlistment trace identifiers. + // It is only incremented when a new enlistment is created for this instance of RealOletxTransaction. + // Enlistments on all clones of this Real transaction use this value. + internal int _enlistmentCount; + + private DateTime _creationTime; + private DateTime _lastStateChangeTime; + private TransactionTraceIdentifier _traceIdentifier = TransactionTraceIdentifier.Empty; + + // This field is set directly from the OletxCommittableTransaction constructor. It will be null + // for non-root RealOletxTransactions. + internal OletxCommittableTransaction? CommittableTransaction; + + // This is an internal OletxTransaction. It is created as part of the RealOletxTransaction constructor. + // It is used by the DependentCloneEnlistments when creating their volatile enlistments. + internal OletxTransaction InternalClone; + + // This is set initialized to false. It is set to true when the OletxPhase1VolatileContainer gets a VoteRequest or + // when any OletxEnlistment attached to this transaction gets a PrepareRequest. At that point, it is too late for any + // more enlistments. + internal bool TooLateForEnlistments { get; set; } + + // This is the InternalTransaction that instigated creation of this RealOletxTransaction. When we get the outcome + // of the transaction, we use this to notify the InternalTransaction of the outcome. We do this to avoid the LTM + // always creating a volatile enlistment just to get the outcome. + internal InternalTransaction? InternalTransaction { get; set; } + + internal Guid Identifier + { + get + { + // The txGuid will be empty if the oletx transaction was already committed or aborted when we + // tried to create the RealOletxTransaction. We still allow creation of the RealOletxTransaction + // for COM+ interop purposes, but we can't get the guid or the status of the transaction. + if (TxGuid.Equals(Guid.Empty)) + { + throw TransactionException.Create(SR.GetResourceString(SR.CannotGetTransactionIdentifier), null); + } + + return TxGuid; + } + } + + internal Guid DistributedTxId + { + get + { + Guid returnValue = Guid.Empty; + + if (InternalTransaction != null) + { + returnValue = InternalTransaction.DistributedTxId; + } + + return returnValue; + } + } + + internal void IncrementUndecidedEnlistments() + { + // Avoid taking a lock on the transaction here. Decrement + // will be called by a thread owning a lock on enlistment + // containers. When creating new enlistments the transaction + // will attempt to get a lock on the container when it + // already holds a lock on the transaction. This can result + // in a deadlock. + Interlocked.Increment(ref _undecidedEnlistmentCount); + } + + internal void DecrementUndecidedEnlistments() + { + // Avoid taking a lock on the transaction here. Decrement + // will be called by a thread owning a lock on enlistment + // containers. When creating new enlistments the transaction + // will attempt to get a lock on the container when it + // already holds a lock on the transaction. This can result + // in a deadlock. + Interlocked.Decrement(ref _undecidedEnlistmentCount); + } + + internal int UndecidedEnlistments + => _undecidedEnlistmentCount; + + internal TransactionShim TransactionShim + { + get + { + TransactionShim? shim = _transactionShim; + if (shim == null) + { + throw TransactionInDoubtException.Create(SR.TransactionIndoubt, null, DistributedTxId); + } + + return shim; + } + } + + // Common constructor used by all types of constructors + // Create a clean and fresh transaction. + internal RealOletxTransaction( + OletxTransactionManager transactionManager, + TransactionShim? transactionShim, + OutcomeEnlistment? outcomeEnlistment, + Guid identifier, + OletxTransactionIsolationLevel oletxIsoLevel, + bool isRoot) + { + bool successful = false; + + try + { + // initialize the member fields + OletxTransactionManagerInstance = transactionManager; + _transactionShim = transactionShim; + _outcomeEnlistment = outcomeEnlistment; + TxGuid = identifier; + TransactionIsolationLevel = OletxTransactionManager.ConvertIsolationLevelFromProxyValue(oletxIsoLevel); + Status = TransactionStatus.Active; + _undisposedOletxTransactionCount = 0; + Phase0EnlistVolatilementContainerList = null; + Phase1EnlistVolatilementContainer = null; + TooLateForEnlistments = false; + InternalTransaction = null; + + _creationTime = DateTime.UtcNow; + _lastStateChangeTime = _creationTime; + + // Connect this object with the OutcomeEnlistment. + InternalClone = new OletxTransaction( this ); + + // We have have been created without an outcome enlistment if it was too late to create + // a clone from the ITransactionNative that we were created from. + if (_outcomeEnlistment != null) + { + _outcomeEnlistment.SetRealTransaction(this); + } + else + { + Status = TransactionStatus.InDoubt; + } + + successful = true; + } + finally + { + if (!successful) + { + if (_outcomeEnlistment != null) + { + _outcomeEnlistment.UnregisterOutcomeCallback(); + _outcomeEnlistment = null; + } + } + } + } + + internal OletxVolatileEnlistmentContainer AddDependentClone(bool delayCommit) + { + Phase0EnlistmentShim? phase0Shim = null; + VoterBallotShim? voterShim = null; + bool needVoterEnlistment = false; + bool needPhase0Enlistment = false; + OletxVolatileEnlistmentContainer? returnValue = null; + OletxPhase0VolatileEnlistmentContainer? localPhase0VolatileContainer = null; + OletxPhase1VolatileEnlistmentContainer? localPhase1VolatileContainer = null; + bool phase0ContainerLockAcquired = false; + + // Yes, we are talking to the proxy while holding the lock on the RealOletxTransaction. + // If we don't then things get real sticky with other threads allocating containers. + // We only do this the first time we get a depenent clone of a given type (delay vs. non-delay). + // After that, we don't create a new container, except for Phase0 if we need to create one + // for a second wave. + try + { + lock (this) + { + if (delayCommit) + { + // Not using a MemoryBarrier because all access to this member variable is under a lock of the + // object. + Phase0EnlistVolatilementContainerList ??= new ArrayList(1); + + // We may have failed the proxy enlistment for the first container, but we would have + // allocated the list. That is why we have this check here. + if (Phase0EnlistVolatilementContainerList.Count == 0) + { + localPhase0VolatileContainer = new OletxPhase0VolatileEnlistmentContainer(this); + needPhase0Enlistment = true; + } + else + { + localPhase0VolatileContainer = Phase0EnlistVolatilementContainerList[^1] as OletxPhase0VolatileEnlistmentContainer; + + if (localPhase0VolatileContainer != null) + { + TakeContainerLock(localPhase0VolatileContainer, ref phase0ContainerLockAcquired); + } + + if (!localPhase0VolatileContainer!.NewEnlistmentsAllowed) + { + //It is OK to release the lock at this time because we are creating a new container that has not yet + //been enlisted with DTC. So there is no race to worry about + ReleaseContainerLock(localPhase0VolatileContainer, ref phase0ContainerLockAcquired); + + localPhase0VolatileContainer = new OletxPhase0VolatileEnlistmentContainer( this ); + needPhase0Enlistment = true; + } + else + { + needPhase0Enlistment = false; + } + } + } + else // ! delayCommit + { + if (Phase1EnlistVolatilementContainer == null) + { + localPhase1VolatileContainer = new OletxPhase1VolatileEnlistmentContainer(this); + needVoterEnlistment = true; + } + else + { + needVoterEnlistment = false; + localPhase1VolatileContainer = Phase1EnlistVolatilementContainer; + } + } + + try + { + //At this point, we definitely need the lock on the phase0 container so that it doesnt race with shim notifications from unmanaged code + //corrupting state while we are in the middle of an AddDependentClone processing + if (localPhase0VolatileContainer != null) + { + TakeContainerLock(localPhase0VolatileContainer, ref phase0ContainerLockAcquired); + } + + // If enlistDuringPrepareRequired is true, we need to ask the proxy to create a Phase0 enlistment. + if (needPhase0Enlistment) + { + _transactionShim!.Phase0Enlist(localPhase0VolatileContainer!, out phase0Shim); + localPhase0VolatileContainer!.Phase0EnlistmentShim = phase0Shim; + } + + if (needVoterEnlistment) + { + // We need to use shims if native threads are not allowed to enter managed code. + OletxTransactionManagerInstance.DtcTransactionManagerLock.AcquireReaderLock(-1); + try + { + _transactionShim!.CreateVoter(localPhase1VolatileContainer!, out voterShim); + } + finally + { + OletxTransactionManagerInstance.DtcTransactionManagerLock.ReleaseReaderLock(); + } + + localPhase1VolatileContainer!.VoterBallotShim = voterShim; + } + + if (delayCommit) + { + // if we needed a Phase0 enlistment, we need to add the container to the + // list. + if (needPhase0Enlistment) + { + Phase0EnlistVolatilementContainerList!.Add(localPhase0VolatileContainer); + } + localPhase0VolatileContainer!.AddDependentClone(); + returnValue = localPhase0VolatileContainer; + } + else + { + // If we needed a voter enlistment, we need to save the container as THE + // phase1 container for this transaction. + if (needVoterEnlistment) + { + Debug.Assert(Phase1EnlistVolatilementContainer == null, + "RealOletxTransaction.AddDependentClone - phase1VolContainer not null when expected" ); + Phase1EnlistVolatilementContainer = localPhase1VolatileContainer; + } + localPhase1VolatileContainer!.AddDependentClone(); + returnValue = localPhase1VolatileContainer; + } + + } + catch (COMException comException) + { + OletxTransactionManager.ProxyException(comException); + throw; + } + } + } + finally + { + //First release the lock on the phase 0 container if it was acquired. Any work on localPhase0VolatileContainer + //that needs its state to be consistent while processing should do so before this statement is executed. + if (localPhase0VolatileContainer != null) + { + ReleaseContainerLock(localPhase0VolatileContainer, ref phase0ContainerLockAcquired); + } + } + return returnValue; + } + + private static void ReleaseContainerLock(OletxPhase0VolatileEnlistmentContainer localPhase0VolatileContainer, ref bool phase0ContainerLockAcquired) + { + if (phase0ContainerLockAcquired) + { + Monitor.Exit(localPhase0VolatileContainer); + phase0ContainerLockAcquired = false; + } + } + + private static void TakeContainerLock(OletxPhase0VolatileEnlistmentContainer localPhase0VolatileContainer, ref bool phase0ContainerLockAcquired) + { + if (!phase0ContainerLockAcquired) + { + Monitor.Enter(localPhase0VolatileContainer); + phase0ContainerLockAcquired = true; + } + } + + internal IPromotedEnlistment CommonEnlistVolatile( + IEnlistmentNotificationInternal enlistmentNotification, + EnlistmentOptions enlistmentOptions, + OletxTransaction oletxTransaction) + { + OletxVolatileEnlistment? enlistment = null; + bool needVoterEnlistment = false; + bool needPhase0Enlistment = false; + OletxPhase0VolatileEnlistmentContainer? localPhase0VolatileContainer = null; + OletxPhase1VolatileEnlistmentContainer? localPhase1VolatileContainer = null; + VoterBallotShim? voterShim = null; + Phase0EnlistmentShim? phase0Shim = null; + + // Yes, we are talking to the proxy while holding the lock on the RealOletxTransaction. + // If we don't then things get real sticky with other threads allocating containers. + // We only do this the first time we get a depenent clone of a given type (delay vs. non-delay). + // After that, we don't create a new container, except for Phase0 if we need to create one + // for a second wave. + lock (this) + { + enlistment = new OletxVolatileEnlistment( + enlistmentNotification, + enlistmentOptions, + oletxTransaction); + + if ((enlistmentOptions & EnlistmentOptions.EnlistDuringPrepareRequired) != 0) + { + if (Phase0EnlistVolatilementContainerList == null) + { + // Not using a MemoryBarrier because all access to this member variable is done when holding + // a lock on the object. + Phase0EnlistVolatilementContainerList = new ArrayList(1); + } + // We may have failed the proxy enlistment for the first container, but we would have + // allocated the list. That is why we have this check here. + if (Phase0EnlistVolatilementContainerList.Count == 0) + { + localPhase0VolatileContainer = new OletxPhase0VolatileEnlistmentContainer(this); + needPhase0Enlistment = true; + } + else + { + localPhase0VolatileContainer = Phase0EnlistVolatilementContainerList[^1] as OletxPhase0VolatileEnlistmentContainer; + if (!localPhase0VolatileContainer!.NewEnlistmentsAllowed) + { + localPhase0VolatileContainer = new OletxPhase0VolatileEnlistmentContainer(this); + needPhase0Enlistment = true; + } + else + { + needPhase0Enlistment = false; + } + } + } + else // not EDPR = TRUE - may need a voter... + { + if (Phase1EnlistVolatilementContainer == null) + { + needVoterEnlistment = true; + localPhase1VolatileContainer = new OletxPhase1VolatileEnlistmentContainer(this); + } + else + { + needVoterEnlistment = false; + localPhase1VolatileContainer = Phase1EnlistVolatilementContainer; + } + } + + try + { + // If enlistDuringPrepareRequired is true, we need to ask the proxy to create a Phase0 enlistment. + if (needPhase0Enlistment) + { + lock (localPhase0VolatileContainer!) + { + _transactionShim!.Phase0Enlist(localPhase0VolatileContainer, out phase0Shim); + + localPhase0VolatileContainer.Phase0EnlistmentShim = phase0Shim; + } + } + + if (needVoterEnlistment) + { + _transactionShim!.CreateVoter(localPhase1VolatileContainer!, out voterShim); + + localPhase1VolatileContainer!.VoterBallotShim = voterShim; + } + + if ((enlistmentOptions & EnlistmentOptions.EnlistDuringPrepareRequired) != 0) + { + localPhase0VolatileContainer!.AddEnlistment(enlistment); + if (needPhase0Enlistment) + { + Phase0EnlistVolatilementContainerList!.Add(localPhase0VolatileContainer); + } + } + else + { + localPhase1VolatileContainer!.AddEnlistment(enlistment); + + if (needVoterEnlistment) + { + Debug.Assert(Phase1EnlistVolatilementContainer == null, + "RealOletxTransaction.CommonEnlistVolatile - phase1VolContainer not null when expected."); + Phase1EnlistVolatilementContainer = localPhase1VolatileContainer; + } + } + } + catch (COMException comException) + { + OletxTransactionManager.ProxyException(comException); + throw; + } + } + + return enlistment; + } + + internal IPromotedEnlistment EnlistVolatile( + ISinglePhaseNotificationInternal enlistmentNotification, + EnlistmentOptions enlistmentOptions, + OletxTransaction oletxTransaction) + => CommonEnlistVolatile( + enlistmentNotification, + enlistmentOptions, + oletxTransaction); + + internal IPromotedEnlistment EnlistVolatile( + IEnlistmentNotificationInternal enlistmentNotification, + EnlistmentOptions enlistmentOptions, + OletxTransaction oletxTransaction) + => CommonEnlistVolatile( + enlistmentNotification, + enlistmentOptions, + oletxTransaction); + + internal void Commit() + { + try + { + _transactionShim!.Commit(); + } + catch (COMException comException) + { + if (comException.ErrorCode == OletxHelper.XACT_E_ABORTED || + comException.ErrorCode == OletxHelper.XACT_E_INDOUBT) + { + Interlocked.CompareExchange(ref InnerException, comException, null); + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, comException); + } + } + else if (comException.ErrorCode == OletxHelper.XACT_E_ALREADYINPROGRESS) + { + throw TransactionException.Create(SR.TransactionAlreadyOver, comException); + } + else + { + OletxTransactionManager.ProxyException(comException); + throw; + } + } + } + + internal void Rollback() + { + Guid tempGuid = Guid.Empty; + + lock (this) + { + // if status is not active and not aborted, then throw an exception + if (TransactionStatus.Aborted != Status && + TransactionStatus.Active != Status) + { + throw TransactionException.Create(SR.TransactionAlreadyOver, null, DistributedTxId); + } + + // If the transaciton is already aborted, we can get out now. Calling Rollback on an already aborted transaction + // is legal. + if (TransactionStatus.Aborted == Status) + { + return; + } + + // If there are still undecided enlistments, we can doom the transaction. + // We can safely make this check because we ALWAYS have a Phase1 Volatile enlistment to + // get the outcome. If we didn't have that enlistment, we would not be able to do this + // because not all instances of RealOletxTransaction would have enlistments. + if (_undecidedEnlistmentCount > 0) + { + Doomed = true; + } + else if (TooLateForEnlistments ) + { + // It's too late for rollback to be called here. + throw TransactionException.Create(SR.TransactionAlreadyOver, null, DistributedTxId); + } + + // Tell the volatile enlistment containers to vote no now if they have outstanding + // notifications. + if (Phase0EnlistVolatilementContainerList != null) + { + foreach (OletxPhase0VolatileEnlistmentContainer phase0VolatileContainer in Phase0EnlistVolatilementContainerList) + { + phase0VolatileContainer.RollbackFromTransaction(); + } + } + if (Phase1EnlistVolatilementContainer != null) + { + Phase1EnlistVolatilementContainer.RollbackFromTransaction(); + } + } + + try + { + _transactionShim!.Abort(); + } + catch (COMException comException) + { + // If the ErrorCode is XACT_E_ALREADYINPROGRESS and the transaciton is already doomed, we must be + // the root transaction and we have already called Commit - ignore the exception. The + // Rollback is allowed and one of the enlistments that hasn't voted yet will make sure it is + // aborted. + if (comException.ErrorCode == OletxHelper.XACT_E_ALREADYINPROGRESS) + { + if (Doomed) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, comException); + } + } + else + { + throw TransactionException.Create(SR.TransactionAlreadyOver, comException, DistributedTxId); + } + } + else + { + // Otherwise, throw the exception out to the app. + OletxTransactionManager.ProxyException(comException); + + throw; + } + } + } + + internal void OletxTransactionCreated() + => Interlocked.Increment(ref _undisposedOletxTransactionCount); + + internal void OletxTransactionDisposed() + { + int localCount = Interlocked.Decrement(ref _undisposedOletxTransactionCount); + Debug.Assert(localCount >= 0, "RealOletxTransction.undisposedOletxTransationCount < 0"); + } + + internal void FireOutcome(TransactionStatus statusArg) + { + lock (this) + { + if (statusArg == TransactionStatus.Committed) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.TransactionCommitted(TraceSourceType.TraceSourceOleTx, TransactionTraceId); + } + + Status = TransactionStatus.Committed; + } + else if (statusArg == TransactionStatus.Aborted) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.TransactionAborted(TraceSourceType.TraceSourceOleTx, TransactionTraceId); + } + + Status = TransactionStatus.Aborted; + } + else + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.TransactionInDoubt(TraceSourceType.TraceSourceOleTx, TransactionTraceId); + } + + Status = TransactionStatus.InDoubt; + } + } + + // Let the InternalTransaciton know about the outcome. + if (InternalTransaction != null) + { + InternalTransaction.DistributedTransactionOutcome(InternalTransaction, Status); + } + + } + + internal TransactionTraceIdentifier TransactionTraceId + { + get + { + if (TransactionTraceIdentifier.Empty == _traceIdentifier) + { + lock (this) + { + if (_traceIdentifier == TransactionTraceIdentifier.Empty) + { + if (TxGuid != Guid.Empty) + { + TransactionTraceIdentifier temp = new(TxGuid.ToString(), 0); + Thread.MemoryBarrier(); + _traceIdentifier = temp; + } + else + { + // We don't have a txGuid if we couldn't determine the guid of the + // transaction because the transaction was already committed or aborted before the RealOletxTransaction was + // created. If that happens, we don't want to throw just because we are trying to trace. So just use the + // TransactionTraceIdentifier.Empty. + } + } + } + } + return _traceIdentifier; + } + } + + internal void TMDown() + { + lock (this) + { + // Tell the volatile enlistment containers that the TM went down. + if (Phase0EnlistVolatilementContainerList != null) + { + foreach (OletxPhase0VolatileEnlistmentContainer phase0VolatileContainer in Phase0EnlistVolatilementContainerList) + { + phase0VolatileContainer.TMDown(); + } + } + } + // Tell the outcome enlistment the TM went down. We are doing this outside the lock + // because this may end up making calls out to user code through enlistments. + _outcomeEnlistment!.TMDown(); + } + } + + internal sealed class OutcomeEnlistment + { + private WeakReference? _weakRealTransaction; + + internal Guid TransactionIdentifier { get; private set; } + + private bool _haveIssuedOutcome; + + private TransactionStatus _savedStatus; + + internal OutcomeEnlistment() + { + _haveIssuedOutcome = false; + _savedStatus = TransactionStatus.InDoubt; + } + + internal void SetRealTransaction(RealOletxTransaction realTx) + { + bool localHaveIssuedOutcome = false; + TransactionStatus localStatus = TransactionStatus.InDoubt; + + lock (this) + { + localHaveIssuedOutcome = _haveIssuedOutcome; + localStatus = _savedStatus; + + // We want to do this while holding the lock. + if (!localHaveIssuedOutcome) + { + // We don't use MemoryBarrier here because all access to these member variables is done while holding + // a lock on the object. + + // We are going to use a weak reference so the transaction object can get garbage + // collected before we receive the outcome. + _weakRealTransaction = new WeakReference(realTx); + + // Save the transaction guid so that the transaction can be removed from the + // TransactionTable + TransactionIdentifier = realTx.TxGuid; + } + } + + // We want to do this outside the lock because we are potentially calling out to user code. + if (localHaveIssuedOutcome) + { + realTx.FireOutcome(localStatus); + + // We may be getting this notification while there are still volatile prepare notifications outstanding. Tell the + // container to drive the aborted notification in that case. + if ( localStatus is TransactionStatus.Aborted or TransactionStatus.InDoubt && + realTx.Phase1EnlistVolatilementContainer != null) + { + realTx.Phase1EnlistVolatilementContainer.OutcomeFromTransaction(localStatus); + } + } + } + + internal void UnregisterOutcomeCallback() + { + _weakRealTransaction = null; + } + + private void InvokeOutcomeFunction(TransactionStatus status) + { + WeakReference? localTxWeakRef; + + // In the face of TMDown notifications, we may have already issued + // the outcome of the transaction. + lock (this) + { + if (_haveIssuedOutcome) + { + return; + } + _haveIssuedOutcome = true; + _savedStatus = status; + localTxWeakRef = _weakRealTransaction; + } + + // It is possible for the weakRealTransaction member to be null if some exception was thrown + // during the RealOletxTransaction constructor after the OutcomeEnlistment object was created. + // In the finally block of the constructor, it calls UnregisterOutcomeCallback, which will + // null out weakRealTransaction. If this is the case, there is nothing to do. + if (localTxWeakRef != null) + { + if (localTxWeakRef.Target is RealOletxTransaction realOletxTransaction) + { + realOletxTransaction.FireOutcome(status); + + // The container list won't be changing on us now because the transaction status has changed such that + // new enlistments will not be created. + // Tell the Phase0Volatile containers, if any, about the outcome of the transaction. + // I am not protecting the access to phase0EnlistVolatilementContainerList with a lock on "this" + // because it is too late for these to be allocated anyway. + if (realOletxTransaction.Phase0EnlistVolatilementContainerList != null) + { + foreach (OletxPhase0VolatileEnlistmentContainer phase0VolatileContainer in realOletxTransaction.Phase0EnlistVolatilementContainerList) + { + phase0VolatileContainer.OutcomeFromTransaction( status ); + } + } + + // We may be getting this notification while there are still volatile prepare notifications outstanding. Tell the + // container to drive the aborted notification in that case. + if ( status is TransactionStatus.Aborted or TransactionStatus.InDoubt && + realOletxTransaction.Phase1EnlistVolatilementContainer != null) + { + realOletxTransaction.Phase1EnlistVolatilementContainer.OutcomeFromTransaction(status); + } + } + + localTxWeakRef.Target = null; + } + } + + // + // We need to figure out if the transaction is InDoubt as a result of TMDown. This + // can happen for a number of reasons. For instance we have responded prepared + // to all of our enlistments or we have no enlistments. + // + internal static bool TransactionIsInDoubt(RealOletxTransaction realTx) + { + if (realTx.CommittableTransaction is { CommitCalled: false } ) + { + // If this is a committable transaction and commit has not been called + // then we know the outcome. + return false; + } + + return realTx.UndecidedEnlistments == 0; + } + + internal void TMDown() + { + // Assume that we don't know because that is the safest answer. + bool transactionIsInDoubt = true; + RealOletxTransaction? realOletxTransaction = null; + lock (this) + { + if (_weakRealTransaction != null) + { + realOletxTransaction = _weakRealTransaction.Target as RealOletxTransaction; + } + } + + if (realOletxTransaction != null) + { + lock (realOletxTransaction) + { + transactionIsInDoubt = TransactionIsInDoubt(realOletxTransaction); + } + } + + // If we have already voted, then we can't tell what the outcome + // is. We do this outside the lock because it may end up invoking user + // code when it calls into the enlistments later on the stack. + if (transactionIsInDoubt) + { + InDoubt(); + } + // We have not yet voted, so just say it aborted. + else + { + Aborted(); + } + } + + #region ITransactionOutcome Members + + public void Committed() + => InvokeOutcomeFunction(TransactionStatus.Committed); + + public void Aborted() + => InvokeOutcomeFunction(TransactionStatus.Aborted); + + public void InDoubt() + => InvokeOutcomeFunction(TransactionStatus.InDoubt); + + #endregion + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxTransactionManager.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxTransactionManager.cs new file mode 100644 index 00000000000000..86329f235b424d --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxTransactionManager.cs @@ -0,0 +1,804 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Runtime.InteropServices; +using System.Threading; +using System.Transactions.DtcProxyShim; + +namespace System.Transactions.Oletx; + +internal sealed class OletxTransactionManager +{ + private IsolationLevel _isolationLevelProperty; + + private TimeSpan _timeoutProperty; + + private TransactionOptions _configuredTransactionOptions = default; + + // Object for synchronizing access to the entire class( avoiding lock( typeof( ... )) ) + private static object? _classSyncObject; + + // These have to be static because we can only add an RM with the proxy once, even if we + // have multiple OletxTransactionManager instances. + internal static Hashtable? _resourceManagerHashTable; + public static ReaderWriterLock ResourceManagerHashTableLock = null!; + + internal static volatile bool ProcessingTmDown; + + internal ReaderWriterLock DtcTransactionManagerLock; + private DtcTransactionManager _dtcTransactionManager; + internal OletxInternalResourceManager InternalResourceManager; + + internal static DtcProxyShimFactory ProxyShimFactory = null!; // Late initialization + + // Double-checked locking pattern requires volatile for read/write synchronization + internal static volatile EventWaitHandle? _shimWaitHandle; + internal static EventWaitHandle ShimWaitHandle + { + get + { + if (_shimWaitHandle == null) + { + lock (ClassSyncObject) + { + _shimWaitHandle ??= new EventWaitHandle(false, EventResetMode.AutoReset); + } + } + + return _shimWaitHandle; + } + } + + private string? _nodeNameField; + + internal static void ShimNotificationCallback(object? state, bool timeout) + { + // First we need to get the notification from the shim factory. + object? enlistment2 = null; + ShimNotificationType shimNotificationType = ShimNotificationType.None; + bool isSinglePhase; + bool abortingHint; + + byte[]? prepareInfoBuffer = null; + + bool holdingNotificationLock = false; + + DtcProxyShimFactory localProxyShimFactory; + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, $"{nameof(OletxTransactionManager)}.{nameof(ShimNotificationCallback)}"); + } + + // This lock doesn't really protect any of our data. It is here so that if an exception occurs + // while calling out to the app, we get an escalation to AppDomainUnload. + Thread.BeginCriticalRegion(); + try + { + do + { + // Take a local copy of the proxyShimFactory because if we get an RM TMDown notification, + // we will still hold the critical section in that factory, but processing of the TMDown will + // cause replacement of the OletxTransactionManager.proxyShimFactory. + localProxyShimFactory = ProxyShimFactory; + try + { + Thread.BeginThreadAffinity(); + try + { + localProxyShimFactory.GetNotification( + out enlistment2, + out shimNotificationType, + out isSinglePhase, + out abortingHint, + out holdingNotificationLock, + out prepareInfoBuffer); + } + finally + { + if (holdingNotificationLock) + { + if (enlistment2 is OletxInternalResourceManager) + { + // In this case we know that the TM has gone down and we need to exchange + // the native lock for a managed lock. + ProcessingTmDown = true; + Monitor.Enter(ProxyShimFactory); + } + else + { + holdingNotificationLock = false; + } + localProxyShimFactory.ReleaseNotificationLock(); + } + Thread.EndThreadAffinity(); + } + + // If a TM down is being processed it is possible that the native lock + // has been exchanged for a managed lock. In that case we need to attempt + // to take a lock to hold up processing more events until the TM down + // processing is complete. + if (ProcessingTmDown) + { + lock (ProxyShimFactory) + { + // We don't do any work under this lock just make sure that we + // can take it. + } + } + + if (shimNotificationType != ShimNotificationType.None) + { + // Next, based on the notification type, cast the Handle accordingly and make + // the appropriate call on the enlistment. + switch (shimNotificationType) + { + case ShimNotificationType.Phase0RequestNotify: + { + if (enlistment2 is OletxPhase0VolatileEnlistmentContainer ph0VolEnlistContainer) + { + ph0VolEnlistContainer.Phase0Request(abortingHint); + } + else + { + if (enlistment2 is OletxEnlistment oletxEnlistment) + { + oletxEnlistment.Phase0Request(abortingHint); + } + else + { + Environment.FailFast(SR.InternalError); + } + } + + break; + } + + case ShimNotificationType.VoteRequestNotify: + { + if (enlistment2 is OletxPhase1VolatileEnlistmentContainer ph1VolEnlistContainer) + { + ph1VolEnlistContainer.VoteRequest(); + } + else + { + Environment.FailFast(SR.InternalError); + } + + break; + } + + case ShimNotificationType.CommittedNotify: + { + if (enlistment2 is OutcomeEnlistment outcomeEnlistment) + { + outcomeEnlistment.Committed(); + } + else + { + if (enlistment2 is OletxPhase1VolatileEnlistmentContainer ph1VolEnlistContainer) + { + ph1VolEnlistContainer.Committed(); + } + else + { + Environment.FailFast(SR.InternalError); + } + } + + break; + } + + case ShimNotificationType.AbortedNotify: + { + if (enlistment2 is OutcomeEnlistment outcomeEnlistment) + { + outcomeEnlistment.Aborted(); + } + else + { + if (enlistment2 is OletxPhase1VolatileEnlistmentContainer ph1VolEnlistContainer) + { + ph1VolEnlistContainer.Aborted(); + } + // else + // Voters may receive notifications even + // in cases where they therwise respond + // negatively to the vote request. It is + // also not guaranteed that we will get a + // notification if we do respond negatively. + // The only safe thing to do is to free the + // Handle when we abort the transaction + // with a voter. These two things together + // mean that we cannot guarantee that this + // Handle will be alive when we get this + // notification. + } + + break; + } + + case ShimNotificationType.InDoubtNotify: + { + if (enlistment2 is OutcomeEnlistment outcomeEnlistment) + { + outcomeEnlistment.InDoubt(); + } + else + { + if (enlistment2 is OletxPhase1VolatileEnlistmentContainer ph1VolEnlistContainer) + { + ph1VolEnlistContainer.InDoubt(); + } + else + { + Environment.FailFast(SR.InternalError); + } + } + + break; + } + + case ShimNotificationType.PrepareRequestNotify: + { + bool enlistmentDone = true; + + if (enlistment2 is OletxEnlistment enlistment) + { + enlistmentDone = enlistment.PrepareRequest(isSinglePhase, prepareInfoBuffer!); + } + else + { + Environment.FailFast(SR.InternalError); + } + + break; + } + + case ShimNotificationType.CommitRequestNotify: + { + if (enlistment2 is OletxEnlistment enlistment) + { + enlistment.CommitRequest(); + } + else + { + Environment.FailFast(SR.InternalError); + } + + break; + } + + case ShimNotificationType.AbortRequestNotify: + { + if (enlistment2 is OletxEnlistment enlistment) + { + enlistment.AbortRequest(); + } + else + { + Environment.FailFast(SR.InternalError); + } + + break; + } + + case ShimNotificationType.EnlistmentTmDownNotify: + { + if (enlistment2 is OletxEnlistment enlistment) + { + enlistment.TMDown(); + } + else + { + Environment.FailFast(SR.InternalError); + } + + break; + } + + case ShimNotificationType.ResourceManagerTmDownNotify: + { + switch (enlistment2) + { + case OletxResourceManager resourceManager: + resourceManager.TMDown(); + break; + + case OletxInternalResourceManager internalResourceManager: + internalResourceManager.TMDown(); + break; + + default: + Environment.FailFast(SR.InternalError); + break; + } + + break; + } + + default: + { + Environment.FailFast(SR.InternalError); + break; + } + } + } + } + finally + { + if (holdingNotificationLock) + { + holdingNotificationLock = false; + ProcessingTmDown = false; + Monitor.Exit(ProxyShimFactory); + } + } + } + while (shimNotificationType != ShimNotificationType.None); + } + finally + { + if (holdingNotificationLock) + { + holdingNotificationLock = false; + ProcessingTmDown = false; + Monitor.Exit(ProxyShimFactory); + } + + Thread.EndCriticalRegion(); + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, $"{nameof(OletxTransactionManager)}.{nameof(ShimNotificationCallback)}"); + } + } + + internal OletxTransactionManager(string nodeName) + { + lock (ClassSyncObject) + { + // If we have not already initialized the shim factory and started the notification + // thread, do so now. + if (ProxyShimFactory == null) + { + ProxyShimFactory = new DtcProxyShimFactory(ShimWaitHandle); + + ThreadPool.UnsafeRegisterWaitForSingleObject( + ShimWaitHandle, + ShimNotificationCallback, + null, + -1, + false); + } + } + + DtcTransactionManagerLock = new ReaderWriterLock(); + + _nodeNameField = nodeName; + + // The DTC proxy doesn't like an empty string for node name on 64-bit platforms when + // running as WOW64. It treats any non-null node name as a "remote" node and turns off + // the WOW64 bit, causing problems when reading the registry. So if we got on empty + // string for the node name, just treat it as null. + if (_nodeNameField is { Length: 0 }) + { + _nodeNameField = null; + } + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.OleTxTransactionManagerCreate(GetType(), _nodeNameField); + } + + // Initialize the properties from config. + _configuredTransactionOptions.IsolationLevel = _isolationLevelProperty = TransactionManager.DefaultIsolationLevel; + _configuredTransactionOptions.Timeout = _timeoutProperty = TransactionManager.DefaultTimeout; + + InternalResourceManager = new OletxInternalResourceManager( this ); + + DtcTransactionManagerLock.AcquireWriterLock(-1); + try + { + _dtcTransactionManager = new DtcTransactionManager(_nodeNameField, this); + } + finally + { + DtcTransactionManagerLock.ReleaseWriterLock(); + } + + if (_resourceManagerHashTable == null) + { + _resourceManagerHashTable = new Hashtable(2); + ResourceManagerHashTableLock = new ReaderWriterLock(); + } + } + + internal OletxCommittableTransaction CreateTransaction(TransactionOptions properties) + { + OletxCommittableTransaction tx; + RealOletxTransaction realTransaction; + TransactionShim? transactionShim = null; + Guid txIdentifier = Guid.Empty; + OutcomeEnlistment outcomeEnlistment; + + TransactionManager.ValidateIsolationLevel(properties.IsolationLevel); + + // Never create a transaction with an IsolationLevel of Unspecified. + if (IsolationLevel.Unspecified == properties.IsolationLevel) + { + properties.IsolationLevel = _configuredTransactionOptions.IsolationLevel; + } + + properties.Timeout = TransactionManager.ValidateTimeout(properties.Timeout); + + DtcTransactionManagerLock.AcquireReaderLock(-1); + try + { + OletxTransactionIsolationLevel oletxIsoLevel = ConvertIsolationLevel(properties.IsolationLevel); + uint oletxTimeout = DtcTransactionManager.AdjustTimeout(properties.Timeout); + + outcomeEnlistment = new OutcomeEnlistment(); + try + { + _dtcTransactionManager.ProxyShimFactory.BeginTransaction( + oletxTimeout, + oletxIsoLevel, + outcomeEnlistment, + out txIdentifier, + out transactionShim); + } + catch (COMException ex) + { + ProxyException(ex); + throw; + } + + realTransaction = new RealOletxTransaction( + this, + transactionShim, + outcomeEnlistment, + txIdentifier, + oletxIsoLevel, + true); + tx = new OletxCommittableTransaction(realTransaction); + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.TransactionCreated(TraceSourceType.TraceSourceOleTx, tx.TransactionTraceId, "OletxTransaction"); + } + } + finally + { + DtcTransactionManagerLock.ReleaseReaderLock(); + } + + return tx; + } + + internal OletxEnlistment ReenlistTransaction( + Guid resourceManagerIdentifier, + byte[] recoveryInformation, + IEnlistmentNotificationInternal enlistmentNotification) + { + if (recoveryInformation == null) + { + throw new ArgumentNullException(nameof(recoveryInformation)); + } + + if (enlistmentNotification == null) + { + throw new ArgumentNullException(nameof(enlistmentNotification)); + } + + // Now go find the resource manager in the collection. + OletxResourceManager oletxResourceManager = RegisterResourceManager(resourceManagerIdentifier); + if (oletxResourceManager == null) + { + throw new ArgumentException(SR.InvalidArgument, nameof(resourceManagerIdentifier)); + } + + if (oletxResourceManager.RecoveryCompleteCalledByApplication) + { + throw new InvalidOperationException(SR.ReenlistAfterRecoveryComplete); + } + + // Now ask the resource manager to reenlist. + OletxEnlistment returnValue = oletxResourceManager.Reenlist(recoveryInformation, enlistmentNotification); + + return returnValue; + } + + internal void ResourceManagerRecoveryComplete(Guid resourceManagerIdentifier) + { + OletxResourceManager oletxRm = RegisterResourceManager(resourceManagerIdentifier); + + if (oletxRm.RecoveryCompleteCalledByApplication) + { + throw new InvalidOperationException(SR.DuplicateRecoveryComplete); + } + + oletxRm.RecoveryComplete(); + } + + internal OletxResourceManager RegisterResourceManager(Guid resourceManagerIdentifier) + { + OletxResourceManager? oletxResourceManager; + + ResourceManagerHashTableLock.AcquireWriterLock(-1); + + try + { + // If this resource manager has already been registered, don't register it again. + oletxResourceManager = _resourceManagerHashTable![resourceManagerIdentifier] as OletxResourceManager; + if (oletxResourceManager != null) + { + return oletxResourceManager; + } + + oletxResourceManager = new OletxResourceManager(this, resourceManagerIdentifier); + + _resourceManagerHashTable.Add(resourceManagerIdentifier, oletxResourceManager); + } + finally + { + ResourceManagerHashTableLock.ReleaseWriterLock(); + } + + return oletxResourceManager; + } + + internal string? CreationNodeName + => _nodeNameField; + + internal OletxResourceManager FindOrRegisterResourceManager(Guid resourceManagerIdentifier) + { + if (resourceManagerIdentifier == Guid.Empty) + { + throw new ArgumentException(SR.BadResourceManagerId, nameof(resourceManagerIdentifier)); + } + + OletxResourceManager? oletxResourceManager; + + ResourceManagerHashTableLock.AcquireReaderLock(-1); + try + { + oletxResourceManager = _resourceManagerHashTable![resourceManagerIdentifier] as OletxResourceManager; + } + finally + { + ResourceManagerHashTableLock.ReleaseReaderLock(); + } + + if (oletxResourceManager == null) + { + return RegisterResourceManager(resourceManagerIdentifier); + } + + return oletxResourceManager; + } + + internal DtcTransactionManager DtcTransactionManager + { + get + { + if (DtcTransactionManagerLock.IsReaderLockHeld ||DtcTransactionManagerLock.IsWriterLockHeld) + { + if (_dtcTransactionManager == null) + { + throw TransactionException.Create(SR.DtcTransactionManagerUnavailable, null); + } + + return _dtcTransactionManager; + } + + // Internal programming error. A reader or writer lock should be held when this property is invoked. + throw TransactionException.Create(SR.InternalError, null); + } + } + + internal string? NodeName + => _nodeNameField; + + internal static void ProxyException(COMException comException) + { + if (comException.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN || + comException.ErrorCode == OletxHelper.XACT_E_TMNOTAVAILABLE) + { + throw TransactionManagerCommunicationException.Create( + SR.TransactionManagerCommunicationException, + comException); + } + if (comException.ErrorCode == OletxHelper.XACT_E_NETWORK_TX_DISABLED) + { + throw TransactionManagerCommunicationException.Create( + SR.NetworkTransactionsDisabled, + comException); + } + // Else if the error is a transaction oriented error, throw a TransactionException + if (comException.ErrorCode >= OletxHelper.XACT_E_FIRST && + comException.ErrorCode <= OletxHelper.XACT_E_LAST) + { + // Special casing XACT_E_NOTRANSACTION + throw TransactionException.Create( + OletxHelper.XACT_E_NOTRANSACTION == comException.ErrorCode + ? SR.TransactionAlreadyOver + : comException.Message, + comException); + } + } + + internal void ReinitializeProxy() + { + // This is created by the static constructor. + DtcTransactionManagerLock.AcquireWriterLock(-1); + + try + { + _dtcTransactionManager?.ReleaseProxy(); + } + finally + { + DtcTransactionManagerLock.ReleaseWriterLock(); + } + } + + internal static OletxTransactionIsolationLevel ConvertIsolationLevel(IsolationLevel isolationLevel) + => isolationLevel switch + { + IsolationLevel.Serializable => OletxTransactionIsolationLevel.ISOLATIONLEVEL_SERIALIZABLE, + IsolationLevel.RepeatableRead => OletxTransactionIsolationLevel.ISOLATIONLEVEL_REPEATABLEREAD, + IsolationLevel.ReadCommitted => OletxTransactionIsolationLevel.ISOLATIONLEVEL_READCOMMITTED, + IsolationLevel.ReadUncommitted => OletxTransactionIsolationLevel.ISOLATIONLEVEL_READUNCOMMITTED, + IsolationLevel.Chaos => OletxTransactionIsolationLevel.ISOLATIONLEVEL_CHAOS, + IsolationLevel.Unspecified => OletxTransactionIsolationLevel.ISOLATIONLEVEL_UNSPECIFIED, + _ => OletxTransactionIsolationLevel.ISOLATIONLEVEL_SERIALIZABLE + }; + + internal static IsolationLevel ConvertIsolationLevelFromProxyValue(OletxTransactionIsolationLevel proxyIsolationLevel) + => proxyIsolationLevel switch + { + OletxTransactionIsolationLevel.ISOLATIONLEVEL_SERIALIZABLE => IsolationLevel.Serializable, + OletxTransactionIsolationLevel.ISOLATIONLEVEL_REPEATABLEREAD => IsolationLevel.RepeatableRead, + OletxTransactionIsolationLevel.ISOLATIONLEVEL_READCOMMITTED => IsolationLevel.ReadCommitted, + OletxTransactionIsolationLevel.ISOLATIONLEVEL_READUNCOMMITTED => IsolationLevel.ReadUncommitted, + OletxTransactionIsolationLevel.ISOLATIONLEVEL_UNSPECIFIED => IsolationLevel.Unspecified, + OletxTransactionIsolationLevel.ISOLATIONLEVEL_CHAOS => IsolationLevel.Chaos, + _ => IsolationLevel.Serializable + }; + + // Helper object for static synchronization + internal static object ClassSyncObject + { + get + { + if (_classSyncObject == null) + { + object o = new(); + Interlocked.CompareExchange(ref _classSyncObject, o, null); + } + + return _classSyncObject; + } + } +} + +internal sealed class OletxInternalResourceManager +{ + private OletxTransactionManager _oletxTm; + + internal Guid Identifier { get; } + + internal ResourceManagerShim? ResourceManagerShim; + + internal OletxInternalResourceManager(OletxTransactionManager oletxTm) + { + _oletxTm = oletxTm; + Identifier = Guid.NewGuid(); + } + + public void TMDown() + { + // Let's set ourselves up for reinitialization with the proxy by releasing our + // reference to the resource manager shim, which will release its reference + // to the proxy when it destructs. + ResourceManagerShim = null; + + // We need to look through all the transactions and tell them about + // the TMDown so they can tell their Phase0VolatileEnlistmentContainers. + Transaction? tx; + RealOletxTransaction realTx; + IDictionaryEnumerator tableEnum; + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxInternalResourceManager)}.{nameof(TMDown)}"); + } + + // make a local copy of the hash table to avoid possible deadlocks when we lock both the global hash table + // and the transaction object. + Hashtable txHashTable; + lock (TransactionManager.PromotedTransactionTable.SyncRoot) + { + txHashTable = (Hashtable)TransactionManager.PromotedTransactionTable.Clone(); + } + + // No need to lock my hashtable, nobody is going to change it. + tableEnum = txHashTable.GetEnumerator(); + while (tableEnum.MoveNext()) + { + WeakReference? txWeakRef = (WeakReference?)tableEnum.Value; + if (txWeakRef != null) + { + tx = (Transaction?)txWeakRef.Target; + if (tx != null) + { + realTx = tx._internalTransaction.PromotedTransaction!.RealOletxTransaction; + // Only deal with transactions owned by my OletxTm. + if (realTx.OletxTransactionManagerInstance == _oletxTm) + { + realTx.TMDown(); + } + } + } + } + + // Now make a local copy of the hash table of resource managers and tell each of them. This is to + // deal with Durable EDPR=true (phase0) enlistments. Each RM will also get a TMDown, but it will + // come AFTER the "buggy" Phase0Request with abortHint=true - COMPlus bug 36760/36758. + Hashtable? rmHashTable = null; + if (OletxTransactionManager._resourceManagerHashTable != null) + { + OletxTransactionManager.ResourceManagerHashTableLock.AcquireReaderLock(Timeout.Infinite); + try + { + rmHashTable = (Hashtable)OletxTransactionManager._resourceManagerHashTable.Clone(); + } + finally + { + OletxTransactionManager.ResourceManagerHashTableLock.ReleaseReaderLock(); + } + } + + if (rmHashTable != null) + { + // No need to lock my hashtable, nobody is going to change it. + tableEnum = rmHashTable.GetEnumerator(); + while (tableEnum.MoveNext()) + { + OletxResourceManager? oletxRM = (OletxResourceManager?)tableEnum.Value; + if (oletxRM != null) + { + // When the RM spins through its enlistments, it will need to make sure that + // the enlistment is for this particular TM. + oletxRM.TMDownFromInternalRM(_oletxTm); + } + } + } + + // Now let's reinitialize the shim. + _oletxTm.DtcTransactionManagerLock.AcquireWriterLock(-1); + try + { + _oletxTm.ReinitializeProxy(); + } + finally + { + _oletxTm.DtcTransactionManagerLock.ReleaseWriterLock(); + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxInternalResourceManager)}.{nameof(TMDown)}"); + } + } + + internal void CallReenlistComplete() + => ResourceManagerShim!.ReenlistComplete(); +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxVolatileEnlistment.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxVolatileEnlistment.cs new file mode 100644 index 00000000000000..82240b5f5ddf3c --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/Oletx/OletxVolatileEnlistment.cs @@ -0,0 +1,1508 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections; +using System.Diagnostics; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Threading; +using System.Transactions.DtcProxyShim; + +namespace System.Transactions.Oletx; + +internal abstract class OletxVolatileEnlistmentContainer +{ + protected OletxVolatileEnlistmentContainer(RealOletxTransaction realOletxTransaction) + { + Debug.Assert(realOletxTransaction != null, "Argument is null"); + + RealOletxTransaction = realOletxTransaction; + } + + protected RealOletxTransaction RealOletxTransaction; + protected ArrayList EnlistmentList = new(); + protected int Phase; + protected int OutstandingNotifications; + protected bool CollectedVoteYes; + protected int IncompleteDependentClones; + protected bool AlreadyVoted; + + internal abstract void DecrementOutstandingNotifications(bool voteYes); + + internal abstract void AddDependentClone(); + + internal abstract void DependentCloneCompleted(); + + internal abstract void RollbackFromTransaction(); + + internal abstract void OutcomeFromTransaction(TransactionStatus outcome); + + internal abstract void Committed(); + + internal abstract void Aborted(); + + internal abstract void InDoubt(); + + internal Guid TransactionIdentifier + => RealOletxTransaction.Identifier; +} + +internal sealed class OletxPhase0VolatileEnlistmentContainer : OletxVolatileEnlistmentContainer +{ + private Phase0EnlistmentShim? _phase0EnlistmentShim; + private bool _aborting; + private bool _tmWentDown; + + internal OletxPhase0VolatileEnlistmentContainer(RealOletxTransaction realOletxTransaction) + : base(realOletxTransaction) + { + // This will be set later, after the caller creates the enlistment with the proxy. + _phase0EnlistmentShim = null; + + Phase = -1; + _aborting = false; + _tmWentDown = false; + OutstandingNotifications = 0; + IncompleteDependentClones = 0; + AlreadyVoted = false; + // If anybody votes false, this will get set to false. + CollectedVoteYes = true; + + // This is a new undecided enlistment on the transaction. Do this last since it has side affects. + realOletxTransaction.IncrementUndecidedEnlistments(); + } + + internal void TMDown() + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxPhase0VolatileEnlistmentContainer)}.{nameof(TMDown)}"); + } + + _tmWentDown = true; + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxPhase0VolatileEnlistmentContainer)}.{nameof(TMDown)}"); + } + } + + // Be sure to lock this object before calling this. + internal bool NewEnlistmentsAllowed + => Phase == -1; + + internal void AddEnlistment(OletxVolatileEnlistment enlistment) + { + Debug.Assert(enlistment != null, "Argument is null"); + + lock (this) + { + if (Phase != -1) + { + throw TransactionException.Create(SR.TooLate, null); + } + + EnlistmentList.Add(enlistment); + } + } + + internal override void AddDependentClone() + { + lock (this) + { + if (Phase != -1) + { + throw TransactionException.CreateTransactionStateException(null); + } + + IncompleteDependentClones++; + } + } + + internal override void DependentCloneCompleted() + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + + bool doDecrement = false; + lock (this) + { + if (etwLog.IsEnabled()) + { + string description = "OletxPhase0VolatileEnlistmentContainer.DependentCloneCompleted, outstandingNotifications = " + + OutstandingNotifications.ToString(CultureInfo.CurrentCulture) + + ", incompleteDependentClones = " + + IncompleteDependentClones.ToString(CultureInfo.CurrentCulture) + + ", phase = " + Phase.ToString(CultureInfo.CurrentCulture); + + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, description); + } + + IncompleteDependentClones--; + Debug.Assert(IncompleteDependentClones >= 0, "OletxPhase0VolatileEnlistmentContainer.DependentCloneCompleted - incompleteDependentClones < 0"); + + // If we have not more incomplete dependent clones and we are in Phase 0, we need to "fake out" a notification completion. + if (IncompleteDependentClones == 0 && Phase == 0) + { + OutstandingNotifications++; + doDecrement = true; + } + } + if (doDecrement) + { + DecrementOutstandingNotifications(true); + } + + if (etwLog.IsEnabled()) + { + string description = "OletxPhase0VolatileEnlistmentContainer.DependentCloneCompleted"; + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, description); + } + } + + internal override void RollbackFromTransaction() + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + + lock (this) + { + if (etwLog.IsEnabled()) + { + string description = "OletxPhase0VolatileEnlistmentContainer.RollbackFromTransaction, outstandingNotifications = " + + OutstandingNotifications.ToString(CultureInfo.CurrentCulture) + + ", incompleteDependentClones = " + IncompleteDependentClones.ToString(CultureInfo.CurrentCulture); + + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, description); + } + + if (Phase == 0 && (OutstandingNotifications > 0 || IncompleteDependentClones > 0)) + { + AlreadyVoted = true; + // All we are going to do is release the Phase0Enlistment interface because there + // is no negative vote to Phase0Request. + if (Phase0EnlistmentShim != null) + { + Phase0EnlistmentShim.Phase0Done(false); + } + } + } + + if (etwLog.IsEnabled()) + { + string description = "OletxPhase0VolatileEnlistmentContainer.RollbackFromTransaction"; + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, description); + } + } + + + internal Phase0EnlistmentShim? Phase0EnlistmentShim + { + get + { + lock (this) + { + return _phase0EnlistmentShim; + } + } + set + { + lock (this) + { + // If this.aborting is set to true, then we must have already received a + // Phase0Request. This could happen if the transaction aborts after the + // enlistment is made, but before we are given the shim. + if (_aborting || _tmWentDown) + { + value!.Phase0Done(false); + } + _phase0EnlistmentShim = value; + } + } + } + + internal override void DecrementOutstandingNotifications(bool voteYes) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + bool respondToProxy = false; + Phase0EnlistmentShim? localPhase0Shim = null; + + lock (this) + { + if (etwLog.IsEnabled()) + { + string description = "OletxPhase0VolatileEnlistmentContainer.DecrementOutstandingNotifications, outstandingNotifications = " + + OutstandingNotifications.ToString(CultureInfo.CurrentCulture) + + ", incompleteDependentClones = " + + IncompleteDependentClones.ToString(CultureInfo.CurrentCulture); + + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, description); + } + OutstandingNotifications--; + Debug.Assert(OutstandingNotifications >= 0, "OletxPhase0VolatileEnlistmentContainer.DecrementOutstandingNotifications - outstandingNotifications < 0"); + + CollectedVoteYes = CollectedVoteYes && voteYes; + if (OutstandingNotifications == 0 && IncompleteDependentClones == 0) + { + if (Phase == 0 && !AlreadyVoted) + { + respondToProxy = true; + AlreadyVoted = true; + localPhase0Shim = _phase0EnlistmentShim; + } + RealOletxTransaction.DecrementUndecidedEnlistments(); + } + } + + try + { + if (respondToProxy) + { + if (localPhase0Shim != null) + { + localPhase0Shim.Phase0Done(CollectedVoteYes && !RealOletxTransaction.Doomed); + } + } + } + catch (COMException ex) + { + if ((ex.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN || ex.ErrorCode == OletxHelper.XACT_E_TMNOTAVAILABLE) && etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + + // In the case of Phase0, there is a bug in the proxy that causes an XACT_E_PROTOCOL + // error if the TM goes down while the enlistment is still active. The Phase0Request is + // sent out with abortHint false, but the state of the proxy object is not changed, causing + // Phase0Done request to fail with XACT_E_PROTOCOL. + // For Prepared, we want to make sure the proxy aborts the transaction. We don't need + // to drive the abort to the application here because the Phase1 enlistment will do that. + // In other words, treat this as if the proxy said Phase0Request( abortingHint = true ). + else if (OletxHelper.XACT_E_PROTOCOL == ex.ErrorCode) + { + _phase0EnlistmentShim = null; + + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + else + { + throw; + } + } + + if (etwLog.IsEnabled()) + { + string description = "OletxPhase0VolatileEnlistmentContainer.DecrementOutstandingNotifications"; + + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, description); + } + } + + internal override void OutcomeFromTransaction(TransactionStatus outcome) + { + switch (outcome) + { + case TransactionStatus.Committed: + Committed(); + break; + case TransactionStatus.Aborted: + Aborted(); + break; + case TransactionStatus.InDoubt: + InDoubt(); + break; + default: + Debug.Assert(false, "OletxPhase0VolatileEnlistmentContainer.OutcomeFromTransaction, outcome is not Commited or Aborted or InDoubt"); + break; + } + } + + internal override void Committed() + { + OletxVolatileEnlistment? enlistment; + int localCount; + + lock (this) + { + Debug.Assert(Phase == 0 && OutstandingNotifications == 0); + Phase = 2; + localCount = EnlistmentList.Count; + } + + for (int i = 0; i < localCount; i++) + { + enlistment = EnlistmentList[i] as OletxVolatileEnlistment; + if (enlistment == null) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Assert(false, "OletxPhase1VolatileEnlistmentContainer.Committed, enlistmentList element is not an OletxVolatileEnlistment."); + throw new InvalidOperationException(SR.InternalError); + } + + enlistment.Commit(); + } + } + + internal override void Aborted() + { + OletxVolatileEnlistment? enlistment; + int localCount; + + lock (this) + { + // Tell all the enlistments that the transaction aborted and let the enlistment + // state determine if the notification should be delivered. + Phase = 2; + localCount = EnlistmentList.Count; + } + + for (int i = 0; i < localCount; i++) + { + enlistment = EnlistmentList[i] as OletxVolatileEnlistment; + if (enlistment == null) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Assert(false, "OletxPhase1VolatileEnlistmentContainer.Aborted, enlistmentList element is not an OletxVolatileEnlistment."); + throw new InvalidOperationException(SR.InternalError); + } + + enlistment.Rollback(); + } + } + + internal override void InDoubt() + { + OletxVolatileEnlistment? enlistment; + int localCount; + + lock (this) + { + // Tell all the enlistments that the transaction is InDoubt and let the enlistment + // state determine if the notification should be delivered. + Phase = 2; + localCount = EnlistmentList.Count; + } + + for (int i = 0; i < localCount; i++ ) + { + enlistment = EnlistmentList[i] as OletxVolatileEnlistment; + if (enlistment == null) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxPhase1VolatileEnlistmentContainer.InDoubt, enlistmentList element is not an OletxVolatileEnlistment."); + throw new InvalidOperationException( SR.InternalError); + } + + enlistment.InDoubt(); + } + } + + internal void Phase0Request(bool abortHint) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + OletxVolatileEnlistment? enlistment; + int localCount; + OletxCommittableTransaction? committableTx; + bool commitNotYetCalled = false; + + lock (this) + { + if (etwLog.IsEnabled()) + { + string description = "OletxPhase0VolatileEnlistmentContainer.Phase0Request, abortHint = " + + abortHint.ToString(CultureInfo.CurrentCulture) + + ", phase = " + Phase.ToString(CultureInfo.CurrentCulture); + + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, description); + } + + _aborting = abortHint; + committableTx = RealOletxTransaction.CommittableTransaction; + if (committableTx != null) + { + // We are dealing with the committable transaction. If Commit or BeginCommit has NOT been + // called, then we are dealing with a situation where the TM went down and we are getting + // a bogus Phase0Request with abortHint = false (COMPlus bug 36760/36758). This is an attempt + // to not send the app a Prepare request when we know the transaction is going to abort. + if (!committableTx.CommitCalled) + { + commitNotYetCalled = true; + _aborting = true; + } + } + // It's possible that we are in phase 2 if we got an Aborted outcome from the transaction before we got the + // Phase0Request. In both cases, we just respond to the proxy and don't bother telling the enlistments. + // They have either already heard about the abort or will soon. + if (Phase == 2 || Phase == -1) + { + if (Phase == -1) + { + Phase = 0; + } + + // If we got an abort hint or we are the committable transaction and Commit has not yet been called or the TM went down, + // we don't want to do any more work on the transaction. The abort notifications will be sent by the phase 1 + // enlistment + if (_aborting || _tmWentDown || commitNotYetCalled || Phase == 2) + { + // There is a possible race where we could get the Phase0Request before we are given the + // shim. In that case, we will vote "no" when we are given the shim. + if (_phase0EnlistmentShim != null) + { + try + { + _phase0EnlistmentShim.Phase0Done(false); + // We need to set the alreadyVoted flag to true once we successfully voted, so later we don't vote again when OletxDependentTransaction::Complete is called + // Otherwise, in OletxPhase0VolatileEnlistmentContainer::DecrementOutstandingNotifications code path, we are going to call Phase0Done( true ) again + // and result in an access violation while accessing the pPhase0EnlistmentAsync member variable of the Phase0Shim object. + AlreadyVoted = true; + } + // I am not going to check for XACT_E_PROTOCOL here because that check is a workaround for a bug + // that only shows up if abortingHint is false. + catch (COMException ex) + { + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + } + return; + } + OutstandingNotifications = EnlistmentList.Count; + localCount = EnlistmentList.Count; + // If we don't have any enlistments, then we must have created this container for + // delay commit dependent clones only. So we need to fake a notification. + if (localCount == 0) + { + OutstandingNotifications = 1; + } + } + else // any other phase is bad news. + { + if (etwLog.IsEnabled()) + { + etwLog.InternalError("OletxPhase0VolatileEnlistmentContainer.Phase0Request, phase != -1"); + } + + Debug.Fail("OletxPhase0VolatileEnlistmentContainer.Phase0Request, phase != -1"); + throw new InvalidOperationException( SR.InternalError); + } + } + + // We may not have any Phase0 volatile enlistments, which means that this container + // got created solely for delay commit dependent transactions. We need to fake out a + // notification completion. + if (localCount == 0) + { + DecrementOutstandingNotifications(true); + } + else + { + for (int i = 0; i < localCount; i++) + { + enlistment = EnlistmentList[i] as OletxVolatileEnlistment; + if (enlistment == null) + { + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxPhase0VolatileEnlistmentContainer.Phase0Request, enlistmentList element is not an OletxVolatileEnlistment."); + throw new InvalidOperationException( SR.InternalError); + } + + // Do the notification outside any locks. + Debug.Assert(enlistment.EnlistDuringPrepareRequired, "OletxPhase0VolatileEnlistmentContainer.Phase0Request, enlistmentList element not marked as EnlistmentDuringPrepareRequired."); + Debug.Assert(!abortHint, "OletxPhase0VolatileEnlistmentContainer.Phase0Request, abortingHint is true just before sending Prepares."); + + enlistment.Prepare(this); + } + } + + if (etwLog.IsEnabled()) + { + string description = "OletxPhase0VolatileEnlistmentContainer.Phase0Request, abortHint = " + abortHint.ToString(CultureInfo.CurrentCulture); + + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, description); + } + } +} + +internal sealed class OletxPhase1VolatileEnlistmentContainer : OletxVolatileEnlistmentContainer +{ + private VoterBallotShim? _voterBallotShim; + + internal OletxPhase1VolatileEnlistmentContainer(RealOletxTransaction realOletxTransaction) + : base(realOletxTransaction) + { + // This will be set later, after the caller creates the enlistment with the proxy. + _voterBallotShim = null; + + Phase = -1; + OutstandingNotifications = 0; + IncompleteDependentClones = 0; + AlreadyVoted = false; + + // If anybody votes false, this will get set to false. + CollectedVoteYes = true; + + // This is a new undecided enlistment on the transaction. Do this last since it has side affects. + realOletxTransaction.IncrementUndecidedEnlistments(); + } + + // Returns true if this container is enlisted for Phase 0. + internal void AddEnlistment(OletxVolatileEnlistment enlistment) + { + Debug.Assert(enlistment != null, "Argument is null"); + + lock (this) + { + if (Phase != -1) + { + throw TransactionException.Create(SR.TooLate, null); + } + + EnlistmentList.Add( enlistment ); + } + } + + internal override void AddDependentClone() + { + lock (this) + { + if (Phase != -1) + { + throw TransactionException.CreateTransactionStateException(null, Guid.Empty); + } + + // We simply need to block the response to the proxy until all clone is completed. + IncompleteDependentClones++; + } + } + + internal override void DependentCloneCompleted() + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + string description = "OletxPhase1VolatileEnlistmentContainer.DependentCloneCompleted, outstandingNotifications = " + + OutstandingNotifications.ToString(CultureInfo.CurrentCulture) + + ", incompleteDependentClones = " + + IncompleteDependentClones.ToString(CultureInfo.CurrentCulture) + + ", phase = " + Phase.ToString(CultureInfo.CurrentCulture); + + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, description); + } + + // This is to synchronize with the corresponding AddDependentClone which takes the container lock while incrementing the incompleteDependentClone count + lock (this) + { + IncompleteDependentClones--; + } + + Debug.Assert(OutstandingNotifications >= 0, "OletxPhase1VolatileEnlistmentContainer.DependentCloneCompleted - DependentCloneCompleted < 0"); + + if (etwLog.IsEnabled()) + { + string description = "OletxPhase1VolatileEnlistmentContainer.DependentCloneCompleted"; + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, description); + } + } + + internal override void RollbackFromTransaction() + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + bool voteNo = false; + VoterBallotShim? localVoterShim = null; + + lock (this) + { + if (etwLog.IsEnabled()) + { + string description = "OletxPhase1VolatileEnlistmentContainer.RollbackFromTransaction, outstandingNotifications = " + + OutstandingNotifications.ToString(CultureInfo.CurrentCulture) + + ", incompleteDependentClones = " + IncompleteDependentClones.ToString(CultureInfo.CurrentCulture); + + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, description); + } + + if (Phase == 1 && OutstandingNotifications > 0) + { + AlreadyVoted = true; + voteNo = true; + localVoterShim = _voterBallotShim; + } + } + + if (voteNo) + { + try + { + localVoterShim?.Vote(false); + + // We are not going to hear anymore from the proxy if we voted no, so we need to tell the + // enlistments to rollback. The state of the OletxVolatileEnlistment will determine whether or + // not the notification actually goes out to the app. + Aborted(); + } + catch (COMException ex) when (ex.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN || ex.ErrorCode == OletxHelper.XACT_E_TMNOTAVAILABLE) + { + lock (this) + { + // If we are in phase 1, we need to tell the enlistments that the transaction is InDoubt. + if (Phase == 1) + { + InDoubt(); + } + } + + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, "OletxPhase1VolatileEnlistmentContainer.RollbackFromTransaction"); + } + } + + internal VoterBallotShim? VoterBallotShim + { + get + { + lock (this) + { + return _voterBallotShim; + } + } + set + { + lock (this) + { + _voterBallotShim = value; + } + } + } + + internal override void DecrementOutstandingNotifications(bool voteYes) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + bool respondToProxy = false; + VoterBallotShim? localVoterShim = null; + + lock (this) + { + if (etwLog.IsEnabled()) + { + string description = "OletxPhase1VolatileEnlistmentContainer.DecrementOutstandingNotifications, outstandingNotifications = " + + OutstandingNotifications.ToString(CultureInfo.CurrentCulture) + + ", incompleteDependentClones = " + + IncompleteDependentClones.ToString(CultureInfo.CurrentCulture); + + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, description); + } + + OutstandingNotifications--; + Debug.Assert(OutstandingNotifications >= 0, "OletxPhase1VolatileEnlistmentContainer.DecrementOutstandingNotifications - outstandingNotifications < 0"); + CollectedVoteYes = CollectedVoteYes && voteYes; + if (OutstandingNotifications == 0) + { + if (Phase == 1 && !AlreadyVoted) + { + respondToProxy = true; + AlreadyVoted = true; + localVoterShim = VoterBallotShim; + } + RealOletxTransaction.DecrementUndecidedEnlistments(); + } + } + + try + { + if (respondToProxy) + { + if (CollectedVoteYes && !RealOletxTransaction.Doomed) + { + localVoterShim?.Vote(true); + } + else // we need to vote no. + { + localVoterShim?.Vote(false); + + // We are not going to hear anymore from the proxy if we voted no, so we need to tell the + // enlistments to rollback. The state of the OletxVolatileEnlistment will determine whether or + // not the notification actually goes out to the app. + Aborted(); + } + } + } + catch (COMException ex) when (ex.ErrorCode == OletxHelper.XACT_E_CONNECTION_DOWN || ex.ErrorCode == OletxHelper.XACT_E_TMNOTAVAILABLE) + { + lock (this) + { + // If we are in phase 1, we need to tell the enlistments that the transaction is InDoubt. + if (Phase == 1) + { + InDoubt(); + } + + // There is nothing special to do for phase 2. + } + + if (etwLog.IsEnabled()) + { + etwLog.ExceptionConsumed(TraceSourceType.TraceSourceOleTx, ex); + } + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, "OletxPhase1VolatileEnlistmentContainer.DecrementOutstandingNotifications"); + } + } + + internal override void OutcomeFromTransaction(TransactionStatus outcome) + { + bool driveAbort = false; + bool driveInDoubt = false; + + lock (this) + { + // If we are in Phase 1 and still have outstanding notifications, we need + // to drive sending of the outcome to the enlistments. If we are in any + // other phase, or we don't have outstanding notifications, we will eventually + // get the outcome notification on our OWN voter enlistment, so we will just + // wait for that. + if (Phase == 1 && OutstandingNotifications > 0) + { + switch (outcome) + { + case TransactionStatus.Aborted: + driveAbort = true; + break; + case TransactionStatus.InDoubt: + driveInDoubt = true; + break; + default: + Debug.Fail("OletxPhase1VolatileEnlistmentContainer.OutcomeFromTransaction, outcome is not Aborted or InDoubt"); + break; + } + } + } + + if (driveAbort) + { + Aborted(); + } + + if (driveInDoubt) + { + InDoubt(); + } + } + + internal override void Committed() + { + OletxVolatileEnlistment? enlistment; + int localPhase1Count; + + lock (this) + { + Phase = 2; + localPhase1Count = EnlistmentList.Count; + } + + for ( int i = 0; i < localPhase1Count; i++ ) + { + enlistment = EnlistmentList[i] as OletxVolatileEnlistment; + if (enlistment == null) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxPhase1VolatileEnlistmentContainer.Committed, enlistmentList element is not an OletxVolatileEnlistment."); + throw new InvalidOperationException(SR.InternalError); + } + + enlistment.Commit(); + } + } + + internal override void Aborted() + { + OletxVolatileEnlistment? enlistment; + int localPhase1Count; + + lock (this) + { + Phase = 2; + localPhase1Count = EnlistmentList.Count; + } + + for (int i = 0; i < localPhase1Count; i++) + { + enlistment = EnlistmentList[i] as OletxVolatileEnlistment; + if (enlistment == null) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxPhase1VolatileEnlistmentContainer.Aborted, enlistmentList element is not an OletxVolatileEnlistment."); + throw new InvalidOperationException( SR.InternalError); + } + + enlistment.Rollback(); + } + } + + internal override void InDoubt() + { + OletxVolatileEnlistment? enlistment; + int localPhase1Count; + + lock (this) + { + Phase = 2; + localPhase1Count = EnlistmentList.Count; + } + + for (int i = 0; i < localPhase1Count; i++) + { + enlistment = EnlistmentList[i] as OletxVolatileEnlistment; + if (enlistment == null) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxPhase1VolatileEnlistmentContainer.InDoubt, enlistmentList element is not an OletxVolatileEnlistment."); + throw new InvalidOperationException( SR.InternalError); + } + + enlistment.InDoubt(); + } + } + + internal void VoteRequest() + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + OletxVolatileEnlistment? enlistment; + int localPhase1Count = 0; + bool voteNo = false; + + lock (this) + { + if (etwLog.IsEnabled()) + { + string description = "OletxPhase1VolatileEnlistmentContainer.VoteRequest"; + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, description); + } + + Phase = 1; + + // If we still have incomplete dependent clones, vote no now. + if (IncompleteDependentClones > 0) + { + voteNo = true; + OutstandingNotifications = 1; + } + else + { + OutstandingNotifications = EnlistmentList.Count; + localPhase1Count = EnlistmentList.Count; + // We may not have an volatile phase 1 enlistments, which means that this + // container was created only for non-delay commit dependent clones. If that + // is the case, fake out a notification and response. + if (localPhase1Count == 0) + { + OutstandingNotifications = 1; + } + } + + RealOletxTransaction.TooLateForEnlistments = true; + } + + if (voteNo) + { + DecrementOutstandingNotifications( false ); + } + else if (localPhase1Count == 0) + { + DecrementOutstandingNotifications( true ); + } + else + { + for (int i = 0; i < localPhase1Count; i++) + { + enlistment = EnlistmentList[i] as OletxVolatileEnlistment; + if (enlistment == null) + { + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxPhase1VolatileEnlistmentContainer.VoteRequest, enlistmentList element is not an OletxVolatileEnlistment."); + throw new InvalidOperationException( SR.InternalError); + } + + enlistment.Prepare(this); + } + } + + if (etwLog.IsEnabled()) + { + string description = "OletxPhase1VolatileEnlistmentContainer.VoteRequest"; + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, description); + } + } +} + +internal sealed class OletxVolatileEnlistment : OletxBaseEnlistment, IPromotedEnlistment +{ + private enum OletxVolatileEnlistmentState + { + Active, + Preparing, + Committing, + Aborting, + Prepared, + Aborted, + InDoubt, + Done + } + + private IEnlistmentNotificationInternal _iEnlistmentNotification; + private OletxVolatileEnlistmentState _state = OletxVolatileEnlistmentState.Active; + private OletxVolatileEnlistmentContainer? _container; + internal bool EnlistDuringPrepareRequired; + + // This is used if the transaction outcome is received while a prepare request + // is still outstanding to an app. Active means no outcome, yet. Aborted means + // we should tell the app Aborted. InDoubt means tell the app InDoubt. This + // should never be Committed because we shouldn't receive a Committed notification + // from the proxy while we have a Prepare outstanding. + private TransactionStatus _pendingOutcome; + + internal OletxVolatileEnlistment( + IEnlistmentNotificationInternal enlistmentNotification, + EnlistmentOptions enlistmentOptions, + OletxTransaction oletxTransaction) + : base(null!, oletxTransaction) + { + _iEnlistmentNotification = enlistmentNotification; + EnlistDuringPrepareRequired = (enlistmentOptions & EnlistmentOptions.EnlistDuringPrepareRequired) != 0; + + // We get a container when we are asked to vote. + _container = null; + + _pendingOutcome = TransactionStatus.Active; + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentCreated(TraceSourceType.TraceSourceOleTx, InternalTraceIdentifier, EnlistmentType.Volatile, enlistmentOptions); + } + } + + internal void Prepare(OletxVolatileEnlistmentContainer container) + { + OletxVolatileEnlistmentState localState = OletxVolatileEnlistmentState.Active; + IEnlistmentNotificationInternal localEnlistmentNotification; + + lock (this) + { + localEnlistmentNotification = _iEnlistmentNotification; + + // The app may have already called EnlistmentDone. If this occurs, don't bother sending + // the notification to the app. + if (OletxVolatileEnlistmentState.Active == _state) + { + localState = _state = OletxVolatileEnlistmentState.Preparing; + } + else + { + localState = _state; + } + _container = container; + } + + // Tell the application to do the work. + if (localState == OletxVolatileEnlistmentState.Preparing) + { + if (localEnlistmentNotification != null) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceOleTx, InternalTraceIdentifier, NotificationCall.Prepare); + } + + localEnlistmentNotification.Prepare(this); + } + else + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxVolatileEnlistment.Prepare, no enlistmentNotification member."); + throw new InvalidOperationException(SR.InternalError); + } + } + else if (localState == OletxVolatileEnlistmentState.Done) + { + // Voting yes because it was an early read-only vote. + container.DecrementOutstandingNotifications( true ); + + // We must have had a race between EnlistmentDone and the proxy telling + // us Phase0Request. Just return. + return; + } + // It is okay to be in Prepared state if we are edpr=true because we already + // did our prepare in Phase0. + else if (localState == OletxVolatileEnlistmentState.Prepared && EnlistDuringPrepareRequired) + { + container.DecrementOutstandingNotifications(true); + return; + } + else if (localState is OletxVolatileEnlistmentState.Aborting or OletxVolatileEnlistmentState.Aborted) + { + // An abort has raced with this volatile Prepare + // decrement the outstanding notifications making sure to vote no. + container.DecrementOutstandingNotifications(false); + return; + } + else + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxVolatileEnlistment.Prepare, invalid state."); + throw new InvalidOperationException( SR.InternalError); + } + } + + internal void Commit() + { + OletxVolatileEnlistmentState localState = OletxVolatileEnlistmentState.Active; + IEnlistmentNotificationInternal? localEnlistmentNotification = null; + + lock (this) + { + // The app may have already called EnlistmentDone. If this occurs, don't bother sending + // the notification to the app and we don't need to tell the proxy. + if (_state == OletxVolatileEnlistmentState.Prepared) + { + localState = _state = OletxVolatileEnlistmentState.Committing; + localEnlistmentNotification = _iEnlistmentNotification; + } + else + { + localState = _state; + } + } + + // Tell the application to do the work. + if (OletxVolatileEnlistmentState.Committing == localState) + { + if (localEnlistmentNotification != null) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceOleTx, InternalTraceIdentifier, NotificationCall.Commit); + } + + localEnlistmentNotification.Commit(this); + } + else + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxVolatileEnlistment.Commit, no enlistmentNotification member."); + throw new InvalidOperationException(SR.InternalError); + } + } + else if (localState == OletxVolatileEnlistmentState.Done) + { + // Early Exit - state was Done + } + else + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxVolatileEnlistment.Commit, invalid state."); + throw new InvalidOperationException(SR.InternalError); + } + } + + internal void Rollback() + { + OletxVolatileEnlistmentState localState = OletxVolatileEnlistmentState.Active; + IEnlistmentNotificationInternal? localEnlistmentNotification = null; + + lock (this) + { + // The app may have already called EnlistmentDone. If this occurs, don't bother sending + // the notification to the app and we don't need to tell the proxy. + if ( _state is OletxVolatileEnlistmentState.Prepared or OletxVolatileEnlistmentState.Active) + { + localState = _state = OletxVolatileEnlistmentState.Aborting; + localEnlistmentNotification = _iEnlistmentNotification; + } + else + { + if (_state == OletxVolatileEnlistmentState.Preparing) + { + _pendingOutcome = TransactionStatus.Aborted; + } + + localState = _state; + } + } + + switch (localState) + { + // Tell the application to do the work. + case OletxVolatileEnlistmentState.Aborting: + { + if (localEnlistmentNotification != null) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceOleTx, InternalTraceIdentifier, NotificationCall.Rollback); + } + + localEnlistmentNotification.Rollback(this); + } + + // There is a small race where Rollback could be called when the enlistment is already + // aborting the transaciton, so just ignore that call. When the app enlistment + // finishes responding to its Rollback notification with EnlistmentDone, things will get + // cleaned up. + break; + } + case OletxVolatileEnlistmentState.Preparing: + // We need to tolerate this state, but we have already marked the + // enlistment as pendingRollback, so there is nothing else to do here. + break; + case OletxVolatileEnlistmentState.Done: + // Early Exit - state was Done + break; + default: + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxVolatileEnlistment.Rollback, invalid state."); + throw new InvalidOperationException(SR.InternalError); + } + } + } + + internal void InDoubt() + { + OletxVolatileEnlistmentState localState = OletxVolatileEnlistmentState.Active; + IEnlistmentNotificationInternal? localEnlistmentNotification = null; + + lock (this) + { + // The app may have already called EnlistmentDone. If this occurs, don't bother sending + // the notification to the app and we don't need to tell the proxy. + if (_state == OletxVolatileEnlistmentState.Prepared) + { + localState = _state = OletxVolatileEnlistmentState.InDoubt; + localEnlistmentNotification = _iEnlistmentNotification; + } + else + { + if (_state == OletxVolatileEnlistmentState.Preparing) + { + _pendingOutcome = TransactionStatus.InDoubt; + } + localState = _state; + } + } + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + + switch (localState) + { + // Tell the application to do the work. + case OletxVolatileEnlistmentState.InDoubt when localEnlistmentNotification != null: + { + if (etwLog.IsEnabled()) + { + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceOleTx, InternalTraceIdentifier, NotificationCall.InDoubt); + } + + localEnlistmentNotification.InDoubt(this); + break; + } + case OletxVolatileEnlistmentState.InDoubt: + { + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxVolatileEnlistment.InDoubt, no enlistmentNotification member."); + throw new InvalidOperationException(SR.InternalError); + } + case OletxVolatileEnlistmentState.Preparing: + // We have already set pendingOutcome, so there is nothing else to do. + break; + case OletxVolatileEnlistmentState.Done: + // Early Exit - state was Done + break; + default: + { + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxVolatileEnlistment.InDoubt, invalid state."); + throw new InvalidOperationException( SR.InternalError); + } + } + } + + void IPromotedEnlistment.EnlistmentDone() + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxEnlistment)}.{nameof(IPromotedEnlistment.EnlistmentDone)}"); + etwLog.EnlistmentCallbackPositive(InternalTraceIdentifier, EnlistmentCallback.Done); + } + + OletxVolatileEnlistmentState localState = OletxVolatileEnlistmentState.Active; + OletxVolatileEnlistmentContainer? localContainer; + + lock (this) + { + localState = _state; + localContainer = _container; + + if (_state != OletxVolatileEnlistmentState.Active && + _state != OletxVolatileEnlistmentState.Preparing && + _state != OletxVolatileEnlistmentState.Aborting && + _state != OletxVolatileEnlistmentState.Committing && + _state != OletxVolatileEnlistmentState.InDoubt) + { + throw TransactionException.CreateEnlistmentStateException(null, DistributedTxId); + } + + _state = OletxVolatileEnlistmentState.Done; + } + + // For the Preparing state, we need to decrement the outstanding + // count with the container. If the state is Active, it is an early vote so we + // just stay in the Done state and when we get the Prepare, we will vote appropriately. + if (localState == OletxVolatileEnlistmentState.Preparing) + { + if (localContainer != null) + { + // Specify true. If aborting, it is okay because the transaction is already + // aborting. + localContainer.DecrementOutstandingNotifications(true); + } + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"{nameof(OletxEnlistment)}.{nameof(IPromotedEnlistment.EnlistmentDone)}"); + } + } + + void IPromotedEnlistment.Prepared() + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"OletxPreparingEnlistment.{nameof(IPromotedEnlistment.Prepared)}"); + etwLog.EnlistmentCallbackPositive(InternalTraceIdentifier, EnlistmentCallback.Prepared); + } + + OletxVolatileEnlistmentContainer localContainer; + TransactionStatus localPendingOutcome = TransactionStatus.Active; + + lock (this) + { + if (_state != OletxVolatileEnlistmentState.Preparing) + { + throw TransactionException.CreateEnlistmentStateException(null, DistributedTxId); + } + + _state = OletxVolatileEnlistmentState.Prepared; + localPendingOutcome = _pendingOutcome; + + if (_container == null) + { + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxVolatileEnlistment.Prepared, no container member."); + throw new InvalidOperationException(SR.InternalError); + } + + localContainer = _container; + } + + // Vote yes. + localContainer.DecrementOutstandingNotifications(true); + + switch (localPendingOutcome) + { + case TransactionStatus.Active: + // nothing to do. Everything is proceeding as normal. + break; + + case TransactionStatus.Aborted: + // The transaction aborted while the Prepare was outstanding. + // We need to tell the app to rollback. + Rollback(); + break; + + case TransactionStatus.InDoubt: + // The transaction went InDoubt while the Prepare was outstanding. + // We need to tell the app. + InDoubt(); + break; + + default: + // This shouldn't happen. + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxVolatileEnlistment.Prepared, invalid pending outcome value."); + throw new InvalidOperationException(SR.InternalError); + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"OletxPreparingEnlistment.{nameof(IPromotedEnlistment.Prepared)}"); + } + } + + void IPromotedEnlistment.ForceRollback() + => ((IPromotedEnlistment)this).ForceRollback(null); + + void IPromotedEnlistment.ForceRollback(Exception? e) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this, $"OletxPreparingEnlistment.{nameof(IPromotedEnlistment.ForceRollback)}"); + etwLog.EnlistmentCallbackNegative(InternalTraceIdentifier, EnlistmentCallback.ForceRollback); + } + + OletxVolatileEnlistmentContainer localContainer; + + lock (this) + { + if (_state != OletxVolatileEnlistmentState.Preparing) + { + throw TransactionException.CreateEnlistmentStateException(null, DistributedTxId); + } + + // There are no more notifications that need to happen on this enlistment. + _state = OletxVolatileEnlistmentState.Done; + + if (_container == null) + { + if (etwLog.IsEnabled()) + { + etwLog.InternalError(); + } + + Debug.Fail("OletxVolatileEnlistment.ForceRollback, no container member."); + throw new InvalidOperationException(SR.InternalError); + } + + localContainer = _container; + } + + Interlocked.CompareExchange(ref oletxTransaction!.RealOletxTransaction.InnerException, e, null); + + // Vote no. + localContainer.DecrementOutstandingNotifications(false); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this, $"OletxPreparingEnlistment.{nameof(IPromotedEnlistment.ForceRollback)}"); + } + } + + void IPromotedEnlistment.Committed() => throw new InvalidOperationException(); + void IPromotedEnlistment.Aborted() => throw new InvalidOperationException(); + void IPromotedEnlistment.Aborted(Exception? e) => throw new InvalidOperationException(); + void IPromotedEnlistment.InDoubt() => throw new InvalidOperationException(); + void IPromotedEnlistment.InDoubt(Exception? e) => throw new InvalidOperationException(); + + byte[] IPromotedEnlistment.GetRecoveryInformation() + => throw TransactionException.CreateInvalidOperationException( + TraceSourceType.TraceSourceOleTx, + SR.VolEnlistNoRecoveryInfo, + null, + DistributedTxId); + + InternalEnlistment? IPromotedEnlistment.InternalEnlistment + { + get => InternalEnlistment; + set => InternalEnlistment = value; + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/Transaction.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/Transaction.cs index ba6d5af2f3d66f..10eac90c862d37 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/Transaction.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/Transaction.cs @@ -6,7 +6,7 @@ using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Threading; -using System.Transactions.Distributed; +using System.Transactions.Oletx; namespace System.Transactions { @@ -276,7 +276,7 @@ internal Transaction(IsolationLevel isoLevel, InternalTransaction? internalTrans } } - internal Transaction(DistributedTransaction distributedTransaction) + internal Transaction(OletxTransaction distributedTransaction) { _isoLevel = distributedTransaction.IsolationLevel; _internalTransaction = new InternalTransaction(this, distributedTransaction); @@ -566,7 +566,7 @@ public void Rollback() if (etwLog.IsEnabled()) { etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this); - etwLog.TransactionRollback(this, "Transaction"); + etwLog.TransactionRollback(TraceSourceType.TraceSourceLtm, TransactionTraceId, "Transaction"); } ObjectDisposedException.ThrowIf(Disposed, this); @@ -590,7 +590,7 @@ public void Rollback(Exception? e) if (etwLog.IsEnabled()) { etwLog.MethodEnter(TraceSourceType.TraceSourceLtm, this); - etwLog.TransactionRollback(this, "Transaction"); + etwLog.TransactionRollback(TraceSourceType.TraceSourceLtm, TransactionTraceId, "Transaction"); } ObjectDisposedException.ThrowIf(Disposed, this); @@ -965,7 +965,7 @@ public Enlistment PromoteAndEnlistDurable(Guid resourceManagerIdentifier, TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.MethodEnter(TraceSourceType.TraceSourceDistributed, this); + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, this); } ObjectDisposedException.ThrowIf(Disposed, this); @@ -996,7 +996,7 @@ public Enlistment PromoteAndEnlistDurable(Guid resourceManagerIdentifier, if (etwLog.IsEnabled()) { - etwLog.MethodExit(TraceSourceType.TraceSourceDistributed, this); + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, this); } return enlistment; @@ -1041,7 +1041,7 @@ public void SetDistributedTransactionIdentifier(IPromotableSinglePhaseNotificati } } - internal DistributedTransaction? Promote() + internal OletxTransaction? Promote() { lock (_internalTransaction) { diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionInterop.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionInterop.cs index a65f8c24b9a90a..a8983b5afad2bc 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionInterop.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionInterop.cs @@ -1,26 +1,17 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; using System.Runtime.InteropServices; -using System.Transactions.Distributed; +using System.Transactions.DtcProxyShim; +using System.Transactions.DtcProxyShim.DtcInterfaces; +using System.Transactions.Oletx; namespace System.Transactions { - [ComImport] - [Guid("0fb15084-af41-11ce-bd2b-204c4f4f5020")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - public interface IDtcTransaction - { - void Commit(int retaining, [MarshalAs(UnmanagedType.I4)] int commitType, int reserved); - - void Abort(IntPtr reason, int retaining, int async); - - void GetTransactionInfo(IntPtr transactionInformation); - } - public static class TransactionInterop { - internal static DistributedTransaction ConvertToDistributedTransaction(Transaction transaction) + internal static OletxTransaction ConvertToOletxTransaction(Transaction transaction) { ArgumentNullException.ThrowIfNull(transaction); @@ -31,12 +22,10 @@ internal static DistributedTransaction ConvertToDistributedTransaction(Transacti throw TransactionException.CreateTransactionCompletedException(transaction.DistributedTxId); } - DistributedTransaction? distributedTx = transaction.Promote(); - if (distributedTx == null) - { - throw DistributedTransaction.NotSupported(); - } - return distributedTx; + OletxTransaction? oletxTx = transaction.Promote(); + Debug.Assert(oletxTx != null, "transaction.Promote returned null instead of throwing."); + + return oletxTx; } /// @@ -62,19 +51,39 @@ public static byte[] GetExportCookie(Transaction transaction, byte[] whereabouts TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.MethodEnter(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetExportCookie"); + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionInterop)}.{nameof(GetExportCookie)}"); } + byte[] cookie; + // Copy the whereabouts so that it cannot be modified later. var whereaboutsCopy = new byte[whereabouts.Length]; Buffer.BlockCopy(whereabouts, 0, whereaboutsCopy, 0, whereabouts.Length); - ConvertToDistributedTransaction(transaction); - byte[] cookie = DistributedTransaction.GetExportCookie(whereaboutsCopy); + // First, make sure we are working with an OletxTransaction. + OletxTransaction oletxTx = ConvertToOletxTransaction(transaction); + + try + { + oletxTx.RealOletxTransaction.TransactionShim.Export(whereabouts, out cookie); + } + catch (COMException comException) + { + OletxTransactionManager.ProxyException(comException); + + // We are unsure of what the exception may mean. It is possible that + // we could get E_FAIL when trying to contact a transaction manager that is + // being blocked by a fire wall. On the other hand we may get a COMException + // based on bad data. The more common situation is that the data is fine + // (since it is generated by Microsoft code) and the problem is with + // communication. So in this case we default for unknown exceptions to + // assume that the problem is with communication. + throw TransactionManagerCommunicationException.Create(null, comException); + } if (etwLog.IsEnabled()) { - etwLog.MethodExit(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetExportCookie"); + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionInterop)}.{nameof(GetExportCookie)}"); } return cookie; @@ -92,13 +101,20 @@ public static Transaction GetTransactionFromExportCookie(byte[] cookie) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.MethodEnter(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetTransactionFromExportCookie"); + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionInterop)}.{nameof(GetTransactionFromExportCookie)}"); } var cookieCopy = new byte[cookie.Length]; Buffer.BlockCopy(cookie, 0, cookieCopy, 0, cookie.Length); cookie = cookieCopy; + Transaction? transaction; + TransactionShim? transactionShim = null; + Guid txIdentifier = Guid.Empty; + OletxTransactionIsolationLevel oletxIsoLevel = OletxTransactionIsolationLevel.ISOLATIONLEVEL_SERIALIZABLE; + OutcomeEnlistment? outcomeEnlistment; + OletxTransaction? oleTx; + // Extract the transaction guid from the propagation token to see if we already have a // transaction object for the transaction. // In a cookie, the transaction guid is preceded by a signature guid. @@ -106,24 +122,64 @@ public static Transaction GetTransactionFromExportCookie(byte[] cookie) // First check to see if there is a promoted LTM transaction with the same ID. If there // is, just return that. - Transaction? transaction = TransactionManager.FindPromotedTransaction(txId); + transaction = TransactionManager.FindPromotedTransaction(txId); if (transaction != null) { if (etwLog.IsEnabled()) { - etwLog.MethodExit(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetTransactionFromExportCookie"); + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromExportCookie"); } return transaction; } - // Find or create the promoted transaction. - DistributedTransaction dTx = DistributedTransactionManager.GetTransactionFromExportCookie(cookieCopy, txId); - transaction = TransactionManager.FindOrCreatePromotedTransaction(txId, dTx); + // We need to create a new transaction + RealOletxTransaction? realTx = null; + OletxTransactionManager oletxTm = TransactionManager.DistributedTransactionManager; + + oletxTm.DtcTransactionManagerLock.AcquireReaderLock(-1); + try + { + outcomeEnlistment = new OutcomeEnlistment(); + oletxTm.DtcTransactionManager.ProxyShimFactory.Import(cookie, outcomeEnlistment, out txIdentifier, out oletxIsoLevel, out transactionShim); + } + catch (COMException comException) + { + OletxTransactionManager.ProxyException(comException); + + // We are unsure of what the exception may mean. It is possible that + // we could get E_FAIL when trying to contact a transaction manager that is + // being blocked by a fire wall. On the other hand we may get a COMException + // based on bad data. The more common situation is that the data is fine + // (since it is generated by Microsoft code) and the problem is with + // communication. So in this case we default for unknown exceptions to + // assume that the problem is with communication. + throw TransactionManagerCommunicationException.Create(SR.TraceSourceOletx, comException); + } + finally + { + oletxTm.DtcTransactionManagerLock.ReleaseReaderLock(); + } + + // We need to create a new RealOletxTransaction. + realTx = new RealOletxTransaction( + oletxTm, + transactionShim, + outcomeEnlistment, + txIdentifier, + oletxIsoLevel, + false); + + // Now create the associated OletxTransaction. + oleTx = new OletxTransaction(realTx); + + // If a transaction is found then FindOrCreate will Dispose the oletx + // created. + transaction = TransactionManager.FindOrCreatePromotedTransaction(txId, oleTx); if (etwLog.IsEnabled()) { - etwLog.MethodExit(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetTransactionFromExportCookie"); + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionInterop)}.{nameof(GetTransactionFromExportCookie)}"); } return transaction; @@ -136,20 +192,39 @@ public static byte[] GetTransmitterPropagationToken(Transaction transaction) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.MethodEnter(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetTransmitterPropagationToken"); + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionInterop)}.{nameof(GetTransmitterPropagationToken)}"); } - ConvertToDistributedTransaction(transaction); - byte[] token = DistributedTransaction.GetTransmitterPropagationToken(); + // First, make sure we are working with an OletxTransaction. + OletxTransaction oletxTx = ConvertToOletxTransaction(transaction); + + byte[] token = GetTransmitterPropagationToken(oletxTx); if (etwLog.IsEnabled()) { - etwLog.MethodExit(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetTransmitterPropagationToken"); + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionInterop)}.{nameof(GetTransmitterPropagationToken)}"); } return token; } + internal static byte[] GetTransmitterPropagationToken(OletxTransaction oletxTx) + { + byte[]? propagationToken = null; + + try + { + propagationToken = oletxTx.RealOletxTransaction.TransactionShim.GetPropagationToken(); + } + catch (COMException comException) + { + OletxTransactionManager.ProxyException(comException); + throw; + } + + return propagationToken; + } + public static Transaction GetTransactionFromTransmitterPropagationToken(byte[] propagationToken) { ArgumentNullException.ThrowIfNull(propagationToken); @@ -162,7 +237,7 @@ public static Transaction GetTransactionFromTransmitterPropagationToken(byte[] p TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.MethodEnter(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetTransactionFromTransmitterPropagationToken"); + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromTransmitterPropagationToken"); } // Extract the transaction guid from the propagation token to see if we already have a @@ -176,20 +251,20 @@ public static Transaction GetTransactionFromTransmitterPropagationToken(byte[] p { if (etwLog.IsEnabled()) { - etwLog.MethodExit(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetTransactionFromTransmitterPropagationToken"); + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromTransmitterPropagationToken"); } return tx; } - DistributedTransaction dTx = GetDistributedTransactionFromTransmitterPropagationToken(propagationToken); + OletxTransaction dTx = GetOletxTransactionFromTransmitterPropagationToken(propagationToken); // If a transaction is found then FindOrCreate will Dispose the distributed transaction created. Transaction returnValue = TransactionManager.FindOrCreatePromotedTransaction(txId, dTx); if (etwLog.IsEnabled()) { - etwLog.MethodExit(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetTransactionFromTransmitterPropagationToken"); + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromTransmitterPropagationToken"); } return returnValue; } @@ -201,15 +276,27 @@ public static IDtcTransaction GetDtcTransaction(Transaction transaction) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.MethodEnter(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetDtcTransaction"); + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionInterop)}.{nameof(GetDtcTransaction)}"); } - ConvertToDistributedTransaction(transaction); - IDtcTransaction transactionNative = DistributedTransaction.GetDtcTransaction(); + IDtcTransaction? transactionNative; + + // First, make sure we are working with an OletxTransaction. + OletxTransaction oletxTx = ConvertToOletxTransaction(transaction); + + try + { + oletxTx.RealOletxTransaction.TransactionShim.GetITransactionNative(out transactionNative); + } + catch (COMException comException) + { + OletxTransactionManager.ProxyException(comException); + throw; + } if (etwLog.IsEnabled()) { - etwLog.MethodExit(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetDtcTransaction"); + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionInterop)}.{nameof(GetDtcTransaction)}"); } return transactionNative; @@ -217,20 +304,121 @@ public static IDtcTransaction GetDtcTransaction(Transaction transaction) public static Transaction GetTransactionFromDtcTransaction(IDtcTransaction transactionNative) { - ArgumentNullException.ThrowIfNull(transactionNative); + ArgumentNullException.ThrowIfNull(transactionNative, nameof(transactionNative)); TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.MethodEnter(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetTransactionFromDtcTransaction"); + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionInterop)}.{nameof(GetTransactionFromDtcTransaction)}"); + } + + Transaction? transaction = null; + bool tooLate = false; + TransactionShim? transactionShim = null; + Guid txIdentifier = Guid.Empty; + OletxTransactionIsolationLevel oletxIsoLevel = OletxTransactionIsolationLevel.ISOLATIONLEVEL_SERIALIZABLE; + OutcomeEnlistment? outcomeEnlistment = null; + RealOletxTransaction? realTx = null; + OletxTransaction? oleTx = null; + + // Let's get the guid of the transaction from the proxy to see if we already have an object. + if (transactionNative is not ITransaction myTransactionNative) + { + throw new ArgumentException(SR.InvalidArgument, nameof(transactionNative)); + } + + OletxXactTransInfo xactInfo; + try + { + myTransactionNative.GetTransactionInfo(out xactInfo); + } + catch (COMException ex) when (ex.ErrorCode == OletxHelper.XACT_E_NOTRANSACTION) + { + // If we get here, the transaction has appraently already been committed or aborted. Allow creation of the + // OletxTransaction, but it will be marked with a status of InDoubt and attempts to get its Identifier + // property will result in a TransactionException. + tooLate = true; + xactInfo.Uow = Guid.Empty; } - Transaction transaction = DistributedTransactionManager.GetTransactionFromDtcTransaction(transactionNative); + OletxTransactionManager oletxTm = TransactionManager.DistributedTransactionManager; + if (!tooLate) + { + // First check to see if there is a promoted LTM transaction with the same ID. If there + // is, just return that. + transaction = TransactionManager.FindPromotedTransaction(xactInfo.Uow); + if (transaction != null) + { + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionInterop)}.{nameof(GetTransactionFromDtcTransaction)}"); + } + + return transaction; + } + + // We need to create a new RealOletxTransaction... + oletxTm.DtcTransactionManagerLock.AcquireReaderLock(-1); + try + { + outcomeEnlistment = new OutcomeEnlistment(); + oletxTm.DtcTransactionManager.ProxyShimFactory.CreateTransactionShim( + transactionNative, + outcomeEnlistment, + out txIdentifier, + out oletxIsoLevel, + out transactionShim); + } + catch (COMException comException) + { + OletxTransactionManager.ProxyException(comException); + throw; + } + finally + { + oletxTm.DtcTransactionManagerLock.ReleaseReaderLock(); + } + + // We need to create a new RealOletxTransaction. + realTx = new RealOletxTransaction( + oletxTm, + transactionShim, + outcomeEnlistment, + txIdentifier, + oletxIsoLevel, + false); + + oleTx = new OletxTransaction(realTx); + + // If a transaction is found then FindOrCreate will Dispose the oletx + // created. + transaction = TransactionManager.FindOrCreatePromotedTransaction(xactInfo.Uow, oleTx); + } + else + { + // It was too late to do a clone of the provided ITransactionNative, so we are just going to + // create a RealOletxTransaction without a transaction shim or outcome enlistment. + realTx = new RealOletxTransaction( + oletxTm, + null, + null, + txIdentifier, + OletxTransactionIsolationLevel.ISOLATIONLEVEL_SERIALIZABLE, + false); + + oleTx = new OletxTransaction(realTx); + transaction = new Transaction(oleTx); + TransactionManager.FireDistributedTransactionStarted(transaction); + oleTx.SavedLtmPromotedTransaction = transaction; + + InternalTransaction.DistributedTransactionOutcome(transaction._internalTransaction, TransactionStatus.InDoubt); + } if (etwLog.IsEnabled()) { - etwLog.MethodExit(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetTransactionFromDtcTransaction"); + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionInterop)}.{nameof(GetTransactionFromDtcTransaction)}"); } + return transaction; } @@ -239,19 +427,36 @@ public static byte[] GetWhereabouts() TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.MethodEnter(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetWhereabouts"); + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionInterop)}.${nameof(GetWhereabouts)}"); + } + + OletxTransactionManager oletxTm = TransactionManager.DistributedTransactionManager; + if (oletxTm == null) + { + throw new ArgumentException(SR.InvalidArgument, "transactionManager"); } - byte[] returnValue = DistributedTransactionManager.GetWhereabouts(); + byte[]? returnValue; + + oletxTm.DtcTransactionManagerLock.AcquireReaderLock(-1); + try + { + returnValue = oletxTm.DtcTransactionManager.Whereabouts; + } + finally + { + oletxTm.DtcTransactionManagerLock.ReleaseReaderLock(); + } if (etwLog.IsEnabled()) { - etwLog.MethodExit(TraceSourceType.TraceSourceDistributed, "TransactionInterop.GetWhereabouts"); + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionInterop)}.${nameof(GetWhereabouts)}"); } + return returnValue; } - internal static DistributedTransaction GetDistributedTransactionFromTransmitterPropagationToken(byte[] propagationToken) + internal static OletxTransaction GetOletxTransactionFromTransmitterPropagationToken(byte[] propagationToken) { ArgumentNullException.ThrowIfNull(propagationToken); @@ -260,10 +465,56 @@ internal static DistributedTransaction GetDistributedTransactionFromTransmitterP throw new ArgumentException(SR.InvalidArgument, nameof(propagationToken)); } + Guid identifier; + OletxTransactionIsolationLevel oletxIsoLevel; + OutcomeEnlistment outcomeEnlistment; + TransactionShim? transactionShim = null; + byte[] propagationTokenCopy = new byte[propagationToken.Length]; Array.Copy(propagationToken, propagationTokenCopy, propagationToken.Length); + propagationToken = propagationTokenCopy; + + // First we need to create an OletxTransactionManager from Config. + OletxTransactionManager oletxTm = TransactionManager.DistributedTransactionManager; + + oletxTm.DtcTransactionManagerLock.AcquireReaderLock(-1); + try + { + outcomeEnlistment = new OutcomeEnlistment(); + oletxTm.DtcTransactionManager.ProxyShimFactory.ReceiveTransaction( + propagationToken, + outcomeEnlistment, + out identifier, + out oletxIsoLevel, + out transactionShim); + } + catch (COMException comException) + { + OletxTransactionManager.ProxyException(comException); + + // We are unsure of what the exception may mean. It is possible that + // we could get E_FAIL when trying to contact a transaction manager that is + // being blocked by a fire wall. On the other hand we may get a COMException + // based on bad data. The more common situation is that the data is fine + // (since it is generated by Microsoft code) and the problem is with + // communication. So in this case we default for unknown exceptions to + // assume that the problem is with communication. + throw TransactionManagerCommunicationException.Create(SR.TraceSourceOletx, comException); + } + finally + { + oletxTm.DtcTransactionManagerLock.ReleaseReaderLock(); + } + + var realTx = new RealOletxTransaction( + oletxTm, + transactionShim, + outcomeEnlistment, + identifier, + oletxIsoLevel, + false); - return DistributedTransactionManager.GetDistributedTransactionFromTransmitterPropagationToken(propagationTokenCopy); + return new OletxTransaction(realTx); } } } diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionInteropNonWindows.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionInteropNonWindows.cs new file mode 100644 index 00000000000000..d656c1a6458d59 --- /dev/null +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionInteropNonWindows.cs @@ -0,0 +1,257 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using System.Transactions.Oletx; + +namespace System.Transactions +{ + public static class TransactionInterop + { + internal static OletxTransaction ConvertToOletxTransaction(Transaction transaction) + { + ArgumentNullException.ThrowIfNull(transaction); + + ObjectDisposedException.ThrowIf(transaction.Disposed, transaction); + + if (transaction._complete) + { + throw TransactionException.CreateTransactionCompletedException(transaction.DistributedTxId); + } + + OletxTransaction? distributedTx = transaction.Promote(); + if (distributedTx == null) + { + throw OletxTransaction.NotSupported(); + } + return distributedTx; + } + + /// + /// This is the PromoterType value that indicates that the transaction is promoting to MSDTC. + /// + /// If using the variation of Transaction.EnlistPromotableSinglePhase that takes a PromoterType and the + /// ITransactionPromoter being used promotes to MSDTC, then this is the value that should be + /// specified for the PromoterType parameter to EnlistPromotableSinglePhase. + /// + /// If using the variation of Transaction.EnlistPromotableSinglePhase that assumes promotion to MSDTC and + /// it that returns false, the caller can compare this value with Transaction.PromoterType to + /// verify that the transaction promoted, or will promote, to MSDTC. If the Transaction.PromoterType + /// matches this value, then the caller can continue with its enlistment with MSDTC. But if it + /// does not match, the caller will not be able to enlist with MSDTC. + /// + public static readonly Guid PromoterTypeDtc = new Guid("14229753-FFE1-428D-82B7-DF73045CB8DA"); + + public static byte[] GetExportCookie(Transaction transaction, byte[] whereabouts) + { + ArgumentNullException.ThrowIfNull(transaction); + ArgumentNullException.ThrowIfNull(whereabouts); + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetExportCookie"); + } + + // Copy the whereabouts so that it cannot be modified later. + var whereaboutsCopy = new byte[whereabouts.Length]; + Buffer.BlockCopy(whereabouts, 0, whereaboutsCopy, 0, whereabouts.Length); + + ConvertToOletxTransaction(transaction); + byte[] cookie = OletxTransaction.GetExportCookie(whereaboutsCopy); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetExportCookie"); + } + + return cookie; + } + + public static Transaction GetTransactionFromExportCookie(byte[] cookie) + { + ArgumentNullException.ThrowIfNull(cookie); + + if (cookie.Length < 32) + { + throw new ArgumentException(SR.InvalidArgument, nameof(cookie)); + } + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromExportCookie"); + } + + var cookieCopy = new byte[cookie.Length]; + Buffer.BlockCopy(cookie, 0, cookieCopy, 0, cookie.Length); + cookie = cookieCopy; + + // Extract the transaction guid from the propagation token to see if we already have a + // transaction object for the transaction. + // In a cookie, the transaction guid is preceded by a signature guid. + var txId = new Guid(cookie.AsSpan(16, 16)); + + // First check to see if there is a promoted LTM transaction with the same ID. If there + // is, just return that. + Transaction? transaction = TransactionManager.FindPromotedTransaction(txId); + if (transaction != null) + { + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromExportCookie"); + } + + return transaction; + } + + // Find or create the promoted transaction. + OletxTransaction dTx = OletxTransactionManager.GetTransactionFromExportCookie(cookieCopy, txId); + transaction = TransactionManager.FindOrCreatePromotedTransaction(txId, dTx); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromExportCookie"); + } + + return transaction; + } + + public static byte[] GetTransmitterPropagationToken(Transaction transaction) + { + ArgumentNullException.ThrowIfNull(transaction); + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransmitterPropagationToken"); + } + + ConvertToOletxTransaction(transaction); + byte[] token = OletxTransaction.GetTransmitterPropagationToken(); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransmitterPropagationToken"); + } + + return token; + } + + public static Transaction GetTransactionFromTransmitterPropagationToken(byte[] propagationToken) + { + ArgumentNullException.ThrowIfNull(propagationToken); + + if (propagationToken.Length < 24) + { + throw new ArgumentException(SR.InvalidArgument, nameof(propagationToken)); + } + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromTransmitterPropagationToken"); + } + + // Extract the transaction guid from the propagation token to see if we already have a + // transaction object for the transaction. + // In a propagation token, the transaction guid is preceded by two version DWORDs. + var txId = new Guid(propagationToken.AsSpan(8, 16)); + + // First check to see if there is a promoted LTM transaction with the same ID. If there is, just return that. + Transaction? tx = TransactionManager.FindPromotedTransaction(txId); + if (null != tx) + { + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromTransmitterPropagationToken"); + } + + return tx; + } + + OletxTransaction dTx = GetOletxTransactionFromTransmitterPropagationToken(propagationToken); + + // If a transaction is found then FindOrCreate will Dispose the distributed transaction created. + Transaction returnValue = TransactionManager.FindOrCreatePromotedTransaction(txId, dTx); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromTransmitterPropagationToken"); + } + return returnValue; + } + + public static IDtcTransaction GetDtcTransaction(Transaction transaction) + { + ArgumentNullException.ThrowIfNull(transaction); + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetDtcTransaction"); + } + + ConvertToOletxTransaction(transaction); + IDtcTransaction transactionNative = OletxTransaction.GetDtcTransaction(); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetDtcTransaction"); + } + + return transactionNative; + } + + public static Transaction GetTransactionFromDtcTransaction(IDtcTransaction transactionNative) + { + ArgumentNullException.ThrowIfNull(transactionNative); + + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromDtcTransaction"); + } + + Transaction transaction = OletxTransactionManager.GetTransactionFromDtcTransaction(transactionNative); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromDtcTransaction"); + } + return transaction; + } + + public static byte[] GetWhereabouts() + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetWhereabouts"); + } + + byte[] returnValue = OletxTransactionManager.GetWhereabouts(); + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetWhereabouts"); + } + return returnValue; + } + + internal static OletxTransaction GetOletxTransactionFromTransmitterPropagationToken(byte[] propagationToken) + { + ArgumentNullException.ThrowIfNull(propagationToken); + + if (propagationToken.Length < 24) + { + throw new ArgumentException(SR.InvalidArgument, nameof(propagationToken)); + } + + byte[] propagationTokenCopy = new byte[propagationToken.Length]; + Array.Copy(propagationToken, propagationTokenCopy, propagationToken.Length); + + return OletxTransactionManager.GetOletxTransactionFromTransmitterPropagationToken(propagationTokenCopy); + } + } +} diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs index ff811ad32a6de8..7ce21c51bad238 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs @@ -6,7 +6,7 @@ using System.IO; using System.Threading; using System.Transactions.Configuration; -using System.Transactions.Distributed; +using System.Transactions.Oletx; namespace System.Transactions { @@ -18,6 +18,7 @@ public static class TransactionManager { // Revovery Information Version private const int RecoveryInformationVersion1 = 1; + private const int CurrentRecoveryVersion = RecoveryInformationVersion1; // Hashtable of promoted transactions, keyed by identifier guid. This is used by // FindPromotedTransaction to support transaction equivalence when a transaction is @@ -215,9 +216,9 @@ public static Enlistment Reenlist( } - private static DistributedTransactionManager CheckTransactionManager(string? nodeName) + private static OletxTransactionManager CheckTransactionManager(string? nodeName) { - DistributedTransactionManager tm = DistributedTransactionManager; + OletxTransactionManager tm = DistributedTransactionManager; if (!((tm.NodeName == null && (nodeName == null || nodeName.Length == 0)) || (tm.NodeName != null && tm.NodeName.Equals(nodeName)))) { @@ -390,6 +391,55 @@ public static TimeSpan MaximumTimeout } } + // This routine writes the "header" for the recovery information, based on the + // type of the calling object and its provided parameter collection. This information + // we be read back by the static Reenlist method to create the necessary transaction + // manager object with the right parameters in order to do a ReenlistTransaction call. + internal static byte[] GetRecoveryInformation( + string? startupInfo, + byte[] resourceManagerRecoveryInformation + ) + { + TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; + if (etwLog.IsEnabled()) + { + etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionManager)}.{nameof(GetRecoveryInformation)}"); + } + + MemoryStream stream = new MemoryStream(); + byte[]? returnValue = null; + + try + { + // Manually write the recovery information + BinaryWriter writer = new BinaryWriter(stream); + + writer.Write(CurrentRecoveryVersion); + if (startupInfo != null) + { + writer.Write(startupInfo); + } + else + { + writer.Write(""); + } + writer.Write(resourceManagerRecoveryInformation); + writer.Flush(); + returnValue = stream.ToArray(); + } + finally + { + stream.Close(); + } + + if (etwLog.IsEnabled()) + { + etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, $"{nameof(TransactionManager)}.{nameof(GetRecoveryInformation)}"); + } + + return returnValue; + } + /// /// This static function throws an ArgumentOutOfRange if the specified IsolationLevel is not within /// the range of valid values. @@ -414,7 +464,6 @@ internal static void ValidateIsolationLevel(IsolationLevel transactionIsolationL } } - /// /// This static function throws an ArgumentOutOfRange if the specified TimeSpan does not meet /// requirements of a valid transaction timeout. Timeout values must be positive. @@ -462,7 +511,7 @@ internal static TimeSpan ValidateTimeout(TimeSpan transactionTimeout) return null; } - internal static Transaction FindOrCreatePromotedTransaction(Guid transactionIdentifier, DistributedTransaction dtx) + internal static Transaction FindOrCreatePromotedTransaction(Guid transactionIdentifier, OletxTransaction dtx) { Transaction? tx = null; Hashtable promotedTransactionTable = PromotedTransactionTable; @@ -511,9 +560,10 @@ internal static Transaction FindOrCreatePromotedTransaction(Guid transactionIden LazyInitializer.EnsureInitialized(ref s_transactionTable, ref s_classSyncObject, () => new TransactionTable()); // Fault in a DistributedTransactionManager if one has not already been created. - internal static DistributedTransactionManager? distributedTransactionManager; - internal static DistributedTransactionManager DistributedTransactionManager => + internal static OletxTransactionManager? distributedTransactionManager; + internal static OletxTransactionManager DistributedTransactionManager => // If the distributed transaction manager is not configured, throw an exception - LazyInitializer.EnsureInitialized(ref distributedTransactionManager, ref s_classSyncObject, () => new DistributedTransactionManager()); + LazyInitializer.EnsureInitialized(ref distributedTransactionManager, ref s_classSyncObject, + () => new OletxTransactionManager(DefaultSettingsSection.DistributedTransactionManagerName)); } } diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionScope.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionScope.cs index 938a4c0735beb5..0ca05d76d4bb7b 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionScope.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionScope.cs @@ -854,7 +854,7 @@ private static void TimerCallback(object? state) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.TransactionScopeInternalError("TransactionScopeTimerObjectInvalid"); + etwLog.InternalError("TransactionScopeTimerObjectInvalid"); } throw TransactionException.Create(TraceSourceType.TraceSourceBase, SR.InternalError + SR.TransactionScopeTimerObjectInvalid, null); diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionState.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionState.cs index f6a99cb47d940b..331e679db04ea1 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionState.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionState.cs @@ -5,7 +5,7 @@ using System.Diagnostics; using System.Runtime.Serialization; using System.Threading; -using System.Transactions.Distributed; +using System.Transactions.Oletx; namespace System.Transactions { @@ -1532,7 +1532,7 @@ internal override void EnterState(InternalTransaction tx) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.TransactionCommitted(tx.TransactionTraceId); + etwLog.TransactionCommitted(TraceSourceType.TraceSourceLtm, tx.TransactionTraceId); } // Fire Completion for anyone listening @@ -1595,7 +1595,7 @@ internal override void EnterState(InternalTransaction tx) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.TransactionInDoubt(tx.TransactionTraceId); + etwLog.TransactionInDoubt(TraceSourceType.TraceSourceLtm, tx.TransactionTraceId); } // Fire Completion for anyone listening @@ -1676,7 +1676,7 @@ Transaction atomicTransaction EnlistmentState.EnlistmentStatePromoted.EnterState(en.InternalEnlistment); en.InternalEnlistment.PromotedEnlistment = - DistributedTransaction.EnlistVolatile( + tx.PromotedTransaction.EnlistVolatile( en.InternalEnlistment, enlistmentOptions); return en; } @@ -1705,7 +1705,7 @@ Transaction atomicTransaction EnlistmentState.EnlistmentStatePromoted.EnterState(en.InternalEnlistment); en.InternalEnlistment.PromotedEnlistment = - DistributedTransaction.EnlistVolatile( + tx.PromotedTransaction.EnlistVolatile( en.InternalEnlistment, enlistmentOptions); return en; } @@ -1742,7 +1742,7 @@ Transaction atomicTransaction EnlistmentState.EnlistmentStatePromoted.EnterState(en.InternalEnlistment); en.InternalEnlistment.PromotedEnlistment = - DistributedTransaction.EnlistDurable( + tx.PromotedTransaction.EnlistDurable( resourceManagerIdentifier, (DurableInternalEnlistment)en.InternalEnlistment, false, @@ -1783,7 +1783,7 @@ Transaction atomicTransaction EnlistmentState.EnlistmentStatePromoted.EnterState(en.InternalEnlistment); en.InternalEnlistment.PromotedEnlistment = - DistributedTransaction.EnlistDurable( + tx.PromotedTransaction.EnlistDurable( resourceManagerIdentifier, (DurableInternalEnlistment)en.InternalEnlistment, true, @@ -1809,7 +1809,7 @@ internal override void Rollback(InternalTransaction tx, Exception? e) Monitor.Exit(tx); try { - DistributedTransaction.Rollback(); + tx.PromotedTransaction.Rollback(); } finally { @@ -1894,16 +1894,17 @@ internal override void CompleteBlockingClone(InternalTransaction tx) Debug.Assert(tx._phase0WaveDependentCloneCount >= 0); if (tx._phase0WaveDependentCloneCount == 0) { + OletxDependentTransaction dtx = tx._phase0WaveDependentClone!; tx._phase0WaveDependentClone = null; Monitor.Exit(tx); try { - DistributedDependentTransaction.Complete(); + dtx.Complete(); } finally { - Monitor.Enter(tx); + dtx.Dispose(); } } } @@ -1930,22 +1931,22 @@ internal override void CompleteAbortingClone(InternalTransaction tx) { // We need to complete our dependent clone on the promoted transaction and null it out // so if we get a new one, a new one will be created on the promoted transaction. + OletxDependentTransaction dtx = tx._abortingDependentClone!; tx._abortingDependentClone = null; Monitor.Exit(tx); try { - DistributedDependentTransaction.Complete(); + dtx.Complete(); } finally { - Monitor.Enter(tx); + dtx.Dispose(); } } } } - internal override void CreateBlockingClone(InternalTransaction tx) { // Once the transaction is promoted leverage the distributed @@ -1954,7 +1955,7 @@ internal override void CreateBlockingClone(InternalTransaction tx) if (tx._phase0WaveDependentClone == null) { Debug.Assert(tx.PromotedTransaction != null); - tx._phase0WaveDependentClone = DistributedTransaction.DependentClone(true); + tx._phase0WaveDependentClone = tx.PromotedTransaction.DependentClone(true); } tx._phase0WaveDependentCloneCount++; @@ -1976,7 +1977,7 @@ internal override void CreateAbortingClone(InternalTransaction tx) if (null == tx._abortingDependentClone) { Debug.Assert(tx.PromotedTransaction != null); - tx._abortingDependentClone = DistributedTransaction.DependentClone(false); + tx._abortingDependentClone = tx.PromotedTransaction.DependentClone(false); } tx._abortingDependentCloneCount++; } @@ -2050,7 +2051,7 @@ internal override void Timeout(InternalTransaction tx) { tx._innerException ??= new TimeoutException(SR.TraceTransactionTimeout); Debug.Assert(tx.PromotedTransaction != null); - DistributedTransaction.Rollback(); + tx.PromotedTransaction.Rollback(); TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) @@ -2143,7 +2144,7 @@ internal override void EnterState(InternalTransaction tx) CommonEnterState(tx); // Create a transaction with the distributed transaction manager - DistributedCommittableTransaction? distributedTx = null; + OletxCommittableTransaction? distributedTx = null; try { TimeSpan newTimeout; @@ -2169,7 +2170,7 @@ internal override void EnterState(InternalTransaction tx) // Create a new distributed transaction. distributedTx = - DistributedTransactionManager.CreateTransaction(options); + TransactionManager.DistributedTransactionManager.CreateTransaction(options); distributedTx.SavedLtmPromotedTransaction = tx._outcomeSource; TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; @@ -2240,7 +2241,7 @@ protected static bool PromotePhaseVolatiles( } Debug.Assert(tx.PromotedTransaction != null); - volatiles.VolatileDemux._promotedEnlistment = DistributedTransaction.EnlistVolatile(volatiles.VolatileDemux, + volatiles.VolatileDemux._promotedEnlistment = tx.PromotedTransaction.EnlistVolatile(volatiles.VolatileDemux, phase0 ? EnlistmentOptions.EnlistDuringPrepareRequired : EnlistmentOptions.None); } @@ -2256,7 +2257,7 @@ internal virtual bool PromoteDurable(InternalTransaction tx) // Directly enlist the durable enlistment with the resource manager. InternalEnlistment enlistment = tx._durableEnlistment; Debug.Assert(tx.PromotedTransaction != null); - IPromotedEnlistment promotedEnlistment = DistributedTransaction.EnlistDurable( + IPromotedEnlistment promotedEnlistment = tx.PromotedTransaction.EnlistDurable( enlistment.ResourceManagerIdentifier, (DurableInternalEnlistment)enlistment, enlistment.SinglePhaseNotification != null, @@ -2306,7 +2307,7 @@ internal virtual void PromoteEnlistmentsAndOutcome(InternalTransaction tx) { if (!enlistmentsPromoted) { - DistributedTransaction.Rollback(); + tx.PromotedTransaction.Rollback(); // Now abort this transaction. tx.State.ChangeStateAbortedDuringPromotion(tx); @@ -2335,7 +2336,7 @@ internal virtual void PromoteEnlistmentsAndOutcome(InternalTransaction tx) { if (!enlistmentsPromoted) { - DistributedTransaction.Rollback(); + tx.PromotedTransaction.Rollback(); // Now abort this transaction. tx.State.ChangeStateAbortedDuringPromotion(tx); @@ -2365,7 +2366,7 @@ internal virtual void PromoteEnlistmentsAndOutcome(InternalTransaction tx) { if (!enlistmentsPromoted) { - DistributedTransaction.Rollback(); + tx.PromotedTransaction.Rollback(); // Now abort this transaction. tx.State.ChangeStateAbortedDuringPromotion(tx); @@ -2450,7 +2451,8 @@ internal override void EnterState(InternalTransaction tx) CommonEnterState(tx); // Use the asynchronous commit provided by the promoted transaction - DistributedCommittableTransaction.BeginCommit(tx); + OletxCommittableTransaction ctx = (OletxCommittableTransaction)tx.PromotedTransaction!; + ctx.BeginCommit(tx); } @@ -2762,7 +2764,7 @@ internal override void EnterState(InternalTransaction tx) { Debug.Assert(tx.PromotedTransaction != null); // Otherwise make sure that the transaction rolls back. - DistributedTransaction.Rollback(); + tx.PromotedTransaction.Rollback(); } } @@ -2932,7 +2934,7 @@ internal override void EnterState(InternalTransaction tx) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.TransactionAborted(tx.TransactionTraceId); + etwLog.TransactionAborted(TraceSourceType.TraceSourceLtm, tx.TransactionTraceId); } } @@ -3076,7 +3078,7 @@ internal override void EnterState(InternalTransaction tx) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.TransactionCommitted(tx.TransactionTraceId); + etwLog.TransactionCommitted(TraceSourceType.TraceSourceLtm, tx.TransactionTraceId); } } @@ -3146,7 +3148,7 @@ internal override void EnterState(InternalTransaction tx) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.TransactionInDoubt(tx.TransactionTraceId); + etwLog.TransactionInDoubt(TraceSourceType.TraceSourceLtm, tx.TransactionTraceId); } } @@ -3247,7 +3249,7 @@ internal override void EnterState(InternalTransaction tx) CommonEnterState(tx); // Create a transaction with the distributed transaction manager - DistributedTransaction? distributedTx = null; + Oletx.OletxTransaction? distributedTx = null; try { // Ask the delegation interface to promote the transaction. @@ -3256,7 +3258,7 @@ internal override void EnterState(InternalTransaction tx) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.EnlistmentStatus(tx._durableEnlistment, NotificationCall.Promote); + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, tx._durableEnlistment.EnlistmentTraceId, NotificationCall.Promote); } } @@ -3834,7 +3836,7 @@ internal override void EnterState(InternalTransaction tx) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.EnlistmentStatus(tx._durableEnlistment, NotificationCall.SinglePhaseCommit); + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, tx._durableEnlistment.EnlistmentTraceId, NotificationCall.SinglePhaseCommit); } // We are about to tell the PSPE to do the SinglePhaseCommit. It is too late for us to timeout the transaction. @@ -4036,7 +4038,7 @@ internal override void EnterState(InternalTransaction tx) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.TransactionAborted(tx.TransactionTraceId); + etwLog.TransactionAborted(TraceSourceType.TraceSourceLtm, tx.TransactionTraceId); } } @@ -4129,7 +4131,7 @@ internal override void EnterState(InternalTransaction tx) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.TransactionCommitted(tx.TransactionTraceId); + etwLog.TransactionCommitted(TraceSourceType.TraceSourceLtm, tx.TransactionTraceId); } } @@ -4173,7 +4175,7 @@ internal override void EnterState(InternalTransaction tx) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.TransactionInDoubt(tx.TransactionTraceId); + etwLog.TransactionInDoubt(TraceSourceType.TraceSourceLtm, tx.TransactionTraceId); } } @@ -4233,7 +4235,7 @@ internal override void EnterState(InternalTransaction tx) CommonEnterState(tx); // We are never going to have an DistributedTransaction for this one. - DistributedTransaction? distributedTx; + OletxTransaction? distributedTx; try { // Ask the delegation interface to promote the transaction. @@ -4242,7 +4244,7 @@ internal override void EnterState(InternalTransaction tx) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.EnlistmentStatus(tx._durableEnlistment, NotificationCall.Promote); + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, tx._durableEnlistment.EnlistmentTraceId, NotificationCall.Promote); } } @@ -4291,7 +4293,7 @@ internal override void Rollback(InternalTransaction tx, Exception? e) tx._innerException ??= e; Debug.Assert(tx.PromotedTransaction != null); - DistributedTransaction.Rollback(); + tx.PromotedTransaction.Rollback(); TransactionStatePromotedAborted.EnterState(tx); } @@ -4379,7 +4381,7 @@ internal void Phase0PSPEInitialize( } } - internal DistributedTransaction? PSPEPromote(InternalTransaction tx) + internal Oletx.OletxTransaction? PSPEPromote(InternalTransaction tx) { bool changeToReturnState = true; @@ -4390,7 +4392,7 @@ internal void Phase0PSPEInitialize( "PSPEPromote called from state other than TransactionStateDelegated[NonMSDTC]"); CommonEnterState(tx); - DistributedTransaction? distributedTx = null; + Oletx.OletxTransaction? distributedTx = null; try { if (tx._attemptingPSPEPromote) @@ -4457,7 +4459,7 @@ internal void Phase0PSPEInitialize( { try { - distributedTx = TransactionInterop.GetDistributedTransactionFromTransmitterPropagationToken( + distributedTx = TransactionInterop.GetOletxTransactionFromTransmitterPropagationToken( propagationToken! ); } @@ -4603,13 +4605,12 @@ internal override void EnterState(InternalTransaction tx) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.EnlistmentStatus(tx._durableEnlistment, NotificationCall.SinglePhaseCommit); + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, tx._durableEnlistment.EnlistmentTraceId, NotificationCall.SinglePhaseCommit); } try { - tx._durableEnlistment.PromotableSinglePhaseNotification.SinglePhaseCommit( - tx._durableEnlistment.SinglePhaseEnlistment); + tx._durableEnlistment.PromotableSinglePhaseNotification.SinglePhaseCommit(tx._durableEnlistment.SinglePhaseEnlistment); } finally { @@ -4640,7 +4641,7 @@ internal override void EnterState(InternalTransaction tx) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.EnlistmentStatus(tx._durableEnlistment, NotificationCall.Rollback); + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, tx._durableEnlistment.EnlistmentTraceId, NotificationCall.Rollback); } tx._durableEnlistment.PromotableSinglePhaseNotification.Rollback( diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionsEtwProvider.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionsEtwProvider.cs index f6fd5f6b87e2d7..ec230efce89728 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionsEtwProvider.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionsEtwProvider.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Collections; +using System.Transactions.Oletx; namespace System.Transactions { @@ -34,6 +35,16 @@ internal enum NotificationCall Promote = 5 } + internal enum EnlistmentCallback + { + Done = 0, + Prepared = 1, + ForceRollback = 2, + Committed = 3, + Aborted = 4, + InDoubt = 5 + } + internal enum TransactionScopeResult { CreatedTransaction = 0, @@ -57,7 +68,7 @@ internal enum TraceSourceType { TraceSourceBase = 0, TraceSourceLtm = 1, - TraceSourceDistributed = 2 + TraceSourceOleTx = 2 } /// Provides an event source for tracing Transactions information. [EventSource( @@ -94,7 +105,7 @@ private TransactionsEtwProvider() { } /// The event ID for the enlistment done event. private const int ENLISTMENT_DONE_EVENTID = 4; /// The event ID for the enlistment status. - private const int ENLISTMENT_EVENTID = 5; + private const int ENLISTMENT_LTM_EVENTID = 5; /// The event ID for the enlistment forcerollback event. private const int ENLISTMENT_FORCEROLLBACK_EVENTID = 6; /// The event ID for the enlistment indoubt event. @@ -114,33 +125,33 @@ private TransactionsEtwProvider() { } /// The event ID for method exit event. private const int METHOD_EXIT_BASE_EVENTID = 14; /// The event ID for method enter event. - private const int METHOD_ENTER_DISTRIBUTED_EVENTID = 15; + private const int METHOD_ENTER_OLETX_EVENTID = 15; /// The event ID for method exit event. - private const int METHOD_EXIT_DISTRIBUTED_EVENTID = 16; + private const int METHOD_EXIT_OLETX_EVENTID = 16; /// The event ID for transaction aborted event. - private const int TRANSACTION_ABORTED_EVENTID = 17; + private const int TRANSACTION_ABORTED_LTM_EVENTID = 17; /// The event ID for the transaction clone create event. private const int TRANSACTION_CLONECREATE_EVENTID = 18; /// The event ID for the transaction commit event. - private const int TRANSACTION_COMMIT_EVENTID = 19; + private const int TRANSACTION_COMMIT_LTM_EVENTID = 19; /// The event ID for transaction committed event. - private const int TRANSACTION_COMMITTED_EVENTID = 20; + private const int TRANSACTION_COMMITTED_LTM_EVENTID = 20; /// The event ID for when we encounter a new Transactions object that hasn't had its name traced to the trace file. - private const int TRANSACTION_CREATED_EVENTID = 21; + private const int TRANSACTION_CREATED_LTM_EVENTID = 21; /// The event ID for the transaction dependent clone complete event. - private const int TRANSACTION_DEPENDENT_CLONE_COMPLETE_EVENTID = 22; + private const int TRANSACTION_DEPENDENT_CLONE_COMPLETE_LTM_EVENTID = 22; /// The event ID for the transaction exception event. private const int TRANSACTION_EXCEPTION_LTM_EVENTID = 23; /// The event ID for the transaction exception event. private const int TRANSACTION_EXCEPTION_BASE_EVENTID = 24; /// The event ID for transaction indoubt event. - private const int TRANSACTION_INDOUBT_EVENTID = 25; + private const int TRANSACTION_INDOUBT_LTM_EVENTID = 25; /// The event ID for the transaction invalid operation event. private const int TRANSACTION_INVALID_OPERATION_EVENTID = 26; /// The event ID for transaction promoted event. private const int TRANSACTION_PROMOTED_EVENTID = 27; /// The event ID for the transaction rollback event. - private const int TRANSACTION_ROLLBACK_EVENTID = 28; + private const int TRANSACTION_ROLLBACK_LTM_EVENTID = 28; /// The event ID for the transaction serialized event. private const int TRANSACTION_SERIALIZED_EVENTID = 29; /// The event ID for transaction timeout event. @@ -158,7 +169,7 @@ private TransactionsEtwProvider() { } /// The event ID for transactionscope incomplete event. private const int TRANSACTIONSCOPE_INCOMPLETE_EVENTID = 36; /// The event ID for transactionscope internal error event. - private const int TRANSACTIONSCOPE_INTERNAL_ERROR_EVENTID = 37; + private const int INTERNAL_ERROR_EVENTID = 37; /// The event ID for transactionscope nested incorrectly event. private const int TRANSACTIONSCOPE_NESTED_INCORRECTLY_EVENTID = 38; /// The event ID for transactionscope timeout event. @@ -166,6 +177,43 @@ private TransactionsEtwProvider() { } /// The event ID for enlistment event. private const int TRANSACTIONSTATE_ENLIST_EVENTID = 40; + /// The event ID for the transaction commit event. + private const int TRANSACTION_COMMIT_OLETX_EVENTID = 41; + /// The event ID for the transaction rollback event. + private const int TRANSACTION_ROLLBACK_OLETX_EVENTID = 42; + /// The event ID for exception consumed event. + private const int EXCEPTION_CONSUMED_OLETX_EVENTID = 43; + /// The event ID for transaction committed event. + private const int TRANSACTION_COMMITTED_OLETX_EVENTID = 44; + /// The event ID for transaction aborted event. + private const int TRANSACTION_ABORTED_OLETX_EVENTID = 45; + /// The event ID for transaction indoubt event. + private const int TRANSACTION_INDOUBT_OLETX_EVENTID = 46; + /// The event ID for the transaction dependent clone complete event. + private const int TRANSACTION_DEPENDENT_CLONE_COMPLETE_OLETX_EVENTID = 47; + /// The event ID for the transaction dependent clone complete event. + private const int TRANSACTION_DEPENDENT_CLONE_CREATE_LTM_EVENTID = 48; + /// The event ID for the transaction dependent clone complete event. + private const int TRANSACTION_DEPENDENT_CLONE_CREATE_OLETX_EVENTID = 49; + /// The event ID for the transaction deserialized event. + private const int TRANSACTION_DESERIALIZED_EVENTID = 50; + /// The event ID for when we encounter a new Transactions object that hasn't had its name traced to the trace file. + private const int TRANSACTION_CREATED_OLETX_EVENTID = 11; + + /// The event ID for the enlistment status. + private const int ENLISTMENT_OLETX_EVENTID = 52; + /// The event ID for the enlistment callback positive event. + private const int ENLISTMENT_CALLBACK_POSITIVE_EVENTID = 53; + /// The event ID for the enlistment callback positive event. + private const int ENLISTMENT_CALLBACK_NEGATIVE_EVENTID = 54; + /// The event ID for when we create an enlistment. + private const int ENLISTMENT_CREATED_LTM_EVENTID = 55; + /// The event ID for when we create an enlistment. + private const int ENLISTMENT_CREATED_OLETX_EVENTID = 56; + + /// The event ID for transactionmanager reenlist event. + private const int TRANSACTIONMANAGER_CREATE_OLETX_EVENTID = 57; + //----------------------------------------------------------------------------------- // // Transactions Events @@ -183,28 +231,34 @@ private TransactionsEtwProvider() { } public static int GetHashCode(object? value) => value?.GetHashCode() ?? 0; #region Transaction Creation - /// Trace an event when a new transaction is created. - /// The transaction that was created. - /// The type of transaction.Method [NonEvent] - internal void TransactionCreated(Transaction transaction, string? type) + internal void TransactionCreated(TraceSourceType traceSource, TransactionTraceIdentifier txTraceId, string? type) { - Debug.Assert(transaction != null, "Transaction needed for the ETW event."); - if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS)) { - if (transaction != null && transaction.TransactionTraceId.TransactionIdentifier != null) - TransactionCreated(transaction.TransactionTraceId.TransactionIdentifier, type); - else - TransactionCreated(string.Empty, type); + if (traceSource == TraceSourceType.TraceSourceLtm) + { + TransactionCreatedLtm(txTraceId.TransactionIdentifier, type); + } + else if (traceSource == TraceSourceType.TraceSourceOleTx) + { + TransactionCreatedOleTx(txTraceId.TransactionIdentifier, type); + } } } - [Event(TRANSACTION_CREATED_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Informational, Task = Tasks.Transaction, Opcode = Opcodes.Create, Message = "Transaction Created. ID is {0}, type is {1}")] - private void TransactionCreated(string transactionIdentifier, string? type) + [Event(TRANSACTION_CREATED_LTM_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Informational, Task = Tasks.Transaction, Opcode = Opcodes.Create, Message = "Transaction Created (LTM). ID is {0}, type is {1}")] + private void TransactionCreatedLtm(string transactionIdentifier, string? type) + { + SetActivityId(transactionIdentifier); + WriteEvent(TRANSACTION_CREATED_LTM_EVENTID, transactionIdentifier, type); + } + + [Event(TRANSACTION_CREATED_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Informational, Task = Tasks.Transaction, Opcode = Opcodes.Create, Message = "Transaction Created (OLETX). ID is {0}, type is {1}")] + private void TransactionCreatedOleTx(string transactionIdentifier, string? type) { SetActivityId(transactionIdentifier); - WriteEvent(TRANSACTION_CREATED_EVENTID, transactionIdentifier, type); + WriteEvent(TRANSACTION_CREATED_OLETX_EVENTID, transactionIdentifier, type); } #endregion @@ -235,28 +289,38 @@ private void TransactionCloneCreate(string transactionIdentifier, string type) #endregion #region Transaction Serialized - /// Trace an event when a transaction is serialized. - /// The transaction that was serialized. - /// The type of transaction. [NonEvent] - internal void TransactionSerialized(Transaction transaction, string type) + internal void TransactionSerialized(TransactionTraceIdentifier transactionTraceId) { - Debug.Assert(transaction != null, "Transaction needed for the ETW event."); - if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS)) { - if (transaction != null && transaction.TransactionTraceId.TransactionIdentifier != null) - TransactionSerialized(transaction.TransactionTraceId.TransactionIdentifier, type); - else - TransactionSerialized(string.Empty, type); + TransactionSerialized(transactionTraceId.TransactionIdentifier); + } + } + + [Event(TRANSACTION_SERIALIZED_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Informational, Task = Tasks.Transaction, Opcode = Opcodes.Serialized, Message = "Transaction Serialized. ID is {0}")] + private void TransactionSerialized(string transactionIdentifier) + { + SetActivityId(transactionIdentifier); + WriteEvent(TRANSACTION_SERIALIZED_EVENTID, transactionIdentifier); + } + #endregion + + #region Transaction Deserialized + [NonEvent] + internal void TransactionDeserialized(TransactionTraceIdentifier transactionTraceId) + { + if (IsEnabled(EventLevel.Verbose, ALL_KEYWORDS)) + { + TransactionDeserialized(transactionTraceId.TransactionIdentifier); } } - [Event(TRANSACTION_SERIALIZED_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Informational, Task = Tasks.Transaction, Opcode = Opcodes.Serialized, Message = "Transaction Serialized. ID is {0}, type is {1}")] - private void TransactionSerialized(string transactionIdentifier, string type) + [Event(TRANSACTION_DESERIALIZED_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Verbose, Task = Tasks.Transaction, Opcode = Opcodes.Serialized, Message = "Transaction Deserialized. ID is {0}")] + private void TransactionDeserialized(string transactionIdentifier) { SetActivityId(transactionIdentifier); - WriteEvent(TRANSACTION_SERIALIZED_EVENTID, transactionIdentifier, type); + WriteEvent(TRANSACTION_DESERIALIZED_EVENTID, transactionIdentifier); } #endregion @@ -332,106 +396,196 @@ private void TransactionInvalidOperation(string? transactionIdentifier, string? #endregion #region Transaction Rollback - /// Trace an event when rollback on a transaction. - /// The transaction to rollback. - /// The type of transaction. [NonEvent] - internal void TransactionRollback(Transaction transaction, string? type) + internal void TransactionRollback(TraceSourceType traceSource, TransactionTraceIdentifier txTraceId, string? type) { - Debug.Assert(transaction != null, "Transaction needed for the ETW event."); - if (IsEnabled(EventLevel.Warning, ALL_KEYWORDS)) { - if (transaction != null && transaction.TransactionTraceId.TransactionIdentifier != null) - TransactionRollback(transaction.TransactionTraceId.TransactionIdentifier, type); - else - TransactionRollback(string.Empty, type); + if (traceSource == TraceSourceType.TraceSourceLtm) + { + TransactionRollbackLtm(txTraceId.TransactionIdentifier, type); + } + else if (traceSource == TraceSourceType.TraceSourceOleTx) + { + TransactionRollbackOleTx(txTraceId.TransactionIdentifier, type); + } } } - [Event(TRANSACTION_ROLLBACK_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Warning, Task = Tasks.Transaction, Opcode = Opcodes.Rollback, Message = "Transaction Rollback. ID is {0}, type is {1}")] - private void TransactionRollback(string transactionIdentifier, string? type) + [Event(TRANSACTION_ROLLBACK_LTM_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Warning, Task = Tasks.Transaction, Opcode = Opcodes.Rollback, Message = "Transaction LTM Rollback. ID is {0}, type is {1}")] + private void TransactionRollbackLtm(string transactionIdentifier, string? type) { SetActivityId(transactionIdentifier); - WriteEvent(TRANSACTION_ROLLBACK_EVENTID, transactionIdentifier, type); + WriteEvent(TRANSACTION_ROLLBACK_LTM_EVENTID, transactionIdentifier, type); + } + + [Event(TRANSACTION_ROLLBACK_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Warning, Task = Tasks.Transaction, Opcode = Opcodes.Rollback, Message = "Transaction OleTx Rollback. ID is {0}, type is {1}")] + private void TransactionRollbackOleTx(string transactionIdentifier, string? type) + { + SetActivityId(transactionIdentifier); + WriteEvent(TRANSACTION_ROLLBACK_OLETX_EVENTID, transactionIdentifier, type); } #endregion - #region Transaction Dependent Clone Complete - /// Trace an event when transaction dependent clone complete. - /// The transaction that do dependent clone. - /// The type of transaction. + #region Transaction Dependent Clone Create [NonEvent] - internal void TransactionDependentCloneComplete(Transaction transaction, string? type) + internal void TransactionDependentCloneCreate(TraceSourceType traceSource, TransactionTraceIdentifier txTraceId, DependentCloneOption option) { - Debug.Assert(transaction != null, "Transaction needed for the ETW event."); + if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS)) + { + if (traceSource == TraceSourceType.TraceSourceLtm) + { + TransactionDependentCloneCreateLtm(txTraceId.TransactionIdentifier, option.ToString()); + } + else if (traceSource == TraceSourceType.TraceSourceOleTx) + { + TransactionDependentCloneCreateOleTx(txTraceId.TransactionIdentifier, option.ToString()); + } + } + } + + [Event(TRANSACTION_DEPENDENT_CLONE_CREATE_LTM_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Informational, Task = Tasks.Transaction, Opcode = Opcodes.DependentCloneComplete, Message = "Transaction Dependent Clone Created (LTM). ID is {0}, option is {1}")] + private void TransactionDependentCloneCreateLtm(string transactionIdentifier, string? option) + { + SetActivityId(transactionIdentifier); + WriteEvent(TRANSACTION_DEPENDENT_CLONE_CREATE_LTM_EVENTID, transactionIdentifier, option); + } + + [Event(TRANSACTION_DEPENDENT_CLONE_CREATE_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Informational, Task = Tasks.Transaction, Opcode = Opcodes.DependentCloneComplete, Message = "Transaction Dependent Clone Created (OLETX). ID is {0}, option is {1}")] + private void TransactionDependentCloneCreateOleTx(string transactionIdentifier, string? option) + { + SetActivityId(transactionIdentifier); + WriteEvent(TRANSACTION_DEPENDENT_CLONE_CREATE_OLETX_EVENTID, transactionIdentifier, option); + } + #endregion + #region Transaction Dependent Clone Complete + [NonEvent] + internal void TransactionDependentCloneComplete(TraceSourceType traceSource, TransactionTraceIdentifier txTraceId, string? type) + { if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS)) { - if (transaction != null && transaction.TransactionTraceId.TransactionIdentifier != null) - TransactionDependentCloneComplete(transaction.TransactionTraceId.TransactionIdentifier, type); - else - TransactionDependentCloneComplete(string.Empty, type); + if (traceSource == TraceSourceType.TraceSourceLtm) + { + TransactionDependentCloneCompleteLtm(txTraceId.TransactionIdentifier, type); + } + else if (traceSource == TraceSourceType.TraceSourceOleTx) + { + TransactionDependentCloneCompleteOleTx(txTraceId.TransactionIdentifier, type); + } } } - [Event(TRANSACTION_DEPENDENT_CLONE_COMPLETE_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Informational, Task = Tasks.Transaction, Opcode = Opcodes.DependentCloneComplete, Message = "Transaction Dependent Clone Completed. ID is {0}, type is {1}")] - private void TransactionDependentCloneComplete(string transactionIdentifier, string? type) + [Event(TRANSACTION_DEPENDENT_CLONE_COMPLETE_LTM_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Informational, Task = Tasks.Transaction, Opcode = Opcodes.DependentCloneComplete, Message = "Transaction Dependent Clone Completed (LTM). ID is {0}, type is {1}")] + private void TransactionDependentCloneCompleteLtm(string transactionIdentifier, string? type) + { + SetActivityId(transactionIdentifier); + WriteEvent(TRANSACTION_DEPENDENT_CLONE_COMPLETE_LTM_EVENTID, transactionIdentifier, type); + } + + [Event(TRANSACTION_DEPENDENT_CLONE_COMPLETE_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Informational, Task = Tasks.Transaction, Opcode = Opcodes.DependentCloneComplete, Message = "Transaction Dependent Clone Completed (OLETX). ID is {0}, type is {1}")] + private void TransactionDependentCloneCompleteOleTx(string transactionIdentifier, string? type) { SetActivityId(transactionIdentifier); - WriteEvent(TRANSACTION_DEPENDENT_CLONE_COMPLETE_EVENTID, transactionIdentifier, type); + WriteEvent(TRANSACTION_DEPENDENT_CLONE_COMPLETE_OLETX_EVENTID, transactionIdentifier, type); } #endregion #region Transaction Commit - /// Trace an event when there is commit on that transaction. - /// The transaction to commit. - /// The type of transaction. [NonEvent] - internal void TransactionCommit(Transaction transaction, string? type) + internal void TransactionCommit(TraceSourceType traceSource, TransactionTraceIdentifier txTraceId, string? type) { - Debug.Assert(transaction != null, "Transaction needed for the ETW event."); - if (IsEnabled(EventLevel.Verbose, ALL_KEYWORDS)) { - if (transaction != null && transaction.TransactionTraceId.TransactionIdentifier != null) - TransactionCommit(transaction.TransactionTraceId.TransactionIdentifier, type); - else - TransactionCommit(string.Empty, type); + if (traceSource == TraceSourceType.TraceSourceLtm) + { + TransactionCommitLtm(txTraceId.TransactionIdentifier, type); + } + else if (traceSource == TraceSourceType.TraceSourceOleTx) + { + TransactionCommitOleTx(txTraceId.TransactionIdentifier, type); + } } } - [Event(TRANSACTION_COMMIT_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Verbose, Task = Tasks.Transaction, Opcode = Opcodes.Commit, Message = "Transaction Commit: ID is {0}, type is {1}")] - private void TransactionCommit(string transactionIdentifier, string? type) + [Event(TRANSACTION_COMMIT_LTM_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Verbose, Task = Tasks.Transaction, Opcode = Opcodes.Commit, Message = "Transaction LTM Commit: ID is {0}, type is {1}")] + private void TransactionCommitLtm(string transactionIdentifier, string? type) + { + SetActivityId(transactionIdentifier); + WriteEvent(TRANSACTION_COMMIT_LTM_EVENTID, transactionIdentifier, type); + } + + [Event(TRANSACTION_COMMIT_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Verbose, Task = Tasks.Transaction, Opcode = Opcodes.Commit, Message = "Transaction OleTx Commit: ID is {0}, type is {1}")] + private void TransactionCommitOleTx(string transactionIdentifier, string? type) { SetActivityId(transactionIdentifier); - WriteEvent(TRANSACTION_COMMIT_EVENTID, transactionIdentifier, type); + WriteEvent(TRANSACTION_COMMIT_OLETX_EVENTID, transactionIdentifier, type); } #endregion #region Enlistment - /// Trace an event for enlistment status. - /// The enlistment to report status. - /// The notification call on the enlistment. [NonEvent] - internal void EnlistmentStatus(InternalEnlistment enlistment, NotificationCall notificationCall) + internal void EnlistmentStatus(TraceSourceType traceSource, EnlistmentTraceIdentifier enlistmentTraceId, NotificationCall notificationCall) { - Debug.Assert(enlistment != null, "Enlistment needed for the ETW event."); - if (IsEnabled(EventLevel.Verbose, ALL_KEYWORDS)) { - if (enlistment != null && enlistment.EnlistmentTraceId.EnlistmentIdentifier != 0) - EnlistmentStatus(enlistment.EnlistmentTraceId.EnlistmentIdentifier, notificationCall.ToString()); - else - EnlistmentStatus(0, notificationCall.ToString()); + if (traceSource == TraceSourceType.TraceSourceLtm) + { + EnlistmentStatusLtm(enlistmentTraceId.EnlistmentIdentifier, notificationCall.ToString()); + } + else if (traceSource == TraceSourceType.TraceSourceOleTx) + { + EnlistmentStatusOleTx(enlistmentTraceId.EnlistmentIdentifier, notificationCall.ToString()); + } + } + } + + [Event(ENLISTMENT_LTM_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Verbose, Task = Tasks.Enlistment, Message = "Enlistment status (LTM): ID is {0}, notificationcall is {1}")] + private void EnlistmentStatusLtm(int enlistmentIdentifier, string notificationCall) + { + SetActivityId(string.Empty); + WriteEvent(ENLISTMENT_LTM_EVENTID, enlistmentIdentifier, notificationCall); + } + + [Event(ENLISTMENT_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Verbose, Task = Tasks.Enlistment, Message = "Enlistment status (OLETX): ID is {0}, notificationcall is {1}")] + private void EnlistmentStatusOleTx(int enlistmentIdentifier, string notificationCall) + { + SetActivityId(string.Empty); + WriteEvent(ENLISTMENT_OLETX_EVENTID, enlistmentIdentifier, notificationCall); + } + #endregion + + #region Enlistment Creation + [NonEvent] + internal void EnlistmentCreated(TraceSourceType traceSource, EnlistmentTraceIdentifier enlistmentTraceId, EnlistmentType enlistmentType, EnlistmentOptions enlistmentOptions) + { + if (IsEnabled(EventLevel.Informational, ALL_KEYWORDS)) + { + if (traceSource == TraceSourceType.TraceSourceLtm) + { + EnlistmentCreatedLtm(enlistmentTraceId.EnlistmentIdentifier, enlistmentType.ToString(), enlistmentOptions.ToString()); + } + else if (traceSource == TraceSourceType.TraceSourceOleTx) + { + EnlistmentCreatedOleTx(enlistmentTraceId.EnlistmentIdentifier, enlistmentType.ToString(), enlistmentOptions.ToString()); + } } } - [Event(ENLISTMENT_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Verbose, Task = Tasks.Enlistment, Message = "Enlistment status: ID is {0}, notificationcall is {1}")] - private void EnlistmentStatus(int enlistmentIdentifier, string notificationCall) + [Event(ENLISTMENT_CREATED_LTM_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Informational, Task = Tasks.Enlistment, Opcode = Opcodes.Create, Message = "Enlistment Created (LTM). ID is {0}, type is {1}, options is {2}")] + [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026", Justification = "Only string/int are passed")] + private void EnlistmentCreatedLtm(int enlistmentIdentifier, string enlistmentType, string enlistmentOptions) + { + SetActivityId(string.Empty); + WriteEvent(ENLISTMENT_CREATED_LTM_EVENTID, enlistmentIdentifier, enlistmentType, enlistmentOptions); + } + + [Event(ENLISTMENT_CREATED_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Informational, Task = Tasks.Enlistment, Opcode = Opcodes.Create, Message = "Enlistment Created (OLETX). ID is {0}, type is {1}, options is {2}")] + [UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026", Justification = "Only string/int are passed")] + private void EnlistmentCreatedOleTx(int enlistmentIdentifier, string enlistmentType, string enlistmentOptions) { SetActivityId(string.Empty); - WriteEvent(ENLISTMENT_EVENTID, enlistmentIdentifier, notificationCall); + WriteEvent(ENLISTMENT_CREATED_OLETX_EVENTID, enlistmentIdentifier, enlistmentType, enlistmentOptions); } #endregion @@ -586,6 +740,42 @@ private void EnlistmentInDoubt(int enlistmentIdentifier) } #endregion + #region Enlistment Callback Positive + [NonEvent] + internal void EnlistmentCallbackPositive(EnlistmentTraceIdentifier enlistmentTraceIdentifier, EnlistmentCallback callback) + { + if (IsEnabled(EventLevel.Verbose, ALL_KEYWORDS)) + { + EnlistmentCallbackPositive(enlistmentTraceIdentifier.EnlistmentIdentifier, callback.ToString()); + } + } + + [Event(ENLISTMENT_CALLBACK_POSITIVE_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Verbose, Task = Tasks.Enlistment, Opcode = Opcodes.CallbackPositive, Message = "Enlistment callback positive: ID is {0}, callback is {1}")] + private void EnlistmentCallbackPositive(int enlistmentIdentifier, string? callback) + { + SetActivityId(string.Empty); + WriteEvent(ENLISTMENT_CALLBACK_POSITIVE_EVENTID, enlistmentIdentifier, callback); + } + #endregion + + #region Enlistment Callback Negative + [NonEvent] + internal void EnlistmentCallbackNegative(EnlistmentTraceIdentifier enlistmentTraceIdentifier, EnlistmentCallback callback) + { + if (IsEnabled(EventLevel.Warning, ALL_KEYWORDS)) + { + EnlistmentCallbackNegative(enlistmentTraceIdentifier.EnlistmentIdentifier, callback.ToString()); + } + } + + [Event(ENLISTMENT_CALLBACK_NEGATIVE_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Warning, Task = Tasks.Enlistment, Opcode = Opcodes.CallbackNegative, Message = "Enlistment callback negative: ID is {0}, callback is {1}")] + private void EnlistmentCallbackNegative(int enlistmentIdentifier, string? callback) + { + SetActivityId(string.Empty); + WriteEvent(ENLISTMENT_CALLBACK_NEGATIVE_EVENTID, enlistmentIdentifier, callback); + } + #endregion + #region Method Enter /// Trace an event when enter a method. /// trace source @@ -604,7 +794,7 @@ internal void MethodEnter(TraceSourceType traceSource, object? thisOrContextObje { MethodEnterTraceBase(IdOf(thisOrContextObject), methodname); } - else if (traceSource == TraceSourceType.TraceSourceDistributed) + else if (traceSource == TraceSourceType.TraceSourceOleTx) { MethodEnterTraceDistributed(IdOf(thisOrContextObject), methodname); } @@ -627,7 +817,7 @@ internal void MethodEnter(TraceSourceType traceSource, [CallerMemberName] string { MethodEnterTraceBase(string.Empty, methodname); } - else if (traceSource == TraceSourceType.TraceSourceDistributed) + else if (traceSource == TraceSourceType.TraceSourceOleTx) { MethodEnterTraceDistributed(string.Empty, methodname); } @@ -646,11 +836,11 @@ private void MethodEnterTraceBase(string thisOrContextObject, string? methodname SetActivityId(string.Empty); WriteEvent(METHOD_ENTER_BASE_EVENTID, thisOrContextObject, methodname); } - [Event(METHOD_ENTER_DISTRIBUTED_EVENTID, Keywords = Keywords.TraceDistributed, Level = EventLevel.Verbose, Task = Tasks.Method, Opcode = Opcodes.Enter, Message = "Enter method : {0}.{1}")] + [Event(METHOD_ENTER_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Verbose, Task = Tasks.Method, Opcode = Opcodes.Enter, Message = "Enter method : {0}.{1}")] private void MethodEnterTraceDistributed(string thisOrContextObject, string? methodname) { SetActivityId(string.Empty); - WriteEvent(METHOD_ENTER_DISTRIBUTED_EVENTID, thisOrContextObject, methodname); + WriteEvent(METHOD_ENTER_OLETX_EVENTID, thisOrContextObject, methodname); } #endregion @@ -672,7 +862,7 @@ internal void MethodExit(TraceSourceType traceSource, object? thisOrContextObjec { MethodExitTraceBase(IdOf(thisOrContextObject), methodname); } - else if (traceSource == TraceSourceType.TraceSourceDistributed) + else if (traceSource == TraceSourceType.TraceSourceOleTx) { MethodExitTraceDistributed(IdOf(thisOrContextObject), methodname); } @@ -695,7 +885,7 @@ internal void MethodExit(TraceSourceType traceSource, [CallerMemberName] string? { MethodExitTraceBase(string.Empty, methodname); } - else if (traceSource == TraceSourceType.TraceSourceDistributed) + else if (traceSource == TraceSourceType.TraceSourceOleTx) { MethodExitTraceDistributed(string.Empty, methodname); } @@ -714,11 +904,11 @@ private void MethodExitTraceBase(string thisOrContextObject, string? methodname) SetActivityId(string.Empty); WriteEvent(METHOD_EXIT_BASE_EVENTID, thisOrContextObject, methodname); } - [Event(METHOD_EXIT_DISTRIBUTED_EVENTID, Keywords = Keywords.TraceDistributed, Level = EventLevel.Verbose, Task = Tasks.Method, Opcode = Opcodes.Exit, Message = "Exit method: {0}.{1}")] + [Event(METHOD_EXIT_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Verbose, Task = Tasks.Method, Opcode = Opcodes.Exit, Message = "Exit method: {0}.{1}")] private void MethodExitTraceDistributed(string thisOrContextObject, string? methodname) { SetActivityId(string.Empty); - WriteEvent(METHOD_EXIT_DISTRIBUTED_EVENTID, thisOrContextObject, methodname); + WriteEvent(METHOD_EXIT_OLETX_EVENTID, thisOrContextObject, methodname); } #endregion @@ -732,13 +922,17 @@ internal void ExceptionConsumed(TraceSourceType traceSource, Exception exception { if (IsEnabled(EventLevel.Verbose, ALL_KEYWORDS)) { - if (traceSource == TraceSourceType.TraceSourceBase) - { - ExceptionConsumedBase(exception.ToString()); - } - else + switch (traceSource) { - ExceptionConsumedLtm(exception.ToString()); + case TraceSourceType.TraceSourceBase: + ExceptionConsumedBase(exception.ToString()); + return; + case TraceSourceType.TraceSourceLtm: + ExceptionConsumedLtm(exception.ToString()); + return; + case TraceSourceType.TraceSourceOleTx: + ExceptionConsumedOleTx(exception.ToString()); + return; } } } @@ -765,6 +959,30 @@ private void ExceptionConsumedLtm(string exceptionStr) SetActivityId(string.Empty); WriteEvent(EXCEPTION_CONSUMED_LTM_EVENTID, exceptionStr); } + [Event(EXCEPTION_CONSUMED_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Verbose, Opcode = Opcodes.ExceptionConsumed, Message = "Exception consumed: {0}")] + private void ExceptionConsumedOleTx(string exceptionStr) + { + SetActivityId(string.Empty); + WriteEvent(EXCEPTION_CONSUMED_OLETX_EVENTID, exceptionStr); + } + #endregion + + #region OleTx TransactionManager Create + [NonEvent] + internal void OleTxTransactionManagerCreate(Type tmType, string? nodeName) + { + if (IsEnabled(EventLevel.Verbose, ALL_KEYWORDS)) + { + OleTxTransactionManagerCreate(tmType.ToString(), nodeName); + } + } + + [Event(TRANSACTIONMANAGER_CREATE_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Verbose, Task = Tasks.TransactionManager, Opcode = Opcodes.Created, Message = "Created OleTx transaction manager, type is {0}, node name is {1}")] + private void OleTxTransactionManagerCreate(string tmType, string? nodeName) + { + SetActivityId(string.Empty); + WriteEvent(TRANSACTIONMANAGER_CREATE_OLETX_EVENTID, tmType, nodeName); + } #endregion #region TransactionManager Reenlist @@ -940,26 +1158,6 @@ private void TransactionScopeIncomplete(string transactionID) } #endregion - #region Transactionscope Internal Error - /// Trace an event when there is an internal error on transactionscope. - /// The error information. - [NonEvent] - internal void TransactionScopeInternalError(string? error) - { - if (IsEnabled(EventLevel.Critical, ALL_KEYWORDS)) - { - TransactionScopeInternalErrorTrace(error); - } - } - - [Event(TRANSACTIONSCOPE_INTERNAL_ERROR_EVENTID, Keywords = Keywords.TraceBase, Level = EventLevel.Critical, Task = Tasks.TransactionScope, Opcode = Opcodes.InternalError, Message = "Transactionscope internal error: {0}")] - private void TransactionScopeInternalErrorTrace(string? error) - { - SetActivityId(string.Empty); - WriteEvent(TRANSACTIONSCOPE_INTERNAL_ERROR_EVENTID, error); - } - #endregion - #region Transactionscope Timeout /// Trace an event when there is timeout on transactionscope. /// The transaction ID. @@ -1000,7 +1198,7 @@ private void TransactionTimeout(string transactionID) } #endregion - #region Transactionstate Enlist + #region Transaction Enlist /// Trace an event when there is enlist. /// The enlistment ID. /// The enlistment type. @@ -1025,43 +1223,65 @@ private void TransactionstateEnlist(string enlistmentID, string type, string opt } #endregion - #region Transactionstate committed - /// Trace an event when transaction is committed. - /// The transaction ID. + #region Transaction committed [NonEvent] - internal void TransactionCommitted(TransactionTraceIdentifier transactionID) + internal void TransactionCommitted(TraceSourceType traceSource, TransactionTraceIdentifier transactionID) { if (IsEnabled(EventLevel.Verbose, ALL_KEYWORDS)) { - TransactionCommitted(transactionID.TransactionIdentifier ?? string.Empty); + if (traceSource == TraceSourceType.TraceSourceLtm) + { + TransactionCommittedLtm(transactionID.TransactionIdentifier ?? string.Empty); + } + else if (traceSource == TraceSourceType.TraceSourceOleTx) + { + TransactionCommittedOleTx(transactionID.TransactionIdentifier ?? string.Empty); + } } } - [Event(TRANSACTION_COMMITTED_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Verbose, Task = Tasks.Transaction, Opcode = Opcodes.Committed, Message = "Transaction committed: transaction ID is {0}")] - private void TransactionCommitted(string transactionID) + [Event(TRANSACTION_COMMITTED_LTM_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Verbose, Task = Tasks.Transaction, Opcode = Opcodes.Committed, Message = "Transaction committed LTM: transaction ID is {0}")] + private void TransactionCommittedLtm(string transactionID) { SetActivityId(transactionID); - WriteEvent(TRANSACTION_COMMITTED_EVENTID, transactionID); + WriteEvent(TRANSACTION_COMMITTED_LTM_EVENTID, transactionID); + } + [Event(TRANSACTION_COMMITTED_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Verbose, Task = Tasks.Transaction, Opcode = Opcodes.Committed, Message = "Transaction committed OleTx: transaction ID is {0}")] + private void TransactionCommittedOleTx(string transactionID) + { + SetActivityId(transactionID); + WriteEvent(TRANSACTION_COMMITTED_OLETX_EVENTID, transactionID); } #endregion - #region Transactionstate indoubt - /// Trace an event when transaction is indoubt. - /// The transaction ID. + #region Transaction indoubt [NonEvent] - internal void TransactionInDoubt(TransactionTraceIdentifier transactionID) + internal void TransactionInDoubt(TraceSourceType traceSource, TransactionTraceIdentifier transactionID) { if (IsEnabled(EventLevel.Warning, ALL_KEYWORDS)) { - TransactionInDoubt(transactionID.TransactionIdentifier ?? string.Empty); + if (traceSource == TraceSourceType.TraceSourceLtm) + { + TransactionInDoubtLtm(transactionID.TransactionIdentifier ?? string.Empty); + } + else if (traceSource == TraceSourceType.TraceSourceOleTx) + { + TransactionInDoubtOleTx(transactionID.TransactionIdentifier ?? string.Empty); + } } } - [Event(TRANSACTION_INDOUBT_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Warning, Task = Tasks.Transaction, Opcode = Opcodes.InDoubt, Message = "Transaction indoubt: transaction ID is {0}")] - private void TransactionInDoubt(string transactionID) + [Event(TRANSACTION_INDOUBT_LTM_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Warning, Task = Tasks.Transaction, Opcode = Opcodes.InDoubt, Message = "Transaction indoubt LTM: transaction ID is {0}")] + private void TransactionInDoubtLtm(string transactionID) { SetActivityId(transactionID); - WriteEvent(TRANSACTION_INDOUBT_EVENTID, transactionID); + WriteEvent(TRANSACTION_INDOUBT_LTM_EVENTID, transactionID); + } + [Event(TRANSACTION_INDOUBT_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Warning, Task = Tasks.Transaction, Opcode = Opcodes.InDoubt, Message = "Transaction indoubt OleTx: transaction ID is {0}")] + private void TransactionInDoubtOleTx(string transactionID) + { + SetActivityId(transactionID); + WriteEvent(TRANSACTION_INDOUBT_OLETX_EVENTID, transactionID); } #endregion @@ -1086,25 +1306,57 @@ private void TransactionPromoted(string transactionID, string distributedTxID) } #endregion - #region Transactionstate aborted - /// Trace an event when transaction is aborted. - /// The transaction ID. + #region Transaction aborted [NonEvent] - internal void TransactionAborted(TransactionTraceIdentifier transactionID) + internal void TransactionAborted(TraceSourceType traceSource, TransactionTraceIdentifier transactionID) { if (IsEnabled(EventLevel.Warning, ALL_KEYWORDS)) { - TransactionAborted(transactionID.TransactionIdentifier ?? string.Empty); + if (traceSource == TraceSourceType.TraceSourceLtm) + { + TransactionAbortedLtm(transactionID.TransactionIdentifier ?? string.Empty); + } + else if (traceSource == TraceSourceType.TraceSourceOleTx) + { + TransactionAbortedOleTx(transactionID.TransactionIdentifier ?? string.Empty); + } } } - [Event(TRANSACTION_ABORTED_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Warning, Task = Tasks.Transaction, Opcode = Opcodes.Aborted, Message = "Transaction aborted: transaction ID is {0}")] - private void TransactionAborted(string transactionID) + [Event(TRANSACTION_ABORTED_LTM_EVENTID, Keywords = Keywords.TraceLtm, Level = EventLevel.Warning, Task = Tasks.Transaction, Opcode = Opcodes.Aborted, Message = "Transaction aborted LTM: transaction ID is {0}")] + private void TransactionAbortedLtm(string transactionID) + { + SetActivityId(transactionID); + WriteEvent(TRANSACTION_ABORTED_LTM_EVENTID, transactionID); + } + [Event(TRANSACTION_ABORTED_OLETX_EVENTID, Keywords = Keywords.TraceOleTx, Level = EventLevel.Warning, Task = Tasks.Transaction, Opcode = Opcodes.Aborted, Message = "Transaction aborted OleTx: transaction ID is {0}")] + private void TransactionAbortedOleTx(string transactionID) { SetActivityId(transactionID); - WriteEvent(TRANSACTION_ABORTED_EVENTID, transactionID); + WriteEvent(TRANSACTION_ABORTED_OLETX_EVENTID, transactionID); } #endregion + + #region Internal Error + /// Trace an event when there is an internal error. + /// The error information. + [NonEvent] + internal void InternalError(string? error = null) + { + if (IsEnabled(EventLevel.Critical, ALL_KEYWORDS)) + { + InternalErrorTrace(error); + } + } + + [Event(INTERNAL_ERROR_EVENTID, Keywords = Keywords.TraceBase, Level = EventLevel.Critical, Task = Tasks.TransactionScope, Opcode = Opcodes.InternalError, Message = "Transactionscope internal error: {0}")] + private void InternalErrorTrace(string? error) + { + SetActivityId(string.Empty); + WriteEvent(INTERNAL_ERROR_EVENTID, error); + } + #endregion + public static class Opcodes { public const EventOpcode Aborted = (EventOpcode)100; @@ -1136,6 +1388,8 @@ public static class Opcodes public const EventOpcode Rollback = (EventOpcode)126; public const EventOpcode Serialized = (EventOpcode)127; public const EventOpcode Timeout = (EventOpcode)128; + public const EventOpcode CallbackPositive = (EventOpcode)129; + public const EventOpcode CallbackNegative = (EventOpcode)130; } public static class Tasks @@ -1155,7 +1409,7 @@ public static class Keywords { public const EventKeywords TraceBase = (EventKeywords)0x0001; public const EventKeywords TraceLtm = (EventKeywords)0x0002; - public const EventKeywords TraceDistributed = (EventKeywords)0x0004; + public const EventKeywords TraceOleTx = (EventKeywords)0x0004; } private static void SetActivityId(string str) diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/VolatileEnlistmentState.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/VolatileEnlistmentState.cs index 360dc3e3a80e77..a0bf1387b3ab5c 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/VolatileEnlistmentState.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/VolatileEnlistmentState.cs @@ -141,7 +141,7 @@ internal override void EnterState(InternalEnlistment enlistment) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.EnlistmentStatus(enlistment, NotificationCall.Prepare); + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, enlistment.EnlistmentTraceId, NotificationCall.Prepare); } Debug.Assert(enlistment.EnlistmentNotification != null); @@ -213,7 +213,7 @@ internal override void EnterState(InternalEnlistment enlistment) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.EnlistmentStatus(enlistment, NotificationCall.SinglePhaseCommit); + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, enlistment.EnlistmentTraceId, NotificationCall.SinglePhaseCommit); } Monitor.Exit(enlistment.Transaction); @@ -366,7 +366,7 @@ internal override void EnterState(InternalEnlistment enlistment) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.EnlistmentStatus(enlistment, NotificationCall.Rollback); + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, enlistment.EnlistmentTraceId, NotificationCall.Rollback); } Debug.Assert(enlistment.EnlistmentNotification != null); @@ -409,7 +409,7 @@ internal override void EnterState(InternalEnlistment enlistment) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.EnlistmentStatus(enlistment, NotificationCall.Commit); + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, enlistment.EnlistmentTraceId, NotificationCall.Commit); } Debug.Assert(enlistment.EnlistmentNotification != null); @@ -443,7 +443,7 @@ internal override void EnterState(InternalEnlistment enlistment) TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log; if (etwLog.IsEnabled()) { - etwLog.EnlistmentStatus(enlistment, NotificationCall.InDoubt); + etwLog.EnlistmentStatus(TraceSourceType.TraceSourceLtm, enlistment.EnlistmentTraceId, NotificationCall.InDoubt); } Debug.Assert(enlistment.EnlistmentNotification != null); diff --git a/src/libraries/System.Transactions.Local/tests/LTMEnlistmentTests.cs b/src/libraries/System.Transactions.Local/tests/LTMEnlistmentTests.cs index a5fd3047fa64a1..c686049d742b5a 100644 --- a/src/libraries/System.Transactions.Local/tests/LTMEnlistmentTests.cs +++ b/src/libraries/System.Transactions.Local/tests/LTMEnlistmentTests.cs @@ -36,83 +36,28 @@ public void SinglePhaseDurable(int volatileCount, EnlistmentOptions volatileEnli Transaction tx = null; try { - using (TransactionScope ts = new TransactionScope()) - { - tx = Transaction.Current.Clone(); - - if (volatileCount > 0) - { - TestSinglePhaseEnlistment[] volatiles = new TestSinglePhaseEnlistment[volatileCount]; - for (int i = 0; i < volatileCount; i++) - { - // It doesn't matter what we specify for SinglePhaseVote. - volatiles[i] = new TestSinglePhaseEnlistment(volatilePhase1Vote, SinglePhaseVote.InDoubt, expectedVolatileOutcome); - tx.EnlistVolatile(volatiles[i], volatileEnlistmentOption); - } - } + using var ts = new TransactionScope(); - // Doesn't really matter what we specify for EnlistmentOutcome here. This is an SPC, so Phase2 won't happen for this enlistment. - TestSinglePhaseEnlistment durable = new TestSinglePhaseEnlistment(Phase1Vote.Prepared, singlePhaseVote, EnlistmentOutcome.Committed); - tx.EnlistDurable(Guid.NewGuid(), durable, EnlistmentOptions.None); + tx = Transaction.Current!.Clone(); - if (commit) + if (volatileCount > 0) + { + TestSinglePhaseEnlistment[] volatiles = new TestSinglePhaseEnlistment[volatileCount]; + for (int i = 0; i < volatileCount; i++) { - ts.Complete(); + // It doesn't matter what we specify for SinglePhaseVote. + volatiles[i] = new TestSinglePhaseEnlistment(volatilePhase1Vote, SinglePhaseVote.InDoubt, expectedVolatileOutcome); + tx.EnlistVolatile(volatiles[i], volatileEnlistmentOption); } } - } - catch (TransactionInDoubtException) - { - Assert.Equal(TransactionStatus.InDoubt, expectedTxStatus); - } - catch (TransactionAbortedException) - { - Assert.Equal(TransactionStatus.Aborted, expectedTxStatus); - } - - Assert.NotNull(tx); - Assert.Equal(expectedTxStatus, tx.TransactionInformation.Status); - } + // Doesn't really matter what we specify for EnlistmentOutcome here. This is an SPC, so Phase2 won't happen for this enlistment. + TestSinglePhaseEnlistment durable = new TestSinglePhaseEnlistment(Phase1Vote.Prepared, singlePhaseVote, EnlistmentOutcome.Committed); + tx.EnlistDurable(Guid.NewGuid(), durable, EnlistmentOptions.None); - [Theory] - // This test needs to change once we have promotion support. - // Right now any attempt to create a two phase durable enlistment will attempt to promote and will fail because promotion is not supported. This results in the transaction being - // aborted. - [InlineData(0, EnlistmentOptions.None, EnlistmentOptions.None, Phase1Vote.Prepared, true, EnlistmentOutcome.Aborted, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] - [InlineData(1, EnlistmentOptions.None, EnlistmentOptions.None, Phase1Vote.Prepared, true, EnlistmentOutcome.Aborted, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] - [InlineData(2, EnlistmentOptions.None, EnlistmentOptions.None, Phase1Vote.Prepared, true, EnlistmentOutcome.Aborted, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] - public void TwoPhaseDurable(int volatileCount, EnlistmentOptions volatileEnlistmentOption, EnlistmentOptions durableEnlistmentOption, Phase1Vote volatilePhase1Vote, bool commit, EnlistmentOutcome expectedVolatileOutcome, EnlistmentOutcome expectedDurableOutcome, TransactionStatus expectedTxStatus) - { - Transaction tx = null; - try - { - using (TransactionScope ts = new TransactionScope()) + if (commit) { - tx = Transaction.Current.Clone(); - - if (volatileCount > 0) - { - TestEnlistment[] volatiles = new TestEnlistment[volatileCount]; - for (int i = 0; i < volatileCount; i++) - { - // It doesn't matter what we specify for SinglePhaseVote. - volatiles[i] = new TestEnlistment(volatilePhase1Vote, expectedVolatileOutcome); - tx.EnlistVolatile(volatiles[i], volatileEnlistmentOption); - } - } - - TestEnlistment durable = new TestEnlistment(Phase1Vote.Prepared, expectedDurableOutcome); - // This needs to change once we have promotion support. - Assert.Throws(() => // Creation of two phase durable enlistment attempts to promote to MSDTC - { - tx.EnlistDurable(Guid.NewGuid(), durable, durableEnlistmentOption); - }); - - if (commit) - { - ts.Complete(); - } + ts.Complete(); } } catch (TransactionInDoubtException) @@ -137,17 +82,16 @@ public void EnlistDuringPhase0(EnlistmentOptions enlistmentOption, Phase1Vote ph AutoResetEvent outcomeEvent = null; try { - using (TransactionScope ts = new TransactionScope()) - { - tx = Transaction.Current.Clone(); - outcomeEvent = new AutoResetEvent(false); - TestEnlistment enlistment = new TestEnlistment(phase1Vote, expectedOutcome, true, expectPhase0EnlistSuccess, outcomeEvent); - tx.EnlistVolatile(enlistment, enlistmentOption); + using var ts = new TransactionScope(); - if (commit) - { - ts.Complete(); - } + tx = Transaction.Current!.Clone(); + outcomeEvent = new AutoResetEvent(false); + var enlistment = new TestEnlistment(phase1Vote, expectedOutcome, true, expectPhase0EnlistSuccess, outcomeEvent); + tx.EnlistVolatile(enlistment, enlistmentOption); + + if (commit) + { + ts.Complete(); } } catch (TransactionInDoubtException) @@ -173,30 +117,29 @@ public void EnlistVolatile(int volatileCount, EnlistmentOptions enlistmentOption Transaction tx = null; try { - using (TransactionScope ts = new TransactionScope()) - { - tx = Transaction.Current.Clone(); + using var ts = new TransactionScope(); - if (volatileCount > 0) - { - TestEnlistment[] volatiles = new TestEnlistment[volatileCount]; - outcomeEvents = new AutoResetEvent[volatileCount]; - for (int i = 0; i < volatileCount-1; i++) - { - outcomeEvents[i] = new AutoResetEvent(false); - volatiles[i] = new TestEnlistment(volatilePhase1Vote, expectedEnlistmentOutcome, false, true, outcomeEvents[i]); - tx.EnlistVolatile(volatiles[i], enlistmentOption); - } - - outcomeEvents[volatileCount-1] = new AutoResetEvent(false); - volatiles[volatileCount - 1] = new TestEnlistment(lastPhase1Vote, expectedEnlistmentOutcome, false, true, outcomeEvents[volatileCount-1]); - tx.EnlistVolatile(volatiles[volatileCount - 1], enlistmentOption); - } + tx = Transaction.Current!.Clone(); - if (commit) + if (volatileCount > 0) + { + TestEnlistment[] volatiles = new TestEnlistment[volatileCount]; + outcomeEvents = new AutoResetEvent[volatileCount]; + for (int i = 0; i < volatileCount-1; i++) { - ts.Complete(); + outcomeEvents[i] = new AutoResetEvent(false); + volatiles[i] = new TestEnlistment(volatilePhase1Vote, expectedEnlistmentOutcome, false, true, outcomeEvents[i]); + tx.EnlistVolatile(volatiles[i], enlistmentOption); } + + outcomeEvents[volatileCount-1] = new AutoResetEvent(false); + volatiles[volatileCount - 1] = new TestEnlistment(lastPhase1Vote, expectedEnlistmentOutcome, false, true, outcomeEvents[volatileCount-1]); + tx.EnlistVolatile(volatiles[volatileCount - 1], enlistmentOption); + } + + if (commit) + { + ts.Complete(); } } catch (TransactionInDoubtException) @@ -216,6 +159,5 @@ public void EnlistVolatile(int volatileCount, EnlistmentOptions enlistmentOption Assert.NotNull(tx); Assert.Equal(expectedTxStatus, tx.TransactionInformation.Status); } - } } diff --git a/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs b/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs index 747d33a280d8c0..dc2ba01f63bcbb 100644 --- a/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs +++ b/src/libraries/System.Transactions.Local/tests/NonMsdtcPromoterTests.cs @@ -724,106 +724,6 @@ public void Rollback(Enlistment enlistment) } #endregion - // This class is used in conjunction with SubordinateTransaction. When asked via the Promote - // method, it needs to create a DTC transaction and return the propagation token. Since we - // can't just create another CommittableTransaction and promote it and return it's propagation - // token in the same AppDomain, we spin up another AppDomain and do it there. - private class MySimpleTransactionSuperior : ISimpleTransactionSuperior - { - private DtcTxCreator _dtcTxCreator = new DtcTxCreator() { TraceEnabled = false }; - private PromotedTx _promotedTx; - - public byte[] Promote() - { - byte[] propagationToken = null; - - Trace("MySimpleTransactionSuperior.Promote"); - propagationToken = _dtcTxCreator.CreatePromotedTx(ref _promotedTx); - - return propagationToken; - } - - public void Rollback() - { - Trace("MySimpleTransactionSuperior.Rollback"); - _promotedTx.Rollback(); - } - - public void Commit() - { - Trace("MySimpleTransactionSuperior.Commit"); - _promotedTx.Commit(); - } - } - - public class DtcTxCreator // : MarshalByRefObject - { - private static bool s_trace = false; - - public bool TraceEnabled - { - get { return s_trace; } - set { s_trace = value; } - } - public static void Trace(string stringToTrace, params object[] args) - { - if (s_trace) - { - Debug.WriteLine(stringToTrace, args); - } - } - - public byte[] CreatePromotedTx(ref PromotedTx promotedTx) - { - DtcTxCreator.Trace("DtcTxCreator.CreatePromotedTx"); - byte[] propagationToken; - CommittableTransaction commitTx = new CommittableTransaction(); - promotedTx = new PromotedTx(commitTx); - propagationToken = TransactionInterop.GetTransmitterPropagationToken(commitTx); - return propagationToken; - } - } - - // This is the class that is created in the "other" AppDomain to create a - // CommittableTransaction, promote it to DTC, and return the propagation token. - // It also commits or aborts the transaction. Used by MySimpleTransactionSuperior - // to create a DTC transaction when asked to promote. - public class PromotedTx // : MarshalByRefObject - { - private CommittableTransaction _commitTx; - - public PromotedTx(CommittableTransaction commitTx) - { - DtcTxCreator.Trace("PromotedTx constructor"); - _commitTx = commitTx; - } - - ~PromotedTx() - { - DtcTxCreator.Trace("PromotedTx destructor"); - if (_commitTx != null) - { - DtcTxCreator.Trace("PromotedTx destructor calling Rollback"); - _commitTx.Rollback(); - _commitTx = null; - } - } - - public void Commit() - { - DtcTxCreator.Trace("PromotedTx.Commit"); - _commitTx.Commit(); - _commitTx = null; - } - - public void Rollback() - { - DtcTxCreator.Trace("PromotedTx.Rollback"); - _commitTx.Rollback(); - _commitTx = null; - } - } - #region TestCase_ methods private static void TestCase_VolatileEnlistments( int count, @@ -2062,7 +1962,6 @@ private static void TestCase_SetDistributedIdWithWrongNotificationObject() #endregion - /// /// This test case is very basic Volatile Enlistment test. /// @@ -2273,29 +2172,5 @@ public void PSPENonMsdtcSetDistributedTransactionIdentifierCallWithWrongNotifica // Call SetDistributedTransactionIdentifier at the wrong time. TestCase_SetDistributedIdWithWrongNotificationObject(); } - - [Fact] - public void SimpleTransactionSuperior() - { - MySimpleTransactionSuperior superior = new MySimpleTransactionSuperior(); - SubordinateTransaction subTx = new SubordinateTransaction(IsolationLevel.Serializable, superior); - - AutoResetEvent durableCompleted = new AutoResetEvent(false); - MyEnlistment durable = null; - - durable = new MyEnlistment( - durableCompleted, - true, - false, - EnlistmentOptions.None, - /*expectSuccessfulEnlist=*/ false, - /*secondEnlistmentCompleted=*/ null); - durable.TransactionToEnlist = Transaction.Current; - - Assert.Throws(() => // SubordinateTransaction promotes to MSDTC - { - subTx.EnlistDurable(Guid.NewGuid(), durable, EnlistmentOptions.None); - }); - } } } diff --git a/src/libraries/System.Transactions.Local/tests/OleTxNonWindowsUnsupportedTests.cs b/src/libraries/System.Transactions.Local/tests/OleTxNonWindowsUnsupportedTests.cs new file mode 100644 index 00000000000000..fc753cef0e2d0a --- /dev/null +++ b/src/libraries/System.Transactions.Local/tests/OleTxNonWindowsUnsupportedTests.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.Transactions.Tests; + +#nullable enable + +[SkipOnPlatform(TestPlatforms.Windows, "These tests assert that OleTx operations properly throw PlatformNotSupportedException on non-Windows platforms")] +public class OleTxNonWindowsUnsupportedTests +{ + [Fact] + public void Durable_enlistment() + { + var tx = new CommittableTransaction(); + + // Votes and outcomes don't matter, the 2nd enlistment fails in non-Windows + var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Aborted); + + Assert.Throws(() => tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None)); + Assert.Equal(TransactionStatus.Aborted, tx.TransactionInformation.Status); + } + + [Fact] + public void Promotable_enlistments() + { + var tx = new CommittableTransaction(); + + var promotableEnlistment1 = new TestPromotableSinglePhaseEnlistment(() => new byte[24], EnlistmentOutcome.Aborted); + var promotableEnlistment2 = new TestPromotableSinglePhaseEnlistment(null, EnlistmentOutcome.Aborted); + + // 1st promotable enlistment - no distributed transaction yet. + Assert.True(tx.EnlistPromotableSinglePhase(promotableEnlistment1)); + Assert.True(promotableEnlistment1.InitializedCalled); + + // 2nd promotable enlistment returns false. + tx.EnlistPromotableSinglePhase(promotableEnlistment2); + Assert.False(promotableEnlistment2.InitializedCalled); + + // Now enlist a durable enlistment, this will cause the escalation to a distributed transaction and fail on non-Windows. + var durableEnlistment = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Aborted); + Assert.Throws(() => tx.EnlistDurable(Guid.NewGuid(), durableEnlistment, EnlistmentOptions.None)); + + Assert.True(promotableEnlistment1.PromoteCalled); + Assert.False(promotableEnlistment2.PromoteCalled); + + Assert.Equal(TransactionStatus.Aborted, tx.TransactionInformation.Status); + } + + [Fact] + public void TransmitterPropagationToken() + => Assert.Throws(() => + TransactionInterop.GetTransmitterPropagationToken(new CommittableTransaction())); + + [Fact] + public void GetWhereabouts() + => Assert.Throws(() => TransactionInterop.GetWhereabouts()); + + [Fact] + public void GetExportCookie() + => Assert.Throws(() => TransactionInterop.GetExportCookie( + new CommittableTransaction(), new byte[200])); +} diff --git a/src/libraries/System.Transactions.Local/tests/OleTxTests.cs b/src/libraries/System.Transactions.Local/tests/OleTxTests.cs new file mode 100644 index 00000000000000..fd89ef40714591 --- /dev/null +++ b/src/libraries/System.Transactions.Local/tests/OleTxTests.cs @@ -0,0 +1,477 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.DotNet.RemoteExecutor; +using Xunit; +using Xunit.Sdk; + +namespace System.Transactions.Tests; + +#nullable enable + +[PlatformSpecific(TestPlatforms.Windows)] +public class OleTxTests +{ + //private static readonly TimeSpan Timeout = TimeSpan.FromMinutes(3); + private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(10); + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] + [InlineData(Phase1Vote.Prepared, Phase1Vote.Prepared, EnlistmentOutcome.Committed, EnlistmentOutcome.Committed, TransactionStatus.Committed)] + [InlineData(Phase1Vote.Prepared, Phase1Vote.ForceRollback, EnlistmentOutcome.Aborted, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] + [InlineData(Phase1Vote.ForceRollback, Phase1Vote.Prepared, EnlistmentOutcome.Aborted, EnlistmentOutcome.Aborted, TransactionStatus.Aborted)] + public void Two_durable_enlistments_commit(Phase1Vote vote1, Phase1Vote vote2, EnlistmentOutcome expectedOutcome1, EnlistmentOutcome expectedOutcome2, TransactionStatus expectedTxStatus) + { + if (RuntimeInformation.ProcessArchitecture == Architecture.X86) + { + return; // Temporarily skip on 32-bit where we have an issue + } + + var tx = new CommittableTransaction(); + + try + { + var enlistment1 = new TestEnlistment(vote1, expectedOutcome1); + var enlistment2 = new TestEnlistment(vote2, expectedOutcome2); + + tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None); + tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None); + + Assert.Equal(TransactionStatus.Active, tx.TransactionInformation.Status); + tx.Commit(); + } + catch (TransactionInDoubtException) + { + Assert.Equal(TransactionStatus.InDoubt, expectedTxStatus); + } + catch (TransactionAbortedException) + { + Assert.Equal(TransactionStatus.Aborted, expectedTxStatus); + } + + Retry(() => Assert.Equal(expectedTxStatus, tx.TransactionInformation.Status)); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] + public void Two_durable_enlistments_rollback() + { + if (RuntimeInformation.ProcessArchitecture == Architecture.X86) + { + return; // Temporarily skip on 32-bit where we have an issue + } + + var tx = new CommittableTransaction(); + + var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Aborted); + var enlistment2 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Aborted); + + tx.EnlistDurable(Guid.NewGuid(), enlistment1, EnlistmentOptions.None); + tx.EnlistDurable(Guid.NewGuid(), enlistment2, EnlistmentOptions.None); + + tx.Rollback(); + + Assert.False(enlistment1.WasPreparedCalled); + Assert.False(enlistment2.WasPreparedCalled); + + // This matches the .NET Framework behavior + Retry(() => Assert.Equal(TransactionStatus.Aborted, tx.TransactionInformation.Status)); + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + public void Volatile_and_durable_enlistments(int volatileCount) + { + if (RuntimeInformation.ProcessArchitecture == Architecture.X86) + { + return; // Temporarily skip on 32-bit where we have an issue + } + + var tx = new CommittableTransaction(); + + if (volatileCount > 0) + { + TestEnlistment[] volatiles = new TestEnlistment[volatileCount]; + for (int i = 0; i < volatileCount; i++) + { + // It doesn't matter what we specify for SinglePhaseVote. + volatiles[i] = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); + tx.EnlistVolatile(volatiles[i], EnlistmentOptions.None); + } + } + + var durable = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed); + + // Creation of two phase durable enlistment attempts to promote to MSDTC + tx.EnlistDurable(Guid.NewGuid(), durable, EnlistmentOptions.None); + + tx.Commit(); + + Retry(() => Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status)); + } + + protected static bool IsRemoteExecutorSupportedAndNotNano => RemoteExecutor.IsSupported && PlatformDetection.IsNotWindowsNanoServer; + + [ConditionalFact(nameof(IsRemoteExecutorSupportedAndNotNano))] + public void Promotion() + { + if (RuntimeInformation.ProcessArchitecture == Architecture.X86) + { + return; // Temporarily skip on 32-bit where we have an issue + } + + // This simulates the full promotable flow, as implemented for SQL Server. + + // We are going to spin up two external processes. + // 1. The 1st external process will create the transaction and save its propagation token to disk. + // 2. The main process will read that, and propagate the transaction to the 2nd external process. + // 3. The main process will then notify the 1st external process to commit (as the main's transaction is delegated to it). + // 4. At that point the MSDTC Commit will be triggered; enlistments on both the 1st and 2nd processes will be notified + // to commit, and the transaction status will reflect the committed status in the main process. + var tx = new CommittableTransaction(); + + string propagationTokenFilePath = Path.GetTempFileName(); + string exportCookieFilePath = Path.GetTempFileName(); + using var waitHandle1 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion1"); + using var waitHandle2 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion2"); + using var waitHandle3 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion3"); + + RemoteInvokeHandle? remote1 = null, remote2 = null; + + try + { + remote1 = RemoteExecutor.Invoke(Remote1, propagationTokenFilePath, new RemoteInvokeOptions { ExpectedExitCode = 42 }); + + // Wait for the external process to start a transaction and save its propagation token + Assert.True(waitHandle1.WaitOne(Timeout)); + + // Enlist the first PSPE. No escalation happens yet, since its the only enlistment. + var pspe1 = new TestPromotableSinglePhaseNotification(propagationTokenFilePath); + Assert.True(tx.EnlistPromotableSinglePhase(pspe1)); + Assert.True(pspe1.WasInitializedCalled); + Assert.False(pspe1.WasPromoteCalled); + Assert.False(pspe1.WasRollbackCalled); + Assert.False(pspe1.WasSinglePhaseCommitCalled); + + // Enlist the second PSPE. This returns false and does nothing, since there's already an enlistment. + var pspe2 = new TestPromotableSinglePhaseNotification(propagationTokenFilePath); + Assert.False(tx.EnlistPromotableSinglePhase(pspe2)); + Assert.False(pspe2.WasInitializedCalled); + Assert.False(pspe2.WasPromoteCalled); + Assert.False(pspe2.WasRollbackCalled); + Assert.False(pspe2.WasSinglePhaseCommitCalled); + + // Now generate an export cookie for the 2nd external process. This causes escalation and promotion. + byte[] whereabouts = TransactionInterop.GetWhereabouts(); + byte[] exportCookie = TransactionInterop.GetExportCookie(tx, whereabouts); + + Assert.True(pspe1.WasPromoteCalled); + Assert.False(pspe1.WasRollbackCalled); + Assert.False(pspe1.WasSinglePhaseCommitCalled); + + // Write the export cookie and start the 2nd external process, which will read the cookie and enlist in the transaction. + // Wait for it to complete. + File.WriteAllBytes(exportCookieFilePath, exportCookie); + remote2 = RemoteExecutor.Invoke(Remote2, exportCookieFilePath, new RemoteInvokeOptions { ExpectedExitCode = 42 }); + Assert.True(waitHandle2.WaitOne(Timeout)); + + // We now have two external processes with enlistments to our distributed transaction. Commit. + // Since our transaction is delegated to the 1st PSPE enlistment, Sys.Tx will call SinglePhaseCommit on it. + // In SQL Server this contacts the 1st DB to actually commit the transaction with MSDTC. In this simulation we'll just use a wait handle to trigger this. + tx.Commit(); + Assert.True(pspe1.WasSinglePhaseCommitCalled); + waitHandle3.Set(); + + Retry(() => Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status)); + } + catch + { + try + { + remote1?.Process.Kill(); + remote2?.Process.Kill(); + } + catch + { + } + + throw; + } + finally + { + File.Delete(propagationTokenFilePath); + } + + // Disposal of the RemoteExecutor handles will wait for the external processes to exit with the right exit code, + // which will happen when their enlistments receive the commit. + remote1?.Dispose(); + remote2?.Dispose(); + + static void Remote1(string propagationTokenFilePath) + { + var tx = new CommittableTransaction(); + + var outcomeEvent = new AutoResetEvent(false); + var enlistment = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent); + tx.EnlistDurable(Guid.NewGuid(), enlistment, EnlistmentOptions.None); + + // We now have an OleTx transaction. Save its propagation token to disk so that the main process can read it when promoting. + byte[] propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); + File.WriteAllBytes(propagationTokenFilePath, propagationToken); + + // Signal to the main process that the propagation token is ready to be read + using var waitHandle1 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion1"); + waitHandle1.Set(); + + // The main process will now import our transaction via the propagation token, and propagate it to a 2nd process. + // In the main process the transaction is delegated; we're the one who started it, and so we're the one who need to Commit. + // When Commit() is called in the main process, that will trigger a SinglePhaseCommit on the PSPE which represents us. In SQL Server this + // contacts the DB to actually commit the transaction with MSDTC. In this simulation we'll just use the wait handle again to trigger this. + using var waitHandle3 = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion3"); + Assert.True(waitHandle3.WaitOne(Timeout)); + + tx.Commit(); + + // Wait for the commit to occur on our enlistment, then exit successfully. + Assert.True(outcomeEvent.WaitOne(Timeout)); + Environment.Exit(42); // 42 is error code expected by RemoteExecutor + } + + static void Remote2(string exportCookieFilePath) + { + // Load the export cookie and enlist durably + byte[] exportCookie = File.ReadAllBytes(exportCookieFilePath); + var tx = TransactionInterop.GetTransactionFromExportCookie(exportCookie); + + // Now enlist durably. This triggers promotion of the first PSPE, reading the propagation token. + var outcomeEvent = new AutoResetEvent(false); + var enlistment = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent); + tx.EnlistDurable(Guid.NewGuid(), enlistment, EnlistmentOptions.None); + + // Signal to the main process that we're enlisted and ready to commit + using var waitHandle = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Promotion2"); + waitHandle.Set(); + + // Wait for the main process to commit the transaction + Assert.True(outcomeEvent.WaitOne(Timeout)); + Environment.Exit(42); // 42 is error code expected by RemoteExecutor + } + } + + public class TestPromotableSinglePhaseNotification : IPromotableSinglePhaseNotification + { + private string _propagationTokenFilePath; + + public TestPromotableSinglePhaseNotification(string propagationTokenFilePath) + => _propagationTokenFilePath = propagationTokenFilePath; + + public bool WasInitializedCalled { get; private set; } + public bool WasPromoteCalled { get; private set; } + public bool WasRollbackCalled { get; private set; } + public bool WasSinglePhaseCommitCalled { get; private set; } + + public void Initialize() + => WasInitializedCalled = true; + + public byte[] Promote() + { + WasPromoteCalled = true; + + return File.ReadAllBytes(_propagationTokenFilePath); + } + + public void Rollback(SinglePhaseEnlistment singlePhaseEnlistment) + => WasRollbackCalled = true; + + public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment) + { + WasSinglePhaseCommitCalled = true; + + singlePhaseEnlistment.Committed(); + } + } + + [ConditionalFact(nameof(IsRemoteExecutorSupportedAndNotNano))] + public void Recovery() + { + if (RuntimeInformation.ProcessArchitecture == Architecture.X86) + { + return; // Temporarily skip on 32-bit where we have an issue + } + + // We are going to spin up an external process to also enlist in the transaction, and then to crash when it + // receives the commit notification. We will then initiate the recovery flow. + + var tx = new CommittableTransaction(); + + var outcomeEvent1 = new AutoResetEvent(false); + var enlistment1 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent1); + var guid1 = Guid.NewGuid(); + tx.EnlistDurable(guid1, enlistment1, EnlistmentOptions.None); + + // The propagation token is used to propagate the transaction to that process so it can enlist to our + // transaction. We also provide the resource manager identifier GUID, and a path where the external process will + // write the recovery information it will receive from the MSDTC when preparing. + // We'll need these two elements later in order to Reenlist and trigger recovery. + byte[] propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); + string propagationTokenText = Convert.ToBase64String(propagationToken); + var guid2 = Guid.NewGuid(); + string secondEnlistmentRecoveryFilePath = Path.GetTempFileName(); + + using var waitHandle = new EventWaitHandle( + initialState: false, + EventResetMode.ManualReset, + "System.Transactions.Tests.OleTxTests.Recovery"); + + try + { + using (RemoteExecutor.Invoke( + EnlistAndCrash, + propagationTokenText, guid2.ToString(), secondEnlistmentRecoveryFilePath, + new RemoteInvokeOptions { ExpectedExitCode = 42 })) + { + // Wait for the external process to enlist in the transaction, it will signal this EventWaitHandle. + Assert.True(waitHandle.WaitOne(Timeout)); + + tx.Commit(); + } + + // The other has crashed when the MSDTC notified it to commit. + // Load the recovery information the other process has written to disk for us and reenlist with + // the failed RM's Guid to commit. + var outcomeEvent3 = new AutoResetEvent(false); + var enlistment3 = new TestEnlistment(Phase1Vote.Prepared, EnlistmentOutcome.Committed, outcomeReceived: outcomeEvent3); + byte[] secondRecoveryInformation = File.ReadAllBytes(secondEnlistmentRecoveryFilePath); + _ = TransactionManager.Reenlist(guid2, secondRecoveryInformation, enlistment3); + TransactionManager.RecoveryComplete(guid2); + + Assert.True(outcomeEvent1.WaitOne(Timeout)); + Assert.True(outcomeEvent3.WaitOne(Timeout)); + Assert.Equal(EnlistmentOutcome.Committed, enlistment1.Outcome); + Assert.Equal(EnlistmentOutcome.Committed, enlistment3.Outcome); + Assert.Equal(TransactionStatus.Committed, tx.TransactionInformation.Status); + + // Note: verify manually in the MSDTC console that the distributed transaction is gone + // (i.e. successfully committed), + // (Start -> Component Services -> Computers -> My Computer -> Distributed Transaction Coordinator -> + // Local DTC -> Transaction List) + } + finally + { + File.Delete(secondEnlistmentRecoveryFilePath); + } + + static void EnlistAndCrash(string propagationTokenText, string resourceManagerIdentifierGuid, string recoveryInformationFilePath) + { + byte[] propagationToken = Convert.FromBase64String(propagationTokenText); + var tx = TransactionInterop.GetTransactionFromTransmitterPropagationToken(propagationToken); + + var crashingEnlistment = new CrashingEnlistment(recoveryInformationFilePath); + tx.EnlistDurable(Guid.Parse(resourceManagerIdentifierGuid), crashingEnlistment, EnlistmentOptions.None); + + // Signal to the main process that we've enlisted and are ready to accept prepare/commit. + using var waitHandle = new EventWaitHandle(initialState: false, EventResetMode.ManualReset, "System.Transactions.Tests.OleTxTests.Recovery"); + waitHandle.Set(); + + // We've enlisted, and set it up so that when the MSDTC tells us to commit, the process will crash. + Thread.Sleep(Timeout); + } + } + + public class CrashingEnlistment : IEnlistmentNotification + { + private string _recoveryInformationFilePath; + + public CrashingEnlistment(string recoveryInformationFilePath) + => _recoveryInformationFilePath = recoveryInformationFilePath; + + public void Prepare(PreparingEnlistment preparingEnlistment) + { + // Received a prepare notification from MSDTC, persist the recovery information so that the main process can perform recovery for it. + File.WriteAllBytes(_recoveryInformationFilePath, preparingEnlistment.RecoveryInformation()); + + preparingEnlistment.Prepared(); + } + + public void Commit(Enlistment enlistment) + => Environment.Exit(42); // 42 is error code expected by RemoteExecutor + + public void Rollback(Enlistment enlistment) + => Environment.Exit(1); + + public void InDoubt(Enlistment enlistment) + => Environment.Exit(1); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] + public void TransmitterPropagationToken() + { + if (RuntimeInformation.ProcessArchitecture == Architecture.X86) + { + return; // Temporarily skip on 32-bit where we have an issue + } + + var tx = new CommittableTransaction(); + + Assert.Equal(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + + var propagationToken = TransactionInterop.GetTransmitterPropagationToken(tx); + + Assert.NotEqual(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + + var tx2 = TransactionInterop.GetTransactionFromTransmitterPropagationToken(propagationToken); + + Assert.Equal(tx.TransactionInformation.DistributedIdentifier, tx2.TransactionInformation.DistributedIdentifier); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))] + public void GetExportCookie() + { + if (RuntimeInformation.ProcessArchitecture == Architecture.X86) + { + return; // Temporarily skip on 32-bit where we have an issue + } + + var tx = new CommittableTransaction(); + + var whereabouts = TransactionInterop.GetWhereabouts(); + + Assert.Equal(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + + var exportCookie = TransactionInterop.GetExportCookie(tx, whereabouts); + + Assert.NotEqual(Guid.Empty, tx.TransactionInformation.DistributedIdentifier); + + var tx2 = TransactionInterop.GetTransactionFromExportCookie(exportCookie); + + Assert.Equal(tx.TransactionInformation.DistributedIdentifier, tx2.TransactionInformation.DistributedIdentifier); + } + + // MSDTC is aynchronous, i.e. Commit/Rollback may return before the transaction has actually completed; + // so allow some time for assertions to succeed. + private static void Retry(Action action) + { + const int Retries = 50; + + for (var i = 0; i < Retries; i++) + { + try + { + action(); + return; + } + catch (EqualException) + { + if (i == Retries - 1) + { + throw; + } + + Thread.Sleep(100); + } + } + } +} diff --git a/src/libraries/System.Transactions.Local/tests/System.Transactions.Local.Tests.csproj b/src/libraries/System.Transactions.Local/tests/System.Transactions.Local.Tests.csproj index a5dda3d5689350..2f5841b9f63d97 100644 --- a/src/libraries/System.Transactions.Local/tests/System.Transactions.Local.Tests.csproj +++ b/src/libraries/System.Transactions.Local/tests/System.Transactions.Local.Tests.csproj @@ -8,6 +8,8 @@ + + @@ -17,4 +19,7 @@ + + + \ No newline at end of file diff --git a/src/libraries/System.Transactions.Local/tests/TestEnlistments.cs b/src/libraries/System.Transactions.Local/tests/TestEnlistments.cs index 800b4aedd8fdbc..039a7cc296c79f 100644 --- a/src/libraries/System.Transactions.Local/tests/TestEnlistments.cs +++ b/src/libraries/System.Transactions.Local/tests/TestEnlistments.cs @@ -2,11 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using System.Reflection; +using System.IO; using System.Threading; -using System.Threading.Tasks; using Xunit; +#nullable enable + namespace System.Transactions.Tests { public enum Phase1Vote { Prepared, ForceRollback, Done }; @@ -91,25 +92,35 @@ public void InDoubt(Enlistment enlistment) public class TestEnlistment : IEnlistmentNotification { - Phase1Vote _phase1Vote; - EnlistmentOutcome _expectedOutcome; - bool _volatileEnlistDuringPrepare; - bool _expectEnlistToSucceed; - AutoResetEvent _outcomeReceived; - Transaction _txToEnlist; + readonly Phase1Vote _phase1Vote; + readonly EnlistmentOutcome _expectedOutcome; + readonly bool _volatileEnlistDuringPrepare; + readonly bool _expectEnlistToSucceed; + readonly AutoResetEvent? _outcomeReceived; + readonly Transaction _txToEnlist; - public TestEnlistment(Phase1Vote phase1Vote, EnlistmentOutcome expectedOutcome, bool volatileEnlistDuringPrepare = false, bool expectEnlistToSucceed = true, AutoResetEvent outcomeReceived = null) + public TestEnlistment( + Phase1Vote phase1Vote, + EnlistmentOutcome expectedOutcome, + bool volatileEnlistDuringPrepare = false, + bool expectEnlistToSucceed = true, + AutoResetEvent? outcomeReceived = null) { _phase1Vote = phase1Vote; _expectedOutcome = expectedOutcome; _volatileEnlistDuringPrepare = volatileEnlistDuringPrepare; _expectEnlistToSucceed = expectEnlistToSucceed; _outcomeReceived = outcomeReceived; - _txToEnlist = Transaction.Current; + _txToEnlist = Transaction.Current!; } + public EnlistmentOutcome? Outcome { get; private set; } + public bool WasPreparedCalled { get; private set; } + public void Prepare(PreparingEnlistment preparingEnlistment) { + WasPreparedCalled = true; + switch (_phase1Vote) { case Phase1Vote.Prepared: @@ -132,19 +143,13 @@ public void Prepare(PreparingEnlistment preparingEnlistment) } case Phase1Vote.ForceRollback: { - if (_outcomeReceived != null) - { - _outcomeReceived.Set(); - } + _outcomeReceived?.Set(); preparingEnlistment.ForceRollback(); break; } case Phase1Vote.Done: { - if (_outcomeReceived != null) - { - _outcomeReceived.Set(); - } + _outcomeReceived?.Set(); preparingEnlistment.Done(); break; } @@ -153,32 +158,76 @@ public void Prepare(PreparingEnlistment preparingEnlistment) public void Commit(Enlistment enlistment) { + Outcome = EnlistmentOutcome.Committed; Assert.Equal(EnlistmentOutcome.Committed, _expectedOutcome); - if (_outcomeReceived != null) - { - _outcomeReceived.Set(); - } + _outcomeReceived?.Set(); enlistment.Done(); } public void Rollback(Enlistment enlistment) { + Outcome = EnlistmentOutcome.Aborted; Assert.Equal(EnlistmentOutcome.Aborted, _expectedOutcome); - if (_outcomeReceived != null) - { - _outcomeReceived.Set(); - } + _outcomeReceived?.Set(); enlistment.Done(); } public void InDoubt(Enlistment enlistment) { + Outcome = EnlistmentOutcome.InDoubt; Assert.Equal(EnlistmentOutcome.InDoubt, _expectedOutcome); - if (_outcomeReceived != null) + _outcomeReceived?.Set(); + enlistment.Done(); + } + } + + public class TestPromotableSinglePhaseEnlistment : IPromotableSinglePhaseNotification + { + private readonly Func? _promoteDelegate; + private EnlistmentOutcome _expectedOutcome; + private AutoResetEvent? _outcomeReceived; + + public bool InitializedCalled { get; private set; } + public bool PromoteCalled { get; private set; } + + public TestPromotableSinglePhaseEnlistment(Func? promoteDelegate, EnlistmentOutcome expectedOutcome, AutoResetEvent? outcomeReceived = null) + { + _promoteDelegate = promoteDelegate; + _expectedOutcome = expectedOutcome; + _outcomeReceived = outcomeReceived; + } + + public void Initialize() + => InitializedCalled = true; + + public byte[]? Promote() + { + PromoteCalled = true; + + if (_promoteDelegate is null) { - _outcomeReceived.Set(); + Assert.Fail("Promote called but no promotion delegate was provided"); } - enlistment.Done(); + + return _promoteDelegate(); + } + + public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment) + { + Assert.Equal(EnlistmentOutcome.Committed, _expectedOutcome); + + _outcomeReceived?.Set(); + + singlePhaseEnlistment.Done(); + } + + public void Rollback(SinglePhaseEnlistment singlePhaseEnlistment) + { + Assert.Equal(EnlistmentOutcome.Aborted, _expectedOutcome); + + _outcomeReceived?.Set(); + + singlePhaseEnlistment.Done(); } } } From 55f5c7c0e0eb1d2a610b600da93c6415d7d46c98 Mon Sep 17 00:00:00 2001 From: Katelyn Gadd Date: Fri, 12 Aug 2022 04:05:27 -0700 Subject: [PATCH 50/68] Introduce a default synchronization context for wasm (#72652) Introduce a JSSynchronizationContext that automatically remotes function calls back to the browser thread and queues them as background jobs. Exercise sync context in threads sample to display the complete progress indicator Clean up an old copy-paste error in the typescript --- ....Runtime.InteropServices.JavaScript.csproj | 4 + .../JavaScript/Interop/JavaScriptExports.cs | 15 ++ .../JavaScript/JSSynchronizationContext.cs | 143 ++++++++++++++++++ .../sample/wasm/browser-threads/Program.cs | 21 ++- src/mono/wasm/runtime/driver.c | 6 +- src/mono/wasm/runtime/managed-exports.ts | 36 +++-- src/mono/wasm/runtime/startup.ts | 1 - src/mono/wasm/runtime/types.ts | 3 + 8 files changed, 200 insertions(+), 29 deletions(-) create mode 100644 src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj index 0605e250b9cea1..353071ad0c5a0c 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System.Runtime.InteropServices.JavaScript.csproj @@ -61,6 +61,8 @@ + + @@ -69,6 +71,8 @@ + + diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs index 7434974ae1319f..3c7ba82f162641 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/Interop/JavaScriptExports.cs @@ -192,6 +192,21 @@ public static void CompleteTask(JSMarshalerArgument* arguments_buffer) } } + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 + // the marshaled signature is: + // void InstallSynchronizationContext() + public static void InstallSynchronizationContext (JSMarshalerArgument* arguments_buffer) { + ref JSMarshalerArgument arg_exc = ref arguments_buffer[0]; // initialized by caller in alloc_stack_frame() + try + { + JSSynchronizationContext.Install(); + } + catch (Exception ex) + { + arg_exc.ToJS(ex); + } + } + [MethodImpl(MethodImplOptions.NoInlining)] // https://github.com/dotnet/runtime/issues/71425 public static void StopProfile() { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs new file mode 100644 index 00000000000000..9c87e4ec351408 --- /dev/null +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/src/System/Runtime/InteropServices/JavaScript/JSSynchronizationContext.cs @@ -0,0 +1,143 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Threading; +using System.Threading.Channels; +using System.Runtime; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using QueueType = System.Threading.Channels.Channel; + +namespace System.Runtime.InteropServices.JavaScript { + /// + /// Provides a thread-safe default SynchronizationContext for the browser that will automatically + /// route callbacks to the main browser thread where they can interact with the DOM and other + /// thread-affinity-having APIs like WebSockets, fetch, WebGL, etc. + /// Callbacks are processed during event loop turns via the runtime's background job system. + /// + internal sealed unsafe class JSSynchronizationContext : SynchronizationContext { + public readonly Thread MainThread; + + internal readonly struct WorkItem { + public readonly SendOrPostCallback Callback; + public readonly object? Data; + public readonly ManualResetEventSlim? Signal; + + public WorkItem (SendOrPostCallback callback, object? data, ManualResetEventSlim? signal) { + Callback = callback; + Data = data; + Signal = signal; + } + } + + private static JSSynchronizationContext? MainThreadSynchronizationContext; + private readonly QueueType Queue; + private readonly Action _DataIsAvailable; + + private JSSynchronizationContext (Thread mainThread) + : this ( + mainThread, + Channel.CreateUnbounded( + new UnboundedChannelOptions { SingleWriter = false, SingleReader = true, AllowSynchronousContinuations = true } + ) + ) + { + } + + private JSSynchronizationContext (Thread mainThread, QueueType queue) { + MainThread = mainThread; + Queue = queue; + _DataIsAvailable = DataIsAvailable; + } + + public override SynchronizationContext CreateCopy () { + return new JSSynchronizationContext(MainThread, Queue); + } + + private void AwaitNewData () { + var vt = Queue.Reader.WaitToReadAsync(); + if (vt.IsCompleted) { + DataIsAvailable(); + return; + } + + // Once data is added to the queue, vt will be completed on the thread that added the data + // because we created the channel with AllowSynchronousContinuations = true. We want to + // fire a callback that will schedule a background job to pump the queue on the main thread. + var awaiter = vt.AsTask().ConfigureAwait(false).GetAwaiter(); + // UnsafeOnCompleted avoids spending time flowing the execution context (we don't need it.) + awaiter.UnsafeOnCompleted(_DataIsAvailable); + } + + private void DataIsAvailable () { + // While we COULD pump here, we don't want to. We want the pump to happen on the next event loop turn. + // Otherwise we could get a chain where a pump generates a new work item and that makes us pump again, forever. + ScheduleBackgroundJob((void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler); + } + + public override void Post (SendOrPostCallback d, object? state) { + var workItem = new WorkItem(d, state, null); + if (!Queue.Writer.TryWrite(workItem)) + throw new Exception("Internal error"); + } + + // This path can only run when threading is enabled + #pragma warning disable CA1416 + + public override void Send (SendOrPostCallback d, object? state) { + if (Thread.CurrentThread == MainThread) { + d(state); + return; + } + + using (var signal = new ManualResetEventSlim(false)) { + var workItem = new WorkItem(d, state, signal); + if (!Queue.Writer.TryWrite(workItem)) + throw new Exception("Internal error"); + + signal.Wait(); + } + } + + internal static void Install () { + if (MainThreadSynchronizationContext == null) + MainThreadSynchronizationContext = new JSSynchronizationContext(Thread.CurrentThread); + + SynchronizationContext.SetSynchronizationContext(MainThreadSynchronizationContext); + MainThreadSynchronizationContext.AwaitNewData(); + } + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + internal static extern unsafe void ScheduleBackgroundJob(void* callback); + +#pragma warning disable CS3016 // Arrays as attribute arguments is not CLS-compliant + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] + private static unsafe void BackgroundJobHandler () { + MainThreadSynchronizationContext!.Pump(); + } + + [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] + private static unsafe void RequestPumpCallback () { + ScheduleBackgroundJob((void*)(delegate* unmanaged[Cdecl])&BackgroundJobHandler); + } + + private void Pump () { + try { + while (Queue.Reader.TryRead(out var item)) { + try { + item.Callback(item.Data); + // While we would ideally have a catch block here and do something to dispatch/forward unhandled + // exceptions, the standard threadpool (and thus standard synchronizationcontext) have zero + // error handling, so for consistency with them we do nothing. Don't throw in SyncContext callbacks. + } finally { + item.Signal?.Set(); + } + } + } finally { + // If an item throws, we want to ensure that the next pump gets scheduled appropriately regardless. + AwaitNewData(); + } + } + } +} diff --git a/src/mono/sample/wasm/browser-threads/Program.cs b/src/mono/sample/wasm/browser-threads/Program.cs index e3ce64b19cc38f..f836fdff4fcf3c 100644 --- a/src/mono/sample/wasm/browser-threads/Program.cs +++ b/src/mono/sample/wasm/browser-threads/Program.cs @@ -30,9 +30,18 @@ public static void Start(int n) { var comp = new ExpensiveComputation(n); comp.Start(); + #pragma warning disable CS4014 + WaitForCompletion(comp); _demo = new Demo(UpdateProgress, comp); } + public static async Task WaitForCompletion (ExpensiveComputation comp) { + Console.WriteLine($"WaitForCompletion started on thread {Thread.CurrentThread.ManagedThreadId}"); + await comp.Completion; + Console.WriteLine($"WaitForCompletion completed on thread {Thread.CurrentThread.ManagedThreadId}"); + UpdateProgress("✌︎"); + } + [JSExport] public static int Progress() { @@ -135,16 +144,12 @@ public Demo(Action updateProgress, ExpensiveComputation comp) public bool Progress() { - _animation.Step($"{_expensiveComputation.CallCounter} calls"); - if (_expensiveComputation.Completion.IsCompleted) - { - _updateProgress("✌︎"); - return true; - } - else + if (!_expensiveComputation.Completion.IsCompleted) { - return false; + _animation.Step($"{_expensiveComputation.CallCounter} calls"); } + + return _expensiveComputation.Completion.IsCompleted; } public int Result => _expensiveComputation.Completion.Result; diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index 0ddc467ae5966f..e2db123f7ab5f2 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -449,6 +449,9 @@ get_native_to_interp (MonoMethod *method, void *extra_arg) return addr; } +typedef void (*background_job_cb)(void); +void mono_threads_schedule_background_job (background_job_cb cb); + void mono_initialize_internals () { // Blazor specific custom routines - see dotnet_support.js for backing code @@ -458,6 +461,7 @@ void mono_initialize_internals () core_initialize_internals(); #endif + mono_add_internal_call ("System.Runtime.InteropServices.JavaScript.JSSynchronizationContext::ScheduleBackgroundJob", mono_threads_schedule_background_job); } EMSCRIPTEN_KEEPALIVE void @@ -542,7 +546,7 @@ mono_wasm_load_runtime (const char *unused, int debug_level) mono_dl_fallback_register (wasm_dl_load, wasm_dl_symbol, NULL, NULL); mono_wasm_install_get_native_to_interp_tramp (get_native_to_interp); - + #ifdef GEN_PINVOKE mono_wasm_install_interp_to_native_callback (mono_wasm_interp_to_native_callback); #endif diff --git a/src/mono/wasm/runtime/managed-exports.ts b/src/mono/wasm/runtime/managed-exports.ts index 8934a2844b77a0..bbd48aa9e30140 100644 --- a/src/mono/wasm/runtime/managed-exports.ts +++ b/src/mono/wasm/runtime/managed-exports.ts @@ -3,28 +3,12 @@ import { GCHandle, MarshalerToCs, MarshalerToJs, MonoMethod, mono_assert } from "./types"; import cwraps from "./cwraps"; -import { Module, runtimeHelpers } from "./imports"; +import { Module, runtimeHelpers, ENVIRONMENT_IS_PTHREAD } from "./imports"; import { alloc_stack_frame, get_arg, get_arg_gc_handle, MarshalerType, set_arg_type, set_gc_handle } from "./marshal"; import { invoke_method_and_handle_exception } from "./invoke-cs"; import { marshal_array_to_cs_impl, marshal_exception_to_cs, marshal_intptr_to_cs } from "./marshal-to-cs"; import { marshal_int32_to_js, marshal_task_to_js } from "./marshal-to-js"; -// in all the exported internals methods, we use the same data structures for stack frame as normal full blow interop -// see src\libraries\System.Runtime.InteropServices.JavaScript\src\System\Runtime\InteropServices\JavaScript\Interop\JavaScriptExports.cs -export interface JavaScriptExports { - // the marshaled signature is: void ReleaseJSOwnedObjectByGCHandle(GCHandle gcHandle) - release_js_owned_object_by_gc_handle(gc_handle: GCHandle): void; - // the marshaled signature is: GCHandle CreateTaskCallback() - create_task_callback(): GCHandle; - // the marshaled signature is: void CompleteTask(GCHandle holder, Exception? exceptionResult, T? result) - complete_task(holder_gc_handle: GCHandle, error?: any, data?: any, res_converter?: MarshalerToCs): void; - // the marshaled signature is: TRes? CallDelegate(GCHandle callback, T1? arg1, T2? arg2, T3? arg3) - call_delegate(callback_gc_handle: GCHandle, arg1_js: any, arg2_js: any, arg3_js: any, - res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs, arg3_converter?: MarshalerToCs): any; - // the marshaled signature is: Task? CallEntrypoint(MonoMethod* entrypointPtr, string[] args) - call_entry_point(entry_point: MonoMethod, args?: string[]): Promise; -} - export function init_managed_exports(): void { const anyModule = Module as any; const exports_fqn_asm = "System.Runtime.InteropServices.JavaScript"; @@ -38,7 +22,8 @@ export function init_managed_exports(): void { if (!runtimeHelpers.runtime_interop_exports_class) throw "Can't find " + runtimeHelpers.runtime_interop_namespace + "." + runtimeHelpers.runtime_interop_exports_classname + " class"; - + const install_sync_context = get_method("InstallSynchronizationContext"); + mono_assert(install_sync_context, "Can't find InstallSynchronizationContext method"); const call_entry_point = get_method("CallEntrypoint"); mono_assert(call_entry_point, "Can't find CallEntrypoint method"); const release_js_owned_object_by_gc_handle_method = get_method("ReleaseJSOwnedObjectByGCHandle"); @@ -149,6 +134,19 @@ export function init_managed_exports(): void { anyModule.stackRestore(sp); } }; + runtimeHelpers.javaScriptExports.install_synchronization_context = () => { + const sp = anyModule.stackSave(); + try { + const args = alloc_stack_frame(2); + invoke_method_and_handle_exception(install_sync_context, args); + } finally { + anyModule.stackRestore(sp); + } + }; + + if (!ENVIRONMENT_IS_PTHREAD) + // Install our sync context so that async continuations will migrate back to this thread (the main thread) automatically + runtimeHelpers.javaScriptExports.install_synchronization_context(); } export function get_method(method_name: string): MonoMethod { @@ -156,4 +154,4 @@ export function get_method(method_name: string): MonoMethod { if (!res) throw "Can't find method " + runtimeHelpers.runtime_interop_namespace + "." + runtimeHelpers.runtime_interop_exports_classname + "." + method_name; return res; -} \ No newline at end of file +} diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 9ee66858a7ea75..f97655bf6b3a57 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -454,7 +454,6 @@ export function bindings_init(): void { initialize_marshalers_to_js(); initialize_marshalers_to_cs(); runtimeHelpers._i52_error_scratch_buffer = Module._malloc(4); - } catch (err) { _print_error("MONO_WASM: Error in bindings_init", err); throw err; diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index b7082b21f41500..a42f7a1f81d9e0 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -361,6 +361,9 @@ export interface JavaScriptExports { // the marshaled signature is: Task? CallEntrypoint(MonoMethod* entrypointPtr, string[] args) call_entry_point(entry_point: MonoMethod, args?: string[]): Promise; + + // the marshaled signature is: void InstallSynchronizationContext() + install_synchronization_context(): void; } export type MarshalerToJs = (arg: JSMarshalerArgument, sig?: JSMarshalerType, res_converter?: MarshalerToJs, arg1_converter?: MarshalerToCs, arg2_converter?: MarshalerToCs) => any; From 0a173d12930122c07a326c7bd3457b22c0fa48b0 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Fri, 12 Aug 2022 07:58:52 -0400 Subject: [PATCH 51/68] [wasm] Disable some tests on NodeJS/Windows (#73834) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Disable some tests on NodeJS/Windows - Introduce NodeJS platform in PlatformDetection. Co-authored-by: Marek Fišera --- .../tests/TestUtilities/System/PlatformDetection.cs | 9 +++++++++ .../tests/System/CodeDom/Compiler/CodeCompilerTests.cs | 8 ++++++++ .../tests/CtorsDelimiterTests.cs | 1 + .../tests/XmlWriterTraceListenerTests.cs | 1 + .../tests/BasicScenarioTests.cs | 1 + src/mono/wasm/test-main.js | 3 +++ 6 files changed, 23 insertions(+) diff --git a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs index 8851446e6b2b5f..449f9900c33d3a 100644 --- a/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs +++ b/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs @@ -106,6 +106,7 @@ public static partial class PlatformDetection public static bool IsWebSocketSupported => IsEnvironmentVariableTrue("IsWebSocketSupported"); public static bool IsNodeJS => IsEnvironmentVariableTrue("IsNodeJS"); public static bool IsNotNodeJS => !IsNodeJS; + public static bool IsNodeJSOnWindows => GetNodeJSPlatform() == "win32"; public static bool LocalEchoServerIsNotAvailable => !LocalEchoServerIsAvailable; public static bool LocalEchoServerIsAvailable => IsBrowser; @@ -593,6 +594,14 @@ private static bool IsEnvironmentVariableTrue(string variableName) return (val != null && val == "true"); } + private static string GetNodeJSPlatform() + { + if (!IsNodeJS) + return null; + + return Environment.GetEnvironmentVariable("NodeJSPlatform"); + } + private static bool AssemblyConfigurationEquals(string configuration) { AssemblyConfigurationAttribute assemblyConfigurationAttribute = typeof(string).Assembly.GetCustomAttribute(); diff --git a/src/libraries/System.CodeDom/tests/System/CodeDom/Compiler/CodeCompilerTests.cs b/src/libraries/System.CodeDom/tests/System/CodeDom/Compiler/CodeCompilerTests.cs index 7a53cd95b1d9eb..244931f1f5876b 100644 --- a/src/libraries/System.CodeDom/tests/System/CodeDom/Compiler/CodeCompilerTests.cs +++ b/src/libraries/System.CodeDom/tests/System/CodeDom/Compiler/CodeCompilerTests.cs @@ -55,6 +55,7 @@ public void CompileAssemblyFromDom_NullOptions_ThrowsArgumentNullException() [Theory] [MemberData(nameof(CodeCompileUnit_TestData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73721", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJSOnWindows))] public void FromDom_ValidCodeCompileUnit_ReturnsExpected(CodeCompileUnit compilationUnit) { var compiler = new StubCompiler(); @@ -66,6 +67,7 @@ public void FromDom_ValidCodeCompileUnit_ReturnsExpected(CodeCompileUnit compila [Theory] [MemberData(nameof(CodeCompileUnit_TestData))] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73721", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJSOnWindows))] public void FromDom_ValidCodeCompileUnit_ThrowsPlatformNotSupportedException(CodeCompileUnit compilationUnit) { var compiler = new Compiler(); @@ -361,6 +363,7 @@ public void CompileAssemblyFromSource_NullOptions_ThrowsArgumentNullException() } [Theory] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73721", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJSOnWindows))] [MemberData(nameof(Source_TestData))] public void FromSource_ValidSource_ReturnsExpected(string source) { @@ -371,6 +374,7 @@ public void FromSource_ValidSource_ReturnsExpected(string source) [Theory] [MemberData(nameof(Source_TestData))] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73721", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJSOnWindows))] public void FromSource_ValidSource_ThrowsPlatformNotSupportedException(string source) { var compiler = new Compiler(); @@ -394,6 +398,7 @@ public static IEnumerable Sources_TestData() [Theory] [MemberData(nameof(Sources_TestData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73721", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJSOnWindows))] public void CompileAssemblyFromSourceBatch_ValidSources_ReturnsExpected(string[] sources) { ICodeCompiler compiler = new StubCompiler(); @@ -425,6 +430,7 @@ public void CompileAssemblyFromSourceBatch_NullSources_ThrowsArgumentNullExcepti [Theory] [MemberData(nameof(Sources_TestData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73721", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJSOnWindows))] public void FromSourceBatch_ValidSources_ReturnsExpected(string[] sources) { var compiler = new StubCompiler(); @@ -434,6 +440,7 @@ public void FromSourceBatch_ValidSources_ReturnsExpected(string[] sources) [Theory] [MemberData(nameof(Sources_TestData))] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework)] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73721", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJSOnWindows))] public void FromSourceBatch_ValidSources_ThrowsPlatformNotSupportedException(string[] sources) { var compiler = new Compiler(); @@ -459,6 +466,7 @@ public void FromSourceBatch_NullSources_ThrowsArgumentNullException() [InlineData("")] [InlineData("cmdArgs")] [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Occasionally fails in .NET framework, probably caused from a very edge case bug in .NET Framework which we will not be fixing")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73721", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJSOnWindows))] public void GetResponseFileCmdArgs_ValidCmdArgs_ReturnsExpected(string cmdArgs) { var compiler = new Compiler(); diff --git a/src/libraries/System.Diagnostics.TextWriterTraceListener/tests/CtorsDelimiterTests.cs b/src/libraries/System.Diagnostics.TextWriterTraceListener/tests/CtorsDelimiterTests.cs index 352d0d1a6b29cb..cb4059bc43e72c 100644 --- a/src/libraries/System.Diagnostics.TextWriterTraceListener/tests/CtorsDelimiterTests.cs +++ b/src/libraries/System.Diagnostics.TextWriterTraceListener/tests/CtorsDelimiterTests.cs @@ -114,6 +114,7 @@ public void TestConstructorWithFileName() [Theory] [MemberData(nameof(TestNames))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73721", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJSOnWindows))] public void TestConstructorWithFileNameAndName(string testName) { var target = new DelimitedListTraceListener(Path.GetTempFileName(), testName); diff --git a/src/libraries/System.Diagnostics.TextWriterTraceListener/tests/XmlWriterTraceListenerTests.cs b/src/libraries/System.Diagnostics.TextWriterTraceListener/tests/XmlWriterTraceListenerTests.cs index 20576cc9190b91..46638320c0cdb0 100644 --- a/src/libraries/System.Diagnostics.TextWriterTraceListener/tests/XmlWriterTraceListenerTests.cs +++ b/src/libraries/System.Diagnostics.TextWriterTraceListener/tests/XmlWriterTraceListenerTests.cs @@ -44,6 +44,7 @@ public static IEnumerable ConstructorsTestData() [Theory] [MemberData(nameof(ConstructorsTestData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73721", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJSOnWindows))] public void SingleArgumentConstructorTest(XmlWriterTraceListener listener, string expectedName) { Assert.Equal(expectedName, listener.Name); diff --git a/src/libraries/System.ServiceModel.Syndication/tests/BasicScenarioTests.cs b/src/libraries/System.ServiceModel.Syndication/tests/BasicScenarioTests.cs index f1269d75e0a2d5..92fbf89181261d 100644 --- a/src/libraries/System.ServiceModel.Syndication/tests/BasicScenarioTests.cs +++ b/src/libraries/System.ServiceModel.Syndication/tests/BasicScenarioTests.cs @@ -177,6 +177,7 @@ public static void SyndicationFeed_Load_Write_Atom_Feed_() } [Fact] + [ActiveIssue("https://github.com/dotnet/runtime/issues/73721", typeof(PlatformDetection), nameof(PlatformDetection.IsNodeJSOnWindows))] public static void SyndicationFeed_Write_RSS_Atom() { string RssPath = Path.GetTempFileName(); diff --git a/src/mono/wasm/test-main.js b/src/mono/wasm/test-main.js index ac8dd0016a119c..eba90e542a172f 100644 --- a/src/mono/wasm/test-main.js +++ b/src/mono/wasm/test-main.js @@ -398,6 +398,9 @@ Promise.all([argsPromise, loadDotnetPromise]).then(async ([runArgs, createDotnet // Must be after loading npm modules. config.environmentVariables["IsWebSocketSupported"] = ("WebSocket" in globalThis).toString().toLowerCase(); + if (is_node) { + config.environmentVariables["NodeJSPlatform"] = process.platform; + } }, preRun: () => { if (!runArgs.enableGC) { From 16f1d26228c6134dabce4372569f027c11b2a592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 12 Aug 2022 21:50:45 +0900 Subject: [PATCH 52/68] Prevent devirtualization into unallocated types (#73839) If we were in a situation like in the regression test, we would devirtualize the `GrabObject` call to `SomeUnallocatedClass.GrabObject`. But because `SomeUnallocatedClass` was never allocated, the scanner didn't scan it, and bad things would happen. Prevent devirtualizing into types that were not seen as allocated. This is not a real issue for non-generic (non-shareable) types because the tentative instance method optimization generates throwing bodies for these. But tentative method optimization doesn't run for shared generics: https://github.com/dotnet/runtime/blob/4cbe6f99d23e04c56a89251d49de1b0f14000427/src/coreclr/tools/Common/Compiler/MethodExtensions.cs#L115 This was rare enough that we haven't seen it until I did #73683 and there was one useless constructor that we stopped generating and triggered this. This also includes what is essentially a rollback of https://github.com/dotnet/runtimelab/pull/1700. This should have been rolled back with https://github.com/dotnet/runtime/pull/66145 but I forgot we had this. It was not needed. * Update tests.proj --- .../ILCompiler.Compiler/Compiler/ILScanner.cs | 25 +++++-------------- src/libraries/tests.proj | 1 + .../SmokeTests/UnitTests/Devirtualization.cs | 25 +++++++++++++++++++ 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs index c2caeb24b3341f..da72ea35cb05b0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs @@ -365,8 +365,8 @@ public override DictionaryLayoutNode GetLayout(TypeSystemEntity methodOrType) private class ScannedDevirtualizationManager : DevirtualizationManager { private HashSet _constructedTypes = new HashSet(); + private HashSet _canonConstructedTypes = new HashSet(); private HashSet _unsealedTypes = new HashSet(); - private HashSet _abstractButNonabstractlyOverriddenTypes = new HashSet(); public ScannedDevirtualizationManager(ImmutableArray> markedNodes) { @@ -390,16 +390,13 @@ public ScannedDevirtualizationManager(ImmutableArray + diff --git a/src/tests/nativeaot/SmokeTests/UnitTests/Devirtualization.cs b/src/tests/nativeaot/SmokeTests/UnitTests/Devirtualization.cs index 73e3ab4d3064fb..5954c6118e6696 100644 --- a/src/tests/nativeaot/SmokeTests/UnitTests/Devirtualization.cs +++ b/src/tests/nativeaot/SmokeTests/UnitTests/Devirtualization.cs @@ -12,6 +12,7 @@ internal static int Run() { RegressionBug73076.Run(); DevirtualizationCornerCaseTests.Run(); + DevirtualizeIntoUnallocatedGenericType.Run(); return 100; } @@ -111,4 +112,28 @@ public static void Run() TestIntf2((IIntf2)Activator.CreateInstance(typeof(Intf2Impl2<>).MakeGenericType(typeof(object))), 456); } } + + class DevirtualizeIntoUnallocatedGenericType + { + class Never { } + + class SomeGeneric + { + public virtual object GrabObject() => null; + } + + sealed class SomeUnallocatedClass : SomeGeneric + { + public override object GrabObject() => new Never(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static SomeUnallocatedClass GrabInst() => null; + + public static void Run() + { + if (GrabInst() != null) + GrabInst().GrabObject(); + } + } } From ae24786fbffbbac5b0f370cf316f2f2633285ac2 Mon Sep 17 00:00:00 2001 From: Caleb Cornett Date: Fri, 12 Aug 2022 08:52:54 -0400 Subject: [PATCH 53/68] Add FEATURE_READONLY_GS_COOKIE for NativeAOT (#73744) --- src/coreclr/nativeaot/Runtime/CMakeLists.txt | 1 + src/coreclr/nativeaot/Runtime/startup.cpp | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/coreclr/nativeaot/Runtime/CMakeLists.txt b/src/coreclr/nativeaot/Runtime/CMakeLists.txt index 7feb64dc6b59f8..4ccefca81f5328 100644 --- a/src/coreclr/nativeaot/Runtime/CMakeLists.txt +++ b/src/coreclr/nativeaot/Runtime/CMakeLists.txt @@ -223,6 +223,7 @@ if(WIN32) add_definitions(-DFEATURE_EVENT_TRACE) add_definitions(-DFEATURE_SUSPEND_REDIRECTION) else() + add_definitions(-DFEATURE_READONLY_GS_COOKIE) add_definitions(-DNO_UI_ASSERT) include(unix/configure.cmake) include_directories(${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/coreclr/nativeaot/Runtime/startup.cpp b/src/coreclr/nativeaot/Runtime/startup.cpp index 2d8d3b2b2baa29..20caf9d03e5da2 100644 --- a/src/coreclr/nativeaot/Runtime/startup.cpp +++ b/src/coreclr/nativeaot/Runtime/startup.cpp @@ -66,6 +66,9 @@ static bool InitGSCookie(); //----------------------------------------------------------------------------- // GSCookies (guard-stack cookies) for detecting buffer overruns //----------------------------------------------------------------------------- +typedef size_t GSCookie; + +#ifdef FEATURE_READONLY_GS_COOKIE #ifdef __APPLE__ #define READONLY_ATTR_ARGS section("__DATA,__const") @@ -74,14 +77,15 @@ static bool InitGSCookie(); #endif #define READONLY_ATTR __attribute__((READONLY_ATTR_ARGS)) -// Guard-stack cookie for preventing against stack buffer overruns -typedef size_t GSCookie; - // const is so that it gets placed in the .text section (which is read-only) // volatile is so that accesses to it do not get optimized away because of the const // extern "C" volatile READONLY_ATTR const GSCookie __security_cookie = 0; +#else +extern "C" volatile GSCookie __security_cookie = 0; +#endif // FEATURE_READONLY_GS_COOKIE + #endif // TARGET_UNIX static bool InitDLL(HANDLE hPalInstance) @@ -312,11 +316,13 @@ bool InitGSCookie() { volatile GSCookie * pGSCookiePtr = GetProcessGSCookiePtr(); +#ifdef FEATURE_READONLY_GS_COOKIE // The GS cookie is stored in a read only data segment if (!PalVirtualProtect((void*)pGSCookiePtr, sizeof(GSCookie), PAGE_READWRITE)) { return false; } +#endif // REVIEW: Need something better for PAL... GSCookie val = (GSCookie)PalGetTickCount64(); @@ -328,7 +334,11 @@ bool InitGSCookie() *pGSCookiePtr = val; +#ifdef FEATURE_READONLY_GS_COOKIE return PalVirtualProtect((void*)pGSCookiePtr, sizeof(GSCookie), PAGE_READONLY); +#else + return true; +#endif } #endif // TARGET_UNIX From 1ae7c5f816b9910beaea67c45bb3681e8630662d Mon Sep 17 00:00:00 2001 From: Victor Irzak <6209775+virzak@users.noreply.github.com> Date: Fri, 12 Aug 2022 09:21:32 -0400 Subject: [PATCH 54/68] Handle FileStream.Length for devices (#73708) Co-authored-by: Theodore Tsirpanis Co-authored-by: Adam Sitnik Co-authored-by: Jan Kotas --- .../Kernel32/Interop.DeviceIoControl.cs | 9 +++-- .../Kernel32/Interop.STORAGE_READ_CAPACITY.cs | 22 ++++++++++++ .../tests/RandomAccess/GetLength.cs | 17 +++++++++ .../SafeHandles/SafeFileHandle.Windows.cs | 36 +++++++++++++++++-- .../System.Private.CoreLib.Shared.projitems | 3 ++ .../src/System/IO/FileSystem.Windows.cs | 23 +++++++----- 6 files changed, 96 insertions(+), 14 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Windows/Kernel32/Interop.STORAGE_READ_CAPACITY.cs diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.DeviceIoControl.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.DeviceIoControl.cs index 1d202087e5de07..aae40b9bb7fddc 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.DeviceIoControl.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.DeviceIoControl.cs @@ -12,14 +12,17 @@ internal static partial class Kernel32 // https://docs.microsoft.com/windows/win32/api/winioctl/ni-winioctl-fsctl_get_reparse_point internal const int FSCTL_GET_REPARSE_POINT = 0x000900a8; + // https://docs.microsoft.com/windows-hardware/drivers/ddi/ntddstor/ni-ntddstor-ioctl_storage_read_capacity + internal const int IOCTL_STORAGE_READ_CAPACITY = 0x002D5140; + [LibraryImport(Libraries.Kernel32, EntryPoint = "DeviceIoControl", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool DeviceIoControl( + internal static unsafe partial bool DeviceIoControl( SafeHandle hDevice, uint dwIoControlCode, - IntPtr lpInBuffer, + void* lpInBuffer, uint nInBufferSize, - byte[] lpOutBuffer, + void* lpOutBuffer, uint nOutBufferSize, out uint lpBytesReturned, IntPtr lpOverlapped); diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.STORAGE_READ_CAPACITY.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.STORAGE_READ_CAPACITY.cs new file mode 100644 index 00000000000000..49d3af39f9b48f --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.STORAGE_READ_CAPACITY.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Kernel32 + { + // https://docs.microsoft.com/en-us/windows/win32/devio/storage-read-capacity + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct STORAGE_READ_CAPACITY + { + internal uint Version; + internal uint Size; + internal uint BlockLength; + internal long NumberOfBlocks; + internal long DiskLength; + } + } +} diff --git a/src/libraries/System.IO.FileSystem/tests/RandomAccess/GetLength.cs b/src/libraries/System.IO.FileSystem/tests/RandomAccess/GetLength.cs index 1c21748f0198e8..0bb405c487f02c 100644 --- a/src/libraries/System.IO.FileSystem/tests/RandomAccess/GetLength.cs +++ b/src/libraries/System.IO.FileSystem/tests/RandomAccess/GetLength.cs @@ -36,5 +36,22 @@ public void ReturnsExactSizeForNonEmptyFiles(FileOptions options) Assert.Equal(fileSize, RandomAccess.GetLength(handle)); } } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsWindowsAndElevated))] + [MemberData(nameof(GetSyncAsyncOptions))] + public void ReturnsActualLengthForDevices(FileOptions options) + { + // both File.Exists and Path.Exists return false when "\\?\PhysicalDrive0" exists + // that is why we just try and swallow the exception when it occurs + try + { + using (SafeFileHandle handle = File.OpenHandle(@"\\?\PhysicalDrive0", FileMode.Open, options: options)) + { + long length = RandomAccess.GetLength(handle); + Assert.True(length > 0); + } + } + catch (FileNotFoundException) { } + } } } diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs index 5223557f5143f9..bbf81283719a17 100644 --- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs @@ -6,6 +6,7 @@ using System.IO; using System.Runtime.InteropServices; using System.Threading; +using System.Buffers; namespace Microsoft.Win32.SafeHandles { @@ -286,12 +287,43 @@ unsafe long GetFileLengthCore() { Interop.Kernel32.FILE_STANDARD_INFO info; - if (!Interop.Kernel32.GetFileInformationByHandleEx(this, Interop.Kernel32.FileStandardInfo, &info, (uint)sizeof(Interop.Kernel32.FILE_STANDARD_INFO))) + if (Interop.Kernel32.GetFileInformationByHandleEx(this, Interop.Kernel32.FileStandardInfo, &info, (uint)sizeof(Interop.Kernel32.FILE_STANDARD_INFO))) + { + return info.EndOfFile; + } + + // In theory when GetFileInformationByHandleEx fails, then + // a) IsDevice can modify last error (not true today, but can be in the future), + // b) DeviceIoControl can succeed (last error set to ERROR_SUCCESS) but return fewer bytes than requested. + // The error is stored and in such cases exception for the first failure is going to be thrown. + int lastError = Marshal.GetLastWin32Error(); + + if (Path is null || !PathInternal.IsDevice(Path)) + { + throw Win32Marshal.GetExceptionForWin32Error(lastError, Path); + } + + Interop.Kernel32.STORAGE_READ_CAPACITY storageReadCapacity; + bool success = Interop.Kernel32.DeviceIoControl( + this, + dwIoControlCode: Interop.Kernel32.IOCTL_STORAGE_READ_CAPACITY, + lpInBuffer: null, + nInBufferSize: 0, + lpOutBuffer: &storageReadCapacity, + nOutBufferSize: (uint)sizeof(Interop.Kernel32.STORAGE_READ_CAPACITY), + out uint bytesReturned, + IntPtr.Zero); + + if (!success) { throw Win32Marshal.GetExceptionForLastWin32Error(Path); } + else if (bytesReturned != sizeof(Interop.Kernel32.STORAGE_READ_CAPACITY)) + { + throw Win32Marshal.GetExceptionForWin32Error(lastError, Path); + } - return info.EndOfFile; + return storageReadCapacity.DiskLength; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index d025ab28e0e7c3..6f3a16e6136363 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -1720,6 +1720,9 @@ Common\Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs + + Common\Interop\Windows\Kernel32\Interop.STORAGE_READ_CAPACITY.cs + Common\Interop\Windows\Interop.UNICODE_STRING.cs diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs index fbea595b861f3d..22d723cf1bec3b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileSystem.Windows.cs @@ -574,15 +574,20 @@ internal static void CreateSymbolicLink(string path, string pathToTarget, bool i byte[] buffer = ArrayPool.Shared.Rent(Interop.Kernel32.MAXIMUM_REPARSE_DATA_BUFFER_SIZE); try { - bool success = Interop.Kernel32.DeviceIoControl( - handle, - dwIoControlCode: Interop.Kernel32.FSCTL_GET_REPARSE_POINT, - lpInBuffer: IntPtr.Zero, - nInBufferSize: 0, - lpOutBuffer: buffer, - nOutBufferSize: Interop.Kernel32.MAXIMUM_REPARSE_DATA_BUFFER_SIZE, - out _, - IntPtr.Zero); + bool success; + + fixed (byte* pBuffer = buffer) + { + success = Interop.Kernel32.DeviceIoControl( + handle, + dwIoControlCode: Interop.Kernel32.FSCTL_GET_REPARSE_POINT, + lpInBuffer: null, + nInBufferSize: 0, + lpOutBuffer: pBuffer, + nOutBufferSize: Interop.Kernel32.MAXIMUM_REPARSE_DATA_BUFFER_SIZE, + out _, + IntPtr.Zero); + } if (!success) { From 820ffd46ba476a023b45e040e0b909f50d99d613 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Fri, 12 Aug 2022 16:54:02 +0200 Subject: [PATCH 55/68] Tar: restore directory permissions while extracting. (#72078) * Tar: restore directory permissions while extracting. * PR feedback. * On Windows, as on Unix: don't set group/other write permission by default. * Fix Windows compilation. * Fix Windows compilation II. * Update test for Windows. * Fix test WindowsFileMode value. * Remove branch. * Apply suggestions from code review Co-authored-by: Eric Erhardt * Add back DefaultWindowsMode. * Fix build failure in TarWriter due to DefaultWindowsMode usage. * Fix DefaultWindowsMode. * Apply suggestions from code review Co-authored-by: Carlos Sanchez <1175054+carlossanlop@users.noreply.github.com> Co-authored-by: Eric Erhardt Co-authored-by: Carlos Sanchez <1175054+carlossanlop@users.noreply.github.com> --- .../src/System.Formats.Tar.csproj | 2 + .../src/System/Formats/Tar/TarEntry.cs | 27 +++- .../src/System/Formats/Tar/TarFile.cs | 35 ++-- .../src/System/Formats/Tar/TarHelpers.Unix.cs | 149 ++++++++++++++++++ .../System/Formats/Tar/TarHelpers.Windows.cs | 22 +++ .../src/System/Formats/Tar/TarHelpers.cs | 4 +- .../System/Formats/Tar/TarWriter.Windows.cs | 4 +- .../TarEntry.ExtractToFile.Tests.Unix.cs | 3 + .../TarEntry/TarEntry.ExtractToFile.Tests.cs | 15 ++ .../TarEntry.ExtractToFileAsync.Tests.cs | 13 ++ .../TarFile.CreateFromDirectory.File.Tests.cs | 36 ++++- ...ile.CreateFromDirectoryAsync.File.Tests.cs | 121 ++++++++------ .../TarFile.ExtractToDirectory.File.Tests.cs | 114 ++++++++++++++ ...File.ExtractToDirectoryAsync.File.Tests.cs | 90 +++++++++++ .../System.Formats.Tar/tests/TarTestsBase.cs | 85 +++++++++- .../TarWriter/TarWriter.File.Base.Windows.cs | 5 +- 16 files changed, 646 insertions(+), 79 deletions(-) create mode 100644 src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Unix.cs create mode 100644 src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Windows.cs diff --git a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj index 7d122684fcef9f..c0487caf28049e 100644 --- a/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj +++ b/src/libraries/System.Formats.Tar/src/System.Formats.Tar.csproj @@ -38,6 +38,7 @@ + @@ -51,6 +52,7 @@ + diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs index ea965322d78095..00c790762e4b65 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarEntry.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Threading; @@ -280,24 +281,24 @@ public Stream? DataStream internal abstract bool IsDataStreamSetterSupported(); // Extracts the current entry to a location relative to the specified directory. - internal void ExtractRelativeToDirectory(string destinationDirectoryPath, bool overwrite) + internal void ExtractRelativeToDirectory(string destinationDirectoryPath, bool overwrite, SortedDictionary? pendingModes) { (string fileDestinationPath, string? linkTargetPath) = GetDestinationAndLinkPaths(destinationDirectoryPath); if (EntryType == TarEntryType.Directory) { - Directory.CreateDirectory(fileDestinationPath); + TarHelpers.CreateDirectory(fileDestinationPath, Mode, overwrite, pendingModes); } else { // If it is a file, create containing directory. - Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!); + TarHelpers.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!, mode: null, overwrite, pendingModes); ExtractToFileInternal(fileDestinationPath, linkTargetPath, overwrite); } } // Asynchronously extracts the current entry to a location relative to the specified directory. - internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, bool overwrite, CancellationToken cancellationToken) + internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, bool overwrite, SortedDictionary? pendingModes, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -308,13 +309,13 @@ internal Task ExtractRelativeToDirectoryAsync(string destinationDirectoryPath, b if (EntryType == TarEntryType.Directory) { - Directory.CreateDirectory(fileDestinationPath); + TarHelpers.CreateDirectory(fileDestinationPath, Mode, overwrite, pendingModes); return Task.CompletedTask; } else { // If it is a file, create containing directory. - Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!); + TarHelpers.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!, mode: null, overwrite, pendingModes); return ExtractToFileInternalAsync(fileDestinationPath, linkTargetPath, overwrite, cancellationToken); } } @@ -403,7 +404,19 @@ private void CreateNonRegularFile(string filePath, string? linkTargetPath) { case TarEntryType.Directory: case TarEntryType.DirectoryList: - Directory.CreateDirectory(filePath); + // Mode must only be used for the leaf directory. + // VerifyPathsForEntryType ensures we're only creating a leaf. + Debug.Assert(Directory.Exists(Path.GetDirectoryName(filePath))); + Debug.Assert(!Directory.Exists(filePath)); + + if (!OperatingSystem.IsWindows()) + { + Directory.CreateDirectory(filePath, Mode); + } + else + { + Directory.CreateDirectory(filePath); + } break; case TarEntryType.SymbolicLink: diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs index 8ce12347d73a3c..a77cb2c36a65ea 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarFile.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Threading; @@ -279,7 +280,6 @@ private static void CreateFromDirectoryInternal(string sourceDirectoryName, Stre using (TarWriter writer = new TarWriter(destination, TarEntryFormat.Pax, leaveOpen)) { - bool baseDirectoryIsEmpty = true; DirectoryInfo di = new(sourceDirectoryName); string basePath = GetBasePathForCreateFromDirectory(di, includeBaseDirectory); @@ -287,15 +287,14 @@ private static void CreateFromDirectoryInternal(string sourceDirectoryName, Stre try { - foreach (FileSystemInfo file in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) + if (includeBaseDirectory) { - baseDirectoryIsEmpty = false; - writer.WriteEntry(file.FullName, GetEntryNameForFileSystemInfo(file, basePath.Length, ref entryNameBuffer)); + writer.WriteEntry(di.FullName, GetEntryNameForBaseDirectory(di.Name, ref entryNameBuffer)); } - if (includeBaseDirectory && baseDirectoryIsEmpty) + foreach (FileSystemInfo file in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) { - writer.WriteEntry(GetEntryForBaseDirectory(di.Name, ref entryNameBuffer)); + writer.WriteEntry(file.FullName, GetEntryNameForFileSystemInfo(file, basePath.Length, ref entryNameBuffer)); } } finally @@ -337,7 +336,6 @@ private static async Task CreateFromDirectoryInternalAsync(string sourceDirector TarWriter writer = new TarWriter(destination, TarEntryFormat.Pax, leaveOpen); await using (writer.ConfigureAwait(false)) { - bool baseDirectoryIsEmpty = true; DirectoryInfo di = new(sourceDirectoryName); string basePath = GetBasePathForCreateFromDirectory(di, includeBaseDirectory); @@ -345,15 +343,14 @@ private static async Task CreateFromDirectoryInternalAsync(string sourceDirector try { - foreach (FileSystemInfo file in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) + if (includeBaseDirectory) { - baseDirectoryIsEmpty = false; - await writer.WriteEntryAsync(file.FullName, GetEntryNameForFileSystemInfo(file, basePath.Length, ref entryNameBuffer), cancellationToken).ConfigureAwait(false); + await writer.WriteEntryAsync(di.FullName, GetEntryNameForBaseDirectory(di.Name, ref entryNameBuffer), cancellationToken).ConfigureAwait(false); } - if (includeBaseDirectory && baseDirectoryIsEmpty) + foreach (FileSystemInfo file in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) { - await writer.WriteEntryAsync(GetEntryForBaseDirectory(di.Name, ref entryNameBuffer), cancellationToken).ConfigureAwait(false); + await writer.WriteEntryAsync(file.FullName, GetEntryNameForFileSystemInfo(file, basePath.Length, ref entryNameBuffer), cancellationToken).ConfigureAwait(false); } } finally @@ -377,11 +374,9 @@ private static string GetEntryNameForFileSystemInfo(FileSystemInfo file, int bas return ArchivingUtils.EntryFromPath(file.FullName, basePathLength, entryNameLength, ref entryNameBuffer, appendPathSeparator: isDirectory); } - // Constructs a PaxTarEntry for a base directory entry when creating an archive. - private static PaxTarEntry GetEntryForBaseDirectory(string name, ref char[] entryNameBuffer) + private static string GetEntryNameForBaseDirectory(string name, ref char[] entryNameBuffer) { - string entryName = ArchivingUtils.EntryFromPath(name, 0, name.Length, ref entryNameBuffer, appendPathSeparator: true); - return new PaxTarEntry(TarEntryType.Directory, entryName); + return ArchivingUtils.EntryFromPath(name, 0, name.Length, ref entryNameBuffer, appendPathSeparator: true); } // Extracts an archive into the specified directory. @@ -392,14 +387,16 @@ private static void ExtractToDirectoryInternal(Stream source, string destination using TarReader reader = new TarReader(source, leaveOpen); + SortedDictionary? pendingModes = TarHelpers.CreatePendingModesDictionary(); TarEntry? entry; while ((entry = reader.GetNextEntry()) != null) { if (entry.EntryType is not TarEntryType.GlobalExtendedAttributes) { - entry.ExtractRelativeToDirectory(destinationDirectoryPath, overwriteFiles); + entry.ExtractRelativeToDirectory(destinationDirectoryPath, overwriteFiles, pendingModes); } } + TarHelpers.SetPendingModes(pendingModes); } // Asynchronously extracts the contents of a tar file into the specified directory. @@ -430,6 +427,7 @@ private static async Task ExtractToDirectoryInternalAsync(Stream source, string VerifyExtractToDirectoryArguments(source, destinationDirectoryPath); cancellationToken.ThrowIfCancellationRequested(); + SortedDictionary? pendingModes = TarHelpers.CreatePendingModesDictionary(); TarReader reader = new TarReader(source, leaveOpen); await using (reader.ConfigureAwait(false)) { @@ -438,10 +436,11 @@ private static async Task ExtractToDirectoryInternalAsync(Stream source, string { if (entry.EntryType is not TarEntryType.GlobalExtendedAttributes) { - await entry.ExtractRelativeToDirectoryAsync(destinationDirectoryPath, overwriteFiles, cancellationToken).ConfigureAwait(false); + await entry.ExtractRelativeToDirectoryAsync(destinationDirectoryPath, overwriteFiles, pendingModes, cancellationToken).ConfigureAwait(false); } } } + TarHelpers.SetPendingModes(pendingModes); } [Conditional("DEBUG")] diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Unix.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Unix.cs new file mode 100644 index 00000000000000..f684dd78fde1c6 --- /dev/null +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Unix.cs @@ -0,0 +1,149 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Diagnostics; + +namespace System.Formats.Tar +{ + internal static partial class TarHelpers + { + private static readonly Lazy s_umask = new Lazy(DetermineUMask); + + private static UnixFileMode DetermineUMask() + { + // To determine the umask, we'll create a file with full permissions and see + // what gets filtered out. + // note: only the owner of a file, and root can change file permissions. + + const UnixFileMode OwnershipPermissions = + UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | + UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.GroupExecute | + UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute; + + string filename = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + FileStreamOptions options = new() + { + Mode = FileMode.CreateNew, + UnixCreateMode = OwnershipPermissions, + Options = FileOptions.DeleteOnClose, + Access = FileAccess.Write, + BufferSize = 0 + }; + using var fs = new FileStream(filename, options); + UnixFileMode actual = File.GetUnixFileMode(fs.SafeFileHandle); + + return OwnershipPermissions & ~actual; + } + + private sealed class ReverseStringComparer : IComparer + { + public int Compare (string? x, string? y) + => StringComparer.Ordinal.Compare(y, x); + } + + private static readonly ReverseStringComparer s_reverseStringComparer = new(); + + private static UnixFileMode UMask => s_umask.Value; + + /* + Tar files are usually ordered: parent directories come before their child entries. + + They may be unordered. In that case we need to create parent directories before + we know the proper permissions for these directories. + + We create these directories with restrictive permissions. If we encounter an entry for + the directory later, we store the mode to apply it later. + + If the archive doesn't have an entry for the parent directory, we use the default mask. + + The pending modes to be applied are tracked through a reverse-sorted dictionary. + The reverse order is needed to apply permissions to children before their parent. + Otherwise we may apply a restrictive mask to the parent, that prevents us from + changing a child. + */ + + internal static SortedDictionary? CreatePendingModesDictionary() + => new SortedDictionary(s_reverseStringComparer); + + internal static void CreateDirectory(string fullPath, UnixFileMode? mode, bool overwriteMetadata, SortedDictionary? pendingModes) + { + // Restrictive mask for creating the missing parent directories while extracting. + const UnixFileMode ExtractPermissions = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute; + + Debug.Assert(pendingModes is not null); + + if (Directory.Exists(fullPath)) + { + // Apply permissions to an existing directory when we're overwriting metadata + // or the directory was created as a missing parent (stored in pendingModes). + if (mode.HasValue) + { + bool hasExtractPermissions = (mode.Value & ExtractPermissions) == ExtractPermissions; + if (hasExtractPermissions) + { + bool removed = pendingModes.Remove(fullPath); + if (overwriteMetadata || removed) + { + UnixFileMode umask = UMask; + File.SetUnixFileMode(fullPath, mode.Value & ~umask); + } + } + else if (overwriteMetadata || pendingModes.ContainsKey(fullPath)) + { + pendingModes[fullPath] = mode.Value; + } + } + return; + } + + if (mode.HasValue) + { + // Ensure we have sufficient permissions to extract in the directory. + if ((mode.Value & ExtractPermissions) != ExtractPermissions) + { + pendingModes[fullPath] = mode.Value; + mode = ExtractPermissions; + } + } + else + { + pendingModes.Add(fullPath, DefaultDirectoryMode); + mode = ExtractPermissions; + } + + string parentDir = Path.GetDirectoryName(fullPath)!; + string rootDir = Path.GetPathRoot(parentDir)!; + bool hasMissingParents = false; + for (string dir = parentDir; dir != rootDir && !Directory.Exists(dir); dir = Path.GetDirectoryName(dir)!) + { + pendingModes.Add(dir, DefaultDirectoryMode); + hasMissingParents = true; + } + + if (hasMissingParents) + { + Directory.CreateDirectory(parentDir, ExtractPermissions); + } + + Directory.CreateDirectory(fullPath, mode.Value); + } + + internal static void SetPendingModes(SortedDictionary? pendingModes) + { + Debug.Assert(pendingModes is not null); + + if (pendingModes.Count == 0) + { + return; + } + + UnixFileMode umask = UMask; + foreach (KeyValuePair dir in pendingModes) + { + File.SetUnixFileMode(dir.Key, dir.Value & ~umask); + } + } + } +} diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Windows.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Windows.cs new file mode 100644 index 00000000000000..e00f6476764aba --- /dev/null +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.Windows.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Diagnostics; + +namespace System.Formats.Tar +{ + internal static partial class TarHelpers + { + internal static SortedDictionary? CreatePendingModesDictionary() + => null; + + internal static void CreateDirectory(string fullPath, UnixFileMode? mode, bool overwriteMetadata, SortedDictionary? pendingModes) + => Directory.CreateDirectory(fullPath); + + internal static void SetPendingModes(SortedDictionary? pendingModes) + => Debug.Assert(pendingModes is null); + } +} diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs index a34bdd0506a20e..d4592eb85d4b5f 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs @@ -12,7 +12,7 @@ namespace System.Formats.Tar { // Static class containing a variety of helper methods. - internal static class TarHelpers + internal static partial class TarHelpers { internal const short RecordSize = 512; internal const int MaxBufferLength = 4096; @@ -22,11 +22,13 @@ internal static class TarHelpers internal const byte EqualsChar = 0x3d; internal const byte NewLineChar = 0xa; + // Default mode for TarEntry created for a file-type. private const UnixFileMode DefaultFileMode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead; + // Default mode for TarEntry created for a directory-type. private const UnixFileMode DefaultDirectoryMode = DefaultFileMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs index a3fa4ee0f595f8..bfb6cf17f11076 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarWriter.Windows.cs @@ -11,8 +11,8 @@ namespace System.Formats.Tar // Windows specific methods for the TarWriter class. public sealed partial class TarWriter : IDisposable { - // Creating archives in Windows always sets the mode to 777 - private const UnixFileMode DefaultWindowsMode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.GroupExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.UserExecute; + // Windows files don't have a mode. Use a mode of 755 for directories and files. + private const UnixFileMode DefaultWindowsMode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.GroupRead | UnixFileMode.GroupExecute | UnixFileMode.OtherRead | UnixFileMode.OtherExecute; // Windows specific implementation of the method that reads an entry from disk and writes it into the archive stream. private TarEntry ConstructEntryForWriting(string fullPath, string entryName, FileOptions fileOptions) diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFile.Tests.Unix.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFile.Tests.Unix.cs index f86136bcc8021d..cee5f6bc7dfd85 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFile.Tests.Unix.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFile.Tests.Unix.cs @@ -67,6 +67,7 @@ public async Task Extract_SpecialFiles_Async(TarEntryFormat format, TarEntryType entry.DeviceMajor = TestCharacterDeviceMajor; entry.DeviceMinor = TestCharacterDeviceMinor; } + entry.Mode = TestPermission1; return (entryName, destination, entry); } @@ -106,6 +107,8 @@ private void Verify_Extract_SpecialFiles(string destination, PosixTarEntry entry Assert.Equal((int)major, entry.DeviceMajor); Assert.Equal((int)minor, entry.DeviceMinor); } + + AssertFileModeEquals(destination, TestPermission1); } } } \ No newline at end of file diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFile.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFile.Tests.cs index 93ba1559d2a7ce..3cfcf4e6e36ca7 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFile.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFile.Tests.cs @@ -1,8 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using Xunit; namespace System.Formats.Tar.Tests @@ -92,5 +94,18 @@ public void ExtractToFile_Link_Throws(TarEntryFormat format, TarEntryType entryT Assert.Equal(0, Directory.GetFileSystemEntries(root.Path).Count()); } + + [Theory] + [MemberData(nameof(GetFormatsAndFiles))] + public void Extract(TarEntryFormat format, TarEntryType entryType) + { + using TempDirectory root = new TempDirectory(); + + (string entryName, string destination, TarEntry entry) = Prepare_Extract(root, format, entryType); + + entry.ExtractToFile(destination, overwrite: true); + + Verify_Extract(destination, entry, entryType); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFileAsync.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFileAsync.Tests.cs index 58d0143b1f6ce2..c5404e9fd4d848 100644 --- a/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFileAsync.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarEntry/TarEntry.ExtractToFileAsync.Tests.cs @@ -113,5 +113,18 @@ public async Task ExtractToFile_Link_Throws_Async(TarEntryFormat format, TarEntr Assert.Equal(0, Directory.GetFileSystemEntries(root.Path).Count()); } } + + [Theory] + [MemberData(nameof(GetFormatsAndFiles))] + public async Task Extract_Async(TarEntryFormat format, TarEntryType entryType) + { + using TempDirectory root = new TempDirectory(); + + (string entryName, string destination, TarEntry entry) = Prepare_Extract(root, format, entryType); + + await entry.ExtractToFileAsync(destination, overwrite: true); + + Verify_Extract(destination, entry, entryType); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs index d2fe36deaaa3ca..75a3495d94e5e5 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectory.File.Tests.cs @@ -52,17 +52,26 @@ public void VerifyIncludeBaseDirectory(bool includeBaseDirectory) using TempDirectory source = new TempDirectory(); using TempDirectory destination = new TempDirectory(); + UnixFileMode baseDirectoryMode = TestPermission1; + SetUnixFileMode(source.Path, baseDirectoryMode); + string fileName1 = "file1.txt"; string filePath1 = Path.Join(source.Path, fileName1); File.Create(filePath1).Dispose(); + UnixFileMode filename1Mode = TestPermission2; + SetUnixFileMode(filePath1, filename1Mode); string subDirectoryName = "dir/"; // The trailing separator is preserved in the TarEntry.Name string subDirectoryPath = Path.Join(source.Path, subDirectoryName); Directory.CreateDirectory(subDirectoryPath); + UnixFileMode subDirectoryMode = TestPermission3; + SetUnixFileMode(subDirectoryPath, subDirectoryMode); string fileName2 = "file2.txt"; string filePath2 = Path.Join(subDirectoryPath, fileName2); File.Create(filePath2).Dispose(); + UnixFileMode filename2Mode = TestPermission4; + SetUnixFileMode(filePath2, filename2Mode); string destinationArchiveFileName = Path.Join(destination.Path, "output.tar"); TarFile.CreateFromDirectory(source.Path, destinationArchiveFileName, includeBaseDirectory); @@ -78,25 +87,38 @@ public void VerifyIncludeBaseDirectory(bool includeBaseDirectory) entries.Add(entry); } - Assert.Equal(3, entries.Count); + int expectedCount = 3 + (includeBaseDirectory ? 1 : 0); + Assert.Equal(expectedCount, entries.Count); string prefix = includeBaseDirectory ? Path.GetFileName(source.Path) + '/' : string.Empty; + if (includeBaseDirectory) + { + TarEntry baseEntry = entries.FirstOrDefault(x => + x.EntryType == TarEntryType.Directory && + x.Name == prefix); + Assert.NotNull(baseEntry); + AssertEntryModeFromFileSystemEquals(baseEntry, baseDirectoryMode); + } + TarEntry entry1 = entries.FirstOrDefault(x => x.EntryType == TarEntryType.RegularFile && x.Name == prefix + fileName1); Assert.NotNull(entry1); + AssertEntryModeFromFileSystemEquals(entry1, filename1Mode); TarEntry directory = entries.FirstOrDefault(x => x.EntryType == TarEntryType.Directory && x.Name == prefix + subDirectoryName); Assert.NotNull(directory); + AssertEntryModeFromFileSystemEquals(directory, subDirectoryMode); string actualFileName2 = subDirectoryName + fileName2; // Notice the trailing separator in subDirectoryName TarEntry entry2 = entries.FirstOrDefault(x => x.EntryType == TarEntryType.RegularFile && x.Name == prefix + actualFileName2); Assert.NotNull(entry2); + AssertEntryModeFromFileSystemEquals(entry2, filename2Mode); } [Fact] @@ -144,7 +166,17 @@ public void IncludeAllSegmentsOfPath(bool includeBaseDirectory) string prefix = includeBaseDirectory ? Path.GetFileName(source.Path) + '/' : string.Empty; - TarEntry entry = reader.GetNextEntry(); + TarEntry entry; + + if (includeBaseDirectory) + { + entry = reader.GetNextEntry(); + Assert.NotNull(entry); + Assert.Equal(TarEntryType.Directory, entry.EntryType); + Assert.Equal(prefix, entry.Name); + } + + entry = reader.GetNextEntry(); Assert.NotNull(entry); Assert.Equal(TarEntryType.Directory, entry.EntryType); Assert.Equal(prefix + "segment1/", entry.Name); diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs index 8891ea856ebb6e..c9b4377cd03285 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.CreateFromDirectoryAsync.File.Tests.cs @@ -61,65 +61,86 @@ public async Task DestinationExists_Throws_Async() [InlineData(true)] public async Task VerifyIncludeBaseDirectory_Async(bool includeBaseDirectory) { - using (TempDirectory source = new TempDirectory()) - using (TempDirectory destination = new TempDirectory()) - { - string fileName1 = "file1.txt"; - string filePath1 = Path.Join(source.Path, fileName1); - File.Create(filePath1).Dispose(); + using TempDirectory source = new TempDirectory(); + using TempDirectory destination = new TempDirectory(); - string subDirectoryName = "dir/"; // The trailing separator is preserved in the TarEntry.Name - string subDirectoryPath = Path.Join(source.Path, subDirectoryName); - Directory.CreateDirectory(subDirectoryPath); + UnixFileMode baseDirectoryMode = TestPermission1; + SetUnixFileMode(source.Path, baseDirectoryMode); - string fileName2 = "file2.txt"; - string filePath2 = Path.Join(subDirectoryPath, fileName2); - File.Create(filePath2).Dispose(); + string fileName1 = "file1.txt"; + string filePath1 = Path.Join(source.Path, fileName1); + File.Create(filePath1).Dispose(); + UnixFileMode filename1Mode = TestPermission2; + SetUnixFileMode(filePath1, filename1Mode); - string destinationArchiveFileName = Path.Join(destination.Path, "output.tar"); - TarFile.CreateFromDirectory(source.Path, destinationArchiveFileName, includeBaseDirectory); + string subDirectoryName = "dir/"; // The trailing separator is preserved in the TarEntry.Name + string subDirectoryPath = Path.Join(source.Path, subDirectoryName); + Directory.CreateDirectory(subDirectoryPath); + UnixFileMode subDirectoryMode = TestPermission3; + SetUnixFileMode(subDirectoryPath, subDirectoryMode); - List entries = new List(); + string fileName2 = "file2.txt"; + string filePath2 = Path.Join(subDirectoryPath, fileName2); + File.Create(filePath2).Dispose(); + UnixFileMode filename2Mode = TestPermission4; + SetUnixFileMode(filePath2, filename2Mode); - FileStreamOptions readOptions = new() - { - Access = FileAccess.Read, - Mode = FileMode.Open, - Options = FileOptions.Asynchronous, - }; + string destinationArchiveFileName = Path.Join(destination.Path, "output.tar"); + TarFile.CreateFromDirectory(source.Path, destinationArchiveFileName, includeBaseDirectory); - await using (FileStream fileStream = File.Open(destinationArchiveFileName, readOptions)) + List entries = new List(); + + FileStreamOptions readOptions = new() + { + Access = FileAccess.Read, + Mode = FileMode.Open, + Options = FileOptions.Asynchronous, + }; + + await using (FileStream fileStream = File.Open(destinationArchiveFileName, readOptions)) + { + await using (TarReader reader = new TarReader(fileStream)) { - await using (TarReader reader = new TarReader(fileStream)) + TarEntry entry; + while ((entry = await reader.GetNextEntryAsync()) != null) { - TarEntry entry; - while ((entry = await reader.GetNextEntryAsync()) != null) - { - entries.Add(entry); - } + entries.Add(entry); } } + } - Assert.Equal(3, entries.Count); - - string prefix = includeBaseDirectory ? Path.GetFileName(source.Path) + '/' : string.Empty; + int expectedCount = 3 + (includeBaseDirectory ? 1 : 0); + Assert.Equal(expectedCount, entries.Count); - TarEntry entry1 = entries.FirstOrDefault(x => - x.EntryType == TarEntryType.RegularFile && - x.Name == prefix + fileName1); - Assert.NotNull(entry1); + string prefix = includeBaseDirectory ? Path.GetFileName(source.Path) + '/' : string.Empty; - TarEntry directory = entries.FirstOrDefault(x => + if (includeBaseDirectory) + { + TarEntry baseEntry = entries.FirstOrDefault(x => x.EntryType == TarEntryType.Directory && - x.Name == prefix + subDirectoryName); - Assert.NotNull(directory); - - string actualFileName2 = subDirectoryName + fileName2; // Notice the trailing separator in subDirectoryName - TarEntry entry2 = entries.FirstOrDefault(x => - x.EntryType == TarEntryType.RegularFile && - x.Name == prefix + actualFileName2); - Assert.NotNull(entry2); + x.Name == prefix); + Assert.NotNull(baseEntry); + AssertEntryModeFromFileSystemEquals(baseEntry, baseDirectoryMode); } + + TarEntry entry1 = entries.FirstOrDefault(x => + x.EntryType == TarEntryType.RegularFile && + x.Name == prefix + fileName1); + Assert.NotNull(entry1); + AssertEntryModeFromFileSystemEquals(entry1, filename1Mode); + + TarEntry directory = entries.FirstOrDefault(x => + x.EntryType == TarEntryType.Directory && + x.Name == prefix + subDirectoryName); + Assert.NotNull(directory); + AssertEntryModeFromFileSystemEquals(directory, subDirectoryMode); + + string actualFileName2 = subDirectoryName + fileName2; // Notice the trailing separator in subDirectoryName + TarEntry entry2 = entries.FirstOrDefault(x => + x.EntryType == TarEntryType.RegularFile && + x.Name == prefix + actualFileName2); + Assert.NotNull(entry2); + AssertEntryModeFromFileSystemEquals(entry2, filename2Mode); } [Fact] @@ -186,7 +207,17 @@ public async Task IncludeAllSegmentsOfPath_Async(bool includeBaseDirectory) { string prefix = includeBaseDirectory ? Path.GetFileName(source.Path) + '/' : string.Empty; - TarEntry entry = await reader.GetNextEntryAsync(); + TarEntry entry; + + if (includeBaseDirectory) + { + entry = await reader.GetNextEntryAsync(); + Assert.NotNull(entry); + Assert.Equal(TarEntryType.Directory, entry.EntryType); + Assert.Equal(prefix, entry.Name); + } + + entry = await reader.GetNextEntryAsync(); Assert.NotNull(entry); Assert.Equal(TarEntryType.Directory, entry.EntryType); Assert.Equal(prefix + "segment1/", entry.Name); diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs index a753358cb8a394..f741926cdd8ef1 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectory.File.Tests.cs @@ -159,5 +159,119 @@ public void ExtractArchiveWithEntriesThatStartWithSlashDotPrefix() Assert.True(Path.Exists(entryPath), $"Entry was not extracted: {entryPath}"); } } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void UnixFileModes(bool overwrite) + { + using TempDirectory source = new TempDirectory(); + using TempDirectory destination = new TempDirectory(); + + string archivePath = Path.Join(source.Path, "archive.tar"); + using FileStream archiveStream = File.Create(archivePath); + using (TarWriter writer = new TarWriter(archiveStream)) + { + PaxTarEntry dir = new PaxTarEntry(TarEntryType.Directory, "dir"); + dir.Mode = TestPermission1; + writer.WriteEntry(dir); + + PaxTarEntry file = new PaxTarEntry(TarEntryType.RegularFile, "file"); + file.Mode = TestPermission2; + writer.WriteEntry(file); + + // Archive has no entry for missing_parent. + PaxTarEntry missingParentDir = new PaxTarEntry(TarEntryType.Directory, "missing_parent/dir"); + missingParentDir.Mode = TestPermission3; + writer.WriteEntry(missingParentDir); + + // out_of_order_parent/file entry comes before out_of_order_parent entry. + PaxTarEntry outOfOrderFile = new PaxTarEntry(TarEntryType.RegularFile, "out_of_order_parent/file"); + writer.WriteEntry(outOfOrderFile); + + PaxTarEntry outOfOrderDir = new PaxTarEntry(TarEntryType.Directory, "out_of_order_parent"); + outOfOrderDir.Mode = TestPermission4; + writer.WriteEntry(outOfOrderDir); + } + + string dirPath = Path.Join(destination.Path, "dir"); + string filePath = Path.Join(destination.Path, "file"); + string missingParentPath = Path.Join(destination.Path, "missing_parent"); + string missingParentDirPath = Path.Join(missingParentPath, "dir"); + string outOfOrderDirPath = Path.Join(destination.Path, "out_of_order_parent"); + + if (overwrite) + { + File.OpenWrite(filePath).Dispose(); + Directory.CreateDirectory(dirPath); + Directory.CreateDirectory(missingParentDirPath); + Directory.CreateDirectory(outOfOrderDirPath); + } + + TarFile.ExtractToDirectory(archivePath, destination.Path, overwriteFiles: overwrite); + + Assert.True(Directory.Exists(dirPath), $"{dirPath}' does not exist."); + AssertFileModeEquals(dirPath, TestPermission1); + + Assert.True(File.Exists(filePath), $"{filePath}' does not exist."); + AssertFileModeEquals(filePath, TestPermission2); + + // Missing parents are created with DefaultDirectoryMode. + // The mode is not set when overwrite == true if there is no entry and the directory exists before extracting. + Assert.True(Directory.Exists(missingParentPath), $"{missingParentPath}' does not exist."); + if (!overwrite) + { + AssertFileModeEquals(missingParentPath, DefaultDirectoryMode); + } + + Assert.True(Directory.Exists(missingParentDirPath), $"{missingParentDirPath}' does not exist."); + AssertFileModeEquals(missingParentDirPath, TestPermission3); + + // Directory modes that are out-of-order are still applied. + Assert.True(Directory.Exists(outOfOrderDirPath), $"{outOfOrderDirPath}' does not exist."); + AssertFileModeEquals(outOfOrderDirPath, TestPermission4); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void UnixFileModes_RestrictiveParentDir(bool overwrite) + { + using TempDirectory source = new TempDirectory(); + using TempDirectory destination = new TempDirectory(); + + string archivePath = Path.Join(source.Path, "archive.tar"); + using FileStream archiveStream = File.Create(archivePath); + using (TarWriter writer = new TarWriter(archiveStream)) + { + PaxTarEntry dir = new PaxTarEntry(TarEntryType.Directory, "dir"); + dir.Mode = UnixFileMode.None; // Restrict permissions. + writer.WriteEntry(dir); + + PaxTarEntry file = new PaxTarEntry(TarEntryType.RegularFile, "dir/file"); + file.Mode = TestPermission1; + writer.WriteEntry(file); + } + + string dirPath = Path.Join(destination.Path, "dir"); + string filePath = Path.Join(dirPath, "file"); + + if (overwrite) + { + Directory.CreateDirectory(dirPath); + File.OpenWrite(filePath).Dispose(); + } + + TarFile.ExtractToDirectory(archivePath, destination.Path, overwriteFiles: overwrite); + + Assert.True(Directory.Exists(dirPath), $"{dirPath}' does not exist."); + AssertFileModeEquals(dirPath, UnixFileMode.None); + + // Set dir permissions so we can access file. + SetUnixFileMode(dirPath, UserAll); + + Assert.True(File.Exists(filePath), $"{filePath}' does not exist."); + AssertFileModeEquals(filePath, TestPermission1); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs index dfebe737493acc..01d2457018ce24 100644 --- a/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs +++ b/src/libraries/System.Formats.Tar/tests/TarFile/TarFile.ExtractToDirectoryAsync.File.Tests.cs @@ -190,5 +190,95 @@ public async Task ExtractArchiveWithEntriesThatStartWithSlashDotPrefix_Async() } } } + + [Fact] + public async Task UnixFileModes_Async() + { + using TempDirectory source = new TempDirectory(); + using TempDirectory destination = new TempDirectory(); + + string archivePath = Path.Join(source.Path, "archive.tar"); + using FileStream archiveStream = File.Create(archivePath); + using (TarWriter writer = new TarWriter(archiveStream)) + { + PaxTarEntry dir = new PaxTarEntry(TarEntryType.Directory, "dir"); + dir.Mode = TestPermission1; + writer.WriteEntry(dir); + + PaxTarEntry file = new PaxTarEntry(TarEntryType.RegularFile, "file"); + file.Mode = TestPermission2; + writer.WriteEntry(file); + + // Archive has no entry for missing_parent. + PaxTarEntry missingParentDir = new PaxTarEntry(TarEntryType.Directory, "missing_parent/dir"); + missingParentDir.Mode = TestPermission3; + writer.WriteEntry(missingParentDir); + + // out_of_order_parent/file entry comes before out_of_order_parent entry. + PaxTarEntry outOfOrderFile = new PaxTarEntry(TarEntryType.RegularFile, "out_of_order_parent/file"); + writer.WriteEntry(outOfOrderFile); + + PaxTarEntry outOfOrderDir = new PaxTarEntry(TarEntryType.Directory, "out_of_order_parent"); + outOfOrderDir.Mode = TestPermission4; + writer.WriteEntry(outOfOrderDir); + } + + await TarFile.ExtractToDirectoryAsync(archivePath, destination.Path, overwriteFiles: false); + + string dirPath = Path.Join(destination.Path, "dir"); + Assert.True(Directory.Exists(dirPath), $"{dirPath}' does not exist."); + AssertFileModeEquals(dirPath, TestPermission1); + + string filePath = Path.Join(destination.Path, "file"); + Assert.True(File.Exists(filePath), $"{filePath}' does not exist."); + AssertFileModeEquals(filePath, TestPermission2); + + // Missing parents are created with DefaultDirectoryMode. + string missingParentPath = Path.Join(destination.Path, "missing_parent"); + Assert.True(Directory.Exists(missingParentPath), $"{missingParentPath}' does not exist."); + AssertFileModeEquals(missingParentPath, DefaultDirectoryMode); + + string missingParentDirPath = Path.Join(missingParentPath, "dir"); + Assert.True(Directory.Exists(missingParentDirPath), $"{missingParentDirPath}' does not exist."); + AssertFileModeEquals(missingParentDirPath, TestPermission3); + + // Directory modes that are out-of-order are still applied. + string outOfOrderDirPath = Path.Join(destination.Path, "out_of_order_parent"); + Assert.True(Directory.Exists(outOfOrderDirPath), $"{outOfOrderDirPath}' does not exist."); + AssertFileModeEquals(outOfOrderDirPath, TestPermission4); + } + + [Fact] + public async Task UnixFileModes_RestrictiveParentDir_Async() + { + using TempDirectory source = new TempDirectory(); + using TempDirectory destination = new TempDirectory(); + + string archivePath = Path.Join(source.Path, "archive.tar"); + using FileStream archiveStream = File.Create(archivePath); + using (TarWriter writer = new TarWriter(archiveStream)) + { + PaxTarEntry dir = new PaxTarEntry(TarEntryType.Directory, "dir"); + dir.Mode = UnixFileMode.None; // Restrict permissions. + writer.WriteEntry(dir); + + PaxTarEntry file = new PaxTarEntry(TarEntryType.RegularFile, "dir/file"); + file.Mode = TestPermission1; + writer.WriteEntry(file); + } + + await TarFile.ExtractToDirectoryAsync(archivePath, destination.Path, overwriteFiles: false); + + string dirPath = Path.Join(destination.Path, "dir"); + Assert.True(Directory.Exists(dirPath), $"{dirPath}' does not exist."); + AssertFileModeEquals(dirPath, UnixFileMode.None); + + // Set dir permissions so we can access file. + SetUnixFileMode(dirPath, UserAll); + + string filePath = Path.Join(dirPath, "file"); + Assert.True(File.Exists(filePath), $"{filePath}' does not exist."); + AssertFileModeEquals(filePath, TestPermission1); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs b/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs index 1c61d2e3ae1a7f..b167a980acd2b0 100644 --- a/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs +++ b/src/libraries/System.Formats.Tar/tests/TarTestsBase.cs @@ -15,8 +15,18 @@ public abstract partial class TarTestsBase : FileCleanupTestBase // Default values are what a TarEntry created with its constructor will set protected const UnixFileMode DefaultFileMode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead; // 644 in octal, internally used as default - private const UnixFileMode DefaultDirectoryMode = DefaultFileMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; // 755 in octal, internally used as default - protected const UnixFileMode DefaultWindowsMode = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.GroupExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.UserExecute; // Creating archives in Windows always sets the mode to 777 + protected const UnixFileMode DefaultDirectoryMode = DefaultFileMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; // 755 in octal, internally used as default + + // Mode assumed for files and directories on Windows. + protected const UnixFileMode DefaultWindowsMode = DefaultFileMode | UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute; // 755 in octal, internally used as default + + // Permissions used by tests. User has all permissions to avoid permission errors. + protected const UnixFileMode UserAll = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute; + protected const UnixFileMode TestPermission1 = UserAll | UnixFileMode.GroupRead; + protected const UnixFileMode TestPermission2 = UserAll | UnixFileMode.GroupExecute; + protected const UnixFileMode TestPermission3 = UserAll | UnixFileMode.OtherRead; + protected const UnixFileMode TestPermission4 = UserAll | UnixFileMode.OtherExecute; + protected const int DefaultGid = 0; protected const int DefaultUid = 0; protected const int DefaultDeviceMajor = 0; @@ -363,5 +373,76 @@ public static IEnumerable GetFormatsAndLinks() yield return new object[] { format, TarEntryType.HardLink }; } } + + public static IEnumerable GetFormatsAndFiles() + { + foreach (TarEntryType entryType in new[] { TarEntryType.V7RegularFile, TarEntryType.Directory }) + { + yield return new object[] { TarEntryFormat.V7, entryType }; + } + foreach (TarEntryFormat format in new[] { TarEntryFormat.Ustar, TarEntryFormat.Pax, TarEntryFormat.Gnu }) + { + foreach (TarEntryType entryType in new[] { TarEntryType.RegularFile, TarEntryType.Directory }) + { + yield return new object[] { format, entryType }; + } + } + } + + protected static void SetUnixFileMode(string path, UnixFileMode mode) + { + if (!PlatformDetection.IsWindows) + { + File.SetUnixFileMode(path, mode); + } + } + + protected static void AssertEntryModeFromFileSystemEquals(TarEntry entry, UnixFileMode fileMode) + { + if (PlatformDetection.IsWindows) + { + // Windows files don't have a mode. Set the expected value. + fileMode = DefaultWindowsMode; + } + Assert.Equal(fileMode, entry.Mode); + } + + protected static void AssertFileModeEquals(string path, UnixFileMode mode) + { + if (!PlatformDetection.IsWindows) + { + Assert.Equal(mode, File.GetUnixFileMode(path)); + } + } + + protected (string, string, TarEntry) Prepare_Extract(TempDirectory root, TarEntryFormat format, TarEntryType entryType) + { + string entryName = entryType.ToString(); + string destination = Path.Join(root.Path, entryName); + + TarEntry entry = InvokeTarEntryCreationConstructor(format, entryType, entryName); + Assert.NotNull(entry); + entry.Mode = TestPermission1; + + return (entryName, destination, entry); + } + + protected void Verify_Extract(string destination, TarEntry entry, TarEntryType entryType) + { + if (entryType is TarEntryType.RegularFile or TarEntryType.V7RegularFile) + { + Assert.True(File.Exists(destination)); + } + else if (entryType is TarEntryType.Directory) + { + Assert.True(Directory.Exists(destination)); + } + else + { + Assert.True(false, "Unchecked entry type."); + } + + AssertFileModeEquals(destination, TestPermission1); + } } } diff --git a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.Windows.cs b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.Windows.cs index 16fb8205685e7b..f37354a9ffef7e 100644 --- a/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.Windows.cs +++ b/src/libraries/System.Formats.Tar/tests/TarWriter/TarWriter.File.Base.Windows.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.IO; using Xunit; namespace System.Formats.Tar.Tests @@ -11,8 +12,8 @@ protected void VerifyPlatformSpecificMetadata(string filePath, TarEntry entry) { Assert.True(entry.ModificationTime > DateTimeOffset.UnixEpoch); - // Archives created in Windows always set mode to 777 - Assert.Equal(DefaultWindowsMode, entry.Mode); + UnixFileMode expectedMode = DefaultWindowsMode; + Assert.Equal(expectedMode, entry.Mode); Assert.Equal(DefaultUid, entry.Uid); Assert.Equal(DefaultGid, entry.Gid); From 199436b51fba6f0bee469c63ee42fa266d70222d Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Fri, 12 Aug 2022 10:21:32 -0500 Subject: [PATCH 56/68] EnableAOTAnalyzer for Microsoft.Extensions.DependencyInjection (#73829) Using MEDI is annotated as RequiresDynamicCode because it supports enumerable and generic servcies with ValueTypes. When using DI with ValuesTypes, the array and generic code might not be available. Contributes to #71654 --- ...icrosoft.Extensions.DependencyInjection.Abstractions.cs | 1 + ...soft.Extensions.DependencyInjection.Abstractions.csproj | 4 ++++ ...soft.Extensions.DependencyInjection.Abstractions.csproj | 5 +++++ .../src/ServiceProviderServiceExtensions.cs | 1 + .../ref/Microsoft.Extensions.DependencyInjection.cs | 4 ++++ .../ref/Microsoft.Extensions.DependencyInjection.csproj | 4 ++++ .../src/DefaultServiceProviderFactory.cs | 2 ++ .../src/Microsoft.Extensions.DependencyInjection.csproj | 7 ++++++- .../src/ServiceCollectionContainerBuilderExtensions.cs | 4 ++++ .../src/ServiceLookup/CallSiteFactory.cs | 1 + .../src/ServiceLookup/CallSiteRuntimeResolver.cs | 3 ++- .../src/ServiceLookup/CompiledServiceProviderEngine.cs | 2 ++ .../src/ServiceLookup/DynamicServiceProviderEngine.cs | 2 ++ .../ServiceLookup/Expressions/ExpressionResolverBuilder.cs | 2 ++ .../Expressions/ExpressionsServiceProviderEngine.cs | 2 ++ .../src/ServiceLookup/IEnumerableCallSite.cs | 2 ++ .../src/ServiceLookup/ILEmit/ILEmitResolverBuilder.cs | 2 ++ .../ServiceLookup/ILEmit/ILEmitServiceProviderEngine.cs | 3 +++ .../src/ServiceLookup/RuntimeServiceProviderEngine.cs | 2 ++ .../src/ServiceLookup/ServiceLookupHelpers.cs | 1 + .../src/ServiceProvider.cs | 6 ++++++ .../ref/Microsoft.Extensions.Logging.cs | 1 + .../ref/Microsoft.Extensions.Logging.csproj | 4 ++++ .../Microsoft.Extensions.Logging/src/LoggerFactory.cs | 1 + .../src/Microsoft.Extensions.Logging.csproj | 4 ++++ 25 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/ref/Microsoft.Extensions.DependencyInjection.Abstractions.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/ref/Microsoft.Extensions.DependencyInjection.Abstractions.cs index 1b5dfaf70fb9af..0d7359b076e6d8 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/ref/Microsoft.Extensions.DependencyInjection.Abstractions.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/ref/Microsoft.Extensions.DependencyInjection.Abstractions.cs @@ -142,6 +142,7 @@ public static partial class ServiceProviderServiceExtensions public static Microsoft.Extensions.DependencyInjection.IServiceScope CreateScope(this System.IServiceProvider provider) { throw null; } public static object GetRequiredService(this System.IServiceProvider provider, System.Type serviceType) { throw null; } public static T GetRequiredService(this System.IServiceProvider provider) where T : notnull { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("The native code for an IEnumerable might not be available at runtime.")] public static System.Collections.Generic.IEnumerable GetServices(this System.IServiceProvider provider, System.Type serviceType) { throw null; } public static System.Collections.Generic.IEnumerable GetServices(this System.IServiceProvider provider) { throw null; } public static T? GetService(this System.IServiceProvider provider) { throw null; } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/ref/Microsoft.Extensions.DependencyInjection.Abstractions.csproj b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/ref/Microsoft.Extensions.DependencyInjection.Abstractions.csproj index d2f35679e26cb7..9742788ba462ff 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/ref/Microsoft.Extensions.DependencyInjection.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/ref/Microsoft.Extensions.DependencyInjection.Abstractions.csproj @@ -12,6 +12,10 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Microsoft.Extensions.DependencyInjection.Abstractions.csproj b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Microsoft.Extensions.DependencyInjection.Abstractions.csproj index 9afe192027166e..ec15fbd2398dfb 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Microsoft.Extensions.DependencyInjection.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/Microsoft.Extensions.DependencyInjection.Abstractions.csproj @@ -4,6 +4,7 @@ $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.1;netstandard2.0;$(NetFrameworkMinimum) true true + true Abstractions for dependency injection. Commonly Used Types: @@ -30,6 +31,10 @@ Microsoft.Extensions.DependencyInjection.IServiceCollection + + + + diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceProviderServiceExtensions.cs b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceProviderServiceExtensions.cs index dbeacc1d50a135..f3a15fde1a6ef8 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceProviderServiceExtensions.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceProviderServiceExtensions.cs @@ -84,6 +84,7 @@ public static IEnumerable GetServices(this IServiceProvider provider) /// The to retrieve the services from. /// An object that specifies the type of service object to get. /// An enumeration of services of type . + [RequiresDynamicCode("The native code for an IEnumerable might not be available at runtime.")] public static IEnumerable GetServices(this IServiceProvider provider, Type serviceType) { ThrowHelper.ThrowIfNull(provider); diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.cs index 0b85156d608859..92a159382c79f3 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.cs @@ -6,6 +6,7 @@ namespace Microsoft.Extensions.DependencyInjection { + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")] public partial class DefaultServiceProviderFactory : Microsoft.Extensions.DependencyInjection.IServiceProviderFactory { public DefaultServiceProviderFactory() { } @@ -15,8 +16,11 @@ public DefaultServiceProviderFactory(Microsoft.Extensions.DependencyInjection.Se } public static partial class ServiceCollectionContainerBuilderExtensions { + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")] public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")] public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, Microsoft.Extensions.DependencyInjection.ServiceProviderOptions options) { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services.")] public static Microsoft.Extensions.DependencyInjection.ServiceProvider BuildServiceProvider(this Microsoft.Extensions.DependencyInjection.IServiceCollection services, bool validateScopes) { throw null; } } public sealed partial class ServiceProvider : System.IAsyncDisposable, System.IDisposable, System.IServiceProvider diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.csproj b/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.csproj index 7e9a119ec87578..d049b8e4e4fbe3 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.csproj +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/ref/Microsoft.Extensions.DependencyInjection.csproj @@ -12,6 +12,10 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/DefaultServiceProviderFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/DefaultServiceProviderFactory.cs index 7fe53e3aa8551d..13863a4618a7dc 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/DefaultServiceProviderFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/DefaultServiceProviderFactory.cs @@ -2,12 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.Extensions.DependencyInjection { /// /// Default implementation of . /// + [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] public class DefaultServiceProviderFactory : IServiceProviderFactory { private readonly ServiceProviderOptions _options; diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/Microsoft.Extensions.DependencyInjection.csproj b/src/libraries/Microsoft.Extensions.DependencyInjection/src/Microsoft.Extensions.DependencyInjection.csproj index 2cbd54cbe94cae..7c63e352515e6b 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/Microsoft.Extensions.DependencyInjection.csproj +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/Microsoft.Extensions.DependencyInjection.csproj @@ -1,4 +1,4 @@ - + $(NetCoreAppCurrent);$(NetCoreAppMinimum);netstandard2.1;netstandard2.0;$(NetFrameworkMinimum) @@ -7,6 +7,7 @@ $(NoWarn);CP0001 true + true Default implementation of dependency injection for Microsoft.Extensions.DependencyInjection. @@ -35,6 +36,10 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceCollectionContainerBuilderExtensions.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceCollectionContainerBuilderExtensions.cs index c18f164cbb77bf..1cde6c4c690296 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceCollectionContainerBuilderExtensions.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceCollectionContainerBuilderExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Microsoft.Extensions.DependencyInjection.ServiceLookup; @@ -18,6 +19,7 @@ public static class ServiceCollectionContainerBuilderExtensions /// The containing service descriptors. /// The . + [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] public static ServiceProvider BuildServiceProvider(this IServiceCollection services) { return BuildServiceProvider(services, ServiceProviderOptions.Default); @@ -32,6 +34,7 @@ public static ServiceProvider BuildServiceProvider(this IServiceCollection servi /// true to perform check verifying that scoped services never gets resolved from root provider; otherwise false. /// /// The . + [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] public static ServiceProvider BuildServiceProvider(this IServiceCollection services, bool validateScopes) { return services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = validateScopes }); @@ -46,6 +49,7 @@ public static ServiceProvider BuildServiceProvider(this IServiceCollection servi /// Configures various service provider behaviors. /// /// The . + [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] public static ServiceProvider BuildServiceProvider(this IServiceCollection services, ServiceProviderOptions options) { if (services is null) diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs index 07052e920c67ea..22b3d4fd153ad6 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs @@ -11,6 +11,7 @@ namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { + [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] internal sealed class CallSiteFactory : IServiceProviderIsService { private const int DefaultSlot = 0; diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs index 96fcd070295cf5..f41e18238bcf69 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs @@ -3,13 +3,14 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Runtime.ExceptionServices; using System.Threading; -using System.Diagnostics; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { + [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] internal sealed class CallSiteRuntimeResolver : CallSiteVisitor { public static CallSiteRuntimeResolver Instance { get; } = new(); diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CompiledServiceProviderEngine.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CompiledServiceProviderEngine.cs index eef3e1fe702141..2045140755d425 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CompiledServiceProviderEngine.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CompiledServiceProviderEngine.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { @@ -13,6 +14,7 @@ internal abstract class CompiledServiceProviderEngine : ServiceProviderEngine public ExpressionResolverBuilder ResolverBuilder { get; } #endif + [RequiresDynamicCode("Creates DynamicMethods")] public CompiledServiceProviderEngine(ServiceProvider provider) { ResolverBuilder = new(provider); diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/DynamicServiceProviderEngine.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/DynamicServiceProviderEngine.cs index 5c52efcc4f1815..45fe2c39ddf24b 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/DynamicServiceProviderEngine.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/DynamicServiceProviderEngine.cs @@ -3,10 +3,12 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Threading; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { + [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] internal sealed class DynamicServiceProviderEngine : CompiledServiceProviderEngine { private readonly ServiceProvider _serviceProvider; diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionResolverBuilder.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionResolverBuilder.cs index 7e0b988fa2b9e6..c8d4b7a6c57d8e 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionResolverBuilder.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionResolverBuilder.cs @@ -4,12 +4,14 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { + [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] internal sealed class ExpressionResolverBuilder : CallSiteVisitor { private static readonly ParameterExpression ScopeParameter = Expression.Parameter(typeof(ServiceProviderEngineScope)); diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionsServiceProviderEngine.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionsServiceProviderEngine.cs index 9de2fa8acbb09a..d179cb6f36a4e0 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionsServiceProviderEngine.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/Expressions/ExpressionsServiceProviderEngine.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { @@ -9,6 +10,7 @@ internal sealed class ExpressionsServiceProviderEngine : ServiceProviderEngine { private readonly ExpressionResolverBuilder _expressionResolverBuilder; + [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] public ExpressionsServiceProviderEngine(ServiceProvider serviceProvider) { _expressionResolverBuilder = new ExpressionResolverBuilder(serviceProvider); diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/IEnumerableCallSite.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/IEnumerableCallSite.cs index 7e9b0f7f899541..5774a2c3212fca 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/IEnumerableCallSite.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/IEnumerableCallSite.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { + [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] internal sealed class IEnumerableCallSite : ServiceCallSite { internal Type ItemType { get; } diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ILEmit/ILEmitResolverBuilder.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ILEmit/ILEmitResolverBuilder.cs index 8067a6676e9662..345dce793d0349 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ILEmit/ILEmitResolverBuilder.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ILEmit/ILEmitResolverBuilder.cs @@ -5,11 +5,13 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Reflection.Emit; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { + [RequiresDynamicCode("Creates DynamicMethods")] internal sealed class ILEmitResolverBuilder : CallSiteVisitor { private static readonly MethodInfo ResolvedServicesGetter = typeof(ServiceProviderEngineScope).GetProperty( diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ILEmit/ILEmitServiceProviderEngine.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ILEmit/ILEmitServiceProviderEngine.cs index 5facb1a8ab95df..cd6d6866b616d3 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ILEmit/ILEmitServiceProviderEngine.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ILEmit/ILEmitServiceProviderEngine.cs @@ -2,12 +2,15 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { internal sealed class ILEmitServiceProviderEngine : ServiceProviderEngine { private readonly ILEmitResolverBuilder _expressionResolverBuilder; + + [RequiresDynamicCode("Creates DynamicMethods")] public ILEmitServiceProviderEngine(ServiceProvider serviceProvider) { _expressionResolverBuilder = new ILEmitResolverBuilder(serviceProvider); diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/RuntimeServiceProviderEngine.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/RuntimeServiceProviderEngine.cs index 52c1afbafcbd09..b25e7f8216d0b3 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/RuntimeServiceProviderEngine.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/RuntimeServiceProviderEngine.cs @@ -2,9 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.Extensions.DependencyInjection.ServiceLookup { + [RequiresDynamicCode(ServiceProvider.RequiresDynamicCodeMessage)] internal sealed class RuntimeServiceProviderEngine : ServiceProviderEngine { public static RuntimeServiceProviderEngine Instance { get; } = new RuntimeServiceProviderEngine(); diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceLookupHelpers.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceLookupHelpers.cs index d41f9cb7cec4fd..e33aadb591888b 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceLookupHelpers.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceLookupHelpers.cs @@ -34,6 +34,7 @@ internal static class ServiceLookupHelpers internal static readonly MethodInfo MonitorExitMethodInfo = typeof(Monitor) .GetMethod(nameof(Monitor.Exit), BindingFlags.Public | BindingFlags.Static, null, new Type[] { typeof(object) }, null)!; + [RequiresDynamicCode("The code for an array of the specified type might not be available.")] [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2060:MakeGenericMethod", Justification = "Calling Array.Empty() is safe since the T doesn't have trimming annotations.")] internal static MethodInfo GetArrayEmptyMethodInfo(Type itemType) => diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs index f058108744c602..f66f36b3cf6ed2 100644 --- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs +++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection.ServiceLookup; @@ -15,6 +16,8 @@ namespace Microsoft.Extensions.DependencyInjection /// public sealed class ServiceProvider : IServiceProvider, IDisposable, IAsyncDisposable { + internal const string RequiresDynamicCodeMessage = "Using Microsoft.Extensions.DependencyInjection requires generating code dynamically at runtime. For example, when using enumerable and generic ValueType services."; + private readonly CallSiteValidator? _callSiteValidator; private readonly Func> _createServiceAccessor; @@ -33,6 +36,7 @@ public sealed class ServiceProvider : IServiceProvider, IDisposable, IAsyncDispo internal static bool VerifyOpenGenericServiceTrimmability { get; } = AppContext.TryGetSwitch("Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability", out bool verifyOpenGenerics) ? verifyOpenGenerics : false; + [RequiresDynamicCode(RequiresDynamicCodeMessage)] internal ServiceProvider(ICollection serviceDescriptors, ServiceProviderOptions options) { // note that Root needs to be set before calling GetEngine(), because the engine may need to access Root @@ -151,6 +155,7 @@ private void ValidateService(ServiceDescriptor descriptor) } } + [RequiresDynamicCode(RequiresDynamicCodeMessage)] private Func CreateServiceAccessor(Type serviceType) { ServiceCallSite? callSite = CallSiteFactory.GetCallSite(serviceType, new CallSiteChain()); @@ -187,6 +192,7 @@ internal IServiceScope CreateScope() return new ServiceProviderEngineScope(this, isRootScope: false); } + [RequiresDynamicCode(RequiresDynamicCodeMessage)] private ServiceProviderEngine GetEngine() { ServiceProviderEngine engine; diff --git a/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.cs b/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.cs index 5b7fade6eaa438..dd65f6f0e04a60 100644 --- a/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.cs +++ b/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.cs @@ -61,6 +61,7 @@ public LoggerFactory(System.Collections.Generic.IEnumerable providers, Microsoft.Extensions.Options.IOptionsMonitor filterOption, Microsoft.Extensions.Options.IOptions? options = null, Microsoft.Extensions.Logging.IExternalScopeProvider? scopeProvider = null) { } public void AddProvider(Microsoft.Extensions.Logging.ILoggerProvider provider) { } protected virtual bool CheckDisposed() { throw null; } + [System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute("LoggerFactory.Create uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public static Microsoft.Extensions.Logging.ILoggerFactory Create(System.Action configure) { throw null; } public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) { throw null; } public void Dispose() { } diff --git a/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.csproj b/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.csproj index 5ad076b2963813..3d305cb991b0dd 100644 --- a/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.csproj +++ b/src/libraries/Microsoft.Extensions.Logging/ref/Microsoft.Extensions.Logging.csproj @@ -7,6 +7,10 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.Logging/src/LoggerFactory.cs b/src/libraries/Microsoft.Extensions.Logging/src/LoggerFactory.cs index 315ea9115857aa..39651be0330078 100644 --- a/src/libraries/Microsoft.Extensions.Logging/src/LoggerFactory.cs +++ b/src/libraries/Microsoft.Extensions.Logging/src/LoggerFactory.cs @@ -103,6 +103,7 @@ public LoggerFactory(IEnumerable providers, IOptionsMonitor /// A delegate to configure the . /// The that was created. + [RequiresDynamicCode("LoggerFactory.Create uses Microsoft.Extensions.DependencyInjection, which may require generating code dynamically at runtime.")] public static ILoggerFactory Create(Action configure) { var serviceCollection = new ServiceCollection(); diff --git a/src/libraries/Microsoft.Extensions.Logging/src/Microsoft.Extensions.Logging.csproj b/src/libraries/Microsoft.Extensions.Logging/src/Microsoft.Extensions.Logging.csproj index 2bd514f088cd3a..e7cbf1e48f7d8a 100644 --- a/src/libraries/Microsoft.Extensions.Logging/src/Microsoft.Extensions.Logging.csproj +++ b/src/libraries/Microsoft.Extensions.Logging/src/Microsoft.Extensions.Logging.csproj @@ -30,6 +30,10 @@ + + + + From 4fac43b00e3d5ba8159b5f877041f8e01462b4ba Mon Sep 17 00:00:00 2001 From: Brennan Date: Fri, 12 Aug 2022 08:28:26 -0700 Subject: [PATCH 57/68] [RateLimiting] Update TranslateKey to allow disposal of wrapped limiter (#73160) --- .../ref/System.Threading.RateLimiting.cs | 2 +- .../RateLimiting/PartitionedRateLimiter.T.cs | 5 +- .../RateLimiting/TranslatingLimiter.cs | 33 +++--- .../tests/PartitionedRateLimiterTests.cs | 100 ++++++++++++++++-- 4 files changed, 115 insertions(+), 25 deletions(-) diff --git a/src/libraries/System.Threading.RateLimiting/ref/System.Threading.RateLimiting.cs b/src/libraries/System.Threading.RateLimiting/ref/System.Threading.RateLimiting.cs index c8e968fa401e9b..1d48e8c717d126 100644 --- a/src/libraries/System.Threading.RateLimiting/ref/System.Threading.RateLimiting.cs +++ b/src/libraries/System.Threading.RateLimiting/ref/System.Threading.RateLimiting.cs @@ -79,7 +79,7 @@ protected virtual void Dispose(bool disposing) { } public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; } protected virtual System.Threading.Tasks.ValueTask DisposeAsyncCore() { throw null; } public abstract int GetAvailablePermits(TResource resource); - public System.Threading.RateLimiting.PartitionedRateLimiter TranslateKey(System.Func keyAdapter) { throw null; } + public System.Threading.RateLimiting.PartitionedRateLimiter WithTranslatedKey(System.Func keyAdapter, bool leaveOpen) { throw null; } } public enum QueueProcessingOrder { diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/PartitionedRateLimiter.T.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/PartitionedRateLimiter.T.cs index 03c709bd16151a..fdc21eebeb61fb 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/PartitionedRateLimiter.T.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/PartitionedRateLimiter.T.cs @@ -127,15 +127,16 @@ public async ValueTask DisposeAsync() /// The type to translate into . /// The function to be called every time a is passed to /// PartitionedRateLimiter<TOuter>.Acquire(TOuter, int) or PartitionedRateLimiter<TOuter>.WaitAsync(TOuter, int, CancellationToken). + /// Specifies whether the returned will dispose the wrapped . /// or does not dispose the wrapped . /// A new PartitionedRateLimiter<TOuter> that translates /// to and calls the inner . - public PartitionedRateLimiter TranslateKey(Func keyAdapter) + public PartitionedRateLimiter WithTranslatedKey(Func keyAdapter, bool leaveOpen) { // REVIEW: Do we want to have an option to dispose the inner limiter? // Should the default be to dispose the inner limiter and have an option to not dispose it? // See Stream wrappers like SslStream for prior-art - return new TranslatingLimiter(this, keyAdapter); + return new TranslatingLimiter(this, keyAdapter, leaveOpen); } } } diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TranslatingLimiter.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TranslatingLimiter.cs index f2b80147fc1262..b5a59fe079bab7 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TranslatingLimiter.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TranslatingLimiter.cs @@ -1,10 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace System.Threading.RateLimiting @@ -13,13 +9,15 @@ internal sealed class TranslatingLimiter : PartitionedRateLim { private readonly PartitionedRateLimiter _innerRateLimiter; private readonly Func _keyAdapter; + private readonly bool _disposeInnerLimiter; - private bool _disposed; + private int _disposed; - public TranslatingLimiter(PartitionedRateLimiter inner, Func keyAdapter) + public TranslatingLimiter(PartitionedRateLimiter inner, Func keyAdapter, bool leaveOpen) { _innerRateLimiter = inner; _keyAdapter = keyAdapter; + _disposeInnerLimiter = !leaveOpen; } public override int GetAvailablePermits(TResource resource) @@ -45,21 +43,32 @@ protected override ValueTask AcquireAsyncCore(TResource resource protected override void Dispose(bool disposing) { - _disposed = true; - base.Dispose(disposing); + if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 0) + { + if (_disposeInnerLimiter) + { + _innerRateLimiter.Dispose(); + } + } } protected override ValueTask DisposeAsyncCore() { - _disposed = true; - return base.DisposeAsyncCore(); + if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 0) + { + if (_disposeInnerLimiter) + { + return _innerRateLimiter.DisposeAsync(); + } + } + return default(ValueTask); } private void ThrowIfDispose() { - if (_disposed) + if (_disposed == 1) { - throw new ObjectDisposedException(nameof(PartitionedRateLimiter)); + throw new ObjectDisposedException(nameof(PartitionedRateLimiter)); } } } diff --git a/src/libraries/System.Threading.RateLimiting/tests/PartitionedRateLimiterTests.cs b/src/libraries/System.Threading.RateLimiting/tests/PartitionedRateLimiterTests.cs index 88710d19ef75e8..4ba9b60ae75f2e 100644 --- a/src/libraries/System.Threading.RateLimiting/tests/PartitionedRateLimiterTests.cs +++ b/src/libraries/System.Threading.RateLimiting/tests/PartitionedRateLimiterTests.cs @@ -662,11 +662,11 @@ public void Translate_AcquirePassesThroughToInnerLimiter() }); var translateCallCount = 0; - var translateLimiter = limiter.TranslateKey(i => + var translateLimiter = limiter.WithTranslatedKey(i => { translateCallCount++; return i.ToString(); - }); + }, leaveOpen: true); var lease = translateLimiter.AttemptAcquire(1); Assert.True(lease.IsAcquired); @@ -711,11 +711,11 @@ public async Task Translate_WaitAsyncPassesThroughToInnerLimiter() }); var translateCallCount = 0; - var translateLimiter = limiter.TranslateKey(i => + var translateLimiter = limiter.WithTranslatedKey(i => { translateCallCount++; return i.ToString(); - }); + }, leaveOpen: true); var lease = await translateLimiter.AcquireAsync(1); Assert.True(lease.IsAcquired); @@ -760,11 +760,11 @@ public void Translate_GetAvailablePermitsPassesThroughToInnerLimiter() }); var translateCallCount = 0; - var translateLimiter = limiter.TranslateKey(i => + var translateLimiter = limiter.WithTranslatedKey(i => { translateCallCount++; return i.ToString(); - }); + }, leaveOpen: true); Assert.Equal(1, translateLimiter.GetAvailablePermits(1)); Assert.Equal(1, translateCallCount); @@ -818,11 +818,11 @@ public void Translate_DisposeDoesNotDisposeInnerLimiter() }); var translateCallCount = 0; - var translateLimiter = limiter.TranslateKey(i => + var translateLimiter = limiter.WithTranslatedKey(i => { translateCallCount++; return i.ToString(); - }); + }, leaveOpen: true); translateLimiter.Dispose(); @@ -832,6 +832,46 @@ public void Translate_DisposeDoesNotDisposeInnerLimiter() Assert.Throws(() => translateLimiter.AttemptAcquire(1)); } + [Fact] + public void Translate_DisposeDoesDisposeInnerLimiter() + { + using var limiter = PartitionedRateLimiter.Create(resource => + { + if (resource == "1") + { + return RateLimitPartition.GetConcurrencyLimiter(1, + _ => new ConcurrencyLimiterOptions + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1 + }); + } + else + { + return RateLimitPartition.GetConcurrencyLimiter(1, + _ => new ConcurrencyLimiterOptions + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1 + }); + } + }); + + var translateCallCount = 0; + var translateLimiter = limiter.WithTranslatedKey(i => + { + translateCallCount++; + return i.ToString(); + }, leaveOpen: false); + + translateLimiter.Dispose(); + + Assert.Throws(() => limiter.AttemptAcquire("1")); + Assert.Throws(() => translateLimiter.AttemptAcquire(1)); + } + [Fact] public async Task Translate_DisposeAsyncDoesNotDisposeInnerLimiter() { @@ -860,11 +900,11 @@ public async Task Translate_DisposeAsyncDoesNotDisposeInnerLimiter() }); var translateCallCount = 0; - var translateLimiter = limiter.TranslateKey(i => + var translateLimiter = limiter.WithTranslatedKey(i => { translateCallCount++; return i.ToString(); - }); + }, leaveOpen: true); await translateLimiter.DisposeAsync(); @@ -873,5 +913,45 @@ public async Task Translate_DisposeAsyncDoesNotDisposeInnerLimiter() Assert.Throws(() => translateLimiter.AttemptAcquire(1)); } + + [Fact] + public async Task Translate_DisposeAsyncDoesDisposeInnerLimiter() + { + using var limiter = PartitionedRateLimiter.Create(resource => + { + if (resource == "1") + { + return RateLimitPartition.GetConcurrencyLimiter(1, + _ => new ConcurrencyLimiterOptions + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1 + }); + } + else + { + return RateLimitPartition.GetConcurrencyLimiter(1, + _ => new ConcurrencyLimiterOptions + { + PermitLimit = 1, + QueueProcessingOrder = QueueProcessingOrder.NewestFirst, + QueueLimit = 1 + }); + } + }); + + var translateCallCount = 0; + var translateLimiter = limiter.WithTranslatedKey(i => + { + translateCallCount++; + return i.ToString(); + }, leaveOpen: false); + + await translateLimiter.DisposeAsync(); + + Assert.Throws(() => limiter.AttemptAcquire("1")); + Assert.Throws(() => translateLimiter.AttemptAcquire(1)); + } } } From f4e8005a1e52a85af146b34a922ffe2a7d52bfb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Fi=C5=A1era?= Date: Fri, 12 Aug 2022 17:39:05 +0200 Subject: [PATCH 58/68] [wasm] Log as warning in interop generator when method signature is not supported (#71477) Introduce WASM0001 warning code for not supported pinvoke and icall signatures. --- .../WasmAppBuilder/IcallTableGenerator.cs | 14 +++- .../ManagedToNativeGenerator.cs | 4 +- .../WasmAppBuilder/PInvokeTableGenerator.cs | 15 +++-- .../PInvokeTableGeneratorTests.cs | 65 +++++++++++++++---- 4 files changed, 75 insertions(+), 23 deletions(-) diff --git a/src/tasks/WasmAppBuilder/IcallTableGenerator.cs b/src/tasks/WasmAppBuilder/IcallTableGenerator.cs index 1b689ad196a334..a1620338bcbe7a 100644 --- a/src/tasks/WasmAppBuilder/IcallTableGenerator.cs +++ b/src/tasks/WasmAppBuilder/IcallTableGenerator.cs @@ -32,7 +32,7 @@ internal sealed class IcallTableGenerator // The runtime icall table should be generated using // mono --print-icall-table // - public IEnumerable GenIcallTable(string? runtimeIcallTableFile, string[] assemblies, string? outputPath) + public IEnumerable Generate(string? runtimeIcallTableFile, string[] assemblies, string? outputPath) { _icalls.Clear(); _signatures.Clear(); @@ -146,7 +146,15 @@ private void ProcessType(Type type) if ((method.GetMethodImplementationFlags() & MethodImplAttributes.InternalCall) == 0) continue; - AddSignature(type, method); + try + { + AddSignature(type, method); + } + catch (Exception ex) when (ex is not LogAsErrorException) + { + Log.LogWarning(null, "WASM0001", "", "", 0, 0, 0, 0, $"Could not get icall, or callbacks for method '{method.Name}' because '{ex.Message}'"); + continue; + } var className = method.DeclaringType!.FullName!; if (!_runtimeIcalls.ContainsKey(className)) @@ -214,7 +222,7 @@ void AddSignature(Type type, MethodInfo method) throw new LogAsErrorException($"Unsupported parameter type in method '{type.FullName}.{method.Name}'"); } - Log.LogMessage(MessageImportance.Normal, $"[icall] Adding signature {signature} for method '{type.FullName}.{method.Name}'"); + Log.LogMessage(MessageImportance.Low, $"Adding icall signature {signature} for method '{type.FullName}.{method.Name}'"); _signatures.Add(signature); } } diff --git a/src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs b/src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs index 26c174e297f638..df48afaa52f84a 100644 --- a/src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs +++ b/src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs @@ -69,8 +69,8 @@ private void ExecuteInternal() var icall = new IcallTableGenerator(Log); IEnumerable cookies = Enumerable.Concat( - pinvoke.GenPInvokeTable(PInvokeModules, Assemblies!, PInvokeOutputPath!), - icall.GenIcallTable(RuntimeIcallTableFile, Assemblies!, IcallOutputPath) + pinvoke.Generate(PInvokeModules, Assemblies!, PInvokeOutputPath!), + icall.Generate(RuntimeIcallTableFile, Assemblies!, IcallOutputPath) ); var m2n = new InterpToNativeGenerator(Log); diff --git a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs index 4f2631a20cd721..46073228f6777d 100644 --- a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs +++ b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs @@ -19,7 +19,7 @@ internal sealed class PInvokeTableGenerator public PInvokeTableGenerator(TaskLoggingHelper log) => Log = log; - public IEnumerable GenPInvokeTable(string[] pinvokeModules, string[] assemblies, string outputPath) + public IEnumerable Generate(string[] pinvokeModules, string[] assemblies, string outputPath) { var modules = new Dictionary(); foreach (var module in pinvokeModules) @@ -69,10 +69,9 @@ private void CollectPInvokes(List pinvokes, List callb { CollectPInvokesForMethod(method); } - catch (Exception ex) + catch (Exception ex) when (ex is not LogAsErrorException) { - Log.LogMessage(MessageImportance.Low, $"Could not get pinvoke, or callbacks for method {method.Name}: {ex}"); - continue; + Log.LogWarning(null, "WASM0001", "", "", 0, 0, 0, 0, $"Could not get pinvoke, or callbacks for method '{method.Name}' because '{ex.Message}'"); } } @@ -88,10 +87,10 @@ void CollectPInvokesForMethod(MethodInfo method) string? signature = SignatureMapper.MethodToSignature(method); if (signature == null) { - throw new LogAsErrorException($"Unsupported parameter type in method '{type.FullName}.{method.Name}'"); + throw new NotSupportedException($"Unsupported parameter type in method '{type.FullName}.{method.Name}'"); } - Log.LogMessage(MessageImportance.Normal, $"[pinvoke] Adding signature {signature} for method '{type.FullName}.{method.Name}'"); + Log.LogMessage(MessageImportance.Low, $"Adding pinvoke signature {signature} for method '{type.FullName}.{method.Name}'"); signatures.Add(signature); } @@ -101,6 +100,7 @@ void CollectPInvokesForMethod(MethodInfo method) { if (cattr.AttributeType.FullName == "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute" || cattr.AttributeType.Name == "MonoPInvokeCallbackAttribute") + callbacks.Add(new PInvokeCallback(method)); } catch @@ -301,7 +301,8 @@ private static bool TryIsMethodGetParametersUnsupported(MethodInfo method, [NotN if (TryIsMethodGetParametersUnsupported(pinvoke.Method, out string? reason)) { - Log.LogWarning($"Skipping the following DllImport because '{reason}'. {Environment.NewLine} {pinvoke.Method}"); + Log.LogWarning(null, "WASM0001", "", "", 0, 0, 0, 0, $"Skipping pinvoke '{pinvoke.Method}' because '{reason}'."); + pinvoke.Skip = true; return null; } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs index 79c8d349802a53..791c211dfdf111 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; +using System.Reflection; using Xunit; using Xunit.Abstractions; @@ -58,29 +59,32 @@ public static int Main(string[] args) [BuildAndRun(host: RunHost.Chrome)] public void DllImportWithFunctionPointersCompilesWithWarning(BuildArgs buildArgs, RunHost host, string id) { - string code = @" + string code = + """ using System; using System.Runtime.InteropServices; public class Test { public static int Main() { - Console.WriteLine($""Main running""); + Console.WriteLine("Main running"); return 42; } - [DllImport(""variadic"", EntryPoint=""sum"")] + [DllImport("variadic", EntryPoint="sum")] public unsafe static extern int using_sum_one(delegate* unmanaged callback); - [DllImport(""variadic"", EntryPoint=""sum"")] + [DllImport("variadic", EntryPoint="sum")] public static extern int sum_one(int a, int b); - }"; + } + """; (buildArgs, string output) = BuildForVariadicFunctionTests(code, buildArgs with { ProjectName = $"fnptr_{buildArgs.Config}_{id}" }, id); - Assert.Matches("warning.*Skipping.*because.*function pointer", output); - Assert.Matches("warning.*using_sum_one", output); + + Assert.Matches("warning\\sWASM0001.*Could\\snot\\sget\\spinvoke.*Parsing\\sfunction\\spointer\\stypes", output); + Assert.Matches("warning\\sWASM0001.*Skipping.*using_sum_one.*because.*function\\spointer", output); output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); Assert.Contains("Main running", output); @@ -108,8 +112,44 @@ public static int Main() (buildArgs, string output) = BuildForVariadicFunctionTests(code, buildArgs with { ProjectName = $"fnptr_variadic_{buildArgs.Config}_{id}" }, id); - Assert.Matches("warning.*Skipping.*because.*function pointer", output); - Assert.Matches("warning.*using_sum_one", output); + + Assert.Matches("warning\\sWASM0001.*Could\\snot\\sget\\spinvoke.*Parsing\\sfunction\\spointer\\stypes", output); + Assert.Matches("warning\\sWASM0001.*Skipping.*using_sum_one.*because.*function\\spointer", output); + + output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); + Assert.Contains("Main running", output); + } + + [Theory] + [BuildAndRun(host: RunHost.Chrome)] + public void DllImportWithFunctionPointers_WarningsAsMessages(BuildArgs buildArgs, RunHost host, string id) + { + string code = + """ + using System; + using System.Runtime.InteropServices; + public class Test + { + public static int Main() + { + Console.WriteLine("Main running"); + return 42; + } + + [DllImport("someting")] + public unsafe static extern void SomeFunction1(delegate* unmanaged callback); + } + """; + + (buildArgs, string output) = BuildForVariadicFunctionTests( + code, + buildArgs with { ProjectName = $"fnptr_{buildArgs.Config}_{id}" }, + id, + verbosity: "normal", + extraProperties: "$(MSBuildWarningsAsMessage);WASM0001" + ); + + Assert.DoesNotContain("warning WASM0001", output); output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); Assert.Contains("Main running", output); @@ -166,12 +206,14 @@ public void BuildNativeInNonEnglishCulture(BuildArgs buildArgs, string culture, Assert.Contains("square: 25", output); } - private (BuildArgs, string) BuildForVariadicFunctionTests(string programText, BuildArgs buildArgs, string id) + private (BuildArgs, string) BuildForVariadicFunctionTests(string programText, BuildArgs buildArgs, string id, string? verbosity = null, string extraProperties = "") { + extraProperties += "true<_WasmDevel>true"; + string filename = "variadic.o"; buildArgs = ExpandBuildArgs(buildArgs, extraItems: $"", - extraProperties: "true<_WasmDevel>true"); + extraProperties: extraProperties); (_, string output) = BuildProject(buildArgs, id: id, @@ -183,6 +225,7 @@ public void BuildNativeInNonEnglishCulture(BuildArgs buildArgs, string culture, Path.Combine(_projectDir!, filename)); }, Publish: buildArgs.AOT, + Verbosity: verbosity, DotnetWasmFromRuntimePack: false)); return (buildArgs, output); From 77d95331e5c34a5a676f9e3396ce7351651cfc8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20K=C3=B6plinger?= Date: Fri, 12 Aug 2022 17:51:13 +0200 Subject: [PATCH 59/68] Fix build error in mini-ppc.h (#73845) Looks like this got inadvertently broken by https://github.com/dotnet/runtime/pull/65723 --- src/mono/mono/mini/mini-ppc.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mono/mono/mini/mini-ppc.h b/src/mono/mono/mini/mini-ppc.h index 281eee97b5bb1b..688ca1f7992624 100644 --- a/src/mono/mono/mini/mini-ppc.h +++ b/src/mono/mono/mini/mini-ppc.h @@ -118,8 +118,7 @@ typedef struct MonoCompileArch { #else #define MONO_ARCH_CALLEE_FREGS (0xff << ppc_f1) #endif -#define MONO_ARCH_CALLEE_SAVED_FREGS (~(MONO_ARCH_CALLEE_FRE -GS | 1)) +#define MONO_ARCH_CALLEE_SAVED_FREGS (~(MONO_ARCH_CALLEE_FREGS | 1)) #ifdef TARGET_POWERPC64 #define MONO_ARCH_INST_FIXED_REG(desc) (((desc) == 'a')? ppc_r3: \ From dd05959f93a9415643d51b99814945ced936b6ad Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 12 Aug 2022 08:52:12 -0700 Subject: [PATCH 60/68] Ensure partial Vector256 acceleration is still possible on AVX only hardware (#73720) * Ensure that Vector256 APIs exposed in .NET Core 3.1 remain accelerated in .NET 7 * Ensure new Vector256 APIs that are trivially limited to Avx only are accelerated --- src/coreclr/jit/hwintrinsic.cpp | 4 +- src/coreclr/jit/hwintrinsic.h | 14 +++- src/coreclr/jit/hwintrinsiclistxarch.h | 92 +++++++++++++------------- 3 files changed, 61 insertions(+), 49 deletions(-) diff --git a/src/coreclr/jit/hwintrinsic.cpp b/src/coreclr/jit/hwintrinsic.cpp index 58a06532ae0ea7..b8e74010ca8f92 100644 --- a/src/coreclr/jit/hwintrinsic.cpp +++ b/src/coreclr/jit/hwintrinsic.cpp @@ -383,8 +383,8 @@ NamedIntrinsic HWIntrinsicInfo::lookupId(Compiler* comp, NamedIntrinsic ni = intrinsicInfo.id; #if defined(TARGET_XARCH) - // on AVX1-only CPUs we only support NI_Vector256_Create intrinsic in Vector256 - if (isLimitedVector256Isa && (ni != NI_Vector256_Create)) + // on AVX1-only CPUs we only support a subset of intrinsics in Vector256 + if (isLimitedVector256Isa && !AvxOnlyCompatible(ni)) { return NI_Illegal; } diff --git a/src/coreclr/jit/hwintrinsic.h b/src/coreclr/jit/hwintrinsic.h index e15f3ae6029ab0..8a82605991e165 100644 --- a/src/coreclr/jit/hwintrinsic.h +++ b/src/coreclr/jit/hwintrinsic.h @@ -147,7 +147,11 @@ enum HWIntrinsicFlag : unsigned int // Returns Per-Element Mask // the intrinsic returns a vector containing elements that are either "all bits set" or "all bits clear" // this output can be used as a per-element mask - HW_Flag_ReturnsPerElementMask = 0x20000 + HW_Flag_ReturnsPerElementMask = 0x20000, + + // AvxOnlyCompatible + // the intrinsic can be used on hardware with AVX but not AVX2 support + HW_Flag_AvxOnlyCompatible = 0x40000, #elif defined(TARGET_ARM64) // The intrinsic has an immediate operand @@ -658,6 +662,14 @@ struct HWIntrinsicInfo #endif } +#if defined(TARGET_XARCH) + static bool AvxOnlyCompatible(NamedIntrinsic id) + { + HWIntrinsicFlag flags = lookupFlags(id); + return (flags & HW_Flag_AvxOnlyCompatible) != 0; + } +#endif + static bool BaseTypeFromFirstArg(NamedIntrinsic id) { HWIntrinsicFlag flags = lookupFlags(id); diff --git a/src/coreclr/jit/hwintrinsiclistxarch.h b/src/coreclr/jit/hwintrinsiclistxarch.h index 8b5f283f02042f..7068e39bd1b560 100644 --- a/src/coreclr/jit/hwintrinsiclistxarch.h +++ b/src/coreclr/jit/hwintrinsiclistxarch.h @@ -129,44 +129,44 @@ HARDWARE_INTRINSIC(Vector128, Xor, // Vector256 Intrinsics HARDWARE_INTRINSIC(Vector256, Abs, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, Add, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, AndNot, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, As, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, AsByte, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, AsDouble, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, AsInt16, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, AsInt32, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, AsInt64, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, AsSByte, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, AsSingle, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, AsUInt16, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, AsUInt32, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, AsUInt64, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, AsVector, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, AsVector256, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, BitwiseAnd, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, BitwiseOr, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, Ceiling, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, ConditionalSelect, 32, 3, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) +HARDWARE_INTRINSIC(Vector256, AndNot, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, As, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, AsByte, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, AsDouble, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, AsInt16, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, AsInt32, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, AsInt64, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, AsSByte, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, AsSingle, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, AsUInt16, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, AsUInt32, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, AsUInt64, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, AsVector, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, AsVector256, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, BitwiseAnd, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, BitwiseOr, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, Ceiling, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, ConditionalSelect, 32, 3, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector256, ConvertToDouble, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) -HARDWARE_INTRINSIC(Vector256, ConvertToInt32, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(Vector256, ConvertToInt32, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector256, ConvertToInt64, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) -HARDWARE_INTRINSIC(Vector256, ConvertToSingle, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) +HARDWARE_INTRINSIC(Vector256, ConvertToSingle, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector256, ConvertToUInt32, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(Vector256, ConvertToUInt64, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) -HARDWARE_INTRINSIC(Vector256, Create, 32, -1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, CreateScalarUnsafe, 32, 1, {INS_movd, INS_movd, INS_movd, INS_movd, INS_movd, INS_movd, INS_movd, INS_movd, INS_movss, INS_movsdsse2}, HW_Category_SIMDScalar, HW_Flag_SpecialImport|HW_Flag_SpecialCodeGen|HW_Flag_NoRMWSemantics) +HARDWARE_INTRINSIC(Vector256, Create, 32, -1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, CreateScalarUnsafe, 32, 1, {INS_movd, INS_movd, INS_movd, INS_movd, INS_movd, INS_movd, INS_movd, INS_movd, INS_movss, INS_movsdsse2}, HW_Category_SIMDScalar, HW_Flag_SpecialImport|HW_Flag_SpecialCodeGen|HW_Flag_NoRMWSemantics|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector256, Divide, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, Dot, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, Equals, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_ReturnsPerElementMask) HARDWARE_INTRINSIC(Vector256, EqualsAll, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, EqualsAny, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, ExtractMostSignificantBits, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, Floor, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, get_AllBitsSet, 32, 0, {INS_pcmpeqd, INS_pcmpeqd, INS_pcmpeqd, INS_pcmpeqd, INS_pcmpeqd, INS_pcmpeqd, INS_pcmpeqd, INS_pcmpeqd, INS_cmpps, INS_cmpps}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_ReturnsPerElementMask) -HARDWARE_INTRINSIC(Vector256, get_Count, 32, 0, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, get_Zero, 32, 0, {INS_xorps, INS_xorps, INS_xorps, INS_xorps, INS_xorps, INS_xorps, INS_xorps, INS_xorps, INS_xorps, INS_xorps}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_ReturnsPerElementMask) -HARDWARE_INTRINSIC(Vector256, GetElement, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg) -HARDWARE_INTRINSIC(Vector256, GetLower, 32, 1, {INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movups, INS_movupd}, HW_Category_SimpleSIMD, HW_Flag_SpecialImport|HW_Flag_SpecialCodeGen|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoRMWSemantics) +HARDWARE_INTRINSIC(Vector256, Floor, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, get_AllBitsSet, 32, 0, {INS_pcmpeqd, INS_pcmpeqd, INS_pcmpeqd, INS_pcmpeqd, INS_pcmpeqd, INS_pcmpeqd, INS_pcmpeqd, INS_pcmpeqd, INS_cmpps, INS_cmpps}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_ReturnsPerElementMask|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, get_Count, 32, 0, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, get_Zero, 32, 0, {INS_xorps, INS_xorps, INS_xorps, INS_xorps, INS_xorps, INS_xorps, INS_xorps, INS_xorps, INS_xorps, INS_xorps}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_ReturnsPerElementMask|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, GetElement, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, GetLower, 32, 1, {INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movdqu, INS_movups, INS_movupd}, HW_Category_SimpleSIMD, HW_Flag_SpecialImport|HW_Flag_SpecialCodeGen|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoRMWSemantics|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector256, GreaterThan, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_ReturnsPerElementMask) HARDWARE_INTRINSIC(Vector256, GreaterThanAll, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, GreaterThanAny, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) @@ -179,44 +179,44 @@ HARDWARE_INTRINSIC(Vector256, LessThanAny, HARDWARE_INTRINSIC(Vector256, LessThanOrEqual, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_ReturnsPerElementMask) HARDWARE_INTRINSIC(Vector256, LessThanOrEqualAll, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, LessThanOrEqualAny, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, Load, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, LoadAligned, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, LoadAlignedNonTemporal, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, LoadUnsafe, 32, -1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) +HARDWARE_INTRINSIC(Vector256, Load, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, LoadAligned, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, LoadAlignedNonTemporal, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, LoadUnsafe, 32, -1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector256, Max, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, Min, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, Multiply, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, Narrow, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, Negate, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, OnesComplement, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) +HARDWARE_INTRINSIC(Vector256, OnesComplement, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector256, op_Addition, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, op_BitwiseAnd, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_Commutative) -HARDWARE_INTRINSIC(Vector256, op_BitwiseOr, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_Commutative) +HARDWARE_INTRINSIC(Vector256, op_BitwiseAnd, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_Commutative|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, op_BitwiseOr, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_Commutative|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector256, op_Division, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, op_Equality, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_Commutative) -HARDWARE_INTRINSIC(Vector256, op_ExclusiveOr, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) +HARDWARE_INTRINSIC(Vector256, op_ExclusiveOr, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector256, op_Inequality, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_Commutative) HARDWARE_INTRINSIC(Vector256, op_Multiply, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, op_OnesComplement, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) +HARDWARE_INTRINSIC(Vector256, op_OnesComplement, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector256, op_Subtraction, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, op_UnaryNegation, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, op_UnaryPlus, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) +HARDWARE_INTRINSIC(Vector256, op_UnaryPlus, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector256, ShiftLeft, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, ShiftRightArithmetic, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, ShiftRightLogical, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, Shuffle, 32, -1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, Sqrt, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, Store, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, StoreAligned, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, StoreAlignedNonTemporal, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, StoreUnsafe, 32, -1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) +HARDWARE_INTRINSIC(Vector256, Sqrt, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, Store, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, StoreAligned, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, StoreAlignedNonTemporal, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, StoreUnsafe, 32, -1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector256, Subtract, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) HARDWARE_INTRINSIC(Vector256, Sum, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoCodeGen) -HARDWARE_INTRINSIC(Vector256, ToScalar, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movss, INS_movsdsse2}, HW_Category_SimpleSIMD, HW_Flag_SpecialImport|HW_Flag_SpecialCodeGen|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoRMWSemantics) +HARDWARE_INTRINSIC(Vector256, ToScalar, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_movss, INS_movsdsse2}, HW_Category_SimpleSIMD, HW_Flag_SpecialImport|HW_Flag_SpecialCodeGen|HW_Flag_BaseTypeFromFirstArg|HW_Flag_NoRMWSemantics|HW_Flag_AvxOnlyCompatible) HARDWARE_INTRINSIC(Vector256, WidenLower, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) HARDWARE_INTRINSIC(Vector256, WidenUpper, 32, 1, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_BaseTypeFromFirstArg) -HARDWARE_INTRINSIC(Vector256, WithElement, 32, 3, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoContainment|HW_Flag_BaseTypeFromFirstArg) -HARDWARE_INTRINSIC(Vector256, Xor, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen) +HARDWARE_INTRINSIC(Vector256, WithElement, 32, 3, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoContainment|HW_Flag_BaseTypeFromFirstArg|HW_Flag_AvxOnlyCompatible) +HARDWARE_INTRINSIC(Vector256, Xor, 32, 2, {INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid, INS_invalid}, HW_Category_Helper, HW_Flag_SpecialImport|HW_Flag_NoCodeGen|HW_Flag_AvxOnlyCompatible) // *************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************** // ISA Function name SIMD size NumArg Instructions Category Flags From 21dc0040b5d6cd52501d9c502fb3ddbc23db97d7 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Fri, 12 Aug 2022 18:54:33 +0300 Subject: [PATCH 61/68] [mono][jit] Don't inflate Mono.ValueTuple with byrefs (#73689) Mono.ValueTuple's must have the same layout as the original vtype. Use normal IntPtr for ref fields. This also allows sharing of more signatures. Before this change the behavior was dubious when sharing valuetypes with ref fields (like Span). The value tuple class was inflated with byref types and it ended up having byref fields, even though it is not byreflike, resulting in failure during class loading. --- src/mono/mono/mini/mini-generic-sharing.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/mono/mono/mini/mini-generic-sharing.c b/src/mono/mono/mini/mini-generic-sharing.c index 8a4ba64a79ea91..50fe18a1c034eb 100644 --- a/src/mono/mono/mini/mini-generic-sharing.c +++ b/src/mono/mono/mini/mini-generic-sharing.c @@ -1246,6 +1246,16 @@ get_wrapper_shared_vtype (MonoType *t) } g_assert (tuple_class); + for (int i = 0; i < findex; i++) { + if (m_type_is_byref (args [i])) { +#if TARGET_SIZEOF_VOID_P == 8 + args [i] = m_class_get_byval_arg (mono_defaults.int_class); +#else + args [i] = m_class_get_byval_arg (mono_defaults.int32_class); +#endif + } + } + memset (&ctx, 0, sizeof (ctx)); ctx.class_inst = mono_metadata_get_generic_inst (findex, args); From 3db2ebfcfa2b5c7074a7e513a56a39f07f6ce6c0 Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Fri, 12 Aug 2022 13:28:46 -0300 Subject: [PATCH 62/68] [debugger] First draft of managed debugger on wasi (#67272) * first version of debug wasi * remove comment * fixing readme * Fix compilation error * fix compilation error * fix compilation * addressing steve comment * Stepping working * fix debugging aspnetcore app * wrong merge * addressing @viniciusjarina comment * addressing steve comments * work for remote debugging * make it work on windows * Update mini-wasi-debugger.c fix indentation * Update mini-wasi-debugger.c * Update mini-wasi-debugger.c * Trying to fix compilation * Update src/mono/mono/component/mini-wasi-debugger.c Co-authored-by: Ankit Jain * Update src/mono/mono/component/mini-wasi-debugger.c Co-authored-by: Ankit Jain * Update src/mono/mono/component/mini-wasi-debugger.c Co-authored-by: Ankit Jain * Update src/mono/mono/component/debugger-agent.c Co-authored-by: Ankit Jain * Update src/mono/mono/component/debugger-agent.c Co-authored-by: Ankit Jain * Update src/mono/mono/mini/interp/interp.c Co-authored-by: Ankit Jain * Update src/mono/mono/mini/interp/interp.c Co-authored-by: Ankit Jain * Update src/mono/wasi/Makefile Co-authored-by: Ankit Jain * Update src/mono/wasi/mono-wasi-driver/driver.c Co-authored-by: Ankit Jain * Update src/mono/mono/component/debugger-agent.c Co-authored-by: Ankit Jain * Addressing @radical comments * fix compilation errors * fixing readme * debugger working again * addressing @BrzVlad comments * Addressing @radical comments * Fix merge * Fix merge * Update diagnostics_server.c * Treat root directory as always existing, even if WASI isn't granting filesystem access. Needed for ASP.NET Core. * Update sample build config * Stop trying to use JS (browser) APIs for crypto, as there is no JS in wasi * Revert unneeded change * Apply suggestions from code review Co-authored-by: Ankit Jain * Addressing @radical comments * Addressing @radical comments * fixing debugger behavior and addressing @radical comments * Addressing @ SteveSandersonMS comments * Apply suggestions from code review Co-authored-by: Ankit Jain * Addressing radical comments * [wasi] Provision wasmtime as needed * [wasi] build, and run wasi sample on CI * [wasi] don't install wasmtime by default, but do that for CI * [wasi] Show a useful error if wasmtime is not found * [wasi] provision ninja * Fix path * Fix warnings to make CI green * debug * Move building wasi into a separate yml step, so it gets built after the whole wasm build is complete * fix yml * Enable wasi build on libtests job * Fix yml again Co-authored-by: Ankit Jain Co-authored-by: Steve Sanderson --- .../templates/additional-steps-then-helix.yml | 40 ++++ .../common/templates/wasm-library-tests.yml | 11 +- eng/pipelines/runtime.yml | 1 + src/mono/CMakeLists.txt | 5 +- src/mono/mono/component/CMakeLists.txt | 1 + src/mono/mono/component/debugger-agent.c | 185 +++++++++++++----- src/mono/mono/component/debugger-agent.h | 14 ++ src/mono/mono/component/debugger-engine.h | 6 + src/mono/mono/component/debugger-stub.c | 21 ++ src/mono/mono/component/debugger.c | 5 +- src/mono/mono/component/debugger.h | 3 + src/mono/mono/component/diagnostics_server.c | 2 +- src/mono/mono/component/event_pipe-stub.c | 4 +- src/mono/mono/component/event_pipe-wasm.h | 2 +- src/mono/mono/component/event_pipe.c | 10 +- src/mono/mono/component/mini-wasi-debugger.c | 180 +++++++++++++++++ src/mono/mono/component/mini-wasm-debugger.c | 3 + src/mono/mono/mini/CMakeLists.txt | 2 +- src/mono/mono/mini/debugger-agent-external.h | 3 + src/mono/mono/mini/interp/interp.c | 12 ++ src/mono/mono/mini/mini-exceptions.c | 7 +- src/mono/mono/utils/mono-threads-wasm.c | 2 + src/mono/mono/utils/mono-threads.c | 3 +- src/mono/wasi/Makefile | 30 ++- src/mono/wasi/Makefile.variable | 12 +- src/mono/wasi/README.md | 40 +++- src/mono/wasi/mono-wasi-driver/driver.c | 58 ++++-- src/mono/wasi/mono-wasi-driver/driver.h | 1 + src/mono/wasi/mono-wasi-driver/stubs.c | 2 +- src/mono/wasi/sample/SampleMakefile.variable | 8 +- src/mono/wasi/sample/console/Makefile | 14 +- .../WasiConsoleApp/WasiConsoleApp.csproj | 1 + src/mono/wasi/sample/console/main.c | 2 +- src/mono/wasm/wasm.proj | 1 - 34 files changed, 594 insertions(+), 97 deletions(-) create mode 100644 eng/pipelines/common/templates/additional-steps-then-helix.yml create mode 100644 src/mono/mono/component/mini-wasi-debugger.c diff --git a/eng/pipelines/common/templates/additional-steps-then-helix.yml b/eng/pipelines/common/templates/additional-steps-then-helix.yml new file mode 100644 index 00000000000000..af5c335b6d8535 --- /dev/null +++ b/eng/pipelines/common/templates/additional-steps-then-helix.yml @@ -0,0 +1,40 @@ +parameters: + archType: '' + buildConfig: '' + condition: always() + creator: '' + extraHelixArguments: '' + helixQueues: '' + interpreter: '' + osGroup: '' + additionalSteps: [] + runTests: true + runtimeFlavor: '' + shouldContinueOnError: false + targetRid: '' + testRunNamePrefixSuffix: '' + testScope: 'innerloop' # innerloop | outerloop | all + scenarios: ['normal'] + +steps: + - ${{ if ne(parameters.additionalSteps, '') }}: + - ${{ each additionalStep in parameters.additionalSteps }}: + - ${{ additionalStep }} + + - ${{ if eq(parameters.runTests, true) }}: + - template: /eng/pipelines/libraries/helix.yml + parameters: + runtimeFlavor: ${{ parameters.runtimeFlavor }} + archType: ${{ parameters.archType }} + buildConfig: ${{ parameters.buildConfig }} + helixQueues: ${{ parameters.helixQueues }} + osGroup: ${{ parameters.osGroup }} + targetRid: ${{ parameters.targetRid }} + testRunNamePrefixSuffix: ${{ parameters.testRunNamePrefixSuffix }} + testScope: ${{ parameters.testScope }} + interpreter: ${{ parameters.interpreter }} + condition: ${{ parameters.condition }} + shouldContinueOnError: ${{ parameters.shouldContinueOnError }} + extraHelixArguments: ${{ parameters.extraHelixArguments }} /p:BrowserHost=$(_hostedOs) + creator: dotnet-bot + scenarios: ${{ parameters.scenarios }} diff --git a/eng/pipelines/common/templates/wasm-library-tests.yml b/eng/pipelines/common/templates/wasm-library-tests.yml index ada2484fedc375..a1f28093bfc9f2 100644 --- a/eng/pipelines/common/templates/wasm-library-tests.yml +++ b/eng/pipelines/common/templates/wasm-library-tests.yml @@ -1,5 +1,6 @@ parameters: alwaysRun: false + buildAndRunWasi: false extraBuildArgs: '' extraHelixArgs: '' isExtraPlatformsBuild: false @@ -49,8 +50,16 @@ jobs: eq(dependencies.evaluate_paths.outputs['SetPathVars_allwasm.containsChange'], true), eq(dependencies.evaluate_paths.outputs['SetPathVars_mono.containsChange'], true)) # extra steps, run tests - extraStepsTemplate: /eng/pipelines/libraries/helix.yml + extraStepsTemplate: /eng/pipelines/common/templates/additional-steps-then-helix.yml extraStepsParameters: + additionalSteps: + - ${{ if eq(parameters.buildAndRunWasi, true) }}: + - script: >- + make -C src/mono/wasi provision-deps all && + make -C src/mono/wasi/sample/console run + name: build_wasi + displayName: Build Wasi, and run a sample + creator: dotnet-bot testRunNamePrefixSuffix: Mono_$(_BuildConfig) extraHelixArguments: /p:BrowserHost=$(_hostedOs) ${{ parameters.runSmokeOnlyArg }} ${{ parameters.extraHelixArgs }} diff --git a/eng/pipelines/runtime.yml b/eng/pipelines/runtime.yml index 1fa8399501b037..dddba18e3d1d52 100644 --- a/eng/pipelines/runtime.yml +++ b/eng/pipelines/runtime.yml @@ -360,6 +360,7 @@ jobs: parameters: platforms: - Browser_wasm + buildAndRunWasi: true alwaysRun: ${{ variables.isRollingBuild }} scenarios: - normal diff --git a/src/mono/CMakeLists.txt b/src/mono/CMakeLists.txt index dc4ff5b1886481..1629fe4dbcec93 100644 --- a/src/mono/CMakeLists.txt +++ b/src/mono/CMakeLists.txt @@ -247,13 +247,14 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Emscripten") set(INTERNAL_ZLIB 1) elseif(CMAKE_SYSTEM_NAME STREQUAL "WASI") set(HOST_WASI 1) - add_definitions(-D_WASI_EMULATED_SIGNAL -D_WASI_EMULATED_MMAN) + add_definitions(-D_WASI_EMULATED_SIGNAL -D_WASI_EMULATED_MMAN -DHOST_WASI) add_definitions(-DNO_GLOBALIZATION_SHIM) add_definitions(-D_THREAD_SAFE) + add_definitions(-DGEN_PINVOKE) set(DISABLE_SHARED_LIBS 1) set(INTERNAL_ZLIB 1) set(DISABLE_EXECUTABLES 1) - set(DISABLE_COMPONENTS 1) + set(STATIC_COMPONENTS 1) set(WASI_DRIVER_SOURCES wasi/mono-wasi-driver/driver.c diff --git a/src/mono/mono/component/CMakeLists.txt b/src/mono/mono/component/CMakeLists.txt index b8c1d608dd6832..d83c9144af07f9 100644 --- a/src/mono/mono/component/CMakeLists.txt +++ b/src/mono/mono/component/CMakeLists.txt @@ -31,6 +31,7 @@ set(${MONO_DEBUGGER_COMPONENT_NAME}-sources ${MONO_COMPONENT_PATH}/debugger-state-machine.h ${MONO_COMPONENT_PATH}/debugger-state-machine.c ${MONO_COMPONENT_PATH}/mini-wasm-debugger.c + ${MONO_COMPONENT_PATH}/mini-wasi-debugger.c ${MONO_COMPONENT_PATH}/debugger-protocol.h ${MONO_COMPONENT_PATH}/debugger-protocol.c ) diff --git a/src/mono/mono/component/debugger-agent.c b/src/mono/mono/component/debugger-agent.c index c92fdad737e4cb..45404d5967b3f5 100644 --- a/src/mono/mono/component/debugger-agent.c +++ b/src/mono/mono/component/debugger-agent.c @@ -177,6 +177,7 @@ typedef struct { int keepalive; gboolean setpgid; gboolean using_icordbg; + char *debugger_fd; } AgentConfig; struct _DebuggerTlsData { @@ -336,6 +337,10 @@ static AgentConfig agent_config; */ static gint32 agent_inited; +#ifdef HOST_WASI +static gboolean resumed_from_wasi; +#endif + #ifndef DISABLE_SOCKET_TRANSPORT static SOCKET conn_fd; static SOCKET listen_fd; @@ -429,11 +434,16 @@ static gboolean buffer_replies; tls = &debugger_wasm_thread; #endif +static void (*start_debugger_thread_func) (MonoError *error); +static void (*suspend_vm_func) (void); +static void (*suspend_current_func) (void); + //mono_native_tls_get_value (debugger_tls_id); #define dbg_lock mono_de_lock #define dbg_unlock mono_de_unlock +static void suspend_vm (void); static void transport_init (void); static void transport_connect (const char *address); static gboolean transport_handshake (void); @@ -601,7 +611,7 @@ debugger_agent_parse_options (char *options) int port; char *extra; -#ifndef MONO_ARCH_SOFT_DEBUG_SUPPORTED +#if !defined(MONO_ARCH_SOFT_DEBUG_SUPPORTED) && !defined(HOST_WASI) PRINT_ERROR_MSG ("--debugger-agent is not supported on this platform.\n"); exit (1); #endif @@ -656,6 +666,8 @@ debugger_agent_parse_options (char *options) agent_config.keepalive = atoi (arg + 10); } else if (strncmp (arg, "setpgid=", 8) == 0) { agent_config.setpgid = parse_flag ("setpgid", arg + 8); + } else if (strncmp (arg, "debugger_fd=", 12) == 0) { + agent_config.debugger_fd = g_strdup (arg + 12); } else { print_usage (); exit (1); @@ -677,10 +689,12 @@ debugger_agent_parse_options (char *options) exit (1); } +#ifndef HOST_WASI if (agent_config.address == NULL && !agent_config.server) { PRINT_ERROR_MSG ("debugger-agent: The 'address' option is mandatory.\n"); exit (1); } +#endif // FIXME: if (!strcmp (agent_config.transport, "dt_socket")) { @@ -750,8 +764,8 @@ mono_debugger_is_disconnected (void) return disconnected; } -static void -debugger_agent_init (void) +void +mono_debugger_agent_init_internal (void) { if (!agent_config.enabled) return; @@ -769,8 +783,12 @@ debugger_agent_init (void) cbs.handle_multiple_ss_requests = handle_multiple_ss_requests; mono_de_init (&cbs); - transport_init (); + + start_debugger_thread_func = start_debugger_thread; + suspend_vm_func = suspend_vm; + suspend_current_func = suspend_current; + /* Need to know whenever a thread has acquired the loader mutex */ mono_loader_lock_track_ownership (TRUE); @@ -873,14 +891,17 @@ finish_agent_init (gboolean on_startup) } #endif } - +#ifndef HOST_WASI transport_connect (agent_config.address); +#else + transport_connect (agent_config.debugger_fd); +#endif if (!on_startup) { /* Do some which is usually done after sending the VMStart () event */ vm_start_event_sent = TRUE; ERROR_DECL (error); - start_debugger_thread (error); + start_debugger_thread_func (error); mono_error_assert_ok (error); } } @@ -2234,6 +2255,25 @@ mono_wasm_get_tls (void) } #endif +#ifdef HOST_WASI +void +mono_wasi_suspend_current (void) +{ + GET_DEBUGGER_TLS(); + g_assert (tls); + tls->really_suspended = TRUE; + return; +} + +void +mono_debugger_agent_initialize_function_pointers (void *start_debugger_thread, void *suspend_vm, void *suspend_current) +{ + start_debugger_thread_func = start_debugger_thread; + suspend_vm_func = suspend_vm; + suspend_current_func = suspend_current; +} +#endif + static MonoCoopMutex suspend_mutex; /* Cond variable used to wait for suspend_count becoming 0 */ @@ -2501,7 +2541,7 @@ process_suspend (DebuggerTlsData *tls, MonoContext *ctx) save_thread_context (ctx); - suspend_current (); + suspend_current_func (); } @@ -2566,6 +2606,7 @@ suspend_vm (void) static void resume_vm (void) { +#ifndef HOST_WASI g_assert (is_debugger_thread ()); mono_loader_lock (); @@ -2590,6 +2631,9 @@ resume_vm (void) //g_assert (err == 0); mono_loader_unlock (); +#else + resumed_from_wasi = TRUE; +#endif } /* @@ -2765,6 +2809,9 @@ count_threads_to_wait_for (void) static void wait_for_suspend (void) { +#ifdef HOST_WASI + return; +#endif int nthreads, nwait, err; gboolean waited = FALSE; @@ -3057,8 +3104,13 @@ compute_frame_info (MonoInternalThread *thread, DebuggerTlsData *tls, gboolean f mono_walk_stack_with_state (process_frame, &tls->context, opts, &user_data); } else { // FIXME: +#ifdef HOST_WASI + mono_walk_stack_with_state (process_frame, NULL, opts, &user_data); + tls->context.valid = TRUE; +#else tls->frame_count = 0; return; +#endif } new_frame_count = g_slist_length (user_data.frames); @@ -3668,7 +3720,7 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx if (event == EVENT_KIND_VM_START) { if (!agent_config.defer) { ERROR_DECL (error); - start_debugger_thread (error); + start_debugger_thread_func (error); mono_error_assert_ok (error); } } @@ -3690,7 +3742,7 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx save_thread_context (ctx); DebuggerTlsData *tls = (DebuggerTlsData *)mono_g_hash_table_lookup (thread_to_tls, mono_thread_internal_current ()); tls->suspend_count++; - suspend_vm (); + suspend_vm_func (); if (keepalive_obj) /* This will keep this object alive */ @@ -3728,7 +3780,7 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx case SUSPEND_POLICY_NONE: break; case SUSPEND_POLICY_ALL: - suspend_current (); + suspend_current_func (); break; case SUSPEND_POLICY_EVENT_THREAD: NOT_IMPLEMENTED; @@ -3736,6 +3788,15 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx default: g_assert_not_reached (); } +#ifdef HOST_WASI + resumed_from_wasi = FALSE; + while (suspend_policy != SUSPEND_POLICY_NONE && !resumed_from_wasi) + { + GET_DEBUGGER_TLS(); + tls->really_suspended = TRUE; + mono_debugger_agent_receive_and_process_command (); + } +#endif } static void @@ -3765,7 +3826,7 @@ runtime_initialized (MonoProfiler *prof) process_profiler_event (EVENT_KIND_ASSEMBLY_LOAD, (mono_get_corlib ()->assembly)); if (agent_config.defer) { ERROR_DECL (error); - start_debugger_thread (error); + start_debugger_thread_func (error); mono_error_assert_ok (error); } } @@ -3839,7 +3900,7 @@ thread_startup (MonoProfiler *prof, uintptr_t tid) * suspend_vm () could have missed this thread, so wait for a resume. */ - suspend_current (); + suspend_current_func (); } static void @@ -4546,6 +4607,9 @@ handle_multiple_ss_requests (void) static int ensure_runtime_is_suspended (void) { +#ifdef HOST_WASI + return ERR_NONE; +#endif if (suspend_count == 0) return ERR_NOT_SUSPENDED; @@ -4565,8 +4629,11 @@ mono_ss_create_init_args (SingleStepReq *ss_req, SingleStepArgs *args) gboolean set_ip = FALSE; StackFrame **frames = NULL; int nframes = 0; - +#ifndef HOST_WASI GET_TLS_DATA_FROM_THREAD (ss_req->thread); +#else + GET_DEBUGGER_TLS(); +#endif g_assert (tls); if (!tls->context.valid) { @@ -6279,7 +6346,7 @@ invoke_method (void) if (mindex == invoke->nmethods - 1) { if (!(invoke->flags & INVOKE_FLAG_SINGLE_THREADED)) { for (guint32 i = 0; i < invoke->suspend_count; ++i) - suspend_vm (); + suspend_vm_func (); } } @@ -6673,10 +6740,11 @@ vm_commands (int command, int id, guint8 *p, guint8 *end, Buffer *buf) break; } case CMD_VM_SUSPEND: - suspend_vm (); + suspend_vm_func (); wait_for_suspend (); break; case CMD_VM_RESUME: +#ifndef HOST_WASI if (suspend_count == 0) { if (agent_config.defer && !agent_config.suspend) // Workaround for issue in debugger-libs when running in defer attach mode. @@ -6684,6 +6752,7 @@ vm_commands (int command, int id, guint8 *p, guint8 *end, Buffer *buf) else return ERR_NOT_SUSPENDED; } +#endif resume_vm (); clear_suspended_objs (); break; @@ -6723,7 +6792,7 @@ vm_commands (int command, int id, guint8 *p, guint8 *end, Buffer *buf) * better than doing the shutdown ourselves, since it avoids various races. */ - suspend_vm (); + suspend_vm_func (); wait_for_suspend (); #ifdef TRY_MANAGED_SYSTEM_ENVIRONMENT_EXIT @@ -7228,7 +7297,7 @@ event_commands (int command, guint8 *p, guint8 *end, Buffer *buf) g_free (req); return err; } -#ifdef TARGET_WASM +#if defined(TARGET_WASM) && !defined(HOST_WASI) int isBPOnManagedCode = 0; SingleStepReq *ss_req = req->info; if (ss_req && ss_req->bps) { @@ -9045,10 +9114,12 @@ thread_commands (int command, guint8 *p, guint8 *end, Buffer *buf) // Wait for suspending if it already started // FIXME: Races with suspend_count +#ifndef HOST_WASI while (!is_suspended ()) { if (suspend_count) wait_for_suspend (); } +#endif /* if (suspend_count) wait_for_suspend (); @@ -10228,19 +10299,10 @@ mono_process_dbg_packet (int id, CommandSet command_set, int command, gboolean * static gsize WINAPI debugger_thread (void *arg) { - int res, len, id, flags, command = 0; - CommandSet command_set = (CommandSet)0; - guint8 header [HEADER_LENGTH]; - guint8 *data, *p, *end; - Buffer buf; - ErrorCode err; - gboolean no_reply; gboolean attach_failed = FALSE; PRINT_DEBUG_MSG (1, "[dbg] Agent thread started, pid=%p\n", (gpointer) (gsize) mono_native_thread_id_get ()); - gboolean log_each_step = g_hasenv ("MONO_DEBUGGER_LOG_AFTER_COMMAND"); - debugger_thread_id = mono_native_thread_id_get (); MonoInternalThread *internal = mono_thread_internal_current (); @@ -10267,22 +10329,58 @@ debugger_thread (void *arg) if (mono_metadata_has_updates_api ()) { PRINT_DEBUG_MSG (1, "[dbg] Cannot attach after System.Reflection.Metadata.MetadataUpdater.ApplyChanges has been called.\n"); attach_failed = TRUE; - command_set = (CommandSet)0; - command = 0; dispose_vm (); } } #endif + gboolean is_vm_dispose_command = FALSE; + if (!attach_failed) + { + is_vm_dispose_command = mono_debugger_agent_receive_and_process_command (); + } + + + mono_set_is_debugger_attached (FALSE); + + mono_coop_mutex_lock (&debugger_thread_exited_mutex); + debugger_thread_exited = TRUE; + mono_coop_cond_signal (&debugger_thread_exited_cond); + mono_coop_mutex_unlock (&debugger_thread_exited_mutex); + + PRINT_DEBUG_MSG (1, "[dbg] Debugger thread exited.\n"); + + if (!attach_failed && is_vm_dispose_command && !(vm_death_event_sent || mono_runtime_is_shutting_down ())) { + PRINT_DEBUG_MSG (2, "[dbg] Detached - restarting clean debugger thread.\n"); + ERROR_DECL (error); + start_debugger_thread_func (error); + mono_error_cleanup (error); + } + return 0; +} + +bool mono_debugger_agent_receive_and_process_command (void) +{ + int res, len, id, flags, command = 0; + CommandSet command_set = (CommandSet)0; + guint8 header [HEADER_LENGTH]; + guint8 *data, *p, *end; + Buffer buf; + ErrorCode err; + gboolean no_reply; + + gboolean log_each_step = g_hasenv ("MONO_DEBUGGER_LOG_AFTER_COMMAND"); - while (!attach_failed) { + while (TRUE) { res = transport_recv (header, HEADER_LENGTH); /* This will break if the socket is closed during shutdown too */ if (res != HEADER_LENGTH) { +#ifndef HOST_WASI //on wasi we can try to get message from debugger and don't have any message PRINT_DEBUG_MSG (1, "[dbg] transport_recv () returned %d, expected %d.\n", res, HEADER_LENGTH); command_set = (CommandSet)0; command = 0; dispose_vm (); +#endif break; } else { p = header; @@ -10362,32 +10460,20 @@ debugger_thread (void *arg) if (command_set == CMD_SET_VM && (command == CMD_VM_DISPOSE || command == CMD_VM_EXIT)) break; } - - mono_set_is_debugger_attached (FALSE); - - mono_coop_mutex_lock (&debugger_thread_exited_mutex); - debugger_thread_exited = TRUE; - mono_coop_cond_signal (&debugger_thread_exited_cond); - mono_coop_mutex_unlock (&debugger_thread_exited_mutex); - - PRINT_DEBUG_MSG (1, "[dbg] Debugger thread exited.\n"); - - if (!attach_failed && command_set == CMD_SET_VM && command == CMD_VM_DISPOSE && !(vm_death_event_sent || mono_runtime_is_shutting_down ())) { - PRINT_DEBUG_MSG (2, "[dbg] Detached - restarting clean debugger thread.\n"); - ERROR_DECL (error); - start_debugger_thread (error); - mono_error_cleanup (error); - } - - return 0; + return !(command_set == CMD_SET_VM && command == CMD_VM_DISPOSE); } +static gboolean +debugger_agent_enabled (void) +{ + return agent_config.enabled; +} void debugger_agent_add_function_pointers(MonoComponentDebugger* fn_table) { fn_table->parse_options = debugger_agent_parse_options; - fn_table->init = debugger_agent_init; + fn_table->init = mono_debugger_agent_init_internal; fn_table->breakpoint_hit = debugger_agent_breakpoint_hit; fn_table->single_step_event = debugger_agent_single_step_event; fn_table->single_step_from_context = debugger_agent_single_step_from_context; @@ -10402,8 +10488,7 @@ debugger_agent_add_function_pointers(MonoComponentDebugger* fn_table) fn_table->debug_log_is_enabled = debugger_agent_debug_log_is_enabled; fn_table->transport_handshake = debugger_agent_transport_handshake; fn_table->send_enc_delta = send_enc_delta; + fn_table->debugger_enabled = debugger_agent_enabled; } - - #endif /* DISABLE_SDB */ diff --git a/src/mono/mono/component/debugger-agent.h b/src/mono/mono/component/debugger-agent.h index ec77b0bfaba8a1..13a9f504f8afb6 100644 --- a/src/mono/mono/component/debugger-agent.h +++ b/src/mono/mono/component/debugger-agent.h @@ -36,6 +36,17 @@ mono_wasm_save_thread_context (void); void mini_wasm_debugger_add_function_pointers (MonoComponentDebugger* fn_table); +void +mini_wasi_debugger_add_function_pointers (MonoComponentDebugger* fn_table); + +#if defined(HOST_WASI) +void +mono_wasi_suspend_current (void); + +void +mono_debugger_agent_initialize_function_pointers (void *start_debugger_thread, void *suspend_vm, void *suspend_current); +#endif + MdbgProtErrorCode mono_do_invoke_method (DebuggerTlsData *tls, MdbgProtBuffer *buf, InvokeData *invoke, guint8 *p, guint8 **endp); @@ -56,4 +67,7 @@ mono_ss_args_destroy (SingleStepArgs *ss_args); int mono_de_frame_async_id (DbgEngineStackFrame *frame); + +bool +mono_debugger_agent_receive_and_process_command (void); #endif diff --git a/src/mono/mono/component/debugger-engine.h b/src/mono/mono/component/debugger-engine.h index e6e47280e50b08..d174f3e4034502 100644 --- a/src/mono/mono/component/debugger-engine.h +++ b/src/mono/mono/component/debugger-engine.h @@ -330,6 +330,9 @@ mono_debugger_get_thread_states (void); gboolean mono_debugger_is_disconnected (void); +void +mono_debugger_agent_init (void); + gsize mono_debugger_tls_thread_id (DebuggerTlsData *debuggerTlsData); @@ -380,6 +383,9 @@ void mono_debugger_free_objref(gpointer value); #ifdef HOST_ANDROID #define PRINT_DEBUG_MSG(level, ...) do { if (G_UNLIKELY ((level) <= log_level)) { g_print (__VA_ARGS__); } } while (0) #define DEBUG(level,s) do { if (G_UNLIKELY ((level) <= log_level)) { s; } } while (0) +#elif HOST_WASI +#define PRINT_DEBUG_MSG(level, ...) do { if (G_UNLIKELY ((level) <= log_level)) { g_print (__VA_ARGS__); } } while (0) +#define DEBUG(level,s) do { if (G_UNLIKELY ((level) <= log_level)) { s; } } while (0) #elif HOST_WASM void wasm_debugger_log(int level, const gchar *format, ...); #define PRINT_DEBUG_MSG(level, ...) do { if (G_UNLIKELY ((level) <= log_level)) { wasm_debugger_log (level, __VA_ARGS__); } } while (0) diff --git a/src/mono/mono/component/debugger-stub.c b/src/mono/mono/component/debugger-stub.c index 18b0f6a143487b..615383b2a00ea5 100644 --- a/src/mono/mono/component/debugger-stub.c +++ b/src/mono/mono/component/debugger-stub.c @@ -66,6 +66,12 @@ stub_mono_wasm_single_step_hit (void); static void stub_send_enc_delta (MonoImage *image, gconstpointer dmeta_bytes, int32_t dmeta_len, gconstpointer dpdb_bytes, int32_t dpdb_len); +static void +stub_receive_and_process_command_from_debugger_agent (void); + +static gboolean +stub_debugger_enabled (void); + static MonoComponentDebugger fn_table = { { MONO_COMPONENT_ITF_VERSION, &debugger_available }, &stub_debugger_init, @@ -90,6 +96,10 @@ static MonoComponentDebugger fn_table = { //HotReload &stub_send_enc_delta, + + //wasi + &stub_receive_and_process_command_from_debugger_agent, + &stub_debugger_enabled, }; static bool @@ -204,6 +214,17 @@ stub_send_enc_delta (MonoImage *image, gconstpointer dmeta_bytes, int32_t dmeta_ { } +static void +stub_receive_and_process_command_from_debugger_agent (void) +{ +} + +static gboolean +stub_debugger_enabled (void) +{ + return FALSE; +} + #ifdef HOST_BROWSER #include diff --git a/src/mono/mono/component/debugger.c b/src/mono/mono/component/debugger.c index d8de70ab74f588..f6255d4c85d9a3 100644 --- a/src/mono/mono/component/debugger.c +++ b/src/mono/mono/component/debugger.c @@ -28,8 +28,11 @@ MonoComponentDebugger * mono_component_debugger_init (void) { debugger_agent_add_function_pointers (&fn_table); -#ifdef TARGET_WASM +#if defined(TARGET_WASM) && !defined(HOST_WASI) mini_wasm_debugger_add_function_pointers (&fn_table); +#endif +#if defined(HOST_WASI) + mini_wasi_debugger_add_function_pointers (&fn_table); #endif return &fn_table; } diff --git a/src/mono/mono/component/debugger.h b/src/mono/mono/component/debugger.h index 633bfc492cc033..f871535baf61c2 100644 --- a/src/mono/mono/component/debugger.h +++ b/src/mono/mono/component/debugger.h @@ -196,6 +196,9 @@ typedef struct MonoComponentDebugger { //HotReload void (*send_enc_delta) (MonoImage *image, gconstpointer dmeta_bytes, int32_t dmeta_len, gconstpointer dpdb_bytes, int32_t dpdb_len); + //wasi + void (*receive_and_process_command_from_debugger_agent) (void); + gboolean (*debugger_enabled) (void); } MonoComponentDebugger; diff --git a/src/mono/mono/component/diagnostics_server.c b/src/mono/mono/component/diagnostics_server.c index 4c1124c478258b..f81de18046f23b 100644 --- a/src/mono/mono/component/diagnostics_server.c +++ b/src/mono/mono/component/diagnostics_server.c @@ -7,7 +7,7 @@ #include #include #include -#ifdef HOST_WASM +#if defined (HOST_WASM) && !defined(HOST_WASI) #include #include #include diff --git a/src/mono/mono/component/event_pipe-stub.c b/src/mono/mono/component/event_pipe-stub.c index 194b7c05823b89..3865b3148a6fa7 100644 --- a/src/mono/mono/component/event_pipe-stub.c +++ b/src/mono/mono/component/event_pipe-stub.c @@ -517,7 +517,7 @@ mono_component_event_pipe_init (void) return component_event_pipe_stub_init (); } -#ifdef HOST_WASM +#if defined(HOST_WASM) && !defined(HOST_WASI) EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_event_pipe_enable (const ep_char8_t *output_path, @@ -548,4 +548,4 @@ mono_wasm_event_pipe_session_disable (MonoWasmEventPipeSessionID session_id) { g_assert_not_reached (); } -#endif /* HOST_WASM */ +#endif /* HOST_WASM && !HOST_WASI */ \ No newline at end of file diff --git a/src/mono/mono/component/event_pipe-wasm.h b/src/mono/mono/component/event_pipe-wasm.h index 3ed49233b90834..b75ddce0b15a94 100644 --- a/src/mono/mono/component/event_pipe-wasm.h +++ b/src/mono/mono/component/event_pipe-wasm.h @@ -10,7 +10,7 @@ #include #include -#ifdef HOST_WASM +#if defined(HOST_WASM) && !defined(HOST_WASI) #include #include diff --git a/src/mono/mono/component/event_pipe.c b/src/mono/mono/component/event_pipe.c index 4d59f5af94b681..4ba3489101263b 100644 --- a/src/mono/mono/component/event_pipe.c +++ b/src/mono/mono/component/event_pipe.c @@ -13,7 +13,7 @@ #include #include -#ifdef HOST_WASM +#if defined(HOST_WASM) && !defined(HOST_WASI) #include #endif @@ -98,14 +98,14 @@ event_pipe_wait_for_session_signal ( EventPipeSessionID session_id, uint32_t timeout); -#ifdef HOST_WASM +#if defined(HOST_WASM) && !defined(HOST_WASI) static void mono_wasm_event_pipe_init (void); #endif static MonoComponentEventPipe fn_table = { { MONO_COMPONENT_ITF_VERSION, &event_pipe_available }, -#ifndef HOST_WASM +#if !defined(HOST_WASM) || defined(HOST_WASI) &ep_init, #else &mono_wasm_event_pipe_init, @@ -345,7 +345,7 @@ mono_component_event_pipe_init (void) } -#ifdef HOST_WASM +#if defined(HOST_WASM) && !defined(HOST_WASI) static MonoWasmEventPipeSessionID @@ -426,4 +426,4 @@ mono_wasm_event_pipe_init (void) mono_wasm_event_pipe_early_startup_callback (); } -#endif /* HOST_WASM */ +#endif /* HOST_WASM && !HOST_WASI */ diff --git a/src/mono/mono/component/mini-wasi-debugger.c b/src/mono/mono/component/mini-wasi-debugger.c new file mode 100644 index 00000000000000..8b1b87f0b3fb37 --- /dev/null +++ b/src/mono/mono/component/mini-wasi-debugger.c @@ -0,0 +1,180 @@ +#include +#include +#include +#include + +#ifdef HOST_WASI +static int conn_fd; +static int log_level = 1; +static int retry_receive_message = 100; +static int connection_wait_us = 1000; + +__attribute__((import_module("wasi_snapshot_preview1"))) +__attribute__((import_name("sock_accept"))) +int sock_accept(int fd, int fdflags, int* result_ptr); + +static long long +time_in_milliseconds () +{ + struct timeval tv; + gettimeofday(&tv,NULL); + return (((long long)tv.tv_sec)*1000)+(tv.tv_usec/1000); +} + +static int +wasi_transport_recv (void *buf, int len) +{ + int res; + int total = 0; + int fd = conn_fd; + int num_recv_calls = 0; + + do { + res = read (fd, (char *) buf + total, len - total); + if (res > 0) + total += res; + num_recv_calls++; + if ((res > 0 && total < len) || (res == -1 && num_recv_calls < retry_receive_message)) { + // Wasmtime on Windows doesn't seem to be able to sleep for short periods like 1ms so we'll have to spinlock + long long start = time_in_milliseconds (); + while ((time_in_milliseconds () - start) < (connection_wait_us/1000)); + } else { + break; + } + } while (true); + return total; +} + +static gboolean +wasi_transport_send (void *data, int len) +{ + int res; + do { + res = write (conn_fd, (const char*)data, len); + } while (res == -1); + + return res == len; +} + +/* + * wasi_transport_connect: + * + * listen on the socket that is already opened by wasm runtime + */ +static void +wasi_transport_connect (const char *socket_fd) +{ + PRINT_DEBUG_MSG (1, "wasi_transport_connect.\n"); + bool handshake_ok = FALSE; + conn_fd = -1; + char *ptr; + PRINT_DEBUG_MSG (1, "Waiting for connection from client, socket fd=%s.\n", socket_fd); + long socket_fd_long = strtol(socket_fd, &ptr, 10); + if (socket_fd_long == 0) { + PRINT_DEBUG_MSG (1, "Invalid socket fd = %s.\n", socket_fd); + return; + } + while (!handshake_ok) { + int ret_accept = sock_accept (socket_fd_long, __WASI_FDFLAGS_NONBLOCK, &conn_fd); + if (ret_accept < 0) { + PRINT_DEBUG_MSG (1, "Error while accepting connection from client = %d.\n", ret_accept); + return; + } + int res = -1; + if (conn_fd != -1) { + res = write (conn_fd, (const char*)"", 0); + if (res != -1) { + handshake_ok = mono_component_debugger ()->transport_handshake (); + } + } + if (conn_fd == -1 || res == -1) { + sleep(1); + continue; + } + } + PRINT_DEBUG_MSG (1, "Accepted connection from client, socket fd=%d.\n", conn_fd); +} + +static void +wasi_transport_close1 (void) +{ +/* shutdown (conn_fd, SHUT_RD); + shutdown (listen_fd, SHUT_RDWR); + close (listen_fd);*/ +} + +static void +wasi_transport_close2 (void) +{ +// shutdown (conn_fd, SHUT_RDWR); +} + +static void +mono_wasi_start_debugger_thread (MonoError *error) +{ + mono_debugger_agent_receive_and_process_command (); + connection_wait_us = 250; +} + +static void +mono_wasi_suspend_vm (void) +{ +} + +static void +mono_wasi_debugger_init (void) +{ + DebuggerTransport trans; + trans.name = "wasi_socket"; + trans.send = wasi_transport_send; + trans.connect = wasi_transport_connect; + trans.recv = wasi_transport_recv; + trans.send = wasi_transport_send; + trans.close1 = wasi_transport_close1; + + mono_debugger_agent_register_transport (&trans); + + mono_debugger_agent_init_internal(); + + mono_debugger_agent_initialize_function_pointers(mono_wasi_start_debugger_thread, mono_wasi_suspend_vm, mono_wasi_suspend_current); +} + +static void +mono_wasi_receive_and_process_command_from_debugger_agent (void) +{ + retry_receive_message = 2; //when it comes from interpreter we don't want to spend a long time waiting for messages + mono_debugger_agent_receive_and_process_command (); + retry_receive_message = 50; //when it comes from debugger we are waiting for a debugger command (resume/step), we want to retry more times +} + +static void +mono_wasi_single_step_hit (void) +{ + mono_wasm_save_thread_context (); + mono_de_process_single_step (mono_wasm_get_tls (), FALSE); +} + +static void +mono_wasi_breakpoint_hit (void) +{ + mono_wasm_save_thread_context (); + mono_de_process_breakpoint (mono_wasm_get_tls (), FALSE); +} + +void +mini_wasi_debugger_add_function_pointers (MonoComponentDebugger* fn_table) +{ + fn_table->init = mono_wasi_debugger_init; + fn_table->receive_and_process_command_from_debugger_agent = mono_wasi_receive_and_process_command_from_debugger_agent; + fn_table->mono_wasm_breakpoint_hit = mono_wasi_breakpoint_hit; + fn_table->mono_wasm_single_step_hit = mono_wasi_single_step_hit; +} + +#else // HOST_WASI + +void +mini_wasi_debugger_add_function_pointers (MonoComponentDebugger* fn_table) +{ +} + +#endif diff --git a/src/mono/mono/component/mini-wasm-debugger.c b/src/mono/mono/component/mini-wasm-debugger.c index 5de1f3422090ca..98da53aa693b40 100644 --- a/src/mono/mono/component/mini-wasm-debugger.c +++ b/src/mono/mono/component/mini-wasm-debugger.c @@ -1,3 +1,4 @@ +#ifndef HOST_WASI #include #include #include @@ -486,3 +487,5 @@ mini_wasm_debugger_add_function_pointers (MonoComponentDebugger* fn_table) fn_table->mono_wasm_breakpoint_hit = mono_wasm_breakpoint_hit; fn_table->mono_wasm_single_step_hit = mono_wasm_single_step_hit; } + +#endif \ No newline at end of file diff --git a/src/mono/mono/mini/CMakeLists.txt b/src/mono/mono/mini/CMakeLists.txt index 9a71594e43c589..6ef0ebf57b4ea2 100644 --- a/src/mono/mono/mini/CMakeLists.txt +++ b/src/mono/mono/mini/CMakeLists.txt @@ -448,7 +448,7 @@ if(NOT DISABLE_SHARED_LIBS) endif() endif() -if(HOST_WASM) +if(HOST_WASM AND NOT HOST_WASI) # Add two static libs containing llvm-runtime.cpp compiled for JS based/WASM EH # This is the only source file which contains a c++ throw or catch add_library(mono-wasm-eh-js STATIC llvm-runtime.cpp) diff --git a/src/mono/mono/mini/debugger-agent-external.h b/src/mono/mono/mini/debugger-agent-external.h index b37cd6b5e046ef..661537e0e69378 100644 --- a/src/mono/mono/mini/debugger-agent-external.h +++ b/src/mono/mono/mini/debugger-agent-external.h @@ -20,6 +20,9 @@ mono_debugger_agent_transport_handshake (void); MONO_API void mono_debugger_agent_register_transport (DebuggerTransport *trans); +MONO_API void +mono_debugger_agent_init_internal (void); + MONO_COMPONENT_API DebuggerTransport * mono_debugger_agent_get_transports (int *ntrans); diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c index 0c69d67bf6376b..b89f8a7a181c7f 100644 --- a/src/mono/mono/mini/interp/interp.c +++ b/src/mono/mono/mini/interp/interp.c @@ -243,6 +243,10 @@ static gboolean ss_enabled; static gboolean interp_init_done = FALSE; +#ifdef HOST_WASI +static gboolean debugger_enabled = FALSE; +#endif + static void interp_exec_method (InterpFrame *frame, ThreadContext *context, FrameClauseArgs *clause_args); @@ -6792,6 +6796,10 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK; MINT_IN_BREAK; MINT_IN_CASE(MINT_SDB_SEQ_POINT) /* Just a placeholder for a breakpoint */ +#if HOST_WASI + if (debugger_enabled) + mono_component_debugger()->receive_and_process_command_from_debugger_agent (); +#endif ++ip; MINT_IN_BREAK; MINT_IN_CASE(MINT_SDB_BREAKPOINT) { @@ -8189,4 +8197,8 @@ mono_ee_interp_init (const char *opts) mini_install_interp_callbacks (&mono_interp_callbacks); register_interp_stats (); + +#ifdef HOST_WASI + debugger_enabled = mini_get_debug_options ()->mdb_optimizations; +#endif } diff --git a/src/mono/mono/mini/mini-exceptions.c b/src/mono/mono/mini/mini-exceptions.c index 76d6dc7baf327f..f948c57444c025 100644 --- a/src/mono/mono/mini/mini-exceptions.c +++ b/src/mono/mono/mini/mini-exceptions.c @@ -1345,7 +1345,12 @@ mono_walk_stack_full (MonoJitStackWalk func, MonoContext *start_ctx, MonoJitTlsD unwinder_init (&unwinder); - while (MONO_CONTEXT_GET_SP (&ctx) < jit_tls->end_of_stack) { +#ifdef HOST_WASI + gboolean ignore_end_of_stack = true; +#else + gboolean ignore_end_of_stack = false; +#endif + while (ignore_end_of_stack || MONO_CONTEXT_GET_SP (&ctx) < jit_tls->end_of_stack) { frame.lmf = lmf; res = unwinder_unwind_frame (&unwinder, jit_tls, NULL, &ctx, &new_ctx, NULL, &lmf, get_reg_locations ? new_reg_locations : NULL, &frame); if (!res) diff --git a/src/mono/mono/utils/mono-threads-wasm.c b/src/mono/mono/utils/mono-threads-wasm.c index 0727fe393606bf..436974f947db38 100644 --- a/src/mono/mono/utils/mono-threads-wasm.c +++ b/src/mono/mono/utils/mono-threads-wasm.c @@ -180,7 +180,9 @@ mono_threads_platform_yield (void) void mono_threads_platform_get_stack_bounds (guint8 **staddr, size_t *stsize) { +#ifndef HOST_WASI int tmp; +#endif #ifdef __EMSCRIPTEN_PTHREADS__ pthread_attr_t attr; gint res; diff --git a/src/mono/mono/utils/mono-threads.c b/src/mono/mono/utils/mono-threads.c index fb75708a43d945..ac6d2078bb63b8 100644 --- a/src/mono/mono/utils/mono-threads.c +++ b/src/mono/mono/utils/mono-threads.c @@ -1616,7 +1616,9 @@ mono_thread_info_is_async_context (void) void mono_thread_info_get_stack_bounds (guint8 **staddr, size_t *stsize) { +#ifndef HOST_WASI guint8 *current = (guint8 *)&stsize; +#endif mono_threads_platform_get_stack_bounds (staddr, stsize); if (!*staddr) return; @@ -2173,4 +2175,3 @@ mono_thread_info_get_tools_data (void) return info ? info->tools_data : NULL; } - diff --git a/src/mono/wasi/Makefile b/src/mono/wasi/Makefile index df109a147be17d..53ae87f4f76eaf 100644 --- a/src/mono/wasi/Makefile +++ b/src/mono/wasi/Makefile @@ -5,25 +5,47 @@ all: build-all build-all: $(WASI_SDK_CLANG) mkdir -p $(WASI_OBJ_DIR) cd $(WASI_OBJ_DIR) && \ - cmake -G Ninja \ + PATH=$(NINJA_DIR):${PATH} cmake -G Ninja \ -DWASI_SDK_PREFIX=$(WASI_SDK_ROOT) \ -DCMAKE_SYSROOT=$(WASI_SDK_ROOT)/share/wasi-sysroot \ -DCMAKE_TOOLCHAIN_FILE=$(WASI_SDK_ROOT)/share/cmake/wasi-sdk.cmake \ - -DCMAKE_C_FLAGS="--sysroot=$(WASI_SDK_ROOT)/share/wasi-sysroot -I$(CURDIR)/include -I$(TOP)/src/mono -I$(TOP)/src/native/public" \ + -DCMAKE_C_FLAGS="--sysroot=$(WASI_SDK_ROOT)/share/wasi-sysroot \ + -I$(CURDIR)/include -I$(TOP)/src/mono -I$(TOP)/src/native/public -I$(TOP)/src/mono/mono/eglib -I$(WASI_OBJ_DIR)/mono/eglib -I$(WASI_OBJ_DIR) -I$(TOP)/artifacts/obj/wasm -I$(TOP)/src/mono/wasm/runtime" \ -DCMAKE_CXX_FLAGS="--sysroot=$(WASI_SDK_ROOT)/share/wasi-sysroot" \ -DENABLE_MINIMAL=jit,sgen_major_marksweep_conc,sgen_split_nursery,sgen_gc_bridge,sgen_toggleref,sgen_debug_helpers,sgen_binary_protocol,logging,interpreter,threads,qcalls,debugger_agent,sockets,eventpipe \ -DDISABLE_SHARED_LIBS=1 \ -Wl,--allow-undefined \ $(TOP)/src/mono - cd $(WASI_OBJ_DIR) && ninja + cd $(WASI_OBJ_DIR) && PATH=$(NINJA_DIR):${PATH} ninja mkdir -p $(WASI_BIN_DIR) cp $(WASI_OBJ_DIR)/mono/mini/*.a $(WASI_OBJ_DIR)/libmono-wasi-driver.a $(WASI_BIN_DIR) + rm -rf $(WASI_BIN_DIR)/libmono-component-hot_reload-static.a + rm -rf $(WASI_BIN_DIR)/libmono-component-diagnostics_tracing-static.a mkdir -p $(WASI_BIN_DIR)/include/mono-wasi cp mono-wasi-driver/*.h $(WASI_BIN_DIR)/include/mono-wasi $(WASI_SDK_CLANG): mkdir -p $(WASI_OBJ_DIR) cd $(WASI_OBJ_DIR) && \ - wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$(WASI_SDK_VERSION)/wasi-sdk-$(WASI_SDK_VERSION).0-linux.tar.gz && \ + wget -q https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$(WASI_SDK_VERSION)/wasi-sdk-$(WASI_SDK_VERSION).0-linux.tar.gz && \ tar xf wasi-sdk-*.tar.gz + +.stamp-wasmtime-$(WASMTIME_VERSION): + @echo "** Provisioning wasmtime $(WASMTIME_VERSION) **" + rm -Rf $(WASMTIME_DIR) && \ + wget -q https://github.com/bytecodealliance/wasmtime/releases/download/$(WASMTIME_VERSION)/$(WASMTIME_DIR_NAME).tar.xz -O - | tar -C `dirname $(WASMTIME_DIR)` -Jxf - && \ + touch $@ + +.stamp-ninja-$(NINJA_VERSION): + @echo "** Provisioning ninja $(NINJA_VERSION) **" + rm -Rf $(NINJA_DIR); \ + mkdir $(NINJA_DIR) && \ + wget -q https://github.com/ninja-build/ninja/releases/download/v1.11.0/ninja-linux.zip -O $(NINJA_DIR)/ninja.zip && \ + (cd $(NINJA_DIR) && unzip -q ninja.zip && rm ninja.zip) && \ + touch $@ + +provision-deps: .stamp-wasmtime-$(WASMTIME_VERSION) .stamp-ninja-$(NINJA_VERSION) + @echo "-------------------------------------------" + @echo "** Installed wasmtime in $(WASMTIME_DIR)" + @echo "** Installed ninja in $(NINJA_DIR)" diff --git a/src/mono/wasi/Makefile.variable b/src/mono/wasi/Makefile.variable index fb1d6d5a6e5de0..ef8b91b245c246 100644 --- a/src/mono/wasi/Makefile.variable +++ b/src/mono/wasi/Makefile.variable @@ -1,8 +1,16 @@ TOP=$(realpath $(CURDIR)/../../..) CONFIG?=Release +RUNTIME_CONFIG?=Release WASI_SDK_VERSION=12 -WASI_OBJ_DIR=$(TOP)/artifacts/obj/mono/Wasi.$(CONFIG) -WASI_BIN_DIR=$(TOP)/artifacts/bin/mono/Wasi.$(CONFIG) +WASI_OBJ_DIR=$(TOP)/artifacts/obj/mono/Wasi.$(RUNTIME_CONFIG) +WASI_BIN_DIR=$(TOP)/artifacts/bin/mono/Wasi.$(RUNTIME_CONFIG) WASI_SDK_ROOT=$(WASI_OBJ_DIR)/wasi-sdk-$(WASI_SDK_VERSION).0 WASI_SDK_CLANG=$(WASI_SDK_ROOT)/bin/clang + +WASMTIME_VERSION=v0.39.1 +WASMTIME_DIR_NAME=wasmtime-$(WASMTIME_VERSION)-x86_64-linux +WASMTIME_DIR=$(TOP)/artifacts/bin/$(WASMTIME_DIR_NAME) + +NINJA_VERSION=v1.11.0 +NINJA_DIR=$(TOP)/artifacts/bin/ninja diff --git a/src/mono/wasi/README.md b/src/mono/wasi/README.md index b4037f7701593b..8b17456eb864c0 100644 --- a/src/mono/wasi/README.md +++ b/src/mono/wasi/README.md @@ -8,6 +8,18 @@ The mechanism for executing .NET code in a WASI runtime environment is equivalen ## How to build the runtime +### 1. Build the WASM runtime + +To build the wasi runtime we need the file `wasm_m2n_invoke.g.h` which is generated when compiling wasm runtime + +``` +make -C src/mono/wasm provision-wasm +export EMDK_PATH=[path_printed_by_provision_wasm] +./build.sh mono+libs -os browser +``` + +### 2. Build the WASI runtime + Currently this can only be built in Linux or WSL (tested on Windows 11). Simply run `make` in this directory. It will automatically download and use [WASI SDK](https://github.com/WebAssembly/wasi-sdk). The resulting libraries are placed in `(repo_root)/artifacts/bin/mono/Wasi.Release`. @@ -47,6 +59,32 @@ You'll need to update these paths to match the location where you extracted the Finally, you can build and run the sample: ``` -cd samples/console +cd sample/console make run ``` + +### 4. Debug it + +Also, you can build and debug the sample: + +``` +cd sample/console +make debug +``` + +Using Visual Studio code, add a breakpoint on Program.cs line 17. +Download the Mono Debug extension and configure a launch.json like this: +``` +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach", + "type": "mono", + "request": "attach", + "address": "localhost", + "port": 64000 + } + ] +} +``` \ No newline at end of file diff --git a/src/mono/wasi/mono-wasi-driver/driver.c b/src/mono/wasi/mono-wasi-driver/driver.c index d101ddf29866f2..78b1bfa94e95c0 100644 --- a/src/mono/wasi/mono-wasi-driver/driver.c +++ b/src/mono/wasi/mono-wasi-driver/driver.c @@ -19,6 +19,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include @@ -39,6 +45,7 @@ int mono_wasm_register_root (char *start, size_t size, const char *name); void mono_wasm_deregister_root (char *addr); void mono_ee_interp_init (const char *opts); +void mono_marshal_ilgen_init (void); void mono_marshal_lightweight_init (void); void mono_method_builder_ilgen_init (void); void mono_sgen_mono_ilgen_init (void); @@ -244,6 +251,14 @@ int32_t SystemNative_Stat2(const char* path, FileStatus* output) ? 0x4000 // Dir : 0x8000; // File + // Never fail when looking for the root directory. Even if the WASI host isn't giving us filesystem access + // (which is the default), we need the root directory to appear to exist, otherwise things like ASP.NET Core + // will fail by default, whether or not it needs to read anything from the filesystem. + if (ret != 0 && path[0] == '/' && path[1] == 0) { + output->Mode = 0x4000; // Dir + return 0; + } + //printf("SystemNative_Stat2 for %s has ISDIR=%i and will return mode %i; ret=%i\n", path, S_ISDIR (stat_result.st_mode), output->Mode, ret); return ret; @@ -309,6 +324,15 @@ static PinvokeImport SystemGlobalizationNativeImports [] = { {NULL, NULL} }; +int SystemCryptoNativeBrowser_CanUseSubtleCryptoImpl() { + return 0; +} + +static PinvokeImport SystemSecurityCryptographyNativeBrowserImports [] = { + {"SystemCryptoNativeBrowser_CanUseSubtleCryptoImpl", SystemCryptoNativeBrowser_CanUseSubtleCryptoImpl }, + {NULL, NULL} +}; + static void* wasm_dl_load (const char *name, int flags, char **err, void *user_data) { @@ -316,6 +340,9 @@ wasm_dl_load (const char *name, int flags, char **err, void *user_data) return SystemNativeImports; if (!strcmp (name, "libSystem.Globalization.Native")) return SystemGlobalizationNativeImports; + if (!strcmp (name, "libSystem.Security.Cryptography.Native.Browser")) + return SystemSecurityCryptographyNativeBrowserImports; + //printf("In wasm_dl_load for name %s but treating as NOT FOUND\n", name); return 0; } @@ -394,7 +421,7 @@ cleanup_runtime_config (MonovmRuntimeConfigArguments *args, void *user_data) } void -mono_wasm_load_runtime (const char *unused, int debug_level) +mono_wasm_load_runtime (const char *argv, int debug_level) { const char *interp_opts = ""; @@ -412,6 +439,18 @@ mono_wasm_load_runtime (const char *unused, int debug_level) // corlib assemblies. monoeg_g_setenv ("COMPlus_DebugWriteToStdErr", "1", 0); #endif + char* debugger_fd = monoeg_g_getenv ("DEBUGGER_FD"); + if (debugger_fd != 0) + { + const char *debugger_str = "--debugger-agent=transport=wasi_socket,debugger_fd=%-2s,loglevel=0"; + char *debugger_str_with_fd = (char *)malloc (sizeof (char) * (strlen(debugger_str) + strlen(debugger_fd) + 1)); + snprintf (debugger_str_with_fd, strlen(debugger_str) + strlen(debugger_fd) + 1, debugger_str, debugger_fd); + mono_jit_parse_options (1, &debugger_str_with_fd); + mono_debug_init (MONO_DEBUG_FORMAT_MONO); + // Disable optimizations which interfere with debugging + interp_opts = "-all"; + free (debugger_str_with_fd); + } // When the list of app context properties changes, please update RuntimeConfigReservedProperties for // target _WasmGenerateRuntimeConfig in WasmApp.targets file const char *appctx_keys[2]; @@ -452,20 +491,8 @@ mono_wasm_load_runtime (const char *unused, int debug_level) mono_jit_set_aot_mode (MONO_AOT_MODE_INTERP_ONLY); - /* - * debug_level > 0 enables debugging and sets the debug log level to debug_level - * debug_level == 0 disables debugging and enables interpreter optimizations - * debug_level < 0 enabled debugging and disables debug logging. - * - * Note: when debugging is enabled interpreter optimizations are disabled. - */ - if (debug_level) { - // Disable optimizations which interfere with debugging - interp_opts = "-all"; - mono_wasm_enable_debugging (debug_level); - } - mono_ee_interp_init (interp_opts); + mono_marshal_lightweight_init (); mono_marshal_ilgen_init (); mono_method_builder_ilgen_init (); mono_sgen_mono_ilgen_init (); @@ -643,6 +670,7 @@ mono_wasm_string_get_utf8 (MonoString *str) return mono_string_to_utf8 (str); //XXX JS is responsible for freeing this } +MonoString * mono_wasm_string_from_js (const char *str) { if (str) @@ -778,4 +806,4 @@ MonoMethod* lookup_dotnet_method(const char* assembly_name, const char* namespac MonoMethod* method = mono_wasm_assembly_find_method (class, method_name, num_params); assert (method); return method; -} +} \ No newline at end of file diff --git a/src/mono/wasi/mono-wasi-driver/driver.h b/src/mono/wasi/mono-wasi-driver/driver.h index af5043e0a27a63..a27f2b8480614d 100644 --- a/src/mono/wasi/mono-wasi-driver/driver.h +++ b/src/mono/wasi/mono-wasi-driver/driver.h @@ -3,6 +3,7 @@ #include #include +#include void mono_wasm_load_runtime (const char *unused, int debug_level); int mono_wasm_add_assembly (const char *name, const unsigned char *data, unsigned int size); diff --git a/src/mono/wasi/mono-wasi-driver/stubs.c b/src/mono/wasi/mono-wasi-driver/stubs.c index d6f3d0d0a75378..6c09722ee9971f 100644 --- a/src/mono/wasi/mono-wasi-driver/stubs.c +++ b/src/mono/wasi/mono-wasi-driver/stubs.c @@ -10,7 +10,7 @@ int sem_destroy(int x) { return 0; } int sem_post(int x) { return 0; } int sem_wait(int x) { return 0; } int sem_trywait(int x) { return 0; } -int sem_timedwait (int *sem, const struct timespec *abs_timeout) { assert(0); return 0; } +int sem_timedwait (int *sem, void *abs_timeout) { assert(0); return 0; } int __errno_location() { return 0; } diff --git a/src/mono/wasi/sample/SampleMakefile.variable b/src/mono/wasi/sample/SampleMakefile.variable index ba14317ec54cba..99f7e6916e9cdc 100644 --- a/src/mono/wasi/sample/SampleMakefile.variable +++ b/src/mono/wasi/sample/SampleMakefile.variable @@ -4,15 +4,17 @@ COMPILE_FLAGS=\ $(WASI_BIN_DIR)/*.a \ $(BROWSER_WASM_RUNTIME_PATH)/native/libSystem.Native.a \ --sysroot=$(WASI_SDK_ROOT)/share/wasi-sysroot \ - -I$(TOP)/src/mono + -I$(TOP)/src/mono -I$(TOP)/src/native/public LINK_FLAGS=\ -Wl,--export=malloc,--export=free,--export=__heap_base,--export=__data_end \ -Wl,-z,stack-size=1048576,--initial-memory=5242880,--max-memory=52428800,-lwasi-emulated-mman ifndef DOTNET_ROOT -$(error DOTNET_ROOT is undefined. Example: export DOTNET_ROOT=~/dotnet7) +DOTNET_ROOT=$(TOP)/.dotnet +echo "DOTNET_ROOT is undefined. Example: export DOTNET_ROOT=~/dotnet7. Defaulting to $(DOTNET_ROOT) endif ifndef BROWSER_WASM_RUNTIME_PATH -$(error BROWSER_WASM_RUNTIME_PATH is undefined. Example: export BROWSER_WASM_RUNTIME_PATH=$(DOTNET_ROOT)/packs/Microsoft.NETCore.App.Runtime.Mono.browser-wasm/7.0.0-alpha.1.22061.11/runtimes/browser-wasm) +BROWSER_WASM_RUNTIME_PATH=$(TOP)/artifacts/bin/microsoft.netcore.app.runtime.browser-wasm/$(CONFIG)/runtimes/browser-wasm +echo "BROWSER_WASM_RUNTIME_PATH is undefined. Example: export BROWSER_WASM_RUNTIME_PATH=$(DOTNET_ROOT)/packs/Microsoft.NETCore.App.Runtime.Mono.browser-wasm/7.0.0-alpha.1.22061.11/runtimes/browser-wasm). Defaulting to $(BROWSER_WASM_RUNTIME_PATH)" endif diff --git a/src/mono/wasi/sample/console/Makefile b/src/mono/wasi/sample/console/Makefile index bc9fd14dcb7510..b4d731f9d3f37b 100644 --- a/src/mono/wasi/sample/console/Makefile +++ b/src/mono/wasi/sample/console/Makefile @@ -1,12 +1,15 @@ include ../../Makefile.variable include ../SampleMakefile.variable -BIN_DIR=WasiConsoleApp/bin/$(CONFIG)/net7.0 +BIN_DIR=WasiConsoleApp/bin/Debug/net7.0 all: console.wasm $(BIN_DIR)/WasiConsoleApp.dll run: all - wasmtime --dir=. console.wasm + @(which wasmtime || PATH=$(WASMTIME_DIR):${PATH} which wasmtime); \ + test "$$?" -ne 0 \ + && echo "wasmtime not found. Either install that yourself, or use 'make provision-deps' to install one." \ + || PATH=$(WASMTIME_DIR):${PATH} wasmtime --dir=. console.wasm run-wasmer: all wasmer --dir=. console.wasm @@ -15,6 +18,11 @@ console.wasm: main.c $(WASI_SDK_CLANG) main.c -o console.wasm $(COMPILE_FLAGS) $(LINK_FLAGS) $(BIN_DIR)/WasiConsoleApp.dll: WasiConsoleApp/*.csproj WasiConsoleApp/*.cs - cd WasiConsoleApp && $(DOTNET_ROOT)/dotnet build -c $(CONFIG) + find $(BROWSER_WASM_RUNTIME_PATH) -type f + cd WasiConsoleApp && $(DOTNET_ROOT)/dotnet build -c Debug touch $(BIN_DIR)/*.dll cp -R $(BROWSER_WASM_RUNTIME_PATH) $(BIN_DIR)/runtime + +debug: all +#wasmtime has a bug when passing --tcplisten and --dir, to make it work we should use this PR https://github.com/bytecodealliance/wasmtime/pull/3995 and then the fd of the socket will be 4. + PATH=$(WASMTIME_DIR):${PATH} wasmtime --tcplisten localhost:64000 --dir=. console.wasm --env DEBUGGER_FD=4 diff --git a/src/mono/wasi/sample/console/WasiConsoleApp/WasiConsoleApp.csproj b/src/mono/wasi/sample/console/WasiConsoleApp/WasiConsoleApp.csproj index 49755d024c8a13..2e577472d68606 100644 --- a/src/mono/wasi/sample/console/WasiConsoleApp/WasiConsoleApp.csproj +++ b/src/mono/wasi/sample/console/WasiConsoleApp/WasiConsoleApp.csproj @@ -4,6 +4,7 @@ Exe net7.0 true + embedded diff --git a/src/mono/wasi/sample/console/main.c b/src/mono/wasi/sample/console/main.c index 4ee373135cd728..8647c4878f2aba 100644 --- a/src/mono/wasi/sample/console/main.c +++ b/src/mono/wasi/sample/console/main.c @@ -4,7 +4,7 @@ int main() { // Assume the runtime pack has been copied into the output directory as 'runtime' // Otherwise we have to mount an unrelated part of the filesystem within the WASM environment - const char* app_base_dir = "./WasiConsoleApp/bin/Release/net7.0"; + const char* app_base_dir = "./WasiConsoleApp/bin/Debug/net7.0"; char* assemblies_path; asprintf(&assemblies_path, "%s:%s/runtime/native:%s/runtime/lib/net7.0", app_base_dir, app_base_dir, app_base_dir); diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 5d7f1facca07a9..9c362aa5ed513a 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -388,5 +388,4 @@ - From ec0662c8b53058b9e80abab521abb3f50988d3cf Mon Sep 17 00:00:00 2001 From: madelson <1269046+madelson@users.noreply.github.com> Date: Fri, 12 Aug 2022 12:42:45 -0400 Subject: [PATCH 63/68] Improve NullabilityInfoContext behavior for ByRef types. (#73520) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve NullabilityInfoContext behavior for ByRef types. fix #72320 Co-authored-by: Buyaa Namnan Co-authored-by: Michal Strehovský --- .../Reflection/NullabilityInfoContext.cs | 44 ++-- .../Reflection/NullabilityInfoContextTests.cs | 214 ++++++++++++++++-- 2 files changed, 228 insertions(+), 30 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs index 57727d1b0dc3e3..67590653f595ac 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Reflection/NullabilityInfoContext.cs @@ -141,7 +141,7 @@ private static void CheckNullabilityAttributes(NullabilityInfo nullability, ILis else if ((attribute.AttributeType.Name == "MaybeNullAttribute" || attribute.AttributeType.Name == "MaybeNullWhenAttribute") && codeAnalysisReadState == NullabilityState.Unknown && - !nullability.Type.IsValueType) + !IsValueTypeOrValueTypeByRef(nullability.Type)) { codeAnalysisReadState = NullabilityState.Nullable; } @@ -151,7 +151,7 @@ private static void CheckNullabilityAttributes(NullabilityInfo nullability, ILis } else if (attribute.AttributeType.Name == "AllowNullAttribute" && codeAnalysisWriteState == NullabilityState.Unknown && - !nullability.Type.IsValueType) + !IsValueTypeOrValueTypeByRef(nullability.Type)) { codeAnalysisWriteState = NullabilityState.Nullable; } @@ -327,7 +327,7 @@ private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, Nul int index = 0; NullabilityInfo nullability = GetNullabilityInfo(memberInfo, type, parser, ref index); - if (!type.IsValueType && nullability.ReadState != NullabilityState.Unknown) + if (nullability.ReadState != NullabilityState.Unknown) { TryLoadGenericMetaTypeNullability(memberInfo, nullability); } @@ -340,19 +340,22 @@ private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, Nul NullabilityState state = NullabilityState.Unknown; NullabilityInfo? elementState = null; NullabilityInfo[] genericArgumentsState = Array.Empty(); - Type? underlyingType = type; + Type underlyingType = type; - if (type.IsValueType) + if (underlyingType.IsByRef || underlyingType.IsPointer) { - underlyingType = Nullable.GetUnderlyingType(type); + underlyingType = underlyingType.GetElementType()!; + } - if (underlyingType != null) + if (underlyingType.IsValueType) + { + if (Nullable.GetUnderlyingType(underlyingType) is { } nullableUnderlyingType) { + underlyingType = nullableUnderlyingType; state = NullabilityState.Nullable; } else { - underlyingType = type; state = NullabilityState.NotNull; } @@ -369,9 +372,9 @@ private NullabilityInfo GetNullabilityInfo(MemberInfo memberInfo, Type type, Nul state = contextState; } - if (type.IsArray) + if (underlyingType.IsArray) { - elementState = GetNullabilityInfo(memberInfo, type.GetElementType()!, parser, ref index); + elementState = GetNullabilityInfo(memberInfo, underlyingType.GetElementType()!, parser, ref index); } } @@ -468,6 +471,12 @@ private void CheckGenericParameters(NullabilityInfo nullability, MemberInfo meta { CheckGenericParameters(elementNullability, metaMember, metaType.GetElementType()!, reflectedType); } + // We could also follow this branch for metaType.IsPointer, but since pointers must be unmanaged this + // will be a no-op regardless + else if (metaType.IsByRef) + { + CheckGenericParameters(nullability, metaMember, metaType.GetElementType()!, reflectedType); + } } } @@ -482,6 +491,11 @@ private bool TryUpdateGenericParameterNullability(NullabilityInfo nullability, T return true; } + if (IsValueTypeOrValueTypeByRef(nullability.Type)) + { + return true; + } + var state = NullabilityState.Unknown; if (CreateParser(genericParameter.GetCustomAttributesData()).ParseNullableState(0, ref state)) { @@ -549,9 +563,10 @@ static int CountNullabilityStates(Type type) } return count; } - if (underlyingType.IsArray) + + if (underlyingType.HasElementType) { - return 1 + CountNullabilityStates(underlyingType.GetElementType()!); + return (underlyingType.IsArray ? 1 : 0) + CountNullabilityStates(underlyingType.GetElementType()!); } return type.IsValueType ? 0 : 1; @@ -560,7 +575,7 @@ static int CountNullabilityStates(Type type) private bool TryPopulateNullabilityInfo(NullabilityInfo nullability, NullableAttributeStateParser parser, ref int index) { - bool isValueType = nullability.Type.IsValueType; + bool isValueType = IsValueTypeOrValueTypeByRef(nullability.Type); if (!isValueType) { var state = NullabilityState.Unknown; @@ -606,6 +621,9 @@ private static NullabilityState TranslateByte(byte b) => _ => NullabilityState.Unknown }; + private static bool IsValueTypeOrValueTypeByRef(Type type) => + type.IsValueType || ((type.IsByRef || type.IsPointer) && type.GetElementType()!.IsValueType); + private readonly struct NullableAttributeStateParser { private static readonly object UnknownByte = (byte)0; diff --git a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs index 89f2fe49c9c499..aa0e9c0d464939 100644 --- a/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs +++ b/src/libraries/System.Runtime/tests/System/Reflection/NullabilityInfoContextTests.cs @@ -670,6 +670,139 @@ public void MethodParametersTest(string methodName, NullabilityState stringState Assert.Null(dictionaryNullability.ElementType); } + public static IEnumerable MethodByRefParametersTestData() => new[] + { + new object[] { -1, NullabilityState.Nullable }, // ref readonly int? + new object[] { 0, NullabilityState.Nullable }, // out int? a + new object[] { 1, NullabilityState.NotNull }, // out string b + new object[] { 2, NullabilityState.Nullable }, // ref object? c + new object[] { 3, NullabilityState.NotNull }, // ref Type d + new object[] { 4, NullabilityState.Nullable }, // in decimal? e + new object[] { 5, NullabilityState.NotNull }, // in ICloneable f + }; + + [Theory] + [SkipOnMono("Nullability attributes trimmed on Mono")] + [MemberData(nameof(MethodByRefParametersTestData))] + public void MethodByRefParametersTest(int parameterIndex, NullabilityState state) + { + MethodInfo method = typeof(TypeWithNotNullContext).GetMethod(nameof(TypeWithNotNullContext.MethodWithByRefs))!; + ParameterInfo parameter = parameterIndex == -1 ? method.ReturnParameter : method.GetParameters()[parameterIndex]; + NullabilityInfo info = nullabilityContext.Create(parameter); + + Assert.Equal(parameter.ParameterType, info.Type); + Assert.Equal(state, info.ReadState); + Assert.Equal(state, info.WriteState); + } + + public static IEnumerable MethodGenericByRefParametersTestData() => new[] +{ + // ref Tuple + new object?[] { -1, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }, + // out KeyValuePair a + new object?[] { 0, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, null }, + // ref Tuple? b + new object?[] { 1, NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull, null }, + // in KeyValuePair? c + new object?[] { 2, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable, null }, + }; + + [Theory] + [SkipOnMono("Nullability attributes trimmed on Mono")] + [MemberData(nameof(MethodGenericByRefParametersTestData))] + public void MethodGenericByRefParametersTest( + int parameterIndex, + NullabilityState state, + NullabilityState genericArgument0State, + NullabilityState genericArgument1State, + NullabilityState? genericArgument2State) + { + MethodInfo method = typeof(TypeWithNotNullContext).GetMethod(nameof(TypeWithNotNullContext.MethodWithGenericByRefs))!; + ParameterInfo parameter = parameterIndex == -1 ? method.ReturnParameter : method.GetParameters()[parameterIndex]; + NullabilityInfo info = nullabilityContext.Create(parameter); + Assert.Equal(parameter.ParameterType, info.Type); + Assert.Equal(state, info.ReadState); + Assert.Equal(state, info.WriteState); + + NullabilityState?[] genericArgumentStates = new[] { genericArgument0State, genericArgument1State, genericArgument2State }; + for (var i = 0; i < genericArgumentStates.Length; ++i) + { + if (genericArgumentStates[i] is null) + { + Assert.Equal(i, info.GenericTypeArguments.Length); + break; + } + + Assert.Equal(genericArgumentStates[i], info.GenericTypeArguments[i].ReadState); + Assert.Equal(genericArgumentStates[i], info.GenericTypeArguments[i].WriteState); + } + } + + [Fact] + [SkipOnMono("Nullability attributes trimmed on Mono")] + public void ConstrainedGenericMethodWithByRefTest() + { + // ref T ConstrainedGenericMethodWithByRef(out T[] array) where T : class? + MethodInfo method = typeof(TypeWithNotNullContext).GetMethod(nameof(TypeWithNotNullContext.ConstrainedGenericMethodWithByRef))! + .MakeGenericMethod(typeof(string)); + + NullabilityInfo info = nullabilityContext.Create(method.ReturnParameter); + Assert.Equal(method.ReturnType, info.Type); + Assert.Equal(NullabilityState.Nullable, info.ReadState); + Assert.Equal(NullabilityState.Nullable, info.WriteState); + + info = nullabilityContext.Create(method.GetParameters()[0]); + Assert.Equal(typeof(string[]).MakeByRefType(), info.Type); + Assert.Equal(NullabilityState.NotNull, info.ReadState); + Assert.Equal(NullabilityState.NotNull, info.WriteState); + Assert.Equal(typeof(string), info.ElementType!.Type); + Assert.Equal(NullabilityState.Nullable, info.ElementType.ReadState); + Assert.Equal(NullabilityState.Nullable, info.ElementType.WriteState); + } + + [Fact] + [SkipOnMono("Nullability attributes trimmed on Mono")] + public void MethodWithPointersTest() + { + // MethodWithPointers(int* a, int?* b) + MethodInfo method = typeof(TypeWithNotNullContext).GetMethod(nameof(TypeWithNotNullContext.MethodWithPointers))!; + ParameterInfo[] parameters = method.GetParameters(); + + NullabilityInfo info = nullabilityContext.Create(parameters[0]); + Assert.Equal(parameters[0].ParameterType, info.Type); + Assert.Equal(NullabilityState.NotNull, info.ReadState); + Assert.Equal(NullabilityState.NotNull, info.WriteState); + + info = nullabilityContext.Create(parameters[1]); + Assert.Equal(parameters[1].ParameterType, info.Type); + Assert.Equal(NullabilityState.Nullable, info.ReadState); + Assert.Equal(NullabilityState.Nullable, info.WriteState); + } + + [Fact] + [SkipOnMono("Nullability attributes trimmed on Mono")] + public unsafe void GenericMethodWithPointersTest() + { + // Dummy call to MakeGenericMethod to make the code generated ahead of time + if (string.Empty.Length > 0) + new TypeWithNotNullContext().GenericMethodWithPointers(null, null); + + // GenericMethodWithPointers(T* a, T?* b) + MethodInfo method = typeof(TypeWithNotNullContext).GetMethod(nameof(TypeWithNotNullContext.GenericMethodWithPointers))! + .MakeGenericMethod(typeof(float)); + ParameterInfo[] parameters = method.GetParameters(); + + NullabilityInfo info = nullabilityContext.Create(parameters[0]); + Assert.Equal(parameters[0].ParameterType, info.Type); + Assert.Equal(NullabilityState.NotNull, info.ReadState); + Assert.Equal(NullabilityState.NotNull, info.WriteState); + + info = nullabilityContext.Create(parameters[1]); + Assert.Equal(parameters[1].ParameterType, info.Type); + Assert.Equal(NullabilityState.Nullable, info.ReadState); + Assert.Equal(NullabilityState.Nullable, info.WriteState); + } + public static IEnumerable MethodGenericParametersTestData() { yield return new object[] { "MethodParametersUnknown", NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown, NullabilityState.Unknown }; @@ -692,6 +825,34 @@ public void MethodGenericParametersTest(string methodName, NullabilityState para Assert.Equal(dictValue, dictionaryNullability.GenericTypeArguments[1].ReadState); } + [Fact] + [SkipOnMono("Nullability attributes trimmed on Mono")] + public void PropertyWithByRefGenericParametersTest() + { + // ref T? this[in T a, in List b] { get; } + PropertyInfo property = typeof(GenericTest).GetProperty("Item", typeof(string).MakeByRefType())!; + + NullabilityInfo info = nullabilityContext.Create(property); + Assert.Equal(typeof(string).MakeByRefType(), info.Type); + Assert.Equal(NullabilityState.Nullable, info.ReadState); + Assert.Equal(NullabilityState.Unknown, info.WriteState); + + info = nullabilityContext.Create(property.GetIndexParameters()[0]); + Assert.Equal(typeof(string).MakeByRefType(), info.Type); + // in T a is nullable because T is not constrained + Assert.Equal(NullabilityState.Nullable, info.ReadState); + Assert.Equal(NullabilityState.Nullable, info.WriteState); + + info = nullabilityContext.Create(property.GetIndexParameters()[1]); + Assert.Equal(typeof(List).MakeByRefType(), info.Type); + Assert.Equal(NullabilityState.NotNull, info.ReadState); + Assert.Equal(NullabilityState.NotNull, info.WriteState); + info = Assert.Single(info.GenericTypeArguments); + Assert.Equal(typeof(string), info.Type); + Assert.Equal(NullabilityState.Nullable, info.ReadState); + Assert.Equal(NullabilityState.Nullable, info.WriteState); + } + public static IEnumerable StringTypeTestData() { yield return new object[] { "Format", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.Nullable, new Type[] { typeof(string), typeof(object), typeof(object) } }; @@ -946,7 +1107,7 @@ public static IEnumerable RefReturnData() yield return new object[] { "RefReturnNotNullable", NullabilityState.NotNull, NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull }; // [return: NotNull]public ref string? RefReturnNotNull([NotNull] ref string? id) yield return new object[] { "RefReturnNotNull", NullabilityState.NotNull, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; - // publiic ref string? RefReturnNullable([AllowNull] ref string id) + // public ref string? RefReturnNullable([AllowNull] ref string id) yield return new object[] { "RefReturnNullable", NullabilityState.Nullable, NullabilityState.Nullable, NullabilityState.NotNull, NullabilityState.Nullable }; } @@ -1090,12 +1251,18 @@ public void TestNestedGenericInheritanceWithMultipleParameters() Assert.Equal(NullabilityState.NotNull, item3Info.ElementType.WriteState); } - [Fact] - [SkipOnMono("Nullability attributes trimmed on Mono")] - public void TestNullabilityInfoCreationOnPropertiesWithNestedGenericTypeArguments() + public static IEnumerable TestNullabilityInfoCreationOnPropertiesWithNestedGenericTypeArgumentsData() => new[] { - Type type = typeof(TypeWithPropertiesNestingItsGenericTypeArgument); + new object[] { typeof(TypeWithPropertiesNestingItsGenericTypeArgument), NullabilityState.NotNull }, + new object[] { typeof(TypeWithPropertiesNestingItsGenericTypeArgument), NullabilityState.Nullable }, + new object[] { typeof(TypeWithPropertiesNestingItsGenericTypeArgument), NullabilityState.Nullable }, + }; + [Theory] + [SkipOnMono("Nullability attributes trimmed on Mono")] + [MemberData(nameof(TestNullabilityInfoCreationOnPropertiesWithNestedGenericTypeArgumentsData))] + public void TestNullabilityInfoCreationOnPropertiesWithNestedGenericTypeArguments(Type type, NullabilityState expectedGenericArgumentNullability) + { NullabilityInfo shallow1Info = nullabilityContext.Create(type.GetProperty("Shallow1")!); NullabilityInfo deep1Info = nullabilityContext.Create(type.GetProperty("Deep1")!); NullabilityInfo deep2Info = nullabilityContext.Create(type.GetProperty("Deep2")!); @@ -1103,51 +1270,51 @@ public void TestNullabilityInfoCreationOnPropertiesWithNestedGenericTypeArgument NullabilityInfo deep4Info = nullabilityContext.Create(type.GetProperty("Deep4")!); NullabilityInfo deep5Info = nullabilityContext.Create(type.GetProperty("Deep5")!); - //public Tuple? Shallow1 { get; set; } + // public Tuple? Shallow1 { get; set; } NullabilityInfo info = shallow1Info; Assert.Equal(1, info.GenericTypeArguments.Length); - Assert.Equal(NullabilityState.Nullable, info.GenericTypeArguments[0].ReadState); + Assert.Equal(expectedGenericArgumentNullability, info.GenericTypeArguments[0].ReadState); - //public Tuple>? Deep1 { get; set; } + // public Tuple>? Deep1 { get; set; } info = deep1Info; Assert.Equal(1, info.GenericTypeArguments.Length); Assert.Equal(1, info.GenericTypeArguments[0].GenericTypeArguments.Length); Assert.Equal(NullabilityState.NotNull, info.GenericTypeArguments[0].ReadState); - Assert.Equal(NullabilityState.Nullable, info.GenericTypeArguments[0].GenericTypeArguments[0].ReadState); + Assert.Equal(expectedGenericArgumentNullability, info.GenericTypeArguments[0].GenericTypeArguments[0].ReadState); - //public Tuple, int>? Deep2 { get; set; } + // public Tuple, int>? Deep2 { get; set; } info = deep2Info; Assert.Equal(2, info.GenericTypeArguments.Length); Assert.Equal(1, info.GenericTypeArguments[0].GenericTypeArguments.Length); Assert.Equal(NullabilityState.NotNull, info.GenericTypeArguments[0].ReadState); Assert.Equal(NullabilityState.NotNull, info.GenericTypeArguments[1].ReadState); - Assert.Equal(NullabilityState.Nullable, info.GenericTypeArguments[0].GenericTypeArguments[0].ReadState); + Assert.Equal(expectedGenericArgumentNullability, info.GenericTypeArguments[0].GenericTypeArguments[0].ReadState); - //public Tuple>? Deep3 { get; set; } + // public Tuple>? Deep3 { get; set; } info = deep3Info; Assert.Equal(2, info.GenericTypeArguments.Length); Assert.Equal(1, info.GenericTypeArguments[1].GenericTypeArguments.Length); Assert.Equal(NullabilityState.Nullable, info.GenericTypeArguments[0].ReadState); Assert.Equal(NullabilityState.NotNull, info.GenericTypeArguments[1].ReadState); - Assert.Equal(NullabilityState.Nullable, info.GenericTypeArguments[1].GenericTypeArguments[0].ReadState); + Assert.Equal(expectedGenericArgumentNullability, info.GenericTypeArguments[1].GenericTypeArguments[0].ReadState); - //public Tuple>? Deep4 { get; set; } + // public Tuple>? Deep4 { get; set; } info = deep4Info; Assert.Equal(3, info.GenericTypeArguments.Length); Assert.Equal(1, info.GenericTypeArguments[2].GenericTypeArguments.Length); Assert.Equal(NullabilityState.NotNull, info.GenericTypeArguments[0].ReadState); Assert.Equal(NullabilityState.Nullable, info.GenericTypeArguments[1].ReadState); Assert.Equal(NullabilityState.NotNull, info.GenericTypeArguments[2].ReadState); - Assert.Equal(NullabilityState.Nullable, info.GenericTypeArguments[2].GenericTypeArguments[0].ReadState); + Assert.Equal(expectedGenericArgumentNullability, info.GenericTypeArguments[2].GenericTypeArguments[0].ReadState); - //public Tuple?>? Deep5 { get; set; } + // public Tuple?>? Deep5 { get; set; } info = deep5Info; Assert.Equal(3, info.GenericTypeArguments.Length); Assert.Equal(2, info.GenericTypeArguments[2].GenericTypeArguments.Length); Assert.Equal(NullabilityState.NotNull, info.GenericTypeArguments[0].ReadState); Assert.Equal(NullabilityState.NotNull, info.GenericTypeArguments[1].ReadState); Assert.Equal(NullabilityState.Nullable, info.GenericTypeArguments[2].ReadState); - Assert.Equal(NullabilityState.Nullable, info.GenericTypeArguments[2].GenericTypeArguments[0].ReadState); + Assert.Equal(expectedGenericArgumentNullability, info.GenericTypeArguments[2].GenericTypeArguments[0].ReadState); Assert.Equal(NullabilityState.NotNull, info.GenericTypeArguments[2].GenericTypeArguments[1].ReadState); } } @@ -1298,6 +1465,17 @@ public void MethodNullNonNullNonNon(string? s, IDictionary dict public void MethodNonNullNonNullNotNull(string s, [NotNull] IDictionary? dict) { dict = new Dictionary(); } public void MethodNullNonNullNullNon(string? s, IDictionary dict) { } public void MethodAllowNullNonNonNonNull([AllowNull] string s, IDictionary? dict) { } + + public ref readonly int? MethodWithByRefs(out int? a, out string b, ref object? c, ref Type d, in decimal? e, in ICloneable f) => + throw new NotImplementedException(); + public ref Tuple MethodWithGenericByRefs( + out KeyValuePair a, + ref Tuple? b, + in KeyValuePair? c) => + throw new NotImplementedException(); + public ref T ConstrainedGenericMethodWithByRef(out T[] array) where T : class? => throw new NotImplementedException(); + public unsafe void MethodWithPointers(int* a, int?* b) { } + public unsafe void GenericMethodWithPointers(T* a, T?* b) where T : unmanaged { } } public struct GenericStruct { } @@ -1353,6 +1531,8 @@ public void MethodParametersUnknown(T s, IDictionary dict) { } public List? MethodNullListNonNullGeneric() => null; public void MethodArgsNullGenericNullDictValueGeneric(T? s, IDictionary? dict) { } public void MethodArgsGenericDictValueNullGeneric(T s, IDictionary dict) { } + + public ref T? this[in T a, in List b] => throw new NotImplementedException(); } internal class GenericTestConstrainedNotNull where T : notnull From e8adb12e9c3c1c7f83e5988db756a02558620516 Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Fri, 12 Aug 2022 18:58:43 +0200 Subject: [PATCH 64/68] Access the User-Agent for CONNECT tunnels early (#73331) --- .../SocketsHttpHandler/HttpConnectionPool.cs | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index e560915288e56a..4a2362329b9f79 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -43,6 +43,9 @@ internal sealed class HttpConnectionPool : IDisposable /// If true, the will persist across a network change. If false, it will be reset to . private bool _persistAuthority; + /// The User-Agent header to use when creating a CONNECT tunnel. + private string? _connectTunnelUserAgent; + /// /// When an Alt-Svc authority fails due to 421 Misdirected Request, it is placed in the blocklist to be ignored /// for milliseconds. Initialized on first use. @@ -1482,6 +1485,15 @@ public ValueTask SendWithProxyAuthAsync(HttpRequestMessage public ValueTask SendAsync(HttpRequestMessage request, bool async, bool doRequestAuth, CancellationToken cancellationToken) { + // We need the User-Agent header when we send a CONNECT request to the proxy. + // We must read the header early, before we return the ownership of the request back to the user. + if ((Kind is HttpConnectionKind.ProxyTunnel or HttpConnectionKind.SslProxyTunnel) && + request.HasHeaders && + request.Headers.NonValidated.TryGetValues(HttpKnownHeaderNames.UserAgent, out HeaderStringValues userAgent)) + { + _connectTunnelUserAgent = userAgent.ToString(); + } + if (doRequestAuth && Settings._credentials != null) { return AuthenticationHelper.SendWithRequestAuthAsync(request, async, Settings._credentials, Settings._preAuthenticate, this, cancellationToken); @@ -1510,7 +1522,7 @@ public ValueTask SendAsync(HttpRequestMessage request, bool case HttpConnectionKind.ProxyTunnel: case HttpConnectionKind.SslProxyTunnel: - stream = await EstablishProxyTunnelAsync(async, request.HasHeaders ? request.Headers : null, cancellationToken).ConfigureAwait(false); + stream = await EstablishProxyTunnelAsync(async, cancellationToken).ConfigureAwait(false); break; case HttpConnectionKind.SocksTunnel: @@ -1700,7 +1712,7 @@ private async ValueTask ConstructHttp2ConnectionAsync(Stream st return http2Connection; } - private async ValueTask EstablishProxyTunnelAsync(bool async, HttpRequestHeaders? headers, CancellationToken cancellationToken) + private async ValueTask EstablishProxyTunnelAsync(bool async, CancellationToken cancellationToken) { Debug.Assert(_originAuthority != null); @@ -1708,9 +1720,9 @@ private async ValueTask EstablishProxyTunnelAsync(bool async, HttpReques HttpRequestMessage tunnelRequest = new HttpRequestMessage(HttpMethod.Connect, _proxyUri); tunnelRequest.Headers.Host = $"{_originAuthority.IdnHost}:{_originAuthority.Port}"; // This specifies destination host/port to connect to - if (headers != null && headers.TryGetValues(HttpKnownHeaderNames.UserAgent, out IEnumerable? values)) + if (_connectTunnelUserAgent is not null) { - tunnelRequest.Headers.TryAddWithoutValidation(HttpKnownHeaderNames.UserAgent, values); + tunnelRequest.Headers.TryAddWithoutValidation(KnownHeaders.UserAgent.Descriptor, _connectTunnelUserAgent); } HttpResponseMessage tunnelResponse = await _poolManager.SendProxyConnectAsync(tunnelRequest, _proxyUri!, async, cancellationToken).ConfigureAwait(false); From 80aeaa094df95c408a1fd1b530d20b5da2f23575 Mon Sep 17 00:00:00 2001 From: Alhad Deshpande <97085048+alhad-deshpande@users.noreply.github.com> Date: Fri, 12 Aug 2022 22:32:23 +0530 Subject: [PATCH 65/68] [Ppc64le] Added memory patch thunking for function calls (#73616) * [ppc64le] Added memory patch thunking for function calls * Incorporated code review comments and changes in OP_TAILCALL for mempry patching * [ppc64le] Fixed test failures and code refactoring * [ppc64le] Enabled g_assert for code sequence check and removed white spaces * [ppc64le] Fixed build issue in mini-ppc.h * [ppc64le] Incorporated code review comments --- src/mono/mono/mini/cpu-ppc.mdesc | 2 +- src/mono/mono/mini/cpu-ppc64.mdesc | 2 +- src/mono/mono/mini/mini-ppc.c | 64 ++++++++++++++++++++++++++++-- src/mono/mono/mini/mini-ppc.h | 9 +++++ src/mono/mono/mini/tramp-ppc.c | 12 +++++- 5 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/mono/mono/mini/cpu-ppc.mdesc b/src/mono/mono/mini/cpu-ppc.mdesc index c92495c356fa51..301b96a6530bdf 100644 --- a/src/mono/mono/mini/cpu-ppc.mdesc +++ b/src/mono/mono/mini/cpu-ppc.mdesc @@ -348,4 +348,4 @@ atomic_cas_i4: src1:b src2:i src3:i dest:i len:38 liverange_start: len:0 liverange_end: len:0 -gc_safe_point: clob:c src1:i len:40 +gc_safe_point: clob:c src1:i len:44 diff --git a/src/mono/mono/mini/cpu-ppc64.mdesc b/src/mono/mono/mini/cpu-ppc64.mdesc index 4dca1bb3ab43cf..d2437a34fda45e 100644 --- a/src/mono/mono/mini/cpu-ppc64.mdesc +++ b/src/mono/mono/mini/cpu-ppc64.mdesc @@ -417,4 +417,4 @@ atomic_cas_i8: src1:b src2:i src3:i dest:i len:38 liverange_start: len:0 liverange_end: len:0 -gc_safe_point: clob:c src1:i len:40 +gc_safe_point: clob:c src1:i len:44 diff --git a/src/mono/mono/mini/mini-ppc.c b/src/mono/mono/mini/mini-ppc.c index 30dc899ab6969b..93abd63562ff53 100644 --- a/src/mono/mono/mini/mini-ppc.c +++ b/src/mono/mono/mini/mini-ppc.c @@ -311,7 +311,12 @@ mono_ppc_is_direct_call_sequence (guint32 *code) if (ppc_opcode (code [-2]) == 24 && ppc_opcode (code [-3]) == 31) /* mr/nop */ return is_load_sequence (&code [-8]); else +#if !defined(PPC_USES_FUNCTION_DESCRIPTOR) + /* the memory patch thunk sequence for ppc64le is: lis/ori/sldi/oris/ori/ld/mtlr/blrl */ + return is_load_sequence (&code [-7]); +#else return is_load_sequence (&code [-6]); +#endif } return FALSE; #else @@ -2692,6 +2697,10 @@ emit_float_to_int (MonoCompile *cfg, guchar *code, int dreg, int sreg, int size, static void emit_thunk (guint8 *code, gconstpointer target) { +#if defined(TARGET_POWERPC64) && !defined(PPC_USES_FUNCTION_DESCRIPTOR) + *(guint64*)code = (guint64)target; + code += sizeof (guint64); +#else guint8 *p = code; /* 2 bytes on 32bit, 5 bytes on 64bit */ @@ -2701,6 +2710,7 @@ emit_thunk (guint8 *code, gconstpointer target) ppc_bcctr (code, PPC_BR_ALWAYS, 0); mono_arch_flush_icache (p, code - p); +#endif } static void @@ -2731,9 +2741,14 @@ handle_thunk (MonoCompile *cfg, guchar *code, const guchar *target) } g_assert (*(guint32*)thunks == 0); + + emit_thunk (thunks, target); +#if defined(TARGET_POWERPC64) && !defined(PPC_USES_FUNCTION_DESCRIPTOR) + ppc_load_ptr_sequence (code, PPC_CALL_REG, thunks); +#else ppc_patch (code, thunks); - +#endif cfg->arch.thunks += THUNK_SIZE; cfg->arch.thunks_size -= THUNK_SIZE; } else { @@ -2753,7 +2768,9 @@ handle_thunk (MonoCompile *cfg, guchar *code, const guchar *target) if (orig_target >= thunks && orig_target < thunks + thunks_size) { /* The call already points to a thunk, because of trampolines etc. */ target_thunk = orig_target; - } else { + } +#if (defined(TARGET_POWERPC64) && defined(PPC_USES_FUNCTION_DESCRIPTOR)) || !defined(TARGET_POWERPC64) + else { for (p = thunks; p < thunks + thunks_size; p += THUNK_SIZE) { if (((guint32 *) p) [0] == 0) { /* Free entry */ @@ -2777,7 +2794,7 @@ handle_thunk (MonoCompile *cfg, guchar *code, const guchar *target) } } } - +#endif // g_print ("THUNK: %p %p %p\n", code, target, target_thunk); if (!target_thunk) { @@ -2787,7 +2804,9 @@ handle_thunk (MonoCompile *cfg, guchar *code, const guchar *target) } emit_thunk (target_thunk, target); +#if (defined(TARGET_POWERPC64) && defined(PPC_USES_FUNCTION_DESCRIPTOR)) || !defined(TARGET_POWERPC64) ppc_patch (code, target_thunk); +#endif mono_mini_arch_unlock (); } @@ -2875,6 +2894,9 @@ ppc_patch_full (MonoCompile *cfg, guchar *code, const guchar *target, gboolean i if (prim == 15 || ins == 0x4e800021 || ins == 0x4e800020 || ins == 0x4e800420) { #ifdef TARGET_POWERPC64 +#if !defined(PPC_USES_FUNCTION_DESCRIPTOR) + handle_thunk (cfg, code, target); +#else guint32 *seq = (guint32*)code; guint32 *branch_ins; @@ -2923,6 +2945,7 @@ ppc_patch_full (MonoCompile *cfg, guchar *code, const guchar *target, gboolean i ppc_load_ptr_sequence (code, PPC_CALL_REG, target); #endif mono_arch_flush_icache ((guint8*)seq, 28); +#endif #else guint32 *seq; /* the trampoline code will try to patch the blrl, blr, bcctr */ @@ -3349,6 +3372,10 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) mono_add_patch_info (cfg, code - cfg->native_code, MONO_PATCH_INFO_JIT_ICALL_ID, GUINT_TO_POINTER (MONO_JIT_ICALL_mono_break)); if ((FORCE_INDIR_CALL || cfg->method->dynamic) && !cfg->compile_aot) { ppc_load_func (code, PPC_CALL_REG, 0); +#if defined(TARGET_POWERPC64) && !defined(PPC_USES_FUNCTION_DESCRIPTOR) + ppc_ldr (code, PPC_CALL_REG, 0, PPC_CALL_REG); + cfg->thunk_area += THUNK_SIZE; +#endif ppc_mtlr (code, PPC_CALL_REG); ppc_blrl (code); } else { @@ -3794,7 +3821,14 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) ppc_mtctr (code, ppc_r0); ppc_bcctr (code, PPC_BR_ALWAYS, 0); } else { +#if defined(TARGET_POWERPC64) && !defined(PPC_USES_FUNCTION_DESCRIPTOR) + ppc_load_func (code, PPC_CALL_REG, 0); + ppc_ldr (code, PPC_CALL_REG, 0, PPC_CALL_REG); + ppc_mtctr (code, PPC_CALL_REG); + ppc_bcctr (code, PPC_BR_ALWAYS, 0); +#else ppc_b (code, 0); +#endif } break; } @@ -3823,6 +3857,10 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) mono_call_add_patch_info (cfg, call, offset); if ((FORCE_INDIR_CALL || cfg->method->dynamic) && !cfg->compile_aot) { ppc_load_func (code, PPC_CALL_REG, 0); +#if defined(TARGET_POWERPC64) && !defined(PPC_USES_FUNCTION_DESCRIPTOR) + ppc_ldr (code, PPC_CALL_REG, 0, PPC_CALL_REG); + cfg->thunk_area += THUNK_SIZE; +#endif ppc_mtlr (code, PPC_CALL_REG); ppc_blrl (code); } else { @@ -3926,6 +3964,10 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) mono_add_patch_info (cfg, code - cfg->native_code, MONO_PATCH_INFO_JIT_ICALL_ID, GUINT_TO_POINTER (MONO_JIT_ICALL_mono_arch_throw_exception)); if ((FORCE_INDIR_CALL || cfg->method->dynamic) && !cfg->compile_aot) { ppc_load_func (code, PPC_CALL_REG, 0); +#if defined(TARGET_POWERPC64) && !defined(PPC_USES_FUNCTION_DESCRIPTOR) + ppc_ldr (code, PPC_CALL_REG, 0, PPC_CALL_REG); + cfg->thunk_area += THUNK_SIZE; +#endif ppc_mtlr (code, PPC_CALL_REG); ppc_blrl (code); } else { @@ -3940,6 +3982,10 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) GUINT_TO_POINTER (MONO_JIT_ICALL_mono_arch_rethrow_exception)); if ((FORCE_INDIR_CALL || cfg->method->dynamic) && !cfg->compile_aot) { ppc_load_func (code, PPC_CALL_REG, 0); +#if defined(TARGET_POWERPC64) && !defined(PPC_USES_FUNCTION_DESCRIPTOR) + ppc_ldr (code, PPC_CALL_REG, 0, PPC_CALL_REG); + cfg->thunk_area += THUNK_SIZE; +#endif ppc_mtlr (code, PPC_CALL_REG); ppc_blrl (code); } else { @@ -4584,6 +4630,10 @@ mono_arch_output_basic_block (MonoCompile *cfg, MonoBasicBlock *bb) GUINT_TO_POINTER (MONO_JIT_ICALL_mono_threads_state_poll)); if ((FORCE_INDIR_CALL || cfg->method->dynamic) && !cfg->compile_aot) { ppc_load_func (code, PPC_CALL_REG, 0); +#if defined(TARGET_POWERPC64) && !defined(PPC_USES_FUNCTION_DESCRIPTOR) + ppc_ldr (code, PPC_CALL_REG, 0, PPC_CALL_REG); + cfg->thunk_area += THUNK_SIZE; +#endif ppc_mtlr (code, PPC_CALL_REG); ppc_blrl (code); } else { @@ -5185,6 +5235,10 @@ mono_arch_emit_prolog (MonoCompile *cfg) GUINT_TO_POINTER (MONO_JIT_ICALL_mono_tls_get_lmf_addr_extern)); if ((FORCE_INDIR_CALL || cfg->method->dynamic) && !cfg->compile_aot) { ppc_load_func (code, PPC_CALL_REG, 0); +#if defined(TARGET_POWERPC64) && !defined(PPC_USES_FUNCTION_DESCRIPTOR) + ppc_ldr (code, PPC_CALL_REG, 0, PPC_CALL_REG); + cfg->thunk_area += THUNK_SIZE; +#endif ppc_mtlr (code, PPC_CALL_REG); ppc_blrl (code); } else { @@ -5466,6 +5520,10 @@ mono_arch_emit_exceptions (MonoCompile *cfg) patch_info->ip.i = code - cfg->native_code; if (FORCE_INDIR_CALL || cfg->method->dynamic) { ppc_load_func (code, PPC_CALL_REG, 0); +#if defined(TARGET_POWERPC64) && !defined(PPC_USES_FUNCTION_DESCRIPTOR) + ppc_ldr (code, PPC_CALL_REG, 0, PPC_CALL_REG); + cfg->thunk_area += THUNK_SIZE; +#endif ppc_mtctr (code, PPC_CALL_REG); ppc_bcctr (code, PPC_BR_ALWAYS, 0); } else { diff --git a/src/mono/mono/mini/mini-ppc.h b/src/mono/mono/mini/mini-ppc.h index 688ca1f7992624..0b962aac233d24 100644 --- a/src/mono/mono/mini/mini-ppc.h +++ b/src/mono/mono/mini/mini-ppc.h @@ -33,7 +33,16 @@ #define MONO_ARCH_CODE_ALIGNMENT 32 #ifdef TARGET_POWERPC64 +#if !defined(PPC_USES_FUNCTION_DESCRIPTOR) +#define THUNK_SIZE 8 +#define GET_MEMORY_SLOT_THUNK_ADDRESS(c) \ + ((guint64)(((c)) [0] & 0x0000ffff) << 48) \ + + ((guint64)(((c)) [1] & 0x0000ffff) << 32) \ + + ((guint64)(((c)) [3] & 0x0000ffff) << 16) \ + + (guint64)(((c)) [4] & 0x0000ffff) +#else #define THUNK_SIZE ((2 + 5) * 4) +#endif #else #define THUNK_SIZE ((2 + 2) * 4) #endif diff --git a/src/mono/mono/mini/tramp-ppc.c b/src/mono/mono/mini/tramp-ppc.c index 3a78709c6f8cb9..59bcb275a48609 100644 --- a/src/mono/mono/mini/tramp-ppc.c +++ b/src/mono/mono/mini/tramp-ppc.c @@ -669,7 +669,17 @@ mono_arch_get_call_target (guint8 *code) guint8 *target = code - 4 + (disp * 4); return target; - } else { + } +#if defined(TARGET_POWERPC64) && !defined(PPC_USES_FUNCTION_DESCRIPTOR) + else if (((guint32*)(code - 32)) [0] >> 26 == 15) { + guint8 *thunk = GET_MEMORY_SLOT_THUNK_ADDRESS((guint32*)(code - 32)); + return thunk; + } else if (((guint32*)(code - 4)) [0] >> 26 == 15) { + guint8 *thunk = GET_MEMORY_SLOT_THUNK_ADDRESS((guint32*)(code - 4)); + return thunk; + } +#endif + else { return NULL; } } From da24b54466d693c7fe8175ece3b60119b0ed841f Mon Sep 17 00:00:00 2001 From: Jose Perez Rodriguez Date: Fri, 12 Aug 2022 10:21:25 -0700 Subject: [PATCH 66/68] Adding documentation explaining case-insensitive behavior across engines (#73814) Co-authored-by: Stephen Toub --- .../RegularExpressions/GeneratedRegexAttribute.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/GeneratedRegexAttribute.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/GeneratedRegexAttribute.cs index cab35c27c1039b..d78299f651da10 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/GeneratedRegexAttribute.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/GeneratedRegexAttribute.cs @@ -8,7 +8,20 @@ namespace System.Text.RegularExpressions; /// Instructs the System.Text.RegularExpressions source generator to generate an implementation of the specified regular expression. -/// The generator associated with this attribute only supports C#. It only supplies an implementation when applied to static, partial, parameterless, non-generic methods that are typed to return . +/// +/// +/// The generator associated with this attribute only supports C#. It only supplies an implementation when applied to static, partial, parameterless, non-generic methods that +/// are typed to return . +/// +/// +/// When the supports case-insensitive matches (either by passing or using the inline `(?i)` switch in the pattern) the regex +/// engines will use an internal casing table to transform the passed in pattern into an equivalent case-sensitive one. For example, given the pattern `abc`, the engines +/// will transform it to the equivalent pattern `[Aa][Bb][Cc]`. The equivalences found in this internal casing table can change over time, for example in the case new characters are added to +/// a new version of Unicode. When using the source generator, this transformation happens at compile time, which means the casing table used to find the +/// equivalences will depend on the target framework at compile time. This differs from the rest of the engines, which perform this transformation at run-time, meaning +/// they will always use casing table for the current runtime. +/// +/// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public sealed class GeneratedRegexAttribute : Attribute { From 31d5d23e9c6f3da877343ccb020e85ca9f136c05 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Fri, 12 Aug 2022 10:21:44 -0700 Subject: [PATCH 67/68] [main] Update dependencies from dotnet/linker (#73410) * Update dependencies from https://github.com/dotnet/linker build 20220804.4 Microsoft.NET.ILLink.Tasks From Version 7.0.100-1.22377.1 -> To Version 7.0.100-1.22404.4 * Update dependencies from https://github.com/dotnet/linker build 20220804.5 Microsoft.NET.ILLink.Tasks From Version 7.0.100-1.22377.1 -> To Version 7.0.100-1.22404.5 * Update dependencies from https://github.com/dotnet/linker build 20220805.1 Microsoft.NET.ILLink.Tasks From Version 7.0.100-1.22377.1 -> To Version 7.0.100-1.22405.1 * Update dependencies from https://github.com/dotnet/linker build 20220808.3 Microsoft.NET.ILLink.Tasks From Version 7.0.100-1.22377.1 -> To Version 7.0.100-1.22408.3 * Update dependencies from https://github.com/dotnet/linker build 20220809.9 Microsoft.NET.ILLink.Tasks From Version 7.0.100-1.22377.1 -> To Version 7.0.100-1.22409.9 * Added and modified suppressions * Disable IL2121 everywhere for now * Disable IL2121 in trimming tests * Disable IL2121 in wam samples * Disable IL2121 in libraries tests * Update dependencies from https://github.com/dotnet/linker build 20220811.2 Microsoft.NET.ILLink.Tasks From Version 7.0.100-1.22377.1 -> To Version 7.0.100-1.22411.2 Co-authored-by: dotnet-maestro[bot] Co-authored-by: Ankit Jain Co-authored-by: Jeremi Kurdek Co-authored-by: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Co-authored-by: Sven Boemer --- eng/Version.Details.xml | 4 ++-- eng/Versions.props | 2 +- eng/illink.targets | 2 ++ eng/testing/linker/SupportFiles/Directory.Build.props | 1 + src/libraries/Directory.Build.props | 2 ++ .../System.Data.Common/src/System/Data/DataRowExtensions.cs | 2 ++ .../Converters/FSharp/FSharpTypeConverterFactory.cs | 2 ++ .../Serialization/Converters/Value/EnumConverterFactory.cs | 2 +- .../Converters/Value/NullableConverterFactory.cs | 2 +- src/libraries/oob.proj | 2 ++ src/libraries/sfx.proj | 2 ++ src/mono/sample/wasm/Directory.Build.props | 2 ++ 12 files changed, 20 insertions(+), 5 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 1c412944b83883..43b226bb71a595 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -234,9 +234,9 @@ https://github.com/dotnet/runtime 0c5ca95fec9b6e2cc5e757ad36d0b094f81a59a6 - + https://github.com/dotnet/linker - f09bacf09ef10b61cf9f19825f8782171a816dab + 81ffbb5af38a45ff60648999df8f35a79061ae43 https://github.com/dotnet/xharness diff --git a/eng/Versions.props b/eng/Versions.props index 06c3924253c555..36423dc87d38f0 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -171,7 +171,7 @@ 7.0.0-preview-20220721.1 - 7.0.100-1.22377.1 + 7.0.100-1.22411.2 $(MicrosoftNETILLinkTasksVersion) 7.0.0-rc.1.22408.1 diff --git a/eng/illink.targets b/eng/illink.targets index 44fbe03e50f75b..91ff0da2c3abc6 100644 --- a/eng/illink.targets +++ b/eng/illink.targets @@ -256,6 +256,8 @@ $(LinkerNoWarn);IL2062;IL2063;IL2064;IL2065;IL2066 $(LinkerNoWarn);IL2067;IL2068;IL2069;IL2070;IL2071;IL2072;IL2073;IL2074;IL2075;IL2076;IL2077;IL2078;IL2079;IL2080;IL2081;IL2082;IL2083;IL2084;IL2085;IL2086;IL2087;IL2088;IL2089;IL2090;IL2091 + + $(LinkerNoWarn);IL2121 $(ILLinkArgs) --nowarn $(LinkerNoWarn) $(ILLinkArgs) --disable-opt ipconstprop diff --git a/eng/testing/linker/SupportFiles/Directory.Build.props b/eng/testing/linker/SupportFiles/Directory.Build.props index c3be2bf7d265ab..0a48403f391258 100644 --- a/eng/testing/linker/SupportFiles/Directory.Build.props +++ b/eng/testing/linker/SupportFiles/Directory.Build.props @@ -13,5 +13,6 @@ false true + $(NoWarn);IL2121 diff --git a/src/libraries/Directory.Build.props b/src/libraries/Directory.Build.props index 392e6ba80664c4..2e3613356868ee 100644 --- a/src/libraries/Directory.Build.props +++ b/src/libraries/Directory.Build.props @@ -46,6 +46,8 @@ $(NoWarn);SYSLIB0011 + + $(NoWarn);IL2121 annotations diff --git a/src/libraries/System.Data.Common/src/System/Data/DataRowExtensions.cs b/src/libraries/System.Data.Common/src/System/Data/DataRowExtensions.cs index a10d119256e28b..8e6ba12be825d1 100644 --- a/src/libraries/System.Data.Common/src/System/Data/DataRowExtensions.cs +++ b/src/libraries/System.Data.Common/src/System/Data/DataRowExtensions.cs @@ -144,6 +144,8 @@ private static class UnboxT { internal static readonly Func s_unbox = Create(); + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2090:MakeGenericMethod", + Justification = "'NullableField where TElem : struct' implies 'TElem : new()'. Nullable does not make use of new() so it is safe.")] private static Func Create() { if (typeof(T).IsValueType && default(T) == null) diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpTypeConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpTypeConverterFactory.cs index f42adbc31fe409..a95c813bf68169 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpTypeConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/FSharp/FSharpTypeConverterFactory.cs @@ -24,6 +24,8 @@ public override bool CanConvert(Type typeToConvert) => [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "The ctor is marked RequiresUnreferencedCode.")] + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2055:MakeGenericType", + Justification = "The ctor is marked RequiresUnreferencedCode.")] public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) { Debug.Assert(CanConvert(typeToConvert)); diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverterFactory.cs index 2fe950eac54b6e..b23623fcfb86a9 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/EnumConverterFactory.cs @@ -27,7 +27,7 @@ internal static JsonConverter Create(Type enumType, EnumConverterOptions convert new object?[] { converterOptions, namingPolicy, options })!; } - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2055:MakeGenericType", + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", Justification = "'EnumConverter where T : struct' implies 'T : new()', so the trimmer is warning calling MakeGenericType here because enumType's constructors are not annotated. " + "But EnumConverter doesn't call new T(), so this is safe.")] [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs index cbc89a4fb7bba6..a674f805731a0e 100644 --- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs +++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Value/NullableConverterFactory.cs @@ -44,7 +44,7 @@ public static JsonConverter CreateValueConverter(Type valueTypeToConvert, JsonCo culture: null)!; } - [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2055:MakeGenericType", + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern", Justification = "'NullableConverter where T : struct' implies 'T : new()', so the trimmer is warning calling MakeGenericType here because valueTypeToConvert's constructors are not annotated. " + "But NullableConverter doesn't call new T(), so this is safe.")] [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] diff --git a/src/libraries/oob.proj b/src/libraries/oob.proj index cc910f2d6c8c41..6dbbd63794b270 100644 --- a/src/libraries/oob.proj +++ b/src/libraries/oob.proj @@ -61,6 +61,8 @@ $(ILLinkArgs) + + $(ILLinkArgs) --nowarn IL2121 $(OOBILLinkArgs) --link-attributes "@(OOBLibrarySuppressionsXml->'%(FullPath)', '" --link-attributes "')" diff --git a/src/libraries/sfx.proj b/src/libraries/sfx.proj index aa345f53952e2f..bfe5a1c4902232 100644 --- a/src/libraries/sfx.proj +++ b/src/libraries/sfx.proj @@ -48,6 +48,8 @@ $(ILLinkArgs) + + $(ILLinkArgs) --nowarn IL2121 $(SharedFrameworkILLinkArgs) -b true $(SharedFrameworkILLinkArgs) --link-attributes "@(SharedFrameworkSuppressionsXml->'%(FullPath)', '" --link-attributes "')" diff --git a/src/mono/sample/wasm/Directory.Build.props b/src/mono/sample/wasm/Directory.Build.props index 9e62d874f5ee94..6db0cf52ee3ca1 100644 --- a/src/mono/sample/wasm/Directory.Build.props +++ b/src/mono/sample/wasm/Directory.Build.props @@ -6,6 +6,8 @@ browser wasm browser-wasm + + $(NoWarn);IL2121 From 30bec968fa52d414210a3d5a19469b7637981ba5 Mon Sep 17 00:00:00 2001 From: Katya Sokolova Date: Fri, 12 Aug 2022 19:28:01 +0200 Subject: [PATCH 68/68] No end stream on ws connect and flush every message (#73762) * No end stream on ws connect and flush every message * Apply suggestions from code review Co-authored-by: Natalia Kondratyeva * Await flush in web socket after write * Flush for web socket tests should be supported * feedback * Refactoring write and flush tasks in WebSocket * feedback * Replace check for more generic extended connect * Apply suggestions from code review Co-authored-by: Stephen Toub * feedback Co-authored-by: Natalia Kondratyeva Co-authored-by: Stephen Toub --- .../src/System/Net/Http/HttpRequestMessage.cs | 2 +- .../SocketsHttpHandler/Http2Connection.cs | 6 +++--- .../Http/SocketsHttpHandler/Http2Stream.cs | 4 ++-- .../SocketsHttpHandler/HttpConnectionPool.cs | 6 +++--- .../System/Net/WebSockets/ManagedWebSocket.cs | 21 ++++++++++++++++--- .../tests/WebSocketTestStream.cs | 2 +- 6 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs b/src/libraries/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs index f258c5ca8ae800..40884f72085e9e 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/HttpRequestMessage.cs @@ -168,7 +168,7 @@ public override string ToString() internal bool WasRedirected() => (_sendStatus & MessageIsRedirect) != 0; - internal bool IsWebSocketH2Request() => _version.Major == 2 && Method == HttpMethod.Connect && HasHeaders && string.Equals(Headers.Protocol, "websocket", StringComparison.OrdinalIgnoreCase); + internal bool IsExtendedConnectRequest => Method == HttpMethod.Connect && _headers?.Protocol != null; #region IDisposable Members diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs index 25c27d7d0fdc52..3b2019f5af657e 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Connection.cs @@ -1600,7 +1600,7 @@ private async ValueTask SendHeadersAsync(HttpRequestMessage request // Start the write. This serializes access to write to the connection, and ensures that HEADERS // and CONTINUATION frames stay together, as they must do. We use the lock as well to ensure new // streams are created and started in order. - await PerformWriteAsync(totalSize, (thisRef: this, http2Stream, headerBytes, endStream: (request.Content == null && !http2Stream.ConnectProtocolEstablished), mustFlush), static (s, writeBuffer) => + await PerformWriteAsync(totalSize, (thisRef: this, http2Stream, headerBytes, endStream: (request.Content == null && !request.IsExtendedConnectRequest), mustFlush), static (s, writeBuffer) => { if (NetEventSource.Log.IsEnabled()) s.thisRef.Trace(s.http2Stream.StreamId, $"Started writing. Total header bytes={s.headerBytes.Length}"); @@ -1962,8 +1962,8 @@ public async Task SendAsync(HttpRequestMessage request, boo try { // Send request headers - bool shouldExpectContinue = request.Content != null && request.HasHeaders && request.Headers.ExpectContinue == true; - Http2Stream http2Stream = await SendHeadersAsync(request, cancellationToken, mustFlush: shouldExpectContinue).ConfigureAwait(false); + bool shouldExpectContinue = (request.Content != null && request.HasHeaders && request.Headers.ExpectContinue == true); + Http2Stream http2Stream = await SendHeadersAsync(request, cancellationToken, mustFlush: shouldExpectContinue || request.IsExtendedConnectRequest).ConfigureAwait(false); bool duplex = request.Content != null && request.Content.AllowDuplex; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs index 1ecb968fa322a4..927a8e74b18a13 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http2Stream.cs @@ -108,7 +108,7 @@ public Http2Stream(HttpRequestMessage request, Http2Connection connection) if (_request.Content == null) { _requestCompletionState = StreamCompletionState.Completed; - if (_request.IsWebSocketH2Request()) + if (_request.IsExtendedConnectRequest) { _requestBodyCancellationSource = new CancellationTokenSource(); } @@ -637,7 +637,7 @@ private void OnStatus(int statusCode) } else { - if (statusCode == 200 && _response.RequestMessage!.IsWebSocketH2Request()) + if (statusCode == 200 && _response.RequestMessage!.IsExtendedConnectRequest) { ConnectProtocolEstablished = true; } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs index 4a2362329b9f79..354b50b59668d0 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs @@ -1021,7 +1021,7 @@ public async ValueTask SendWithVersionDetectionAndRetryAsyn // Use HTTP/3 if possible. if (IsHttp3Supported() && // guard to enable trimming HTTP/3 support _http3Enabled && - !request.IsWebSocketH2Request() && + !request.IsExtendedConnectRequest && (request.Version.Major >= 3 || (request.VersionPolicy == HttpVersionPolicy.RequestVersionOrHigher && IsSecure))) { Debug.Assert(async); @@ -1050,7 +1050,7 @@ public async ValueTask SendWithVersionDetectionAndRetryAsyn Debug.Assert(connection is not null || !_http2Enabled); if (connection is not null) { - if (request.IsWebSocketH2Request()) + if (request.IsExtendedConnectRequest) { await connection.InitialSettingsReceived.WaitWithCancellationAsync(cancellationToken).ConfigureAwait(false); if (!connection.IsConnectEnabled) @@ -1123,7 +1123,7 @@ public async ValueTask SendWithVersionDetectionAndRetryAsyn if (request.VersionPolicy != HttpVersionPolicy.RequestVersionOrLower) { HttpRequestException exception = new HttpRequestException(SR.Format(SR.net_http_requested_version_server_refused, request.Version, request.VersionPolicy), e); - if (request.IsWebSocketH2Request()) + if (request.IsExtendedConnectRequest) { exception.Data["HTTP2_ENABLED"] = false; } diff --git a/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs b/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs index 4130cff1cc65df..4c0c4e8d693b3f 100644 --- a/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs +++ b/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs @@ -423,7 +423,17 @@ private ValueTask SendFrameLockAcquiredNonCancelableAsync(MessageOpcode opcode, // the task, and we're done. if (writeTask.IsCompleted) { - return writeTask; + writeTask.GetAwaiter().GetResult(); + ValueTask flushTask = new ValueTask(_stream.FlushAsync()); + if (flushTask.IsCompleted) + { + return flushTask; + } + else + { + releaseSendBufferAndSemaphore = false; + return WaitForWriteTaskAsync(flushTask, shouldFlush: false); + } } // Up until this point, if an exception occurred (such as when accessing _stream or when @@ -447,14 +457,18 @@ private ValueTask SendFrameLockAcquiredNonCancelableAsync(MessageOpcode opcode, } } - return WaitForWriteTaskAsync(writeTask); + return WaitForWriteTaskAsync(writeTask, shouldFlush: true); } - private async ValueTask WaitForWriteTaskAsync(ValueTask writeTask) + private async ValueTask WaitForWriteTaskAsync(ValueTask writeTask, bool shouldFlush) { try { await writeTask.ConfigureAwait(false); + if (shouldFlush) + { + await _stream.FlushAsync().ConfigureAwait(false); + } } catch (Exception exc) when (!(exc is OperationCanceledException)) { @@ -478,6 +492,7 @@ private async ValueTask SendFrameFallbackAsync(MessageOpcode opcode, bool endOfM using (cancellationToken.Register(static s => ((ManagedWebSocket)s!).Abort(), this)) { await _stream.WriteAsync(new ReadOnlyMemory(_sendBuffer, 0, sendBytes), cancellationToken).ConfigureAwait(false); + await _stream.FlushAsync(cancellationToken).ConfigureAwait(false); } } catch (Exception exc) when (!(exc is OperationCanceledException)) diff --git a/src/libraries/System.Net.WebSockets/tests/WebSocketTestStream.cs b/src/libraries/System.Net.WebSockets/tests/WebSocketTestStream.cs index b7dfb3ea7f26da..1e416f4dc49ccb 100644 --- a/src/libraries/System.Net.WebSockets/tests/WebSocketTestStream.cs +++ b/src/libraries/System.Net.WebSockets/tests/WebSocketTestStream.cs @@ -206,7 +206,7 @@ public override async ValueTask WriteAsync(ReadOnlyMemory buffer, Cancella Write(buffer.Span); } - public override void Flush() => throw new NotSupportedException(); + public override void Flush() { } public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();