Skip to content

Possible progress callback leak in Parser.parse() #287

Description

@OvOhao

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>.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions