feat(parser)!: reduce MAX_LEN to 256 bytes below u32::MAX (#26352)

Technically a breaking change, but one that's extremely unlikely to affect any real-world applications.

On 64-bit systems, the maximum source text length was exactly `u32::MAX`.

Lower it by 256 bytes, for 2 reasons:

1. `oxc_lexer` will require source text to be followed by 64 bytes of padding. Reducing max source length ensures _padded_ length can always fit in a `u32`.
2. Counts of tokens, comments and errors are all bounded by source length plus a small constant. By reducing max source length, these can all be stored as `u32`s without any possibility of overflow.

`oxc_lexer` only needs 64 bytes of padding. Have gone with 256 to leave headroom in case later redesigns require more than 64. This avoids another breaking change later. We can always switch to 64 later on if we want, and this later relaxation would not be a breaking change.

All the changes to other crates in this PR are just updating comments which talk about max source length.
This commit is contained in:
overlookmotel committed 2026-09-05 15:33:52 +00:00
1 parent 766e12f420
commit 2c9a947363
8 files changed
+36 -23

No files matched your search

@@ -101,8 +101,8 @@ pub fn build_translations(source_text: &str, translations: &mut Vec<Translation>
// Record the index of the end of this Unicode character, because it's only offsets
// *after* this Unicode character that need to be shifted.
// Offsets are relative to the start of the untrimmed source, so `offset` is added
// back on. Addition cannot overflow because the untrimmed source text is max
// `u32::MAX` bytes long.
// back on. Addition cannot overflow because the untrimmed source text is less than
// `u32::MAX` bytes long (bounded by `MAX_LEN`, defined in `oxc_parser`).
let bytes_in_char =
difference_for_this_byte as usize + usize::from(byte >= 0xF0) + 1;
#[expect(clippy::cast_possible_truncation)]
+8 -6
View File
@@ -260,10 +260,11 @@ impl PrintStringState<'_> {
/// Calculate optimum quote character to use, when backtick (`) is an option.
fn calculate_quote_maybe_backtick(&self) -> Quote {
// Max string length is:
// * 64-bit platforms: `u32::MAX`.
// Strings are assumed to be no longer than `MAX_LEN` (defined in `oxc_parser`),
// which holds for all strings produced by the parser:
// * 64-bit platforms: `u32::MAX - 256`.
// * 32-bit platforms: `i32::MAX`.
// In either case, `isize` is sufficient to make overflow impossible.
// Each byte changes a cost by at most 1, so in either case `isize` cannot overflow.
let mut single_cost: isize = 0;
let mut double_cost: isize = 0;
let mut backtick_cost: isize = 0;
@@ -302,10 +303,11 @@ impl PrintStringState<'_> {
/// Calculate optimum quote character to use, when backtick (`) is not an option.
fn calculate_quote_no_backtick(&self) -> Quote {
// Max string length is:
// * 64-bit platforms: `u32::MAX`.
// Strings are assumed to be no longer than `MAX_LEN` (defined in `oxc_parser`),
// which holds for all strings produced by the parser:
// * 64-bit platforms: `u32::MAX - 256`.
// * 32-bit platforms: `i32::MAX`.
// In either case, `isize` is sufficient to make overflow impossible.
// Each byte changes a cost by at most 1, so in either case `isize` cannot overflow.
let mut single_cost: isize = 0;
for &b in self.bytes.clone() {
match b {
+2 -1
View File
@@ -12,7 +12,8 @@ use oxc_span::{GetSpan, Span};
/// This is the only hard prerequisite for any consumer:
/// - `u32` means byte offsets, not UTF-16 code units or `char` indices
/// - `u32` is the oxc-wide convention
/// - `oxc_span::Span` is `u32`-based, and `oxc_parser` rejects sources longer than `u32::MAX` bytes
/// - `oxc_span::Span` is `u32`-based, and `oxc_parser` rejects sources longer than `MAX_LEN` bytes
/// (which is less than `u32::MAX`)
/// - so casting a `usize` offset down to `u32` never truncates for parsed sources)
#[derive(Debug, Clone, Copy)]
pub struct SourceText<'a> {
@@ -272,10 +272,9 @@ impl<'a> UidGenerator<'a> {
} else {
// Identifier `_<base>4294967295` was already used.
// Can't increment `postfix` as it would wrap around, so increment `underscore_count` instead.
// It shouldn't be possible for `underscore_count` to be `u32::MAX` too, because that
// would require an identifier comprising `u32::MAX` x underscores in source text.
// That's theoretically possible, but source text is limited to `u32::MAX` bytes,
// so it'd be the entirety of the source text. Therefore `postfix` would be 1.
// `underscore_count` cannot be `u32::MAX` too, because that would require an identifier
// comprising `u32::MAX` underscores. Identifiers are assumed to be no longer than `MAX_LEN`
// (defined in `oxc_parser`), which is less than `u32::MAX`. So this addition cannot overflow.
uid_name.underscore_count += 1;
uid_name.postfix = 2;
}
+13 -2
View File
@@ -111,13 +111,24 @@ use crate::{
/// Maximum length of source which can be parsed (in bytes).
/// ~4 GiB on 64-bit systems, ~2 GiB on 32-bit systems.
//
// Length is constrained by 2 factors:
// 1. `Span`'s `start` and `end` are `u32`s, which limits length to `u32::MAX` bytes.
// 2. Rust's allocator APIs limit allocations to `isize::MAX`.
// https://doc.rust-lang.org/std/alloc/struct.Layout.html#method.from_size_align
// https://doc.rust-lang.org/std/alloc/struct.Layout.html#method.from_size_align
//
// On 64-bit systems, the limit is 256 bytes below `u32::MAX` rather than `u32::MAX` itself:
//
// 1. `oxc_lexer` will require source text to be followed by 64 bytes of padding.
// This means *padded* length can fit in a `u32`.
// 256 instead of 64 to leave headroom, in case the padding requirement expands in future.
// 2. Counts of tokens, comments and errors are all bounded by source length plus a small constant,
// so they can be stored as `u32`s without any possibility of overflow.
// 3. No real `Span` can have `start` or `end` of `u32::MAX`, so such a `Span` can be used as a sentinel.
// `oxc_transformer`'s styled-components plugin relies on this.
pub(crate) const MAX_LEN: usize = if size_of::<usize>() >= 8 {
// 64-bit systems
u32::MAX as usize
u32::MAX as usize - 256
} else {
// 32-bit or 16-bit systems
isize::MAX as usize
@@ -242,8 +242,9 @@ impl<'a> Keys<'a> {
#[cold]
#[inline(never)]
fn get_unique_slow(&mut self, ctx: &TraverseCtx<'a>) -> Str<'a> {
// Source text length is limited to `u32::MAX` so impossible to have more than `u32::MAX`
// private keys. So `u32` is sufficient here.
// Source text is limited to `MAX_LEN` (defined in `oxc_parser`), which is less than `u32::MAX`.
// So a class cannot have anywhere near `u32::MAX` private keys.
// So `u32` is sufficient here, and `i` cannot overflow.
let mut i = 2u32;
let mut buffer = ItoaBuffer::new();
let mut num_str;
@@ -874,8 +874,8 @@ fn is_valid_styled_component_source(source: &str) -> bool {
fn minify_template_literal<'a>(lit: &mut TemplateLiteral<'a>, ast: &AstBuilder<'a>) {
const NOT_IN_STRING: u8 = 0;
/// `Span` used as a sentinel indicating quasi should be removed.
/// Source text is limited to max `u32::MAX` bytes, so it's impossible for a `TemplateElement`
/// to have this span, because it's always followed by (at minimum) a '`'.
/// Source text is limited to `MAX_LEN` (defined in `oxc_parser`), which is less than `u32::MAX`,
/// so no real `Span` can have `start` or `end` of `u32::MAX`.
const REMOVE_SENTINEL: Span = Span::new(u32::MAX, u32::MAX);
debug_assert_eq!(lit.quasis.len(), lit.expressions.len() + 1);
+3 -4
View File
@@ -272,10 +272,9 @@ impl<'a> UidGenerator<'a> {
} else {
// Identifier `_<base>4294967295` was already used.
// Can't increment `postfix` as it would wrap around, so increment `underscore_count` instead.
// It shouldn't be possible for `underscore_count` to be `u32::MAX` too, because that
// would require an identifier comprising `u32::MAX` x underscores in source text.
// That's theoretically possible, but source text is limited to `u32::MAX` bytes,
// so it'd be the entirety of the source text. Therefore `postfix` would be 1.
// `underscore_count` cannot be `u32::MAX` too, because that would require an identifier
// comprising `u32::MAX` underscores. Identifiers are assumed to be no longer than `MAX_LEN`
// (defined in `oxc_parser`), which is less than `u32::MAX`. So this addition cannot overflow.
uid_name.underscore_count += 1;
uid_name.postfix = 2;
}