Skip to content

Commit 884f02c

Browse files
authored
Fix "Cannot read properties of undefined" crash on malformed arbitrary value (#18133)
This PR fixes a crash when an arbitrary value was malformed and crashed the build. If you have a utility like `[--btn-border:var(--color-maroon)/90)]` which is malformed, it will crash the build. It might not be easy to spot but the easy is the additional `)` after the `90`. The reason this crashes is because we parse the value `var(--color-maroon)/90)` and when we see `)` we assume it's the end of a "function" which also assumes it was preceded by a `(`. This is not the case and we crash. This PR fixes that by not assuming the parsed object is available and uses `?` to be safe and only access `nodes` if it's available. I'm actually not 100% sure what the best solution is in this scenario because these candidates could (and will) be returned from Oxide so even if we throw a more descriptive error, it will still crash the build and you might not even have control over the candidate. This candidate will now eventually generate the following CSS: ```css .\[--btn-border\:var\(--color-maroon\)\/90\)\] { --btn-border: var(--color-maroon) / ; } ``` Which still looks odd, but even Lightning CSS doesn't throw an error in this case (because it's a CSS variable definition), so I think it's the best we can do. If you open your devtools you will see the weird values, so it's still debug-able. <img width="359" alt="image" src="https://github.com/user-attachments/assets/2eb48662-64de-4417-a2da-1577bf9075b5" /> Fixes: #17064 ## Test plan Manually tested the candidate that crashed it, and after the change generated the above CSS. Then used it in JSFiddle to proof it's fixed now. https://jsfiddle.net/z850ykew/ Couldn't use Tailwind Play because the candidate will cause a crash there as well 😅
1 parent 9cb3899 commit 884f02c

File tree

4 files changed

+35
-17
lines changed

4 files changed

+35
-17
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Upgrade: Improve `pnpm` workspaces support ([#18065](https://github.com/tailwindlabs/tailwindcss/pull/18065))
1414
- Upgrade: Migrate deprecated `order-none` to `order-0` ([#18126](https://github.com/tailwindlabs/tailwindcss/pull/18126))
1515
- Support Leptos `class:` attributes when extracting classes ([#18093](https://github.com/tailwindlabs/tailwindcss/pull/18093))
16+
- Fix "Cannot read properties of undefined" crash on malformed arbitrary value ([#18133](https://github.com/tailwindlabs/tailwindcss/pull/18133))
1617

1718
## [4.1.7] - 2025-05-15
1819

packages/tailwindcss/src/index.test.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4305,11 +4305,11 @@ it("should error when `layer(…)` is used, but it's not the first param", async
43054305

43064306
describe('`@reference "…" imports`', () => {
43074307
test('recursively removes styles', async () => {
4308-
let loadStylesheet = async (id: string, base: string) => {
4308+
let loadStylesheet = async (id: string, base = '/root/foo') => {
43094309
if (id === './foo/baz.css') {
43104310
return {
4311+
base,
43114312
path: '',
4312-
base: '/root/foo',
43134313
content: css`
43144314
.foo {
43154315
color: red;
@@ -4355,11 +4355,11 @@ describe('`@reference "…" imports`', () => {
43554355
})
43564356

43574357
test('does not generate utilities', async () => {
4358-
let loadStylesheet = async (id: string, base: string) => {
4358+
let loadStylesheet = async (id: string, base = '/root/foo') => {
43594359
if (id === './foo/baz.css') {
43604360
return {
4361+
base,
43614362
path: '',
4362-
base: '/root/foo',
43634363
content: css`
43644364
@layer utilities {
43654365
@tailwind utilities;
@@ -4442,15 +4442,15 @@ describe('`@reference "…" imports`', () => {
44424442
})
44434443
matchUtilities(
44444444
{
4445-
'match-utility': (value) => ({
4445+
'match-utility': (_value) => ({
44464446
'@keyframes match-utilities': { '100%': { opacity: '0' } },
44474447
}),
44484448
},
44494449
{ values: { initial: 'initial' } },
44504450
)
44514451
matchComponents(
44524452
{
4453-
'match-components': (value) => ({
4453+
'match-components': (_value) => ({
44544454
'@keyframes match-components': { '100%': { opacity: '0' } },
44554455
}),
44564456
},
@@ -4474,30 +4474,30 @@ describe('`@reference "…" imports`', () => {
44744474
})
44754475

44764476
test('emits CSS variable fallback and keyframes defined inside @reference-ed files', async () => {
4477-
let loadStylesheet = async (id: string, base: string) => {
4477+
let loadStylesheet = async (id: string, base = '/root') => {
44784478
switch (id) {
44794479
case './one.css': {
44804480
return {
4481+
base,
44814482
path: '',
4482-
base: '/root',
44834483
content: css`
44844484
@import './two.css' layer(two);
44854485
`,
44864486
}
44874487
}
44884488
case './two.css': {
44894489
return {
4490+
base,
44904491
path: '',
4491-
base: '/root',
44924492
content: css`
44934493
@import './three.css' layer(three);
44944494
`,
44954495
}
44964496
}
44974497
case './three.css': {
44984498
return {
4499+
base,
44994500
path: '',
4500-
base: '/root',
45014501
content: css`
45024502
.foo {
45034503
color: red;
@@ -5519,7 +5519,7 @@ describe('feature detection', () => {
55195519
css`
55205520
@import 'tailwindcss/preflight';
55215521
`,
5522-
{ loadStylesheet: async (_, base) => ({ base, content: '' }) },
5522+
{ loadStylesheet: async (_, base) => ({ base, path: '', content: '' }) },
55235523
)
55245524

55255525
expect(compiler.features & Features.AtImport).toBeTruthy()
@@ -5530,7 +5530,7 @@ describe('feature detection', () => {
55305530
css`
55315531
@import 'tailwindcss/preflight';
55325532
`,
5533-
{ loadStylesheet: async (_, base) => ({ base, content: '' }) },
5533+
{ loadStylesheet: async (_, base) => ({ base, path: '', content: '' }) },
55345534
)
55355535

55365536
// There's little difference between `@reference` and `@import` on a feature
@@ -5551,7 +5551,7 @@ describe('feature detection', () => {
55515551
color: theme(--color-red);
55525552
}
55535553
`,
5554-
{ loadStylesheet: async (_, base) => ({ base, content: '' }) },
5554+
{ loadStylesheet: async (_, base) => ({ base, path: '', content: '' }) },
55555555
)
55565556

55575557
expect(compiler.features & Features.ThemeFunction).toBeTruthy()
@@ -5562,7 +5562,7 @@ describe('feature detection', () => {
55625562
css`
55635563
@plugin "./some-plugin.js";
55645564
`,
5565-
{ loadModule: async (_, base) => ({ base, module: () => {} }) },
5565+
{ loadModule: async (_, base) => ({ base, path: '', module: () => {} }) },
55665566
)
55675567

55685568
expect(compiler.features & Features.JsPluginCompat).toBeTruthy()
@@ -5573,7 +5573,7 @@ describe('feature detection', () => {
55735573
css`
55745574
@config "./some-config.js";
55755575
`,
5576-
{ loadModule: async (_, base) => ({ base, module: {} }) },
5576+
{ loadModule: async (_, base) => ({ base, path: '', module: {} }) },
55775577
)
55785578

55795579
expect(compiler.features & Features.JsPluginCompat).toBeTruthy()
@@ -5605,9 +5605,10 @@ describe('feature detection', () => {
56055605
@reference "tailwindcss/utilities";
56065606
`,
56075607
{
5608-
async loadStylesheet(id, base) {
5608+
async loadStylesheet(_id, base) {
56095609
return {
56105610
base,
5611+
path: '',
56115612
content: css`
56125613
@tailwind utilities;
56135614
`,

packages/tailwindcss/src/value-parser.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,22 @@ describe('parse', () => {
165165
},
166166
])
167167
})
168+
169+
it('should not error when extra `)` was passed', () => {
170+
expect(parse('calc(1 + 2))')).toEqual([
171+
{
172+
kind: 'function',
173+
value: 'calc',
174+
nodes: [
175+
{ kind: 'word', value: '1' },
176+
{ kind: 'separator', value: ' ' },
177+
{ kind: 'word', value: '+' },
178+
{ kind: 'separator', value: ' ' },
179+
{ kind: 'word', value: '2' },
180+
],
181+
},
182+
])
183+
})
168184
})
169185

170186
describe('toCss', () => {

packages/tailwindcss/src/value-parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ export function parse(input: string) {
302302
// Handle everything before the closing paren a word
303303
if (buffer.length > 0) {
304304
let node = word(buffer)
305-
tail!.nodes.push(node)
305+
tail?.nodes.push(node)
306306
buffer = ''
307307
}
308308

0 commit comments

Comments
 (0)