Conversation
Automated security fix generated by OrbisAI Security
GrigoryEvko
added a commit
to GrigoryEvko/tree-sitter-cpp
that referenced
this pull request
Sep 17, 2026
A translation unit and a namespace body hold declarations and no statement ([basic.link] p1, [namespace.def] p1, GCC cp_parser_toplevel_declaration, Clang ParseExternalDeclaration), so `name(args);` there is no call. 00e3c7a reads such an item as a macro when the name has no lowercase letter, and the shape of the name is its evidence. `PyDoc_STRVAR(doc, "text");`, `TF_CALL_half(REGISTER);` and `DEFINE_int32(flag, 1, "help");` hold a lowercase letter, and that commit named them as the rows it does not reach. THIS COMMIT TAKES THE ROW OF THE SEED AS THE EVIDENCE IN PLACE OF THE SHAPE. A name with a function-macro row of the seed of its own project reads as a macro where an item starts. The row must be the function-like one, because an object-like macro takes no arguments ([cpp.replace] p10): `printf("good");` in 7 files of the Clang interpreter tests has an object row, and a statement at file scope is the reading of Clang itself there. The two lookups of 00e3c7a stay: a name that a class head of the file or a type row of the seed declares keeps its reading, and a group with the shape of a parenthesized declarator keeps its declaration. THE POPULATION AT 7c0d954. A census of the class walks the tree, as the census of the gate does: an expression_statement whose first named child is a call_expression, whose nearest ancestor that is no preprocessor node is the translation_unit or a declaration_list. It holds 13,895 clean sites with a callee that has a lowercase letter. 7,714 of them have a field_expression as the callee, the `BENCHMARK(BM_Run)->Arg(0)->Unit(...);` chains that 00e3c7a named, and 5,977 have a plain name. Of those 5,977 sites, in 764 files with 585 names, the seed of the project holds a function-macro row for 2,646. THE HEAD CENSUS HOLDS 0 OF THE 2,646, so each one is a macro invocation now. The treediff gives 2,660 regions of a new macro_invocation in 364 clean files: the 2,646 sites, 2 sites of hpx swapcontext32.ipp, which the census counts as an include fragment, and 12 sites in 5 files that the base read as a declaration with a parenthesized declarator, because the argument is a type keyword. Those 12 are `__usdt_def_signed(short);` and two more lines of hhvm usdt.h, `BOOST_dynamic_bitset_is_numeric(bool);` and two more lines of the mongo copy of boost dynamic_bitset.hpp, `REGISTER_MatMulNBits(float);` of onnxruntime matmul_nbits.cc, `TestInitializerDataFieldSpecialized(float);` and one more line of onnxruntime initializer_test.cc, and `REGISTER_GPU_int32(int32_t);` and one more line of tensorflow split_v_op.cc. A declaration of that text declares nothing, because it has no name. THE ROWS THIS COMMIT DOES NOT REACH, BY TASK. 1,699 sites hold a name that only another project of the corpus declares as a macro: `DEFINE_bool` 433, `DEFINE_int32` 378, `DECLARE_bool` 157 of gflags. A seed is a fact about one project, and a name of a dependency reaches a file through its include graph, so those sites wait for the per-file seed deltas of tree-sitter#372 and the dependency map of item 6 of tree-sitter#360. 1,623 sites hold a name with no row anywhere, `PyDoc_STRVAR` 1,129 of them, whose macro is in a Python header that the corpus does not hold, and the same include work reaches them. 38 sites of `common();` and 38 of `specific();` in the boost type-traits tests are calls in a function body that `TT_TEST_BEGIN(BOOST_TT_TRAIT_NAME)` opens in its expansion, where the tree of today is right, and no row declines them. THE BRANCH OF A CALL BEFORE A DIRECTIVE KEEPS ITS READ NOW. A first form of this rule repaired 1,652 of the 2,646 sites, and every site that it missed stood in a conditional group: `TF_CALL_half(DECLARE_GPU_SPECS);` in a `#if` group of tensorflow kept its call, and the same line outside the group became a macro invocation. `scan_macro_start` reads a call of a name with a lowercase letter before the directive that ends a branch of a structured group. That branch read the group, found a `;` and not a directive, and returned false, which gave up the whole scan before the item form ran. The branch now keeps its read of the group and lets the scan go on. MEASURED AGAINST 7c0d954. The rule reads a seed, so every plain step measures zero: 0 tree changes over the corpus and over the test files, and 0 changes of the treediff. The seeded steps change 364 clean files and 64 files with an error, of which one loses its error. No file gets a new error or more error bytes. The ties population keeps 3,110 rows and the dedupe population 2,818, with none risen and none fallen. The census of the clean files gives no detector that rose. The pins do not change, 624 corpus examples pass, and the fuzz gives no failure over 5,037 inputs. THE CO-VALIDITY TABLE LOSES ONE ROW WITH NO TOKEN CHANGE. The table holds one row for each stop site of the scanner, keyed by the function and the line, and a row whose condition names no token prints that fact. `scan_macro_start` had three such rows: the branch of a call before a directive, the read of the group, and `if (call)`. This commit makes the first two one `if` and one `else if`, so that function has two. The key of the comparison holds no line number, so the count falls from 13 to 12. No parse state and no token moves, and the co-validity of every other row is the row of the base.
GrigoryEvko
added a commit
to GrigoryEvko/tree-sitter-cpp
that referenced
this pull request
Sep 17, 2026
A MEMBER WITH NO TYPE AND A BODY, WHOSE NAME IS NOT THE NAME OF ITS CLASS, READ
AS A FUNCTION DEFINITION. C++ gives a member with no decl-specifier-seq three
readings, a constructor, a destructor and a conversion function ([class.mem],
GCC `cp_parser_member_declaration`, Clang `ParseCXXClassMemberDeclaration`), and
a constructor takes the name of its class. `TEST_METHOD(TestReflow) { ... }` in
`class TextBufferTests` of terminal, `SERIALIZE_METHODS(AddrInfo, obj) { ... }`
of bitcoin, `DENC(bufferlist::const_iterator& p) { ... }` of ceph and
`BOOST_CONCEPT_USAGE(WritableIterator) { ... }` of boost are macro invocations
with a body, and each read as a function definition with a declarator and no
type. C++ cannot produce that tree. The member form before a `;` reads such a
name as a macro since a62d1cb, and this commit gives the same reading to the
form with a body.
THE POPULATION. A census of the class bodies of the corpus at 7c0d954 counts
2,417 members with no type, a body and a name that the class does not carry, in
611 files and 556 distinct trees. 1,878 of them stand in a file with no error,
and 1,680 have a name with no lowercase letter.
THE REPAIR. The branch that reads `NAME(args) { ... }` where a macro call
statement can start now reads it where a member starts as well. The two
positions share the test of the brace, and the member adds the guard of the
name: `member_macro_name` holds where a member starts, the name has the shape of
a macro name with two characters or more, and no class head of the file recorded
the name. That is the guard of the member form before a `;`.
THE NAME IS THE EVIDENCE, BECAUSE THE ARGUMENTS AND THE BRACE PROVE NOTHING
HERE. A constructor definition takes the same arguments and the same brace, so
only the name of the class tells the two apart. The class record holds that
name. The brace test of the call form stays: a brace that holds a list of
expressions with a `;` after it, or with a comma in it, is a declaration and not
a body, so `COUNTER(int, x){0};` and `LIST(int, y) { 1, 2 };` in a class body
keep the reading of the base.
WHAT THE SHAPE OF THE NAME LEAVES OUT, MEASURED WITH A DRAFT THAT DROPS IT. A
draft that takes every member name that the class record does not hold changes
1,706 sites in 407 files with no error at 7c0d954, 158 more than this commit.
At fd2e2db it changes the same 407 files with no error and 73 files with an
error, and its NEW ERROR is 0 as well. Every one of
the 158 is a constructor that C++ accepts: 148 in a class whose head ends with a
macro, `class MatOp_Identity CV_FINAL : public MatOp {` of opencv, which is the
population of tree-sitter#280, 8 whose return type a macro call gives on the line before or
which stand in a branch for the documentation only, and 2 of `class alignas(void
*) Stmt {` in `Stmt.h` of clang, where 97 class heads stand in one file and the
record of 32 names gave up `Stmt` before line 1496. The shape of the name leaves
each of them with its constructor.
THE NUMBERS AT fd2e2db, measured with the command of each gate step. Changed
hash with no error in A: 278 corpus files and 0 compiler test files. With an
error in A: 35 files, each with one error before and one error after. NEW ERROR
0, FIXED 0, more error bytes 0. The regions read whole: 1,548 sites in the 278
clean files, each a `function_definition` that becomes a `macro_invocation`, and
no other root pair. The sites carry 32 names, and each name is a
function-like macro. 1,011 sites have the name in the seed of their own project.
534 sites of terminal use `TEST_METHOD`, `TEST_CLASS_SETUP`,
`TEST_CLASS_CLEANUP`, `TEST_METHOD_SETUP` and `TEST_METHOD_CLEANUP`, which the
TAEF header `WexTestClass.h` of the Windows test framework defines. The corpus
holds no copy of that header, so no seed of the corpus can hold those five
names, and the name shape is the only evidence there (tree-sitter#372). 3 sites of cgal use
`BOOST_CONCEPT_USAGE`, which boost defines and cgal only uses, so the name
stands in the seed of a different project of the corpus (tree-sitter#360, item 6). The 35 files with an error hold 94 more sites of the same shape, read
with both binaries: `BOOST_CATCH(...)`, `SFL_CATCH (...)`, `JSON_CATCH(...)` and
`STDEXEC_CATCH(...)` after a try macro, and the TAEF methods of terminal. Each
of them becomes a macro invocation with a body, and the one error of the file
stays.
THE SEEDED PASS AND THE BASELINES AT fd2e2db. Each side collects its own seeds,
1,239,343 names in 64 projects, and the two collections are byte-identical. The
seeded pass changes the same 278 clean files and the same 35 files with an
error, with the same 1,548 sites, NEW ERROR 0 and FIXED 0. Ties 3,110 and dedupe
2,818 did not rise and did not fall. The seed check agrees. The co-validity
inventory holds 12 rows on each side, added 0 and removed 0. The census of the
gate: 0 detectors rose, `inclass_notype` fell from 2,731 to 1,183 in the stable
population, which is the 1,548 sites, and `nested_fn` fell by 3, because the
body of a macro invocation is no longer the block of a nested function. The
differ: added 0, fell 0, agreement 99.110% on each side. Parse operations
0.99999 of the base, parse time 1.002.
THE BASE OF THESE NUMBERS. Each number above was measured at fd2e2db, and the
same command gave the same number at 7c0d954 before it, with four commits
between the two. The commit sits on 3a9ad80, whose own gate measured 0 changed
trees in the corpus, 0 in the compiler tests and 0 under the seeds, so its
changed-file list is empty and no file of it meets the 313 files of this
commit.
WHAT STAYS, AND WHY. Of the 1,878 population sites in a file with no error, 330
keep their tree.
291 are constructors in a class whose head ends with a macro, `class
MatOp_Identity CV_FINAL : public MatOp {` of opencv. The record takes the
last name of the head, so it holds `CV_FINAL` and not the name of the class.
That is the population of tree-sitter#280, and the decision on it belongs to the user.
148 of them stand at a member start, and the draft with no shape guard turns
each one into a macro. 143 stand behind a specifier, a macro or a template
head, where no token of this scan can start.
7 have an uppercase name and a word between the group and the brace:
`BOOST_CNV_STRING_ENABLE(...) const { ... }` of boost,
`NS_IMETHOD_(int32_t) ID() override { ... }` of firefox, and
`timeout ASIO_PREVENT_MACRO_SUBSTITUTION()` of asio. The scan reads the
brace after the group only.
32 have a mixed-case name: a return type that a macro call gives on the line
before, a constructor of the class that the other branch of a directive
names, and other misses of the record.
One pin is new, with fields: the TAEF methods of a test class, the constructor
of that class, a field, and `DENC(int x) { }` in `class DENC`, where the record
holds the name and the constructor keeps its reading. With the macros defined,
GCC 16.2 and Clang 22.1 accept the text. Two syntax snippets are new, with the
terminal, bitcoin and ceph shapes.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fix high severity security issue in
src/scanner.c.Vulnerability
V-001src/scanner.c:98Description: The ts_calloc return value is not checked for NULL before being used. If memory allocation fails under memory pressure, ts_calloc returns NULL, and the subsequent memset() call dereferences a NULL pointer, causing an immediate application crash.
Evidence
Exploitation scenario: An attacker induces memory pressure through resource exhaustion (e.g., triggering many parser instantiations).
Scanner confirmation: multi_agent_ai rule
V-001flagged this pattern.Production code: This file is in the production codebase, not test-only code.
Threat Model Context
This is a Node.js library - vulnerabilities affect downstream consumers who use this package.
Changes
src/scanner.cBehavior Preservation
The change is scoped to 1 file on the vulnerable path.
Automated security fix by OrbisAI Security