diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index fc83086fad4719..e21dd9e89bf8a1 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -15,7 +15,7 @@
]
},
"microsoft.dotnet.xharness.cli": {
- "version": "9.0.0-prerelease.25228.2",
+ "version": "9.0.0-prerelease.25269.3",
"commands": [
"xharness"
]
diff --git a/NuGet.config b/NuGet.config
index 5950278d8673db..e4806514865c0f 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -13,7 +13,7 @@
-
+
-
+
https://github.com/dotnet/cecil
- 6aaf3af113593a4c993854bff4141bbc73061ea5
+ bbb895e8e9f2d566eae04f09977b8c5f895057d2
@@ -92,139 +92,139 @@
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
-
+
https://github.com/dotnet/runtime-assets
- c9371153c0f06168c3344b806331a29389d1171e
+ 7f6eab719b1c6834f694cfddb73c3891942e31e4
-
+
https://github.com/dotnet/runtime-assets
- c9371153c0f06168c3344b806331a29389d1171e
+ 7f6eab719b1c6834f694cfddb73c3891942e31e4
-
+
https://github.com/dotnet/runtime-assets
- c9371153c0f06168c3344b806331a29389d1171e
+ 7f6eab719b1c6834f694cfddb73c3891942e31e4
-
+
https://github.com/dotnet/runtime-assets
- c9371153c0f06168c3344b806331a29389d1171e
+ 7f6eab719b1c6834f694cfddb73c3891942e31e4
-
+
https://github.com/dotnet/runtime-assets
- c9371153c0f06168c3344b806331a29389d1171e
+ 7f6eab719b1c6834f694cfddb73c3891942e31e4
-
+
https://github.com/dotnet/runtime-assets
- c9371153c0f06168c3344b806331a29389d1171e
+ 7f6eab719b1c6834f694cfddb73c3891942e31e4
-
+
https://github.com/dotnet/runtime-assets
- c9371153c0f06168c3344b806331a29389d1171e
+ 7f6eab719b1c6834f694cfddb73c3891942e31e4
-
+
https://github.com/dotnet/runtime-assets
- c9371153c0f06168c3344b806331a29389d1171e
+ 7f6eab719b1c6834f694cfddb73c3891942e31e4
-
+
https://github.com/dotnet/runtime-assets
- c9371153c0f06168c3344b806331a29389d1171e
+ 7f6eab719b1c6834f694cfddb73c3891942e31e4
-
+
https://github.com/dotnet/runtime-assets
- c9371153c0f06168c3344b806331a29389d1171e
+ 7f6eab719b1c6834f694cfddb73c3891942e31e4
-
+
https://github.com/dotnet/runtime-assets
- c9371153c0f06168c3344b806331a29389d1171e
+ 7f6eab719b1c6834f694cfddb73c3891942e31e4
-
+
https://github.com/dotnet/runtime-assets
- c9371153c0f06168c3344b806331a29389d1171e
+ 7f6eab719b1c6834f694cfddb73c3891942e31e4
-
+
https://github.com/dotnet/runtime-assets
- c9371153c0f06168c3344b806331a29389d1171e
+ 7f6eab719b1c6834f694cfddb73c3891942e31e4
https://github.com/dotnet/llvm-project
@@ -320,21 +320,21 @@
https://github.com/dotnet/runtime
b030c4dfdfa1bf287f10f96006619a06bc2000ae
-
+
https://github.com/dotnet/xharness
- 5e21f691093804fcd40096109c51a96334ac38cc
+ 4d797652fa667e94a39db06cbb5a3cad4f72a51f
-
+
https://github.com/dotnet/xharness
- 5e21f691093804fcd40096109c51a96334ac38cc
+ 4d797652fa667e94a39db06cbb5a3cad4f72a51f
-
+
https://github.com/dotnet/xharness
- 5e21f691093804fcd40096109c51a96334ac38cc
+ 4d797652fa667e94a39db06cbb5a3cad4f72a51f
-
+
https://github.com/dotnet/arcade
- 1cfa39f82d00b3659a3d367bc344241946e10681
+ 0d52a8b262d35fa2fde84e398cb2e791b8454bd2
https://dev.azure.com/dnceng/internal/_git/dotnet-optimization
@@ -356,19 +356,19 @@
https://github.com/dotnet/hotreload-utils
46df3d5e763fdd0e57eeafcb898a86bb955483cb
-
+
https://github.com/dotnet/runtime-assets
- c9371153c0f06168c3344b806331a29389d1171e
+ 7f6eab719b1c6834f694cfddb73c3891942e31e4
-
+
https://github.com/dotnet/roslyn
3f5cf9fbbd91f2047e988801a5142ca1cb6bab45
-
+
https://github.com/dotnet/roslyn
3f5cf9fbbd91f2047e988801a5142ca1cb6bab45
-
+
https://github.com/dotnet/roslyn
3f5cf9fbbd91f2047e988801a5142ca1cb6bab45
@@ -381,19 +381,19 @@
16865ea61910500f1022ad2b96c499e5df02c228
-
+
https://github.com/dotnet/roslyn
3f5cf9fbbd91f2047e988801a5142ca1cb6bab45
https://github.com/dotnet/sdk
- b562a3008a23dc5e7dca299c31056684b16b617e
+ 38e51fe4e1fa36644ea66191001e82078989f051
-
+
https://github.com/dotnet/sdk
- b562a3008a23dc5e7dca299c31056684b16b617e
+ 38e51fe4e1fa36644ea66191001e82078989f051
diff --git a/eng/Versions.props b/eng/Versions.props
index bdad4e07fca67b..1658031bdfbee1 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -44,9 +44,9 @@
Any tools that contribute to the design-time experience should use the MicrosoftCodeAnalysisVersion_LatestVS property above to ensure
they do not break the local dev experience.
-->
- 4.12.0-3.25256.6
- 4.12.0-3.25256.6
- 4.12.0-3.25256.6
+ 4.12.0-3.25275.3
+ 4.12.0-3.25275.3
+ 4.12.0-3.25275.3
9.0.107
- 9.0.0-beta.25255.5
- 9.0.0-beta.25255.5
- 9.0.0-beta.25255.5
- 9.0.0-beta.25255.5
- 2.9.0-beta.25255.5
- 9.0.0-beta.25255.5
- 2.9.0-beta.25255.5
- 9.0.0-beta.25255.5
- 9.0.0-beta.25255.5
- 9.0.0-beta.25255.5
- 9.0.0-beta.25255.5
- 9.0.0-beta.25255.5
- 9.0.0-beta.25255.5
- 9.0.0-beta.25255.5
- 9.0.0-beta.25255.5
- 9.0.0-beta.25255.5
+ 9.0.0-beta.25302.2
+ 9.0.0-beta.25302.2
+ 9.0.0-beta.25302.2
+ 9.0.0-beta.25302.2
+ 2.9.0-beta.25302.2
+ 9.0.0-beta.25302.2
+ 2.9.0-beta.25302.2
+ 9.0.0-beta.25302.2
+ 9.0.0-beta.25302.2
+ 9.0.0-beta.25302.2
+ 9.0.0-beta.25302.2
+ 9.0.0-beta.25302.2
+ 9.0.0-beta.25302.2
+ 9.0.0-beta.25302.2
+ 9.0.0-beta.25302.2
+ 9.0.0-beta.25302.2
1.4.0
@@ -141,20 +141,20 @@
8.0.0
8.0.0
- 9.0.0-beta.25211.3
- 9.0.0-beta.25211.3
- 9.0.0-beta.25211.3
- 9.0.0-beta.25211.3
- 9.0.0-beta.25211.3
- 9.0.0-beta.25211.3
- 9.0.0-beta.25211.3
- 9.0.0-beta.25211.3
- 9.0.0-beta.25211.3
- 9.0.0-beta.25211.3
- 9.0.0-beta.25211.3
- 9.0.0-beta.25211.3
- 9.0.0-beta.25211.3
- 9.0.0-beta.25211.3
+ 9.0.0-beta.25266.2
+ 9.0.0-beta.25266.2
+ 9.0.0-beta.25266.2
+ 9.0.0-beta.25266.2
+ 9.0.0-beta.25266.2
+ 9.0.0-beta.25266.2
+ 9.0.0-beta.25266.2
+ 9.0.0-beta.25266.2
+ 9.0.0-beta.25266.2
+ 9.0.0-beta.25266.2
+ 9.0.0-beta.25266.2
+ 9.0.0-beta.25266.2
+ 9.0.0-beta.25266.2
+ 9.0.0-beta.25266.2
1.0.0-prerelease.24462.2
1.0.0-prerelease.24462.2
@@ -184,9 +184,9 @@
1.4.0
17.4.0-preview-20220707-01
- 9.0.0-prerelease.25228.2
- 9.0.0-prerelease.25228.2
- 9.0.0-prerelease.25228.2
+ 9.0.0-prerelease.25269.3
+ 9.0.0-prerelease.25269.3
+ 9.0.0-prerelease.25269.3
9.0.0-alpha.0.25209.2
3.12.0
4.5.0
@@ -215,11 +215,11 @@
9.0.0-preview-20241010.1
- 0.11.5-alpha.25228.2
+ 0.11.5-alpha.25275.2
9.0.0-rtm.24511.16
- 9.0.0-rtm.25258.1
+ 9.0.0-rtm.25304.1
9.0.0-rtm.24466.4
2.4.8
@@ -263,7 +263,7 @@
1.0.406601
- 9.0.105
+ 9.0.106
9.0.0-alpha.1.24175.1
$(MicrosoftNETRuntimeEmscriptenVersion)
$(runtimewinx64MicrosoftNETCoreRuntimeWasmNodeTransportPackageVersion)
diff --git a/eng/common/core-templates/post-build/post-build.yml b/eng/common/core-templates/post-build/post-build.yml
index 454fd75c7aff19..a8c0bd3b9214e5 100644
--- a/eng/common/core-templates/post-build/post-build.yml
+++ b/eng/common/core-templates/post-build/post-build.yml
@@ -44,6 +44,11 @@ parameters:
displayName: Publish installers and checksums
type: boolean
default: true
+
+ - name: requireDefaultChannels
+ displayName: Fail the build if there are no default channel(s) registrations for the current build
+ type: boolean
+ default: false
- name: SDLValidationParameters
type: object
@@ -312,5 +317,6 @@ stages:
-PublishingInfraVersion ${{ parameters.publishingInfraVersion }}
-AzdoToken '$(System.AccessToken)'
-WaitPublishingFinish true
+ -RequireDefaultChannels ${{ parameters.requireDefaultChannels }}
-ArtifactsPublishingAdditionalParameters '${{ parameters.artifactsPublishingAdditionalParameters }}'
-SymbolPublishingAdditionalParameters '${{ parameters.symbolPublishingAdditionalParameters }}'
diff --git a/eng/common/post-build/publish-using-darc.ps1 b/eng/common/post-build/publish-using-darc.ps1
index 90b58e32a87bfb..a261517ef90679 100644
--- a/eng/common/post-build/publish-using-darc.ps1
+++ b/eng/common/post-build/publish-using-darc.ps1
@@ -5,7 +5,8 @@ param(
[Parameter(Mandatory=$false)][string] $MaestroApiEndPoint = 'https://maestro.dot.net',
[Parameter(Mandatory=$true)][string] $WaitPublishingFinish,
[Parameter(Mandatory=$false)][string] $ArtifactsPublishingAdditionalParameters,
- [Parameter(Mandatory=$false)][string] $SymbolPublishingAdditionalParameters
+ [Parameter(Mandatory=$false)][string] $SymbolPublishingAdditionalParameters,
+ [Parameter(Mandatory=$false)][string] $RequireDefaultChannels
)
try {
@@ -33,6 +34,10 @@ try {
if ("false" -eq $WaitPublishingFinish) {
$optionalParams.Add("--no-wait") | Out-Null
}
+
+ if ("true" -eq $RequireDefaultChannels) {
+ $optionalParams.Add("--default-channels-required") | Out-Null
+ }
& $darc add-build-to-channel `
--id $buildId `
diff --git a/global.json b/global.json
index c2eefc4a16603b..e7a948a8c83a06 100644
--- a/global.json
+++ b/global.json
@@ -1,16 +1,16 @@
{
"sdk": {
- "version": "9.0.105",
+ "version": "9.0.106",
"allowPrerelease": true,
"rollForward": "major"
},
"tools": {
- "dotnet": "9.0.105"
+ "dotnet": "9.0.106"
},
"msbuild-sdks": {
- "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25255.5",
- "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.25255.5",
- "Microsoft.DotNet.SharedFramework.Sdk": "9.0.0-beta.25255.5",
+ "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25302.2",
+ "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.25302.2",
+ "Microsoft.DotNet.SharedFramework.Sdk": "9.0.0-beta.25302.2",
"Microsoft.Build.NoTargets": "3.7.0",
"Microsoft.Build.Traversal": "3.4.0",
"Microsoft.NET.Sdk.IL": "9.0.0-rtm.24511.16"
diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp
index 95cf5c545418fa..c80e14e88f57e6 100644
--- a/src/coreclr/gc/gc.cpp
+++ b/src/coreclr/gc/gc.cpp
@@ -10717,6 +10717,14 @@ size_t gc_heap::sort_mark_list()
size_t region_index = get_basic_region_index_for_address (heap_segment_mem (region));
uint8_t* region_limit = heap_segment_allocated (region);
+ // Due to GC holes, x can point to something in a region that already got freed. And that region's
+ // allocated would be 0 and cause an infinite loop which is much harder to handle on production than
+ // simply throwing an exception.
+ if (region_limit == 0)
+ {
+ FATAL_GC_ERROR();
+ }
+
uint8_t*** mark_list_piece_start_ptr = &mark_list_piece_start[region_index];
uint8_t*** mark_list_piece_end_ptr = &mark_list_piece_end[region_index];
#else // USE_REGIONS
diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp
index 4102d4c7711150..32506047d2269d 100644
--- a/src/coreclr/jit/codegenxarch.cpp
+++ b/src/coreclr/jit/codegenxarch.cpp
@@ -6268,6 +6268,18 @@ void CodeGen::genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackA
{
retSize = emitTypeSize(retTypeDesc->GetReturnRegType(0));
secondRetSize = emitTypeSize(retTypeDesc->GetReturnRegType(1));
+
+ if (retTypeDesc->GetABIReturnReg(1, call->GetUnmanagedCallConv()) == REG_INTRET)
+ {
+ // If the second return register is REG_INTRET, then the first
+ // return is expected to be in a SIMD register.
+ // The emitter has hardcoded belief that retSize corresponds to
+ // REG_INTRET and secondRetSize to REG_INTRET_1, so fix up the
+ // situation here.
+ assert(!EA_IS_GCREF_OR_BYREF(retSize));
+ retSize = secondRetSize;
+ secondRetSize = EA_UNKNOWN;
+ }
}
else
{
diff --git a/src/coreclr/jit/gcencode.cpp b/src/coreclr/jit/gcencode.cpp
index ae05bf797f86ab..a1e9f935327d37 100644
--- a/src/coreclr/jit/gcencode.cpp
+++ b/src/coreclr/jit/gcencode.cpp
@@ -49,8 +49,21 @@ ReturnKind GCInfo::getReturnKind()
case 1:
return VarTypeToReturnKind(retTypeDesc.GetReturnRegType(0));
case 2:
- return GetStructReturnKind(VarTypeToReturnKind(retTypeDesc.GetReturnRegType(0)),
- VarTypeToReturnKind(retTypeDesc.GetReturnRegType(1)));
+ {
+ var_types first = retTypeDesc.GetReturnRegType(0);
+ var_types second = retTypeDesc.GetReturnRegType(1);
+#ifdef UNIX_AMD64_ABI
+ if (varTypeUsesFloatReg(first))
+ {
+ // first does not consume an int register in this case so an obj/ref
+ // in the second ReturnKind would actually be found in the first int reg.
+ first = second;
+ second = TYP_UNDEF;
+ }
+#endif // UNIX_AMD64_ABI
+ return GetStructReturnKind(VarTypeToReturnKind(first),
+ VarTypeToReturnKind(second));
+ }
default:
#ifdef DEBUG
for (unsigned i = 0; i < regCount; i++)
diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp
index 20645cbc3660f8..76660c67f26f42 100644
--- a/src/coreclr/jit/gentree.cpp
+++ b/src/coreclr/jit/gentree.cpp
@@ -30167,7 +30167,7 @@ unsigned* SsaNumInfo::GetOutlinedNumSlot(Compiler* compiler, unsigned index) con
// Copy over all of the already encoded numbers.
if (!baseNum.IsInvalid())
{
- for (int i = 0; i < SIMPLE_NUM_COUNT; i++)
+ for (int i = 0; i < count; i++)
{
pFirstSlot[i] = baseNum.GetNum(compiler, i);
}
diff --git a/src/coreclr/jit/morphblock.cpp b/src/coreclr/jit/morphblock.cpp
index f21cc2355a4861..ed1479804ceb6b 100644
--- a/src/coreclr/jit/morphblock.cpp
+++ b/src/coreclr/jit/morphblock.cpp
@@ -250,8 +250,6 @@ void MorphInitBlockHelper::PropagateBlockAssertions()
//
void MorphInitBlockHelper::PropagateExpansionAssertions()
{
- // Consider doing this for FieldByField as well
- //
if (m_comp->optLocalAssertionProp && (m_transformationDecision == BlockTransformation::OneStoreBlock))
{
m_comp->fgAssertionGen(m_store);
@@ -408,6 +406,7 @@ void MorphInitBlockHelper::TryInitFieldByField()
if (m_comp->fgGlobalMorph && m_dstLclNode->IsLastUse(i))
{
JITDUMP("Field-by-field init skipping write to dead field V%02u\n", fieldLclNum);
+ m_comp->fgKillDependentAssertionsSingle(m_dstLclNum DEBUGARG(m_store));
continue;
}
@@ -1236,6 +1235,7 @@ GenTree* MorphCopyBlockHelper::CopyFieldByField()
{
INDEBUG(unsigned dstFieldLclNum = m_comp->lvaGetDesc(m_dstLclNum)->lvFieldLclStart + i);
JITDUMP("Field-by-field copy skipping write to dead field V%02u\n", dstFieldLclNum);
+ m_comp->fgKillDependentAssertionsSingle(m_dstLclNum DEBUGARG(m_store));
continue;
}
diff --git a/src/coreclr/vm/method.cpp b/src/coreclr/vm/method.cpp
index 8b7239896a0ea6..50607f57d9e911 100644
--- a/src/coreclr/vm/method.cpp
+++ b/src/coreclr/vm/method.cpp
@@ -1324,6 +1324,15 @@ ReturnKind MethodDesc::ParseReturnKindFromSig(INDEBUG(bool supportStringConstruc
regKinds[i] = RT_Scalar;
}
}
+
+ if (eeClass->GetEightByteClassification(0) == SystemVClassificationTypeSSE)
+ {
+ // Skip over SSE types since they do not consume integer registers.
+ // An obj/byref in the 2nd eight bytes will be in the first integer register.
+ regKinds[0] = regKinds[1];
+ regKinds[1] = RT_Scalar;
+ }
+
ReturnKind structReturnKind = GetStructReturnKind(regKinds[0], regKinds[1]);
return structReturnKind;
}
@@ -3687,10 +3696,14 @@ MethodDesc::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
ILCodeVersion ilVersion = pCodeVersionManager->GetActiveILCodeVersion(dac_cast(this));
if (!ilVersion.IsNull())
{
- ilVersion.GetActiveNativeCodeVersion(dac_cast(this));
- ilVersion.GetVersionId();
- ilVersion.GetRejitState();
- ilVersion.GetIL();
+ EX_TRY
+ {
+ ilVersion.GetActiveNativeCodeVersion(dac_cast(this));
+ ilVersion.GetVersionId();
+ ilVersion.GetRejitState();
+ ilVersion.GetIL();
+ }
+ EX_CATCH_RETHROW_ONLY_COR_E_OPERATIONCANCELLED
}
#endif
diff --git a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs
index d440d3eae2caa2..5afab32a0881d2 100644
--- a/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs
+++ b/src/installer/tests/HostActivation.Tests/SymbolicLinks.cs
@@ -2,8 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Runtime.InteropServices;
using FluentAssertions;
using Microsoft.DotNet.Cli.Build.Framework;
@@ -21,6 +23,165 @@ public SymbolicLinks(SymbolicLinks.SharedTestState fixture)
sharedTestState = fixture;
}
+ [Theory]
+ [InlineData("a/b/SymlinkToFrameworkDependentApp")]
+ [InlineData("a/SymlinkToFrameworkDependentApp")]
+ public void Symlink_all_files_fx(string symlinkRelativePath)
+ {
+ using var testDir = TestArtifact.Create("symlink");
+ Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath)));
+
+ // Symlink every file in the app directory
+ var symlinks = new List();
+ try
+ {
+ foreach (var file in Directory.EnumerateFiles(sharedTestState.FrameworkDependentApp.Location))
+ {
+ var fileName = Path.GetFileName(file);
+ var symlinkPath = Path.Combine(testDir.Location, symlinkRelativePath, fileName);
+ Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath));
+ symlinks.Add(new SymLink(symlinkPath, file));
+ }
+
+ var result = Command.Create(Path.Combine(testDir.Location, symlinkRelativePath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe)))
+ .CaptureStdErr()
+ .CaptureStdOut()
+ .DotNetRoot(TestContext.BuiltDotNet.BinPath)
+ .Execute();
+
+ // This should succeed on all platforms, but for different reasons:
+ // * Windows: The apphost will look next to the symlink for the app dll and find the symlinked dll
+ // * Unix: The apphost will look next to the resolved apphost for the app dll and find the real thing
+ result
+ .Should().Pass()
+ .And.HaveStdOutContaining("Hello World");
+ }
+ finally
+ {
+ foreach (var symlink in symlinks)
+ {
+ symlink.Dispose();
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData("a/b/SymlinkToFrameworkDependentApp")]
+ [InlineData("a/SymlinkToFrameworkDependentApp")]
+ public void Symlink_split_files_fx(string symlinkRelativePath)
+ {
+ using var testDir = TestArtifact.Create("symlink");
+
+ // Split the app into two directories, one for the apphost and one for the rest of the files
+ var appHostDir = Path.Combine(testDir.Location, "apphost");
+ var appFilesDir = Path.Combine(testDir.Location, "appfiles");
+ Directory.CreateDirectory(appHostDir);
+ Directory.CreateDirectory(appFilesDir);
+
+ var appHostName = Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe);
+
+ File.Copy(
+ sharedTestState.FrameworkDependentApp.AppExe,
+ Path.Combine(appHostDir, appHostName));
+
+ foreach (var file in Directory.EnumerateFiles(sharedTestState.FrameworkDependentApp.Location))
+ {
+ var fileName = Path.GetFileName(file);
+ if (fileName != appHostName)
+ {
+ File.Copy(file, Path.Combine(appFilesDir, fileName));
+ }
+ }
+
+ // Symlink all of the above into a single directory
+ var targetPath = Path.Combine(testDir.Location, symlinkRelativePath);
+ Directory.CreateDirectory(targetPath);
+ var symlinks = new List();
+ try
+ {
+ foreach (var file in Directory.EnumerateFiles(appFilesDir))
+ {
+ var fileName = Path.GetFileName(file);
+ var symlinkPath = Path.Combine(targetPath, fileName);
+ Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath));
+ symlinks.Add(new SymLink(symlinkPath, file));
+ }
+ symlinks.Add(new SymLink(
+ Path.Combine(targetPath, appHostName),
+ Path.Combine(appHostDir, appHostName)));
+
+ var result = Command.Create(Path.Combine(targetPath, appHostName))
+ .CaptureStdErr()
+ .CaptureStdOut()
+ .DotNetRoot(TestContext.BuiltDotNet.BinPath)
+ .Execute();
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ // On Windows, the apphost will look next to the symlink for the app dll and find the symlinks
+ result
+ .Should().Pass()
+ .And.HaveStdOutContaining("Hello World");
+ }
+ else
+ {
+ // On Unix, the apphost will not find the app files next to the symlink
+ result
+ .Should().Fail()
+ .And.HaveStdErrContaining("The application to execute does not exist");
+ }
+ }
+ finally
+ {
+ foreach (var symlink in symlinks)
+ {
+ symlink.Dispose();
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData("a/b/SymlinkToFrameworkDependentApp")]
+ [InlineData("a/SymlinkToFrameworkDependentApp")]
+ public void Symlink_all_files_self_contained(string symlinkRelativePath)
+ {
+ using var testDir = TestArtifact.Create("symlink");
+ Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath)));
+
+ // Symlink every file in the app directory
+ var symlinks = new List();
+ try
+ {
+ foreach (var file in Directory.EnumerateFiles(sharedTestState.SelfContainedApp.Location))
+ {
+ var fileName = Path.GetFileName(file);
+ var symlinkPath = Path.Combine(testDir.Location, symlinkRelativePath, fileName);
+ Directory.CreateDirectory(Path.GetDirectoryName(symlinkPath));
+ symlinks.Add(new SymLink(symlinkPath, file));
+ }
+
+ var result = Command.Create(Path.Combine(testDir.Location, symlinkRelativePath, Path.GetFileName(sharedTestState.FrameworkDependentApp.AppExe)))
+ .CaptureStdErr()
+ .CaptureStdOut()
+ .DotNetRoot(TestContext.BuiltDotNet.BinPath)
+ .Execute();
+
+ // This should succeed on all platforms, but for different reasons:
+ // * Windows: The apphost will look next to the symlink for the files and find the symlinks
+ // * Unix: The apphost will look next to the resolved apphost for the files and find the real thing
+ result
+ .Should().Pass()
+ .And.HaveStdOutContaining("Hello World");
+ }
+ finally
+ {
+ foreach (var symlink in symlinks)
+ {
+ symlink.Dispose();
+ }
+ }
+ }
+
[Theory]
[InlineData ("a/b/SymlinkToApphost")]
[InlineData ("a/SymlinkToApphost")]
@@ -33,12 +194,23 @@ public void Run_apphost_behind_symlink(string symlinkRelativePath)
var symlinkFullPath = Path.Combine(testDir.Location, symlinkRelativePath);
using var symlink = new SymLink(symlinkFullPath, sharedTestState.SelfContainedApp.AppExe);
- Command.Create(symlinkFullPath)
+ var result = Command.Create(symlinkFullPath)
.CaptureStdErr()
.CaptureStdOut()
- .Execute()
- .Should().Pass()
- .And.HaveStdOutContaining("Hello World");
+ .Execute();
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ result
+ .Should().Fail()
+ .And.HaveStdErrContaining("The application to execute does not exist");
+ }
+ else
+ {
+ result
+ .Should().Pass()
+ .And.HaveStdOutContaining("Hello World");
+ }
}
}
@@ -63,12 +235,23 @@ public void Run_apphost_behind_transitive_symlinks(string firstSymlinkRelativePa
Directory.CreateDirectory(Path.GetDirectoryName(symlink1Path));
using var symlink1 = new SymLink(symlink1Path, symlink2Path);
- Command.Create(symlink1.SrcPath)
+ var result = Command.Create(symlink1.SrcPath)
.CaptureStdErr()
.CaptureStdOut()
- .Execute()
- .Should().Pass()
- .And.HaveStdOutContaining("Hello World");
+ .Execute();
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ result
+ .Should().Fail()
+ .And.HaveStdErrContaining("The application to execute does not exist");
+ }
+ else
+ {
+ result
+ .Should().Pass()
+ .And.HaveStdOutContaining("Hello World");
+ }
}
}
@@ -86,13 +269,24 @@ public void Run_framework_dependent_app_behind_symlink(string symlinkRelativePat
Directory.CreateDirectory(Path.Combine(testDir.Location, Path.GetDirectoryName(symlinkRelativePath)));
using var symlink = new SymLink(Path.Combine(testDir.Location, symlinkRelativePath), sharedTestState.FrameworkDependentApp.AppExe);
- Command.Create(symlink.SrcPath)
+ var result = Command.Create(symlink.SrcPath)
.CaptureStdErr()
.CaptureStdOut()
.DotNetRoot(TestContext.BuiltDotNet.BinPath)
- .Execute()
- .Should().Pass()
- .And.HaveStdOutContaining("Hello World");
+ .Execute();
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ result
+ .Should().Fail()
+ .And.HaveStdErrContaining("The application to execute does not exist");
+ }
+ else
+ {
+ result
+ .Should().Pass()
+ .And.HaveStdOutContaining("Hello World");
+ }
}
}
@@ -144,12 +338,23 @@ public void Put_dotnet_behind_symlink()
var dotnetSymlink = Path.Combine(testDir.Location, Binaries.DotNet.FileName);
using var symlink = new SymLink(dotnetSymlink, TestContext.BuiltDotNet.DotnetExecutablePath);
- Command.Create(symlink.SrcPath, sharedTestState.SelfContainedApp.AppDll)
+ var result = Command.Create(symlink.SrcPath, sharedTestState.SelfContainedApp.AppDll)
.CaptureStdErr()
.CaptureStdOut()
- .Execute()
- .Should().Pass()
- .And.HaveStdOutContaining("Hello World");
+ .Execute();
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ result
+ .Should().Fail()
+ .And.HaveStdErrContaining($"[{Path.Combine(testDir.Location, "host", "fxr")}] does not exist");
+ }
+ else
+ {
+ result
+ .Should().Pass()
+ .And.HaveStdOutContaining("Hello World");
+ }
}
}
diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs
index 9f3d05e43e96af..b9d755d988203f 100644
--- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs
+++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs
@@ -123,7 +123,14 @@ internal static unsafe ReadOnlySpan SslGetAlpnSelected(SafeSslHandle ssl)
internal static partial IntPtr SslGetCertificate(IntPtr ssl);
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslGetPeerCertChain")]
- internal static partial SafeSharedX509StackHandle SslGetPeerCertChain(SafeSslHandle ssl);
+ private static partial SafeSharedX509StackHandle SslGetPeerCertChain_private(SafeSslHandle ssl);
+
+ internal static SafeSharedX509StackHandle SslGetPeerCertChain(SafeSslHandle ssl)
+ {
+ return SafeInteriorHandle.OpenInteriorHandle(
+ SslGetPeerCertChain_private,
+ ssl);
+ }
[LibraryImport(Libraries.CryptoNative, EntryPoint = "CryptoNative_SslGetPeerFinished")]
internal static partial int SslGetPeerFinished(SafeSslHandle ssl, IntPtr buf, int count);
diff --git a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Unix.cs b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Unix.cs
index 4f6db79a54bf5d..7a0b6dc8e37055 100644
--- a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Unix.cs
+++ b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Unix.cs
@@ -139,10 +139,7 @@ internal override void DisposeCore(bool disposing)
if (disposing)
{
- if (State != PipeState.Closed)
- {
- _internalTokenSource.Cancel();
- }
+ _internalTokenSource.Cancel();
}
}
diff --git a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Windows.cs b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Windows.cs
index c0f066da805214..b986a7342538ba 100644
--- a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Windows.cs
+++ b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/NamedPipeServerStream.Windows.cs
@@ -62,7 +62,11 @@ internal override void TryToReuse(PipeValueTaskSource source)
{
if (Interlocked.CompareExchange(ref _reusableConnectionValueTaskSource, connectionSource, null) is not null)
{
- source._preallocatedOverlapped.Dispose();
+ source.Dispose();
+ }
+ else if (State == PipeState.Closed)
+ {
+ Interlocked.Exchange(ref _reusableConnectionValueTaskSource, null)?.Dispose();
}
}
}
diff --git a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Windows.cs b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Windows.cs
index 453fe1153bd4be..ddff733119a7a8 100644
--- a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Windows.cs
+++ b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.Windows.cs
@@ -255,9 +255,25 @@ internal virtual void TryToReuse(PipeValueTaskSource source)
if (source is ReadWriteValueTaskSource readWriteSource)
{
ref ReadWriteValueTaskSource? field = ref readWriteSource._isWrite ? ref _reusableWriteValueTaskSource : ref _reusableReadValueTaskSource;
+
+ // Try to return the instance. If there's already something in the field, then there had been concurrent
+ // operations and someone else already returned their instance, so just dispose of this one (the "pool" has
+ // a size of 1). If there's not something there and we're successful in storing our instance, the 99% case is
+ // this is normal operation, there wasn't a concurrent operation, and we just successfully returned the rented
+ // instance. However, it could also be because the stream has been disposed, in which case the disposal process
+ // would have nulled out the field, and since this instance wasn't present when disposal happened, it won't have
+ // been disposed. Further, disposal won't happen again, so just leaving the instance there will result in its
+ // resources being leaked. Instead, _after_ returning the instance we then check whether the stream is now
+ // disposed, and if it is, we then try to re-rent the instance and dispose of it. It's fine if the disposal happens
+ // after we've stored the instance back and before we check the stream's state, as then this code will be racing
+ // with disposal and only one of the two will succeed in exchanging out the instance.
if (Interlocked.CompareExchange(ref field, readWriteSource, null) is not null)
{
- source._preallocatedOverlapped.Dispose();
+ source.Dispose();
+ }
+ else if (State == PipeState.Closed)
+ {
+ Interlocked.Exchange(ref field, null)?.Dispose();
}
}
}
diff --git a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs
index a52b3cac99bc1b..d1fdf314344a50 100644
--- a/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs
+++ b/src/libraries/System.IO.Pipes/src/System/IO/Pipes/PipeStream.cs
@@ -144,6 +144,11 @@ public override Task FlushAsync(CancellationToken cancellationToken)
protected override void Dispose(bool disposing)
{
+ // Mark the pipe as closed before calling DisposeCore. That way, other threads that might
+ // be synchronizing on shared resources disposed of in DisposeCore will be guaranteed to
+ // see the closed state after that synchronization.
+ _state = PipeState.Closed;
+
try
{
// Nothing will be done differently based on whether we are
@@ -159,8 +164,6 @@ protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
-
- _state = PipeState.Closed;
}
// ********************** Public Properties *********************** //
diff --git a/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Windows.cs b/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Windows.cs
index 72fc7685dffe90..4585e92a8ac9dc 100644
--- a/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Windows.cs
+++ b/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionPal.Windows.cs
@@ -437,6 +437,11 @@ public void RegisterForCancellation(CancellationToken cancellationToken)
NetEventSource.Info(@this, $"GetAddrInfoExCancel returned error {cancelResult}");
}
}
+ catch (ObjectDisposedException)
+ {
+ // There is a race between checking @this._completed and @this.DangerousAddRef and disposing from another thread.
+ // We lost the race. No further action needed.
+ }
finally
{
if (needRelease)
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs
index db0a4a6c84bf19..bb6407ea4e0e43 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Protocol.cs
@@ -1057,8 +1057,9 @@ internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remot
return true;
}
- _remoteCertificate = certificate;
- if (_remoteCertificate == null)
+ // don't assign to _remoteCertificate yet, this prevents weird exceptions if SslStream is disposed in parallel with X509Chain building
+
+ if (certificate == null)
{
if (NetEventSource.Log.IsEnabled() && RemoteCertRequired) NetEventSource.Error(this, $"Remote certificate required, but no remote certificate received");
sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNotAvailable;
@@ -1100,15 +1101,17 @@ internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remot
sslPolicyErrors |= CertificateValidationPal.VerifyCertificateProperties(
_securityContext!,
chain,
- _remoteCertificate,
+ certificate,
_sslAuthenticationOptions.CheckCertName,
_sslAuthenticationOptions.IsServer,
TargetHostNameHelper.NormalizeHostName(_sslAuthenticationOptions.TargetHost));
}
+ _remoteCertificate = certificate;
+
if (remoteCertValidationCallback != null)
{
- success = remoteCertValidationCallback(this, _remoteCertificate, chain, sslPolicyErrors);
+ success = remoteCertValidationCallback(this, certificate, chain, sslPolicyErrors);
}
else
{
diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamDisposeTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamDisposeTest.cs
index de7aa502933b02..9864029c7a0b34 100644
--- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamDisposeTest.cs
+++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamDisposeTest.cs
@@ -4,6 +4,7 @@
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
+using System.Security.Authentication;
using System.Threading.Tasks;
using Xunit;
@@ -59,6 +60,7 @@ public async Task Dispose_PendingReadAsync_ThrowsODE(bool bufferedRead)
using CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TestConfiguration.PassingTestTimeout);
+
(SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(leaveInnerStreamOpen: true);
using (client)
using (server)
@@ -102,5 +104,65 @@ await TestConfiguration.WhenAllOrAnyFailedWithTimeout(
await Assert.ThrowsAnyAsync(() => client.ReadAsync(readBuffer, cts.Token).AsTask());
}
}
+
+ [Fact]
+ [OuterLoop("Computationally expensive")]
+ public async Task Dispose_ParallelWithHandshake_ThrowsODE()
+ {
+ using CancellationTokenSource cts = new CancellationTokenSource();
+ cts.CancelAfter(TestConfiguration.PassingTestTimeout);
+
+ await Parallel.ForEachAsync(System.Linq.Enumerable.Range(0, 10000), cts.Token, async (i, token) =>
+ {
+ (Stream clientStream, Stream serverStream) = TestHelper.GetConnectedStreams();
+
+ using SslStream client = new SslStream(clientStream);
+ using SslStream server = new SslStream(serverStream);
+ using X509Certificate2 serverCertificate = Configuration.Certificates.GetServerCertificate();
+ using X509Certificate2 clientCertificate = Configuration.Certificates.GetClientCertificate();
+
+ SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions()
+ {
+ TargetHost = Guid.NewGuid().ToString("N"),
+ RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true,
+ };
+
+ SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions()
+ {
+ ServerCertificate = serverCertificate,
+ };
+
+ var clientTask = Task.Run(() => client.AuthenticateAsClientAsync(clientOptions, cts.Token));
+ var serverTask = Task.Run(() => server.AuthenticateAsServerAsync(serverOptions, cts.Token));
+
+ // Dispose the instances while the handshake is in progress.
+ client.Dispose();
+ server.Dispose();
+
+ await ValidateExceptionAsync(clientTask);
+ await ValidateExceptionAsync(serverTask);
+ });
+
+ static async Task ValidateExceptionAsync(Task task)
+ {
+ try
+ {
+ await task;
+ }
+ catch (InvalidOperationException ex) when (ex.StackTrace?.Contains("System.IO.StreamBuffer.WriteAsync") ?? true)
+ {
+ // Writing to a disposed ConnectedStream (test only, does not happen with NetworkStream)
+ return;
+ }
+ catch (Exception ex) when (ex
+ is ObjectDisposedException // disposed locally
+ or IOException // disposed remotely (received unexpected EOF)
+ or AuthenticationException) // disposed wrapped in AuthenticationException or error from platform library
+ {
+ // expected
+ return;
+ }
+ }
+ }
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs
index c2eab851bd60e2..f8a075cc61c76f 100644
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.OverlappedValueTaskSource.Windows.cs
@@ -35,7 +35,11 @@ private void TryToReuse(OverlappedValueTaskSource source)
if (Interlocked.CompareExchange(ref _reusableOverlappedValueTaskSource, source, null) is not null)
{
- source._preallocatedOverlapped.Dispose();
+ source.Dispose();
+ }
+ else if (IsClosed)
+ {
+ Interlocked.Exchange(ref _reusableOverlappedValueTaskSource, null)?.Dispose();
}
}
diff --git a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionMarshallingFails.cs b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionMarshallingFails.cs
index e238ca51f24044..dd2759297b3a20 100644
--- a/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionMarshallingFails.cs
+++ b/src/libraries/System.Runtime.InteropServices/tests/LibraryImportGenerator.Tests/CollectionMarshallingFails.cs
@@ -110,7 +110,8 @@ public static partial void NegateBoolsRef2D_ClearMarshalling(
public class CollectionMarshallingFails
{
[Fact]
- public void UTFStringConversionFailures()
+ [SkipOnCI("Allocates enough memory that the OOM killer can kill the process on our Helix machines.")]
+ public void BigUTFStringConversionFailures()
{
bool threw = false;
try
diff --git a/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj b/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj
index e956491c0a63b1..5d98f157e174a3 100644
--- a/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj
+++ b/src/libraries/System.Runtime.Intrinsics/tests/System.Runtime.Intrinsics.Tests.csproj
@@ -13,6 +13,7 @@
+
diff --git a/src/libraries/System.Runtime.Intrinsics/tests/Wasm/PackedSimdTests.cs b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/PackedSimdTests.cs
new file mode 100644
index 00000000000000..a53ce142c37cac
--- /dev/null
+++ b/src/libraries/System.Runtime.Intrinsics/tests/Wasm/PackedSimdTests.cs
@@ -0,0 +1,447 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.Wasm;
+using System.Tests;
+using Xunit;
+
+namespace System.Runtime.Intrinsics.Wasm.Tests
+{
+ [PlatformSpecific(TestPlatforms.Browser)]
+ public sealed class PackedSimdTests
+ {
+ [Fact]
+ public unsafe void BasicArithmeticTest()
+ {
+ var v1 = Vector128.Create(1, 2, 3, 4);
+ var v2 = Vector128.Create(5, 6, 7, 8);
+
+ var addResult = PackedSimd.Add(v1, v2);
+ var subResult = PackedSimd.Subtract(v1, v2);
+ var mulResult = PackedSimd.Multiply(v1, v2);
+
+ Assert.Equal(Vector128.Create(6, 8, 10, 12), addResult);
+ Assert.Equal(Vector128.Create(-4, -4, -4, -4), subResult);
+ Assert.Equal(Vector128.Create(5, 12, 21, 32), mulResult);
+ }
+
+ [Fact]
+ public unsafe void BitwiseOperationsTest()
+ {
+ var v1 = Vector128.Create(0b1100, 0b1010, 0b1110, 0b1111);
+ var v2 = Vector128.Create(0b1010, 0b1100, 0b0011, 0b0101);
+
+ var andResult = PackedSimd.And(v1, v2);
+ var orResult = PackedSimd.Or(v1, v2);
+ var xorResult = PackedSimd.Xor(v1, v2);
+
+ Assert.Equal(Vector128.Create(0b1000, 0b1000, 0b0010, 0b0101), andResult);
+ Assert.Equal(Vector128.Create(0b1110, 0b1110, 0b1111, 0b1111), orResult);
+ Assert.Equal(Vector128.Create(0b0110, 0b0110, 0b1101, 0b1010), xorResult);
+ }
+
+ [Fact]
+ public unsafe void ShiftOperationsTest()
+ {
+ var v = Vector128.Create(16, -16, 32, -32);
+
+ var leftShift = PackedSimd.ShiftLeft(v, 2);
+ var rightShiftArith = PackedSimd.ShiftRightArithmetic(v, 2);
+ var rightShiftLogical = PackedSimd.ShiftRightLogical(v, 2);
+
+ Assert.Equal(Vector128.Create(64, -64, 128, -128), leftShift);
+ Assert.Equal(Vector128.Create(4, -4, 8, -8), rightShiftArith);
+ Assert.Equal(Vector128.Create(4, 1073741820, 8, 1073741816), rightShiftLogical);
+ }
+
+ [Fact]
+ public unsafe void ComparisonOperationsTest()
+ {
+ var v1 = Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f);
+ var v2 = Vector128.Create(4.0f, 3.0f, 2.0f, 1.0f);
+
+ var minResult = PackedSimd.Min(v1, v2);
+ var maxResult = PackedSimd.Max(v1, v2);
+
+ Assert.Equal(Vector128.Create(1.0f, 2.0f, 2.0f, 1.0f), minResult);
+ Assert.Equal(Vector128.Create(4.0f, 3.0f, 3.0f, 4.0f), maxResult);
+ }
+
+ [Fact]
+ public unsafe void FloatingPointOperationsTest()
+ {
+ var v = Vector128.Create(4.0f, 9.0f, 16.0f, 25.0f);
+
+ var sqrtResult = PackedSimd.Sqrt(v);
+ var ceilResult = PackedSimd.Ceiling(v);
+ var floorResult = PackedSimd.Floor(v);
+
+ Assert.Equal(Vector128.Create(2.0f, 3.0f, 4.0f, 5.0f), sqrtResult);
+ Assert.Equal(Vector128.Create(4.0f, 9.0f, 16.0f, 25.0f), ceilResult);
+ Assert.Equal(Vector128.Create(4.0f, 9.0f, 16.0f, 25.0f), floorResult);
+ }
+
+ [Fact]
+ public unsafe void LoadStoreTest()
+ {
+ int[] values = new int[] { 1, 2, 3, 4 };
+ fixed (int* ptr = values)
+ {
+ var loaded = PackedSimd.LoadVector128(ptr);
+ Assert.Equal(Vector128.Create(1, 2, 3, 4), loaded);
+
+ int[] storeTarget = new int[4];
+ fixed (int* storePtr = storeTarget)
+ {
+ PackedSimd.Store(storePtr, loaded);
+ Assert.Equal(values, storeTarget);
+ }
+ }
+ }
+
+ [Fact]
+ public unsafe void ExtractInsertScalarTest()
+ {
+ var v = Vector128.Create(1, 2, 3, 4);
+
+ int extracted = PackedSimd.ExtractScalar(v, 2);
+ Assert.Equal(3, extracted);
+
+ var modified = PackedSimd.ReplaceScalar(v, 2, 10);
+ Assert.Equal(Vector128.Create(1, 2, 10, 4), modified);
+ }
+
+ [Fact]
+ public unsafe void SaturatingArithmeticTest()
+ {
+ var v1 = Vector128.Create((byte)250, (byte)251, (byte)252, (byte)253, (byte)254, (byte)255, (byte)255, (byte)255,
+ (byte)250, (byte)251, (byte)252, (byte)253, (byte)254, (byte)255, (byte)255, (byte)255);
+ var v2 = Vector128.Create((byte)10, (byte)10, (byte)10, (byte)10, (byte)10, (byte)10, (byte)10, (byte)10,
+ (byte)10, (byte)10, (byte)10, (byte)10, (byte)10, (byte)10, (byte)10, (byte)10);
+
+ var addSat = PackedSimd.AddSaturate(v1, v2);
+ var subSat = PackedSimd.SubtractSaturate(v1, v2);
+
+ // Verify saturation at 255 for addition
+ Assert.Equal(Vector128.Create((byte)255, (byte)255, (byte)255, (byte)255, (byte)255, (byte)255, (byte)255, (byte)255,
+ (byte)255, (byte)255, (byte)255, (byte)255, (byte)255, (byte)255, (byte)255, (byte)255), addSat);
+
+ // Verify expected subtraction results
+ Assert.Equal(Vector128.Create((byte)240, (byte)241, (byte)242, (byte)243, (byte)244, (byte)245, (byte)245, (byte)245,
+ (byte)240, (byte)241, (byte)242, (byte)243, (byte)244, (byte)245, (byte)245, (byte)245), subSat);
+ }
+
+ [Fact]
+ public unsafe void WideningOperationsTest()
+ {
+ var v = Vector128.Create((short)1000, (short)2000, (short)3000, (short)4000,
+ (short)5000, (short)6000, (short)7000, (short)8000);
+
+ var lowerWidened = PackedSimd.SignExtendWideningLower(v);
+ var upperWidened = PackedSimd.SignExtendWideningUpper(v);
+
+ Assert.Equal(Vector128.Create(1000, 2000, 3000, 4000), lowerWidened);
+ Assert.Equal(Vector128.Create(5000, 6000, 7000, 8000), upperWidened);
+ }
+
+ [Fact]
+ public unsafe void SwizzleTest()
+ {
+ var v = Vector128.Create((byte)1, (byte)2, (byte)3, (byte)4, (byte)5, (byte)6, (byte)7, (byte)8,
+ (byte)9, (byte)10, (byte)11, (byte)12, (byte)13, (byte)14, (byte)15, (byte)16);
+ var indices = Vector128.Create((byte)3, (byte)2, (byte)1, (byte)0, (byte)7, (byte)6, (byte)5, (byte)4,
+ (byte)11, (byte)10, (byte)9, (byte)8, (byte)15, (byte)14, (byte)13, (byte)12);
+
+ var swizzled = PackedSimd.Swizzle(v, indices);
+
+ Assert.Equal(Vector128.Create((byte)4, (byte)3, (byte)2, (byte)1, (byte)8, (byte)7, (byte)6, (byte)5,
+ (byte)12, (byte)11, (byte)10, (byte)9, (byte)16, (byte)15, (byte)14, (byte)13), swizzled);
+ }
+
+ [Fact]
+ public unsafe void LoadScalarAndSplatTest()
+ {
+ int value = 42;
+ float fValue = 3.14f;
+
+ int* intPtr = &value;
+ float* floatPtr = &fValue;
+
+ var intSplat = PackedSimd.LoadScalarAndSplatVector128(intPtr);
+ var floatSplat = PackedSimd.LoadScalarAndSplatVector128(floatPtr);
+
+ Assert.Equal(Vector128.Create(42, 42, 42, 42), intSplat);
+ Assert.Equal(Vector128.Create(3.14f, 3.14f, 3.14f, 3.14f), floatSplat);
+ }
+
+ [Fact]
+ public unsafe void LoadWideningTest()
+ {
+ byte[] bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
+ fixed (byte* ptr = bytes)
+ {
+ var widened = PackedSimd.LoadWideningVector128(ptr);
+ Assert.Equal(Vector128.Create((ushort)1, (ushort)2, (ushort)3, (ushort)4,
+ (ushort)5, (ushort)6, (ushort)7, (ushort)8), widened);
+ }
+ }
+
+ [Fact]
+ public unsafe void StoreSelectedScalarTest()
+ {
+ var v = Vector128.Create(1, 2, 3, 4);
+ int value = 0;
+ int* ptr = &value;
+
+ PackedSimd.StoreSelectedScalar(ptr, v, 2);
+ Assert.Equal(3, value);
+ }
+
+ [Fact]
+ public unsafe void LoadScalarAndInsertTest()
+ {
+ var v = Vector128.Create(1, 2, 3, 4);
+ int newValue = 42;
+ int* ptr = &newValue;
+
+ var result = PackedSimd.LoadScalarAndInsert(ptr, v, 2);
+ Assert.Equal(Vector128.Create(1, 2, 42, 4), result);
+ }
+
+ [Fact]
+ public unsafe void ConversionTest()
+ {
+ var intVector = Vector128.Create(1, 2, 3, 4);
+ var floatVector = Vector128.Create(1.5f, 2.5f, 3.5f, 4.5f);
+ var doubleVector = Vector128.Create(1.5, 2.5);
+
+ var intToFloat = PackedSimd.ConvertToSingle(intVector);
+ var floatToDouble = PackedSimd.ConvertToDoubleLower(floatVector);
+
+ Assert.Equal(Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f), intToFloat);
+ Assert.Equal(Vector128.Create(1.5, 2.5), floatToDouble);
+ }
+
+ [Fact]
+ public unsafe void AddPairwiseWideningTest()
+ {
+ var bytes = Vector128.Create((byte)1, (byte)2, (byte)3, (byte)4,
+ (byte)5, (byte)6, (byte)7, (byte)8,
+ (byte)9, (byte)10, (byte)11, (byte)12,
+ (byte)13, (byte)14, (byte)15, (byte)16);
+
+ var widened = PackedSimd.AddPairwiseWidening(bytes);
+
+ Assert.Equal(Vector128.Create((ushort)3, (ushort)7, (ushort)11, (ushort)15,
+ (ushort)19, (ushort)23, (ushort)27, (ushort)31), widened);
+ }
+
+ [Fact]
+ public unsafe void MultiplyWideningTest()
+ {
+ var shorts = Vector128.Create((short)10, (short)20, (short)30, (short)40,
+ (short)50, (short)60, (short)70, (short)80);
+ var multiplier = Vector128.Create((short)2, (short)2, (short)2, (short)2,
+ (short)2, (short)2, (short)2, (short)2);
+
+ var lowerResult = PackedSimd.MultiplyWideningLower(shorts, multiplier);
+ var upperResult = PackedSimd.MultiplyWideningUpper(shorts, multiplier);
+
+ Assert.Equal(Vector128.Create(20, 40, 60, 80), lowerResult);
+ Assert.Equal(Vector128.Create(100, 120, 140, 160), upperResult);
+ }
+
+ [Fact]
+ public unsafe void DotProductTest()
+ {
+ var v1 = Vector128.Create((short)1, (short)2, (short)3, (short)4,
+ (short)5, (short)6, (short)7, (short)8);
+ var v2 = Vector128.Create((short)2, (short)2, (short)2, (short)2,
+ (short)2, (short)2, (short)2, (short)2);
+
+ var dot = PackedSimd.Dot(v1, v2);
+
+ // Each pair of values is multiplied and added:
+ // (1*2 + 2*2) = 6 for first int
+ // (3*2 + 4*2) = 14 for second int
+ // (5*2 + 6*2) = 22 for third int
+ // (7*2 + 8*2) = 30 for fourth int
+ Assert.Equal(Vector128.Create(6, 14, 22, 30), dot);
+ }
+
+ [Fact]
+ public unsafe void FloatingPointNegationTest()
+ {
+ var v = Vector128.Create(1.0f, -2.0f, 3.0f, -4.0f);
+ var d = Vector128.Create(1.0, -2.0);
+
+ var negatedFloat = PackedSimd.Negate(v);
+ var negatedDouble = PackedSimd.Negate(d);
+
+ Assert.Equal(Vector128.Create(-1.0f, 2.0f, -3.0f, 4.0f), negatedFloat);
+ Assert.Equal(Vector128.Create(-1.0, 2.0), negatedDouble);
+ }
+
+ [Fact]
+ public unsafe void FloatingPointAbsTest()
+ {
+ var v = Vector128.Create(-1.0f, 2.0f, -3.0f, 4.0f);
+ var d = Vector128.Create(-1.0, 2.0);
+
+ var absFloat = PackedSimd.Abs(v);
+ var absDouble = PackedSimd.Abs(d);
+
+ Assert.Equal(Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f), absFloat);
+ Assert.Equal(Vector128.Create(1.0, 2.0), absDouble);
+ }
+
+ [Fact]
+ public unsafe void FloatingPointDivisionTest()
+ {
+ var v1 = Vector128.Create(2.0f, 4.0f, 6.0f, 8.0f);
+ var v2 = Vector128.Create(2.0f, 2.0f, 2.0f, 2.0f);
+ var d1 = Vector128.Create(2.0, 4.0);
+ var d2 = Vector128.Create(2.0, 2.0);
+
+ var divFloat = PackedSimd.Divide(v1, v2);
+ var divDouble = PackedSimd.Divide(d1, d2);
+
+ Assert.Equal(Vector128.Create(1.0f, 2.0f, 3.0f, 4.0f), divFloat);
+ Assert.Equal(Vector128.Create(1.0, 2.0), divDouble);
+ }
+
+ [Fact]
+ public unsafe void IntegerAbsTest()
+ {
+ var bytes = Vector128.Create((sbyte)-1, (sbyte)2, (sbyte)-3, (sbyte)4,
+ (sbyte)-5, (sbyte)6, (sbyte)-7, (sbyte)8,
+ (sbyte)-9, (sbyte)10, (sbyte)-11, (sbyte)12,
+ (sbyte)-13, (sbyte)14, (sbyte)-15, (sbyte)16);
+ var shorts = Vector128.Create((short)-1, (short)2, (short)-3, (short)4,
+ (short)-5, (short)6, (short)-7, (short)8);
+ var ints = Vector128.Create(-1, 2, -3, 4);
+
+ var absBytes = PackedSimd.Abs(bytes);
+ var absShorts = PackedSimd.Abs(shorts);
+
+ Assert.Equal(Vector128.Create((sbyte)1, (sbyte)2, (sbyte)3, (sbyte)4,
+ (sbyte)5, (sbyte)6, (sbyte)7, (sbyte)8,
+ (sbyte)9, (sbyte)10, (sbyte)11, (sbyte)12,
+ (sbyte)13, (sbyte)14, (sbyte)15, (sbyte)16), absBytes);
+ Assert.Equal(Vector128.Create((short)1, (short)2, (short)3, (short)4,
+ (short)5, (short)6, (short)7, (short)8), absShorts);
+ }
+
+ [Fact]
+ public unsafe void AverageRoundedTest()
+ {
+ var bytes1 = Vector128.Create((byte)1, (byte)3, (byte)5, (byte)7,
+ (byte)9, (byte)11, (byte)13, (byte)15,
+ (byte)17, (byte)19, (byte)21, (byte)23,
+ (byte)25, (byte)27, (byte)29, (byte)31);
+ var bytes2 = Vector128.Create((byte)3, (byte)5, (byte)7, (byte)9,
+ (byte)11, (byte)13, (byte)15, (byte)17,
+ (byte)19, (byte)21, (byte)23, (byte)25,
+ (byte)27, (byte)29, (byte)31, (byte)33);
+
+ var avgBytes = PackedSimd.AverageRounded(bytes1, bytes2);
+
+ // Average is rounded up: (a + b + 1) >> 1
+ Assert.Equal(Vector128.Create((byte)2, (byte)4, (byte)6, (byte)8,
+ (byte)10, (byte)12, (byte)14, (byte)16,
+ (byte)18, (byte)20, (byte)22, (byte)24,
+ (byte)26, (byte)28, (byte)30, (byte)32), avgBytes);
+ }
+
+ [Fact]
+ public unsafe void MinMaxSignedUnsignedTest()
+ {
+ var signedBytes = Vector128.Create((sbyte)-1, (sbyte)2, (sbyte)-3, (sbyte)4,
+ (sbyte)-5, (sbyte)6, (sbyte)-7, (sbyte)8,
+ (sbyte)-9, (sbyte)10, (sbyte)-11, (sbyte)12,
+ (sbyte)-13, (sbyte)14, (sbyte)-15, (sbyte)16);
+
+ var unsignedBytes = Vector128.Create((byte)255, (byte)2, (byte)253, (byte)4,
+ (byte)251, (byte)6, (byte)249, (byte)8,
+ (byte)247, (byte)10, (byte)245, (byte)12,
+ (byte)243, (byte)14, (byte)241, (byte)16);
+
+ var signedMin = PackedSimd.Min(signedBytes, signedBytes.WithElement(0, (sbyte)0));
+ var unsignedMin = PackedSimd.Min(unsignedBytes, unsignedBytes.WithElement(0, (byte)0));
+
+ // Verify different comparison behavior for signed vs unsigned
+ Assert.Equal((sbyte)-1, signedMin.GetElement(0));
+ Assert.Equal((byte)0, unsignedMin.GetElement(0));
+ }
+
+ [Fact]
+ public unsafe void LoadScalarAndSplatInfinityTest()
+ {
+ float fInf = float.PositiveInfinity;
+ double dInf = double.PositiveInfinity;
+
+ float* fPtr = &fInf;
+ double* dPtr = &dInf;
+
+ var floatSplat = PackedSimd.LoadScalarAndSplatVector128(fPtr);
+ var doubleSplat = PackedSimd.LoadScalarAndSplatVector128(dPtr);
+
+ for (int i = 0; i < 4; i++)
+ {
+ Assert.True(float.IsPositiveInfinity(floatSplat.GetElement(i)));
+ }
+
+ for (int i = 0; i < 2; i++)
+ {
+ Assert.True(double.IsPositiveInfinity(doubleSplat.GetElement(i)));
+ }
+ }
+
+ [Fact]
+ public unsafe void FloatingPointTruncateTest()
+ {
+ var v1 = Vector128.Create(1.7f, -2.3f, 3.5f, -4.8f);
+ var d1 = Vector128.Create(1.7, -2.3);
+
+ var truncFloat = PackedSimd.Truncate(v1);
+ var truncDouble = PackedSimd.Truncate(d1);
+
+ Assert.Equal(Vector128.Create(1.0f, -2.0f, 3.0f, -4.0f), truncFloat);
+ Assert.Equal(Vector128.Create(1.0, -2.0), truncDouble);
+ }
+
+ [Fact]
+ public unsafe void ComparisonWithNaNTest()
+ {
+ var v1 = Vector128.Create(1.0f, float.NaN, 3.0f, float.PositiveInfinity);
+ var v2 = Vector128.Create(float.NegativeInfinity, 2.0f, float.NaN, 4.0f);
+
+ var minResult = PackedSimd.Min(v1, v2);
+ var maxResult = PackedSimd.Max(v1, v2);
+
+ // IEEE 754 rules: if either operand is NaN, the result should be NaN
+ Assert.True(float.IsNaN(minResult.GetElement(1)));
+ Assert.True(float.IsNaN(maxResult.GetElement(2)));
+ Assert.Equal(float.NegativeInfinity, minResult.GetElement(0));
+ Assert.Equal(float.PositiveInfinity, maxResult.GetElement(3));
+ }
+
+ [Fact]
+ public unsafe void NativeIntegerArithmeticTest()
+ {
+ var v1 = PackedSimd.Splat((nint)1);
+ var v2 = PackedSimd.Splat((nint)2);
+
+ var addResult = PackedSimd.Add(v1, v2);
+ var subResult = PackedSimd.Subtract(v1, v2);
+ var mulResult = PackedSimd.Multiply(v1, v2);
+
+ Assert.Equal(PackedSimd.Splat((nint)3), addResult);
+ Assert.Equal(PackedSimd.Splat((nint)(-1)), subResult);
+ Assert.Equal(PackedSimd.Splat((nint)2), mulResult);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/FSharpCoreReflectionProxy.cs b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/FSharpCoreReflectionProxy.cs
index e0bd83a0c1c9bf..835da6e002f905 100644
--- a/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/FSharpCoreReflectionProxy.cs
+++ b/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Metadata/FSharpCoreReflectionProxy.cs
@@ -204,7 +204,10 @@ public Func>, TFSharpMap> CreateFSharpMapConstru
}
private Attribute? GetFSharpCompilationMappingAttribute(Type type)
- => type.GetCustomAttribute(_compilationMappingAttributeType, inherit: true);
+ {
+ object[] attributes = type.GetCustomAttributes(_compilationMappingAttributeType, inherit: false);
+ return attributes.Length == 0 ? null : (Attribute)attributes[0];
+ }
private SourceConstructFlags GetSourceConstructFlags(Attribute compilationMappingAttribute)
=> _sourceConstructFlagsGetter is null ? SourceConstructFlags.None : (SourceConstructFlags)_sourceConstructFlagsGetter.Invoke(compilationMappingAttribute, null)!;
@@ -212,7 +215,7 @@ private SourceConstructFlags GetSourceConstructFlags(Attribute compilationMappin
// If the provided type is generated by the F# compiler, returns the runtime FSharp.Core assembly.
private static Assembly? GetFSharpCoreAssembly(Type type)
{
- foreach (Attribute attr in type.GetCustomAttributes(inherit: true))
+ foreach (Attribute attr in type.GetCustomAttributes(inherit: false))
{
Type attributeType = attr.GetType();
if (attributeType.FullName == CompilationMappingAttributeTypeName)
diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.FSharp.Tests/RecordTests.fs b/src/libraries/System.Text.Json/tests/System.Text.Json.FSharp.Tests/RecordTests.fs
index d9ac41cbd2b41d..6044b692904ef4 100644
--- a/src/libraries/System.Text.Json/tests/System.Text.Json.FSharp.Tests/RecordTests.fs
+++ b/src/libraries/System.Text.Json/tests/System.Text.Json.FSharp.Tests/RecordTests.fs
@@ -76,3 +76,22 @@ let ``Recursive struct records are supported``() =
Assert.Equal("""{"Next":[{"Next":[null]}]}""", json)
let deserializedValue = JsonSerializer.Deserialize(json)
Assert.Equal(value, deserializedValue)
+
+
+[, "derived")>]
+type BaseClass(x : int) =
+ member _.X = x
+
+and DerivedClass(x : int, y : bool) =
+ inherit BaseClass(x)
+ member _.Y = y
+
+[]
+let ``Support F# class hierarchies`` () =
+ let value = DerivedClass(42, true) :> BaseClass
+ let json = JsonSerializer.Serialize(value)
+ Assert.Equal("""{"$type":"derived","Y":true,"X":42}""", json)
+ let deserializedValue = JsonSerializer.Deserialize(json)
+ let derived = Assert.IsType(deserializedValue)
+ Assert.Equal(42, deserializedValue.X)
+ Assert.Equal(true, derived.Y)
diff --git a/src/mono/mono/metadata/sgen-bridge.c b/src/mono/mono/metadata/sgen-bridge.c
index 579fc0d376cd6a..1f7dc31c9b4b11 100644
--- a/src/mono/mono/metadata/sgen-bridge.c
+++ b/src/mono/mono/metadata/sgen-bridge.c
@@ -316,24 +316,24 @@ dump_processor_state (SgenBridgeProcessor *p)
{
int i;
- printf ("------\n");
- printf ("SCCS %d\n", p->num_sccs);
+ g_message ("------\n");
+ g_message ("SCCS %d\n", p->num_sccs);
for (i = 0; i < p->num_sccs; ++i) {
int j;
MonoGCBridgeSCC *scc = p->api_sccs [i];
- printf ("\tSCC %d:", i);
+ g_message ("\tSCC %d:", i);
for (j = 0; j < scc->num_objs; ++j) {
MonoObject *obj = scc->objs [j];
- printf (" %p(%s)", obj, SGEN_LOAD_VTABLE (obj)->klass->name);
+ g_message (" %p(%s)", obj, m_class_get_name (SGEN_LOAD_VTABLE (obj)->klass));
}
- printf ("\n");
+ g_message ("\n");
}
- printf ("XREFS %d\n", p->num_xrefs);
+ g_message ("XREFS %d\n", p->num_xrefs);
for (i = 0; i < p->num_xrefs; ++i)
- printf ("\t%d -> %d\n", p->api_xrefs [i].src_scc_index, p->api_xrefs [i].dst_scc_index);
+ g_message ("\t%d -> %d\n", p->api_xrefs [i].src_scc_index, p->api_xrefs [i].dst_scc_index);
- printf ("-------\n");
+ g_message ("-------\n");
}
*/
@@ -352,7 +352,7 @@ sgen_compare_bridge_processor_results (SgenBridgeProcessor *a, SgenBridgeProcess
if (a->num_sccs != b->num_sccs)
g_error ("SCCS count expected %d but got %d", a->num_sccs, b->num_sccs);
if (a->num_xrefs != b->num_xrefs)
- g_error ("SCCS count expected %d but got %d", a->num_xrefs, b->num_xrefs);
+ g_error ("XREFS count expected %d but got %d", a->num_xrefs, b->num_xrefs);
/*
* First we build a hash of each object in `a` to its respective SCC index within
diff --git a/src/mono/mono/metadata/sgen-tarjan-bridge.c b/src/mono/mono/metadata/sgen-tarjan-bridge.c
index b0c9cf1f83baef..7a8ad5961d98e7 100644
--- a/src/mono/mono/metadata/sgen-tarjan-bridge.c
+++ b/src/mono/mono/metadata/sgen-tarjan-bridge.c
@@ -400,16 +400,7 @@ static const char*
safe_name_bridge (GCObject *obj)
{
GCVTable vt = SGEN_LOAD_VTABLE (obj);
- return vt->klass->name;
-}
-
-static ScanData*
-find_or_create_data (GCObject *obj)
-{
- ScanData *entry = find_data (obj);
- if (!entry)
- entry = create_data (obj);
- return entry;
+ return m_class_get_name (vt->klass);
}
#endif
@@ -566,10 +557,15 @@ find_in_cache (int *insert_index)
// Populate other_colors for a give color (other_colors represent the xrefs for this color)
static void
-add_other_colors (ColorData *color, DynPtrArray *other_colors)
+add_other_colors (ColorData *color, DynPtrArray *other_colors, gboolean check_visited)
{
for (int i = 0; i < dyn_array_ptr_size (other_colors); ++i) {
ColorData *points_to = (ColorData *)dyn_array_ptr_get (other_colors, i);
+ if (check_visited) {
+ if (points_to->visited)
+ continue;
+ points_to->visited = TRUE;
+ }
dyn_array_ptr_add (&color->other_colors, points_to);
// Inform targets
points_to->incoming_colors = MIN (points_to->incoming_colors + 1, INCOMING_COLORS_MAX);
@@ -593,7 +589,7 @@ new_color (gboolean has_bridges)
cd = alloc_color_data ();
cd->api_index = -1;
- add_other_colors (cd, &color_merge_array);
+ add_other_colors (cd, &color_merge_array, FALSE);
/* if cacheSlot >= 0, it means we prepared a given slot to receive the new color */
if (cacheSlot >= 0)
@@ -700,11 +696,11 @@ compute_low_index (ScanData *data, GCObject *obj)
obj = bridge_object_forward (obj);
other = find_data (obj);
-#if DUMP_GRAPH
- printf ("\tcompute low %p ->%p (%s) %p (%d / %d, color %p)\n", data->obj, obj, safe_name_bridge (obj), other, other ? other->index : -2, other ? other->low_index : -2, other->color);
-#endif
if (!other)
return;
+#if DUMP_GRAPH
+ printf ("\tcompute low %p ->%p (%s) %p (%d / %d, color %p)\n", data->obj, obj, safe_name_bridge (obj), other, other ? other->index : -2, other->low_index, other->color);
+#endif
g_assert (other->state != INITIAL);
@@ -777,10 +773,16 @@ create_scc (ScanData *data)
gboolean found = FALSE;
gboolean found_bridge = FALSE;
ColorData *color_data = NULL;
+ gboolean can_reduce_color = TRUE;
for (i = dyn_array_ptr_size (&loop_stack) - 1; i >= 0; --i) {
ScanData *other = (ScanData *)dyn_array_ptr_get (&loop_stack, i);
found_bridge |= other->is_bridge;
+ if (dyn_array_ptr_size (&other->xrefs) > 0 || found_bridge) {
+ // This scc will have more xrefs than the ones from the color_merge_array,
+ // we will need to create a new color to store this information.
+ can_reduce_color = FALSE;
+ }
if (found_bridge || other == data)
break;
}
@@ -788,13 +790,15 @@ create_scc (ScanData *data)
if (found_bridge) {
color_data = new_color (TRUE);
++num_colors_with_bridges;
- } else {
+ } else if (can_reduce_color) {
color_data = reduce_color ();
+ } else {
+ color_data = new_color (FALSE);
}
#if DUMP_GRAPH
printf ("|SCC %p rooted in %s (%p) has bridge %d\n", color_data, safe_name_bridge (data->obj), data->obj, found_bridge);
printf ("\tloop stack: ");
- for (int i = 0; i < dyn_array_ptr_size (&loop_stack); ++i) {
+ for (i = 0; i < dyn_array_ptr_size (&loop_stack); ++i) {
ScanData *other = dyn_array_ptr_get (&loop_stack, i);
printf ("(%d/%d)", other->index, other->low_index);
}
@@ -824,10 +828,19 @@ create_scc (ScanData *data)
dyn_array_ptr_add (&color_data->bridges, other->obj);
}
- // Maybe we should make sure we are not adding duplicates here. It is not really a problem
- // since we will get rid of duplicates before submitting the SCCs to the client in gather_xrefs
- if (color_data)
- add_other_colors (color_data, &other->xrefs);
+ if (dyn_array_ptr_size (&other->xrefs) > 0) {
+ g_assert (color_data != NULL);
+ g_assert (can_reduce_color == FALSE);
+ // We need to eliminate duplicates early otherwise the heaviness property
+ // can change in gather_xrefs and it breaks down the loop that reports the
+ // xrefs to the client.
+ //
+ // We reuse the visited flag to mark the objects that are already part of
+ // the color_data array. The array was created above with the new_color call
+ // and xrefs were populated from color_merge_array, which is already
+ // deduplicated and every entry is marked as visited.
+ add_other_colors (color_data, &other->xrefs, TRUE);
+ }
dyn_array_ptr_uninit (&other->xrefs);
if (other == data) {
@@ -837,11 +850,22 @@ create_scc (ScanData *data)
}
g_assert (found);
+ // Clear the visited flag on nodes that were added with add_other_colors in the loop above
+ if (!can_reduce_color) {
+ for (i = dyn_array_ptr_size (&color_merge_array); i < dyn_array_ptr_size (&color_data->other_colors); i++) {
+ ColorData *cd = (ColorData *)dyn_array_ptr_get (&color_data->other_colors, i);
+ g_assert (cd->visited);
+ cd->visited = FALSE;
+ }
+ }
+
#if DUMP_GRAPH
- printf ("\tpoints-to-colors: ");
- for (int i = 0; i < dyn_array_ptr_size (&color_data->other_colors); i++)
- printf ("%p ", dyn_array_ptr_get (&color_data->other_colors, i));
- printf ("\n");
+ if (color_data) {
+ printf ("\tpoints-to-colors: ");
+ for (i = 0; i < dyn_array_ptr_size (&color_data->other_colors); i++)
+ printf ("%p ", dyn_array_ptr_get (&color_data->other_colors, i));
+ printf ("\n");
+ }
#endif
}
@@ -966,8 +990,11 @@ dump_color_table (const char *why, gboolean do_index)
printf (" bridges: ");
for (j = 0; j < dyn_array_ptr_size (&cd->bridges); ++j) {
GCObject *obj = dyn_array_ptr_get (&cd->bridges, j);
- ScanData *data = find_or_create_data (obj);
- printf ("%d ", data->index);
+ ScanData *data = find_data (obj);
+ if (!data)
+ printf ("%p ", obj);
+ else
+ printf ("%p(%d) ", obj, data->index);
}
}
printf ("\n");
@@ -1027,7 +1054,7 @@ processing_stw_step (void)
#if defined (DUMP_GRAPH)
printf ("----summary----\n");
printf ("bridges:\n");
- for (int i = 0; i < bridge_count; ++i) {
+ for (i = 0; i < bridge_count; ++i) {
ScanData *sd = find_data (dyn_array_ptr_get (®istered_bridges, i));
printf ("\t%s (%p) index %d color %p\n", safe_name_bridge (sd->obj), sd->obj, sd->index, sd->color);
}
@@ -1141,6 +1168,7 @@ processing_build_callback_data (int generation)
gather_xrefs (cd);
reset_xrefs (cd);
dyn_array_ptr_set_all (&cd->other_colors, &color_merge_array);
+ g_assert (color_visible_to_client (cd));
xref_count += dyn_array_ptr_size (&cd->other_colors);
}
}
diff --git a/src/mono/mono/mini/interp/interp-simd-intrins.def b/src/mono/mono/mini/interp/interp-simd-intrins.def
index d88e543af23471..197b3c269d6612 100644
--- a/src/mono/mono/mini/interp/interp-simd-intrins.def
+++ b/src/mono/mono/mini/interp/interp-simd-intrins.def
@@ -351,7 +351,7 @@ INTERP_WASM_SIMD_INTRINSIC_V_V (ConvertToSingle, U4, wasm_f32x4_convert_u32x4,
INTERP_WASM_SIMD_INTRINSIC_V_V (ConvertToSingle, R8, wasm_f32x4_demote_f64x2_zero, 0x5e)
INTERP_WASM_SIMD_INTRINSIC_V_V (ConvertToDoubleLower, I4, wasm_f64x2_convert_low_i32x4, 0xfe)
INTERP_WASM_SIMD_INTRINSIC_V_V (ConvertToDoubleLower, U4, wasm_f64x2_convert_low_u32x4, 0xff)
-INTERP_WASM_SIMD_INTRINSIC_V_V (ConvertToDoubleLower, R8, wasm_f64x2_promote_low_f32x4, 0x5f)
+INTERP_WASM_SIMD_INTRINSIC_V_V (ConvertToDoubleLower, R4, wasm_f64x2_promote_low_f32x4, 0x5f)
INTERP_WASM_SIMD_INTRINSIC_V_V (ConvertToInt32Saturate, R4, wasm_i32x4_trunc_sat_f32x4, 0xf8)
INTERP_WASM_SIMD_INTRINSIC_V_V (ConvertToUInt32Saturate, R4, wasm_u32x4_trunc_sat_f32x4, 0xf9)
INTERP_WASM_SIMD_INTRINSIC_V_V (ConvertToInt32Saturate, R8, wasm_i32x4_trunc_sat_f64x2_zero, 0xfc)
diff --git a/src/mono/mono/mini/interp/interp.c b/src/mono/mono/mini/interp/interp.c
index c3016252942f4a..011d99a9625dda 100644
--- a/src/mono/mono/mini/interp/interp.c
+++ b/src/mono/mono/mini/interp/interp.c
@@ -8981,6 +8981,10 @@ mono_ee_interp_init (const char *opts)
set_context (NULL);
interp_parse_options (opts);
+
+ const char *env_opts = g_getenv ("MONO_INTERPRETER_OPTIONS");
+ if (env_opts)
+ interp_parse_options (env_opts);
/* Don't do any optimizations if running under debugger */
if (mini_get_debug_options ()->mdb_optimizations)
mono_interp_opt = 0;
diff --git a/src/mono/mono/mini/interp/transform-opt.c b/src/mono/mono/mini/interp/transform-opt.c
index b259e116c14404..f5ffbc89df7fdd 100644
--- a/src/mono/mono/mini/interp/transform-opt.c
+++ b/src/mono/mono/mini/interp/transform-opt.c
@@ -3124,6 +3124,7 @@ interp_cprop (TransformData *td)
ins->data [2] = GINT_TO_UINT16 (ldsize);
interp_clear_ins (ins->prev);
+ td->var_values [ins->dreg].def = ins;
}
if (td->verbose_level) {
g_print ("Replace ldloca/ldobj_vt pair :\n\t");
@@ -3204,6 +3205,7 @@ interp_cprop (TransformData *td)
ins->data [2] = vtsize;
interp_clear_ins (ins->prev);
+ td->var_values [ins->dreg].def = ins;
// MINT_MOV_DST_OFF doesn't work if dreg is allocated at the same location as the
// field value to be stored, because its behavior is not atomic in nature. We first
@@ -3400,9 +3402,11 @@ interp_super_instructions (TransformData *td)
current_liveness.bb_dfs_index = bb->dfs_index;
current_liveness.ins_index = 0;
for (InterpInst *ins = bb->first_ins; ins != NULL; ins = ins->next) {
- int opcode = ins->opcode;
+ int opcode;
if (bb->dfs_index >= td->bblocks_count_no_eh || bb->dfs_index == -1 || (ins->flags & INTERP_INST_FLAG_LIVENESS_MARKER))
current_liveness.ins_index++;
+retry_ins:
+ opcode = ins->opcode;
if (MINT_IS_NOP (opcode))
continue;
@@ -3801,9 +3805,7 @@ interp_super_instructions (TransformData *td)
g_print ("superins: ");
interp_dump_ins (ins, td->data_items);
}
- // The newly added opcode could be part of further superinstructions. Retry
- ins = ins->prev;
- continue;
+ goto retry_ins;
}
}
}
diff --git a/src/mono/mono/mini/interp/transform-simd.c b/src/mono/mono/mini/interp/transform-simd.c
index c1d6b81019d02f..70b88cf0c148d0 100644
--- a/src/mono/mono/mini/interp/transform-simd.c
+++ b/src/mono/mono/mini/interp/transform-simd.c
@@ -779,6 +779,24 @@ emit_sn_vector4 (TransformData *td, MonoMethod *cmethod, MonoMethodSignature *cs
}
#if HOST_BROWSER
+static MonoTypeEnum
+resolve_native_size (MonoTypeEnum type)
+{
+ if (type == MONO_TYPE_I)
+#if TARGET_SIZEOF_VOID_P == 4
+ return MONO_TYPE_I4;
+#else
+ return MONO_TYPE_I8;
+#endif
+ else if (type == MONO_TYPE_U)
+#if TARGET_SIZEOF_VOID_P == 4
+ return MONO_TYPE_U4;
+#else
+ return MONO_TYPE_U8;
+#endif
+ return type;
+
+}
#define PSIMD_ARGTYPE_I1 MONO_TYPE_I1
#define PSIMD_ARGTYPE_I2 MONO_TYPE_I2
@@ -803,6 +821,7 @@ emit_sn_vector4 (TransformData *td, MonoMethod *cmethod, MonoMethodSignature *cs
static gboolean
packedsimd_type_matches (MonoTypeEnum type, int expected_type)
{
+ type = resolve_native_size (type);
if (expected_type == PSIMD_ARGTYPE_ANY)
return TRUE;
else if (type == expected_type)
@@ -906,6 +925,8 @@ lookup_packedsimd_intrinsic (const char *name, MonoType *arg1)
arg_type = mono_class_get_context (vector_klass)->class_inst->type_argv [0];
} else if (arg1->type == MONO_TYPE_PTR) {
arg_type = arg1->data.type;
+ } else if (MONO_TYPE_IS_VECTOR_PRIMITIVE(arg1)) {
+ arg_type = arg1;
} else {
// g_printf ("%s arg1 type was not pointer or simd type: %s\n", name, m_class_get_name (vector_klass));
return FALSE;
@@ -989,7 +1010,13 @@ emit_sri_packedsimd (TransformData *td, MonoMethod *cmethod, MonoMethodSignature
int id = lookup_intrins (sri_packedsimd_methods, sizeof (sri_packedsimd_methods), cmethod->name);
// We don't early-out for an unrecognized method, we will generate an NIY later
- MonoClass *vector_klass = mono_class_from_mono_type_internal (csignature->ret);
+ MonoClass *vector_klass = NULL;
+ if (csignature->ret->type == MONO_TYPE_VOID && csignature->param_count > 1 && mono_type_is_pointer (csignature->params [0])) {
+ // The Store* methods have a more complicated signature
+ vector_klass = mono_class_from_mono_type_internal (csignature->params [1]);
+ } else {
+ vector_klass = mono_class_from_mono_type_internal (csignature->ret);
+ }
MonoTypeEnum atype;
int vector_size = -1, arg_size, scalar_arg;
diff --git a/src/native/corehost/corehost.cpp b/src/native/corehost/corehost.cpp
index c8a94312c5d6d4..c233a52237a9fb 100644
--- a/src/native/corehost/corehost.cpp
+++ b/src/native/corehost/corehost.cpp
@@ -115,7 +115,7 @@ int exe_start(const int argc, const pal::char_t* argv[])
// Use realpath to find the path of the host, resolving any symlinks.
// hostfxr (for dotnet) and the app dll (for apphost) are found relative to the host.
pal::string_t host_path;
- if (!pal::get_own_executable_path(&host_path) || !pal::realpath(&host_path))
+ if (!pal::get_own_executable_path(&host_path) || !pal::fullpath(&host_path))
{
trace::error(_X("Failed to resolve full path of the current executable [%s]"), host_path.c_str());
return StatusCode::CoreHostCurHostFindFailure;
diff --git a/src/native/corehost/fxr_resolver.cpp b/src/native/corehost/fxr_resolver.cpp
index a69bf7ced896a5..0d1cbddf9deb5c 100644
--- a/src/native/corehost/fxr_resolver.cpp
+++ b/src/native/corehost/fxr_resolver.cpp
@@ -95,7 +95,7 @@ bool fxr_resolver::try_get_path(
bool search_global = (search & search_location_global) != 0;
pal::string_t default_install_location;
pal::string_t dotnet_root_env_var_name;
- if (search_app_relative && pal::realpath(app_relative_dotnet_root))
+ if (search_app_relative && pal::fullpath(app_relative_dotnet_root))
{
trace::info(_X("Using app-relative location [%s] as runtime location."), app_relative_dotnet_root->c_str());
out_dotnet_root->assign(*app_relative_dotnet_root);
diff --git a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c
index e444d8c0c57b12..f4a79df5fa1498 100644
--- a/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c
+++ b/src/native/libs/System.Security.Cryptography.Native/pal_evp_pkey_rsa.c
@@ -215,6 +215,15 @@ int32_t CryptoNative_RsaEncrypt(EVP_PKEY* pkey,
static bool ConfigureSignature(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const EVP_MD* digest)
{
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wcast-qual"
+ if (EVP_PKEY_CTX_set_signature_md(ctx, digest) <= 0)
+#pragma clang diagnostic pop
+ {
+ return false;
+ }
+
if (padding == RsaPaddingPkcs1)
{
if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
@@ -233,14 +242,6 @@ static bool ConfigureSignature(EVP_PKEY_CTX* ctx, RsaPaddingMode padding, const
}
}
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wcast-qual"
- if (EVP_PKEY_CTX_set_signature_md(ctx, digest) <= 0)
-#pragma clang diagnostic pop
- {
- return false;
- }
-
return true;
}
diff --git a/src/tests/GC/Features/Bridge/Bridge.cs b/src/tests/GC/Features/Bridge/Bridge.cs
new file mode 100644
index 00000000000000..c481087943efcd
--- /dev/null
+++ b/src/tests/GC/Features/Bridge/Bridge.cs
@@ -0,0 +1,422 @@
+// 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;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+// False pinning cases are still possible. For example the thread can die
+// and its stack reused by another thread. It also seems that a thread that
+// does a GC can keep on the stack references to objects it encountered
+// during the collection which are never released afterwards. This would
+// be more likely to happen with the interpreter which reuses more stack.
+public static class FinalizerHelpers
+{
+ private static IntPtr aptr;
+
+ private static unsafe void NoPinActionHelper(int depth, Action act)
+ {
+ // Avoid tail calls
+ int* values = stackalloc int[20];
+ aptr = new IntPtr(values);
+
+ if (depth <= 0)
+ {
+ //
+ // When the action is called, this new thread might have not allocated
+ // anything yet in the nursery. This means that the address of the first
+ // object that would be allocated would be at the start of the tlab and
+ // implicitly the end of the previous tlab (address which can be in use
+ // when allocating on another thread, at checking if an object fits in
+ // this other tlab). We allocate a new dummy object to avoid this type
+ // of false pinning for most common cases.
+ //
+ new object();
+ act();
+ ClearStack();
+ }
+ else
+ {
+ NoPinActionHelper(depth - 1, act);
+ }
+ }
+
+ private static unsafe void ClearStack()
+ {
+ int* values = stackalloc int[25000];
+ for (int i = 0; i < 25000; i++)
+ values[i] = 0;
+ }
+
+ public static void PerformNoPinAction(Action act)
+ {
+ Thread thr = new Thread(() => NoPinActionHelper (128, act));
+ thr.Start();
+ thr.Join();
+ }
+}
+
+public class BridgeBase
+{
+ public static int fin_count;
+
+ ~BridgeBase()
+ {
+ fin_count++;
+ }
+}
+
+public class Bridge : BridgeBase
+{
+ public List