diff --git a/src/Compilers/Core/Portable/CodeAnalysis.csproj b/src/Compilers/Core/Portable/CodeAnalysis.csproj index 931a9d78a3f4b..51197ada515c7 100644 --- a/src/Compilers/Core/Portable/CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/CodeAnalysis.csproj @@ -51,6 +51,7 @@ + @@ -110,7 +111,7 @@ - + @@ -819,4 +820,4 @@ - + \ No newline at end of file diff --git a/src/Compilers/Core/Portable/CryptographicHashProvider.cs b/src/Compilers/Core/Portable/CryptographicHashProvider.cs index 2844a1757cc6a..3515b5e520254 100644 --- a/src/Compilers/Core/Portable/CryptographicHashProvider.cs +++ b/src/Compilers/Core/Portable/CryptographicHashProvider.cs @@ -169,9 +169,10 @@ internal static ImmutableArray ComputeSha1(byte[] bytes) internal static ImmutableArray ComputeSha1(BlobBuilder bytes) { - using (var hashProvider = new SHA1CryptoServiceProvider()) + using (var incrementalHash = IncrementalHash.Create(AssemblyHashAlgorithm.Sha1)) { - return ImmutableArray.Create(hashProvider.ComputeHash(bytes)); + incrementalHash.AppendData(bytes); + return ImmutableArray.Create(incrementalHash.GetHashAndReset()); } } } diff --git a/src/Compilers/Core/Portable/InternalUtilities/HashAlgorithmExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/HashAlgorithmExtensions.cs deleted file mode 100644 index abc13c0142aa4..0000000000000 --- a/src/Compilers/Core/Portable/InternalUtilities/HashAlgorithmExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Diagnostics; -using System.Reflection; - -namespace Roslyn.Utilities -{ - using Roslyn.Reflection; - - internal static class HashAlgorithmExtensions - { - internal static byte[] ComputeHash(this HashAlgorithm algorithm, BlobBuilder builder) - { - int remaining = builder.Count; - foreach (var blob in builder.GetBlobs()) - { - var segment = blob.GetBytes(); - remaining -= segment.Count; - if (remaining == 0) - { - algorithm.TransformFinalBlock(segment.Array, segment.Offset, segment.Count); - } - else - { - algorithm.TransformBlock(segment.Array, segment.Offset, segment.Count); - } - } - - Debug.Assert(remaining == 0); - return algorithm.Hash; - } - } -} diff --git a/src/Compilers/Core/Portable/InternalUtilities/HashAlgorithms.cs b/src/Compilers/Core/Portable/InternalUtilities/HashAlgorithms.cs index 5d1f5659288a4..979ebe7fc6ced 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/HashAlgorithms.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/HashAlgorithms.cs @@ -12,18 +12,6 @@ namespace Roslyn.Utilities internal abstract class HashAlgorithm : IDisposable { - private static readonly MethodInfo s_transformBlock = PortableShim.HashAlgorithm.Type - .GetTypeInfo() - .GetDeclaredMethod(nameof(TransformBlock), new[] { typeof(byte[]), typeof(int), typeof(int), typeof(byte[]), typeof(int) }); - - private static readonly MethodInfo s_transformFinalBlock = PortableShim.HashAlgorithm.Type - .GetTypeInfo() - .GetDeclaredMethod(nameof(TransformFinalBlock), new[] { typeof(byte[]), typeof(int), typeof(int) }); - - private static readonly PropertyInfo s_hash = PortableShim.HashAlgorithm.Type - .GetTypeInfo() - .GetDeclaredProperty(nameof(Hash)); - private readonly IDisposable _hashInstance; protected HashAlgorithm(IDisposable hashInstance) @@ -46,32 +34,6 @@ public byte[] ComputeHash(Stream inputStream) return PortableShim.HashAlgorithm.ComputeHash(_hashInstance, inputStream); } - public bool SupportsTransform => - s_transformBlock != null && - s_transformFinalBlock != null && - s_hash != null; - - /// - /// Invoke the underlying HashAlgorithm's TransformBlock operation on the provided data. - /// - public void TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount) - { - while (inputCount > 0) - { - int written = (int)s_transformBlock.Invoke(_hashInstance, new object[] { inputBuffer, inputOffset, inputCount, inputBuffer, inputOffset }); - Debug.Assert(inputCount == written); // does the TransformBlock method always consume the complete data given to it? - inputCount -= written; - inputOffset += written; - } - } - - public void TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) - { - s_transformFinalBlock.Invoke(_hashInstance, new object[] { inputBuffer, inputOffset, inputCount }); - } - - public byte[] Hash => (byte[])s_hash.GetMethod.Invoke(_hashInstance, new object[] { }); - public void Dispose() { _hashInstance.Dispose(); diff --git a/src/Compilers/Core/Portable/InternalUtilities/IncrementalHash.cs b/src/Compilers/Core/Portable/InternalUtilities/IncrementalHash.cs new file mode 100644 index 0000000000000..63144c138329b --- /dev/null +++ b/src/Compilers/Core/Portable/InternalUtilities/IncrementalHash.cs @@ -0,0 +1,151 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Reflection; +using Microsoft.CodeAnalysis; + +namespace Roslyn.Utilities +{ + /// + /// Implementation of System.Security.Cryptography.IncrementalHash that works accross Desktop and Core CLR. + /// Remove once we can use the real one from netstandard1.3. + /// + internal abstract class IncrementalHash : IDisposable + { + /// + /// Append the entire contents of to the data already processed in the hash or HMAC. + /// + /// The data to process. + public void AppendData(byte[] data) => AppendData(data, 0, data.Length); + + /// + /// Append bytes of , starting at , + /// to the data already processed in the hash. + /// + /// The data to process. + /// The offset into the byte array from which to begin using data. + /// The number of bytes in the array to use as data. + public abstract void AppendData(byte[] data, int offset, int count); + + /// + /// Retrieve the hash for the data accumulated from prior calls to + /// , and return to the state the object + /// was in at construction. + /// + public abstract byte[] GetHashAndReset(); + + public abstract void Dispose(); + + public static IncrementalHash Create(AssemblyHashAlgorithm hashAlgorithm) + { + if (PortableShim.IncrementalHash.TypeOpt != null) + { + return new Core(hashAlgorithm); + } + else + { + return new Desktop(hashAlgorithm); + } + } + + /// + /// CoreCLR implementation. + /// + private sealed class Core : IncrementalHash + { + // IncrementalHash + private readonly IDisposable _incrementalHashImpl; + + internal Core(AssemblyHashAlgorithm hashAlgorithm) + { + var name = GetHashAlgorithmNameObj(hashAlgorithm); + _incrementalHashImpl = PortableShim.IncrementalHash.CreateHash(name); + } + + /// + /// Returns the actual FX implementation of HashAlgorithmName for given hash algorithm id. + /// + private static object GetHashAlgorithmNameObj(AssemblyHashAlgorithm algorithmId) + { + switch (algorithmId) + { + case AssemblyHashAlgorithm.Sha1: + return PortableShim.HashAlgorithmName.SHA1; + + default: + // More algorithms can be added as needed. + throw ExceptionUtilities.UnexpectedValue(algorithmId); + } + } + + public override void AppendData(byte[] data, int offset, int count) => PortableShim.IncrementalHash.AppendData(_incrementalHashImpl, data, offset, count); + public override byte[] GetHashAndReset() => PortableShim.IncrementalHash.GetHashAndReset(_incrementalHashImpl); + public override void Dispose() => _incrementalHashImpl.Dispose(); + } + + /// + /// Desktop implementation. + /// + private sealed class Desktop : IncrementalHash + { + // HashAlgorithm + private readonly IDisposable _hashAlgorithmImpl; + + internal Desktop(AssemblyHashAlgorithm hashAlgorithm) + { + _hashAlgorithmImpl = GetAlgorithmImpl(hashAlgorithm); + } + + /// + /// Returns the actual FX implementation of HashAlgorithm. + /// + private static IDisposable GetAlgorithmImpl(AssemblyHashAlgorithm algorithmId) + { + switch (algorithmId) + { + case AssemblyHashAlgorithm.None: + case AssemblyHashAlgorithm.Sha1: + return PortableShim.SHA1.Create(); + + case AssemblyHashAlgorithm.Sha256: + return PortableShim.SHA256.Create(); + + case AssemblyHashAlgorithm.Sha384: + return PortableShim.SHA384.Create(); + + case AssemblyHashAlgorithm.Sha512: + return PortableShim.SHA512.Create(); + + case AssemblyHashAlgorithm.MD5: + return PortableShim.MD5.Create(); + + default: + throw ExceptionUtilities.UnexpectedValue(algorithmId); + } + } + + public override void AppendData(byte[] data, int offset, int count) + { + while (count > 0) + { + int written = PortableShim.HashAlgorithm.TransformBlock(_hashAlgorithmImpl, data, offset, count, data, offset); + Debug.Assert(count == written); // does the TransformBlock method always consume the complete data given to it? + count -= written; + offset += written; + } + } + + public override byte[] GetHashAndReset() + { + PortableShim.HashAlgorithm.TransformFinalBlock(_hashAlgorithmImpl, SpecializedCollections.EmptyBytes, 0, 0); + return PortableShim.HashAlgorithm.Hash(_hashAlgorithmImpl); + } + + public override void Dispose() + { + _hashAlgorithmImpl.Dispose(); + } + } + } +} diff --git a/src/Compilers/Core/Portable/InternalUtilities/IncrementalHashExtensions.cs b/src/Compilers/Core/Portable/InternalUtilities/IncrementalHashExtensions.cs new file mode 100644 index 0000000000000..094101c5d2008 --- /dev/null +++ b/src/Compilers/Core/Portable/InternalUtilities/IncrementalHashExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Roslyn.Reflection; + +namespace Roslyn.Utilities +{ + internal static class IncrementalHashExtensions + { + internal static void AppendData(this IncrementalHash hash, BlobBuilder builder) + { + foreach (var blob in builder.GetBlobs()) + { + hash.AppendData(blob.GetBytes()); + } + } + + internal static void AppendData(this IncrementalHash hash, ArraySegment segment) + { + hash.AppendData(segment.Array, segment.Offset, segment.Count); + } + } +} diff --git a/src/Compilers/Core/Portable/InternalUtilities/ReflectionUtilities.cs b/src/Compilers/Core/Portable/InternalUtilities/ReflectionUtilities.cs index a631c962a27fb..797fd111bb31b 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/ReflectionUtilities.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/ReflectionUtilities.cs @@ -10,6 +10,8 @@ namespace Roslyn.Utilities { internal static class ReflectionUtilities { + private static readonly Type Missing = typeof(void); + public static Type TryGetType(string assemblyQualifiedName) { try @@ -23,6 +25,16 @@ public static Type TryGetType(string assemblyQualifiedName) } } + public static Type TryGetType(ref Type lazyType, string assemblyQualifiedName) + { + if (lazyType == null) + { + lazyType = TryGetType(assemblyQualifiedName) ?? Missing; + } + + return (lazyType == Missing) ? null : lazyType; + } + /// /// Find a instance by first probing the contract name and then the name as it /// would exist in mscorlib. This helps satisfy both the CoreCLR and Desktop scenarios. @@ -39,6 +51,16 @@ public static Type GetTypeFromEither(string contractName, string desktopName) return type; } + public static Type GetTypeFromEither(ref Type lazyType, string contractName, string desktopName) + { + if (lazyType == null) + { + lazyType = GetTypeFromEither(contractName, desktopName) ?? Missing; + } + + return (lazyType == Missing) ? null : lazyType; + } + public static T FindItem(IEnumerable collection, params Type[] paramTypes) where T : MethodBase { diff --git a/src/Compilers/Core/Portable/NativePdbWriter/PdbWriter.cs b/src/Compilers/Core/Portable/NativePdbWriter/PdbWriter.cs index 925de8ae460e1..6fc3ea9c9b21a 100644 --- a/src/Compilers/Core/Portable/NativePdbWriter/PdbWriter.cs +++ b/src/Compilers/Core/Portable/NativePdbWriter/PdbWriter.cs @@ -55,7 +55,7 @@ internal sealed class PdbLogger private readonly bool _logging; private readonly BlobBuilder _logData; private const int bufferFlushLimit = 64 * 1024; - private readonly HashAlgorithm _hashAlgorithm; + private readonly IncrementalHash _incrementalHash; internal PdbLogger(bool logging) { @@ -67,13 +67,12 @@ internal PdbLogger(bool logging) // and we need just one per compile session // pooling will be couter-productive in such scenario _logData = new BlobBuilder(bufferFlushLimit); - _hashAlgorithm = new SHA1CryptoServiceProvider(); - Debug.Assert(_hashAlgorithm.SupportsTransform); + _incrementalHash = IncrementalHash.Create(AssemblyHashAlgorithm.Sha1); } else { _logData = null; - _hashAlgorithm = null; + _incrementalHash = null; } } @@ -83,12 +82,7 @@ private void EnsureSpace(int space) // that should be very rare though. if (_logData.Count + space >= bufferFlushLimit) { - foreach (var blob in _logData.GetBlobs()) - { - var segment = blob.GetBytes(); - _hashAlgorithm.TransformBlock(segment.Array, segment.Offset, segment.Count); - } - + _incrementalHash.AppendData(_logData); _logData.Clear(); } } @@ -97,14 +91,15 @@ internal byte[] GetLogHash() { Debug.Assert(_logData != null); - var hash = _hashAlgorithm.ComputeHash(_logData); + _incrementalHash.AppendData(_logData); _logData.Clear(); - return hash; + + return _incrementalHash.GetHashAndReset(); } internal void Close() { - _hashAlgorithm?.Dispose(); + _incrementalHash?.Dispose(); } internal enum PdbWriterOperation : byte diff --git a/src/Compilers/Core/Portable/PortableShim.cs b/src/Compilers/Core/Portable/PortableShim.cs index 6305bcf61ea94..4000201058e59 100644 --- a/src/Compilers/Core/Portable/PortableShim.cs +++ b/src/Compilers/Core/Portable/PortableShim.cs @@ -60,7 +60,6 @@ internal static void Initialize() Touch(StackTrace.Type); Touch(Thread.Type); Touch(XPath.Extensions.Type); - Touch(HashAlgorithm.Type); Touch(SHA1.Type); Touch(SHA256.Type); Touch(SHA512.Type); @@ -557,38 +556,131 @@ internal static class HashAlgorithm { private const string TypeName = "System.Security.Cryptography.HashAlgorithm"; - internal static readonly Type Type = ReflectionUtilities.GetTypeFromEither( - contractName: $"{TypeName}, {CoreNames.System_Security_Cryptography_Primitives}", + private static Type s_lazyType; + + internal static Type TypeOpt => ReflectionUtilities.GetTypeFromEither(ref s_lazyType, + contractName: TypeName + ", " + CoreNames.System_Security_Cryptography_Primitives, desktopName: TypeName); - private static readonly MethodInfo s_computeHash_byte = Type - .GetTypeInfo() - .GetDeclaredMethod(nameof(ComputeHash), new[] { typeof(byte[]) }); + private static MethodInfo s_lazyTransformBlock, s_lazyTransformFinalBlock, s_lazyComputeHashByte, s_lazyComputeHashByteIntInt, s_lazyComputeHashStream; + private static PropertyInfo s_lazyHash; + + internal static int TransformBlock(object hashAlgorithm, byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) + { + if (s_lazyTransformBlock == null) + { + s_lazyTransformBlock = TypeOpt.GetTypeInfo().GetDeclaredMethod(nameof(TransformBlock), new[] { typeof(byte[]), typeof(int), typeof(int), typeof(byte[]), typeof(int) }); + } - private static readonly MethodInfo s_computeHash_byte_int_int = Type - .GetTypeInfo() - .GetDeclaredMethod(nameof(ComputeHash), new[] { typeof(byte[]), typeof(int), typeof(int) }); + return (int)s_lazyTransformBlock.Invoke(hashAlgorithm, new object[] { inputBuffer, inputOffset, inputCount, inputBuffer, inputOffset }); + } - private static readonly MethodInfo s_computeHash_stream = Type - .GetTypeInfo() - .GetDeclaredMethod(nameof(ComputeHash), new[] { typeof(Stream) }); + internal static byte[] TransformFinalBlock(object hashAlgorithm, byte[] inputBuffer, int inputOffset, int inputCount) + { + if (s_lazyTransformFinalBlock == null) + { + s_lazyTransformFinalBlock = TypeOpt.GetTypeInfo().GetDeclaredMethod(nameof(TransformFinalBlock), new[] { typeof(byte[]), typeof(int), typeof(int) }); + } + + return (byte[])s_lazyTransformFinalBlock.Invoke(hashAlgorithm, new object[] { inputBuffer, inputOffset, inputCount }); + } + + internal static byte[] Hash(object hashAlgorithm) + { + if (s_lazyHash == null) + { + s_lazyHash = TypeOpt.GetTypeInfo().GetDeclaredProperty(nameof(Hash)); + } + + return (byte[])s_lazyHash.GetValue(hashAlgorithm); + } internal static byte[] ComputeHash(object hashInstance, byte[] buffer) { - return (byte[])s_computeHash_byte.Invoke(hashInstance, new object[] { buffer }); + if (s_lazyComputeHashByte == null) + { + s_lazyComputeHashByte = TypeOpt.GetTypeInfo().GetDeclaredMethod(nameof(ComputeHash), new[] { typeof(byte[]) }); + } + + return (byte[])s_lazyComputeHashByte.Invoke(hashInstance, new object[] { buffer }); } internal static byte[] ComputeHash(object hashInstance, byte[] buffer, int offset, int count) { - return (byte[])s_computeHash_byte_int_int.Invoke(hashInstance, new object[] { buffer, offset, count }); + if (s_lazyComputeHashByteIntInt == null) + { + s_lazyComputeHashByteIntInt = TypeOpt.GetTypeInfo().GetDeclaredMethod(nameof(ComputeHash), new[] { typeof(byte[]), typeof(int), typeof(int) }); + } + + return (byte[])s_lazyComputeHashByteIntInt.Invoke(hashInstance, new object[] { buffer, offset, count }); } internal static byte[] ComputeHash(object hashInstance, Stream inputStream) { - return (byte[])s_computeHash_stream.Invoke(hashInstance, new object[] { inputStream }); + if (s_lazyComputeHashStream == null) + { + s_lazyComputeHashStream = TypeOpt.GetTypeInfo().GetDeclaredMethod(nameof(ComputeHash), new[] { typeof(Stream) }); + } + + return (byte[])s_lazyComputeHashStream.Invoke(hashInstance, new object[] { inputStream }); + } + } + + internal static class IncrementalHash + { + private const string TypeName = "System.Security.Cryptography.IncrementalHash"; + + private static Type s_lazyType; + private static MethodInfo s_lazyCreateHash, s_lazyAppendData, s_lazyHashAndReset; + + internal static Type TypeOpt => ReflectionUtilities.TryGetType(ref s_lazyType, + TypeName + ", " + CoreNames.System_Security_Cryptography_Algorithms); + + internal static IDisposable CreateHash(object hashAlgorithmName) + { + if (s_lazyCreateHash == null) + { + s_lazyCreateHash = TypeOpt.GetTypeInfo().GetDeclaredMethod(nameof(CreateHash), new[] { HashAlgorithmName.TypeOpt }); + } + + return (IDisposable)s_lazyCreateHash.Invoke(null, new[] { hashAlgorithmName }); + } + + internal static void AppendData(object incrementalHash, byte[] data, int offset, int count) + { + if (s_lazyAppendData == null) + { + s_lazyAppendData = TypeOpt.GetTypeInfo().GetDeclaredMethod(nameof(AppendData), new[] { typeof(byte[]), typeof(int), typeof(int) }); + } + + s_lazyAppendData.Invoke(incrementalHash, new object[] { data, offset, count }); + } + + internal static byte[] GetHashAndReset(object incrementalHash) + { + if (s_lazyHashAndReset == null) + { + s_lazyHashAndReset = TypeOpt.GetTypeInfo().GetDeclaredMethod(nameof(GetHashAndReset), new Type[0]); + } + + return (byte[])s_lazyHashAndReset.Invoke(incrementalHash, new object[0]); } } + internal static class HashAlgorithmName + { + private const string TypeName = "System.Security.Cryptography.HashAlgorithmName"; + + private static Type s_lazyType; + private static object s_lazySHA1; + + internal static Type TypeOpt => ReflectionUtilities.GetTypeFromEither(ref s_lazyType, + contractName: TypeName + ", " + CoreNames.System_Security_Cryptography_Primitives, + desktopName: TypeName); + + internal static object SHA1 => s_lazySHA1 ?? (s_lazySHA1 = TypeOpt.GetTypeInfo().GetDeclaredProperty("SHA1").GetValue(null)); + } + internal static class SHA1 { private const string TypeName = "System.Security.Cryptography.SHA1";