Skip to content
Open
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
37 changes: 10 additions & 27 deletions src/bundler/ParseTask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,35 +665,14 @@ pub mod parse_worker {
}

// ───────────────────────────────────────────────────────────────────────────
// getEmptyCSSAST / getEmptyAST
// getEmptyAST
// ───────────────────────────────────────────────────────────────────────────

// `transpiler: *mut Transpiler` stays raw. Callers
// (`get_ast`, `run_with_source_code`) may also hold a raw pointer to
// `(*transpiler).resolver`; materializing `&mut Transpiler` here would assert
// exclusive access to the whole struct and invalidate that sibling pointer.
// We only touch the disjoint `options.define` field.
fn get_empty_css_ast(
log: &mut Log,
transpiler: *mut Transpiler,
opts: ParserOptions<'static>,
bump: &'static Bump,
source: &'static Source<'static>,
) -> core::result::Result<JSAst<'static>, AnyError> {
let root = Expr::init(E::Object::default(), Loc { start: 0 });
// SAFETY: `transpiler` is a live worker-owned `*mut Transpiler`; `options`
// is disjoint from any other field the caller may hold a pointer to.
let define = unsafe { &mut (*transpiler).options.define };
let mut ast = JSAst::init(
js_parser::new_lazy_export_ast(bump, define, opts, log, root, source, b"")?
.ok_or(AnyError::ParserError)?,
);
ast.css = Some(crate::bundled_ast::CssAstRef::from_bump(
bump.alloc(bun_css::BundlerStyleSheet::empty()),
));
Ok(ast)
}

