Fix SuperPMI implementation of `appendClassName` (#67280)
authorBruce Forstall <brucefo@microsoft.com>
Wed, 30 Mar 2022 20:47:49 +0000 (13:47 -0700)
committerGitHub <noreply@github.com>
Wed, 30 Mar 2022 20:47:49 +0000 (13:47 -0700)
* Fix SuperPMI implementation of `appendClassName`

Change #67135 added more and different calls to `appendClassName`, exposing
that it was not correctly implemented in SuperPMI. Fix this, especially, to
handle the case of passing a zero length to determine the correct size output buffer.

Also, add `eeTryGetClassSize` to wrap `getClassSize` and use it in two cases in the
importer that only use the output in JitDump, but was preventing a clean JitDump
replay of some collections.

Change the JIT-EE GUID because the SuperPMI collection data structures were changed.

* Update semantics of `appendClassName` API

Document the semantics more clearly.

Update EE and crossgen2 to conform to the same semantics.

Notable changes:
1. ppBuf argument is allowed to be nullptr, if `*pnBufLen` is zero.
2. If `*pnBufLen` is greater than zero, `*ppBuf` and `*pnBufLen` are
advanced by the number of characters actually written rather than the
number in the full class name string. This will differ in the case of
truncation due to a small output buffer. The advance does not include
the null terminator. Thus, `*ppBuf` will point at the null terminator
on return, and `*pnBufLen` will be at least `1`.
3. The EE will copy as much as possible (i.e., will truncate if necessary),
rather than crashing on a too-small output buffer.
4. Crossgen2 now advances `*ppBuf` and `*pnBufLen` as noted above.
5. SuperPMI implements these new semantics.

* Fix non-Windows build

Set `JIT_BUILD` when building SuperPMI. This disables contracts and some PAL debugging,
including the debugreturn.h debugging that was causing a build break.

Also, adjust a couple loops based on code review feedback.

16 files changed:
src/coreclr/inc/corinfo.h
src/coreclr/inc/jiteeversionguid.h
src/coreclr/jit/compiler.h
src/coreclr/jit/ee_il_dll.cpp
src/coreclr/jit/importer.cpp
src/coreclr/jit/simd.cpp
src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
src/coreclr/tools/superpmi/superpmi-shared/agnostic.h
src/coreclr/tools/superpmi/superpmi-shared/lwmlist.h
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp
src/coreclr/tools/superpmi/superpmi-shared/methodcontext.h
src/coreclr/tools/superpmi/superpmi-shared/spmiutil.h
src/coreclr/tools/superpmi/superpmi-shared/standardpch.h
src/coreclr/tools/superpmi/superpmi-shim-collector/icorjitinfo.cpp
src/coreclr/tools/superpmi/superpmi/icorjitinfo.cpp
src/coreclr/vm/jitinterface.cpp

index 46bf1a7..19bfed2 100644 (file)
@@ -198,6 +198,7 @@ TODO: Talk about initializing strutures before use
 #define _Outptr_opt_result_maybenull_
 #define _Outptr_result_z_
 #define _Outptr_result_buffer_(size)
+#define _Outptr_opt_result_buffer_(size)
 #endif
 
 #include "jiteeversionguid.h"
@@ -2274,19 +2275,40 @@ public:
             unsigned             index
             ) = 0;
 
-
-    // Append a (possibly truncated) representation of the type cls to the preallocated buffer ppBuf of length pnBufLen
-    // If fNamespace=TRUE, include the namespace/enclosing classes
-    // If fFullInst=TRUE (regardless of fNamespace and fAssembly), include namespace and assembly for any type parameters
-    // If fAssembly=TRUE, suffix with a comma and the full assembly qualification
-    // return size of representation
+    // Append a (possibly truncated) textual representation of the type `cls` to a preallocated buffer.
+    //
+    // Arguments:
+    //    ppBuf      - Pointer to buffer pointer. See below for details.
+    //    pnBufLen   - Pointer to buffer length. Must not be nullptr. See below for details.
+    //    fNamespace - If true, include the namespace/enclosing classes.
+    //    fFullInst  - If true (regardless of fNamespace and fAssembly), include namespace and assembly for any type parameters.
+    //    fAssembly  - If true, suffix with a comma and the full assembly qualification.
+    //
+    // Returns the length of the representation, as a count of characters (but not including a terminating null character).
+    // Note that this will always be the actual number of characters required by the representation, even if the string
+    // was truncated when copied to the buffer.
+    //
+    // Operation:
+    // 
+    // On entry, `*pnBufLen` specifies the size of the buffer pointed to by `*ppBuf` as a count of characters.
+    // There are two cases:
+    // 1. If the size is zero, the function computes the length of the representation and returns that.
+    //    `ppBuf` is ignored (and may be nullptr) and `*ppBuf` and `*pnBufLen` are not updated.
+    // 2. If the size is non-zero, the buffer pointed to by `*ppBuf` is (at least) that size. The class name
+    //    representation is copied to the buffer pointed to by `*ppBuf`. As many characters of the name as will fit in the
+    //    buffer are copied. Thus, if the name is larger than the size of the buffer, the name will be truncated in the buffer.
+    //    The buffer is guaranteed to be null terminated. Thus, the size must be large enough to include a terminating null
+    //    character, or the string will be truncated to include one. On exit, `*pnBufLen` is updated by subtracting the
+    //    number of characters that were actually copied to the buffer. Also, `*ppBuf` is updated to point at the null
+    //    character that was added to the end of the name.
+    //
     virtual int appendClassName(
-            _Outptr_result_buffer_(*pnBufLen) char16_t** ppBuf,
-            int* pnBufLen,
-            CORINFO_CLASS_HANDLE    cls,
-            bool fNamespace,
-            bool fFullInst,
-            bool fAssembly
+            _Outptr_opt_result_buffer_(*pnBufLen) char16_t**    ppBuf,    /* IN OUT */
+            int*                                                pnBufLen, /* IN OUT */
+            CORINFO_CLASS_HANDLE                                cls,
+            bool                                                fNamespace,
+            bool                                                fFullInst,
+            bool                                                fAssembly
             ) = 0;
 
     // Quick check whether the type is a value class. Returns the same value as getClassAttribs(cls) & CORINFO_FLG_VALUECLASS, except faster.
