Skip to content

Commit

Permalink
return 0 on calc evaluating to nan in alpha component
Browse files Browse the repository at this point in the history
  • Loading branch information
zackradisic committed Feb 10, 2025
1 parent 5415c51 commit adabe37
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 16 deletions.
11 changes: 11 additions & 0 deletions src/css/error.zig
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,11 @@ pub const ParserError = union(enum) {
invalid_page_selector,
/// An invalid value was encountered.
invalid_value,
/// Invalid NaN - tried to parse a calc() which evaluates to NaN
/// e.g. `calc(0 / 0)`
/// In some places we need to handle this, e.g.:
/// `rgba(0, 0, 0, calc(0 / 0))` -> `#0000`
invalid_calc_nan,
/// Invalid qualified rule.
qualified_rule_invalid,
/// A selector was invalid.
Expand All @@ -271,6 +276,12 @@ pub const ParserError = union(enum) {
.deprecated_nest_rule => writer.writeAll("The @nest rule is deprecated, use standard CSS nesting instead"),
.invalid_page_selector => writer.writeAll("Invalid @page selector"),
.invalid_value => writer.writeAll("Invalid value"),
.invalid_calc_nan => {
if (comptime bun.Environment.isDebug) {
@panic("We probably should be handling this error rather than bubbling it up to the user.");
}
return writer.writeAll("calc() value evaluates to NaN");
},
.qualified_rule_invalid => writer.writeAll("Invalid qualified rule"),
.selector_error => |err| writer.print("Invalid selector. {s}", .{err}),
.unexpected_import_rule => writer.writeAll("@import rules must come before any other rules except @charset and @layer"),
Expand Down
2 changes: 2 additions & 0 deletions src/css/values/calc.zig
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,8 @@ pub fn Calc(comptime V: type) type {
if (val != 0.0) {
node = node.mulF32(input.allocator(), 1.0 / val);
continue;
} else {
return .{ .err = input.newCustomError(css.ParserError{ .invalid_calc_nan = {} }) };
}
}
return .{ .err = input.newCustomError(css.ParserError{ .invalid_value = {} }) };
Expand Down
48 changes: 36 additions & 12 deletions src/css/values/color.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1287,13 +1287,21 @@ fn parseRgb(input: *css.Parser, parser: *ComponentParser) Result(CssColor) {
// return .{ .result = .{ r, g, b, is_legacy_syntax } };
// }

fn parseLegacyAlpha(input: *css.Parser, parser: *const ComponentParser) Result(f32) {
fn parseLegacyAlpha(input: *css.Parser, parser: *ComponentParser) Result(f32) {
if (!input.isExhausted()) {
if (input.expectComma().asErr()) |e| return .{ .err = e };
const old_parsing_alpha = parser.parsing_alpha;
parser.parsing_alpha = true;
defer parser.parsing_alpha = old_parsing_alpha;
return .{ .result = bun.clamp(
switch (parseNumberOrPercentage(input, parser)) {
.result => |vv| vv,
.err => |e| return .{ .err = e },
.err => |e| {
if (e.kind == .custom and e.kind.custom == .invalid_calc_nan) {
return .{ .result = 0.0 };
}
return .{ .err = e };
},
},
0.0,
1.0,
Expand All @@ -1302,14 +1310,21 @@ fn parseLegacyAlpha(input: *css.Parser, parser: *const ComponentParser) Result(f
return .{ .result = 1.0 };
}

fn parseAlpha(input: *css.Parser, parser: *const ComponentParser) Result(f32) {
const res = if (input.tryParse(css.Parser.expectDelim, .{'/'}).isOk())
bun.clamp(switch (parseNumberOrPercentage(input, parser)) {
fn parseAlpha(input: *css.Parser, parser: *ComponentParser) Result(f32) {
const res = if (input.tryParse(css.Parser.expectDelim, .{'/'}).isOk()) brk: {
const old_parsing_alpha = parser.parsing_alpha;
parser.parsing_alpha = true;
defer parser.parsing_alpha = old_parsing_alpha;
break :brk bun.clamp(switch (parseNumberOrPercentage(input, parser)) {
.result => |v| v,
.err => |e| return .{ .err = e },
}, 0.0, 1.0)
else
1.0;
.err => |e| {
if (e.kind == .custom and e.kind.custom == .invalid_calc_nan) {
return .{ .result = 0.0 };
}
return .{ .err = e };
},
}, 0.0, 1.0);
} else 1.0;

return .{ .result = res };
}
Expand Down Expand Up @@ -2033,6 +2048,9 @@ pub const OKLCH = struct {

pub const ComponentParser = struct {
allow_none: bool,
/// When parsing an alpha component, if it is calc() function which
/// evaluates to NaN, we should return 0.0 instead of NaN.
parsing_alpha: bool = false,
from: ?RelativeComponentParser,

pub fn new(allow_none: bool) ComponentParser {
Expand Down Expand Up @@ -2102,9 +2120,15 @@ pub const ComponentParser = struct {
}
}

if (input.tryParse(CSSNumberFns.parse, .{}).asValue()) |value| {
return .{ .result = NumberOrPercentage{ .number = .{ .value = value } } };
} else if (input.tryParse(Percentage.parse, .{}).asValue()) |value| {
switch (input.tryParse(CSSNumberFns.parseImpl, .{this.parsing_alpha})) {
.result => |value| return .{ .result = NumberOrPercentage{ .number = .{ .value = value } } },
.err => |e| {
if (this.parsing_alpha and e.kind == .custom and e.kind.custom == .invalid_calc_nan) {
return .{ .err = e };
}
},
}
if (input.tryParse(Percentage.parse, .{}).asValue()) |value| {
return .{
.result = NumberOrPercentage{
.percentage = .{ .unit_value = value.v },
Expand Down
16 changes: 12 additions & 4 deletions src/css/values/number.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@ const Calc = css.css_values.calc.Calc;
pub const CSSNumber = f32;
pub const CSSNumberFns = struct {
pub fn parse(input: *css.Parser) Result(CSSNumber) {
if (input.tryParse(Calc(f32).parse, .{}).asValue()) |calc_value| {
switch (calc_value) {
return parseImpl(input, false);
}

pub fn parseImpl(input: *css.Parser, return_on_nan: bool) Result(CSSNumber) {
switch (input.tryParse(Calc(f32).parse, .{})) {
.result => |calc_value| switch (calc_value) {
.value => |v| return .{ .result = v.* },
.number => |n| return .{ .result = n },
// Numbers are always compatible, so they will always compute to a value.
else => return .{ .err = input.newCustomError(css.ParserError.invalid_value) },
}
},
.err => |e| {
if (return_on_nan and e.kind == .custom and e.kind.custom == .invalid_calc_nan) {
return .{ .err = e };
}
},
}

return input.expectNumber();
Expand Down

0 comments on commit adabe37

Please sign in to comment.