fn get_empty_ast<RootType: Default + bun_ast::expr::IntoExprData>(
log: &mut Log,
transpiler: *mut Transpiler,
Expand All @@ -702,7 +681,8 @@ pub mod parse_worker {
source: &'static Source<'static>,
) -> core::result::Result<JSAst<'static>, AnyError> {
let root = Expr::init(RootType::default(), Loc::EMPTY);
// SAFETY: see `get_empty_css_ast` — disjoint field of a live `*mut Transpiler`.
// SAFETY: `transpiler` is a live worker-owned `*mut Transpiler`; `options`
// is disjoint from any other field the caller may hold a pointer to.
let define = unsafe { &mut (*transpiler).options.define };
Ok(JSAst::init(
js_parser::new_lazy_export_ast(bump, define, opts, log, root, source, b"")?
Expand Down Expand Up @@ -1286,7 +1266,12 @@ pub mod parse_worker {
Loader::Css => {
// make css ast
let mut import_records = Vec::<ImportRecord>::default();
let source_code = &source.contents;
// A blank file is an empty stylesheet. U+000B is blank to the bundler, not to the CSS tokenizer.
let source_code: &[u8] = if strings::is_all_whitespace(&source.contents) {
b""
} else {
&source.contents
};
let mut temp_log = Log::init();
// `temp_log` is flushed into `log` on every exit path via linear
// control flow (scopeguard would alias `log`/`temp_log`).
Expand Down Expand Up @@ -2662,7 +2647,7 @@ pub mod parse_worker {
if let Some(message) = unsupported_directive {
log.add_range_error(Some(source), UseDirective::range(entry_contents), message);
Err(crate::Error::ParserError)
} else if !is_empty || loader.handles_empty_file() {
} else if !is_empty || loader.handles_empty_file() || loader.is_css() {
get_ast(
log,
transpiler,
Expand All @@ -2675,8 +2660,6 @@ pub mod parse_worker {
&mut unique_key_for_additional_file,
&task_ctx.linker.has_any_css_locals,
)
} else if loader.is_css() {
get_empty_css_ast(log, transpiler, opts, bump, source)
} else if module_type == options::ModuleType::Esm {
get_empty_ast::<E::Undefined>(log, transpiler, opts, bump, source)
} else {
Expand Down
3 changes: 2 additions & 1 deletion src/bundler/linker_context/findImportedFilesInCSSOrder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@ fn debug_css_order_impl(
order: &Vec<CssImportOrder>,
step: CssOrderDebugStep,
) {
use crate::bun_css::{ImportInfo, LocalsResultsMap, Printer, PrinterOptions};
use crate::bun_css::{ImportInfo, LocalsResultsMap, Printer, PrinterOptions, Targets};

let tag = step.tag_name();
debug!("CSS order {}:\n", tag);
Expand Down Expand Up @@ -1038,6 +1038,7 @@ fn debug_css_order_impl(
bun_alloc::ArenaVec::new_in(&arena),
&mut writer,
&PrinterOptions::default(),
&Targets::default(),
Some(ImportInfo {
import_records: &entry.condition_import_records,
ast_urls_for_css,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use core::sync::atomic::Ordering;
use bun_collections::VecExt;
use bun_threading::thread_pool as ThreadPoolLib;

use crate::bun_css::{BundlerStyleSheet, ImportInfo, LocalsResultsMap, PrinterOptions, Targets};
use crate::bun_css::{BundlerStyleSheet, ImportInfo, LocalsResultsMap, PrinterOptions};

use crate::chunk::{Content, CssImportOrderKind};
use crate::linker_context_mod::LinkerContext;
Expand Down Expand Up @@ -97,7 +97,6 @@ fn generate_compile_result_for_css_chunk_impl(
let printer_options = PrinterOptions {
// TODO: make this more configurable
minify: c.options.minify_whitespace,
targets: Targets::for_bundler_target(c.options.target),
..Default::default()
};
match css.to_css_with_writer(
Expand Down Expand Up @@ -130,7 +129,6 @@ fn generate_compile_result_for_css_chunk_impl(
}
CssImportOrderKind::SourceIndex(idx) => {
let printer_options = PrinterOptions {
targets: Targets::for_bundler_target(c.options.target),
// TODO: make this more configurable
minify: c.options.minify_whitespace
|| c.options.minify_syntax
Expand Down
87 changes: 44 additions & 43 deletions src/bundler/linker_context/prepareCssAstsForChunk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::bun_css::css_parser::{
BundlerCssRule, BundlerCssRuleList, BundlerLayerBlockRule, BundlerMediaRule,
BundlerSupportsRule, ImportRule, LayerName, LayerStatementRule, Location, SmallList,
};
use crate::bun_css::{BundlerStyleSheet, ImportConditions, ImportInfo, PrinterOptions, Targets};
use crate::bun_css::{BundlerStyleSheet, ImportConditions, ImportInfo, PrinterOptions};
use crate::bun_fs::Path;
use bun_ast::{ImportKind, ImportRecord, ImportRecordFlags, ImportRecordTag, Index as AstIndex};
use bun_ast::{Loc, Range};
Expand Down Expand Up @@ -94,6 +94,19 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: &
let Content::Css(css_chunk) = &mut chunk.content else {
unreachable!()
};
// The order is postorder, so the last source entry is the chunk's root. No root means no entries at all.
let Some(root) = css_chunk
.imports_in_chunk_in_order
.slice()
.iter()
.rev()
.find_map(|entry| match entry.kind {
CssImportOrderKind::SourceIndex(idx) => asts[idx.get() as usize].as_deref(),
_ => None,
})
else {
return;
};
let mut i: usize = css_chunk.imports_in_chunk_in_order.len() as usize;
while i != 0 {
i -= 1;
Expand Down Expand Up @@ -140,14 +153,7 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: &
} else {
BundlerCssRuleList::default()
};
let mut ast = BundlerStyleSheet {
rules,
sources: Default::default(),
source_map_urls: Default::default(),
license_comments: Default::default(),
composes: Default::default(),
..BundlerStyleSheet::empty()
};
let mut ast = BundlerStyleSheet::synthesized(rules, root);
Comment thread
robobun marked this conversation as resolved.
Resolved
wrap_rules_with_conditions(&mut ast, bump, &entry.conditions);
css_chunk.asts[i] = ast;
}
Expand Down Expand Up @@ -191,34 +197,33 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: &
// `entry.conditions[j]`, i.e. a double-free / UAF. Wrap in
// `ManuallyDrop` so the duplicate is abandoned instead; the rule
// slab itself is arena-owned so it is reclaimed on arena reset.
let ast_import = core::mem::ManuallyDrop::new(BundlerStyleSheet {
license_comments: Default::default(),
sources: Default::default(),
source_map_urls: Default::default(),
rules: {
let mut import_rule = ImportRule {
// SAFETY: `bun_css` types its strings `'static`. This
// stylesheet is printed and dropped with the chunk.
url: unsafe { bun_ptr::detach_lifetime(p.pretty) },
import_record_idx: entry.condition_import_records.len()
as u32,
loc: Location::dummy(),
..Default::default()
};
// SAFETY: shallow struct
// copy. The duplicate is never dropped (`ManuallyDrop`
// above), so the aliased heap stays singly-owned by
// `entry.conditions[j]`.
*import_rule.conditions_mut() =
unsafe { core::ptr::read(entry.conditions.at(j)) };
arena_rule_list_one(bump, BundlerCssRule::Import(import_rule))
},
composes: Default::default(),
..BundlerStyleSheet::empty()
});
let ast_import =
core::mem::ManuallyDrop::new(BundlerStyleSheet::synthesized(
{
let mut import_rule = ImportRule {
// SAFETY: `bun_css` types its strings `'static`. This
// stylesheet is printed and dropped with the chunk.
url: unsafe { bun_ptr::detach_lifetime(p.pretty) },
import_record_idx: entry.condition_import_records.len()
as u32,
loc: Location::dummy(),
..Default::default()
};
// SAFETY: shallow struct
// copy. The duplicate is never dropped (`ManuallyDrop`
// above), so the aliased heap stays singly-owned by
// `entry.conditions[j]`.
*import_rule.conditions_mut() =
unsafe { core::ptr::read(entry.conditions.at(j)) };
arena_rule_list_one(
bump,
BundlerCssRule::Import(import_rule),
)
},
root,
));

let printer_options = PrinterOptions {
targets: Targets::for_bundler_target(c.options.target),
// TODO: make this more configurable
minify: c.options.minify_whitespace
|| c.options.minify_syntax
Expand Down Expand Up @@ -295,8 +300,8 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: &
flags: ImportRecordFlags::default(),
});

css_chunk.asts[i] = BundlerStyleSheet {
rules: {
css_chunk.asts[i] = BundlerStyleSheet::synthesized(
{
let mut import_rule = ImportRule::from_url_and_import_record_idx(
// SAFETY: as for the `ImportRule` above.
unsafe { bun_ptr::detach_lifetime(p.pretty) },
Expand All @@ -312,12 +317,8 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: &
unsafe { core::ptr::read(actual_conditions) };
arena_rule_list_one(bump, BundlerCssRule::Import(import_rule))
},
sources: Default::default(),
source_map_urls: Default::default(),
license_comments: Default::default(),
composes: Default::default(),
..BundlerStyleSheet::empty()
};
root,
);
}
CssImportOrderKind::SourceIndex(source_index) => {
let source_index = *source_index;
Expand Down
7 changes: 5 additions & 2 deletions src/bundler/transpiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3115,7 +3115,11 @@ impl<'a> Transpiler<'a> {
return None;
}
};
if let Err(e) = sheet.minify(alloc, &bun_css::MinifyOptions::default(), &extra) {
let minify_options = bun_css::MinifyOptions {
targets: bun_css::Targets::for_bundler_target(self.options.target),
..Default::default()
};
if let Err(e) = sheet.minify(alloc, &minify_options, &extra) {
self.log_mut().add_error_fmt(
None,
bun_ast::Loc::EMPTY,
Expand All @@ -3127,7 +3131,6 @@ impl<'a> Transpiler<'a> {
let result = match sheet.to_css(
alloc,
&bun_css::PrinterOptions {
targets: bun_css::Targets::for_bundler_target(self.options.target),
minify: self.options.minify_whitespace,
..bun_css::PrinterOptions::default()
},
Expand Down
17 changes: 17 additions & 0 deletions src/css/css_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2373,6 +2373,8 @@ pub struct StyleSheet<AtRule> {
/// [`ParserOptions::flags`] of the parse.
pub flags: ParserFlags,
pub layer_names: Vec<LayerName>,
/// What [`StyleSheet::minify`] lowered this sheet for. [`StyleSheet::to_css_with_writer`] prints for the same targets.
pub(crate) targets: Targets,

/// Used when css modules is enabled. Maps `local name string` -> `Ref`.
pub local_scope: LocalScope,
Expand All @@ -2394,11 +2396,21 @@ impl<AtRule> StyleSheet<AtRule> {
css_modules: None,
flags: ParserFlags::default(),
layer_names: Vec::new(),
targets: Targets::default(),
local_scope: LocalScope::default(),
local_properties: LocalPropertyUsage::default(),
composes: ComposesMap::default(),
}
}

/// A sheet that the bundler builds, not parses. It prints for the targets `like` was minified for.
pub fn synthesized(rules: CssRuleList<AtRule>, like: &Self) -> Self {
Self {
rules,
targets: like.targets,
..Self::empty()
}
}
}

/// Positions are `i32`, and a column at the end of the input is `len + 1`.
Expand Down Expand Up @@ -2433,6 +2445,7 @@ mod stylesheet_impl {
where
AtRule: for<'b> generic::DeepClone<'b>,
{
self.targets = options.targets;
let ctx = PropertyHandlerContext::new(arena, &options.targets, &options.unused_symbols);
let mut handler = DeclarationHandler::new(arena);
let mut important_handler = DeclarationHandler::new(arena);
Expand Down Expand Up @@ -2518,6 +2531,7 @@ mod stylesheet_impl {
bun_alloc::ArenaVec::new_in(arena),
writer,
options,
&self.targets,
import_info,
local_names,
symbols,
Expand Down Expand Up @@ -2727,6 +2741,7 @@ mod stylesheet_impl {
css_modules: options.css_modules,
flags: options.flags,
layer_names,
targets: Targets::default(),
local_scope: parser_extra.local_scope,
local_properties,
composes,
Expand Down Expand Up @@ -2781,6 +2796,7 @@ mod stylesheet_impl {
&'a self,
arena: &'a Bump,
options: &PrinterOptions<'a>,
targets: &Targets,
import_info: Option<ImportInfo<'a>>,
) -> Result<ToCssResult, PrintErr> {
// #[cfg(feature = "sourcemap")]
Expand All @@ -2796,6 +2812,7 @@ mod stylesheet_impl {
bun_alloc::ArenaVec::new_in(arena),
&mut dest,
options,
targets,
import_info,
None,
&symbols,
Expand Down
9 changes: 2 additions & 7 deletions src/css/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ pub struct PrinterOptions<'a> {
pub minify: bool,
/// An optional project root path, used to generate relative paths for sources used in CSS module hashes.
pub project_root: Option<&'a [u8]>,
/// Targets to output the CSS for.
pub targets: Targets,
/// Whether to analyze dependencies (i.e. `@import` and `url()`).
/// If true, the dependencies are returned as part of the
/// [ToCssResult](super::stylesheet::ToCssResult).
Expand All @@ -44,10 +42,6 @@ impl<'a> PrinterOptions<'a> {
PrinterOptions {
minify,
project_root: None,
targets: Targets {
browsers: None,
..Targets::default()
},
analyze_dependencies: None,
pseudo_classes: None,
}
Expand Down Expand Up @@ -262,6 +256,7 @@ impl<'a> Printer<'a> {
scratchbuf: BumpVec<'a, u8>,
dest: &'a mut dyn Write,
options: &PrinterOptions<'a>,
targets: &Targets,
import_info: Option<ImportInfo<'a>>,
local_names: Option<&'a css::LocalsResultsMap>,
symbols: &'a SymbolMap,
Expand All @@ -270,7 +265,7 @@ impl<'a> Printer<'a> {
sources: None,
dest,
minify: options.minify,
targets: options.targets,
targets: *targets,
dependencies: if options.analyze_dependencies.is_some() {
Some(BumpVec::new_in(arena))
} else {
Expand Down
1 change: 1 addition & 0 deletions src/css_jsc/color_js.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ pub(crate) fn color<'a>(
bun_alloc::ArenaVec::<u8>::new_in(&arena),
&mut dest,
&css::PrinterOptions::default(),
&css::Targets::default(),
None,
None,
&symbols,
Expand Down
Loading
Loading