I count 1,672 functions in openssh-portable that are both structurally complex and changing recently, out of 4,710 analyzed at commit 6545e6a, and 810 of them land in the critical band. The top five all sit in that live-fire group: main in ssh.c and process_server_config_line_depth in servconf.c tie for the top activity-weighted risk score at 20.86, with main in ssh-keygen.c close behind at 20.37. I would treat these as live regression risks rather than backlog items, because functions with cyclomatic complexity of 84 to 474 sit on paths last modified 4 to 30 days ago. Anyone merging a change this week is most likely to land it in, or next to, one of these five.
The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).
Top 5 Hotspots
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
main | ssh.c | 20.9 | 211 | 16 | 125 |
process_server_config_line_depth | servconf.c | 20.9 | 474 | 17 | 70 |
main | ssh-keygen.c | 20.4 | 177 | 11 | 80 |
match_cfg_line | readconf.c | 20.0 | 87 | 13 | 18 |
dopr | openbsd-compat/bsd-snprintf.c | 19.6 | 84 | 8 | 10 |
Large Repo Analysis
openssh-portable is a large repository. To stay within memory constraints, this analysis used hybrid touch mode: structural complexity — CC, ND, FO — is measured precisely for every function. Git activity is tracked at the function level (via git log -L) only for files with 5 or more commits in the touch window (365 days by default); other files use a file-level approximation. Rankings therefore surface functions that are both structurally complex and in the most actively-changing parts of the codebase. Dormant code with high structural complexity will rank lower than it would under a full per-function analysis — to surface it, run hotspots analyze . --per-function-touches on a machine with sufficient memory.
Codemod / Tooling Files in Results
openbsd-compat/bsd-snprintf.c is a bundled compatibility implementation of snprintf rather than OpenSSH’s own application logic, and I kept dopr in the analysis because it is part of the portable build and is still receiving commits. If you want to focus a report on application code instead, the exclude pattern is { "exclude": ["openbsd-compat/"] }.
This is not a verdict on OpenSSH’s quality. The score combines structural complexity with recent commit activity, and it says where review effort buys the most protection, not where bugs are known to exist.
4,710 functions analyzed
The quadrant split is unusual. Zero functions fall into the debt or ok quadrants. The question is not which complex code is dormant, but which complex code is moving.
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.Deeply Nested×5Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.Exit Heavy×5Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.God Function×5God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Long Function×5Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
All five top hotspots carry the same five patterns: complex branching, deep nesting, exit-heavy control flow, god function, and long function. That uniformity is informative in itself. These are not five different problems but one problem, large functions that do everything in one body, showing up in five places. In C, manual memory management and preprocessor conditionals compound the effect, adding paths that cyclomatic complexity alone does not fully capture.
main — ssh.c
I would start with main in ssh.c. It combines a cyclomatic complexity of 211 with a nesting depth of 16 and a fan-out of 125, and it was last changed 15 days ago, with 3 touches in the last year. The excerpt shows what it carries: closing stray file descriptors, saving argv for process-title emulation under a platform conditional, looking up the user, setting the umask, initializing options, allocating session state, and then parsing command-line arguments. That is the whole client lifecycle in one body.
A fan-out of 125 means this function directly calls 125 distinct functions. A change to any of those callees can surface here, and there is no seam to test argument parsing separately from connection setup. The file has 5 total commits in the data, none linked to bugs or reverts, spread across 3 distinct authors in the last 90 days. The history is quiet, so the risk is structural and multi-author: three people editing one 211-path function is where merge-time interactions happen.
My recommendation is to extract the command-line parsing loop and the post-parse option resolution into separate functions with explicit input structs. The comment in match_cfg_line that says to keep it in sync with ssh.c:main() shows the logic is already duplicated across the boundary, which makes it the natural first seam.
process_server_config_line_depth — servconf.c
This function has the highest cyclomatic complexity in the data, 474, and the deepest nesting, 17. From the name and the excerpt, it parses one line of the sshd server configuration, organized around a switch on a keyword opcode with a case per directive, including platform-specific ones guarded by preprocessor conditionals such as the PAM option. It was last changed 13 days ago and touched once in the last year.
A large keyword-dispatch switch is the most forgivable shape for a high complexity number, since many of the 474 paths are siblings rather than nested logic. Even so, every new directive adds paths to a function that already needs hundreds of test cases for full coverage. The file shows a bug-fix rate of 20% across 5 commits, with no bug-linked commits or reverts recorded. I read that as mild supporting context, not evidence of a defect in this function.
I would replace the switch body with a table of directive descriptors carrying the target field, type, and allowed contexts, keeping hand-written handlers only for the genuinely irregular directives. The excerpt’s repeated pointer variables for ints, strings, and string arrays are exactly what a table-driven approach consolidates.
main — ssh-keygen.c
The main in ssh-keygen.c has a cyclomatic complexity of 177, a nesting depth of 11, and a fan-out of 80, and it was last changed 30 days ago. The excerpt opens with a long block of local flags for distinct operations (changing passphrases, finding hosts, deleting hosts, hashing hosts, generating hostkeys, building revocation lists), followed by a getopt loop with a case per option. In effect, one function dispatches for many unrelated tools.
This file has the thinnest history of the five: 1 commit in the data and 1 author in the last 90 days, with no bug-linked or revert signals. I read that as a live regression risk with a single point of knowledge. I would split post-parse behavior into one function per operation mode, so the option loop only sets state and a small dispatcher picks the handler.
match_cfg_line — readconf.c
At 87, the cyclomatic complexity is the second smallest of the top five, just above dopr at 84, but the nesting depth of 13 is steep. The function was last changed 10 days ago, with 3 touches in the last year. From the excerpt, it evaluates a client-side Match line by walking attributes, handling negation, treating special cases like ‘all’, ‘canonical’, and ‘final’ as criteria that take no argument, and then comparing prefixed criteria such as host and user.
Three different jobs are interleaved in one loop with gotos to cleanup labels: parsing attribute tokens, handling no-argument criteria, and evaluating argument-bearing criteria. The file has 3 commits in the data and 3 distinct authors in the last 90 days, so several people are editing it. With no bug-linked commits, I would frame this as a coordination risk. I would extract one evaluator function per criterion type, returning match, no-match, or error, so the loop becomes a thin driver and each criterion gets its own unit tests.
dopr — openbsd-compat/bsd-snprintf.c
dopr is the format-string engine behind the bundled snprintf replacement. It has a cyclomatic complexity of 84, a nesting depth of 8, and a low fan-out of 10, so unlike the other four, its complexity is self-contained rather than coupled outward. It was last changed 4 days ago, the most recent of the five, with 2 touches in the last year.
The excerpt shows a state machine: a loop over states for default text, flags, minimum width, precision, and conversion modifiers, with nested switches inside each state. That shape explains the numbers. It is a classic hand-written printf parser, and the nesting comes from switch inside switch inside loop.
This is a compatibility shim, so I would not recommend a rewrite. The practical step is to cover the format-parsing states with table-driven tests, one row per flag, width, precision, and length-modifier combination, before the next edit lands. With a function this contained, the tests are cheap to write and give a safety net for exactly the kind of change made 4 days ago.
What I would do first
The five functions share one structural diagnosis, so one habit addresses most of it: extract before you extend. The risk is concentrated, not diffuse.
Codebase Risk Distribution
All five top hotspots share the same structural patterns (complex_branching, deeply_nested, exit_heavy, god_function, long_function), which is typical of the highest-risk functions in any large codebase — they accumulate every structural signal on the way to the top. More useful context is how the risk is distributed across all 4,710 analyzed functions:
| Band | Functions |
|---|---|
| Critical | 810 |
| High | 862 |
| Moderate | 1,318 |
| Low | 1,720 |
Hotspot patterns belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.
See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function.
Reproduce This Analysis
git clone https://github.com/openssh/openssh-portable
cd openssh-portable
git checkout 6545e6a55d4aaad0f48f2bb5c865935692ce32ea
hotspots analyze . --mode snapshot --explain-patterns --force --hybrid-touches 5
To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.
I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →