Following a Memory Region Through Code
A safety requirement is meaningful only when it is attached to the memory or value it actually protects. CULPA’s Memory Segment Constructor and Safeguard Analyzer address that problem by following an object as it is allocated, divided into relevant regions, passed through functions, and changed by later operations. The focus here is the state of that evidence: how a vector’s initialized bytes differ from its spare capacity, why symbolic metadata is needed, and how CULPA decides that a safeguard either reaches an unsafe sink or has been compromised on the way.
An allocation is not one undifferentiated region
Ordinary source code often gives one variable responsibility for several distinct parts of memory. A Vec<T> has an allocation, a current length, and a capacity. The initialized elements occupy the range from the start through the current length. The remaining capacity belongs to the allocation, but it is not automatically a range of initialized T values. If the program increases the vector’s length, it changes which part of the allocation later code will treat as elements.
That difference is easy for a human to describe and difficult for a conventional data-flow analysis to preserve. The type Vec<T> does not pass every boundary as a separate argument to each operation. A call may receive a pointer produced from the vector while the capacity remains hidden in the original container’s metadata. A later length value may be computed in another function. If the analyzer follows only variable names or whole allocations, it can lose the distinction between the initialized prefix and the spare region.
CULPA represents a memory segment as two pieces of information: the object where the memory was allocated and a partition describing the relevant portion. The partition may be the entire object, a range inside a container, or one field of a structure. This gives a requirement a concrete target. An initialization condition can apply to the initialized range of one vector; an alignment condition can apply to a pointer view of one allocation; a field-sensitive condition can apply to one member without being credited to every byte in the containing object.
The constructor builds this representation in stages. It first seeds an identity at a pointer parameter or a heap-allocation site, because those are the places where an object enters the function’s reasoning. It then propagates that identity through assignments. When code takes a container range, the constructor creates a partition based on the container’s metadata. When code projects a structure field, it creates a field-specific partition. When a pointer argument reaches an unsafe function, the analysis records a sink and maps the argument back to the segments it may refer to.
This is not a claim that the program has created new physical allocations every time it creates a segment. The segments are an analysis abstraction for answering “which part of which object does this condition concern?” A single allocation can therefore produce several logical segments, and two pointer variables can share one segment when assignments or aliases show that they refer to the same underlying object.
Dynamic metadata makes the boundary move. CULPA uses symbolic values such as len(vec) and cap(vec) rather than replacing every length with one concrete number.