Skip to content
This repository was archived by the owner on Sep 1, 2026. It is now read-only.

Fix fatal panic on non-absolute path in VFS - #4248

Closed
Daniel Rosenwasser (DanielRosenwasser) with Copilot wants to merge 3 commits into
mainfrom
copilot/fix-fatal-error-at-rootlength
Closed

Fix fatal panic on non-absolute path in VFS#4248
Daniel Rosenwasser (DanielRosenwasser) with Copilot wants to merge 3 commits into
mainfrom
copilot/fix-fatal-error-at-rootlength

Conversation

Copilot AI commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

The VFS layer panics when a non-absolute path reaches RootLength() via the module resolution pipeline. This can occur when containingDirectory is empty, causing normalizePathForCJSResolution("", "./foo") to produce a relative path that propagates through FileExistsStatRootAndPathSplitPathRootLength.

TypeScript's sys.fileExists simply returns false for such paths. The Go port's defensive panic is too strict.

Changes

  • RootLength returns 0 instead of panicking for non-absolute paths
  • SplitPath returns ("", "") when root length is zero
  • RootAndPath returns nil fsys when rootName is empty, causing downstream FileExists/DirectoryExists to return false and ReadFile to return ("", false)
  • Updated test that asserted panic behavior to assert graceful false/empty return
  • Added a focused module resolver regression test for resolving a relative specifier with an empty containing file

Instead of panicking when a non-absolute path reaches VFS operations,
RootLength now returns 0, SplitPath returns empty strings, and
RootAndPath returns nil fsys. This causes FileExists/DirectoryExists
to return false and ReadFile to return ("", false) for non-absolute paths,
matching TypeScript's behavior where sys.fileExists simply returns false.

Fixes #1291

Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix fatal error in vfs path handling Fix fatal panic on non-absolute path in VFS Jun 8, 2026
@DanielRosenwasser

Copy link
Copy Markdown
Member

Copilot you did not add a test

Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com>

Copilot AI commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Copilot you did not add a test

Added a focused regression test in internal/module/resolver_test.go covering ResolveModuleName("./foo", "", ...), which previously reached the non-absolute VFS lookup path and panicked. Addressed in f7f88aa.

@DanielRosenwasser

Copy link
Copy Markdown
Member

Copilot can you investigate adding a real world example as a compiler or fourslash test that fails without any fix?

Copilot AI commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

Copilot can you investigate adding a real world example as a compiler or fourslash test that fails without any fix?

I investigated compiler and fourslash coverage paths. Standard compiler tests normalize @filename units against an absolute current directory, and fourslash normalizes fixture files under / or ^/ for dynamic URIs, so the usual real-world import/untitled-file cases do not reproduce the pre-fix empty-containing-file panic. I also tried a relative CLI root-file repro; that path is normalized before module resolution as well. Because the failing condition is specifically a resolver/VFS call after file context has already become empty/relative, I did not add a compiler/fourslash test that would give false confidence; the focused resolver regression remains the only test I found that fails without the fix.

p = tspath.NormalizePath(p)
l := RootLength(p)
if l == 0 {
return "", ""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually want to return "" for the root/rest in these cases?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems weird to do that, but I think the callers check for empty rootName anyway?

@jakebailey Jake Bailey (jakebailey) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It pains me to remove this assert, because it has caught bugs, but, I don't feel strongly

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Prevents VFS panics when module resolution supplies relative paths.

Changes:

  • Makes VFS path splitting tolerate unrooted paths.
  • Updates unrooted read behavior tests.
  • Adds an empty-containing-file resolver regression test.
Show a summary per file
File Description
internal/vfs/internal/internal.go Adds graceful handling for unrooted paths.
internal/vfs/iovfs/iofs_test.go Expects failed reads instead of panics.
internal/module/resolver_test.go Tests resolution with an empty containing file.

Review details

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

  • Files reviewed: 3/3 changed files
  • Comments generated: 2
  • Review effort level: Balanced

l := tspath.GetEncodedRootLength(p)
if l == 0 {
panic(fmt.Sprintf("vfs: path %q is not absolute", p))
return 0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeahhhhhhhhh

Comment on lines +32 to +34
if l == 0 {
return "", ""
}
@DanielRosenwasser

Copy link
Copy Markdown
Member

Can't fix this in time for the repo move.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fatal error during resolution through RootLength

4 participants