tree-sitter / tree-sitter/node-tree-sitter

Possible progress callback leak in `Parser.parse()`

Open
#287 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
876
Forks
172
PR merge metrics
No merged PRs in 30d

Description

Possible progress callback leak in Parser.parse()

I found a possible native and JavaScript reference leak when Parser.parse() is called with progressCallback.

File: src/parser.cc

Functions: CallbackProgress::Make, Parser::Parse

Relevant JavaScript API:

Parser.prototype.parse = function(input, oldTree, {
  bufferSize,
  includedRanges,
  progressCallback
} = {}) {
  const tree = parse.call(
    this,
    input,
    oldTree,
    bufferSize,
    includedRanges,
    progressCallback,
  );
  // ...
}

The native wrapper allocates a callback payload and stores a persistent function
reference:

class CallbackProgress final {
 public:
  static TSParseOptions Make(const Napi::Function &func) {
    TSParseOptions options;
    auto *callback = new CallbackProgress();
    callback->func = Napi::Persistent(func);
    options.payload = static_cast<void *>(callback);
    options.progress_callback = Cancel;
    return options;
  }

 private:
  Napi::FunctionReference func;
  // ...
};

Parser::Parse() passes the options to Tree-sitter:

if (info.Length() > 4 && info[4].IsFunction()) {
  TSParseOptions options = CallbackProgress::Make(info[4].As<Function>());
  tree = ts_parser_parse_with_options(parser_, old_tree, callback_input.Input(), options);
} else {
  tree = ts_parser_parse(parser_, old_tree, callback_input.Input());
}
return Tree::NewInstance(env, tree);

There is no matching delete or func.Reset() after
ts_parser_parse_with_options() returns. TSParseOptions is stack-local, and
Tree-sitter only receives the raw payload pointer for the parse operation.

For comparison, logger payload ownership is handled explicitly:

if (current_logger.payload != nullptr) {
  delete static_cast<Logger *>(current_logger.payload);
}

Suggested fix: keep the progress callback payload owned by Parser::Parse()
and release it after ts_parser_parse_with_options() returns, for example by
using a stack object or std::unique_ptr<CallbackProgress>.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/parser.cc, reading CallbackProgress::Make and Parser::Parse, then compare their ownership handling with the logger payload cleanup shown in the issue. Verify the progress callback payload and persistent function reference are released after ts_parser_parse_with_options() returns, without affecting parsing or callback behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, javascript
Domain
api, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.