index 53633dc..86335c0 100644 (file)
@@ -43,11 +43,11 @@ typedef const GUID *LPCGUID;
 #define GUID_DEFINED
 #endif // !GUID_DEFINED
 
-constexpr GUID JITEEVersionIdentifier = { /* 398270b8-d474-428f-8113-3834281853cf */
-    0x398270b8,
-    0xd474,
-    0x428f,
-    {0x81, 0x13, 0x38, 0x34, 0x28, 0x18, 0x53, 0xcf}
+constexpr GUID JITEEVersionIdentifier = { /* b2d3c86f-87fd-4724-9e5d-4c44905eba91 */
+    0xb2d3c86f,
+    0x87fd,
+    0x4724,
+    {0x9e, 0x5d, 0x4c, 0x44, 0x90, 0x5e, 0xba, 0x91}
   };
 
 //////////////////////////////////////////////////////////////////////////////////////////////////////////
index d22f6bb..4277969 100644 (file)
@@ -8476,6 +8476,7 @@ public:
 
 #if defined(DEBUG)
     const WCHAR* eeGetCPString(size_t stringHandle);
+    unsigned eeTryGetClassSize(CORINFO_CLASS_HANDLE clsHnd);
     const char16_t* eeGetShortClassName(CORINFO_CLASS_HANDLE clsHnd);
 #endif
 
index 04286c6..fc354aa 100644 (file)
@@ -1371,6 +1371,7 @@ struct FilterSuperPMIExceptionsParam_ee_il
     const char**          classNamePtr;
     const char*           fieldOrMethodOrClassNamePtr;
     char16_t*             classNameWidePtr;
+    unsigned              classSize;
     EXCEPTION_POINTERS    exceptionPointers;
 };
 
@@ -1482,6 +1483,36 @@ const char* Compiler::eeGetClassName(CORINFO_CLASS_HANDLE clsHnd)
 #ifdef DEBUG
 
 //------------------------------------------------------------------------
+// eeTryGetClassSize: wraps getClassSize but if doing SuperPMI replay
+// and the value isn't found, use a bogus size.
+//
+// NOTE: This is only allowed for JitDump output.
+//
+// Return value:
+//      Either the actual class size, or (unsigned)-1 if SuperPMI didn't have it.
+//
+unsigned Compiler::eeTryGetClassSize(CORINFO_CLASS_HANDLE clsHnd)
+{
+    FilterSuperPMIExceptionsParam_ee_il param;
+
+    param.pThis    = this;
+    param.pJitInfo = &info;
+    param.clazz    = clsHnd;
+
+    bool success = eeRunWithSPMIErrorTrap<FilterSuperPMIExceptionsParam_ee_il>(
+        [](FilterSuperPMIExceptionsParam_ee_il* pParam) {
+            pParam->classSize = pParam->pJitInfo->compCompHnd->getClassSize(pParam->clazz);
+        },
+        &param);
+
+    if (!success)
+    {
+        param.classSize = (unsigned)-1; // Use the maximum unsigned value as the size
+    }
+    return param.classSize;
+}
+
+//------------------------------------------------------------------------
 // eeGetShortClassName: wraps appendClassName to provide functionality
 // similar to getClassName(), but returns a class name that is shortened,
 // not using full assembly info.
