ronantakizawa/codereview-bench huggingface dataset:
Repository: dotnet/runtime
File: src/coreclr/jit/regallocwasm.cpp (C++)

Code before:

//------------------------------------------------------------------------
// CollectReferencesForDivMod: Collect virtual register references for a DIV/MOD.
//
// Consumes temporary registers for the div-by-zero and overflow checks.
//
// Arguments:
//    divModNode - The [U]DIV/[U]MOD node
//
void WasmRegAlloc::CollectReferencesForDivMod(GenTreeOp* divModNode)
{
    ConsumeTemporaryRegForOperand(divModNode->gtGetOp2() DEBUGARG("div-by-zero / overflow check"));
    ConsumeTemporaryRegForOperand(divModNode->gtGetOp1() DEBUGARG("div-by-zero / overflow check"));
}

//------------------------------------------------------------------------
// CollectReferencesForLclHeap: Collect virtual register references for a LCLHEAP.
//
// Consumes a temporary register for the size operand of the LCLHEAP operation.
//
// Arguments:
//    lclHeapNode - The LCLHEAP node
//
void WasmRegAlloc::CollectReferencesForLclHeap(GenTreeOp* lclHeapNode)
{
    ConsumeTemporaryRegForOperand(lclHeapNode->gtGetOp1() DEBUGARG("lcl heap"));
}

// CollectReferencesForCall: Collect virtual register references for a call.
//
// Consumes temporary registers for a call.
//
// Arguments:
//    callNode - The GT_CALL node
//
void WasmRegAlloc::CollectReferencesForCall(GenTreeCall* callNode)
{
    CallArg* thisArg = callNode->gtArgs.GetThisArg();

    if (thisArg != nullptr)
    {
        ConsumeTemporaryRegForOperand(thisArg->GetNode() DEBUGARG("call this argument"));
    }
}

//------------------------------------------------------------------------
// CollectReferencesForCast: Collect virtual register references for a cast.
//
// Consumes temporary registers for a cast.
//
// Arguments:

Review comment (bug):
```suggestion
    ConsumeTemporaryRegForOperand(lclHeapNode->gtGetOp1() DEBUGARG("lcl heap size"));
```

Code after:
            CollectReferencesForLclVar(node->AsLclVar());
            break;

        case GT_STORE_LCL_VAR:
        case GT_STORE_LCL_FLD:
        {
            LclVarDsc* varDsc = m_compiler->lvaGetDesc(node->AsLclVarCommon());
            if (varDsc->lvIsRegCandidate())
            {
                assert(node->OperIs(GT_STORE_LCL_VAR));
                CollectReference(node->AsLclVarCommon());
            }
            else
            {
                RewriteLocalStackStore(node->AsLclVarCommon());
            }
        }
        break;

        case GT_DIV:
        case GT_UDIV:
        case GT_MOD:
        case GT_UMOD:
            CollectReferencesForDivMod(node->AsOp());
            break;

        case GT_LCLHEAP:
            CollectReferencesForLclHeap(node->AsOp());
            break;

        case GT_CALL:
            CollectReferencesForCall(node->AsCall());
            break;

        case GT_CAST:
            CollectReferencesForCast(node->AsOp());
            break;

        case GT_ADD:
        case GT_SUB:
        case GT_MUL:
            CollectReferencesForBinop(node->AsOp());
            break;

        default:
            assert(!node->OperIsLocalStore());
            break;
    }

    RequestTemporaryRegisterForMultiplyUsedNode(node);
}
Repository: dotnet/runtime
File: src/coreclr/jit/emitwasm.cpp (C++)

Code before:
{
    #define INST(id, nm, info, fmt, opcode) info,
    #include "instrs.h"
};
// clang-format on

void emitter::emitIns(instruction ins)
{
    instrDesc* id  = emitNewInstrSmall(EA_8BYTE);
    insFormat  fmt = emitInsFormat(ins);
    id->idIns(ins);
    id->idInsFmt(fmt);

    dispIns(id);
    appendToCurIG(id);
}

