Papers
Topics
Authors
Recent
Search
2000 character limit reached

SpeContext: Automated C Context Generation

Updated 4 July 2026
  • SpeContext is a technique to automatically generate a C function context from formal ACSL specifications, ensuring functions are invoked in valid states.
  • It rewrites preconditions into disjunctive normal form, infers state constraints, and generates initialization code based on dependency analysis.
  • By translating formal contracts into executable C code, SpeContext bridges specification and analysis tools, improving safety and reducing manual coding errors.

SpeContext is a technique for automatically generating an analysis context for a C function from a formal specification, with the aim of supplying abstract interpretation, symbolic execution, or testing tools with inputs and memory states that satisfy the function’s preconditions. In the formulation implemented for Frama-C, SpeContext operates on a subset of ACSL, rewrites preconditions into disjunctive normal form, infers symbolic ranges and runtime checks for relevant left-values, and emits a small “main-like” C function that initializes fresh variables accordingly before calling the target function. The method is described in “Context Generation from Formal Specifications for C Analysis Tools” (Alberti et al., 2017), where it is realized in the CfP plug-in and reported as being in operational industrial use.

1. Definition and problem setting

SpeContext addresses a standard difficulty in program analysis: many analysis tools require a suitable context before they can analyze a function meaningfully. Such a context must initialize parameters and globals so that the function is called only in states consistent with its requirements. Handwritten contexts are described as error-prone, because they may contain bugs or fail to match the intended specification (Alberti et al., 2017).

The technique therefore shifts the burden from manual context coding to context generation from formal preconditions. In the concrete instantiation summarized in the source material, the input is a function specification written in a restricted fragment of ACSL, and the output is generated C code that declares fresh variables, initializes them to satisfy the specification, and invokes the target function. This generated context is intended for the abstract interpretation-based value analysis plug-ins of Frama-C, although the underlying motivation is broader and includes abstract interpreters, symbolic execution tools, and testing tools (Alberti et al., 2017).

A central design choice is that SpeContext does not require the downstream analysis tool to natively interpret the full specification language. Instead, the specification is compiled into ordinary C code plus Frama-C built-ins such as Frama_C_unsigned_int_interval and Frama_C_make_unknown. This suggests a translation layer between formal contracts and existing analyzers, rather than a redesign of the analyzers themselves.

2. Core specification language and semantic domain

The handled ACSL fragment is explicitly restricted to quantifier-free predicates over integer arithmetic, left-values, pointer displacements, and the single predicate \defined. The grammar is given as follows (Alberti et al., 2017):

 ⁣[Emin,Emax]\![E_{\min}, E_{\max}]1

Within this fragment, T₁ cop T₂ denotes one of (≡, ≤, <, ≥, >), defined(M) states that M points to an allocated, initialized block of memory, M ++ T denotes pointer arithmetic, and M ++ T₁..T₂ denotes the set of pointers { M++T₁, …, M++T₂ }, restricted to occurrence under defined(·) (Alberti et al., 2017). The formulation also assumes that all terms are well-typed, so pointer-typed terms do not appear in arithmetic expressions.

The representation used internally by SpeContext is the state constraint. For each left-value LL, the inferred information has the form

C(L)=RXC(L) = R \oplus X

where RR is a symbolic range  ⁣[Emin,Emax]\![E_{\min}, E_{\max}] and XX is a finite set of runtime checks RTC(T₁ cop T₂) (Alberti et al., 2017). The implementation maintains both a map

Σ:{L}{state constraints}\Sigma : \{L\} \to \{\text{state constraints}\}

and a dependency graph GG over left-values. The dependency graph is used to order generated initializations and to reject cyclic dependencies.

The source also fixes normal-form assumptions before inference. Arithmetic is flattened and constant-folded, so an expression such as (L++(i..j))++(k..l) is rewritten to L++((i+k)..(j+l)), and singleton ranges are eliminated (Alberti et al., 2017). This normalization is important because the inference rules operate on canonicalized forms.

3. Inference from preconditions to state constraints

SpeContext begins by rewriting the conjunction of all requires clauses into disjunctive normal form:

i=1n  j=1miPij\bigvee_{i=1}^n \; \bigwedge_{j=1}^{m_i} P_{ij}

