diff options
Diffstat (limited to 'src/mscorlib/src')
27 files changed, 1074 insertions, 1448 deletions
diff --git a/src/mscorlib/src/Microsoft/Win32/Registry.cs b/src/mscorlib/src/Microsoft/Win32/Registry.cs index aa2dd9b396..d0dbb0ff7f 100644 --- a/src/mscorlib/src/Microsoft/Win32/Registry.cs +++ b/src/mscorlib/src/Microsoft/Win32/Registry.cs @@ -2,12 +2,142 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + namespace Microsoft.Win32 { + /** + * Registry encapsulation. Contains members representing all top level system + * keys. + * + * @security(checkClassLinking=on) + */ + //This class contains only static members and does not need to be serializable. internal static class Registry { - public static readonly RegistryKey CurrentUser = RegistryKey.CurrentUser; - public static readonly RegistryKey LocalMachine = RegistryKey.LocalMachine; + /** + * Current User Key. + * + * This key should be used as the root for all user specific settings. + */ + public static readonly RegistryKey CurrentUser = RegistryKey.GetBaseKey(RegistryKey.HKEY_CURRENT_USER); + + /** + * Local Machine Key. + * + * This key should be used as the root for all machine specific settings. + */ + public static readonly RegistryKey LocalMachine = RegistryKey.GetBaseKey(RegistryKey.HKEY_LOCAL_MACHINE); + + /** + * Classes Root Key. + * + * This is the root key of class information. + */ + public static readonly RegistryKey ClassesRoot = RegistryKey.GetBaseKey(RegistryKey.HKEY_CLASSES_ROOT); + + /** + * Users Root Key. + * + * This is the root of users. + */ + public static readonly RegistryKey Users = RegistryKey.GetBaseKey(RegistryKey.HKEY_USERS); + + /** + * Performance Root Key. + * + * This is where dynamic performance data is stored on NT. + */ + public static readonly RegistryKey PerformanceData = RegistryKey.GetBaseKey(RegistryKey.HKEY_PERFORMANCE_DATA); + + /** + * Current Config Root Key. + * + * This is where current configuration information is stored. + */ + public static readonly RegistryKey CurrentConfig = RegistryKey.GetBaseKey(RegistryKey.HKEY_CURRENT_CONFIG); + + // + // Following function will parse a keyName and returns the basekey for it. + // It will also store the subkey name in the out parameter. + // If the keyName is not valid, we will throw ArgumentException. + // The return value shouldn't be null. + // + private static RegistryKey GetBaseKeyFromKeyName(string keyName, out string subKeyName) + { + if (keyName == null) + { + throw new ArgumentNullException(nameof(keyName)); + } + + string basekeyName; + int i = keyName.IndexOf('\\'); + if (i != -1) + { + basekeyName = keyName.Substring(0, i).ToUpper(System.Globalization.CultureInfo.InvariantCulture); + } + else + { + basekeyName = keyName.ToUpper(System.Globalization.CultureInfo.InvariantCulture); + } + RegistryKey basekey = null; + + switch (basekeyName) + { + case "HKEY_CURRENT_USER": + basekey = Registry.CurrentUser; + break; + case "HKEY_LOCAL_MACHINE": + basekey = Registry.LocalMachine; + break; + case "HKEY_CLASSES_ROOT": + basekey = Registry.ClassesRoot; + break; + case "HKEY_USERS": + basekey = Registry.Users; + break; + case "HKEY_PERFORMANCE_DATA": + basekey = Registry.PerformanceData; + break; + case "HKEY_CURRENT_CONFIG": + basekey = Registry.CurrentConfig; + break; + default: + throw new ArgumentException(SR.Format(SR.Arg_RegInvalidKeyName, nameof(keyName))); + } + if (i == -1 || i == keyName.Length) + { + subKeyName = string.Empty; + } + else + { + subKeyName = keyName.Substring(i + 1, keyName.Length - i - 1); + } + return basekey; + } + + public static object GetValue(string keyName, string valueName, object defaultValue) + { + string subKeyName; + RegistryKey basekey = GetBaseKeyFromKeyName(keyName, out subKeyName); + BCLDebug.Assert(basekey != null, "basekey can't be null."); + RegistryKey key = basekey.OpenSubKey(subKeyName); + if (key == null) + { // if the key doesn't exist, do nothing + return null; + } + try + { + return key.GetValue(valueName, defaultValue); + } + finally + { + key.Close(); + } + } } } diff --git a/src/mscorlib/src/Microsoft/Win32/RegistryKey.cs b/src/mscorlib/src/Microsoft/Win32/RegistryKey.cs index e39b95903e..521ea65e10 100644 --- a/src/mscorlib/src/Microsoft/Win32/RegistryKey.cs +++ b/src/mscorlib/src/Microsoft/Win32/RegistryKey.cs @@ -2,6 +2,28 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + +/* + Note on transaction support: + Eventually we will want to add support for NT's transactions to our + RegistryKey API's (possibly Whidbey M3?). When we do this, here's + the list of API's we need to make transaction-aware: + + RegCreateKeyEx + RegDeleteKey + RegDeleteValue + RegEnumKeyEx + RegEnumValue + RegOpenKeyEx + RegQueryInfoKey + RegQueryValueEx + RegSetValueEx + + We can ignore RegConnectRegistry (remote registry access doesn't yet have + transaction support) and RegFlushKey. RegCloseKey doesn't require any + additional work. . + */ + /* Note on ACL support: The key thing to note about ACL's is you set them on a kernel object like a @@ -30,28 +52,34 @@ using Microsoft.Win32.SafeHandles; using System; using System.Buffers; using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics.Contracts; using System.IO; +using System.Text; namespace Microsoft.Win32 { /** * Registry encapsulation. To get an instance of a RegistryKey use the * Registry class's static members then call OpenSubKey. + * + * @see Registry + * @security(checkDllCalls=off) + * @security(checkClassLinking=on) */ internal sealed class RegistryKey : MarshalByRefObject, IDisposable { - // Use the public Registry.CurrentUser - internal static readonly RegistryKey CurrentUser = - GetBaseKey(new IntPtr(unchecked((int)0x80000001)), "HKEY_CURRENT_USER"); - - // Use the public Registry.LocalMachine - internal static readonly RegistryKey LocalMachine = - GetBaseKey(new IntPtr(unchecked((int)0x80000002)), "HKEY_LOCAL_MACHINE"); - // We could use const here, if C# supported ELEMENT_TYPE_I fully. - private static readonly IntPtr HKEY_CURRENT_USER = new IntPtr(unchecked((int)0x80000001)); - private static readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(unchecked((int)0x80000002)); + internal static readonly IntPtr HKEY_CLASSES_ROOT = new IntPtr(unchecked((int)0x80000000)); + internal static readonly IntPtr HKEY_CURRENT_USER = new IntPtr(unchecked((int)0x80000001)); + internal static readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(unchecked((int)0x80000002)); + internal static readonly IntPtr HKEY_USERS = new IntPtr(unchecked((int)0x80000003)); + internal static readonly IntPtr HKEY_PERFORMANCE_DATA = new IntPtr(unchecked((int)0x80000004)); + internal static readonly IntPtr HKEY_CURRENT_CONFIG = new IntPtr(unchecked((int)0x80000005)); + + // Dirty indicates that we have munged data that should be potentially + // written to disk. + // + private const int STATE_DIRTY = 0x0001; // SystemKey indicates that this is a "SYSTEMKEY" and shouldn't be "opened" // or "closed". @@ -62,6 +90,20 @@ namespace Microsoft.Win32 // private const int STATE_WRITEACCESS = 0x0004; + // Indicates if this key is for HKEY_PERFORMANCE_DATA + private const int STATE_PERF_DATA = 0x0008; + + // Names of keys. This array must be in the same order as the HKEY values listed above. + // + private static readonly String[] hkeyNames = new String[] { + "HKEY_CLASSES_ROOT", + "HKEY_CURRENT_USER", + "HKEY_LOCAL_MACHINE", + "HKEY_USERS", + "HKEY_PERFORMANCE_DATA", + "HKEY_CURRENT_CONFIG", + }; + // MSDN defines the following limits for registry key names & values: // Key Name: 255 characters // Value name: 16,383 Unicode characters @@ -71,7 +113,31 @@ namespace Microsoft.Win32 private volatile SafeRegistryHandle hkey = null; private volatile int state = 0; - private volatile string keyName; + private volatile String keyName; + private volatile bool remoteKey = false; + private volatile RegistryKeyPermissionCheck checkMode; + private volatile RegistryView regView = RegistryView.Default; + + /** + * RegistryInternalCheck values. Useful only for CheckPermission + */ + private enum RegistryInternalCheck + { + CheckSubKeyWritePermission = 0, + CheckSubKeyReadPermission = 1, + CheckSubKeyCreatePermission = 2, + CheckSubTreeReadPermission = 3, + CheckSubTreeWritePermission = 4, + CheckSubTreeReadWritePermission = 5, + CheckValueWritePermission = 6, + CheckValueCreatePermission = 7, + CheckValueReadPermission = 8, + CheckKeyReadPermission = 9, + CheckSubTreePermission = 10, + CheckOpenSubKeyWithWritablePermission = 11, + CheckOpenSubKeyPermission = 12 + }; + /** * Creates a RegistryKey. @@ -82,10 +148,12 @@ namespace Microsoft.Win32 * The remoteKey flag when set to true indicates that we are dealing with registry entries * on a remote machine and requires the program making these calls to have full trust. */ - private RegistryKey(SafeRegistryHandle hkey, bool writable, bool systemkey) + private RegistryKey(SafeRegistryHandle hkey, bool writable, bool systemkey, bool remoteKey, bool isPerfData, RegistryView view) { this.hkey = hkey; keyName = ""; + this.remoteKey = remoteKey; + regView = view; if (systemkey) { state |= STATE_SYSTEMKEY; @@ -94,6 +162,17 @@ namespace Microsoft.Win32 { state |= STATE_WRITEACCESS; } + if (isPerfData) + state |= STATE_PERF_DATA; + ValidateKeyView(view); + } + + /** + * Closes this key, flushes it to disk if the contents have been modified. + */ + public void Close() + { + Dispose(true); } private void Dispose(bool disposing) @@ -115,6 +194,21 @@ namespace Microsoft.Win32 hkey = null; } } + else if (disposing && IsPerfDataKey()) + { + // System keys should never be closed. However, we want to call RegCloseKey + // on HKEY_PERFORMANCE_DATA when called from PerformanceCounter.CloseSharedResources + // (i.e. when disposing is true) so that we release the PERFLIB cache and cause it + // to be refreshed (by re-reading the registry) when accessed subsequently. + // This is the only way we can see the just installed perf counter. + // NOTE: since HKEY_PERFORMANCE_DATA is process wide, there is inherent race condition in closing + // the key asynchronously. While Vista is smart enough to rebuild the PERFLIB resources + // in this situation the down level OSes are not. We have a small window between + // the dispose below and usage elsewhere (other threads). This is By Design. + // This is less of an issue when OS > NT5 (i.e Vista & higher), we can close the perfkey + // (to release & refresh PERFLIB resources) and the OS will rebuild PERFLIB as necessary. + SafeRegistryHandle.RegCloseKey(RegistryKey.HKEY_PERFORMANCE_DATA); + } } } @@ -123,13 +217,13 @@ namespace Microsoft.Win32 Dispose(true); } - public void DeleteValue(string name, bool throwOnMissingValue) + public void DeleteValue(String name, bool throwOnMissingValue) { EnsureWriteable(); int errorCode = Win32Native.RegDeleteValue(hkey, name); // - // From windows 2003 server, if the name is too long we will get error code ERROR_FILENAME_EXCED_RANGE + // From windows 2003 server, if the name is too long we will get error code ERROR_FILENAME_EXCED_RANGE // This still means the name doesn't exist. We need to be consistent with previous OS. // if (errorCode == Win32Native.ERROR_FILE_NOT_FOUND || errorCode == Win32Native.ERROR_FILENAME_EXCED_RANGE) @@ -138,44 +232,77 @@ namespace Microsoft.Win32 { ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSubKeyValueAbsent); } - // Otherwise, just return giving no indication to the user. // (For compatibility) } - // We really should throw an exception here if errorCode was bad, // but we can't for compatibility reasons. - Debug.Assert(errorCode == 0, "RegDeleteValue failed. Here's your error code: " + errorCode); + BCLDebug.Correctness(errorCode == 0, "RegDeleteValue failed. Here's your error code: " + errorCode); + } + + /** + * Retrieves a new RegistryKey that represents the requested key. Valid + * values are: + * + * HKEY_CLASSES_ROOT, + * HKEY_CURRENT_USER, + * HKEY_LOCAL_MACHINE, + * HKEY_USERS, + * HKEY_PERFORMANCE_DATA, + * HKEY_CURRENT_CONFIG, + * HKEY_DYN_DATA. + * + * @param hKey HKEY_* to open. + * + * @return the RegistryKey requested. + */ + internal static RegistryKey GetBaseKey(IntPtr hKey) + { + return GetBaseKey(hKey, RegistryView.Default); } - private static RegistryKey GetBaseKey(IntPtr hKey, string keyName) + internal static RegistryKey GetBaseKey(IntPtr hKey, RegistryView view) { - SafeRegistryHandle srh = new SafeRegistryHandle(hKey, ownsHandle: false); + int index = ((int)hKey) & 0x0FFFFFFF; + BCLDebug.Assert(index >= 0 && index < hkeyNames.Length, "index is out of range!"); + BCLDebug.Assert((((int)hKey) & 0xFFFFFFF0) == 0x80000000, "Invalid hkey value!"); + + bool isPerf = hKey == HKEY_PERFORMANCE_DATA; + // only mark the SafeHandle as ownsHandle if the key is HKEY_PERFORMANCE_DATA. + SafeRegistryHandle srh = new SafeRegistryHandle(hKey, isPerf); - RegistryKey key = new RegistryKey(srh, true, true); - key.keyName = keyName; + RegistryKey key = new RegistryKey(srh, true, true, false, isPerf, view); + key.checkMode = RegistryKeyPermissionCheck.Default; + key.keyName = hkeyNames[index]; return key; } - /// <summary> - /// Retrieves a subkey or null if the operation failed. - /// </summary> - /// <param name="writable">True to open writable, otherwise opens the key read-only.</param> + /** + * Retrieves a subkey. If readonly is <b>true</b>, then the subkey is opened with + * read-only access. + * + * @param name Name or path of subkey to open. + * @param readonly Set to <b>true</b> if you only need readonly access. + * + * @return the Subkey requested, or <b>null</b> if the operation failed. + */ public RegistryKey OpenSubKey(string name, bool writable) { ValidateKeyName(name); EnsureNotDisposed(); + name = FixupName(name); // Fixup multiple slashes to a single slash SafeRegistryHandle result = null; int ret = Win32Native.RegOpenKeyEx(hkey, name, 0, - writable ? Win32Native.KEY_READ | Win32Native.KEY_WRITE : Win32Native.KEY_READ, + GetRegistryKeyAccess(writable) | (int)regView, out result); if (ret == 0 && !result.IsInvalid) { - RegistryKey key = new RegistryKey(result, writable, false); + RegistryKey key = new RegistryKey(result, writable, false, remoteKey, false, regView); + key.checkMode = GetSubKeyPermissonCheck(writable); key.keyName = keyName + "\\" + name; return key; } @@ -191,6 +318,41 @@ namespace Microsoft.Win32 return null; } + // This required no security checks. This is to get around the Deleting SubKeys which only require + // write permission. They call OpenSubKey which required read. Now instead call this function w/o security checks + internal RegistryKey InternalOpenSubKey(String name, bool writable) + { + ValidateKeyName(name); + EnsureNotDisposed(); + + SafeRegistryHandle result = null; + int ret = Win32Native.RegOpenKeyEx(hkey, + name, + 0, + GetRegistryKeyAccess(writable) | (int)regView, + out result); + + if (ret == 0 && !result.IsInvalid) + { + RegistryKey key = new RegistryKey(result, writable, false, remoteKey, false, regView); + key.keyName = keyName + "\\" + name; + return key; + } + return null; + } + + /** + * Returns a subkey with read only permissions. + * + * @param name Name or path of subkey to open. + * + * @return the Subkey requested, or <b>null</b> if the operation failed. + */ + public RegistryKey OpenSubKey(String name) + { + return OpenSubKey(name, false); + } + /// <summary> /// Retrieves an array of strings containing all the subkey names. /// </summary> @@ -319,6 +481,22 @@ namespace Microsoft.Win32 } /** + * Retrieves the specified value. <b>null</b> is returned if the value + * doesn't exist. + * + * Note that <var>name</var> can be null or "", at which point the + * unnamed or default value of this Registry key is returned, if any. + * + * @param name Name of value to retrieve. + * + * @return the data associated with the value. + */ + public Object GetValue(String name) + { + return InternalGetValue(name, null, false, true); + } + + /** * Retrieves the specified value. <i>defaultValue</i> is returned if the value doesn't exist. * * Note that <var>name</var> can be null or "", at which point the @@ -333,11 +511,30 @@ namespace Microsoft.Win32 * * @return the data associated with the value. */ - public object GetValue(string name, object defaultValue = null, bool doNotExpand = false) + public Object GetValue(String name, Object defaultValue) { - EnsureNotDisposed(); + return InternalGetValue(name, defaultValue, false, true); + } + + public Object GetValue(String name, Object defaultValue, RegistryValueOptions options) + { + if (options < RegistryValueOptions.None || options > RegistryValueOptions.DoNotExpandEnvironmentNames) + { + throw new ArgumentException(SR.Format(SR.Arg_EnumIllegalVal, (int)options), nameof(options)); + } + bool doNotExpand = (options == RegistryValueOptions.DoNotExpandEnvironmentNames); + return InternalGetValue(name, defaultValue, doNotExpand, true); + } + + internal Object InternalGetValue(String name, Object defaultValue, bool doNotExpand, bool checkSecurity) + { + if (checkSecurity) + { + // Name can be null! It's the most common use of RegQueryValueEx + EnsureNotDisposed(); + } - object data = defaultValue; + Object data = defaultValue; int type = 0; int datasize = 0; @@ -345,20 +542,54 @@ namespace Microsoft.Win32 if (ret != 0) { - // For stuff like ERROR_FILE_NOT_FOUND, we want to return null (data). - // Some OS's returned ERROR_MORE_DATA even in success cases, so we - // want to continue on through the function. - if (ret != Win32Native.ERROR_MORE_DATA) - return data; + if (IsPerfDataKey()) + { + int size = 65000; + int sizeInput = size; + + int r; + byte[] blob = new byte[size]; + while (Win32Native.ERROR_MORE_DATA == (r = Win32Native.RegQueryValueEx(hkey, name, null, ref type, blob, ref sizeInput))) + { + if (size == Int32.MaxValue) + { + // ERROR_MORE_DATA was returned however we cannot increase the buffer size beyond Int32.MaxValue + Win32Error(r, name); + } + else if (size > (Int32.MaxValue / 2)) + { + // at this point in the loop "size * 2" would cause an overflow + size = Int32.MaxValue; + } + else + { + size *= 2; + } + sizeInput = size; + blob = new byte[size]; + } + if (r != 0) + Win32Error(r, name); + return blob; + } + else + { + // For stuff like ERROR_FILE_NOT_FOUND, we want to return null (data). + // Some OS's returned ERROR_MORE_DATA even in success cases, so we + // want to continue on through the function. + if (ret != Win32Native.ERROR_MORE_DATA) + return data; + } } if (datasize < 0) { // unexpected code path - Debug.Assert(false, "[InternalGetValue] RegQueryValue returned ERROR_SUCCESS but gave a negative datasize"); + BCLDebug.Assert(false, "[InternalGetValue] RegQueryValue returned ERROR_SUCCESS but gave a negative datasize"); datasize = 0; } + switch (type) { case Win32Native.REG_NONE: @@ -378,7 +609,7 @@ namespace Microsoft.Win32 goto case Win32Native.REG_BINARY; } long blob = 0; - Debug.Assert(datasize == 8, "datasize==8"); + BCLDebug.Assert(datasize == 8, "datasize==8"); // Here, datasize must be 8 when calling this ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, ref blob, ref datasize); @@ -393,7 +624,7 @@ namespace Microsoft.Win32 goto case Win32Native.REG_QWORD; } int blob = 0; - Debug.Assert(datasize == 4, "datasize==4"); + BCLDebug.Assert(datasize == 4, "datasize==4"); // Here, datasize must be four when calling this ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, ref blob, ref datasize); @@ -420,13 +651,13 @@ namespace Microsoft.Win32 ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, blob, ref datasize); if (blob.Length > 0 && blob[blob.Length - 1] == (char)0) { - data = new string(blob, 0, blob.Length - 1); + data = new String(blob, 0, blob.Length - 1); } else { // in the very unlikely case the data is missing null termination, // pass in the whole char[] to prevent truncating a character - data = new string(blob); + data = new String(blob); } } break; @@ -451,17 +682,17 @@ namespace Microsoft.Win32 if (blob.Length > 0 && blob[blob.Length - 1] == (char)0) { - data = new string(blob, 0, blob.Length - 1); + data = new String(blob, 0, blob.Length - 1); } else { // in the very unlikely case the data is missing null termination, // pass in the whole char[] to prevent truncating a character - data = new string(blob); + data = new String(blob); } if (!doNotExpand) - data = Environment.ExpandEnvironmentVariables((string)data); + data = Environment.ExpandEnvironmentVariables((String)data); } break; case Win32Native.REG_MULTI_SZ: @@ -502,7 +733,8 @@ namespace Microsoft.Win32 blob[blob.Length - 1] = (char)0; } - IList<string> strings = new List<string>(); + + IList<String> strings = new List<String>(); int cur = 0; int len = blob.Length; @@ -516,28 +748,28 @@ namespace Microsoft.Win32 if (nextNull < len) { - Debug.Assert(blob[nextNull] == (char)0, "blob[nextNull] should be 0"); + BCLDebug.Assert(blob[nextNull] == (char)0, "blob[nextNull] should be 0"); if (nextNull - cur > 0) { - strings.Add(new string(blob, cur, nextNull - cur)); + strings.Add(new String(blob, cur, nextNull - cur)); } else { // we found an empty string. But if we're at the end of the data, // it's just the extra null terminator. if (nextNull != len - 1) - strings.Add(string.Empty); + strings.Add(String.Empty); } } else { - strings.Add(new string(blob, cur, len - cur)); + strings.Add(new String(blob, cur, len - cur)); } cur = nextNull + 1; } - data = new string[strings.Count]; - strings.CopyTo((string[])data, 0); + data = new String[strings.Count]; + strings.CopyTo((String[])data, 0); } break; case Win32Native.REG_LINK: @@ -560,10 +792,26 @@ namespace Microsoft.Win32 private bool IsPerfDataKey() { - return false; + return (state & STATE_PERF_DATA) != 0; + } + + private void SetDirty() + { + state |= STATE_DIRTY; + } + + /** + * Sets the specified value. + * + * @param name Name of value to store data in. + * @param value Data to store. + */ + public void SetValue(String name, Object value) + { + SetValue(name, value, RegistryValueKind.Unknown); } - public unsafe void SetStringValue(string name, string value) + public unsafe void SetValue(String name, Object value, RegistryValueKind valueKind) { if (value == null) ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value); @@ -573,17 +821,167 @@ namespace Microsoft.Win32 throw new ArgumentException(SR.Arg_RegValStrLenBug); } + if (!Enum.IsDefined(typeof(RegistryValueKind), valueKind)) + throw new ArgumentException(SR.Arg_RegBadKeyKind, nameof(valueKind)); + EnsureWriteable(); - int result = Win32Native.RegSetValueEx(hkey, - name, - 0, - RegistryValueKind.String, - value, - checked(value.Length * 2 + 2)); + if (valueKind == RegistryValueKind.Unknown) + { + // this is to maintain compatibility with the old way of autodetecting the type. + // SetValue(string, object) will come through this codepath. + valueKind = CalculateValueKind(value); + } + + int ret = 0; + try + { + switch (valueKind) + { + case RegistryValueKind.ExpandString: + case RegistryValueKind.String: + { + String data = value.ToString(); + ret = Win32Native.RegSetValueEx(hkey, + name, + 0, + valueKind, + data, + checked(data.Length * 2 + 2)); + break; + } + + case RegistryValueKind.MultiString: + { + // Other thread might modify the input array after we calculate the buffer length. + // Make a copy of the input array to be safe. + string[] dataStrings = (string[])(((string[])value).Clone()); + int sizeInBytes = 0; + + // First determine the size of the array + // + for (int i = 0; i < dataStrings.Length; i++) + { + if (dataStrings[i] == null) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetStrArrNull); + } + sizeInBytes = checked(sizeInBytes + (dataStrings[i].Length + 1) * 2); + } + sizeInBytes = checked(sizeInBytes + 2); + + byte[] basePtr = new byte[sizeInBytes]; + fixed (byte* b = basePtr) + { + IntPtr currentPtr = new IntPtr((void*)b); + + // Write out the strings... + // + for (int i = 0; i < dataStrings.Length; i++) + { + // Assumes that the Strings are always null terminated. + String.InternalCopy(dataStrings[i], currentPtr, (checked(dataStrings[i].Length * 2))); + currentPtr = new IntPtr((long)currentPtr + (checked(dataStrings[i].Length * 2))); + *(char*)(currentPtr.ToPointer()) = '\0'; + currentPtr = new IntPtr((long)currentPtr + 2); + } + + *(char*)(currentPtr.ToPointer()) = '\0'; + currentPtr = new IntPtr((long)currentPtr + 2); + + ret = Win32Native.RegSetValueEx(hkey, + name, + 0, + RegistryValueKind.MultiString, + basePtr, + sizeInBytes); + } + break; + } + + case RegistryValueKind.None: + case RegistryValueKind.Binary: + byte[] dataBytes = (byte[])value; + ret = Win32Native.RegSetValueEx(hkey, + name, + 0, + (valueKind == RegistryValueKind.None ? Win32Native.REG_NONE : RegistryValueKind.Binary), + dataBytes, + dataBytes.Length); + break; + + case RegistryValueKind.DWord: + { + // We need to use Convert here because we could have a boxed type cannot be + // unboxed and cast at the same time. I.e. ((int)(object)(short) 5) will fail. + int data = Convert.ToInt32(value, System.Globalization.CultureInfo.InvariantCulture); + + ret = Win32Native.RegSetValueEx(hkey, + name, + 0, + RegistryValueKind.DWord, + ref data, + 4); + break; + } - if (result != 0) - Win32Error(result, null); + case RegistryValueKind.QWord: + { + long data = Convert.ToInt64(value, System.Globalization.CultureInfo.InvariantCulture); + + ret = Win32Native.RegSetValueEx(hkey, + name, + 0, + RegistryValueKind.QWord, + ref data, + 8); + break; + } + } + } + catch (OverflowException) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetMismatchedKind); + } + catch (InvalidOperationException) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetMismatchedKind); + } + catch (FormatException) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetMismatchedKind); + } + catch (InvalidCastException) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetMismatchedKind); + } + + if (ret == 0) + { + SetDirty(); + } + else + Win32Error(ret, null); + } + + private RegistryValueKind CalculateValueKind(Object value) + { + // This logic matches what used to be in SetValue(string name, object value) in the v1.0 and v1.1 days. + // Even though we could add detection for an int64 in here, we want to maintain compatibility with the + // old behavior. + if (value is Int32) + return RegistryValueKind.DWord; + else if (value is Array) + { + if (value is byte[]) + return RegistryValueKind.Binary; + else if (value is String[]) + return RegistryValueKind.MultiString; + else + throw new ArgumentException(SR.Format(SR.Arg_RegSetBadArrType, value.GetType().Name)); + } + else + return RegistryValueKind.String; } /** @@ -591,7 +989,7 @@ namespace Microsoft.Win32 * * @return a string representing the key. */ - public override string ToString() + public override String ToString() { EnsureNotDisposed(); return keyName; @@ -604,7 +1002,7 @@ namespace Microsoft.Win32 * error, and depending on the error, insert a string into the message * gotten from the ResourceManager. */ - internal void Win32Error(int errorCode, string str) + internal void Win32Error(int errorCode, String str) { switch (errorCode) { @@ -613,6 +1011,27 @@ namespace Microsoft.Win32 throw new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_RegistryKeyGeneric_Key, str)); else throw new UnauthorizedAccessException(); + + case Win32Native.ERROR_INVALID_HANDLE: + /** + * For normal RegistryKey instances we dispose the SafeRegHandle and throw IOException. + * However, for HKEY_PERFORMANCE_DATA (on a local or remote machine) we avoid disposing the + * SafeRegHandle and only throw the IOException. This is to workaround reentrancy issues + * in PerformanceCounter.NextValue() where the API could throw {NullReference, ObjectDisposed, ArgumentNull}Exception + * on reentrant calls because of this error code path in RegistryKey + * + * Normally we'd make our caller synchronize access to a shared RegistryKey instead of doing something like this, + * however we shipped PerformanceCounter.NextValue() un-synchronized in v2.0RTM and customers have taken a dependency on + * this behavior (being able to simultaneously query multiple remote-machine counters on multiple threads, instead of + * having serialized access). + */ + if (!IsPerfDataKey()) + { + hkey.SetHandleAsInvalid(); + hkey = null; + } + goto default; + case Win32Native.ERROR_FILE_NOT_FOUND: throw new IOException(SR.Arg_RegKeyNotFound, errorCode); @@ -621,6 +1040,76 @@ namespace Microsoft.Win32 } } + internal static String FixupName(String name) + { + BCLDebug.Assert(name != null, "[FixupName]name!=null"); + if (name.IndexOf('\\') == -1) + return name; + + StringBuilder sb = new StringBuilder(name); + FixupPath(sb); + int temp = sb.Length - 1; + if (temp >= 0 && sb[temp] == '\\') // Remove trailing slash + sb.Length = temp; + return sb.ToString(); + } + + + private static void FixupPath(StringBuilder path) + { + Contract.Requires(path != null); + int length = path.Length; + bool fixup = false; + char markerChar = (char)0xFFFF; + + int i = 1; + while (i < length - 1) + { + if (path[i] == '\\') + { + i++; + while (i < length) + { + if (path[i] == '\\') + { + path[i] = markerChar; + i++; + fixup = true; + } + else + break; + } + } + i++; + } + + if (fixup) + { + i = 0; + int j = 0; + while (i < length) + { + if (path[i] == markerChar) + { + i++; + continue; + } + path[j] = path[i]; + i++; + j++; + } + path.Length += j - i; + } + } + + private bool ContainsRegistryValue(string name) + { + int type = 0; + int datasize = 0; + int retval = Win32Native.RegQueryValueEx(hkey, name, null, ref type, (byte[])null, ref datasize); + return retval == 0; + } + private void EnsureNotDisposed() { if (hkey == null) @@ -638,8 +1127,41 @@ namespace Microsoft.Win32 } } + private static int GetRegistryKeyAccess(bool isWritable) + { + int winAccess; + if (!isWritable) + { + winAccess = Win32Native.KEY_READ; + } + else + { + winAccess = Win32Native.KEY_READ | Win32Native.KEY_WRITE; + } + + return winAccess; + } + + private RegistryKeyPermissionCheck GetSubKeyPermissonCheck(bool subkeyWritable) + { + if (checkMode == RegistryKeyPermissionCheck.Default) + { + return checkMode; + } + + if (subkeyWritable) + { + return RegistryKeyPermissionCheck.ReadWriteSubTree; + } + else + { + return RegistryKeyPermissionCheck.ReadSubTree; + } + } + static private void ValidateKeyName(string name) { + Contract.Ensures(name != null); if (name == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.name); @@ -660,9 +1182,33 @@ namespace Microsoft.Win32 ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegKeyStrLenBug); } + static private void ValidateKeyView(RegistryView view) + { + if (view != RegistryView.Default && view != RegistryView.Registry32 && view != RegistryView.Registry64) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidRegistryViewCheck, ExceptionArgument.view); + } + } + // Win32 constants for error handling private const int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; private const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; private const int FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000; } + + [Flags] + internal enum RegistryValueOptions + { + None = 0, + DoNotExpandEnvironmentNames = 1 + } + + // the name for this API is meant to mimic FileMode, which has similar values + + internal enum RegistryKeyPermissionCheck + { + Default = 0, + ReadSubTree = 1, + ReadWriteSubTree = 2 + } } diff --git a/src/mscorlib/src/Microsoft/Win32/RegistryValueKind.cs b/src/mscorlib/src/Microsoft/Win32/RegistryValueKind.cs index 90c880b278..6e2a4f3b6e 100644 --- a/src/mscorlib/src/Microsoft/Win32/RegistryValueKind.cs +++ b/src/mscorlib/src/Microsoft/Win32/RegistryValueKind.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. + namespace Microsoft.Win32 { internal enum RegistryValueKind @@ -12,11 +13,8 @@ namespace Microsoft.Win32 DWord = Win32Native.REG_DWORD, MultiString = Win32Native.REG_MULTI_SZ, QWord = Win32Native.REG_QWORD, - Unknown = 0, - - // REG_NONE is defined as zero but BCL, mistakingly overrode this value. - // Now instead of using Win32Native.REG_NONE we use "-1" and play games internally. - None = unchecked((int)0xFFFFFFFF), - } + Unknown = 0, // REG_NONE is defined as zero but BCL + None = unchecked((int)0xFFFFFFFF), // mistakingly overrode this value. + } // Now instead of using Win32Native.REG_NONE we use "-1" and play games internally. } diff --git a/src/mscorlib/src/Microsoft/Win32/RegistryView.cs b/src/mscorlib/src/Microsoft/Win32/RegistryView.cs new file mode 100644 index 0000000000..e4158656a8 --- /dev/null +++ b/src/mscorlib/src/Microsoft/Win32/RegistryView.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// +// +// +// Implements Microsoft.Win32.RegistryView +// +// ====================================================================================== + +using System; + +namespace Microsoft.Win32 +{ + internal enum RegistryView + { + Default = 0, // 0x0000 operate on the default registry view + Registry64 = Win32Native.KEY_WOW64_64KEY, // 0x0100 operate on the 64-bit registry view + Registry32 = Win32Native.KEY_WOW64_32KEY, // 0x0200 operate on the 32-bit registry view + }; +} diff --git a/src/mscorlib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.cs b/src/mscorlib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.cs index 1215000ec5..64dbb2c638 100644 --- a/src/mscorlib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.cs +++ b/src/mscorlib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.cs @@ -2,9 +2,19 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +// +// +// +// Implements Microsoft.Win32.SafeHandles.SafeRegistryHandle +// +// ====================================================================================== + using System; using System.Security; using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Runtime.ConstrainedExecution; +using System.Runtime.Versioning; namespace Microsoft.Win32.SafeHandles { diff --git a/src/mscorlib/src/System/Collections/Generic/Dictionary.cs b/src/mscorlib/src/System/Collections/Generic/Dictionary.cs index 409b23b541..e360eef897 100644 --- a/src/mscorlib/src/System/Collections/Generic/Dictionary.cs +++ b/src/mscorlib/src/System/Collections/Generic/Dictionary.cs @@ -48,6 +48,7 @@ namespace System.Collections.Generic using System.Collections; using System.Diagnostics; using System.Diagnostics.Contracts; + using System.Runtime.CompilerServices; using System.Runtime.Serialization; /// <summary> @@ -428,8 +429,7 @@ namespace System.Collections.Generic if (buckets == null) Initialize(0); int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; - int targetBucket = hashCode % buckets.Length; - + int targetBucket = hashCode % buckets.Length; #if FEATURE_RANDOMIZED_STRING_HASHING int collisionCount = 0; #endif @@ -452,7 +452,6 @@ namespace System.Collections.Generic return false; } - #if FEATURE_RANDOMIZED_STRING_HASHING collisionCount++; #endif @@ -599,27 +598,40 @@ namespace System.Collections.Generic int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; int bucket = hashCode % buckets.Length; int last = -1; - for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next) + int i = buckets[bucket]; + while (i >= 0) { - if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) + ref Entry entry = ref entries[i]; + + if (entry.hashCode == hashCode && comparer.Equals(entry.key, key)) { if (last < 0) { - buckets[bucket] = entries[i].next; + buckets[bucket] = entry.next; } else { - entries[last].next = entries[i].next; + entries[last].next = entry.next; + } + entry.hashCode = -1; + entry.next = freeList; + + if (RuntimeHelpers.IsReferenceOrContainsReferences<TKey>()) + { + entry.key = default(TKey); + } + if (RuntimeHelpers.IsReferenceOrContainsReferences<TValue>()) + { + entry.value = default(TValue); } - entries[i].hashCode = -1; - entries[i].next = freeList; - entries[i].key = default(TKey); - entries[i].value = default(TValue); freeList = i; freeCount++; version++; return true; } + + last = i; + i = entry.next; } } return false; @@ -640,30 +652,43 @@ namespace System.Collections.Generic int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF; int bucket = hashCode % buckets.Length; int last = -1; - for (int i = buckets[bucket]; i >= 0; last = i, i = entries[i].next) + int i = buckets[bucket]; + while (i >= 0) { - if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) + ref Entry entry = ref entries[i]; + + if (entry.hashCode == hashCode && comparer.Equals(entry.key, key)) { if (last < 0) { - buckets[bucket] = entries[i].next; + buckets[bucket] = entry.next; } else { - entries[last].next = entries[i].next; + entries[last].next = entry.next; } - value = entries[i].value; + value = entry.value; - entries[i].hashCode = -1; - entries[i].next = freeList; - entries[i].key = default(TKey); - entries[i].value = default(TValue); + entry.hashCode = -1; + entry.next = freeList; + + if (RuntimeHelpers.IsReferenceOrContainsReferences<TKey>()) + { + entry.key = default(TKey); + } + if (RuntimeHelpers.IsReferenceOrContainsReferences<TValue>()) + { + entry.value = default(TValue); + } freeList = i; freeCount++; version++; return true; } + + last = i; + i = entry.next; } } value = default(TValue); @@ -955,13 +980,13 @@ namespace System.Collections.Generic // dictionary.count+1 could be negative if dictionary.count is Int32.MaxValue while ((uint)index < (uint)dictionary.count) { - if (dictionary.entries[index].hashCode >= 0) + ref Entry entry = ref dictionary.entries[index++]; + + if (entry.hashCode >= 0) { - current = new KeyValuePair<TKey, TValue>(dictionary.entries[index].key, dictionary.entries[index].value); - index++; + current = new KeyValuePair<TKey, TValue>(entry.key, entry.value); return true; } - index++; } index = dictionary.count + 1; @@ -1231,13 +1256,13 @@ namespace System.Collections.Generic while ((uint)index < (uint)dictionary.count) { - if (dictionary.entries[index].hashCode >= 0) + ref Entry entry = ref dictionary.entries[index++]; + + if (entry.hashCode >= 0) { - currentKey = dictionary.entries[index].key; - index++; + currentKey = entry.key; return true; } - index++; } index = dictionary.count + 1; @@ -1459,13 +1484,13 @@ namespace System.Collections.Generic while ((uint)index < (uint)dictionary.count) { - if (dictionary.entries[index].hashCode >= 0) + ref Entry entry = ref dictionary.entries[index++]; + + if (entry.hashCode >= 0) { - currentValue = dictionary.entries[index].value; - index++; + currentValue = entry.value; return true; } - index++; } index = dictionary.count + 1; currentValue = default(TValue); diff --git a/src/mscorlib/src/System/Collections/Generic/List.cs b/src/mscorlib/src/System/Collections/Generic/List.cs index 67d1668aad..4e480885ef 100644 --- a/src/mscorlib/src/System/Collections/Generic/List.cs +++ b/src/mscorlib/src/System/Collections/Generic/List.cs @@ -350,6 +350,7 @@ namespace System.Collections.Generic // Clears the contents of List. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) diff --git a/src/mscorlib/src/System/Delegate.cs b/src/mscorlib/src/System/Delegate.cs index de0ff6532c..75ec57a003 100644 --- a/src/mscorlib/src/System/Delegate.cs +++ b/src/mscorlib/src/System/Delegate.cs @@ -179,7 +179,10 @@ namespace System else return unchecked((int)((long)this._methodPtrAux)); */ - return GetType().GetHashCode(); + if (_methodPtrAux.IsNull()) + return ( _target != null ? RuntimeHelpers.GetHashCode(_target) * 33 : 0) + GetType().GetHashCode(); + else + return GetType().GetHashCode(); } public static Delegate Combine(Delegate a, Delegate b) diff --git a/src/mscorlib/src/System/Environment.cs b/src/mscorlib/src/System/Environment.cs index b9070ae713..dddbdc242d 100644 --- a/src/mscorlib/src/System/Environment.cs +++ b/src/mscorlib/src/System/Environment.cs @@ -820,7 +820,7 @@ namespace System } else { - environmentKey.SetStringValue(variable, value); + environmentKey.SetValue(variable, value); } } } diff --git a/src/mscorlib/src/System/Globalization/CompareInfo.cs b/src/mscorlib/src/System/Globalization/CompareInfo.cs index 285a81d906..b2c2208db7 100644 --- a/src/mscorlib/src/System/Globalization/CompareInfo.cs +++ b/src/mscorlib/src/System/Globalization/CompareInfo.cs @@ -752,7 +752,6 @@ namespace System.Globalization return IndexOfCore(source, new string(value, 1), startIndex, count, options, null); } - public unsafe virtual int IndexOf(string source, string value, int startIndex, int count, CompareOptions options) { // Validate inputs @@ -802,6 +801,53 @@ namespace System.Globalization return IndexOfCore(source, value, startIndex, count, options, null); } + // The following IndexOf overload is mainly used by String.Replace. This overload assumes the parameters are already validated + // and the caller is passing a valid matchLengthPtr pointer. + internal unsafe int IndexOf(string source, string value, int startIndex, int count, CompareOptions options, int* matchLengthPtr) + { + Debug.Assert(source != null); + Debug.Assert(value != null); + Debug.Assert(startIndex >= 0); + Debug.Assert(matchLengthPtr != null); + *matchLengthPtr = 0; + + if (source.Length == 0) + { + if (value.Length == 0) + { + return 0; + } + return -1; + } + + if (startIndex >= source.Length) + { + return -1; + } + + if (options == CompareOptions.OrdinalIgnoreCase) + { + int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: true); + if (res >= 0) + { + *matchLengthPtr = value.Length; + } + return res; + } + + if (_invariantMode) + { + int res = IndexOfOrdinal(source, value, startIndex, count, ignoreCase: (options & (CompareOptions.IgnoreCase | CompareOptions.OrdinalIgnoreCase)) != 0); + if (res >= 0) + { + *matchLengthPtr = value.Length; + } + return res; + } + + return IndexOfCore(source, value, startIndex, count, options, matchLengthPtr); + } + internal int IndexOfOrdinal(string source, string value, int startIndex, int count, bool ignoreCase) { if (_invariantMode) diff --git a/src/mscorlib/src/System/Globalization/HijriCalendar.Win32.cs b/src/mscorlib/src/System/Globalization/HijriCalendar.Win32.cs index 4ba95c8fa8..869b809bff 100644 --- a/src/mscorlib/src/System/Globalization/HijriCalendar.Win32.cs +++ b/src/mscorlib/src/System/Globalization/HijriCalendar.Win32.cs @@ -42,41 +42,54 @@ namespace System.Globalization int hijriAdvance = 0; Microsoft.Win32.RegistryKey key = null; - using (key = Registry.CurrentUser.OpenSubKey(InternationalRegKey, writable: false)) + try { - if (key == null) - return 0; + // Open in read-only mode. + // Use InternalOpenSubKey so that we avoid the security check. + key = RegistryKey.GetBaseKey(RegistryKey.HKEY_CURRENT_USER).OpenSubKey(InternationalRegKey, false); + } + //If this fails for any reason, we'll just return 0. + catch (ObjectDisposedException) { return 0; } + catch (ArgumentException) { return 0; } - Object value = key.GetValue(HijriAdvanceRegKeyEntry); - if (value == null) - { - return (0); - } - String str = value.ToString(); - if (String.Compare(str, 0, HijriAdvanceRegKeyEntry, 0, HijriAdvanceRegKeyEntry.Length, StringComparison.OrdinalIgnoreCase) == 0) + if (key != null) + { + try { - if (str.Length == HijriAdvanceRegKeyEntry.Length) - hijriAdvance = -1; - else + Object value = key.InternalGetValue(HijriAdvanceRegKeyEntry, null, false, false); + if (value == null) { - str = str.Substring(HijriAdvanceRegKeyEntry.Length); - try + return (0); + } + String str = value.ToString(); + if (String.Compare(str, 0, HijriAdvanceRegKeyEntry, 0, HijriAdvanceRegKeyEntry.Length, StringComparison.OrdinalIgnoreCase) == 0) + { + if (str.Length == HijriAdvanceRegKeyEntry.Length) + hijriAdvance = -1; + else { - int advance = Int32.Parse(str.ToString(), CultureInfo.InvariantCulture); - if ((advance >= MinAdvancedHijri) && (advance <= MaxAdvancedHijri)) + str = str.Substring(HijriAdvanceRegKeyEntry.Length); + try { - hijriAdvance = advance; + int advance = Int32.Parse(str.ToString(), CultureInfo.InvariantCulture); + if ((advance >= MinAdvancedHijri) && (advance <= MaxAdvancedHijri)) + { + hijriAdvance = advance; + } } + // If we got garbage from registry just ignore it. + // hijriAdvance = 0 because of declaraction assignment up above. + catch (ArgumentException) { } + catch (FormatException) { } + catch (OverflowException) { } } - // If we got garbage from registry just ignore it. - // hijriAdvance = 0 because of declaraction assignment up above. - catch (ArgumentException) { } - catch (FormatException) { } - catch (OverflowException) { } } } + finally + { + key.Close(); + } } - return (hijriAdvance); } } diff --git a/src/mscorlib/src/System/Globalization/JapaneseCalendar.Win32.cs b/src/mscorlib/src/System/Globalization/JapaneseCalendar.Win32.cs index fe8b1b5896..a83c4fad9e 100644 --- a/src/mscorlib/src/System/Globalization/JapaneseCalendar.Win32.cs +++ b/src/mscorlib/src/System/Globalization/JapaneseCalendar.Win32.cs @@ -36,30 +36,30 @@ namespace System.Globalization try { - using (RegistryKey key = Registry.LocalMachine.OpenSubKey(c_japaneseErasHive, writable: false)) + // Need to access registry + RegistryKey key = RegistryKey.GetBaseKey(RegistryKey.HKEY_LOCAL_MACHINE).OpenSubKey(c_japaneseErasHive, false); + + // Abort if we didn't find anything + if (key == null) return null; + + // Look up the values in our reg key + String[] valueNames = key.GetValueNames(); + if (valueNames != null && valueNames.Length > 0) { - // Abort if we didn't find anything - if (key == null) return null; + registryEraRanges = new EraInfo[valueNames.Length]; - // Look up the values in our reg key - String[] valueNames = key.GetValueNames(); - if (valueNames != null && valueNames.Length > 0) + // Loop through the registry and read in all the values + for (int i = 0; i < valueNames.Length; i++) { - registryEraRanges = new EraInfo[valueNames.Length]; - - // Loop through the registry and read in all the values - for (int i = 0; i < valueNames.Length; i++) - { - // See if the era is a valid date - EraInfo era = GetEraFromValue(valueNames[i], key.GetValue(valueNames[i]).ToString()); + // See if the era is a valid date + EraInfo era = GetEraFromValue(valueNames[i], key.GetValue(valueNames[i]).ToString()); - // continue if not valid - if (era == null) continue; + // continue if not valid + if (era == null) continue; - // Remember we found one. - registryEraRanges[iFoundEras] = era; - iFoundEras++; - } + // Remember we found one. + registryEraRanges[iFoundEras] = era; + iFoundEras++; } } } diff --git a/src/mscorlib/src/System/IO/Stream.cs b/src/mscorlib/src/System/IO/Stream.cs index 92fe374f19..65cc4ddcb9 100644 --- a/src/mscorlib/src/System/IO/Stream.cs +++ b/src/mscorlib/src/System/IO/Stream.cs @@ -522,14 +522,14 @@ namespace System.IO // If the wait has already completed, run the task. if (asyncWaiter.IsCompleted) { - Debug.Assert(asyncWaiter.IsRanToCompletion, "The semaphore wait should always complete successfully."); + Debug.Assert(asyncWaiter.IsCompletedSuccessfully, "The semaphore wait should always complete successfully."); RunReadWriteTask(readWriteTask); } else // Otherwise, wait for our turn, and then run the task. { asyncWaiter.ContinueWith((t, state) => { - Debug.Assert(t.IsRanToCompletion, "The semaphore wait should always complete successfully."); + Debug.Assert(t.IsCompletedSuccessfully, "The semaphore wait should always complete successfully."); var rwt = (ReadWriteTask)state; rwt._stream.RunReadWriteTask(rwt); // RunReadWriteTask(readWriteTask); }, readWriteTask, default(CancellationToken), TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); diff --git a/src/mscorlib/src/System/ReadOnlySpan.cs b/src/mscorlib/src/System/ReadOnlySpan.cs deleted file mode 100644 index 8d0fbad0fc..0000000000 --- a/src/mscorlib/src/System/ReadOnlySpan.cs +++ /dev/null @@ -1,313 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics; -using System.Runtime.CompilerServices; -using EditorBrowsableState = System.ComponentModel.EditorBrowsableState; -using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute; - -#pragma warning disable 0809 //warning CS0809: Obsolete member 'Span<T>.Equals(object)' overrides non-obsolete member 'object.Equals(object)' - -namespace System -{ - /// <summary> - /// ReadOnlySpan represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed - /// or native memory, or to memory allocated on the stack. It is type- and memory-safe. - /// </summary> - public struct ReadOnlySpan<T> - { - /// <summary>A byref or a native ptr.</summary> - private readonly ByReference<T> _pointer; - /// <summary>The number of elements this ReadOnlySpan contains.</summary> - private readonly int _length; - - /// <summary> - /// Creates a new read-only span over the entirety of the target array. - /// </summary> - /// <param name="array">The target array.</param> - /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="array"/> is a null - /// reference (Nothing in Visual Basic).</exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan(T[] array) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - - _pointer = new ByReference<T>(ref JitHelpers.GetArrayData(array)); - _length = array.Length; - } - - /// <summary> - /// Creates a new read-only span over the portion of the target array beginning - /// at 'start' index and covering the remainder of the array. - /// </summary> - /// <param name="array">The target array.</param> - /// <param name="start">The index at which to begin the read-only span.</param> - /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="array"/> is a null - /// reference (Nothing in Visual Basic).</exception> - /// <exception cref="System.ArgumentOutOfRangeException"> - /// Thrown when the specified <paramref name="start"/> is not in the range (<0 or >=Length). - /// </exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan(T[] array, int start) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - if ((uint)start > (uint)array.Length) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - _pointer = new ByReference<T>(ref Unsafe.Add(ref JitHelpers.GetArrayData(array), start)); - _length = array.Length - start; - } - - /// <summary> - /// Creates a new read-only span over the portion of the target array beginning - /// at 'start' index and ending at 'end' index (exclusive). - /// </summary> - /// <param name="array">The target array.</param> - /// <param name="start">The index at which to begin the read-only span.</param> - /// <param name="length">The number of items in the read-only span.</param> - /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="array"/> is a null - /// reference (Nothing in Visual Basic).</exception> - /// <exception cref="System.ArgumentOutOfRangeException"> - /// Thrown when the specified <paramref name="start"/> or end index is not in the range (<0 or >=Length). - /// </exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan(T[] array, int start, int length) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - _pointer = new ByReference<T>(ref Unsafe.Add(ref JitHelpers.GetArrayData(array), start)); - _length = length; - } - - /// <summary> - /// Creates a new read-only span over the target unmanaged buffer. Clearly this - /// is quite dangerous, because we are creating arbitrarily typed T's - /// out of a void*-typed block of memory. And the length is not checked. - /// But if this creation is correct, then all subsequent uses are correct. - /// </summary> - /// <param name="pointer">An unmanaged pointer to memory.</param> - /// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param> - /// <exception cref="System.ArgumentException"> - /// Thrown when <typeparamref name="T"/> is reference type or contains pointers and hence cannot be stored in unmanaged memory. - /// </exception> - /// <exception cref="System.ArgumentOutOfRangeException"> - /// Thrown when the specified <paramref name="length"/> is negative. - /// </exception> - [CLSCompliant(false)] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe ReadOnlySpan(void* pointer, int length) - { - if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); - if (length < 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - _pointer = new ByReference<T>(ref Unsafe.As<byte, T>(ref *(byte*)pointer)); - _length = length; - } - - /// <summary> - /// Create a new read-only span over a portion of a regular managed object. This can be useful - /// if part of a managed object represents a "fixed array." This is dangerous because neither the - /// <paramref name="length"/> is checked, nor <paramref name="obj"/> being null, nor the fact that - /// "rawPointer" actually lies within <paramref name="obj"/>. - /// </summary> - /// <param name="obj">The managed object that contains the data to span over.</param> - /// <param name="objectData">A reference to data within that object.</param> - /// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan<T> DangerousCreate(object obj, ref T objectData, int length) => new ReadOnlySpan<T>(ref objectData, length); - - // Constructor for internal use only. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan(ref T ptr, int length) - { - Debug.Assert(length >= 0); - - _pointer = new ByReference<T>(ref ptr); - _length = length; - } - - /// <summary> - /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element - /// would have been stored. Such a reference can be used for pinning but must never be dereferenced. - /// </summary> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T DangerousGetPinnableReference() - { - return ref _pointer.Value; - } - - /// <summary> - /// The number of items in the read-only span. - /// </summary> - public int Length => _length; - - /// <summary> - /// Returns true if Length is 0. - /// </summary> - public bool IsEmpty => _length == 0; - - /// <summary> - /// Returns the specified element of the read-only span. - /// </summary> - /// <param name="index"></param> - /// <returns></returns> - /// <exception cref="System.IndexOutOfRangeException"> - /// Thrown when index less than 0 or index greater than or equal to Length - /// </exception> - public T this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - if ((uint)index >= (uint)_length) - ThrowHelper.ThrowIndexOutOfRangeException(); - - return Unsafe.Add(ref _pointer.Value, index); - } - } - - /// <summary> - /// Copies the contents of this read-only span into destination span. If the source - /// and destinations overlap, this method behaves as if the original values in - /// a temporary location before the destination is overwritten. - /// - /// <param name="destination">The span to copy items into.</param> - /// <exception cref="System.ArgumentException"> - /// Thrown when the destination Span is shorter than the source Span. - /// </exception> - /// </summary> - public void CopyTo(Span<T> destination) - { - if (!TryCopyTo(destination)) - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - } - - /// Copies the contents of this read-only span into destination span. If the source - /// and destinations overlap, this method behaves as if the original values in - /// a temporary location before the destination is overwritten. - /// </summary> - /// <returns>If the destination span is shorter than the source span, this method - /// return false and no data is written to the destination.</returns> - /// <param name="destination">The span to copy items into.</param> - public bool TryCopyTo(Span<T> destination) - { - if ((uint)_length > (uint)destination.Length) - return false; - - SpanHelper.CopyTo<T>(ref destination.DangerousGetPinnableReference(), ref _pointer.Value, _length); - return true; - } - - /// <summary> - /// Returns true if left and right point at the same memory and have the same length. Note that - /// this does *not* check to see if the *contents* are equal. - /// </summary> - public static bool operator ==(ReadOnlySpan<T> left, ReadOnlySpan<T> right) - { - return left._length == right._length && Unsafe.AreSame<T>(ref left._pointer.Value, ref right._pointer.Value); - } - - /// <summary> - /// Returns false if left and right point at the same memory and have the same length. Note that - /// this does *not* check to see if the *contents* are equal. - /// </summary> - public static bool operator !=(ReadOnlySpan<T> left, ReadOnlySpan<T> right) => !(left == right); - - /// <summary> - /// This method is not supported as spans cannot be boxed. To compare two spans, use operator==. - /// <exception cref="System.NotSupportedException"> - /// Always thrown by this method. - /// </exception> - /// </summary> - [Obsolete("Equals() on Span will always throw an exception. Use == instead.")] - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object obj) - { - throw new NotSupportedException(SR.NotSupported_CannotCallEqualsOnSpan); - } - - /// <summary> - /// This method is not supported as spans cannot be boxed. - /// <exception cref="System.NotSupportedException"> - /// Always thrown by this method. - /// </exception> - /// </summary> - [Obsolete("GetHashCode() on Span will always throw an exception.")] - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() - { - throw new NotSupportedException(SR.NotSupported_CannotCallGetHashCodeOnSpan); - } - - /// <summary> - /// Defines an implicit conversion of an array to a <see cref="ReadOnlySpan{T}"/> - /// </summary> - public static implicit operator ReadOnlySpan<T>(T[] array) => new ReadOnlySpan<T>(array); - - /// <summary> - /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="ReadOnlySpan{T}"/> - /// </summary> - public static implicit operator ReadOnlySpan<T>(ArraySegment<T> arraySegment) => new ReadOnlySpan<T>(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - /// <summary> - /// Forms a slice out of the given read-only span, beginning at 'start'. - /// </summary> - /// <param name="start">The index at which to begin this slice.</param> - /// <exception cref="System.ArgumentOutOfRangeException"> - /// Thrown when the specified <paramref name="start"/> index is not in range (<0 or >=Length). - /// </exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan<T> Slice(int start) - { - if ((uint)start > (uint)_length) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - return new ReadOnlySpan<T>(ref Unsafe.Add(ref _pointer.Value, start), _length - start); - } - - /// <summary> - /// Forms a slice out of the given read-only span, beginning at 'start', of given length - /// </summary> - /// <param name="start">The index at which to begin this slice.</param> - /// <param name="length">The desired length for the slice (exclusive).</param> - /// <exception cref="System.ArgumentOutOfRangeException"> - /// Thrown when the specified <paramref name="start"/> or end index is not in range (<0 or >=Length). - /// </exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan<T> Slice(int start, int length) - { - if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - return new ReadOnlySpan<T>(ref Unsafe.Add(ref _pointer.Value, start), length); - } - - /// <summary> - /// Copies the contents of this read-only span into a new array. This heap - /// allocates, so should generally be avoided, however it is sometimes - /// necessary to bridge the gap with APIs written in terms of arrays. - /// </summary> - public T[] ToArray() - { - if (_length == 0) - return Array.Empty<T>(); - - var destination = new T[_length]; - SpanHelper.CopyTo<T>(ref JitHelpers.GetArrayData(destination), ref _pointer.Value, _length); - return destination; - } - - /// <summary> - /// Returns a 0-length read-only span whose base is the null pointer. - /// </summary> - public static ReadOnlySpan<T> Empty => default(ReadOnlySpan<T>); - } -} diff --git a/src/mscorlib/src/System/Reflection/Assembly.CoreCLR.cs b/src/mscorlib/src/System/Reflection/Assembly.CoreCLR.cs index 82966dba60..708f79b64f 100644 --- a/src/mscorlib/src/System/Reflection/Assembly.CoreCLR.cs +++ b/src/mscorlib/src/System/Reflection/Assembly.CoreCLR.cs @@ -101,14 +101,20 @@ namespace System.Reflection { Contract.Ensures(Contract.Result<Assembly>() != null); Contract.Ensures(!Contract.Result<Assembly>().ReflectionOnly); - + + AssemblyName modifiedAssemblyRef = null; if (assemblyRef != null && assemblyRef.CodeBase != null) { - throw new NotSupportedException(SR.NotSupported_AssemblyLoadCodeBase); + modifiedAssemblyRef = (AssemblyName)assemblyRef.Clone(); + modifiedAssemblyRef.CodeBase = null; } - + else + { + modifiedAssemblyRef = assemblyRef; + } + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; - return RuntimeAssembly.InternalLoadAssemblyName(assemblyRef, null, null, ref stackMark, true /*thrownOnFileNotFound*/, false /*forIntrospection*/); + return RuntimeAssembly.InternalLoadAssemblyName(modifiedAssemblyRef, null, null, ref stackMark, true /*thrownOnFileNotFound*/, false /*forIntrospection*/); } // Locate an assembly by its name. The name can be strong or @@ -119,13 +125,19 @@ namespace System.Reflection Contract.Ensures(Contract.Result<Assembly>() != null); Contract.Ensures(!Contract.Result<Assembly>().ReflectionOnly); + AssemblyName modifiedAssemblyRef = null; if (assemblyRef != null && assemblyRef.CodeBase != null) { - throw new NotSupportedException(SR.NotSupported_AssemblyLoadCodeBase); + modifiedAssemblyRef = (AssemblyName)assemblyRef.Clone(); + modifiedAssemblyRef.CodeBase = null; + } + else + { + modifiedAssemblyRef = assemblyRef; } StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; - return RuntimeAssembly.InternalLoadAssemblyName(assemblyRef, null, null, ref stackMark, true /*thrownOnFileNotFound*/, false /*forIntrospection*/, ptrLoadContextBinder); + return RuntimeAssembly.InternalLoadAssemblyName(modifiedAssemblyRef, null, null, ref stackMark, true /*thrownOnFileNotFound*/, false /*forIntrospection*/, ptrLoadContextBinder); } // Loads the assembly with a COFF based IMAGE containing diff --git a/src/mscorlib/src/System/Runtime/CompilerServices/TaskAwaiter.cs b/src/mscorlib/src/System/Runtime/CompilerServices/TaskAwaiter.cs index e2fa6caa2d..c35658e54c 100644 --- a/src/mscorlib/src/System/Runtime/CompilerServices/TaskAwaiter.cs +++ b/src/mscorlib/src/System/Runtime/CompilerServices/TaskAwaiter.cs @@ -146,7 +146,7 @@ namespace System.Runtime.CompilerServices task.NotifyDebuggerOfWaitCompletionIfNecessary(); // And throw an exception if the task is faulted or canceled. - if (!task.IsRanToCompletion) ThrowForNonSuccess(task); + if (!task.IsCompletedSuccessfully) ThrowForNonSuccess(task); } /// <summary>Throws an exception to handle a task that completed in a state other than RanToCompletion.</summary> diff --git a/src/mscorlib/src/System/Runtime/CompilerServices/jithelpers.cs b/src/mscorlib/src/System/Runtime/CompilerServices/jithelpers.cs index b86835f778..d5201350ec 100644 --- a/src/mscorlib/src/System/Runtime/CompilerServices/jithelpers.cs +++ b/src/mscorlib/src/System/Runtime/CompilerServices/jithelpers.cs @@ -216,14 +216,7 @@ namespace System.Runtime.CompilerServices private extern static bool IsAddressInStack(IntPtr ptr); #endif - static internal bool ByRefLessThan<T>(ref T refA, ref T refB) - { - // The body of this function will be replaced by the EE with unsafe code!!! - // See getILIntrinsicImplementation for how this happens. - throw new InvalidOperationException(); - } - - static internal ref T GetArrayData<T>(T[] array) + static internal ref byte GetRawSzArrayData(this Array array) { // The body of this function will be replaced by the EE with unsafe code!!! // See getILIntrinsicImplementation for how this happens. diff --git a/src/mscorlib/src/System/Runtime/InteropServices/Marshal.cs b/src/mscorlib/src/System/Runtime/InteropServices/Marshal.cs index 248e0d5778..6fb631121b 100644 --- a/src/mscorlib/src/System/Runtime/InteropServices/Marshal.cs +++ b/src/mscorlib/src/System/Runtime/InteropServices/Marshal.cs @@ -824,6 +824,9 @@ namespace System.Runtime.InteropServices } [MethodImplAttribute(MethodImplOptions.InternalCall)] + public static extern /* struct _EXCEPTION_POINTERS* */ IntPtr GetExceptionPointers(); + + [MethodImplAttribute(MethodImplOptions.InternalCall)] public static extern int GetExceptionCode(); diff --git a/src/mscorlib/src/System/Span.cs b/src/mscorlib/src/System/Span.cs deleted file mode 100644 index 4211083def..0000000000 --- a/src/mscorlib/src/System/Span.cs +++ /dev/null @@ -1,910 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Diagnostics; -using System.Runtime; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using EditorBrowsableState = System.ComponentModel.EditorBrowsableState; -using EditorBrowsableAttribute = System.ComponentModel.EditorBrowsableAttribute; - -#pragma warning disable 0809 //warning CS0809: Obsolete member 'Span<T>.Equals(object)' overrides non-obsolete member 'object.Equals(object)' - -#if BIT64 -using nuint = System.UInt64; -#else -using nuint = System.UInt32; -#endif - -namespace System -{ - /// <summary> - /// Span represents a contiguous region of arbitrary memory. Unlike arrays, it can point to either managed - /// or native memory, or to memory allocated on the stack. It is type- and memory-safe. - /// </summary> - public struct Span<T> - { - /// <summary>A byref or a native ptr.</summary> - private readonly ByReference<T> _pointer; - /// <summary>The number of elements this Span contains.</summary> - private readonly int _length; - - /// <summary> - /// Creates a new span over the entirety of the target array. - /// </summary> - /// <param name="array">The target array.</param> - /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="array"/> is a null - /// reference (Nothing in Visual Basic).</exception> - /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span(T[] array) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - if (default(T) == null && array.GetType() != typeof(T[])) - ThrowHelper.ThrowArrayTypeMismatchException(); - - _pointer = new ByReference<T>(ref JitHelpers.GetArrayData(array)); - _length = array.Length; - } - - /// <summary> - /// Creates a new span over the portion of the target array beginning - /// at 'start' index and covering the remainder of the array. - /// </summary> - /// <param name="array">The target array.</param> - /// <param name="start">The index at which to begin the span.</param> - /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="array"/> is a null - /// reference (Nothing in Visual Basic).</exception> - /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception> - /// <exception cref="System.ArgumentOutOfRangeException"> - /// Thrown when the specified <paramref name="start"/> is not in the range (<0 or >=Length). - /// </exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span(T[] array, int start) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - if (default(T) == null && array.GetType() != typeof(T[])) - ThrowHelper.ThrowArrayTypeMismatchException(); - if ((uint)start > (uint)array.Length) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - _pointer = new ByReference<T>(ref Unsafe.Add(ref JitHelpers.GetArrayData(array), start)); - _length = array.Length - start; - } - - /// <summary> - /// Creates a new span over the portion of the target array beginning - /// at 'start' index and ending at 'end' index (exclusive). - /// </summary> - /// <param name="array">The target array.</param> - /// <param name="start">The index at which to begin the span.</param> - /// <param name="length">The number of items in the span.</param> - /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="array"/> is a null - /// reference (Nothing in Visual Basic).</exception> - /// <exception cref="System.ArrayTypeMismatchException">Thrown when <paramref name="array"/> is covariant and array's type is not exactly T[].</exception> - /// <exception cref="System.ArgumentOutOfRangeException"> - /// Thrown when the specified <paramref name="start"/> or end index is not in the range (<0 or >=Length). - /// </exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span(T[] array, int start, int length) - { - if (array == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); - if (default(T) == null && array.GetType() != typeof(T[])) - ThrowHelper.ThrowArrayTypeMismatchException(); - if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start)) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - _pointer = new ByReference<T>(ref Unsafe.Add(ref JitHelpers.GetArrayData(array), start)); - _length = length; - } - - /// <summary> - /// Creates a new span over the target unmanaged buffer. Clearly this - /// is quite dangerous, because we are creating arbitrarily typed T's - /// out of a void*-typed block of memory. And the length is not checked. - /// But if this creation is correct, then all subsequent uses are correct. - /// </summary> - /// <param name="pointer">An unmanaged pointer to memory.</param> - /// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param> - /// <exception cref="System.ArgumentException"> - /// Thrown when <typeparamref name="T"/> is reference type or contains pointers and hence cannot be stored in unmanaged memory. - /// </exception> - /// <exception cref="System.ArgumentOutOfRangeException"> - /// Thrown when the specified <paramref name="length"/> is negative. - /// </exception> - [CLSCompliant(false)] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe Span(void* pointer, int length) - { - if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); - if (length < 0) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - _pointer = new ByReference<T>(ref Unsafe.As<byte, T>(ref *(byte*)pointer)); - _length = length; - } - - /// <summary> - /// Create a new span over a portion of a regular managed object. This can be useful - /// if part of a managed object represents a "fixed array." This is dangerous because neither the - /// <paramref name="length"/> is checked, nor <paramref name="obj"/> being null, nor the fact that - /// "rawPointer" actually lies within <paramref name="obj"/>. - /// </summary> - /// <param name="obj">The managed object that contains the data to span over.</param> - /// <param name="objectData">A reference to data within that object.</param> - /// <param name="length">The number of <typeparamref name="T"/> elements the memory contains.</param> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span<T> DangerousCreate(object obj, ref T objectData, int length) => new Span<T>(ref objectData, length); - - // Constructor for internal use only. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal Span(ref T ptr, int length) - { - Debug.Assert(length >= 0); - - _pointer = new ByReference<T>(ref ptr); - _length = length; - } - - /// <summary> - /// Returns a reference to the 0th element of the Span. If the Span is empty, returns a reference to the location where the 0th element - /// would have been stored. Such a reference can be used for pinning but must never be dereferenced. - /// </summary> - public ref T DangerousGetPinnableReference() - { - return ref _pointer.Value; - } - - /// <summary> - /// The number of items in the span. - /// </summary> - public int Length => _length; - - /// <summary> - /// Returns true if Length is 0. - /// </summary> - public bool IsEmpty => _length == 0; - - /// <summary> - /// Returns a reference to specified element of the Span. - /// </summary> - /// <param name="index"></param> - /// <returns></returns> - /// <exception cref="System.IndexOutOfRangeException"> - /// Thrown when index less than 0 or index greater than or equal to Length - /// </exception> - public ref T this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - if ((uint)index >= (uint)_length) - ThrowHelper.ThrowIndexOutOfRangeException(); - - return ref Unsafe.Add(ref _pointer.Value, index); - } - } - - /// <summary> - /// Clears the contents of this span. - /// </summary> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear() - { - if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) - { - SpanHelper.ClearWithReferences(ref Unsafe.As<T, IntPtr>(ref _pointer.Value), (nuint)_length * (nuint)(Unsafe.SizeOf<T>() / sizeof(nuint))); - } - else - { - SpanHelper.ClearWithoutReferences(ref Unsafe.As<T, byte>(ref _pointer.Value), (nuint)_length * (nuint)Unsafe.SizeOf<T>()); - } - } - - /// <summary> - /// Fills the contents of this span with the given value. - /// </summary> - public void Fill(T value) - { - if (Unsafe.SizeOf<T>() == 1) - { - uint length = (uint)_length; - if (length == 0) - return; - - T tmp = value; // Avoid taking address of the "value" argument. It would regress performance of the loop below. - Unsafe.InitBlockUnaligned(ref Unsafe.As<T, byte>(ref _pointer.Value), Unsafe.As<T, byte>(ref tmp), length); - } - else - { - // Do all math as nuint to avoid unnecessary 64->32->64 bit integer truncations - nuint length = (uint)_length; - if (length == 0) - return; - - ref T r = ref DangerousGetPinnableReference(); - - // TODO: Create block fill for value types of power of two sizes e.g. 2,4,8,16 - - nuint elementSize = (uint)Unsafe.SizeOf<T>(); - nuint i = 0; - for (; i < (length & ~(nuint)7); i += 8) - { - Unsafe.AddByteOffset<T>(ref r, (i + 0) * elementSize) = value; - Unsafe.AddByteOffset<T>(ref r, (i + 1) * elementSize) = value; - Unsafe.AddByteOffset<T>(ref r, (i + 2) * elementSize) = value; - Unsafe.AddByteOffset<T>(ref r, (i + 3) * elementSize) = value; - Unsafe.AddByteOffset<T>(ref r, (i + 4) * elementSize) = value; - Unsafe.AddByteOffset<T>(ref r, (i + 5) * elementSize) = value; - Unsafe.AddByteOffset<T>(ref r, (i + 6) * elementSize) = value; - Unsafe.AddByteOffset<T>(ref r, (i + 7) * elementSize) = value; - } - if (i < (length & ~(nuint)3)) - { - Unsafe.AddByteOffset<T>(ref r, (i + 0) * elementSize) = value; - Unsafe.AddByteOffset<T>(ref r, (i + 1) * elementSize) = value; - Unsafe.AddByteOffset<T>(ref r, (i + 2) * elementSize) = value; - Unsafe.AddByteOffset<T>(ref r, (i + 3) * elementSize) = value; - i += 4; - } - for (; i < length; i++) - { - Unsafe.AddByteOffset<T>(ref r, i * elementSize) = value; - } - } - } - - /// <summary> - /// Copies the contents of this span into destination span. If the source - /// and destinations overlap, this method behaves as if the original values in - /// a temporary location before the destination is overwritten. - /// </summary> - /// <param name="destination">The span to copy items into.</param> - /// <exception cref="System.ArgumentException"> - /// Thrown when the destination Span is shorter than the source Span. - /// </exception> - public void CopyTo(Span<T> destination) - { - if (!TryCopyTo(destination)) - ThrowHelper.ThrowArgumentException_DestinationTooShort(); - } - - /// <summary> - /// Copies the contents of this span into destination span. If the source - /// and destinations overlap, this method behaves as if the original values in - /// a temporary location before the destination is overwritten. - /// </summary> - /// <param name="destination">The span to copy items into.</param> - /// <returns>If the destination span is shorter than the source span, this method - /// return false and no data is written to the destination.</returns> - public bool TryCopyTo(Span<T> destination) - { - if ((uint)_length > (uint)destination.Length) - return false; - - SpanHelper.CopyTo<T>(ref destination._pointer.Value, ref _pointer.Value, _length); - return true; - } - - /// <summary> - /// Returns true if left and right point at the same memory and have the same length. Note that - /// this does *not* check to see if the *contents* are equal. - /// </summary> - public static bool operator ==(Span<T> left, Span<T> right) - { - return left._length == right._length && Unsafe.AreSame<T>(ref left._pointer.Value, ref right._pointer.Value); - } - - /// <summary> - /// Returns false if left and right point at the same memory and have the same length. Note that - /// this does *not* check to see if the *contents* are equal. - /// </summary> - public static bool operator !=(Span<T> left, Span<T> right) => !(left == right); - - /// <summary> - /// This method is not supported as spans cannot be boxed. To compare two spans, use operator==. - /// <exception cref="System.NotSupportedException"> - /// Always thrown by this method. - /// </exception> - /// </summary> - [Obsolete("Equals() on Span will always throw an exception. Use == instead.")] - [EditorBrowsable(EditorBrowsableState.Never)] - public override bool Equals(object obj) - { - throw new NotSupportedException(SR.NotSupported_CannotCallEqualsOnSpan); - } - - /// <summary> - /// This method is not supported as spans cannot be boxed. - /// <exception cref="System.NotSupportedException"> - /// Always thrown by this method. - /// </exception> - /// </summary> - [Obsolete("GetHashCode() on Span will always throw an exception.")] - [EditorBrowsable(EditorBrowsableState.Never)] - public override int GetHashCode() - { - throw new NotSupportedException(SR.NotSupported_CannotCallGetHashCodeOnSpan); - } - - /// <summary> - /// Defines an implicit conversion of an array to a <see cref="Span{T}"/> - /// </summary> - public static implicit operator Span<T>(T[] array) => new Span<T>(array); - - /// <summary> - /// Defines an implicit conversion of a <see cref="ArraySegment{T}"/> to a <see cref="Span{T}"/> - /// </summary> - public static implicit operator Span<T>(ArraySegment<T> arraySegment) => new Span<T>(arraySegment.Array, arraySegment.Offset, arraySegment.Count); - - /// <summary> - /// Defines an implicit conversion of a <see cref="Span{T}"/> to a <see cref="ReadOnlySpan{T}"/> - /// </summary> - public static implicit operator ReadOnlySpan<T>(Span<T> span) => new ReadOnlySpan<T>(ref span._pointer.Value, span._length); - - /// <summary> - /// Forms a slice out of the given span, beginning at 'start'. - /// </summary> - /// <param name="start">The index at which to begin this slice.</param> - /// <exception cref="System.ArgumentOutOfRangeException"> - /// Thrown when the specified <paramref name="start"/> index is not in range (<0 or >=Length). - /// </exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span<T> Slice(int start) - { - if ((uint)start > (uint)_length) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - return new Span<T>(ref Unsafe.Add(ref _pointer.Value, start), _length - start); - } - - /// <summary> - /// Forms a slice out of the given span, beginning at 'start', of given length - /// </summary> - /// <param name="start">The index at which to begin this slice.</param> - /// <param name="length">The desired length for the slice (exclusive).</param> - /// <exception cref="System.ArgumentOutOfRangeException"> - /// Thrown when the specified <paramref name="start"/> or end index is not in range (<0 or >=Length). - /// </exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Span<T> Slice(int start, int length) - { - if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start)) - ThrowHelper.ThrowArgumentOutOfRangeException(); - - return new Span<T>(ref Unsafe.Add(ref _pointer.Value, start), length); - } - - /// <summary> - /// Copies the contents of this span into a new array. This heap - /// allocates, so should generally be avoided, however it is sometimes - /// necessary to bridge the gap with APIs written in terms of arrays. - /// </summary> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T[] ToArray() - { - if (_length == 0) - return Array.Empty<T>(); - - var destination = new T[_length]; - SpanHelper.CopyTo<T>(ref JitHelpers.GetArrayData(destination), ref _pointer.Value, _length); - return destination; - } - - // <summary> - /// Returns an empty <see cref="Span{T}"/> - /// </summary> - public static Span<T> Empty => default(Span<T>); - } - - public static class Span - { - /// <summary> - /// Casts a Span of one primitive type <typeparamref name="T"/> to Span of bytes. - /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety. - /// </summary> - /// <param name="source">The source slice, of type <typeparamref name="T"/>.</param> - /// <exception cref="System.ArgumentException"> - /// Thrown when <typeparamref name="T"/> contains pointers. - /// </exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span<byte> AsBytes<T>(this Span<T> source) - where T : struct - { - if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); - - return new Span<byte>( - ref Unsafe.As<T, byte>(ref source.DangerousGetPinnableReference()), - checked(source.Length * Unsafe.SizeOf<T>())); - } - - /// <summary> - /// Casts a ReadOnlySpan of one primitive type <typeparamref name="T"/> to ReadOnlySpan of bytes. - /// That type may not contain pointers or references. This is checked at runtime in order to preserve type safety. - /// </summary> - /// <param name="source">The source slice, of type <typeparamref name="T"/>.</param> - /// <exception cref="System.ArgumentException"> - /// Thrown when <typeparamref name="T"/> contains pointers. - /// </exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan<byte> AsBytes<T>(this ReadOnlySpan<T> source) - where T : struct - { - if (RuntimeHelpers.IsReferenceOrContainsReferences<T>()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(T)); - - return new ReadOnlySpan<byte>( - ref Unsafe.As<T, byte>(ref source.DangerousGetPinnableReference()), - checked(source.Length * Unsafe.SizeOf<T>())); - } - - /// <summary> - /// Casts a Span of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>. - /// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety. - /// </summary> - /// <remarks> - /// Supported only for platforms that support misaligned memory access. - /// </remarks> - /// <param name="source">The source slice, of type <typeparamref name="TFrom"/>.</param> - /// <exception cref="System.ArgumentException"> - /// Thrown when <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> contains pointers. - /// </exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Span<TTo> NonPortableCast<TFrom, TTo>(this Span<TFrom> source) - where TFrom : struct - where TTo : struct - { - if (RuntimeHelpers.IsReferenceOrContainsReferences<TFrom>()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TFrom)); - if (RuntimeHelpers.IsReferenceOrContainsReferences<TTo>()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo)); - - return new Span<TTo>( - ref Unsafe.As<TFrom, TTo>(ref source.DangerousGetPinnableReference()), - checked((int)((long)source.Length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()))); - } - - /// <summary> - /// Casts a ReadOnlySpan of one primitive type <typeparamref name="TFrom"/> to another primitive type <typeparamref name="TTo"/>. - /// These types may not contain pointers or references. This is checked at runtime in order to preserve type safety. - /// </summary> - /// <remarks> - /// Supported only for platforms that support misaligned memory access. - /// </remarks> - /// <param name="source">The source slice, of type <typeparamref name="TFrom"/>.</param> - /// <exception cref="System.ArgumentException"> - /// Thrown when <typeparamref name="TFrom"/> or <typeparamref name="TTo"/> contains pointers. - /// </exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan<TTo> NonPortableCast<TFrom, TTo>(this ReadOnlySpan<TFrom> source) - where TFrom : struct - where TTo : struct - { - if (RuntimeHelpers.IsReferenceOrContainsReferences<TFrom>()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TFrom)); - if (RuntimeHelpers.IsReferenceOrContainsReferences<TTo>()) - ThrowHelper.ThrowInvalidTypeWithPointersNotSupported(typeof(TTo)); - - return new ReadOnlySpan<TTo>( - ref Unsafe.As<TFrom, TTo>(ref source.DangerousGetPinnableReference()), - checked((int)((long)source.Length * Unsafe.SizeOf<TFrom>() / Unsafe.SizeOf<TTo>()))); - } - - /// <summary> - /// Creates a new readonly span over the portion of the target string. - /// </summary> - /// <param name="text">The target string.</param> - /// <exception cref="System.ArgumentNullException">Thrown when <paramref name="text"/> is a null - /// reference (Nothing in Visual Basic).</exception> - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ReadOnlySpan<char> AsSpan(this string text) - { - if (text == null) - ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text); - - return new ReadOnlySpan<char>(ref text.GetFirstCharRef(), text.Length); - } - } - - internal static class SpanHelper - { - internal static unsafe void CopyTo<T>(ref T destination, ref T source, int elementsCount) - { - if (Unsafe.AreSame(ref destination, ref source)) - return; - - if (elementsCount <= 1) - { - if (elementsCount == 1) - { - destination = source; - } - return; - } - - nuint byteCount = (nuint)elementsCount * (nuint)Unsafe.SizeOf<T>(); - if (!RuntimeHelpers.IsReferenceOrContainsReferences<T>()) - { - fixed (byte* pDestination = &Unsafe.As<T, byte>(ref destination)) - { - fixed (byte* pSource = &Unsafe.As<T, byte>(ref source)) - { - Buffer.Memmove(pDestination, pSource, byteCount); - } - } - } - else - { - RuntimeImports.RhBulkMoveWithWriteBarrier( - ref Unsafe.As<T, byte>(ref destination), - ref Unsafe.As<T, byte>(ref source), - byteCount); - } - } - - internal static unsafe void ClearWithoutReferences(ref byte b, nuint byteLength) - { - if (byteLength == 0) - return; - -#if AMD64 - if (byteLength > 4096) goto PInvoke; - Unsafe.InitBlockUnaligned(ref b, 0, (uint)byteLength); - return; -#else // AMD64 - // TODO: Optimize this method on X86 machine - // Note: It's important that this switch handles lengths at least up to 22. - // See notes below near the main loop for why. - - // The switch will be very fast since it can be implemented using a jump - // table in assembly. See http://stackoverflow.com/a/449297/4077294 for more info. - - switch (byteLength) - { - case 1: - b = 0; - return; - case 2: - Unsafe.As<byte, short>(ref b) = 0; - return; - case 3: - Unsafe.As<byte, short>(ref b) = 0; - Unsafe.Add<byte>(ref b, 2) = 0; - return; - case 4: - Unsafe.As<byte, int>(ref b) = 0; - return; - case 5: - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.Add<byte>(ref b, 4) = 0; - return; - case 6: - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 4)) = 0; - return; - case 7: - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 4)) = 0; - Unsafe.Add<byte>(ref b, 6) = 0; - return; - case 8: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; -#endif - return; - case 9: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; -#endif - Unsafe.Add<byte>(ref b, 8) = 0; - return; - case 10: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; -#endif - Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 8)) = 0; - return; - case 11: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; -#endif - Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 8)) = 0; - Unsafe.Add<byte>(ref b, 10) = 0; - return; - case 12: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; -#endif - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0; - return; - case 13: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; -#endif - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0; - Unsafe.Add<byte>(ref b, 12) = 0; - return; - case 14: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; -#endif - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0; - Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 12)) = 0; - return; - case 15: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; -#endif - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0; - Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 12)) = 0; - Unsafe.Add<byte>(ref b, 14) = 0; - return; - case 16: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; - Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0; -#endif - return; - case 17: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; - Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0; -#endif - Unsafe.Add<byte>(ref b, 16) = 0; - return; - case 18: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; - Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0; -#endif - Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 16)) = 0; - return; - case 19: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; - Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0; -#endif - Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 16)) = 0; - Unsafe.Add<byte>(ref b, 18) = 0; - return; - case 20: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; - Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0; -#endif - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0; - return; - case 21: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; - Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0; -#endif - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0; - Unsafe.Add<byte>(ref b, 20) = 0; - return; - case 22: -#if BIT64 - Unsafe.As<byte, long>(ref b) = 0; - Unsafe.As<byte, long>(ref Unsafe.Add<byte>(ref b, 8)) = 0; -#else - Unsafe.As<byte, int>(ref b) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 4)) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 8)) = 0; - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 12)) = 0; -#endif - Unsafe.As<byte, int>(ref Unsafe.Add<byte>(ref b, 16)) = 0; - Unsafe.As<byte, short>(ref Unsafe.Add<byte>(ref b, 20)) = 0; - return; - } - - // P/Invoke into the native version for large lengths - if (byteLength >= 512) goto PInvoke; - - nuint i = 0; // byte offset at which we're copying - - if ((Unsafe.As<byte, int>(ref b) & 3) != 0) - { - if ((Unsafe.As<byte, int>(ref b) & 1) != 0) - { - Unsafe.AddByteOffset<byte>(ref b, i) = 0; - i += 1; - if ((Unsafe.As<byte, int>(ref b) & 2) != 0) - goto IntAligned; - } - Unsafe.As<byte, short>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0; - i += 2; - } - - IntAligned: - - // On 64-bit IntPtr.Size == 8, so we want to advance to the next 8-aligned address. If - // (int)b % 8 is 0, 5, 6, or 7, we will already have advanced by 0, 3, 2, or 1 - // bytes to the next aligned address (respectively), so do nothing. On the other hand, - // if it is 1, 2, 3, or 4 we will want to copy-and-advance another 4 bytes until - // we're aligned. - // The thing 1, 2, 3, and 4 have in common that the others don't is that if you - // subtract one from them, their 3rd lsb will not be set. Hence, the below check. - - if (((Unsafe.As<byte, int>(ref b) - 1) & 4) == 0) - { - Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0; - i += 4; - } - - nuint end = byteLength - 16; - byteLength -= i; // lower 4 bits of byteLength represent how many bytes are left *after* the unrolled loop - - // We know due to the above switch-case that this loop will always run 1 iteration; max - // bytes we clear before checking is 23 (7 to align the pointers, 16 for 1 iteration) so - // the switch handles lengths 0-22. - Debug.Assert(end >= 7 && i <= end); - - // This is separated out into a different variable, so the i + 16 addition can be - // performed at the start of the pipeline and the loop condition does not have - // a dependency on the writes. - nuint counter; - - do - { - counter = i + 16; - - // This loop looks very costly since there appear to be a bunch of temporary values - // being created with the adds, but the jit (for x86 anyways) will convert each of - // these to use memory addressing operands. - - // So the only cost is a bit of code size, which is made up for by the fact that - // we save on writes to b. - -#if BIT64 - Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0; - Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i + 8)) = 0; -#else - Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0; - Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 4)) = 0; - Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 8)) = 0; - Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 12)) = 0; -#endif - - i = counter; - - // See notes above for why this wasn't used instead - // i += 16; - } - while (counter <= end); - - if ((byteLength & 8) != 0) - { -#if BIT64 - Unsafe.As<byte, long>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0; -#else - Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0; - Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i + 4)) = 0; -#endif - i += 8; - } - if ((byteLength & 4) != 0) - { - Unsafe.As<byte, int>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0; - i += 4; - } - if ((byteLength & 2) != 0) - { - Unsafe.As<byte, short>(ref Unsafe.AddByteOffset<byte>(ref b, i)) = 0; - i += 2; - } - if ((byteLength & 1) != 0) - { - Unsafe.AddByteOffset<byte>(ref b, i) = 0; - // We're not using i after this, so not needed - // i += 1; - } - - return; -#endif // AMD64 - - PInvoke: - RuntimeImports.RhZeroMemory(ref b, byteLength); - } - - internal static unsafe void ClearWithReferences(ref IntPtr ip, nuint pointerSizeLength) - { - if (pointerSizeLength == 0) - return; - - // TODO: Perhaps do switch casing to improve small size perf - - nuint i = 0; - nuint n = 0; - while ((n = i + 8) <= (pointerSizeLength)) - { - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr); - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 1) * (nuint)sizeof(IntPtr)) = default(IntPtr); - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 2) * (nuint)sizeof(IntPtr)) = default(IntPtr); - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 3) * (nuint)sizeof(IntPtr)) = default(IntPtr); - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 4) * (nuint)sizeof(IntPtr)) = default(IntPtr); - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 5) * (nuint)sizeof(IntPtr)) = default(IntPtr); - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 6) * (nuint)sizeof(IntPtr)) = default(IntPtr); - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 7) * (nuint)sizeof(IntPtr)) = default(IntPtr); - i = n; - } - if ((n = i + 4) <= (pointerSizeLength)) - { - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr); - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 1) * (nuint)sizeof(IntPtr)) = default(IntPtr); - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 2) * (nuint)sizeof(IntPtr)) = default(IntPtr); - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 3) * (nuint)sizeof(IntPtr)) = default(IntPtr); - i = n; - } - if ((n = i + 2) <= (pointerSizeLength)) - { - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr); - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 1) * (nuint)sizeof(IntPtr)) = default(IntPtr); - i = n; - } - if ((i + 1) <= (pointerSizeLength)) - { - Unsafe.AddByteOffset<IntPtr>(ref ip, (i + 0) * (nuint)sizeof(IntPtr)) = default(IntPtr); - } - } - } -} diff --git a/src/mscorlib/src/System/String.Manipulation.cs b/src/mscorlib/src/System/String.Manipulation.cs index 33e341c58e..b71ffa2f63 100644 --- a/src/mscorlib/src/System/String.Manipulation.cs +++ b/src/mscorlib/src/System/String.Manipulation.cs @@ -1022,8 +1022,10 @@ namespace System { if (oldValue == null) throw new ArgumentNullException(nameof(oldValue)); + if (oldValue.Length == 0) + throw new ArgumentException(SR.Argument_StringZeroLength, nameof(oldValue)); - // If they asked to replace oldValue with a null, replace all occurences + // If they asked to replace oldValue with a null, replace all occurrences // with the empty string. if (newValue == null) newValue = string.Empty; @@ -1037,10 +1039,11 @@ namespace System int matchLength = 0; bool hasDoneAnyReplacements = false; + CompareInfo ci = referenceCulture.CompareInfo; do { - index = referenceCulture.CompareInfo.IndexOfCore(this, oldValue, startIndex, m_stringLength - startIndex, options, &matchLength); + index = ci.IndexOf(this, oldValue, startIndex, m_stringLength - startIndex, options, &matchLength); if (index >= 0) { // append the unmodified portion of string diff --git a/src/mscorlib/src/System/String.cs b/src/mscorlib/src/System/String.cs index f3a4d0f197..0a17c1ee24 100644 --- a/src/mscorlib/src/System/String.cs +++ b/src/mscorlib/src/System/String.cs @@ -876,7 +876,7 @@ namespace System } } - internal ref char GetFirstCharRef() + internal ref char GetRawStringData() { return ref m_firstChar; } diff --git a/src/mscorlib/src/System/Threading/Tasks/Task.cs b/src/mscorlib/src/System/Threading/Tasks/Task.cs index 8e2e6a4cb0..0a9248cba8 100644 --- a/src/mscorlib/src/System/Threading/Tasks/Task.cs +++ b/src/mscorlib/src/System/Threading/Tasks/Task.cs @@ -1467,8 +1467,7 @@ namespace System.Threading.Tasks return (flags & TASK_STATE_COMPLETED_MASK) != 0; } - // For use in InternalWait -- marginally faster than (Task.Status == TaskStatus.RanToCompletion) - internal bool IsRanToCompletion + public bool IsCompletedSuccessfully { get { return (m_stateFlags & TASK_STATE_COMPLETED_MASK) == TASK_STATE_RAN_TO_COMPLETION; } } diff --git a/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs b/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs index 848a0ecbc2..de222352c9 100644 --- a/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs +++ b/src/mscorlib/src/System/Threading/Tasks/TaskContinuation.cs @@ -320,7 +320,7 @@ namespace System.Threading.Tasks // activation criteria of the TaskContinuationOptions. TaskContinuationOptions options = m_options; bool isRightKind = - completedTask.IsRanToCompletion ? + completedTask.IsCompletedSuccessfully ? (options & TaskContinuationOptions.NotOnRanToCompletion) == 0 : (completedTask.IsCanceled ? (options & TaskContinuationOptions.NotOnCanceled) == 0 : diff --git a/src/mscorlib/src/System/Threading/Tasks/future.cs b/src/mscorlib/src/System/Threading/Tasks/future.cs index 26c2388e6b..bf9000ea00 100644 --- a/src/mscorlib/src/System/Threading/Tasks/future.cs +++ b/src/mscorlib/src/System/Threading/Tasks/future.cs @@ -374,7 +374,7 @@ namespace System.Threading.Tasks { get { - return IsRanToCompletion ? "" + m_result : SR.TaskT_DebuggerNoResult; + return IsCompletedSuccessfully ? "" + m_result : SR.TaskT_DebuggerNoResult; } } @@ -491,10 +491,10 @@ namespace System.Threading.Tasks if (waitCompletionNotification) NotifyDebuggerOfWaitCompletionIfNecessary(); // Throw an exception if appropriate. - if (!IsRanToCompletion) ThrowIfExceptional(includeTaskCanceledExceptions: true); + if (!IsCompletedSuccessfully) ThrowIfExceptional(includeTaskCanceledExceptions: true); // We shouldn't be here if the result has not been set. - Debug.Assert(IsRanToCompletion, "Task<T>.Result getter: Expected result to have been set."); + Debug.Assert(IsCompletedSuccessfully, "Task<T>.Result getter: Expected result to have been set."); return m_result; } diff --git a/src/mscorlib/src/System/ThrowHelper.cs b/src/mscorlib/src/System/ThrowHelper.cs index 4dcf8d4511..ff76738e8e 100644 --- a/src/mscorlib/src/System/ThrowHelper.cs +++ b/src/mscorlib/src/System/ThrowHelper.cs @@ -416,6 +416,7 @@ namespace System type, stateMachine, pHandle, + values } // diff --git a/src/mscorlib/src/System/TimeZoneInfo.Win32.cs b/src/mscorlib/src/System/TimeZoneInfo.Win32.cs index b6585bd0a0..4f740bd355 100644 --- a/src/mscorlib/src/System/TimeZoneInfo.Win32.cs +++ b/src/mscorlib/src/System/TimeZoneInfo.Win32.cs @@ -398,7 +398,7 @@ namespace System baseOffset = baseOffset + match.Rule.BaseUtcOffsetDelta; if (match.Rule.HasDaylightSaving) { - isDaylightSavings = GetIsDaylightSavingsFromUtc(time, timeYear, match.Offset, match.Rule, out isAmbiguousLocalDst, Local); + isDaylightSavings = GetIsDaylightSavingsFromUtc(time, timeYear, match.Offset, match.Rule, null, out isAmbiguousLocalDst, Local); baseOffset += (isDaylightSavings ? match.Rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */); } } @@ -576,8 +576,8 @@ namespace System // read LastEntry {(yearN, 1, 1) - MaxValue } // read the FirstEntry and LastEntry key values (ex: "1980", "2038") - int first = (int)dynamicKey.GetValue(FirstEntryValue, -1); - int last = (int)dynamicKey.GetValue(LastEntryValue, -1); + int first = (int)dynamicKey.GetValue(FirstEntryValue, -1, RegistryValueOptions.None); + int last = (int)dynamicKey.GetValue(LastEntryValue, -1, RegistryValueOptions.None); if (first == -1 || last == -1 || first > last) { @@ -587,7 +587,7 @@ namespace System // read the first year entry Win32Native.RegistryTimeZoneInformation dtzi; - byte[] regValue = dynamicKey.GetValue(first.ToString(CultureInfo.InvariantCulture)) as byte[]; + byte[] regValue = dynamicKey.GetValue(first.ToString(CultureInfo.InvariantCulture), null, RegistryValueOptions.None) as byte[]; if (regValue == null || regValue.Length != RegByteLength) { rules = null; @@ -620,7 +620,7 @@ namespace System // read the middle year entries for (int i = first + 1; i < last; i++) { - regValue = dynamicKey.GetValue(i.ToString(CultureInfo.InvariantCulture)) as byte[]; + regValue = dynamicKey.GetValue(i.ToString(CultureInfo.InvariantCulture), null, RegistryValueOptions.None) as byte[]; if (regValue == null || regValue.Length != RegByteLength) { rules = null; @@ -640,7 +640,7 @@ namespace System } // read the last year entry - regValue = dynamicKey.GetValue(last.ToString(CultureInfo.InvariantCulture)) as byte[]; + regValue = dynamicKey.GetValue(last.ToString(CultureInfo.InvariantCulture), null, RegistryValueOptions.None) as byte[]; dtzi = new Win32Native.RegistryTimeZoneInformation(regValue); if (regValue == null || regValue.Length != RegByteLength) { @@ -719,7 +719,7 @@ namespace System } Win32Native.RegistryTimeZoneInformation registryTimeZoneInfo; - byte[] regValue = key.GetValue(TimeZoneInfoValue) as byte[]; + byte[] regValue = key.GetValue(TimeZoneInfoValue, null, RegistryValueOptions.None) as byte[]; if (regValue == null || regValue.Length != RegByteLength) return false; registryTimeZoneInfo = new Win32Native.RegistryTimeZoneInformation(regValue); @@ -756,7 +756,7 @@ namespace System // if (result) { - string registryStandardName = key.GetValue(StandardValue, string.Empty) as string; + string registryStandardName = key.GetValue(StandardValue, string.Empty, RegistryValueOptions.None) as string; result = string.Equals(registryStandardName, timeZone.StandardName, StringComparison.Ordinal); } return result; @@ -884,9 +884,9 @@ namespace System daylightName = string.Empty; // read the MUI_ registry keys - string displayNameMuiResource = key.GetValue(MuiDisplayValue, string.Empty) as string; - string standardNameMuiResource = key.GetValue(MuiStandardValue, string.Empty) as string; - string daylightNameMuiResource = key.GetValue(MuiDaylightValue, string.Empty) as string; + string displayNameMuiResource = key.GetValue(MuiDisplayValue, string.Empty, RegistryValueOptions.None) as string; + string standardNameMuiResource = key.GetValue(MuiStandardValue, string.Empty, RegistryValueOptions.None) as string; + string daylightNameMuiResource = key.GetValue(MuiDaylightValue, string.Empty, RegistryValueOptions.None) as string; // try to load the strings from the native resource DLL(s) if (!string.IsNullOrEmpty(displayNameMuiResource)) @@ -907,15 +907,15 @@ namespace System // fallback to using the standard registry keys if (string.IsNullOrEmpty(displayName)) { - displayName = key.GetValue(DisplayValue, string.Empty) as string; + displayName = key.GetValue(DisplayValue, string.Empty, RegistryValueOptions.None) as string; } if (string.IsNullOrEmpty(standardName)) { - standardName = key.GetValue(StandardValue, string.Empty) as string; + standardName = key.GetValue(StandardValue, string.Empty, RegistryValueOptions.None) as string; } if (string.IsNullOrEmpty(daylightName)) { - daylightName = key.GetValue(DaylightValue, string.Empty) as string; + daylightName = key.GetValue(DaylightValue, string.Empty, RegistryValueOptions.None) as string; } return true; @@ -964,7 +964,7 @@ namespace System } Win32Native.RegistryTimeZoneInformation defaultTimeZoneInformation; - byte[] regValue = key.GetValue(TimeZoneInfoValue) as byte[]; + byte[] regValue = key.GetValue(TimeZoneInfoValue, null, RegistryValueOptions.None) as byte[]; if (regValue == null || regValue.Length != RegByteLength) { // the registry value could not be cast to a byte array diff --git a/src/mscorlib/src/System/TimeZoneInfo.cs b/src/mscorlib/src/System/TimeZoneInfo.cs index 29ea33a8ad..8ed7e229c0 100644 --- a/src/mscorlib/src/System/TimeZoneInfo.cs +++ b/src/mscorlib/src/System/TimeZoneInfo.cs @@ -172,10 +172,11 @@ namespace System DateTime adjustedTime = ConvertTime(dateTimeOffset, this).DateTime; bool isAmbiguous = false; - AdjustmentRule rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime); + int? ruleIndex; + AdjustmentRule rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime, out ruleIndex); if (rule != null && rule.HasDaylightSaving) { - DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule); + DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex); isAmbiguous = GetIsAmbiguousTime(adjustedTime, rule, daylightTime); } @@ -232,10 +233,11 @@ namespace System } bool isAmbiguous = false; - AdjustmentRule rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime); + int? ruleIndex; + AdjustmentRule rule = GetAdjustmentRuleForAmbiguousOffsets(adjustedTime, out ruleIndex); if (rule != null && rule.HasDaylightSaving) { - DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule); + DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex); isAmbiguous = GetIsAmbiguousTime(adjustedTime, rule, daylightTime); } @@ -263,15 +265,15 @@ namespace System } // note the time is already adjusted - private AdjustmentRule GetAdjustmentRuleForAmbiguousOffsets(DateTime adjustedTime) + private AdjustmentRule GetAdjustmentRuleForAmbiguousOffsets(DateTime adjustedTime, out int? ruleIndex) { - AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime); + AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex); if (rule != null && rule.NoDaylightTransitions && !rule.HasDaylightSaving) { // When using NoDaylightTransitions rules, each rule is only for one offset. // When looking for the Daylight savings rules, and we found the non-DST rule, // then we get the rule right before this rule. - return GetPreviousAdjustmentRule(rule); + return GetPreviousAdjustmentRule(rule, ruleIndex); } return rule; @@ -282,12 +284,23 @@ namespace System /// If the specified rule is the first AdjustmentRule, or it isn't in _adjustmentRules, /// then the specified rule is returned. /// </summary> - private AdjustmentRule GetPreviousAdjustmentRule(AdjustmentRule rule) + private AdjustmentRule GetPreviousAdjustmentRule(AdjustmentRule rule, int? ruleIndex) { + Debug.Assert(rule.NoDaylightTransitions, "GetPreviousAdjustmentRule should only be used with NoDaylightTransitions rules."); + + if (ruleIndex.HasValue && 0 < ruleIndex.Value && ruleIndex.Value < _adjustmentRules.Length) + { + return _adjustmentRules[ruleIndex.Value - 1]; + } + AdjustmentRule result = rule; for (int i = 1; i < _adjustmentRules.Length; i++) { - if (rule.Equals(_adjustmentRules[i])) + // use ReferenceEquals here instead of AdjustmentRule.Equals because + // ReferenceEquals is much faster. This is safe because all the callers + // of GetPreviousAdjustmentRule pass in a rule that was retrieved from + // _adjustmentRules. A different approach will be needed if this ever changes. + if (ReferenceEquals(rule, _adjustmentRules[i])) { result = _adjustmentRules[i - 1]; break; @@ -407,10 +420,11 @@ namespace System dateTime.Kind == DateTimeKind.Utc ? ConvertTime(dateTime, s_utcTimeZone, this, flags, cachedData) : dateTime; - AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime); + int? ruleIndex; + AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex); if (rule != null && rule.HasDaylightSaving) { - DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule); + DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex); return GetIsAmbiguousTime(adjustedTime, rule, daylightTime); } return false; @@ -492,10 +506,11 @@ namespace System // // handle the normal cases... // - AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime); + int? ruleIndex; + AdjustmentRule rule = GetAdjustmentRuleForTime(adjustedTime, out ruleIndex); if (rule != null && rule.HasDaylightSaving) { - DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule); + DaylightTimeStruct daylightTime = GetDaylightTime(adjustedTime.Year, rule, ruleIndex); return GetIsDaylightSavings(adjustedTime, rule, daylightTime, flags); } else @@ -515,11 +530,12 @@ namespace System (dateTime.Kind == DateTimeKind.Local && s_cachedData.GetCorrespondingKind(this) == DateTimeKind.Local)) { // only check Unspecified and (Local when this TimeZoneInfo instance is Local) - AdjustmentRule rule = GetAdjustmentRuleForTime(dateTime); + int? ruleIndex; + AdjustmentRule rule = GetAdjustmentRuleForTime(dateTime, out ruleIndex); if (rule != null && rule.HasDaylightSaving) { - DaylightTimeStruct daylightTime = GetDaylightTime(dateTime.Year, rule); + DaylightTimeStruct daylightTime = GetDaylightTime(dateTime.Year, rule, ruleIndex); isInvalid = GetIsInvalidTime(dateTime, rule, daylightTime); } else @@ -661,7 +677,8 @@ namespace System // performance for the normal case at the expense of the 'ArgumentException' // case and Loss-less Local special cases. // - AdjustmentRule sourceRule = sourceTimeZone.GetAdjustmentRuleForTime(dateTime); + int? sourceRuleIndex; + AdjustmentRule sourceRule = sourceTimeZone.GetAdjustmentRuleForTime(dateTime, out sourceRuleIndex); TimeSpan sourceOffset = sourceTimeZone.BaseUtcOffset; if (sourceRule != null) @@ -670,7 +687,7 @@ namespace System if (sourceRule.HasDaylightSaving) { bool sourceIsDaylightSavings = false; - DaylightTimeStruct sourceDaylightTime = sourceTimeZone.GetDaylightTime(dateTime.Year, sourceRule); + DaylightTimeStruct sourceDaylightTime = sourceTimeZone.GetDaylightTime(dateTime.Year, sourceRule, sourceRuleIndex); // 'dateTime' might be in an invalid time range since it is in an AdjustmentRule // period that supports DST @@ -1048,10 +1065,19 @@ namespace System _supportsDaylightSavingTime = (bool)info.GetValue("SupportsDaylightSavingTime", typeof(bool)); } - private AdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime, bool dateTimeisUtc = false) + private AdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime, out int? ruleIndex) + { + AdjustmentRule result = GetAdjustmentRuleForTime(dateTime, dateTimeisUtc: false, ruleIndex: out ruleIndex); + Debug.Assert(result == null || ruleIndex.HasValue, "If an AdjustmentRule was found, ruleIndex should also be set."); + + return result; + } + + private AdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime, bool dateTimeisUtc, out int? ruleIndex) { if (_adjustmentRules == null || _adjustmentRules.Length == 0) { + ruleIndex = null; return null; } @@ -1076,6 +1102,7 @@ namespace System int compareResult = CompareAdjustmentRuleToDateTime(rule, previousRule, dateTime, date, dateTimeisUtc); if (compareResult == 0) { + ruleIndex = median; return rule; } else if (compareResult < 0) @@ -1088,6 +1115,7 @@ namespace System } } + ruleIndex = null; return null; } @@ -1199,7 +1227,7 @@ namespace System /// <summary> /// Helper function that returns a DaylightTime from a year and AdjustmentRule. /// </summary> - private DaylightTimeStruct GetDaylightTime(int year, AdjustmentRule rule) + private DaylightTimeStruct GetDaylightTime(int year, AdjustmentRule rule, int? ruleIndex) { TimeSpan delta = rule.DaylightDelta; DateTime startTime; @@ -1211,7 +1239,7 @@ namespace System // Convert the UTC times into adjusted time zone times. // use the previous rule to calculate the startTime, since the DST change happens w.r.t. the previous rule - AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule); + AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule, ruleIndex); startTime = ConvertFromUtc(rule.DateStart, previousRule.DaylightDelta, previousRule.BaseUtcOffsetDelta); endTime = ConvertFromUtc(rule.DateEnd, rule.DaylightDelta, rule.BaseUtcOffsetDelta); @@ -1301,12 +1329,12 @@ namespace System /// <summary> /// Gets the offset that should be used to calculate DST start times from a UTC time. /// </summary> - private TimeSpan GetDaylightSavingsStartOffsetFromUtc(TimeSpan baseUtcOffset, AdjustmentRule rule) + private TimeSpan GetDaylightSavingsStartOffsetFromUtc(TimeSpan baseUtcOffset, AdjustmentRule rule, int? ruleIndex) { if (rule.NoDaylightTransitions) { // use the previous rule to calculate the startTime, since the DST change happens w.r.t. the previous rule - AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule); + AdjustmentRule previousRule = GetPreviousAdjustmentRule(rule, ruleIndex); return baseUtcOffset + previousRule.BaseUtcOffsetDelta + previousRule.DaylightDelta; } else @@ -1328,7 +1356,7 @@ namespace System /// Helper function that checks if a given dateTime is in Daylight Saving Time (DST). /// This function assumes the dateTime is in UTC and AdjustmentRule is in a different time zone. /// </summary> - private static bool GetIsDaylightSavingsFromUtc(DateTime time, int year, TimeSpan utc, AdjustmentRule rule, out bool isAmbiguousLocalDst, TimeZoneInfo zone) + private static bool GetIsDaylightSavingsFromUtc(DateTime time, int year, TimeSpan utc, AdjustmentRule rule, int? ruleIndex, out bool isAmbiguousLocalDst, TimeZoneInfo zone) { isAmbiguousLocalDst = false; @@ -1338,7 +1366,7 @@ namespace System } // Get the daylight changes for the year of the specified time. - DaylightTimeStruct daylightTime = zone.GetDaylightTime(year, rule); + DaylightTimeStruct daylightTime = zone.GetDaylightTime(year, rule, ruleIndex); // The start and end times represent the range of universal times that are in DST for that year. // Within that there is an ambiguous hour, usually right at the end, but at the beginning in @@ -1352,14 +1380,20 @@ namespace System // Note we handle the similar case when rule year start with daylight saving and previous year end with daylight saving. bool ignoreYearAdjustment = false; - TimeSpan dstStartOffset = zone.GetDaylightSavingsStartOffsetFromUtc(utc, rule); + TimeSpan dstStartOffset = zone.GetDaylightSavingsStartOffsetFromUtc(utc, rule, ruleIndex); DateTime startTime; if (rule.IsStartDateMarkerForBeginningOfYear() && daylightTime.Start.Year > DateTime.MinValue.Year) { - AdjustmentRule previousYearRule = zone.GetAdjustmentRuleForTime(new DateTime(daylightTime.Start.Year - 1, 12, 31)); + int? previousYearRuleIndex; + AdjustmentRule previousYearRule = zone.GetAdjustmentRuleForTime( + new DateTime(daylightTime.Start.Year - 1, 12, 31), + out previousYearRuleIndex); if (previousYearRule != null && previousYearRule.IsEndDateMarkerForEndOfYear()) { - DaylightTimeStruct previousDaylightTime = zone.GetDaylightTime(daylightTime.Start.Year - 1, previousYearRule); + DaylightTimeStruct previousDaylightTime = zone.GetDaylightTime( + daylightTime.Start.Year - 1, + previousYearRule, + previousYearRuleIndex); startTime = previousDaylightTime.Start - utc - previousYearRule.BaseUtcOffsetDelta; ignoreYearAdjustment = true; } @@ -1377,7 +1411,10 @@ namespace System DateTime endTime; if (rule.IsEndDateMarkerForEndOfYear() && daylightTime.End.Year < DateTime.MaxValue.Year) { - AdjustmentRule nextYearRule = zone.GetAdjustmentRuleForTime(new DateTime(daylightTime.End.Year + 1, 1, 1)); + int? nextYearRuleIndex; + AdjustmentRule nextYearRule = zone.GetAdjustmentRuleForTime( + new DateTime(daylightTime.End.Year + 1, 1, 1), + out nextYearRuleIndex); if (nextYearRule != null && nextYearRule.IsStartDateMarkerForBeginningOfYear()) { if (nextYearRule.IsEndDateMarkerForEndOfYear()) @@ -1387,7 +1424,10 @@ namespace System } else { - DaylightTimeStruct nextdaylightTime = zone.GetDaylightTime(daylightTime.End.Year + 1, nextYearRule); + DaylightTimeStruct nextdaylightTime = zone.GetDaylightTime( + daylightTime.End.Year + 1, + nextYearRule, + nextYearRuleIndex); endTime = nextdaylightTime.End - utc - nextYearRule.BaseUtcOffsetDelta - nextYearRule.DaylightDelta; } ignoreYearAdjustment = true; @@ -1644,14 +1684,15 @@ namespace System private static TimeSpan GetUtcOffset(DateTime time, TimeZoneInfo zone, TimeZoneInfoOptions flags) { TimeSpan baseOffset = zone.BaseUtcOffset; - AdjustmentRule rule = zone.GetAdjustmentRuleForTime(time); + int? ruleIndex; + AdjustmentRule rule = zone.GetAdjustmentRuleForTime(time, out ruleIndex); if (rule != null) { baseOffset = baseOffset + rule.BaseUtcOffsetDelta; if (rule.HasDaylightSaving) { - DaylightTimeStruct daylightTime = zone.GetDaylightTime(time.Year, rule); + DaylightTimeStruct daylightTime = zone.GetDaylightTime(time.Year, rule, ruleIndex); bool isDaylightSavings = GetIsDaylightSavings(time, rule, daylightTime, flags); baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */); } @@ -1690,21 +1731,24 @@ namespace System isAmbiguousLocalDst = false; TimeSpan baseOffset = zone.BaseUtcOffset; int year; + int? ruleIndex; AdjustmentRule rule; if (time > s_maxDateOnly) { - rule = zone.GetAdjustmentRuleForTime(DateTime.MaxValue); + rule = zone.GetAdjustmentRuleForTime(DateTime.MaxValue, out ruleIndex); year = 9999; } else if (time < s_minDateOnly) { - rule = zone.GetAdjustmentRuleForTime(DateTime.MinValue); + rule = zone.GetAdjustmentRuleForTime(DateTime.MinValue, out ruleIndex); year = 1; } else { - rule = zone.GetAdjustmentRuleForTime(time, dateTimeisUtc: true); + rule = zone.GetAdjustmentRuleForTime(time, dateTimeisUtc: true, ruleIndex: out ruleIndex); + Debug.Assert(rule == null || ruleIndex.HasValue, + "If GetAdjustmentRuleForTime returned an AdjustmentRule, ruleIndex should also be set."); // As we get the associated rule using the adjusted targetTime, we should use the adjusted year (targetTime.Year) too as after adding the baseOffset, // sometimes the year value can change if the input datetime was very close to the beginning or the end of the year. Examples of such cases: @@ -1719,7 +1763,7 @@ namespace System baseOffset = baseOffset + rule.BaseUtcOffsetDelta; if (rule.HasDaylightSaving) { - isDaylightSavings = GetIsDaylightSavingsFromUtc(time, year, zone._baseUtcOffset, rule, out isAmbiguousLocalDst, zone); + isDaylightSavings = GetIsDaylightSavingsFromUtc(time, year, zone._baseUtcOffset, rule, ruleIndex, out isAmbiguousLocalDst, zone); baseOffset += (isDaylightSavings ? rule.DaylightDelta : TimeSpan.Zero /* FUTURE: rule.StandardDelta */); } } |