Fix crash due to invalid characters in candidate (#19829)
This PR fixes an issue where the compiler can crash if it encounters an invalid codepoint. When we extract potential candidates from files, it could be that we encounter values that look like a class or a CSS variable, if it turns out that it's an invalid CSS variable we can ignore it. The problem is that sometimes there are escaped values in there that result in invalid code points crashing the compiler. This PR fixes that by gracefully handling that and making sure that invalid code points are replaced by `\uFFFD` as per the spec. The bug report (https://github.com/tailwindlabs/tailwindcss/issues/19786) has a clean example where a piece of text looks like a CSS variable, but contains invalid code points. ``` --Coding-Projects-CharacterMapper-Master-Workspace\d8819554-4725-4235-9d22-2d0ed572e924 ``` Luckily we can fix this today by ignoring the file paths that contain these strings using `@source not "…";`, but the better way is to actually fix this. To solve this, instead of blindly passing numbers to `String.fromCodePoint`, we will first validate whether it's a valid codepoint: 1. `0x0000` — `0x10FFFF` (inclusive) is the range of valid code points. See: https://infra.spec.whatwg.org/#code-point 2. `0xD800` — `0xDBFF` (inclusive) are leading surrogates. See: https://infra.spec.whatwg.org/#leading-surrogate 3. `0xDC00` — `0xDFFF` (inclusive) are trailing surrogates. See: https://infra.spec.whatwg.org/#trailing-surrogate In the code we use the `0xD800` — `0xDFFF` range because the ranges overlap. There are various references in the spec to replace surrogates (and invalid codepoints) with `\uFFFD`. Here is one of them: https://drafts.csswg.org/css-syntax-3/#consume-escaped-code-point Fixes: https://github.com/tailwindlabs/tailwindcss/issues/19786 Fixes: #19801 (this issue talks about a similar invalid code point issue) ## Test plan 1. Added a regression test where the above string was used as a CSS variable 2. Added a regression test for the unescape functionality to make sure that invalid code points and surrogates are replaced by the `\uFFFD` replacement character. [ci-all] Just to verify on Windows as well
This commit is contained in:
parent
7482d47a54
commit
bd30a716e6
4 changed files with 38 additions and 3 deletions
|
|
@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
|
||||
- Improve canonicalizations for `tracking-*` utilities ([#19827](https://github.com/tailwindlabs/tailwindcss/pull/19827))
|
||||
- Fix crash due to invalid characters in candidate ([#19829](https://github.com/tailwindlabs/tailwindcss/pull/19829))
|
||||
|
||||
## [4.2.2] - 2026-03-18
|
||||
|
||||
|
|
|
|||
|
|
@ -1501,6 +1501,14 @@ describe('Parsing theme values from CSS', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
// https://github.com/tailwindlabs/tailwindcss/issues/19786
|
||||
test('out-of-range escaped CSS variable candidates do not crash the build', async () => {
|
||||
// Shouldn't crash
|
||||
await run([
|
||||
String.raw`--Coding-Projects-CharacterMapper-Master-Workspace\d8819554-4725-4235-9d22-2d0ed572e924`,
|
||||
])
|
||||
})
|
||||
|
||||
test('`@keyframes` in `@theme` are hoisted', async () => {
|
||||
expect(
|
||||
await compileCss(
|
||||
|
|
|
|||
|
|
@ -11,4 +11,14 @@ describe('unescape', () => {
|
|||
test('removes backslashes', () => {
|
||||
expect(unescape(String.raw`red-1\/2`)).toMatchInlineSnapshot(`"red-1/2"`)
|
||||
})
|
||||
|
||||
test('replaces out-of-range escaped code points', () => {
|
||||
expect(
|
||||
unescape(
|
||||
String.raw`--Coding-Projects-CharacterMapper-Master-Workspace\d8819554-4725-4235-9d22-2d0ed572e924`,
|
||||
),
|
||||
).toMatchInlineSnapshot(
|
||||
`"--Coding-Projects-CharacterMapper-Master-Workspace<63>54-4725-4235-9d22-2d0ed572e924"`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -74,8 +74,24 @@ export function escape(value: string) {
|
|||
|
||||
export function unescape(escaped: string) {
|
||||
return escaped.replace(/\\([\dA-Fa-f]{1,6}[\t\n\f\r ]?|[\S\s])/g, (match) => {
|
||||
return match.length > 2
|
||||
? String.fromCodePoint(Number.parseInt(match.slice(1).trim(), 16))
|
||||
: match[1]
|
||||
if (match.length <= 2) {
|
||||
return match[1]
|
||||
}
|
||||
|
||||
let codePoint = Number.parseInt(match.slice(1).trim(), 16)
|
||||
|
||||
if (
|
||||
// Invalid codepoint: https://infra.spec.whatwg.org/#code-point
|
||||
codePoint === 0x0000 ||
|
||||
codePoint > 0x10ffff ||
|
||||
// Is surrogate: https://infra.spec.whatwg.org/#leading-surrogate
|
||||
// - A leading surrogate is a code point that is in the range U+D800 to U+DBFF, inclusive.
|
||||
// - A trailing surrogate is a code point that is in the range U+DC00 to U+DFFF, inclusive.
|
||||
(codePoint >= 0xd800 && codePoint <= 0xdfff)
|
||||
) {
|
||||
return '\uFFFD'
|
||||
}
|
||||
|
||||
return String.fromCodePoint(codePoint)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue