Skip to content

UriIdentityService retains stale canonical URI cache after path casing capability changes #328677

Description

@zainnadeem786

Does this issue occur when all extensions are disabled?: Yes

  • VS Code Version: Current main branch, built from source (1.132.0)
  • OS Version: Windows 11 24H2 (Build 26200.x)

Summary

UriIdentityService does not invalidate cached canonical URIs when the path-casing capability of a filesystem provider changes at runtime.

The provider capability-change listener correctly recomputes the new ignorePathCasing value for the affected URI scheme. However, it compares the newly computed value to itself rather than comparing the previous cached value with the new value.

Because this condition is always true, the listener returns before removing stale entries from _canonicalUris.

As a result, direct URI comparisons can correctly reflect the updated case-sensitivity behavior while asCanonicalUri() continues returning canonical URI values that were cached under the previous casing semantics.

Affected Component

  • File: src/vs/platform/uriIdentity/common/uriIdentityService.ts
  • Component: UriIdentityService
  • Affected behavior: canonical URI cache invalidation after filesystem provider capability changes

Root Cause

The filesystem provider registration/capability-change listener contains the following condition:

if (newIgnorePathCasingValue === newIgnorePathCasingValue) {
	return;
}

This comparison is tautological and always evaluates to true.

The intended behavior appears to be comparing the previously cached path-casing value with the newly computed value:

if (oldIgnorePathCasingValue === newIgnorePathCasingValue) {
	return;
}

If the provider's path-casing behavior has not changed, returning early is appropriate. If the behavior has changed, canonical URI cache entries for the affected scheme should be removed.

Because the existing condition always returns, that invalidation step is never reached.

Steps to Reproduce

  1. Create or register a filesystem scheme whose paths are initially treated as case-insensitive.

  2. Canonicalize the following URIs:

const upper = URI.parse('foo://bar/BANG');
const lower = URI.parse('foo://bar/bang');
  1. Call asCanonicalUri() for both URIs while the scheme is case-insensitive.

The lowercase URI is canonicalized to the previously cached uppercase URI:

foo://bar/BANG
  1. Change the filesystem provider capability for the foo scheme to:
FileSystemProviderCapabilities.PathCaseSensitive
  1. Emit the corresponding filesystem provider capability-change event.

  2. Verify direct URI comparison:

service.extUri.isEqual(upper, lower)

This correctly changes from true to false, showing that the current path-casing capability is being used for direct comparisons.

  1. Canonicalize the lowercase URI again:
service.asCanonicalUri(lower)

Expected Behavior

After the filesystem provider changes from case-insensitive to case-sensitive:

  • foo://bar/BANG and foo://bar/bang should no longer compare as equal.
  • Cached canonical URI entries created under the previous case-insensitive rules should be invalidated.
  • asCanonicalUri(foo://bar/bang) should return:
foo://bar/bang

Actual Behavior

Direct URI comparison correctly reflects the new case-sensitive behavior:

service.extUri.isEqual(upper, lower) === false

However, asCanonicalUri(lower) continues returning the stale canonical URI cached under the previous case-insensitive rules:

foo://bar/BANG

This produces inconsistent behavior between direct URI identity comparison and canonical URI lookup.

Observed Regression Test Failure

A focused regression test reproduces the stale-cache behavior on the current implementation:

URI Identity
  asCanonicalUri clears cache when provider path casing changes:

AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

+ actual - expected

+ 'foo://bar/BANG'
- 'foo://bar/bang'

Proposed Fix

Compare the previous path-casing value with the newly computed value:

- if (newIgnorePathCasingValue === newIgnorePathCasingValue) {
+ if (oldIgnorePathCasingValue === newIgnorePathCasingValue) {
	return;
}

This allows the existing cache invalidation logic to run when the filesystem provider's path-casing capability actually changes.

Regression Test

I prepared a deterministic regression test in:

src/vs/platform/uriIdentity/test/common/uriIdentityService.test.ts

The test:

  • caches foo://bar/BANG while the foo scheme is case-insensitive;
  • confirms foo://bar/BANG and foo://bar/bang are initially treated as equal;
  • changes the provider to PathCaseSensitive;
  • emits the provider capability-change event;
  • verifies direct URI comparison becomes case-sensitive;
  • verifies asCanonicalUri(foo://bar/bang) no longer returns the stale uppercase URI.

The test fails before the fix and passes after the proposed one-line change.

Validation

The proposed fix has been validated with:

npm run compile

Result:

compile-src ... with 0 errors

Focused regression test:

.\scripts\test.bat --grep "asCanonicalUri clears cache when provider path casing changes"

Result:

1 passing
errorlevel: 0

URI Identity test suite:

.\scripts\test.bat --grep "URI Identity"

Result:

8 passing
errorlevel: 0

Whitespace validation:

git diff --check

Result:

exit code: 0

Impact

This is a functional cache invalidation bug.

When a filesystem provider changes its path-casing semantics after canonical URI entries have already been cached, asCanonicalUri() can return stale URI values based on the previous case-sensitivity rules.

This may result in inconsistent canonicalization, deduplication, or identity behavior for providers that support runtime changes to PathCaseSensitive.

I did not identify a demonstrated security impact or Workspace Trust boundary violation.

Additional Notes

The issue was identified during a focused review of URI identity and path-casing behavior.

I have prepared a minimal fix and a focused regression test and will shortly open a pull request referencing this issue.

Metadata

Metadata

Assignees

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