feat: add dependency
This commit is contained in:
4
javascript-engine/external/boa/fuzz/.gitignore
vendored
Normal file
4
javascript-engine/external/boa/fuzz/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
target
|
||||
corpus
|
||||
artifacts
|
||||
coverage
|
||||
41
javascript-engine/external/boa/fuzz/Cargo.toml
vendored
Normal file
41
javascript-engine/external/boa/fuzz/Cargo.toml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
[package]
|
||||
name = "boa_fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
|
||||
boa_ast = { path = "../boa_ast", features = ["fuzz"] }
|
||||
boa_engine = { path = "../boa_engine", features = ["fuzz"] }
|
||||
boa_interner = { path = "../boa_interner", features = ["fuzz"] }
|
||||
boa_parser = { path = "../boa_parser" }
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[profile.release]
|
||||
debug = 1
|
||||
|
||||
[[bin]]
|
||||
name = "parser-idempotency"
|
||||
path = "fuzz_targets/parser-idempotency.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "vm-implied"
|
||||
path = "fuzz_targets/vm-implied.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "bytecompiler-implied"
|
||||
path = "fuzz_targets/bytecompiler-implied.rs"
|
||||
test = false
|
||||
doc = false
|
||||
56
javascript-engine/external/boa/fuzz/README.md
vendored
Normal file
56
javascript-engine/external/boa/fuzz/README.md
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
# boa_engine-fuzz
|
||||
|
||||
This directory contains fuzzers which can be used to automatically identify faults present in Boa. All the fuzzers in
|
||||
this directory are [grammar-aware](https://www.fuzzingbook.org/html/Grammars.html) (based on
|
||||
[Arbitrary](https://docs.rs/arbitrary/latest/arbitrary/)) and coverage-guided. See [common.rs](fuzz/fuzz_targets/common.rs)
|
||||
for implementation specifics.
|
||||
|
||||
You can run any fuzzer you wish with the following command (replacing `your-fuzzer` with a fuzzer availble in
|
||||
fuzz_targets, e.g. `parser-idempotency`):
|
||||
|
||||
```bash
|
||||
cargo fuzz run -s none your-fuzzer
|
||||
```
|
||||
|
||||
Note that you may wish to use a different sanitizer option (`-s`) according to what kind of issue you're looking for.
|
||||
Refer to the [cargo-fuzz book](https://rust-fuzz.github.io/book/cargo-fuzz.html) for details on how to select a
|
||||
sanitizer and other flags.
|
||||
|
||||
## Parser Fuzzer
|
||||
|
||||
The parser fuzzer, located in [parser-idempotency.rs](fuzz/fuzz_targets/parser-idempotency.rs), identifies
|
||||
correctness issues in both the parser and the AST-to-source conversion process (e.g., via `to_interned_string`) by
|
||||
searching for inputs which are not idempotent over parsing and conversion back to source. It does this by doing the
|
||||
following:
|
||||
|
||||
1. Generate an arbitrary AST
|
||||
2. Convert that AST to source code with `to_interned_string`; we'll call this the "original source"
|
||||
3. Parse the original source into an AST; we'll call this the "first AST"
|
||||
- Arbitrary ASTs aren't guaranteed to be parseable; to avoid errors caused by this, we discard errors here.
|
||||
4. Convert the first AST to source code with `to_interned_string`; we'll call this the "first source"
|
||||
5. Parse the first source into an AST; we'll call this the "second AST"
|
||||
- Since the original source was parseable, the first source must be parseable; emit any errors parsing produces.
|
||||
6. Compare the first AST and the second AST. If they are not equal, emit an error.
|
||||
- An error here indicates that either the parser or the AST-to-source conversion lost information or added incorrect
|
||||
information, as the inputs parsed between the two should be the same.
|
||||
|
||||
In this way, this fuzzer can identify correctness issues present in the parser.
|
||||
|
||||
## Bytecompiler Fuzzer
|
||||
|
||||
The bytecompiler fuzzer, located in [bytecompiler-implied.rs](fuzz_targets/bytecompiler-implied.rs), identifies cases
|
||||
which cause an assertion failure in the bytecompiler. These crashes can cause denial of service issues and may block the
|
||||
discovery of crash cases in the VM fuzzer.
|
||||
|
||||
## VM Fuzzer
|
||||
|
||||
The VM fuzzer, located in [vm-implied.rs](fuzz_targets/vm-implied.rs), identifies crash cases in the VM. It does so by
|
||||
generating an arbitrary AST, converting it to source code (to remove invalid inputs), then executing that source code.
|
||||
Because we are not comparing against any invariants other than "does it crash", this fuzzer will only discover faults
|
||||
which cause the VM to terminate unexpectedly, e.g. as a result of a panic. It will not discover logic errors present in
|
||||
the VM.
|
||||
|
||||
To ensure that the VM does not attempt to execute an infinite loop, Boa is restricted to a finite number of instructions
|
||||
before the VM is terminated. If a program takes more than a second or so to execute, it likely indicates an issue in the
|
||||
VM (as we expect the fuzzer to execute only a certain amount of instructions, which should take significantly less
|
||||
time).
|
||||
25
javascript-engine/external/boa/fuzz/fuzz_targets/bytecompiler-implied.rs
vendored
Normal file
25
javascript-engine/external/boa/fuzz/fuzz_targets/bytecompiler-implied.rs
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
#![no_main]
|
||||
|
||||
mod common;
|
||||
|
||||
use crate::common::FuzzSource;
|
||||
use boa_engine::Context;
|
||||
use boa_parser::Parser;
|
||||
use libfuzzer_sys::{fuzz_target, Corpus};
|
||||
use std::io::Cursor;
|
||||
|
||||
fn do_fuzz(original: FuzzSource) -> Corpus {
|
||||
let mut ctx = Context::builder()
|
||||
.interner(original.interner)
|
||||
.instructions_remaining(0)
|
||||
.build();
|
||||
let mut parser = Parser::new(Cursor::new(&original.source));
|
||||
if let Ok(parsed) = parser.parse_all(ctx.interner_mut()) {
|
||||
let _ = ctx.compile(&parsed);
|
||||
Corpus::Keep
|
||||
} else {
|
||||
Corpus::Reject
|
||||
}
|
||||
}
|
||||
|
||||
fuzz_target!(|original: FuzzSource| -> Corpus { do_fuzz(original) });
|
||||
96
javascript-engine/external/boa/fuzz/fuzz_targets/common.rs
vendored
Normal file
96
javascript-engine/external/boa/fuzz/fuzz_targets/common.rs
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
use boa_ast::{
|
||||
visitor::{VisitWith, VisitorMut},
|
||||
Expression, StatementList,
|
||||
};
|
||||
use boa_interner::{Interner, Sym, ToInternedString};
|
||||
use libfuzzer_sys::arbitrary;
|
||||
use libfuzzer_sys::arbitrary::{Arbitrary, Unstructured};
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::ops::ControlFlow;
|
||||
|
||||
/// Context for performing fuzzing. This structure contains both the generated AST as well as the
|
||||
/// context used to resolve the symbols therein.
|
||||
pub struct FuzzData {
|
||||
pub interner: Interner,
|
||||
pub ast: StatementList,
|
||||
}
|
||||
|
||||
impl<'a> Arbitrary<'a> for FuzzData {
|
||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
let mut interner = Interner::with_capacity(8);
|
||||
let mut syms_available = Vec::with_capacity(8);
|
||||
for c in 'a'..='h' {
|
||||
syms_available.push(interner.get_or_intern(&*String::from(c)));
|
||||
}
|
||||
|
||||
let mut ast = StatementList::arbitrary(u)?;
|
||||
|
||||
struct FuzzReplacer<'a, 's, 'u> {
|
||||
syms: &'s [Sym],
|
||||
u: &'u mut Unstructured<'a>,
|
||||
}
|
||||
impl<'a, 's, 'u, 'ast> VisitorMut<'ast> for FuzzReplacer<'a, 's, 'u> {
|
||||
type BreakTy = arbitrary::Error;
|
||||
|
||||
// TODO arbitrary strings literals?
|
||||
|
||||
fn visit_expression_mut(
|
||||
&mut self,
|
||||
node: &'ast mut Expression,
|
||||
) -> ControlFlow<Self::BreakTy> {
|
||||
if matches!(node, Expression::FormalParameterList(_)) {
|
||||
match self.u.arbitrary() {
|
||||
Ok(id) => *node = Expression::Identifier(id),
|
||||
Err(e) => return ControlFlow::Break(e),
|
||||
}
|
||||
}
|
||||
node.visit_with_mut(self)
|
||||
}
|
||||
|
||||
fn visit_sym_mut(&mut self, node: &'ast mut Sym) -> ControlFlow<Self::BreakTy> {
|
||||
*node = self.syms[node.get() % self.syms.len()];
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
}
|
||||
|
||||
let mut replacer = FuzzReplacer {
|
||||
syms: &syms_available,
|
||||
u,
|
||||
};
|
||||
if let ControlFlow::Break(e) = replacer.visit_statement_list_mut(&mut ast) {
|
||||
Err(e)
|
||||
} else {
|
||||
Ok(Self { interner, ast })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for FuzzData {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("FuzzData")
|
||||
.field("ast", &self.ast)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FuzzSource {
|
||||
pub interner: Interner,
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
impl<'a> Arbitrary<'a> for FuzzSource {
|
||||
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
let data = FuzzData::arbitrary(u)?;
|
||||
let source = data.ast.to_interned_string(&data.interner);
|
||||
Ok(Self {
|
||||
interner: data.interner,
|
||||
source,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for FuzzSource {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!("Fuzzed source:\n{}", self.source))
|
||||
}
|
||||
}
|
||||
74
javascript-engine/external/boa/fuzz/fuzz_targets/parser-idempotency.rs
vendored
Normal file
74
javascript-engine/external/boa/fuzz/fuzz_targets/parser-idempotency.rs
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
#![no_main]
|
||||
|
||||
mod common;
|
||||
|
||||
use crate::common::FuzzData;
|
||||
use boa_interner::ToInternedString;
|
||||
use boa_parser::Parser;
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use libfuzzer_sys::Corpus;
|
||||
use std::error::Error;
|
||||
use std::io::Cursor;
|
||||
|
||||
/// Fuzzer test harness. This function accepts the arbitrary AST and performs the fuzzing operation.
|
||||
///
|
||||
/// See [README.md](../README.md) for details on the design of this fuzzer.
|
||||
fn do_fuzz(mut data: FuzzData) -> Result<(), Box<dyn Error>> {
|
||||
let original = data.ast.to_interned_string(&data.interner);
|
||||
|
||||
let mut parser = Parser::new(Cursor::new(&original));
|
||||
|
||||
let before = data.interner.len();
|
||||
// For a variety of reasons, we may not actually produce valid code here (e.g., nameless function).
|
||||
// Fail fast and only make the next checks if we were valid.
|
||||
if let Ok(first) = parser.parse_all(&mut data.interner) {
|
||||
let after_first = data.interner.len();
|
||||
let first_interned = first.to_interned_string(&data.interner);
|
||||
|
||||
assert_eq!(
|
||||
before,
|
||||
after_first,
|
||||
"The number of interned symbols changed; a new string was read.\nBefore:\n{}\nAfter:\n{}\nBefore (AST):\n{:#?}\nAfter (AST):\n{:#?}",
|
||||
original,
|
||||
first_interned,
|
||||
data.ast,
|
||||
first
|
||||
);
|
||||
let mut parser = Parser::new(Cursor::new(&first_interned));
|
||||
|
||||
// Now, we most assuredly should produce valid code. It has already gone through a first pass.
|
||||
let second = parser
|
||||
.parse_all(&mut data.interner)
|
||||
.expect("Could not parse the first-pass interned copy.");
|
||||
let second_interned = second.to_interned_string(&data.interner);
|
||||
let after_second = data.interner.len();
|
||||
assert_eq!(
|
||||
after_first,
|
||||
after_second,
|
||||
"The number of interned symbols changed; a new string was read.\nBefore:\n{}\nAfter:\n{}\nBefore (AST):\n{:#?}\nAfter (AST):\n{:#?}",
|
||||
first_interned,
|
||||
second_interned,
|
||||
first,
|
||||
second
|
||||
);
|
||||
assert_eq!(
|
||||
first,
|
||||
second,
|
||||
"Expected the same AST after two intern passes, but found dissimilar.\nOriginal:\n{}\nFirst:\n{}\nSecond:\n{}",
|
||||
original,
|
||||
first_interned,
|
||||
second_interned,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Fuzz harness wrapper to expose it to libfuzzer (and thus cargo-fuzz)
|
||||
// See: https://rust-fuzz.github.io/book/cargo-fuzz.html
|
||||
fuzz_target!(|data: FuzzData| -> Corpus {
|
||||
if do_fuzz(data).is_ok() {
|
||||
Corpus::Keep
|
||||
} else {
|
||||
Corpus::Reject
|
||||
}
|
||||
});
|
||||
19
javascript-engine/external/boa/fuzz/fuzz_targets/vm-implied.rs
vendored
Normal file
19
javascript-engine/external/boa/fuzz/fuzz_targets/vm-implied.rs
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
#![no_main]
|
||||
|
||||
mod common;
|
||||
|
||||
use crate::common::FuzzSource;
|
||||
use boa_engine::{Context, JsResult, JsValue};
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fn do_fuzz(original: FuzzSource) -> JsResult<JsValue> {
|
||||
let mut ctx = Context::builder()
|
||||
.interner(original.interner)
|
||||
.instructions_remaining(1 << 16)
|
||||
.build();
|
||||
ctx.eval(&original.source)
|
||||
}
|
||||
|
||||
fuzz_target!(|original: FuzzSource| {
|
||||
let _ = do_fuzz(original);
|
||||
});
|
||||
Reference in New Issue
Block a user