STIG code metrics and NEXY
This page describes the code-complexity metrics shown in STIG reports and the normalized score called NEXY.
NEXY means Normalized Engineering compleXitY.
NEXY is used at two levels:
Elementary NEXY is the percentile of one raw metric under its fitted metric model.
Composite NEXY is the weighted average of multiple elementary NEXY percentiles.
Internally, an elementary NEXY value is a 0.0 .. 1.0 percentile fraction p. Report payloads keep that exact fraction and may also carry a compact display value, for example 78 for the stored fraction 0.782.
A raw measurement remains the real engineering value, for example a count of lines, statements, parameters, branches, local variables, source nodes, or analyzer-mapped source lines. Raw values are not averaged directly. Each raw value is first mapped through the fitted model for that metric and scope.
A report therefore shows both values:
The raw value answers: how big is it in real units?
The NEXY value answers: how unusual is it compared with the reference baseline?
Why NEXY is useful
Raw engineering quantities cannot be compared directly:
These values have different units, different natural ranges, overlapping meaning, and different statistical distributions. Averaging the raw numbers would be meaningless. STIG converts each metric into the same normalized percentile scale first, then computes a weighted average of the available included elementary percentiles.
STIG then maps the percentile to a fixed NEXY zone. In this guide, lowercase p is the exact internal percentile fraction in 0.0 .. 1.0. Uppercase P<n> is the displayed integer percentile marker on the 0 .. 100 scale, written without a space, for example P9, P98, or P100.
In the gauge, these zones are drawn as an equal-zone visual scale: each zone gets exactly 1/7 of the colored arc, even though the underlying percentile ranges have different widths. This keeps narrow zones such as outliers visible and comparable in the UI. The pointer is placed by first selecting the zone from p, then interpolating within that zone's equal-width visual band.
Zone | Percentile range (0-1 float) | Percentile range in P notation (0-100) | Boundary meaning |
|---|---|---|---|
■ Outlier low |
|
| Lower 2% of the reference population. |
■ Very low |
|
| Between the 2nd and 9th percentile. |
■ Low |
|
| Between the 9th percentile and the lower quartile. |
■ Medium |
|
| Interquartile range, the middle 50%. |
■ High |
|
| Between the upper quartile and the 91st percentile. |
■ Very high |
|
| Between the 91st and 98th percentile. |
■ Outlier high |
|
| Top 2% of the reference population. |
For example, P9 means the 9th percentile, i.e. 9% of the reference population is at or below that modeled value. It is the boundary from Very low to Low.
After normalization, different metrics can be compared, displayed, and combined without pretending that every metric is linear, normally distributed, or measured in the same unit.
NEXY is designed for STIG software metrics, but the idea is general. It can be used for any engineering quantity for which a sufficiently large and representative measurement set exists: code size, path complexity, number of interface signals, timing values, resource usage, physical magnitudes, reference thresholds, or other system measurements.
Engineering quantity
A NEXY-modeled quantity is any measurable engineering value.
Examples:
Quantity | Raw value kind | STIG scope |
|---|---|---|
Lines of code | count | Function, sink/output, component/activity |
Statements | count | Function, sink/output, component/activity |
Parameters | count | Function, sink/output, component/activity |
Local variables | count | Function, sink/output, component/activity |
Branches | count | Function, sink/output, component/activity |
Cognitive complexity | score | Function, sink/output, component/activity |
Branches from data flow ( | count | Sink/output when analyzer evidence is available |
Statements from data flow ( | count | Sink/output when analyzer evidence is available |
Statements from control flow ( | count | Sink/output when analyzer evidence is available |
Sources used | count | Sink/output when analyzer evidence is available; not in NEXY |
Each quantity has a separate metric model for the scope where it is used. A function-level line-count model is not the same model as a sink-level or component-level line-count model.
Scopes
STIG uses the same source-code metric names at three scopes. Analyzer-derived source-location metrics are emitted only for scopes where the analyzer produced the required IR graph evidence.
Scope | What is measured | Typical report location |
|---|---|---|
Function | One selected C/C++ source function from | Per-function code metrics table. |
Sink/output | The code slice involved in producing one configured sink/output. Analyzer IR metrics are available only when the analyzer produced the required graph evidence. Current shipped IR metric descriptors do not require Z3 evidence. | Per-sink complexity totals and data-flow/control-flow diagrams. |
Component | The measured source-code aggregate for the element/component. Whole-component IR graph metrics are a later extension and must be produced explicitly; they are not inferred from missing analyzer data. | Component-level complexity section and project overview. |
For source-code function metrics, sourceCodeSelection.from(...) chooses the compile-command artifact. Then sourceCodeSelection.include(...) and sourceCodeSelection.exclude(...) filter both the compilation units passed to clang-tidy and the function-definition rows accepted from its diagnostics. This second filter is important because clang-tidy can report functions defined in headers included by a selected compilation unit. Dependency functions outside the selected element source folder do not enter element-core totals.
Raw values are scope-specific. Component totals are naturally larger than single-function values because a component contains more code. NEXY values are scope-normalized, so the same displayed NEXY scale is used for functions, sinks, and components.
Metric catalog
LLVM-derived source links in this table are pinned to Extra Clang Tools 18.1.8, matching STIG's pinned LLVM major version 18. The readability-function-size check is the correct clang-tidy source for lines, statements, branches, parameters, and variables: STIG parses those values from the check's diagnostic notes and links each row to the corresponding threshold option.
Key | Label | Source | Definition | HIS relationship |
|---|---|---|---|---|
| Cognitive complexity ( | Human-readability complexity. Control-flow breaks increase the score; nesting makes later breaks more expensive. | Not a classic HIS metric. STIG uses it as a modern readability metric. | |
| Branches ( | Branch count reported for the function body. | Closest clang-tidy proxy for HIS cyclomatic/path-count style metrics. For simple flat flow, McCabe is roughly branches plus one. | |
| Statements ( | Statement count reported for the function body. | Corresponds to HIS-style statement metrics such as | |
| Parameters ( | Number of function parameters. | Corresponds to HIS-style parameter metrics such as | |
| Variables ( | Number of local variables declared in the function. | Similar to local-variable count metrics where available. | |
| Lines ( | Physical source lines as reported by clang-tidy for the function. | Related to LOC-style size metrics, but not identical to every line-count definition. | |
| Minimal test count ( | STIG | Derived lower-bound planning count based on selected function metrics. | Planning metric, not a HIS metric, and not included in composite NEXY. |
| Branches from data flow ( | STIG analyzer graph | Unique selected source lines associated with a branch in the sink's data-flow graph. | Source-level branching evidence for the selected data-flow slice. |
| Statements from data flow ( | STIG analyzer graph | Unique selected source lines mapped from IR instructions in the sink's data-flow graph. | Source-size evidence for the selected data-flow slice. |
| Statements from control flow ( | STIG analyzer graph | Unique selected source lines mapped from the configured-source-to-sink ICFG slice. | Source-size evidence for the selected control-flow slice. |
| Sources used ( | STIG flow graph | Number of configured source nodes used by the selected tainted slice. | STIG data-flow metric. |
| NEXY | STIG | Composite normalized complexity based on elementary NEXY percentile values. | STIG composite index, not a HIS base metric. |
Analyzer graph metrics are availability-sensitive. If the analyzer was not run, the metric is unavailable and STIG omits it from the scored NEXY input set. STIG does not substitute 0 for an unavailable analyzer metric, and the composite NEXY denominator is the sum of the weights of available scored metrics only. Z3/path-feasibility checking is not required for the current analyzer graph metrics.
Analyzer-derived statement and branch metrics
StDf, StCf, and BrDf are computed per sink. Although LLVM IR graph evidence selects the relevant code, their raw values count unique source locations rather than LLVM instructions:
Metric | Graph used | How a source line contributes |
|---|---|---|
| Sink IR data-flow graph | The line is mapped from at least one IR instruction in the data-flow graph. |
| Configured-source-to-sink IR ICFG | The line is mapped from at least one IR instruction in the independently selected control-flow slice. |
| Sink IR data-flow graph | At least one IR instruction mapped to the line is an LLVM terminator with more than one successor. Non-branch instructions on that same line do not prevent the line being a branch. |
All three metrics apply the exact include globs, exclude globs, and analysis-provided-source exclusions recorded by the clang-tidy function_metrics.json report. An IR node contributes only when it has a non-empty source file, a positive source line, and a file inside that activity scope. STIG identifies the location by (file, line) and counts it once. Consequently, ten IR instructions mapped to one line contribute one StDf or StCf statement location. For BrDf, that same line contributes one branch location when any of its mapped data-flow IR instructions is branch-classified; it still contributes only once if several of them are branches.
These values are source-line proxies for statements selected through the two IR graphs. They are deliberately distinct from clang-tidy St, which is the statement count for every touched source function, and clang-tidy Br, which is the branch count for those complete functions. Clang-tidy also measures St independently from physical lines (Li): compact statements and macro expansions can make St greater than Li, so STIG does not impose an ordering between those two metrics.
Metric postconditions compare only compatible counting units. STIG checks BrDf <= StDf because both count unique source locations in the same data-flow slice, and StDf <= StCf because the selected data-flow locations must belong to the independently selected control-flow slice. It does not compare BrDf with clang-tidy Br, or StDf/StCf with clang-tidy St: the left side counts unique (file, line) locations while the right side counts AST constructs in complete touched functions. Such cross-unit comparisons can legitimately go in either direction and are not errors.
Minimal test count
recommendedTests is a separate planning number. It can be displayed with a raw ruler/model like other metrics, but it is not included in the composite NEXY weighted average.
For each function, STIG currently computes it from cognitive complexity, branches, and parameters:
The default minimumRecommendedTests is 1. Sink/output and component/activity recommendedTests values are sums of the contributing function-level values.
Cognitive complexity and BrDf
BrDf is computed from data-flow graph evidence. It counts unique selected source lines mapped from branch-classified LLVM IR instructions in the per-sink data-flow slice. Cognitive complexity for straight-line code is normally 0, because there is no control flow to understand.
For simple flat control flow, a useful rule of thumb is:
That approximation stops being reliable when nesting, boolean operator sequences, switch/case handling, lambdas, early exits, or other readability rules matter. Cognitive complexity intentionally penalizes code that is harder for humans to follow, while cyclomatic complexity stays closer to path counting.
For that reason, STIG uses cognitive complexity as the primary function-level readability metric and keeps BrDf for graph/slice-level branching evidence.
Metric model
For every metric and every scope, STIG uses a metric model.
A metric model contains:
the metric key and scope;
the metric direction, for example
HIGHER_IS_MORE_COMPLEX;one distribution model;
one raw-value display model.
The distribution model converts between raw metric values and percentile fractions. For code metrics this is typically a right-skewed count/score model, for example a lognormal model. It is either:
a manual/reference quantile table, where STIG stores explicit percentile to raw-value points; or
a typed statistical distribution with typed factors, for example
LogNormalDistributionModel(logMean, logStandardDeviation)orPoissonDistributionModel(lambda).
The type system can also represent a normal distribution, but the shipped STIG code-metric defaults do not use normal models. Raw code metrics are usually non-negative counts or scores with right-skewed tails, so lognormal models are the current default.
The raw-value display model carries units, tick rounding, and reference range metadata. It is separate from the distribution model. Gauge positions use the equal-zone visual axis: each NEXY zone receives the same angular width, and the pointer is interpolated inside that zone from the percentile's relative position inside the zone.
The intended calibration workflow is empirical: fit each model on a large, good-quality, representative software baseline, then keep the fitted model fixed for report interpretation. This makes NEXY a reality-based percentile metric: the raw value is compared with an observed reference distribution, not only with normative threshold values. External thresholds such as HIS can still be used as anchors or cross-checks; in the current defaults, the function-level medium-to-high boundary is aligned with HIS-style values where a suitable reference exists.
Current STIG defaults use lognormal distribution models and logarithmic raw-value metadata for all shipped NEXY metric models. These defaults are approximate engineering calibrations and are expected to improve when a larger curated measurement baseline is available. At function scope, the median (P50) is derived as P75 / 2, and the low-to-medium boundary (P25) is derived as P75 / 4.
Function metric | P25 raw boundary | P50 raw boundary | P75 raw boundary |
|---|---|---|---|
Cognitive complexity |
|
|
|
Branches |
|
|
|
Statements |
|
|
|
Parameters |
|
|
|
Variables |
|
|
|
Lines |
|
|
|
Minimal test count |
|
|
|
Sink and component models use the same metric ratios as the function models. The default sink scale is 20x the function raw boundaries, and the default component scale is 50x the function raw boundaries. Therefore, if one function-level metric has twice the raw boundary of another, the same ratio is kept for sink and component models.
The analyzer-derived metrics are sink-only. Their calibration uses reduced reference anchors because they count unique source locations selected for one sink rather than complete-function clang-tidy totals. The current raw boundaries are:
Sink metric | P25 raw boundary | P50 raw boundary | P75 raw boundary |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
This halves every raw quantile of the former analyzer-metric calibration while preserving the lognormal distribution shape and the P25:P50:P75 ratio.
STIG does not compute or select models when a report is opened. Reports use the stored metric model as the single source of truth. In the future, an offline model-building workflow may produce better metric models from a larger measurement baseline, but the report-time model shape remains the same.
From raw value to elementary NEXY
For an elementary metric, elementary NEXY is the percentile of the raw value under the selected model:
If no parametric distribution is selected, STIG can use a manual/reference quantile table:
This makes NEXY model-independent at the user level. The report does not need to explain whether the metric was lognormal, Poisson, or a quantile table in order for the user to interpret NEXY 92: it means the value is higher than about 92% of the reference baseline. This is a percentile rank, not a statistical p-value.
NEXY zones
NEXY uses the same percentile zones for every metric. The table uses displayed NEXY values:
NEXY range | Zone | Engineering meaning |
|---|---|---|
| Outlier low | Lower 2% value for this metric and scope. Not negative by itself. |
| Very low | Small/simple value. |
| Low | Below the middle range. |
| Medium | Interquartile range, the middle 50% of the reference population. |
| High | Top quartile begins; worth attention. |
| Very high | Strong attention; focused review/verification expected. |
| Outlier high | Top 2%; hard exception, refactor or justify. |
50 is shown as the median/middle marker. It is useful for understanding the scale, but it is not a split between action bands.
Why These Percentiles
The zone cuts are universal percentile cuts, not raw-value cuts. That is the reason they work for lognormal, normal, Poisson, empirical, and quantile-table models:
The metric distribution may change, but the percentile coordinate system stays the same. A Poisson model may have step-like percentiles and a lognormal model may have very wide raw-value tails, but P75 still means "higher than about 75% of the reference population" in both cases.
The current zone cuts are selected from common statistical landmarks:
Marker | Rationale |
|---|---|
| Quartiles. The medium zone is the interquartile range, the middle 50% of the reference population. |
| Rounded normal-equivalent two-sigma tail cuts. The exact normal |
| Rounded normal-quantile midpoints between |
| Display/reference range markers, close to normal-equivalent three-sigma tails ( |
This does not mean STIG assumes code metrics are normally distributed. Normal-equivalent sigma is only a naming and spacing convention for percentile rarity. For a lognormal metric, sigma is measured in log-space. The log base is only parameterization:
Both parameterizations describe the same fitted distribution when converted correctly. A +3σ lognormal value is:
or equivalently:
It is 1000x the median only if one standard deviation in log10 space is one full decade (sigma_10 = 1). That is a model property, not a consequence of using a base-10 display ruler.
Low and very-low values are descriptive. For code-complexity metrics, low values are usually good: a one-line function, a function without parameters, or a slice with few graph instructions can be very low without being a problem. For other engineering quantities, too-low and too-high values may both be problematic. That should be modeled as a separate quality/risk score or rule, not by redefining NEXY itself. NEXY remains a complexity percentile rank.
Decision thresholds
The primary process thresholds are:
Threshold | Meaning | Typical action |
|---|---|---|
| Low-to-medium threshold | Start treating the value as normal engineering complexity. |
| Medium-to-high threshold | Focused review and additional verification are recommended. |
| Outlier-high threshold | Refactor or justify; special measures if safety-relevant. |
The metric model may also define P0.1, P50, and P99.9 reference points for range calculations and diagnostics. The compact gauge labels focus on zone boundaries, because those are the decision edges.
Gauges and rulers
STIG reports use two complementary visualizations.
NEXY gauge
The NEXY value is always a percentile-rank fraction. The rendered gauge uses the same equal-zone visual axis as raw metric rulers: every zone receives the same angular width, and the pointer is interpolated inside the selected zone from the stored 0.0 .. 1.0 NEXY fraction. It is used for decisions, comparisons, and composite scores.
The percentile split between zones is always the same: 0.02, 0.09, 0.25, 0.75, 0.91, and 0.98. The rendered gauge does not use those numbers as linear angular coordinates; it maps the seven zones to equal-width visual bands.
Raw-value ruler
The raw-value ruler shows the real metric unit on the same equal-zone visual axis as NEXY. It answers the question that a normalized gauge cannot answer: how large is the value in absolute engineering terms?
Example:
The raw ruler has fixed equal-zone positions for the percentile boundaries, but the tick values are computed from the selected model:
The raw-value display model is selected per metric:
Metadata model | Used when |
|---|---|
Linear | The raw quantity is naturally additive or the model is close to normal. |
Logarithmic | The raw quantity is multiplicative or strongly right-skewed, such as many size metrics. |
Therefore, the zone boundaries have the same percentile meaning and the same visual width on every gauge, even though the underlying percentile ranges are not equally wide. Integer/count behavior is a rounding policy, not a separate axis model.
Precomputed render plans
All gauge and ruler mathematics is computed in Kotlin during report generation. The generated report embeds a small render plan for each displayed gauge/ruler. JavaScript only renders the already computed plan.
For an elementary metric, the render plan contains:
raw value, with a separate visible label that omits the unit;
unitful raw label for metadata/export;
exact NEXY value as a
0.0 .. 1.0fraction;visible NEXY label rounded to the nearest integer on the
0 .. 100scale;zone/band;
raw tick labels;
equal-zone axis positions;
overflow information, if the raw value is outside the visible range.
For a composite metric, the render plan contains only normalized NEXY values, because a composite score has no raw physical unit.
This keeps the browser renderer simple and makes Kotlin the single source of truth for metric-model application, CDF/inverse-CDF calculations, rounding, and classification.
Rounding
Classification uses the exact model-derived NEXY value. Displayed NEXY labels are rounded to the nearest integer on the 0 .. 100 scale, and raw tick labels are rounded for readability.
For integer count metrics, the default display policy is compact integer rounding: small values stay exact integers, while larger values are rounded to three significant digits so compact gauges do not print noisy labels.
The rounded label is not parsed back into the decision. A value is classified by its exact percentile, not by the rounded NEXY or raw-value text printed on the ruler.
If a project exports a raw integer gate for another process, that export may choose a conservative rounding rule explicitly. That is separate from the visual rounding used by the NEXY report.
Overflow
The raw ruler reports whether a value is outside the reference raw range (P0.1 .. P99.9 in the default models). The marker remains at its computed equal-zone position for the computed percentile, coerced to the 0 .. 100 gauge range.
For a value above P99.9, the report may show:
This avoids hiding the difference between a value that is slightly above P98 and a value that is massively above the displayed range.
Composite NEXY
A component, function, or sink/output can have several elementary metric NEXY values:
STIG keeps the individual values visible, because engineers need to know why a metric was flagged. It also computes one summary value: the weighted average of all available included elementary NEXY values.
For example, if lines has NEXY 55 with weight 0.25, and statements has NEXY 65 with weight 0.10, their combined contribution is:
Weights are deliberately allowed to be non-equal. Metrics can overlap in meaning, for example lines and statements both describe size. STIG can keep both metrics visible while using weights to control their relative contribution to the composite.
The current function-level clang-tidy collection path uses these default source-code metric weights:
Metric | Weight |
|---|---|
Cognitive complexity |
|
Branches |
|
Statements |
|
Lines |
|
Parameters |
|
Variables |
|
Report-generation paths that do not pass an explicit weight map use the model application default weight 1.0 for each included available metric.
Weights do not need to sum to 1. STIG divides by the sum of the weights of the available scored metrics.
If an analyzer-gated metric is unavailable, STIG omits that metric from the scored input set and emits an availability diagnostic. It does not substitute 0, and it does not keep the unavailable metric in the denominator.
Generated results report composite coverage: candidate metric count/weight, scored metric count/weight, and omitted metric count/weight. That makes a score based on partial evidence visibly different from a score based on the full candidate set.
The composite NEXY is classified using the same NEXY zones as elementary metrics.
Safety-mechanism interpretation
NEXY does not replace hazard analysis. It answers a narrower question:
For a component output or sink, the report can map the NEXY zone to additional engineering labels:
NEXY zone | Complexity meaning | Measures label |
|---|---|---|
Outlier low, very low, low | Low complexity |
|
Medium | Medium complexity |
|
High | High complexity |
|
Very high, outlier high | Very high or outlier complexity |
|
The compact report column is titled Measures. It classifies the complexity-driven need for supplementary safety mechanisms, additional analysis, additional verification, or alternative argumentation. It does not downgrade the baseline measures required by ASIL, hazard analysis, interface criticality, actuator authority, or explicit safety requirements. These four values and their popup explanations are defined by the SafetyMeasureImpact enum; its exhaustive mapping from NexyZone is the single policy used by component, sink, aggregate, and JSON reports.
For components, the same zone can also be mapped to a project-defined complexity class. STIG derives that class from the component-level NEXY band instead of computing it with a second formula.
The careful wording is:
not:
A safety mechanism can still be required by ASIL, hazard analysis, interface criticality, actuator authority, or an explicit safety requirement even when all complexity metrics are low.
External thresholds such as HIS
NEXY can be aligned with external engineering thresholds.
For example, if a HIS-style threshold for statements is 50, the metric model can compute:
The report can then show that the external threshold is the medium-to-high boundary for the function-level statement model. This is better than hard-coding a magic raw number into every scope, because the metric model explains where that raw number sits in the observed engineering population.
External thresholds do not replace NEXY. They are additional reference marks on the same reference scale.
What users should read in the report
For each metric, read the report as follows:
Look at the raw value to understand the absolute size.
Look at the NEXY value to understand how unusual the value is.
Use the raw ruler to see the reference threshold values in real units.
Use the NEXY gauge and zone to understand the engineering action band.
For composite decisions, use the weighted-average NEXY zone.
A good report line therefore contains both views:
and, for a composite:
The individual metrics explain the cause. The composite NEXY values summarize the engineering attention level.