Merge branch 'main' into browser_http_streaming_response_test

This commit is contained in:
Pavel Savara 2025-07-30 13:33:17 +02:00 committed by GitHub
commit de0059a3d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
211 changed files with 5586 additions and 6744 deletions

View File

@ -19,7 +19,7 @@ configuration:
- adamsitnik
- bartonjs
- jeffhandley
- terrajobst
- JeremyKuhne
replyTemplate: >-
Tagging subscribers to 'binaryformatter-migration': ${mentionees}
assignMentionees: False

View File

@ -523,7 +523,7 @@ int ManagedReferencesSum(int[] buffer)
Vector128<int> sum = Vector128<int>.Zero;
while (!Unsafe.IsAddressGreaterThan(ref current, ref oneVectorAwayFromEnd))
while (Unsafe.IsAddressLessThanOrEqualTo(ref current, ref oneVectorAwayFromEnd))
{
sum += Vector128.LoadUnsafe(ref current);
@ -561,7 +561,7 @@ do
return ...;
}
while (!Unsafe.IsAddressLessThan(ref currentSearchSpace, ref searchSpace));
while (Unsafe.IsAddressGreaterThanOrEqualTo(ref currentSearchSpace, ref searchSpace));
```
It was part of `LastIndexOf` implementation, where we were iterating from the end to the beginning of the buffer. In the last iteration of the loop, `currentSearchSpace` could become a pointer to unknown memory that lied before the beginning of the buffer:
@ -573,7 +573,7 @@ currentSearchSpace = ref Unsafe.Subtract(ref currentSearchSpace, Vector128<TValu
And it was fine until GC kicked right after that, moved objects in memory, updated all valid managed references and resumed the execution, which run following condition:
```csharp
while (!Unsafe.IsAddressLessThan(ref currentSearchSpace, ref searchSpace));
while (Unsafe.IsAddressGreaterThanOrEqualTo(ref currentSearchSpace, ref searchSpace));
```
Which could return true because `currentSearchSpace` was invalid and not updated. If you are interested in more details, you can check the [issue](https://github.com/dotnet/runtime/issues/75792#issuecomment-1249973858) and the [fix](https://github.com/dotnet/runtime/pull/75857).

View File

@ -56,6 +56,7 @@ ModuleHandle GetModuleHandleFromModulePtr(TargetPointer module);
ModuleHandle GetModuleHandleFromAssemblyPtr(TargetPointer assemblyPointer);
IEnumerable<ModuleHandle> GetModuleHandles(TargetPointer appDomain, AssemblyIterationFlags iterationFlags);
TargetPointer GetRootAssembly();
string GetAppDomainFriendlyName();
TargetPointer GetModule(ModuleHandle handle);
TargetPointer GetAssembly(ModuleHandle handle);
TargetPointer GetPEAssembly(ModuleHandle handle);
@ -67,6 +68,7 @@ string GetPath(ModuleHandle handle);
string GetFileName(ModuleHandle handle);
TargetPointer GetLoaderAllocator(ModuleHandle handle);
TargetPointer GetILBase(ModuleHandle handle);
TargetPointer GetAssemblyLoadContext(ModuleHandle handle);
ModuleLookupTables GetLookupTables(ModuleHandle handle);
TargetPointer GetModuleLookupMapElement(TargetPointer table, uint token, out TargetNUInt flags);
bool IsCollectible(ModuleHandle handle);
@ -106,6 +108,8 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer);
| `Assembly` | `NotifyFlags` | Flags relating to the debugger/profiler notification state of the assembly |
| `Assembly` | `Level` | File load level of the assembly |
| `PEAssembly` | `PEImage` | Pointer to the PEAssembly's PEImage |
| `PEAssembly` | `AssemblyBinder` | Pointer to the PEAssembly's binder |
| `AssemblyBinder` | `ManagedAssemblyLoadContext` | Pointer to the AssemblyBinder's ManagedAssemblyLoadContext |
| `PEImage` | `LoadedImageLayout` | Pointer to the PEImage's loaded PEImageLayout |
| `PEImage` | `ProbeExtensionResult` | PEImage's ProbeExtensionResult |
| `ProbeExtensionResult` | `Type` | Type of ProbeExtensionResult |
@ -116,6 +120,7 @@ TargetPointer GetStubHeap(TargetPointer loaderAllocatorPointer);
| `CGrowableSymbolStream` | `Size` | Size of the raw symbol stream buffer |
| `AppDomain` | `RootAssembly` | Pointer to the root assembly |
| `AppDomain` | `DomainAssemblyList` | ArrayListBase of assemblies in the AppDomain |
| `AppDomain` | `FriendlyName` | Friendly name of the AppDomain |
| `LoaderAllocator` | `ReferenceCount` | Reference count of LoaderAllocator |
| `LoaderAllocator` | `HighFrequencyHeap` | High-frequency heap of LoaderAllocator |
| `LoaderAllocator` | `LowFrequencyHeap` | Low-frequency heap of LoaderAllocator |
@ -270,6 +275,15 @@ TargetPointer GetRootAssembly()
return appDomain.RootAssembly;
}
string ILoader.GetAppDomainFriendlyName()
{
TargetPointer appDomainPointer = target.ReadGlobalPointer(Constants.Globals.AppDomain);
TargetPointer appDomain = target.ReadPointer(appDomainPointer)
TargetPointer pathStart = appDomain + /* AppDomain::FriendlyName offset */;
char[] name = // Read<char> from target starting at pathStart until null terminator
return new string(name);
}
TargetPointer ILoader.GetModule(ModuleHandle handle)
{
return handle.Address;
@ -373,6 +387,14 @@ TargetPointer GetILBase(ModuleHandle handle)
return target.ReadPointer(handle.Address + /* Module::Base offset */);
}
TargetPointer ILoader.GetAssemblyLoadContext(ModuleHandle handle)
{
PEAssembly peAssembly = target.ReadPointer(handle.Address + /* Module::PEAssembly offset */);
AssemblyBinder binder = target.ReadPointer(peAssembly + /* PEAssembly::AssemblyBinder offset */);
ObjectHandle objectHandle = new ObjectHandle(binder);
return objectHandle.Object;
}
ModuleLookupTables GetLookupTables(ModuleHandle handle)
{
return new ModuleLookupTables(
@ -395,20 +417,20 @@ TargetPointer GetModuleLookupMapElement(TargetPointer table, uint token, out Tar
uint index = rid;
// have to read lookupMap an extra time upfront because only the first map
// has valid supportedFlagsMask
TargetNUInt supportedFlagsMask = _target.ReadNUInt(table + /* ModuleLookupMap::SupportedFlagsMask */);
TargetNUInt supportedFlagsMask = target.ReadNUInt(table + /* ModuleLookupMap::SupportedFlagsMask */);
do
{
if (index < _target.Read<uint>(table + /*ModuleLookupMap::Count*/))
if (index < target.Read<uint>(table + /*ModuleLookupMap::Count*/))
{
TargetPointer entryAddress = _target.ReadPointer(lookupMap + /*ModuleLookupMap::TableData*/) + (ulong)(index * _target.PointerSize);
TargetPointer rawValue = _target.ReadPointer(entryAddress);
TargetPointer entryAddress = target.ReadPointer(lookupMap + /*ModuleLookupMap::TableData*/) + (ulong)(index * target.PointerSize);
TargetPointer rawValue = target.ReadPointer(entryAddress);
flags = rawValue & supportedFlagsMask;
return rawValue & ~(supportedFlagsMask.Value);
}
else
{
table = _target.ReadPointer(lookupMap + /*ModuleLookupMap::Next*/);
index -= _target.Read<uint>(lookupMap + /*ModuleLookupMap::Count*/);
table = target.ReadPointer(lookupMap + /*ModuleLookupMap::Next*/);
index -= target.Read<uint>(lookupMap + /*ModuleLookupMap::Count*/);
}
} while (table != TargetPointer.Null);
return TargetPointer.Null;

View File

@ -46,7 +46,6 @@ record struct ThreadData (
ThreadStoreData GetThreadStoreData();
ThreadStoreCounts GetThreadCounts();
ThreadData GetThreadData(TargetPointer threadPointer);
TargetPointer GetManagedThreadObject(TargetPointer threadPointer);
```
## Version 1
@ -128,10 +127,4 @@ ThreadData GetThreadData(TargetPointer address)
NextThread: target.ReadPointer(address + /* Thread::LinkNext offset */) - threadLinkOffset;
);
}
TargetPointer GetManagedThreadObject(TargetPointer threadPointer)
{
var runtimeThread = new Thread(Target, threadPointer);
return Contracts.GCHandle.GetObject(new DacGCHandle(runtimeThread.m_ExposedObject));
}
```

View File

@ -41,7 +41,7 @@ Each IR instruction is represented by a MonoInst structure. The fields of the st
- ins-\>opcode contains the opcode of the instruction. It is always set.
- ins-\>dreg, ins-\>sreg1, ins-\>sreg2 contain the the destination and source vregs of the instruction. If the instruction doesn't have a destination/and our source, the corresponding field is set to -1.
- ins-\>dreg, ins-\>sreg1, ins-\>sreg2 contain the destination and source vregs of the instruction. If the instruction doesn't have a destination/and our source, the corresponding field is set to -1.
- ins-\>backend is used for various purposes:
- for MonoInst's representing vtype variables, it indicates that the variable is in unmanaged format (used during marshalling)

View File

@ -392,7 +392,7 @@ Each command requires at least one TypeID (of type id) parameter before any addi
| GET_FIELD_CATTRS | 11 | Returns a list of custom attributes of a type's field. Custom attribute definition is given below. | Ask for a FieldID of one the type field and a TypeID of an custom attribute type | INVALID_TYPEID, INVALID_FIELDID |
| GET_PROPERTY_CATTRS | 12 | Returns a list of custom attributes of a type's property. Custom attribute definition is given below. | Ask for a PropertyID of one the type field and a TypeID of an custom attribute type | INVALID_TYPEID, INVALID_PROPERTYID |
| GET_SOURCE_FILES_2 | 13 | Returns a list of source file full paths (string) where the type is defined | None | INVALID_TYPEID |
| GET_VALUES_2 | 14 | Returns a number of variant value equals to the number of FieldID that was passed as parameter. If the field had a ThreadStatic attribute applied to it, value fetched are from the thread parameter point of view. | Ask for an ObjectID representing a System.Thread instance and a list of FieldID representing this type static fields to the the value of. Only static field are supported. | INVALID_OBJECT, INVALID_TYPEID, INVALID_FIELDID |
| GET_VALUES_2 | 14 | Returns a number of variant value equals to the number of FieldID that was passed as parameter. If the field had a ThreadStatic attribute applied to it, value fetched are from the thread parameter point of view. | Ask for an ObjectID representing a System.Thread instance and a list of FieldID representing this type static fields to the value of. Only static field are supported. | INVALID_OBJECT, INVALID_TYPEID, INVALID_FIELDID |
The main functions handling these commands are `type_commands` and `type_commands_internal` and are situated at `debugger-agent.c:6726` and `debugger-agent.c:6403` respectively.

View File

@ -128,7 +128,7 @@ Concretely, in the face of adversarial input:
## Implementation
The `HashCode` type uses the [**xxHash32**](https://github.com/Cyan4973/xxHash) algorithm, which is a non-cryptographic hash algorithm with a 32-bit seed and a 32-bit digest. All instances of the `HashCode` type use the same seed value, generated randomly at app start. This value is chosen independently of other random seed values in the runtime, such as the the global 64-bit seed used in `string.GetHashCode`'s Marvin32 routine.
The `HashCode` type uses the [**xxHash32**](https://github.com/Cyan4973/xxHash) algorithm, which is a non-cryptographic hash algorithm with a 32-bit seed and a 32-bit digest. All instances of the `HashCode` type use the same seed value, generated randomly at app start. This value is chosen independently of other random seed values in the runtime, such as the global 64-bit seed used in `string.GetHashCode`'s Marvin32 routine.
The xxHash32 repo's README file touts good performance and avalanching. This can be validated through a simple C# program.

View File

@ -297,7 +297,7 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL
APIs can be marked as `[Experimental]` if their shape or functionality is included in a release but not yet officially supported. Experimental APIs offer the opportunity to collect customer feedback on these APIs in a major release, usually refining the APIs and removing the `[Experimental]` attribute in the next release. The `[Experimental]` attribute differs from `[RequiresPreviewFeatures]`, wherein:
* `[RequiresPreviewFeatures]` APIs require a corresponding preview feature in another product area such as the compiler or SDK
- Using these APIs requires enabling preview features for the the project and all its consumers
- Using these APIs requires enabling preview features for the project and all its consumers
* `[Experimental]` APIs are self-contained within the libraries and do not require preview features in other parts of the product
- These APIs can be used by suppressing specific diagnostics without enabling preview features for the project

View File

@ -11,8 +11,8 @@
<PackageVersionNet8>8.0.$([MSBuild]::Add($([System.Version]::Parse('$(PackageVersionNet9)').Build),11))</PackageVersionNet8>
<PackageVersionNet7>7.0.20</PackageVersionNet7>
<PackageVersionNet6>6.0.36</PackageVersionNet6>
<PreReleaseVersionLabel>preview</PreReleaseVersionLabel>
<PreReleaseVersionIteration>7</PreReleaseVersionIteration>
<PreReleaseVersionLabel>rc</PreReleaseVersionLabel>
<PreReleaseVersionIteration>1</PreReleaseVersionIteration>
<!-- Enable to remove prerelease label. -->
<StabilizePackageVersion Condition="'$(StabilizePackageVersion)' == ''">false</StabilizePackageVersion>
<DotNetFinalVersionKind Condition="'$(StabilizePackageVersion)' == 'true'">release</DotNetFinalVersionKind>

View File

@ -900,13 +900,13 @@ extends:
# WASI/WASM
- template: /eng/pipelines/common/templates/simple-wasm-build-tests.yml
parameters:
platforms:
- wasi_wasm
- wasi_wasm_win
extraBuildArgs: /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS)
alwaysRun: ${{ variables.isRollingBuild }}
# - template: /eng/pipelines/common/templates/simple-wasm-build-tests.yml
# parameters:
# platforms:
# - wasi_wasm
# - wasi_wasm_win
# extraBuildArgs: /p:AotHostArchitecture=x64 /p:AotHostOS=$(_hostedOS)
# alwaysRun: ${{ variables.isRollingBuild }}
#
# Android devices

View File

@ -1,7 +1 @@
Wasi.Build.Tests.InvariantTests
Wasi.Build.Tests.ILStripTests
Wasi.Build.Tests.SdkMissingTests
Wasi.Build.Tests.RuntimeConfigTests
Wasi.Build.Tests.WasiTemplateTests
Wasi.Build.Tests.PInvokeTableGeneratorTests
Wasi.Build.Tests.WasiLibraryModeTests

View File

@ -223,6 +223,7 @@
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\Marshal.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\MemoryMarshal.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeLibrary.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\InteropServices\Java\JavaMarshal.CoreCLR.cs" Condition="'$(FeatureJavaMarshal)' == 'true'" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\Intrinsics\X86\X86Base.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\Loader\AssemblyLoadContext.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\RuntimeArgumentHandle.cs" />

View File

@ -12,39 +12,24 @@ namespace System.Runtime.InteropServices.Java
{
public static unsafe void Initialize(delegate* unmanaged<MarkCrossReferencesArgs*, void> markCrossReferences)
{
#if NATIVEAOT
throw new NotImplementedException();
#elif FEATURE_JAVAMARSHAL
ArgumentNullException.ThrowIfNull(markCrossReferences);
if (!InitializeInternal((IntPtr)markCrossReferences))
{
throw new InvalidOperationException(SR.InvalidOperation_ReinitializeJavaMarshal);
}
#else
throw new PlatformNotSupportedException();
#endif
}
public static unsafe GCHandle CreateReferenceTrackingHandle(object obj, void* context)
{
#if NATIVEAOT
throw new NotImplementedException();
#elif FEATURE_JAVAMARSHAL
ArgumentNullException.ThrowIfNull(obj);
IntPtr handle = CreateReferenceTrackingHandleInternal(ObjectHandleOnStack.Create(ref obj), context);
return GCHandle.FromIntPtr(handle);
#else
throw new PlatformNotSupportedException();
#endif
}
public static unsafe void* GetContext(GCHandle obj)
{
#if NATIVEAOT
throw new NotImplementedException();
#elif FEATURE_JAVAMARSHAL
IntPtr handle = GCHandle.ToIntPtr(obj);
if (handle == IntPtr.Zero
|| !GetContextInternal(handle, out void* context))
@ -53,18 +38,12 @@ namespace System.Runtime.InteropServices.Java
}
return context;
#else
throw new PlatformNotSupportedException();
#endif
}
public static unsafe void FinishCrossReferenceProcessing(
MarkCrossReferencesArgs* crossReferences,
ReadOnlySpan<GCHandle> unreachableObjectHandles)
{
#if NATIVEAOT
throw new NotImplementedException();
#elif FEATURE_JAVAMARSHAL
fixed (GCHandle* pHandles = unreachableObjectHandles)
{
FinishCrossReferenceProcessing(
@ -72,12 +51,8 @@ namespace System.Runtime.InteropServices.Java
(nuint)unreachableObjectHandles.Length,
pHandles);
}
#else
throw new PlatformNotSupportedException();
#endif
}
#if FEATURE_JAVAMARSHAL && !NATIVEAOT
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "JavaMarshal_Initialize")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool InitializeInternal(IntPtr callback);
@ -92,6 +67,5 @@ namespace System.Runtime.InteropServices.Java
[SuppressGCTransition]
[return: MarshalAs(UnmanagedType.Bool)]
private static unsafe partial bool GetContextInternal(IntPtr handle, out void* context);
#endif
}
}

View File

@ -18,7 +18,7 @@ extern const uintptr_t contractDescriptorPointerData[];
const uintptr_t contractDescriptorPointerData[] = {
(uintptr_t)0, // placeholder
#define CDAC_GLOBAL_POINTER(name,value) (uintptr_t)(value),
#include "datadescriptor.h"
#include "datadescriptor.inc"
};
}

View File

@ -89,7 +89,7 @@ struct CDacStringPoolSizes
#define CDAC_GLOBAL_POINTER(name,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name))
#define CDAC_GLOBAL(name,tyname,value) DECL_LEN(MAKE_GLOBALLEN_NAME(name), sizeof(#name)) \
DECL_LEN(MAKE_GLOBALTYPELEN_NAME(name), sizeof(#tyname))
#include "datadescriptor.h"
#include "datadescriptor.inc"
char cdac_string_pool_trailing_nil;
#undef DECL_LEN
};
@ -107,7 +107,7 @@ enum
CDacBlobTypesCount =
#define CDAC_TYPES_BEGIN() 0
#define CDAC_TYPE_BEGIN(name) + 1
#include "datadescriptor.h"
#include "datadescriptor.inc"
};
// count the field pool size.
@ -118,7 +118,7 @@ enum
#define CDAC_TYPES_BEGIN() 1
#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) + 1
#define CDAC_TYPE_END(name) + 1
#include "datadescriptor.h"
#include "datadescriptor.inc"
};
// count the literal globals
@ -127,7 +127,7 @@ enum
CDacBlobGlobalLiteralsCount =
#define CDAC_GLOBALS_BEGIN() 0
#define CDAC_GLOBAL(name,tyname,value) + 1
#include "datadescriptor.h"
#include "datadescriptor.inc"
};
// count the aux vector globals
@ -136,7 +136,7 @@ enum
CDacBlobGlobalPointersCount =
#define CDAC_GLOBALS_BEGIN() 0
#define CDAC_GLOBAL_POINTER(name,value) + 1
#include "datadescriptor.h"
#include "datadescriptor.inc"
};
// count the global strings
@ -145,7 +145,7 @@ enum
CDacBlobGlobalStringsCount =
#define CDAC_GLOBALS_BEGIN() 0
#define CDAC_GLOBAL_STRING(name,value) + 1
#include "datadescriptor.h"
#include "datadescriptor.inc"
};
@ -175,7 +175,7 @@ struct CDacFieldsPoolSizes
#define CDAC_TYPE_FIELD(tyname,membertyname,membername,offset) DECL_LEN(CONCAT4(cdac_fields_pool_member__, tyname, __, membername))
#define CDAC_TYPE_END(name) DECL_LEN(CONCAT4(cdac_fields_pool_member__, tyname, _, endmarker)) \
} MAKE_TYPEFIELDS_TYNAME(name);
#include "datadescriptor.h"
#include "datadescriptor.inc"
#undef DECL_LEN
};
@ -197,7 +197,7 @@ struct CDacGlobalPointerIndex
#define DECL_LEN(membername) char membername;
#define CDAC_GLOBALS_BEGIN() DECL_LEN(cdac_global_pointer_index_start_placeholder__)
#define CDAC_GLOBAL_POINTER(name,value) DECL_LEN(CONCAT(cdac_global_pointer_index__, name))
#include "datadescriptor.h"
#include "datadescriptor.inc"
#undef DECL_LEN
};
@ -295,7 +295,7 @@ struct MagicAndBlob BlobDataDescriptor = {
#define CDAC_TYPE_INDETERMINATE(name) /*.Size = */ 0,
#define CDAC_TYPE_SIZE(size) /* .Size = */ size,
#define CDAC_TYPE_END(name) },
#include "datadescriptor.h"
#include "datadescriptor.inc"
},
/* .FieldsPool = */ {
@ -306,22 +306,22 @@ struct MagicAndBlob BlobDataDescriptor = {
/* .FieldOffset = */ offset, \
},
#define CDAC_TYPE_END(name) { 0, },
#include "datadescriptor.h"
#include "datadescriptor.inc"
},
/* .GlobalLiteralValues = */ {
#define CDAC_GLOBAL(name,tyname,value) { /*.Name = */ GET_GLOBAL_NAME(name), /* .TypeName = */ GET_GLOBALTYPE_NAME(name), /* .Value = */ value },
#include "datadescriptor.h"
#include "datadescriptor.inc"
},
/* .GlobalPointerValues = */ {
#define CDAC_GLOBAL_POINTER(name,value) { /* .Name = */ GET_GLOBAL_NAME(name), /* .PointerDataIndex = */ GET_GLOBAL_POINTER_INDEX(name) },
#include "datadescriptor.h"
#include "datadescriptor.inc"
},
/* .GlobalStringValues = */ {
#define CDAC_GLOBAL_STRING(name,value) { /* .Name = */ GET_GLOBAL_NAME(name), /* .Value = */ GET_GLOBALSTRING_VALUE(name) },
#include "datadescriptor.h"
#include "datadescriptor.inc"
},
/* .NamesPool = */ ("\0" // starts with a nul
@ -331,7 +331,7 @@ struct MagicAndBlob BlobDataDescriptor = {
#define CDAC_GLOBAL_STRING(name,value) #name "\0" STRINGIFY(value) "\0"
#define CDAC_GLOBAL_POINTER(name,value) #name "\0"
#define CDAC_GLOBAL(name,tyname,value) #name "\0" #tyname "\0"
#include "datadescriptor.h"
#include "datadescriptor.inc"
),
/* .EndMagic = */ { 0x01, 0x02, 0x03, 0x04 },

View File

@ -266,8 +266,14 @@ CDAC_TYPE_END(LoaderAllocator)
CDAC_TYPE_BEGIN(PEAssembly)
CDAC_TYPE_INDETERMINATE(PEAssembly)
CDAC_TYPE_FIELD(PEAssembly, /*pointer*/, PEImage, cdac_data<PEAssembly>::PEImage)
CDAC_TYPE_FIELD(PEAssembly, /*pointer*/, AssemblyBinder, cdac_data<PEAssembly>::AssemblyBinder)
CDAC_TYPE_END(PEAssembly)
CDAC_TYPE_BEGIN(AssemblyBinder)
CDAC_TYPE_INDETERMINATE(AssemblyBinder)
CDAC_TYPE_FIELD(AssemblyBinder, /*pointer*/, ManagedAssemblyLoadContext, cdac_data<AssemblyBinder>::ManagedAssemblyLoadContext)
CDAC_TYPE_END(AssemblyBinder)
CDAC_TYPE_BEGIN(PEImage)
CDAC_TYPE_INDETERMINATE(PEImage)
CDAC_TYPE_FIELD(PEImage, /*pointer*/, LoadedImageLayout, cdac_data<PEImage>::LoadedImageLayout)
@ -295,6 +301,7 @@ CDAC_TYPE_BEGIN(AppDomain)
CDAC_TYPE_INDETERMINATE(AppDomain)
CDAC_TYPE_FIELD(AppDomain, /*pointer*/, RootAssembly, cdac_data<AppDomain>::RootAssembly)
CDAC_TYPE_FIELD(AppDomain, /*DomainAssemblyList*/, DomainAssemblyList, cdac_data<AppDomain>::DomainAssemblyList)
CDAC_TYPE_FIELD(AppDomain, /*pointer*/, FriendlyName, cdac_data<AppDomain>::FriendlyName)
CDAC_TYPE_END(AppDomain)
CDAC_TYPE_BEGIN(SystemDomain)

View File

@ -1392,8 +1392,6 @@ int32_t InterpCompiler::GetInterpTypeStackSize(CORINFO_CLASS_HANDLE clsHnd, Inte
size = m_compHnd->getClassSize(clsHnd);
align = m_compHnd->getClassAlignmentRequirement(clsHnd);
assert(align <= INTERP_STACK_ALIGNMENT);
// All vars are stored at 8 byte aligned offsets
if (align < INTERP_STACK_SLOT_SIZE)
align = INTERP_STACK_SLOT_SIZE;
@ -2208,6 +2206,25 @@ static int32_t GetLdindForType(InterpType interpType)
return -1;
}
static bool DoesValueTypeContainGCRefs(COMP_HANDLE compHnd, CORINFO_CLASS_HANDLE clsHnd)
{
unsigned size = compHnd->getClassSize(clsHnd);
// getClassGClayout assumes it's given a buffer of exactly this size
unsigned maxGcPtrs = (size + sizeof(void *) - 1) / sizeof(void *);
BYTE *gcLayout = (BYTE *)alloca(maxGcPtrs + 1);
uint32_t numSlots = compHnd->getClassGClayout(clsHnd, gcLayout);
for (uint32_t i = 0; i < numSlots; ++i)
{
if (gcLayout[i] != TYPE_GC_NONE)
{
return true;
}
}
return false;
}
bool InterpCompiler::EmitNamedIntrinsicCall(NamedIntrinsic ni, CORINFO_CLASS_HANDLE clsHnd, CORINFO_METHOD_HANDLE method, CORINFO_SIG_INFO sig)
{
bool mustExpand = (method == m_methodHnd);
@ -2308,19 +2325,7 @@ bool InterpCompiler::EmitNamedIntrinsicCall(NamedIntrinsic ni, CORINFO_CLASS_HAN
if (isValueType)
{
// Walk the layout to see if any field is a GC pointer
const uint32_t maxSlots = 256;
BYTE gcLayout[maxSlots];
uint32_t numSlots = m_compHnd->getClassGClayout(clsHnd, gcLayout);
for (uint32_t i = 0; i < numSlots; ++i)
{
if (gcLayout[i] != TYPE_GC_NONE)
{
hasGCRefs = true;
break;
}
}
hasGCRefs = DoesValueTypeContainGCRefs(m_compHnd, clsHnd);
}
int32_t result = (!isValueType || hasGCRefs) ? 1 : 0;
@ -2732,12 +2737,17 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re
CORINFO_CALLINFO_FLAGS flags = (CORINFO_CALLINFO_FLAGS)(CORINFO_CALLINFO_ALLOWINSTPARAM | CORINFO_CALLINFO_SECURITYCHECKS | CORINFO_CALLINFO_DISALLOW_STUB);
if (isVirtual)
flags = (CORINFO_CALLINFO_FLAGS)(flags | CORINFO_CALLINFO_CALLVIRT);
flags = (CORINFO_CALLINFO_FLAGS)(flags | CORINFO_CALLINFO_CALLVIRT);
m_compHnd->getCallInfo(&resolvedCallToken, pConstrainedToken, m_methodInfo->ftn, flags, &callInfo);
if (callInfo.methodFlags & CORINFO_FLG_INTRINSIC)
{
if (InterpConfig.InterpMode() >= 3)
// If we are being asked explicitly to compile an intrinsic for interpreting, we need to forcibly enable
// intrinsics for the recursive call. Otherwise we will just recurse infinitely and overflow stack.
// This expansion can produce value that is inconsistent with the value seen by JIT/R2R code that can
// cause user code to misbehave. This is by design. One-off method Interpretation is for internal use only.
bool isMustExpand = (callInfo.hMethod == m_methodHnd);
if ((InterpConfig.InterpMode() == 3) || isMustExpand)
{
NamedIntrinsic ni = GetNamedIntrinsic(m_compHnd, m_methodHnd, callInfo.hMethod);
if (EmitNamedIntrinsicCall(ni, resolvedCallToken.hClass, callInfo.hMethod, callInfo.sig))

View File

@ -1730,10 +1730,8 @@ BasicBlock* AsyncTransformation::CreateResumption(BasicBlock* bloc
BasicBlock* resumeBB = m_comp->fgNewBBafter(BBJ_ALWAYS, m_lastResumptionBB, true);
FlowEdge* remainderEdge = m_comp->fgAddRefPred(remainder, resumeBB);
// It does not really make sense to inherit from the target, but given this
// is always 0% this just propagates the profile weight flag + sets
// BBF_RUN_RARELY.
resumeBB->inheritWeightPercentage(remainder, 0);
resumeBB->bbSetRunRarely();
resumeBB->CopyFlags(remainder, BBF_PROF_WEIGHT);
resumeBB->SetTargetEdge(remainderEdge);
resumeBB->clearTryIndex();
resumeBB->clearHndIndex();

View File

@ -491,7 +491,6 @@ void BasicBlock::dspFlags() const
{BBF_IMPORTED, "i"},
{BBF_IS_LIR, "LIR"},
{BBF_PROF_WEIGHT, "IBC"},
{BBF_RUN_RARELY, "rare"},
{BBF_MARKED, "m"},
{BBF_REMOVED, "del"},
{BBF_DONT_REMOVE, "keep"},
@ -1735,79 +1734,3 @@ bool BasicBlock::StatementCountExceeds(unsigned limit, unsigned* count /* = null
return overLimit;
}
//------------------------------------------------------------------------
// ComplexityExceeds: check if the number of nodes in the trees in the block
// exceeds some limit
//
// Arguments:
// comp - compiler instance
// limit - limit on the number of nodes
// count - [out, optional] actual number of nodes (if less than or equal to limit)
//
// Returns:
// true if the number of nodes is greater than limit
//
bool BasicBlock::ComplexityExceeds(Compiler* comp, unsigned limit, unsigned* count /* = nullptr */)
{
unsigned localCount = 0;
bool overLimit = false;
for (Statement* const stmt : Statements())
{
unsigned slack = limit - localCount;
unsigned actual = 0;
if (comp->gtComplexityExceeds(stmt->GetRootNode(), slack, &actual))
{
overLimit = true;
break;
}
localCount += actual;
}
if (count != nullptr)
{
*count = localCount;
}
return overLimit;
}
//------------------------------------------------------------------------
// ComplexityExceeds: check if the number of nodes in the trees in the blocks
// in the range exceeds some limit
//
// Arguments:
// comp - compiler instance
// limit - limit on the number of nodes
// count - [out, optional] actual number of nodes (if less than or equal to limit)
//
// Returns:
// true if the number of nodes is greater than limit
//
bool BasicBlockRangeList::ComplexityExceeds(Compiler* comp, unsigned limit, unsigned* count /* = nullptr */)
{
unsigned localCount = 0;
bool overLimit = false;
for (BasicBlock* const block : *this)
{
unsigned slack = limit - localCount;
unsigned actual = 0;
if (block->ComplexityExceeds(comp, slack, &actual))
{
overLimit = true;
break;
}
localCount += actual;
}
if (count != nullptr)
{
*count = localCount;
}
return overLimit;
}

View File

@ -415,37 +415,36 @@ enum BasicBlockFlags : uint64_t
BBF_CLONED_FINALLY_BEGIN = MAKE_BBFLAG( 7), // First block of a cloned finally region
BBF_CLONED_FINALLY_END = MAKE_BBFLAG( 8), // Last block of a cloned finally region
BBF_HAS_SUPPRESSGC_CALL = MAKE_BBFLAG( 9), // BB contains a call to a method with SuppressGCTransitionAttribute
BBF_RUN_RARELY = MAKE_BBFLAG(10), // BB is rarely run (catch clauses, blocks with throws etc)
BBF_HAS_LABEL = MAKE_BBFLAG(11), // BB needs a label
BBF_LOOP_ALIGN = MAKE_BBFLAG(12), // Block is lexically the first block in a loop we intend to align.
BBF_HAS_ALIGN = MAKE_BBFLAG(13), // BB ends with 'align' instruction
BBF_HAS_JMP = MAKE_BBFLAG(14), // BB executes a JMP instruction (instead of return)
BBF_GC_SAFE_POINT = MAKE_BBFLAG(15), // BB has a GC safe point (e.g. a call)
BBF_HAS_MDARRAYREF = MAKE_BBFLAG(16), // Block has a multi-dimensional array reference
BBF_HAS_NEWOBJ = MAKE_BBFLAG(17), // BB contains 'new' of an object type.
BBF_HAS_LABEL = MAKE_BBFLAG(10), // BB needs a label
BBF_LOOP_ALIGN = MAKE_BBFLAG(11), // Block is lexically the first block in a loop we intend to align.
BBF_HAS_ALIGN = MAKE_BBFLAG(12), // BB ends with 'align' instruction
BBF_HAS_JMP = MAKE_BBFLAG(13), // BB executes a JMP instruction (instead of return)
BBF_GC_SAFE_POINT = MAKE_BBFLAG(14), // BB has a GC safe point (e.g. a call)
BBF_HAS_MDARRAYREF = MAKE_BBFLAG(15), // Block has a multi-dimensional array reference
BBF_HAS_NEWOBJ = MAKE_BBFLAG(16), // BB contains 'new' of an object type.
BBF_RETLESS_CALL = MAKE_BBFLAG(18), // BBJ_CALLFINALLY that will never return (and therefore, won't need a paired
BBF_RETLESS_CALL = MAKE_BBFLAG(17), // BBJ_CALLFINALLY that will never return (and therefore, won't need a paired
// BBJ_CALLFINALLYRET); see isBBCallFinallyPair().
BBF_COLD = MAKE_BBFLAG(19), // BB is cold
BBF_PROF_WEIGHT = MAKE_BBFLAG(20), // BB weight is computed from profile data
BBF_KEEP_BBJ_ALWAYS = MAKE_BBFLAG(21), // A special BBJ_ALWAYS block, used by EH code generation. Keep the jump kind
BBF_COLD = MAKE_BBFLAG(18), // BB is cold
BBF_PROF_WEIGHT = MAKE_BBFLAG(19), // BB weight is computed from profile data
BBF_KEEP_BBJ_ALWAYS = MAKE_BBFLAG(20), // A special BBJ_ALWAYS block, used by EH code generation. Keep the jump kind
// as BBJ_ALWAYS. Used on x86 for the final step block out of a finally.
BBF_HAS_CALL = MAKE_BBFLAG(22), // BB contains a call
BBF_DOMINATED_BY_EXCEPTIONAL_ENTRY = MAKE_BBFLAG(23), // Block is dominated by exceptional entry.
BBF_BACKWARD_JUMP = MAKE_BBFLAG(24), // BB is surrounded by a backward jump/switch arc
BBF_BACKWARD_JUMP_SOURCE = MAKE_BBFLAG(25), // Block is a source of a backward jump
BBF_BACKWARD_JUMP_TARGET = MAKE_BBFLAG(26), // Block is a target of a backward jump
BBF_PATCHPOINT = MAKE_BBFLAG(27), // Block is a patchpoint
BBF_PARTIAL_COMPILATION_PATCHPOINT = MAKE_BBFLAG(28), // Block is a partial compilation patchpoint
BBF_HAS_HISTOGRAM_PROFILE = MAKE_BBFLAG(29), // BB contains a call needing a histogram profile
BBF_TAILCALL_SUCCESSOR = MAKE_BBFLAG(30), // BB has pred that has potential tail call
BBF_RECURSIVE_TAILCALL = MAKE_BBFLAG(31), // Block has recursive tailcall that may turn into a loop
BBF_NO_CSE_IN = MAKE_BBFLAG(32), // Block should kill off any incoming CSE
BBF_CAN_ADD_PRED = MAKE_BBFLAG(33), // Ok to add pred edge to this block, even when "safe" edge creation disabled
BBF_HAS_VALUE_PROFILE = MAKE_BBFLAG(34), // Block has a node that needs a value probing
BBF_HAS_NEWARR = MAKE_BBFLAG(35), // BB contains 'new' of an array type.
BBF_MAY_HAVE_BOUNDS_CHECKS = MAKE_BBFLAG(36), // BB *likely* has a bounds check (after rangecheck phase).
BBF_ASYNC_RESUMPTION = MAKE_BBFLAG(37), // Block is a resumption block in an async method
BBF_HAS_CALL = MAKE_BBFLAG(21), // BB contains a call
BBF_DOMINATED_BY_EXCEPTIONAL_ENTRY = MAKE_BBFLAG(22), // Block is dominated by exceptional entry.
BBF_BACKWARD_JUMP = MAKE_BBFLAG(23), // BB is surrounded by a backward jump/switch arc
BBF_BACKWARD_JUMP_SOURCE = MAKE_BBFLAG(24), // Block is a source of a backward jump
BBF_BACKWARD_JUMP_TARGET = MAKE_BBFLAG(25), // Block is a target of a backward jump
BBF_PATCHPOINT = MAKE_BBFLAG(26), // Block is a patchpoint
BBF_PARTIAL_COMPILATION_PATCHPOINT = MAKE_BBFLAG(27), // Block is a partial compilation patchpoint
BBF_HAS_HISTOGRAM_PROFILE = MAKE_BBFLAG(28), // BB contains a call needing a histogram profile
BBF_TAILCALL_SUCCESSOR = MAKE_BBFLAG(29), // BB has pred that has potential tail call
BBF_RECURSIVE_TAILCALL = MAKE_BBFLAG(30), // Block has recursive tailcall that may turn into a loop
BBF_NO_CSE_IN = MAKE_BBFLAG(31), // Block should kill off any incoming CSE
BBF_CAN_ADD_PRED = MAKE_BBFLAG(32), // Ok to add pred edge to this block, even when "safe" edge creation disabled
BBF_HAS_VALUE_PROFILE = MAKE_BBFLAG(33), // Block has a node that needs a value probing
BBF_HAS_NEWARR = MAKE_BBFLAG(34), // BB contains 'new' of an array type.
BBF_MAY_HAVE_BOUNDS_CHECKS = MAKE_BBFLAG(35), // BB *likely* has a bounds check (after rangecheck phase).
BBF_ASYNC_RESUMPTION = MAKE_BBFLAG(36), // Block is a resumption block in an async method
// The following are sets of flags.
@ -468,7 +467,6 @@ enum BasicBlockFlags : uint64_t
// Flags gained by the bottom block when a block is split.
// Note, this is a conservative guess.
// For example, the bottom block might or might not have BBF_HAS_NEWARR, but we assume it has BBF_HAS_NEWARR.
// TODO: Should BBF_RUN_RARELY be added to BBF_SPLIT_GAINED ?
BBF_SPLIT_GAINED = BBF_DONT_REMOVE | BBF_HAS_JMP | BBF_BACKWARD_JUMP | BBF_PROF_WEIGHT | BBF_HAS_NEWARR | \
BBF_HAS_NEWOBJ | BBF_KEEP_BBJ_ALWAYS | BBF_CLONED_FINALLY_END | BBF_HAS_HISTOGRAM_PROFILE | BBF_HAS_VALUE_PROFILE | BBF_HAS_MDARRAYREF | BBF_NEEDS_GCPOLL | BBF_MAY_HAVE_BOUNDS_CHECKS | BBF_ASYNC_RESUMPTION,
@ -1169,9 +1167,16 @@ public:
unsigned bbRefs; // number of blocks that can reach here, either by fall-through or a branch. If this falls to zero,
// the block is unreachable.
#define BB_UNITY_WEIGHT 100.0 // how much a normal execute once block weighs
#define BB_UNITY_WEIGHT_UNSIGNED 100 // how much a normal execute once block weighs
#define BB_LOOP_WEIGHT_SCALE 8.0 // synthetic profile scale factor for loops
#define BB_ZERO_WEIGHT 0.0
#define BB_COLD_WEIGHT 0.01 // Upper bound for cold weights; used during block layout
#define BB_MAX_WEIGHT FLT_MAX // maximum finite weight -- needs rethinking.
bool isRunRarely() const
{
return HasFlag(BBF_RUN_RARELY);
return (bbWeight == BB_ZERO_WEIGHT);
}
bool isLoopAlign() const
@ -1196,13 +1201,6 @@ public:
const char* dspToString(int blockNumPadding = 0) const;
#endif // DEBUG
#define BB_UNITY_WEIGHT 100.0 // how much a normal execute once block weighs
#define BB_UNITY_WEIGHT_UNSIGNED 100 // how much a normal execute once block weighs
#define BB_LOOP_WEIGHT_SCALE 8.0 // synthetic profile scale factor for loops
#define BB_ZERO_WEIGHT 0.0
#define BB_COLD_WEIGHT 0.01 // Upper bound for cold weights; used during block layout
#define BB_MAX_WEIGHT FLT_MAX // maximum finite weight -- needs rethinking.
weight_t bbWeight; // The dynamic execution weight of this block
// getCalledCount -- get the value used to normalize weights for this method
@ -1235,15 +1233,6 @@ public:
{
this->SetFlags(BBF_PROF_WEIGHT);
this->bbWeight = weight;
if (weight == BB_ZERO_WEIGHT)
{
this->SetFlags(BBF_RUN_RARELY);
}
else
{
this->RemoveFlags(BBF_RUN_RARELY);
}
}
// increaseBBProfileWeight -- Increase the profile-derived weight for a basic block
@ -1278,25 +1267,10 @@ public:
{
assert(0 <= percentage && percentage <= 100);
this->bbWeight = (bSrc->bbWeight * percentage) / 100;
if (bSrc->hasProfileWeight())
{
this->SetFlags(BBF_PROF_WEIGHT);
}
else
{
this->RemoveFlags(BBF_PROF_WEIGHT);
}
if (this->bbWeight == BB_ZERO_WEIGHT)
{
this->SetFlags(BBF_RUN_RARELY);
}
else
{
this->RemoveFlags(BBF_RUN_RARELY);
}
this->bbWeight = (bSrc->bbWeight * percentage) / 100;
const BasicBlockFlags hasProfileWeight = bSrc->GetFlagsRaw() & BBF_PROF_WEIGHT;
this->RemoveFlags(BBF_PROF_WEIGHT);
this->SetFlags(hasProfileWeight);
}
// Scale a blocks' weight by some factor.
@ -1304,18 +1278,9 @@ public:
void scaleBBWeight(weight_t scale)
{
this->bbWeight = this->bbWeight * scale;
if (this->bbWeight == BB_ZERO_WEIGHT)
{
this->SetFlags(BBF_RUN_RARELY);
}
else
{
this->RemoveFlags(BBF_RUN_RARELY);
}
}
// Set block weight to zero, and set run rarely flag.
// Set block weight to zero.
//
void bbSetRunRarely()
{
@ -1779,7 +1744,9 @@ public:
//
unsigned StatementCount();
bool StatementCountExceeds(unsigned limit, unsigned* count = nullptr);
bool ComplexityExceeds(Compiler* comp, unsigned limit, unsigned* complexity = nullptr);
template <typename TFunc>
bool ComplexityExceeds(Compiler* comp, unsigned limit, TFunc getTreeComplexity);
GenTree* lastNode() const;
@ -1806,71 +1773,6 @@ public:
{
}
// Iteratable collection of successors of a block.
template <typename TPosition>
class Successors
{
Compiler* m_comp;
BasicBlock* m_block;
public:
Successors(Compiler* comp, BasicBlock* block)
: m_comp(comp)
, m_block(block)
{
}
class iterator
{
Compiler* m_comp;
BasicBlock* m_block;
TPosition m_pos;
public:
iterator(Compiler* comp, BasicBlock* block)
: m_comp(comp)
, m_block(block)
, m_pos(comp, block)
{
}
iterator()
: m_pos()
{
}
void operator++(void)
{
m_pos.Advance(m_comp, m_block);
}
BasicBlock* operator*()
{
return m_pos.Current(m_comp, m_block);
}
bool operator==(const iterator& other)
{
return m_pos == other.m_pos;
}
bool operator!=(const iterator& other)
{
return m_pos != other.m_pos;
}
};
iterator begin()
{
return iterator(m_comp, m_block);
}
iterator end()
{
return iterator();
}
};
template <typename TFunc>
BasicBlockVisit VisitEHEnclosedHandlerSecondPassSuccs(Compiler* comp, TFunc func);
@ -2086,7 +1988,8 @@ public:
return BasicBlockIterator(m_end->Next()); // walk until we see the block *following* the `m_end` block
}
bool ComplexityExceeds(Compiler* comp, unsigned limit, unsigned* count = nullptr);
template <typename TFunc>
bool ComplexityExceeds(Compiler* comp, unsigned limit, TFunc getTreeComplexity);
};
// BBJumpTable -- descriptor blocks with N successors

View File

@ -3602,11 +3602,11 @@ public:
void gtUpdateNodeOperSideEffects(GenTree* tree);
// Returns "true" iff the complexity (not formally defined, but first interpretation
// is #of nodes in subtree) of "tree" is greater than "limit".
// Returns "true" iff the complexity (defined by 'getComplexity') of "tree" is greater than "limit".
// (This is somewhat redundant with the "GetCostEx()/GetCostSz()" fields, but can be used
// before they have been set.)
bool gtComplexityExceeds(GenTree* tree, unsigned limit, unsigned* complexity = nullptr);
// before they have been set, if 'getComplexity' is independent of them.)
template <typename TFunc>
bool gtComplexityExceeds(GenTree* tree, unsigned limit, TFunc getComplexity);
GenTree* gtReverseCond(GenTree* tree);
@ -7007,7 +7007,8 @@ public:
bool optCanonicalizeExits(FlowGraphNaturalLoop* loop);
bool optCanonicalizeExit(FlowGraphNaturalLoop* loop, BasicBlock* exit);
bool optLoopComplexityExceeds(FlowGraphNaturalLoop* loop, unsigned limit);
template <typename TFunc>
bool optLoopComplexityExceeds(FlowGraphNaturalLoop* loop, unsigned limit, TFunc getTreeComplexity);
PhaseStatus optCloneLoops();
PhaseStatus optRangeCheckCloning();

View File

@ -5411,6 +5411,171 @@ BasicBlockVisit FlowGraphNaturalLoop::VisitRegularExitBlocks(TFunc func)
return BasicBlockVisit::Continue;
}
//-----------------------------------------------------------
// gtComplexityExceeds: Check if a tree exceeds a specified complexity limit.
//
// Type parameters:
// TFunc - Callback functor type
// Arguments:
// tree - The tree to check
// limit - complexity limit
// getTreeComplexity - Callback functor that takes a GenTree* and returns its complexity
//
// Return Value:
// True if 'tree' exceeds the complexity limit, otherwise false.
//
template <typename TFunc>
bool Compiler::gtComplexityExceeds(GenTree* tree, unsigned limit, TFunc getComplexity)
{
struct ComplexityVisitor : GenTreeVisitor<ComplexityVisitor>
{
enum
{
DoPreOrder = true,
};
ComplexityVisitor(Compiler* comp, unsigned limit, TFunc getComplexity)
: GenTreeVisitor<ComplexityVisitor>(comp)
, m_complexity(0)
, m_limit(limit)
, m_getComplexity(getComplexity)
{
}
fgWalkResult PreOrderVisit(GenTree** use, GenTree* user)
{
m_complexity += m_getComplexity(*use);
return (m_complexity > m_limit) ? WALK_ABORT : WALK_CONTINUE;
}
private:
unsigned m_complexity;
const unsigned m_limit;
TFunc m_getComplexity;
};
assert(tree != nullptr);
ComplexityVisitor visitor(this, limit, getComplexity);
fgWalkResult result = visitor.WalkTree(&tree, nullptr);
return (result == WALK_ABORT);
}
//------------------------------------------------------------------------
// ComplexityExceeds: Check if the trees in a block exceed a specified complexity limit.
//
// Type parameters:
// TFunc - Callback functor type
//
// Arguments:
// comp - compiler instance
// limit - complexity limit
// getTreeComplexity - Callback functor that takes a GenTree* and returns its complexity
//
// Returns:
// True if the trees in the block exceed the complexity limit, otherwise false.
//
template <typename TFunc>
bool BasicBlock::ComplexityExceeds(Compiler* comp, unsigned limit, TFunc getTreeComplexity)
{
assert(comp != nullptr);
unsigned localCount = 0;
auto getComplexity = [&](GenTree* tree) -> unsigned {
const unsigned treeComplexity = getTreeComplexity(tree);
localCount += treeComplexity;
return treeComplexity;
};
for (Statement* const stmt : Statements())
{
const unsigned slack = limit - localCount;
if (comp->gtComplexityExceeds(stmt->GetRootNode(), slack, getComplexity))
{
return true;
}
}
return false;
}
//------------------------------------------------------------------------
// ComplexityExceeds: Check if the trees in a range of blocks exceed a specified complexity limit.
//
// Type parameters:
// TFunc - Callback functor type
//
// Arguments:
// comp - compiler instance
// limit - complexity limit
// getTreeComplexity - Callback functor that takes a GenTree* and returns its complexity
//
// Returns:
// True if the trees in the block range exceed the complexity limit, otherwise false.
//
template <typename TFunc>
bool BasicBlockRangeList::ComplexityExceeds(Compiler* comp, unsigned limit, TFunc getTreeComplexity)
{
assert(comp != nullptr);
unsigned localCount = 0;
auto getComplexity = [&](GenTree* tree) -> unsigned {
const unsigned treeComplexity = getTreeComplexity(tree);
localCount += treeComplexity;
return treeComplexity;
};
for (BasicBlock* const block : *this)
{
const unsigned slack = limit - localCount;
if (block->ComplexityExceeds(comp, slack, getComplexity))
{
return true;
}
}
return false;
}
//------------------------------------------------------------------------
// optLoopComplexityExceeds: Check if the trees in a loop exceed a specified complexity limit.
//
// Type parameters:
// TFunc - Callback functor type
//
// Arguments:
// comp - compiler instance
// limit - complexity limit
// getTreeComplexity - Callback functor that takes a GenTree* and returns its complexity
//
// Returns:
// True if the trees in 'loop' exceed the complexity limit, otherwise false.
//
template <typename TFunc>
bool Compiler::optLoopComplexityExceeds(FlowGraphNaturalLoop* loop, unsigned limit, TFunc getTreeComplexity)
{
assert(loop != nullptr);
unsigned loopComplexity = 0;
auto getComplexity = [&](GenTree* tree) -> unsigned {
const unsigned treeComplexity = getTreeComplexity(tree);
loopComplexity += treeComplexity;
return treeComplexity;
};
BasicBlockVisit const result = loop->VisitLoopBlocks([&](BasicBlock* block) {
assert(limit >= loopComplexity);
const unsigned slack = limit - loopComplexity;
return block->ComplexityExceeds(this, slack, getComplexity) ? BasicBlockVisit::Abort
: BasicBlockVisit::Continue;
});
return (result == BasicBlockVisit::Abort);
}
/*****************************************************************************/
#endif //_COMPILER_HPP_
/*****************************************************************************/

View File

@ -5195,7 +5195,11 @@ inline UNATIVE_OFFSET emitter::emitInsSizeSVCalcDisp(instrDesc* id, code_t code,
{
ssize_t compressedDsp;
if (TryEvexCompressDisp8Byte(id, dsp, &compressedDsp, &dspInByte))
// Only the scaling factor of the original EVEX instructions can be changed by embedded broadcast.
// If the instruction does not have tuple type info, say extended EVEX from APX, the scaling factor is
// constantly 1, then this optimization cannot be performed, and whether disp8 or disp32 should be applied
// only depends dspInByte.
if (TryEvexCompressDisp8Byte(id, dsp, &compressedDsp, &dspInByte) && hasTupleTypeInfo(ins))
{
SetEvexCompressedDisplacement(id);
}
@ -5368,7 +5372,7 @@ UNATIVE_OFFSET emitter::emitInsSizeAM(instrDesc* id, code_t code)
{
ssize_t compressedDsp;
if (TryEvexCompressDisp8Byte(id, dsp, &compressedDsp, &dspInByte))
if (TryEvexCompressDisp8Byte(id, dsp, &compressedDsp, &dspInByte) && hasTupleTypeInfo(ins))
{
SetEvexCompressedDisplacement(id);
}
@ -14672,13 +14676,16 @@ GOT_DSP:
assert(isCompressed && dspInByte);
dsp = compressedDsp;
}
else if (TakesEvexPrefix(id) || TakesApxExtendedEvexPrefix(id))
else if (TakesEvexPrefix(id) && !IsBMIInstruction(ins))
{
assert(!TryEvexCompressDisp8Byte(id, dsp, &compressedDsp, &dspInByte));
assert(!(TryEvexCompressDisp8Byte(id, dsp, &compressedDsp, &dspInByte) && hasTupleTypeInfo(ins)));
dspInByte = false;
}
else
{
// TODO-XArch-APX: for now, Extended Evex instruction will not have compressed displacement, or more
// accurately, extended evex may not have compressed displacement optimization as the scaling factor is
// constantly 1.
dspInByte = ((signed char)dsp == (ssize_t)dsp);
}
dspIsZero = (dsp == 0);
@ -15556,7 +15563,7 @@ BYTE* emitter::emitOutputSV(BYTE* dst, instrDesc* id, code_t code, CnsVal* addc)
assert(isCompressed && dspInByte);
dsp = (int)compressedDsp;
}
else if (TakesEvexPrefix(id) || TakesApxExtendedEvexPrefix(id))
else if (TakesEvexPrefix(id) && !IsBMIInstruction(ins))
{
#if FEATURE_FIXED_OUT_ARGS
// TODO-AMD64-CQ: We should be able to accurately predict this when FEATURE_FIXED_OUT_ARGS
@ -17904,6 +17911,7 @@ BYTE* emitter::emitOutputLJ(insGroup* ig, BYTE* dst, instrDesc* i)
idAmd->idCodeSize(sz);
code = insCodeRM(ins);
code = AddX86PrefixIfNeeded(id, code, id->idOpSize());
code |= (insEncodeReg345(id, id->idReg1(), EA_PTRSIZE, &code) << 8);
dst = emitOutputAM(dst, idAmd, code, nullptr);

View File

@ -1245,7 +1245,9 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed
case NI_SRCS_UNSAFE_AreSame:
case NI_SRCS_UNSAFE_ByteOffset:
case NI_SRCS_UNSAFE_IsAddressGreaterThan:
case NI_SRCS_UNSAFE_IsAddressGreaterThanOrEqualTo:
case NI_SRCS_UNSAFE_IsAddressLessThan:
case NI_SRCS_UNSAFE_IsAddressLessThanOrEqualTo:
case NI_SRCS_UNSAFE_IsNullRef:
case NI_SRCS_UNSAFE_Subtract:
case NI_SRCS_UNSAFE_SubtractByteOffset:
@ -1260,7 +1262,9 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed
{
case NI_SRCS_UNSAFE_AreSame:
case NI_SRCS_UNSAFE_IsAddressGreaterThan:
case NI_SRCS_UNSAFE_IsAddressGreaterThanOrEqualTo:
case NI_SRCS_UNSAFE_IsAddressLessThan:
case NI_SRCS_UNSAFE_IsAddressLessThanOrEqualTo:
case NI_SRCS_UNSAFE_IsNullRef:
{
fgObserveInlineConstants(opcode, pushedStack, isInlining);

View File

@ -828,7 +828,7 @@ bool Compiler::fgDumpFlowGraph(Phases phase, PhasePosition pos)
const bool isTryEntryBlock = bbIsTryBeg(block);
const bool isFuncletEntryBlock = fgFuncletsCreated && bbIsFuncletBeg(block);
if (isTryEntryBlock || isFuncletEntryBlock || block->HasAnyFlag(BBF_RUN_RARELY | BBF_LOOP_ALIGN))
if (isTryEntryBlock || isFuncletEntryBlock || block->HasFlag(BBF_LOOP_ALIGN))
{
// Display a very few, useful, block flags
fprintf(fgxFile, " [");
@ -840,10 +840,6 @@ bool Compiler::fgDumpFlowGraph(Phases phase, PhasePosition pos)
{
fprintf(fgxFile, "F");
}
if (block->HasFlag(BBF_RUN_RARELY))
{
fprintf(fgxFile, "R");
}
if (block->HasFlag(BBF_LOOP_ALIGN))
{
fprintf(fgxFile, "A");
@ -3133,16 +3129,6 @@ void Compiler::fgDebugCheckBBlist(bool checkBBNum /* = false */, bool checkBBRef
}
}
}
/* Check if BBF_RUN_RARELY is set that we have bbWeight of zero */
if (block->isRunRarely())
{
assert(block->bbWeight == BB_ZERO_WEIGHT);
}
else
{
assert(block->bbWeight > BB_ZERO_WEIGHT);
}
}
assert(fgBBcount == numBlocks);
@ -3942,6 +3928,12 @@ void Compiler::fgDebugCheckBlockLinks()
// If this is a switch, check that the tables are consistent.
if (block->KindIs(BBJ_SWITCH))
{
// Switch blocks with dominant cases must have profile-derived weights.
if (block->GetSwitchTargets()->HasDominantCase())
{
assert(block->hasProfileWeight());
}
// Create a set with all the successors.
BitVecTraits bitVecTraits(fgBBNumMax + 1, this);
BitVec succBlocks(BitVecOps::MakeEmpty(&bitVecTraits));

View File

@ -2505,7 +2505,7 @@ BasicBlock* Compiler::fgCloneTryRegion(BasicBlock* tryEntry, CloneTryInfo& info,
//
// We need to clone to the entire try region plus any
// enclosed regions and any enclosing mutual protect regions,
// plus all the the associated handlers and filters and any
// plus all the associated handlers and filters and any
// regions they enclose, plus any callfinallies that follow.
//
// This is necessary because try regions can't have multiple entries, or

View File

@ -1591,8 +1591,8 @@ void Compiler::fgInsertInlineeBlocks(InlineInfo* pInlineInfo)
noway_assert((inlineeBlockFlags & BBF_HAS_JMP) == 0);
noway_assert((inlineeBlockFlags & BBF_KEEP_BBJ_ALWAYS) == 0);
// Todo: we may want to exclude other flags here.
iciBlock->SetFlags(inlineeBlockFlags & ~BBF_RUN_RARELY);
// Todo: we may want to exclude some flags here.
iciBlock->SetFlags(inlineeBlockFlags);
#ifdef DEBUG
if (verbose)

View File

@ -2538,11 +2538,11 @@ bool Compiler::fgOptimizeBranch(BasicBlock* bJump)
if (fgIsUsingProfileWeights())
{
// Only rely upon the profile weight when all three of these blocks
// have either good profile weights or are rarelyRun
// have either good profile weights or are rarely run
//
if (bJump->HasAnyFlag(BBF_PROF_WEIGHT | BBF_RUN_RARELY) &&
bDest->HasAnyFlag(BBF_PROF_WEIGHT | BBF_RUN_RARELY) &&
trueTarget->HasAnyFlag(BBF_PROF_WEIGHT | BBF_RUN_RARELY))
if ((bJump->hasProfileWeight() || bJump->isRunRarely()) &&
(bDest->hasProfileWeight() || bDest->isRunRarely()) &&
(trueTarget->hasProfileWeight() || trueTarget->isRunRarely()))
{
haveProfileWeights = true;
@ -3132,7 +3132,6 @@ bool Compiler::fgExpandRarelyRunBlocks()
// Set the BBJ_CALLFINALLY block to the same weight as the BBJ_CALLFINALLYRET block and
// mark it rarely run.
bPrev->bbWeight = block->bbWeight;
bPrev->SetFlags(BBF_RUN_RARELY);
#ifdef DEBUG
if (verbose)
{
@ -3147,7 +3146,6 @@ bool Compiler::fgExpandRarelyRunBlocks()
// Set the BBJ_CALLFINALLYRET block to the same weight as the BBJ_CALLFINALLY block and
// mark it rarely run.
block->bbWeight = bPrev->bbWeight;
block->SetFlags(BBF_RUN_RARELY);
#ifdef DEBUG
if (verbose)
{

View File

@ -4345,14 +4345,6 @@ bool Compiler::fgComputeMissingBlockWeights()
changed = true;
modified = true;
bDst->bbWeight = newWeight;
if (newWeight == BB_ZERO_WEIGHT)
{
bDst->SetFlags(BBF_RUN_RARELY);
}
else
{
bDst->RemoveFlags(BBF_RUN_RARELY);
}
}
}
else if (!bDst->hasProfileWeight() && bbIsHandlerBeg(bDst) && !bDst->isRunRarely())

View File

@ -569,8 +569,11 @@ bool Compiler::fgForwardSubStatement(Statement* stmt)
// Consider instead using the height of the fwdSubNode.
//
unsigned const nodeLimit = 16;
auto countNode = [](GenTree* tree) -> unsigned {
return 1;
};
if (gtComplexityExceeds(fwdSubNode, nodeLimit))
if (gtComplexityExceeds(fwdSubNode, nodeLimit, countNode))
{
JITDUMP(" tree to sub has more than %u nodes\n", nodeLimit);
return false;
@ -633,7 +636,7 @@ bool Compiler::fgForwardSubStatement(Statement* stmt)
// height of the fwdSubNode.
//
unsigned const nextTreeLimit = 200;
if ((fsv.GetComplexity() > nextTreeLimit) && gtComplexityExceeds(fwdSubNode, 1))
if ((fsv.GetComplexity() > nextTreeLimit) && gtComplexityExceeds(fwdSubNode, 1, countNode))
{
JITDUMP(" next stmt tree is too large (%u)\n", fsv.GetComplexity());
return false;

View File

@ -17867,65 +17867,6 @@ ExceptionSetFlags Compiler::gtCollectExceptions(GenTree* tree)
return walker.GetFlags();
}
//-----------------------------------------------------------
// gtComplexityExceeds: Check if a tree exceeds a specified complexity in terms
// of number of sub nodes.
//
// Arguments:
// tree - The tree to check
// limit - The limit in terms of number of nodes
// complexity - [out, optional] the actual node count (if not greater than limit)
//
// Return Value:
// True if there are more than limit nodes in tree; otherwise false.
//
bool Compiler::gtComplexityExceeds(GenTree* tree, unsigned limit, unsigned* complexity)
{
struct ComplexityVisitor : GenTreeVisitor<ComplexityVisitor>
{
enum
{
DoPreOrder = true,
};
ComplexityVisitor(Compiler* comp, unsigned limit)
: GenTreeVisitor(comp)
, m_limit(limit)
{
}
fgWalkResult PreOrderVisit(GenTree** use, GenTree* user)
{
if (++m_numNodes > m_limit)
{
return WALK_ABORT;
}
return WALK_CONTINUE;
}
unsigned NumNodes()
{
return m_numNodes;
}
private:
unsigned m_limit;
unsigned m_numNodes = 0;
};
ComplexityVisitor visitor(this, limit);
fgWalkResult result = visitor.WalkTree(&tree, nullptr);
if (complexity != nullptr)
{
*complexity = visitor.NumNodes();
}
return (result == WALK_ABORT);
}
bool GenTree::IsPhiNode()
{
return (OperGet() == GT_PHI_ARG) || (OperGet() == GT_PHI) || IsPhiDefn();
@ -28520,22 +28461,6 @@ bool GenTree::OperIsConvertVectorToMask() const
return false;
}
//------------------------------------------------------------------------
// OperIsVectorConditionalSelect: Is this a vector ConditionalSelect hwintrinsic
//
// Return Value:
// true if the node is a vector ConditionalSelect hwintrinsic
// otherwise; false
//
bool GenTree::OperIsVectorConditionalSelect() const
{
if (OperIsHWIntrinsic())
{
return AsHWIntrinsic()->OperIsVectorConditionalSelect();
}
return false;
}
//------------------------------------------------------------------------
// OperIsVectorFusedMultiplyOp: Is this a vector FusedMultiplyOp hwintrinsic
//

View File

@ -1715,7 +1715,6 @@ public:
bool OperIsHWIntrinsic(NamedIntrinsic intrinsicId) const;
bool OperIsConvertMaskToVector() const;
bool OperIsConvertVectorToMask() const;
bool OperIsVectorConditionalSelect() const;
bool OperIsVectorFusedMultiplyOp() const;
// This is here for cleaner GT_LONG #ifdefs.
@ -6576,34 +6575,6 @@ struct GenTreeHWIntrinsic : public GenTreeJitIntrinsic
#endif
}
bool OperIsVectorConditionalSelect() const
{
switch (GetHWIntrinsicId())
{
#if defined(TARGET_XARCH)
case NI_Vector128_ConditionalSelect:
case NI_Vector256_ConditionalSelect:
case NI_Vector512_ConditionalSelect:
{
return true;
}
#endif // TARGET_XARCH
#if defined(TARGET_ARM64)
case NI_AdvSimd_BitwiseSelect:
case NI_Sve_ConditionalSelect:
{
return true;
}
#endif // TARGET_ARM64
default:
{
return false;
}
}
}
bool OperIsVectorFusedMultiplyOp() const
{
switch (GetHWIntrinsicId())

View File

@ -5078,7 +5078,6 @@ void Compiler::impImportLeave(BasicBlock* block)
}
step2->inheritWeight(block);
step2->CopyFlags(block, BBF_RUN_RARELY);
step2->SetFlags(BBF_IMPORTED);
#ifdef DEBUG
@ -5361,10 +5360,9 @@ void Compiler::impResetLeaveBlock(BasicBlock* block, unsigned jmpAddr)
// b) weight zero
// c) prevent from being imported
// d) as internal
// e) as rarely run
dupBlock->bbRefs = 0;
dupBlock->bbWeight = BB_ZERO_WEIGHT;
dupBlock->SetFlags(BBF_IMPORTED | BBF_INTERNAL | BBF_RUN_RARELY);
dupBlock->bbRefs = 0;
dupBlock->bbSetRunRarely();
dupBlock->SetFlags(BBF_IMPORTED | BBF_INTERNAL);
// Insert the block right after the block which is getting reset so that BBJ_CALLFINALLY and BBJ_ALWAYS
// will be next to each other.

View File

@ -5503,6 +5503,25 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic,
return gtFoldExpr(tmp);
}
case NI_SRCS_UNSAFE_IsAddressGreaterThanOrEqualTo:
{
assert(sig->sigInst.methInstCount == 1);
// ldarg.0
// ldarg.1
// clt.un
// ldc.i4.0
// ceq
// ret
GenTree* op2 = impPopStack().val;
GenTree* op1 = impPopStack().val;
GenTree* tmp = gtNewOperNode(GT_GE, TYP_INT, op1, op2);
tmp->gtFlags |= GTF_UNSIGNED;
return gtFoldExpr(tmp);
}
case NI_SRCS_UNSAFE_IsAddressLessThan:
{
assert(sig->sigInst.methInstCount == 1);
@ -5520,6 +5539,25 @@ GenTree* Compiler::impSRCSUnsafeIntrinsic(NamedIntrinsic intrinsic,
return gtFoldExpr(tmp);
}
case NI_SRCS_UNSAFE_IsAddressLessThanOrEqualTo:
{
assert(sig->sigInst.methInstCount == 1);
// ldarg.0
// ldarg.1
// cgt.un
// ldc.i4.0
// ceq
// ret
GenTree* op2 = impPopStack().val;
GenTree* op1 = impPopStack().val;
GenTree* tmp = gtNewOperNode(GT_LE, TYP_INT, op1, op2);
tmp->gtFlags |= GTF_UNSIGNED;
return gtFoldExpr(tmp);
}
case NI_SRCS_UNSAFE_IsNullRef:
{
assert(sig->sigInst.methInstCount == 1);
@ -10722,10 +10760,18 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
{
result = NI_SRCS_UNSAFE_IsAddressGreaterThan;
}
else if (strcmp(methodName, "IsAddressGreaterThanOrEqualTo") == 0)
{
result = NI_SRCS_UNSAFE_IsAddressGreaterThanOrEqualTo;
}
else if (strcmp(methodName, "IsAddressLessThan") == 0)
{
result = NI_SRCS_UNSAFE_IsAddressLessThan;
}
else if (strcmp(methodName, "IsAddressLessThanOrEqualTo") == 0)
{
result = NI_SRCS_UNSAFE_IsAddressLessThanOrEqualTo;
}
else if (strcmp(methodName, "IsNullRef") == 0)
{
result = NI_SRCS_UNSAFE_IsNullRef;

View File

@ -692,7 +692,7 @@ void CodeGen::inst_SET(emitJumpKind condition, regNumber reg, insOpts instOption
assert(INS_setge == (INS_setge_apx + offset));
assert(INS_setle == (INS_setle_apx + offset));
assert(INS_setg == (INS_setg_apx + offset));
ins = (instruction)(ins + offset);
ins = (instruction)(ins - offset);
}
#endif

View File

@ -1241,10 +1241,13 @@ public:
break;
case GT_CAST:
{
assert(TopValue(1).Node() == node);
assert(TopValue(0).Node() == node->AsCast()->CastOp());
if (!node->TypeIs(TYP_I_IMPL, TYP_BYREF) || node->gtOverflow() || !TopValue(0).IsAddress() ||
var_types castToType = node->CastToType();
bool isPtrCast = (castToType == TYP_I_IMPL) || (castToType == TYP_U_IMPL) || (castToType == TYP_BYREF);
if (!isPtrCast || node->gtOverflow() || !TopValue(0).IsAddress() ||
!TopValue(1).AddOffset(TopValue(0), 0))
{
EscapeValue(TopValue(0), node);
@ -1252,7 +1255,7 @@ public:
PopValue();
break;
}
case GT_CALL:
while (TopValue(0).Node() != node)
{

View File

@ -3080,6 +3080,10 @@ PhaseStatus Compiler::optCloneLoops()
bool allTrue = false;
bool anyFalse = false;
const int sizeLimit = JitConfig.JitCloneLoopsSizeLimit();
auto countNode = [](GenTree* tree) -> unsigned {
return 1;
};
context.EvaluateConditions(loop->GetIndex(), &allTrue, &anyFalse DEBUGARG(verbose));
if (anyFalse)
{
@ -3102,8 +3106,9 @@ PhaseStatus Compiler::optCloneLoops()
// tree nodes in all statements in all blocks in the loop.
// This value is compared to a hard-coded threshold, and if bigger,
// then the method returns false.
else if ((sizeLimit >= 0) && optLoopComplexityExceeds(loop, (unsigned)sizeLimit))
else if ((sizeLimit >= 0) && optLoopComplexityExceeds(loop, (unsigned)sizeLimit, countNode))
{
JITDUMP(FMT_LP " exceeds cloning size limit %d\n", loop->GetIndex(), sizeLimit);
context.CancelLoopOptInfo(loop->GetIndex());
}
}

View File

@ -1157,12 +1157,12 @@ int LinearScan::BuildShiftRotate(GenTree* tree)
#endif
if (!source->isContained())
{
tgtPrefUse = BuildUse(source, srcCandidates);
tgtPrefUse = BuildUse(source, ForceLowGprForApxIfNeeded(source, srcCandidates, getEvexIsSupported()));
srcCount++;
}
else
{
srcCount += BuildOperandUses(source, srcCandidates);
srcCount += BuildOperandUses(source, ForceLowGprForApxIfNeeded(source, srcCandidates, getEvexIsSupported()));
}
if (!tree->isContained())
@ -1172,6 +1172,9 @@ int LinearScan::BuildShiftRotate(GenTree* tree)
srcCount += BuildDelayFreeUses(shiftBy, source, SRBM_RCX);
buildKillPositionsForNode(tree, currentLoc + 1, SRBM_RCX);
}
dstCandidates = (tree->GetRegNum() == REG_NA)
? ForceLowGprForApxIfNeeded(tree, dstCandidates, getEvexIsSupported())
: dstCandidates;
BuildDef(tree, dstCandidates);
}
else
@ -3280,8 +3283,8 @@ int LinearScan::BuildMul(GenTree* tree)
srcCandidates1 = SRBM_RDX;
}
srcCount = BuildOperandUses(op1, srcCandidates1);
srcCount += BuildOperandUses(op2, srcCandidates2);
srcCount = BuildOperandUses(op1, ForceLowGprForApxIfNeeded(op1, srcCandidates1, getEvexIsSupported()));
srcCount += BuildOperandUses(op2, ForceLowGprForApxIfNeeded(op2, srcCandidates2, getEvexIsSupported()));
#if defined(TARGET_X86)
if (tree->OperIs(GT_MUL_LONG))

View File

@ -2987,8 +2987,11 @@ GenTree* Compiler::fgMorphIndexAddr(GenTreeIndexAddr* indexAddr)
// If we're doing range checking, introduce a GT_BOUNDS_CHECK node for the address.
if (indexAddr->IsBoundsChecked())
{
GenTree* arrRef2 = nullptr; // The second copy will be used in array address expression
GenTree* index2 = nullptr;
GenTree* arrRef2 = nullptr; // The second copy will be used in array address expression
GenTree* index2 = nullptr;
auto countNode = [](GenTree* node) -> unsigned {
return 1;
};
// If the arrRef or index expressions involves a store, a call, or reads from global memory,
// then we *must* allocate a temporary in which to "localize" those values, to ensure that the
@ -3000,7 +3003,7 @@ GenTree* Compiler::fgMorphIndexAddr(GenTreeIndexAddr* indexAddr)
// TODO-Bug: GLOB_REF is not yet set for all trees in pre-order morph.
//
if (((arrRef->gtFlags & (GTF_ASG | GTF_CALL | GTF_GLOB_REF)) != 0) ||
gtComplexityExceeds(arrRef, MAX_ARR_COMPLEXITY) || arrRef->OperIs(GT_LCL_FLD) ||
gtComplexityExceeds(arrRef, MAX_ARR_COMPLEXITY, countNode) || arrRef->OperIs(GT_LCL_FLD) ||
(arrRef->OperIs(GT_LCL_VAR) && lvaIsLocalImplicitlyAccessedByRef(arrRef->AsLclVar()->GetLclNum())))
{
unsigned arrRefTmpNum = lvaGrabTemp(true DEBUGARG("arr expr"));
@ -3015,7 +3018,7 @@ GenTree* Compiler::fgMorphIndexAddr(GenTreeIndexAddr* indexAddr)
}
if (((index->gtFlags & (GTF_ASG | GTF_CALL | GTF_GLOB_REF)) != 0) ||
gtComplexityExceeds(index, MAX_INDEX_COMPLEXITY) || index->OperIs(GT_LCL_FLD) ||
gtComplexityExceeds(index, MAX_INDEX_COMPLEXITY, countNode) || index->OperIs(GT_LCL_FLD) ||
(index->OperIs(GT_LCL_VAR) && lvaIsLocalImplicitlyAccessedByRef(index->AsLclVar()->GetLclNum())))
{
unsigned indexTmpNum = lvaGrabTemp(true DEBUGARG("index expr"));

View File

@ -221,7 +221,9 @@ enum NamedIntrinsic : unsigned short
NI_SRCS_UNSAFE_InitBlock,
NI_SRCS_UNSAFE_InitBlockUnaligned,
NI_SRCS_UNSAFE_IsAddressGreaterThan,
NI_SRCS_UNSAFE_IsAddressGreaterThanOrEqualTo,
NI_SRCS_UNSAFE_IsAddressLessThan,
NI_SRCS_UNSAFE_IsAddressLessThanOrEqualTo,
NI_SRCS_UNSAFE_IsNullRef,
NI_SRCS_UNSAFE_NullRef,
NI_SRCS_UNSAFE_Read,

View File

@ -3613,22 +3613,24 @@ bool ObjectAllocator::ShouldClone(CloneInfo* info)
unsigned const sizeLimit = (sizeConfig >= 0) ? (unsigned)sizeConfig : UINT_MAX;
unsigned size = 0;
bool shouldClone = true;
auto countNode = [&size](GenTree* tree) -> unsigned {
size++;
return 1;
};
for (BasicBlock* const block : *info->m_blocksToClone)
{
// Note this overstates the size a bit since we'll resolve GDVs
// in the clone and the original...
//
unsigned const slack = sizeLimit - size;
unsigned blockSize = 0;
if (block->ComplexityExceeds(comp, slack, &blockSize))
unsigned const slack = sizeLimit - size;
if (block->ComplexityExceeds(comp, slack, countNode))
{
JITDUMP("Rejecting");
JITDUMPEXEC(DumpIndex(info->m_pseudoIndex));
JITDUMP(" cloning: exceeds size limit %u\n", sizeLimit);
return false;
}
size += blockSize;
}
// TODO: some kind of profile check...

View File

@ -175,37 +175,11 @@ public:
// Look for:
// user: ConvertVectorToMask(use:LCL_VAR(x)))
// -or-
// user: ConditionalSelect(use:LCL_VAR(x), y, z)
if ((user != nullptr) && user->OperIsHWIntrinsic())
if ((user != nullptr) && user->OperIsConvertVectorToMask())
{
GenTreeHWIntrinsic* hwintrin = user->AsHWIntrinsic();
NamedIntrinsic ni = hwintrin->GetHWIntrinsicId();
if (hwintrin->OperIsConvertVectorToMask())
{
convertOp = user->AsHWIntrinsic();
hasConversion = true;
}
else if (hwintrin->OperIsVectorConditionalSelect())
{
// We don't actually have a convert here, but we do have a case where
// the mask is being used in a ConditionalSelect and therefore can be
// consumed directly as a mask. While the IR shows TYP_SIMD, it gets
// handled in lowering as part of the general embedded-mask support.
// We notably don't check that op2 supported embedded masking directly
// because we can still consume the mask directly in such cases. We'll just
// emit `vblendmps zmm1 {k1}, zmm2, zmm3` instead of containing the CndSel
// as part of something like `vaddps zmm1 {k1}, zmm2, zmm3`
if (hwintrin->Op(1) == lclOp)
{
convertOp = user->AsHWIntrinsic();
hasConversion = true;
}
}
convertOp = user->AsHWIntrinsic();
hasConversion = true;
}
break;
}
@ -402,7 +376,6 @@ public:
lclOp->Data() = lclOp->Data()->AsHWIntrinsic()->Op(1);
}
else if (isLocalStore && addConversion)
{
// Convert
@ -416,7 +389,6 @@ public:
lclOp->Data() = m_compiler->gtNewSimdCvtVectorToMaskNode(TYP_MASK, lclOp->Data(), weight->simdBaseJitType,
weight->simdSize);
}
else if (isLocalUse && removeConversion)
{
// Convert
@ -426,7 +398,6 @@ public:
*use = lclOp;
}
else if (isLocalUse && addConversion)
{
// Convert
@ -510,7 +481,6 @@ private:
PhaseStatus Compiler::fgOptimizeMaskConversions()
{
#if defined(FEATURE_MASKED_HW_INTRINSICS)
if (opts.OptimizationDisabled())
{
JITDUMP("Skipping. Optimizations Disabled\n");

View File

@ -1903,10 +1903,45 @@ bool Compiler::optTryInvertWhileLoop(FlowGraphNaturalLoop* loop)
// Check if loop is small enough to consider for inversion.
// Large loops are less likely to benefit from inversion.
const int sizeLimit = JitConfig.JitLoopInversionSizeLimit();
if ((sizeLimit >= 0) && optLoopComplexityExceeds(loop, (unsigned)sizeLimit))
const int invertSizeLimit = JitConfig.JitLoopInversionSizeLimit();
if (invertSizeLimit >= 0)
{
return false;
const int cloneSizeLimit = JitConfig.JitCloneLoopsSizeLimit();
bool mightBenefitFromCloning = false;
unsigned loopSize = 0;
// Loops with bounds checks can benefit from cloning, which depends on inversion running.
// Thus, we will try to proceed with inversion for slightly oversize loops if they show potential for cloning.
auto countNode = [&mightBenefitFromCloning, &loopSize](GenTree* tree) -> unsigned {
mightBenefitFromCloning |= tree->OperIs(GT_BOUNDS_CHECK);
loopSize++;
return 1;
};
optLoopComplexityExceeds(loop, (unsigned)max(invertSizeLimit, cloneSizeLimit), countNode);
if (loopSize > (unsigned)invertSizeLimit)
{
// Don't try to invert oversize loops if they don't show cloning potential,
// or if they're too big to be cloned anyway.
JITDUMP(FMT_LP " exceeds inversion size limit of %d\n", loop->GetIndex(), invertSizeLimit);
const bool tooBigToClone = (cloneSizeLimit >= 0) && (loopSize > (unsigned)cloneSizeLimit);
if (!mightBenefitFromCloning || tooBigToClone)
{
JITDUMP("No inversion for " FMT_LP ": %s\n", loop->GetIndex(),
tooBigToClone ? "too big to clone" : "unlikely to benefit from cloning");
return false;
}
// If the loop shows cloning potential, tolerate some excess size.
const unsigned liberalInvertSizeLimit = (unsigned)(invertSizeLimit * 1.25);
if (loopSize > liberalInvertSizeLimit)
{
JITDUMP(FMT_LP " might benefit from cloning, but is too large to invert.\n", loop->GetIndex());
return false;
}
JITDUMP(FMT_LP " might benefit from cloning. Continuing.\n", loop->GetIndex());
}
}
unsigned estDupCostSz = 0;
@ -2343,7 +2378,6 @@ void Compiler::optResetLoopInfo()
if (!block->hasProfileWeight())
{
block->bbWeight = BB_UNITY_WEIGHT;
block->RemoveFlags(BBF_RUN_RARELY);
}
}
}
@ -2864,47 +2898,6 @@ bool Compiler::optCanonicalizeExit(FlowGraphNaturalLoop* loop, BasicBlock* exit)
return true;
}
//------------------------------------------------------------------------
// optLoopComplexityExceeds: Check if the number of nodes in the loop exceeds some limit
//
// Arguments:
// loop - the loop to compute the number of nodes in
// limit - limit on the number of nodes
//
// Returns:
// true if the number of nodes exceeds the limit
//
bool Compiler::optLoopComplexityExceeds(FlowGraphNaturalLoop* loop, unsigned limit)
{
assert(loop != nullptr);
// See if loop size exceeds the limit.
//
unsigned size = 0;
BasicBlockVisit const result = loop->VisitLoopBlocks([this, limit, &size](BasicBlock* block) {
assert(limit >= size);
unsigned const slack = limit - size;
unsigned blockSize = 0;
if (block->ComplexityExceeds(this, slack, &blockSize))
{
return BasicBlockVisit::Abort;
}
size += blockSize;
return BasicBlockVisit::Continue;
});
if (result == BasicBlockVisit::Abort)
{
JITDUMP("Loop " FMT_LP ": exceeds size limit %u\n", loop->GetIndex(), limit);
return true;
}
JITDUMP("Loop " FMT_LP ": size %u does not exceed size limit %u\n", loop->GetIndex(), size, limit);
return false;
}
//-----------------------------------------------------------------------------
// optSetWeightForPreheaderOrExit: Set the weight of a newly created preheader
// or exit, after it has been added to the flowgraph.
@ -2934,15 +2927,6 @@ void Compiler::optSetWeightForPreheaderOrExit(FlowGraphNaturalLoop* loop, BasicB
{
block->RemoveFlags(BBF_PROF_WEIGHT);
}
if (newWeight == BB_ZERO_WEIGHT)
{
block->SetFlags(BBF_RUN_RARELY);
}
else
{
block->RemoveFlags(BBF_RUN_RARELY);
}
}
/*****************************************************************************

View File

@ -443,8 +443,13 @@ static bool DoesComplexityExceed(Compiler* comp, ArrayStack<BoundsCheckInfo>* bn
GenTree* rootNode = currentStmt->GetRootNode();
if (rootNode != nullptr)
{
unsigned actual = 0;
if (comp->gtComplexityExceeds(rootNode, budget, &actual))
unsigned actual = 0;
auto countNode = [&actual](GenTree* tree) -> unsigned {
actual++;
return 1;
};
if (comp->gtComplexityExceeds(rootNode, budget, countNode))
{
JITDUMP("\tExceeded budget!");
return true;

View File

@ -583,7 +583,7 @@ public:
//------------------------------------------------------------------------
// HashTableBase::Remove: removes a key from the hash table and asserts
// that it did exist in the the table.
// that it did exist in the table.
//
// Arguments:
// key - The key to remove from the table.

View File

@ -30,7 +30,14 @@ endif (CLR_CMAKE_HOST_UNIX)
if(CLR_CMAKE_TARGET_ANDROID)
add_definitions(-DFEATURE_EMULATED_TLS)
set(FEATURE_JAVAMARSHAL 1)
endif()
if(NOT DEFINED FEATURE_JAVAMARSHAL)
set(FEATURE_JAVAMARSHAL $<IF:$<CONFIG:Debug,Checked>,1,0>)
endif()
add_compile_definitions($<${FEATURE_JAVAMARSHAL}:FEATURE_JAVAMARSHAL>)
add_subdirectory(Bootstrap)
add_subdirectory(Runtime)

View File

@ -20,6 +20,7 @@ set(COMMON_RUNTIME_SOURCES
gcenv.ee.cpp
GcStressControl.cpp
HandleTableHelpers.cpp
interoplibinterface_java.cpp
MathHelpers.cpp
MiscHelpers.cpp
TypeManager.cpp
@ -38,6 +39,7 @@ set(COMMON_RUNTIME_SOURCES
yieldprocessornormalized.cpp
${GC_DIR}/gceventstatus.cpp
${GC_DIR}/gcbridge.cpp
${GC_DIR}/gcload.cpp
${GC_DIR}/gcconfig.cpp
${GC_DIR}/gchandletable.cpp

View File

@ -6,6 +6,7 @@
#include "objecthandle.h"
#include "RestrictedCallouts.h"
#include "gchandleutilities.h"
#include "interoplibinterface.h"
FCIMPL2(OBJECTHANDLE, RhpHandleAlloc, Object *pObject, int type)
@ -64,6 +65,27 @@ FCIMPL2(void, RhUnregisterRefCountedHandleCallback, void * pCallout, MethodTable
}
FCIMPLEND
FCIMPL2(OBJECTHANDLE, RhpHandleAllocCrossReference, Object *pPrimary, void *pContext)
{
return GCHandleUtilities::GetGCHandleManager()->GetGlobalHandleStore()->CreateHandleWithExtraInfo(pPrimary, HNDTYPE_CROSSREFERENCE, pContext);
}
FCIMPLEND
FCIMPL2(FC_BOOL_RET, RhHandleTryGetCrossReferenceContext, OBJECTHANDLE handle, void **pContext)
{
*pContext = nullptr;
IGCHandleManager* gcHandleManager = GCHandleUtilities::GetGCHandleManager();
if (gcHandleManager->HandleFetchType(handle) != HNDTYPE_CROSSREFERENCE)
{
FC_RETURN_BOOL(false);
}
*pContext = gcHandleManager->GetExtraInfoFromHandle(handle);
FC_RETURN_BOOL(true);
}
FCIMPLEND
// This structure mirrors the managed type System.Runtime.InteropServices.ComWrappers.ManagedObjectWrapper.
struct ManagedObjectWrapper
{

View File

@ -205,7 +205,7 @@ size_t GetDefaultStackSizeSetting()
if (g_pRhConfig->ReadConfigValue("Thread_DefaultStackSize", &uiStacksize)
|| g_pRhConfig->ReadKnobUInt64Value("System.Threading.DefaultStackSize", &uiStacksize))
{
if (uiStacksize < maxStack || uiStacksize >= minStack)
if (uiStacksize < maxStack && uiStacksize >= minStack)
{
return (size_t)uiStacksize;
}

View File

@ -809,4 +809,11 @@ void GCToEEInterface::FreeStringConfigValue(const char* value)
delete[] value;
}
void GCToEEInterface::TriggerClientBridgeProcessing(MarkCrossReferencesArgs* args)
{
#ifdef FEATURE_JAVAMARSHAL
JavaMarshalNative::TriggerClientBridgeProcessing(args);
#endif
}
#endif // !DACCESS_COMPILE

View File

@ -22,3 +22,15 @@ public: // GC interaction
};
#endif // FEATURE_OBJCMARSHAL
#ifdef FEATURE_JAVAMARSHAL
struct MarkCrossReferencesArgs;
class JavaMarshalNative
{
public:
static void TriggerClientBridgeProcessing(
MarkCrossReferencesArgs* args);
};
#endif // FEATURE_JAVAMARSHAL

View File

@ -0,0 +1,173 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#ifdef FEATURE_JAVAMARSHAL
// Runtime headers
#include "common.h"
#include "gcenv.h"
#include "gcenv.ee.h"
#include "gcheaputilities.h"
#include "gchandleutilities.h"
#include "thread.h"
#include "threadstore.h"
#include "threadstore.inl"
#include "event.h"
#include "interoplibinterface.h"
using CrossreferenceHandleCallback = void(__stdcall *)(MarkCrossReferencesArgs*);
namespace
{
volatile CrossreferenceHandleCallback g_MarkCrossReferences = NULL;
Volatile<bool> g_GCBridgeActive = false;
CLREventStatic g_bridgeFinished;
void ReleaseGCBridgeArgumentsWorker(
MarkCrossReferencesArgs* args)
{
_ASSERTE(args != NULL);
// Memory was allocated for the collections by the GC.
// See callers of GCToEEInterface::TriggerGCBridge().
// Free memory in each of the SCCs
for (size_t i = 0; i < args->ComponentCount; i++)
{
delete[] args->Components[i].Contexts;
}
delete[] args->Components;
delete[] args->CrossReferences;
delete args;
}
}
void JavaMarshalNative::TriggerClientBridgeProcessing(
_In_ MarkCrossReferencesArgs* args)
{
_ASSERTE(GCHeapUtilities::IsGCInProgress());
if (g_GCBridgeActive)
{
// Release the memory allocated since the GCBridge
// is already running and we're not passing them to it.
ReleaseGCBridgeArgumentsWorker(args);
return;
}
// Not initialized
if (g_MarkCrossReferences == NULL)
{
// Release the memory allocated since we
// don't have a GC bridge callback.
ReleaseGCBridgeArgumentsWorker(args);
return;
}
g_MarkCrossReferences(args);
// This runs during GC while the world is stopped, no synchronisation required
g_bridgeFinished.Reset();
// Mark the GCBridge as active.
g_GCBridgeActive = true;
}
extern "C" BOOL QCALLTYPE JavaMarshal_Initialize(
void* markCrossReferences)
{
_ASSERTE(markCrossReferences != NULL);
BOOL success = FALSE;
// Switch to Cooperative mode since we are setting callbacks that
// will be used during a GC and we want to ensure a GC isn't occurring
// while they are being set.
Thread* pThisThread = ThreadStore::GetCurrentThreadIfAvailable();
pThisThread->DeferTransitionFrame();
pThisThread->DisablePreemptiveMode();
if (PalInterlockedCompareExchangePointer((void* volatile*)&g_MarkCrossReferences, (void*)markCrossReferences, NULL) == NULL)
{
success = g_bridgeFinished.CreateManualEventNoThrow(false);
}
pThisThread->EnablePreemptiveMode();
return success;
}
extern "C" void QCALLTYPE JavaMarshal_FinishCrossReferenceProcessing(
MarkCrossReferencesArgs *crossReferences,
size_t length,
void* unreachableObjectHandles)
{
_ASSERTE(crossReferences->ComponentCount >= 0);
_ASSERTE(g_GCBridgeActive);
// Mark the GCBridge as inactive.
// This must be synchronized with the GC so switch to cooperative mode.
{
Thread* pThisThread = ThreadStore::GetCurrentThreadIfAvailable();
pThisThread->DeferTransitionFrame();
pThisThread->DisablePreemptiveMode();
GCHeapUtilities::GetGCHeap()->NullBridgeObjectsWeakRefs(length, unreachableObjectHandles);
IGCHandleManager* pHandleManager = GCHandleUtilities::GetGCHandleManager();
OBJECTHANDLE* handles = (OBJECTHANDLE*)unreachableObjectHandles;
for (size_t i = 0; i < length; i++)
pHandleManager->DestroyHandleOfUnknownType(handles[i]);
g_GCBridgeActive = false;
g_bridgeFinished.Set();
pThisThread->EnablePreemptiveMode();
}
ReleaseGCBridgeArgumentsWorker(crossReferences);
}
FCIMPL2(FC_BOOL_RET, GCHandle_InternalTryGetBridgeWait, OBJECTHANDLE handle, OBJECTREF* pObjResult)
{
if (g_GCBridgeActive)
{
FC_RETURN_BOOL(false);
}
*pObjResult = ObjectFromHandle(handle);
FC_RETURN_BOOL(true);
}
FCIMPLEND
extern "C" void QCALLTYPE GCHandle_InternalGetBridgeWait(OBJECTHANDLE handle, OBJECTREF* pObj)
{
_ASSERTE(pObj != NULL);
// Transition to cooperative mode to ensure that the GC is not in progress
Thread* pThisThread = ThreadStore::GetCurrentThreadIfAvailable();
pThisThread->DeferTransitionFrame();
pThisThread->DisablePreemptiveMode();
while (g_GCBridgeActive)
{
// This wait will transition to pre-emptive mode to wait for the bridge to finish.
g_bridgeFinished.Wait(INFINITE, false, false);
}
// If we reach here, then the bridge has finished processing and we can be sure that
// it isn't currently active.
// No GC can happen between the wait and obtaining of the reference, so the
// bridge processing status can't change, guaranteeing the nulling of weak refs
// took place in the bridge processing finish stage.
*pObj = ObjectFromHandle(handle);
// Re-enable preemptive mode before we exit the QCall to ensure we're in the right GC state.
pThisThread->EnablePreemptiveMode();
}
#endif // FEATURE_JAVAMARSHAL

View File

@ -159,6 +159,7 @@
<Compile Include="System\CrashInfo.cs" />
<Compile Include="System\Runtime\InteropServices\ComAwareWeakReference.NativeAot.cs" />
<Compile Include="System\Runtime\InteropServices\CriticalHandle.NativeAot.cs" />
<Compile Include="System\Runtime\InteropServices\Java\JavaMarshal.NativeAot.cs" Condition="'$(FeatureJavaMarshal)' == 'true'" />
<Compile Include="System\Activator.NativeAot.cs" />
<Compile Include="System\AppContext.NativeAot.cs" />
<Compile Include="System\ArgIterator.cs" />

View File

@ -1,6 +1,8 @@
// 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.CompilerServices;
namespace System.Runtime.InteropServices
{
public partial struct GCHandle
@ -14,11 +16,24 @@ namespace System.Runtime.InteropServices
internal static void InternalSet(IntPtr handle, object? value) => RuntimeImports.RhHandleSet(handle, value);
#if FEATURE_JAVAMARSHAL
// FIXME implement waiting for bridge processing
internal static object? InternalGetBridgeWait(IntPtr handle)
internal static unsafe object? InternalGetBridgeWait(IntPtr handle)
{
return InternalGet(handle);
object? target = null;
if (InternalTryGetBridgeWait(handle, ref target))
return target;
InternalGetBridgeWait(handle, &target);
return target;
}
[MethodImpl(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeImports.RuntimeLibrary, "GCHandle_InternalTryGetBridgeWait")]
private static extern bool InternalTryGetBridgeWait(IntPtr handle, ref object? result);
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCHandle_InternalGetBridgeWait")]
private static unsafe partial void InternalGetBridgeWait(IntPtr handle, object?* result);
#endif
}
}

View File

@ -0,0 +1,63 @@
// 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.CompilerServices;
using System.Runtime.Versioning;
namespace System.Runtime.InteropServices.Java
{
[CLSCompliant(false)]
[SupportedOSPlatform("android")]
public static partial class JavaMarshal
{
public static unsafe void Initialize(delegate* unmanaged<MarkCrossReferencesArgs*, void> markCrossReferences)
{
ArgumentNullException.ThrowIfNull(markCrossReferences);
if (!InitializeInternal((IntPtr)markCrossReferences))
{
throw new InvalidOperationException(SR.InvalidOperation_ReinitializeJavaMarshal);
}
}
public static unsafe GCHandle CreateReferenceTrackingHandle(object obj, void* context)
{
ArgumentNullException.ThrowIfNull(obj);
return GCHandle.FromIntPtr(RuntimeImports.RhHandleAllocCrossReference(obj, (IntPtr)context));
}
public static unsafe void* GetContext(GCHandle obj)
{
IntPtr handle = GCHandle.ToIntPtr(obj);
if (handle == IntPtr.Zero
|| !RuntimeImports.RhHandleTryGetCrossReferenceContext(handle, out nint context))
{
throw new InvalidOperationException(SR.InvalidOperation_IncorrectGCHandleType);
}
return (void*)context;
}
public static unsafe void FinishCrossReferenceProcessing(
MarkCrossReferencesArgs* crossReferences,
ReadOnlySpan<GCHandle> unreachableObjectHandles)
{
fixed (GCHandle* handlesPtr = unreachableObjectHandles)
{
FinishCrossReferenceProcessingBridge(
crossReferences,
(nuint)unreachableObjectHandles.Length,
handlesPtr);
}
}
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "JavaMarshal_Initialize")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static unsafe partial bool InitializeInternal(IntPtr markCrossReferences);
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "JavaMarshal_FinishCrossReferenceProcessing")]
internal static unsafe partial void FinishCrossReferenceProcessingBridge(
MarkCrossReferencesArgs* crossReferences,
nuint numHandles,
GCHandle* unreachableObjectHandles);
}
}

View File

@ -294,6 +294,26 @@ namespace System.Runtime
return h;
}
[MethodImpl(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhpHandleAllocCrossReference")]
private static extern IntPtr RhpHandleAllocCrossReference(object value, IntPtr context);
internal static IntPtr RhHandleAllocCrossReference(object value, IntPtr context)
{
IntPtr h = RhpHandleAllocCrossReference(value, context);
if (h == IntPtr.Zero)
throw new OutOfMemoryException();
return h;
}
[MethodImpl(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhHandleTryGetCrossReferenceContext")]
internal static extern bool RhHandleTryGetCrossReferenceContext(IntPtr handle, out IntPtr context);
[MethodImpl(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhIsGCBridgeActive")]
internal static extern bool RhIsGCBridgeActive();
// Free handle.
[MethodImpl(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhHandleFree")]

View File

@ -330,7 +330,7 @@ namespace Internal.Runtime.TypeLoader
RuntimeTypeHandle openTargetTypeHandle = targetType.GetTypeDefinition().RuntimeTypeHandle;
#if GVM_RESOLUTION_TRACE
Debug.WriteLine("INTERFACE GVM call = " + GetTypeNameDebug(slotMethod.OwningType) + "." + slotMethod.NameAndSignature.Name);
Debug.WriteLine("INTERFACE GVM call = " + GetTypeNameDebug(slotMethod.OwningType) + "." + slotMethod.Name);
#endif
foreach (NativeFormatModuleInfo module in ModuleList.EnumerateModules(RuntimeAugments.GetModuleFromTypeHandle(openTargetTypeHandle)))
@ -459,7 +459,7 @@ namespace Internal.Runtime.TypeLoader
hashCode = ((hashCode << 13) ^ hashCode) ^ openTargetTypeHandle.GetHashCode();
#if GVM_RESOLUTION_TRACE
Debug.WriteLine("GVM Target Resolution = " + GetTypeNameDebug(targetType) + "." + slotMethod.NameAndSignature.Name);
Debug.WriteLine("GVM Target Resolution = " + GetTypeNameDebug(targetType) + "." + slotMethod.Name);
#endif
foreach (NativeFormatModuleInfo module in ModuleList.EnumerateModules(RuntimeAugments.GetModuleFromTypeHandle(openTargetTypeHandle)))

View File

@ -248,7 +248,7 @@ Abstract:
void
InitializeDefaultStackSize()
{
CLRConfigNoCache defStackSize = CLRConfigNoCache::Get("DefaultStackSize", /*noprefix*/ false, &getenv);
CLRConfigNoCache defStackSize = CLRConfigNoCache::Get("Thread_DefaultStackSize", /*noprefix*/ false, &getenv);
if (defStackSize.IsSet())
{
DWORD size;

View File

@ -93,6 +93,9 @@ extern "C"
} \
} while (false)
// On macOS 26, sem_open fails if debugger and debugee are signed with different team ids.
// Use fifos instead of semaphores to avoid this issue, https://github.com/dotnet/runtime/issues/116545
#define ENABLE_RUNTIME_EVENTS_OVER_PIPES
#endif // __APPLE__
#ifdef __NetBSD__
@ -1401,21 +1404,217 @@ static uint64_t HashSemaphoreName(uint64_t a, uint64_t b)
static const char *const TwoWayNamedPipePrefix = "clr-debug-pipe";
static const char* IpcNameFormat = "%s-%d-%llu-%s";
/*++
PAL_NotifyRuntimeStarted
#ifdef ENABLE_RUNTIME_EVENTS_OVER_PIPES
static const char* RuntimeStartupPipeName = "st";
static const char* RuntimeContinuePipeName = "co";
Signals the debugger waiting for runtime startup notification to continue and
waits until the debugger signals us to continue.
#define PIPE_OPEN_RETRY_DELAY_NS 500000000 // 500 ms
Parameters:
None
typedef enum
{
RuntimeEventsOverPipes_Disabled = 0,
RuntimeEventsOverPipes_Succeeded = 1,
RuntimeEventsOverPipes_Failed = 2,
} RuntimeEventsOverPipes;
Return value:
TRUE - successfully launched by debugger, FALSE - not launched or some failure in the handshake
--*/
typedef enum
{
RuntimeEvent_Unknown = 0,
RuntimeEvent_Started = 1,
RuntimeEvent_Continue = 2,
} RuntimeEvent;
static
int
OpenPipe(const char* name, int mode)
{
int fd = -1;
int flags = mode | O_NONBLOCK;
#if defined(FD_CLOEXEC)
flags |= O_CLOEXEC;
#endif
while (fd == -1)
{
fd = open(name, flags);
if (fd == -1)
{
if (mode == O_WRONLY && errno == ENXIO)
{
PAL_nanosleep(PIPE_OPEN_RETRY_DELAY_NS);
continue;
}
else if (errno == EINTR)
{
continue;
}
else
{
break;
}
}
}
if (fd != -1)
{
flags = fcntl(fd, F_GETFL);
if (flags != -1)
{
flags &= ~O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1)
{
close(fd);
fd = -1;
}
}
else
{
close(fd);
fd = -1;
}
}
return fd;
}
static
void
ClosePipe(int fd)
{
if (fd != -1)
{
while (close(fd) < 0 && errno == EINTR);
}
}
static
RuntimeEventsOverPipes
NotifyRuntimeUsingPipes()
{
RuntimeEventsOverPipes result = RuntimeEventsOverPipes_Disabled;
char startupPipeName[MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH];
char continuePipeName[MAX_DEBUGGER_TRANSPORT_PIPE_NAME_LENGTH];
int startupPipeFd = -1;
int continuePipeFd = -1;
size_t offset = 0;
LPCSTR applicationGroupId = PAL_GetApplicationGroupId();
PAL_GetTransportPipeName(continuePipeName, gPID, applicationGroupId, RuntimeContinuePipeName);
TRACE("NotifyRuntimeUsingPipes: opening continue '%s' pipe\n", continuePipeName);
continuePipeFd = OpenPipe(continuePipeName, O_RDONLY);
if (continuePipeFd == -1)
{
if (errno == ENOENT || errno == EACCES)
{
TRACE("NotifyRuntimeUsingPipes: pipe %s not found/accessible, runtime events over pipes disabled\n", continuePipeName);
}
else
{
TRACE("NotifyRuntimeUsingPipes: open(%s) failed: %d (%s)\n", continuePipeName, errno, strerror(errno));
result = RuntimeEventsOverPipes_Failed;
}
goto exit;
}
PAL_GetTransportPipeName(startupPipeName, gPID, applicationGroupId, RuntimeStartupPipeName);
TRACE("NotifyRuntimeUsingPipes: opening startup '%s' pipe\n", startupPipeName);
startupPipeFd = OpenPipe(startupPipeName, O_WRONLY);
if (startupPipeFd == -1)
{
if (errno == ENOENT || errno == EACCES)
{
TRACE("NotifyRuntimeUsingPipes: pipe %s not found/accessible, runtime events over pipes disabled\n", startupPipeName);
}
else
{
TRACE("NotifyRuntimeUsingPipes: open(%s) failed: %d (%s)\n", startupPipeName, errno, strerror(errno));
result = RuntimeEventsOverPipes_Failed;
}
goto exit;
}
TRACE("NotifyRuntimeUsingPipes: sending started event\n");
{
unsigned char event = (unsigned char)RuntimeEvent_Started;
unsigned char *buffer = &event;
int bytesToWrite = sizeof(event);
int bytesWritten = 0;
do
{
bytesWritten = write(startupPipeFd, buffer + offset, bytesToWrite - offset);
if (bytesWritten > 0)
{
offset += bytesWritten;
}
}
while ((bytesWritten > 0 && offset < bytesToWrite) || (bytesWritten == -1 && errno == EINTR));
if (offset != bytesToWrite)
{
TRACE("NotifyRuntimeUsingPipes: write(%s) failed: %d (%s)\n", startupPipeName, errno, strerror(errno));
goto exit;
}
}
TRACE("NotifyRuntimeUsingPipes: waiting on continue event\n");
{
unsigned char event = (unsigned char)RuntimeEvent_Unknown;
unsigned char *buffer = &event;
int bytesToRead = sizeof(event);
int bytesRead = 0;
offset = 0;
do
{
bytesRead = read(continuePipeFd, buffer + offset, bytesToRead - offset);
if (bytesRead > 0)
{
offset += bytesRead;
}
}
while ((bytesRead > 0 && offset < bytesToRead) || (bytesRead == -1 && errno == EINTR));
if (offset == bytesToRead && event == (unsigned char)RuntimeEvent_Continue)
{
TRACE("NotifyRuntimeUsingPipes: received continue event\n");
}
else
{
TRACE("NotifyRuntimeUsingPipes: received invalid event\n");
goto exit;
}
}
result = RuntimeEventsOverPipes_Succeeded;
exit:
if (startupPipeFd != -1)
{
ClosePipe(startupPipeFd);
}
if (continuePipeFd != -1)
{
ClosePipe(continuePipeFd);
}
return result;
}
#endif // ENABLE_RUNTIME_EVENTS_OVER_PIPES
static
BOOL
PALAPI
PAL_NotifyRuntimeStarted()
NotifyRuntimeUsingSemaphores()
{
char startupSemName[CLR_SEM_MAX_NAMELEN];
char continueSemName[CLR_SEM_MAX_NAMELEN];
@ -1436,13 +1635,13 @@ PAL_NotifyRuntimeStarted()
CreateSemaphoreName(startupSemName, RuntimeStartupSemaphoreName, unambiguousProcessDescriptor, applicationGroupId);
CreateSemaphoreName(continueSemName, RuntimeContinueSemaphoreName, unambiguousProcessDescriptor, applicationGroupId);
TRACE("PAL_NotifyRuntimeStarted opening continue '%s' startup '%s'\n", continueSemName, startupSemName);
TRACE("NotifyRuntimeUsingSemaphores: opening continue '%s' startup '%s'\n", continueSemName, startupSemName);
// Open the debugger startup semaphore. If it doesn't exists, then we do nothing and return
startupSem = sem_open(startupSemName, 0);
if (startupSem == SEM_FAILED)
{
TRACE("sem_open(%s) failed: %d (%s)\n", startupSemName, errno, strerror(errno));
TRACE("NotifyRuntimeUsingSemaphores: sem_open(%s) failed: %d (%s)\n", startupSemName, errno, strerror(errno));
goto exit;
}
@ -1465,7 +1664,7 @@ PAL_NotifyRuntimeStarted()
{
if (EINTR == errno)
{
TRACE("sem_wait() failed with EINTR; re-waiting");
TRACE("NotifyRuntimeUsingSemaphores: sem_wait() failed with EINTR; re-waiting");
continue;
}
ASSERT("sem_wait(continueSem) failed: errno is %d (%s)\n", errno, strerror(errno));
@ -1487,6 +1686,45 @@ exit:
return launched;
}
/*++
PAL_NotifyRuntimeStarted
Signals the debugger waiting for runtime startup notification to continue and
waits until the debugger signals us to continue.
Parameters:
None
Return value:
TRUE - successfully launched by debugger, FALSE - not launched or some failure in the handshake
--*/
BOOL
PALAPI
PAL_NotifyRuntimeStarted()
{
#ifdef ENABLE_RUNTIME_EVENTS_OVER_PIPES
// Test pipes as runtime event transport.
RuntimeEventsOverPipes result = NotifyRuntimeUsingPipes();
switch (result)
{
case RuntimeEventsOverPipes_Disabled:
TRACE("PAL_NotifyRuntimeStarted: pipe handshake disabled, try semaphores\n");
return NotifyRuntimeUsingSemaphores();
case RuntimeEventsOverPipes_Failed:
TRACE("PAL_NotifyRuntimeStarted: pipe handshake failed\n");
return FALSE;
case RuntimeEventsOverPipes_Succeeded:
TRACE("PAL_NotifyRuntimeStarted: pipe handshake succeeded\n");
return TRUE;
default:
// Unexpected result.
return FALSE;
}
#else
return NotifyRuntimeUsingSemaphores();
#endif // ENABLE_RUNTIME_EVENTS_OVER_PIPES
}
LPCSTR
PALAPI
PAL_GetApplicationGroupId()

View File

@ -592,10 +592,8 @@ namespace ILCompiler.Dataflow
case ILOpcode.conv_r8:
case ILOpcode.ldind_ref:
case ILOpcode.ldobj:
case ILOpcode.mkrefany:
case ILOpcode.unbox:
case ILOpcode.unbox_any:
case ILOpcode.box:
case ILOpcode.neg:
case ILOpcode.not:
PopUnknown(currentStack, 1, methodBody, offset);
@ -603,6 +601,13 @@ namespace ILCompiler.Dataflow
reader.Skip(opcode);
break;
case ILOpcode.box:
case ILOpcode.mkrefany:
HandleTypeTokenAccess(methodBody, offset, (TypeDesc)methodBody.GetObject(reader.ReadILToken()));
PopUnknown(currentStack, 1, methodBody, offset);
PushUnknown(currentStack);
break;
case ILOpcode.isinst:
case ILOpcode.castclass:
// We can consider a NOP because the value doesn't change.
@ -622,6 +627,7 @@ namespace ILCompiler.Dataflow
{
StackSlot count = PopUnknown(currentStack, 1, methodBody, offset);
var arrayElement = (TypeDesc)methodBody.GetObject(reader.ReadILToken());
HandleTypeTokenAccess(methodBody, offset, arrayElement);
currentStack.Push(new StackSlot(ArrayValue.Create(count.Value, arrayElement)));
}
break;

View File

@ -60,6 +60,14 @@ namespace ILCompiler.Dataflow
field.DoesFieldRequire(DiagnosticUtilities.RequiresDynamicCodeAttribute, out _);
}
public static bool RequiresReflectionMethodBodyScannerForAccess(FlowAnnotations flowAnnotations, TypeDesc type)
{
return GenericArgumentDataFlow.RequiresGenericArgumentDataFlow(flowAnnotations, type) ||
type.DoesTypeRequire(DiagnosticUtilities.RequiresUnreferencedCodeAttribute, out _) ||
type.DoesTypeRequire(DiagnosticUtilities.RequiresAssemblyFilesAttribute, out _) ||
type.DoesTypeRequire(DiagnosticUtilities.RequiresDynamicCodeAttribute, out _);
}
internal static void CheckAndReportAllRequires(in DiagnosticContext diagnosticContext, TypeSystemEntity calledMember)
{
CheckAndReportRequires(diagnosticContext, calledMember, DiagnosticUtilities.RequiresUnreferencedCodeAttribute);
@ -429,10 +437,10 @@ namespace ILCompiler.Dataflow
private void ProcessGenericArgumentDataFlow(MethodDesc method)
{
// We only need to validate static methods and then all generic methods
// Instance non-generic methods don't need validation because the creation of the instance
// is the place where the validation will happen.
if (!method.Signature.IsStatic && !method.HasInstantiation && !method.IsConstructor)
// We mostly need to validate static methods and generic methods
// Instance non-generic methods on reference types don't need validation
// because the creation of the instance is the place where the validation will happen.
if (!method.Signature.IsStatic && !method.HasInstantiation && !method.IsConstructor && !method.OwningType.IsValueType)
return;
if (GenericArgumentDataFlow.RequiresGenericArgumentDataFlow(_annotations, method))

View File

@ -69,15 +69,6 @@ namespace ILCompiler.DependencyAnalysis
if (_typeDefinition.HasBaseType)
{
if (_typeDefinition.BaseType.DoesTypeRequire(DiagnosticUtilities.RequiresUnreferencedCodeAttribute, out var requiresAttribute) &&
!_typeDefinition.DoesTypeRequire(DiagnosticUtilities.RequiresUnreferencedCodeAttribute, out _))
{
UsageBasedMetadataManager metadataManager = (UsageBasedMetadataManager)factory.MetadataManager;
string arg1 = MessageFormat.FormatRequiresAttributeMessageArg(DiagnosticUtilities.GetRequiresAttributeMessage(requiresAttribute.Value));
string arg2 = MessageFormat.FormatRequiresAttributeUrlArg(DiagnosticUtilities.GetRequiresAttributeUrl(requiresAttribute.Value));
metadataManager.Logger.LogWarning(new MessageOrigin(_typeDefinition), DiagnosticId.RequiresUnreferencedCodeOnBaseClass, _typeDefinition.GetDisplayName(), _typeDefinition.BaseType.GetDisplayName(), arg1, arg2);
}
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(ref dependencies, factory, new MessageOrigin(_typeDefinition), _typeDefinition.BaseType, _typeDefinition);
}

View File

@ -1240,6 +1240,10 @@ namespace ILCompiler
protected abstract MetadataCategory GetMetadataCategory(TypeDesc type);
protected abstract MetadataCategory GetMetadataCategory(FieldDesc field);
public virtual void GetDependenciesDueToAccess(ref DependencyList dependencies, NodeFactory factory, MethodIL methodIL, TypeDesc accessedType)
{
}
public virtual void GetDependenciesDueToAccess(ref DependencyList dependencies, NodeFactory factory, MethodIL methodIL, MethodDesc calledMethod)
{
}

View File

@ -773,6 +773,15 @@ namespace ILCompiler
}
}
public override void GetDependenciesDueToAccess(ref DependencyList dependencies, NodeFactory factory, MethodIL methodIL, TypeDesc accessedType)
{
bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0;
if (scanReflection && Dataflow.ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForAccess(FlowAnnotations, accessedType))
{
AddDataflowDependency(ref dependencies, factory, methodIL, "Access to interesting type");
}
}
public override void GetDependenciesDueToAccess(ref DependencyList dependencies, NodeFactory factory, MethodIL methodIL, MethodDesc calledMethod)
{
bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0;

View File

@ -921,6 +921,7 @@ namespace Internal.IL
{
_dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.TypeHandleToRuntimeType), "mkrefany");
_dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.TypeHandleToRuntimeTypeHandle), "mkrefany");
_factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, (TypeDesc)_canonMethodIL.GetObject(token));
ImportTypedRefOperationDependencies(token, "mkrefany");
}
@ -960,6 +961,8 @@ namespace Internal.IL
}
}
_factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, (TypeDesc)_canonMethodIL.GetObject(token));
_dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeTypeHandle), "ldtoken");
_dependencies.Add(GetHelperEntrypoint(ReadyToRunHelper.GetRuntimeType), "ldtoken");
@ -1190,6 +1193,9 @@ namespace Internal.IL
if (!type.IsValueType)
return;
TypeDesc typeForAccessCheck = type.IsRuntimeDeterminedSubtype ? type.ConvertToCanonForm(CanonicalFormKind.Specific) : type;
_factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, typeForAccessCheck);
if (type.IsRuntimeDeterminedSubtype)
{
_dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, type), reason);
@ -1217,6 +1223,7 @@ namespace Internal.IL
private void ImportNewArray(int token)
{
var elementType = (TypeDesc)_methodIL.GetObject(token);
_factory.MetadataManager.GetDependenciesDueToAccess(ref _dependencies, _factory, _methodIL, (TypeDesc)_canonMethodIL.GetObject(token));
if (elementType.IsRuntimeDeterminedSubtype)
{
_dependencies.Add(GetGenericLookupHelper(ReadyToRunHelperId.TypeHandle, elementType.MakeArrayType()), "newarr");

View File

@ -416,7 +416,7 @@ bool NearDiffer::mungeOffsets(
// We might want to do something similar for mov/movk/movk/movk sequences on Arm64. The following code was
// previously used, but currently is not active.
//
// One difference is we might see a different number of movk on arm64 depending on the the data. Our "hack"
// One difference is we might see a different number of movk on arm64 depending on the data. Our "hack"
// of zeroing out the constant so they compare equal doesn't work well. In this case, we really do want
// a callback to the disassembler to skip the instructions.
//

View File

@ -1688,7 +1688,7 @@ Arguments:
returned.
HandlerData - Supplies a pointer to a variable that receives a pointer
the the language handler data.
the language handler data.
UnwindParams - Additional parameters shared with caller.
@ -2536,7 +2536,7 @@ Arguments:
ContextRecord - Supplies the address of a context record.
HandlerData - Supplies a pointer to a variable that receives a pointer
the the language handler data.
the language handler data.
EstablisherFrame - Supplies a pointer to a variable that receives the
the establisher frame pointer value.

View File

@ -1571,6 +1571,7 @@ struct cdac_data<AppDomain>
{
static constexpr size_t RootAssembly = offsetof(AppDomain, m_pRootAssembly);
static constexpr size_t DomainAssemblyList = offsetof(AppDomain, m_Assemblies) + offsetof(AppDomain::DomainAssemblyList, m_array);
static constexpr size_t FriendlyName = offsetof(AppDomain, m_friendlyName);
};
typedef DPTR(class SystemDomain) PTR_SystemDomain;

View File

@ -431,10 +431,7 @@ Assembly *Assembly::CreateDynamic(AssemblyBinder* pBinder, NativeAssemblyNamePar
IfFailThrow(pAssemblyEmit->DefineAssembly(pAssemblyNameParts->_pPublicKeyOrToken, pAssemblyNameParts->_cbPublicKeyOrToken, hashAlgorithm,
pAssemblyNameParts->_pName, &assemData, pAssemblyNameParts->_flags,
&ma));
pPEAssembly = PEAssembly::Create(pAssemblyEmit);
// Set it as the fallback load context binder for the dynamic assembly being created
pPEAssembly->SetFallbackBinder(pBinder);
pPEAssembly = PEAssembly::Create(pAssemblyEmit, pBinder);
}
AppDomain* pDomain = ::GetAppDomain();

View File

@ -128,6 +128,12 @@ private:
INT_PTR m_ptrManagedAssemblyLoadContext;
SArray<Assembly*> m_loadedAssemblies;
friend struct cdac_data<AssemblyBinder>;
};
template<>
struct cdac_data<AssemblyBinder>
{
static constexpr size_t ManagedAssemblyLoadContext = offsetof(AssemblyBinder, m_ptrManagedAssemblyLoadContext);
};
#endif

View File

@ -36,11 +36,11 @@ namespace
// Free memory in each of the SCCs
for (size_t i = 0; i < args->ComponentCount; i++)
{
free(args->Components[i].Contexts);
delete[] args->Components[i].Contexts;
}
free(args->Components);
free(args->CrossReferences);
free(args);
delete[] args->Components;
delete[] args->CrossReferences;
delete args;
}
}

View File

@ -647,6 +647,7 @@ PEAssembly::PEAssembly(
BINDER_SPACE::Assembly* pBindResultInfo,
IMetaDataEmit* pEmit,
BOOL isSystem,
AssemblyBinder* pFallbackBinder /*= NULL*/,
PEImage * pPEImage /*= NULL*/,
BINDER_SPACE::Assembly * pHostAssembly /*= NULL*/)
{
@ -670,7 +671,7 @@ PEAssembly::PEAssembly(
m_refCount = 1;
m_isSystem = isSystem;
m_pHostAssembly = nullptr;
m_pFallbackBinder = nullptr;
m_pAssemblyBinder = nullptr;
pPEImage = pBindResultInfo ? pBindResultInfo->GetPEImage() : pPEImage;
if (pPEImage)
@ -722,6 +723,15 @@ PEAssembly::PEAssembly(
m_pHostAssembly = pBindResultInfo;
}
if (m_pHostAssembly != nullptr)
{
m_pAssemblyBinder = m_pHostAssembly->GetBinder();
}
else
{
m_pAssemblyBinder = pFallbackBinder;
}
#ifdef LOGGING
GetPathOrCodeBase(m_debugName);
m_pDebugName = m_debugName.GetUTF8();
@ -740,6 +750,7 @@ PEAssembly *PEAssembly::Open(
nullptr, // BindResult
nullptr, // IMetaDataEmit
FALSE, // isSystem
nullptr, // FallbackBinder
pPEImageIL,
pHostAssembly);
@ -833,7 +844,7 @@ PEAssembly* PEAssembly::Open(BINDER_SPACE::Assembly* pBindResult)
};
/* static */
PEAssembly *PEAssembly::Create(IMetaDataAssemblyEmit *pAssemblyEmit)
PEAssembly *PEAssembly::Create(IMetaDataAssemblyEmit *pAssemblyEmit, AssemblyBinder *pFallbackBinder)
{
CONTRACT(PEAssembly *)
{
@ -847,7 +858,7 @@ PEAssembly *PEAssembly::Create(IMetaDataAssemblyEmit *pAssemblyEmit)
// we have.)
SafeComHolder<IMetaDataEmit> pEmit;
pAssemblyEmit->QueryInterface(IID_IMetaDataEmit, (void **)&pEmit);
RETURN new PEAssembly(NULL, pEmit, FALSE);
RETURN new PEAssembly(NULL, pEmit, FALSE, pFallbackBinder);
}
#endif // #ifndef DACCESS_COMPILE
@ -1083,25 +1094,5 @@ TADDR PEAssembly::GetMDInternalRWAddress()
// Returns the AssemblyBinder* instance associated with the PEAssembly
PTR_AssemblyBinder PEAssembly::GetAssemblyBinder()
{
LIMITED_METHOD_CONTRACT;
PTR_AssemblyBinder pBinder = NULL;
PTR_BINDER_SPACE_Assembly pHostAssembly = GetHostAssembly();
if (pHostAssembly)
{
pBinder = pHostAssembly->GetBinder();
}
else
{
// If we do not have a host assembly, check if we are dealing with
// a dynamically emitted assembly and if so, use its fallback load context
// binder reference.
if (IsReflectionEmit())
{
pBinder = GetFallbackBinder();
}
}
return pBinder;
return m_pAssemblyBinder;
}

View File

@ -312,20 +312,18 @@ public:
// For Dynamic assemblies this is the fallback binder.
PTR_AssemblyBinder GetAssemblyBinder();
#ifndef DACCESS_COMPILE
void SetFallbackBinder(PTR_AssemblyBinder pFallbackBinder)
{
LIMITED_METHOD_CONTRACT;
m_pFallbackBinder = pFallbackBinder;
}
#endif //!DACCESS_COMPILE
// For certain assemblies, we do not have m_pHostAssembly since they are not bound using an actual binder.
// An example is Ref-Emitted assemblies. Thus, when such assemblies trigger load of their dependencies,
// we need to ensure they are loaded in appropriate load context.
//
// To enable this, we maintain a concept of "FallbackBinder", which will be set to the Binder of the
// assembly that created the dynamic assembly. If the creator assembly is dynamic itself, then its fallback
// load context would be propagated to the assembly being dynamically generated.
PTR_AssemblyBinder GetFallbackBinder()
{
LIMITED_METHOD_CONTRACT;
return m_pFallbackBinder;
return (m_pHostAssembly != NULL) ? NULL : m_pAssemblyBinder;
}
// ------------------------------------------------------------
@ -341,7 +339,7 @@ public:
static PEAssembly* Open(BINDER_SPACE::Assembly* pBindResult);
static PEAssembly* Create(IMetaDataAssemblyEmit* pEmit);
static PEAssembly* Create(IMetaDataAssemblyEmit* pEmit, AssemblyBinder* pFallbackBinder);
// ------------------------------------------------------------
// Utility functions
@ -372,6 +370,7 @@ private:
BINDER_SPACE::Assembly* pBindResultInfo,
IMetaDataEmit* pEmit,
BOOL isSystem,
AssemblyBinder* pFallbackBinder = NULL,
PEImage* pPEImageIL = NULL,
BINDER_SPACE::Assembly* pHostAssembly = NULL
);
@ -425,15 +424,7 @@ private:
bool m_isSystem;
PTR_BINDER_SPACE_Assembly m_pHostAssembly;
// For certain assemblies, we do not have m_pHostAssembly since they are not bound using an actual binder.
// An example is Ref-Emitted assemblies. Thus, when such assemblies trigger load of their dependencies,
// we need to ensure they are loaded in appropriate load context.
//
// To enable this, we maintain a concept of "FallbackBinder", which will be set to the Binder of the
// assembly that created the dynamic assembly. If the creator assembly is dynamic itself, then its fallback
// load context would be propagated to the assembly being dynamically generated.
PTR_AssemblyBinder m_pFallbackBinder;
PTR_AssemblyBinder m_pAssemblyBinder;
friend struct cdac_data<PEAssembly>;
}; // class PEAssembly
@ -442,6 +433,7 @@ template<>
struct cdac_data<PEAssembly>
{
static constexpr size_t PEImage = offsetof(PEAssembly, m_PEImage);
static constexpr size_t AssemblyBinder = offsetof(PEAssembly, m_pAssemblyBinder);
};
typedef ReleaseHolder<PEAssembly> PEAssemblyHolder;

View File

@ -3309,12 +3309,12 @@ DWORD Thread::DoAppropriateWaitWorker(int countHandles, HANDLE *handles, BOOL wa
}
ULONGLONG dwStart = 0, dwEnd;
retry:
if (millis != INFINITE)
{
dwStart = minipal_lowres_ticks();
}
retry:
if (tryNonblockingWaitFirst)
{
// We have a final wait result from the nonblocking wait above
@ -3344,10 +3344,9 @@ retry:
ret = WAIT_TIMEOUT;
goto WaitCompleted;
}
else
{
millis -= (DWORD)(dwEnd - dwStart);
}
millis -= (DWORD)(dwEnd - dwStart);
dwStart = dwEnd;
}
goto retry;
}
@ -3421,18 +3420,17 @@ retry:
// Compute the new timeout value by assume that the timeout
// is not large enough for more than one wrap
dwEnd = minipal_lowres_ticks();
if (millis != INFINITE)
{
dwEnd = minipal_lowres_ticks();
if (dwEnd - dwStart >= millis)
{
ret = WAIT_TIMEOUT;
goto WaitCompleted;
}
else
{
millis -= (DWORD)(dwEnd - dwStart);
}
millis -= (DWORD)(dwEnd - dwStart);
dwStart = dwEnd;
}
goto retry;
}
@ -3574,11 +3572,9 @@ retry:
ret = WAIT_TIMEOUT;
goto WaitCompleted;
}
else
{
millis -= (DWORD)(dwEnd - dwStart);
}
dwStart = minipal_lowres_ticks();
millis -= (DWORD)(dwEnd - dwStart);
dwStart = dwEnd;
}
//Retry case we don't want to signal again so only do the wait...
ret = WaitForSingleObjectEx(pHandles[1],millis,TRUE);

View File

@ -11,5 +11,9 @@ internal static partial class Interop
[LibraryImport(Libraries.Crypt32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static unsafe partial bool CertSetCertificateContextProperty(SafeCertContextHandle pCertContext, CertContextPropId dwPropId, CertSetPropertyFlags dwFlags, CRYPT_KEY_PROV_INFO* pvData);
[LibraryImport(Libraries.Crypt32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static unsafe partial bool CertSetCertificateContextProperty(nint pCertContext, CertContextPropId dwPropId, CertSetPropertyFlags dwFlags, CRYPT_KEY_PROV_INFO* pvData);
}
}

View File

@ -11,5 +11,9 @@ internal static partial class Interop
[LibraryImport(Libraries.Crypt32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static unsafe partial bool CertSetCertificateContextProperty(SafeCertContextHandle pCertContext, CertContextPropId dwPropId, CertSetPropertyFlags dwFlags, SafeNCryptKeyHandle keyHandle);
[LibraryImport(Libraries.Crypt32, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static unsafe partial bool CertSetCertificateContextProperty(nint pCertContext, CertContextPropId dwPropId, CertSetPropertyFlags dwFlags, SafeNCryptKeyHandle keyHandle);
}
}

View File

@ -1,12 +1,11 @@
// 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.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using Internal.Cryptography;
using Microsoft.Win32.SafeHandles;
internal static partial class Interop
@ -56,9 +55,7 @@ internal static partial class Interop
{
fixed (int* pResult = &result)
{
#if NETSTANDARD || NET
Debug.Assert(RuntimeInformation.IsOSPlatform(OSPlatform.Windows));
#endif
Debug.Assert(Helpers.IsOSPlatformWindows);
ErrorCode errorCode = Interop.NCrypt.NCryptGetProperty(
hObject,

View File

@ -1,7 +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.Runtime.InteropServices;
using Internal.Cryptography;
namespace System.Security.Cryptography
{
@ -15,51 +15,50 @@ namespace System.Security.Cryptography
internal static partial bool SupportsAny()
{
#if !NETFRAMEWORK
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (!Helpers.IsOSPlatformWindows)
{
return false;
}
#endif
return CompositeMLDsaManaged.SupportsAny();
}
internal static partial bool IsAlgorithmSupportedImpl(CompositeMLDsaAlgorithm algorithm)
{
#if !NETFRAMEWORK
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (!Helpers.IsOSPlatformWindows)
{
return false;
}
#endif
return CompositeMLDsaManaged.IsAlgorithmSupportedImpl(algorithm);
}
internal static partial CompositeMLDsa GenerateKeyImpl(CompositeMLDsaAlgorithm algorithm) =>
throw new PlatformNotSupportedException();
internal static partial CompositeMLDsa ImportCompositeMLDsaPublicKeyImpl(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source)
internal static partial CompositeMLDsa GenerateKeyImpl(CompositeMLDsaAlgorithm algorithm)
{
#if !NETFRAMEWORK
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (!Helpers.IsOSPlatformWindows)
{
throw new PlatformNotSupportedException();
}
return CompositeMLDsaManaged.GenerateKeyImpl(algorithm);
}
internal static partial CompositeMLDsa ImportCompositeMLDsaPublicKeyImpl(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source)
{
if (!Helpers.IsOSPlatformWindows)
{
throw new PlatformNotSupportedException();
}
#endif
return CompositeMLDsaManaged.ImportCompositeMLDsaPublicKeyImpl(algorithm, source);
}
internal static partial CompositeMLDsa ImportCompositeMLDsaPrivateKeyImpl(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source)
{
#if !NETFRAMEWORK
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (!Helpers.IsOSPlatformWindows)
{
throw new PlatformNotSupportedException();
}
#endif
return CompositeMLDsaManaged.ImportCompositeMLDsaPrivateKeyImpl(algorithm, source);
}

View File

@ -35,8 +35,28 @@ namespace System.Security.Cryptography
#if NETFRAMEWORK
// RSA-PSS requires RSACng on .NET Framework
private static RSACng CreateRSA() => new RSACng();
private static RSACng CreateRSA(int keySizeInBits) => new RSACng(keySizeInBits);
#elif NETSTANDARD2_0
private static RSA CreateRSA() => RSA.Create();
private static RSA CreateRSA(int keySizeInBits)
{
RSA rsa = RSA.Create();
try
{
rsa.KeySize = keySizeInBits;
return rsa;
}
catch
{
rsa.Dispose();
throw;
}
}
#else
private static RSA CreateRSA() => RSA.Create();
private static RSA CreateRSA(int keySizeInBits) => RSA.Create(keySizeInBits);
#endif
internal override int SignData(
@ -80,8 +100,25 @@ namespace System.Security.Cryptography
#endif
}
public static RsaComponent GenerateKey(RsaAlgorithm algorithm) =>
throw new NotImplementedException();
public static RsaComponent GenerateKey(RsaAlgorithm algorithm)
{
RSA? rsa = null;
try
{
rsa = CreateRSA(algorithm.KeySizeInBits);
// RSA key generation is lazy, so we need to force it to happen eagerly.
_ = rsa.ExportParameters(includePrivateParameters: false);
return new RsaComponent(rsa, algorithm.HashAlgorithmName, algorithm.Padding);
}
catch (CryptographicException)
{
rsa?.Dispose();
throw;
}
}
public static RsaComponent ImportPrivateKey(RsaAlgorithm algorithm, ReadOnlySpan<byte> source)
{

View File

@ -52,8 +52,82 @@ namespace System.Security.Cryptography
});
}
internal static CompositeMLDsa GenerateKeyImpl(CompositeMLDsaAlgorithm algorithm) =>
throw new PlatformNotSupportedException();
internal static CompositeMLDsa GenerateKeyImpl(CompositeMLDsaAlgorithm algorithm)
{
Debug.Assert(IsAlgorithmSupportedImpl(algorithm));
AlgorithmMetadata metadata = s_algorithmMetadata[algorithm];
// draft-ietf-lamps-pq-composite-sigs-latest (July 7, 2025), 4.1
// 1. Generate component keys
//
// mldsaSeed = Random(32)
// (mldsaPK, _) = ML-DSA.KeyGen(mldsaSeed)
// (tradPK, tradSK) = Trad.KeyGen()
MLDsa? mldsaKey = null;
ComponentAlgorithm? tradKey = null;
try
{
mldsaKey = MLDsaImplementation.GenerateKey(metadata.MLDsaAlgorithm);
}
catch (CryptographicException)
{
}
try
{
tradKey = metadata.TraditionalAlgorithm switch
{
RsaAlgorithm rsaAlgorithm => RsaComponent.GenerateKey(rsaAlgorithm),
ECDsaAlgorithm ecdsaAlgorithm => ECDsaComponent.GenerateKey(ecdsaAlgorithm),
_ => FailAndGetNull(),
};
static ComponentAlgorithm? FailAndGetNull()
{
Debug.Fail("Only supported algorithms should reach here.");
return null;
}
}
catch (CryptographicException)
{
}
// 2. Check for component key gen failure
//
// if NOT (mldsaPK, mldsaSK) or NOT (tradPK, tradSK):
// output "Key generation error"
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
static bool KeyGenFailed([NotNullWhen(false)] MLDsa? mldsaKey, [NotNullWhen(false)] ComponentAlgorithm? tradKey) =>
(mldsaKey is null) | (tradKey is null);
if (KeyGenFailed(mldsaKey, tradKey))
{
try
{
Debug.Assert(mldsaKey is null || tradKey is null);
mldsaKey?.Dispose();
tradKey?.Dispose();
}
catch (CryptographicException)
{
}
throw new CryptographicException();
}
// 3. Output the composite public and private keys
//
// pk = SerializePublicKey(mldsaPK, tradPK)
// sk = SerializePrivateKey(mldsaSeed, tradSK)
// return (pk, sk)
return new CompositeMLDsaManaged(algorithm, mldsaKey, tradKey);
}
internal static CompositeMLDsa ImportCompositeMLDsaPublicKeyImpl(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source)
{

View File

@ -53,6 +53,14 @@ namespace Internal.Cryptography
true;
#endif
[SupportedOSPlatformGuard("windows")]
internal static bool IsOSPlatformWindows =>
#if NETFRAMEWORK
true;
#else
RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
#endif
[return: NotNullIfNotNull(nameof(src))]
public static byte[]? CloneByteArray(this byte[]? src)
{

View File

@ -2,8 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Internal.Cryptography;
namespace System.Security.Cryptography
{
@ -50,12 +50,10 @@ namespace System.Security.Cryptography
private static MLDsaAlgorithm AlgorithmFromHandleWithPlatformCheck(CngKey key, out CngKey duplicateKey)
{
#if !NETFRAMEWORK
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (!Helpers.IsOSPlatformWindows)
{
throw new PlatformNotSupportedException();
}
#endif
return AlgorithmFromHandle(key, out duplicateKey);
}

View File

@ -7,7 +7,9 @@ namespace System.Security.Cryptography
{
internal sealed partial class MLDsaImplementation
{
#if !SYSTEM_SECURITY_CRYPTOGRAPHY
[SupportedOSPlatform("windows")]
#endif
internal CngKey CreateEphemeralCng()
{
string bcryptBlobType =

View File

@ -3,7 +3,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Internal.Cryptography;
using Internal.NativeCrypto;
using Microsoft.Win32.SafeHandles;
@ -248,12 +248,10 @@ namespace System.Security.Cryptography
private static SafeBCryptAlgorithmHandle? OpenAlgorithmHandle()
{
#if !NETFRAMEWORK
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (!Helpers.IsOSPlatformWindows)
{
return null;
}
#endif
NTSTATUS status = Interop.BCrypt.BCryptOpenAlgorithmProvider(
out SafeBCryptAlgorithmHandle hAlgorithm,

View File

@ -1,9 +1,8 @@
// 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.Runtime.Versioning;
using Internal.Cryptography;
namespace System.Security.Cryptography
{
@ -50,12 +49,10 @@ namespace System.Security.Cryptography
private static MLKemAlgorithm AlgorithmFromHandleWithPlatformCheck(CngKey key, out CngKey duplicateKey)
{
#if !NETFRAMEWORK
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (!Helpers.IsOSPlatformWindows)
{
throw new PlatformNotSupportedException();
}
#endif
return AlgorithmFromHandle(key, out duplicateKey);
}

View File

@ -3,8 +3,6 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Text;
using Internal.NativeCrypto;
using Microsoft.Win32.SafeHandles;
@ -12,6 +10,7 @@ using NTSTATUS = Interop.BCrypt.NTSTATUS;
using KeyBlobMagicNumber = Interop.BCrypt.KeyBlobMagicNumber;
using KeyBlobType = Interop.BCrypt.KeyBlobType;
using BCRYPT_MLKEM_KEY_BLOB = Interop.BCrypt.BCRYPT_MLKEM_KEY_BLOB;
using Internal.Cryptography;
namespace System.Security.Cryptography
{
@ -139,12 +138,10 @@ namespace System.Security.Cryptography
private static SafeBCryptAlgorithmHandle? OpenAlgorithmHandle()
{
#if !NETFRAMEWORK
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
if (!Helpers.IsOSPlatformWindows)
{
return null;
}
#endif
NTSTATUS status = Interop.BCrypt.BCryptOpenAlgorithmProvider(
out SafeBCryptAlgorithmHandle hAlgorithm,

View File

@ -0,0 +1,333 @@
// 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.Runtime.Versioning;
using Internal.Cryptography;
using Microsoft.Win32.SafeHandles;
#if SYSTEM_SECURITY_CRYPTOGRAPHY
using TCertificate = System.Security.Cryptography.X509Certificates.CertificatePal;
#else
using TCertificate = System.Security.Cryptography.X509Certificates.X509Certificate2;
#endif
namespace System.Security.Cryptography.X509Certificates
{
internal static partial class CertificateHelpers
{
private static partial CryptographicException GetExceptionForLastError();
private static partial SafeNCryptKeyHandle CreateSafeNCryptKeyHandle(IntPtr handle, SafeHandle parentHandle);
private static partial TCertificate CopyFromRawBytes(TCertificate certificate);
private static partial int GuessKeySpec(CngProvider provider, string keyName, bool machineKey, CngAlgorithmGroup? algorithmGroup);
#if !SYSTEM_SECURITY_CRYPTOGRAPHY
[SupportedOSPlatform("windows")]
#endif
internal static TCertificate CopyWithPrivateKey(TCertificate certificate, MLDsa privateKey)
{
if (privateKey is MLDsaCng mldsaCng)
{
CngKey key = mldsaCng.KeyNoDuplicate;
TCertificate? clone = CopyWithPersistedCngKey(certificate, key);
if (clone is not null)
{
return clone;
}
}
if (privateKey is MLDsaImplementation mldsaImplementation)
{
using (CngKey clonedKey = mldsaImplementation.CreateEphemeralCng())
{
return CopyWithEphemeralKey(certificate, clonedKey);
}
}
// MLDsaCng and third-party implementations can be copied by exporting the PKCS#8 and importing it into
// a new MLDsaCng. An alternative to PKCS#8 would be to try the private seed and fall back to secret key,
// but that potentially requires two calls and wouldn't allow implementations to do anything smarter internally.
// Blobs may also be an option for MLDsaCng, but for now we will stick with PKCS#8.
byte[] exportedPkcs8 = privateKey.ExportPkcs8PrivateKey();
using (PinAndClear.Track(exportedPkcs8))
using (MLDsaCng clonedKey = MLDsaCng.ImportPkcs8PrivateKey(exportedPkcs8, out _))
{
CngKey clonedCngKey = clonedKey.KeyNoDuplicate;
if (clonedCngKey.AlgorithmGroup != CngAlgorithmGroup.MLDsa)
{
Debug.Fail($"{nameof(MLDsaCng)} should only give ML-DSA keys.");
throw new CryptographicException();
}
return CopyWithEphemeralKey(certificate, clonedCngKey);
}
}
[SupportedOSPlatform("windows")]
internal static T? GetPrivateKey<T>(TCertificate certificate, Func<CspParameters, T> createCsp, Func<CngKey, T?> createCng)
where T : class, IDisposable
{
using (SafeCertContextHandle certContext = Interop.Crypt32.CertDuplicateCertificateContext(certificate.Handle))
{
SafeNCryptKeyHandle? ncryptKey = TryAcquireCngPrivateKey(certContext, out CngKeyHandleOpenOptions cngHandleOptions);
if (ncryptKey != null)
{
#if SYSTEM_SECURITY_CRYPTOGRAPHY
CngKey cngKey = CngKey.OpenNoDuplicate(ncryptKey, cngHandleOptions);
#else
CngKey cngKey = CngKey.Open(ncryptKey, cngHandleOptions);
#endif
T? result = createCng(cngKey);
// Dispose of cngKey if its ownership did not transfer to the underlying algorithm.
if (result is null)
{
cngKey.Dispose();
}
return result;
}
CspParameters? cspParameters = GetPrivateKeyCsp(certContext);
if (cspParameters == null)
return null;
if (cspParameters.ProviderType == 0)
{
// ProviderType being 0 signifies that this is actually a CNG key, not a CAPI key. Crypt32.dll stuffs the CNG Key Storage Provider
// name into CRYPT_KEY_PROV_INFO->ProviderName, and the CNG key name into CRYPT_KEY_PROV_INFO->KeyContainerName.
string keyStorageProvider = cspParameters.ProviderName!;
string keyName = cspParameters.KeyContainerName!;
CngKey cngKey = CngKey.Open(keyName, new CngProvider(keyStorageProvider));
return createCng(cngKey);
}
else
{
// ProviderType being non-zero signifies that this is a CAPI key.
// We never want to stomp over certificate private keys.
cspParameters.Flags |= CspProviderFlags.UseExistingKey;
return createCsp(cspParameters);
}
}
}
#if !SYSTEM_SECURITY_CRYPTOGRAPHY
[SupportedOSPlatform("windows")]
#endif
private static SafeNCryptKeyHandle? TryAcquireCngPrivateKey(
SafeCertContextHandle certificateContext,
out CngKeyHandleOpenOptions handleOptions)
{
Debug.Assert(certificateContext != null);
Debug.Assert(!certificateContext.IsClosed && !certificateContext.IsInvalid);
// If the certificate has a key handle without a key prov info, return the
// ephemeral key
if (!certificateContext.HasPersistedPrivateKey)
{
int cbData = IntPtr.Size;
if (Interop.Crypt32.CertGetCertificateContextProperty(
certificateContext,
Interop.Crypt32.CertContextPropId.CERT_NCRYPT_KEY_HANDLE_PROP_ID,
out IntPtr privateKeyPtr,
ref cbData))
{
handleOptions = CngKeyHandleOpenOptions.EphemeralKey;
return CreateSafeNCryptKeyHandle(privateKeyPtr, certificateContext);
}
}
bool freeKey = true;
SafeNCryptKeyHandle? privateKey = null;
handleOptions = CngKeyHandleOpenOptions.None;
try
{
if (!Interop.Crypt32.CryptAcquireCertificatePrivateKey(
certificateContext,
Interop.Crypt32.CryptAcquireCertificatePrivateKeyFlags.CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG,
IntPtr.Zero,
out privateKey,
out Interop.Crypt32.CryptKeySpec _,
out freeKey))
{
// The documentation for CryptAcquireCertificatePrivateKey says that freeKey
// should already be false if "key acquisition fails", and it can be presumed
// that privateKey was set to 0. But, just in case:
freeKey = false;
privateKey?.SetHandleAsInvalid();
return null;
}
// It is very unlikely that Windows will tell us !freeKey other than when reporting failure,
// because we set neither CRYPT_ACQUIRE_CACHE_FLAG nor CRYPT_ACQUIRE_USE_PROV_INFO_FLAG, which are
// currently the only two success situations documented. However, any !freeKey response means the
// key's lifetime is tied to that of the certificate, so re-register the handle as a child handle
// of the certificate.
if (!freeKey && privateKey != null && !privateKey.IsInvalid)
{
SafeNCryptKeyHandle newKeyHandle = CreateSafeNCryptKeyHandle(privateKey.DangerousGetHandle(), certificateContext);
privateKey.SetHandleAsInvalid();
privateKey = newKeyHandle;
freeKey = true;
}
return privateKey;
}
catch
{
// If we aren't supposed to free the key, and we're not returning it,
// just tell the SafeHandle to not free itself.
if (privateKey != null && !freeKey)
{
privateKey.SetHandleAsInvalid();
}
throw;
}
}
//
// Returns the private key referenced by a store certificate. Note that despite the return type being declared "CspParameters",
// the key can actually be a CNG key. To distinguish, examine the ProviderType property. If it is 0, this key is a CNG key with
// the various properties of CspParameters being "repurposed" into storing CNG info.
//
// This is a behavior this method inherits directly from the Crypt32 CRYPT_KEY_PROV_INFO semantics.
//
// It would have been nice not to let this ugliness escape out of this helper method. But X509Certificate2.ToString() calls this
// method too so we cannot just change it without breaking its output.
//
#if !SYSTEM_SECURITY_CRYPTOGRAPHY
[SupportedOSPlatform("windows")]
#endif
internal static CspParameters? GetPrivateKeyCsp(SafeCertContextHandle hCertContext)
{
int cbData = 0;
if (!Interop.Crypt32.CertGetCertificateContextProperty(hCertContext, Interop.Crypt32.CertContextPropId.CERT_KEY_PROV_INFO_PROP_ID, null, ref cbData))
{
#if NETFRAMEWORK
int dwErrorCode = Marshal.GetHRForLastWin32Error();
#else
int dwErrorCode = Marshal.GetLastPInvokeError();
#endif
if (dwErrorCode == ErrorCode.CRYPT_E_NOT_FOUND)
return null;
throw dwErrorCode.ToCryptographicException();
}
unsafe
{
byte[] privateKey = new byte[cbData];
fixed (byte* pPrivateKey = privateKey)
{
if (!Interop.Crypt32.CertGetCertificateContextProperty(hCertContext, Interop.Crypt32.CertContextPropId.CERT_KEY_PROV_INFO_PROP_ID, privateKey, ref cbData))
throw GetExceptionForLastError();
Interop.Crypt32.CRYPT_KEY_PROV_INFO* pKeyProvInfo = (Interop.Crypt32.CRYPT_KEY_PROV_INFO*)pPrivateKey;
return new CspParameters
{
ProviderName = Marshal.PtrToStringUni((IntPtr)(pKeyProvInfo->pwszProvName)),
KeyContainerName = Marshal.PtrToStringUni((IntPtr)(pKeyProvInfo->pwszContainerName)),
ProviderType = pKeyProvInfo->dwProvType,
KeyNumber = pKeyProvInfo->dwKeySpec,
Flags = (pKeyProvInfo->dwFlags & Interop.Crypt32.CryptAcquireContextFlags.CRYPT_MACHINE_KEYSET) == Interop.Crypt32.CryptAcquireContextFlags.CRYPT_MACHINE_KEYSET ? CspProviderFlags.UseMachineKeyStore : 0,
};
}
}
}
#if !SYSTEM_SECURITY_CRYPTOGRAPHY
[UnsupportedOSPlatform("browser")]
#endif
internal static unsafe TCertificate? CopyWithPersistedCngKey(TCertificate certificate, CngKey cngKey)
{
if (string.IsNullOrEmpty(cngKey.KeyName))
{
return null;
}
// Make a new pal from bytes.
TCertificate newCert = CopyFromRawBytes(certificate);
CngProvider provider = cngKey.Provider!;
string keyName = cngKey.KeyName;
bool machineKey = cngKey.IsMachineKey;
// CAPI RSA_SIGN keys won't open correctly under CNG without the key number being specified, so
// check to see if we can figure out what key number it needs to re-open.
int keySpec = GuessKeySpec(provider, keyName, machineKey, cngKey.AlgorithmGroup);
Interop.Crypt32.CRYPT_KEY_PROV_INFO keyProvInfo = default;
fixed (char* keyNamePtr = cngKey.KeyName)
fixed (char* provNamePtr = cngKey.Provider!.Provider)
{
keyProvInfo.pwszContainerName = keyNamePtr;
keyProvInfo.pwszProvName = provNamePtr;
keyProvInfo.dwFlags = machineKey ? Interop.Crypt32.CryptAcquireContextFlags.CRYPT_MACHINE_KEYSET : 0;
keyProvInfo.dwKeySpec = keySpec;
if (!Interop.Crypt32.CertSetCertificateContextProperty(
newCert.Handle,
Interop.Crypt32.CertContextPropId.CERT_KEY_PROV_INFO_PROP_ID,
Interop.Crypt32.CertSetPropertyFlags.None,
&keyProvInfo))
{
Exception e = GetExceptionForLastError();
newCert.Dispose();
throw e;
}
}
return newCert;
}
#if !SYSTEM_SECURITY_CRYPTOGRAPHY
[UnsupportedOSPlatform("browser")]
#endif
internal static TCertificate CopyWithEphemeralKey(TCertificate certificate, CngKey cngKey)
{
Debug.Assert(string.IsNullOrEmpty(cngKey.KeyName));
// Handle makes a copy of the handle. This is required given that CertSetCertificateContextProperty accepts a SafeHandle
// and transfers ownership of the handle to the certificate. We can't transfer that ownership out of the cngKey, as it's
// owned by the caller, so we make a copy.
using (SafeNCryptKeyHandle handle = cngKey.Handle)
{
// Make a new certificate from bytes.
TCertificate newCert = CopyFromRawBytes(certificate);
try
{
if (!Interop.Crypt32.CertSetCertificateContextProperty(
newCert.Handle,
Interop.Crypt32.CertContextPropId.CERT_NCRYPT_KEY_HANDLE_PROP_ID,
Interop.Crypt32.CertSetPropertyFlags.CERT_SET_PROPERTY_INHIBIT_PERSIST_FLAG,
handle))
{
throw GetExceptionForLastError();
}
// The value was transferred to the certificate.
handle.SetHandleAsInvalid();
return newCert;
}
catch
{
newCert.Dispose();
throw;
}
}
}
}
}

View File

@ -13,6 +13,7 @@ namespace System.Security.Cryptography.Tests
[Fact]
public static void NullArgumentValidation()
{
AssertExtensions.Throws<ArgumentNullException>("algorithm", static () => CompositeMLDsa.GenerateKey(null));
AssertExtensions.Throws<ArgumentNullException>("algorithm", static () => CompositeMLDsa.IsAlgorithmSupported(null));
AssertExtensions.Throws<ArgumentNullException>("algorithm", static () => CompositeMLDsa.ImportCompositeMLDsaPrivateKey(null, Array.Empty<byte>()));
AssertExtensions.Throws<ArgumentNullException>("algorithm", static () => CompositeMLDsa.ImportCompositeMLDsaPrivateKey(null, ReadOnlySpan<byte>.Empty));
@ -169,6 +170,17 @@ namespace System.Security.Cryptography.Tests
key);
}
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public static void AlgorithmMatches_GenerateKey(CompositeMLDsaAlgorithm algorithm)
{
AssertThrowIfNotSupported(() =>
{
using CompositeMLDsa dsa = CompositeMLDsa.GenerateKey(algorithm);
Assert.Equal(algorithm, dsa.Algorithm);
});
}
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmIetfVectorsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public static void AlgorithmMatches_Import(CompositeMLDsaTestData.CompositeMLDsaTestVector vector)
@ -186,7 +198,7 @@ namespace System.Security.Cryptography.Tests
public static void IsSupported_AgreesWithPlatform()
{
// Composites are supported everywhere MLDsa is supported
Assert.Equal(MLDsa.IsSupported && !PlatformDetection.IsLinux, CompositeMLDsa.IsSupported);
Assert.Equal(MLDsa.IsSupported, CompositeMLDsa.IsSupported);
}
[Theory]
@ -195,7 +207,7 @@ namespace System.Security.Cryptography.Tests
{
bool supported = CompositeMLDsaTestHelpers.ExecuteComponentFunc(
algorithm,
_ => MLDsa.IsSupported && !PlatformDetection.IsLinux,
_ => MLDsa.IsSupported,
_ => false,
_ => false);

View File

@ -8,6 +8,13 @@ namespace System.Security.Cryptography.Tests
[ConditionalClass(typeof(CompositeMLDsa), nameof(CompositeMLDsa.IsSupported))]
public sealed class CompositeMLDsaImplementationTests : CompositeMLDsaTestsBase
{
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public static void CompositeMLDsaIsOnlyPublicAncestor_GenerateKey(CompositeMLDsaAlgorithm algorithm)
{
AssertCompositeMLDsaIsOnlyPublicAncestor(() => CompositeMLDsa.GenerateKey(algorithm));
}
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmIetfVectorsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public static void CompositeMLDsaIsOnlyPublicAncestor_Import(CompositeMLDsaTestData.CompositeMLDsaTestVector info)
@ -32,6 +39,66 @@ namespace System.Security.Cryptography.Tests
Assert.Equal(typeof(CompositeMLDsa), keyType);
}
#region Roundtrip by exporting then importing
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public void RoundTrip_Export_Import_PublicKey(CompositeMLDsaAlgorithm algorithm)
{
// Generate new key
using CompositeMLDsa dsa = GenerateKey(algorithm);
CompositeMLDsaTestHelpers.AssertExportPublicKey(
export =>
{
// Roundtrip using public key. First export it.
byte[] exportedPublicKey = export(dsa);
CompositeMLDsaTestHelpers.AssertImportPublicKey(
import =>
{
// Then import it.
using CompositeMLDsa roundTrippedDsa = import();
// Verify the roundtripped object has the same key
Assert.Equal(algorithm, roundTrippedDsa.Algorithm);
AssertExtensions.SequenceEqual(dsa.ExportCompositeMLDsaPublicKey(), roundTrippedDsa.ExportCompositeMLDsaPublicKey());
Assert.Throws<CryptographicException>(() => roundTrippedDsa.ExportCompositeMLDsaPrivateKey());
},
algorithm,
exportedPublicKey);
});
}
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public void RoundTrip_Export_Import_PrivateKey(CompositeMLDsaAlgorithm algorithm)
{
// Generate new key
using CompositeMLDsa dsa = GenerateKey(algorithm);
CompositeMLDsaTestHelpers.AssertExportPrivateKey(
export =>
{
// Roundtrip using secret key. First export it.
byte[] exportedSecretKey = export(dsa);
CompositeMLDsaTestHelpers.AssertImportPrivateKey(
import =>
{
// Then import it.
using CompositeMLDsa roundTrippedDsa = import();
// Verify the roundtripped object has the same key
Assert.Equal(algorithm, roundTrippedDsa.Algorithm);
AssertExtensions.SequenceEqual(dsa.ExportCompositeMLDsaPrivateKey(), roundTrippedDsa.ExportCompositeMLDsaPrivateKey());
AssertExtensions.SequenceEqual(dsa.ExportCompositeMLDsaPublicKey(), roundTrippedDsa.ExportCompositeMLDsaPublicKey());
},
algorithm,
exportedSecretKey);
});
}
#endregion Roundtrip by exporting then importing
#region Roundtrip by importing then exporting
[Theory]
@ -60,6 +127,9 @@ namespace System.Security.Cryptography.Tests
#endregion Roundtrip by importing then exporting
protected override CompositeMLDsa GenerateKey(CompositeMLDsaAlgorithm algorithm) =>
CompositeMLDsa.GenerateKey(algorithm);
protected override CompositeMLDsa ImportPublicKey(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source) =>
CompositeMLDsa.ImportCompositeMLDsaPublicKey(algorithm, source);

View File

@ -71,6 +71,9 @@ namespace System.Security.Cryptography.Tests
public static IEnumerable<object[]> AllAlgorithmsTestData =>
AllAlgorithms.Select(v => new object[] { v });
public static IEnumerable<object[]> SupportedAlgorithmsTestData =>
AllAlgorithms.Where(CompositeMLDsa.IsAlgorithmSupported).Select(v => new object[] { v });
internal static MLDsaKeyInfo GetMLDsaIetfTestVector(CompositeMLDsaAlgorithm algorithm)
{
MLDsaAlgorithm mldsaAlgorithm = CompositeMLDsaTestHelpers.MLDsaAlgorithms[algorithm];

View File

@ -8,9 +8,113 @@ namespace System.Security.Cryptography.Tests
[ConditionalClass(typeof(CompositeMLDsa), nameof(CompositeMLDsa.IsSupported))]
public abstract class CompositeMLDsaTestsBase
{
protected abstract CompositeMLDsa GenerateKey(CompositeMLDsaAlgorithm algorithm);
protected abstract CompositeMLDsa ImportPrivateKey(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
protected abstract CompositeMLDsa ImportPublicKey(CompositeMLDsaAlgorithm algorithm, ReadOnlySpan<byte> source);
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public void GenerateSignVerifyWithPublicKey(CompositeMLDsaAlgorithm algorithm)
{
byte[] signature;
byte[] data = [0, 1, 2, 3];
byte[] exportedPublicKey;
using (CompositeMLDsa generatedKey = GenerateKey(algorithm))
{
signature = generatedKey.SignData(data);
ExerciseSuccessfulVerify(generatedKey, data, signature, []);
exportedPublicKey = generatedKey.ExportCompositeMLDsaPublicKey();
}
using (CompositeMLDsa publicKey = ImportPublicKey(algorithm, exportedPublicKey))
{
ExerciseSuccessfulVerify(publicKey, data, signature, []);
Assert.Throws<CryptographicException>(() => publicKey.SignData(data));
}
}
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public void GenerateSignVerifyWithPrivateKey(CompositeMLDsaAlgorithm algorithm)
{
byte[] signature;
byte[] data = [0, 1, 2, 3];
byte[] exportedPrivateKey;
using (CompositeMLDsa generatedKey = GenerateKey(algorithm))
{
signature = generatedKey.SignData(data);
exportedPrivateKey = generatedKey.ExportCompositeMLDsaPrivateKey();
}
using (CompositeMLDsa privateKey = ImportPrivateKey(algorithm, exportedPrivateKey))
{
ExerciseSuccessfulVerify(privateKey, data, signature, []);
signature.AsSpan().Clear();
privateKey.SignData(data, signature, []);
ExerciseSuccessfulVerify(privateKey, data, signature, []);
}
}
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public void GenerateSignVerifyNoContext(CompositeMLDsaAlgorithm algorithm)
{
using CompositeMLDsa dsa = GenerateKey(algorithm);
byte[] data = [1, 2, 3, 4, 5];
byte[] signature = dsa.SignData(data);
ExerciseSuccessfulVerify(dsa, data, signature, []);
signature.AsSpan().Clear();
dsa.SignData(data, signature, Array.Empty<byte>());
ExerciseSuccessfulVerify(dsa, data, signature, Array.Empty<byte>());
}
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public void GenerateSignVerifyWithContext(CompositeMLDsaAlgorithm algorithm)
{
using CompositeMLDsa dsa = GenerateKey(algorithm);
byte[] context = [1, 1, 3, 5, 6];
byte[] data = [1, 2, 3, 4, 5];
byte[] signature = dsa.SignData(data, context);
ExerciseSuccessfulVerify(dsa, data, signature, context);
}
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public void GenerateSignVerifyEmptyMessageNoContext(CompositeMLDsaAlgorithm algorithm)
{
using CompositeMLDsa dsa = GenerateKey(algorithm);
byte[] signature = dsa.SignData([]);
ExerciseSuccessfulVerify(dsa, [], signature, []);
signature.AsSpan().Clear();
dsa.SignData(Array.Empty<byte>(), signature, Array.Empty<byte>());
ExerciseSuccessfulVerify(dsa, [], signature, []);
}
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public void GenerateSignVerifyEmptyMessageWithContext(CompositeMLDsaAlgorithm algorithm)
{
using CompositeMLDsa dsa = GenerateKey(algorithm);
byte[] context = [1, 1, 3, 5, 6];
byte[] signature = dsa.SignData([], context);
ExerciseSuccessfulVerify(dsa, [], signature, context);
signature.AsSpan().Clear();
dsa.SignData(Array.Empty<byte>(), signature, context);
ExerciseSuccessfulVerify(dsa, [], signature, context);
}
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmIetfVectorsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public void ImportExportVerify(CompositeMLDsaTestData.CompositeMLDsaTestVector vector)
@ -87,6 +191,56 @@ namespace System.Security.Cryptography.Tests
export => CompositeMLDsaTestHelpers.AssertPrivateKeyEquals(vector.Algorithm, vector.SecretKey, export(dsa)));
}
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public void Generate_Export_Import_PublicKey(CompositeMLDsaAlgorithm algorithm)
{
byte[] exportedPublicKey;
using (CompositeMLDsa dsa = GenerateKey(algorithm))
{
exportedPublicKey = dsa.ExportCompositeMLDsaPublicKey();
using (CompositeMLDsa importedDsa = ImportPublicKey(algorithm, exportedPublicKey))
{
Assert.Throws<CryptographicException>(() => importedDsa.ExportCompositeMLDsaPrivateKey());
AssertExtensions.SequenceEqual(exportedPublicKey, importedDsa.ExportCompositeMLDsaPublicKey());
}
}
using (CompositeMLDsa importedDsa = ImportPublicKey(algorithm, exportedPublicKey))
{
Assert.Throws<CryptographicException>(() => importedDsa.ExportCompositeMLDsaPrivateKey());
AssertExtensions.SequenceEqual(exportedPublicKey, importedDsa.ExportCompositeMLDsaPublicKey());
}
}
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public void Generate_Export_Import_PrivateKey(CompositeMLDsaAlgorithm algorithm)
{
byte[] exportedPrivateKey;
byte[] exportedPublicKey;
using (CompositeMLDsa dsa = GenerateKey(algorithm))
{
exportedPrivateKey = dsa.ExportCompositeMLDsaPrivateKey();
exportedPublicKey = dsa.ExportCompositeMLDsaPublicKey();
using (CompositeMLDsa importedDsa = ImportPrivateKey(algorithm, exportedPrivateKey))
{
AssertExtensions.SequenceEqual(exportedPrivateKey, importedDsa.ExportCompositeMLDsaPrivateKey());
AssertExtensions.SequenceEqual(exportedPublicKey, importedDsa.ExportCompositeMLDsaPublicKey());
}
}
using (CompositeMLDsa importedDsa = ImportPrivateKey(algorithm, exportedPrivateKey))
{
AssertExtensions.SequenceEqual(exportedPrivateKey, importedDsa.ExportCompositeMLDsaPrivateKey());
AssertExtensions.SequenceEqual(exportedPublicKey, importedDsa.ExportCompositeMLDsaPublicKey());
}
}
[Theory]
[MemberData(nameof(CompositeMLDsaTestData.SupportedAlgorithmIetfVectorsTestData), MemberType = typeof(CompositeMLDsaTestData))]
public void SignData_PublicKeyOnlyThrows(CompositeMLDsaTestData.CompositeMLDsaTestVector vector)

View File

@ -6,7 +6,6 @@ using System.Security.Cryptography.Tests;
using System.Security.Cryptography.SLHDsa.Tests;
using Test.Cryptography;
using Xunit;
using Xunit.Sdk;
namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreation
{
@ -18,6 +17,9 @@ namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreatio
private static partial Func<X509Certificate2, SlhDsa, X509Certificate2> CopyWithPrivateKey_SlhDsa { get; }
private static partial Func<X509Certificate2, SlhDsa> GetSlhDsaPublicKey { get; }
private static partial Func<X509Certificate2, SlhDsa> GetSlhDsaPrivateKey { get; }
private static partial Func<X509Certificate2, MLDsa, X509Certificate2> CopyWithPrivateKey_MLDsa { get; }
private static partial Func<X509Certificate2, MLDsa> GetMLDsaPublicKey { get; }
private static partial Func<X509Certificate2, MLDsa> GetMLDsaPrivateKey { get; }
[ConditionalFact(typeof(SlhDsa), nameof(SlhDsa.IsSupported))]
public static void GetSlhDsaPublicKeyTest()
@ -295,6 +297,415 @@ namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreatio
}
}
[ConditionalFact(typeof(MLDsa), nameof(MLDsa.IsSupported))]
public static void GetMLDsaPublicKeyTest()
{
// Cert without private key
using (X509Certificate2 cert = X509CertificateLoader.LoadCertificate(MLDsaTestsData.IetfMLDsa44.Certificate))
using (MLDsa? certKey = GetMLDsaPublicKey(cert))
{
Assert.NotNull(certKey);
byte[] publicKey = certKey.ExportMLDsaPublicKey();
AssertExtensions.SequenceEqual(MLDsaTestsData.IetfMLDsa44.PublicKey, publicKey);
// Verify the key is not actually private
Assert.ThrowsAny<CryptographicException>(() => certKey.SignData([1, 2, 3]));
}
// Cert with private key
using (X509Certificate2 cert = LoadMLDsaIetfCertificateWithPrivateKey())
using (MLDsa? certKey = GetMLDsaPublicKey(cert))
{
Assert.NotNull(certKey);
byte[] publicKey = certKey.ExportMLDsaPublicKey();
AssertExtensions.SequenceEqual(MLDsaTestsData.IetfMLDsa44.PublicKey, publicKey);
// Verify the key is not actually private
Assert.ThrowsAny<CryptographicException>(() => certKey.SignData([1, 2, 3]));
}
}
[ConditionalFact(typeof(MLDsa), nameof(MLDsa.IsSupported))]
public static void GetMLDsaPrivateKeyTest()
{
// Cert without private key
using (X509Certificate2 cert = X509CertificateLoader.LoadCertificate(MLDsaTestsData.IetfMLDsa44.Certificate))
{
using (MLDsa? certKey = GetMLDsaPrivateKey(cert))
{
Assert.Null(certKey);
}
}
// Cert with private key
using (X509Certificate2 certWithPrivateKey = LoadMLDsaIetfCertificateWithPrivateKey())
{
using (MLDsa? certKey = GetMLDsaPrivateKey(certWithPrivateKey))
{
Assert.NotNull(certKey);
// Verify the key is actually private
byte[] privateSeed = certKey.ExportMLDsaPrivateSeed();
AssertExtensions.SequenceEqual(
MLDsaTestsData.IetfMLDsa44.PrivateSeed,
privateSeed);
}
}
}
[ConditionalFact(typeof(MLDsa), nameof(MLDsa.IsSupported))]
public static void CheckCopyWithPrivateKey_MLDSA()
{
Random rng = new Random();
using (X509Certificate2 pubOnly = X509CertificateLoader.LoadCertificate(MLDsaTestsData.IetfMLDsa65.Certificate))
using (MLDsa privKey = MLDsa.ImportMLDsaPrivateSeed(MLDsaAlgorithm.MLDsa65, MLDsaTestsData.IetfMLDsa65.PrivateSeed))
using (X509Certificate2 wrongAlg = X509CertificateLoader.LoadCertificate(TestData.CertWithEnhancedKeyUsage))
{
CheckCopyWithPrivateKey(
pubOnly,
wrongAlg,
privKey,
[
() => MLDsa.GenerateKey(MLDsaAlgorithm.MLDsa44),
() => MLDsa.GenerateKey(MLDsaAlgorithm.MLDsa65),
() => MLDsa.GenerateKey(MLDsaAlgorithm.MLDsa87),
],
(cert, key) => cert.CopyWithPrivateKey(key),
cert => cert.GetMLDsaPublicKey(),
cert => cert.GetMLDsaPrivateKey(),
(priv, pub) =>
{
byte[] data = new byte[rng.Next(97)];
rng.NextBytes(data);
byte[] signature = priv.SignData(data);
Assert.True(pub.VerifyData(data, signature));
});
}
}
[ConditionalFact(typeof(MLDsa), nameof(MLDsa.IsSupported))]
public static void CheckCopyWithPrivateKey_MLDsa_OtherMLDsa_SecretKey()
{
using (X509Certificate2 pubOnly = X509CertificateLoader.LoadCertificate(MLDsaTestsData.IetfMLDsa44.Certificate))
{
using (MLDsaTestImplementation publicMLDsa = MLDsaTestImplementation.CreateOverriddenCoreMethodsFail(MLDsaAlgorithm.MLDsa44))
{
Exception e = new Exception("no secret key");
// The private key can be retrieved directly or from PKCS#8. If the seed is not available,
// it should fall back to the secret key.
publicMLDsa.TryExportPkcs8PrivateKeyHook = (_, out _) => throw e;
publicMLDsa.ExportMLDsaSecretKeyHook = _ => throw e;
publicMLDsa.ExportMLDsaPrivateSeedHook = _ => throw new CryptographicException("Should signal to try secret key");
publicMLDsa.ExportMLDsaPublicKeyHook = MLDsaTestsData.IetfMLDsa44.PublicKey.CopyTo;
Assert.Same(e, AssertExtensions.Throws<Exception>(() => CopyWithPrivateKey_MLDsa(pubOnly, publicMLDsa)));
}
MLDsaTestImplementation privateMLDsa = MLDsaTestImplementation.CreateOverriddenCoreMethodsFail(MLDsaAlgorithm.MLDsa44);
privateMLDsa.ExportMLDsaPrivateSeedHook = _ => throw new CryptographicException("Should signal to try secret key"); ;
privateMLDsa.ExportMLDsaPublicKeyHook = MLDsaTestsData.IetfMLDsa44.PublicKey.CopyTo;
privateMLDsa.ExportMLDsaSecretKeyHook = MLDsaTestsData.IetfMLDsa44.SecretKey.CopyTo;
privateMLDsa.TryExportPkcs8PrivateKeyHook = (dest, out written) =>
{
if (MLDsaTestsData.IetfMLDsa44.Pkcs8PrivateKey_Seed.AsSpan().TryCopyTo(dest))
{
written = MLDsaTestsData.IetfMLDsa44.Pkcs8PrivateKey_Seed.Length;
return true;
}
written = 0;
return false;
};
using (X509Certificate2 privCert = CopyWithPrivateKey_MLDsa(pubOnly, privateMLDsa))
{
AssertExtensions.TrueExpression(privCert.HasPrivateKey);
using (MLDsa certPrivateMLDsa = GetMLDsaPrivateKey(privCert))
{
byte[] secretKey = certPrivateMLDsa.ExportMLDsaSecretKey();
AssertExtensions.SequenceEqual(
MLDsaTestsData.IetfMLDsa44.SecretKey,
secretKey);
privateMLDsa.Dispose();
privateMLDsa.ExportMLDsaPrivateSeedHook = _ => Assert.Fail();
privateMLDsa.ExportMLDsaPublicKeyHook = _ => Assert.Fail();
privateMLDsa.ExportMLDsaSecretKeyHook = _ => Assert.Fail();
privateMLDsa.TryExportPkcs8PrivateKeyHook = (_, out w) => { Assert.Fail(); w = 0; return false; };
// Ensure the key is actual a clone
secretKey = certPrivateMLDsa.ExportMLDsaSecretKey();
AssertExtensions.SequenceEqual(
MLDsaTestsData.IetfMLDsa44.SecretKey,
secretKey);
}
}
}
}
[ConditionalFact(typeof(MLDsa), nameof(MLDsa.IsSupported))]
public static void CheckCopyWithPrivateKey_MLDsa_OtherMLDsa_PrivateSeed()
{
using (X509Certificate2 pubOnly = X509CertificateLoader.LoadCertificate(MLDsaTestsData.IetfMLDsa44.Certificate))
{
using (MLDsaTestImplementation publicMLDsa = MLDsaTestImplementation.CreateOverriddenCoreMethodsFail(MLDsaAlgorithm.MLDsa44))
{
Exception e = new Exception("no secret key");
// The private seed can be retrieved directly or from PKCS#8
publicMLDsa.TryExportPkcs8PrivateKeyHook = (_, out _) => throw e;
publicMLDsa.ExportMLDsaPrivateSeedHook = _ => throw e;
publicMLDsa.ExportMLDsaPublicKeyHook = MLDsaTestsData.IetfMLDsa44.PublicKey.CopyTo;
Assert.Same(e, AssertExtensions.Throws<Exception>(() => CopyWithPrivateKey_MLDsa(pubOnly, publicMLDsa)));
}
MLDsaTestImplementation privateMLDsa = MLDsaTestImplementation.CreateOverriddenCoreMethodsFail(MLDsaAlgorithm.MLDsa44);
privateMLDsa.ExportMLDsaPublicKeyHook = MLDsaTestsData.IetfMLDsa44.PublicKey.CopyTo;
privateMLDsa.ExportMLDsaPrivateSeedHook = MLDsaTestsData.IetfMLDsa44.PrivateSeed.CopyTo;
privateMLDsa.TryExportPkcs8PrivateKeyHook = (dest, out written) =>
{
if (MLDsaTestsData.IetfMLDsa44.Pkcs8PrivateKey_Seed.AsSpan().TryCopyTo(dest))
{
written = MLDsaTestsData.IetfMLDsa44.Pkcs8PrivateKey_Seed.Length;
return true;
}
written = 0;
return false;
};
using (X509Certificate2 privCert = CopyWithPrivateKey_MLDsa(pubOnly, privateMLDsa))
{
AssertExtensions.TrueExpression(privCert.HasPrivateKey);
using (MLDsa certPrivateMLDsa = GetMLDsaPrivateKey(privCert))
{
byte[] secretKey = certPrivateMLDsa.ExportMLDsaPrivateSeed();
AssertExtensions.SequenceEqual(
MLDsaTestsData.IetfMLDsa44.PrivateSeed,
secretKey);
privateMLDsa.Dispose();
privateMLDsa.ExportMLDsaPublicKeyHook = _ => Assert.Fail();
privateMLDsa.ExportMLDsaPrivateSeedHook = _ => Assert.Fail();
privateMLDsa.TryExportPkcs8PrivateKeyHook = (_, out w) => { Assert.Fail(); w = 0; return false; };
// Ensure the key is actual a clone
secretKey = certPrivateMLDsa.ExportMLDsaPrivateSeed();
AssertExtensions.SequenceEqual(
MLDsaTestsData.IetfMLDsa44.PrivateSeed,
secretKey);
}
}
}
}
[ConditionalFact(typeof(MLDsa), nameof(MLDsa.IsSupported))]
public static void CheckCopyWithPrivateKey_MLDsa_OtherMLDsa_WrongPkcs8()
{
const string rsaPkcs8Base64 = """
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCnDBnNKDxAQOPeb8M+OvGurjRi
2lB+a2VdTspZXxvRt2wXwtyyqWcV+wGGvLE74hGE21UFPW4Jd2iefuKlkn9U/dvcPEGQjuClO1EY
FlFcZ1Y9LwNuXuS1IA4doiuzEyYgxXJje8w5CIisqXmwZEJX/OY4yEYE4OV/3xoI5FIGv9Kp2AsU
ttGaD2uzgsrCUe1Cj7L6IhBBiqvcp55icXlWXb0oSjd/ovhFgrn9+IPW7ln8wGRRRHKwIi+TBuXK
k7WOVcUECaPZIR7n8AuHHJ90sZHieSnIiMKzah8ZnFc1yG1Y9EnP3jWT00SZ2at84j/vkwfK87lj
gf9Gx9Gg5kVZAgMBAAECggEARofWcQf3AI4laCq6PhE3MDD/j2lsKSSBRPdaeoeswEx4yEOPWaQr
EV3M1C3hi041ZWoSKMc6KacQNjOO0KfdOW6CISgT6sxYz4sO/2OU8LX09JpgEX7hhBRHwX1ShCam
p5mWZajEnqQayQQ5jB+Y33u5XOo6nh6y5920KWL1u0Ed3aYHVa/+rfCIfctsEx+n2CBsiAX4fTaB
ZtTpZaQlDrDnOPtPDcJ1NOq7L/JwBYn6euBwkOZIl9VQ0q0mZ5YkXr9WB0BwNlRSvqa06b7y16qS
1Y1M4jRzoYEl4hh7mKzVDrkyAVH2oFEsplMIufIQpt3rFvj+vUQciCY2bz8VrQKBgQDcffvWyKnz
Twr/nReoGXvkkYt/RBEZsNVZko4BJK0RYXQLUrPCFy8kxzIidgVPYlVvym3hYYpRXMnydTR0pEHn
UWv9fFp6EISFdssEvP4PvQq1T0EPH6yTo1DflUe82YDtYA/l/nqg24IqYaa7nF2O1RESLYFixh7y
oM1vsn42TwKBgQDB8swph+ei+1v+V64I3s/rQUhl/6FKwxVF4UbmBKlbd/YtAr1ccljNefeXRmWc
KmsVGO/Py5eD+VGNk9EUzZLRFqRnbbhTxPYufCGd4cImvlPceN6U/QS5x/52FJPUvIKVNWw7rgxd
8Fr5YZDNi28ChvVdJBozjIgthElGQ82H1wKBgFikQVmAxGovbcGDex42WIt0Q7t/Nsy4PZ1MANDO
2NDy978RmXi+71H+ztXx0oKuiqBtpi0ElKHPBtT1b4gw/Nms7xgyJQGLoGszbbzS6eST4Dkxynr1
BeE4t+uazQNMAbvscZfJ7ay7cqHtLiWgYDBq0fkX2DtIYOqz4MM14+2bAoGAJ5Qisb74ODxPU6IU
8950U6/o1FfMVHNnHfGRBFOjM/VRGXJbrkfvc08WhZpqFepaG94Q4jjL3LS+PcQSgMpK0bxrJGgx
m3awPmA6g/uUIU/p0S4hTgosMrVrajFc0ab+hvB1+9/SykDIb+fHIwr3Rm7AF5fMeQSOras3QM2J
XdUCgYEAtsHg+5g8UmIivixcowQ0jd4xoFV54oxJqIywtwWbHKkkiTEC87Y/bM+yB/FOz9CY6zsj
czacDBoMMwiiYWhY4fgfwOpsw+B+ZOX95bBd99iGip5Rv+QeOUCoDibCo0thYuF3ZeRCa+A02xVe
urOzLZcZZcL09b35iX6IaxosmNM=
""";
byte[] rsaPkcs8 = Convert.FromBase64String(rsaPkcs8Base64);
using (X509Certificate2 ietfCert = X509CertificateLoader.LoadCertificate(MLDsaTestsData.IetfMLDsa44.Certificate))
using (MLDsaTestImplementation keyThatExportsRsaPkcs8 = MLDsaTestImplementation.CreateOverriddenCoreMethodsFail(MLDsaAlgorithm.MLDsa44))
{
keyThatExportsRsaPkcs8.ExportMLDsaPublicKeyHook = MLDsaTestsData.IetfMLDsa44.PublicKey.CopyTo;
keyThatExportsRsaPkcs8.ExportMLDsaPrivateSeedHook = MLDsaTestsData.IetfMLDsa44.PrivateSeed.CopyTo;
// Export RSA PKCS#8
keyThatExportsRsaPkcs8.TryExportPkcs8PrivateKeyHook = (dest, out written) =>
{
if (rsaPkcs8.AsSpan().TryCopyTo(dest))
{
written = rsaPkcs8.Length;
return true;
}
written = 0;
return false;
};
if (PlatformDetection.IsWindows)
{
// Only Windows uses PKCS#8 for pairing key to cert.
AssertExtensions.Throws<CryptographicException>(() => ietfCert.CopyWithPrivateKey(keyThatExportsRsaPkcs8));
}
else
{
// Assert.NoThrow
using (ietfCert.CopyWithPrivateKey(keyThatExportsRsaPkcs8)) { }
}
}
}
[ConditionalFact(typeof(MLDsa), nameof(MLDsa.IsSupported))]
public static void CheckCopyWithPrivateKey_MLDsa_OtherMLDsa_MalformedPkcs8()
{
using (X509Certificate2 ietfCert = X509CertificateLoader.LoadCertificate(MLDsaTestsData.IetfMLDsa44.Certificate))
using (MLDsaTestImplementation keyThatExportsMalformedPkcs8 = MLDsaTestImplementation.CreateOverriddenCoreMethodsFail(MLDsaAlgorithm.MLDsa44))
{
keyThatExportsMalformedPkcs8.ExportMLDsaPublicKeyHook = MLDsaTestsData.IetfMLDsa44.PublicKey.CopyTo;
keyThatExportsMalformedPkcs8.ExportMLDsaPrivateSeedHook = MLDsaTestsData.IetfMLDsa44.PrivateSeed.CopyTo;
// Export malformed PKCS#8
keyThatExportsMalformedPkcs8.TryExportPkcs8PrivateKeyHook =
(dest, out written) =>
{
written = 0;
return true;
};
if (PlatformDetection.IsWindows)
{
// Only Windows uses PKCS#8 for pairing key to cert.
AssertExtensions.Throws<CryptographicException>(() => ietfCert.CopyWithPrivateKey(keyThatExportsMalformedPkcs8));
}
else
{
// Assert.NoThrow
using (ietfCert.CopyWithPrivateKey(keyThatExportsMalformedPkcs8)) { }
}
}
}
[ConditionalFact(typeof(MLDsa), nameof(MLDsa.IsSupported))]
[PlatformSpecific(TestPlatforms.Windows)]
public static void AssociatePersistedKey_CNG_MLDsa()
{
const string KeyName = $"{nameof(PrivateKeyAssociationTests)}_{nameof(AssociatePersistedKey_CNG_MLDsa)}";
CngKeyCreationParameters creationParameters = new CngKeyCreationParameters()
{
ExportPolicy = CngExportPolicies.None,
Provider = CngProvider.MicrosoftSoftwareKeyStorageProvider,
KeyCreationOptions = CngKeyCreationOptions.OverwriteExistingKey,
};
CngProperty parameterSet = MLDsaTestHelpers.GetCngProperty(MLDsaAlgorithm.MLDsa44);
creationParameters.Parameters.Add(parameterSet);
// Blob for IETF ML-DSA-44 seed
byte[] pqDsaSeedBlob = Convert.FromBase64String("RFNTUwYAAAAgAAAANAA0AAAAAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=");
CngProperty mldsaBlob = new CngProperty(
CngKeyBlobFormat.PQDsaPrivateSeedBlob.ToString(),
pqDsaSeedBlob,
CngPropertyOptions.None);
creationParameters.Parameters.Add(mldsaBlob);
CngKey cngKey = null;
byte[] signature = new byte[MLDsaAlgorithm.MLDsa44.SignatureSizeInBytes];
try
{
cngKey = CngKey.Create(CngAlgorithm.MLDsa, KeyName, creationParameters);
using (MLDsaCng mldsaCng = new MLDsaCng(cngKey))
using (X509Certificate2 unpairedCert = X509CertificateLoader.LoadCertificate(MLDsaTestsData.IetfMLDsa44.Certificate))
using (X509Certificate2 cert = unpairedCert.CopyWithPrivateKey(mldsaCng))
using (MLDsa mldsa = cert.GetMLDsaPrivateKey())
{
mldsa.SignData("test"u8, signature);
Assert.True(mldsaCng.VerifyData("test"u8, signature));
}
// Some certs have disposed, did they delete the key?
using (CngKey stillPersistedKey = CngKey.Open(KeyName, CngProvider.MicrosoftSoftwareKeyStorageProvider))
using (MLDsaCng mldsa = new MLDsaCng(stillPersistedKey))
{
mldsa.SignData("test"u8, signature);
}
}
finally
{
cngKey?.Delete();
}
}
[ConditionalFact(typeof(MLDsa), nameof(MLDsa.IsSupported))]
[PlatformSpecific(TestPlatforms.Windows)]
public static void AssociateEphemeralKey_CNG_MLDsa()
{
CngKeyCreationParameters creationParameters = new CngKeyCreationParameters
{
ExportPolicy = CngExportPolicies.AllowPlaintextExport,
};
CngProperty parameterSet = MLDsaTestHelpers.GetCngProperty(MLDsaAlgorithm.MLDsa44);
creationParameters.Parameters.Add(parameterSet);
// Blob for IETF ML-DSA-44 seed
byte[] pqDsaSeedBlob = Convert.FromBase64String("RFNTUwYAAAAgAAAANAA0AAAAAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=");
CngProperty mldsaBlob = new CngProperty(
CngKeyBlobFormat.PQDsaPrivateSeedBlob.ToString(),
pqDsaSeedBlob,
CngPropertyOptions.None);
creationParameters.Parameters.Add(mldsaBlob);
byte[] signature = new byte[MLDsaAlgorithm.MLDsa44.SignatureSizeInBytes];
using (CngKey ephemeralCngKey = CngKey.Create(CngAlgorithm.MLDsa, keyName: null, creationParameters))
{
using (MLDsaCng mldsaCng = new MLDsaCng(ephemeralCngKey))
{
using (X509Certificate2 unpairedCert = X509CertificateLoader.LoadCertificate(MLDsaTestsData.IetfMLDsa44.Certificate))
using (X509Certificate2 cert = unpairedCert.CopyWithPrivateKey(mldsaCng))
using (MLDsa mldsa = cert.GetMLDsaPrivateKey())
{
mldsa.SignData("test"u8, signature);
Assert.True(mldsaCng.VerifyData("test"u8, signature));
}
// Run a few iterations to catch nondeterministic use-after-dispose issues
for (int i = 0; i < 5; i++)
{
using (X509Certificate2 unpairedCert = X509CertificateLoader.LoadCertificate(MLDsaTestsData.IetfMLDsa44.Certificate))
using (X509Certificate2 cert = unpairedCert.CopyWithPrivateKey(mldsaCng))
{
}
}
mldsaCng.SignData("test"u8, signature);
}
// Some certs have disposed, did they delete the key?
using (MLDsaCng mldsa = new MLDsaCng(ephemeralCngKey))
{
mldsa.SignData("test"u8, signature);
}
}
}
private static partial void CheckCopyWithPrivateKey<TKey>(
X509Certificate2 cert,
X509Certificate2 wrongAlgorithmCert,
@ -376,5 +787,12 @@ namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreatio
using (SlhDsa? privateKey = SlhDsa.ImportSlhDsaSecretKey(SlhDsaAlgorithm.SlhDsaSha2_128s, SlhDsaTestData.IetfSlhDsaSha2_128sPrivateKeyValue))
return cert.CopyWithPrivateKey(privateKey);
}
private static X509Certificate2 LoadMLDsaIetfCertificateWithPrivateKey()
{
using (X509Certificate2 cert = X509CertificateLoader.LoadCertificate(MLDsaTestsData.IetfMLDsa44.Certificate))
using (MLDsa? privateKey = MLDsa.ImportMLDsaPrivateSeed(MLDsaAlgorithm.MLDsa44, MLDsaTestsData.IetfMLDsa44.PrivateSeed))
return cert.CopyWithPrivateKey(privateKey);
}
}
}

View File

@ -135,7 +135,7 @@
<Import Project="$(RepositoryEngineeringDir)testing\runsettings.targets" Condition="'$(EnableRunSettingsSupport)' == 'true'" />
<Import Project="$(RepositoryEngineeringDir)testing\coverage.targets" Condition="'$(EnableRunSettingsSupport)' == 'true' or '$(EnableCoverageSupport)' == 'true'" />
<Import Project="$(RepositoryEngineeringDir)illink.targets" Condition="'$(IsSourceProject)' == 'true' or '$(ExplicitlyImportCustomILLinkTargets)' == 'true'" />
<Import Project="$(RepositoryEngineeringDir)illink.targets" Condition="'$(IsSourceProject)' == 'true' or '$(IsReferenceAssemblyProject)' == 'true' or '$(ExplicitlyImportCustomILLinkTargets)' == 'true'" />
<Import Project="$(RepositoryEngineeringDir)liveILLink.targets" />
<Import Project="$(RepositoryEngineeringDir)nativeSanitizers.targets" />

View File

@ -293,34 +293,12 @@
</ItemGroup>
<ItemGroup Condition="'$(BuildX509Loader)' == 'true' and '$(TargetFrameworkIdentifier)' == '.NETFramework'">
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertCloseStore.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CertCloseStore.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertFreeCertificateContext.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CertFreeCertificateContext.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertQueryObjectType.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CertQueryObjectType.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.ContentType.cs"
Link="Common\Interop\Windows\Crypt32\Interop.ContentType.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CryptQueryObject_IntPtr_out.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CryptQueryObject_IntPtr_out.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.DATA_BLOB.cs"
Link="Common\Interop\Windows\Crypt32\Interop.DATA_BLOB.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.ExpectedContentTypeFlags.cs"
Link="Common\Interop\Windows\Crypt32\Interop.ExpectedContentTypeFlags.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.ExpectedFormatTypeFlags.cs"
Link="Common\Interop\Windows\Crypt32\Interop.ExpectedFormatTypeFlags.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.FormatType.cs"
Link="Common\Interop\Windows\Crypt32\Interop.FormatType.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.PfxCertStoreFlags.cs"
Link="Common\Interop\Windows\Crypt32\Interop.PfxCertStoreFlags.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.PFXImportCertStore.cs"
Link="Common\Interop\Windows\Crypt32\Interop.PFXImportCertStore.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeCertStoreHandle.cs"
Link="Common\Microsoft\Win32\SafeHandles\SafeCertStoreHandle.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeCrypt32Handle.cs"
Link="Common\Microsoft\Win32\SafeHandles\SafeCrypt32Handle.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeHandleCache.cs"
Link="Common\Microsoft\Win32\SafeHandles\SafeHandleCache.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\IncrementalHash.netfx.cs"
Link="Common\System\Security\Cryptography\IncrementalHash.netfx.cs" />
@ -453,13 +431,25 @@
Link="Common\System\Security\Cryptography\MLDsaAlgorithm.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\MLDsaImplementation.cs"
Link="Common\System\Security\Cryptography\MLDsaImplementation.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\X509Certificates\ErrorCode.cs"
Link="Common\System\Security\Cryptography\X509Certificates\ErrorCode.cs" />
<Compile Include="System\Security\Cryptography\PinAndClear.cs" />
</ItemGroup>
<ItemGroup Condition="'$(BuildPqc)' == 'true' and '$(TargetFrameworkIdentifier)' != '.NETStandard'">
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeCertContextHandle.cs"
Link="Common\Microsoft\Win32\SafeHandles\SafeCertContextHandle.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeCertStoreHandle.cs"
Link="Common\Microsoft\Win32\SafeHandles\SafeCertStoreHandle.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeCrypt32Handle.cs"
Link="Common\Microsoft\Win32\SafeHandles\SafeCrypt32Handle.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeCryptMsgHandle.cs"
Link="Common\Microsoft\Win32\SafeHandles\SafeCryptMsgHandle.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeHandleCache.cs"
Link="Common\Microsoft\Win32\SafeHandles\SafeHandleCache.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeUnicodeStringHandle.cs"
Link="Microsoft\Win32\SafeHandles\SafeUnicodeStringHandle.cs" />
Link="Common\Microsoft\Win32\SafeHandles\SafeUnicodeStringHandle.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\CngHelpers.cs"
Link="Common\System\Security\Cryptography\CngHelpers.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\CngHelpers.SignVerify.cs"
@ -480,6 +470,64 @@
Link="Common\System\Security\Cryptography\MLKemCng.Windows.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\SlhDsaCng.cs"
Link="Common\System\Security\Cryptography\SlhDsaCng.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\X509Certificates\CertificateHelpers.Windows.cs"
Link="Common\System\Security\Cryptography\X509Certificates\CertificateHelpers.Windows.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertDuplicateCertificateContext.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CertDuplicateCertificateContext.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertFreeCertificateContext.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CertFreeCertificateContext.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertGetCertificateContextProperty.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CertGetCertificateContextProperty.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_CONTEXT.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CERT_CONTEXT.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_INFO.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CERT_INFO.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_PUBLIC_KEY_INFO.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CERT_PUBLIC_KEY_INFO.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertContextPropId.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CertContextPropId.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertEncodingType.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CertEncodingType.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertQueryObjectType.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CertQueryObjectType.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertSetCertificateContextProperty_CRYPT_KEY_PROV_INFO.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CertSetCertificateContextProperty_CRYPT_KEY_PROV_INFO.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertSetCertificateContextProperty_SafeNCryptKeyHandle.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CertSetCertificateContextProperty_SafeNCryptKeyHandle.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertSetPropertyFlags.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CertSetPropertyFlags.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.ContentType.cs"
Link="Common\Interop\Windows\Crypt32\Interop.ContentType.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CRYPT_KEY_PROV_INFO_ANSI.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CRYPT_KEY_PROV_INFO_ANSI.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CryptAcquireCertificatePrivateKey_SafeNCryptKeyHandle.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CryptAcquireCertificatePrivateKey_SafeNCryptKeyHandle.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CryptAcquireCertificatePrivateKeyFlags.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CryptAcquireCertificatePrivateKeyFlags.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CryptAcquireContextFlags.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CryptAcquireContextFlags.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CryptKeySpec.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CryptKeySpec.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CryptMsgClose.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CryptMsgClose.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CryptQueryObject.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CryptQueryObject.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertCloseStore.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CertCloseStore.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CRYPT_ALGORITHM_IDENTIFIER.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CRYPT_ALGORITHM_IDENTIFIER.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CRYPT_BIT_BLOB.cs"
Link="Common\Interop\Windows\Crypt32\Interop.CRYPT_BIT_BLOB.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.DATA_BLOB.cs"
Link="Common\Interop\Windows\Crypt32\Interop.DATA_BLOB.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.ExpectedContentTypeFlags.cs"
Link="Common\Interop\Windows\Crypt32\Interop.ExpectedContentTypeFlags.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.ExpectedFormatTypeFlags.cs"
Link="Common\Interop\Windows\Crypt32\Interop.ExpectedFormatTypeFlags.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.FormatType.cs"
Link="Common\Interop\Windows\Crypt32\Interop.FormatType.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.MsgEncodingType.cs"
Link="Common\Interop\Windows\Crypt32\Interop.MsgEncodingType.cs" />
<Compile Include="$(CommonPath)Interop\Windows\NCrypt\Interop.AsymmetricPaddingMode.cs"
Link="Common\Interop\Windows\NCrypt\Interop.AsymmetricPaddingMode.cs" />
<Compile Include="$(CommonPath)Interop\Windows\NCrypt\Interop.ErrorCode.cs"
@ -497,6 +545,7 @@
<Compile Include="System\Security\Cryptography\CngExtensions.cs" />
<Compile Include="System\Security\Cryptography\CngIdentifierExtensions.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\CertificateHelpers.Windows.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">

View File

@ -150,6 +150,9 @@
<data name="Cryptography_AuthTagMismatch" xml:space="preserve">
<value>The computed authentication tag did not match the input authentication tag.</value>
</data>
<data name="Cryptography_Cert_AlreadyHasPrivateKey" xml:space="preserve">
<value>The certificate already has an associated private key.</value>
</data>
<data name="Cryptography_CompositeSignDataError" xml:space="preserve">
<value>Composite signature generation failed due to an error in one or both of the components.</value>
</data>
@ -225,6 +228,12 @@
<data name="Cryptography_PqcNoSeed" xml:space="preserve">
<value>The current instance does not contain a seed.</value>
</data>
<data name="Cryptography_PrivateKey_DoesNotMatch" xml:space="preserve">
<value>The provided key does not match the public key for this certificate.</value>
</data>
<data name="Cryptography_PrivateKey_WrongAlgorithm" xml:space="preserve">
<value>The provided key does not match the public key algorithm for this certificate.</value>
</data>
<data name="Cryptography_RSAPrivateKey_VersionTooNew" xml:space="preserve">
<value>The provided RSAPrivateKey value has version '{0}', but version '{1}' is the maximum supported.</value>
</data>

View File

@ -0,0 +1,46 @@
// 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.Runtime.Versioning;
using Internal.Cryptography;
using Microsoft.Win32.SafeHandles;
namespace System.Security.Cryptography.X509Certificates
{
internal static partial class CertificateHelpers
{
private static partial int GuessKeySpec(CngProvider provider, string keyName, bool machineKey, CngAlgorithmGroup? algorithmGroup) => 0;
[UnsupportedOSPlatform("browser")]
private static partial X509Certificate2 CopyFromRawBytes(X509Certificate2 certificate) =>
X509CertificateLoader.LoadCertificate(certificate.RawData);
private static partial CryptographicException GetExceptionForLastError() =>
#if NETFRAMEWORK
Marshal.GetHRForLastWin32Error().ToCryptographicException();
#else
Marshal.GetLastPInvokeError().ToCryptographicException();
#endif
#if NETFRAMEWORK
private static readonly System.Reflection.ConstructorInfo? s_safeNCryptKeyHandleCtor_IntPtr_SafeHandle =
typeof(SafeNCryptKeyHandle).GetConstructor([typeof(IntPtr), typeof(SafeHandle)]);
#endif
[SupportedOSPlatform("windows")]
private static partial SafeNCryptKeyHandle CreateSafeNCryptKeyHandle(IntPtr handle, SafeHandle parentHandle)
{
#if NETFRAMEWORK
if (s_safeNCryptKeyHandleCtor_IntPtr_SafeHandle == null)
{
throw new PlatformNotSupportedException();
}
return (SafeNCryptKeyHandle)s_safeNCryptKeyHandleCtor_IntPtr_SafeHandle.Invoke([handle, parentHandle]);
#else
return new SafeNCryptKeyHandle(handle, parentHandle);
#endif
}
}
}

View File

@ -2,14 +2,17 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using System.Formats.Asn1;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
#if !NET10_0_OR_GREATER
using System.Formats.Asn1;
using System.Security.Cryptography.Asn1;
#endif
#if !NET10_0_OR_GREATER && !NETSTANDARD
using System.Diagnostics;
using Internal.Cryptography;
#endif
namespace System.Security.Cryptography.X509Certificates
{
/// <summary>
@ -35,9 +38,11 @@ namespace System.Security.Cryptography.X509Certificates
/// <exception cref="CryptographicException">
/// The public key was invalid, or otherwise could not be imported.
/// </exception>
[ExperimentalAttribute(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static MLKem? GetMLKemPublicKey(this X509Certificate2 certificate)
{
ArgumentNullException.ThrowIfNull(certificate);
#if NET10_0_OR_GREATER
return certificate.GetMLKemPublicKey();
#else
@ -54,7 +59,7 @@ namespace System.Security.Cryptography.X509Certificates
}
finally
{
// SubjectPublicKeyInfo does not need to clear since it's public
// SubjectPublicKeyInfo does not need to clear since it's public
CryptoPool.Return(encoded, clearSize: 0);
}
#endif
@ -69,16 +74,26 @@ namespace System.Security.Cryptography.X509Certificates
/// <returns>
/// The private key, or <see langword="null"/> if this certificate does not have an ML-KEM private key.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="certificate"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// Retrieving an ML-KEM private key from a certificate is not supported on this platform.
/// </exception>
/// <exception cref="CryptographicException">
/// An error occurred accessing the private key.
/// </exception>
[ExperimentalAttribute(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static MLKem? GetMLKemPrivateKey(this X509Certificate2 certificate) =>
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static MLKem? GetMLKemPrivateKey(this X509Certificate2 certificate)
{
ArgumentNullException.ThrowIfNull(certificate);
#if NET10_0_OR_GREATER
certificate.GetMLKemPrivateKey();
return certificate.GetMLKemPrivateKey();
#else
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(MLKem)));
#endif
}
/// <summary>
/// Combines a private key with a certificate containing the associated public key into a
@ -103,13 +118,180 @@ namespace System.Security.Cryptography.X509Certificates
/// <exception cref="InvalidOperationException">
/// The certificate already has an associated private key.
/// </exception>
[ExperimentalAttribute(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certificate, MLKem privateKey) =>
/// <exception cref="PlatformNotSupportedException">
/// Combining a certificate and an ML-KEM private key is not supported on this platform.
/// </exception>
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certificate, MLKem privateKey)
{
ArgumentNullException.ThrowIfNull(certificate);
ArgumentNullException.ThrowIfNull(privateKey);
#if NET10_0_OR_GREATER
certificate.CopyWithPrivateKey(privateKey);
return certificate.CopyWithPrivateKey(privateKey);
#else
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(MLKem)));
#endif
}
/// <summary>
/// Gets the <see cref="MLDsa"/> public key from this certificate.
/// </summary>
/// <param name="certificate">
/// The X.509 certificate that contains the public key.
/// </param>
/// <returns>
/// The public key, or <see langword="null"/> if this certificate does not have an ML-DSA public key.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="certificate"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// The certificate has an ML-DSA public key, but the platform does not support ML-DSA.
/// </exception>
/// <exception cref="CryptographicException">
/// The public key was invalid, or otherwise could not be imported.
/// </exception>
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static MLDsa? GetMLDsaPublicKey(this X509Certificate2 certificate)
{
ArgumentNullException.ThrowIfNull(certificate);
#if NET10_0_OR_GREATER
return certificate.GetMLDsaPublicKey();
#else
if (MLDsaAlgorithm.GetMLDsaAlgorithmFromOid(certificate.GetKeyAlgorithm()) is null)
{
return null;
}
ArraySegment<byte> encoded = GetCertificateSubjectPublicKeyInfo(certificate);
try
{
return MLDsa.ImportSubjectPublicKeyInfo(encoded);
}
finally
{
// SubjectPublicKeyInfo does not need to clear since it's public
CryptoPool.Return(encoded, clearSize: 0);
}
#endif
}
/// <summary>
/// Gets the <see cref="MLDsa"/> private key from this certificate.
/// </summary>
/// <param name="certificate">
/// The X.509 certificate that contains the private key.
/// </param>
/// <returns>
/// The private key, or <see langword="null"/> if this certificate does not have an ML-DSA private key.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="certificate"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// Retrieving an ML-DSA private key from a certificate is not supported on this platform.
/// </exception>
/// <exception cref="CryptographicException">
/// An error occurred accessing the private key.
/// </exception>
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static MLDsa? GetMLDsaPrivateKey(this X509Certificate2 certificate)
{
ArgumentNullException.ThrowIfNull(certificate);
#if NET10_0_OR_GREATER
return certificate.GetMLDsaPrivateKey();
#elif NETSTANDARD
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(MLDsa)));
#else
if (!Helpers.IsOSPlatformWindows)
throw new PlatformNotSupportedException();
return CertificateHelpers.GetPrivateKey<MLDsa>(
certificate,
_ =>
{
Debug.Fail("CryptoApi does not support ML-DSA.");
throw new PlatformNotSupportedException();
},
cngKey => new MLDsaCng(cngKey, transferOwnership: true));
#endif
}
/// <summary>
/// Combines a private key with a certificate containing the associated public key into a
/// new instance that can access the private key.
/// </summary>
/// <param name="certificate">
/// The X.509 certificate that contains the public key.
/// </param>
/// <param name="privateKey">
/// The ML-DSA private key that corresponds to the ML-DSA public key in this certificate.
/// </param>
/// <returns>
/// A new certificate with the <see cref="X509Certificate2.HasPrivateKey" /> property set to <see langword="true"/>.
/// The current certificate isn't modified.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="certificate"/> or <paramref name="privateKey"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// The specified private key doesn't match the public key for this certificate.
/// </exception>
/// <exception cref="InvalidOperationException">
/// The certificate already has an associated private key.
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// Combining a certificate and an ML-DSA private key is not supported on this platform.
/// </exception>
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certificate, MLDsa privateKey)
{
ArgumentNullException.ThrowIfNull(certificate);
ArgumentNullException.ThrowIfNull(privateKey);
#if NET10_0_OR_GREATER
return certificate.CopyWithPrivateKey(privateKey);
#elif NETSTANDARD
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(MLDsa)));
#else
if (!Helpers.IsOSPlatformWindows)
throw new PlatformNotSupportedException();
if (certificate.HasPrivateKey)
throw new InvalidOperationException(SR.Cryptography_Cert_AlreadyHasPrivateKey);
using (MLDsa? publicKey = GetMLDsaPublicKey(certificate))
{
if (publicKey is null)
{
throw new ArgumentException(SR.Cryptography_PrivateKey_WrongAlgorithm);
}
if (publicKey.Algorithm != privateKey.Algorithm)
{
throw new ArgumentException(SR.Cryptography_PrivateKey_DoesNotMatch, nameof(privateKey));
}
using (CryptoPoolLease pk1 = CryptoPoolLease.Rent(publicKey.Algorithm.PublicKeySizeInBytes, skipClear: true))
using (CryptoPoolLease pk2 = CryptoPoolLease.Rent(publicKey.Algorithm.PublicKeySizeInBytes, skipClear: true))
{
publicKey.ExportMLDsaPublicKey(pk1.Span);
privateKey.ExportMLDsaPublicKey(pk2.Span);
if (!pk1.Span.SequenceEqual(pk2.Span))
{
throw new ArgumentException(SR.Cryptography_PrivateKey_DoesNotMatch, nameof(privateKey));
}
}
}
return CertificateHelpers.CopyWithPrivateKey(certificate, privateKey);
#endif
}
/// <summary>
/// Gets the <see cref="SlhDsa"/> public key from this certificate.
@ -129,13 +311,17 @@ namespace System.Security.Cryptography.X509Certificates
/// <exception cref="CryptographicException">
/// The public key was invalid, or otherwise could not be imported.
/// </exception>
[ExperimentalAttribute(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static SlhDsa? GetSlhDsaPublicKey(this X509Certificate2 certificate) =>
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static SlhDsa? GetSlhDsaPublicKey(this X509Certificate2 certificate)
{
ArgumentNullException.ThrowIfNull(certificate);
#if NET10_0_OR_GREATER
certificate.GetSlhDsaPublicKey();
return certificate.GetSlhDsaPublicKey();
#else
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(SlhDsa)));
#endif
}
/// <summary>
/// Gets the <see cref="SlhDsa"/> private key from this certificate.
@ -146,16 +332,26 @@ namespace System.Security.Cryptography.X509Certificates
/// <returns>
/// The private key, or <see langword="null"/> if this certificate does not have an SLH-DSA private key.
/// </returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="certificate"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="PlatformNotSupportedException">
/// Retrieving an SLH-DSA private key from a certificate is not supported on this platform.
/// </exception>
/// <exception cref="CryptographicException">
/// An error occurred accessing the private key.
/// </exception>
[ExperimentalAttribute(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static SlhDsa? GetSlhDsaPrivateKey(this X509Certificate2 certificate) =>
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static SlhDsa? GetSlhDsaPrivateKey(this X509Certificate2 certificate)
{
ArgumentNullException.ThrowIfNull(certificate);
#if NET10_0_OR_GREATER
certificate.GetSlhDsaPrivateKey();
return certificate.GetSlhDsaPrivateKey();
#else
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(SlhDsa)));
#endif
}
/// <summary>
/// Combines a private key with a certificate containing the associated public key into a
@ -175,7 +371,7 @@ namespace System.Security.Cryptography.X509Certificates
/// <paramref name="certificate"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentNullException">
/// <paramref name="privateKey"/> is <see langword="null"/>.
/// <paramref name="certificate"/> or <paramref name="privateKey"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException">
/// The specified private key doesn't match the public key for this certificate.
@ -183,13 +379,21 @@ namespace System.Security.Cryptography.X509Certificates
/// <exception cref="InvalidOperationException">
/// The certificate already has an associated private key.
/// </exception>
[ExperimentalAttribute(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certificate, SlhDsa privateKey) =>
/// <exception cref="PlatformNotSupportedException">
/// Combining a certificate and an SLH-DSA private key is not supported on this platform.
/// </exception>
[Experimental(Experimentals.PostQuantumCryptographyDiagId, UrlFormat = Experimentals.SharedUrlFormat)]
public static X509Certificate2 CopyWithPrivateKey(this X509Certificate2 certificate, SlhDsa privateKey)
{
ArgumentNullException.ThrowIfNull(certificate);
ArgumentNullException.ThrowIfNull(privateKey);
#if NET10_0_OR_GREATER
certificate.CopyWithPrivateKey(privateKey);
return certificate.CopyWithPrivateKey(privateKey);
#else
throw new PlatformNotSupportedException(SR.Format(SR.Cryptography_AlgorithmNotSupported, nameof(SlhDsa)));
#endif
}
#if !NET10_0_OR_GREATER
private static ArraySegment<byte> GetCertificateSubjectPublicKeyInfo(X509Certificate2 certificate)

View File

@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using Xunit;
namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreation
{
@ -25,6 +26,15 @@ namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreatio
private static partial Func<X509Certificate2, SlhDsa> GetSlhDsaPrivateKey =>
X509CertificateKeyAccessors.GetSlhDsaPrivateKey;
private static partial Func<X509Certificate2, MLDsa, X509Certificate2> CopyWithPrivateKey_MLDsa =>
X509CertificateKeyAccessors.CopyWithPrivateKey;
private static partial Func<X509Certificate2, MLDsa> GetMLDsaPublicKey =>
X509CertificateKeyAccessors.GetMLDsaPublicKey;
private static partial Func<X509Certificate2, MLDsa> GetMLDsaPrivateKey =>
X509CertificateKeyAccessors.GetMLDsaPrivateKey;
private static partial void CheckCopyWithPrivateKey<TKey>(
X509Certificate2 cert,
X509Certificate2 wrongAlgorithmCert,
@ -35,5 +45,26 @@ namespace System.Security.Cryptography.X509Certificates.Tests.CertificateCreatio
Func<X509Certificate2, TKey> getPrivateKey,
Action<TKey, TKey> keyProver)
where TKey : class, IDisposable;
[Fact]
public static void ArgumentValidation()
{
Assert.Throws<ArgumentNullException>("certificate", () => X509CertificateKeyAccessors.CopyWithPrivateKey(null, default(MLKem)));
Assert.Throws<ArgumentNullException>("certificate", () => X509CertificateKeyAccessors.CopyWithPrivateKey(null, default(MLDsa)));
Assert.Throws<ArgumentNullException>("certificate", () => X509CertificateKeyAccessors.CopyWithPrivateKey(null, default(SlhDsa)));
Assert.Throws<ArgumentNullException>("certificate", () => X509CertificateKeyAccessors.GetMLKemPublicKey(null));
Assert.Throws<ArgumentNullException>("certificate", () => X509CertificateKeyAccessors.GetMLDsaPublicKey(null));
Assert.Throws<ArgumentNullException>("certificate", () => X509CertificateKeyAccessors.GetSlhDsaPublicKey(null));
#pragma warning disable SYSLIB0026 // X509Certificate and X509Certificate2 are immutable
// This constructor is deprecated, but we use it here for test purposes.
using (X509Certificate2 cert = new X509Certificate2())
#pragma warning restore SYSLIB0026 // X509Certificate and X509Certificate2 are immutable
{
Assert.Throws<ArgumentNullException>("privateKey", () => X509CertificateKeyAccessors.CopyWithPrivateKey(cert, default(MLKem)));
Assert.Throws<ArgumentNullException>("privateKey", () => X509CertificateKeyAccessors.CopyWithPrivateKey(cert, default(MLDsa)));
Assert.Throws<ArgumentNullException>("privateKey", () => X509CertificateKeyAccessors.CopyWithPrivateKey(cert, default(SlhDsa)));
}
}
}
}

View File

@ -127,8 +127,10 @@ namespace Microsoft.Extensions.DependencyModel
public partial class ResourceAssembly
{
public ResourceAssembly(string path, string locale) { }
public ResourceAssembly(string path, string locale, string? localPath) { }
public string Locale { get { throw null; } set { } }
public string Path { get { throw null; } set { } }
public string? LocalPath { get { throw null; } }
}
public partial class RuntimeAssembly
{
@ -156,9 +158,11 @@ namespace Microsoft.Extensions.DependencyModel
public partial class RuntimeFile
{
public RuntimeFile(string path, string? assemblyVersion, string? fileVersion) { }
public RuntimeFile(string path, string? assemblyVersion, string? fileVersion, string? localPath) { }
public string? AssemblyVersion { get { throw null; } }
public string? FileVersion { get { throw null; } }
public string Path { get { throw null; } }
public string? LocalPath { get { throw null; } }
}
public partial class RuntimeLibrary : Microsoft.Extensions.DependencyModel.Library
{

Some files were not shown because too many files have changed in this diff Show More