Skip to content

External Models

Hugo edited this page Sep 25, 2026 · 2 revisions

External Models

The analyzer reads model files that describe API behavior, so a library's contracts do not have to be hardcoded. Three models exist, each with its own flag:

Model Flag Feeds
Resource lifetime --resource-model=<path> ResourceLifetime.*
Stack escape --escape-model=<path> StackPointerEscape
Buffer write --buffer-model=<path> MemcpyWithStackDest, copy/string overflow checks

Bundled models live under models/, one generic.txt per family.

Common syntax rules

  • Lines starting with # are comments; empty lines are ignored.
  • Argument indices are 0-based.
  • <function-pattern> supports exact names and the globs * and ?.
  • Matching is tried against the mangled name, the demangled name and the demangled base name.

Character classes are not supported. [...] is rejected with an explicit parse error in the escape and buffer models — only * and ? are implemented. Some bundled model files still carry a header comment mentioning [ ... ]; the parser is authoritative.


Resource Lifetime Model (--resource-model)

acquire_out <pattern> <arg> <kind> [if_ret...]
acquire_ret <pattern> <kind>       [if_ret...]
release_arg <pattern> <arg> <kind> [if_ret...]
noeffect    <pattern>
Directive Meaning
acquire_out The function writes a new handle to the out-parameter at <arg>
acquire_ret The function returns a new handle
release_arg The function releases the resource passed at <arg>
noeffect The callee neither releases nor retains any pointer it receives — string and IO libc functions

<kind> is a user-defined string (HeapAlloc, VkBuffer, …) used to pair acquires with releases. It must match between the acquire_* and release_arg directives.

Conditional effects: if_ret

An optional trailing qualifier makes the effect conditional on the call's return value:

if_ret==0, if_ret!=0, if_ret>=0, if_ret<0, if_ret==null, if_ret!=null

Without it the effect is unconditional. This is what lets a model describe an API that only hands over ownership when it succeeded:

acquire_out vkCreateBuffer 3 VkBuffer if_ret==0

noeffect matters as much as the acquires: without it, a handle passed to strlen or fprintf looks like it might have been retained, and the analysis cannot conclude.

Example

# C heap allocation
acquire_ret malloc HeapAlloc
release_arg free 0 HeapAlloc

# C++ heap allocation (demangled names contain spaces, use globs)
acquire_ret operator*new* CppHeap
release_arg operator*delete* 0 CppHeap

# Custom API, ownership only on success
acquire_out acquire_handle 0 GenericHandle if_ret==0
release_arg release_handle 0 GenericHandle

# Vulkan
acquire_out vkCreateBuffer 3 VkBuffer
release_arg vkDestroyBuffer 1 VkBuffer

# Callees that keep nothing
noeffect strlen
noeffect fprintf

Bundled model

models/resource-lifetime/generic.txt covers C malloc/free, C++ new/delete, 20+ Vulkan resource types, and the noeffect entries for common libc functions.

Cross-TU summaries

With a resource model and more than one input file, the analyzer builds cross-TU summaries to propagate ownership across translation units.

Option Effect
--resource-cross-tu Enable (default: on)
--no-resource-cross-tu Local-only analysis
--resource-summary-cache-dir=<path> Filesystem cache (default .cache/resource-lifetime)
--resource-summary-cache-memory-only In-process cache only, no filesystem writes

When ownership still cannot be decided, the analysis says so — ResourceLifetime.AnalysisIncomplete (Info) or ResourceLifetime.IncompleteInterproc (Warning) — rather than reporting nothing. See Diagnostic Rule Catalog.


Stack Escape Model (--escape-model)

Declares which arguments are consumed synchronously and not captured, which suppresses false StackPointerEscape reports for APIs that use a pointer during the call only.

noescape_arg <pattern> <arg-index>

noescape_arg is the only directive; anything else is a parse error naming the line.

# Vulkan descriptor updates consume pointer arrays immediately
noescape_arg vkUpdateDescriptorSets 2
noescape_arg vkUpdateDescriptorSets 4

Resolution order

  1. LLVM attributes at the call site: nocapture, byval, byref
  2. Interprocedural summary, for functions whose definition is analysed
  3. External escape model: the noescape_arg rules
  4. Opaque external call: no strong escape diagnostic is emitted

The model only has to describe what the first two steps cannot see.

Bundled: models/stack-escape/generic.txt.


Buffer Write Model (--buffer-model)

Declares which calls write into a destination buffer, and whether the write is bounded by a size argument. This is what the copy and string overflow checks read.

bounded_write   <pattern> <dst-arg-index> <size-arg-index>
unbounded_write <pattern> <dst-arg-index>
Directive Meaning
bounded_write The call writes into <dst-arg-index>, limited by <size-arg-index>
unbounded_write The call writes into <dst-arg-index> with no size argument

The distinction drives the CWE: a write with an explicit length maps to CWE-121, one without to CWE-120 — see Diagnostic Rule Catalog.

# Bounded byte writes/copies
bounded_write memcpy 0 2
bounded_write memmove 0 2
bounded_write memset 0 2
bounded_write strncpy 0 2
bounded_write strncat 0 2
bounded_write stpncpy 0 2

# Fortified / wrapped forms frequently seen in optimized builds
bounded_write *__memcpy_chk* 0 2
bounded_write *__strncpy_chk* 0 2

# Unbounded writes (no explicit size argument)
unbounded_write strcpy 0

The fortified *_chk patterns matter in practice: an optimized build replaces memcpy with __memcpy_chk, and without the pattern the model would silently stop matching.

Bundled: models/buffer-overflow/generic.txt.


Writing Your Own Models

  1. Start from the bundled models and extend them.
  2. Use globs for C++ overloads: operator*new*.
  3. Keep resource kinds consistent between acquire_* and release_arg.
  4. Add noeffect entries for callees that keep nothing — it is what turns "cannot conclude" into a verdict.
  5. Use if_ret when an API only transfers ownership on success.
  6. Do not use [...]: it is rejected.

Models in regression tests

A test source names its models in a comment:

// resource-model: models/resource-lifetime/generic.txt
// escape-model: models/stack-escape/generic.txt
// buffer-model: models/buffer-overflow/generic.txt

See Test Infrastructure.

Clone this wiki locally