Each conjunctive clause Ci=jPij\mathcal C_i = \bigwedge_j P_{ij} is processed independently (Alberti et al., 2017). The inference judgment is written

ΣPΣ\Sigma \vdash P \Rightarrow \Sigma'

meaning that literal C(L)=RXC(L) = R \oplus X0, interpreted under the current constraint map C(L)=RXC(L) = R \oplus X1, yields an updated map C(L)=RXC(L) = R \oplus X2.

For defined predicates, the rules introduce neutral constraints when needed. The source defines

C(L)=RXC(L) = R \oplus X3

and uses this in rules such as Var for defined(x) and Deref for defined(*M) (Alberti et al., 2017). There is also a Range-1 rule for patterns such as defined(L) ∧ defined(L++T₁..T₂), which extends the admissible offset range of L by joining with [0..T₂] and records a dependency C(L)=RXC(L) = R \oplus X4.

For integer comparisons, SpeContext first attempts to rewrite the constraint into the form L cop E, where C(L)=RXC(L) = R \oplus X5 is a left-value and C(L)=RXC(L) = R \oplus X6 is an integer term. If successful, it tightens the current range of C(L)=RXC(L) = R \oplus X7 by intersecting it with the interval induced by cop:

C(L)=RXC(L) = R \oplus X8

If such a rewriting is not possible, the system emits a runtime check instead of a tighter range (Alberti et al., 2017). Pointer equality is handled by solving aliasing constraints when both sides are of the form L_i++k_i; negation is limited, with ¬defined(M) permitted only if M is not yet in C(L)=RXC(L) = R \oplus X9, and non-equality of pointers checked against non-overlapping current ranges (Alberti et al., 2017).

After all literals in a conjunctive clause have been processed, SpeContext obtains a clause-specific map RR0 and dependency graph RR1. If a clause is inconsistent or violates a rule, it is dropped as an empty case (Alberti et al., 2017). This clause-dropping behavior is part of how the translation preserves satisfiable cases while excluding infeasible branches.

4. Context generation algorithm

The generation algorithm operates in two levels: clause-wise inference followed by code emission. In high-level pseudo-code, the procedure is given as follows (Alberti et al., 2017):

 ⁣[Emin,Emax]\![E_{\min}, E_{\max}]2

GenerateContextBlock first topologically sorts the left-values in RR2, then emits declarations for fresh variables, emits range initializations or memory initializations for each constrained left-value, guards these initializations with any accumulated runtime checks, and finally emits the function call f(args…) (Alberti et al., 2017). If a constrained left-value denotes a block of memory, generation uses a loop and assignments or Frama_C_make_unknown; for scalar intervals it uses make_range(type(L′), Emin, Emax) at the abstract level, realized concretely in Frama-C via built-ins such as Frama_C_unsigned_int_interval (Alberti et al., 2017).

When the precondition has multiple satisfiable DNF branches, the generated context combines them through a synthetic switch:

 ⁣[Emin,Emax]\![E_{\min}, E_{\max}]3

This top-level disjunction is the mechanism by which one generated function covers all admissible precondition cases (Alberti et al., 2017). A plausible implication is that the emitted context behaves like an explicit symbolic input partition over the precondition’s DNF branches.

5. Soundness and relation to abstract interpretation

The key correctness claim is presented as a soundness theorem for each generated block corresponding to one conjunctive clause. For a conjunctive precondition RR3 and inferred state constraints RR4, the generated block RR5 satisfies two properties (Alberti et al., 2017):

  1. Every path through RR6 that reaches f(…) yields a state that models RR7.
  2. For every model state of RR8, there is some path in RR9 that reaches f(…) in that state.

The supporting intuition rests on three components. First, range initializations use Frama-C built-ins that abstract-interpret to exactly the symbolic ranges computed by the inference system. Second, runtime checks introduced as if (expr) … enforce the residual conditions that could not be compiled into ranges. Third, the DNF disjunction switch covers the distinct satisfiable branches of the original precondition (Alberti et al., 2017).

The theorem is stated under the assumption that the Frama-C built-ins respect their specification, namely that the interpreter assigns exactly the requested range to the variable (Alberti et al., 2017). Within that assumption, the generated context is both safe and complete relative to the handled specification fragment.

