From 05933cd275c880a64e3ed18dac85cce6cf5db4f1 Mon Sep 17 00:00:00 2001 From: Rob Hogan Date: Wed, 16 Sep 2026 13:06:32 +0100 Subject: [PATCH] metro-file-map: Identify TreeFS directory nodes by shape, not realm Summary: `TreeFS` told directory nodes apart from file nodes with `node instanceof Map`, which holds only when the tree was built in the same realm as the code reading it. A tree restored through `TreeFS.fromDeserializedSnapshot` is used as-is, so a snapshot deserialized in another realm traverses wrongly: every directory below the root reads as a symlink and lookups through it fail with `Expected symlink target to be populated`, while `hierarchicalLookup` treats the root as a non-directory and returns null without probing it. Nothing in a Metro process crosses a realm, but Jest does: each test file runs in its own `vm` context while `node:v8` is a host module, so a file map cache read back within a test holds `Map`s from another realm. The `metro` tests that build two `DependencyGraph`s on one config have been traversing such a tree, and pass only because the walks they make happen to fail in the direction their expectations need. File nodes are metadata tuples, so `isDirectory` checks `!Array.isArray(node)` instead, which reads an internal slot and is realm-independent. The new test round-trips a snapshot through `v8.serialize`/`deserialize` and looks up a nested file in the result. Changelog: [Internal] --- packages/metro-file-map/src/lib/TreeFS.js | 2 +- .../src/lib/__tests__/TreeFS-test.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/metro-file-map/src/lib/TreeFS.js b/packages/metro-file-map/src/lib/TreeFS.js index 3dc71cb7ff..5f78836665 100644 --- a/packages/metro-file-map/src/lib/TreeFS.js +++ b/packages/metro-file-map/src/lib/TreeFS.js @@ -30,7 +30,7 @@ type FileNode = FileMetadata; type MixedNode = FileNode | DirectoryNode; function isDirectory(node: ?MixedNode): node is DirectoryNode { - return node instanceof Map; + return node != null && !Array.isArray(node); } function isRegularFile(node: FileNode): boolean { diff --git a/packages/metro-file-map/src/lib/__tests__/TreeFS-test.js b/packages/metro-file-map/src/lib/__tests__/TreeFS-test.js index a94efce125..152ca14933 100644 --- a/packages/metro-file-map/src/lib/__tests__/TreeFS-test.js +++ b/packages/metro-file-map/src/lib/__tests__/TreeFS-test.js @@ -90,6 +90,20 @@ describe.each([['win32'], ['posix']])('TreeFS on %s', platform => { expect(tfs.exists(p('/project/link-to-nowhere'))).toBe(false); }); + test('traverses a snapshot deserialized in another realm', () => { + // Under Jest, `node:v8` is a host module, so `deserialize` returns `Map`s + // whose prototype is not the sandbox's `Map.prototype`. + const {deserialize, serialize} = require('node:v8'); + const deserialized = TreeFS.fromDeserializedSnapshot({ + rootDir: p('/project'), + fileSystemData: deserialize(serialize(tfs.getSerializableSnapshot())), + processFile: () => { + throw new Error('Not implemented'); + }, + }); + expect(deserialized.exists(p('/project/foo/another.js'))).toBe(true); + }); + test('implements linkStats()', () => { expect(tfs.linkStats(p('/project/link-to-foo/another.js'))).toEqual({ fileType: 'f',