|
| 1 | +# C / C++ — what enola extracts |
| 2 | + |
| 3 | +Parsed with tree-sitter-c and tree-sitter-cpp. Detected by a C source (`.c`), a C++ |
| 4 | +source (`.cpp`/`.cc`/`.cxx`/`.hpp`/…), or a build file (`CMakeLists.txt`, `Makefile`, |
| 5 | +`meson.build`, `*.vcxproj`) plus any header. Language is recorded **per fact**, so a mixed |
| 6 | +tree reports `c` and `cpp` separately rather than collapsing to one. |
| 7 | + |
| 8 | +Fixture: [`cpp_sample`](../../internal/engine/testdata/repos/cpp_sample/) |
| 9 | + |
| 10 | +## At a glance |
| 11 | + |
| 12 | +| You write | enola stores | Kind | |
| 13 | +|---|---|---| |
| 14 | +| a source directory | one module per directory | `module` | |
| 15 | +| `int foo(void) { … }` | a symbol with `has_body=true` | `symbol` | |
| 16 | +| `static int foo(void)` | the same, with `static=true`, `exported=false` | `symbol` | |
| 17 | +| a declaration in a header + definition in a `.c` | **one** merged symbol, not two | `symbol` | |
| 18 | +| `#define X`, `const int` | a `constant` symbol | `symbol` | |
| 19 | +| `gc->set = xlp_gpio_set;` | a call edge to the assigned function | relation | |
| 20 | +| `.lock = fn` in a compound literal | a call edge to `fn` | relation | |
| 21 | +| a function name inside a `#define` body | a call edge recovered by a macro pre-pass | relation | |
| 22 | +| a token-pasted callback (`_pfx##_name##_show`) | a call edge to the pasted name | relation | |
| 23 | +| a call inside a `for`/`while` | `calls_in_loop` / `calls_in_scaling_loop` | props | |
| 24 | + |
| 25 | +## Symbols, and why `has_body` matters |
| 26 | + |
| 27 | +```c |
| 28 | +static int omap_reserve(void) { … } |
| 29 | +``` |
| 30 | +
|
| 31 | +``` |
| 32 | +symbol drivers.omap_reserve drivers/board.c:12 |
| 33 | + props: symbol_kind=function, language=c, static=true, exported=false, |
| 34 | + has_body=true, cyclomatic=1 |
| 35 | +``` |
| 36 | +
|
| 37 | +A header declaration and its definition are the same entity. Merging them means the |
| 38 | +symbol has one location — the definition — and one set of callers, instead of a phantom |
| 39 | +zero-caller declaration sitting next to the real thing. |
| 40 | +
|
| 41 | +## The three ways a C callback gets its only caller |
| 42 | +
|
| 43 | +C code wires behaviour through function pointers, and none of it looks like a call. All |
| 44 | +three forms are recovered, because otherwise most of a driver reads as dead code. |
| 45 | +
|
| 46 | +**1. Function-pointer field assignment** |
| 47 | +
|
| 48 | +```c |
| 49 | +static int probe(struct gpio_chip *gc) |
| 50 | +{ |
| 51 | + gc->set = xlp_gpio_set; /* plain */ |
| 52 | + gc->get = &xlp_gpio_get; /* address-of */ |
| 53 | + ct->chip.irq_mask = mvebu_mask; /* nested field */ |
| 54 | + gc->ngpio = 32; /* plain data — must NOT create an edge */ |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +The first three create a `calls` edge from `probe` to the assigned function. The fourth |
| 59 | +assigns an integer and creates nothing — the discriminator is whether the right-hand side |
| 60 | +names a known function, not whether the statement is an assignment. |
| 61 | + |
| 62 | +**2. Compound-literal designated initializers** |
| 63 | + |
| 64 | +```c |
| 65 | +cfg = (struct regmap_config) { |
| 66 | + .reg_bits = 8, |
| 67 | + .lock = dio48e_regmap_lock, |
| 68 | + .unlock = dio48e_regmap_unlock, |
| 69 | +}; |
| 70 | +``` |
| 71 | + |
| 72 | +`dio48e_regmap_lock` and `dio48e_regmap_unlock` get inbound edges from the enclosing |
| 73 | +function. `.reg_bits = 8` does not. |
| 74 | + |
| 75 | +**3. References that exist only inside macro bodies** |
| 76 | + |
| 77 | +A function named only in a `#define` replacement list is invisible to the AST — the |
| 78 | +preprocessor would have to run first. A project-wide macro pre-pass recovers both the call |
| 79 | +position and the value position: |
| 80 | + |
| 81 | +```c |
| 82 | +#define ATTR_PERM(_pfx, _name, _perm) \ |
| 83 | + static struct configfs_attribute _pfx##attr_##_name = { \ |
| 84 | + .show = _pfx##_name##_show, \ |
| 85 | + .store = _pfx##_name##_store, \ |
| 86 | + } |
| 87 | +``` |
| 88 | +
|
| 89 | +The pre-pass is **project-wide, not include-scoped**, so the pasted `cfg_label_show` / |
| 90 | +`cfg_label_store` callbacks are recovered even though the invoking file does not literally |
| 91 | +`#include` the header that defines the macro. Following `#include` graphs exactly would |
| 92 | +lose these, and the kernel's sysfs and configfs attribute surfaces are built almost |
| 93 | +entirely this way. |
| 94 | +
|
| 95 | +## Loops |
| 96 | +
|
| 97 | +``` |
| 98 | +symbol drivers.constant_loop drivers/loops.cpp:12 |
| 99 | + props: loop_count=1, loop_depth=1, scaling_loop_depth=0, |
| 100 | + calls_in_loop=[drivers.step], calls_in_scaling_loop=[] |
| 101 | +``` |
| 102 | +
|
| 103 | +Same model as [Go](go.md#loops-for-n1-hunting) and [Ruby](ruby.md): a constant-bounded |
| 104 | +loop records the call but keeps the scaling set empty rather than absent. |
| 105 | +
|
| 106 | +## C++ specifics |
| 107 | +
|
| 108 | +Namespaces, templates and class methods are extracted, with header and source methods |
| 109 | +merged into one symbol as above. |
| 110 | +
|
| 111 | +> **A note on `override`.** The C++ corpus used to validate this extractor |
| 112 | +> (getdp, gmsh, the Linux kernel) is pre-C++11 and never uses the `override` |
| 113 | +> specifier, so the code path that consumes it has zero exercise on real code. |
| 114 | +> `virtual` is the specifier that is actually in play there. |
| 115 | +
|
| 116 | +## What is deliberately not extracted |
| 117 | +
|
| 118 | +- **Preprocessor evaluation.** `#if`/`#ifdef` branches are all parsed; enola does not pick |
| 119 | + a configuration, so facts from mutually exclusive branches can coexist. |
| 120 | +- **Template instantiation.** A template is one symbol, not one per instantiation. |
| 121 | +- **Virtual dispatch.** A call through a base-class pointer resolves to the declared |
| 122 | + method, not to every override. |
| 123 | +- **Linker-level symbol resolution.** Two static functions with the same name in different |
| 124 | + translation units are distinguished by module, not by object file. |
| 125 | +
|
| 126 | +--- |
| 127 | +
|
| 128 | +Measured on real C/C++ repositories — including the Linux kernel: [BENCHMARKS.md](../BENCHMARKS.md). |
0 commit comments