//------------------------------------------------------------------------
// emitIns_Block_Ty: Emit a block instruction with simple type signature
//
// Arguments:
//   ins      - instruction to emit
//   valType  - type of object left on the stack at block end (for the block sig),
//              with WasmValueType::Invalid representing no object left on the stack.
//
void emitter::emitIns_Block_Ty(instruction ins, WasmValueType valType)
{
    emitIns_I(ins, EA_4BYTE, (cnsval_ssize_t)valType);
}

//------------------------------------------------------------------------
// emitIns_I: Emit an instruction with an immediate operand.
//
// Arguments:
//   ins      - instruction to emit
//   attr     - emit attributes
//   imm      - immediate value
//
void emitter::emitIns_I(instruction ins, emitAttr attr, cnsval_ssize_t imm)
{
    instrDesc* id  = emitNewInstrSC(attr, imm);
    insFormat  fmt = emitInsFormat(ins);

    id->idIns(ins);
    id->idInsFmt(fmt);

    dispIns(id);
    appendToCurIG(id);
}

//------------------------------------------------------------------------

Review comment (suggestion):
```suggestion
void emitter::emitIns_BlockTy(instruction ins, WasmValueType valType)
```
Nit: if we go by usual emitter conventions, this would mean two operands (`Block` and `Ty`).

Code after:
{
    #define INST(id, nm, info, fmt, opcode) info,
    #include "instrs.h"
};
// clang-format on

void emitter::emitIns(instruction ins)
{
    instrDesc* id  = emitNewInstrSmall(EA_8BYTE);
    insFormat  fmt = emitInsFormat(ins);
    id->idIns(ins);
    id->idInsFmt(fmt);

    dispIns(id);
    appendToCurIG(id);
}

//------------------------------------------------------------------------
// emitIns_BlockTy: Emit a block instruction with simple type signature
//
// Arguments:
//   ins      - instruction to emit
//   valType  - type of object left on the stack at block end (for the block sig),
//              with WasmValueType::Invalid representing no object left on the stack.
//
void emitter::emitIns_BlockTy(instruction ins, WasmValueType valType)
{
    emitIns_I(ins, EA_4BYTE, (cnsval_ssize_t)valType);
}

//------------------------------------------------------------------------
// emitIns_I: Emit an instruction with an immediate operand.
//
// Arguments:
//   ins      - instruction to emit
//   attr     - emit attributes
//   imm      - immediate value
//
void emitter::emitIns_I(instruction ins, emitAttr attr, cnsval_ssize_t imm)
{
    instrDesc* id  = emitNewInstrSC(attr, imm);
    insFormat  fmt = emitInsFormat(ins);

    id->idIns(ins);
    id->idInsFmt(fmt);

    dispIns(id);
    appendToCurIG(id);
}

//------------------------------------------------------------------------
Repository: dotnet/runtime
File: src/coreclr/jit/codegenwasm.cpp (C++)

Code before:
// Notes:
//    Incoming stack args are index; length (tos).
//
void CodeGen::genRangeCheck(GenTree* tree)
{
    assert(tree->OperIs(GT_BOUNDS_CHECK));
    genConsumeOperands(tree->AsOp());
    GetEmitter()->emitIns(INS_I_ge_u);
    genJumpToThrowHlpBlk(SCK_RNGCHK_FAIL);
}

//------------------------------------------------------------------------
// genCodeForIndexAddr: Produce code for a GT_INDEX_ADDR node.
//
// Arguments:
//    tree - the GT_INDEX_ADDR node
//
void CodeGen::genCodeForIndexAddr(GenTreeIndexAddr* node)
{
    genConsumeOperands(node);

    GenTree* const base  = node->Arr();
    GenTree* const index = node->Index();

    assert(varTypeIsIntegral(index->TypeGet()));
    assert(!varTypeIsSmall(index->TypeGet()));

    // Generate the bounds check if necessary.
    if (node->IsBoundsChecked())
    {
        // We need internal registers for this case.
        NYI_WASM("GT_INDEX_ADDR with bounds check");
    }

    // Zero extend index if necessary.
    if (genTypeSize(index->TypeGet()) < TARGET_POINTER_SIZE)
    {
        assert(TARGET_POINTER_SIZE == 8);
        GetEmitter()->emitIns(INS_i64_extend_u_i32);
    }

    // Result is the address of the array element.
    unsigned const scale = node->gtElemSize;

    if (scale > 1)
    {
        GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, scale);
        GetEmitter()->emitIns(INS_I_mul);
    }
    GetEmitter()->emitIns(INS_I_add);
    GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, node->gtElemOffset);