@@ -1504,28 +1535,20 @@ const char16_t* Compiler::eeGetShortClassName(CORINFO_CLASS_HANDLE clsHnd)
 
     bool success = eeRunWithSPMIErrorTrap<FilterSuperPMIExceptionsParam_ee_il>(
         [](FilterSuperPMIExceptionsParam_ee_il* pParam) {
-            char16_t       dummyClassName[1] = {0};
-            char16_t*      pbuf              = &dummyClassName[0];
-            int            len               = 0;
-            constexpr bool fNamespace        = true;
-            constexpr bool fFullInst         = false;
-            constexpr bool fAssembly         = false;
+            int            len        = 0;
+            constexpr bool fNamespace = true;
+            constexpr bool fFullInst  = false;
+            constexpr bool fAssembly  = false;
 
             // Warning: crossgen2 doesn't fully implement the `appendClassName` API.
-            // If the size passed isn't large enough to the hold the entire string, crossgen2 will copy as much
-            // as possible. However, the EE will assert. So we need to pass zero, get back the actual buffer size
-            // required, allocate that space, and call the API again to get the full string. Both EE and crossgen2
-            // will return the actual string size if *pnBufLen is zero.
-            // The resulting string is guaranteed to be null-terminated.
-            // Returned value is size of requested string, in characters, not including the terminating null
-            // character. This could be larger than the buffer size and thus larger than the number of characters
-            // actually copied to *pBuf (in the case of crossgen2, which doesn't assert on too-small buffers).
-            int cchStrLen = pParam->pJitInfo->compCompHnd->appendClassName(&pbuf, &len, pParam->clazz, fNamespace,
+            // We need to pass size zero, get back the actual buffer size required, allocate that space,
+            // and call the API again to get the full string.
+            int cchStrLen = pParam->pJitInfo->compCompHnd->appendClassName(nullptr, &len, pParam->clazz, fNamespace,
                                                                            fFullInst, fAssembly);
 
             size_t cchBufLen         = (size_t)cchStrLen + /* null terminator */ 1;
             pParam->classNameWidePtr = pParam->pThis->getAllocator(CMK_DebugOnly).allocate<char16_t>(cchBufLen);
-            pbuf                     = pParam->classNameWidePtr;
+            char16_t* pbuf           = pParam->classNameWidePtr;
             len                      = (int)cchBufLen;
 
             int cchResultStrLen = pParam->pJitInfo->compCompHnd->appendClassName(&pbuf, &len, pParam->clazz, fNamespace,
@@ -1542,6 +1565,7 @@ const char16_t* Compiler::eeGetShortClassName(CORINFO_CLASS_HANDLE clsHnd)
         param.classNameWidePtr               = getAllocator(CMK_DebugOnly).allocate<char16_t>(cchLen);
         memcpy(param.classNameWidePtr, substituteClassName, cchLen * sizeof(char16_t));
     }
+
     return param.classNameWidePtr;
 }
 
index 349e706..c0ab1d4 100644 (file)
@@ -8793,9 +8793,8 @@ var_types Compiler::impImportCall(OPCODE                  opcode,
 #ifdef DEBUG
         if (verbose)
         {
-            unsigned structSize =
-                (callRetTyp == TYP_STRUCT) ? info.compCompHnd->getClassSize(calliSig.retTypeSigClass) : 0;
-            printf("\nIn Compiler::impImportCall: opcode is %s, kind=%d, callRetType is %s, structSize is %d\n",
+            unsigned structSize = (callRetTyp == TYP_STRUCT) ? eeTryGetClassSize(calliSig.retTypeSigClass) : 0;
+            printf("\nIn Compiler::impImportCall: opcode is %s, kind=%d, callRetType is %s, structSize is %u\n",
                    opcodeNames[opcode], callInfo->kind, varTypeName(callRetTyp), structSize);
         }
 #endif
@@ -8821,8 +8820,8 @@ var_types Compiler::impImportCall(OPCODE                  opcode,
 #ifdef DEBUG
         if (verbose)
         {
-            unsigned structSize = (callRetTyp == TYP_STRUCT) ? info.compCompHnd->getClassSize(sig->retTypeSigClass) : 0;
-            printf("\nIn Compiler::impImportCall: opcode is %s, kind=%d, callRetType is %s, structSize is %d\n",
+            unsigned structSize = (callRetTyp == TYP_STRUCT) ? eeTryGetClassSize(sig->retTypeSigClass) : 0;
+            printf("\nIn Compiler::impImportCall: opcode is %s, kind=%d, callRetType is %s, structSize is %u\n",
                    opcodeNames[opcode], callInfo->kind, varTypeName(callRetTyp), structSize);
         }
 #endif
index 4d0520b..014d4c4 100644 (file)
@@ -297,8 +297,9 @@ CorInfoType Compiler::getBaseJitTypeAndSizeOfSIMDType(CORINFO_CLASS_HANDLE typeH
             WCHAR  className[256] = {0};
             WCHAR* pbuf           = &className[0];
             int    len            = ArrLen(className);
-            info.compCompHnd->appendClassName((char16_t**)&pbuf, &len, typeHnd, true, false, false);
-            noway_assert(pbuf < &className[256]);
+            int    outlen = info.compCompHnd->appendClassName((char16_t**)&pbuf, &len, typeHnd, true, false, false);
+            noway_assert(outlen >= 0);
+            noway_assert((size_t)(outlen + 1) <= ArrLen(className));
             JITDUMP("SIMD Candidate Type %S\n", className);
 
             if (wcsncmp(className, W("System.Numerics."), 16) == 0)
index 5e239c4..bca84db 100644 (file)
@@ -1934,14 +1934,21 @@ namespace Internal.JitInterface
             if (pnBufLen > 0)
             {
                 char* buffer = *ppBuf;
-                for (int i = 0; i < Math.Min(name.Length, pnBufLen); i++)
+                int lengthToCopy = Math.Min(name.Length, pnBufLen);
+                for (int i = 0; i < lengthToCopy; i++)
                     buffer[i] = name[i];
                 if (name.Length < pnBufLen)
+                {
                     buffer[name.Length] = (char)0;
+                }
                 else
+                {
                     buffer[pnBufLen - 1] = (char)0;
-                pnBufLen -= length;
-                *ppBuf = buffer + length;
+                    lengthToCopy -= 1;
+                }
+
+                pnBufLen -= lengthToCopy;
+                *ppBuf = buffer + lengthToCopy;
             }
 
             return length;
index 571edea..c9b67a1 100644 (file)
@@ -384,14 +384,21 @@ struct Agnostic_CanAccessClassOut
     DWORD                        result;
 };
 
-struct Agnostic_AppendClassName
+struct Agnostic_AppendClassNameIn
 {
+    DWORD     nBufLenIsZero;
     DWORDLONG classHandle;
     DWORD     fNamespace;
     DWORD     fFullInst;
     DWORD     fAssembly;
 };
 
+struct Agnostic_AppendClassNameOut
+{
+    DWORD nLen;
+    DWORD name_index;
+};
+
 struct Agnostic_CheckMethodModifier
 {
     DWORDLONG hMethod;
index 4c0ebcc..87a4383 100644 (file)
@@ -18,7 +18,7 @@
 
 LWM(AllocPgoInstrumentationBySchema, DWORDLONG, Agnostic_AllocPgoInstrumentationBySchema)
 LWM(GetPgoInstrumentationResults, DWORDLONG, Agnostic_GetPgoInstrumentationResults)
-LWM(AppendClassName, Agnostic_AppendClassName, DWORD)
+LWM(AppendClassName, Agnostic_AppendClassNameIn, Agnostic_AppendClassNameOut)
 LWM(AreTypesEquivalent, DLDL, DWORD)
 LWM(AsCorInfoType, DWORDLONG, DWORD)
 LWM(CanAccessClass, Agnostic_CanAccessClassIn, Agnostic_CanAccessClassOut)
index ed685c2..e97dab1 100644 (file)
@@ -6450,58 +6450,116 @@ CORINFO_CLASS_HANDLE MethodContext::repGetTypeInstantiationArgument(CORINFO_CLAS
 }
 
 void MethodContext::recAppendClassName(
-    CORINFO_CLASS_HANDLE cls, bool fNamespace, bool fFullInst, bool fAssembly, const char16_t* result)
+    int nBufLenIn,
+    CORINFO_CLASS_HANDLE cls,
+    bool fNamespace,
+    bool fFullInst,
+    bool fAssembly,
+    int nLenOut,
+    const char16_t* result)
 {
     if (AppendClassName == nullptr)
-        AppendClassName = new LightWeightMap<Agnostic_AppendClassName, DWORD>();
-
-    Agnostic_AppendClassName key;
+        AppendClassName = new LightWeightMap<Agnostic_AppendClassNameIn, Agnostic_AppendClassNameOut>();
+
+    // The API has two different behaviors depending on whether the input specified length is zero or non-zero:
+    // (1) zero: returns the length of the string (which the caller can use to allocate a buffer)
+    // (2) non-zero: fill as much of the buffer with the name as there is space available.
+    // We don't want the input length to be part of the key, since the caller could potentially pass in a smaller
+    // or larger buffer, and we'll fill in as much of the buffer as possible. We do need to handle both the zero
+    // and non-zero cases, though, so we'll get one record for first call using zero (with the correct buffer size result),
+    // and another for the second call with a big-enough buffer. We could presumably just store the second case
+    // (and overwrite the first record), since it contains the same output length, but it is useful to store
+    // (and see) all the JIT-EE interface calls.
+
+    Agnostic_AppendClassNameIn key;
     ZeroMemory(&key, sizeof(key)); // Zero key including any struct padding
-    key.classHandle = CastHandle(cls);
-    key.fNamespace  = fNamespace;
-    key.fFullInst   = fFullInst;
-    key.fAssembly   = fAssembly;
+    key.nBufLenIsZero = (nBufLenIn == 0) ? 1 : 0;
+    key.classHandle   = CastHandle(cls);
+    key.fNamespace    = fNamespace;
+    key.fFullInst     = fFullInst;
+    key.fAssembly     = fAssembly;
 
-    DWORD value = (DWORD)-1;
-    if (result != nullptr)
-        value = (DWORD)AppendClassName->AddBuffer((unsigned char*)result, (unsigned int)((wcslen((LPCWSTR)result) * 2) + 2));
+    Agnostic_AppendClassNameOut value;
+    value.nLen       = nLenOut;
+    value.name_index = (DWORD)-1;
+
+    // Don't save the string buffer if the incoming buffer length is zero, even if the string buffer is non-null.
+    // In that case, the EE/crossgen2 don't zero-terminate the buffer (since they assume it is zero sized).
+    if ((result != nullptr) && (nBufLenIn > 0))
+    {
+        value.name_index = (DWORD)AppendClassName->AddBuffer(
+            (unsigned char*)result,
+            (unsigned int)((wcslen((LPCWSTR)result) + 1) * sizeof(char16_t)));
+    }
 
     AppendClassName->Add(key, value);
     DEBUG_REC(dmpAppendClassName(key, value));
 }
 
-void MethodContext::dmpAppendClassName(const Agnostic_AppendClassName& key, DWORD value)
+void MethodContext::dmpAppendClassName(const Agnostic_AppendClassNameIn& key, const Agnostic_AppendClassNameOut& value)
 {
-    printf("AppendClassName key cls-%016llX ns-%u fi-%u as-%u, value %s", key.classHandle, key.fNamespace,
-           key.fFullInst, key.fAssembly, AppendClassName->GetBuffer(value));
+    const char16_t* name = (const char16_t*)AppendClassName->GetBuffer(value.name_index);
+    printf("AppendClassName key lenzero-%s cls-%016llX ns-%u fi-%u as-%u, value len-%u ni-%u name-%S",
+        key.nBufLenIsZero ? "true" : "false", key.classHandle, key.fNamespace, key.fFullInst, key.fAssembly,
+        value.nLen, value.name_index, (const WCHAR*)name);
     AppendClassName->Unlock();
 }
 
-const WCHAR* MethodContext::repAppendClassName(CORINFO_CLASS_HANDLE cls,
-                                               bool                 fNamespace,
-                                               bool                 fFullInst,
-                                               bool                 fAssembly)
+int MethodContext::repAppendClassName(char16_t**           ppBuf,
+                                      int*                 pnBufLen,
+                                      CORINFO_CLASS_HANDLE cls,
+                                      bool                 fNamespace,
+                                      bool                 fFullInst,
+                                      bool                 fAssembly)
 {
-    if (AppendClassName == nullptr)
-        return W("hackishClassName");
+    static const char16_t unknownClass[]     = u"hackishClassName";
+    static const int      unknownClassLength = (int)(ArrLen(unknownClass) - 1); // Don't include null terminator in length.
 
-    Agnostic_AppendClassName key;
-    ZeroMemory(&key, sizeof(key)); // Zero key including any struct padding
-    key.classHandle = CastHandle(cls);
-    key.fNamespace  = fNamespace;
-    key.fFullInst   = fFullInst;
-    key.fAssembly   = fAssembly;
+    // By default, at least return something.
+    const char16_t* name = unknownClass;
+    int             nLen = unknownClassLength;
 
-    int index = AppendClassName->GetIndex(key);
-    if (index == -1)
-        return W("hackishClassName");
+    if (AppendClassName != nullptr)
+    {
+        Agnostic_AppendClassNameIn key;
+        ZeroMemory(&key, sizeof(key)); // Zero key including any struct padding
+        key.nBufLenIsZero = (*pnBufLen == 0) ? 1 : 0;
+        key.classHandle   = CastHandle(cls);
+        key.fNamespace    = fNamespace;
+        key.fFullInst     = fFullInst;
+        key.fAssembly     = fAssembly;
 
-    DWORD value = AppendClassName->Get(key);
-    DEBUG_REP(dmpAppendClassName(key, value));
+        // First, see if we have an entry for this query.
+        int index = AppendClassName->GetIndex(key);
+        if (index != -1)
+        {
+            // Then, actually get the value.
+            Agnostic_AppendClassNameOut value = AppendClassName->Get(key);
+            DEBUG_REP(dmpAppendClassName(key, value));
 
-    int offset = (int)value;
-    const WCHAR* name = (const WCHAR*)AppendClassName->GetBuffer(offset);
-    return name;
+            nLen = value.nLen;
+            name = (const char16_t*)AppendClassName->GetBuffer(value.name_index);
+        }
+    }
+
+    if ((ppBuf != nullptr) && (*ppBuf != nullptr) && (*pnBufLen > 0) && (name != nullptr))
+    {
+        // Copy as much as will fit.
+        char16_t* pBuf = *ppBuf;
+        int nLenToCopy = min(*pnBufLen, nLen + /* null terminator */ 1);
+        for (int i = 0; i < nLenToCopy - 1; i++)
+        {
+            pBuf[i] = name[i];
+        }
+        pBuf[nLenToCopy - 1] = 0; // null terminate the string if it wasn't already
+
+        // Update the buffer pointer and buffer size pointer based on the amount actually copied.
+        // Don't include the null terminator. `*ppBuf` will point at the added null terminator.
+        (*ppBuf) += nLenToCopy - 1;
+        (*pnBufLen) -= nLenToCopy - 1;
+    }
+
+    return nLen;
 }
 
 void MethodContext::recGetTailCallHelpers(
index bcd2f59..465b6e0 100644 (file)
@@ -785,10 +785,20 @@ public:
     void dmpGetTypeInstantiationArgument(DWORDLONG key, DWORDLONG value);
     CORINFO_CLASS_HANDLE repGetTypeInstantiationArgument(CORINFO_CLASS_HANDLE cls, unsigned index);
 
-    void recAppendClassName(
-        CORINFO_CLASS_HANDLE cls, bool fNamespace, bool fFullInst, bool fAssembly, const char16_t* result);
-    void dmpAppendClassName(const Agnostic_AppendClassName& key, DWORD value);
-    const WCHAR* repAppendClassName(CORINFO_CLASS_HANDLE cls, bool fNamespace, bool fFullInst, bool fAssembly);
+    void recAppendClassName(int                  nBufLenIn,
+                            CORINFO_CLASS_HANDLE cls,
+                            bool                 fNamespace,
+                            bool                 fFullInst,
+                            bool                 fAssembly,
+                            int                  nLenOut,
+                            const char16_t*      result);
+    void dmpAppendClassName(const Agnostic_AppendClassNameIn& key, const Agnostic_AppendClassNameOut& value);
+    int repAppendClassName(char16_t**           ppBuf,
+                           int*                 pnBufLen,
+                           CORINFO_CLASS_HANDLE cls,
+                           bool                 fNamespace,
+                           bool                 fFullInst,
+                           bool                 fAssembly);
 
     void recGetTailCallHelpers(
         CORINFO_RESOLVED_TOKEN* callToken,
index bc6bfa4..a4863a1 100644 (file)
@@ -76,4 +76,10 @@ void PutArm64Rel12(UINT32* pCode, INT32 imm12);
 void PutThumb2Mov32(UINT16* p, UINT32 imm32);
 void PutThumb2BlRel24(UINT16* p, INT32 imm24);
 
+template <typename T, int size>
+inline constexpr unsigned ArrLen(T (&)[size])
+{
+    return size;
+}
+
 #endif // !_SPMIUtil
index 33bdca9..af32198 100644 (file)
 //#define USE_COREDISTOOLS
 #endif // INTERNAL_BUILD
 
-#ifdef _MSC_VER
-// On Windows, we build against PAL macros that convert to Windows SEH. But we don't want all the
-// Contract stuff that normally gets pulled it. Defining JIT_BUILD prevents this, just as it does
-// when building the JIT using parts of utilcode.
+// JIT_BUILD disables certain PAL_TRY debugging and Contracts features. Set this just as the JIT sets it.
 #define JIT_BUILD
 
+#ifdef _MSC_VER
 // Defining this prevents:
 //   error C2338 : / RTCc rejects conformant code, so it isn't supported by the C++ Standard Library.
 //   Either remove this compiler option, or define _ALLOW_RTCc_IN_STL to acknowledge that you have received this
index e328562..0ff9e93 100644 (file)
@@ -486,23 +486,46 @@ CORINFO_CLASS_HANDLE interceptor_ICJI::getTypeInstantiationArgument(CORINFO_CLAS
     return temp;
 }
 
-// Append a (possibly truncated) representation of the type cls to the preallocated buffer ppBuf of length pnBufLen
-// If fNamespace=TRUE, include the namespace/enclosing classes
-// If fFullInst=TRUE (regardless of fNamespace and fAssembly), include namespace and assembly for any type parameters
-// If fAssembly=TRUE, suffix with a comma and the full assembly qualification
-// return size of representation
-int interceptor_ICJI::appendClassName(_Outptr_result_buffer_(*pnBufLen) char16_t** ppBuf,
-                                      int*                                       pnBufLen,
-                                      CORINFO_CLASS_HANDLE                       cls,
-                                      bool                                       fNamespace,
-                                      bool                                       fFullInst,
-                                      bool                                       fAssembly)
+// Append a (possibly truncated) textual representation of the type `cls` to a preallocated buffer.
+//
+// Arguments:
+//    ppBuf      - Pointer to buffer pointer. See below for details.
+//    pnBufLen   - Pointer to buffer length. Must not be nullptr. See below for details.
+//    fNamespace - If true, include the namespace/enclosing classes.
+//    fFullInst  - If true (regardless of fNamespace and fAssembly), include namespace and assembly for any type parameters.
+//    fAssembly  - If true, suffix with a comma and the full assembly qualification.
+//
+// Returns the length of the representation, as a count of characters (but not including a terminating null character).
+// Note that this will always be the actual number of characters required by the representation, even if the string
+// was truncated when copied to the buffer.
+//
+// Operation:
+// 
+// On entry, `*pnBufLen` specifies the size of the buffer pointed to by `*ppBuf` as a count of characters.
+// There are two cases:
+// 1. If the size is zero, the function computes the length of the representation and returns that.
+//    `ppBuf` is ignored (and may be nullptr) and `*ppBuf` and `*pnBufLen` are not updated.
+// 2. If the size is non-zero, the buffer pointed to by `*ppBuf` is (at least) that size. The class name
+//    representation is copied to the buffer pointed to by `*ppBuf`. As many characters of the name as will fit in the
+//    buffer are copied. Thus, if the name is larger than the size of the buffer, the name will be truncated in the buffer.
+//    The buffer is guaranteed to be null terminated. Thus, the size must be large enough to include a terminating null
+//    character, or the string will be truncated to include one. On exit, `*pnBufLen` is updated by subtracting the
+//    number of characters that were actually copied to the buffer. Also, `*ppBuf` is updated to point at the null
+//    character that was added to the end of the name.
+//
+int interceptor_ICJI::appendClassName(_Outptr_opt_result_buffer_(*pnBufLen) char16_t**  ppBuf,
+                                      int*                                              pnBufLen,
+                                      CORINFO_CLASS_HANDLE                              cls,
+                                      bool                                              fNamespace,
+                                      bool                                              fFullInst,
+                                      bool                                              fAssembly)
 {
     mc->cr->AddCall("appendClassName");
-    char16_t* pBuf = *ppBuf;
-    int    nLen = original_ICorJitInfo->appendClassName(ppBuf, pnBufLen, cls, fNamespace, fFullInst, fAssembly);
-    mc->recAppendClassName(cls, fNamespace, fFullInst, fAssembly, pBuf);
-    return nLen;
+    char16_t* pBufIn    = (ppBuf == nullptr) ? nullptr : *ppBuf;
+    int       nBufLenIn = (pnBufLen == nullptr) ? 0 : *pnBufLen; // pnBufLen should never be nullptr, but don't crash if it is.
+    int       nLenOut   = original_ICorJitInfo->appendClassName(ppBuf, pnBufLen, cls, fNamespace, fFullInst, fAssembly);
+    mc->recAppendClassName(nBufLenIn, cls, fNamespace, fFullInst, fAssembly, nLenOut, pBufIn);
+    return nLenOut;
 }
 
 // Quick check whether the type is a value class. Returns the same value as getClassAttribs(cls) &
index b081979..32827ec 100644 (file)
@@ -418,32 +418,42 @@ CORINFO_CLASS_HANDLE MyICJI::getTypeInstantiationArgument(CORINFO_CLASS_HANDLE c
     return result;
 }
 
-// Append a (possibly truncated) representation of the type cls to the preallocated buffer ppBuf of length pnBufLen
-// If fNamespace=TRUE, include the namespace/enclosing classes
-// If fFullInst=TRUE (regardless of fNamespace and fAssembly), include namespace and assembly for any type parameters
-// If fAssembly=TRUE, suffix with a comma and the full assembly qualification
-// return size of representation
-int MyICJI::appendClassName(_Outptr_result_buffer_(*pnBufLen) char16_t** ppBuf,
-                            int*                                    pnBufLen,
-                            CORINFO_CLASS_HANDLE                    cls,
-                            bool                                    fNamespace,
-                            bool                                    fFullInst,
-                            bool                                    fAssembly)
+// Append a (possibly truncated) textual representation of the type `cls` to a preallocated buffer.
+//
+// Arguments:
+//    ppBuf      - Pointer to buffer pointer. See below for details.
+//    pnBufLen   - Pointer to buffer length. Must not be nullptr. See below for details.
+//    fNamespace - If true, include the namespace/enclosing classes.
+//    fFullInst  - If true (regardless of fNamespace and fAssembly), include namespace and assembly for any type parameters.
+//    fAssembly  - If true, suffix with a comma and the full assembly qualification.
+//
+// Returns the length of the representation, as a count of characters (but not including a terminating null character).
+// Note that this will always be the actual number of characters required by the representation, even if the string
+// was truncated when copied to the buffer.
+//
+// Operation:
+// 
+// On entry, `*pnBufLen` specifies the size of the buffer pointed to by `*ppBuf` as a count of characters.
+// There are two cases:
+// 1. If the size is zero, the function computes the length of the representation and returns that.
+//    `ppBuf` is ignored (and may be nullptr) and `*ppBuf` and `*pnBufLen` are not updated.
+// 2. If the size is non-zero, the buffer pointed to by `*ppBuf` is (at least) that size. The class name
+//    representation is copied to the buffer pointed to by `*ppBuf`. As many characters of the name as will fit in the
+//    buffer are copied. Thus, if the name is larger than the size of the buffer, the name will be truncated in the buffer.
+//    The buffer is guaranteed to be null terminated. Thus, the size must be large enough to include a terminating null
+//    character, or the string will be truncated to include one. On exit, `*pnBufLen` is updated by subtracting the
+//    number of characters that were actually copied to the buffer. Also, `*ppBuf` is updated to point at the null
+//    character that was added to the end of the name.
+//
+int MyICJI::appendClassName(_Outptr_opt_result_buffer_(*pnBufLen) char16_t** ppBuf,
+                            int*                                             pnBufLen,
+                            CORINFO_CLASS_HANDLE                             cls,
+                            bool                                             fNamespace,
+                            bool                                             fFullInst,
+                            bool                                             fAssembly)
 {
     jitInstance->mc->cr->AddCall("appendClassName");
-    const WCHAR* result = jitInstance->mc->repAppendClassName(cls, fNamespace, fFullInst, fAssembly);
-    int          nLen   = 0;
-    if (ppBuf != nullptr && result != nullptr)
-    {
-        nLen = (int)wcslen(result);
-        if (*pnBufLen > nLen)
-        {
-            wcscpy_s((WCHAR*)*ppBuf, *pnBufLen, result);
-            (*ppBuf) += nLen;
-            (*pnBufLen) -= nLen;
-        }
-    }
-    return nLen;
+    return jitInstance->mc->repAppendClassName(ppBuf, pnBufLen, cls, fNamespace, fFullInst, fAssembly);
 }
 
 // Quick check whether the type is a value class. Returns the same value as getClassAttribs(cls) &
index a44f20c..6c78bee 100644 (file)
@@ -3342,12 +3342,12 @@ const char* CEEInfo::getHelperName (CorInfoHelpFunc ftnNum)
 
 
 /*********************************************************************/
-int CEEInfo::appendClassName(_Outptr_result_buffer_(*pnBufLen) char16_t** ppBuf,
-                             int* pnBufLen,
-                             CORINFO_CLASS_HANDLE    clsHnd,
-                             bool fNamespace,
-                             bool fFullInst,
-                             bool fAssembly)
+int CEEInfo::appendClassName(_Outptr_opt_result_buffer_(*pnBufLen) char16_t**   ppBuf,
+                             int*                                               pnBufLen,
+                             CORINFO_CLASS_HANDLE                               clsHnd,
+                             bool                                               fNamespace,
+                             bool                                               fFullInst,
+                             bool                                               fAssembly)
 {
     CONTRACTL {
         MODE_PREEMPTIVE;
@@ -3369,14 +3369,24 @@ int CEEInfo::appendClassName(_Outptr_result_buffer_(*pnBufLen) char16_t** ppBuf,
     nLen = (int)wcslen(szString);
     if (*pnBufLen > 0)
     {
-    wcscpy_s((WCHAR*)*ppBuf, *pnBufLen, szString );
-    (*ppBuf)[(*pnBufLen) - 1] = W('\0');
-    (*ppBuf) += nLen;
-    (*pnBufLen) -= nLen;
+        // Copy as much as will fit.
+        WCHAR* pBuf = (WCHAR*)*ppBuf;
+        int nLenToCopy = min(*pnBufLen, nLen + /* null terminator */ 1);
+        for (int i = 0; i < nLenToCopy - 1; i++)
+        {
+            pBuf[i] = szString[i];
+        }
+        pBuf[nLenToCopy - 1] = 0; // null terminate the string if it wasn't already
+
+        // Update the buffer pointer and buffer size pointer based on the amount actually copied.
+        // Don't include the null terminator. `*ppBuf` will point at the added null terminator.
+        (*ppBuf) += nLenToCopy - 1;
+        (*pnBufLen) -= nLenToCopy - 1;
     }
 
     EE_TO_JIT_TRANSITION();
 
+    // Return the actual length of the string, not including the null terminator.
     return nLen;
 }