// 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. /***************************************************************************** ** ** ** Corhlprpriv.h - ** ** ** *****************************************************************************/ #ifndef __CORHLPRPRIV_H__ #define __CORHLPRPRIV_H__ #include "corhlpr.h" #include "fstring.h" #if defined(_MSC_VER) && defined(_TARGET_X86_) #pragma optimize("y", on) // If routines don't get inlined, don't pay the EBP frame penalty #endif //***************************************************************************** // //***** Utility helpers // //***************************************************************************** #ifndef SOS_INCLUDE //***************************************************************************** // // **** CQuickBytes // This helper class is useful for cases where 90% of the time you allocate 512 // or less bytes for a data structure. This class contains a 512 byte buffer. // Alloc() will return a pointer to this buffer if your allocation is small // enough, otherwise it asks the heap for a larger buffer which is freed for // you. No mutex locking is required for the small allocation case, making the // code run faster, less heap fragmentation, etc... Each instance will allocate // 520 bytes, so use accordinly. // //***************************************************************************** namespace NSQuickBytesHelper { template struct _AllocBytes; template <> struct _AllocBytes { static BYTE *Invoke(SIZE_T iItems) { return NEW_THROWS(iItems); } }; template <> struct _AllocBytes { static BYTE *Invoke(SIZE_T iItems) { return NEW_NOTHROW(iItems); } }; }; void DECLSPEC_NORETURN ThrowHR(HRESULT hr); template class CQuickMemoryBase { protected: template static ELEM_T Min(ELEM_T a, ELEM_T b) { return a < b ? a : b; } template static ELEM_T Max(ELEM_T a, ELEM_T b) { return a < b ? b : a; } // bGrow - indicates that this is a resize and that the original data // needs to be copied over. // bThrow - indicates whether or not memory allocations will throw. template void *_Alloc(SIZE_T iItems) { #if defined(_BLD_CLR) && defined(_DEBUG) { // Exercise heap for OOM-fault injection purposes BYTE * pb = NSQuickBytesHelper::_AllocBytes::Invoke(iItems); _ASSERTE(!bThrow || pb != NULL); // _AllocBytes would have thrown if bThrow == TRUE if (pb == NULL) return NULL; // bThrow == FALSE and we failed to allocate memory delete [] pb; // Success, delete allocated memory. } #endif if (iItems <= cbTotal) { // Fits within existing memory allocation iSize = iItems; } else if (iItems <= SIZE) { // Will fit in internal buffer. if (pbBuff == NULL) { // Any previous allocation is in the internal buffer and the new // allocation fits in the internal buffer, so just update the size. iSize = iItems; cbTotal = SIZE; } else { // There was a previous allocation, sitting in pbBuff if (bGrow) { // If growing, need to copy any existing data over. memcpy(&rgData[0], pbBuff, Min(cbTotal, SIZE)); } delete [] pbBuff; pbBuff = NULL; iSize = iItems; cbTotal = SIZE; } } else { // Need to allocate a new buffer SIZE_T cbTotalNew = iItems + (bGrow ? INCREMENT : 0); BYTE * pbBuffNew = NSQuickBytesHelper::_AllocBytes::Invoke(cbTotalNew); if (!bThrow && pbBuffNew == NULL) { // Allocation failed. Zero out structure. if (pbBuff != NULL) { // Delete old buffer delete [] pbBuff; } pbBuff = NULL; iSize = 0; cbTotal = 0; return NULL; } if (bGrow && cbTotal > 0) { // If growing, need to copy any existing data over. memcpy(pbBuffNew, (BYTE *)Ptr(), Min(cbTotal, cbTotalNew)); } if (pbBuff != NULL) { // Delete old pre-existing buffer delete [] pbBuff; pbBuff = NULL; } pbBuff = pbBuffNew; cbTotal = cbTotalNew; iSize = iItems; } return Ptr(); } public: void Init() { pbBuff = 0; iSize = 0; cbTotal = SIZE; } void Destroy() { if (pbBuff) { delete [] pbBuff; pbBuff = 0; } } void *AllocThrows(SIZE_T iItems) { return _Alloc(iItems); } void *AllocNoThrow(SIZE_T iItems) { return _Alloc(iItems); } void ReSizeThrows(SIZE_T iItems) { _Alloc(iItems); } #ifdef __llvm__ // This makes sure that we will not get an undefined symbol // when building a release version of libcoreclr using LLVM. __attribute__((used)) #endif // __llvm__ HRESULT ReSizeNoThrow(SIZE_T iItems); void Shrink(SIZE_T iItems) { _ASSERTE(iItems <= cbTotal); iSize = iItems; } operator PVOID() { return ((pbBuff) ? pbBuff : (PVOID)&rgData[0]); } void *Ptr() { return ((pbBuff) ? pbBuff : (PVOID)&rgData[0]); } const void *Ptr() const { return ((pbBuff) ? pbBuff : (PVOID)&rgData[0]); } SIZE_T Size() const { return (iSize); } SIZE_T MaxSize() const { return (cbTotal); } void Maximize() { iSize = cbTotal; } // Convert UTF8 string to UNICODE string, optimized for speed HRESULT ConvertUtf8_UnicodeNoThrow(const char * utf8str) { bool allAscii; DWORD length; HRESULT hr = FString::Utf8_Unicode_Length(utf8str, & allAscii, & length); if (SUCCEEDED(hr)) { LPWSTR buffer = (LPWSTR) AllocNoThrow((length + 1) * sizeof(WCHAR)); if (buffer == NULL) { hr = E_OUTOFMEMORY; } else { hr = FString::Utf8_Unicode(utf8str, allAscii, buffer, length); } } return hr; } // Convert UTF8 string to UNICODE string, optimized for speed void ConvertUtf8_Unicode(const char * utf8str) { bool allAscii; DWORD length; HRESULT hr = FString::Utf8_Unicode_Length(utf8str, & allAscii, & length); if (SUCCEEDED(hr)) { LPWSTR buffer = (LPWSTR) AllocThrows((length + 1) * sizeof(WCHAR)); hr = FString::Utf8_Unicode(utf8str, allAscii, buffer, length); } if (FAILED(hr)) { ThrowHR(hr); } } // Convert UNICODE string to UTF8 string, optimized for speed void ConvertUnicode_Utf8(const WCHAR * pString) { bool allAscii; DWORD length; HRESULT hr = FString::Unicode_Utf8_Length(pString, & allAscii, & length); if (SUCCEEDED(hr)) { LPSTR buffer = (LPSTR) AllocThrows((length + 1) * sizeof(char)); hr = FString::Unicode_Utf8(pString, allAscii, buffer, length); } if (FAILED(hr)) { ThrowHR(hr); } } // Copy single byte string and hold it const char * SetStringNoThrow(const char * pStr, SIZE_T len) { LPSTR buffer = (LPSTR) AllocNoThrow(len + 1); if (buffer != NULL) { memcpy(buffer, pStr, len); buffer[len] = 0; } return buffer; } #ifdef DACCESS_COMPILE void EnumMemoryRegions(CLRDataEnumMemoryFlags flags) { // Assume that 'this' is enumerated, either explicitly // or because this class is embedded in another. DacEnumMemoryRegion(dac_cast(pbBuff), iSize); } #endif // DACCESS_COMPILE BYTE *pbBuff; SIZE_T iSize; // number of bytes used SIZE_T cbTotal; // total bytes allocated in the buffer // use UINT64 to enforce the alignment of the memory UINT64 rgData[(SIZE+sizeof(UINT64)-1)/sizeof(UINT64)]; }; // These should be multiples of 8 so that data can be naturally aligned. #define CQUICKBYTES_BASE_SIZE 512 #define CQUICKBYTES_INCREMENTAL_SIZE 128 class CQuickBytesBase : public CQuickMemoryBase { }; class CQuickBytes : public CQuickBytesBase { public: CQuickBytes() { Init(); } ~CQuickBytes() { Destroy(); } }; /* to be used as static variable - no constructor/destructor, assumes zero initialized memory */ class CQuickBytesStatic : public CQuickBytesBase { }; template class CQuickBytesSpecifySizeBase : public CQuickMemoryBase { }; template class CQuickBytesSpecifySize : public CQuickBytesSpecifySizeBase { public: CQuickBytesSpecifySize() { this->Init(); } ~CQuickBytesSpecifySize() { this->Destroy(); } }; /* to be used as static variable - no constructor/destructor, assumes zero initialized memory */ template class CQuickBytesSpecifySizeStatic : public CQuickBytesSpecifySizeBase { }; template class CQuickArrayBase : public CQuickBytesBase { public: T* AllocThrows(SIZE_T iItems) { CheckOverflowThrows(iItems); return (T*)CQuickBytesBase::AllocThrows(iItems * sizeof(T)); } void ReSizeThrows(SIZE_T iItems) { CheckOverflowThrows(iItems); CQuickBytesBase::ReSizeThrows(iItems * sizeof(T)); } T* AllocNoThrow(SIZE_T iItems) { if (!CheckOverflowNoThrow(iItems)) { return NULL; } return (T*)CQuickBytesBase::AllocNoThrow(iItems * sizeof(T)); } HRESULT ReSizeNoThrow(SIZE_T iItems) { if (!CheckOverflowNoThrow(iItems)) { return E_OUTOFMEMORY; } return CQuickBytesBase::ReSizeNoThrow(iItems * sizeof(T)); } void Shrink(SIZE_T iItems) { CQuickBytesBase::Shrink(iItems * sizeof(T)); } T* Ptr() { return (T*) CQuickBytesBase::Ptr(); } const T* Ptr() const { return (T*) CQuickBytesBase::Ptr(); } SIZE_T Size() const { return CQuickBytesBase::Size() / sizeof(T); } SIZE_T MaxSize() const { return CQuickBytesBase::cbTotal / sizeof(T); } T& operator[] (SIZE_T ix) { _ASSERTE(ix < Size()); return *(Ptr() + ix); } const T& operator[] (SIZE_T ix) const { _ASSERTE(ix < Size()); return *(Ptr() + ix); } private: inline BOOL CheckOverflowNoThrow(SIZE_T iItems) { SIZE_T totalSize = iItems * sizeof(T); if (totalSize / sizeof(T) != iItems) { return FALSE; } return TRUE; } inline void CheckOverflowThrows(SIZE_T iItems) { if (!CheckOverflowNoThrow(iItems)) { THROW_OUT_OF_MEMORY(); } } }; template class CQuickArray : public CQuickArrayBase { public: CQuickArray() { this->Init(); } ~CQuickArray() { this->Destroy(); } }; // This is actually more of a stack with array access. Essentially, you can // only add elements through Push and remove them through Pop, but you can // access and modify any random element with the index operator. You cannot // access elements that have not been added. template class CQuickArrayList : protected CQuickArray { private: SIZE_T m_curSize; public: // Make these specific functions public. using CQuickArray::AllocThrows; using CQuickArray::ReSizeThrows; using CQuickArray::AllocNoThrow; using CQuickArray::ReSizeNoThrow; using CQuickArray::MaxSize; CQuickArrayList() : m_curSize(0) { this->Init(); } ~CQuickArrayList() { this->Destroy(); } // Can only access values that have been pushed. T& operator[] (SIZE_T ix) { _ASSERTE(ix < m_curSize); return CQuickArray::operator[](ix); } // Can only access values that have been pushed. const T& operator[] (SIZE_T ix) const { _ASSERTE(ix < m_curSize); return CQuickArray::operator[](ix); } // THROWS: Resizes if necessary. void Push(const T & value) { // Resize if necessary - thows. if (m_curSize + 1 >= CQuickArray::Size()) ReSizeThrows((m_curSize + 1) * 2); // Append element to end of array. _ASSERTE(m_curSize + 1 < CQuickArray::Size()); SIZE_T ix = m_curSize++; (*this)[ix] = value; } T Pop() { _ASSERTE(m_curSize > 0); T retval = (*this)[m_curSize - 1]; INDEBUG(ZeroMemory(&(this->Ptr()[m_curSize - 1]), sizeof(T));) --m_curSize; return retval; } SIZE_T Size() const { return m_curSize; } void Shrink() { CQuickArray::Shrink(m_curSize); } }; /* to be used as static variable - no constructor/destructor, assumes zero initialized memory */ template class CQuickArrayStatic : public CQuickArrayBase { }; typedef CQuickArrayBase CQuickWSTRBase; typedef CQuickArray CQuickWSTR; typedef CQuickArrayStatic CQuickWSTRStatic; typedef CQuickArrayBase CQuickSTRBase; typedef CQuickArray CQuickSTR; typedef CQuickArrayStatic CQuickSTRStatic; class RidBitmap { public: HRESULT InsertToken(mdToken token) { HRESULT hr = S_OK; mdToken rid = RidFromToken(token); SIZE_T index = rid / 8; BYTE bit = (1 << (rid % 8)); if (index >= buffer.Size()) { SIZE_T oldSize = buffer.Size(); SIZE_T newSize = index+1+oldSize/8; IfFailRet(buffer.ReSizeNoThrow(newSize)); memset(&buffer[oldSize], 0, newSize-oldSize); } buffer[index] |= bit; return hr; } bool IsTokenInBitmap(mdToken token) { mdToken rid = RidFromToken(token); SIZE_T index = rid / 8; BYTE bit = (1 << (rid % 8)); return ((index < buffer.Size()) && (buffer[index] & bit)); } void Reset() { if (buffer.Size()) { memset(&buffer[0], 0, buffer.Size()); } } private: CQuickArray buffer; }; //***************************************************************************** // //***** Signature helpers // //***************************************************************************** HRESULT _CountBytesOfOneArg( PCCOR_SIGNATURE pbSig, ULONG *pcbTotal); HRESULT _GetFixedSigOfVarArg( // S_OK or error. PCCOR_SIGNATURE pvSigBlob, // [IN] point to a blob of CLR signature ULONG cbSigBlob, // [IN] size of signature CQuickBytes *pqbSig, // [OUT] output buffer for fixed part of VarArg Signature ULONG *pcbSigBlob); // [OUT] number of bytes written to the above output buffer #endif //!SOS_INCLUDE #if defined(_MSC_VER) && defined(_TARGET_X86_) #pragma optimize("", on) // restore command line default optimizations #endif //--------------------------------------------------------------------------------------- // // Reads compressed integer from buffer pData, fills the result to *pnDataOut. Advances buffer pointer. // Doesn't read behind the end of the buffer (the end starts at pDataEnd). // inline __checkReturn HRESULT CorSigUncompressData_EndPtr( PCCOR_SIGNATURE & pData, // [IN,OUT] Buffer PCCOR_SIGNATURE pDataEnd, // End of buffer DWORD * pnDataOut) // [OUT] Compressed integer read from the buffer { _ASSERTE(pData <= pDataEnd); HRESULT hr = S_OK; INT_PTR cbDataSize = pDataEnd - pData; if (cbDataSize > 4) { // Compressed integer cannot be bigger than 4 bytes cbDataSize = 4; } DWORD dwDataSize = (DWORD)cbDataSize; ULONG cbDataOutLength; IfFailRet(CorSigUncompressData( pData, dwDataSize, pnDataOut, &cbDataOutLength)); pData += cbDataOutLength; return hr; } // CorSigUncompressData_EndPtr //--------------------------------------------------------------------------------------- // // Reads CorElementType (1 byte) from buffer pData, fills the result to *pTypeOut. Advances buffer pointer. // Doesn't read behind the end of the buffer (the end starts at pDataEnd). // inline __checkReturn HRESULT CorSigUncompressElementType_EndPtr( PCCOR_SIGNATURE & pData, // [IN,OUT] Buffer PCCOR_SIGNATURE pDataEnd, // End of buffer CorElementType * pTypeOut) // [OUT] ELEMENT_TYPE_* value read from the buffer { _ASSERTE(pData <= pDataEnd); // We don't expect pData > pDataEnd, but the runtime check doesn't cost much and it is more secure in // case caller has a bug if (pData >= pDataEnd) { // No data return META_E_BAD_SIGNATURE; } // Read 'type' as 1 byte *pTypeOut = (CorElementType)*pData; pData++; return S_OK; } // CorSigUncompressElementType_EndPtr //--------------------------------------------------------------------------------------- // // Reads pointer (4/8 bytes) from buffer pData, fills the result to *ppvPointerOut. Advances buffer pointer. // Doesn't read behind the end of the buffer (the end starts at pDataEnd). // inline __checkReturn HRESULT CorSigUncompressPointer_EndPtr( PCCOR_SIGNATURE & pData, // [IN,OUT] Buffer PCCOR_SIGNATURE pDataEnd, // End of buffer void ** ppvPointerOut) // [OUT] Pointer value read from the buffer { _ASSERTE(pData <= pDataEnd); // We could just skip this check as pointers should be only in trusted (and therefore correct) // signatures and we check for that on the caller side, but it won't hurt to have this check and it will // make it easier to catch invalid signatures in trusted code (e.g. IL stubs, NGEN images, etc.) if (pData + sizeof(void *) > pDataEnd) { // Not enough data in the buffer _ASSERTE(!"This signature is invalid. Note that caller should check that it is not comming from untrusted source!"); return META_E_BAD_SIGNATURE; } *ppvPointerOut = *(void * UNALIGNED *)pData; pData += sizeof(void *); return S_OK; } // CorSigUncompressPointer_EndPtr //--------------------------------------------------------------------------------------- // // Reads compressed TypeDef/TypeRef/TypeSpec token, fills the result to *pnDataOut. Advances buffer pointer. // Doesn't read behind the end of the buffer (the end starts at pDataEnd). // inline __checkReturn HRESULT CorSigUncompressToken_EndPtr( PCCOR_SIGNATURE & pData, // [IN,OUT] Buffer PCCOR_SIGNATURE pDataEnd, // End of buffer mdToken * ptkTokenOut) // [OUT] Token read from the buffer { _ASSERTE(pData <= pDataEnd); HRESULT hr = S_OK; INT_PTR cbDataSize = pDataEnd - pData; if (cbDataSize > 4) { // Compressed token cannot be bigger than 4 bytes cbDataSize = 4; } DWORD dwDataSize = (DWORD)cbDataSize; ULONG cbTokenOutLength; IfFailRet(CorSigUncompressToken( pData, dwDataSize, ptkTokenOut, &cbTokenOutLength)); pData += cbTokenOutLength; return hr; } // CorSigUncompressToken_EndPtr #endif // __CORHLPRPRIV_H__