Review comment (suggestion):
```suggestion
```
Nothing preventing us from having a small-typed index.

Code after:
// Notes:
//    Incoming stack args are index; length (tos).
//
void CodeGen::genRangeCheck(GenTree* tree)
{
    assert(tree->OperIs(GT_BOUNDS_CHECK));
    genConsumeOperands(tree->AsOp());
    GetEmitter()->emitIns(INS_I_ge_u);
    genJumpToThrowHlpBlk(SCK_RNGCHK_FAIL);
}

//------------------------------------------------------------------------
// genCodeForIndexAddr: Produce code for a GT_INDEX_ADDR node.
//
// Arguments:
//    tree - the GT_INDEX_ADDR node
//
void CodeGen::genCodeForIndexAddr(GenTreeIndexAddr* node)
{
    genConsumeOperands(node);

    GenTree* const base  = node->Arr();
    GenTree* const index = node->Index();

    assert(varTypeIsIntegral(index->TypeGet()));

    // Generate the bounds check if necessary.
    if (node->IsBoundsChecked())
    {
        // We need internal registers for this case.
        NYI_WASM("GT_INDEX_ADDR with bounds check");
    }

    // Zero extend index if necessary.
    if (genTypeSize(index->TypeGet()) < TARGET_POINTER_SIZE)
    {
        assert(TARGET_POINTER_SIZE == 8);
        GetEmitter()->emitIns(INS_i64_extend_u_i32);
    }

    // Result is the address of the array element.
    unsigned const scale = node->gtElemSize;

    if (scale > 1)
    {
        GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, scale);
        GetEmitter()->emitIns(INS_I_mul);
    }
    GetEmitter()->emitIns(INS_I_add);
    GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, node->gtElemOffset);
    GetEmitter()->emitIns(INS_I_add);
Repository: dotnet/runtime
File: src/native/corehost/browserhost/loader/polyfills.ts (TypeScript)

Code before:
            } catch {
                // Ignore if the implementation does not allow redefining `url`
            }
        }

        return response;
    }
    return <Response><any>{
        ok: body !== null && options.status === 200,
        headers: {
            ...options.headers,
            get: (name: string) => (options.headers as any)[name] || null
        },
        url,
        arrayBuffer: () => {
            dotnetAssert.check(body !== null && body instanceof ArrayBuffer, "Response body is not a ArrayBuffer.");
            return Promise.resolve(body);
        },
        json: () => {
            dotnetAssert.check(body !== null && typeof body === "string", "Response body is not a string.");
            return Promise.resolve(JSON.parse(body));
        },
        text: () => {
            if (typeof body !== "string" && typeof globalThis.TextDecoder !== "undefined") {
                const decoder = new globalThis.TextDecoder("utf-8");
                return Promise.resolve(body).then(buffer => decoder.decode(buffer || new Uint8Array()));
            }
            dotnetAssert.check(body !== null && typeof body === "string", "Response body is not a string.");
            return Promise.resolve(body);
        }
    };
}


Review comment (suggestion):
`body` should not be a promise like object so this can be simplified:

```suggestion
                return decoder.decode(body || new Uint8Array());
```

Code after:
            } catch {
                // Ignore if the implementation does not allow redefining `url`
            }
        }

        return response;
    }
    return <Response><any>{
        ok: body !== null && options.status === 200,
        headers: {
            ...options.headers,
            get: (name: string) => (options.headers as any)[name] || null
        },
        url,
        arrayBuffer: () => {
            dotnetAssert.check(body !== null && body instanceof ArrayBuffer, "Response body is not a ArrayBuffer.");
            return Promise.resolve(body);
        },
        json: () => {
            dotnetAssert.check(body !== null && typeof body === "string", "Response body is not a string.");
            return Promise.resolve(JSON.parse(body));
        },
        text: () => {
            if (typeof body !== "string" && typeof globalThis.TextDecoder !== "undefined") {
                const decoder = new globalThis.TextDecoder("utf-8");
                return Promise.resolve(decoder.decode(body || new Uint8Array()));
            }
            dotnetAssert.check(body !== null && typeof body === "string", "Response body is not a string.");
            return Promise.resolve(body);
        }
    };
}
Repository: dotnet/runtime
File: src/native/libs/System.Native.Browser/diagnostics/symbolicate.ts (TypeScript)

