Contents
C and C++
What gitta reads in C and C++ code, how it finds headers, and how it treats the preprocessor.
What gitta follows
gitta reads .c and .h files as C, and .cc, .cpp, .cxx, .c++, .hpp, .hh, .hxx, .h++, .ipp, .inl and .tpp files as C++. It keeps track of these names:
- Functions, including
staticandinlineones, and their prototypes in headers. - Global variables, structs, unions and enums with their fields and constants, and typedefs.
- Macros, both plain values and macros with parameters.
- C++ namespaces (nested, inline and anonymous), classes, member functions with constructors, destructors and operators, data members, templates and their specialisations, and
usingaliases.
Names are written as in C++ code: leveldb::DB::Put, fmt::vformat. A C struct is struct client, and its field is client::querybuf. Inline namespaces are left out, because callers leave them out too.
Overloaded functions are separate items, keyed by their parameter types. A call is matched by name, so a change to one overload reaches every call of that name.
A static function or variable, a macro defined in a source file and anything in an anonymous namespace belong to that one file. A function with the same name in another file is a different function. Static inline functions and macros in headers belong to every file that includes them.
Uses
gitta counts calls, type names, macros (also inside other macros), template arguments, and fields read with . or ->. For a field, it uses the declared type of the variable in front of it: a parameter, a local variable, a global, this, a data member of the class, or a chain of members such as data->set.timeout. A global declared in a header, such as extern struct redisServer server;, is followed too. C++ names are looked up in the enclosing namespaces and class, in using namespace, in using a::b and through namespace aliases.
Headers
A header usually has many users. When one agent changes a declaration, a macro or a struct field in a header, every agent whose new code uses it is warned, in any file. The agent that changed the header does not have to know where it is included.
gitta looks for #include "x.h" next to the including file first. Then it looks in the include directories, then in include/ and src/ of the repository. If none has the file, it uses a file anywhere in the repository whose path ends in x.h. It looks for #include <x.h> only in the include directories and include/. A header that is not in the repository is a system or library header, and gitta leaves it alone.
Include directories
List your include directories in a committed .gitta-include file, one per line, the way you give them to the compiler with -I:
include
deps/hiredis
# .h files under src/ are C++
.h c++ src/
# a macro that opens a namespace
namespace PROTOBUF_NAMESPACE_OPEN googleYou can also add directories for one checkout with the include list in .gitta/config.json.
Is a .h file C or C++?
gitta decides in this order:
- A
.h c++or.h cline in.gitta-include, for all headers or for one directory. The most specific line wins. - A header that a C++ source file of the repository includes is C++.
- A header whose text uses C++, such as classes, namespaces or templates, is C++.
- Any other header is C.
When the chosen language reads a header badly, gitta tries the other one and keeps the better result.
The preprocessor
gitta does not run the preprocessor. It reads the code of every branch of #if, #ifdef, #elif and #else, so a function that exists only on one platform is still followed. When two branches write different headers for the same function, gitta reads the first branch. Code under #if 0 is skipped.
Line continuations, X-macros, export macros such as class LEVELDB_EXPORT DB and macros that open a namespace, such as FMT_BEGIN_NAMESPACE, are read. A macro that gitta does not know, for example one used as a statement without a semicolon, leaves a small region that does not parse. The rest of the file is read as usual, and the uses inside that region still count. gitta reports a file as not parsed only when more than half of it does not parse.
z/OS XL C/C++
With gitta Enterprise, gitta reads the parts of the z/OS XL C/C++ dialect that change what a name means. It keeps every byte offset, so line numbers stay right.
- Trigraphs and digraphs.
??(??)??<??>??=??/??'??!??-and<::><%%>%:%:%:are read as the characters they stand for. Trigraphs are read everywhere, as the compiler does. Digraphs are read outside strings and comments. _Packed. A_Packedstruct or union is read like any other. Adding or removing_Packedis a change to its layout.#pragma map(name, "EXTNAME"). The external name is the same symbol as the C name. A change to the function reaches code that calls either name, also COBOL that calls'EXTNAME'. A change to the external name is a change to the function’s interface.#pragma linkage(name, OS). The linkage is part of the function’s interface, so a change to it is high severity.- Other pragmas, such as
options,runopts,packandinline, are skipped. They never stop a file from being read. - Embedded SQL and CICS.
EXEC SQL ... ;andEXEC CICS ... ;do not stop a file from being read. Host variables such as:hv_idand the arguments of CICS options count as uses.EXEC CICS LINK,XCTL,LOADandRELEASE PROGRAM(...)call that program, as they do in COBOL. A program name in a variable is found through the literal the file gives it, such aschar pgm[9] = "ACCTINQ";orstrcpy(pgm, "ACCTINQ"). - Fixed-length records. Sequence numbers in columns 73 to 80 and blanks that pad lines to column 72 or 80 are not part of any name or text. Renumbering a file changes nothing.
For code pages of C sources on z/OS, see Set the code page for each folder.
What counts as a change of meaning
These changes are high severity. Code that uses the changed name must be checked:
- The parameters of a function: their types, their number or their order. A C++ parameter added at the end with a default value is not, because every call stays the same.
- The return type, or what a function returns.
const,volatile,&and&&on a member function, andvirtual,override,final,noexcept,= 0and= delete.- The layout of a struct or class: the type, order or bit-field width of a data member, a member added or removed, a base class, or a virtual function. Code that works with the struct depends on its size.
- The value of an enum constant. A constant added in the middle changes the values after it. A constant added at the end does not.
- The value or the parameters of a macro.
- The type or initial value of a global or a constant, and the target of a typedef or
usingalias.
gitta also follows a change through helper functions. A function that returns what a changed helper returns has changed meaning too.
Merging
When two agents edit the same file, Git merges it line by line first. When the lines collide, gitta merges functions, classes and namespaces as units, so two agents who change different functions next to each other both keep their work. This structural merge needs a file that parses cleanly. Other files merge line by line, and a collision there is a conflict.
Security checks
gitta checks the lines that an agent adds to C and C++ files. It reports these risks:
| Risk | Severity |
|---|---|
A copy into a buffer with no size limit: strcpy, strcat, sprintf, gets, scanf with %s, or memcpy and memmove with a length nothing bounds. A length is bounded when it is a sizeof, a constant or the smaller of two values, or when the few lines before compare it, set it from a sizeof or allocate the buffer with it. | Medium |
A variable used as the format of printf, fprintf, sprintf, snprintf, syslog and similar calls | Medium |
A command that is not a literal given to system, popen or an exec call | High |
Certificate checks turned off: SSL_VERIFY_NONE in OpenSSL or wolfSSL, MBEDTLS_SSL_VERIFY_NONE, curl’s CURLOPT_SSL_VERIFYPEER or CURLOPT_SSL_VERIFYHOST set to 0 | High |
| MD5 or SHA-1 on a password | Medium |
| Secrets in the code, or written to a log or the console | Medium |
Between agents, a function named like a check, for example verify_peer, ossl_verify_peer or check_permission, counts as a security control. When one agent weakens it, every agent whose new code calls it is warned, and the merge reports it as a weakened check. See Warnings.
Tests
gitta recognises C and C++ test files by name, such as db_test.cc, cache_unittest.cpp and test_sds.c, and by content: GoogleTest, Catch2, doctest, Boost.Test, CppUnit, Unity and CMocka test cases. A merge reports a weakened check when an agent
- removes assertions such as
ASSERT_,EXPECT_,REQUIREorCHECK, - adds
DISABLED_to a test name,GTEST_SKIP,SKIPor a hidden Catch2 tag, - puts tests under
#if 0, or - removes
add_testorgtest_discover_testslines from CMake files.
Measured on real code
We read every C and C++ file of these projects with the release build in October 2026. When part of a file does not parse, gitta reads the rest of the file as usual. The table shows how much was left unread.
| Project | Files | Files with an unread part | Bytes left unread | Files not parsed | Read speed |
|---|---|---|---|---|---|
| SQLite 3.50.4 amalgamation | 4 | 2 | 0.01% | 0 | 6.8 MB/s |
| curl | 1071 | 15 (1%) | 0.02% | 0 | 5.5 MB/s |
| Redis, with its dependencies | 854 | 39 (5%) | 0.14% | 3 (0.35%) | 5.4 MB/s |
| fmt | 74 | 19 (26%) | 0.42% | 0 | 2.2 MB/s |
| LevelDB | 133 | 0 | 0 | 0 | 3.0 MB/s |
| Linux kernel/, mm/ and include/linux/ | 3718 | 111 (3%) | 0.08% | 0 | 5.2 MB/s |
Read speed is on one core of an Apple M1 Pro. A hook scans only its own agent's worktree and reuses the last scan of the others.