class Analyzer::Cpp::Cli

Overview

Surfaces the command-line attack surface of C++ programs as cli:// endpoints: one endpoint per (sub)command with named options (param_type "flag"), positional arguments ("argument") and consumed environment variables ("env"). Covers CLI11, getopt/getopt_long, cxxopts, boost::program_options, gflags, Abseil Flags and p-ranav/argparse, plus gated getenv reads.

Line-scan analyzer (Go/Ruby/Rust CLI house style) merging endpoints by URL. There is no C++ engine, so it subclasses Analyzer directly.

Defined in:

analyzer/analyzers/cpp/cli.cr

Constant Summary

ABSL_FLAG_MARK = /\bABSL_FLAG\s*\(/

Abseil Flags. ABSL_FLAG(type, name, default, help) — the type is matched with a depth-aware manual scan (see absl_flag_names) rather than a character-class regex, so a template type with a top-level comma (e.g. std::map<std::string,int>) doesn't truncate/misparse the name field.

ADD_FLAG = /(\w+)\s*(?:\.|->)\s*add_flag\s*\(\s*"([^"]+)"/
ADD_OPTION = /(\w+)\s*(?:\.|->)\s*add_option\s*\(\s*"([^"]+)"/
APP_DECL = /\bCLI::App\s+(\w+)/

CLI11. Variable-mapped: an option/flag binds to its receiver variable (app vs a subcommand), so a root option declared after a subcommand isn't mis-attributed to it.

ARGPARSE_ADD_ARG = /(\w+)\s*(?:\.|->)\s*add_argument\s*\(\s*"([^"]+)"(?:\s*,\s*"([^"]+)")?/
ARGPARSE_DECL = /\bargparse::ArgumentParser\s+(\w+)\s*\(\s*"([^"]+)"/

p-ranav/argparse. Declare-then-register style: an ArgumentParser variable's role (root vs subcommand) is only known once it's observed either as the receiver of .parse_args(argc, argv) / owner side of .add_subparser(...), or by elimination when it's the file's only declared parser. Resolved once per file in resolve_argparse_vars before options are attributed, so declaration order in the file never decides which parser is root (a helper-function-local parser can never be mistaken for the real root).

ARGPARSE_PARSE = /(\w+)\s*(?:\.|->)\s*parse_args\s*\(\s*argc\s*,\s*argv\s*\)/
ARGPARSE_SUBPARSER = /(\w+)\s*(?:\.|->)\s*add_subparser\s*\(\s*(\w+)\s*\)/
ARGV_IDX = /\bargv\s*\[\s*(\d+)\s*\]/
CLI_MARKERS_RE = Regex.union("CLI::App", "getopt", "struct option", "cxxopts::", "program_options", "DEFINE_string", "DEFINE_int", "DEFINE_bool", "ABSL_FLAG", "argparse::ArgumentParser")

Whitespace-tolerant substring markers (no trailing "(" on macro-style entries) so this gate can never diverge from what the extraction regexes above actually accept, e.g. ABSL_FLAG (int32_t, ...) with a space before the paren is valid C++ and must not be silently skipped. Precompiled as a single Regex.union so the per-file evidence gate costs one PCRE2 match instead of up to ten naive substring scans.

CPP_TEST_PATH_RE = Regex.new((Regex.union("/test/", "/tests/", "_test.", "test_", ".test.")).source, Regex::Options::IGNORE_CASE)

Precompiled union for the per-file test-path skip gate (cpp_test_path?), matched via String#matches? instead of five naive substring scans.

EXTS = [".cpp", ".cc", ".cxx", ".c++", ".hpp", ".hh", ".hxx"]
GETENV = /\b(?:std::)?getenv\s*\(\s*"([^"]+)"/
GETOPT_CALL = /\bgetopt(?:_long)?\s*\([^,]+,[^,]+,\s*"([^"]*)"/
GFLAGS_DEF = /\bDEFINE_(?:string|int32|int64|bool|double|uint32|uint64)\s*\(\s*(\w+)/

gflags.

LONG_OPT_ENTRY = /\{\s*"([A-Za-z0-9][\w-]*)"\s*,\s*(?:no_argument|required_argument|optional_argument|\d)/

getopt_long struct option entries + short spec.

OPTS_CALL = /add_options\s*\(\s*\)/
OPTS_PAIR = /(?<!\w)\(\s*"([A-Za-z0-9][\w,-]*)"\s*,/

cxxopts / boost::program_options option specs are bare ("name", ...) grouping/chaining calls — the ( is preceded by ) or whitespace, not an identifier (which would make it a helper call like default_value(...)).

SUBCOMMAND = /(?:\.|->)\s*add_subcommand\s*\(\s*"([^"]+)"/
SUBCOMMAND_VAR = /(\w+)\s*=\s*\w+\s*(?:\.|->)\s*add_subcommand\s*\(\s*"([^"]+)"/
WEB_RE = /\b(?:Crow|crow|drogon|httplib|oatpp)\b|Crow::|drogon::|httplib::|oatpp::/

Instance Method Summary

Instance methods inherited from class Analyzer

analyze analyze, base_path : String base_path, base_paths : Array(String) base_paths, callees_needed? : Bool callees_needed?, http_header_name(name : String) : String | Nil http_header_name, line_number_for_index(content : String, char_index : Int32) : Int32 line_number_for_index, logger : NoirLogger logger, parallel_analyze(files : Array(String), &block : String -> Nil) parallel_analyze, read_file_content(path : String) : String read_file_content, result : Array(Endpoint) result, unique_params(params : Array(Param)) : Array(Param) unique_params, url : String url, web_root_path(path : String, markers : Array(String)) : String web_root_path

Constructor methods inherited from class Analyzer

new(options : Hash(String, YAML::Any)) new

Instance methods inherited from module FileHelper

all_files : Array(String) all_files, get_files_by_extension(extension : String) : Array(String) get_files_by_extension, get_files_by_extensions(extensions : Array(String)) : Array(String) get_files_by_extensions, get_files_by_prefix(prefix : String) : Array(String) get_files_by_prefix, get_files_by_prefix_and_extension(prefix : String, extension : String) : Array(String) get_files_by_prefix_and_extension, get_public_dir_files(base_path : String, folder : String) : Array(String) get_public_dir_files, get_public_files(base_path : String, anchors : Array(String) = ["shard.yml", "Gemfile"]) : Array(String) get_public_files

Instance Method Detail

def analyze #

[View source]