Code before:
    }
}

function initSymbolMap() {
    if (!symbolTable)
        return;

    // V8
    //   at <anonymous>:wasm-function[1900]:0x83f63
    //   at dlfree (<anonymous>:wasm-function[18739]:0x2328ef)
    regexes.push(/at (?<replaceSection>[^:()]+:wasm-function\[(?<funcNum>\d+)\]:0x[a-fA-F\d]+)((?![^)a-fA-F\d])|$)/g);

    //# 5: WASM [009712b2], function #111 (''), pc=0x7c16595c973 (+0x53), pos=38740 (+11)
    regexes.push(/(?:WASM \[[\da-zA-Z]+\], (?<replaceSection>function #(?<funcNum>[\d]+) \(''\)))/g);

    //# chrome
    //# at http://127.0.0.1:63817/dotnet.wasm:wasm-function[8963]:0x1e23f4
    regexes.push(/(?<replaceSection>[a-z]+:\/\/[a-zA-Z0-9.:/_~-]*:wasm-function\[(?<funcNum>\d+)\]:0x[a-fA-F\d]+)/g);

    //# <?>.wasm-function[8962]
    regexes.push(/(?<replaceSection><[^ >]+>[.:]wasm-function\[(?<funcNum>[0-9]+)\])/g);

    const text = symbolTable;
    symbolTable = undefined;
    try {
        text.split(/[\r\n]/).forEach((line: string) => {
            const parts: string[] = line.split(/:/);
            if (parts.length < 2)
                return;

            parts[1] = parts.splice(1).join(":").replace(/\\([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
            symbol_map.set(Number(parts[0]), parts[1]);
        });
    } catch (exc) {
        dotnetLogger.debug(`failed to parse symbol table: ${exc}`);
    }
}


Review comment (suggestion):
```suggestion
        text.split(/\r?\n|\r/).forEach((line: string) => {
```

Code after:
    }
}

function initSymbolMap() {
    if (!symbolTable)
        return;

    // V8
    //   at <anonymous>:wasm-function[1900]:0x83f63
    //   at dlfree (<anonymous>:wasm-function[18739]:0x2328ef)
    regexes.push(/at (?<replaceSection>[^:()]+:wasm-function\[(?<funcNum>\d+)\]:0x[a-fA-F\d]+)((?![^)a-fA-F\d])|$)/g);

    //# 5: WASM [009712b2], function #111 (''), pc=0x7c16595c973 (+0x53), pos=38740 (+11)
    regexes.push(/(?:WASM \[[\da-zA-Z]+\], (?<replaceSection>function #(?<funcNum>[\d]+) \(''\)))/g);

    //# chrome
    //# at http://127.0.0.1:63817/dotnet.wasm:wasm-function[8963]:0x1e23f4
    regexes.push(/(?<replaceSection>[a-z]+:\/\/[a-zA-Z0-9.:/_~-]*:wasm-function\[(?<funcNum>\d+)\]:0x[a-fA-F\d]+)/g);

    //# <?>.wasm-function[8962]
    regexes.push(/(?<replaceSection><[^ >]+>[.:]wasm-function\[(?<funcNum>[0-9]+)\])/g);

    const text = symbolTable;
    symbolTable = undefined;
    try {
        text.split(/\r?\n|\r/).forEach((line: string) => {
            const parts: string[] = line.split(/:/);
            if (parts.length < 2)
                return;

            parts[1] = parts.splice(1).join(":").replace(/\\([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
            symbol_map.set(Number(parts[0]), parts[1]);
        });
    } catch (exc) {
        dotnetLogger.debug(`failed to parse symbol table: ${exc}`);
    }
}
Repository: dotnet/runtime
File: src/libraries/System.Security.Cryptography.OpenSsl/tests/SafeEvpPKeyHandleTests.cs (C#)

Code before:
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading;
using Xunit;

namespace System.Security.Cryptography.OpenSsl.Tests
{
    public static class SafeEvpPKeyHandleTests
    {
        [Fact]
        public static void DuplicateHandle_ConcurrentWithDispose_DoesNotProduceInvalidHandle()
        {
            using ECDsaOpenSsl ecdsa = new ECDsaOpenSsl();

            for (int i = 0; i < 1000; i++)
            {
                SafeEvpPKeyHandle keyHandle = ecdsa.DuplicateKeyHandle();
                SafeEvpPKeyHandle? duplicated = null;

                Thread disposeThread = new Thread(() =>
                {
                    Thread.Sleep(Random.Shared.Next(0, 3));
                    keyHandle.Dispose();
                });

                Thread duplicateThread = new Thread(() =>
                {
                    Thread.Sleep(Random.Shared.Next(0, 3));
                    try { duplicated = keyHandle.DuplicateHandle(); } catch { }
                });

                disposeThread.Start();
                duplicateThread.Start();
                disposeThread.Join();
                duplicateThread.Join();

                if (duplicated is not null)
                {
                    bool refAdded = false;

                    try
                    {

Review comment (suggestion):
@copilot `keyHandle` should be disposed before we continue the loop.

Code after:
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading;
using Xunit;

namespace System.Security.Cryptography.OpenSsl.Tests
{
    public static class SafeEvpPKeyHandleTests
    {
        [Fact]
        public static void DuplicateHandle_ConcurrentWithDispose_DoesNotProduceInvalidHandle()
        {
            using ECDsaOpenSsl ecdsa = new ECDsaOpenSsl();

            for (int i = 0; i < 1000; i++)
            {
                SafeEvpPKeyHandle keyHandle = ecdsa.DuplicateKeyHandle();
                SafeEvpPKeyHandle? duplicated = null;

                Thread disposeThread = new Thread(() =>
                {
                    Thread.Sleep(Random.Shared.Next(0, 3));
                    keyHandle.Dispose();
                });

                Thread duplicateThread = new Thread(() =>
                {
                    Thread.Sleep(Random.Shared.Next(0, 3));
                    try
                    {
                        duplicated = keyHandle.DuplicateHandle();
                    }
                    catch
                    {
                        // We are only interested in crashes, not managed exceptions.
                    }
                });

                disposeThread.Start();
                duplicateThread.Start();
                disposeThread.Join();
                duplicateThread.Join();
Repository: dotnet/runtime
File: src/coreclr/jit/lowerwasm.cpp (C++)

Code before:
//------------------------------------------------------------------------
// LowerNeg: Lower a GT_NEG node.
//
// Arguments:
//    node - The node to lower
//
// Return Value:
//    The next node to lower.
//
GenTree* Lowering::LowerNeg(GenTreeOp* node)
{
    if (!node->TypeIs(TYP_INT, TYP_LONG))
    {
        return node->gtNext;
    }

    // For integer types (TYP_INT and TYP_LONG), NEG(x) ==> SUB(0, x)
    //
    GenTree* x    = node->gtGetOp1();
    GenTree* zero = m_compiler->gtNewZeroConNode(node->TypeGet());

    // To preserve stack order we must insert the zero before the entire
    // tree rooted at x.
    //
    bool     isClosed;
    GenTree* insertBefore = BlockRange().GetTreeRange(x, &isClosed).FirstNode();
    assert(isClosed);
    BlockRange().InsertBefore(insertBefore, zero);
    LowerNode(zero);
    node->ChangeOper(GT_SUB);
    node->gtOp1 = zero;
    node->gtOp2 = x;

    return LowerNode(node);
}

//------------------------------------------------------------------------
// Lowering::LowerJTrue: Lowers a JTRUE node.
//
// Arguments:
//    jtrue - the JTRUE node
//
// Return Value:
//    The next node to lower (usually nullptr).
//
// Notes:
//    For wasm we handle all this in codegen
//
GenTree* Lowering::LowerJTrue(GenTreeOp* jtrue)
{
    // TODO-WASM: recognize eqz cases

Review comment (refactor):
`GetTreeRange` is a bit heavy for this task. It would be better to repackage what we have in `RewriteLocalStackStore` into a helper and use it here.

Code after:
//------------------------------------------------------------------------
// LowerNeg: Lower a GT_NEG node.
//
// Arguments:
//    node - The node to lower
//
// Return Value:
//    The next node to lower.
//
GenTree* Lowering::LowerNeg(GenTreeOp* node)
{
    if (!node->TypeIs(TYP_INT, TYP_LONG))
    {
        return node->gtNext;
    }

    // For integer types (TYP_INT and TYP_LONG), NEG(x) ==> SUB(0, x)
    //
    GenTree* x    = node->gtGetOp1();
    GenTree* zero = m_compiler->gtNewZeroConNode(node->TypeGet());

    // To preserve stack order we must insert the zero before the entire
    // tree rooted at x.
    //
    GenTree* insertBefore = x->gtFirstNodeInOperandOrder();
    BlockRange().InsertBefore(insertBefore, zero);
    LowerNode(zero);
    node->ChangeOper(GT_SUB);
    node->gtOp1 = zero;
    node->gtOp2 = x;

    return LowerNode(node);
}

//------------------------------------------------------------------------
// Lowering::LowerJTrue: Lowers a JTRUE node.
//
// Arguments:
//    jtrue - the JTRUE node
//
// Return Value:
//    The next node to lower (usually nullptr).
//
// Notes:
//    For wasm we handle all this in codegen
//
GenTree* Lowering::LowerJTrue(GenTreeOp* jtrue)
{
    // TODO-WASM: recognize eqz cases
    return nullptr;
}
Repository: dotnet/runtime
File: src/libraries/System.Configuration.ConfigurationManager/src/System/Configuration/StringUtil.cs (C#)

Code before:
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Configuration
{
    internal static class StringUtil
    {
        internal static bool EqualsOrBothNullOrEmpty(string s1, string s2)
        {
            return string.Equals(s1 ?? string.Empty, s2 ?? string.Empty, StringComparison.Ordinal);
        }

        internal static bool EqualsIgnoreCase(string s1, string s2)
        {
            return string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase);
        }

        internal static bool StartsWithOrdinal(string s1, string s2)
        {
            if (s2 == null) return false;
            return s1.StartsWith(s2, StringComparison.Ordinal);
        }

        internal static bool StartsWithOrdinalIgnoreCase(string s1, string s2)
        {
            if (s2 == null) return false;
            return s1.StartsWith(s2, StringComparison.OrdinalIgnoreCase);
        }

        internal static string[] ObjectArrayToStringArray(object[] objectArray)
        {
            string[] stringKeys = new string[objectArray.Length];
            objectArray.CopyTo(stringKeys, 0);
            return stringKeys;
        }
    }
}


Review comment (suggestion):
Be aware: this pattern of replacement can be a breaking behavioral change because it can introduce a null check where one did not previously exist.

Consider the simple one-liner:
```cs
Console.WriteLine(StartsWithOrdinal(null, ""));
```

Prior to the change, this would print _False_. After the change, this throws NRE.

Code after:
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Configuration
{
    internal static class StringUtil
    {
        internal static bool EqualsOrBothNullOrEmpty(string s1, string s2)
        {
            return string.Equals(s1 ?? string.Empty, s2 ?? string.Empty, StringComparison.Ordinal);
        }

        internal static bool EqualsIgnoreCase(string s1, string s2)
        {
            return string.Equals(s1, s2, StringComparison.OrdinalIgnoreCase);
        }

        internal static bool StartsWithOrdinal(string s1, string s2)
        {
            if (s1 is null || s2 is null) return false;
            return s1.StartsWith(s2, StringComparison.Ordinal);
        }

        internal static bool StartsWithOrdinalIgnoreCase(string s1, string s2)
        {
            if (s1 is null || s2 is null) return false;
            return s1.StartsWith(s2, StringComparison.OrdinalIgnoreCase);
        }

        internal static string[] ObjectArrayToStringArray(object[] objectArray)
        {
            string[] stringKeys = new string[objectArray.Length];
            objectArray.CopyTo(stringKeys, 0);
            return stringKeys;
        }
    }
}