Skip to content

API: closeProjects then openFiles crashes tsgo (nil ChangeIf in acquireConfigForFile) #65071

Open

Description

🔎 Search Terms

closeProjects, openFiles, acquireConfigForFile, configFileRegistryBuilder, SyncMapEntry ChangeIf, nil pointer dereference, typescript/sync, tombstone LoadOrStore

🕗 Version & Regression Information

⏯ Playground Link

N/A — typescript/sync API, not the language/checker.

💻 Code

// repro.mjs — run with: node repro.mjs  (typescript@next installed)
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { API } from "typescript/sync";

const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ts-close-openfiles-"));
const config = path.join(dir, "tsconfig.json");
const file = path.join(dir, "a.ts");
fs.writeFileSync(config, JSON.stringify({ compilerOptions: { strict: true }, files: ["a.ts"] }));
fs.writeFileSync(file, "export const a = 1;\n");

const api = new API();
const snap = api.createSnapshot({
  openProjects: [config],
  openFiles: [file],
});

// Any of these panics the tsgo child:
snap.update({ closeProjects: [config], openFiles: [file] });

// Also panics as separate updates:
// const mid = snap.update({ closeProjects: [config] });
// mid.update({ openFiles: [file] });
// mid.update({ openProjects: [config], openFiles: [file] });

🙁 Actual behavior

The API server panics with a nil dereference; the client then sees Unexpected EOF while reading from child process. Later requests on the same API fail with EPIPE.

panic: runtime error: invalid memory address or nil pointer dereference
[signal ...]

github.com/microsoft/TypeScript/tsc/internal/project/dirty.(*SyncMapEntry[...]).ChangeIf(...)
	tsc/internal/project/dirty/syncmap.go:128
github.com/microsoft/TypeScript/tsc/internal/project.(*configFileRegistryBuilder).acquireConfigForFile(...)
	tsc/internal/project/configfileregistrybuilder.go:322
github.com/microsoft/TypeScript/tsc/internal/project.(*configFileRegistryBuilder).findOrAcquireConfigForFile(...)
	...
github.com/microsoft/TypeScript/tsc/internal/project.(*ProjectCollectionBuilder).findOrCreateDefaultConfiguredProjectWorker.func2(...)
	tsc/internal/project/projectcollectionbuilder.go:1108

acquireConfigForFile does entry, _ := c.configs.LoadOrStore(...) and immediately calls entry.ChangeIf(...). When dirty.SyncMap.LoadOrStore returns (nil, false) for a tombstoned key (after closeProjects deleted the configured project / config retention), entry is nil and ChangeIf crashes.

🙂 Expected behavior

The update succeeds: the project is closed and the file is (re)opened, either under a recreated configured project or the inferred project — without killing the server.

For comparison, these paths do not panic on the same build:

  • closeProjects + openProjects in the same update (open cancels the pending close)
  • closeProjects, then a later update with only openProjects (no openFiles)

Additional information about the issue

Matrix on 7.1.0-dev.20261030.1 (Windows x64), starting from createSnapshot({ openProjects, openFiles }):

Update Result
{ closeProjects, openFiles } same request panic
{ closeProjects } then { openFiles } panic
{ closeProjects } then { openProjects, openFiles } panic
{ closeProjects, openProjects } same request OK
{ closeProjects, openProjects, openFiles } same request OK
{ closeProjects } then { openProjects } only OK

Same acquireConfigForFile / nil-ChangeIf frame as #65054, but the trigger here is closing a configured project that had open files and then opening files again. #64911 is the synthetic-program sibling of the same LoadOrStore-after-tombstone pattern.

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