mirror of https://github.com/dotnet/runtime
Merge branch 'main' into browser_http_streaming_response_test
This commit is contained in:
commit
de0059a3d8
|
@ -19,7 +19,7 @@ configuration:
|
|||
- adamsitnik
|
||||
- bartonjs
|
||||
- jeffhandley
|
||||
- terrajobst
|
||||
- JeremyKuhne
|
||||
replyTemplate: >-
|
||||
Tagging subscribers to 'binaryformatter-migration': ${mentionees}
|
||||
assignMentionees: False
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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)
|
|
@ -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))
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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_
|
||||
/*****************************************************************************/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
//
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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...
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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" />
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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")]
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
|
||||
|
|
|
@ -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'">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue