Coccinelle (spatch): Semantic Patches for C, the Way the Linux Kernel Does Sweeping Refactors

2026-07-16

Every C programmer has stared at 800 call sites of some API that needs to change and reached for sed, then given up. Coccinelle (Common Coccinelle), from ENS/Inria, is the tool the Linux kernel actually uses. Its command is spatch, and it applies semantic patches written in SmPL — a little language that looks like a unified diff with metavariables, but is applied against a parsed AST with type information, control flow, and configurable isomorphisms.

The classic example: replace the kmalloc() + memset(0) pattern with kzalloc().

@@
expression E, size;
@@
- E = kmalloc(size, GFP_KERNEL);
+ E = kzalloc(size, GFP_KERNEL);
  if (E == NULL) return -ENOMEM;
- memset(E, 0, size);

Run it:

spatch --sp-file kzalloc.cocci --in-place --dir drivers/

Isomorphisms mean E == NULL, NULL == E, and !E are all matched by the same rule. You don't write the variants; Coccinelle knows they're equivalent. Try that in sed.

Type-awareness is where it leaves text tools behind. This only fires when the multiplication is actually a size calculation for the pointed-to type:

@@
type T;
T *ptr;
expression n;
@@
- ptr = malloc(n * sizeof(T));
+ ptr = calloc(n, sizeof(T));

Because T is bound to the declared type of ptr, Coccinelle refuses to match malloc(count * sizeof(struct other)). And thanks to the arithmetic isomorphism, sizeof(T) * n matches too — for free.

You can add code around control flow. This inserts locking on every path out of a function:

@@
identifier fn;
@@
fn(...) {
+ mutex_lock(&my_lock);
  ...
+ mutex_unlock(&my_lock);
}

The ... is not a literal — it's SmPL for "any sequence of statements," and the transformation is placed on every exit including returns and gotos, not just textually before the closing brace.

Match-only rules (prefix lines with *) turn spatch into a linter that reports without modifying:

@@
identifier f;
@@
* f(void) {
    return -EINVAL;
  }

For a real invocation on a big tree, parallelism and header handling matter:

spatch --sp-file convert.cocci \
       --dir . --include-headers \
       --jobs 8 --chunksize 100 \
       --in-place

Why not sed, perl, or comby? sed and perl don't know a comment from a string literal, let alone a type. comby understands brackets and gives you structural search — genuinely useful, and covered here already — but it operates on syntax. Coccinelle actually parses C. It knows the difference between a function name and a variable name that shadows it, respects preprocessor conditionals, handles whitespace and comment preservation, and lets you match on semantic shape: "a function that returns -ENOMEM on the error path" is expressible.

The Linux kernel keeps ~90 semantic patches in scripts/coccinelle/, run in CI on every submission. Read scripts/coccinelle/api/ for battle-tested examples — kzalloc-simple.cocci is the exact rule above, refined.

The learning curve is a real day of frustration. SmPL isn't C, and you'll fight the parser. But when you write one 20-line semantic patch that correctly refactors 500 files across a 30M-line codebase with type-checked correctness, you'll never grep for a function name again.

Key Takeaway: When a C API change touches thousands of call sites, spatch refactors your code by understanding its types and control flow — not by matching its text.

All newsletters