You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2026-10-23 21:28:56 +03:00
The block macros pair a BEGIN with the one END that matches it, but several closers are an alias for another: MEM_CONTEXT_PRIOR_END(), MEM_CONTEXT_OBJ_END(), and TEST_ERROR_MEM_CONTEXT_END() are each defined as the base MEM_CONTEXT_END(), and OBJ_NEW_END() as MEM_CONTEXT_NEW_END(). Closing with the wrong one expands identically, so the compiler has nothing to report and the mistake stays. Eight sites had drifted to the base closer when this was first run, fixed in 42699b1ff.
Checking it needs a lexer rather than a parser. The block macros are brace-unbalanced by design, so a C file in this project is not valid C as written and only balances once it has been preprocessed. A parser that reads the source as written puts everything in the block at the wrong depth and, being error tolerant, produces a tree that is wrong rather than failing, and a parser that reads preprocessed source never sees a macro at all since every one worth checking has been expanded by then. A lexer has neither problem: a macro is just an identifier and the rule reading the tokens supplies what it means.
Add the lexer as lex.py and the check as macro.py, where each macro that opens a block is declared with the one macro that may close it. That table cannot be derived from the names, since several openers share a closer and several closers are an alias, so a second check requires every macro defined with a name ending in _BEGIN or _END to be classified as opening a block or not. Without it a block macro added with a closer of its own is a false negative: neither name is known, both are skipped, and the pairing check passes with nothing to say. It found 14 macros that had been missed by hand, including the whole HRN_FORK_* family, which was 172 pairs going unchecked while the check still reported nothing.
Report what the linter finds without stopping the run, and fail on it once the tests have run. A linter reads the source as written, so a mistake shows up wherever the source stopped making sense to it rather than where it is: a misspelled MEM_CONTEXT_BEGIN is not a block macro at all, so the pairing check reports the next closer 14 lines later and blames a block that opened 33 lines before the typo. The compiler places the same mistake exactly, naming the line and the macro that was meant, so the run needs to reach it. The linter now returns what it found instead of raising, which also means one bad file no longer stops the rest of the repository from being scanned, and it names the file after the warnings for it since they carry only the line they are on.
The checks apply to hand-written C, which is what the StringId check already applies to, and add about half a second to a lint of the repository. They wait for the file to be ASCII, since what the lexer would report about a character it cannot read is what the ASCII check has already reported.
A sentinel variable in the macros themselves, as debug.h already declares for FUNCTION_TEST_BEGIN/END, would make closing with an alias a compile error and is the better fix where one is possible. It fits only the four cases where an alias exists, and leaves the classification check standing either way, so it is left as a possibility rather than done here.