This formulation is significant for abstract interpretation because it avoids the common mismatch between logical preconditions and executable initialization code. The generated code is not merely a witness for satisfiability; it is intended to characterize the admissible state space of the function call with enough precision for value analysis. This suggests why the method is framed around symbolic ranges plus runtime checks rather than arbitrary program synthesis.

6. CfP implementation and the aes_crypt_cbc example

The implementation described in the source is the CfP plug-in for Frama-C, written in OCaml as a standard Frama-C plug-in (Alberti et al., 2017). Its architecture includes a front-end specification parser, an inference engine implementing the state-constraint rules and DNF unfolding, and a code generator that emits a helper function cfp_<fname>() in Frama-C’s AST. The code generator inserts calls to Frama_C_unsigned_int_interval( lo, hi ) for integer ranges and Frama_C_make_unknown(ptr, size) for pointer blocks, generates if (RTC…) guards, and constructs the disjunction switch when necessary (Alberti et al., 2017). The plug-in is activated with -specontext, and it can automatically launch a value analysis on the generated helper (Alberti et al., 2017).

The concrete example in the source is aes_crypt_cbc, accompanied by an ACSL contract containing constraints on ctx, mode, length, iv, input, and output (Alberti et al., 2017). From that contract, SpeContext infers, among other constraints:

  • length: interval  ⁣[Emin,Emax]\![E_{\min}, E_{\max}]0 plus RTC(length%16≡0)
  • ctx->buf: pointer range [0..63* sizeof(unsigned long)]
  • ctx->nr: integer range [14..14]
  • ctx->rk: alias constraint ctx->rk ≡ ctx->buf
  • mode: interval [0..1]
  • iv: buffer size 16, initialized
  • input: buffer size length, initialized
  • output: buffer size length, uninitialized but valid (Alberti et al., 2017)

The dependency graph for this example includes

 ⁣[Emin,Emax]\![E_{\min}, E_{\max}]4

and the generated context initializes cfp_length with Frama_C_unsigned_int_interval(16,16672), guards the rest with if (cfp_length % 16 == 0), initializes cfp_ctx.buf with Frama_C_make_unknown, sets cfp_ctx.nr = 14, aliases cfp_ctx.rk = cfp_ctx.buf, creates and initializes cfp_iv, allocates buffers for cfp_input and cfp_output, chooses cfp_mode from {0,1}, and finally calls aes_crypt_cbc (Alberti et al., 2017).

This example illustrates the clause-to-code correspondence emphasized by SpeContext: interval constraints become range generators, equalities become assignments or aliasing, initialization requirements become Frama_C_make_unknown, and residual arithmetic constraints become explicit guards (Alberti et al., 2017).

7. Scope, limitations, and nomenclature

SpeContext is explicitly scoped to a subset of ACSL: quantifier-free predicates over integer arithmetic, left-values, pointer displacements, and \defined (Alberti et al., 2017). The CfP inference engine reports an error when the specification is too rich, for example when it contains quantifiers or unsupported predicates (Alberti et al., 2017). The handling of negation is also restricted, and disjunctive reasoning proceeds only through DNF decomposition plus per-clause inference.

These restrictions are not incidental. They reflect a design that aims to preserve soundness for Frama-C value analysis while generating executable contexts in a disciplined way. This suggests a trade-off between expressiveness of the accepted contract language and predictability of the generated initialization code.

A potential source of confusion is nomenclature. The term “SpeContext” in the present sense refers to the formal-specification-driven context generation technique for C analysis tools (Alberti et al., 2017). The same label also appears in later, unrelated literature on long-context LLM inference, specifically “SpeContext: Enabling Efficient Long-context Reasoning with Speculative Context Sparsity in LLMs” (Xu et al., 30 Nov 2025). These are distinct research lines sharing a name rather than a methodology.

Within its intended setting, SpeContext is characterized by four defining elements: formalization of preconditions as a restricted logical language, inference of state constraints and dependencies, code generation of a small context function, and a soundness argument tied to Frama-C built-ins (Alberti et al., 2017). That combination places it at the intersection of contract-based program analysis, context generation, and abstract-interpretation-oriented tooling.

Topic to Video (Beta)

No one has generated a video about this topic yet.

Whiteboard

No one has generated a whiteboard explanation for this topic yet.

Follow Topic

Get notified by email when new papers are published related to SpeContext.