Skip to content

Latest commit

 

History

History
91 lines (69 loc) · 3.88 KB

stable-ids.md

File metadata and controls

91 lines (69 loc) · 3.88 KB

Stable identifiers

Every finding is named by what it contains. That is what lets a decision recorded against it — a suppression, a baseline entry, a comment in a review — survive the edits around it.

Stable identifiers, and what moves them

The four kinds

Name What it identifies
UnitFingerprint one whole body — a function, a method
FragmentFingerprint a run of statements inside one body
CloneGroupFingerprint a clone group; the id that closes its heading
FindingId one occurrence inside a group; printed under -v

Line numbers, AST node ids, WASM function indexes and ELF symbol indexes are never inputs to any of them. That is a deliberate constraint rather than an implementation detail: an id derived from a position moves when an unrelated edit above it moves the position, and every record kept against it goes stale silently.

Artifact symbols are named by their content too. The one exception is a set of copies of one function, same name and same body, that their callers, callees and roots cannot tell apart either: those are numbered in file order and marked identity_by_order, and a comparison of two builds pairs them by content. See Artifact analysis.

The same tree, read the same way, always produces the same ids and the same group order.

Group ids and occurrence ids are not interchangeable

The id in a group's heading is the group's. It is what [suppression] clone-ids and a baseline take, because both name whole groups — so an occurrence's id written into either matches nothing. Occurrence ids are printed as [finding <ID>] under -v.

Either kind can be opened:

codehelion explain 64f5bc34
codehelion explain 64f5bc34 --format json

A prefix is accepted as long as it is unambiguous; the report prints one short enough to type and long enough to be unique in that run, and -vv prints identifiers in full.

What moves an id, and what does not

An id is derived from normalized content, so a group, unit or fragment id stands through a comment-only edit, a reflow, a moved line, and any edit elsewhere in the file. It changes when the content it names changes.

An occurrence's FindingId also depends on where it sits: its group, the unit it is in (or the file's content, for an occurrence outside every unit), and its rank among that scope's occurrences of the group. It therefore moves when its host unit's content changes, while the group id does not.

That matters most while duplication is being removed, because removing a duplication also rewrites the code around it. A group that comes out of the rearrangement carries a new id — and would read as duplication somebody added, if nothing connected the two. Something does: when a group changes identity but retains enough member content, its lineage links the two runs, and the report says of each group whether it kept the identity it had or inherited one, and from which group.

The totals also say what became of the previous comparable run's highest-ranked groups. How many are followed is configurable:

# [report]
# churn-top = 100

What a run's identity includes

Ids identify content; a run identifies the conditions the content was read under. The mode, the frontend and normalization versions, the language, and the build variant are all part of it, and the JSON and SARIF reports carry the build variant's digest. Two runs read under different conditions are kept in separate spaces rather than compared — which is why, for instance, changing how a bare .h is read puts the results beside the previous ones rather than on top of them.

Where ids are used