Skip to content

Commit 5a4380e

Browse files
authored
Merge branch 'main' into fix/function-no-return-undefined-4485
2 parents 0e6358e + 6e76eae commit 5a4380e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+3280
-758
lines changed

.github/workflows/rust.yml

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ jobs:
6969
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_RUSTFLAGS: ${{ (github.ref == 'refs/heads/main' || github.base_ref == 'main') && '-D warnings' || '' }}
7070
CARGO_TARGET_AARCH64_APPLE_DARWIN_RUSTFLAGS: ${{ (github.ref == 'refs/heads/main' || github.base_ref == 'main') && '-D warnings' || '' }}
7171
CARGO_TARGET_X86_64_APPLE_DARWIN_RUSTFLAGS: ${{ (github.ref == 'refs/heads/main' || github.base_ref == 'main') && '-D warnings' || '' }}
72-
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU: ${{ (github.ref == 'refs/heads/main' || github.base_ref == 'main') && '-D warnings' || '' }}
72+
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS: ${{ (github.ref == 'refs/heads/main' || github.base_ref == 'main') && '-D warnings' || '' }}
7373
strategy:
7474
matrix:
7575
include:
@@ -107,6 +107,41 @@ jobs:
107107
- name: Test docs
108108
run: cargo test --doc --profile ci --features annex-b,intl_bundled,experimental
109109

110+
miri:
111+
name: Miri
112+
runs-on: ubuntu-latest
113+
timeout-minutes: 120
114+
steps:
115+
- name: Set environment
116+
# Setting `RUSTFLAGS` overrides any flags set on .cargo/config.toml, so we need to
117+
# set the target flags instead which are cumulative.
118+
# Track https://github.com/rust-lang/cargo/issues/5376
119+
run: |
120+
target=$(rustc -vV | awk '/^host/ { print $2 }' | tr [:lower:] [:upper:] | tr '-' '_')
121+
echo "CARGO_TARGET_${target}_RUSTFLAGS=$W_FLAGS" >> $GITHUB_ENV
122+
- name: Checkout repository
123+
uses: actions/checkout@v6
124+
125+
- name: Install Rust nightly with miri
126+
uses: dtolnay/rust-toolchain@nightly
127+
with:
128+
components: miri
129+
130+
- name: Cache cargo
131+
uses: actions/cache@v5
132+
with:
133+
path: |
134+
target
135+
~/.cargo/git
136+
~/.cargo/registry
137+
key: ${{ runner.os }}-${{ runner.arch }}-cargo-miri-${{ hashFiles('**/Cargo.lock') }}
138+
139+
- name: Setup miri
140+
run: cargo miri setup
141+
142+
- name: Run miri tests
143+
run: cargo miri test --workspace --exclude boa_cli --exclude boa_examples miri
144+
110145
msrv:
111146
name: MSRV
112147
runs-on: ubuntu-latest
@@ -332,4 +367,3 @@ jobs:
332367

333368
- name: Check Semver
334369
uses: obi1kenobi/cargo-semver-checks-action@v2
335-

.github/workflows/test262.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ jobs:
6565
comment="$(./target/release/boa_tester compare ../data/test262/refs/heads/main/latest.json ../results/test262/refs/heads/main/latest.json -m)"
6666
echo "comment<<EOF" >> $GITHUB_OUTPUT
6767
echo "$comment" >> $GITHUB_OUTPUT
68+
echo "" >> $GITHUB_OUTPUT
69+
echo "Tested PR commit: [\`${{ github.event.pull_request.head.sha }}\`](${{ github.event.pull_request.head.repo.html_url }}/commit/${{ github.event.pull_request.head.sha }})" >> $GITHUB_OUTPUT
6870
echo "EOF" >> $GITHUB_OUTPUT
6971
7072
- name: Get the PR number

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ Or if you'd like to use Boa's REPL, simply type:
3232
boa
3333
```
3434

35+
You can also pipe JavaScript into Boa:
36+
37+
```shell
38+
echo 'console.log(1 + 2)' | boa
39+
cat script.js | boa
40+
boa < script.js
41+
```
42+
3543
## CLI Options
3644

3745
```txt

cli/src/main.rs

Lines changed: 151 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod logger;
1313

