diff options
Diffstat (limited to 'src/vm/comutilnative.cpp')
-rw-r--r-- | src/vm/comutilnative.cpp | 3282 |
1 files changed, 3282 insertions, 0 deletions
diff --git a/src/vm/comutilnative.cpp b/src/vm/comutilnative.cpp new file mode 100644 index 0000000000..b55c63549d --- /dev/null +++ b/src/vm/comutilnative.cpp @@ -0,0 +1,3282 @@ +// 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. +// + +// + +/*============================================================ +** +** File: COMUtilNative +** +** +** +** Purpose: A dumping ground for classes which aren't large +** enough to get their own file in the EE. +** +** +** +===========================================================*/ +#include "common.h" +#include "object.h" +#include "excep.h" +#include "vars.hpp" +#include "comutilnative.h" + +#include "utilcode.h" +#include "frames.h" +#include "field.h" +#include "winwrap.h" +#include "gc.h" +#include "fcall.h" +#include "invokeutil.h" +#include "eeconfig.h" +#include "typestring.h" +#include "sha1.h" +#include "finalizerthread.h" + +#ifdef FEATURE_COMINTEROP + #include "comcallablewrapper.h" + #include "comcache.h" +#endif // FEATURE_COMINTEROP + +#define STACK_OVERFLOW_MESSAGE W("StackOverflowException") + +//These are defined in System.ParseNumbers and should be kept in sync. +#define PARSE_TREATASUNSIGNED 0x200 +#define PARSE_TREATASI1 0x400 +#define PARSE_TREATASI2 0x800 +#define PARSE_ISTIGHT 0x1000 +#define PARSE_NOSPACE 0x2000 + + +// +// +// PARSENUMBERS (and helper functions) +// +// + +/*===================================IsDigit==================================== +**Returns a bool indicating whether the character passed in represents a ** +**digit. +==============================================================================*/ +bool IsDigit(WCHAR c, int radix, int *result) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(CheckPointer(result)); + } + CONTRACTL_END; + + if (IS_DIGIT(c)) { + *result = DIGIT_TO_INT(c); + } + else if (c>='A' && c<='Z') { + //+10 is necessary because A is actually 10, etc. + *result = c-'A'+10; + } + else if (c>='a' && c<='z') { + //+10 is necessary because a is actually 10, etc. + *result = c-'a'+10; + } + else { + *result = -1; + } + + if ((*result >=0) && (*result < radix)) + return true; + + return false; +} + +INT32 wtoi(__in_ecount(length) WCHAR* wstr, DWORD length) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(CheckPointer(wstr)); + PRECONDITION(length >= 0); + } + CONTRACTL_END; + + DWORD i = 0; + int value; + INT32 result = 0; + + while ( (i < length) && (IsDigit(wstr[i], 10 ,&value)) ) { + //Read all of the digits and convert to a number + result = result*10 + value; + i++; + } + + return result; +} + +INT32 ParseNumbers::GrabInts(const INT32 radix, __in_ecount(length) WCHAR *buffer, const int length, int *i, BOOL isUnsigned) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(buffer)); + PRECONDITION(CheckPointer(i)); + PRECONDITION(*i >= 0); + PRECONDITION(length >= 0); + PRECONDITION( radix==2 || radix==8 || radix==10 || radix==16 ); + } + CONTRACTL_END; + + UINT32 result=0; + int value; + UINT32 maxVal; + + // Allow all non-decimal numbers to set the sign bit. + if (radix==10 && !isUnsigned) { + maxVal = (0x7FFFFFFF / 10); + + //Read all of the digits and convert to a number + while (*i<length&&(IsDigit(buffer[*i],radix,&value))) { + // Check for overflows - this is sufficient & correct. + if (result > maxVal || ((INT32)result)<0) + COMPlusThrow(kOverflowException, W("Overflow_Int32")); + result = result*radix + value; + (*i)++; + } + if ((INT32)result<0 && result!=0x80000000) + COMPlusThrow(kOverflowException, W("Overflow_Int32")); + + } + else { + maxVal = ((UINT32) -1) / radix; + + //Read all of the digits and convert to a number + while (*i<length&&(IsDigit(buffer[*i],radix,&value))) { + // Check for overflows - this is sufficient & correct. + if (result > maxVal) + COMPlusThrow(kOverflowException, W("Overflow_UInt32")); + // the above check won't cover 4294967296 to 4294967299 + UINT32 temp = result*radix + value; + if( temp < result) { // this means overflow as well + COMPlusThrow(kOverflowException, W("Overflow_UInt32")); + } + + result = temp; + (*i)++; + } + } + return(INT32) result; +} + +INT64 ParseNumbers::GrabLongs(const INT32 radix, __in_ecount(length) WCHAR *buffer, const int length, int *i, BOOL isUnsigned) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_COOPERATIVE; + PRECONDITION(CheckPointer(buffer)); + PRECONDITION(CheckPointer(i)); + PRECONDITION(*i >= 0); + PRECONDITION(length >= 0); + } + CONTRACTL_END; + + UINT64 result=0; + int value; + UINT64 maxVal; + + // Allow all non-decimal numbers to set the sign bit. + if (radix==10 && !isUnsigned) { + maxVal = (UI64(0x7FFFFFFFFFFFFFFF) / 10); + + //Read all of the digits and convert to a number + while (*i<length&&(IsDigit(buffer[*i],radix,&value))) { + // Check for overflows - this is sufficient & correct. + if (result > maxVal || ((INT64)result)<0) + COMPlusThrow(kOverflowException, W("Overflow_Int64")); + result = result*radix + value; + (*i)++; + } + if ((INT64)result<0 && result!=UI64(0x8000000000000000)) + COMPlusThrow(kOverflowException, W("Overflow_Int64")); + + } + else { + maxVal = ((UINT64) -1L) / radix; + + //Read all of the digits and convert to a number + while (*i<length&&(IsDigit(buffer[*i],radix,&value))) { + // Check for overflows - this is sufficient & correct. + if (result > maxVal) + COMPlusThrow(kOverflowException, W("Overflow_UInt64")); + + UINT64 temp = result*radix + value; + if( temp < result) { // this means overflow as well + COMPlusThrow(kOverflowException, W("Overflow_UInt64")); + } + result = temp; + + (*i)++; + } + } + return(INT64) result; +} + +void EatWhiteSpace(__in_ecount(length) WCHAR *buffer, int length, int *i) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + PRECONDITION(CheckPointer(buffer)); + PRECONDITION(CheckPointer(i)); + PRECONDITION(length >= 0); + } + CONTRACTL_END; + + for (; *i<length && COMCharacter::nativeIsWhiteSpace(buffer[*i]); (*i)++); +} + +FCIMPL5_VII(LPVOID, ParseNumbers::LongToString, INT64 n, INT32 radix, INT32 width, CLR_CHAR paddingChar, INT32 flags) +{ + FCALL_CONTRACT; + + LPVOID rv = NULL; + + HELPER_METHOD_FRAME_BEGIN_RET_0(); + + bool isNegative = false; + int index=0; + int charVal; + UINT64 l; + INT32 i; + INT32 buffLength=0; + WCHAR buffer[67];//Longest possible string length for an integer in binary notation with prefix + + if (radix<MinRadix || radix>MaxRadix) + COMPlusThrowArgumentException(W("radix"), W("Arg_InvalidBase")); + + //If the number is negative, make it positive and remember the sign. + if (n<0) { + isNegative=true; + + // For base 10, write out -num, but other bases write out the + // 2's complement bit pattern + if (10==radix) + l = (UINT64)(-n); + else + l = (UINT64)n; + } + else { + l=(UINT64)n; + } + + if (flags&PrintAsI1) + l = l&0xFF; + else if (flags&PrintAsI2) + l = l&0xFFFF; + else if (flags&PrintAsI4) + l=l&0xFFFFFFFF; + + //Special case the 0. + if (0==l) { + buffer[0]='0'; + index=1; + } + else { + //Pull apart the number and put the digits (in reverse order) into the buffer. + for (index=0; l>0; l=l/radix, index++) { + if ((charVal=(int)(l%radix))<10) + buffer[index] = (WCHAR)(charVal + '0'); + else + buffer[index] = (WCHAR)(charVal + 'a' - 10); + } + } + + //If they want the base, append that to the string (in reverse order) + if (radix!=10 && ((flags&PrintBase)!=0)) { + if (16==radix) { + buffer[index++]='x'; + buffer[index++]='0'; + } + else if (8==radix) { + buffer[index++]='0'; + } + else if ((flags&PrintRadixBase)!=0) { + buffer[index++]='#'; + buffer[index++]=((radix%10)+'0'); + buffer[index++]=((static_cast<char>(radix)/10)+'0'); + } + } + + if (10==radix) { + //If it was negative, append the sign. + if (isNegative) { + buffer[index++]='-'; + } + + //else if they requested, add the '+'; + else if ((flags&PrintSign)!=0) { + buffer[index++]='+'; + } + + //If they requested a leading space, put it on. + else if ((flags&PrefixSpace)!=0) { + buffer[index++]=' '; + } + } + + //Figure out the size of our string. + if (width<=index) + buffLength=index; + else + buffLength=width; + + STRINGREF Local = StringObject::NewString(buffLength); + WCHAR *LocalBuffer = Local->GetBuffer(); + + //Put the characters into the String in reverse order + //Fill the remaining space -- if there is any -- + //with the correct padding character. + if ((flags&LeftAlign)!=0) { + for (i=0; i<index; i++) { + LocalBuffer[i]=buffer[index-i-1]; + } + for (;i<buffLength; i++) { + LocalBuffer[i]=paddingChar; + } + } + else { + for (i=0; i<index; i++) { + LocalBuffer[buffLength-i-1]=buffer[i]; + } + for (int j=buffLength-i-1; j>=0; j--) { + LocalBuffer[j]=paddingChar; + } + } + + *((STRINGREF *)&rv)=Local; + + HELPER_METHOD_FRAME_END(); + + return rv; +} +FCIMPLEND + + +FCIMPL5(LPVOID, ParseNumbers::IntToString, INT32 n, INT32 radix, INT32 width, CLR_CHAR paddingChar, INT32 flags) +{ + FCALL_CONTRACT; + + LPVOID rv = NULL; + + HELPER_METHOD_FRAME_BEGIN_RET_0(); + + bool isNegative = false; + int index=0; + int charVal; + int buffLength; + int i; + UINT32 l; + WCHAR buffer[66]; //Longest possible string length for an integer in binary notation with prefix + + if (radix<MinRadix || radix>MaxRadix) + COMPlusThrowArgumentException(W("radix"), W("Arg_InvalidBase")); + + //If the number is negative, make it positive and remember the sign. + //If the number is MIN_VALUE, this will still be negative, so we'll have to + //special case this later. + if (n<0) { + isNegative=true; + // For base 10, write out -num, but other bases write out the + // 2's complement bit pattern + if (10==radix) + l = (UINT32)(-n); + else + l = (UINT32)n; + } + else { + l=(UINT32)n; + } + + //The conversion to a UINT will sign extend the number. In order to ensure + //that we only get as many bits as we expect, we chop the number. + if (flags&PrintAsI1) { + l = l&0xFF; + } + else if (flags&PrintAsI2) { + l = l&0xFFFF; + } + else if (flags&PrintAsI4) { + l=l&0xFFFFFFFF; + } + + //Special case the 0. + if (0==l) { + buffer[0]='0'; + index=1; + } + else { + do { + charVal = l%radix; + l=l/radix; + if (charVal<10) { + buffer[index++] = (WCHAR)(charVal + '0'); + } + else { + buffer[index++] = (WCHAR)(charVal + 'a' - 10); + } + } + while (l!=0); + } + + //If they want the base, append that to the string (in reverse order) + if (radix!=10 && ((flags&PrintBase)!=0)) { + if (16==radix) { + buffer[index++]='x'; + buffer[index++]='0'; + } + else if (8==radix) { + buffer[index++]='0'; + } + } + + if (10==radix) { + //If it was negative, append the sign. + if (isNegative) { + buffer[index++]='-'; + } + + //else if they requested, add the '+'; + else if ((flags&PrintSign)!=0) { + buffer[index++]='+'; + } + + //If they requested a leading space, put it on. + else if ((flags&PrefixSpace)!=0) { + buffer[index++]=' '; + } + } + + //Figure out the size of our string. + if (width<=index) { + buffLength=index; + } + else { + buffLength=width; + } + + STRINGREF Local = StringObject::NewString(buffLength); + WCHAR *LocalBuffer = Local->GetBuffer(); + + //Put the characters into the String in reverse order + //Fill the remaining space -- if there is any -- + //with the correct padding character. + if ((flags&LeftAlign)!=0) { + for (i=0; i<index; i++) { + LocalBuffer[i]=buffer[index-i-1]; + } + for (;i<buffLength; i++) { + LocalBuffer[i]=paddingChar; + } + } + else { + for (i=0; i<index; i++) { + LocalBuffer[buffLength-i-1]=buffer[i]; + } + for (int j=buffLength-i-1; j>=0; j--) { + LocalBuffer[j]=paddingChar; + } + } + + *((STRINGREF *)&rv)=Local; + + HELPER_METHOD_FRAME_END(); + + return rv; +} +FCIMPLEND + + +/*===================================FixRadix=================================== +**It's possible that we parsed the radix in a base other than 10 by accident. +**This method will take that number, verify that it only contained valid base 10 +**digits, and then do the conversion to base 10. If it contained invalid digits, +**they tried to pass us a radix such as 1A, so we throw a FormatException. +** +**Args: oldVal: The value that we had actually parsed in some arbitrary base. +** oldBase: The base in which we actually did the parsing. +** +**Returns: oldVal as if it had been parsed as a base-10 number. +**Exceptions: FormatException if either of the digits in the radix aren't +** valid base-10 numbers. +==============================================================================*/ +int FixRadix(int oldVal, int oldBase) +{ + CONTRACTL + { + THROWS; + GC_NOTRIGGER; + MODE_COOPERATIVE; + } + CONTRACTL_END; + + int firstDigit = (oldVal/oldBase); + int secondDigit = (oldVal%oldBase); + + if ((firstDigit>=10) || (secondDigit>=10)) + COMPlusThrow(kFormatException, W("Format_BadBase")); + + return(firstDigit*10)+secondDigit; +} + +/*=================================StringToLong================================= +**Action: +**Returns: +**Exceptions: +==============================================================================*/ +FCIMPL4(INT64, ParseNumbers::StringToLong, StringObject * s, INT32 radix, INT32 flags, INT32 *currPos) +{ + FCALL_CONTRACT; + + INT64 result = 0; + + HELPER_METHOD_FRAME_BEGIN_RET_1(s); + + int sign = 1; + WCHAR *input; + int length; + int i; + int grabNumbersStart=0; + INT32 r; + + _ASSERTE((flags & PARSE_TREATASI1) == 0 && (flags & PARSE_TREATASI2) == 0); + + if (s) { + i = currPos ? *currPos : 0; + + //Do some radix checking. + //A radix of -1 says to use whatever base is spec'd on the number. + //Parse in Base10 until we figure out what the base actually is. + r = (-1==radix)?10:radix; + + if (r!=2 && r!=10 && r!=8 && r!=16) + COMPlusThrow(kArgumentException, W("Arg_InvalidBase")); + + s->RefInterpretGetStringValuesDangerousForGC(&input, &length); + + if (i<0 || i>=length) + COMPlusThrowArgumentOutOfRange(W("startIndex"), W("ArgumentOutOfRange_Index")); + + //Get rid of the whitespace and then check that we've still got some digits to parse. + if (!(flags & PARSE_ISTIGHT) && !(flags & PARSE_NOSPACE)) { + EatWhiteSpace(input,length,&i); + if (i==length) + COMPlusThrow(kFormatException, W("Format_EmptyInputString")); + } + + //Check for a sign + if (input[i]=='-') { + if (r != 10) + COMPlusThrow(kArgumentException, W("Arg_CannotHaveNegativeValue")); + + if (flags & PARSE_TREATASUNSIGNED) + COMPlusThrow(kOverflowException, W("Overflow_NegativeUnsigned")); + + sign = -1; + i++; + } + else if (input[i]=='+') { + i++; + } + + if ((radix==-1 || radix==16) && (i+1<length) && input[i]=='0') { + if (input[i+1]=='x' || input [i+1]=='X') { + r=16; + i+=2; + } + } + + grabNumbersStart=i; + result = GrabLongs(r,input,length,&i, (flags & PARSE_TREATASUNSIGNED)); + + //Check if they passed us a string with no parsable digits. + if (i==grabNumbersStart) + COMPlusThrow(kFormatException, W("Format_NoParsibleDigits")); + + if (flags & PARSE_ISTIGHT) { + //If we've got effluvia left at the end of the string, complain. + if (i<length) + COMPlusThrow(kFormatException, W("Format_ExtraJunkAtEnd")); + } + + //Put the current index back into the correct place. + if (currPos != NULL) *currPos = i; + + //Return the value properly signed. + if ((UINT64) result==UI64(0x8000000000000000) && sign==1 && r==10 && !(flags & PARSE_TREATASUNSIGNED)) + COMPlusThrow(kOverflowException, W("Overflow_Int64")); + + if (r == 10) + result *= sign; + } + else { + result = 0; + } + + HELPER_METHOD_FRAME_END(); + + return result; +} +FCIMPLEND + +FCIMPL4(INT32, ParseNumbers::StringToInt, StringObject * s, INT32 radix, INT32 flags, INT32* currPos) +{ + FCALL_CONTRACT; + + INT32 result = 0; + + HELPER_METHOD_FRAME_BEGIN_RET_1(s); + + int sign = 1; + WCHAR *input; + int length; + int i; + int grabNumbersStart=0; + INT32 r; + + // TreatAsI1 and TreatAsI2 are mutually exclusive. + _ASSERTE(!((flags & PARSE_TREATASI1) != 0 && (flags & PARSE_TREATASI2) != 0)); + + if (s) { + //They're requied to tell me where to start parsing. + i = currPos ? (*currPos) : 0; + + //Do some radix checking. + //A radix of -1 says to use whatever base is spec'd on the number. + //Parse in Base10 until we figure out what the base actually is. + r = (-1==radix)?10:radix; + + if (r!=2 && r!=10 && r!=8 && r!=16) + COMPlusThrow(kArgumentException, W("Arg_InvalidBase")); + + s->RefInterpretGetStringValuesDangerousForGC(&input, &length); + + if (i<0 || i>=length) + COMPlusThrowArgumentOutOfRange(W("startIndex"), W("ArgumentOutOfRange_Index")); + + //Get rid of the whitespace and then check that we've still got some digits to parse. + if (!(flags & PARSE_ISTIGHT) && !(flags & PARSE_NOSPACE)) { + EatWhiteSpace(input,length,&i); + if (i==length) + COMPlusThrow(kFormatException, W("Format_EmptyInputString")); + } + + //Check for a sign + if (input[i]=='-') { + if (r != 10) + COMPlusThrow(kArgumentException, W("Arg_CannotHaveNegativeValue")); + + if (flags & PARSE_TREATASUNSIGNED) + COMPlusThrow(kOverflowException, W("Overflow_NegativeUnsigned")); + + sign = -1; + i++; + } + else if (input[i]=='+') { + i++; + } + + //Consume the 0x if we're in an unknown base or in base-16. + if ((radix==-1||radix==16) && (i+1<length) && input[i]=='0') { + if (input[i+1]=='x' || input [i+1]=='X') { + r=16; + i+=2; + } + } + + grabNumbersStart=i; + result = GrabInts(r,input,length,&i, (flags & PARSE_TREATASUNSIGNED)); + + //Check if they passed us a string with no parsable digits. + if (i==grabNumbersStart) + COMPlusThrow(kFormatException, W("Format_NoParsibleDigits")); + + if (flags & PARSE_ISTIGHT) { + //If we've got effluvia left at the end of the string, complain. + if (i<(length)) + COMPlusThrow(kFormatException, W("Format_ExtraJunkAtEnd")); + } + + //Put the current index back into the correct place. + if (currPos != NULL) *currPos = i; + + //Return the value properly signed. + if (flags & PARSE_TREATASI1) { + if ((UINT32)result > 0xFF) + COMPlusThrow(kOverflowException, W("Overflow_SByte")); + + // result looks positive when parsed as an I4 + _ASSERTE(sign==1 || r==10); + } + else if (flags & PARSE_TREATASI2) { + if ((UINT32)result > 0xFFFF) + COMPlusThrow(kOverflowException, W("Overflow_Int16")); + + // result looks positive when parsed as an I4 + _ASSERTE(sign==1 || r==10); + } + else if ((UINT32) result==0x80000000U && sign==1 && r==10 && !(flags & PARSE_TREATASUNSIGNED)) { + COMPlusThrow(kOverflowException, W("Overflow_Int32")); + } + + if (r == 10) + result *= sign; + } + else { + result = 0; + } + + HELPER_METHOD_FRAME_END(); + + return result; +} +FCIMPLEND + +// +// +// EXCEPTION NATIVE +// +// +FCIMPL1(FC_BOOL_RET, ExceptionNative::IsImmutableAgileException, Object* pExceptionUNSAFE) +{ + FCALL_CONTRACT; + + ASSERT(pExceptionUNSAFE != NULL); + + OBJECTREF pException = (OBJECTREF) pExceptionUNSAFE; + + // The preallocated exception objects may be used from multiple AppDomains + // and therefore must remain immutable from the application's perspective. + FC_RETURN_BOOL(CLRException::IsPreallocatedExceptionObject(pException)); +} +FCIMPLEND + +FCIMPL1(FC_BOOL_RET, ExceptionNative::IsTransient, INT32 hresult) +{ + FCALL_CONTRACT; + + FC_RETURN_BOOL(Exception::IsTransient(hresult)); +} +FCIMPLEND + +#ifndef FEATURE_CORECLR + +FCIMPL3(StringObject *, ExceptionNative::StripFileInfo, Object *orefExcepUNSAFE, StringObject *orefStrUNSAFE, CLR_BOOL isRemoteStackTrace) +{ + FCALL_CONTRACT; + + OBJECTREF orefExcep = ObjectToOBJECTREF(orefExcepUNSAFE); + STRINGREF orefStr = (STRINGREF)ObjectToOBJECTREF(orefStrUNSAFE); + + if (orefStr == NULL) + { + return NULL; + } + + HELPER_METHOD_FRAME_BEGIN_RET_2(orefExcep, orefStr); + + if (isRemoteStackTrace) + { + if (!AppX::IsAppXProcess() && ExceptionTypeOverridesStackTraceGetter(orefExcep->GetMethodTable())) + { + // In classic processes, the remote stack trace could have been generated using a custom get_StackTrace + // override which means that we would not be able to parse is - strip the whole string by returning NULL. + orefStr = NULL; + } + } + + if (orefStr != NULL) + { + SString stackTrace; + orefStr->GetSString(stackTrace); + + StripFileInfoFromStackTrace(stackTrace); + + orefStr = AllocateString(stackTrace); + } + + HELPER_METHOD_FRAME_END(); + return (StringObject *)OBJECTREFToObject(orefStr); +} +FCIMPLEND + +#endif // !FEATURE_CORECLR + +#if defined(FEATURE_EXCEPTIONDISPATCHINFO) +// This FCall sets a flag against the thread exception state to indicate to +// IL_Throw and the StackTraceInfo implementation to account for the fact +// that we have restored a foreign exception dispatch details. +// +// Refer to the respective methods for details on how they use this flag. +FCIMPL0(VOID, ExceptionNative::PrepareForForeignExceptionRaise) +{ + FCALL_CONTRACT; + + PTR_ThreadExceptionState pCurTES = GetThread()->GetExceptionState(); + + // Set a flag against the TES to indicate this is a foreign exception raise. + pCurTES->SetRaisingForeignException(); +} +FCIMPLEND + +// Given an exception object, this method will extract the stacktrace and dynamic method array and set them up for return to the caller. +FCIMPL3(VOID, ExceptionNative::GetStackTracesDeepCopy, Object* pExceptionObjectUnsafe, Object **pStackTraceUnsafe, Object **pDynamicMethodsUnsafe); +{ + CONTRACTL + { + FCALL_CHECK; + } + CONTRACTL_END; + + ASSERT(pExceptionObjectUnsafe != NULL); + ASSERT(pStackTraceUnsafe != NULL); + ASSERT(pDynamicMethodsUnsafe != NULL); + + struct _gc + { + StackTraceArray stackTrace; + StackTraceArray stackTraceCopy; + EXCEPTIONREF refException; + PTRARRAYREF dynamicMethodsArray; // Object array of Managed Resolvers + PTRARRAYREF dynamicMethodsArrayCopy; // Copy of the object array of Managed Resolvers + }; + _gc gc; + ZeroMemory(&gc, sizeof(gc)); + + // GC protect the array reference + HELPER_METHOD_FRAME_BEGIN_PROTECT(gc); + + // Get the exception object reference + gc.refException = (EXCEPTIONREF)(ObjectToOBJECTREF(pExceptionObjectUnsafe)); + + // Fetch the stacktrace details from the exception under a lock + gc.refException->GetStackTrace(gc.stackTrace, &gc.dynamicMethodsArray); + + bool fHaveStackTrace = false; + bool fHaveDynamicMethodArray = false; + + if ((unsigned)gc.stackTrace.Size() > 0) + { + // Deepcopy the array + gc.stackTraceCopy.CopyFrom(gc.stackTrace); + fHaveStackTrace = true; + } + + if (gc.dynamicMethodsArray != NULL) + { + // Get the number of elements in the dynamic methods array + unsigned cOrigDynamic = gc.dynamicMethodsArray->GetNumComponents(); + + // ..and allocate a new array. This can trigger GC or throw under OOM. + gc.dynamicMethodsArrayCopy = (PTRARRAYREF)AllocateObjectArray(cOrigDynamic, g_pObjectClass); + + // Deepcopy references to the new array we just allocated + memmoveGCRefs(gc.dynamicMethodsArrayCopy->GetDataPtr(), gc.dynamicMethodsArray->GetDataPtr(), + cOrigDynamic * sizeof(Object *)); + + fHaveDynamicMethodArray = true; + } + + // Prep to return + *pStackTraceUnsafe = fHaveStackTrace?OBJECTREFToObject(gc.stackTraceCopy.Get()):NULL; + *pDynamicMethodsUnsafe = fHaveDynamicMethodArray?OBJECTREFToObject(gc.dynamicMethodsArrayCopy):NULL; + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +// Given an exception object and deep copied instances of a stacktrace and/or dynamic method array, this method will set the latter in the exception object instance. +FCIMPL3(VOID, ExceptionNative::SaveStackTracesFromDeepCopy, Object* pExceptionObjectUnsafe, Object *pStackTraceUnsafe, Object *pDynamicMethodsUnsafe); +{ + CONTRACTL + { + FCALL_CHECK; + } + CONTRACTL_END; + + ASSERT(pExceptionObjectUnsafe != NULL); + + struct _gc + { + StackTraceArray stackTrace; + EXCEPTIONREF refException; + PTRARRAYREF dynamicMethodsArray; // Object array of Managed Resolvers + }; + _gc gc; + ZeroMemory(&gc, sizeof(gc)); + + // GC protect the array reference + HELPER_METHOD_FRAME_BEGIN_PROTECT(gc); + + // Get the exception object reference + gc.refException = (EXCEPTIONREF)(ObjectToOBJECTREF(pExceptionObjectUnsafe)); + + if (pStackTraceUnsafe != NULL) + { + // Copy the stacktrace + StackTraceArray stackTraceArray((I1ARRAYREF)ObjectToOBJECTREF(pStackTraceUnsafe)); + gc.stackTrace.Swap(stackTraceArray); + } + + gc.dynamicMethodsArray = NULL; + if (pDynamicMethodsUnsafe != NULL) + { + gc.dynamicMethodsArray = (PTRARRAYREF)ObjectToOBJECTREF(pDynamicMethodsUnsafe); + } + + // If there is no stacktrace, then there cannot be any dynamic method array. Thus, + // save stacktrace only when we have it. + if (gc.stackTrace.Size() > 0) + { + // Save the stacktrace details in the exception under a lock + gc.refException->SetStackTrace(gc.stackTrace, gc.dynamicMethodsArray); + } + else + { + gc.refException->SetNullStackTrace(); + } + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +// This method performs a deep copy of the stack trace array. +FCIMPL1(Object*, ExceptionNative::CopyStackTrace, Object* pStackTraceUNSAFE) +{ + FCALL_CONTRACT; + + ASSERT(pStackTraceUNSAFE != NULL); + + struct _gc + { + StackTraceArray stackTrace; + StackTraceArray stackTraceCopy; + _gc(I1ARRAYREF refStackTrace) + : stackTrace(refStackTrace) + {} + }; + _gc gc((I1ARRAYREF)(ObjectToOBJECTREF(pStackTraceUNSAFE))); + + // GC protect the array reference + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + // Deepcopy the array + gc.stackTraceCopy.CopyFrom(gc.stackTrace); + + HELPER_METHOD_FRAME_END(); + + return OBJECTREFToObject(gc.stackTraceCopy.Get()); +} +FCIMPLEND + +// This method performs a deep copy of the dynamic method array. +FCIMPL1(Object*, ExceptionNative::CopyDynamicMethods, Object* pDynamicMethodsUNSAFE) +{ + FCALL_CONTRACT; + + ASSERT(pDynamicMethodsUNSAFE != NULL); + + struct _gc + { + PTRARRAYREF dynamicMethodsArray; // Object array of Managed Resolvers + PTRARRAYREF dynamicMethodsArrayCopy; // Copy of the object array of Managed Resolvers + _gc() + {} + }; + _gc gc; + ZeroMemory(&gc, sizeof(gc)); + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + gc.dynamicMethodsArray = (PTRARRAYREF)(ObjectToOBJECTREF(pDynamicMethodsUNSAFE)); + + // Get the number of elements in the array + unsigned cOrigDynamic = gc.dynamicMethodsArray->GetNumComponents(); + // ..and allocate a new array. This can trigger GC or throw under OOM. + gc.dynamicMethodsArrayCopy = (PTRARRAYREF)AllocateObjectArray(cOrigDynamic, g_pObjectClass); + + // Copy references to the new array we just allocated + memmoveGCRefs(gc.dynamicMethodsArrayCopy->GetDataPtr(), gc.dynamicMethodsArray->GetDataPtr(), + cOrigDynamic * sizeof(Object *)); + HELPER_METHOD_FRAME_END(); + + return OBJECTREFToObject(gc.dynamicMethodsArrayCopy); +} +FCIMPLEND + +#endif // defined(FEATURE_EXCEPTIONDISPATCHINFO) + +BSTR BStrFromString(STRINGREF s) +{ + CONTRACTL + { + THROWS; + } + CONTRACTL_END; + + WCHAR *wz; + int cch; + BSTR bstr; + + if (s == NULL) + return NULL; + + s->RefInterpretGetStringValuesDangerousForGC(&wz, &cch); + + bstr = SysAllocString(wz); + if (bstr == NULL) + COMPlusThrowOM(); + + return bstr; +} + +static BSTR GetExceptionDescription(OBJECTREF objException) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION( IsException(objException->GetMethodTable()) ); + } + CONTRACTL_END; + + BSTR bstrDescription; + + STRINGREF MessageString = NULL; + GCPROTECT_BEGIN(MessageString) + GCPROTECT_BEGIN(objException) + { +#ifdef FEATURE_APPX + if (AppX::IsAppXProcess()) + { + // In AppX, call Exception.ToString(false, false) which returns a string containing the exception class + // name and callstack without file paths/names. This is used for unhandled exception bucketing/analysis. + MethodDescCallSite getMessage(METHOD__EXCEPTION__TO_STRING, &objException); + + ARG_SLOT GetMessageArgs[] = + { + ObjToArgSlot(objException), + BoolToArgSlot(false), // needFileLineInfo + BoolToArgSlot(false) // needMessage + }; + MessageString = getMessage.Call_RetSTRINGREF(GetMessageArgs); + } + else +#endif // FEATURE_APPX + { + // read Exception.Message property + MethodDescCallSite getMessage(METHOD__EXCEPTION__GET_MESSAGE, &objException); + + ARG_SLOT GetMessageArgs[] = { ObjToArgSlot(objException)}; + MessageString = getMessage.Call_RetSTRINGREF(GetMessageArgs); + + // if the message string is empty then use the exception classname. + if (MessageString == NULL || MessageString->GetStringLength() == 0) { + // call GetClassName + MethodDescCallSite getClassName(METHOD__EXCEPTION__GET_CLASS_NAME, &objException); + ARG_SLOT GetClassNameArgs[] = { ObjToArgSlot(objException)}; + MessageString = getClassName.Call_RetSTRINGREF(GetClassNameArgs); + _ASSERTE(MessageString != NULL && MessageString->GetStringLength() != 0); + } + } + + // Allocate the description BSTR. + int DescriptionLen = MessageString->GetStringLength(); + bstrDescription = SysAllocStringLen(MessageString->GetBuffer(), DescriptionLen); + } + GCPROTECT_END(); + GCPROTECT_END(); + + return bstrDescription; +} + +static BSTR GetExceptionSource(OBJECTREF objException) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION( IsException(objException->GetMethodTable()) ); + } + CONTRACTL_END; + + STRINGREF refRetVal; + GCPROTECT_BEGIN(objException) + + // read Exception.Source property + MethodDescCallSite getSource(METHOD__EXCEPTION__GET_SOURCE, &objException); + + ARG_SLOT GetSourceArgs[] = { ObjToArgSlot(objException)}; + + refRetVal = getSource.Call_RetSTRINGREF(GetSourceArgs); + + GCPROTECT_END(); + return BStrFromString(refRetVal); +} + +static void GetExceptionHelp(OBJECTREF objException, BSTR *pbstrHelpFile, DWORD *pdwHelpContext) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + INJECT_FAULT(COMPlusThrowOM()); + PRECONDITION(IsException(objException->GetMethodTable())); + PRECONDITION(CheckPointer(pbstrHelpFile)); + PRECONDITION(CheckPointer(pdwHelpContext)); + } + CONTRACTL_END; + + *pdwHelpContext = 0; + + GCPROTECT_BEGIN(objException); + + // read Exception.HelpLink property + MethodDescCallSite getHelpLink(METHOD__EXCEPTION__GET_HELP_LINK, &objException); + + ARG_SLOT GetHelpLinkArgs[] = { ObjToArgSlot(objException)}; + *pbstrHelpFile = BStrFromString(getHelpLink.Call_RetSTRINGREF(GetHelpLinkArgs)); + + GCPROTECT_END(); + + // parse the help file to check for the presence of helpcontext + int len = SysStringLen(*pbstrHelpFile); + int pos = len; + WCHAR *pwstr = *pbstrHelpFile; + if (pwstr) { + BOOL fFoundPound = FALSE; + + for (pos = len - 1; pos >= 0; pos--) { + if (pwstr[pos] == W('#')) { + fFoundPound = TRUE; + break; + } + } + + if (fFoundPound) { + int PoundPos = pos; + int NumberStartPos = -1; + BOOL bNumberStarted = FALSE; + BOOL bNumberFinished = FALSE; + BOOL bInvalidDigitsFound = FALSE; + + _ASSERTE(pwstr[pos] == W('#')); + + // Check to see if the string to the right of the pound a valid number. + for (pos++; pos < len; pos++) { + if (bNumberFinished) { + if (!COMCharacter::nativeIsWhiteSpace(pwstr[pos])) { + bInvalidDigitsFound = TRUE; + break; + } + } + else if (bNumberStarted) { + if (COMCharacter::nativeIsWhiteSpace(pwstr[pos])) { + bNumberFinished = TRUE; + } + else if (!COMCharacter::nativeIsDigit(pwstr[pos])) { + bInvalidDigitsFound = TRUE; + break; + } + } + else { + if (COMCharacter::nativeIsDigit(pwstr[pos])) { + NumberStartPos = pos; + bNumberStarted = TRUE; + } + else if (!COMCharacter::nativeIsWhiteSpace(pwstr[pos])) { + bInvalidDigitsFound = TRUE; + break; + } + } + } + + if (bNumberStarted && !bInvalidDigitsFound) { + // Grab the help context and remove it from the help file. + *pdwHelpContext = (DWORD)wtoi(&pwstr[NumberStartPos], len - NumberStartPos); + + // Allocate a new help file string of the right length. + BSTR strOld = *pbstrHelpFile; + *pbstrHelpFile = SysAllocStringLen(strOld, PoundPos); + SysFreeString(strOld); + if (!*pbstrHelpFile) + COMPlusThrowOM(); + } + } + } +} + +// NOTE: caller cleans up any partially initialized BSTRs in pED +void ExceptionNative::GetExceptionData(OBJECTREF objException, ExceptionData *pED) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + PRECONDITION(IsException(objException->GetMethodTable())); + PRECONDITION(CheckPointer(pED)); + } + CONTRACTL_END; + + ZeroMemory(pED, sizeof(ExceptionData)); + + if (objException->GetMethodTable() == g_pStackOverflowExceptionClass) { + // In a low stack situation, most everything else in here will fail. + // <TODO>@TODO: We're not turning the guard page back on here, yet.</TODO> + pED->hr = COR_E_STACKOVERFLOW; + pED->bstrDescription = SysAllocString(STACK_OVERFLOW_MESSAGE); + return; + } + + GCPROTECT_BEGIN(objException); + pED->hr = GetExceptionHResult(objException); + pED->bstrDescription = GetExceptionDescription(objException); + pED->bstrSource = GetExceptionSource(objException); + GetExceptionHelp(objException, &pED->bstrHelpFile, &pED->dwHelpContext); + GCPROTECT_END(); + return; +} + +#ifdef FEATURE_COMINTEROP + +HRESULT SimpleComCallWrapper::IErrorInfo_hr() +{ + WRAPPER_NO_CONTRACT; + return GetExceptionHResult(this->GetObjectRef()); +} + +BSTR SimpleComCallWrapper::IErrorInfo_bstrDescription() +{ + WRAPPER_NO_CONTRACT; + return GetExceptionDescription(this->GetObjectRef()); +} + +BSTR SimpleComCallWrapper::IErrorInfo_bstrSource() +{ + WRAPPER_NO_CONTRACT; + return GetExceptionSource(this->GetObjectRef()); +} + +BSTR SimpleComCallWrapper::IErrorInfo_bstrHelpFile() +{ + WRAPPER_NO_CONTRACT; + BSTR bstrHelpFile; + DWORD dwHelpContext; + GetExceptionHelp(this->GetObjectRef(), &bstrHelpFile, &dwHelpContext); + return bstrHelpFile; +} + +DWORD SimpleComCallWrapper::IErrorInfo_dwHelpContext() +{ + WRAPPER_NO_CONTRACT; + BSTR bstrHelpFile; + DWORD dwHelpContext; + GetExceptionHelp(this->GetObjectRef(), &bstrHelpFile, &dwHelpContext); + SysFreeString(bstrHelpFile); + return dwHelpContext; +} + +GUID SimpleComCallWrapper::IErrorInfo_guid() +{ + LIMITED_METHOD_CONTRACT; + return GUID_NULL; +} + +#endif // FEATURE_COMINTEROP + +FCIMPL0(EXCEPTION_POINTERS*, ExceptionNative::GetExceptionPointers) +{ + FCALL_CONTRACT; + + EXCEPTION_POINTERS* retVal = NULL; + + Thread *pThread = GetThread(); + _ASSERTE(pThread); + + if (pThread->IsExceptionInProgress()) + { + retVal = pThread->GetExceptionState()->GetExceptionPointers(); + } + + return retVal; +} +FCIMPLEND + +FCIMPL0(INT32, ExceptionNative::GetExceptionCode) +{ + FCALL_CONTRACT; + + INT32 retVal = 0; + + Thread *pThread = GetThread(); + _ASSERTE(pThread); + + if (pThread->IsExceptionInProgress()) + { + retVal = pThread->GetExceptionState()->GetExceptionCode(); + } + + return retVal; +} +FCIMPLEND + + +// +// This must be implemented as an FCALL because managed code cannot +// swallow a thread abort exception without resetting the abort, +// which we don't want to do. Additionally, we can run into deadlocks +// if we use the ResourceManager to do resource lookups - it requires +// taking managed locks when initializing Globalization & Security, +// but a thread abort on a separate thread initializing those same +// systems would also do a resource lookup via the ResourceManager. +// We've deadlocked in CompareInfo.GetCompareInfo & +// Environment.GetResourceString. It's not practical to take all of +// our locks within CER's to avoid this problem - just use the CLR's +// unmanaged resources. +// +void QCALLTYPE ExceptionNative::GetMessageFromNativeResources(ExceptionMessageKind kind, QCall::StringHandleOnStack retMesg) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + SString buffer; + HRESULT hr = S_OK; + const WCHAR * wszFallbackString = NULL; + + switch(kind) { + case ThreadAbort: + hr = buffer.LoadResourceAndReturnHR(CCompRC::Error, IDS_EE_THREAD_ABORT); + if (FAILED(hr)) { + wszFallbackString = W("Thread was being aborted."); + } + break; + + case ThreadInterrupted: + hr = buffer.LoadResourceAndReturnHR(CCompRC::Error, IDS_EE_THREAD_INTERRUPTED); + if (FAILED(hr)) { + wszFallbackString = W("Thread was interrupted from a waiting state."); + } + break; + + case OutOfMemory: + hr = buffer.LoadResourceAndReturnHR(CCompRC::Error, IDS_EE_OUT_OF_MEMORY); + if (FAILED(hr)) { + wszFallbackString = W("Insufficient memory to continue the execution of the program."); + } + break; + + default: + _ASSERTE(!"Unknown ExceptionMessageKind value!"); + } + if (FAILED(hr)) { + STRESS_LOG1(LF_BCL, LL_ALWAYS, "LoadResource error: %x", hr); + _ASSERTE(wszFallbackString != NULL); + retMesg.Set(wszFallbackString); + } + else { + retMesg.Set(buffer); + } + + END_QCALL; +} + +// BlockCopy +// This method from one primitive array to another based +// upon an offset into each an a byte count. +FCIMPL5(VOID, Buffer::BlockCopy, ArrayBase *src, int srcOffset, ArrayBase *dst, int dstOffset, int count) +{ + FCALL_CONTRACT; + + // Verify that both the src and dst are Arrays of primitive + // types. + // <TODO>@TODO: We need to check for booleans</TODO> + if (src==NULL || dst==NULL) + FCThrowArgumentNullVoid((src==NULL) ? W("src") : W("dst")); + + SIZE_T srcLen, dstLen; + + // + // Use specialized fast path for byte arrays because of it is what Buffer::BlockCopy is + // typically used for. + // + + MethodTable * pByteArrayMT = g_pByteArrayMT; + _ASSERTE(pByteArrayMT != NULL); + + // Optimization: If src is a byte array, we can + // simply set srcLen to GetNumComponents, without having + // to call GetComponentSize or verifying GetArrayElementType + if (src->GetMethodTable() == pByteArrayMT) + { + srcLen = src->GetNumComponents(); + } + else + { + srcLen = src->GetNumComponents() * src->GetComponentSize(); + + // We only want to allow arrays of primitives, no Objects. + const CorElementType srcET = src->GetArrayElementType(); + if (!CorTypeInfo::IsPrimitiveType_NoThrow(srcET)) + FCThrowArgumentVoid(W("src"), W("Arg_MustBePrimArray")); + } + + // Optimization: If copying to/from the same array, then + // we know that dstLen and srcLen must be the same. + if (src == dst) + { + dstLen = srcLen; + } + else if (dst->GetMethodTable() == pByteArrayMT) + { + dstLen = dst->GetNumComponents(); + } + else + { + dstLen = dst->GetNumComponents() * dst->GetComponentSize(); + if (dst->GetMethodTable() != src->GetMethodTable()) + { + const CorElementType dstET = dst->GetArrayElementType(); + if (!CorTypeInfo::IsPrimitiveType_NoThrow(dstET)) + FCThrowArgumentVoid(W("dest"), W("Arg_MustBePrimArray")); + } + } + + if (srcOffset < 0 || dstOffset < 0 || count < 0) { + const wchar_t* str = W("srcOffset"); + if (dstOffset < 0) str = W("dstOffset"); + if (count < 0) str = W("count"); + FCThrowArgumentOutOfRangeVoid(str, W("ArgumentOutOfRange_NeedNonNegNum")); + } + + if (srcLen < (SIZE_T)srcOffset + (SIZE_T)count || dstLen < (SIZE_T)dstOffset + (SIZE_T)count) { + FCThrowArgumentVoid(NULL, W("Argument_InvalidOffLen")); + } + + PTR_BYTE srcPtr = src->GetDataPtr() + srcOffset; + PTR_BYTE dstPtr = dst->GetDataPtr() + dstOffset; + + if ((srcPtr != dstPtr) && (count > 0)) { + memmove(dstPtr, srcPtr, count); + } + + FC_GC_POLL(); +} +FCIMPLEND + + +// InternalBlockCopy +// This method from one primitive array to another based +// upon an offset into each an a byte count. +FCIMPL5(VOID, Buffer::InternalBlockCopy, ArrayBase *src, int srcOffset, ArrayBase *dst, int dstOffset, int count) +{ + FCALL_CONTRACT; + + // @TODO: We should consider writing this in managed code. We probably + // cannot easily do this though - how do we get at the array's data? + + // Unfortunately, we must do a check to make sure we're writing within + // the bounds of the array. This will ensure that we don't overwrite + // memory elsewhere in the system nor do we write out junk. This can + // happen if multiple threads interact with our IO classes simultaneously + // without being threadsafe. Throw here. + // Unfortunately this even applies to setting our internal buffers to + // null. We don't want to debug races between Close and Read or Write. + if (src == NULL || dst == NULL) + FCThrowResVoid(kIndexOutOfRangeException, W("IndexOutOfRange_IORaceCondition")); + + SIZE_T srcLen = src->GetNumComponents() * src->GetComponentSize(); + SIZE_T dstLen = srcLen; + if (src != dst) + dstLen = dst->GetNumComponents() * dst->GetComponentSize(); + + if (srcOffset < 0 || dstOffset < 0 || count < 0) + FCThrowResVoid(kIndexOutOfRangeException, W("IndexOutOfRange_IORaceCondition")); + + if (srcLen < (SIZE_T)srcOffset + (SIZE_T)count || dstLen < (SIZE_T)dstOffset + (SIZE_T)count) + FCThrowResVoid(kIndexOutOfRangeException, W("IndexOutOfRange_IORaceCondition")); + + _ASSERTE(srcOffset >= 0); + _ASSERTE((src->GetNumComponents() * src->GetComponentSize()) - (unsigned) srcOffset >= (unsigned) count); + _ASSERTE((dst->GetNumComponents() * dst->GetComponentSize()) - (unsigned) dstOffset >= (unsigned) count); + _ASSERTE(dstOffset >= 0); + _ASSERTE(count >= 0); + + // Copy the data. + memmove(dst->GetDataPtr() + dstOffset, src->GetDataPtr() + srcOffset, count); + + FC_GC_POLL(); +} +FCIMPLEND + +void QCALLTYPE Buffer::MemMove(void *dst, void *src, size_t length) +{ + QCALL_CONTRACT; + +#if defined(FEATURE_CORECLR) && !defined(FEATURE_CORESYSTEM) + // Callers of memcpy do expect and handle access violations in some scenarios. + // Access violations in the runtime dll are turned into fail fast by the vector exception handler by default. + // We need to supress this behavior for CoreCLR using AVInRuntimeImplOkayHolder because of memcpy is statically linked in. + AVInRuntimeImplOkayHolder avOk; +#endif + + memmove(dst, src, length); +} + +// Returns a bool to indicate if the array is of primitive types or not. +FCIMPL1(FC_BOOL_RET, Buffer::IsPrimitiveTypeArray, ArrayBase *arrayUNSAFE) +{ + FCALL_CONTRACT; + + _ASSERTE(arrayUNSAFE != NULL); + + // Check the type from the contained element's handle + TypeHandle elementTH = arrayUNSAFE->GetArrayElementTypeHandle(); + BOOL fIsPrimitiveTypeArray = CorTypeInfo::IsPrimitiveType_NoThrow(elementTH.GetVerifierCorElementType()); + + FC_RETURN_BOOL(fIsPrimitiveTypeArray); + +} +FCIMPLEND + +// Gets a particular byte out of the array. The array can't be an array of Objects - it +// must be a primitive array. +FCIMPL2(FC_UINT8_RET, Buffer::GetByte, ArrayBase *arrayUNSAFE, INT32 index) +{ + FCALL_CONTRACT; + + _ASSERTE(arrayUNSAFE != NULL); + _ASSERTE(index >=0 && index < ((INT32)(arrayUNSAFE->GetComponentSize() * arrayUNSAFE->GetNumComponents()))); + + UINT8 bData = *((BYTE*)arrayUNSAFE->GetDataPtr() + index); + return bData; +} +FCIMPLEND + +// Sets a particular byte in an array. The array can't be an array of Objects - it +// must be a primitive array. +// +// Semantically the bData argment is of type BYTE but FCallCheckSignature expects the +// type to be UINT8 and raises an error if this isn't this case when +// COMPlus_ConsistencyCheck is set. +FCIMPL3(VOID, Buffer::SetByte, ArrayBase *arrayUNSAFE, INT32 index, UINT8 bData) +{ + FCALL_CONTRACT; + + _ASSERTE(arrayUNSAFE != NULL); + _ASSERTE(index >=0 && index < ((INT32)(arrayUNSAFE->GetComponentSize() * arrayUNSAFE->GetNumComponents()))); + + *((BYTE*)arrayUNSAFE->GetDataPtr() + index) = (BYTE) bData; +} +FCIMPLEND + +// Returns the length in bytes of an array containing +// primitive type elements +FCIMPL1(INT32, Buffer::ByteLength, ArrayBase* arrayUNSAFE) +{ + FCALL_CONTRACT; + + _ASSERTE(arrayUNSAFE != NULL); + + SIZE_T iRetVal = arrayUNSAFE->GetNumComponents() * arrayUNSAFE->GetComponentSize(); + + // This API is explosed both as Buffer.ByteLength and also used indirectly in argument + // checks for Buffer.GetByte/SetByte. + // + // If somebody called Get/SetByte on 2GB+ arrays, there is a decent chance that + // the computation of the index has overflowed. Thus we intentionally always + // throw on 2GB+ arrays in Get/SetByte argument checks (even for indicies <2GB) + // to prevent people from running into a trap silently. + if (iRetVal > INT32_MAX) + FCThrow(kOverflowException); + + return (INT32)iRetVal; +} +FCIMPLEND + +// +// GCInterface +// +MethodDesc *GCInterface::m_pCacheMethod=NULL; + +UINT64 GCInterface::m_ulMemPressure = 0; +UINT64 GCInterface::m_ulThreshold = MIN_GC_MEMORYPRESSURE_THRESHOLD; +INT32 GCInterface::m_gc_counts[3] = {0,0,0}; +CrstStatic GCInterface::m_MemoryPressureLock; + +UINT64 GCInterface::m_addPressure[NEW_PRESSURE_COUNT] = {0, 0, 0, 0}; // history of memory pressure additions +UINT64 GCInterface::m_remPressure[NEW_PRESSURE_COUNT] = {0, 0, 0, 0}; // history of memory pressure removals + +// incremented after a gen2 GC has been detected, +// (m_iteration % NEW_PRESSURE_COUNT) is used as an index into m_addPressure and m_remPressure +UINT GCInterface::m_iteration = 0; + +FCIMPL0(int, GCInterface::GetGcLatencyMode) +{ + FCALL_CONTRACT; + + FC_GC_POLL_NOT_NEEDED(); + + int result = (INT32)GCHeap::GetGCHeap()->GetGcLatencyMode(); + return result; +} +FCIMPLEND + +FCIMPL1(int, GCInterface::SetGcLatencyMode, int newLatencyMode) +{ + FCALL_CONTRACT; + + FC_GC_POLL_NOT_NEEDED(); + + return GCHeap::GetGCHeap()->SetGcLatencyMode(newLatencyMode); +} +FCIMPLEND + +FCIMPL0(int, GCInterface::GetLOHCompactionMode) +{ + FCALL_CONTRACT; + + FC_GC_POLL_NOT_NEEDED(); + + int result = (INT32)GCHeap::GetGCHeap()->GetLOHCompactionMode(); + return result; +} +FCIMPLEND + +FCIMPL1(void, GCInterface::SetLOHCompactionMode, int newLOHCompactionyMode) +{ + FCALL_CONTRACT; + + FC_GC_POLL_NOT_NEEDED(); + + GCHeap::GetGCHeap()->SetLOHCompactionMode(newLOHCompactionyMode); +} +FCIMPLEND + + +FCIMPL2(FC_BOOL_RET, GCInterface::RegisterForFullGCNotification, UINT32 gen2Percentage, UINT32 lohPercentage) +{ + FCALL_CONTRACT; + + FC_GC_POLL_NOT_NEEDED(); + + FC_RETURN_BOOL(GCHeap::GetGCHeap()->RegisterForFullGCNotification(gen2Percentage, lohPercentage)); +} +FCIMPLEND + +FCIMPL0(FC_BOOL_RET, GCInterface::CancelFullGCNotification) +{ + FCALL_CONTRACT; + + FC_GC_POLL_NOT_NEEDED(); + FC_RETURN_BOOL(GCHeap::GetGCHeap()->CancelFullGCNotification()); +} +FCIMPLEND + +FCIMPL1(int, GCInterface::WaitForFullGCApproach, int millisecondsTimeout) +{ + CONTRACTL + { + THROWS; + MODE_COOPERATIVE; + DISABLED(GC_TRIGGERS); // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F. + SO_TOLERANT; + } + CONTRACTL_END; + + int result = 0; + + //We don't need to check the top end because the GC will take care of that. + HELPER_METHOD_FRAME_BEGIN_RET_0(); + + DWORD dwMilliseconds = ((millisecondsTimeout == -1) ? INFINITE : millisecondsTimeout); + result = GCHeap::GetGCHeap()->WaitForFullGCApproach(dwMilliseconds); + + HELPER_METHOD_FRAME_END(); + + return result; +} +FCIMPLEND + +FCIMPL1(int, GCInterface::WaitForFullGCComplete, int millisecondsTimeout) +{ + CONTRACTL + { + THROWS; + MODE_COOPERATIVE; + DISABLED(GC_TRIGGERS); // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F. + SO_TOLERANT; + } + CONTRACTL_END; + + int result = 0; + + //We don't need to check the top end because the GC will take care of that. + HELPER_METHOD_FRAME_BEGIN_RET_0(); + + DWORD dwMilliseconds = ((millisecondsTimeout == -1) ? INFINITE : millisecondsTimeout); + result = GCHeap::GetGCHeap()->WaitForFullGCComplete(dwMilliseconds); + + HELPER_METHOD_FRAME_END(); + + return result; +} +FCIMPLEND + +/*================================GetGeneration================================= +**Action: Returns the generation in which args->obj is found. +**Returns: The generation in which args->obj is found. +**Arguments: args->obj -- The object to locate. +**Exceptions: ArgumentException if args->obj is null. +==============================================================================*/ +FCIMPL1(int, GCInterface::GetGeneration, Object* objUNSAFE) +{ + FCALL_CONTRACT; + + if (objUNSAFE == NULL) + FCThrowArgumentNull(W("obj")); + + int result = (INT32)GCHeap::GetGCHeap()->WhichGeneration(objUNSAFE); + FC_GC_POLL_RET(); + return result; +} +FCIMPLEND + +/*================================CollectionCount================================= +**Action: Returns the number of collections for this generation since the begining of the life of the process +**Returns: The collection count. +**Arguments: args->generation -- The generation +**Exceptions: Argument exception if args->generation is < 0 or > GetMaxGeneration(); +==============================================================================*/ +FCIMPL2(int, GCInterface::CollectionCount, INT32 generation, INT32 getSpecialGCCount) +{ + FCALL_CONTRACT; + + //We've already checked this in GC.cs, so we'll just assert it here. + _ASSERTE(generation >= 0); + + //We don't need to check the top end because the GC will take care of that. + int result = (INT32)GCHeap::GetGCHeap()->CollectionCount(generation, getSpecialGCCount); + FC_GC_POLL_RET(); + return result; +} +FCIMPLEND + +int QCALLTYPE GCInterface::StartNoGCRegion(INT64 totalSize, BOOL lohSizeKnown, INT64 lohSize, BOOL disallowFullBlockingGC) +{ + QCALL_CONTRACT; + + int retVal = 0; + + BEGIN_QCALL; + + GCX_COOP(); + + retVal = GCHeap::GetGCHeap()->StartNoGCRegion((ULONGLONG)totalSize, + lohSizeKnown, + (ULONGLONG)lohSize, + disallowFullBlockingGC); + + END_QCALL; + + return retVal; +} + +int QCALLTYPE GCInterface::EndNoGCRegion() +{ + QCALL_CONTRACT; + + int retVal = FALSE; + + BEGIN_QCALL; + + retVal = GCHeap::GetGCHeap()->EndNoGCRegion(); + + END_QCALL; + + return retVal; +} + +/*===============================GetGenerationWR================================ +**Action: Returns the generation in which the object pointed to by a WeakReference is found. +**Returns: +**Arguments: args->handle -- the OBJECTHANDLE to the object which we're locating. +**Exceptions: ArgumentException if handle points to an object which is not accessible. +==============================================================================*/ +FCIMPL1(int, GCInterface::GetGenerationWR, LPVOID handle) +{ + FCALL_CONTRACT; + + int iRetVal = 0; + + HELPER_METHOD_FRAME_BEGIN_RET_0(); + + OBJECTREF temp; + temp = ObjectFromHandle((OBJECTHANDLE) handle); + if (temp == NULL) + COMPlusThrowArgumentNull(W("weak handle")); + + iRetVal = (INT32)GCHeap::GetGCHeap()->WhichGeneration(OBJECTREFToObject(temp)); + + HELPER_METHOD_FRAME_END(); + + return iRetVal; +} +FCIMPLEND + +/*================================GetTotalMemory================================ +**Action: Returns the total number of bytes in use +**Returns: The total number of bytes in use +**Arguments: None +**Exceptions: None +==============================================================================*/ +INT64 QCALLTYPE GCInterface::GetTotalMemory() +{ + QCALL_CONTRACT; + + INT64 iRetVal = 0; + + BEGIN_QCALL; + + GCX_COOP(); + iRetVal = (INT64) GCHeap::GetGCHeap()->GetTotalBytesInUse(); + + END_QCALL; + + return iRetVal; +} + +/*==============================Collect========================================= +**Action: Collects all generations <= args->generation +**Returns: void +**Arguments: args->generation: The maximum generation to collect +**Exceptions: Argument exception if args->generation is < 0 or > GetMaxGeneration(); +==============================================================================*/ +void QCALLTYPE GCInterface::Collect(INT32 generation, INT32 mode) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + //We've already checked this in GC.cs, so we'll just assert it here. + _ASSERTE(generation >= -1); + + //We don't need to check the top end because the GC will take care of that. + + GCX_COOP(); + GCHeap::GetGCHeap()->GarbageCollect(generation, FALSE, mode); + + END_QCALL; +} + + +/*==========================WaitForPendingFinalizers============================ +**Action: Run all Finalizers that haven't been run. +**Arguments: None +**Exceptions: None +==============================================================================*/ +void QCALLTYPE GCInterface::WaitForPendingFinalizers() +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + + FinalizerThread::FinalizerThreadWait(); + + END_QCALL; +} + + +/*===============================GetMaxGeneration=============================== +**Action: Returns the largest GC generation +**Returns: The largest GC Generation +**Arguments: None +**Exceptions: None +==============================================================================*/ +FCIMPL0(int, GCInterface::GetMaxGeneration) +{ + FCALL_CONTRACT; + + return(INT32)GCHeap::GetGCHeap()->GetMaxGeneration(); +} +FCIMPLEND + +/*===============================GetAllocatedBytesForCurrentThread=============================== +**Action: Computes the allocated bytes so far on the current thread +**Returns: The allocated bytes so far on the current thread +**Arguments: None +**Exceptions: None +==============================================================================*/ +FCIMPL0(INT64, GCInterface::GetAllocatedBytesForCurrentThread) +{ + FCALL_CONTRACT; + + INT64 currentAllocated = 0; + Thread *pThread = GetThread(); + alloc_context* ac = pThread->GetAllocContext(); + currentAllocated = ac->alloc_bytes + ac->alloc_bytes_loh - (ac->alloc_limit - ac->alloc_ptr); + + return currentAllocated; +} +FCIMPLEND + +/*==============================SuppressFinalize================================ +**Action: Indicate that an object's finalizer should not be run by the system +**Arguments: Object of interest +**Exceptions: None +==============================================================================*/ +FCIMPL1(void, GCInterface::SuppressFinalize, Object *obj) +{ + FCALL_CONTRACT; + + // Checked by the caller + _ASSERTE(obj != NULL); + + if (!obj->GetMethodTable ()->HasFinalizer()) + return; + + GCHeap::GetGCHeap()->SetFinalizationRun(obj); + FC_GC_POLL(); +} +FCIMPLEND + + +/*============================ReRegisterForFinalize============================== +**Action: Indicate that an object's finalizer should be run by the system. +**Arguments: Object of interest +**Exceptions: None +==============================================================================*/ +FCIMPL1(void, GCInterface::ReRegisterForFinalize, Object *obj) +{ + FCALL_CONTRACT; + + // Checked by the caller + _ASSERTE(obj != NULL); + + if (obj->GetMethodTable()->HasFinalizer()) + { + HELPER_METHOD_FRAME_BEGIN_1(obj); + GCHeap::GetGCHeap()->RegisterForFinalization(-1, obj); + HELPER_METHOD_FRAME_END(); + } +} +FCIMPLEND + +FORCEINLINE UINT64 GCInterface::InterlockedAdd (UINT64 *pAugend, UINT64 addend) { + WRAPPER_NO_CONTRACT; + STATIC_CONTRACT_SO_TOLERANT; + + UINT64 oldMemValue; + UINT64 newMemValue; + + do { + oldMemValue = *pAugend; + newMemValue = oldMemValue + addend; + + // check for overflow + if (newMemValue < oldMemValue) + { + newMemValue = UINT64_MAX; + } + } while (InterlockedCompareExchange64((LONGLONG*) pAugend, (LONGLONG) newMemValue, (LONGLONG) oldMemValue) != (LONGLONG) oldMemValue); + + return newMemValue; +} + +FORCEINLINE UINT64 GCInterface::InterlockedSub(UINT64 *pMinuend, UINT64 subtrahend) { + WRAPPER_NO_CONTRACT; + STATIC_CONTRACT_SO_TOLERANT; + + UINT64 oldMemValue; + UINT64 newMemValue; + + do { + oldMemValue = *pMinuend; + newMemValue = oldMemValue - subtrahend; + + // check for underflow + if (newMemValue > oldMemValue) + newMemValue = 0; + + } while (InterlockedCompareExchange64((LONGLONG*) pMinuend, (LONGLONG) newMemValue, (LONGLONG) oldMemValue) != (LONGLONG) oldMemValue); + + return newMemValue; +} + +void QCALLTYPE GCInterface::_AddMemoryPressure(UINT64 bytesAllocated) +{ + QCALL_CONTRACT; + + // AddMemoryPressure could cause a GC, so we need a frame + BEGIN_QCALL; + AddMemoryPressure(bytesAllocated); + END_QCALL; +} + +void GCInterface::AddMemoryPressure(UINT64 bytesAllocated) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + SendEtwAddMemoryPressureEvent(bytesAllocated); + + UINT64 newMemValue = InterlockedAdd(&m_ulMemPressure, bytesAllocated); + + if (newMemValue > m_ulThreshold) + { + INT32 gen_collect = 0; + { + GCX_PREEMP(); + CrstHolder holder(&m_MemoryPressureLock); + + // to avoid collecting too often, take the max threshold of the linear and geometric growth + // heuristics. + UINT64 addMethod; + UINT64 multMethod; + UINT64 bytesAllocatedMax = (UINT64_MAX - m_ulThreshold) / 8; + + if (bytesAllocated >= bytesAllocatedMax) // overflow check + { + addMethod = UINT64_MAX; + } + else + { + addMethod = m_ulThreshold + bytesAllocated * 8; + } + + multMethod = newMemValue + newMemValue / 10; + if (multMethod < newMemValue) // overflow check + { + multMethod = UINT64_MAX; + } + + m_ulThreshold = (addMethod > multMethod) ? addMethod : multMethod; + for (int i = 0; i <= 1; i++) + { + if ((GCHeap::GetGCHeap()->CollectionCount(i) / RELATIVE_GC_RATIO) > GCHeap::GetGCHeap()->CollectionCount(i + 1)) + { + gen_collect = i + 1; + break; + } + } + } + + PREFIX_ASSUME(gen_collect <= 2); + + if ((gen_collect == 0) || (m_gc_counts[gen_collect] == GCHeap::GetGCHeap()->CollectionCount(gen_collect))) + { + GarbageCollectModeAny(gen_collect); + } + + for (int i = 0; i < 3; i++) + { + m_gc_counts [i] = GCHeap::GetGCHeap()->CollectionCount(i); + } + } +} + +#ifdef _WIN64 +const unsigned MIN_MEMORYPRESSURE_BUDGET = 4 * 1024 * 1024; // 4 MB +#else // _WIN64 +const unsigned MIN_MEMORYPRESSURE_BUDGET = 3 * 1024 * 1024; // 3 MB +#endif // _WIN64 + +const unsigned MAX_MEMORYPRESSURE_RATIO = 10; // 40 MB or 30 MB + + +// Resets pressure accounting after a gen2 GC has occurred. +void GCInterface::CheckCollectionCount() +{ + LIMITED_METHOD_CONTRACT; + + GCHeap * pHeap = GCHeap::GetGCHeap(); + + if (m_gc_counts[2] != pHeap->CollectionCount(2)) + { + for (int i = 0; i < 3; i++) + { + m_gc_counts[i] = pHeap->CollectionCount(i); + } + + m_iteration++; + + UINT p = m_iteration % NEW_PRESSURE_COUNT; + + m_addPressure[p] = 0; // new pressure will be accumulated here + m_remPressure[p] = 0; + } +} + + +// New AddMemoryPressure implementation (used by RCW and the CLRServicesImpl class) +// +// 1. Less sensitive than the original implementation (start budget 3 MB) +// 2. Focuses more on newly added memory pressure +// 3. Budget adjusted by effectiveness of last 3 triggered GC (add / remove ratio, max 10x) +// 4. Budget maxed with 30% of current managed GC size +// 5. If Gen2 GC is happening naturally, ignore past pressure +// +// Here's a brief description of the ideal algorithm for Add/Remove memory pressure: +// Do a GC when (HeapStart < X * MemPressureGrowth) where +// - HeapStart is GC Heap size after doing the last GC +// - MemPressureGrowth is the net of Add and Remove since the last GC +// - X is proportional to our guess of the ummanaged memory death rate per GC interval, +// and would be calculated based on historic data using standard exponential approximation: +// Xnew = UMDeath/UMTotal * 0.5 + Xprev +// +void GCInterface::NewAddMemoryPressure(UINT64 bytesAllocated) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + CheckCollectionCount(); + + UINT p = m_iteration % NEW_PRESSURE_COUNT; + + UINT64 newMemValue = InterlockedAdd(&m_addPressure[p], bytesAllocated); + + static_assert(NEW_PRESSURE_COUNT == 4, "NewAddMemoryPressure contains unrolled loops which depend on NEW_PRESSURE_COUNT"); + + UINT64 add = m_addPressure[0] + m_addPressure[1] + m_addPressure[2] + m_addPressure[3] - m_addPressure[p]; + UINT64 rem = m_remPressure[0] + m_remPressure[1] + m_remPressure[2] + m_remPressure[3] - m_remPressure[p]; + + STRESS_LOG4(LF_GCINFO, LL_INFO10000, "AMP Add: %I64u => added=%I64u total_added=%I64u total_removed=%I64u", + bytesAllocated, newMemValue, add, rem); + + SendEtwAddMemoryPressureEvent(bytesAllocated); + + if (newMemValue >= MIN_MEMORYPRESSURE_BUDGET) + { + UINT64 budget = MIN_MEMORYPRESSURE_BUDGET; + + if (m_iteration >= NEW_PRESSURE_COUNT) // wait until we have enough data points + { + // Adjust according to effectiveness of GC + // Scale budget according to past m_addPressure / m_remPressure ratio + if (add >= rem * MAX_MEMORYPRESSURE_RATIO) + { + budget = MIN_MEMORYPRESSURE_BUDGET * MAX_MEMORYPRESSURE_RATIO; + } + else if (add > rem) + { + CONSISTENCY_CHECK(rem != 0); + + // Avoid overflow by calculating addPressure / remPressure as fixed point (1 = 1024) + budget = (add * 1024 / rem) * budget / 1024; + } + } + + // If still over budget, check current managed heap size + if (newMemValue >= budget) + { + GCHeap *pGCHeap = GCHeap::GetGCHeap(); + UINT64 heapOver3 = pGCHeap->GetCurrentObjSize() / 3; + + if (budget < heapOver3) // Max + { + budget = heapOver3; + } + + if (newMemValue >= budget) + { + // last check - if we would exceed 20% of GC "duty cycle", do not trigger GC at this time + if ((pGCHeap->GetNow() - pGCHeap->GetLastGCStartTime(2)) > (pGCHeap->GetLastGCDuration(2) * 5)) + { + STRESS_LOG6(LF_GCINFO, LL_INFO10000, "AMP Budget: pressure=%I64u ? budget=%I64u (total_added=%I64u, total_removed=%I64u, mng_heap=%I64u) pos=%d", + newMemValue, budget, add, rem, heapOver3 * 3, m_iteration); + + GarbageCollectModeAny(2); + + CheckCollectionCount(); + } + } + } + } +} + +void QCALLTYPE GCInterface::_RemoveMemoryPressure(UINT64 bytesAllocated) +{ + QCALL_CONTRACT; + + BEGIN_QCALL; + RemoveMemoryPressure(bytesAllocated); + END_QCALL; +} + +void GCInterface::RemoveMemoryPressure(UINT64 bytesAllocated) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + SendEtwRemoveMemoryPressureEvent(bytesAllocated); + + UINT64 newMemValue = InterlockedSub(&m_ulMemPressure, bytesAllocated); + UINT64 new_th; + UINT64 bytesAllocatedMax = (m_ulThreshold / 4); + UINT64 addMethod; + UINT64 multMethod = (m_ulThreshold - m_ulThreshold / 20); // can never underflow + if (bytesAllocated >= bytesAllocatedMax) // protect against underflow + { + m_ulThreshold = MIN_GC_MEMORYPRESSURE_THRESHOLD; + return; + } + else + { + addMethod = m_ulThreshold - bytesAllocated * 4; + } + + new_th = (addMethod < multMethod) ? addMethod : multMethod; + + if (newMemValue <= new_th) + { + GCX_PREEMP(); + CrstHolder holder(&m_MemoryPressureLock); + if (new_th > MIN_GC_MEMORYPRESSURE_THRESHOLD) + m_ulThreshold = new_th; + else + m_ulThreshold = MIN_GC_MEMORYPRESSURE_THRESHOLD; + + for (int i = 0; i < 3; i++) + { + m_gc_counts [i] = GCHeap::GetGCHeap()->CollectionCount(i); + } + } +} + +void GCInterface::NewRemoveMemoryPressure(UINT64 bytesAllocated) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + CheckCollectionCount(); + + UINT p = m_iteration % NEW_PRESSURE_COUNT; + + SendEtwRemoveMemoryPressureEvent(bytesAllocated); + + InterlockedAdd(&m_remPressure[p], bytesAllocated); + + STRESS_LOG2(LF_GCINFO, LL_INFO10000, "AMP Remove: %I64u => removed=%I64u", + bytesAllocated, m_remPressure[p]); +} + +inline void GCInterface::SendEtwAddMemoryPressureEvent(UINT64 bytesAllocated) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + FireEtwIncreaseMemoryPressure(bytesAllocated, GetClrInstanceId()); +} + +// Out-of-line helper to avoid EH prolog/epilog in functions that otherwise don't throw. +NOINLINE void GCInterface::SendEtwRemoveMemoryPressureEvent(UINT64 bytesAllocated) +{ + CONTRACTL + { + NOTHROW; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + EX_TRY + { + FireEtwDecreaseMemoryPressure(bytesAllocated, GetClrInstanceId()); + } + EX_CATCH + { + // Ignore failures + } + EX_END_CATCH(SwallowAllExceptions) +} + +// Out-of-line helper to avoid EH prolog/epilog in functions that otherwise don't throw. +NOINLINE void GCInterface::GarbageCollectModeAny(int generation) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_ANY; + } + CONTRACTL_END; + + GCX_COOP(); + GCHeap::GetGCHeap()->GarbageCollect(generation, FALSE, collection_non_blocking); +} + +// +// COMInterlocked +// + +#include <optsmallperfcritical.h> + +FCIMPL2(INT32,COMInterlocked::Exchange, INT32 *location, INT32 value) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + return FastInterlockExchange((LONG *) location, value); +} +FCIMPLEND + +FCIMPL2_IV(INT64,COMInterlocked::Exchange64, INT64 *location, INT64 value) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + return FastInterlockExchangeLong((INT64 *) location, value); +} +FCIMPLEND + +FCIMPL2(LPVOID,COMInterlocked::ExchangePointer, LPVOID *location, LPVOID value) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + FCUnique(0x15); + return FastInterlockExchangePointer(location, value); +} +FCIMPLEND + +FCIMPL3(INT32, COMInterlocked::CompareExchange, INT32* location, INT32 value, INT32 comparand) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + return FastInterlockCompareExchange((LONG*)location, value, comparand); +} +FCIMPLEND + +FCIMPL4(INT32, COMInterlocked::CompareExchangeReliableResult, INT32* location, INT32 value, INT32 comparand, CLR_BOOL* succeeded) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + INT32 result = FastInterlockCompareExchange((LONG*)location, value, comparand); + if (result == comparand) + *succeeded = true; + + return result; +} +FCIMPLEND + +FCIMPL3_IVV(INT64, COMInterlocked::CompareExchange64, INT64* location, INT64 value, INT64 comparand) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + return FastInterlockCompareExchangeLong((INT64*)location, value, comparand); +} +FCIMPLEND + +FCIMPL3(LPVOID,COMInterlocked::CompareExchangePointer, LPVOID *location, LPVOID value, LPVOID comparand) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + FCUnique(0x59); + return FastInterlockCompareExchangePointer(location, value, comparand); +} +FCIMPLEND + +FCIMPL2_IV(float,COMInterlocked::ExchangeFloat, float *location, float value) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + LONG ret = FastInterlockExchange((LONG *) location, *(LONG*)&value); + return *(float*)&ret; +} +FCIMPLEND + +FCIMPL2_IV(double,COMInterlocked::ExchangeDouble, double *location, double value) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + + INT64 ret = FastInterlockExchangeLong((INT64 *) location, *(INT64*)&value); + return *(double*)&ret; +} +FCIMPLEND + +FCIMPL3_IVV(float,COMInterlocked::CompareExchangeFloat, float *location, float value, float comparand) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + LONG ret = (LONG)FastInterlockCompareExchange((LONG*) location, *(LONG*)&value, *(LONG*)&comparand); + return *(float*)&ret; +} +FCIMPLEND + +FCIMPL3_IVV(double,COMInterlocked::CompareExchangeDouble, double *location, double value, double comparand) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + INT64 ret = (INT64)FastInterlockCompareExchangeLong((INT64*) location, *(INT64*)&value, *(INT64*)&comparand); + return *(double*)&ret; +} +FCIMPLEND + +FCIMPL2(LPVOID,COMInterlocked::ExchangeObject, LPVOID*location, LPVOID value) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + LPVOID ret = FastInterlockExchangePointer(location, value); +#ifdef _DEBUG + Thread::ObjectRefAssign((OBJECTREF *)location); +#endif + ErectWriteBarrier((OBJECTREF*) location, ObjectToOBJECTREF((Object*) value)); + return ret; +} +FCIMPLEND + +FCIMPL2_VV(void,COMInterlocked::ExchangeGeneric, FC_TypedByRef location, FC_TypedByRef value) +{ + FCALL_CONTRACT; + + LPVOID* loc = (LPVOID*)location.data; + if( NULL == loc) { + FCThrowVoid(kNullReferenceException); + } + + LPVOID val = *(LPVOID*)value.data; + *(LPVOID*)value.data = FastInterlockExchangePointer(loc, val); +#ifdef _DEBUG + Thread::ObjectRefAssign((OBJECTREF *)loc); +#endif + ErectWriteBarrier((OBJECTREF*) loc, ObjectToOBJECTREF((Object*) val)); +} +FCIMPLEND + +FCIMPL3_VVI(void,COMInterlocked::CompareExchangeGeneric, FC_TypedByRef location, FC_TypedByRef value, LPVOID comparand) +{ + FCALL_CONTRACT; + + LPVOID* loc = (LPVOID*)location.data; + LPVOID val = *(LPVOID*)value.data; + if( NULL == loc) { + FCThrowVoid(kNullReferenceException); + } + + LPVOID ret = FastInterlockCompareExchangePointer(loc, val, comparand); + *(LPVOID*)value.data = ret; + if(ret == comparand) + { +#ifdef _DEBUG + Thread::ObjectRefAssign((OBJECTREF *)loc); +#endif + ErectWriteBarrier((OBJECTREF*) loc, ObjectToOBJECTREF((Object*) val)); + } +} +FCIMPLEND + +FCIMPL3(LPVOID,COMInterlocked::CompareExchangeObject, LPVOID *location, LPVOID value, LPVOID comparand) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + // <TODO>@todo: only set ref if is updated</TODO> + LPVOID ret = FastInterlockCompareExchangePointer(location, value, comparand); + if (ret == comparand) { +#ifdef _DEBUG + Thread::ObjectRefAssign((OBJECTREF *)location); +#endif + ErectWriteBarrier((OBJECTREF*) location, ObjectToOBJECTREF((Object*) value)); + } + return ret; +} +FCIMPLEND + +FCIMPL2(INT32,COMInterlocked::ExchangeAdd32, INT32 *location, INT32 value) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + return FastInterlockExchangeAdd((LONG *) location, value); +} +FCIMPLEND + +FCIMPL2_IV(INT64,COMInterlocked::ExchangeAdd64, INT64 *location, INT64 value) +{ + FCALL_CONTRACT; + + if( NULL == location) { + FCThrow(kNullReferenceException); + } + + return FastInterlockExchangeAddLong((INT64 *) location, value); +} +FCIMPLEND + +#include <optdefault.h> + + + +FCIMPL6(INT32, ManagedLoggingHelper::GetRegistryLoggingValues, CLR_BOOL* bLoggingEnabled, CLR_BOOL* bLogToConsole, INT32 *iLogLevel, CLR_BOOL* bPerfWarnings, CLR_BOOL* bCorrectnessWarnings, CLR_BOOL* bSafeHandleStackTraces) +{ + FCALL_CONTRACT; + + INT32 logFacility = 0; + + HELPER_METHOD_FRAME_BEGIN_RET_0(); + + *bLoggingEnabled = (bool)(g_pConfig->GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_LogEnable, 0)!=0); + *bLogToConsole = (bool)(g_pConfig->GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_LogToConsole, 0)!=0); + *iLogLevel = (INT32)(g_pConfig->GetConfigDWORD_DontUse_(CLRConfig::EXTERNAL_LogLevel, 0)); + logFacility = (INT32)(g_pConfig->GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_ManagedLogFacility, 0)); + *bPerfWarnings = (bool)(g_pConfig->GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_BCLPerfWarnings, 0)!=0); + *bCorrectnessWarnings = (bool)(g_pConfig->GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_BCLCorrectnessWarnings, 0)!=0); + *bSafeHandleStackTraces = (bool)(g_pConfig->GetConfigDWORD_DontUse_(CLRConfig::INTERNAL_SafeHandleStackTraces, 0)!=0); + + HELPER_METHOD_FRAME_END(); \ + + return logFacility; +} +FCIMPLEND + +// Return true if the valuetype does not contain pointer and is tightly packed +FCIMPL1(FC_BOOL_RET, ValueTypeHelper::CanCompareBits, Object* obj) +{ + FCALL_CONTRACT; + + _ASSERTE(obj != NULL); + MethodTable* mt = obj->GetMethodTable(); + FC_RETURN_BOOL(!mt->ContainsPointers() && !mt->IsNotTightlyPacked()); +} +FCIMPLEND + +FCIMPL2(FC_BOOL_RET, ValueTypeHelper::FastEqualsCheck, Object* obj1, Object* obj2) +{ + FCALL_CONTRACT; + + _ASSERTE(obj1 != NULL); + _ASSERTE(obj2 != NULL); + _ASSERTE(!obj1->GetMethodTable()->ContainsPointers()); + _ASSERTE(obj1->GetSize() == obj2->GetSize()); + + TypeHandle pTh = obj1->GetTypeHandle(); + + FC_RETURN_BOOL(memcmp(obj1->GetData(),obj2->GetData(),pTh.GetSize()) == 0); +} +FCIMPLEND + +static BOOL CanUseFastGetHashCodeHelper(MethodTable *mt) +{ + LIMITED_METHOD_CONTRACT; + return !mt->ContainsPointers() && !mt->IsNotTightlyPacked(); +} + +static INT32 FastGetValueTypeHashCodeHelper(MethodTable *mt, void *pObjRef) +{ + CONTRACTL + { + NOTHROW; + GC_NOTRIGGER; + MODE_COOPERATIVE; + SO_TOLERANT; + PRECONDITION(CanUseFastGetHashCodeHelper(mt)); + } CONTRACTL_END; + + INT32 hashCode = 0; + INT32 *pObj = (INT32*)pObjRef; + + // this is a struct with no refs and no "strange" offsets, just go through the obj and xor the bits + INT32 size = mt->GetNumInstanceFieldBytes(); + for (INT32 i = 0; i < (INT32)(size / sizeof(INT32)); i++) + hashCode ^= *pObj++; + + return hashCode; +} + +static INT32 RegularGetValueTypeHashCode(MethodTable *mt, void *pObjRef) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + } CONTRACTL_END; + + INT32 hashCode = 0; + INT32 *pObj = (INT32*)pObjRef; + + // While we shouln't get here directly from ValueTypeHelper::GetHashCode, if we recurse we need to + // be able to handle getting the hashcode for an embedded structure whose hashcode is computed by the fast path. + if (CanUseFastGetHashCodeHelper(mt)) + { + return FastGetValueTypeHashCodeHelper(mt, pObjRef); + } + else + { + // it's looking ugly so we'll use the old behavior in managed code. Grab the first non-null + // field and return its hash code or 'it' as hash code + // <TODO> Note that the old behavior has already been broken for value types + // that is qualified for CanUseFastGetHashCodeHelper. So maybe we should + // change the implementation here to use all fields instead of just the 1st one. + // </TODO> + // + // <TODO> check this approximation - we may be losing exact type information </TODO> + ApproxFieldDescIterator fdIterator(mt, ApproxFieldDescIterator::INSTANCE_FIELDS); + INT32 count = (INT32)fdIterator.Count(); + + if (count != 0) + { + for (INT32 i = 0; i < count; i++) + { + FieldDesc *field = fdIterator.Next(); + _ASSERTE(!field->IsRVA()); + void *pFieldValue = (BYTE *)pObj + field->GetOffsetUnsafe(); + if (field->IsObjRef()) + { + // if we get an object reference we get the hash code out of that + if (*(Object**)pFieldValue != NULL) + { + + OBJECTREF fieldObjRef = ObjectToOBJECTREF(*(Object **) pFieldValue); + GCPROTECT_BEGIN(fieldObjRef); + + MethodDescCallSite getHashCode(METHOD__OBJECT__GET_HASH_CODE, &fieldObjRef); + + // Make the call. + ARG_SLOT arg[1] = {ObjToArgSlot(fieldObjRef)}; + hashCode = getHashCode.Call_RetI4(arg); + + GCPROTECT_END(); + } + else + { + // null object reference, try next + continue; + } + } + else + { + UINT fieldSize = field->LoadSize(); + INT32 *pValue = (INT32*)pFieldValue; + CorElementType fieldType = field->GetFieldType(); + if (fieldType != ELEMENT_TYPE_VALUETYPE) + { + for (INT32 j = 0; j < (INT32)(fieldSize / sizeof(INT32)); j++) + hashCode ^= *pValue++; + } + else + { + // got another value type. Get the type + TypeHandle fieldTH = field->LookupFieldTypeHandle(); // the type was loaded already + _ASSERTE(!fieldTH.IsNull()); + hashCode = RegularGetValueTypeHashCode(fieldTH.GetMethodTable(), pValue); + } + } + break; + } + } + } + return hashCode; +} + +// The default implementation of GetHashCode() for all value types. +// Note that this implementation reveals the value of the fields. +// So if the value type contains any sensitive information it should +// implement its own GetHashCode(). +FCIMPL1(INT32, ValueTypeHelper::GetHashCode, Object* objUNSAFE) +{ + FCALL_CONTRACT; + + if (objUNSAFE == NULL) + FCThrow(kNullReferenceException); + + OBJECTREF obj = ObjectToOBJECTREF(objUNSAFE); + VALIDATEOBJECTREF(obj); + + INT32 hashCode = 0; + MethodTable *pMT = objUNSAFE->GetMethodTable(); + + // We don't want to expose the method table pointer in the hash code + // Let's use the typeID instead. + UINT32 typeID = pMT->LookupTypeID(); + if (typeID == TypeIDProvider::INVALID_TYPE_ID) + { + // If the typeID has yet to be generated, fall back to GetTypeID + // This only needs to be done once per MethodTable + HELPER_METHOD_FRAME_BEGIN_RET_1(obj); + typeID = pMT->GetTypeID(); + HELPER_METHOD_FRAME_END(); + } + + // To get less colliding and more evenly distributed hash codes, + // we munge the class index with two big prime numbers + hashCode = typeID * 711650207 + 2506965631U; + + if (CanUseFastGetHashCodeHelper(pMT)) + { + hashCode ^= FastGetValueTypeHashCodeHelper(pMT, obj->UnBox()); + } + else + { + HELPER_METHOD_FRAME_BEGIN_RET_1(obj); + hashCode ^= RegularGetValueTypeHashCode(pMT, obj->UnBox()); + HELPER_METHOD_FRAME_END(); + } + + return hashCode; +} +FCIMPLEND + +static LONG s_dwSeed; + +FCIMPL1(INT32, ValueTypeHelper::GetHashCodeOfPtr, LPVOID ptr) +{ + FCALL_CONTRACT; + + INT32 hashCode = (INT32)((INT64)(ptr)); + + if (hashCode == 0) + { + return 0; + } + + DWORD dwSeed = s_dwSeed; + + // Initialize s_dwSeed lazily + if (dwSeed == 0) + { + // We use the first non-0 pointer as the seed, all hashcodes will be based off that. + // This is to make sure that we only reveal relative memory addresses and never absolute ones. + dwSeed = hashCode; + InterlockedCompareExchange(&s_dwSeed, dwSeed, 0); + dwSeed = s_dwSeed; + } + _ASSERTE(dwSeed != 0); + + return hashCode - dwSeed; +} +FCIMPLEND + +#ifndef FEATURE_CORECLR +FCIMPL1(OBJECTHANDLE, SizedRefHandle::Initialize, Object* _obj) +{ + FCALL_CONTRACT; + + OBJECTHANDLE result = 0; + OBJECTREF obj(_obj); + + HELPER_METHOD_FRAME_BEGIN_RET_0(); + + result = GetAppDomain()->CreateSizedRefHandle(obj); + + HELPER_METHOD_FRAME_END(); + + return result; +} +FCIMPLEND + +FCIMPL1(VOID, SizedRefHandle::Free, OBJECTHANDLE handle) +{ + FCALL_CONTRACT; + + _ASSERTE(handle != NULL); + + HELPER_METHOD_FRAME_BEGIN_0(); + + DestroySizedRefHandle(handle); + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +FCIMPL1(LPVOID, SizedRefHandle::GetTarget, OBJECTHANDLE handle) +{ + FCALL_CONTRACT; + + _ASSERTE(handle != NULL); + + OBJECTREF objRef = NULL; + + objRef = ObjectFromHandle(handle); + + FCUnique(0x33); + return *((LPVOID*)&objRef); +} +FCIMPLEND + +FCIMPL1(INT64, SizedRefHandle::GetApproximateSize, OBJECTHANDLE handle) +{ + FCALL_CONTRACT; + + _ASSERTE(handle != NULL); + + return (INT64)HndGetHandleExtraInfo(handle); +} +FCIMPLEND +#endif //!FEATURE_CORECLR + +#ifdef FEATURE_CORECLR +COMNlsHashProvider COMNlsHashProvider::s_NlsHashProvider; +#endif // FEATURE_CORECLR + + +COMNlsHashProvider::COMNlsHashProvider() +{ + LIMITED_METHOD_CONTRACT; + +#ifdef FEATURE_RANDOMIZED_STRING_HASHING + bUseRandomHashing = FALSE; + pEntropy = NULL; + pDefaultSeed = NULL; +#endif // FEATURE_RANDOMIZED_STRING_HASHING +} + +INT32 COMNlsHashProvider::HashString(LPCWSTR szStr, SIZE_T strLen, BOOL forceRandomHashing, INT64 additionalEntropy) +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + +#ifndef FEATURE_RANDOMIZED_STRING_HASHING + _ASSERTE(forceRandomHashing == false); + _ASSERTE(additionalEntropy == 0); +#endif + +#ifdef FEATURE_RANDOMIZED_STRING_HASHING + if(bUseRandomHashing || forceRandomHashing) + { + int marvinResult[SYMCRYPT_MARVIN32_RESULT_SIZE / sizeof(int)]; + + if(additionalEntropy == 0) + { + SymCryptMarvin32(GetDefaultSeed(), (PCBYTE) szStr, strLen * sizeof(WCHAR), (PBYTE) &marvinResult); + } + else + { + SYMCRYPT_MARVIN32_EXPANDED_SEED seed; + CreateMarvin32Seed(additionalEntropy, &seed); + SymCryptMarvin32(&seed, (PCBYTE) szStr, strLen * sizeof(WCHAR), (PBYTE) &marvinResult); + } + + return marvinResult[0] ^ marvinResult[1]; + } + else + { +#endif // FEATURE_RANDOMIZED_STRING_HASHING + return ::HashString(szStr); +#ifdef FEATURE_RANDOMIZED_STRING_HASHING + } +#endif // FEATURE_RANDOMIZED_STRING_HASHING +} + + +INT32 COMNlsHashProvider::HashSortKey(PCBYTE pSrc, SIZE_T cbSrc, BOOL forceRandomHashing, INT64 additionalEntropy) +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + +#ifndef FEATURE_RANDOMIZED_STRING_HASHING + _ASSERTE(forceRandomHashing == false); + _ASSERTE(additionalEntropy == 0); +#endif + +#ifdef FEATURE_RANDOMIZED_STRING_HASHING + if(bUseRandomHashing || forceRandomHashing) + { + int marvinResult[SYMCRYPT_MARVIN32_RESULT_SIZE / sizeof(int)]; + + // Sort Keys are terminated with a null byte which we didn't hash using the old algorithm, + // so we don't have it with Marvin32 either. + if(additionalEntropy == 0) + { + SymCryptMarvin32(GetDefaultSeed(), pSrc, cbSrc - 1, (PBYTE) &marvinResult); + } + else + { + SYMCRYPT_MARVIN32_EXPANDED_SEED seed; + CreateMarvin32Seed(additionalEntropy, &seed); + SymCryptMarvin32(&seed, pSrc, cbSrc - 1, (PBYTE) &marvinResult); + } + + return marvinResult[0] ^ marvinResult[1]; + } + else + { +#endif // FEATURE_RANDOMIZED_STRING_HASHING + // Ok, lets build the hashcode -- mostly lifted from GetHashCode() in String.cs, for strings. + int hash1 = 5381; + int hash2 = hash1; + const BYTE *pB = pSrc; + BYTE c; + + while (pB != 0 && *pB != 0) { + hash1 = ((hash1 << 5) + hash1) ^ *pB; + c = pB[1]; + + // + // FUTURE: Update NewAPis::LCMapStringEx to perhaps use a different, bug free, Win32 API on Win2k3 to workaround the issue discussed below. + // + // On Win2k3 Server, LCMapStringEx(LCMAP_SORTKEY) output does not correspond to CompareString in all cases, breaking the .NET GetHashCode<->Equality Contract + // Due to a fluke in our GetHashCode method, we avoided this issue due to the break out of the loop on the binary-zero byte. + // + if (c == 0) + break; + + hash2 = ((hash2 << 5) + hash2) ^ c; + pB += 2; + } + + return hash1 + (hash2 * 1566083941); + +#ifdef FEATURE_RANDOMIZED_STRING_HASHING + } +#endif // FEATURE_RANDOMIZED_STRING_HASHING + +} + +INT32 COMNlsHashProvider::HashiStringKnownLower80(LPCWSTR szStr, INT32 strLen, BOOL forceRandomHashing, INT64 additionalEntropy) +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + +#ifndef FEATURE_RANDOMIZED_STRING_HASHING + _ASSERTE(forceRandomHashing == false); + _ASSERTE(additionalEntropy == 0); +#endif + +#ifdef FEATURE_RANDOMIZED_STRING_HASHING + if(bUseRandomHashing || forceRandomHashing) + { + WCHAR buf[SYMCRYPT_MARVIN32_INPUT_BLOCK_SIZE * 8]; + SYMCRYPT_MARVIN32_STATE marvinState; + SYMCRYPT_MARVIN32_EXPANDED_SEED seed; + + if(additionalEntropy == 0) + { + SymCryptMarvin32Init(&marvinState, GetDefaultSeed()); + } + else + { + CreateMarvin32Seed(additionalEntropy, &seed); + SymCryptMarvin32Init(&marvinState, &seed); + } + + LPCWSTR szEnd = szStr + strLen; + + const UINT A_TO_Z_RANGE = (UINT)('z' - 'a'); + + while (szStr != szEnd) + { + size_t count = (sizeof(buf) / sizeof(buf[0])); + + if ((size_t)(szEnd - szStr) < count) + count = (size_t)(szEnd - szStr); + + for (size_t i = 0; i<count; i++) + { + WCHAR c = szStr[i]; + + if ((UINT)(c - 'a') <= A_TO_Z_RANGE) // if (c >='a' && c <= 'z') + { + //If we have a lowercase character, ANDing off 0x20 + // will make it an uppercase character. + c &= ~0x20; + } + + buf[i] = c; + } + + szStr += count; + + SymCryptMarvin32Append(&marvinState, (PCBYTE) &buf, sizeof(WCHAR) * count); + } + + int marvinResult[SYMCRYPT_MARVIN32_RESULT_SIZE / sizeof(int)]; + SymCryptMarvin32Result(&marvinState, (PBYTE) &marvinResult); + return marvinResult[0] ^ marvinResult[1]; + } + else + { +#endif // FEATURE_RANDOMIZED_STRING_HASHING + return ::HashiStringKnownLower80(szStr); +#ifdef FEATURE_RANDOMIZED_STRING_HASHING + } +#endif // FEATURE_RANDOMIZED_STRING_HASHING +} + + +#ifdef FEATURE_RANDOMIZED_STRING_HASHING +void COMNlsHashProvider::InitializeDefaultSeed() +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + PCBYTE pEntropy = GetEntropy(); + AllocMemHolder<SYMCRYPT_MARVIN32_EXPANDED_SEED> pSeed(GetAppDomain()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(SYMCRYPT_MARVIN32_EXPANDED_SEED)))); + SymCryptMarvin32ExpandSeed(pSeed, pEntropy, SYMCRYPT_MARVIN32_SEED_SIZE); + + if(InterlockedCompareExchangeT(&pDefaultSeed, (PCSYMCRYPT_MARVIN32_EXPANDED_SEED) pSeed, NULL) == NULL) + { + pSeed.SuppressRelease(); + } +} + +PCSYMCRYPT_MARVIN32_EXPANDED_SEED COMNlsHashProvider::GetDefaultSeed() +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if(pDefaultSeed == NULL) + { + InitializeDefaultSeed(); + } + + return pDefaultSeed; +} + +PCBYTE COMNlsHashProvider::GetEntropy() +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + if(pEntropy == NULL) + { + AllocMemHolder<BYTE> pNewEntropy(GetAppDomain()->GetLowFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(SYMCRYPT_MARVIN32_SEED_SIZE)))); + +#ifdef FEATURE_PAL + PAL_Random(TRUE, pNewEntropy, SYMCRYPT_MARVIN32_SEED_SIZE); +#else + HCRYPTPROV hCryptProv; + WszCryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT); + CryptGenRandom(hCryptProv, SYMCRYPT_MARVIN32_SEED_SIZE, pNewEntropy); + CryptReleaseContext(hCryptProv, 0); +#endif + + if(InterlockedCompareExchangeT(&pEntropy, (PBYTE) pNewEntropy, NULL) == NULL) + { + pNewEntropy.SuppressRelease(); + } + } + + return (PCBYTE) pEntropy; +} + + +void COMNlsHashProvider::CreateMarvin32Seed(INT64 additionalEntropy, PSYMCRYPT_MARVIN32_EXPANDED_SEED pExpandedMarvinSeed) +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + MODE_ANY; + } + CONTRACTL_END; + + INT64 *pEntropy = (INT64*) GetEntropy(); + INT64 entropy; + + entropy = *pEntropy ^ additionalEntropy; + + SymCryptMarvin32ExpandSeed(pExpandedMarvinSeed, (PCBYTE) &entropy, SYMCRYPT_MARVIN32_SEED_SIZE); +} +#endif // FEATURE_RANDOMIZED_STRING_HASHING + +#ifdef FEATURE_COREFX_GLOBALIZATION +INT32 QCALLTYPE CoreFxGlobalization::HashSortKey(PCBYTE pSortKey, INT32 cbSortKey, BOOL forceRandomizedHashing, INT64 additionalEntropy) +{ + QCALL_CONTRACT; + + INT32 retVal = 0; + + BEGIN_QCALL; + + retVal = COMNlsHashProvider::s_NlsHashProvider.HashSortKey(pSortKey, cbSortKey, forceRandomizedHashing, additionalEntropy); + + END_QCALL; + + return retVal; +} +#endif //FEATURE_COREFX_GLOBALIZATION + +static MethodTable * g_pStreamMT; +static WORD g_slotBeginRead, g_slotEndRead; +static WORD g_slotBeginWrite, g_slotEndWrite; + +static bool HasOverriddenStreamMethod(MethodTable * pMT, WORD slot) +{ + CONTRACTL{ + NOTHROW; + GC_NOTRIGGER; + MODE_ANY; + SO_TOLERANT; + } CONTRACTL_END; + + PCODE actual = pMT->GetRestoredSlot(slot); + PCODE base = g_pStreamMT->GetRestoredSlot(slot); + if (actual == base) + return false; + + if (!g_pStreamMT->IsZapped()) + { + // If mscorlib is JITed, the slots can be patched and thus we need to compare the actual MethodDescs + // to detect match reliably + if (MethodTable::GetMethodDescForSlotAddress(actual) == MethodTable::GetMethodDescForSlotAddress(base)) + return false; + } + + return true; +} + +FCIMPL1(FC_BOOL_RET, StreamNative::HasOverriddenBeginEndRead, Object *stream) +{ + FCALL_CONTRACT; + + if (stream == NULL) + FC_RETURN_BOOL(TRUE); + + if (g_pStreamMT == NULL || g_slotBeginRead == 0 || g_slotEndRead == 0) + { + HELPER_METHOD_FRAME_BEGIN_RET_1(stream); + g_pStreamMT = MscorlibBinder::GetClass(CLASS__STREAM); + g_slotBeginRead = MscorlibBinder::GetMethod(METHOD__STREAM__BEGIN_READ)->GetSlot(); + g_slotEndRead = MscorlibBinder::GetMethod(METHOD__STREAM__END_READ)->GetSlot(); + HELPER_METHOD_FRAME_END(); + } + + MethodTable * pMT = stream->GetMethodTable(); + + FC_RETURN_BOOL(HasOverriddenStreamMethod(pMT, g_slotBeginRead) || HasOverriddenStreamMethod(pMT, g_slotEndRead)); +} +FCIMPLEND + +FCIMPL1(FC_BOOL_RET, StreamNative::HasOverriddenBeginEndWrite, Object *stream) +{ + FCALL_CONTRACT; + + if (stream == NULL) + FC_RETURN_BOOL(TRUE); + + if (g_pStreamMT == NULL || g_slotBeginWrite == 0 || g_slotEndWrite == 0) + { + HELPER_METHOD_FRAME_BEGIN_RET_1(stream); + g_pStreamMT = MscorlibBinder::GetClass(CLASS__STREAM); + g_slotBeginWrite = MscorlibBinder::GetMethod(METHOD__STREAM__BEGIN_WRITE)->GetSlot(); + g_slotEndWrite = MscorlibBinder::GetMethod(METHOD__STREAM__END_WRITE)->GetSlot(); + HELPER_METHOD_FRAME_END(); + } + + MethodTable * pMT = stream->GetMethodTable(); + + FC_RETURN_BOOL(HasOverriddenStreamMethod(pMT, g_slotBeginWrite) || HasOverriddenStreamMethod(pMT, g_slotEndWrite)); +} +FCIMPLEND |