bool CommandLine::Parse()

in src/coreclr/tools/superpmi/superpmi/commandline.cpp [212:868]


bool CommandLine::Parse(int argc, char* argv[], /* OUT */ Options* o)
{
    size_t argLen    = 0;
    size_t tempLen   = 0;
    bool   foundJit  = false;
    bool   foundFile = false;

    if (argc == 1) // Print help when no args are passed
    {
        DumpHelp(argv[0]);
        return false;
    }

    for (int i = 1; i < argc; i++)
    {
        bool isASwitch = (argv[i][0] == '-');
#ifndef TARGET_UNIX
        if (argv[i][0] == '/') // Also accept "/" on Windows
        {
            isASwitch = true;
        }
#endif // !TARGET_UNIX

        // Process a switch
        if (isASwitch)
        {
            argLen = strlen(argv[i]);

            if (argLen > 1)
                argLen--; // adjust for leading switch
            else
            {
                DumpHelp(argv[0]);
                return false;
            }

            if ((_strnicmp(&argv[i][1], "help", argLen) == 0) || (_strnicmp(&argv[i][1], "HELP", argLen) == 0) ||
                (_strnicmp(&argv[i][1], "?", argLen) == 0))
            {
                DumpHelp(argv[0]);
                return false;
            }
            else if ((_strnicmp(&argv[i][1], "load", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

            processMethodContext:

                tempLen = strlen(argv[i]);
                if (tempLen == 0)
                {
                    LogError("Arg '%s' is invalid, name of file missing.", argv[i]);
                    DumpHelp(argv[0]);
                    return false;
                }
                o->nameOfInputMethodContextFile = new char[tempLen + 1];
                strcpy_s(o->nameOfInputMethodContextFile, tempLen + 1, argv[i]);
                foundFile = true;
            }
            else if ((_strnicmp(&argv[i][1], "jit", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

            processJit:

                tempLen = strlen(argv[i]);
                if (tempLen == 0)
                {
                    LogError("Arg '%s' is invalid, name of jit missing.", argv[i]);
                    DumpHelp(argv[0]);
                    return false;
                }
                char* tempStr = new char[tempLen + 1];
                strcpy_s(tempStr, tempLen + 1, argv[i]);
                if (!foundJit)
                {
                    o->nameOfJit = tempStr;
                    foundJit     = true;
                }
                else
                {
                    if (o->nameOfJit2 != nullptr)
                    {
                        LogError("Too many JITs specified.");
                        DumpHelp(argv[0]);
                        return false;
                    }

                    o->nameOfJit2 = tempStr;
                }
            }
            else if ((_strnicmp(&argv[i][1], "reproName", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                tempLen = strlen(argv[i]);
                if (tempLen == 0)
                {
                    LogError("Arg '%s' is invalid, name of prefix missing.", argv[i]);
                    DumpHelp(argv[0]);
                    return false;
                }
                char* tempStr = new char[tempLen + 1];
                strcpy_s(tempStr, tempLen + 1, argv[i]);
                o->reproName = tempStr;
            }
            else if ((_strnicmp(&argv[i][1], "failingMCList", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->mclFilename = argv[i];
            }
            else if ((_strnicmp(&argv[i][1], "target", 6) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->targetArchitecture = argv[i];
            }
            else if ((_strnicmp(&argv[i][1], "boe", 3) == 0))
            {
                o->breakOnError = true;
            }
            else if ((_strnicmp(&argv[i][1], "boa", 3) == 0))
            {
                o->breakOnAssert = true;
            }
            else if ((_strnicmp(&argv[i][1], "box", 3) == 0))
            {
                o->breakOnException = true;
            }
            else if ((_strnicmp(&argv[i][1], "ignoreStoredConfig", 18) == 0))
            {
                o->ignoreStoredConfig = true;
            }
            else if ((_strnicmp(&argv[i][1], "verbosity", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->verbosity = argv[i];
                Logger::SetLogLevel(Logger::ParseLogLevelString(o->verbosity));
            }
            else if ((_strnicmp(&argv[i][1], "writeLogFile", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->writeLogFile = argv[i];
                Logger::OpenLogFile(argv[i]);
            }
            else if ((_strnicmp(&argv[i][1], "emitMethodStats", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->methodStatsTypes = argv[i];
            }
            else if ((_strnicmp(&argv[i][1], "details", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->details = argv[i];
            }
            else if ((_strnicmp(&argv[i][1], "applyDiff", argLen) == 0))
            {
                o->applyDiff = true;
            }
            else if ((_strnicmp(&argv[i][1], "compile", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                bool isValidList = MCList::processArgAsMCL(argv[i], &o->indexCount, &o->indexes);
                if (!isValidList)
                {
                    LogError("Arg '%s' is invalid, needed method context list.", argv[i]);
                    DumpHelp(argv[0]);
                    return false;
                }
                if (o->hash != nullptr)
                {
                    LogError("Cannot use both method context list and method context hash.");
                    DumpHelp(argv[0]);
                    return false;
                }
                if (o->offset > 0 && o->increment > 0)
                {
                    LogError("Cannot use method context list in parallel mode.");
                    DumpHelp(argv[0]);
                    return false;
                }

                o->compileList = argv[i]; // Save this in case we need it for -parallel.
            }
            else if ((_strnicmp(&argv[i][1], "coredistools", argLen) == 0))
            {
#ifndef USE_COREDISTOOLS // If USE_COREDISTOOLS is not defined, then allow the switch, but ignore it.
                o->useCoreDisTools = true;
#endif // USE_COREDISTOOLS
            }
            else if ((_strnicmp(&argv[i][1], "matchHash", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                if (strlen(argv[i]) != (MM3_HASH_BUFFER_SIZE - 1))
                {
                    LogError("Arg '%s' is invalid, needed a valid method context hash.", argv[i]);
                    DumpHelp(argv[0]);
                    return false;
                }
                if (o->indexCount > 0)
                {
                    LogError("Cannot use both method context list and method context hash.");
                    DumpHelp(argv[0]);
                    return false;
                }
                if (o->offset > 0 && o->increment > 0)
                {
                    LogError("Cannot use method context hash in parallel mode.");
                    DumpHelp(argv[0]);
                    return false;
                }
                o->hash = argv[i];
            }
            else if ((_strnicmp(&argv[i][1], "streaming", argLen) == 0))
            {
                if (++i >= argc)
                {
                    LogError("'-streaming' must be followed by a file name or 'stdin'.");
                    DumpHelp(argv[0]);
                    return false;
                }

                o->streamFile = argv[i];
            }
            else if ((_strnicmp(&argv[i][1], "parallel", argLen) == 0))
            {
                o->parallel = true;

                // Is there another argument?
                if (i + 1 < argc)
                {
                    // If so, does it look like a worker count?
                    bool   isWorkerCount = true;
                    size_t nextlen       = strlen(argv[i + 1]);
                    for (size_t j = 0; j < nextlen; j++)
                    {
                        if (!isdigit(argv[i + 1][j]))
                        {
                            isWorkerCount =
                                false; // Doesn't look like a worker count; bail out and let someone else handle it.
                            break;
                        }
                    }
                    if (isWorkerCount)
                    {
                        ++i;
                        o->workerCount = atoi(argv[i]);

                        if (o->workerCount < 1)
                        {
                            LogError("Invalid workers count specified, workers count must be at least 1.");
                            DumpHelp(argv[0]);
                            return false;
                        }
                        if (o->workerCount > MAXIMUM_WAIT_OBJECTS)
                        {
                            LogError("Invalid workers count specified, workers count cannot be more than %d.",
                                     MAXIMUM_WAIT_OBJECTS);
                            DumpHelp(argv[0]);
                            return false;
                        }
                    }
                }
            }
            else if ((_strnicmp(&argv[i][1], "failureLimit", argLen) == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->failureLimit = atoi(argv[i]);

                if (o->failureLimit < 1)
                {
                    LogError(
                        "Incorrect limit specified for -failureLimit. Limit must be > 0.");
                    DumpHelp(argv[0]);
                    return false;
                }
            }
            else if ((_stricmp(&argv[i][1], "skipCleanup") == 0))
            {
                o->skipCleanup = true;
            }
            else if ((_stricmp(&argv[i][1], "repeatCount") == 0))
            {
                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                bool isValidRepeatCount = true;
                size_t nextlen          = strlen(argv[i]);
                for (size_t j = 0; j < nextlen; j++)
                {
                    if (!isdigit(argv[i][j]))
                    {
                        isValidRepeatCount = false;
                        break;
                    }
                }
                if (isValidRepeatCount)
                {
                    o->repeatCount = atoi(argv[i]);
                    if (o->repeatCount < 1)
                    {
                        isValidRepeatCount = false;
                    }
                }

                if (!isValidRepeatCount)
                {
                    LogError("Invalid repeat count specified. Repeat count must be between 1 and INT_MAX.");
                    DumpHelp(argv[0]);
                    return false;
                }
            }
            else if ((_strnicmp(&argv[i][1], "stride", argLen) == 0))
            {
                // "-stride" is an internal switch used by -parallel. Usage is:
                //
                // -stride offset increment
                //
                // It compiles methods in this series until end-of-file:
                //      offset, offset+increment, offset+2*increment, offset+3*increment, ...

                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->offset = atoi(argv[i]);

                if (++i >= argc)
                {
                    DumpHelp(argv[0]);
                    return false;
                }

                o->increment = atoi(argv[i]);

                if (o->offset < 1 || o->increment < 1)
                {
                    LogError(
                        "Incorrect offset/increment specified for -stride. Offset and increment both must be > 0.");
                    DumpHelp(argv[0]);
                    return false;
                }
                if (o->indexCount > 0)
                {
                    LogError("Cannot use method context list in parallel mode.");
                    DumpHelp(argv[0]);
                    return false;
                }
                if (o->hash != nullptr)
                {
                    LogError("Cannot use method context hash in parallel mode.");
                    DumpHelp(argv[0]);
                    return false;
                }
            }
            else if (_strnicmp(&argv[i][1], "jitoption", argLen) == 0)
            {
                i++;
                if (!AddJitOption(i, argc, argv, &o->jitOptions, &o->forceJitOptions))
                {
                    return false;
                }
            }
            else if (_strnicmp(&argv[i][1], "jit2option", argLen) == 0)
            {
                i++;
                if (!AddJitOption(i, argc, argv, &o->jit2Options, &o->forceJit2Options))
                {
                    return false;
                }
            }
            else
            {
                LogError("Unknown switch '%s' passed as argument.", argv[i]);
                DumpHelp(argv[0]);
                return false;
            }
        }
        // Process an input filename
        // String comparisons on file extensions must be case-insensitive since we run on Windows
        else
        {
            char* lastdot = strrchr(argv[i], '.');
            if (lastdot == nullptr)
            {
                DumpHelp(argv[0]);
                return false;
            }

            if (_stricmp(lastdot, PLATFORM_SHARED_LIB_SUFFIX_A) == 0)
                goto processJit;
            else if (_stricmp(lastdot, ".mc") == 0)
                goto processMethodContext;
            else if (_stricmp(lastdot, ".mch") == 0)
                goto processMethodContext;
            else if (_stricmp(lastdot, ".mct") == 0)
                goto processMethodContext;
            else
            {
                LogError("Unknown file type passed as argument, '%s'.", argv[i]);
                DumpHelp(argv[0]);
                return false;
            }
        }
    }

    // Do some argument validation.

    if (o->targetArchitecture != nullptr)
    {
        if ((0 != _stricmp(o->targetArchitecture, "amd64")) &&
            (0 != _stricmp(o->targetArchitecture, "x64")) &&
            (0 != _stricmp(o->targetArchitecture, "x86")) &&
            (0 != _stricmp(o->targetArchitecture, "arm64")) &&
            (0 != _stricmp(o->targetArchitecture, "arm")) &&
            (0 != _stricmp(o->targetArchitecture, "arm32")))
        {
            LogError("Illegal target architecture specified with -target (use 'x64', 'x86', 'arm64', or 'arm').");
            DumpHelp(argv[0]);
            return false;
        }
    }

    if (o->streamFile != nullptr)
    {
        if (o->parallel)
        {
            LogError("streaming mode and parallel mode are incompatible.");
            return false;
        }

        if (o->nameOfJit2 != nullptr)
        {
            LogError("streaming mode and diff mode are incompatible.");
            return false;
        }
    }

    SPMI_TARGET_ARCHITECTURE defaultSpmiTargetArchitecture = GetSpmiTargetArchitecture();
    SetSuperPmiTargetArchitecture(o->targetArchitecture);

    if (o->nameOfInputMethodContextFile == nullptr)
    {
        LogError("Missing name of an input file.");
        DumpHelp(argv[0]);
        return false;
    }

    if (o->nameOfJit == nullptr)
    {
        // Try to find a JIT in the same directory as superpmi.exe. If found (based on targetArchitecture), use it.

        bool allowDefaultJIT = true;

        const char* hostOSTag = "";
        const char* jitHostOSPrefix = "";
        const char* jitHostOSExtension = "";

#if defined(HOST_OSX) // NOTE: HOST_UNIX is also defined for HOST_OSX
        hostOSTag = "unix";
        jitHostOSPrefix = "lib";
        jitHostOSExtension = ".dylib";
#elif defined(HOST_UNIX)
        hostOSTag = "unix";
        jitHostOSPrefix = "lib";
        jitHostOSExtension = ".so";
#elif defined(HOST_WINDOWS)
        hostOSTag = "win";
        jitHostOSExtension = ".dll";
#else
        allowDefaultJIT = false;
#endif

        const char* hostArch = "";

#if defined(HOST_AMD64)
        hostArch = "x64";
#elif defined(HOST_X86)
        hostArch = "x86";
#elif defined(HOST_ARM)
        hostArch = "arm";
#elif defined(HOST_ARM64)
        hostArch = "arm64";
#else
        allowDefaultJIT = false;
#endif

        const char* targetArch = "";

        switch (GetSpmiTargetArchitecture())
        {
            case SPMI_TARGET_ARCHITECTURE_AMD64:
                targetArch = "x64";
                break;
            case SPMI_TARGET_ARCHITECTURE_X86:
                targetArch = "x86";
                break;
            case SPMI_TARGET_ARCHITECTURE_ARM:
                targetArch = "arm";
                break;
            case SPMI_TARGET_ARCHITECTURE_ARM64:
                targetArch = "arm64";
                break;
            default:
                allowDefaultJIT = false;
                break;
        }

        size_t programPathLen = 0;
        char* programPath = nullptr;
        const char* lastSlash = strrchr(argv[0], DIRECTORY_SEPARATOR_CHAR_A);
        if (lastSlash == nullptr)
        {
            allowDefaultJIT = false;
        }
        else
        {
            programPathLen = lastSlash - argv[0] + 1;
            programPath = new char[programPathLen];
            strncpy_s(programPath, programPathLen, argv[0], programPathLen - 1);
        }

        if (allowDefaultJIT)
        {
            const char* jitOSName = nullptr;

            if (defaultSpmiTargetArchitecture != GetSpmiTargetArchitecture())
            {
                // SuperPMI doesn't know about "target OS" so always assume host OS.

                switch (GetSpmiTargetArchitecture())
                {
                    case SPMI_TARGET_ARCHITECTURE_AMD64:
                    case SPMI_TARGET_ARCHITECTURE_X86:
                        jitOSName = hostOSTag;
                        break;
                    case SPMI_TARGET_ARCHITECTURE_ARM:
                    case SPMI_TARGET_ARCHITECTURE_ARM64:
                        jitOSName = "universal";
                        break;
                    default:
                        // Can't get here if `allowDefaultJIT` was properly set above.
                        break;
                }
            }

            const char* const jitBaseName = "clrjit";
            size_t len = programPathLen + strlen(jitHostOSPrefix) + strlen(jitBaseName) + strlen(jitHostOSExtension) + 1;
            if (jitOSName != nullptr)
            {
                len += 3 /* underscores */ + strlen(jitOSName) + strlen(targetArch) + strlen(hostArch);
            }
            char* tempStr = new char[len];
            if (jitOSName == nullptr)
            {
                sprintf_s(tempStr, len, "%s%c%s%s%s",
                    programPath,
                    DIRECTORY_SEPARATOR_CHAR_A,
                    jitHostOSPrefix,
                    jitBaseName,
                    jitHostOSExtension);
            }
            else
            {
                sprintf_s(tempStr, len, "%s%c%s%s_%s_%s_%s%s",
                    programPath,
                    DIRECTORY_SEPARATOR_CHAR_A,
                    jitHostOSPrefix,
                    jitBaseName,
                    jitOSName,
                    targetArch,
                    hostArch,
                    jitHostOSExtension);
            }

            o->nameOfJit = tempStr;

            LogInfo("Using default JIT: %s", o->nameOfJit);
        }
        else
        {
            LogError("Missing name of a Jit.");
            DumpHelp(argv[0]);
            return false;
        }
    }

    if (o->skipCleanup && !o->parallel)
    {
        LogError("-skipCleanup requires -parallel.");
        DumpHelp(argv[0]);
        return false;
    }

    return true;
}