1414
use crate::logger::SharedExternalPrinterLogger;
1515
use boa_engine::context::time::JsInstant;
16+
use boa_engine::error::JsErasedError;
1617
use boa_engine::job::{GenericJob, TimeoutJob};
1718
use boa_engine::{
1819
Context, JsError, JsResult, Source,
@@ -38,15 +39,13 @@ use rustyline::{EditMode, Editor, config::Config, error::ReadlineError};
3839
use std::collections::BTreeMap;
3940
use std::mem;
4041
use std::sync::mpsc::{Sender, TryRecvError};
41-
use std::time::Duration;
42+
use std::time::{Duration, Instant};
4243
use std::{
4344
cell::RefCell,
4445
collections::VecDeque,
45-
eprintln,
4646
fs::OpenOptions,
47-
io,
47+
io::{self, IsTerminal, Read},
4848
path::{Path, PathBuf},
49-
println,
5049
rc::Rc,
5150
thread,
5251
};
@@ -124,6 +123,10 @@ struct Opt {
124123
#[arg(long = "vi")]
125124
vi_mode: bool,
126125

126+
/// Report parsing and execution timings.
127+
#[arg(long)]
128+
time: bool,
129+
127130
#[arg(long, short = 'O', group = "optimizer")]
128131
optimize: bool,
129132

@@ -214,21 +217,90 @@ enum FlowgraphDirection {
214217
RightToLeft,
215218
}
216219

220+
struct Timer<'a> {
221+
name: &'static str,
222+
start: Instant,
223+
counters: &'a mut Vec<(&'static str, Duration)>,
224+
}
225+
226+
impl Drop for Timer<'_> {
227+
fn drop(&mut self) {
228+
self.counters.push((self.name, self.start.elapsed()));
229+
}
230+
}
231+
232+
struct Counters {
233+
counters: Option<Vec<(&'static str, Duration)>>,
234+
}
235+
236+
impl Counters {
237+
fn new(enabled: bool) -> Self {
238+
Self {
239+
counters: enabled.then_some(Vec::new()),
240+
}
241+
}
242+
243+
fn new_timer(&mut self, name: &'static str) -> Option<Timer<'_>> {
244+
self.counters.as_mut().map(|counters| Timer {
245+
name,
246+
start: Instant::now(),
247+
counters,
248+
})
249+
}
250+
}
251+
252+
impl Drop for Counters {
253+
fn drop(&mut self) {
254+
let Some(counters) = self.counters.take() else {
255+
return;
256+
};
257+
if counters.is_empty() {
258+
return;
259+
}
260+
261+
let max_width = counters
262+
.iter()
263+
.map(|(name, _)| name.len())
264+
.max()
265+
.unwrap_or(0)
266+
.max("Total".len())
267+
+ 1; // +1 for the colon
268+
269+
let mut total = Duration::ZERO;
270+
eprintln!();
271+
for (name, elapsed) in &counters {
272+
eprintln!(
273+
"{:<width$} {elapsed:.2?}",
274+
format!("{name}:"),
275+
width = max_width
276+
);
277+
total += *elapsed;
278+
}
279+
if counters.len() > 1 {
280+
eprintln!("{:<width$} {total:.2?}", "Total:", width = max_width);
281+
}
282+
}
283+
}
284+
217285
/// Dumps the AST to stdout with format controlled by the given arguments.
218286
///
219287
/// Returns a error of type String with a error message,
220288
/// if the source has a syntax or parsing error.
221289
fn dump<R: ReadChar>(src: Source<'_, R>, args: &Opt, context: &mut Context) -> Result<()> {
222290
if let Some(arg) = args.dump_ast {
291+
let mut counters = Counters::new(args.time);
223292
let arg = arg.unwrap_or_default();
224293
let mut parser = boa_parser::Parser::new(src);
225294
let dump =
226295
if args.module {
227296
let scope = context.realm().scope().clone();
228-
let module = parser
229-
.parse_module(&scope, context.interner_mut())
230-
.map_err(|e| eyre!("Uncaught SyntaxError: {e}"))?;
231-
297+
let module = {
298+
let _timer = counters.new_timer("Parsing");
299+
parser
300+
.parse_module(&scope, context.interner_mut())
301+
.map_err(|e| eyre!("Uncaught SyntaxError: {e}"))?
302+
};
303+
let _timer = counters.new_timer("AST generation");
232304
match arg {
233305
DumpFormat::Json => serde_json::to_string(&module)
234306
.expect("could not convert AST to a JSON string"),
@@ -238,14 +310,18 @@ fn dump<R: ReadChar>(src: Source<'_, R>, args: &Opt, context: &mut Context) -> R
238310
}
239311
} else {
240312
let scope = context.realm().scope().clone();
241-
let mut script = parser
242-
.parse_script(&scope, context.interner_mut())
243-
.map_err(|e| eyre!("Uncaught SyntaxError: {e}"))?;
313+
let mut script = {
314+
let _timer = counters.new_timer("Parsing");
315+
parser
316+
.parse_script(&scope, context.interner_mut())
317+
.map_err(|e| eyre!("Uncaught SyntaxError: {e}"))?
318+
};
244319

245320
if args.optimize {
246321
context.optimize_statement_list(script.statements_mut());
247322
}
248323

324+
let _timer = counters.new_timer("AST generation");
249325
match arg {
250326
DumpFormat::Json => serde_json::to_string(&script)
251327
.expect("could not convert AST to a JSON string"),
@@ -254,7 +330,7 @@ fn dump<R: ReadChar>(src: Source<'_, R>, args: &Opt, context: &mut Context) -> R
254330
DumpFormat::Debug => format!("{script:#?}"),
255331
}
256332
};
257-
333+
drop(counters);
258334
println!("{dump}");
259335
}
260336

@@ -307,7 +383,7 @@ fn evaluate_expr(
307383
printer: &SharedExternalPrinterLogger,
308384
) -> Result<()> {
309385
if args.has_dump_flag() {
310-
dump(Source::from_bytes(&line), args, context)?;
386+
dump(Source::from_bytes(line), args, context)?;
311387
} else if let Some(flowgraph) = args.flowgraph {
312388
match generate_flowgraph(
313389
context,
@@ -319,8 +395,27 @@ fn evaluate_expr(
319395
Err(v) => eprintln!("{v:?}"),
320396
}
321397
} else {
322-
match context.eval(Source::from_bytes(line)) {
323-
Ok(v) => printer.print(format!("{}\n", v.display())),
398+
let mut counters = Counters::new(args.time);
399+
let script = {
400+
let _timer = counters.new_timer("Parsing");
401+
Script::parse(Source::from_bytes(line), None, context)
402+
};
403+
404+
match script {
405+
Ok(script) => {
406+
let result = {
407+
let _timer = counters.new_timer("Execution");
408+
let result = script.evaluate(context);
409+
if let Err(err) = context.run_jobs() {
410+
printer.print(uncaught_job_error(&err));
411+
}
412+
result
413+
};
414+
match result {
415+
Ok(v) => printer.print(format!("{}\n", v.display())),
416+
Err(ref v) => printer.print(uncaught_error(v)),
417+
}
418+
}
324419
Err(ref v) => printer.print(uncaught_error(v)),
325420
}
326421
}
@@ -353,29 +448,53 @@ fn evaluate_file(
353448
}
354449

355450
if args.module {
356-
let module = Module::parse(Source::from_filepath(file)?, None, context)
357-
.map_err(|e| e.into_erased(context))?;
451+
let source = Source::from_filepath(file)?;
452+
let mut counters = Counters::new(args.time);
453+
let module = {
454+
let _timer = counters.new_timer("Parsing");
455+
Module::parse(source, None, context)
456+
};
457+
let module = module.map_err(|e| e.into_erased(context))?;
358458

359459
loader.insert(
360460
file.canonicalize()
361461
.wrap_err("could not canonicalize input file path")?,
362462
module.clone(),
363463
);
364464

365-
let promise = module.load_link_evaluate(context);
366-
context.run_jobs().map_err(|err| err.into_erased(context))?;
465+
let promise = {
466+
let _timer = counters.new_timer("Execution");
467+
let promise = module.load_link_evaluate(context);
468+
context.run_jobs().map_err(|err| err.into_erased(context))?;
469+
Ok::<_, JsErasedError>(promise)
470+
}?;
367471
let result = promise.state();
368472

369473
return match result {
370474
PromiseState::Pending => Err(eyre!("module didn't execute")),
371475
PromiseState::Fulfilled(_) => Ok(()),
372476
PromiseState::Rejected(err) => {
373-
return Err(JsError::from_opaque(err).into_erased(context).into());
477+
Err(JsError::from_opaque(err).into_erased(context).into())
374478
}
375479
};
376480
}
377481

378-
match context.eval(Source::from_filepath(file)?) {
482+
let source = Source::from_filepath(file)?;
483+
let mut counters = Counters::new(args.time);
484+
let script = {
485+
let _timer = counters.new_timer("Parsing");
486+
Script::parse(source, None, context)
487+
};
488+
let script = script.map_err(|e| e.into_erased(context))?;
489+
490+
let result = {
491+
let _timer = counters.new_timer("Execution");
492+
let result = script.evaluate(context);
493+
context.run_jobs().map_err(|err| err.into_erased(context))?;
494+
result
495+
};
496+
497+
match result {
379498
Ok(v) => {
380499
if !v.is_undefined() {
381500
println!("{}", v.display());
@@ -384,9 +503,7 @@ fn evaluate_file(
384503
Err(v) => printer.print(uncaught_error(&v)),
385504
}
386505

387-
context
388-
.run_jobs()
389-
.map_err(|err| err.into_erased(context).into())
506+
Ok(())
390507
}
391508

392509
fn evaluate_files(
@@ -454,6 +571,16 @@ fn main() -> Result<()> {
454571
} else if let Some(ref expr) = args.expression {
455572
evaluate_expr(expr, &args, &mut context, &printer)?;
456573
return Ok(());
574+
} else if !io::stdin().is_terminal() {
575+
let mut input = String::new();
576+
io::stdin()
577+
.read_to_string(&mut input)
578+
.wrap_err("failed to read stdin")?;
579+
return if input.is_empty() {
580+
Ok(())
581+
} else {
582+
evaluate_expr(&input, &args, &mut context, &printer)
583+
};
457584
}
458585

459586
let handle = start_readline_thread(sender, printer.clone(), args.vi_mode);

core/engine/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ native-backtrace = []
9494
[dependencies]
9595
tag_ptr.workspace = true
9696
boa_interner.workspace = true
97-
boa_gc = { workspace = true, features = ["thin-vec", "boa_string"] }
97+
boa_gc = { workspace = true, features = ["thin-vec", "boa_string", "arrayvec"] }
9898
boa_macros.workspace = true
9999
boa_ast.workspace = true
100100
boa_parser.workspace = true

0 commit comments

Comments
 (0)