Skip to content

Probe the net9 Uri unescape bug rather than infer it from the target framework - #596

Merged
SimonCropp merged 1 commit into
mainfrom
uri-unescape-test-probe
Sep 10, 2026
Merged

Probe the net9 Uri unescape bug rather than infer it from the target framework#596
SimonCropp merged 1 commit into
mainfrom
uri-unescape-test-probe

Conversation

@SimonCropp

Copy link
Copy Markdown
Owner

Follow-up to #595. Test-only; no change to shipped source, so api_list and the API count are untouched.

The problem

#595 pinned the known BCL bug with a compile-time guard:

#if NET9_0_OR_GREATER && !NET11_0_OR_GREATER
    await Assert.That(() => TryUnescape("abc%20def", 2)).Throws<ArgumentOutOfRangeException>();
#else
    ...assert it returns false
#endif

That asserts a property of the runtime, using the target framework as the proxy. It holds today, but it is a bet that neither net9.0 nor net10.0 is ever serviced with the fix — and dotnet/runtime#128610 is a fully written backport to release/10.0 that was deferred, not rejected:

I looked through the usages of this new API on GitHub … and none would be impacted by this particular bug. We'll wait with servicing previous releases until we see more real world impact.

net10.0 is LTS and in support until 2028-11-14, so that PR could be revived at any point. If it were, a patched net10.0 runtime would turn this repo's tests red for a reason that has nothing to do with Polyfill.

The change

The behaviour is probed once, and both the sizing test and the dedicated test key off the result:

static bool throwsOnShortDestination = ProbeThrowsOnShortDestination();

static bool ProbeThrowsOnShortDestination()
{
    try
    {
        Uri.TryUnescapeDataString("abc%20def".AsSpan(), new char[2], out _);
        return false;
    }
    catch (ArgumentOutOfRangeException)
    {
        return true;
    }
}

Relaxing this must not quietly relax it for the polyfill too, which is the thing these tests actually exist to check. So the strict assertion is kept everywhere it can be:

#if !NET9_0_OR_GREATER || NET11_0_OR_GREATER
    // only net9.0 and net10.0 are allowed to throw here
    await Assert.That(throwsOnShortDestination).IsFalse();
#endif

net9.0 and net10.0 are the only frameworks permitted either outcome, and each is still asserted — a throw is asserted as a throw, and a non-throw is asserted to be false with zero written. Any third behaviour, including a different exception type, still fails. On a serviced net10.0 the sizing test would additionally start covering the destination sizes it currently skips, rather than staying silent.

Verification

Solution clean in Release, tests green on net11.0 (1697), net10.0 (1697), net9.0 (1697), net8.0 (1694), net462 (1659), plus PublicTests, EmbeddedTests, UnsafeTests and NoRefsTests.

…framework

The tests added in #595 asserted that Uri.TryUnescapeDataString throws on net9.0 and
net10.0, which bets that neither is ever serviced. dotnet/runtime#128610 is a written
backport of the fix to release/10.0 that was deferred rather than rejected, so that bet
could be lost, and net10.0 is in support until 2028.

The behaviour is now probed once instead. net9.0 and net10.0 accept either outcome, and
the assertion that it must not throw is kept for net11.0 and for every target where the
polyfill is active.
@SimonCropp
SimonCropp merged commit c979463 into main Sep 10, 2026
3 of 5 checks passed
@SimonCropp
SimonCropp deleted the uri-unescape-test-probe branch September 10, 2026 05:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant