Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/codegen/generate-js2native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const rustIdentifierPaths: Record<string, string> = {
"Counters.rs": "jsc/Counters.rs",
"FrameworkRouter.rs": "runtime/bake/FrameworkRouter.rs",
"Listener.rs": "runtime/socket/Listener.rs",
"MarkdownObject.rs": "runtime/api/MarkdownObject.rs",
"SecureContext.rs": "runtime/api/bun/SecureContext.rs",
"Stat.rs": "runtime/node/Stat.rs",
"bindgen_test.rs": "jsc/bindgen_test.rs",
Expand Down
10 changes: 10 additions & 0 deletions src/js/internal-for-testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,16 @@ export const setSyntheticAllocationLimitForTesting: (limit: number) => number =
1,
);

// Shrink the markdown parser's block-metadata cap (in bytes) so its
// `TooManyBlocks` error is reachable without 4 GiB of input. The cap can only
// be lowered, never raised past the real limit. Returns the previous value so
// a test can restore it.
export const setMaxMarkdownBlockBytesForTesting: (limit: number) => number = $newRustFunction(
"MarkdownObject.rs",
"setMaxMarkdownBlockBytesForTesting",
1,
);

export const npm_manifest_test_helpers = $rust("npm.rs", "PackageManifest.bindings.generate") as {
/**
* Returns the parsed manifest file. Currently only returns an array of available versions.
Expand Down
80 changes: 34 additions & 46 deletions src/md/ansi_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use bun_core::output::ansi_b;
use bun_core::strings;

use crate::helpers;
use crate::output::{OutputBuffer, try_extend, try_push};
use crate::root;
use crate::types::{
self, Align, BlockType, JsResult, Renderer, RendererImpl, SpanDetail, SpanType, TextType,
Expand Down Expand Up @@ -259,28 +260,6 @@ impl InlineStyle {
}
}

pub struct OutputBuffer {
pub list: Vec<u8>,
pub oom: bool,
}

impl OutputBuffer {
fn write(&mut self, data: &[u8]) {
if self.oom {
return;
}
// Vec::extend aborts on OOM under the global mimalloc allocator.
self.list.extend_from_slice(data);
}

fn write_byte(&mut self, b: u8) {
if self.oom {
return;
}
self.list.push(b);
}
}

impl<'a> AnsiRenderer<'a> {
pub fn init(src_text: &'a [u8], theme: Theme<'a>) -> AnsiRenderer<'a> {
let mut r = AnsiRenderer {
Expand Down Expand Up @@ -316,7 +295,10 @@ impl<'a> AnsiRenderer<'a> {
last_was_newline: true,
blank_emitted: false,
};
r.out.list.reserve(src_text.len() + src_text.len() / 2);
// The output is usually ~1.5x the input; this is only a throughput
// hint, so on failure fall back to the incremental `try_reserve`s in
// `write` instead of aborting.
let _ = r.out.list.try_reserve(src_text.len() + src_text.len() / 2);
Comment thread
robobun marked this conversation as resolved.
r
}

Expand Down Expand Up @@ -547,19 +529,27 @@ impl<'a> AnsiRenderer<'a> {
// normalized once the table finishes.
let cells: Box<[TableCell]> =
core::mem::take(&mut self.table_cells).into_boxed_slice();
self.table_rows.push(TableRow {
cells,
is_header: self.in_thead,
});
try_push(
&mut self.out.oom,
&mut self.table_rows,
TableRow {
cells,
is_header: self.in_thead,
},
);
self.table_cells.clear();
}
BlockType::Th | BlockType::Td => {
self.in_cell = false;
let owned = Box::<[u8]>::from(self.table_cell_buf.as_slice());
self.table_cells.push(TableCell {
content: owned,
alignment: self.cell_align,
});
try_push(
&mut self.out.oom,
&mut self.table_cells,
TableCell {
content: owned,
alignment: self.cell_align,
},
);
}
}
}
Expand All @@ -583,10 +573,8 @@ impl<'a> AnsiRenderer<'a> {
SpanType::A => {
self.link_depth += 1;
if self.link_depth == 1 {
// Resolve final href (prefixes for autolinks). On OOM
// we leave link_href null so leaveSpan doesn't try to
// free a literal.
self.link_href = resolve_href(&detail).ok();
// Resolve final href (prefixes for autolinks).
self.link_href = Some(resolve_href(&detail));
if self.theme.colors && self.theme.hyperlinks {
if let Some(href) = &self.link_href {
// OSC 8 hyperlink start
Expand Down Expand Up @@ -748,19 +736,19 @@ impl<'a> AnsiRenderer<'a> {
/// heading buffer, table cell, image alt, or directly to output).
fn write_content(&mut self, data: &[u8]) {
if self.image_depth > 0 {
self.image_alt.extend_from_slice(data);
try_extend(&mut self.out.oom, &mut self.image_alt, data);
return;
}
if self.in_code_block {
self.code_buf.extend_from_slice(data);
try_extend(&mut self.out.oom, &mut self.code_buf, data);
return;
}
if self.heading_level > 0 {
self.heading_buf.extend_from_slice(data);
try_extend(&mut self.out.oom, &mut self.heading_buf, data);
return;
}
if self.in_cell {
self.table_cell_buf.extend_from_slice(data);
try_extend(&mut self.out.oom, &mut self.table_cell_buf, data);
return;
}
// Normal paragraph flow: respect wrapping + indent.
Expand Down Expand Up @@ -942,16 +930,16 @@ impl<'a> AnsiRenderer<'a> {
while i < bytes.len() && bytes[i] != 0x1b {
i += 1;
}
self.image_alt.extend_from_slice(&bytes[start..i]);
try_extend(&mut self.out.oom, &mut self.image_alt, &bytes[start..i]);
}
return;
}
if self.in_cell {
self.table_cell_buf.extend_from_slice(bytes);
try_extend(&mut self.out.oom, &mut self.table_cell_buf, bytes);
return;
}
if self.heading_level > 0 {
self.heading_buf.extend_from_slice(bytes);
try_extend(&mut self.out.oom, &mut self.heading_buf, bytes);
return;
}
self.out.write(bytes);
Expand Down Expand Up @@ -1116,11 +1104,11 @@ impl<'a> AnsiRenderer<'a> {
return;
}
if self.in_cell {
self.table_cell_buf.extend_from_slice(data);
try_extend(&mut self.out.oom, &mut self.table_cell_buf, data);
return;
}
if self.heading_level > 0 {
self.heading_buf.extend_from_slice(data);
try_extend(&mut self.out.oom, &mut self.heading_buf, data);
return;
}
self.out.write(data);
Expand Down Expand Up @@ -2438,7 +2426,7 @@ fn extract_language(src_text: &[u8], info_beg: u32) -> &[u8] {

/// Build the final href string with autolink prefixes (mailto:, http://).
/// Caller owns the returned memory.
fn resolve_href(detail: &SpanDetail) -> Result<Box<[u8]>, bun_alloc::AllocError> {
fn resolve_href(detail: &SpanDetail) -> Box<[u8]> {
let mut buf: Vec<u8> = Vec::new();
if detail.autolink_email {
buf.extend_from_slice(b"mailto:");
Expand All @@ -2448,7 +2436,7 @@ fn resolve_href(detail: &SpanDetail) -> Result<Box<[u8]>, bun_alloc::AllocError>
}
let mut scratch: Vec<u8> = Vec::new();
buf.extend_from_slice(sanitize_source_text(detail.href, &mut scratch));
Ok(buf.into_boxed_slice())
buf.into_boxed_slice()
}

// ========================================
Expand Down
27 changes: 9 additions & 18 deletions src/md/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use crate::helpers;
use crate::parser::{self, Parser};
use crate::types::{self, BlockType, Container, Line, OFF, VerbatimLine};

use bun_collections::VecExt as _;
use core::mem::{align_of, size_of};

type BlockHeader = parser::BlockHeader;
Expand Down Expand Up @@ -48,7 +47,7 @@ impl Parser<'_> {
p_end: &mut OFF,
pivot_line: &Line,
line: &mut Line,
) -> Result<(), bun_alloc::AllocError> {
) -> Result<(), parser::Error> {
let mut off = off_start;
let mut total_indent: u32 = 0;
let mut n_parents: u32 = 0;
Expand Down Expand Up @@ -692,7 +691,7 @@ impl Parser<'_> {
cur_line_idx: usize,
line_buf: &mut [Line; 2],
line_idx: &mut usize,
) -> Result<(), bun_alloc::AllocError> {
) -> Result<(), parser::Error> {
// Index into line_buf via cur_line_idx instead of taking a `&mut Line`
// parameter, which would alias line_buf.
let line = &mut line_buf[cur_line_idx];
Expand Down Expand Up @@ -832,7 +831,7 @@ impl Parser<'_> {
Ok(())
}

pub fn start_new_block(&mut self, line: &Line) -> Result<(), bun_alloc::AllocError> {
pub fn start_new_block(&mut self, line: &Line) -> Result<(), parser::Error> {
let block_type: BlockType = match line.r#type {
LineType::Hr => BlockType::Hr,
LineType::Atxheader => BlockType::H,
Expand All @@ -842,24 +841,13 @@ impl Parser<'_> {
_ => BlockType::P,
};

// Align block_bytes for Block alignment
let align_mask: usize = align_of::<BlockHeader>() - 1;
let cur_len = self.block_bytes.len();
let aligned = (cur_len + align_mask) & !align_mask;
let needed = aligned + size_of::<BlockHeader>();
self.block_bytes.ensure_total_capacity(needed);
// Zero-fill to `needed`; bytes in [aligned, needed) are immediately
// overwritten by the BlockHeader write below.
self.block_bytes.resize(needed, 0);

let hdr = self.get_block_header_at(aligned);
*hdr = BlockHeader {
let aligned = self.append_block_header(BlockHeader {
block_type,
_pad: [0; 3],
flags: 0,
data: line.data,
n_lines: 0,
};
})?;

self.current_block = Some(aligned);
self.current_block_lines.clear();
Expand All @@ -879,7 +867,7 @@ impl Parser<'_> {
Ok(())
}

pub fn end_current_block(&mut self) -> Result<(), bun_alloc::AllocError> {
pub fn end_current_block(&mut self) -> Result<(), parser::Error> {
if let Some(cb_off) = self.current_block {
// Capture the header fields, drop the &mut borrow, then access
// other &self fields.
Expand Down Expand Up @@ -926,6 +914,9 @@ impl Parser<'_> {
self.current_block_lines.len() * size_of::<VerbatimLine>(),
)
};
// The block's lines land in `block_bytes` too (12 bytes per
// line), not just its header, so this growth needs the same cap.
parser::check_block_bytes_len(self.block_bytes.len() + line_bytes.len())?;
self.block_bytes.extend_from_slice(line_bytes);
self.current_block = None;
}
Expand Down
28 changes: 11 additions & 17 deletions src/md/containers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ impl Parser<'_> {
self.containers[self.n_containers as usize] = *c;
}

// Record block_byte offset in the container
// Record block_byte offset in the container.
// In range: every `block_bytes` grower enforces `check_block_bytes_len`.
let block_off: u32 = u32::try_from(self.block_bytes.len()).expect("int cast");
self.containers[self.n_containers as usize].block_byte_off = block_off;

Expand All @@ -27,29 +28,18 @@ impl Parser<'_> {
block_type: BlockType,
data: u32,
flags: u32,
) -> Result<(), AllocError> {
let align_mask: usize = align_of::<BlockHeader>() - 1;
let cur_len = self.block_bytes.len();
let aligned = (cur_len + align_mask) & !align_mask;
let needed = aligned + size_of::<BlockHeader>();
self.block_bytes
.reserve(needed.saturating_sub(self.block_bytes.len()));
// Zero-fill to `needed`; bytes in [aligned, needed) are immediately
// overwritten by the BlockHeader assignment below.
self.block_bytes.resize(needed, 0);

let hdr = self.get_block_header_at(aligned);
*hdr = BlockHeader {
) -> Result<(), parser::Error> {
self.append_block_header(BlockHeader {
block_type,
_pad: [0; 3],
flags,
data,
n_lines: 0,
};
})?;
Ok(())
}

pub fn enter_child_containers(&mut self, count: u32) -> Result<(), AllocError> {
pub fn enter_child_containers(&mut self, count: u32) -> Result<(), parser::Error> {
let mut i: u32 = self.n_containers - count;
while i < self.n_containers {
// Capture the container fields before calling &mut self methods.
Expand All @@ -65,6 +55,8 @@ impl Parser<'_> {
} else if ch == b'-' || ch == b'+' || ch == b'*' {
// Save opener position for later loose-list patching
let align_mask_: usize = align_of::<BlockHeader>() - 1;
// In range: every `block_bytes` grower enforces
// `check_block_bytes_len`, which leaves alignment headroom.
self.containers[idx].block_byte_off =
u32::try_from((self.block_bytes.len() + align_mask_) & !align_mask_).unwrap();
// Unordered list + list item
Expand All @@ -81,6 +73,8 @@ impl Parser<'_> {
} else if ch == b'.' || ch == b')' {
// Save opener position for later loose-list patching
let align_mask_: usize = align_of::<BlockHeader>() - 1;
// In range: every `block_bytes` grower enforces
// `check_block_bytes_len`, which leaves alignment headroom.
self.containers[idx].block_byte_off =
u32::try_from((self.block_bytes.len() + align_mask_) & !align_mask_).unwrap();
// Ordered list + list item
Expand All @@ -100,7 +94,7 @@ impl Parser<'_> {
Ok(())
}

pub fn leave_child_containers(&mut self, keep: u32) -> Result<(), AllocError> {
pub fn leave_child_containers(&mut self, keep: u32) -> Result<(), parser::Error> {
while self.n_containers > keep {
self.n_containers -= 1;
// Capture the container fields before calling &mut self methods.
Expand Down
Loading
Loading